@cccsaurora/howler-ui 2.18.0-dev.710 → 2.18.0-dev.722
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/facet/hit.d.ts +3 -1
- package/api/search/facet/index.d.ts +1 -3
- 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 -39
- 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/ModalProvider.d.ts +0 -1
- 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 -2
- 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.d.ts +0 -1
- package/components/elements/view/ViewTitle.js +2 -9
- 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 -94
- 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 +41 -47
- 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 +19 -46
- package/locales/en/translation.json +3 -87
- package/locales/fr/translation.json +3 -85
- 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/models/entities/generated/View.d.ts +0 -1
- package/package.json +1 -18
- package/plugins/clue/components/ClueTypography.js +2 -2
- package/plugins/clue/utils.d.ts +1 -2
- package/tests/server-handlers.js +1 -6
- package/tests/utils.d.ts +0 -4
- package/tests/utils.js +0 -20
- package/utils/constants.d.ts +3 -3
- package/utils/hitFunctions.d.ts +1 -2
- package/utils/hitFunctions.js +4 -4
- package/utils/viewUtils.js +0 -3
- package/api/search/case.d.ts +0 -4
- package/api/search/case.js +0 -8
- package/api/v2/case/index.d.ts +0 -8
- package/api/v2/case/index.js +0 -20
- package/api/v2/case/items.d.ts +0 -6
- package/api/v2/case/items.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 -12
- package/components/elements/case/CaseCard.js +0 -42
- 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 -22
- package/components/elements/observable/ObservablePreview.d.ts +0 -6
- package/components/elements/observable/ObservablePreview.js +0 -12
- package/components/elements/record/RecordContextMenu.js +0 -247
- 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 -11
- package/components/routes/cases/detail/CaseAssets.js +0 -104
- package/components/routes/cases/detail/CaseAssets.test.d.ts +0 -1
- package/components/routes/cases/detail/CaseAssets.test.js +0 -167
- package/components/routes/cases/detail/CaseDashboard.d.ts +0 -7
- package/components/routes/cases/detail/CaseDashboard.js +0 -54
- 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 -8
- package/components/routes/cases/detail/CaseSidebar.js +0 -50
- package/components/routes/cases/detail/CaseTask.d.ts +0 -11
- package/components/routes/cases/detail/CaseTask.js +0 -57
- package/components/routes/cases/detail/CaseTimeline.d.ts +0 -12
- package/components/routes/cases/detail/CaseTimeline.js +0 -106
- package/components/routes/cases/detail/CaseTimeline.test.d.ts +0 -1
- package/components/routes/cases/detail/CaseTimeline.test.js +0 -227
- 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 -30
- 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 -14
- package/components/routes/cases/detail/sidebar/CaseFolder.js +0 -133
- package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.d.ts +0 -34
- package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.js +0 -105
- package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.d.ts +0 -1
- package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.js +0 -351
- 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 -51
- package/components/routes/cases/modals/AddToCaseModal.d.ts +0 -7
- package/components/routes/cases/modals/AddToCaseModal.js +0 -62
- package/components/routes/cases/modals/RenameItemModal.d.ts +0 -9
- package/components/routes/cases/modals/RenameItemModal.js +0 -48
- package/components/routes/cases/modals/ResolveModal.d.ts +0 -7
- package/components/routes/cases/modals/ResolveModal.js +0 -62
- 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
|
@@ -5,6 +5,7 @@ import { useScrollRestoration } from '@cccsaurora/howler-ui/components/hooks/use
|
|
|
5
5
|
import { useCallback, useState } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
import { useSearchParams } from 'react-router-dom';
|
|
8
|
+
import BundleDocumentation from './BundleDocumentation';
|
|
8
9
|
import HelpTabs from './components/HelpTabs';
|
|
9
10
|
import HitBannerDocumentation from './HitBannerDocumentation';
|
|
10
11
|
import HitLabelsDocumentation from './HitLabelsDocumentation';
|
|
@@ -22,7 +23,8 @@ const HitDocumentation = () => {
|
|
|
22
23
|
searchParams.set('tab', _tab);
|
|
23
24
|
setSearchParams(new URLSearchParams(searchParams));
|
|
24
25
|
}, [searchParams, setSearchParams]);
|
|
25
|
-
return (_jsx(PageCenter, { margin: 4, width: "100%", maxWidth: "1750px", textAlign: "left", children: _jsxs(Stack, { sx: { flexDirection: useHorizontal ? 'column' : 'row', '& h1': { mt: 0 } }, children: [_jsxs(HelpTabs, { value: tab, children: [_jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.schema.title') }), value: "schema", onClick: () => onChange('schema') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.banner.title') }), value: "header", onClick: () => onChange('header') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.links.title') }), value: "links", onClick: () => onChange('links') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.labels.title') }), value: "labels", onClick: () => onChange('labels') })] }), _jsx(Box, { children: {
|
|
26
|
+
return (_jsx(PageCenter, { margin: 4, width: "100%", maxWidth: "1750px", textAlign: "left", children: _jsxs(Stack, { sx: { flexDirection: useHorizontal ? 'column' : 'row', '& h1': { mt: 0 } }, children: [_jsxs(HelpTabs, { value: tab, children: [_jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.schema.title') }), value: "schema", onClick: () => onChange('schema') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.banner.title') }), value: "header", onClick: () => onChange('header') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.bundle.title') }), value: "bundle", onClick: () => onChange('bundle') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.links.title') }), value: "links", onClick: () => onChange('links') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.labels.title') }), value: "labels", onClick: () => onChange('labels') })] }), _jsx(Box, { children: {
|
|
27
|
+
bundle: () => _jsx(BundleDocumentation, {}),
|
|
26
28
|
header: () => _jsx(HitBannerDocumentation, {}),
|
|
27
29
|
links: () => _jsx(HitLinksDocumentation, {}),
|
|
28
30
|
labels: () => _jsx(HitLabelsDocumentation, {}),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default "<!-- docs/ingestion/bundles.md -->\n\n# Howler Hit Bundles\n\nHit bundles can be used to easily package together a large number of similar alerts, allowing analysts to easily triage them as a single incident. For example, consider a single computer that repeatedly makes a network call to `baddomain.ru` - while an alert may be generated for every instance of this computer hitting that domain, it makes sense for analysts to treat all these alerts as a single case.\n\n## Creating bundles through the Howler Client\n\nThere are a couple of ways to create a bundle through the howler client:\n\n```python\nfrom howler_client import get_client\n\nhowler = get_client(\"https://howler.dev.analysis.cyber.gc.ca\")\n\n\"\"\"Creating a howler bundle and the hits at the same time\"\"\"\nhowler.bundle.create(\n # First argument is the bundle hit\n {\n \"howler.analytic\": \"example-test\",\n \"howler.score\": 0\n },\n # Second argument is a hit or list of hits to include in the bundle\n [\n {\n \"howler.analytic\": \"example-test\",\n \"howler.score\": 0\n },\n {\n \"howler.analytic\": \"example-test\",\n \"howler.score\": 0\n }\n ]\n)\n\n\"\"\"Creating a howler bundle from existing hits\"\"\"\nhowler.bundle.create(\n {\n \"howler.analytic\": \"example-test\",\n \"howler.score\": 0,\n \"howler.hits\": [\"YcUsL8QsjmwwIdstieROk\", \"6s7MztwuSvz6tM0PgGJhvz\"]\n },\n # Note: In future releases, you won't need to include this argument\n []\n)\n\n\n\"\"\"Creating from a map\"\"\"\nbundle_hit = {\n \"score\": 0,\n \"bundle\": True\n}\n\nmap = {\n \"score\": [\"howler.score\"],\n \"bundle\": [\"howler.is_bundle\"]\n}\n\nhowler.bundle.create_from_map(\"example-test\", bundle_hit, map, [{\"score\": 0}])\n```\n\n## Viewing bundles on the Howler UI\n\nIn order to view created bundles on the Howler UI, you can use the query `howler.is_bundle:true`. This will provide a list of created bundles you can look through.\n\nClicking on a bundle will open up a slightly different search UI to normal. In this case, we automatically filter the search results to include only hits that are included in the bundle. To make this obvious, the header representing the bundle will appear above the search bar.\n\nYou can continue to filter through hits using the same queries as usual, and view them as usual. When triaging a bundle, assessing it will apply this assessment to all hits in the bundle, **except those that have already been triaged**. That is, if the bundle is open, all open hits will be assessed when you assess it.\n\nBundles also have a **Summary** tab not available for regular hits. This summary tab will aid you in aggregating data about all the hits in the bundle. Simply open the tab and click \"Create Summary\". Note that this may take some time, as a large number of queries are being run to aggregate the data.\n"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default "<!-- docs/ingestion/bundles.fr.md -->\n\n# Les groupes des hits Howler\n\nLes groupes des hits peuvent \u00eatre utilis\u00e9s pour regrouper facilement un grand nombre d'alertes similaires, ce qui permet aux analystes de les traiter comme un seul incident. Prenons l'exemple d'un ordinateur qui effectue \u00e0 plusieurs reprises un appel r\u00e9seau vers `baddomain.ru` - bien qu'une alerte puisse \u00eatre g\u00e9n\u00e9r\u00e9e pour chaque cas o\u00f9 cet ordinateur acc\u00e8de \u00e0 ce domaine, il est logique que les analystes traitent toutes ces alertes comme un seul et m\u00eame cas.\n\n## Cr\u00e9ation de groupes via le client Howler\n\nIl y a plusieurs fa\u00e7ons de cr\u00e9er un groupe via le client Howler:\n\n```python\nfrom howler_client import get_client\n\nhowler = get_client(\"https://howler.dev.analysis.cyber.gc.ca\")\n\n\"\"\"Cr\u00e9ation simultan\u00e9e d'un groupe howler et de hits\"\"\"\nhowler.bundle.create(\n # Le premier argument est le hit de l'offre group\u00e9e\n {\n \"howler.analytic\": \"example-test\",\n \"howler.score\": 0\n },\n # Le deuxi\u00e8me argument est un hit ou une liste de hits \u00e0 inclure dans l'offre group\u00e9e.\n [\n {\n \"howler.analytic\": \"example-test\",\n \"howler.score\": 0\n },\n {\n \"howler.analytic\": \"example-test\",\n \"howler.score\": 0\n }\n ]\n)\n\n\"\"\"Cr\u00e9ation d'un groupe howler \u00e0 partir de hits existants\"\"\"\nhowler.bundle.create(\n {\n \"howler.analytic\": \"example-test\",\n \"howler.score\": 0,\n \"howler.hits\": [\"YcUsL8QsjmwwIdstieROk\", \"6s7MztwuSvz6tM0PgGJhvz\"]\n },\n # Noter: Dans les prochaines versions, vous n'aurez plus besoin d'inclure cet argument.\n []\n)\n\n\n\"\"\"Cr\u00e9ation \u00e0 partir d'une carte\"\"\"\nbundle_hit = {\n \"score\": 0,\n \"bundle\": True\n}\n\nmap = {\n \"score\": [\"howler.score\"],\n \"bundle\": [\"howler.is_bundle\"]\n}\n\nhowler.bundle.create_from_map(\"example-test\", bundle_hit, map, [{\"score\": 0}])\n```\n\n## Visualiser les groupes sur l'interface utilisateur de Howler\n\nAfin de visualiser les groupes cr\u00e9\u00e9s sur l'interface utilisateur de Howler, vous pouvez utiliser la requ\u00eate `howler.is_bundle:true`. Cela fournira une liste de groupes cr\u00e9\u00e9s que vous pourrez consulter.\n\nEn cliquant sur un groupe, vous ouvrirez une interface de recherche l\u00e9g\u00e8rement diff\u00e9rente de l'interface normale. Dans ce cas, nous filtrons automatiquement les r\u00e9sultats de la recherche pour n'inclure que les r\u00e9sultats inclus dans le groupe. Pour que cela soit \u00e9vident, l'en-t\u00eate repr\u00e9sentant le groupe appara\u00eet au-dessus de la barre de recherche.\n\nVous pouvez continuer \u00e0 filtrer les r\u00e9sultats en utilisant les m\u00eames requ\u00eates que d'habitude et \u00e0 les visualiser comme d'habitude. Lors du triage d'un groupe, son \u00e9valuation s'appliquera \u00e0 tous les hits du groupe, **sauf ceux qui ont d\u00e9j\u00e0 \u00e9t\u00e9 tri\u00e9s**. En d'autres termes, si le groupe est ouvert, tous les hits ouverts seront \u00e9valu\u00e9s lorsque vous l'\u00e9valuerez.\n\nLes groupes disposent \u00e9galement d'un onglet **R\u00e9sum\u00e9** qui n'est pas disponible pour les hits ordinaires. Cet onglet vous aidera \u00e0 regrouper les donn\u00e9es relatives \u00e0 tous les r\u00e9sultats du groupe. Il suffit d'ouvrir l'onglet et de cliquer sur \"Cr\u00e9er un sommaire\". Notez que cette op\u00e9ration peut prendre un certain temps, car un grand nombre de requ\u00eates sont ex\u00e9cut\u00e9es pour agr\u00e9ger les donn\u00e9es.\n"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AccountTree } from '@mui/icons-material';
|
|
3
|
+
import { IconButton, Paper, Popover, Skeleton, Stack, Tooltip } from '@mui/material';
|
|
4
|
+
import api from '@cccsaurora/howler-ui/api';
|
|
5
|
+
import HowlerCard from '@cccsaurora/howler-ui/components/elements/display/HowlerCard';
|
|
6
|
+
import HitBanner from '@cccsaurora/howler-ui/components/elements/hit/HitBanner';
|
|
7
|
+
import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
|
|
8
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
9
|
+
import { useTranslation } from 'react-i18next';
|
|
10
|
+
import { useNavigate } from 'react-router-dom';
|
|
11
|
+
const BundleParentMenu = ({ bundle }) => {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
const navigate = useNavigate();
|
|
14
|
+
const [parentAnchor, setParentAnchor] = useState(null);
|
|
15
|
+
const [parentHits, setParentHits] = useState([]);
|
|
16
|
+
const onSelect = useCallback((bundleId) => {
|
|
17
|
+
navigate(`/bundles/${bundleId}?span=date.range.all&query=howler.id%3A*`);
|
|
18
|
+
setParentAnchor(null);
|
|
19
|
+
}, [navigate]);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!parentAnchor) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
api.search.hit
|
|
25
|
+
.post({ query: `howler.id:(${bundle.howler.bundles.join(' OR ')})` })
|
|
26
|
+
.then(response => setParentHits(response.items));
|
|
27
|
+
}, [bundle.howler.bundles, parentAnchor]);
|
|
28
|
+
return (_jsxs(_Fragment, { children: [_jsx(Tooltip, { title: t('hit.bundle.parents.show'), children: _jsx(IconButton, { size: "small", onClick: event => setParentAnchor(event.currentTarget), children: _jsx(AccountTree, { fontSize: "small" }) }) }), _jsx(Popover, { open: !!parentAnchor, anchorEl: parentAnchor, anchorOrigin: { vertical: 'top', horizontal: 'left' }, transformOrigin: { horizontal: 'right', vertical: 'top' }, onClose: () => setParentAnchor(null), children: _jsx(Paper, { sx: { p: 1, minWidth: '750px' }, children: _jsx(Stack, { spacing: 1, children: parentHits.length < 1
|
|
29
|
+
? bundle.howler.bundles.map(id => _jsx(Skeleton, { variant: "rounded", height: "100px" }, id))
|
|
30
|
+
: parentHits.map(parent => (_jsx(HowlerCard, { sx: { p: 1, cursor: 'pointer' }, onClick: () => onSelect(parent.howler.id), children: _jsx(HitBanner, { hit: parent, layout: HitLayout.DENSE }) }, parent.howler.id))) }) }) })] }));
|
|
31
|
+
};
|
|
32
|
+
export default BundleParentMenu;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useScrollRestoration } from '@cccsaurora/howler-ui/components/hooks/useScrollRestoration';
|
|
3
|
+
export const BundleScroller = ({ children }) => {
|
|
4
|
+
useScrollRestoration('hitscrollbar');
|
|
5
|
+
return _jsx(_Fragment, { children: children });
|
|
6
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ChevronLeft, Close, ManageSearch } from '@mui/icons-material';
|
|
3
3
|
import { Box, Card, Checkbox, Collapse, Drawer, Fab, IconButton, Stack, Tooltip, Typography, useMediaQuery, useTheme } from '@mui/material';
|
|
4
|
+
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
5
|
+
import HitSearchProvider, { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
4
6
|
import ParameterProvider, { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
5
|
-
import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
|
|
6
|
-
import RecordSearchProvider, { RecordSearchContext } from '@cccsaurora/howler-ui/components/app/providers/RecordSearchProvider';
|
|
7
7
|
import { ViewContext } from '@cccsaurora/howler-ui/components/app/providers/ViewProvider';
|
|
8
8
|
import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
|
|
9
9
|
import FlexPort from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexPort';
|
|
@@ -32,14 +32,14 @@ const HitBrowser = () => {
|
|
|
32
32
|
const setQuery = useContextSelector(ParameterContext, ctx => ctx.setQuery);
|
|
33
33
|
const setOffset = useContextSelector(ParameterContext, ctx => ctx.setOffset);
|
|
34
34
|
const selectedViews = useContextSelector(ParameterContext, ctx => ctx.views);
|
|
35
|
-
const selectedHits = useContextSelector(
|
|
36
|
-
const addHitToSelection = useContextSelector(
|
|
37
|
-
const removeHitFromSelection = useContextSelector(
|
|
38
|
-
const clearSelectedHits = useContextSelector(
|
|
35
|
+
const selectedHits = useContextSelector(HitContext, ctx => ctx.selectedHits);
|
|
36
|
+
const addHitToSelection = useContextSelector(HitContext, ctx => ctx.addHitToSelection);
|
|
37
|
+
const removeHitFromSelection = useContextSelector(HitContext, ctx => ctx.removeHitFromSelection);
|
|
38
|
+
const clearSelectedHits = useContextSelector(HitContext, ctx => ctx.clearSelectedHits);
|
|
39
39
|
const searchPaneWidth = useMyLocalStorageItem(StorageKey.SEARCH_PANE_WIDTH, null)[0];
|
|
40
40
|
const forceDrawer = useMyLocalStorageItem(StorageKey.FORCE_DRAWER, false)[0];
|
|
41
|
-
const displayType = useContextSelector(
|
|
42
|
-
const response = useContextSelector(
|
|
41
|
+
const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
|
|
42
|
+
const response = useContextSelector(HitSearchContext, ctx => ctx.response);
|
|
43
43
|
const location = useLocation();
|
|
44
44
|
const routeParams = useParams();
|
|
45
45
|
const [show, setShow] = useState(!!selected);
|
|
@@ -128,6 +128,6 @@ const HitBrowser = () => {
|
|
|
128
128
|
}, children: _jsx(ChevronLeft, { sx: { transition: 'rotate 250ms', rotate: show ? '180deg' : '0deg' } }) }))] }));
|
|
129
129
|
};
|
|
130
130
|
const HitBrowserProvider = () => {
|
|
131
|
-
return (_jsx(ParameterProvider, { children: _jsx(
|
|
131
|
+
return (_jsx(ParameterProvider, { children: _jsx(HitSearchProvider, { children: _jsx(HitBrowser, {}) }) }));
|
|
132
132
|
};
|
|
133
133
|
export default HitBrowserProvider;
|
package/components/{elements/record/RecordContextMenu.d.ts → routes/hits/search/HitContextMenu.d.ts}
RENAMED
|
@@ -3,7 +3,7 @@ import React from 'react';
|
|
|
3
3
|
/**
|
|
4
4
|
* Props for the HitContextMenu component
|
|
5
5
|
*/
|
|
6
|
-
interface
|
|
6
|
+
interface HitContextMenuProps {
|
|
7
7
|
/**
|
|
8
8
|
* Function to extract the hit ID from a mouse event
|
|
9
9
|
*/
|
|
@@ -18,5 +18,5 @@ interface RecordContextMenuProps {
|
|
|
18
18
|
* Provides quick access to common hit actions including assessment, voting,
|
|
19
19
|
* transitions, and exclusion filters based on template fields.
|
|
20
20
|
*/
|
|
21
|
-
declare const
|
|
22
|
-
export default
|
|
21
|
+
declare const HitContextMenu: FC<PropsWithChildren<HitContextMenuProps>>;
|
|
22
|
+
export default HitContextMenu;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { AddCircleOutline, Assignment, Edit, HowToVote, KeyboardArrowRight, OpenInNew, QueryStats, RemoveCircleOutline, SettingsSuggest, Terminal } from '@mui/icons-material';
|
|
3
|
+
import { Box, Divider, Fade, ListItemIcon, ListItemText, Menu, MenuItem, MenuList, Paper } from '@mui/material';
|
|
4
|
+
import api from '@cccsaurora/howler-ui/api';
|
|
5
|
+
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
6
|
+
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
7
|
+
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
8
|
+
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
9
|
+
import { TOP_ROW, VOTE_OPTIONS } from '@cccsaurora/howler-ui/components/elements/hit/actions/SharedComponents';
|
|
10
|
+
import useHitActions from '@cccsaurora/howler-ui/components/hooks/useHitActions';
|
|
11
|
+
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
12
|
+
import useMyActionFunctions from '@cccsaurora/howler-ui/components/routes/action/useMyActionFunctions';
|
|
13
|
+
import { capitalize, get, groupBy, isEmpty, toString } from 'lodash-es';
|
|
14
|
+
import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
|
|
15
|
+
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
16
|
+
import { useTranslation } from 'react-i18next';
|
|
17
|
+
import { usePluginStore } from 'react-pluggable';
|
|
18
|
+
import { Link } from 'react-router-dom';
|
|
19
|
+
import { useContextSelector } from 'use-context-selector';
|
|
20
|
+
import { DEFAULT_QUERY } from '@cccsaurora/howler-ui/utils/constants';
|
|
21
|
+
import { sanitizeLuceneQuery } from '@cccsaurora/howler-ui/utils/stringUtils';
|
|
22
|
+
/**
|
|
23
|
+
* Order in which action types should be displayed in the context menu
|
|
24
|
+
*/
|
|
25
|
+
const ORDER = ['assessment', 'vote', 'action'];
|
|
26
|
+
/**
|
|
27
|
+
* The margin at the bottom of the screen by which the context menu should be inverted.
|
|
28
|
+
* That is, if right clicking within this many pixels of the bottom, render the context menu to the top right
|
|
29
|
+
* of the pointer instead of the bottom right.
|
|
30
|
+
*/
|
|
31
|
+
const CONTEXTMENU_MARGIN = 350;
|
|
32
|
+
/**
|
|
33
|
+
* Icon mapping for different action types
|
|
34
|
+
*/
|
|
35
|
+
const ICON_MAP = {
|
|
36
|
+
assessment: _jsx(Assignment, {}),
|
|
37
|
+
vote: _jsx(HowToVote, {}),
|
|
38
|
+
action: _jsx(Edit, {})
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Context menu component for hit operations.
|
|
42
|
+
* Provides quick access to common hit actions including assessment, voting,
|
|
43
|
+
* transitions, and exclusion filters based on template fields.
|
|
44
|
+
*/
|
|
45
|
+
const HitContextMenu = ({ children, getSelectedId, Component = Box }) => {
|
|
46
|
+
const { t } = useTranslation();
|
|
47
|
+
const { dispatchApi } = useMyApi();
|
|
48
|
+
const { executeAction } = useMyActionFunctions();
|
|
49
|
+
const { config } = useContext(ApiConfigContext);
|
|
50
|
+
const pluginStore = usePluginStore();
|
|
51
|
+
const { getMatchingAnalytic, getMatchingTemplate } = useMatchers();
|
|
52
|
+
const query = useContextSelector(ParameterContext, ctx => ctx?.query);
|
|
53
|
+
const setQuery = useContextSelector(ParameterContext, ctx => ctx?.setQuery);
|
|
54
|
+
const [id, setId] = useState(null);
|
|
55
|
+
const hit = useContextSelector(HitContext, ctx => ctx.hits[id]);
|
|
56
|
+
const selectedHits = useContextSelector(HitContext, ctx => ctx.selectedHits);
|
|
57
|
+
const [analytic, setAnalytic] = useState(null);
|
|
58
|
+
const [template, setTemplate] = useState(null);
|
|
59
|
+
const [anchorEl, setAnchorEl] = useState();
|
|
60
|
+
const [transformProps, setTransformProps] = useState({});
|
|
61
|
+
const [actions, setActions] = useState([]);
|
|
62
|
+
const [show, setShow] = useState({});
|
|
63
|
+
const hits = useMemo(() => (selectedHits.some(_hit => _hit.howler.id === hit?.howler.id) ? selectedHits : [hit]), [hit, selectedHits]);
|
|
64
|
+
const { availableTransitions, canVote, canAssess, assess, vote } = useHitActions(hits);
|
|
65
|
+
/**
|
|
66
|
+
* Handles right-click context menu events.
|
|
67
|
+
* Opens the context menu at the click location and loads available actions.
|
|
68
|
+
*/
|
|
69
|
+
const onContextMenu = useCallback(async (event) => {
|
|
70
|
+
if (anchorEl) {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
setAnchorEl(null);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
const _id = getSelectedId(event);
|
|
77
|
+
setId(_id);
|
|
78
|
+
if (window.innerHeight - event.clientY < 300) {
|
|
79
|
+
setTransformProps({
|
|
80
|
+
position: 'fixed',
|
|
81
|
+
bottom: `${window.innerHeight - event.clientY}px !important`,
|
|
82
|
+
top: 'unset !important',
|
|
83
|
+
left: `${event.clientX}px !important`
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
setTransformProps({
|
|
88
|
+
position: 'fixed',
|
|
89
|
+
top: `${event.clientY}px !important`,
|
|
90
|
+
left: `${event.clientX}px !important`
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
setAnchorEl(event.target);
|
|
94
|
+
const _actions = (await dispatchApi(api.search.action.post({ query: 'action_id:*' }), { throwError: false }))
|
|
95
|
+
?.items;
|
|
96
|
+
if (_actions) {
|
|
97
|
+
setActions(_actions);
|
|
98
|
+
}
|
|
99
|
+
}, [anchorEl, dispatchApi, getSelectedId]);
|
|
100
|
+
const rowStatus = useMemo(() => ({
|
|
101
|
+
assessment: canAssess,
|
|
102
|
+
vote: canVote
|
|
103
|
+
}), [canAssess, canVote]);
|
|
104
|
+
const pluginActions = howlerPluginStore.plugins.flatMap(plugin => pluginStore.executeFunction(`${plugin}.actions`, hits));
|
|
105
|
+
/**
|
|
106
|
+
* Generates grouped action entries for the context menu.
|
|
107
|
+
* Combines transitions, plugin actions, votes, and assessments based on permissions.
|
|
108
|
+
*/
|
|
109
|
+
const entries = useMemo(() => {
|
|
110
|
+
let _actions = [...availableTransitions, ...pluginActions];
|
|
111
|
+
if (canVote) {
|
|
112
|
+
_actions = [
|
|
113
|
+
..._actions,
|
|
114
|
+
...VOTE_OPTIONS.map(option => ({ ...option, actionFunction: () => vote(option.name.toLowerCase()) }))
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
if (config.lookups?.['howler.assessment'] && canAssess) {
|
|
118
|
+
_actions = [
|
|
119
|
+
..._actions,
|
|
120
|
+
...config.lookups['howler.assessment']
|
|
121
|
+
.filter(_assessment => analytic?.triage_settings?.valid_assessments
|
|
122
|
+
? analytic.triage_settings?.valid_assessments.includes(_assessment)
|
|
123
|
+
: true)
|
|
124
|
+
.sort((a, b) => +TOP_ROW.includes(b) - +TOP_ROW.includes(a))
|
|
125
|
+
.map(assessment => ({
|
|
126
|
+
type: 'assessment',
|
|
127
|
+
name: assessment,
|
|
128
|
+
actionFunction: async () => {
|
|
129
|
+
await assess(assessment, analytic?.triage_settings?.skip_rationale);
|
|
130
|
+
}
|
|
131
|
+
}))
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
return Object.entries(groupBy(_actions, 'type')).sort(([a], [b]) => ORDER.indexOf(a) - ORDER.indexOf(b));
|
|
135
|
+
}, [analytic, assess, availableTransitions, canAssess, canVote, config.lookups, vote, pluginActions]);
|
|
136
|
+
/**
|
|
137
|
+
* Calculates appropriate styles for submenu positioning.
|
|
138
|
+
* Adjusts position based on available screen space to prevent overflow.
|
|
139
|
+
*/
|
|
140
|
+
const calculateSubMenuStyles = useCallback((parent) => {
|
|
141
|
+
const baseStyles = { position: 'absolute', maxHeight: '300px', overflow: 'auto' };
|
|
142
|
+
const defaultStyles = { ...baseStyles, top: 0, left: '100%' };
|
|
143
|
+
if (!parent) {
|
|
144
|
+
return defaultStyles;
|
|
145
|
+
}
|
|
146
|
+
const parentBounds = parent.getBoundingClientRect();
|
|
147
|
+
if (window.innerHeight - parentBounds.y < CONTEXTMENU_MARGIN) {
|
|
148
|
+
return { ...baseStyles, bottom: 0, left: '100%' };
|
|
149
|
+
}
|
|
150
|
+
return defaultStyles;
|
|
151
|
+
}, []);
|
|
152
|
+
// Load analytic and template data when a hit is selected
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (!hit?.howler.analytic) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
getMatchingAnalytic(hit).then(setAnalytic);
|
|
158
|
+
getMatchingTemplate(hit).then(setTemplate);
|
|
159
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
160
|
+
}, [hit]);
|
|
161
|
+
// Reset menu state when context menu is closed
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (!anchorEl) {
|
|
164
|
+
setShow({});
|
|
165
|
+
setAnalytic(null);
|
|
166
|
+
}
|
|
167
|
+
}, [anchorEl]);
|
|
168
|
+
return (_jsxs(Component, { id: "contextMenu", onContextMenu: onContextMenu, children: [children, _jsxs(Menu, { id: "hit-menu", open: !!anchorEl, anchorEl: anchorEl, onClose: () => setAnchorEl(null), slotProps: {
|
|
169
|
+
paper: {
|
|
170
|
+
sx: {
|
|
171
|
+
...transformProps,
|
|
172
|
+
overflow: 'visible !important'
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}, MenuListProps: { dense: true, sx: { minWidth: '250px' } }, anchorOrigin: { vertical: 'top', horizontal: 'left' }, onClick: () => setAnchorEl(null), children: [_jsxs(MenuItem, { component: Link, to: `/hits/${hit?.howler.id}`, disabled: !hit, children: [_jsx(ListItemIcon, { children: _jsx(OpenInNew, {}) }), _jsx(ListItemText, { children: t('hit.panel.open') })] }), _jsxs(MenuItem, { component: Link, to: `/analytics/${analytic?.analytic_id}`, disabled: !analytic, children: [_jsx(ListItemIcon, { children: _jsx(QueryStats, {}) }), _jsx(ListItemText, { children: t('hit.panel.analytic.open') })] }), _jsx(Divider, {}), entries.map(([type, items]) => (_jsxs(MenuItem, { id: `${type}-menu-item`, sx: { position: 'relative' }, onMouseEnter: ev => setShow(_show => ({ ..._show, [type]: ev.target })), onMouseLeave: () => setShow(_show => ({ ..._show, [type]: null })), disabled: rowStatus[type] === false, children: [_jsx(ListItemIcon, { children: ICON_MAP[type] ?? _jsx(Terminal, {}) }), _jsx(ListItemText, { sx: { flex: 1 }, children: t(`hit.details.actions.${type}`) }), rowStatus[type] !== false && (_jsx(KeyboardArrowRight, { fontSize: "small", sx: { color: 'text.secondary', mr: -1 } })), _jsx(Fade, { in: !!show[type], unmountOnExit: true, children: _jsx(Paper, { id: `${type}-submenu`, sx: calculateSubMenuStyles(show[type]), elevation: 8, children: _jsx(MenuList, { sx: { p: 0, borderTopLeftRadius: 0 }, dense: true, role: "group", children: items.map(a => (_jsx(MenuItem, { value: a.name, onClick: a.actionFunction, children: a.i18nKey ? t(a.i18nKey) : capitalize(a.name) }, a.name))) }) }) })] }, type))), _jsxs(MenuItem, { id: "actions-menu-item", sx: { position: 'relative' }, onMouseEnter: ev => setShow(_show => ({ ..._show, actions: ev.target })), onMouseLeave: () => setShow(_show => ({ ..._show, actions: null })), disabled: actions.length < 1, children: [_jsx(ListItemIcon, { children: _jsx(SettingsSuggest, {}) }), _jsx(ListItemText, { sx: { flex: 1 }, children: t('route.actions.change') }), actions.length > 0 && _jsx(KeyboardArrowRight, { fontSize: "small", sx: { color: 'text.secondary', mr: -1 } }), _jsx(Fade, { in: !!show.actions, unmountOnExit: true, children: _jsx(Paper, { id: "actions-submenu", sx: calculateSubMenuStyles(show.actions), elevation: 8, children: _jsx(MenuList, { sx: { p: 0 }, dense: true, role: "group", children: actions.map(action => (_jsx(MenuItem, { onClick: () => executeAction(action.action_id, `howler.id:${hit?.howler.id}`), children: _jsx(ListItemText, { children: action.name }) }, action.action_id))) }) }) })] }), !isEmpty(template?.keys ?? []) && setQuery && (_jsxs(_Fragment, { children: [_jsx(Divider, {}), _jsxs(MenuItem, { id: "excludes-menu-item", sx: { position: 'relative' }, onMouseEnter: ev => setShow(_show => ({ ..._show, excludes: ev.target })), onMouseLeave: () => setShow(_show => ({ ..._show, excludes: null })), children: [_jsx(ListItemIcon, { children: _jsx(RemoveCircleOutline, {}) }), _jsx(ListItemText, { sx: { flex: 1 }, children: t('hit.panel.exclude') }), _jsx(KeyboardArrowRight, { fontSize: "small", sx: { color: 'text.secondary', mr: -1 } }), _jsx(Fade, { in: !!show.excludes, unmountOnExit: true, children: _jsx(Paper, { id: "excludes-submenu", sx: calculateSubMenuStyles(show.excludes), elevation: 8, children: _jsx(MenuList, { sx: { p: 0 }, dense: true, role: "group", children: template?.keys.map(key => {
|
|
176
|
+
// Build exclusion query based on current query and field value
|
|
177
|
+
let newQuery = '';
|
|
178
|
+
if (query !== DEFAULT_QUERY) {
|
|
179
|
+
newQuery = `(${query}) AND `;
|
|
180
|
+
}
|
|
181
|
+
const value = get(hit, key);
|
|
182
|
+
if (!value) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
else if (Array.isArray(value)) {
|
|
186
|
+
// Handle array values by excluding all items
|
|
187
|
+
const sanitizedValues = value
|
|
188
|
+
.map(toString)
|
|
189
|
+
.filter(val => !!val)
|
|
190
|
+
.map(val => `"${sanitizeLuceneQuery(val)}"`);
|
|
191
|
+
if (sanitizedValues.length < 1) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
newQuery += `-${key}:(${sanitizedValues.join(' OR ')})`;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
// Handle single value
|
|
198
|
+
newQuery += `-${key}:"${sanitizeLuceneQuery(value.toString())}"`;
|
|
199
|
+
}
|
|
200
|
+
return (_jsx(MenuItem, { onClick: () => setQuery(newQuery), children: _jsx(ListItemText, { children: key }) }, key));
|
|
201
|
+
}) }) }) })] }), _jsxs(MenuItem, { id: "includes-menu-item", sx: { position: 'relative' }, onMouseEnter: ev => setShow(_show => ({ ..._show, includes: ev.target })), onMouseLeave: () => setShow(_show => ({ ..._show, includes: null })), children: [_jsx(ListItemIcon, { children: _jsx(AddCircleOutline, {}) }), _jsx(ListItemText, { sx: { flex: 1 }, children: t('hit.panel.include') }), _jsx(KeyboardArrowRight, { fontSize: "small", sx: { color: 'text.secondary', mr: -1 } }), _jsx(Fade, { in: !!show.includes, unmountOnExit: true, children: _jsx(Paper, { id: "includes-submenu", sx: calculateSubMenuStyles(show.includes), elevation: 8, children: _jsx(MenuList, { sx: { p: 0 }, dense: true, role: "group", children: template?.keys.map(key => {
|
|
202
|
+
// Build inclusion query based on current query and field
|
|
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
|
+
}) }) }) })] })] }))] })] }));
|
|
226
|
+
};
|
|
227
|
+
export default HitContextMenu;
|