@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é",
package/package.json CHANGED
@@ -101,7 +101,7 @@
101
101
  "internal-slot": "1.0.7"
102
102
  },
103
103
  "type": "module",
104
- "version": "2.17.0-dev.491",
104
+ "version": "2.17.0-dev.497",
105
105
  "exports": {
106
106
  "./i18n": "./i18n.js",
107
107
  "./index.css": "./index.css",