@cccsaurora/howler-ui 2.18.0-dev.736 → 2.18.0-dev.739

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 (274) hide show
  1. package/api/index.d.ts +0 -2
  2. package/api/index.js +2 -4
  3. package/api/search/facet/hit.d.ts +3 -1
  4. package/api/search/facet/index.d.ts +1 -3
  5. package/api/search/index.d.ts +1 -2
  6. package/api/search/index.js +1 -2
  7. package/commons/components/leftnav/LeftNavDrawer.js +1 -1
  8. package/components/app/App.js +7 -39
  9. package/components/app/hooks/useMatchers.d.ts +1 -1
  10. package/components/app/hooks/useMatchers.js +11 -23
  11. package/components/app/hooks/useMatchers.test.js +22 -22
  12. package/components/app/hooks/useTitle.js +3 -3
  13. package/components/app/providers/FavouritesProvider.js +2 -2
  14. package/components/app/providers/HitProvider.d.ts +22 -0
  15. package/components/app/providers/{RecordProvider.js → HitProvider.js} +41 -41
  16. package/components/app/providers/{RecordSearchProvider.d.ts → HitSearchProvider.d.ts} +6 -6
  17. package/components/app/providers/{RecordSearchProvider.js → HitSearchProvider.js} +17 -12
  18. package/components/app/providers/{RecordSearchProvider.test.js → HitSearchProvider.test.js} +70 -51
  19. package/components/app/providers/ModalProvider.d.ts +0 -1
  20. package/components/app/providers/ParameterProvider.d.ts +2 -9
  21. package/components/app/providers/ParameterProvider.js +240 -165
  22. package/components/app/providers/ParameterProvider.test.js +94 -346
  23. package/components/app/providers/UserListProvider.js +8 -28
  24. package/components/elements/PluginTypography.d.ts +1 -2
  25. package/components/elements/PluginTypography.js +2 -3
  26. package/components/elements/UserList.d.ts +2 -5
  27. package/components/elements/UserList.js +8 -18
  28. package/components/elements/addons/search/phrase/Phrase.js +1 -1
  29. package/components/elements/display/ChipPopper.d.ts +1 -1
  30. package/components/elements/display/HowlerCard.js +1 -1
  31. package/components/elements/display/Modal.js +0 -2
  32. package/components/elements/display/icons/BundleButton.d.ts +6 -0
  33. package/components/elements/display/icons/BundleButton.js +32 -0
  34. package/components/elements/hit/HitActions.js +4 -4
  35. package/components/elements/hit/HitBanner.d.ts +0 -1
  36. package/components/elements/hit/HitBanner.js +49 -29
  37. package/components/elements/hit/HitCard.d.ts +0 -2
  38. package/components/elements/hit/HitCard.js +7 -7
  39. package/components/elements/{record/RecordComments.d.ts → hit/HitComments.d.ts} +4 -5
  40. package/components/elements/{record/RecordComments.js → hit/HitComments.js} +28 -29
  41. package/components/elements/{ObjectDetails.js → hit/HitDetails.js} +17 -17
  42. package/components/elements/hit/HitLabels.js +2 -2
  43. package/components/elements/hit/HitOutline.d.ts +0 -1
  44. package/components/elements/hit/HitOutline.js +3 -3
  45. package/components/elements/hit/{HitPreview.d.ts → HitQuickSearch.d.ts} +3 -3
  46. package/components/elements/hit/{HitPreview.js → HitQuickSearch.js} +4 -10
  47. package/components/elements/hit/HitRelated.d.ts +6 -0
  48. package/components/elements/hit/HitRelated.js +7 -0
  49. package/components/elements/hit/HitSummary.d.ts +1 -2
  50. package/components/elements/hit/HitSummary.js +5 -6
  51. package/components/elements/{record/RecordWorklog.d.ts → hit/HitWorklog.d.ts} +3 -4
  52. package/components/elements/{record/RecordWorklog.js → hit/HitWorklog.js} +13 -15
  53. package/components/elements/hit/aggregate/HitGraph.js +8 -8
  54. package/components/elements/hit/outlines/DefaultOutline.js +1 -1
  55. package/components/elements/view/ViewTitle.d.ts +0 -1
  56. package/components/elements/view/ViewTitle.js +2 -9
  57. package/components/hooks/useHitActions.d.ts +1 -1
  58. package/components/hooks/useHitActions.js +4 -4
  59. package/components/hooks/{useRecordSelection.d.ts → useHitSelection.d.ts} +2 -2
  60. package/components/hooks/{useRecordSelection.js → useHitSelection.js} +33 -12
  61. package/components/hooks/useMyPreferences.js +1 -10
  62. package/components/hooks/useMySearch.js +2 -2
  63. package/components/hooks/useMySitemap.js +1 -4
  64. package/components/hooks/useMyTheme.js +2 -9
  65. package/components/hooks/useParamState.test.js +4 -3
  66. package/components/routes/action/edit/ActionEditor.js +2 -2
  67. package/components/routes/action/view/ActionSearch.js +1 -1
  68. package/components/routes/advanced/QueryBuilder.js +1 -1
  69. package/components/routes/advanced/QueryEditor.js +3 -3
  70. package/components/routes/advanced/historyCompletionProvider.js +3 -3
  71. package/components/routes/analytics/AnalyticDetails.js +2 -2
  72. package/components/routes/analytics/AnalyticSearch.js +1 -1
  73. package/components/routes/dossiers/DossierEditor.js +2 -2
  74. package/components/routes/dossiers/DossierEditor.test.js +1 -1
  75. package/components/routes/help/ApiDocumentation.js +1 -1
  76. package/components/routes/help/BundleDocumentation.d.ts +3 -0
  77. package/components/routes/help/BundleDocumentation.js +12 -0
  78. package/components/routes/help/HitBannerDocumentation.js +0 -1
  79. package/components/routes/help/HitDocumentation.js +3 -1
  80. package/components/routes/help/markdown/en/bundles.md.js +1 -0
  81. package/components/routes/help/markdown/fr/bundles.md.js +1 -0
  82. package/components/routes/hits/search/BundleParentMenu.d.ts +6 -0
  83. package/components/routes/hits/search/BundleParentMenu.js +32 -0
  84. package/components/routes/hits/search/BundleScroller.d.ts +2 -0
  85. package/components/routes/hits/search/BundleScroller.js +6 -0
  86. package/components/routes/hits/search/{RecordBrowser.js → HitBrowser.js} +9 -9
  87. package/components/{elements/record/RecordContextMenu.d.ts → routes/hits/search/HitContextMenu.d.ts} +3 -3
  88. package/components/routes/hits/search/HitContextMenu.js +227 -0
  89. package/components/{elements/record/RecordContextMenu.test.js → routes/hits/search/HitContextMenu.test.js} +39 -94
  90. package/components/routes/hits/search/{RecordQuery.d.ts → HitQuery.d.ts} +2 -2
  91. package/components/routes/hits/search/{RecordQuery.js → HitQuery.js} +6 -6
  92. package/components/routes/hits/search/InformationPane.d.ts +0 -1
  93. package/components/routes/hits/search/InformationPane.js +60 -47
  94. package/components/routes/hits/search/LayoutSettings.js +3 -3
  95. package/components/routes/hits/search/QuerySettings.js +1 -2
  96. package/components/routes/hits/search/QuerySettings.test.js +9 -14
  97. package/components/routes/hits/search/SearchPane.js +49 -26
  98. package/components/routes/hits/search/ViewLink.js +3 -3
  99. package/components/routes/hits/search/ViewLink.test.js +8 -8
  100. package/components/routes/hits/search/grid/AddColumnModal.js +4 -5
  101. package/components/routes/hits/search/grid/EnhancedCell.d.ts +1 -2
  102. package/components/routes/hits/search/grid/EnhancedCell.js +2 -2
  103. package/components/routes/hits/search/grid/HitGrid.js +18 -20
  104. package/components/routes/hits/search/grid/{RecordRow.d.ts → HitRow.d.ts} +2 -3
  105. package/components/routes/hits/search/grid/{RecordRow.js → HitRow.js} +8 -10
  106. package/components/routes/hits/view/HitViewer.js +13 -12
  107. package/components/routes/home/ViewCard.js +41 -47
  108. package/components/{elements/MarkdownEditor.js → routes/overviews/OverviewEditor.js} +3 -3
  109. package/components/routes/overviews/OverviewViewer.js +2 -2
  110. package/components/routes/views/ViewComposer.js +19 -46
  111. package/locales/en/translation.json +3 -89
  112. package/locales/fr/translation.json +3 -87
  113. package/models/WithMetadata.d.ts +1 -2
  114. package/models/entities/generated/{ThreatEnrichment.d.ts → Enrichment.d.ts} +1 -1
  115. package/models/entities/generated/Hit.d.ts +0 -1
  116. package/models/entities/generated/Howler.d.ts +4 -0
  117. package/models/entities/generated/Rule.d.ts +10 -2
  118. package/models/entities/generated/Threat.d.ts +2 -2
  119. package/models/entities/generated/View.d.ts +0 -1
  120. package/package.json +103 -120
  121. package/plugins/clue/components/ClueTypography.js +2 -2
  122. package/plugins/clue/utils.d.ts +1 -2
  123. package/tests/mocks.d.ts +1 -11
  124. package/tests/mocks.js +7 -12
  125. package/tests/server-handlers.js +1 -6
  126. package/tests/server.d.ts +1 -1
  127. package/tests/utils.d.ts +0 -4
  128. package/tests/utils.js +0 -20
  129. package/utils/constants.d.ts +3 -3
  130. package/utils/hitFunctions.d.ts +1 -2
  131. package/utils/hitFunctions.js +4 -4
  132. package/utils/viewUtils.js +0 -3
  133. package/api/search/case.d.ts +0 -4
  134. package/api/search/case.js +0 -8
  135. package/api/v2/case/index.d.ts +0 -8
  136. package/api/v2/case/index.js +0 -20
  137. package/api/v2/case/items.d.ts +0 -6
  138. package/api/v2/case/items.js +0 -18
  139. package/api/v2/index.d.ts +0 -4
  140. package/api/v2/index.js +0 -6
  141. package/api/v2/search/facet.d.ts +0 -3
  142. package/api/v2/search/facet.js +0 -12
  143. package/api/v2/search/index.d.ts +0 -5
  144. package/api/v2/search/index.js +0 -24
  145. package/components/app/providers/RecordProvider.d.ts +0 -23
  146. package/components/elements/ContextMenu.d.ts +0 -56
  147. package/components/elements/ContextMenu.js +0 -109
  148. package/components/elements/ContextMenu.test.js +0 -215
  149. package/components/elements/ObjectDetails.d.ts +0 -6
  150. package/components/elements/case/CaseCard.d.ts +0 -12
  151. package/components/elements/case/CaseCard.js +0 -42
  152. package/components/elements/case/CasePreview.d.ts +0 -6
  153. package/components/elements/case/CasePreview.js +0 -17
  154. package/components/elements/case/StatusIcon.d.ts +0 -5
  155. package/components/elements/case/StatusIcon.js +0 -13
  156. package/components/elements/hit/elements/AnalyticLink.d.ts +0 -9
  157. package/components/elements/hit/elements/AnalyticLink.js +0 -22
  158. package/components/elements/hit/related/RelatedRecords.js +0 -63
  159. package/components/elements/observable/ObservableCard.d.ts +0 -6
  160. package/components/elements/observable/ObservableCard.js +0 -22
  161. package/components/elements/observable/ObservablePreview.d.ts +0 -6
  162. package/components/elements/observable/ObservablePreview.js +0 -12
  163. package/components/elements/record/RecordContextMenu.js +0 -247
  164. package/components/elements/record/RecordContextMenu.test.d.ts +0 -1
  165. package/components/elements/record/RecordRelated.d.ts +0 -7
  166. package/components/elements/record/RecordRelated.js +0 -34
  167. package/components/hooks/useRelatedRecords.d.ts +0 -13
  168. package/components/hooks/useRelatedRecords.js +0 -32
  169. package/components/routes/cases/CaseViewer.d.ts +0 -2
  170. package/components/routes/cases/CaseViewer.js +0 -22
  171. package/components/routes/cases/Cases.d.ts +0 -2
  172. package/components/routes/cases/Cases.js +0 -101
  173. package/components/routes/cases/constants.d.ts +0 -5
  174. package/components/routes/cases/constants.js +0 -5
  175. package/components/routes/cases/detail/AlertPanel.d.ts +0 -6
  176. package/components/routes/cases/detail/AlertPanel.js +0 -33
  177. package/components/routes/cases/detail/CaseAssets.d.ts +0 -11
  178. package/components/routes/cases/detail/CaseAssets.js +0 -104
  179. package/components/routes/cases/detail/CaseAssets.test.d.ts +0 -1
  180. package/components/routes/cases/detail/CaseAssets.test.js +0 -167
  181. package/components/routes/cases/detail/CaseDashboard.d.ts +0 -7
  182. package/components/routes/cases/detail/CaseDashboard.js +0 -66
  183. package/components/routes/cases/detail/CaseDetails.d.ts +0 -6
  184. package/components/routes/cases/detail/CaseDetails.js +0 -61
  185. package/components/routes/cases/detail/CaseOverview.d.ts +0 -7
  186. package/components/routes/cases/detail/CaseOverview.js +0 -43
  187. package/components/routes/cases/detail/CaseSidebar.d.ts +0 -8
  188. package/components/routes/cases/detail/CaseSidebar.js +0 -107
  189. package/components/routes/cases/detail/CaseSidebar.test.d.ts +0 -1
  190. package/components/routes/cases/detail/CaseSidebar.test.js +0 -246
  191. package/components/routes/cases/detail/CaseTask.d.ts +0 -11
  192. package/components/routes/cases/detail/CaseTask.js +0 -57
  193. package/components/routes/cases/detail/CaseTimeline.d.ts +0 -12
  194. package/components/routes/cases/detail/CaseTimeline.js +0 -106
  195. package/components/routes/cases/detail/CaseTimeline.test.d.ts +0 -1
  196. package/components/routes/cases/detail/CaseTimeline.test.js +0 -227
  197. package/components/routes/cases/detail/ItemPage.d.ts +0 -6
  198. package/components/routes/cases/detail/ItemPage.js +0 -99
  199. package/components/routes/cases/detail/RelatedCasePanel.d.ts +0 -6
  200. package/components/routes/cases/detail/RelatedCasePanel.js +0 -34
  201. package/components/routes/cases/detail/TaskPanel.d.ts +0 -7
  202. package/components/routes/cases/detail/TaskPanel.js +0 -52
  203. package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +0 -11
  204. package/components/routes/cases/detail/aggregates/CaseAggregate.js +0 -24
  205. package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +0 -6
  206. package/components/routes/cases/detail/aggregates/SourceAggregate.js +0 -26
  207. package/components/routes/cases/detail/assets/Asset.d.ts +0 -14
  208. package/components/routes/cases/detail/assets/Asset.js +0 -12
  209. package/components/routes/cases/detail/assets/Asset.test.d.ts +0 -1
  210. package/components/routes/cases/detail/assets/Asset.test.js +0 -72
  211. package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +0 -20
  212. package/components/routes/cases/detail/sidebar/CaseFolder.js +0 -83
  213. package/components/routes/cases/detail/sidebar/CaseFolder.test.d.ts +0 -1
  214. package/components/routes/cases/detail/sidebar/CaseFolder.test.js +0 -295
  215. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.d.ts +0 -34
  216. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.js +0 -103
  217. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.d.ts +0 -1
  218. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.js +0 -363
  219. package/components/routes/cases/detail/sidebar/FolderEntry.d.ts +0 -25
  220. package/components/routes/cases/detail/sidebar/FolderEntry.js +0 -88
  221. package/components/routes/cases/detail/sidebar/FolderEntry.test.d.ts +0 -1
  222. package/components/routes/cases/detail/sidebar/FolderEntry.test.js +0 -206
  223. package/components/routes/cases/detail/sidebar/RootDropZone.d.ts +0 -5
  224. package/components/routes/cases/detail/sidebar/RootDropZone.js +0 -33
  225. package/components/routes/cases/detail/sidebar/types.d.ts +0 -9
  226. package/components/routes/cases/detail/sidebar/utils.d.ts +0 -3
  227. package/components/routes/cases/detail/sidebar/utils.js +0 -29
  228. package/components/routes/cases/detail/sidebar/utils.test.d.ts +0 -1
  229. package/components/routes/cases/detail/sidebar/utils.test.js +0 -82
  230. package/components/routes/cases/hooks/useCase.d.ts +0 -13
  231. package/components/routes/cases/hooks/useCase.js +0 -51
  232. package/components/routes/cases/modals/AddToCaseModal.d.ts +0 -7
  233. package/components/routes/cases/modals/AddToCaseModal.js +0 -62
  234. package/components/routes/cases/modals/RenameItemModal.d.ts +0 -9
  235. package/components/routes/cases/modals/RenameItemModal.js +0 -48
  236. package/components/routes/cases/modals/ResolveModal.d.ts +0 -7
  237. package/components/routes/cases/modals/ResolveModal.js +0 -115
  238. package/components/routes/cases/modals/ResolveModal.test.d.ts +0 -1
  239. package/components/routes/cases/modals/ResolveModal.test.js +0 -384
  240. package/components/routes/hits/search/shared/IndexPicker.d.ts +0 -2
  241. package/components/routes/hits/search/shared/IndexPicker.js +0 -20
  242. package/components/routes/observables/ObservableViewer.d.ts +0 -7
  243. package/components/routes/observables/ObservableViewer.js +0 -27
  244. package/models/entities/generated/AttachmentsFile.d.ts +0 -12
  245. package/models/entities/generated/Case.d.ts +0 -28
  246. package/models/entities/generated/DestinationOriginal.d.ts +0 -19
  247. package/models/entities/generated/EmailAttachment.d.ts +0 -8
  248. package/models/entities/generated/EmailParent.d.ts +0 -19
  249. package/models/entities/generated/Enrichments.d.ts +0 -7
  250. package/models/entities/generated/EnrichmentsIndicator.d.ts +0 -21
  251. package/models/entities/generated/HttpResponse.d.ts +0 -11
  252. package/models/entities/generated/Item.d.ts +0 -9
  253. package/models/entities/generated/Observable.d.ts +0 -85
  254. package/models/entities/generated/ObservableCloud.d.ts +0 -20
  255. package/models/entities/generated/ObservableDestination.d.ts +0 -23
  256. package/models/entities/generated/ObservableEmail.d.ts +0 -30
  257. package/models/entities/generated/ObservableFile.d.ts +0 -36
  258. package/models/entities/generated/ObservableHowler.d.ts +0 -43
  259. package/models/entities/generated/ObservableHttp.d.ts +0 -11
  260. package/models/entities/generated/ObservableObserver.d.ts +0 -21
  261. package/models/entities/generated/ObservableOrganization.d.ts +0 -7
  262. package/models/entities/generated/ObservableProcess.d.ts +0 -34
  263. package/models/entities/generated/ObservableSource.d.ts +0 -23
  264. package/models/entities/generated/ObservableThreat.d.ts +0 -21
  265. package/models/entities/generated/ObservableTls.d.ts +0 -12
  266. package/models/entities/generated/ObserverIngress.d.ts +0 -9
  267. package/models/entities/generated/Task.d.ts +0 -10
  268. package/utils/typeUtils.d.ts +0 -7
  269. package/utils/typeUtils.js +0 -27
  270. /package/components/app/providers/{RecordSearchProvider.test.d.ts → HitSearchProvider.test.d.ts} +0 -0
  271. /package/components/elements/hit/{related/RelatedRecords.d.ts → HitDetails.d.ts} +0 -0
  272. /package/components/routes/hits/search/{RecordBrowser.d.ts → HitBrowser.d.ts} +0 -0
  273. /package/components/{elements/ContextMenu.test.d.ts → routes/hits/search/HitContextMenu.test.d.ts} +0 -0
  274. /package/components/{elements/MarkdownEditor.d.ts → routes/overviews/OverviewEditor.d.ts} +0 -0
