@cccsaurora/howler-ui 2.17.0-dev.563 → 2.17.0-dev.600

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/api/index.d.ts +2 -0
  2. package/api/index.js +4 -2
  3. package/api/search/case.d.ts +4 -0
  4. package/api/search/case.js +8 -0
  5. package/api/search/index.d.ts +2 -1
  6. package/api/search/index.js +2 -1
  7. package/api/v2/case/index.d.ts +6 -0
  8. package/api/v2/case/index.js +18 -0
  9. package/api/v2/index.d.ts +4 -0
  10. package/api/v2/index.js +6 -0
  11. package/api/v2/search/facet.d.ts +3 -0
  12. package/api/v2/search/facet.js +12 -0
  13. package/api/v2/search/index.d.ts +5 -0
  14. package/api/v2/search/index.js +24 -0
  15. package/commons/components/leftnav/LeftNavDrawer.js +1 -1
  16. package/components/app/App.js +14 -0
  17. package/components/app/providers/FavouritesProvider.js +2 -2
  18. package/components/app/providers/HitSearchProvider.d.ts +0 -1
  19. package/components/app/providers/HitSearchProvider.js +6 -11
  20. package/components/app/providers/HitSearchProvider.test.js +11 -32
  21. package/components/app/providers/ParameterProvider.d.ts +9 -2
  22. package/components/app/providers/ParameterProvider.js +165 -240
  23. package/components/app/providers/ParameterProvider.test.js +307 -14
  24. package/components/{routes/overviews/OverviewEditor.js → elements/MarkdownEditor.js} +3 -3
  25. package/components/elements/ObjectDetails.d.ts +6 -0
  26. package/components/elements/{hit/HitDetails.js → ObjectDetails.js} +17 -17
  27. package/components/elements/PluginTypography.d.ts +2 -1
  28. package/components/elements/PluginTypography.js +3 -2
  29. package/components/elements/UserList.d.ts +5 -2
  30. package/components/elements/UserList.js +14 -5
  31. package/components/elements/addons/search/phrase/Phrase.js +1 -1
  32. package/components/elements/case/CaseCard.d.ts +8 -0
  33. package/components/elements/case/CaseCard.js +39 -0
  34. package/components/elements/case/CasePreview.d.ts +6 -0
  35. package/components/elements/case/CasePreview.js +17 -0
  36. package/components/elements/case/StatusIcon.d.ts +5 -0
  37. package/components/elements/case/StatusIcon.js +13 -0
  38. package/components/elements/display/ChipPopper.d.ts +1 -0
  39. package/components/elements/display/ChipPopper.js +2 -2
  40. package/components/elements/display/HowlerCard.js +1 -1
  41. package/components/elements/display/Modal.js +1 -0
  42. package/components/elements/hit/HitBanner.js +28 -48
  43. package/components/elements/hit/HitCard.js +1 -1
  44. package/components/elements/hit/HitLinks.js +5 -3
  45. package/components/elements/hit/{HitQuickSearch.d.ts → HitPreview.d.ts} +3 -3
  46. package/components/elements/hit/{HitQuickSearch.js → HitPreview.js} +10 -4
  47. package/components/elements/hit/HitRelated.d.ts +1 -1
  48. package/components/elements/hit/HitRelated.js +30 -3
  49. package/components/elements/hit/elements/AnalyticLink.d.ts +8 -0
  50. package/components/elements/hit/elements/AnalyticLink.js +22 -0
  51. package/components/elements/hit/outlines/DefaultOutline.js +1 -1
  52. package/components/elements/hit/related/RelatedRecords.js +63 -0
  53. package/components/elements/observable/ObservableCard.d.ts +5 -0
  54. package/components/elements/observable/ObservableCard.js +7 -0
  55. package/components/elements/observable/ObservablePreview.d.ts +6 -0
  56. package/components/elements/observable/ObservablePreview.js +12 -0
  57. package/components/elements/view/ViewTitle.js +1 -1
  58. package/components/hooks/useHitActions.d.ts +1 -1
  59. package/components/hooks/useHitActions.js +2 -2
  60. package/components/hooks/useHitSelection.js +3 -24
  61. package/components/hooks/useMyPreferences.js +10 -1
  62. package/components/hooks/useMySearch.js +2 -2
  63. package/components/hooks/useMySitemap.js +4 -1
  64. package/components/hooks/useMyTheme.js +9 -2
  65. package/components/hooks/useRelatedRecords.d.ts +13 -0
  66. package/components/hooks/useRelatedRecords.js +32 -0
  67. package/components/routes/action/view/ActionSearch.js +1 -1
  68. package/components/routes/advanced/QueryBuilder.js +1 -1
  69. package/components/routes/analytics/AnalyticDetails.js +2 -2
  70. package/components/routes/analytics/AnalyticSearch.js +1 -1
  71. package/components/routes/cases/CaseViewer.d.ts +2 -0
  72. package/components/routes/cases/CaseViewer.js +24 -0
  73. package/components/routes/cases/Cases.d.ts +2 -0
  74. package/components/routes/cases/Cases.js +101 -0
  75. package/components/routes/cases/constants.d.ts +5 -0
  76. package/components/routes/cases/constants.js +5 -0
  77. package/components/routes/cases/detail/AlertPanel.d.ts +6 -0
  78. package/components/routes/cases/detail/AlertPanel.js +32 -0
  79. package/components/routes/cases/detail/CaseDashboard.d.ts +7 -0
  80. package/components/routes/cases/detail/CaseDashboard.js +49 -0
  81. package/components/routes/cases/detail/CaseDetails.d.ts +6 -0
  82. package/components/routes/cases/detail/CaseDetails.js +61 -0
  83. package/components/routes/cases/detail/CaseOverview.d.ts +7 -0
  84. package/components/routes/cases/detail/CaseOverview.js +43 -0
  85. package/components/routes/cases/detail/CaseSidebar.d.ts +6 -0
  86. package/components/routes/cases/detail/CaseSidebar.js +36 -0
  87. package/components/routes/cases/detail/CaseTask.d.ts +11 -0
  88. package/components/routes/cases/detail/CaseTask.js +57 -0
  89. package/components/routes/cases/detail/ItemPage.d.ts +6 -0
  90. package/components/routes/cases/detail/ItemPage.js +93 -0
  91. package/components/routes/cases/detail/RelatedCasePanel.d.ts +6 -0
  92. package/components/routes/cases/detail/RelatedCasePanel.js +31 -0
  93. package/components/routes/cases/detail/TaskPanel.d.ts +7 -0
  94. package/components/routes/cases/detail/TaskPanel.js +52 -0
  95. package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +12 -0
  96. package/components/routes/cases/detail/aggregates/CaseAggregate.js +19 -0
  97. package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +6 -0
  98. package/components/routes/cases/detail/aggregates/SourceAggregate.js +27 -0
  99. package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +12 -0
  100. package/components/routes/cases/detail/sidebar/CaseFolder.js +179 -0
  101. package/components/routes/cases/detail/sidebar/types.d.ts +3 -0
  102. package/components/routes/cases/hooks/useCase.d.ts +13 -0
  103. package/components/routes/cases/hooks/useCase.js +38 -0
  104. package/components/routes/cases/modals/ResolveModal.d.ts +7 -0
  105. package/components/routes/cases/modals/ResolveModal.js +59 -0
  106. package/components/routes/help/ApiDocumentation.js +1 -1
  107. package/components/routes/help/HitDocumentation.js +1 -3
  108. package/components/routes/hits/search/HitContextMenu.js +3 -2
  109. package/components/routes/hits/search/InformationPane.d.ts +1 -0
  110. package/components/routes/hits/search/InformationPane.js +6 -28
  111. package/components/routes/hits/search/QuerySettings.js +2 -1
  112. package/components/routes/hits/search/QuerySettings.test.js +14 -9
  113. package/components/routes/hits/search/SearchPane.js +7 -32
  114. package/components/routes/hits/search/ViewLink.js +1 -1
  115. package/components/routes/hits/search/grid/EnhancedCell.js +1 -1
  116. package/components/routes/hits/search/shared/IndexPicker.d.ts +2 -0
  117. package/components/routes/hits/search/shared/IndexPicker.js +20 -0
  118. package/components/routes/hits/view/HitViewer.js +3 -4
  119. package/components/routes/home/ViewCard.js +1 -1
  120. package/components/routes/observables/ObservableViewer.d.ts +7 -0
  121. package/components/routes/observables/ObservableViewer.js +27 -0
  122. package/components/routes/overviews/OverviewViewer.js +2 -2
  123. package/locales/en/translation.json +437 -398
  124. package/locales/fr/translation.json +442 -408
  125. package/models/WithMetadata.d.ts +2 -1
  126. package/models/entities/generated/AttachmentsFile.d.ts +12 -0
  127. package/models/entities/generated/Case.d.ts +28 -0
  128. package/models/entities/generated/DestinationOriginal.d.ts +19 -0
  129. package/models/entities/generated/EmailAttachment.d.ts +8 -0
  130. package/models/entities/generated/EmailParent.d.ts +19 -0
  131. package/models/entities/generated/Enrichments.d.ts +7 -0
  132. package/models/entities/generated/EnrichmentsIndicator.d.ts +21 -0
  133. package/models/entities/generated/Howler.d.ts +0 -4
  134. package/models/entities/generated/HttpResponse.d.ts +11 -0
  135. package/models/entities/generated/Item.d.ts +9 -0
  136. package/models/entities/generated/Observable.d.ts +84 -0
  137. package/models/entities/generated/ObservableCloud.d.ts +20 -0
  138. package/models/entities/generated/ObservableDestination.d.ts +23 -0
  139. package/models/entities/generated/ObservableEmail.d.ts +30 -0
  140. package/models/entities/generated/ObservableFile.d.ts +36 -0
  141. package/models/entities/generated/ObservableHowler.d.ts +44 -0
  142. package/models/entities/generated/ObservableHttp.d.ts +11 -0
  143. package/models/entities/generated/ObservableObserver.d.ts +21 -0
  144. package/models/entities/generated/ObservableOrganization.d.ts +7 -0
  145. package/models/entities/generated/ObservableProcess.d.ts +34 -0
  146. package/models/entities/generated/ObservableSource.d.ts +23 -0
  147. package/models/entities/generated/ObservableThreat.d.ts +21 -0
  148. package/models/entities/generated/ObservableTls.d.ts +12 -0
  149. package/models/entities/generated/ObserverIngress.d.ts +9 -0
  150. package/models/entities/generated/Rule.d.ts +2 -10
  151. package/models/entities/generated/Task.d.ts +10 -0
  152. package/models/entities/generated/Threat.d.ts +2 -2
  153. package/models/entities/generated/{Enrichment.d.ts → ThreatEnrichment.d.ts} +1 -1
  154. package/package.json +16 -1
  155. package/plugins/clue/components/ClueTypography.js +2 -2
  156. package/plugins/clue/utils.d.ts +2 -1
  157. package/utils/constants.d.ts +3 -3
  158. package/utils/typeUtils.d.ts +7 -0
  159. package/utils/typeUtils.js +18 -0
  160. package/components/elements/display/icons/BundleButton.d.ts +0 -6
  161. package/components/elements/display/icons/BundleButton.js +0 -32
  162. package/components/routes/help/BundleDocumentation.d.ts +0 -3
  163. package/components/routes/help/BundleDocumentation.js +0 -12
  164. package/components/routes/help/markdown/en/bundles.md.js +0 -1
  165. package/components/routes/help/markdown/fr/bundles.md.js +0 -1
  166. package/components/routes/hits/search/BundleParentMenu.d.ts +0 -6
  167. package/components/routes/hits/search/BundleParentMenu.js +0 -32
  168. /package/components/{routes/overviews/OverviewEditor.d.ts → elements/MarkdownEditor.d.ts} +0 -0
  169. /package/components/elements/hit/{HitDetails.d.ts → related/RelatedRecords.d.ts} +0 -0
