@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.
- package/api/index.d.ts +2 -0
- package/api/index.js +4 -2
- package/api/search/case.d.ts +4 -0
- package/api/search/case.js +8 -0
- package/api/search/index.d.ts +2 -1
- package/api/search/index.js +2 -1
- package/api/v2/case/index.d.ts +6 -0
- package/api/v2/case/index.js +18 -0
- package/api/v2/index.d.ts +4 -0
- package/api/v2/index.js +6 -0
- package/api/v2/search/facet.d.ts +3 -0
- package/api/v2/search/facet.js +12 -0
- package/api/v2/search/index.d.ts +6 -0
- package/api/v2/search/index.js +18 -0
- package/commons/components/leftnav/LeftNavDrawer.js +1 -1
- package/components/app/App.js +14 -0
- package/components/app/providers/FavouritesProvider.js +2 -2
- package/components/{routes/overviews/OverviewEditor.js → elements/MarkdownEditor.js} +3 -3
- package/components/elements/{hit/HitDetails.d.ts → ObjectDetails.d.ts} +2 -1
- package/components/elements/{hit/HitDetails.js → ObjectDetails.js} +14 -14
- package/components/elements/PluginTypography.d.ts +2 -1
- package/components/elements/PluginTypography.js +3 -2
- package/components/elements/UserList.d.ts +5 -2
- package/components/elements/UserList.js +14 -5
- package/components/elements/addons/search/phrase/Phrase.js +1 -1
- package/components/elements/display/HowlerCard.js +1 -1
- package/components/elements/hit/HitBanner.js +19 -31
- package/components/elements/hit/outlines/DefaultOutline.js +1 -1
- package/components/elements/view/ViewTitle.js +1 -1
- package/components/hooks/useHitSelection.js +1 -35
- package/components/hooks/useMyPreferences.js +10 -1
- package/components/hooks/useMySitemap.js +3 -1
- package/components/hooks/useMyTheme.js +9 -2
- package/components/routes/action/view/ActionSearch.js +1 -1
- package/components/routes/action/view/Integrations.js +1 -9
- package/components/routes/advanced/QueryBuilder.js +1 -1
- package/components/routes/analytics/AnalyticDetails.js +2 -2
- package/components/routes/analytics/AnalyticSearch.js +1 -1
- package/components/routes/cases/CaseCard.d.ts +8 -0
- package/components/routes/cases/CaseCard.js +34 -0
- package/components/routes/cases/CaseViewer.d.ts +2 -0
- package/components/routes/cases/CaseViewer.js +24 -0
- package/components/routes/cases/Cases.d.ts +2 -0
- package/components/routes/cases/Cases.js +101 -0
- package/components/routes/cases/constants.d.ts +5 -0
- package/components/routes/cases/constants.js +5 -0
- package/components/routes/cases/detail/AlertPanel.d.ts +6 -0
- package/components/routes/cases/detail/AlertPanel.js +32 -0
- package/components/routes/cases/detail/CaseDashboard.d.ts +7 -0
- package/components/routes/cases/detail/CaseDashboard.js +46 -0
- package/components/routes/cases/detail/CaseDetails.d.ts +6 -0
- package/components/routes/cases/detail/CaseDetails.js +49 -0
- package/components/routes/cases/detail/CaseOverview.d.ts +7 -0
- package/components/routes/cases/detail/CaseOverview.js +43 -0
- package/components/routes/cases/detail/CaseSidebar.d.ts +6 -0
- package/components/routes/cases/detail/CaseSidebar.js +36 -0
- package/components/routes/cases/detail/CaseTask.d.ts +10 -0
- package/components/routes/cases/detail/CaseTask.js +46 -0
- package/components/routes/cases/detail/ItemPage.d.ts +6 -0
- package/components/routes/cases/detail/ItemPage.js +93 -0
- package/components/routes/cases/detail/RelatedCasePanel.d.ts +6 -0
- package/components/routes/cases/detail/RelatedCasePanel.js +31 -0
- package/components/routes/cases/detail/TaskPanel.d.ts +7 -0
- package/components/routes/cases/detail/TaskPanel.js +23 -0
- package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +12 -0
- package/components/routes/cases/detail/aggregates/CaseAggregate.js +19 -0
- package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +6 -0
- package/components/routes/cases/detail/aggregates/SourceAggregate.js +27 -0
- package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +12 -0
- package/components/routes/cases/detail/sidebar/CaseFolder.js +114 -0
- package/components/routes/cases/detail/sidebar/types.d.ts +3 -0
- package/components/routes/cases/hooks/useCase.d.ts +13 -0
- package/components/routes/cases/hooks/useCase.js +38 -0
- package/components/routes/help/ApiDocumentation.js +1 -1
- package/components/routes/help/HitDocumentation.js +1 -3
- package/components/routes/hits/search/HitContextMenu.js +4 -27
- package/components/routes/hits/search/HitContextMenu.test.js +0 -140
- package/components/routes/hits/search/InformationPane.d.ts +1 -0
- package/components/routes/hits/search/InformationPane.js +6 -29
- package/components/routes/hits/search/SearchPane.js +3 -5
- package/components/routes/hits/search/ViewLink.js +1 -1
- package/components/routes/hits/search/grid/EnhancedCell.js +1 -1
- package/components/routes/hits/view/HitViewer.js +3 -4
- package/components/routes/home/ViewCard.js +1 -1
- package/components/routes/observables/ObservableViewer.d.ts +7 -0
- package/components/routes/observables/ObservableViewer.js +27 -0
- package/components/routes/overviews/OverviewViewer.js +2 -2
- package/locales/en/translation.json +422 -397
- package/locales/fr/translation.json +429 -406
- package/models/entities/generated/AttachmentsFile.d.ts +12 -0
- package/models/entities/generated/Case.d.ts +28 -0
- package/models/entities/generated/DestinationOriginal.d.ts +19 -0
- package/models/entities/generated/EmailAttachment.d.ts +8 -0
- package/models/entities/generated/EmailParent.d.ts +19 -0
- package/models/entities/generated/Enrichments.d.ts +7 -0
- package/models/entities/generated/EnrichmentsIndicator.d.ts +21 -0
- package/models/entities/generated/Howler.d.ts +0 -4
- package/models/entities/generated/HttpResponse.d.ts +11 -0
- package/models/entities/generated/Item.d.ts +9 -0
- package/models/entities/generated/Observable.d.ts +84 -0
- package/models/entities/generated/ObservableCloud.d.ts +20 -0
- package/models/entities/generated/ObservableDestination.d.ts +23 -0
- package/models/entities/generated/ObservableEmail.d.ts +30 -0
- package/models/entities/generated/ObservableFile.d.ts +36 -0
- package/models/entities/generated/ObservableHowler.d.ts +44 -0
- package/models/entities/generated/ObservableHttp.d.ts +11 -0
- package/models/entities/generated/ObservableObserver.d.ts +21 -0
- package/models/entities/generated/ObservableOrganization.d.ts +7 -0
- package/models/entities/generated/ObservableProcess.d.ts +34 -0
- package/models/entities/generated/ObservableSource.d.ts +23 -0
- package/models/entities/generated/ObservableThreat.d.ts +21 -0
- package/models/entities/generated/ObservableTls.d.ts +12 -0
- package/models/entities/generated/ObserverIngress.d.ts +9 -0
- package/models/entities/generated/Rule.d.ts +2 -10
- package/models/entities/generated/Task.d.ts +10 -0
- package/models/entities/generated/Threat.d.ts +2 -2
- package/models/entities/generated/{Enrichment.d.ts → ThreatEnrichment.d.ts} +1 -1
- package/package.json +125 -114
- package/plugins/clue/components/ClueTypography.js +2 -2
- package/plugins/clue/utils.d.ts +2 -1
- package/components/elements/display/icons/BundleButton.d.ts +0 -6
- package/components/elements/display/icons/BundleButton.js +0 -32
- package/components/routes/action/view/markdown/integrations.en.md.js +0 -1
- package/components/routes/action/view/markdown/integrations.fr.md.js +0 -1
- package/components/routes/help/BundleDocumentation.d.ts +0 -3
- package/components/routes/help/BundleDocumentation.js +0 -12
- package/components/routes/help/markdown/en/bundles.md.js +0 -1
- package/components/routes/help/markdown/fr/bundles.md.js +0 -1
- package/components/routes/hits/search/BundleParentMenu.d.ts +0 -6
- package/components/routes/hits/search/BundleParentMenu.js +0 -32
- /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 {
|
|
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:
|
|
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" }) }) }));
|
|
@@ -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
|
|
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(
|
|
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,
|
|
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') }),
|
|
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 {
|
|
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,
|
|
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, {}),
|
|
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, {
|
|
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,
|
|
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(
|
|
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, {}) }) }))
|
|
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(
|
|
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,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
|
|
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(
|
|
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,
|