@@ -10,13 +10,12 @@ import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
10
10
  import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
11
11
  import { useTranslation } from 'react-i18next';
12
12
  import { useNavigate } from 'react-router-dom';
13
- import { isHit } from '@cccsaurora/howler-ui/utils/typeUtils';
14
13
  import { compareTimestamp, sortByTimestamp } from '@cccsaurora/howler-ui/utils/utils';
15
14
  import Comment from '../Comment';
16
15
  import HowlerAvatar from '../display/HowlerAvatar';
17
16
  import TypingIndicator from '../display/TypingIndicator';
18
17
  const MAX_LENGTH = 5000;
19
- const RecordComments = ({ record, users }) => {
18
+ const HitComments = ({ hit, users }) => {
20
19
  const { user } = useAppUser();
21
20
  const { t } = useTranslation();
22
21
  const navigate = useNavigate();
@@ -29,7 +28,7 @@ const RecordComments = ({ record, users }) => {
29
28
  const [length, setLength] = useState(0);
30
29
  const [analyticId, setAnalyticId] = useState();
31
30
  const [analyticComments, setAnalyticComments] = useState([]);
32
- const [comments, setComments] = useState(sortByTimestamp(record?.howler?.comment));
31
+ const [comments, setComments] = useState(sortByTimestamp(hit?.howler?.comment));
33
32
  const input = useRef();
34
33
  /**
35
34
  * Set the list of typers based on updates from the websocket
@@ -51,20 +50,20 @@ const RecordComments = ({ record, users }) => {
51
50
  // eslint-disable-next-line react-hooks/exhaustive-deps
52
51
  }, [handler]);
53
52
  useEffect(() => {
54
- if (isHit(record) && record.howler.analytic) {
55
- getMatchingAnalytic(record).then(analytic => {
53
+ if (hit?.howler?.analytic) {
54
+ getMatchingAnalytic(hit).then(analytic => {
56
55
  setAnalyticId(analytic?.analytic_id);
57
56
  setAnalyticComments(sortByTimestamp(analytic?.comment ?? []));
58
57
  });
59
58
  }
60
59
  // eslint-disable-next-line react-hooks/exhaustive-deps
61
- }, [getMatchingAnalytic, record]);
60
+ }, [getMatchingAnalytic, hit?.howler?.analytic]);
62
61
  const onSubmit = useCallback(async () => {
63
- if (!input.current?.value || !record || input.current.value.length > MAX_LENGTH)
62
+ if (!input.current?.value || !hit || input.current.value.length > MAX_LENGTH)
64
63
  return;
65
64
  setLoading(true);
66
65
  try {
67
- const result = await dispatchApi(api.hit.comments.post(record.howler.id, input.current.value), {
66
+ const result = await dispatchApi(api.hit.comments.post(hit.howler.id, input.current.value), {
68
67
  showError: true,
69
68
  throwError: true,
70
69
  logError: false
@@ -76,23 +75,23 @@ const RecordComments = ({ record, users }) => {
76
75
  finally {
77
76
  setLoading(false);
78
77
  }
79
- }, [dispatchApi, record]);
78
+ }, [dispatchApi, hit]);
80
79
  /**
81
80
  * Emit a typing event when textbox is focused
82
81
  */
83
82
  const onFocus = useCallback(() => emit({
84
83
  broadcast: true,
85
84
  action: 'typing',
86
- id: record?.howler?.id
87
- }), [emit, record?.howler?.id]);
85
+ id: hit?.howler?.id
86
+ }), [emit, hit?.howler?.id]);
88
87
  /**
89
88
  * Emit a stop typing event when textbox is blurred
90
89
  */