package/api/index.d.ts CHANGED
@@ -10,6 +10,7 @@ import * as overview from '@cccsaurora/howler-ui/api/overview';
10
10
  import * as search from '@cccsaurora/howler-ui/api/search';
11
11
  import * as template from '@cccsaurora/howler-ui/api/template';
12
12
  import * as user from '@cccsaurora/howler-ui/api/user';
13
+ import * as v2 from '@cccsaurora/howler-ui/api/v2';
13
14
  import * as view from '@cccsaurora/howler-ui/api/view';
14
15
  /**
15
16
  * Defining the default export exposing all children routes of '/api/v1/'.
@@ -28,6 +29,7 @@ declare const api: {
28
29
  user: typeof user;
29
30
  view: typeof view;
30
31
  notebook: typeof notebook;
32
+ v2: typeof v2;
31
33
  };
32
34
  /**
33
35
  * The specification interface of an Howler HTTP response.
package/api/index.js CHANGED
@@ -10,6 +10,7 @@ import * as overview from '@cccsaurora/howler-ui/api/overview';
10
10
  import * as search from '@cccsaurora/howler-ui/api/search';
11
11
  import * as template from '@cccsaurora/howler-ui/api/template';
12
12
  import * as user from '@cccsaurora/howler-ui/api/user';
13
+ import * as v2 from '@cccsaurora/howler-ui/api/v2';
13
14
  import * as view from '@cccsaurora/howler-ui/api/view';
14
15
  import AxiosClient from '@cccsaurora/howler-ui/rest/AxiosClient';
15
16
  import urlJoin from 'url-join';
@@ -38,7 +39,8 @@ const api = {
38
39
  template,
39
40
  user,
40
41
  view,
41
- notebook
42
+ notebook,
43
+ v2
42
44
  };
43
45
  /**
44
46
  * The base section of the Howler API uri.
@@ -57,7 +59,7 @@ export const uri = () => {
57
59
  * @returns `string` - properly formatted howler uri.
58
60
  */
