@cccsaurora/howler-ui 2.17.0-dev.502 → 2.17.0-dev.513

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.
Files changed (131) hide show
  1. package/api/index.d.ts +2 -0
  2. package/api/index.js +4 -2
  3. package/api/search/case.d.ts +4 -0
  4. package/api/search/case.js +8 -0
  5. package/api/search/index.d.ts +2 -1
  6. package/api/search/index.js +2 -1
  7. package/api/v2/case/index.d.ts +6 -0
  8. package/api/v2/case/index.js +18 -0
  9. package/api/v2/index.d.ts +4 -0
  10. package/api/v2/index.js +6 -0
  11. package/api/v2/search/facet.d.ts +3 -0
  12. package/api/v2/search/facet.js +12 -0
  13. package/api/v2/search/index.d.ts +6 -0
  14. package/api/v2/search/index.js +18 -0
  15. package/commons/components/leftnav/LeftNavDrawer.js +1 -1
  16. package/components/app/App.js +14 -0
  17. package/components/app/providers/FavouritesProvider.js +2 -2
  18. package/components/{routes/overviews/OverviewEditor.js → elements/MarkdownEditor.js} +3 -3
  19. package/components/elements/{hit/HitDetails.d.ts → ObjectDetails.d.ts} +2 -1
  20. package/components/elements/{hit/HitDetails.js → ObjectDetails.js} +14 -14
  21. package/components/elements/PluginTypography.d.ts +2 -1
  22. package/components/elements/PluginTypography.js +3 -2
  23. package/components/elements/UserList.d.ts +5 -2
  24. package/components/elements/UserList.js +14 -5
  25. package/components/elements/addons/search/phrase/Phrase.js +1 -1
  26. package/components/elements/display/HowlerCard.js +1 -1
  27. package/components/elements/hit/HitBanner.js +19 -31
  28. package/components/elements/hit/outlines/DefaultOutline.js +1 -1
  29. package/components/elements/view/ViewTitle.js +1 -1
  30. package/components/hooks/useHitSelection.js +1 -35
  31. package/components/hooks/useMyPreferences.js +10 -1
  32. package/components/hooks/useMySitemap.js +3 -1
  33. package/components/hooks/useMyTheme.js +9 -2
  34. package/components/routes/action/view/ActionSearch.js +1 -1
  35. package/components/routes/action/view/Integrations.js +1 -9
  36. package/components/routes/advanced/QueryBuilder.js +1 -1
  37. package/components/routes/analytics/AnalyticDetails.js +2 -2
  38. package/components/routes/analytics/AnalyticSearch.js +1 -1
  39. package/components/routes/cases/CaseCard.d.ts +8 -0
  40. package/components/routes/cases/CaseCard.js +34 -0
  41. package/components/routes/cases/CaseViewer.d.ts +2 -0
  42. package/components/routes/cases/CaseViewer.js +24 -0
  43. package/components/routes/cases/Cases.d.ts +2 -0
  44. package/components/routes/cases/Cases.js +101 -0
  45. package/components/routes/cases/constants.d.ts +5 -0
  46. package/components/routes/cases/constants.js +5 -0
  47. package/components/routes/cases/detail/AlertPanel.d.ts +6 -0
  48. package/components/routes/cases/detail/AlertPanel.js +32 -0
  49. package/components/routes/cases/detail/CaseDashboard.d.ts +7 -0
  50. package/components/routes/cases/detail/CaseDashboard.js +46 -0
  51. package/components/routes/cases/detail/CaseDetails.d.ts +6 -0
  52. package/components/routes/cases/detail/CaseDetails.js +49 -0
  53. package/components/routes/cases/detail/CaseOverview.d.ts +7 -0
  54. package/components/routes/cases/detail/CaseOverview.js +43 -0
  55. package/components/routes/cases/detail/CaseSidebar.d.ts +6 -0
  56. package/components/routes/cases/detail/CaseSidebar.js +36 -0
  57. package/components/routes/cases/detail/CaseTask.d.ts +10 -0
  58. package/components/routes/cases/detail/CaseTask.js +46 -0
  59. package/components/routes/cases/detail/ItemPage.d.ts +6 -0
  60. package/components/routes/cases/detail/ItemPage.js +93 -0
  61. package/components/routes/cases/detail/RelatedCasePanel.d.ts +6 -0
  62. package/components/routes/cases/detail/RelatedCasePanel.js +31 -0
  63. package/components/routes/cases/detail/TaskPanel.d.ts +7 -0
  64. package/components/routes/cases/detail/TaskPanel.js +23 -0
  65. package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +12 -0
  66. package/components/routes/cases/detail/aggregates/CaseAggregate.js +19 -0
  67. package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +6 -0
  68. package/components/routes/cases/detail/aggregates/SourceAggregate.js +27 -0
  69. package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +12 -0
  70. package/components/routes/cases/detail/sidebar/CaseFolder.js +114 -0
  71. package/components/routes/cases/detail/sidebar/types.d.ts +3 -0
  72. package/components/routes/cases/hooks/useCase.d.ts +13 -0
  73. package/components/routes/cases/hooks/useCase.js +38 -0
  74. package/components/routes/help/ApiDocumentation.js +1 -1
  75. package/components/routes/help/HitDocumentation.js +1 -3
  76. package/components/routes/hits/search/HitContextMenu.js +4 -27
  77. package/components/routes/hits/search/HitContextMenu.test.js +0 -140
  78. package/components/routes/hits/search/InformationPane.d.ts +1 -0
  79. package/components/routes/hits/search/InformationPane.js +6 -29
  80. package/components/routes/hits/search/SearchPane.js +3 -5
  81. package/components/routes/hits/search/ViewLink.js +1 -1
  82. package/components/routes/hits/search/grid/EnhancedCell.js +1 -1
  83. package/components/routes/hits/view/HitViewer.js +3 -4
  84. package/components/routes/home/ViewCard.js +1 -1
  85. package/components/routes/observables/ObservableViewer.d.ts +7 -0
  86. package/components/routes/observables/ObservableViewer.js +27 -0
  87. package/components/routes/overviews/OverviewViewer.js +2 -2
  88. package/locales/en/translation.json +422 -397
  89. package/locales/fr/translation.json +429 -406
  90. package/models/entities/generated/AttachmentsFile.d.ts +12 -0
  91. package/models/entities/generated/Case.d.ts +28 -0
  92. package/models/entities/generated/DestinationOriginal.d.ts +19 -0
  93. package/models/entities/generated/EmailAttachment.d.ts +8 -0
  94. package/models/entities/generated/EmailParent.d.ts +19 -0
  95. package/models/entities/generated/Enrichments.d.ts +7 -0
  96. package/models/entities/generated/EnrichmentsIndicator.d.ts +21 -0
  97. package/models/entities/generated/Howler.d.ts +0 -4
  98. package/models/entities/generated/HttpResponse.d.ts +11 -0
  99. package/models/entities/generated/Item.d.ts +9 -0
  100. package/models/entities/generated/Observable.d.ts +84 -0
  101. package/models/entities/generated/ObservableCloud.d.ts +20 -0
  102. package/models/entities/generated/ObservableDestination.d.ts +23 -0
  103. package/models/entities/generated/ObservableEmail.d.ts +30 -0
  104. package/models/entities/generated/ObservableFile.d.ts +36 -0
  105. package/models/entities/generated/ObservableHowler.d.ts +44 -0
  106. package/models/entities/generated/ObservableHttp.d.ts +11 -0
  107. package/models/entities/generated/ObservableObserver.d.ts +21 -0
  108. package/models/entities/generated/ObservableOrganization.d.ts +7 -0
  109. package/models/entities/generated/ObservableProcess.d.ts +34 -0
  110. package/models/entities/generated/ObservableSource.d.ts +23 -0
  111. package/models/entities/generated/ObservableThreat.d.ts +21 -0
  112. package/models/entities/generated/ObservableTls.d.ts +12 -0
  113. package/models/entities/generated/ObserverIngress.d.ts +9 -0
  114. package/models/entities/generated/Rule.d.ts +2 -10
  115. package/models/entities/generated/Task.d.ts +10 -0
  116. package/models/entities/generated/Threat.d.ts +2 -2
  117. package/models/entities/generated/{Enrichment.d.ts → ThreatEnrichment.d.ts} +1 -1
  118. package/package.json +125 -114
  119. package/plugins/clue/components/ClueTypography.js +2 -2
  120. package/plugins/clue/utils.d.ts +2 -1
  121. package/components/elements/display/icons/BundleButton.d.ts +0 -6
  122. package/components/elements/display/icons/BundleButton.js +0 -32
  123. package/components/routes/action/view/markdown/integrations.en.md.js +0 -1
  124. package/components/routes/action/view/markdown/integrations.fr.md.js +0 -1
  125. package/components/routes/help/BundleDocumentation.d.ts +0 -3
  126. package/components/routes/help/BundleDocumentation.js +0 -12
  127. package/components/routes/help/markdown/en/bundles.md.js +0 -1
  128. package/components/routes/help/markdown/fr/bundles.md.js +0 -1
  129. package/components/routes/hits/search/BundleParentMenu.d.ts +0 -6
  130. package/components/routes/hits/search/BundleParentMenu.js +0 -32
  131. /package/components/{routes/overviews/OverviewEditor.d.ts → elements/MarkdownEditor.d.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { AddCircleOutline, Assignment, Edit, HowToVote, KeyboardArrowRight, OpenInNew, QueryStats, RemoveCircleOutline, SettingsSuggest, Terminal } from '@mui/icons-material';
2
+ import { 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';
@@ -170,9 +170,10 @@ const HitContextMenu = ({ children, getSelectedId, Component = Box }) => {
170
170
  sx: {
171
171
  ...transformProps,
172
172
  overflow: 'visible !important'
173
- }
173
+ },
174
+ elevation: 2
174
175
  }
175
- }, MenuListProps: { dense: true, sx: { minWidth: '250px' } }, anchorOrigin: { vertical: 'top', horizontal: 'left' }, onClick: () => setAnchorEl(null), children: [_jsxs(MenuItem, { component: Link, to: `/hits/${hit?.howler.id}`, disabled: !hit, children: [_jsx(ListItemIcon, { children: _jsx(OpenInNew, {}) }), _jsx(ListItemText, { children: t('hit.panel.open') })] }), _jsxs(MenuItem, { component: Link, to: `/analytics/${analytic?.analytic_id}`, disabled: !analytic, children: [_jsx(ListItemIcon, { children: _jsx(QueryStats, {}) }), _jsx(ListItemText, { children: t('hit.panel.analytic.open') })] }), _jsx(Divider, {}), entries.map(([type, items]) => (_jsxs(MenuItem, { id: `${type}-menu-item`, sx: { position: 'relative' }, onMouseEnter: ev => setShow(_show => ({ ..._show, [type]: ev.target })), onMouseLeave: () => setShow(_show => ({ ..._show, [type]: null })), disabled: rowStatus[type] === false, children: [_jsx(ListItemIcon, { children: ICON_MAP[type] ?? _jsx(Terminal, {}) }), _jsx(ListItemText, { sx: { flex: 1 }, children: t(`hit.details.actions.${type}`) }), rowStatus[type] !== false && (_jsx(KeyboardArrowRight, { fontSize: "small", sx: { color: 'text.secondary', mr: -1 } })), _jsx(Fade, { in: !!show[type], unmountOnExit: true, children: _jsx(Paper, { id: `${type}-submenu`, sx: calculateSubMenuStyles(show[type]), elevation: 8, children: _jsx(MenuList, { sx: { p: 0, borderTopLeftRadius: 0 }, dense: true, role: "group", children: items.map(a => (_jsx(MenuItem, { value: a.name, onClick: a.actionFunction, children: a.i18nKey ? t(a.i18nKey) : capitalize(a.name) }, a.name))) }) }) })] }, type))), _jsxs(MenuItem, { id: "actions-menu-item", sx: { position: 'relative' }, onMouseEnter: ev => setShow(_show => ({ ..._show, actions: ev.target })), onMouseLeave: () => setShow(_show => ({ ..._show, actions: null })), disabled: actions.length < 1, children: [_jsx(ListItemIcon, { children: _jsx(SettingsSuggest, {}) }), _jsx(ListItemText, { sx: { flex: 1 }, children: t('route.actions.change') }), actions.length > 0 && _jsx(KeyboardArrowRight, { fontSize: "small", sx: { color: 'text.secondary', mr: -1 } }), _jsx(Fade, { in: !!show.actions, unmountOnExit: true, children: _jsx(Paper, { id: "actions-submenu", sx: calculateSubMenuStyles(show.actions), elevation: 8, children: _jsx(MenuList, { sx: { p: 0 }, dense: true, role: "group", children: actions.map(action => (_jsx(MenuItem, { onClick: () => executeAction(action.action_id, `howler.id:${hit?.howler.id}`), children: _jsx(ListItemText, { children: action.name }) }, action.action_id))) }) }) })] }), !isEmpty(template?.keys ?? []) && (_jsxs(_Fragment, { children: [_jsx(Divider, {}), _jsxs(MenuItem, { id: "excludes-menu-item", sx: { position: 'relative' }, onMouseEnter: ev => setShow(_show => ({ ..._show, excludes: ev.target })), onMouseLeave: () => setShow(_show => ({ ..._show, excludes: null })), children: [_jsx(ListItemIcon, { children: _jsx(RemoveCircleOutline, {}) }), _jsx(ListItemText, { sx: { flex: 1 }, children: t('hit.panel.exclude') }), _jsx(KeyboardArrowRight, { fontSize: "small", sx: { color: 'text.secondary', mr: -1 } }), _jsx(Fade, { in: !!show.excludes, unmountOnExit: true, children: _jsx(Paper, { id: "excludes-submenu", sx: calculateSubMenuStyles(show.excludes), elevation: 8, children: _jsx(MenuList, { sx: { p: 0 }, dense: true, role: "group", children: template?.keys.map(key => {
176
+ }, MenuListProps: { dense: true, sx: { minWidth: '250px' } }, anchorOrigin: { vertical: 'top', horizontal: 'left' }, onClick: () => setAnchorEl(null), children: [_jsxs(MenuItem, { component: Link, to: `/hits/${hit?.howler.id}`, disabled: !hit, children: [_jsx(ListItemIcon, { children: _jsx(OpenInNew, {}) }), _jsx(ListItemText, { children: t('hit.panel.open') })] }), _jsxs(MenuItem, { component: Link, to: `/analytics/${analytic?.analytic_id}`, disabled: !analytic, children: [_jsx(ListItemIcon, { children: _jsx(QueryStats, {}) }), _jsx(ListItemText, { children: t('hit.panel.analytic.open') })] }), _jsx(Divider, {}), entries.map(([type, items]) => (_jsxs(MenuItem, { id: `${type}-menu-item`, sx: { position: 'relative' }, onMouseEnter: ev => setShow(_show => ({ ..._show, [type]: ev.target })), onMouseLeave: () => setShow(_show => ({ ..._show, [type]: null })), disabled: rowStatus[type] === false, children: [_jsx(ListItemIcon, { children: ICON_MAP[type] ?? _jsx(Terminal, {}) }), _jsx(ListItemText, { sx: { flex: 1 }, children: t(`hit.details.actions.${type}`) }), rowStatus[type] !== false && (_jsx(KeyboardArrowRight, { fontSize: "small", sx: { color: 'text.secondary', mr: -1 } })), _jsx(Fade, { in: !!show[type], unmountOnExit: true, children: _jsx(Paper, { id: `${type}-submenu`, sx: calculateSubMenuStyles(show[type]), elevation: 2, children: _jsx(MenuList, { sx: { p: 0, borderTopLeftRadius: 0 }, dense: true, role: "group", children: items.map(a => (_jsx(MenuItem, { value: a.name, onClick: a.actionFunction, children: a.i18nKey ? t(a.i18nKey) : capitalize(a.name) }, a.name))) }) }) })] }, type))), _jsxs(MenuItem, { id: "actions-menu-item", sx: { position: 'relative' }, onMouseEnter: ev => setShow(_show => ({ ..._show, actions: ev.target })), onMouseLeave: () => setShow(_show => ({ ..._show, actions: null })), disabled: actions.length < 1, children: [_jsx(ListItemIcon, { children: _jsx(SettingsSuggest, {}) }), _jsx(ListItemText, { sx: { flex: 1 }, children: t('route.actions.change') }), actions.length > 0 && _jsx(KeyboardArrowRight, { fontSize: "small", sx: { color: 'text.secondary', mr: -1 } }), _jsx(Fade, { in: !!show.actions, unmountOnExit: true, children: _jsx(Paper, { id: "actions-submenu", sx: calculateSubMenuStyles(show.actions), elevation: 2, children: _jsx(MenuList, { sx: { p: 0 }, dense: true, role: "group", children: actions.map(action => (_jsx(MenuItem, { onClick: () => executeAction(action.action_id, `howler.id:${hit?.howler.id}`), children: _jsx(ListItemText, { children: action.name }) }, action.action_id))) }) }) })] }), !isEmpty(template?.keys ?? []) && (_jsxs(_Fragment, { children: [_jsx(Divider, {}), _jsxs(MenuItem, { id: "excludes-menu-item", sx: { position: 'relative' }, onMouseEnter: ev => setShow(_show => ({ ..._show, excludes: ev.target })), onMouseLeave: () => setShow(_show => ({ ..._show, excludes: null })), children: [_jsx(ListItemIcon, { children: _jsx(RemoveCircleOutline, {}) }), _jsx(ListItemText, { sx: { flex: 1 }, children: t('hit.panel.exclude') }), _jsx(KeyboardArrowRight, { fontSize: "small", sx: { color: 'text.secondary', mr: -1 } }), _jsx(Fade, { in: !!show.excludes, unmountOnExit: true, children: _jsx(Paper, { id: "excludes-submenu", sx: calculateSubMenuStyles(show.excludes), elevation: 2, children: _jsx(MenuList, { sx: { p: 0 }, dense: true, role: "group", children: template?.keys.map(key => {
176
177
  // Build exclusion query based on current query and field value
177
178
  let newQuery = '';
178
179
  if (query !== DEFAULT_QUERY) {
@@ -198,30 +199,6 @@ const HitContextMenu = ({ children, getSelectedId, Component = Box }) => {
198
199
  newQuery += `-${key}:"${sanitizeLuceneQuery(value.toString())}"`;
199
200
  }
200
201
  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
203
- // 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));
225
202
  }) }) }) })] })] }))] })] }));
226
203
  };
227
204
  export default HitContextMenu;
@@ -623,132 +623,6 @@ 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
- });
752
626
  describe('Multiple Hit Selection', () => {
753
627
  it('should use selectedHits when current hit is included', async () => {
754
628
  act(() => {
@@ -847,20 +721,6 @@ describe('HitContextMenu', () => {
847
721
  expect(screen.queryByText('Exclude By')).toBeNull();
848
722
  });
849
723
  });
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
- });
864
724
  it('should handle API failure gracefully', async () => {
865
725
  mockDispatchApi.mockResolvedValue(null);
866
726
  rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
@@ -1,5 +1,6 @@
1
1
  import type { FC } from 'react';
2
2
  declare const InformationPane: FC<{
3
+ selected?: string;
3
4
  onClose?: () => void;
4
5
  }>;
5
6
  export default InformationPane;
@@ -12,13 +12,10 @@ import VSBox from '@cccsaurora/howler-ui/components/elements/addons/layout/vsbox
12
12
  import VSBoxContent from '@cccsaurora/howler-ui/components/elements/addons/layout/vsbox/VSBoxContent';
13
13
  import VSBoxHeader from '@cccsaurora/howler-ui/components/elements/addons/layout/vsbox/VSBoxHeader';
14
14
  import Phrase from '@cccsaurora/howler-ui/components/elements/addons/search/phrase/Phrase';
15
- import BundleButton from '@cccsaurora/howler-ui/components/elements/display/icons/BundleButton';
16
15
  import SocketBadge from '@cccsaurora/howler-ui/components/elements/display/icons/SocketBadge';
17
16
  import JSONViewer from '@cccsaurora/howler-ui/components/elements/display/json/JSONViewer';
18
17
  import HitActions from '@cccsaurora/howler-ui/components/elements/hit/HitActions';
19
- import HitBanner from '@cccsaurora/howler-ui/components/elements/hit/HitBanner';
20
18
  import HitComments from '@cccsaurora/howler-ui/components/elements/hit/HitComments';
21
- import HitDetails from '@cccsaurora/howler-ui/components/elements/hit/HitDetails';
22
19
  import HitLabels from '@cccsaurora/howler-ui/components/elements/hit/HitLabels';
23
20
  import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
24
21
  import HitNotebooks from '@cccsaurora/howler-ui/components/elements/hit/HitNotebooks';
@@ -29,6 +26,7 @@ import HitSummary from '@cccsaurora/howler-ui/components/elements/hit/HitSummary
29
26
  import HitWorklog from '@cccsaurora/howler-ui/components/elements/hit/HitWorklog';
30
27
  import PivotLink from '@cccsaurora/howler-ui/components/elements/hit/related/PivotLink';
31
28
  import RelatedLink from '@cccsaurora/howler-ui/components/elements/hit/related/RelatedLink';
29
+ import ObjectDetails from '@cccsaurora/howler-ui/components/elements/ObjectDetails';
32
30
  import useMyUserList from '@cccsaurora/howler-ui/components/hooks/useMyUserList';
33
31
  import ErrorBoundary from '@cccsaurora/howler-ui/components/routes/ErrorBoundary';
34
32
  import { uniqBy } from 'lodash-es';
@@ -42,13 +40,13 @@ import { getUserList } from '@cccsaurora/howler-ui/utils/hitFunctions';
42
40
  import { validateRegex } from '@cccsaurora/howler-ui/utils/stringUtils';
43
41
  import { tryParse } from '@cccsaurora/howler-ui/utils/utils';
44
42
  import LeadRenderer from '../view/LeadRenderer';
45
- const InformationPane = ({ onClose }) => {
43
+ const InformationPane = ({ onClose, selected: _selected }) => {
46
44
  const { t, i18n } = useTranslation();
47
45
  const theme = useTheme();
48
46
  const location = useLocation();
49
47
  const { emit, isOpen } = useContext(SocketContext);
50
48
  const { getMatchingOverview, getMatchingDossiers, getMatchingAnalytic } = useMatchers();
51
- const selected = useContextSelector(ParameterContext, ctx => ctx.selected);
49
+ const selected = useContextSelector(ParameterContext, ctx => ctx?.selected) ?? _selected;
52
50
  const pluginStore = usePluginStore();
53
51
  const getHit = useContextSelector(HitContext, ctx => ctx.getHit);
54
52
  const [userIds, setUserIds] = useState(new Set());
@@ -97,11 +95,6 @@ const InformationPane = ({ onClose }) => {
97
95
  useEffect(() => {
98
96
  getMatchingOverview(hit).then(_overview => setHasOverview(!!_overview));
99
97
  }, [getMatchingOverview, hit]);
100
- useEffect(() => {
101
- if (tab === 'hit_aggregate' && !hit?.howler.is_bundle) {
102
- setTab('overview');
103
- }
104
- }, [hit?.howler.is_bundle, tab]);
105
98
  useEffect(() => {
106
99
  if (selected && isOpen()) {
107
100
  emit({
@@ -125,28 +118,13 @@ const InformationPane = ({ onClose }) => {
125
118
  }
126
119
  // eslint-disable-next-line react-hooks/exhaustive-deps
127
120
  }, [hasOverview]);
128
- /**
129
- * What to show as the header? If loading a skeleton, then it depends on bundle or not. Bundles don't
130
- * show anything while normal hits do
131
- */
132
- const header = useMemo(() => {
133
- if (loading && !hit?.howler?.is_bundle) {
134
- return _jsx(Skeleton, { variant: "rounded", height: 152 });
135
- }
136
- else if (!!hit && !hit.howler.is_bundle) {
137
- return _jsx(HitBanner, { layout: HitLayout.DENSE, hit: hit });
138
- }
139
- else {
140
- return null;
141
- }
142
- }, [hit, loading]);
143
121
  const tabContent = useMemo(() => {
144
122
  if (!tab) {
145
123
  return;
146
124
  }
147
125
  return {
148
126
  overview: () => _jsx(HitOverview, { hit: hit }),
149
- details: () => _jsx(HitDetails, { hit: hit }),
127
+ details: () => _jsx(ObjectDetails, { obj: hit }),
150
128
  hit_comments: () => _jsx(HitComments, { hit: hit, users: users }),
151
129
  hit_raw: () => _jsx(JSONViewer, { data: !loading && hit, hideSearch: true, filter: filter }),
152
130
  hit_data: () => (_jsx(JSONViewer, { data: !loading && hit?.howler?.data?.map(entry => tryParse(entry)), collapse: false, hideSearch: true, filter: filter })),
@@ -164,8 +142,7 @@ const InformationPane = ({ onClose }) => {
164
142
  }[tab]?.();
165
143
  }, [dossiers, filter, hit, loading, tab, users]);
166
144
  const hasError = useMemo(() => !validateRegex(filter), [filter]);
167
- return (_jsxs(VSBox, { top: 10, sx: { height: '100%', flex: 1 }, children: [_jsxs(Stack, { direction: "column", flex: 1, sx: { overflowY: 'auto', flexGrow: 1 }, position: "relative", spacing: 1, ml: 2, children: [_jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, flexShrink: 0, pr: 2, sx: [hit?.howler?.is_bundle && { position: 'absolute', top: 1, right: 0, zIndex: 1100 }], children: [_jsx(FlexOne, {}), onClose && !location.pathname.startsWith('/bundles') && (_jsx(TuiIconButton, { size: "small", onClick: onClose, tooltip: t('hit.panel.details.exit'), children: _jsx(Clear, {}) })), _jsx(SocketBadge, { size: "small" }), analytic && (_jsx(TuiIconButton, { size: "small", tooltip: t('hit.panel.analytic.open'), disabled: !analytic || loading, route: `/analytics/${analytic.analytic_id}`, children: _jsx(QueryStats, {}) })), hit?.howler.bundles?.length > 0 && _jsx(BundleButton, { ids: hit.howler.bundles, disabled: loading }), !!hit && !hit.howler.is_bundle && (_jsx(TuiIconButton, { tooltip: t('hit.panel.open'), href: `/hits/${selected}`, disabled: !hit || loading, size: "small", target: "_blank", children: _jsx(OpenInNew, {}) }))] }), _jsx(Box, { pr: 2, children: header }), !!hit &&
168
- !hit.howler.is_bundle &&
145
+ return (_jsxs(VSBox, { top: 10, sx: { height: '100%', flex: 1 }, children: [_jsxs(Stack, { direction: "column", flex: 1, sx: { overflowY: 'auto', flexGrow: 1 }, position: "relative", spacing: 1, ml: 2, children: [_jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, flexShrink: 0, pr: 2, children: [_jsx(FlexOne, {}), onClose && !location.pathname.startsWith('/bundles') && (_jsx(TuiIconButton, { size: "small", onClick: onClose, tooltip: t('hit.panel.details.exit'), children: _jsx(Clear, {}) })), _jsx(SocketBadge, { size: "small" }), analytic && (_jsx(TuiIconButton, { size: "small", tooltip: t('hit.panel.analytic.open'), disabled: !analytic || loading, route: `/analytics/${analytic.analytic_id}`, children: _jsx(QueryStats, {}) })), !!hit && (_jsx(TuiIconButton, { tooltip: t('hit.panel.open'), href: `/hits/${selected}`, disabled: !hit || loading, size: "small", target: "_blank", children: _jsx(OpenInNew, {}) }))] }), !!hit &&
169
146
  (!loading ? (_jsxs(_Fragment, { children: [_jsx(HitOutline, { hit: hit, layout: HitLayout.DENSE }), _jsx(HitLabels, { hit: hit })] })) : (_jsx(Skeleton, { height: 124 }))), (hit?.howler?.links?.length > 0 ||
170
147
  analytic?.notebooks?.length > 0 ||
171
148
  dossiers.filter(_dossier => _dossier.pivots?.length > 0).length > 0) && (_jsxs(Stack, { direction: "row", spacing: 1, pr: 2, children: [analytic?.notebooks?.length > 0 && _jsx(HitNotebooks, { analytic: analytic, hit: hit }), hit?.howler?.links?.length > 0 &&
@@ -202,7 +179,7 @@ const InformationPane = ({ onClose }) => {
202
179
  right: theme.spacing(-0.5)
203
180
  },
204
181
  '& > svg': { zIndex: 2 }
205
- }, badgeContent: hit?.howler.comment?.length ?? 0, children: _jsx(Comment, {}) }) }), value: "hit_comments", onClick: () => setTab('hit_comments') }), hit?.howler?.is_bundle && (_jsx(Tab, { label: t('hit.viewer.aggregate'), value: "hit_aggregate", onClick: () => setTab('hit_aggregate') })), hasOverview && (_jsx(Tab, { label: t('hit.viewer.overview'), value: "overview", onClick: () => setTab('overview') })), _jsx(Tab, { label: t('hit.viewer.details'), value: "details", onClick: () => setTab('details') }), hit?.howler.dossier?.map((lead, index) => (_jsx(Tab
182
+ }, badgeContent: hit?.howler.comment?.length ?? 0, children: _jsx(Comment, {}) }) }), value: "hit_comments", onClick: () => setTab('hit_comments') }), hasOverview && (_jsx(Tab, { label: t('hit.viewer.overview'), value: "overview", onClick: () => setTab('overview') })), _jsx(Tab, { label: t('hit.viewer.details'), value: "details", onClick: () => setTab('details') }), hit?.howler.dossier?.map((lead, index) => (_jsx(Tab
206
183
  // eslint-disable-next-line react/no-array-index-key
207
184
  , { label: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [lead.icon && _jsx(Icon, { icon: lead.icon }), _jsx("span", { children: i18n.language === 'en' ? lead.label.en : lead.label.fr })] }), value: 'lead:' + index, onClick: () => setTab('lead:' + index) }, 'lead:' + index))), dossiers.flatMap((_dossier, dossierIndex) => (_dossier.leads ?? []).map((_lead, leadIndex) => (_jsx(Tab
208
185
  // eslint-disable-next-line react/no-array-index-key
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Close, ErrorOutline, List, SavedSearch, TableChart, Terminal } from '@mui/icons-material';
2
+ import { ErrorOutline, List, SavedSearch, TableChart, Terminal } from '@mui/icons-material';
3
3
  import { Box, IconButton, LinearProgress, Stack, ToggleButton, ToggleButtonGroup, Tooltip, Typography, useMediaQuery, useTheme } from '@mui/material';
4
4
  import { grey } from '@mui/material/colors';
5
5
  import AppListEmpty from '@cccsaurora/howler-ui/commons/components/display/AppListEmpty';
@@ -23,10 +23,9 @@ import useMyLocalStorage, { useMyLocalStorageItem } from '@cccsaurora/howler-ui/
23
23
  import React, { memo, useCallback, useEffect, useMemo } from 'react';
24
24
  import { isMobile } from 'react-device-detect';
25
25
  import { useTranslation } from 'react-i18next';
26
- import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
26
+ import { Link, useLocation, useParams } from 'react-router-dom';
27
27
  import { useContextSelector } from 'use-context-selector';
28
28
  import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
29
- import BundleParentMenu from './BundleParentMenu';
30
29
  import { BundleScroller } from './BundleScroller';
31
30
  import HitContextMenu from './HitContextMenu';
32
31
  import HitQuery from './HitQuery';
@@ -79,7 +78,6 @@ const Item = memo(({ hit, onClick }) => {
79
78
  const SearchPane = () => {
80
79
  const { t } = useTranslation();
81
80
  const location = useLocation();
82
- const navigate = useNavigate();
83
81
  const routeParams = useParams();
84
82
  const selected = useContextSelector(ParameterContext, ctx => ctx.selected);
85
83
  const setSelected = useContextSelector(ParameterContext, ctx => ctx.setSelected);
@@ -118,7 +116,7 @@ const SearchPane = () => {
118
116
  ], onClick: () => {
119
117
  clearSelectedHits(bundleHit.howler.id);
120
118
  setSelected(bundleHit.howler.id);
121
- }, children: _jsx(HitBanner, { hit: bundleHit, layout: HitLayout.DENSE, useListener: true }) }) }) }) })), _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5 }, variant: "body2", children: t('hit.search.prompt') }), error && (_jsx(Tooltip, { title: `${t('route.advanced.error')}: ${error}`, children: _jsx(ErrorOutline, { fontSize: "small", color: "error" }) })), _jsx(FlexOne, {}), bundleHit?.howler.bundles.length > 0 && _jsx(BundleParentMenu, { bundle: bundleHit }), bundleHit && (_jsx(Tooltip, { title: t('hit.bundle.close'), children: _jsx(IconButton, { size: "small", onClick: () => navigate('/search'), children: _jsx(Close, {}) }) })), _jsx(Tooltip, { title: t('route.views.save'), children: _jsx(IconButton, { component: Link, disabled: !query, to: `/views/create?query=${query}`, children: _jsx(SavedSearch, {}) }) }), _jsx(Tooltip, { title: t('route.actions.save'), children: _jsx(IconButton, { component: Link, disabled: !query, to: `/action/execute?query=${query}`, children: _jsx(Terminal, {}) }) }), _jsxs(ToggleButtonGroup, { exclusive: true, value: displayType, onChange: (__, value) => setDisplayType(value), size: "small", children: [_jsx(ToggleButton, { value: "list", children: _jsx(List, {}) }), _jsx(ToggleButton, { value: "grid", children: _jsx(TableChart, {}) })] })] })] }), _jsxs(VSBoxHeader, { ml: -3, mr: -3, px: 2, pb: 1, sx: { zIndex: 989 }, children: [_jsxs(Stack, { sx: { pt: 1 }, children: [_jsxs(Stack, { sx: { position: 'relative', flex: 1 }, children: [_jsx(HitQuery, { searching: searching, triggerSearch: triggerSearch }), searching && (_jsx(LinearProgress, { sx: theme => ({
119
+ }, children: _jsx(HitBanner, { hit: bundleHit, layout: HitLayout.DENSE, useListener: true }) }) }) }) })), _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5 }, variant: "body2", children: t('hit.search.prompt') }), error && (_jsx(Tooltip, { title: `${t('route.advanced.error')}: ${error}`, children: _jsx(ErrorOutline, { fontSize: "small", color: "error" }) })), _jsx(FlexOne, {}), _jsx(Tooltip, { title: t('route.views.save'), children: _jsx(IconButton, { component: Link, disabled: !query, to: `/views/create?query=${query}`, children: _jsx(SavedSearch, {}) }) }), _jsx(Tooltip, { title: t('route.actions.save'), children: _jsx(IconButton, { component: Link, disabled: !query, to: `/action/execute?query=${query}`, children: _jsx(Terminal, {}) }) }), _jsxs(ToggleButtonGroup, { exclusive: true, value: displayType, onChange: (__, value) => setDisplayType(value), size: "small", children: [_jsx(ToggleButton, { value: "list", children: _jsx(List, {}) }), _jsx(ToggleButton, { value: "grid", children: _jsx(TableChart, {}) })] })] })] }), _jsxs(VSBoxHeader, { ml: -3, mr: -3, px: 2, pb: 1, sx: { zIndex: 989 }, children: [_jsxs(Stack, { sx: { pt: 1 }, children: [_jsxs(Stack, { sx: { position: 'relative', flex: 1 }, children: [_jsx(HitQuery, { searching: searching, triggerSearch: triggerSearch }), searching && (_jsx(LinearProgress, { sx: theme => ({
122
120
  position: 'absolute',
123
121
  left: 0,
124
122
  right: 0,
@@ -47,7 +47,7 @@ const ViewLink = ({ id, viewId }) => {
47
47
  }, [query, sort, span, view]);
48
48
  const options = useMemo(() => Object.values(views).filter(_view => !!_view && !currentViews?.includes(_view.view_id)), [currentViews, views]);
49
49
  if (loading) {
50
- return _jsx(Chip, { size: "small", icon: _jsx(CircularProgress, { size: 12 }) });
50
+ return _jsx(Chip, { icon: _jsx(CircularProgress, { size: 12 }) });
51
51
  }
52
52
  if (viewId === '') {
53
53
  return (_jsx(ChipPopper, { icon: _jsx(SelectAll, {}), label: t('hit.search.view.select'), deleteIcon: _jsx(ArrowDropDown, {}), toggleOnDelete: true, slotProps: { chip: { size: 'small', color: 'warning' } }, children: _jsxs(Stack, { spacing: 1, direction: "row", children: [_jsx(Autocomplete, { fullWidth: true, size: "small", options: options, getOptionLabel: _view => t(_view.title), renderOption: ({ key, ...props }, o) => (_createElement("li", { ...props, key: key },
@@ -13,6 +13,6 @@ const EnhancedCell = ({ hit, value: rawValue, sx = {}, className, field }) => {
13
13
  return (_jsx(TableCell, { sx: { borderBottom: 'none', borderRight: 'thin solid', borderRightColor: 'divider', fontSize: '0.8rem' }, children: _jsx(Stack, { direction: "row", className: className, spacing: 0.5, sx: [
14
14
  { display: 'flex', justifyContent: 'start', width: '100%', overflow: 'hidden' },
15
15
  ...(Array.isArray(sx) ? sx : [sx])
16
- ], children: values.map((value, index) => (_jsx(PluginTypography, { context: "table", sx: { fontSize: 'inherit', textOverflow: 'ellipsis' }, value: value, field: field, hit: hit, children: value }, value + index))) }) }));
16
+ ], children: values.map((value, index) => (_jsx(PluginTypography, { context: "table", sx: { fontSize: 'inherit', textOverflow: 'ellipsis' }, value: value, field: field, obj: hit, children: value }, value + index))) }) }));
17
17
  };
18
18
  export default memo(EnhancedCell);
@@ -7,13 +7,11 @@ import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers'
7
7
  import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
8
8
  import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
9
9
  import HowlerCard from '@cccsaurora/howler-ui/components/elements/display/HowlerCard';
10
- import BundleButton from '@cccsaurora/howler-ui/components/elements/display/icons/BundleButton';
11
10
  import SocketBadge from '@cccsaurora/howler-ui/components/elements/display/icons/SocketBadge';
12
11
  import JSONViewer from '@cccsaurora/howler-ui/components/elements/display/json/JSONViewer';
13
12
  import HitActions from '@cccsaurora/howler-ui/components/elements/hit/HitActions';
14
13
  import HitBanner from '@cccsaurora/howler-ui/components/elements/hit/HitBanner';
15
14
  import HitComments from '@cccsaurora/howler-ui/components/elements/hit/HitComments';
16
- import HitDetails from '@cccsaurora/howler-ui/components/elements/hit/HitDetails';
17
15
  import HitLabels from '@cccsaurora/howler-ui/components/elements/hit/HitLabels';
18
16
  import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
19
17
  import HitNotebooks from '@cccsaurora/howler-ui/components/elements/hit/HitNotebooks';
@@ -23,6 +21,7 @@ import HitRelated from '@cccsaurora/howler-ui/components/elements/hit/HitRelated
23
21
  import HitWorklog from '@cccsaurora/howler-ui/components/elements/hit/HitWorklog';
24
22
  import PivotLink from '@cccsaurora/howler-ui/components/elements/hit/related/PivotLink';
25
23
  import RelatedLink from '@cccsaurora/howler-ui/components/elements/hit/related/RelatedLink';
24
+ import ObjectDetails from '@cccsaurora/howler-ui/components/elements/ObjectDetails';
26
25
  import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
27
26
  import useMyUserList from '@cccsaurora/howler-ui/components/hooks/useMyUserList';
28
27
  import uniqBy from 'lodash-es/uniqBy';
@@ -97,7 +96,7 @@ const HitViewer = () => {
97
96
  }
98
97
  return {
99
98
  overview: () => _jsx(HitOverview, { hit: hit }),
100
- details: () => _jsx(HitDetails, { hit: hit }),
99
+ details: () => _jsx(ObjectDetails, { obj: hit }),
101
100
  hit_comments: () => _jsx(HitComments, { hit: hit, users: users }),
102
101
  hit_raw: () => _jsx(JSONViewer, { data: hit }),
103
102
  hit_data: () => _jsx(JSONViewer, { data: hit?.howler?.data?.map(entry => tryParse(entry)), collapse: false }),
@@ -142,7 +141,7 @@ const HitViewer = () => {
142
141
  position: 'absolute',
143
142
  top: theme.spacing(2),
144
143
  right: theme.spacing(-6)
145
- }, children: [_jsx(Tooltip, { title: t('page.hits.view.layout'), children: _jsx(IconButton, { onClick: onOrientationChange, children: _jsx(ViewAgenda, { sx: { transition: 'rotate 250ms', rotate: orientation === 'vertical' ? '90deg' : '0deg' } }) }) }), _jsx(SocketBadge, { size: "medium" }), analytic && (_jsx(Tooltip, { title: t('hit.panel.analytic.open'), children: _jsx(IconButton, { onClick: () => navigate(`/analytics/${analytic.analytic_id}`), children: _jsx(QueryStats, {}) }) })), hit?.howler.bundles?.length > 0 && _jsx(BundleButton, { ids: hit.howler.bundles })] }))] }), _jsx(HowlerCard, { sx: [orientation === 'horizontal' && { height: '0px' }], children: _jsx(CardContent, { sx: { padding: 1, position: 'relative' }, children: _jsx(HitActions, { hit: hit, orientation: "vertical" }) }) }), _jsx(Box, { sx: { gridColumn: '1 / span 2', mb: 1 }, children: _jsxs(Tabs, { value: tab === 'overview' && !hasOverview ? 'details' : tab, sx: { display: 'flex', flexDirection: 'row', pr: 2, alignItems: 'center' }, children: [hit?.howler?.is_bundle && (_jsx(Tab, { label: t('hit.viewer.aggregate'), value: "hit_aggregate", onClick: () => setTab('hit_aggregate') })), hasOverview && (_jsx(Tab, { label: t('hit.viewer.overview'), value: "overview", onClick: () => setTab('overview') })), _jsx(Tab, { label: t('hit.viewer.details'), value: "details", onClick: () => setTab('details') }), hit?.howler.dossier?.map((lead, index) => (_jsx(Tab
144
+ }, children: [_jsx(Tooltip, { title: t('page.hits.view.layout'), children: _jsx(IconButton, { onClick: onOrientationChange, children: _jsx(ViewAgenda, { sx: { transition: 'rotate 250ms', rotate: orientation === 'vertical' ? '90deg' : '0deg' } }) }) }), _jsx(SocketBadge, { size: "medium" }), analytic && (_jsx(Tooltip, { title: t('hit.panel.analytic.open'), children: _jsx(IconButton, { onClick: () => navigate(`/analytics/${analytic.analytic_id}`), children: _jsx(QueryStats, {}) }) }))] }))] }), _jsx(HowlerCard, { sx: [orientation === 'horizontal' && { height: '0px' }], children: _jsx(CardContent, { sx: { padding: 1, position: 'relative' }, children: _jsx(HitActions, { hit: hit, orientation: "vertical" }) }) }), _jsx(Box, { sx: { gridColumn: '1 / span 2', mb: 1 }, children: _jsxs(Tabs, { value: tab === 'overview' && !hasOverview ? 'details' : tab, sx: { display: 'flex', flexDirection: 'row', pr: 2, alignItems: 'center' }, children: [hasOverview && (_jsx(Tab, { label: t('hit.viewer.overview'), value: "overview", onClick: () => setTab('overview') })), _jsx(Tab, { label: t('hit.viewer.details'), value: "details", onClick: () => setTab('details') }), hit?.howler.dossier?.map((lead, index) => (_jsx(Tab
146
145
  // eslint-disable-next-line react/no-array-index-key
147
146
  , { label: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [lead.icon && _jsx(Icon, { icon: lead.icon }), _jsx("span", { children: i18n.language === 'en' ? lead.label.en : lead.label.fr })] }), value: 'lead:' + index, onClick: () => setTab('lead:' + index) }, 'lead:' + index))), dossiers.flatMap((_dossier, dossierIndex) => (_dossier.leads ?? []).map((_lead, leadIndex) => (_jsx(Tab
148
147
  // eslint-disable-next-line react/no-array-index-key
@@ -39,6 +39,6 @@ const ViewCard = ({ viewId, limit }) => {
39
39
  });
40
40
  }, [dispatchApi, limit, view?.query]);
41
41
  const onClick = useCallback((query) => navigate('/hits?query=' + query), [navigate]);
42
- return (_jsx(Card, { variant: "outlined", sx: { height: '100%' }, children: _jsxs(Stack, { spacing: 1, sx: { p: 1, minHeight: 100 }, children: [_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { variant: "h6", children: t(view?.title) || _jsx(Skeleton, { variant: "text", height: "2em", width: "100px" }) }), _jsx(IconButton, { size: "small", onClick: () => onClick(view.query), children: _jsx(OpenInNew, { fontSize: "small" }) })] }), loading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { height: 150, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 160, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 140, width: "100%", variant: "rounded" })] })) : hits.length > 0 ? (hits.map(h => (_jsx(Card, { variant: "outlined", sx: { cursor: 'pointer' }, onClick: () => navigate((h.howler.is_bundle ? '/bundles/' : '/hits/') + h.howler.id), children: _jsx(CardContent, { children: _jsx(HitBanner, { layout: HitLayout.DENSE, hit: h }) }) }, h.howler.id)))) : (_jsx(AppListEmpty, {}))] }) }));
42
+ return (_jsx(Card, { variant: "outlined", sx: { height: '100%' }, children: _jsxs(Stack, { spacing: 1, sx: { p: 1, minHeight: 100 }, children: [_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { variant: "h6", children: t(view?.title) || _jsx(Skeleton, { variant: "text", height: "2em", width: "100px" }) }), _jsx(IconButton, { size: "small", onClick: () => onClick(view.query), children: _jsx(OpenInNew, { fontSize: "small" }) })] }), loading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { height: 150, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 160, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 140, width: "100%", variant: "rounded" })] })) : hits.length > 0 ? (hits.map(h => (_jsx(Card, { variant: "outlined", sx: { cursor: 'pointer' }, onClick: () => navigate(`/hits/${h.howler.id}`), children: _jsx(CardContent, { children: _jsx(HitBanner, { layout: HitLayout.DENSE, hit: h }) }) }, h.howler.id)))) : (_jsx(AppListEmpty, {}))] }) }));
43
43
  };
44
44
  export default ViewCard;
@@ -0,0 +1,7 @@
1
+ import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
2
+ import { type FC } from 'react';
3
+ declare const ObservableViewer: FC<{
4
+ observable?: Observable;
5
+ observableId?: string;
6
+ }>;
7
+ export default ObservableViewer;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Skeleton } from '@mui/material';
3
+ import api from '@cccsaurora/howler-ui/api';
4
+ import ObjectDetails from '@cccsaurora/howler-ui/components/elements/ObjectDetails';
5
+ import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
6
+ import { useEffect, useState } from 'react';
7
+ const ObservableViewer = ({ observable: provided, observableId }) => {
8
+ const { dispatchApi } = useMyApi();
9
+ const [observable, setObservable] = useState(null);
10
+ useEffect(() => {
11
+ if (provided) {
12
+ setObservable(provided);
13
+ }
14
+ }, [provided]);
15
+ useEffect(() => {
16
+ if (observableId) {
17
+ dispatchApi(api.v2.search.post('observable', { query: `howler.id:${observableId}`, rows: 1 }), {
18
+ throwError: false
19
+ }).then(res => setObservable(res.items[0]));
20
+ }
21
+ }, [dispatchApi, observableId]);
22
+ if (!observable) {
23
+ return;
24
+ }
25
+ return _jsx(Box, { p: 1, children: observable ? _jsx(ObjectDetails, { obj: observable }) : _jsx(Skeleton, { height: 120 }) });
26
+ };
27
+ export default ObservableViewer;
@@ -15,7 +15,7 @@ import useMyTheme from '@cccsaurora/howler-ui/components/hooks/useMyTheme';
15
15
  import { useSearchParams } from 'react-router-dom';
16
16
  import hitsData from '@cccsaurora/howler-ui/utils/hit.json';
17
17
  import { sanitizeLuceneQuery } from '@cccsaurora/howler-ui/utils/stringUtils';
18
- import OverviewEditor from './OverviewEditor';
18
+ import MarkdownEditor from '../../elements/MarkdownEditor';
19
19
  import { useStartingTemplate } from './startingTemplate';
20
20
  const OverviewViewer = () => {
21
21
  const theme = useTheme();
@@ -188,7 +188,7 @@ const OverviewViewer = () => {
188
188
  right: `calc(50% + 7px - ${x}px)`,
189
189
  mr: -2.4,
190
190
  pr: 1.5
191
- }, children: _jsx(OverviewEditor, { height: "100%", content: content, setContent: setContent }) }) }), _jsx(Box, { sx: {
191
+ }, children: _jsx(MarkdownEditor, { height: "100%", content: content, setContent: setContent }) }) }), _jsx(Box, { sx: {
192
192
  position: 'absolute',
193
193
  top: 0,
194
194
  bottom: 0,