91
90
  const onBlur = useCallback(() => emit({
92
91
  broadcast: true,
93
92
  action: 'stop_typing',
94
- id: record?.howler?.id
95
- }), [emit, record?.howler?.id]);
93
+ id: hit?.howler?.id
94
+ }), [emit, hit?.howler?.id]);
96
95
  const onClear = useCallback(() => {
97
96
  input.current.value = '';
98
97
  setShowClear(false);
@@ -109,16 +108,16 @@ const RecordComments = ({ record, users }) => {
109
108
  }, [loading, onSubmit]);
110
109
  const checkLength = useCallback(() => setLength(input.current?.value.length), []);
111
110
  const handleDelete = useCallback(async (commentId) => {
112
- await dispatchApi(api.hit.comments.del(record.howler.id, [commentId]), { throwError: true, showError: true });
111
+ await dispatchApi(api.hit.comments.del(hit.howler.id, [commentId]), { throwError: true, showError: true });
113
112
  setComments(comments.filter(cmt => cmt.id !== commentId));
114
- }, [comments, dispatchApi, record?.howler?.id]);
113
+ }, [comments, dispatchApi, hit?.howler?.id]);
115
114
  const handleEdit = useCallback(async (commentId, editValue) => {
116
- await dispatchApi(api.hit.comments.put(record.howler.id, commentId, editValue), {
115
+ await dispatchApi(api.hit.comments.put(hit.howler.id, commentId, editValue), {
117
116
  throwError: true,
118
117
  showError: true
119
118
  });
120
119
  setComments(comments.map(cmt => (cmt.id !== commentId ? cmt : { ...cmt, value: editValue })));
121
- }, [comments, dispatchApi, record?.howler?.id]);
120
+ }, [comments, dispatchApi, hit?.howler?.id]);
122
121
  const handleQuote = useCallback((value) => {
123
122
  if (input.current) {
124
123
  // We use trimStart here so there isn't a bunch of newlines at the beginning of the comment
@@ -136,27 +135,27 @@ const RecordComments = ({ record, users }) => {
136
135
  }, []);
137
136
  const handleReact = useCallback(async (commentId, type) => {
138
137
  if (type) {
139
- await dispatchApi(api.hit.comments.react.put(record.howler.id, commentId, type));
138
+ await dispatchApi(api.hit.comments.react.put(hit.howler.id, commentId, type));
140
139
  setComments(comments.map(cmt => cmt.id !== commentId ? cmt : { ...cmt, reactions: { ...(cmt?.reactions ?? {}), [user.username]: type } }));
141
140
  }
142
141
  else {
143
- await dispatchApi(api.hit.comments.react.del(record.howler.id, commentId));
142
+ await dispatchApi(api.hit.comments.react.del(hit.howler.id, commentId));
144
143
  setComments(comments.map(cmt => cmt.id !== commentId
145
144
  ? cmt
146
145
  : { ...cmt, reactions: { ...(cmt?.reactions ?? {}), [user.username]: undefined } }));
147
146
  }
148
- }, [comments, dispatchApi, record?.howler.id, user.username]);
147
+ }, [comments, dispatchApi, hit?.howler.id, user.username]);
149
148
  /**
150
149
  * Handle loading the comments when the hit changes
151
150
  */
