@cccsaurora/howler-ui 2.18.0-dev.674 → 2.18.0-dev.676
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 +0 -2
- package/api/index.js +2 -4
- package/api/search/index.d.ts +1 -2
- package/api/search/index.js +1 -2
- package/commons/components/leftnav/LeftNavDrawer.js +1 -1
- package/components/app/App.js +7 -34
- package/components/app/hooks/useMatchers.js +2 -2
- package/components/app/hooks/useMatchers.test.js +22 -22
- package/components/app/hooks/useTitle.js +3 -3
- package/components/app/providers/FavouritesProvider.js +2 -2
- package/components/app/providers/HitProvider.d.ts +22 -0
- package/components/app/providers/{RecordProvider.js → HitProvider.js} +41 -41
- package/components/app/providers/{RecordSearchProvider.d.ts → HitSearchProvider.d.ts} +6 -6
- package/components/app/providers/{RecordSearchProvider.js → HitSearchProvider.js} +17 -12
- package/components/app/providers/{RecordSearchProvider.test.js → HitSearchProvider.test.js} +70 -51
- package/components/app/providers/ParameterProvider.d.ts +2 -9
- package/components/app/providers/ParameterProvider.js +240 -165
- package/components/app/providers/ParameterProvider.test.js +14 -307
- package/components/elements/PluginTypography.d.ts +1 -2
- package/components/elements/PluginTypography.js +2 -3
- package/components/elements/UserList.d.ts +2 -5
- package/components/elements/UserList.js +5 -14
- package/components/elements/addons/search/phrase/Phrase.js +1 -1
- package/components/elements/display/ChipPopper.d.ts +1 -1
- package/components/elements/display/HowlerCard.js +1 -1
- package/components/elements/display/Modal.js +0 -1
- package/components/elements/display/icons/BundleButton.d.ts +6 -0
- package/components/elements/display/icons/BundleButton.js +32 -0
- package/components/elements/hit/HitActions.js +4 -4
- package/components/elements/hit/HitBanner.js +48 -28
- package/components/elements/hit/HitCard.js +5 -5
- package/components/elements/{record/RecordComments.d.ts → hit/HitComments.d.ts} +4 -5
- package/components/elements/{record/RecordComments.js → hit/HitComments.js} +28 -29
- package/components/elements/{ObjectDetails.js → hit/HitDetails.js} +17 -17
- package/components/elements/hit/HitLabels.js +2 -2
- package/components/elements/hit/{HitPreview.d.ts → HitQuickSearch.d.ts} +3 -3
- package/components/elements/hit/{HitPreview.js → HitQuickSearch.js} +4 -10
- package/components/elements/hit/HitRelated.d.ts +6 -0
- package/components/elements/hit/HitRelated.js +7 -0
- package/components/elements/hit/HitSummary.d.ts +1 -2
- package/components/elements/hit/HitSummary.js +5 -6
- package/components/elements/{record/RecordWorklog.d.ts → hit/HitWorklog.d.ts} +3 -4
- package/components/elements/{record/RecordWorklog.js → hit/HitWorklog.js} +13 -15
- package/components/elements/hit/aggregate/HitGraph.js +8 -8
- package/components/elements/hit/outlines/DefaultOutline.js +1 -1
- package/components/elements/view/ViewTitle.js +1 -1
- package/components/hooks/useHitActions.d.ts +1 -1
- package/components/hooks/useHitActions.js +4 -4
- package/components/hooks/{useRecordSelection.d.ts → useHitSelection.d.ts} +2 -2
- package/components/hooks/{useRecordSelection.js → useHitSelection.js} +33 -12
- package/components/hooks/useMyPreferences.js +1 -10
- package/components/hooks/useMySearch.js +2 -2
- package/components/hooks/useMySitemap.js +1 -4
- package/components/hooks/useMyTheme.js +2 -9
- package/components/hooks/useParamState.test.js +4 -3
- package/components/routes/action/edit/ActionEditor.js +2 -2
- package/components/routes/action/view/ActionSearch.js +1 -1
- package/components/routes/advanced/QueryBuilder.js +1 -1
- package/components/routes/advanced/QueryEditor.js +3 -3
- package/components/routes/advanced/historyCompletionProvider.js +3 -3
- package/components/routes/analytics/AnalyticDetails.js +2 -2
- package/components/routes/analytics/AnalyticSearch.js +1 -1
- package/components/routes/dossiers/DossierEditor.js +2 -2
- package/components/routes/dossiers/DossierEditor.test.js +1 -1
- package/components/routes/help/ApiDocumentation.js +1 -1
- package/components/routes/help/BundleDocumentation.d.ts +3 -0
- package/components/routes/help/BundleDocumentation.js +12 -0
- package/components/routes/help/HitBannerDocumentation.js +0 -1
- package/components/routes/help/HitDocumentation.js +3 -1
- package/components/routes/help/markdown/en/bundles.md.js +1 -0
- package/components/routes/help/markdown/fr/bundles.md.js +1 -0
- package/components/routes/hits/search/BundleParentMenu.d.ts +6 -0
- package/components/routes/hits/search/BundleParentMenu.js +32 -0
- package/components/routes/hits/search/BundleScroller.d.ts +2 -0
- package/components/routes/hits/search/BundleScroller.js +6 -0
- package/components/routes/hits/search/{RecordBrowser.js → HitBrowser.js} +9 -9
- package/components/{elements/record/RecordContextMenu.d.ts → routes/hits/search/HitContextMenu.d.ts} +3 -3
- package/components/routes/hits/search/HitContextMenu.js +227 -0
- package/components/{elements/record/RecordContextMenu.test.js → routes/hits/search/HitContextMenu.test.js} +39 -39
- package/components/routes/hits/search/{RecordQuery.d.ts → HitQuery.d.ts} +2 -2
- package/components/routes/hits/search/{RecordQuery.js → HitQuery.js} +6 -6
- package/components/routes/hits/search/InformationPane.d.ts +0 -1
- package/components/routes/hits/search/InformationPane.js +60 -47
- package/components/routes/hits/search/LayoutSettings.js +3 -3
- package/components/routes/hits/search/QuerySettings.js +1 -2
- package/components/routes/hits/search/QuerySettings.test.js +9 -14
- package/components/routes/hits/search/SearchPane.js +49 -26
- package/components/routes/hits/search/ViewLink.js +3 -3
- package/components/routes/hits/search/ViewLink.test.js +8 -8
- package/components/routes/hits/search/grid/AddColumnModal.js +4 -5
- package/components/routes/hits/search/grid/EnhancedCell.d.ts +1 -2
- package/components/routes/hits/search/grid/EnhancedCell.js +2 -2
- package/components/routes/hits/search/grid/HitGrid.js +18 -20
- package/components/routes/hits/search/grid/{RecordRow.d.ts → HitRow.d.ts} +2 -3
- package/components/routes/hits/search/grid/{RecordRow.js → HitRow.js} +8 -10
- package/components/routes/hits/view/HitViewer.js +13 -12
- package/components/routes/home/ViewCard.js +4 -4
- package/components/{elements/MarkdownEditor.js → routes/overviews/OverviewEditor.js} +3 -3
- package/components/routes/overviews/OverviewViewer.js +2 -2
- package/components/routes/views/ViewComposer.js +4 -4
- package/locales/en/translation.json +3 -65
- package/locales/fr/translation.json +3 -63
- package/models/WithMetadata.d.ts +1 -2
- package/models/entities/generated/{ThreatEnrichment.d.ts → Enrichment.d.ts} +1 -1
- package/models/entities/generated/Hit.d.ts +0 -1
- package/models/entities/generated/Howler.d.ts +4 -0
- package/models/entities/generated/Rule.d.ts +10 -2
- package/models/entities/generated/Threat.d.ts +2 -2
- package/package.json +106 -123
- package/plugins/clue/components/ClueTypography.js +2 -2
- package/plugins/clue/utils.d.ts +1 -2
- package/tests/utils.d.ts +0 -2
- package/tests/utils.js +0 -8
- package/utils/constants.d.ts +3 -3
- package/utils/hitFunctions.d.ts +1 -2
- package/utils/hitFunctions.js +4 -4
- package/api/search/case.d.ts +0 -4
- package/api/search/case.js +0 -8
- package/api/v2/case/index.d.ts +0 -6
- package/api/v2/case/index.js +0 -18
- package/api/v2/index.d.ts +0 -4
- package/api/v2/index.js +0 -6
- package/api/v2/search/facet.d.ts +0 -3
- package/api/v2/search/facet.js +0 -12
- package/api/v2/search/index.d.ts +0 -5
- package/api/v2/search/index.js +0 -24
- package/components/app/providers/RecordProvider.d.ts +0 -23
- package/components/elements/ContextMenu.d.ts +0 -56
- package/components/elements/ContextMenu.js +0 -109
- package/components/elements/ContextMenu.test.js +0 -215
- package/components/elements/ObjectDetails.d.ts +0 -6
- package/components/elements/case/CaseCard.d.ts +0 -8
- package/components/elements/case/CaseCard.js +0 -39
- package/components/elements/case/CasePreview.d.ts +0 -6
- package/components/elements/case/CasePreview.js +0 -17
- package/components/elements/case/StatusIcon.d.ts +0 -5
- package/components/elements/case/StatusIcon.js +0 -13
- package/components/elements/hit/elements/AnalyticLink.d.ts +0 -8
- package/components/elements/hit/elements/AnalyticLink.js +0 -22
- package/components/elements/hit/related/RelatedRecords.js +0 -63
- package/components/elements/observable/ObservableCard.d.ts +0 -6
- package/components/elements/observable/ObservableCard.js +0 -23
- package/components/elements/observable/ObservablePreview.d.ts +0 -6
- package/components/elements/observable/ObservablePreview.js +0 -12
- package/components/elements/record/RecordContextMenu.js +0 -235
- package/components/elements/record/RecordContextMenu.test.d.ts +0 -1
- package/components/elements/record/RecordRelated.d.ts +0 -7
- package/components/elements/record/RecordRelated.js +0 -34
- package/components/hooks/useRelatedRecords.d.ts +0 -13
- package/components/hooks/useRelatedRecords.js +0 -32
- package/components/routes/cases/CaseViewer.d.ts +0 -2
- package/components/routes/cases/CaseViewer.js +0 -22
- package/components/routes/cases/Cases.d.ts +0 -2
- package/components/routes/cases/Cases.js +0 -101
- package/components/routes/cases/constants.d.ts +0 -5
- package/components/routes/cases/constants.js +0 -5
- package/components/routes/cases/detail/AlertPanel.d.ts +0 -6
- package/components/routes/cases/detail/AlertPanel.js +0 -33
- package/components/routes/cases/detail/CaseAssets.d.ts +0 -12
- package/components/routes/cases/detail/CaseAssets.js +0 -101
- package/components/routes/cases/detail/CaseAssets.test.d.ts +0 -1
- package/components/routes/cases/detail/CaseAssets.test.js +0 -163
- package/components/routes/cases/detail/CaseDashboard.d.ts +0 -7
- package/components/routes/cases/detail/CaseDashboard.js +0 -51
- package/components/routes/cases/detail/CaseDetails.d.ts +0 -6
- package/components/routes/cases/detail/CaseDetails.js +0 -61
- package/components/routes/cases/detail/CaseOverview.d.ts +0 -7
- package/components/routes/cases/detail/CaseOverview.js +0 -43
- package/components/routes/cases/detail/CaseSidebar.d.ts +0 -6
- package/components/routes/cases/detail/CaseSidebar.js +0 -61
- package/components/routes/cases/detail/CaseTask.d.ts +0 -11
- package/components/routes/cases/detail/CaseTask.js +0 -57
- package/components/routes/cases/detail/ItemPage.d.ts +0 -6
- package/components/routes/cases/detail/ItemPage.js +0 -99
- package/components/routes/cases/detail/RelatedCasePanel.d.ts +0 -6
- package/components/routes/cases/detail/RelatedCasePanel.js +0 -31
- package/components/routes/cases/detail/TaskPanel.d.ts +0 -7
- package/components/routes/cases/detail/TaskPanel.js +0 -52
- package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +0 -12
- package/components/routes/cases/detail/aggregates/CaseAggregate.js +0 -19
- package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +0 -6
- package/components/routes/cases/detail/aggregates/SourceAggregate.js +0 -27
- package/components/routes/cases/detail/assets/Asset.d.ts +0 -14
- package/components/routes/cases/detail/assets/Asset.js +0 -12
- package/components/routes/cases/detail/assets/Asset.test.d.ts +0 -1
- package/components/routes/cases/detail/assets/Asset.test.js +0 -72
- package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +0 -13
- package/components/routes/cases/detail/sidebar/CaseFolder.js +0 -131
- package/components/routes/cases/detail/sidebar/types.d.ts +0 -3
- package/components/routes/cases/detail/sidebar/utils.d.ts +0 -3
- package/components/routes/cases/detail/sidebar/utils.js +0 -25
- package/components/routes/cases/hooks/useCase.d.ts +0 -13
- package/components/routes/cases/hooks/useCase.js +0 -38
- package/components/routes/cases/modals/ResolveModal.d.ts +0 -7
- package/components/routes/cases/modals/ResolveModal.js +0 -59
- package/components/routes/hits/search/shared/IndexPicker.d.ts +0 -2
- package/components/routes/hits/search/shared/IndexPicker.js +0 -20
- package/components/routes/observables/ObservableViewer.d.ts +0 -7
- package/components/routes/observables/ObservableViewer.js +0 -27
- package/models/entities/generated/AttachmentsFile.d.ts +0 -12
- package/models/entities/generated/Case.d.ts +0 -28
- package/models/entities/generated/DestinationOriginal.d.ts +0 -19
- package/models/entities/generated/EmailAttachment.d.ts +0 -8
- package/models/entities/generated/EmailParent.d.ts +0 -19
- package/models/entities/generated/Enrichments.d.ts +0 -7
- package/models/entities/generated/EnrichmentsIndicator.d.ts +0 -21
- package/models/entities/generated/HttpResponse.d.ts +0 -11
- package/models/entities/generated/Item.d.ts +0 -9
- package/models/entities/generated/Observable.d.ts +0 -85
- package/models/entities/generated/ObservableCloud.d.ts +0 -20
- package/models/entities/generated/ObservableDestination.d.ts +0 -23
- package/models/entities/generated/ObservableEmail.d.ts +0 -30
- package/models/entities/generated/ObservableFile.d.ts +0 -36
- package/models/entities/generated/ObservableHowler.d.ts +0 -43
- package/models/entities/generated/ObservableHttp.d.ts +0 -11
- package/models/entities/generated/ObservableObserver.d.ts +0 -21
- package/models/entities/generated/ObservableOrganization.d.ts +0 -7
- package/models/entities/generated/ObservableProcess.d.ts +0 -34
- package/models/entities/generated/ObservableSource.d.ts +0 -23
- package/models/entities/generated/ObservableThreat.d.ts +0 -21
- package/models/entities/generated/ObservableTls.d.ts +0 -12
- package/models/entities/generated/ObserverIngress.d.ts +0 -9
- package/models/entities/generated/Task.d.ts +0 -10
- package/utils/typeUtils.d.ts +0 -7
- package/utils/typeUtils.js +0 -27
- /package/components/app/providers/{RecordSearchProvider.test.d.ts → HitSearchProvider.test.d.ts} +0 -0
- /package/components/elements/hit/{related/RelatedRecords.d.ts → HitDetails.d.ts} +0 -0
- /package/components/routes/hits/search/{RecordBrowser.d.ts → HitBrowser.d.ts} +0 -0
- /package/components/{elements/ContextMenu.test.d.ts → routes/hits/search/HitContextMenu.test.d.ts} +0 -0
- /package/components/{elements/MarkdownEditor.d.ts → routes/overviews/OverviewEditor.d.ts} +0 -0
|
@@ -91,13 +91,13 @@ vi.mock('@mui/material', async () => {
|
|
|
91
91
|
});
|
|
92
92
|
// Import component after mocks
|
|
93
93
|
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
94
|
+
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
94
95
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
95
|
-
import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
|
|
96
96
|
import i18n from '@cccsaurora/howler-ui/i18n';
|
|
97
97
|
import { I18nextProvider } from 'react-i18next';
|
|
98
98
|
import { createMockAction, createMockAnalytic, createMockHit, createMockTemplate } from '@cccsaurora/howler-ui/tests/utils';
|
|
99
99
|
import { DEFAULT_QUERY } from '@cccsaurora/howler-ui/utils/constants';
|
|
100
|
-
import
|
|
100
|
+
import HitContextMenu from './HitContextMenu';
|
|
101
101
|
const mockGetSelectedId = vi.fn(() => 'test-hit-1');
|
|
102
102
|
const mockConfig = {
|
|
103
103
|
lookups: {
|
|
@@ -105,16 +105,16 @@ const mockConfig = {
|
|
|
105
105
|
}
|
|
106
106
|
};
|
|
107
107
|
const mockApiContext = { config: mockConfig };
|
|
108
|
-
const
|
|
109
|
-
|
|
108
|
+
const mockHitContext = {
|
|
109
|
+
hits: {
|
|
110
110
|
'test-hit-1': createMockHit()
|
|
111
111
|
},
|
|
112
|
-
|
|
112
|
+
selectedHits: []
|
|
113
113
|
};
|
|
114
114
|
const mockParameterContext = { query: DEFAULT_QUERY, setQuery: vi.fn() };
|
|
115
115
|
// Test wrapper
|
|
116
116
|
const Wrapper = ({ children }) => {
|
|
117
|
-
return (_jsx(I18nextProvider, { i18n: i18n, children: _jsx(ApiConfigContext.Provider, { value: mockApiContext, children: _jsx(
|
|
117
|
+
return (_jsx(I18nextProvider, { i18n: i18n, children: _jsx(ApiConfigContext.Provider, { value: mockApiContext, children: _jsx(HitContext.Provider, { value: mockHitContext, children: _jsx(ParameterContext.Provider, { value: mockParameterContext, children: children }) }) }) }));
|
|
118
118
|
};
|
|
119
119
|
describe('HitContextMenu', () => {
|
|
120
120
|
let user;
|
|
@@ -122,11 +122,11 @@ describe('HitContextMenu', () => {
|
|
|
122
122
|
beforeEach(() => {
|
|
123
123
|
user = userEvent.setup();
|
|
124
124
|
vi.clearAllMocks();
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
mockHitContext.selectedHits.length = 0;
|
|
126
|
+
mockHitContext.hits['test-hit-1'] = createMockHit();
|
|
127
127
|
mockGetMatchingAnalytic.mockResolvedValue(createMockAnalytic());
|
|
128
128
|
mockGetMatchingTemplate.mockResolvedValue(createMockTemplate());
|
|
129
|
-
rerender = render(_jsx(Wrapper, { children: _jsx(
|
|
129
|
+
rerender = render(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) })).rerender;
|
|
130
130
|
});
|
|
131
131
|
describe('Context Menu Initialization', () => {
|
|
132
132
|
it('should open menu on right-click', async () => {
|
|
@@ -190,13 +190,13 @@ describe('HitContextMenu', () => {
|
|
|
190
190
|
});
|
|
191
191
|
it('should disable "Open Hit" when hit is null', async () => {
|
|
192
192
|
act(() => {
|
|
193
|
-
|
|
193
|
+
mockHitContext.hits['test-hit-1'] = null;
|
|
194
194
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
195
195
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
196
196
|
});
|
|
197
197
|
await waitFor(() => {
|
|
198
198
|
const menuItems = screen.getAllByRole('menuitem');
|
|
199
|
-
const openHitItem = menuItems.find(item => item.textContent?.toLowerCase().includes('open hit'));
|
|
199
|
+
const openHitItem = menuItems.find(item => item.textContent?.toLowerCase().includes('open hit viewer'));
|
|
200
200
|
expect(openHitItem).toHaveAttribute('aria-disabled', 'true');
|
|
201
201
|
});
|
|
202
202
|
});
|
|
@@ -237,7 +237,7 @@ describe('HitContextMenu', () => {
|
|
|
237
237
|
skip_rationale: false
|
|
238
238
|
}
|
|
239
239
|
}));
|
|
240
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
240
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
241
241
|
act(() => {
|
|
242
242
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
243
243
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
@@ -295,7 +295,7 @@ describe('HitContextMenu', () => {
|
|
|
295
295
|
createMockAction({ action_id: 'action-2', name: 'Custom Action 2' })
|
|
296
296
|
];
|
|
297
297
|
mockDispatchApi.mockResolvedValue({ items: mockActions });
|
|
298
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
298
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
299
299
|
act(() => {
|
|
300
300
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
301
301
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
@@ -339,7 +339,7 @@ describe('HitContextMenu', () => {
|
|
|
339
339
|
});
|
|
340
340
|
it('should disable custom actions menu when no actions are available', async () => {
|
|
341
341
|
mockDispatchApi.mockResolvedValueOnce({ items: [] });
|
|
342
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
342
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
343
343
|
act(() => {
|
|
344
344
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
345
345
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
@@ -381,7 +381,7 @@ describe('HitContextMenu', () => {
|
|
|
381
381
|
skip_rationale: true
|
|
382
382
|
}
|
|
383
383
|
}));
|
|
384
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
384
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
385
385
|
act(() => {
|
|
386
386
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
387
387
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
@@ -449,7 +449,7 @@ describe('HitContextMenu', () => {
|
|
|
449
449
|
it('should call executeAction with action_id and hit query', async () => {
|
|
450
450
|
const mockActions = [createMockAction({ action_id: 'action-1', name: 'Custom Action' })];
|
|
451
451
|
mockDispatchApi.mockResolvedValue({ items: mockActions });
|
|
452
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
452
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
453
453
|
act(() => {
|
|
454
454
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
455
455
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
@@ -502,7 +502,7 @@ describe('HitContextMenu', () => {
|
|
|
502
502
|
mockGetMatchingTemplate.mockResolvedValue(createMockTemplate({
|
|
503
503
|
keys: ['howler.detection', 'event.id']
|
|
504
504
|
}));
|
|
505
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
505
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
506
506
|
});
|
|
507
507
|
it('should render exclusion submenu with template keys', async () => {
|
|
508
508
|
act(() => {
|
|
@@ -550,7 +550,7 @@ describe('HitContextMenu', () => {
|
|
|
550
550
|
mockGetMatchingTemplate.mockResolvedValue(createMockTemplate({
|
|
551
551
|
keys: ['howler.outline.indicators']
|
|
552
552
|
}));
|
|
553
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
553
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
554
554
|
act(() => {
|
|
555
555
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
556
556
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
@@ -593,7 +593,7 @@ describe('HitContextMenu', () => {
|
|
|
593
593
|
mockGetMatchingTemplate.mockResolvedValue(createMockTemplate({
|
|
594
594
|
keys: []
|
|
595
595
|
}));
|
|
596
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
596
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
597
597
|
act(() => {
|
|
598
598
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
599
599
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
@@ -605,7 +605,7 @@ describe('HitContextMenu', () => {
|
|
|
605
605
|
});
|
|
606
606
|
it('should skip null field values in exclusion menu', async () => {
|
|
607
607
|
act(() => {
|
|
608
|
-
|
|
608
|
+
mockHitContext.hits['test-hit-1'].event = {};
|
|
609
609
|
});
|
|
610
610
|
act(() => {
|
|
611
611
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
@@ -628,7 +628,7 @@ describe('HitContextMenu', () => {
|
|
|
628
628
|
mockGetMatchingTemplate.mockResolvedValue(createMockTemplate({
|
|
629
629
|
keys: ['howler.detection', 'event.id']
|
|
630
630
|
}));
|
|
631
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
631
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
632
632
|
});
|
|
633
633
|
it('should render inclusion submenu with template keys', async () => {
|
|
634
634
|
act(() => {
|
|
@@ -676,7 +676,7 @@ describe('HitContextMenu', () => {
|
|
|
676
676
|
mockGetMatchingTemplate.mockResolvedValue(createMockTemplate({
|
|
677
677
|
keys: ['howler.outline.indicators']
|
|
678
678
|
}));
|
|
679
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
679
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
680
680
|
act(() => {
|
|
681
681
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
682
682
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
@@ -719,7 +719,7 @@ describe('HitContextMenu', () => {
|
|
|
719
719
|
mockGetMatchingTemplate.mockResolvedValue(createMockTemplate({
|
|
720
720
|
keys: []
|
|
721
721
|
}));
|
|
722
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
722
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
723
723
|
act(() => {
|
|
724
724
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
725
725
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
@@ -731,7 +731,7 @@ describe('HitContextMenu', () => {
|
|
|
731
731
|
});
|
|
732
732
|
it('should skip null field values in inclusion menu', async () => {
|
|
733
733
|
act(() => {
|
|
734
|
-
|
|
734
|
+
mockHitContext.hits['test-hit-1'].event = {};
|
|
735
735
|
});
|
|
736
736
|
act(() => {
|
|
737
737
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
@@ -750,24 +750,24 @@ describe('HitContextMenu', () => {
|
|
|
750
750
|
});
|
|
751
751
|
});
|
|
752
752
|
describe('Multiple Hit Selection', () => {
|
|
753
|
-
it('should use
|
|
753
|
+
it('should use selectedHits when current hit is included', async () => {
|
|
754
754
|
act(() => {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
755
|
+
mockHitContext.hits['hit-1'] = createMockHit({ howler: { id: 'hit-1' } });
|
|
756
|
+
mockHitContext.hits['hit-2'] = createMockHit({ howler: { id: 'hit-2' } });
|
|
757
|
+
mockHitContext.selectedHits.push(mockHitContext.hits['hit-1'], mockHitContext.hits['hit-2']);
|
|
758
758
|
mockGetSelectedId.mockReturnValue('hit-1');
|
|
759
759
|
});
|
|
760
760
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
761
761
|
await user.pointer({ keys: '[MouseRight]', target: contextMenuWrapper });
|
|
762
|
-
// The component should use
|
|
762
|
+
// The component should use selectedHits for actions
|
|
763
763
|
// We can verify this indirectly through the useHitActions hook receiving the right data
|
|
764
764
|
expect(screen.getByRole('menu')).toBeInTheDocument();
|
|
765
765
|
expect(mockGetSelectedId).toHaveBeenCalled();
|
|
766
766
|
});
|
|
767
|
-
it('should use only current hit when not in
|
|
767
|
+
it('should use only current hit when not in selectedHits', async () => {
|
|
768
768
|
act(() => {
|
|
769
|
-
|
|
770
|
-
|
|
769
|
+
mockHitContext.hits['hit-1'] = createMockHit({ howler: { id: 'hit-1' } });
|
|
770
|
+
mockHitContext.selectedHits.push(mockHitContext.hits['hit-1']);
|
|
771
771
|
mockGetSelectedId.mockReturnValue('test-hit-1');
|
|
772
772
|
});
|
|
773
773
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
@@ -786,12 +786,12 @@ describe('HitContextMenu', () => {
|
|
|
786
786
|
});
|
|
787
787
|
it('should call getMatchingAnalytic when hit has analytic', async () => {
|
|
788
788
|
await waitFor(() => {
|
|
789
|
-
expect(mockGetMatchingAnalytic).toHaveBeenCalledWith(
|
|
789
|
+
expect(mockGetMatchingAnalytic).toHaveBeenCalledWith(mockHitContext.hits['test-hit-1']);
|
|
790
790
|
});
|
|
791
791
|
});
|
|
792
792
|
it('should call getMatchingTemplate when menu opens', async () => {
|
|
793
793
|
await waitFor(() => {
|
|
794
|
-
expect(mockGetMatchingTemplate).toHaveBeenCalledWith(
|
|
794
|
+
expect(mockGetMatchingTemplate).toHaveBeenCalledWith(mockHitContext.hits['test-hit-1']);
|
|
795
795
|
});
|
|
796
796
|
});
|
|
797
797
|
it('should reset state when menu closes', async () => {
|
|
@@ -824,7 +824,7 @@ describe('HitContextMenu', () => {
|
|
|
824
824
|
describe('Edge Cases and Error Handling', () => {
|
|
825
825
|
it('should not crash when hit is null', async () => {
|
|
826
826
|
act(() => {
|
|
827
|
-
|
|
827
|
+
mockHitContext.hits = {};
|
|
828
828
|
});
|
|
829
829
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
830
830
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
@@ -835,7 +835,7 @@ describe('HitContextMenu', () => {
|
|
|
835
835
|
});
|
|
836
836
|
it('should not render exclusion menu when template is null', async () => {
|
|
837
837
|
mockGetMatchingTemplate.mockResolvedValue(null);
|
|
838
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
838
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
839
839
|
act(() => {
|
|
840
840
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
841
841
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
@@ -849,7 +849,7 @@ describe('HitContextMenu', () => {
|
|
|
849
849
|
});
|
|
850
850
|
it('should not render inclusion menu when template is null', async () => {
|
|
851
851
|
mockGetMatchingTemplate.mockResolvedValue(null);
|
|
852
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
852
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
853
853
|
act(() => {
|
|
854
854
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
855
855
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
@@ -863,7 +863,7 @@ describe('HitContextMenu', () => {
|
|
|
863
863
|
});
|
|
864
864
|
it('should handle API failure gracefully', async () => {
|
|
865
865
|
mockDispatchApi.mockResolvedValue(null);
|
|
866
|
-
rerender(_jsx(Wrapper, { children: _jsx(
|
|
866
|
+
rerender(_jsx(Wrapper, { children: _jsx(HitContextMenu, { getSelectedId: mockGetSelectedId, children: _jsx("div", { children: "Test Content" }) }) }));
|
|
867
867
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
868
868
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
869
869
|
await waitFor(() => {
|
|
@@ -874,7 +874,7 @@ describe('HitContextMenu', () => {
|
|
|
874
874
|
});
|
|
875
875
|
it('should not call getMatchingAnalytic or getMatchingTemplate when hit has no analytic', async () => {
|
|
876
876
|
act(() => {
|
|
877
|
-
|
|
877
|
+
mockHitContext.hits['test-hit-1'].howler.analytic = null;
|
|
878
878
|
});
|
|
879
879
|
const contextMenuWrapper = screen.getByText('Test Content').parentElement;
|
|
880
880
|
fireEvent.contextMenu(contextMenuWrapper);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type HitQueryProps = {
|
|
2
2
|
triggerSearch: (query: string) => void;
|
|
3
3
|
onChange?: (query: string, isDirty: boolean) => void;
|
|
4
4
|
searching?: boolean;
|
|
5
5
|
disabled?: boolean;
|
|
6
6
|
compact?: boolean;
|
|
7
7
|
};
|
|
8
|
-
declare const _default: import("react").NamedExoticComponent<
|
|
8
|
+
declare const _default: import("react").NamedExoticComponent<HitQueryProps>;
|
|
9
9
|
export default _default;
|
|
@@ -5,7 +5,7 @@ import { Badge, Box, Card, Skeleton, Tooltip, alpha, useTheme } from '@mui/mater
|
|
|
5
5
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
6
6
|
import TuiIconButton from '@cccsaurora/howler-ui/components/elements/addons/buttons/CustomIconButton';
|
|
7
7
|
import QueryEditor from '@cccsaurora/howler-ui/components/routes/advanced/QueryEditor';
|
|
8
|
-
import {
|
|
8
|
+
import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
9
9
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
10
10
|
import { useTranslation } from 'react-i18next';
|
|
11
11
|
import { useLocation } from 'react-router-dom';
|
|
@@ -13,8 +13,8 @@ import { useContextSelector } from 'use-context-selector';
|
|
|
13
13
|
import { DEFAULT_QUERY } from '@cccsaurora/howler-ui/utils/constants';
|
|
14
14
|
import { sanitizeMultilineLucene } from '@cccsaurora/howler-ui/utils/stringUtils';
|
|
15
15
|
const DEFAULT_MULTILINE_HEIGHT = 250;
|
|
16
|
-
const PROMPT_CONTEXT = '
|
|
17
|
-
const
|
|
16
|
+
const PROMPT_CONTEXT = 'isHitQuery && !suggestWidgetVisible && !renameInputVisible && !inSnippetMode && !quickFixWidgetVisible';
|
|
17
|
+
const HitQuery = ({ searching = false, disabled = false, compact = false, triggerSearch, onChange }) => {
|
|
18
18
|
const { t } = useTranslation();
|
|
19
19
|
const location = useLocation();
|
|
20
20
|
const theme = useTheme();
|
|
@@ -22,7 +22,7 @@ const RecordQuery = ({ searching = false, disabled = false, compact = false, tri
|
|
|
22
22
|
const savedQuery = useContextSelector(ParameterContext, ctx => ctx.query || DEFAULT_QUERY);
|
|
23
23
|
const prevQuery = useRef(null);
|
|
24
24
|
const [query, setQuery] = useState(new URLSearchParams(window.location.search).get('query') || DEFAULT_QUERY);
|
|
25
|
-
const fzfSearch = useContextSelector(
|
|
25
|
+
const fzfSearch = useContextSelector(HitSearchContext, ctx => ctx?.fzfSearch ?? false);
|
|
26
26
|
const [loaded, setLoaded] = useState(false);
|
|
27
27
|
const [multiline, setMultiline] = useState(false);
|
|
28
28
|
const [y, setY] = useState(0);
|
|
@@ -90,7 +90,7 @@ const RecordQuery = ({ searching = false, disabled = false, compact = false, tri
|
|
|
90
90
|
window.addEventListener('mouseup', onMouseUp);
|
|
91
91
|
}, [onMouseMove, onMouseUp]);
|
|
92
92
|
const onMount = useCallback((ed) => {
|
|
93
|
-
ed.createContextKey('
|
|
93
|
+
ed.createContextKey('isHitQuery', true);
|
|
94
94
|
setLoaded(true);
|
|
95
95
|
}, []);
|
|
96
96
|
const options = useMemo(() => ({
|
|
@@ -148,4 +148,4 @@ const RecordQuery = ({ searching = false, disabled = false, compact = false, tri
|
|
|
148
148
|
zIndex: 1000
|
|
149
149
|
}, onMouseDown: onMouseDown }))] }));
|
|
150
150
|
};
|
|
151
|
-
export default memo(
|
|
151
|
+
export default memo(HitQuery);
|
|
@@ -4,28 +4,29 @@ import { Badge, Box, Divider, IconButton, Skeleton, Stack, Tab, Tabs, Tooltip, u
|
|
|
4
4
|
import TuiIconButton from '@cccsaurora/howler-ui/components/elements/addons/buttons/CustomIconButton';
|
|
5
5
|
import { Icon } from '@iconify/react';
|
|
6
6
|
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
7
|
+
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
7
8
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
8
|
-
import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
|
|
9
9
|
import { SocketContext } from '@cccsaurora/howler-ui/components/app/providers/SocketProvider';
|
|
10
10
|
import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
|
|
11
11
|
import VSBox from '@cccsaurora/howler-ui/components/elements/addons/layout/vsbox/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';
|
|
15
16
|
import SocketBadge from '@cccsaurora/howler-ui/components/elements/display/icons/SocketBadge';
|
|
16
17
|
import JSONViewer from '@cccsaurora/howler-ui/components/elements/display/json/JSONViewer';
|
|
17
18
|
import HitActions from '@cccsaurora/howler-ui/components/elements/hit/HitActions';
|
|
18
19
|
import HitBanner from '@cccsaurora/howler-ui/components/elements/hit/HitBanner';
|
|
20
|
+
import HitComments from '@cccsaurora/howler-ui/components/elements/hit/HitComments';
|
|
21
|
+
import HitDetails from '@cccsaurora/howler-ui/components/elements/hit/HitDetails';
|
|
19
22
|
import HitLabels from '@cccsaurora/howler-ui/components/elements/hit/HitLabels';
|
|
20
23
|
import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
|
|
21
24
|
import HitLinks from '@cccsaurora/howler-ui/components/elements/hit/HitLinks';
|
|
22
25
|
import HitOutline from '@cccsaurora/howler-ui/components/elements/hit/HitOutline';
|
|
23
26
|
import HitOverview from '@cccsaurora/howler-ui/components/elements/hit/HitOverview';
|
|
27
|
+
import HitRelated from '@cccsaurora/howler-ui/components/elements/hit/HitRelated';
|
|
24
28
|
import HitSummary from '@cccsaurora/howler-ui/components/elements/hit/HitSummary';
|
|
25
|
-
import
|
|
26
|
-
import RecordComments from '@cccsaurora/howler-ui/components/elements/record/RecordComments';
|
|
27
|
-
import RecordRelated from '@cccsaurora/howler-ui/components/elements/record/RecordRelated';
|
|
28
|
-
import RecordWorklog from '@cccsaurora/howler-ui/components/elements/record/RecordWorklog';
|
|
29
|
+
import HitWorklog from '@cccsaurora/howler-ui/components/elements/hit/HitWorklog';
|
|
29
30
|
import useMyUserList from '@cccsaurora/howler-ui/components/hooks/useMyUserList';
|
|
30
31
|
import ErrorBoundary from '@cccsaurora/howler-ui/components/routes/ErrorBoundary';
|
|
31
32
|
import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
|
|
@@ -36,18 +37,17 @@ import { useLocation } from 'react-router-dom';
|
|
|
36
37
|
import { useContextSelector } from 'use-context-selector';
|
|
37
38
|
import { getUserList } from '@cccsaurora/howler-ui/utils/hitFunctions';
|
|
38
39
|
import { validateRegex } from '@cccsaurora/howler-ui/utils/stringUtils';
|
|
39
|
-
import { isHit } from '@cccsaurora/howler-ui/utils/typeUtils';
|
|
40
40
|
import { tryParse } from '@cccsaurora/howler-ui/utils/utils';
|
|
41
41
|
import LeadRenderer from '../view/LeadRenderer';
|
|
42
|
-
const InformationPane = ({ onClose
|
|
42
|
+
const InformationPane = ({ onClose }) => {
|
|
43
43
|
const { t, i18n } = useTranslation();
|
|
44
44
|
const theme = useTheme();
|
|
45
45
|
const location = useLocation();
|
|
46
46
|
const { emit, isOpen } = useContext(SocketContext);
|
|
47
47
|
const { getMatchingOverview, getMatchingDossiers, getMatchingAnalytic } = useMatchers();
|
|
48
|
-
const selected = useContextSelector(ParameterContext, ctx => ctx
|
|
48
|
+
const selected = useContextSelector(ParameterContext, ctx => ctx.selected);
|
|
49
49
|
const pluginStore = usePluginStore();
|
|
50
|
-
const
|
|
50
|
+
const getHit = useContextSelector(HitContext, ctx => ctx.getHit);
|
|
51
51
|
const [userIds, setUserIds] = useState(new Set());
|
|
52
52
|
const [analytic, setAnalytic] = useState();
|
|
53
53
|
const [hasOverview, setHasOverview] = useState(false);
|
|
@@ -58,7 +58,7 @@ const InformationPane = ({ onClose, selected: _selected }) => {
|
|
|
58
58
|
const [_dossiers, setDossiers] = useState(null);
|
|
59
59
|
const dossiers = useMemo(() => _dossiers ?? [], [_dossiers]);
|
|
60
60
|
const users = useMyUserList(userIds);
|
|
61
|
-
const
|
|
61
|
+
const hit = useContextSelector(HitContext, ctx => ctx.hits[selected]);
|
|
62
62
|
howlerPluginStore.plugins.forEach(plugin => {
|
|
63
63
|
pluginStore.executeFunction(`${plugin}.on`, 'viewing');
|
|
64
64
|
});
|
|
@@ -66,14 +66,14 @@ const InformationPane = ({ onClose, selected: _selected }) => {
|
|
|
66
66
|
if (!selected) {
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
|
-
if (!
|
|
69
|
+
if (!hit?.howler.data) {
|
|
70
70
|
setLoading(true);
|
|
71
|
-
|
|
71
|
+
getHit(selected, true).finally(() => setLoading(false));
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
|
-
setUserIds(getUserList(
|
|
74
|
+
setUserIds(getUserList(hit));
|
|
75
75
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
76
|
-
}, [
|
|
76
|
+
}, [getHit, selected]);
|
|
77
77
|
useEffect(() => {
|
|
78
78
|
if (selected) {
|
|
79
79
|
setAnalytic(null);
|
|
@@ -82,20 +82,23 @@ const InformationPane = ({ onClose, selected: _selected }) => {
|
|
|
82
82
|
}
|
|
83
83
|
}, [selected]);
|
|
84
84
|
useEffect(() => {
|
|
85
|
-
if (
|
|
86
|
-
getMatchingAnalytic(
|
|
85
|
+
if (hit && !analytic) {
|
|
86
|
+
getMatchingAnalytic(hit).then(setAnalytic);
|
|
87
87
|
}
|
|
88
|
-
}, [analytic, getMatchingAnalytic,
|
|
88
|
+
}, [analytic, getMatchingAnalytic, hit]);
|
|
89
89
|
useEffect(() => {
|
|
90
|
-
if (
|
|
91
|
-
getMatchingDossiers(
|
|
90
|
+
if (hit && !_dossiers) {
|
|
91
|
+
getMatchingDossiers(hit).then(setDossiers);
|
|
92
92
|
}
|
|
93
|
-
}, [_dossiers, getMatchingDossiers,
|
|
93
|
+
}, [_dossiers, getMatchingDossiers, hit]);
|
|
94
94
|
useEffect(() => {
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
getMatchingOverview(hit).then(_overview => setHasOverview(!!_overview));
|
|
96
|
+
}, [getMatchingOverview, hit]);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (tab === 'hit_aggregate' && !hit?.howler.is_bundle) {
|
|
99
|
+
setTab('overview');
|
|
97
100
|
}
|
|
98
|
-
}, [
|
|
101
|
+
}, [hit?.howler.is_bundle, tab]);
|
|
99
102
|
useEffect(() => {
|
|
100
103
|
if (selected && isOpen()) {
|
|
101
104
|
emit({
|
|
@@ -119,37 +122,48 @@ const InformationPane = ({ onClose, selected: _selected }) => {
|
|
|
119
122
|
}
|
|
120
123
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
121
124
|
}, [hasOverview]);
|
|
125
|
+
/**
|
|
126
|
+
* What to show as the header? If loading a skeleton, then it depends on bundle or not. Bundles don't
|
|
127
|
+
* show anything while normal hits do
|
|
128
|
+
*/
|
|
129
|
+
const header = useMemo(() => {
|
|
130
|
+
if (loading && !hit?.howler?.is_bundle) {
|
|
131
|
+
return _jsx(Skeleton, { variant: "rounded", height: 152 });
|
|
132
|
+
}
|
|
133
|
+
else if (!!hit && !hit.howler.is_bundle) {
|
|
134
|
+
return _jsx(HitBanner, { layout: HitLayout.DENSE, hit: hit });
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}, [hit, loading]);
|
|
122
140
|
const tabContent = useMemo(() => {
|
|
123
141
|
if (!tab) {
|
|
124
142
|
return;
|
|
125
143
|
}
|
|
126
|
-
const defaultContent = {
|
|
127
|
-
details: () => _jsx(ObjectDetails, { obj: record }),
|
|
128
|
-
comments: () => _jsx(RecordComments, { record: record, users: users }),
|
|
129
|
-
raw: () => _jsx(JSONViewer, { data: !loading && record, hideSearch: true, filter: filter }),
|
|
130
|
-
data: () => (_jsx(JSONViewer, { data: !loading && record?.howler?.data?.map(entry => tryParse(entry)), collapse: false, hideSearch: true, filter: filter })),
|
|
131
|
-
related: () => _jsx(RecordRelated, { record: record }),
|
|
132
|
-
worklog: () => _jsx(RecordWorklog, { record: !loading && record, users: users })
|
|
133
|
-
};
|
|
134
|
-
if (!isHit(record)) {
|
|
135
|
-
return defaultContent[tab]?.();
|
|
136
|
-
}
|
|
137
144
|
return {
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
overview: () => _jsx(HitOverview, { hit: hit }),
|
|
146
|
+
details: () => _jsx(HitDetails, { hit: hit }),
|
|
147
|
+
hit_comments: () => _jsx(HitComments, { hit: hit, users: users }),
|
|
148
|
+
hit_raw: () => _jsx(JSONViewer, { data: !loading && hit, hideSearch: true, filter: filter }),
|
|
149
|
+
hit_data: () => (_jsx(JSONViewer, { data: !loading && hit?.howler?.data?.map(entry => tryParse(entry)), collapse: false, hideSearch: true, filter: filter })),
|
|
150
|
+
hit_worklog: () => _jsx(HitWorklog, { hit: !loading && hit, users: users }),
|
|
140
151
|
hit_aggregate: () => _jsx(HitSummary, {}),
|
|
141
|
-
|
|
152
|
+
hit_related: () => _jsx(HitRelated, { hit: hit }),
|
|
153
|
+
...Object.fromEntries((hit?.howler.dossier ?? []).map((lead, index) => [
|
|
142
154
|
'lead:' + index,
|
|
143
|
-
() => _jsx(LeadRenderer, { lead: lead, hit:
|
|
155
|
+
() => _jsx(LeadRenderer, { lead: lead, hit: hit })
|
|
144
156
|
])),
|
|
145
157
|
...Object.fromEntries(dossiers.flatMap((_dossier, dossierIndex) => (_dossier.leads ?? []).map((_lead, leadIndex) => [
|
|
146
158
|
`external-lead:${dossierIndex}:${leadIndex}`,
|
|
147
|
-
() => _jsx(LeadRenderer, { lead: _lead, hit:
|
|
159
|
+
() => _jsx(LeadRenderer, { lead: _lead, hit: hit })
|
|
148
160
|
])))
|
|
149
161
|
}[tab]?.();
|
|
150
|
-
}, [dossiers, filter,
|
|
162
|
+
}, [dossiers, filter, hit, loading, tab, users]);
|
|
151
163
|
const hasError = useMemo(() => !validateRegex(filter), [filter]);
|
|
152
|
-
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('analytic.open'), disabled: !analytic || loading, route: `/analytics/${analytic.analytic_id}`, children: _jsx(QueryStats, {}) })), !!
|
|
164
|
+
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 &&
|
|
165
|
+
!hit.howler.is_bundle &&
|
|
166
|
+
(!loading ? (_jsxs(_Fragment, { children: [_jsx(HitOutline, { hit: hit, layout: HitLayout.DENSE, forceAllFields: true }), _jsx(HitLabels, { hit: hit })] })) : (_jsx(Skeleton, { height: 124 }))), _jsx(HitLinks, { hit: hit, analytic: analytic, dossiers: dossiers }), _jsxs(VSBoxHeader, { ml: -1, mr: -1, pb: 1, sx: { top: '0px' }, children: [_jsxs(Tabs, { value: tab === 'overview' && !hasOverview ? 'details' : tab, sx: {
|
|
153
167
|
display: 'flex',
|
|
154
168
|
flexDirection: 'row',
|
|
155
169
|
pr: 2,
|
|
@@ -171,18 +185,17 @@ const InformationPane = ({ onClose, selected: _selected }) => {
|
|
|
171
185
|
right: 0
|
|
172
186
|
}
|
|
173
187
|
}
|
|
174
|
-
}, variant: "scrollable", children: [_jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('viewer.comments'), children: _jsx(Badge, { sx: {
|
|
188
|
+
}, variant: "scrollable", children: [_jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.comments'), children: _jsx(Badge, { sx: {
|
|
175
189
|
'& > .MuiBadge-badge': {
|
|
176
190
|
backgroundColor: theme.palette.divider,
|
|
177
191
|
zIndex: 1,
|
|
178
192
|
right: theme.spacing(-0.5)
|
|
179
193
|
},
|
|
180
194
|
'& > svg': { zIndex: 2 }
|
|
181
|
-
}, badgeContent:
|
|
182
|
-
|
|
183
|
-
|
|
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
|
|
195
|
+
}, 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
|
|
196
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
197
|
+
, { 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
|
|
185
198
|
// eslint-disable-next-line react/no-array-index-key
|
|
186
|
-
, { 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: `external-lead:${dossierIndex}:${leadIndex}`, onClick: () => setTab(`external-lead:${dossierIndex}:${leadIndex}`) }, `external-lead:${dossierIndex}:${leadIndex}`)))), _jsx(FlexOne, {}), _jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.data'), children: _jsx(DataObject, {}) }), value: "
|
|
199
|
+
, { 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: `external-lead:${dossierIndex}:${leadIndex}`, onClick: () => setTab(`external-lead:${dossierIndex}:${leadIndex}`) }, `external-lead:${dossierIndex}:${leadIndex}`)))), _jsx(FlexOne, {}), _jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.data'), children: _jsx(DataObject, {}) }), value: "hit_data", onClick: () => setTab('hit_data'), disabled: !hit?.howler?.data }), _jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.json'), children: _jsx(Code, {}) }), value: "hit_raw", onClick: () => setTab('hit_raw') }), _jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.worklog'), children: _jsx(History, {}) }), value: "hit_worklog", onClick: () => setTab('hit_worklog') }), _jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.related'), children: _jsx(LinkSharp, {}) }), value: "hit_related", onClick: () => setTab('hit_related') })] }), ['hit_raw', 'hit_data'].includes(tab) && (_jsx(Phrase, { sx: { mt: 1, pr: 1 }, value: filter, onChange: setFilter, error: hasError, label: t('json.viewer.search.label'), placeholder: t('json.viewer.search.prompt'), endAdornment: _jsx(IconButton, { onClick: () => setFilter(''), children: _jsx(Clear, {}) }) }))] }), _jsx(ErrorBoundary, { children: _jsx(VSBoxContent, { mr: -1, ml: -1, height: "100%", children: _jsx(Stack, { height: "100%", flex: 1, children: tabContent }) }) })] }), !!hit && hit?.howler && (_jsxs(Box, { pr: 2, bgcolor: theme.palette.background.default, position: "relative", children: [_jsx(Divider, { orientation: "horizontal" }), _jsx(HitActions, { hit: hit })] }))] }));
|
|
187
200
|
};
|
|
188
201
|
export default InformationPane;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ArrowDropDown, InfoOutlined, List, Settings, TableChart, ViewComfy, ViewCompact, ViewModule } from '@mui/icons-material';
|
|
3
3
|
import { Checkbox, Divider, FormLabel, Stack, TextField, ToggleButton, ToggleButtonGroup, Tooltip } from '@mui/material';
|
|
4
|
-
import {
|
|
4
|
+
import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
5
5
|
import ChipPopper from '@cccsaurora/howler-ui/components/elements/display/ChipPopper';
|
|
6
6
|
import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
|
|
7
7
|
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
@@ -10,8 +10,8 @@ import { useContextSelector } from 'use-context-selector';
|
|
|
10
10
|
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
11
11
|
const LayoutSettings = () => {
|
|
12
12
|
const { t } = useTranslation();
|
|
13
|
-
const displayType = useContextSelector(
|
|
14
|
-
const setDisplayType = useContextSelector(
|
|
13
|
+
const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
|
|
14
|
+
const setDisplayType = useContextSelector(HitSearchContext, ctx => ctx.setDisplayType);
|
|
15
15
|
const [hitLayout, setHitLayout] = useMyLocalStorageItem(StorageKey.HIT_LAYOUT, false);
|
|
16
16
|
const [templateFieldCount, setTemplateFieldCount] = useMyLocalStorageItem(StorageKey.TEMPLATE_FIELD_COUNT, null);
|
|
17
17
|
return (_jsx(ChipPopper, { icon: _jsx(Tooltip, { title: t('search.layout.settings'), children: _jsx(Settings, {}) }), deleteIcon: _jsx(ArrowDropDown, {}), toggleOnDelete: true, disablePortal: false, slotProps: { chip: { size: 'medium', 'aria-label': t('search.layout.settings') } }, placement: "bottom-end", children: _jsxs(Stack, { spacing: 1, alignItems: "start", children: [_jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", alignSelf: "stretch", children: [_jsx(FormLabel, { id: "display_type", children: t('page.settings.local.hits.display_type') }), _jsx("div", { style: { flex: 1 } }), _jsx(Tooltip, { title: t('page.settings.local.hits.display_type.description'), children: _jsx(InfoOutlined, { fontSize: "inherit" }) })] }), _jsxs(ToggleButtonGroup, { exclusive: true, value: displayType, onChange: (__, value) => setDisplayType(value), size: "small", "aria-labelledby": "display_type", children: [_jsx(ToggleButton, { value: "list", children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(List, {}), _jsx("span", { children: t('page.settings.local.hits.display_type.list') })] }) }), _jsx(ToggleButton, { value: "grid", children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(TableChart, {}), _jsx("span", { children: t('page.settings.local.hits.display_type.grid') })] }) })] }), _jsx(Divider, { flexItem: true }), _jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", alignSelf: "stretch", children: [_jsx(FormLabel, { id: "layout", children: t('page.settings.local.hits.layout') }), _jsx("div", { style: { flex: 1 } }), _jsx(Tooltip, { title: t('page.settings.local.hits.layout.description'), children: _jsx(InfoOutlined, { fontSize: "inherit" }) })] }), _jsxs(ToggleButtonGroup, { exclusive: true, size: "small", value: hitLayout, onChange: (_, value) => setHitLayout(value), "aria-labelledby": "layout", children: [_jsx(ToggleButton, { value: HitLayout.DENSE, children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(ViewCompact, {}), _jsx("span", { children: t('page.settings.local.hits.layout.dense') })] }) }), _jsx(ToggleButton, { value: HitLayout.NORMAL, children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(ViewModule, {}), _jsx("span", { children: t('page.settings.local.hits.layout.normal') })] }) }), _jsx(ToggleButton, { value: HitLayout.COMFY, children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(ViewComfy, {}), _jsx("span", { children: t('page.settings.local.hits.layout.comfy') })] }) })] }), _jsx(Divider, { flexItem: true }), _jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", alignSelf: "stretch", children: [_jsx(FormLabel, { id: "field_count", children: t('page.settings.local.hits.field_count') }), _jsx("div", { style: { flex: 1 } }), _jsx(Tooltip, { title: t('page.settings.local.hits.field_count.description'), children: _jsx(InfoOutlined, { fontSize: "inherit" }) })] }), _jsxs(Stack, { direction: "row", spacing: 0.5, alignSelf: "stretch", children: [_jsx(Checkbox, { checked: templateFieldCount !== null, onChange: (_, checked) => setTemplateFieldCount(checked ? 3 : null), size: "small" }), _jsx(TextField, { type: "number", size: "small", disabled: templateFieldCount === null, value: templateFieldCount ?? 3, fullWidth: true, onChange: e => {
|
|
@@ -9,7 +9,6 @@ import { useTranslation } from 'react-i18next';
|
|
|
9
9
|
import { useContextSelector } from 'use-context-selector';
|
|
10
10
|
import HitFilter from './shared/HitFilter';
|
|
11
11
|
import HitSort from './shared/HitSort';
|
|
12
|
-
import IndexPicker from './shared/IndexPicker';
|
|
13
12
|
import SearchSpan from './shared/SearchSpan';
|
|
14
13
|
import ViewLink from './ViewLink';
|
|
15
14
|
const QuerySettings = ({ boxSx }) => {
|
|
@@ -26,6 +25,6 @@ const QuerySettings = ({ boxSx }) => {
|
|
|
26
25
|
await fetchViews();
|
|
27
26
|
addView('');
|
|
28
27
|
};
|
|
29
|
-
return (_jsx(Box, { sx: boxSx ?? { position: 'relative', maxWidth: '1200px' }, children: _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsxs(Grid, { container: true, spacing: 1, sx: theme => ({ ml: `${theme.spacing(-1)} !important`, mt: `${theme.spacing(-1)} !important` }), children: [_jsx(Grid, { item: true, children: _jsx(
|
|
28
|
+
return (_jsx(Box, { sx: boxSx ?? { position: 'relative', maxWidth: '1200px' }, children: _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsxs(Grid, { container: true, spacing: 1, sx: theme => ({ ml: `${theme.spacing(-1)} !important`, mt: `${theme.spacing(-1)} !important` }), children: [_jsx(Grid, { item: true, children: _jsx(HitSort, {}) }), _jsx(Grid, { item: true, children: _jsx(SearchSpan, {}) }), currentViews?.map((view, id) => (_jsx(Grid, { item: true, children: _jsx(ViewLink, { id: id, viewId: view }) }, view))), filters?.map((filter, id) => (_jsx(Grid, { item: true, children: _jsx(HitFilter, { id: id, value: filter }) }, filter)))] }), _jsx(ChipPopper, { icon: _jsx(Add, {}), deleteIcon: _jsx(ArrowDropDown, {}), toggleOnDelete: true, closeOnClick: true, slotProps: { chip: { size: 'small', color: 'primary' }, paper: { sx: { p: 1 } } }, children: _jsxs(Stack, { spacing: 1, children: [_jsxs(Button, { id: "add-filter", "aria-label": t('hit.search.filter.add'), variant: "outlined", onClick: () => addFilter('howler.assessment:*'), disabled: filters?.some(filter => filter.endsWith('*')), sx: { display: 'flex', pl: 1 }, children: [_jsx(Add, { fontSize: "small", sx: { mr: 1 } }), _jsx("div", { style: { flex: 1 } }), _jsx("span", { children: t('hit.search.filter.add') })] }), _jsxs(Button, { id: "add-view", "aria-label": t('hit.search.view.add'), variant: "outlined", onClick: onAddView, disabled: !allowAddViews, sx: { display: 'flex', pl: 1 }, children: [_jsx(Add, { fontSize: "small", sx: { mr: 1 } }), _jsx("div", { style: { flex: 1 } }), _jsx("span", { children: t('hit.search.view.add') })] })] }) })] }) }));
|
|
30
29
|
};
|
|
31
30
|
export default memo(QuerySettings);
|