59
61
  const format = (_uri) => {
60
- return _uri.startsWith(uri()) ? _uri : `${uri()}/${_uri.replace(/\/$/, '')}`;
62
+ return _uri.startsWith('/api') ? _uri : `${uri()}/${_uri.replace(/\/$/, '')}`;
61
63
  };
62
64
  /**
63
65
  * Append series of search parameters to the specified uri.
@@ -0,0 +1,4 @@
1
+ import type { HowlerSearchRequest, HowlerSearchResponse } from '@cccsaurora/howler-ui/api/search';
2
+ import type { Case } from '@cccsaurora/howler-ui/models/entities/generated/Case';
3
+ export declare const uri: () => string;
4
+ export declare const post: (request?: HowlerSearchRequest) => Promise<HowlerSearchResponse<Case>>;
@@ -0,0 +1,8 @@
1
+ import { hpost, joinUri } from '@cccsaurora/howler-ui/api';
2
+ import { uri as parentUri } from '@cccsaurora/howler-ui/api/search';
3
+ export const uri = () => {
4
+ return joinUri(parentUri(), 'case');
5
+ };
6
+ export const post = (request) => {
7
+ return hpost(uri(), { ...(request || {}), query: request?.query || 'case_id:*' });
8
+ };
@@ -1,5 +1,6 @@
1
1
  import * as action from '@cccsaurora/howler-ui/api/search/action';
2
2
  import * as analytic from '@cccsaurora/howler-ui/api/search/analytic';
3
+ import * as case_ from '@cccsaurora/howler-ui/api/search/case';
3
4
  import * as count from '@cccsaurora/howler-ui/api/search/count';
4
5
  import * as dossier from '@cccsaurora/howler-ui/api/search/dossier';
5
6
  import * as facet from '@cccsaurora/howler-ui/api/search/facet';
@@ -60,4 +61,4 @@ export type HowlerExplainSearchResponse = {
60
61
  explanation: string;
61
62
  }[];
62
63
  };
63
- export { action, analytic, count, dossier, facet, fields, grouped, histogram, hit, overview, template, user, view };
64
+ export { action, analytic, case_ as case, count, dossier, facet, fields, grouped, histogram, hit, overview, template, user, view };
@@ -1,6 +1,7 @@
1
1
  import { joinUri, uri as parentUri } from '@cccsaurora/howler-ui/api';
2
2
  import * as action from '@cccsaurora/howler-ui/api/search/action';
3
3
  import * as analytic from '@cccsaurora/howler-ui/api/search/analytic';
4
+ import * as case_ from '@cccsaurora/howler-ui/api/search/case';
4
5
  import * as count from '@cccsaurora/howler-ui/api/search/count';
5
6
  import * as dossier from '@cccsaurora/howler-ui/api/search/dossier';
6
7
  import * as facet from '@cccsaurora/howler-ui/api/search/facet';
@@ -15,4 +16,4 @@ import * as view from '@cccsaurora/howler-ui/api/search/view';
15
16
  export const uri = () => {
16
17
  return joinUri(parentUri(), 'search');
17
18
  };
18
- export { action, analytic, count, dossier, facet, fields, grouped, histogram, hit, overview, template, user, view };
19
+ export { action, analytic, case_ as case, count, dossier, facet, fields, grouped, histogram, hit, overview, template, user, view };
@@ -0,0 +1,6 @@
1
+ import type { Case } from '@cccsaurora/howler-ui/models/entities/generated/Case';
2
+ export declare const uri: (id?: string) => string;
3
+ export declare const get: (id: string) => Promise<Case>;
4
+ export declare const post: (newData: Partial<Case>) => Promise<Case>;
5
+ export declare const put: (id: string, _case: Partial<Case>) => Promise<Case>;
6
+ export declare const del: (id: string) => Promise<void>;
@@ -0,0 +1,18 @@
1
+ // eslint-disable-next-line import/no-cycle
2
+ import { hdelete, hget, hpost, hput, joinAllUri, joinUri } from '@cccsaurora/howler-ui/api';
3
+ import { uri as parentUri } from '@cccsaurora/howler-ui/api/v2';
4
+ export const uri = (id) => {
5
+ return id ? joinAllUri(parentUri(), 'case', id) : joinUri(parentUri(), 'case');
6
+ };
7
+ export const get = (id) => {
8
+ return hget(uri(id));
9
+ };
10
+ export const post = (newData) => {
11
+ return hpost(uri(), newData);
12
+ };
13
+ export const put = (id, _case) => {
14
+ return hput(uri(id), _case);
15
+ };
16
+ export const del = (id) => {
17
+ return hdelete(uri(id));
18
+ };
@@ -0,0 +1,4 @@
1
+ import * as case_ from '@cccsaurora/howler-ui/api/v2/case';
2
+ import * as search from '@cccsaurora/howler-ui/api/v2/search';
3
+ export declare const uri: () => string;
4
+ export { case_ as case, search };
@@ -0,0 +1,6 @@
1
+ import * as case_ from '@cccsaurora/howler-ui/api/v2/case';
2
+ import * as search from '@cccsaurora/howler-ui/api/v2/search';
3
+ export const uri = () => {
4
+ return '/api/v2';
5
+ };
6
+ export { case_ as case, search };
@@ -0,0 +1,3 @@
1
+ import type { HowlerFacetSearchRequest, HowlerFacetSearchResponse } from '@cccsaurora/howler-ui/api/search/facet';
2
+ export declare const uri: (indexes: string[]) => string;
3
+ export declare const post: (indexes: string | string[], request?: HowlerFacetSearchRequest) => Promise<HowlerFacetSearchResponse>;
@@ -0,0 +1,12 @@
1
+ // eslint-disable-next-line import/no-cycle
2
+ import { hpost, joinAllUri } from '@cccsaurora/howler-ui/api';
3
+ import { uri as parentUri } from '@cccsaurora/howler-ui/api/v2';
4
+ export const uri = (indexes) => {
5
+ return joinAllUri(parentUri(), 'search', 'facet', indexes.join(','));
6
+ };
7
+ export const post = (indexes, request) => {
8
+ if (typeof indexes === 'string') {
9
+ indexes = indexes.split(',');
10
+ }
11
+ return hpost(uri(indexes), { ...(request || {}), query: request?.query || 'howler.id:*' });
12
+ };
@@ -0,0 +1,5 @@
1
+ import type { HowlerSearchRequest, HowlerSearchResponse } from '@cccsaurora/howler-ui/api/search';
2
+ import * as facet from './facet';
3
+ export declare const uri: (indexes: string[]) => string;
4
+ export declare const post: <T = any>(indexes: string | string[], request?: HowlerSearchRequest) => Promise<HowlerSearchResponse<T>>;
5
+ export { facet };
@@ -0,0 +1,24 @@
1
+ // eslint-disable-next-line import/no-cycle
2
+ import { hpost, joinAllUri } from '@cccsaurora/howler-ui/api';
3
+ import { uri as parentUri } from '@cccsaurora/howler-ui/api/v2';
4
+ import { identity, isNil } from 'lodash-es';
5
+ import * as facet from './facet';
6
+ export const uri = (indexes) => {
7
+ return joinAllUri(parentUri(), 'search', indexes.join(','));
8
+ };
9
+ export const post = (indexes, request) => {
10
+ if (isNil(indexes)) {
11
+ throw new Error('Indexes cannot be null or undefined.');
12
+ }
13
+ if (typeof indexes === 'string') {
14
+ indexes = indexes.split(',').filter(identity);
15
+ }
16
+ if (indexes.some(index => !['hit', 'observable', 'case'].includes(index))) {
17
+ throw new Error('Only hit, case and observable indexes should be used currently.');
18
+ }
19
+ if (indexes.length < 1) {
20
+ throw new Error('indexes must have length of at least 1.');
21
+ }
22
+ return hpost(uri(indexes), { ...(request || {}), query: request?.query || 'howler.id:*' });
23
+ };
24
+ export { facet };
@@ -75,7 +75,7 @@ const LeftNavDrawer = () => {
75
75
  }
76
76
  }, children: _jsx(AppName, {}) }), !isTopLayout && _jsx(Divider, {})] }));
77
77
  const hide = (_jsx(List, { disablePadding: true, children: _jsxs(ListItemButton, { onClick: leftnav.toggle, children: [_jsx(ListItemIcon, { children: leftnav.open ? _jsx(ChevronLeftIcon, {}) : _jsx(ChevronRightIcon, {}) }), _jsx(ListItemText, { primary: t('drawer.collapse') })] }, "chevron") }));
78
- return (_jsx(ClickAwayListener, { mouseEvent: "onMouseDown", touchEvent: "onTouchStart", onClickAway: onCloseDrawerIfOpen, children: _jsxs(StyledDrawer, { PaperProps: { elevation: 1 }, variant: "permanent", style: { height: '100%' }, width: preferences.leftnav.width, open: leftnav.open, children: [leftnav.open ? (header) : (_jsx(Tooltip, { title: preferences.appName, "aria-label": preferences.appName, placement: "right", children: header })), _jsx(List, { disablePadding: true, children: leftnav.elements.map((e, i) => {
78
+ return (_jsx(ClickAwayListener, { mouseEvent: "onMouseDown", touchEvent: "onTouchStart", onClickAway: onCloseDrawerIfOpen, children: _jsxs(StyledDrawer, { variant: "permanent", style: { height: '100%' }, width: preferences.leftnav.width, open: leftnav.open, children: [leftnav.open ? (header) : (_jsx(Tooltip, { title: preferences.appName, "aria-label": preferences.appName, placement: "right", children: header })), _jsx(List, { disablePadding: true, children: leftnav.elements.map((e, i) => {
79
79
  if (e.type === 'item') {
80
80
  const item = e.element;
81
81
  return _jsx(LeftNavItem, { item: item, onClick: isSmDown && onCloseDrawerIfOpen }, item.id);
@@ -25,6 +25,8 @@ import UserSearchProvider from '@cccsaurora/howler-ui/components/routes/admin/us
25
25
  import QueryBuilder from '@cccsaurora/howler-ui/components/routes/advanced/QueryBuilder';
26
26
  import AnalyticDetails from '@cccsaurora/howler-ui/components/routes/analytics/AnalyticDetails';
27
27
  import AnalyticSearch from '@cccsaurora/howler-ui/components/routes/analytics/AnalyticSearch';
28
+ import CaseViewer from '@cccsaurora/howler-ui/components/routes/cases/CaseViewer';
29
+ import Cases from '@cccsaurora/howler-ui/components/routes/cases/Cases';
28
30
  import DossierEditor from '@cccsaurora/howler-ui/components/routes/dossiers/DossierEditor';
29
31
  import Dossiers from '@cccsaurora/howler-ui/components/routes/dossiers/Dossiers';
30
32
  import ActionDocumentation from '@cccsaurora/howler-ui/components/routes/help/ActionDocumentation';
@@ -185,6 +187,18 @@ const router = createBrowserRouter([
185
187
  path: 'bundles/:id',
186
188
  element: _jsx(HitBrowser, {})
187
189
  },
190
+ {
191
+ path: 'cases',
192
+ element: _jsx(Cases, {})
193
+ },
194
+ {
195
+ path: 'cases/:id',
196
+ element: _jsx(CaseViewer, {})
197
+ },
198
+ {
199
+ path: 'cases/:id/*',
200
+ element: _jsx(CaseViewer, {})
201
+ },
188
202
  {
189
203
  path: 'templates',
190
204
  element: _jsx(Templates, {})
@@ -120,11 +120,11 @@ const FavouriteProvider = ({ children }) => {
120
120
  (async () => {
121
121
  const analyticElement = processAnalyticElement();
122
122
  if (analyticElement) {
123
- newElements.splice(1, 0, analyticElement);
123
+ newElements.splice(2, 0, analyticElement);
124
124
  }
125
125
  const viewElement = await processViewElement();
126
126
  if (viewElement) {
127
- newElements.splice(1, 0, viewElement);
127
+ newElements.splice(2, 0, viewElement);
128
128
  }
129
129
  leftNav.setElements(newElements);
130
130
  })();
@@ -11,7 +11,6 @@ export interface HitSearchContextType {
11
11
  searching: boolean;
12
12
  error: string | null;
13
13
  response: HowlerSearchResponse<WithMetadata<Hit>> | null;
14
- bundleId: string | null;
15
14
  fzfSearch: boolean;
16
15
  setDisplayType: (type: 'list' | 'grid') => void;
17
16
  setFzfSearch: Dispatch<SetStateAction<boolean>>;
@@ -33,6 +33,7 @@ const HitSearchProvider = ({ children }) => {
33
33
  const trackTotalHits = useContextSelector(ParameterContext, ctx => ctx.trackTotalHits);
34
34
  const sort = useContextSelector(ParameterContext, ctx => ctx.sort);
35
35
  const span = useContextSelector(ParameterContext, ctx => ctx.span);
36
+ const indexes = useContextSelector(ParameterContext, ctx => ctx.indexes);
36
37
  const allFilters = useContextSelector(ParameterContext, ctx => ctx.filters);
37
38
  const startDate = useContextSelector(ParameterContext, ctx => ctx.startDate);
38
39
  const endDate = useContextSelector(ParameterContext, ctx => ctx.endDate);
@@ -47,7 +48,6 @@ const HitSearchProvider = ({ children }) => {
47
48
  'howler.id: *': new Date().toISOString()
48
49
  });
49
50
  const [fzfSearch, setFzfSearch] = useState(false);
50
- const bundleId = useMemo(() => (location.pathname.startsWith('/bundles') ? routeParams.id : null), [location.pathname, routeParams.id]);
51
51
  const filters = useMemo(() => allFilters.filter(filter => !filter.endsWith('*')), [allFilters]);
52
52
  // On load check to filter out any queries older than one month
53
53
  useEffect(() => {
@@ -70,11 +70,6 @@ const HitSearchProvider = ({ children }) => {
70
70
  else if (startDate && endDate) {
71
71
  _filters.push(`event.created:${convertCustomDateRangeToLucene(startDate, endDate)}`);
72
72
  }
73
- // Add bundle filter
74
- const bundle = location.pathname.startsWith('/bundles') && routeParams.id;
75
- if (bundle) {
76
- _filters.push(`howler.bundles:${bundle}`);
77
- }
78
73
  // Fetch all view queries
79
74
  if (views.length > 0) {
80
75
  const viewObjects = await getCurrentViews({ views });
@@ -85,7 +80,7 @@ const HitSearchProvider = ({ children }) => {
85
80
  .forEach(viewQuery => _filters.push(viewQuery));
86
81
  }
87
82
  return _filters;
88
- }, [endDate, filters, getCurrentViews, location.pathname, routeParams.id, span, startDate, views]);
83
+ }, [endDate, filters, getCurrentViews, span, startDate, views]);
89
84
  const search = useCallback(async (_query, appendResults) => {
90
85
  THROTTLER.debounce(async () => {
91
86
  if (_query === 'woof!') {
@@ -105,7 +100,7 @@ const HitSearchProvider = ({ children }) => {
105
100
  setSearching(true);
106
101
  setError(null);
107
102
  try {
108
- const _response = await dispatchApi(api.search.hit.post({
103
+ const _response = await dispatchApi(api.v2.search.post(indexes, {
109
104
  offset: appendResults && response ? response.rows : offset,
110
105
  rows: pageCount,
111
106
  query: _query || DEFAULT_QUERY,
@@ -147,6 +142,7 @@ const HitSearchProvider = ({ children }) => {
147
142
  startDate,
148
143
  endDate,
149
144
  filters,
145
+ indexes,
150
146
  setQuery,
151
147
  location.pathname,
152
148
  routeParams.id,
@@ -165,14 +161,14 @@ const HitSearchProvider = ({ children }) => {
165
161
  if (span?.endsWith('custom') && (!startDate || !endDate)) {
166
162
  return;
167
163
  }
168
- if (views.length > 0 || bundleId || (query && query !== DEFAULT_QUERY) || offset > 0 || filters.length > 0) {
164
+ if (views.length > 0 || (query && query !== DEFAULT_QUERY) || offset > 0 || filters.length > 0) {
169
165
  search(query);
170
166
  }
171
167
  else {
172
168
  setResponse(null);
173
169
  }
174
170
  // eslint-disable-next-line react-hooks/exhaustive-deps
175
- }, [offset, pageCount, sort, span, bundleId, location.pathname, startDate, endDate, filters, query, views]);
171
+ }, [offset, pageCount, sort, span, indexes, location.pathname, startDate, endDate, filters, query, views]);
176
172
  return (_jsx(HitSearchContext.Provider, { value: {
177
173
  displayType,
178
174
  setDisplayType,
@@ -181,7 +177,6 @@ const HitSearchProvider = ({ children }) => {
181
177
  getFilters,
182
178
  error,
183
179
  response,
184
- bundleId,
185
180
  setQueryHistory,
186
181
  queryHistory,
187
182
  fzfSearch,
@@ -30,6 +30,7 @@ let mockParameterContext = {
30
30
  mockParameterContext.offset = parseInt(offset);
31
31
  },
32
32
  views: [],
33
+ indexes: ['hit'],
33
34
  addView: vi.fn()
34
35
  };
35
36
  const originalMockParameterContext = cloneDeep(mockParameterContext);
@@ -64,22 +65,14 @@ describe('HitSearchContext', () => {
64
65
  searching: ctx.searching,
65
66
  error: ctx.error,
66
67
  response: ctx.response,
67
- bundleId: ctx.bundleId,
68
68
  fzfSearch: ctx.fzfSearch
69
69
  })), { wrapper: Wrapper });
70
70
  expect(hook.result.current.displayType).toBe('list');
71
71
  expect(hook.result.current.searching).toBe(false);
72
72
  expect(hook.result.current.error).toBeNull();
73
73
  expect(hook.result.current.response).toBeNull();
74
- expect(hook.result.current.bundleId).toBeNull();
75
74
  expect(hook.result.current.fzfSearch).toBe(false);
76
75
  });
77
- it('should set bundleId when on bundles route', () => {
78
- mockLocation.pathname = '/bundles/test_bundle_id';
79
- mockParams.mockReturnValue({ id: 'test_bundle_id' });
80
- const hook = renderHook(() => useContextSelector(HitSearchContext, ctx => ctx.bundleId), { wrapper: Wrapper });
81
- expect(hook.result.current).toBe('test_bundle_id');
82
- });
83
76
  it('should initialize queryHistory from localStorage', () => {
84
77
  const mockHistory = { 'test:query': new Date().toISOString() };
85
78
  mockLocalStorage.setItem(`${MY_LOCAL_STORAGE_PREFIX}.${StorageKey.QUERY_HISTORY}`, JSON.stringify(mockHistory));
@@ -137,7 +130,7 @@ describe('HitSearchContext', () => {
137
130
  hook.result.current.search('test query');
138
131
  });
139
132
  await waitFor(() => {
140
- expect(hpost).toHaveBeenCalledWith('/api/v1/search/hit', expect.objectContaining({
133
+ expect(hpost).toHaveBeenCalledWith('/api/v2/search/hit', expect.objectContaining({
141
134
  query: expect.stringContaining('test query')
142
135
  }));
143
136
  });
@@ -221,27 +214,13 @@ describe('HitSearchContext', () => {
221
214
  expect(hook.result.current.response?.items.length).toBe(2);
222
215
  });
223
216
  });
224
- it('should include bundle filter when on bundles route', async () => {
225
- mockLocation.pathname = '/bundles/test_bundle_id';
226
- mockParams.mockReturnValue({ id: 'test_bundle_id' });
227
- const hook = renderHook(() => useContextSelector(HitSearchContext, ctx => ctx.search), { wrapper: Wrapper });
228
- act(() => {
229
- hook.result.current('test query');
230
- });
231
- await waitFor(() => {
232
- expect(hpost).toHaveBeenCalledWith('/api/v1/search/hit', expect.objectContaining({
233
- query: 'test query',
234
- filters: ['event.created:[now-1w TO now]', 'howler.bundles:test_bundle_id']
235
- }));
236
- });
237
- });
238
217
  it('should apply date range filter from span', async () => {
239
218
  const hook = renderHook(() => useContextSelector(HitSearchContext, ctx => ctx.search), { wrapper: Wrapper });
240
219
  act(() => {
241
220
  hook.result.current('test query');
242
221
  });
243
222
  await waitFor(() => {
244
- expect(hpost).toHaveBeenCalledWith('/api/v1/search/hit', expect.objectContaining({
223
+ expect(hpost).toHaveBeenCalledWith('/api/v2/search/hit', expect.objectContaining({
245
224
  filters: expect.arrayContaining([expect.stringContaining('event.created:')])
246
225
  }));
247
226
  });
@@ -255,7 +234,7 @@ describe('HitSearchContext', () => {
255
234
  hook.result.current('test query');
256
235
  });
257
236
  await waitFor(() => {
258
- expect(hpost).toHaveBeenCalledWith('/api/v1/search/hit', expect.objectContaining({
237
+ expect(hpost).toHaveBeenCalledWith('/api/v2/search/hit', expect.objectContaining({
259
238
  filters: expect.arrayContaining([expect.stringContaining('event.created:')])
260
239
  }));
261
240
  });
@@ -267,7 +246,7 @@ describe('HitSearchContext', () => {
267
246
  hook.result.current('test query');
268
247
  });
269
248
  await waitFor(() => {
270
- expect(hpost).toHaveBeenCalledWith('/api/v1/search/hit', expect.objectContaining({
249
+ expect(hpost).toHaveBeenCalledWith('/api/v2/search/hit', expect.objectContaining({
271
250
  filters: expect.not.arrayContaining([expect.stringContaining('howler.escalation:*')])
272
251
  }));
273
252
  });
@@ -320,7 +299,7 @@ describe('HitSearchContext', () => {
320
299
  expect(hpost).toHaveBeenCalled();
321
300
  }, { timeout: 2000 });
322
301
  });
323
- it('should not trigger search when query is DEFAULT_QUERY and no bundleId', async () => {
302
+ it('should not trigger search when query is DEFAULT_QUERY', async () => {
324
303
  mockParameterContext.query = DEFAULT_QUERY;
325
304
  renderHook(() => useContextSelector(HitSearchContext, ctx => ctx.response), { wrapper: Wrapper });
326
305
  await waitFor(() => {
@@ -358,7 +337,7 @@ describe('HitSearchContext', () => {
358
337
  expect(hpost).toHaveBeenCalledTimes(1);
359
338
  }, { timeout: 2000 });
360
339
  });
361
- it('should clear response when query becomes DEFAULT_QUERY without viewId or bundleId', async () => {
340
+ it('should clear response when query becomes DEFAULT_QUERY without viewId', async () => {
362
341
  const hook = renderHook(() => useContextSelector(HitSearchContext, ctx => ctx.response), { wrapper: Wrapper });
363
342
  await waitFor(() => {
364
343
  expect(hook.result.current).toBeDefined();
@@ -384,7 +363,7 @@ describe('HitSearchContext', () => {
384
363
  hook.result.current('test query');
385
364
  });
386
365
  await waitFor(() => {
387
- expect(hpost).toHaveBeenCalledWith('/api/v1/search/hit', expect.objectContaining({
366
+ expect(hpost).toHaveBeenCalledWith('/api/v2/search/hit', expect.objectContaining({
388
367
  query: 'test query',
389
368
  filters: expect.arrayContaining(['howler.status:open', 'howler.priority:high'])
390
369
  }));
@@ -402,7 +381,7 @@ describe('HitSearchContext', () => {
402
381
  hook.result.current('test query');
403
382
  });
404
383
  await waitFor(() => {
405
- expect(hpost).toHaveBeenCalledWith('/api/v1/search/hit', expect.objectContaining({
384
+ expect(hpost).toHaveBeenCalledWith('/api/v2/search/hit', expect.objectContaining({
406
385
  query: 'test query',
407
386
  filters: [
408
387
  'event.created:[now-1w TO now]',
@@ -459,7 +438,7 @@ describe('HitSearchContext', () => {
459
438
  hook.result.current('test query');
460
439
  });
461
440
  await waitFor(() => {
462
- expect(hpost).toHaveBeenCalledWith('/api/v1/search/hit', expect.objectContaining({
441
+ expect(hpost).toHaveBeenCalledWith('/api/v2/search/hit', expect.objectContaining({
463
442
  query: expect.stringContaining('test query'),
464
443
  filters: ['event.created:[now-1w TO now]']
465
444
  }));
@@ -476,7 +455,7 @@ describe('HitSearchContext', () => {
476
455
  hook.result.current('test query');
477
456
  });
478
457
  await waitFor(() => {
479
- expect(hpost).toHaveBeenCalledWith('/api/v1/search/hit', expect.objectContaining({
458
+ expect(hpost).toHaveBeenCalledWith('/api/v2/search/hit', expect.objectContaining({
480
459
  query: 'test query',
481
460
  filters: ['event.created:[now-1w TO now]', 'howler.status:open', 'howler.priority:high']
482
461
  }));
@@ -1,4 +1,5 @@
1
1
  import type { FC, PropsWithChildren } from 'react';
2
+ export type SearchIndex = 'hit' | 'observable' | 'case';
2
3
  export interface ParameterContextType {
3
4
  selected?: string;
4
5
  query?: string;
@@ -6,6 +7,7 @@ export interface ParameterContextType {
6
7
  trackTotalHits: boolean;
7
8
  sort?: string;
8
9
  span?: string;
10
+ indexes?: SearchIndex[];
9
11
  filters?: string[];
10
12
  startDate?: string;
11
13
  endDate?: string;
@@ -19,11 +21,16 @@ export interface ParameterContextType {
19
21
  addFilter: (filter: string) => void;
20
22
  removeFilter: (filter: string) => void;
21
23
  setFilter: (index: number, filter: string) => void;
22
- clearFilters: () => void;
24
+ resetFilters: () => void;
25
+ addIndex: (index: SearchIndex) => void;
26
+ removeIndex: (index: SearchIndex) => void;
27
+ setIndex: (position: number, index: SearchIndex) => void;
28
+ setIndexes: (indexes: SearchIndex[]) => void;
29
+ resetIndexes: () => void;
23
30
  addView: (view: string) => void;
24
31
  removeView: (view: string) => void;
25
32
  setView: (index: number, view: string) => void;
26
- clearViews: () => void;
33
+ resetViews: () => void;
27
34
  }
28
35
  export declare const ParameterContext: import("use-context-selector").Context<ParameterContextType>;
29
36
  /**