152
151
  useEffect(() => {
153
- if (record?.howler?.comment) {
154
- setComments(record?.howler?.comment.slice().sort((a, b) => compareTimestamp(b.timestamp, a.timestamp)));
152
+ if (hit?.howler?.comment) {
153
+ setComments(hit?.howler?.comment.slice().sort((a, b) => compareTimestamp(b.timestamp, a.timestamp)));
155
154
  }
156
- else if (!record) {
155
+ else if (!hit) {
157
156
  setComments([]);
158
157
  }
159
- }, [record]);
158
+ }, [hit]);
160
159
  /**
161
160
  * This is the comments for the analytic associated with the hit. We show this at the start of the comment
162
161
  * list, as if they've been pinned
@@ -165,13 +164,13 @@ const RecordComments = ({ record, users }) => {
165
164
  if (analyticComments.length < 1) {
166
165
  return null;
167
166
  }
168
- const displayedComments = analyticComments.filter(c => !c.detection || c.detection === record?.howler.detection);
167
+ const displayedComments = analyticComments.filter(c => !c.detection || c.detection === hit?.howler.detection);
169
168
  return (_jsxs(Accordion, { variant: "outlined", children: [_jsx(AccordionSummary, { expandIcon: _jsx(KeyboardArrowDown, { fontSize: "small" }), children: _jsx(Typography, { variant: "body1", children: t('comments.analytic', { count: displayedComments.length }) }) }), _jsx(AccordionDetails, { children: _jsx(Stack, { spacing: 1, children: displayedComments.map(c => (_jsx(Comment, { comment: c, extra: _jsx(Chip, { size: "small", variant: "outlined", onClick: () => navigate('/analytics' +
170
169
  (analyticId
171
170
  ? `/${analyticId}?tab=comments` + (c.detection ? `&filter=${c.detection}` : '')
172
- : '')), sx: theme => ({ marginLeft: '0 !important', mr: `${theme.spacing(2)} !important` }), label: `${record?.howler?.analytic ?? 'Analytic'}${c.detection ? ' - ' + c.detection : ''}` }), users: users }, c.id))) }) })] }));
173
- }, [analyticComments, analyticId, record?.howler.analytic, record?.howler.detection, navigate, t, users]);
171
+ : '')), sx: theme => ({ marginLeft: '0 !important', mr: `${theme.spacing(2)} !important` }), label: `${hit?.howler?.analytic ?? 'Analytic'}${c.detection ? ' - ' + c.detection : ''}` }), users: users }, c.id))) }) })] }));
172
+ }, [analyticComments, analyticId, hit?.howler.analytic, hit?.howler.detection, navigate, t, users]);
174
173
  const renderedComments = useMemo(() => comments.map(c => (_jsx(Comment, { comment: c, users: users, handleDelete: () => handleDelete(c.id), handleEdit: value => handleEdit(c.id, value), handleReact: type => handleReact(c.id, type), handleQuote: () => handleQuote(c.value) }, c.id))), [comments, handleDelete, handleEdit, handleQuote, handleReact, users]);
175
- return (_jsxs(Stack, { sx: { py: 1, pr: 1 }, spacing: 1, children: [record && renderedAnalyticComments, _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(HowlerAvatar, { userId: user.username }), _jsx(TextField, { inputProps: { sx: theme => ({ fontSize: theme.typography.body2.fontSize }) }, InputLabelProps: { shrink: false }, placeholder: t('comments.add'), onKeyDown: checkForSubmit, onChangeCapture: checkLength, inputRef: input, onFocus: onFocus, onBlur: onBlur, error: length > MAX_LENGTH, fullWidth: true, multiline: true })] }), _jsxs(Stack, { direction: "row", alignItems: "center", children: [typers.length > 0 && (_jsxs(_Fragment, { children: [_jsx(AvatarGroup, { componentsProps: { additionalAvatar: { sx: { height: 24, width: 24, fontSize: '12px' } } }, children: typers.map(typer => (_jsx(HowlerAvatar, { userId: typer, sx: { height: 24, width: 24 } }, typer))) }), _jsx(TypingIndicator, {})] })), _jsx(FlexOne, {}), length > 0.9 * MAX_LENGTH && (_jsxs(Typography, { variant: "caption", sx: [{ opacity: 0.7, mr: 1 }, length > MAX_LENGTH && { color: 'error.main' }], children: [length, "/", MAX_LENGTH] })), showClear && (_jsx(IconButton, { size: "small", onClick: onClear, disabled: loading, children: _jsx(Clear, { fontSize: "small" }) })), _jsx(IconButton, { size: "small", onClick: onSubmit, disabled: loading || length > MAX_LENGTH, children: _jsx(Send, { fontSize: "small" }) })] }), record ? (renderedComments) : (_jsxs(_Fragment, { children: [_jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Skeleton, { width: 40, height: 40, variant: "circular" }), _jsx(Skeleton, { width: "100%", height: 80, variant: "rounded" })] }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Skeleton, { width: 40, height: 40, variant: "circular" }), _jsx(Skeleton, { width: "100%", height: 100, variant: "rounded" })] }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Skeleton, { width: 40, height: 40, variant: "circular" }), _jsx(Skeleton, { width: "100%", height: 80, variant: "rounded" })] })] }))] }));
174
+ return (_jsxs(Stack, { sx: { py: 1, pr: 1 }, spacing: 1, children: [hit && renderedAnalyticComments, _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(HowlerAvatar, { userId: user.username }), _jsx(TextField, { inputProps: { sx: theme => ({ fontSize: theme.typography.body2.fontSize }) }, InputLabelProps: { shrink: false }, placeholder: t('comments.add'), onKeyDown: checkForSubmit, onChangeCapture: checkLength, inputRef: input, onFocus: onFocus, onBlur: onBlur, error: length > MAX_LENGTH, fullWidth: true, multiline: true })] }), _jsxs(Stack, { direction: "row", alignItems: "center", children: [typers.length > 0 && (_jsxs(_Fragment, { children: [_jsx(AvatarGroup, { componentsProps: { additionalAvatar: { sx: { height: 24, width: 24, fontSize: '12px' } } }, children: typers.map(typer => (_jsx(HowlerAvatar, { userId: typer, sx: { height: 24, width: 24 } }, typer))) }), _jsx(TypingIndicator, {})] })), _jsx(FlexOne, {}), length > 0.9 * MAX_LENGTH && (_jsxs(Typography, { variant: "caption", sx: [{ opacity: 0.7, mr: 1 }, length > MAX_LENGTH && { color: 'error.main' }], children: [length, "/", MAX_LENGTH] })), showClear && (_jsx(IconButton, { size: "small", onClick: onClear, disabled: loading, children: _jsx(Clear, { fontSize: "small" }) })), _jsx(IconButton, { size: "small", onClick: onSubmit, disabled: loading || length > MAX_LENGTH, children: _jsx(Send, { fontSize: "small" }) })] }), hit ? (renderedComments) : (_jsxs(_Fragment, { children: [_jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Skeleton, { width: 40, height: 40, variant: "circular" }), _jsx(Skeleton, { width: "100%", height: 80, variant: "rounded" })] }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Skeleton, { width: 40, height: 40, variant: "circular" }), _jsx(Skeleton, { width: "100%", height: 100, variant: "rounded" })] }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Skeleton, { width: 40, height: 40, variant: "circular" }), _jsx(Skeleton, { width: "100%", height: 80, variant: "rounded" })] })] }))] }));
176
175
  };
177
- export default RecordComments;
176
+ export default HitComments;
@@ -5,12 +5,12 @@ import { ArrowDropDown, InfoOutlined } from '@mui/icons-material';
5
5
  import { Accordion, AccordionDetails, AccordionSummary, Box, Divider, Grid, Stack, TextField, Tooltip, Typography, useTheme } from '@mui/material';
6
6
  import { flatten } from 'flat';
7
7
  import Fuse from 'fuse.js';
8
- import { capitalize, groupBy, isArray, isBoolean, isEmpty, isNull, isNumber, isObject, isPlainObject, isUndefined, max, sortBy, uniq } from 'lodash-es';
8
+ import { capitalize, groupBy, isArray, isEmpty, isNull, isObject, isPlainObject, isUndefined, max, sortBy, uniq } from 'lodash-es';
9
9
  import { memo, useEffect, useMemo, useState } from 'react';
10
10
  import { useTranslation } from 'react-i18next';
11
11
  import Throttler from '@cccsaurora/howler-ui/utils/Throttler';
12
- import PluginTypography from './PluginTypography';
13
- const ListRenderer = memo(({ obj, objKey: key, entries, maxKeyLength }) => {
12
+ import PluginTypography from '../PluginTypography';
13
+ const ListRenderer = memo(({ hit, objKey: key, entries, maxKeyLength }) => {
14
14
  const theme = useTheme();
15
15
  const { t } = useTranslation();
16
16
  const allPrimitives = useMemo(() => entries.every(entry => !isObject(entry)), [entries]);
@@ -36,15 +36,15 @@ const ListRenderer = memo(({ obj, objKey: key, entries, maxKeyLength }) => {
36
36
  marginBottom: allPrimitives ? 0 : theme.spacing(1)
37
37
  }, children: allPrimitives ? key.padStart(maxKeyLength ?? key.length) : key }) }), _jsxs(Grid, { container: true, spacing: allPrimitives ? 1 : 4, ml: allPrimitives ? -1 : -4, overflow: "hidden", maxWidth: "100%", children: [uniqueEntries.map((entry, index) => {
38
38
  if (Array.isArray(entry)) {
39
- return (_jsx(Grid, { item: true, xs: "auto", maxWidth: "100%", children: _jsx(ListRenderer, { obj: obj, objKey: `${key}.${index}`, entries: entry }) }, index));
39
+ return (_jsx(Grid, { item: true, xs: "auto", maxWidth: "100%", children: _jsx(ListRenderer, { hit: hit, objKey: `${key}.${index}`, entries: entry }) }, index));
40
40
  }
41
41
  if (isPlainObject(entry)) {
42
42
  return (_jsx(Grid, { item: true, xs: 'auto', maxWidth: "100%", minWidth: "350px", children: _jsx(ObjectRenderer, { parentKey: `${key}.${index}`, indent: true, data: entry }) }, index));
43
43
  }
44
- return (_jsxs(Grid, { item: true, maxWidth: "100%", className: `${key}_${index}`.replace(/\./g, '_'), component: "code", display: "flex", flexDirection: "row", children: [_jsx(PluginTypography, { context: "details", component: "code", style: { maxWidth: '100%', font: 'inherit' }, value: entry, field: key.replace(/\.[0-9]+/g, ''), obj: obj, children: entry }), allPrimitives && index < uniqueEntries.length - 1 && _jsx("span", { children: "," })] }, entry));
44
+ return (_jsxs(Grid, { item: true, maxWidth: "100%", className: `${key}_${index}`.replace(/\./g, '_'), component: "code", display: "flex", flexDirection: "row", children: [_jsx(PluginTypography, { context: "details", component: "code", style: { maxWidth: '100%', font: 'inherit' }, value: entry, field: key.replace(/\.[0-9]+/g, ''), hit: hit, children: entry }), allPrimitives && index < uniqueEntries.length - 1 && _jsx("span", { children: "," })] }, entry));
45
45
  }), omittedDuplicates && (_jsx(Grid, { item: true, display: "flex", alignItems: "center", children: _jsx(Tooltip, { title: t('duplicates.omitted'), children: _jsx(InfoOutlined, { sx: { fontSize: '20px', ml: 1 }, color: "disabled" }) }) }))] })] }));
46
46
  });
47
- const ObjectRenderer = memo(({ obj: obj, data, parentKey, indent = false }) => {
47
+ const ObjectRenderer = memo(({ hit, data, parentKey, indent = false }) => {
48
48
  const theme = useTheme();
49
49
  const entries = useMemo(() => {
50
50
  const unsorted = Object.entries(flatten(data, { safe: true })).map(([key, val]) => [key, val]);
@@ -56,10 +56,10 @@ const ObjectRenderer = memo(({ obj: obj, data, parentKey, indent = false }) => {
56
56
  }, [data]);
57
57
  const longestKey = useMemo(() => max(entries.map(([key]) => key.length)), [entries]);
58
58
  return (_jsxs(Stack, { direction: "row", overflow: "hidden", maxWidth: "100%", children: [indent && _jsx(Divider, { orientation: "vertical", flexItem: true, sx: { borderColor: 'primary.main', borderWidth: '2px' } }), _jsx(Stack, { flex: 1, ml: 1, maxWidth: "100%", children: entries
59
- .filter(([__, val]) => !isNull(val) && !isUndefined(val) && !isEmpty(val) && !isBoolean(val) && !isNumber(val))
59
+ .filter(([__, val]) => !isNull(val) && !isUndefined(val) && !isEmpty(val))
60
60
  .map(([key, val]) => {
61
61
  if (Array.isArray(val)) {
62
- return _jsx(ListRenderer, { obj: obj, maxKeyLength: longestKey, objKey: key, entries: val }, key);
62
+ return _jsx(ListRenderer, { hit: hit, maxKeyLength: longestKey, objKey: key, entries: val }, key);
63
63
  }
64
64
  return (_jsxs("code", { className: (parentKey ? `${parentKey}.${key}` : key).replace(/\./g, '_'), style: {
65
65
  display: 'grid',
@@ -75,14 +75,14 @@ const ObjectRenderer = memo(({ obj: obj, data, parentKey, indent = false }) => {
75
75
  paddingRight: theme.spacing(1),
76
76
  height: '100%',
77
77
  wordWrap: 'break-word'
78
- }, children: _jsx("code", { style: { maxWidth: '100%' }, children: key }) }), _jsx(Box, { display: "flex", alignItems: "start", children: _jsx(PluginTypography, { context: "details", component: "code", style: { maxWidth: '100%', font: 'inherit' }, value: val, field: (parentKey ? parentKey.concat('.', key) : key).replace(/\.[0-9]+/g, ''), obj: obj, children: val }) })] }, key));
78
+ }, children: _jsx("code", { style: { maxWidth: '100%' }, children: key }) }), _jsx(Box, { display: "flex", alignItems: "start", children: _jsx(PluginTypography, { context: "details", component: "code", style: { maxWidth: '100%', font: 'inherit' }, value: val, field: (parentKey ? parentKey.concat('.', key) : key).replace(/\.[0-9]+/g, ''), hit: hit, children: val }) })] }, key));
79
79
  }) })] }));
80
80
  });
81
- const Collapsible = memo(({ obj, title, data, query }) => {
81
+ const Collapsible = memo(({ hit, title, data, query }) => {
82
82
  const throttler = useMemo(() => new Throttler(400), []);
83
83
  const [scores, setScores] = useState([]);
84
84
  const [results, setResults] = useState({});
85
- const options = useMemo(() => Object.entries(data).map(([key, value]) => ({ key, value: value.toString() })), [data]);
85
+ const options = useMemo(() => Object.entries(data).map(([key, value]) => ({ key, value })), [data]);
86
86
  const keys = useMemo(() => options
87
87
  .flatMap(option => (isArray(option.value) ? Object.keys(flatten(option.value)) : []))
88
88
  .map(key => key.replace(/\d+/g, 'value'))
@@ -109,20 +109,20 @@ const Collapsible = memo(({ obj, title, data, query }) => {
109
109
  if (isEmpty(results)) {
110
110
  return null;
111
111
  }
112
- return (_jsxs(Accordion, { defaultExpanded: true, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ArrowDropDown, {}), sx: { my: 0 }, children: _jsx(Typography, { children: title }) }), _jsx(AccordionDetails, { children: _jsx(Stack, { spacing: 0.5, justifyContent: "stretch", sx: styles, children: _jsx(ObjectRenderer, { obj: obj, showParentKey: true, data: results }) }) })] }));
112
+ return (_jsxs(Accordion, { defaultExpanded: true, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ArrowDropDown, {}), children: _jsx(Typography, { children: title }) }), _jsx(AccordionDetails, { children: _jsx(Stack, { spacing: 1, justifyContent: "stretch", sx: styles, children: _jsx(ObjectRenderer, { hit: hit, showParentKey: true, data: results }) }) })] }));
113
113
  });
114
- const ObjectDetails = ({ obj }) => {
114
+ const HitDetails = ({ hit }) => {
115
115
  const { t } = useTranslation();
116
116
  const [query, setQuery] = useState('');
117
- const groups = useMemo(() => groupBy(Object.entries(flatten(obj ?? {}, { safe: true })).filter(([key, value]) => !key.startsWith('__') &&
117
+ const groups = useMemo(() => groupBy(Object.entries(flatten(hit ?? {}, { safe: true })).filter(([key, value]) => !key.startsWith('__') &&
118
118
  key.includes('.') &&
119
119
  ['howler', 'labels'].every(prefix => !key.startsWith(prefix)) &&
120
- (!isEmpty(value) || isNumber(value) || isBoolean(value))), ([key]) => key.split('.')[0]), [obj]);
120
+ !isEmpty(value)), ([key]) => key.split('.')[0]), [hit]);
121
121
  return (_jsxs(Stack, { spacing: 1, children: [_jsx(TextField, { value: query, onChange: event => setQuery(event.target.value), label: t('overview.search') }), Object.entries(groups).map(([section, entries]) => {
122
- return (_jsx(Collapsible, { obj: obj, query: query, title: section
122
+ return (_jsx(Collapsible, { hit: hit, query: query, title: section
123
123
  .split('_')
124
124
  .map(word => capitalize(word))
125
125
  .join(' '), data: Object.fromEntries(entries) }, section));
126
126
  })] }));
127
127
  };
128
- export default memo(ObjectDetails);
128
+ export default memo(HitDetails);
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Add, Check, Edit } from '@mui/icons-material';
3
3
  import { Backdrop, Box, Button, Chip, CircularProgress, Drawer, FormControl, FormHelperText, IconButton, InputAdornment, InputLabel, ListItemIcon, ListItemText, MenuItem, Select, Stack, TextField, Tooltip, Typography } from '@mui/material';
4
4
  import api from '@cccsaurora/howler-ui/api';
5
- import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
5
+ import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
6
6
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
7
7
  import { memo, useCallback, useEffect, useState } from 'react';
8
8
  import { Trans, useTranslation } from 'react-i18next';
@@ -44,7 +44,7 @@ const NewLabelForm = ({ handleSubmit }) => {
44
44
  const HitLabels = ({ hit, readOnly = false }) => {
45
45
  const { dispatchApi } = useMyApi();
46
46
  const { t } = useTranslation();
47
- const updateHit = useContextSelector(RecordContext, ctx => ctx.updateRecord);
47
+ const updateHit = useContextSelector(HitContext, ctx => ctx.updateHit);
48
48
  const [openDrawer, setOpenDrawer] = useState(false);
49
49
  const [loading, setLoading] = useState(false);
50
50
  const [labels, setLabels] = useState(Object.entries(hit.howler.labels).flatMap(([key, category]) => {
@@ -4,7 +4,6 @@ import { HitLayout } from './HitLayout';
4
4
  export declare const DEFAULT_FIELDS: string[];
5
5
  declare const _default: import("react").NamedExoticComponent<{
6
6
  hit: WithMetadata<Hit>;
7
- lazy?: boolean;
8
7
  layout: HitLayout;
9
8
  forceAllFields?: boolean;
10
9
  }>;
@@ -9,9 +9,9 @@ import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
9
9
  import { HitLayout } from './HitLayout';
10
10
  import DefaultOutline from './outlines/DefaultOutline';
11
11
  export const DEFAULT_FIELDS = ['event.created', 'howler.id', 'howler.hash'];
12
- const HitOutline = ({ hit, layout, lazy = false, forceAllFields = false }) => {
12
+ const HitOutline = ({ hit, layout, forceAllFields = false }) => {
13
13
  const { t } = useTranslation();
14
- const { getMatchingTemplate } = useMatchers(lazy);
14
+ const { getMatchingTemplate } = useMatchers();
15
15
  const [templateFieldCount] = useMyLocalStorageItem(StorageKey.TEMPLATE_FIELD_COUNT, null);
16
16
  const [template, setTemplate] = useState(null);
17
17
  useEffect(() => {
@@ -36,7 +36,7 @@ const HitOutline = ({ hit, layout, lazy = false, forceAllFields = false }) => {
36
36
  fields: DEFAULT_FIELDS
37
37
  });
38
38
  }
39
- }, [forceAllFields, hit, layout, template, templateFieldCount]);
39
+ }, [hit, layout, template, templateFieldCount]);
40
40
  return (_jsxs(Box, { sx: { py: 1, width: '100%', pr: 2 }, children: [layout === HitLayout.COMFY && (_jsx(Typography, { variant: "body1", fontWeight: "bold", sx: { mb: 1 }, children: t('hit.details.title') })), layout !== HitLayout.DENSE && _jsx(Divider, { orientation: "horizontal", sx: { mb: 1 } }), outline] }));
41
41
  };
42
42
  export default memo(HitOutline);
@@ -1,8 +1,8 @@
1
1
  import type { AppSearchItemRendererOption } from '@cccsaurora/howler-ui/commons/components/app/AppSearchService';
2
2
  import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
3
- type PreviewProps = {
3
+ type QuickSearchProps = {
4
4
  hit: Hit;
5
- options?: AppSearchItemRendererOption<Hit>;
5
+ options: AppSearchItemRendererOption<Hit>;
6
6
  };
7
- declare const _default: import("react").NamedExoticComponent<PreviewProps>;
7
+ declare const _default: import("react").NamedExoticComponent<QuickSearchProps>;
8
8
  export default _default;
@@ -4,18 +4,12 @@ import { memo, useMemo } from 'react';
4
4
  import { Trans, useTranslation } from 'react-i18next';
5
5
  import { ESCALATION_COLORS, PROVIDER_COLORS, STATUS_COLORS } from '@cccsaurora/howler-ui/utils/constants';
6
6
  import { formatDate, stringToColor } from '@cccsaurora/howler-ui/utils/utils';
7
- const HitPreview = ({ hit, options }) => {
7
+ const HitQuickSearch = ({ hit, options }) => {
8
8
  const { t } = useTranslation();
9
9
  const theme = useTheme();
10
10
  const isUnderLg = useMediaQuery(theme.breakpoints.down('lg'));
11
11
  const providerColor = useMemo(() => PROVIDER_COLORS[hit.event?.provider ?? 'unknown'] ?? stringToColor(hit.event.provider), [hit.event?.provider]);
12
- return (_jsxs(Box, { sx: {
13
- overflow: 'hidden',
14
- borderBottom: `thin solid ${theme.palette.divider}`,
15
- pb: 1,
16
- mb: 0,
17
- gap: theme.spacing(1)
18
- }, display: "grid", gridTemplateColumns: "minmax(0, 1fr) minmax(0, auto)", children: [_jsxs(Stack, { flexGrow: 1, gridColumn: options?.state.mode === 'inline' ? 'span 2' : '', children: [_jsxs(Typography, { variant: "body1", fontWeight: "bold", children: [hit.howler.analytic, hit.howler.detection && ': ', hit.howler.detection] }), options?.state.mode !== 'inline' && hit.howler?.outline && (_jsx(Tooltip, { placement: isUnderLg ? 'bottom' : 'left', componentsProps: {
12
+ return (_jsxs(Box, { sx: { overflow: 'hidden', borderBottom: `thin solid ${theme.palette.divider}`, pb: 1, mb: 0 }, display: "grid", gridTemplateColumns: "minmax(0, 1fr) minmax(0, auto)", children: [_jsxs(Stack, { flexGrow: 1, gridColumn: options.state.mode === 'inline' ? 'span 2' : '', children: [_jsxs(Typography, { variant: "body1", fontWeight: "bold", children: [hit.howler.analytic, hit.howler.detection && ': ', hit.howler.detection] }), options.state.mode !== 'inline' && hit.howler?.outline && (_jsx(Tooltip, { placement: isUnderLg ? 'bottom' : 'left', componentsProps: {
19
13
  tooltip: {
20
14
  sx: {
21
15
  fontSize: 12,
@@ -23,7 +17,7 @@ const HitPreview = ({ hit, options }) => {
23
17
  color: theme.palette.getContrastText(theme.palette.background.paper)
24
18
  }
25
19
  }
26
- }, title: _jsxs(Stack, { divider: _jsx(Divider, { sx: { my: 0.5 } }), children: [_jsxs("div", { children: [_jsx(Trans, { i18nKey: "hit.header.threat" }), ": ", hit.howler.outline.threat] }), _jsxs("div", { children: [_jsx(Trans, { i18nKey: "hit.header.target" }), ": ", hit.howler.outline.target] }), _jsx("div", { children: hit.howler.outline.indicators.join(', ') })] }), children: _jsxs(Stack, { direction: { xs: 'column', sm: 'column' }, flex: 1, children: [_jsxs(Typography, { variant: "caption", textOverflow: "ellipsis", sx: { wordBreak: 'break-all', overflow: 'hidden' }, children: [_jsx(Trans, { i18nKey: "hit.header.threat" }), ": ", hit.howler.outline.threat] }), _jsxs(Typography, { variant: "caption", textOverflow: "ellipsis", sx: { wordBreak: 'break-all', overflow: 'hidden' }, children: [_jsx(Trans, { i18nKey: "hit.header.target" }), ": ", hit.howler.outline.target] })] }) }))] }), _jsxs(Stack, { alignItems: options?.state.mode === 'fullscreen' ? 'end' : 'start', spacing: 0.5, children: [options?.state.mode === 'fullscreen' && _jsx(Chip, { label: formatDate(hit.timestamp), size: "small" }), _jsxs(Stack, { direction: "row", spacing: 0.5, children: [options?.state.mode === 'inline' && _jsx(Chip, { label: formatDate(hit.timestamp), size: "small" }), _jsx(Chip, { sx: {
20
+ }, title: _jsxs(Stack, { divider: _jsx(Divider, { sx: { my: 0.5 } }), children: [_jsxs("div", { children: [_jsx(Trans, { i18nKey: "hit.header.threat" }), ": ", hit.howler.outline.threat] }), _jsxs("div", { children: [_jsx(Trans, { i18nKey: "hit.header.target" }), ": ", hit.howler.outline.target] }), _jsx("div", { children: hit.howler.outline.indicators.join(', ') })] }), children: _jsxs(Stack, { direction: { xs: 'column', sm: 'column' }, flex: 1, children: [_jsxs(Typography, { variant: "caption", textOverflow: "ellipsis", sx: { wordBreak: 'break-all', overflow: 'hidden' }, children: [_jsx(Trans, { i18nKey: "hit.header.threat" }), ": ", hit.howler.outline.threat] }), _jsxs(Typography, { variant: "caption", textOverflow: "ellipsis", sx: { wordBreak: 'break-all', overflow: 'hidden' }, children: [_jsx(Trans, { i18nKey: "hit.header.target" }), ": ", hit.howler.outline.target] }), _jsxs(Typography, { variant: "caption", textOverflow: "ellipsis", sx: { wordBreak: 'break-all', overflow: 'hidden' }, children: [_jsx(Trans, { i18nKey: "hit.header.indicators" }), ": ", hit.howler.outline.indicators.map(i => i).join(', ')] })] }) }))] }), _jsxs(Stack, { alignItems: options.state.mode === 'fullscreen' ? 'end' : 'start', spacing: 0.5, children: [options.state.mode === 'fullscreen' && _jsx(Chip, { label: formatDate(hit.timestamp), size: "small" }), _jsxs(Stack, { direction: "row", spacing: 0.5, children: [options.state.mode === 'inline' && _jsx(Chip, { label: formatDate(hit.timestamp), size: "small" }), _jsx(Chip, { sx: {
27
21
  backgroundColor: providerColor,
28
22
  color: theme.palette.getContrastText(providerColor)
29
23
  }, label: hit.organization?.name ?? _jsx(Trans, { i18nKey: "unknown" }), size: "small" }), _jsx(Chip, { label: hit.howler.escalation, size: "small", color: ESCALATION_COLORS[hit.howler.escalation] })] }), _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(Chip, { sx: {
@@ -34,4 +28,4 @@ const HitPreview = ({ hit, options }) => {
34
28
  ? hit?.howler.assignment
35
29
  : t('app.drawer.hit.assignment.unassigned.name'), size: "small" }), _jsx(Chip, { label: hit.howler.status, size: "small", color: STATUS_COLORS[hit.howler.status] })] })] })] }));
36
30
  };
37
- export default memo(HitPreview);
31
+ export default memo(HitQuickSearch);
@@ -0,0 +1,6 @@
1
+ import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
2
+ import type { FC } from 'react';
3
+ declare const HitRelated: FC<{
4
+ hit: Hit;
5
+ }>;
6
+ export default HitRelated;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Grid } from '@mui/material';
3
+ import RelatedLink from './related/RelatedLink';
4
+ const HitRelated = ({ hit }) => {
5
+ return (_jsx(Grid, { container: true, spacing: 1, pr: 2, children: hit?.howler.links?.map(l => (_jsx(Grid, { item: true, xs: 6, sm: 4, md: 3, children: _jsx(RelatedLink, { ...l }) }, l.title + l.href))) }));
6
+ };
7
+ export default HitRelated;
@@ -1,9 +1,8 @@
1
1
  import type { HowlerSearchResponse } from '@cccsaurora/howler-ui/api/search';
2
2
  import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
3
- import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
4
3
  import type { WithMetadata } from '@cccsaurora/howler-ui/models/WithMetadata';
5
4
  declare const _default: import("react").NamedExoticComponent<{
6
- response?: HowlerSearchResponse<WithMetadata<Hit | Observable>>;
5
+ response?: HowlerSearchResponse<WithMetadata<Hit>>;
7
6
  execute?: boolean;
8
7
  onStart?: () => void;
9
8
  onComplete?: () => void;
@@ -4,8 +4,8 @@ import { Alert, AlertTitle, Autocomplete, Box, Button, Chip, CircularProgress, D
4
4
  import api from '@cccsaurora/howler-ui/api';
5
5
  import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
6
6
  import { FieldContext } from '@cccsaurora/howler-ui/components/app/providers/FieldProvider';
7
+ import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
7
8
  import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
8
- import { RecordSearchContext } from '@cccsaurora/howler-ui/components/app/providers/RecordSearchProvider';
9
9
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
10
10
  import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
11
11
  import useMySnackbar from '@cccsaurora/howler-ui/components/hooks/useMySnackbar';
@@ -14,7 +14,6 @@ import { memo, useCallback, useContext, useEffect, useState } from 'react';
14
14
  import { useTranslation } from 'react-i18next';
15
15
  import { useContextSelector } from 'use-context-selector';
16
16
  import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
17
- import { isHit } from '@cccsaurora/howler-ui/utils/typeUtils';
18
17
  import { getTimeRange } from '@cccsaurora/howler-ui/utils/utils';
19
18
  import PluginChip from '../PluginChip';
20
19
  import HitGraph from './aggregate/HitGraph';
@@ -25,9 +24,9 @@ const HitSummary = ({ response, onStart, onComplete }) => {
25
24
  const { showErrorMessage } = useMySnackbar();
26
25
  const pageCount = useMyLocalStorageItem(StorageKey.PAGE_COUNT, 25)[0];
27
26
  const { getMatchingTemplate } = useMatchers();
28
- const searching = useContextSelector(RecordSearchContext, ctx => ctx.searching);
29
- const error = useContextSelector(RecordSearchContext, ctx => ctx.error);
30
- const getFilters = useContextSelector(RecordSearchContext, ctx => ctx.getFilters);
27
+ const searching = useContextSelector(HitSearchContext, ctx => ctx.searching);
28
+ const error = useContextSelector(HitSearchContext, ctx => ctx.error);
29
+ const getFilters = useContextSelector(HitSearchContext, ctx => ctx.getFilters);
31
30
  const query = useContextSelector(ParameterContext, ctx => ctx.query);
32
31
  const setQuery = useContextSelector(ParameterContext, ctx => ctx.setQuery);
33
32
  const views = useContextSelector(ParameterContext, ctx => ctx.views);
@@ -41,7 +40,7 @@ const HitSummary = ({ response, onStart, onComplete }) => {
41
40
  }
42
41
  try {
43
42
  // Get a list of every key in every template of the hits we're searching
44
- const rawCounts = await Promise.all((response?.items ?? []).filter(isHit).map(async (h) => {
43
+ const rawCounts = await Promise.all((response?.items ?? []).map(async (h) => {
45
44
  const matchingTemplate = await getMatchingTemplate(h);
46
45
  return (matchingTemplate?.keys ?? [])
47
46
  .filter(key => !['howler.id', 'howler.hash'].includes(key))
@@ -1,11 +1,10 @@
1
1
  import type { HowlerUser } from '@cccsaurora/howler-ui/models/entities/HowlerUser';
2
2
  import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
3
- import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
4
3
  import type { FC } from 'react';
5
- declare const RecordWorklog: FC<{
6
- record: Hit | Observable;
4
+ declare const HitWorklog: FC<{
5
+ hit: Hit;
7
6
  users: {
8
7
  [id: string]: HowlerUser;
9
8
  };
10
9
  }>;
11
- export default RecordWorklog;
10
+ export default HitWorklog;
@@ -10,7 +10,7 @@ import { compareTimestamp, twitterShort } from '@cccsaurora/howler-ui/utils/util
10
10
  import HowlerAvatar from '../display/HowlerAvatar';
11
11
  import HowlerCard from '../display/HowlerCard';
12
12
  import Markdown from '../display/Markdown';
13
- const RecordWorklog = ({ record, users }) => {
13
+ const HitWorklog = ({ hit, users }) => {
14
14
  const theme = useTheme();
15
15
  const { shiftColor } = useMyUtils();
16
16
  const { t } = useTranslation();
@@ -23,15 +23,15 @@ const RecordWorklog = ({ record, users }) => {
23
23
  */
