@cccsaurora/howler-ui 2.17.0-dev.491 → 2.17.0-dev.497
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Assignment, Edit, HowToVote, KeyboardArrowRight, OpenInNew, QueryStats, RemoveCircleOutline, SettingsSuggest, Terminal } from '@mui/icons-material';
|
|
2
|
+
import { AddCircleOutline, Assignment, Edit, HowToVote, KeyboardArrowRight, OpenInNew, QueryStats, RemoveCircleOutline, SettingsSuggest, Terminal } from '@mui/icons-material';
|
|
3
3
|
import { Box, Divider, Fade, ListItemIcon, ListItemText, Menu, MenuItem, MenuList, Paper } from '@mui/material';
|
|
4
4
|
import api from '@cccsaurora/howler-ui/api';
|
|
5
5
|
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
@@ -198,6 +198,30 @@ const HitContextMenu = ({ children, getSelectedId, Component = Box }) => {
|
|
|
198
198
|
newQuery += `-${key}:"${sanitizeLuceneQuery(value.toString())}"`;
|
|
199
199
|
}
|
|
200
200
|
return (_jsx(MenuItem, { onClick: () => setQuery(newQuery), children: _jsx(ListItemText, { children: key }) }, key));
|
|
201
|
+
}) }) }) })] }), _jsxs(MenuItem, { id: "includes-menu-item", sx: { position: 'relative' }, onMouseEnter: ev => setShow(_show => ({ ..._show, includes: ev.target })), onMouseLeave: () => setShow(_show => ({ ..._show, includes: null })), children: [_jsx(ListItemIcon, { children: _jsx(AddCircleOutline, {}) }), _jsx(ListItemText, { sx: { flex: 1 }, children: t('hit.panel.include') }), _jsx(KeyboardArrowRight, { fontSize: "small", sx: { color: 'text.secondary', mr: -1 } }), _jsx(Fade, { in: !!show.includes, unmountOnExit: true, children: _jsx(Paper, { id: "includes-submenu", sx: calculateSubMenuStyles(show.includes), elevation: 8, children: _jsx(MenuList, { sx: { p: 0 }, dense: true, role: "group", children: template?.keys.map(key => {
|
|
202
|
+
// Build inclusion query based on current query and field vacccccbkrcudeecdukjjikjinhbknblnuhnbhvdjcrdhj
|
|
203
|
+
// lue. If default, we include default query
|
|
204
|
+
let newQuery = `(${query}) AND `;
|
|
205
|
+
const value = get(hit, key);
|
|
206
|
+
if (!value) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
else if (Array.isArray(value)) {
|
|
210
|
+
// Handle array values by including all items
|
|
211
|
+
const sanitizedValues = value
|
|
212
|
+
.map(toString)
|
|
213
|
+
.filter(val => !!val)
|
|
214
|
+
.map(val => `"${sanitizeLuceneQuery(val)}"`);
|
|
215
|
+
if (sanitizedValues.length < 1) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
newQuery += `${key}:(${sanitizedValues.join(' OR ')})`;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
// Handle single value
|
|
222
|
+
newQuery += `${key}:"${sanitizeLuceneQuery(value.toString())}"`;
|
|
223
|
+
}
|
|
224
|
+
return (_jsx(MenuItem, { onClick: () => setQuery(newQuery), children: _jsx(ListItemText, { children: key }) }, key));
|
|
201
225
|
}) }) }) })] })] }))] })] }));
|
|
202
226
|
};
|
|
203
227
|
export default HitContextMenu;
|
|
@@ -623,6 +623,132 @@ describe('HitContextMenu', () => {
|
|
|
623
623
|
});
|
|
624
624
|
});
|
|
625
625
|
});
|
|
626
|
+
describe('Inclusion Filter Functionality', () => {
|
|
627
|
+
beforeEach(() => {
|
|
628
|
+
mockGetMatchingTemplate.mockResolvedValue(createMockTemplate({
|
|
629
|
+
keys: ['howler.detection', 'event.id']
|
|
630
|
+
}));
|
|
631
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
632
|
+
});
|
|
633
|
+
it('should render inclusion submenu with template keys', async () => {
|
|
634
|
+
act(() => {
|
|
635
|
+
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
636
|
+
fireEvent.contextMenu(contextMenuWrapper);
|
|
637
|
+
});
|
|
638
|
+
await waitFor(() => {
|
|
639
|
+
expect(screen.getByRole('menu')).toBeInTheDocument();
|
|
640
|
+
});
|
|
641
|
+
act(() => {
|
|
642
|
+
const includesMenuItem = screen.getByText('Include By');
|
|
643
|
+
fireEvent.mouseEnter(includesMenuItem);
|
|
644
|
+
});
|
|
645
|
+
await waitFor(() => {
|
|
646
|
+
const submenu = screen.getByTestId('includes-submenu');
|
|
647
|
+
expect(submenu).toBeInTheDocument();
|
|
648
|
+
expect(submenu.textContent).toContain('howler.detection');
|
|
649
|
+
expect(submenu.textContent).toContain('event.id');
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
it('should generate inclusion query for single value', async () => {
|
|
653
|
+
act(() => {
|
|
654
|
+
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
655
|
+
fireEvent.contextMenu(contextMenuWrapper);
|
|
656
|
+
});
|
|
657
|
+
await waitFor(() => {
|
|
658
|
+
expect(screen.getByRole('menu')).toBeInTheDocument();
|
|
659
|
+
});
|
|
660
|
+
act(() => {
|
|
661
|
+
const includesMenuItem = screen.getByText('Include By');
|
|
662
|
+
fireEvent.mouseEnter(includesMenuItem);
|
|
663
|
+
});
|
|
664
|
+
await waitFor(() => {
|
|
665
|
+
expect(screen.getByTestId('includes-submenu')).toBeInTheDocument();
|
|
666
|
+
});
|
|
667
|
+
await act(async () => {
|
|
668
|
+
const detectionKey = screen.getByText('howler.detection');
|
|
669
|
+
await user.click(detectionKey);
|
|
670
|
+
});
|
|
671
|
+
await waitFor(() => {
|
|
672
|
+
expect(mockParameterContext.setQuery).toHaveBeenCalledWith('(howler.status:open) AND howler.detection:"Test Detection"');
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
it('should generate inclusion query for array values', async () => {
|
|
676
|
+
mockGetMatchingTemplate.mockResolvedValue(createMockTemplate({
|
|
677
|
+
keys: ['howler.outline.indicators']
|
|
678
|
+
}));
|
|
679
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
680
|
+
act(() => {
|
|
681
|
+
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
682
|
+
fireEvent.contextMenu(contextMenuWrapper);
|
|
683
|
+
});
|
|
684
|
+
await waitFor(() => {
|
|
685
|
+
const includesMenuItem = screen.getByText('Include By');
|
|
686
|
+
fireEvent.mouseEnter(includesMenuItem);
|
|
687
|
+
});
|
|
688
|
+
await waitFor(() => {
|
|
689
|
+
expect(screen.getByTestId('includes-submenu')).toBeInTheDocument();
|
|
690
|
+
});
|
|
691
|
+
await act(async () => {
|
|
692
|
+
const tagsKey = screen.getByText('howler.outline.indicators');
|
|
693
|
+
await user.click(tagsKey);
|
|
694
|
+
});
|
|
695
|
+
await waitFor(() => {
|
|
696
|
+
expect(mockParameterContext.setQuery).toHaveBeenCalledWith('(howler.status:open) AND howler.outline.indicators:("a" OR "b" OR "c")');
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
it('should preserve existing query when adding inclusion', async () => {
|
|
700
|
+
mockParameterContext.query = 'howler.status:open';
|
|
701
|
+
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
702
|
+
fireEvent.contextMenu(contextMenuWrapper);
|
|
703
|
+
await waitFor(() => {
|
|
704
|
+
const includesMenuItem = screen.getByText('Include By');
|
|
705
|
+
fireEvent.mouseEnter(includesMenuItem);
|
|
706
|
+
});
|
|
707
|
+
await waitFor(() => {
|
|
708
|
+
expect(screen.getByTestId('includes-submenu')).toBeInTheDocument();
|
|
709
|
+
});
|
|
710
|
+
await act(async () => {
|
|
711
|
+
const detectionKey = screen.getByText('howler.detection');
|
|
712
|
+
await user.click(detectionKey);
|
|
713
|
+
});
|
|
714
|
+
await waitFor(() => {
|
|
715
|
+
expect(mockParameterContext.setQuery).toHaveBeenCalledWith('(howler.status:open) AND howler.detection:"Test Detection"');
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
it('should not render inclusion menu when template has no keys', async () => {
|
|
719
|
+
mockGetMatchingTemplate.mockResolvedValue(createMockTemplate({
|
|
720
|
+
keys: []
|
|
721
|
+
}));
|
|
722
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
723
|
+
act(() => {
|
|
724
|
+
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
725
|
+
fireEvent.contextMenu(contextMenuWrapper);
|
|
726
|
+
});
|
|
727
|
+
await waitFor(() => {
|
|
728
|
+
expect(screen.getByRole('menu')).toBeInTheDocument();
|
|
729
|
+
});
|
|
730
|
+
expect(screen.queryByText('Include By')).toBeNull();
|
|
731
|
+
});
|
|
732
|
+
it('should skip null field values in inclusion menu', async () => {
|
|
733
|
+
act(() => {
|
|
734
|
+
mockHitContext.hits['test-hit-1'].event = {};
|
|
735
|
+
});
|
|
736
|
+
act(() => {
|
|
737
|
+
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
738
|
+
fireEvent.contextMenu(contextMenuWrapper);
|
|
739
|
+
});
|
|
740
|
+
await waitFor(() => {
|
|
741
|
+
const includesMenuItem = screen.getByText('Include By');
|
|
742
|
+
fireEvent.mouseEnter(includesMenuItem);
|
|
743
|
+
});
|
|
744
|
+
await waitFor(() => {
|
|
745
|
+
const submenu = screen.getByTestId('includes-submenu');
|
|
746
|
+
expect(submenu).toBeInTheDocument();
|
|
747
|
+
expect(submenu.textContent).toContain('howler.detection');
|
|
748
|
+
expect(submenu.textContent).not.toContain('event.id');
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
});
|
|
626
752
|
describe('Multiple Hit Selection', () => {
|
|
627
753
|
it('should use selectedHits when current hit is included', async () => {
|
|
628
754
|
act(() => {
|
|
@@ -721,6 +847,20 @@ describe('HitContextMenu', () => {
|
|
|
721
847
|
expect(screen.queryByText('Exclude By')).toBeNull();
|
|
722
848
|
});
|
|
723
849
|
});
|
|
850
|
+
it('should not render inclusion menu when template is null', async () => {
|
|
851
|
+
mockGetMatchingTemplate.mockResolvedValue(null);
|
|
852
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
853
|
+
act(() => {
|
|
854
|
+
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
855
|
+
fireEvent.contextMenu(contextMenuWrapper);
|
|
856
|
+
});
|
|
857
|
+
await waitFor(() => {
|
|
858
|
+
expect(screen.getByRole('menu')).toBeInTheDocument();
|
|
859
|
+
});
|
|
860
|
+
await waitFor(() => {
|
|
861
|
+
expect(screen.queryByText('Include By')).toBeNull();
|
|
862
|
+
});
|
|
863
|
+
});
|
|
724
864
|
it('should handle API failure gracefully', async () => {
|
|
725
865
|
mockDispatchApi.mockResolvedValue(null);
|
|
726
866
|
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
@@ -182,6 +182,7 @@
|
|
|
182
182
|
"hit.panel.close": "Close",
|
|
183
183
|
"hit.panel.hit.noselection": "No hit has been selected",
|
|
184
184
|
"hit.panel.exclude": "Exclude By",
|
|
185
|
+
"hit.panel.include": "Include By",
|
|
185
186
|
"hit.panel.details.cluster": "Cluster Size",
|
|
186
187
|
"hit.panel.details.cluster.description": "Size of the cluster",
|
|
187
188
|
"hit.panel.details.no.subjects": "No subjects found",
|
|
@@ -183,6 +183,7 @@
|
|
|
183
183
|
"hit.panel.close": "Fermer",
|
|
184
184
|
"hit.panel.hit.noselection": "Aucun résultat n'a été sélectionné",
|
|
185
185
|
"hit.panel.exclude": "Exclure par",
|
|
186
|
+
"hit.panel.include": "Inclure par",
|
|
186
187
|
"hit.panel.details.cluster": "Taille de la grappe",
|
|
187
188
|
"hit.panel.details.cluster.description": "Taille de la grappe",
|
|
188
189
|
"hit.panel.details.no.subjects": "Aucun sujet trouvé",
|