24
24
  const worklogGroups = useMemo(() => {
25
25
  let setInitialVersion = false;
26
- return (record?.howler?.log || [])
26
+ return (hit?.howler?.log || [])
27
27
  .slice()
28
28
  .sort((a, b) => compareTimestamp(b.timestamp, a.timestamp))
29
29
  .reduce((acc, l) => {
30
- if (!initialVersions[record.howler.id] && !setInitialVersion) {
30
+ if (!initialVersions[hit.howler.id] && !setInitialVersion) {
31
31
  setInitialVersion = true;
32
32
  setInitialVersions({
33
33
  ...initialVersions,
34
- [record.howler.id]: l.previous_version
34
+ [hit.howler.id]: l.previous_version
35
35
  });
36
36
  }
37
37
  // Initialize the worklog card groups
@@ -42,9 +42,9 @@ const RecordWorklog = ({ record, users }) => {
42
42
  const currArr = acc[acc.length - 1];
43
43
  if (
44
44
  // Does this log version match the saved version?
45
- l.previous_version === initialVersions[record.howler.id] &&
45
+ l.previous_version === initialVersions[hit.howler.id] &&
46
46
  // Does the previous entry not match?
47
- currArr[currArr.length - 1].previous_version !== initialVersions[record.howler.id]) {
47
+ currArr[currArr.length - 1].previous_version !== initialVersions[hit.howler.id]) {
48
48
  // If so, we've figured out where the new logs should start, so we start a new card.
49
49
  acc.push([l]);
50
50
  return acc;
@@ -59,14 +59,14 @@ const RecordWorklog = ({ record, users }) => {
59
59
  }, []);
60
60
  },
61
61
  // eslint-disable-next-line react-hooks/exhaustive-deps
62
- [record?.howler?.log]);
62
+ [hit?.howler?.log]);
63
63
  useEffect(() => {
64
64
  // On unmount, mark the latest entry version as the last seen version.
65
65
  return () => {
66
- if (record?.howler.id) {
66
+ if (hit?.howler.id) {
67
67
  setInitialVersions({
68
68
  ...initialVersions,
69
- [record.howler.id]: worklogGroups[0][0]?.previous_version ?? initialVersions[record.howler.id]
69
+ [hit.howler.id]: worklogGroups[0][0]?.previous_version ?? initialVersions[hit.howler.id]
70
70
  });
71
71
  }
72
72
  };
@@ -77,9 +77,7 @@ const RecordWorklog = ({ record, users }) => {
77
77
  if (worklogGroups.length > 0) {
78
78
  return worklogGroups.flatMap((ls, index) => {
79
79
  const result = [];
80
- if (index > 0 &&
81
- initialVersions[record.howler.id] &&
82
- ls[0].previous_version === initialVersions[record.howler.id]) {
80
+ if (index > 0 && initialVersions[hit.howler.id] && ls[0].previous_version === initialVersions[hit.howler.id]) {
83
81
  result.push(_jsx(Divider, { children: _jsxs(Stack, { direction: "row", children: [_jsx(KeyboardArrowUp, { sx: { color: 'text.secondary' }, fontSize: "small" }), _jsx(Typography, { variant: "caption", color: "text.secondary", children: t('hit.worklog.new') }), _jsx(KeyboardArrowUp, { sx: { color: 'text.secondary' }, fontSize: "small" })] }) }, "new"));
84
82
  }
85
83
  result.push(_jsxs(HowlerCard, { elevation: 4, children: [_jsx(CardHeader, { avatar: _jsx(HowlerAvatar, { userId: ls[0].user }), title: users[ls[0].user]?.name ?? ls[0].user, subheader: _jsx(Tooltip, { title: new Date(ls[0].timestamp).toLocaleString(), children: _jsx(Typography, { variant: "caption", children: twitterShort(ls[0].timestamp) }) }) }), _jsx(CardContent, { children: _jsx(Stack, { spacing: 1, divider: _jsx(Divider, { orientation: "horizontal" }), children: ls.map(l => (_jsxs(Typography, { variant: "body2", color: "text.secondary", component: "div", position: "relative", children: [l.explanation ? (_jsx(Markdown, { md: l.explanation.trim() })) : (_jsxs(_Fragment, { children: [_jsxs("span", { children: [t('hit.worklog.updated'), "\u00A0"] }), _jsx("code", { children: l.key }), _jsx("span", { children: ":\u00A0" }), {
@@ -90,10 +88,10 @@ const RecordWorklog = ({ record, users }) => {
90
88
  return result;
91
89
  });
92
90
  }
93
- else if (!record?.howler) {
91
+ else if (!hit?.howler) {
94
92
  return (_jsxs(_Fragment, { children: [_jsx(Skeleton, { width: "100%", height: 200, variant: "rounded" }), _jsx(Skeleton, { width: "100%", height: 220, variant: "rounded" }), _jsx(Skeleton, { width: "100%", height: 150, variant: "rounded" })] }));
95
93
  }
96
- }, [worklogGroups, record.howler, initialVersions, users, t, shiftColor, theme.palette.text.primary]);
94
+ }, [worklogGroups, hit.howler, initialVersions, users, t, shiftColor, theme.palette.text.primary]);
97
95
  return (_jsx(Stack, { sx: { p: 2 }, spacing: 1, children: worklogEls }));
98
96
  };
99
- export default RecordWorklog;
97
+ export default HitWorklog;
@@ -4,9 +4,9 @@ import { Alert, AlertTitle, Autocomplete, Box, Button, CircularProgress, IconBut
4
4
  import api from '@cccsaurora/howler-ui/api';
5
5
  import 'chartjs-adapter-dayjs-4';
6
6
  import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
7
+ import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
8
+ import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
7
9
  import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
8
- import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
9
- import { RecordSearchContext } from '@cccsaurora/howler-ui/components/app/providers/RecordSearchProvider';
10
10
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
11
11
  import useMyChart from '@cccsaurora/howler-ui/components/hooks/useMyChart';
12
12
  import dayjs from 'dayjs';
@@ -38,12 +38,12 @@ const HitGraph = () => {
38
38
  const setQuery = useContextSelector(ParameterContext, ctx => ctx.setQuery);
39
39
  const span = useContextSelector(ParameterContext, ctx => ctx.span);
40
40
  const views = useContextSelector(ParameterContext, ctx => ctx.views);
41
- const selectedHits = useContextSelector(RecordContext, ctx => ctx.selectedRecords);
42
- const addHitToSelection = useContextSelector(RecordContext, ctx => ctx.addRecordToSelection);
43
- const removeHitFromSelection = useContextSelector(RecordContext, ctx => ctx.removeRecordFromSelection);
44
- const error = useContextSelector(RecordSearchContext, ctx => ctx.error);
45
- const response = useContextSelector(RecordSearchContext, ctx => ctx.response);
46
- const getFilters = useContextSelector(RecordSearchContext, ctx => ctx.getFilters);
41
+ const selectedHits = useContextSelector(HitContext, ctx => ctx.selectedHits);
42
+ const addHitToSelection = useContextSelector(HitContext, ctx => ctx.addHitToSelection);
43
+ const removeHitFromSelection = useContextSelector(HitContext, ctx => ctx.removeHitFromSelection);
44
+ const error = useContextSelector(HitSearchContext, ctx => ctx.error);
45
+ const response = useContextSelector(HitSearchContext, ctx => ctx.response);
46
+ const getFilters = useContextSelector(HitSearchContext, ctx => ctx.getFilters);
47
47
  const chartRef = useRef();
48
48
  const [loading, setLoading] = useState(false);
49
49
  const [filterField, setFilterField] = useState(FILTER_FIELDS[0]);
@@ -41,7 +41,7 @@ const DefaultOutline = ({ hit, fields, template, layout = HitLayout.NORMAL, read
41
41
  if (!displayedData) {
42
42
  return null;
43
43
  }
44
- return (_jsxs(React.Fragment, { children: [_jsx(Tooltip, { title: (config.indexes.hit[field]?.description ?? t('none')).split('\n')[0], children: _jsxs(Typography, { variant: layout !== HitLayout.COMFY ? 'caption' : 'body1', fontWeight: "bold", children: [field, ":"] }) }), _jsx(PluginTypography, { context: "outline", variant: layout !== HitLayout.COMFY ? 'caption' : 'body1', whiteSpace: "normal", sx: { width: '100%', wordBreak: 'break-all' }, value: displayedData, field: field, obj: hit, children: displayedData })] }, field));
44
+ return (_jsxs(React.Fragment, { children: [_jsx(Tooltip, { title: (config.indexes.hit[field]?.description ?? t('none')).split('\n')[0], children: _jsxs(Typography, { variant: layout !== HitLayout.COMFY ? 'caption' : 'body1', fontWeight: "bold", children: [field, ":"] }) }), _jsx(PluginTypography, { context: "outline", variant: layout !== HitLayout.COMFY ? 'caption' : 'body1', whiteSpace: "normal", sx: { width: '100%', wordBreak: 'break-all' }, value: displayedData, field: field, hit: hit, children: displayedData })] }, field));
45
45
  })] }));
46
46
  };
47
47
  export default memo(DefaultOutline);
@@ -3,7 +3,6 @@ interface ViewTitleProps {
3
3
  title?: string;
4
4
  type?: string;
5
5
  query?: string;
6
- indexes?: string[];
7
6
  sort?: string;
8
7
  span?: string;
9
8
  }