@cccsaurora/howler-ui 2.18.0-dev.716 → 2.18.0-dev.722

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) 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.js +2 -2
  10. package/components/app/hooks/useMatchers.test.js +22 -22
  11. package/components/app/hooks/useTitle.js +3 -3
  12. package/components/app/providers/FavouritesProvider.js +2 -2
  13. package/components/app/providers/HitProvider.d.ts +22 -0
  14. package/components/app/providers/{RecordProvider.js → HitProvider.js} +41 -41
  15. package/components/app/providers/{RecordSearchProvider.d.ts → HitSearchProvider.d.ts} +6 -6
  16. package/components/app/providers/{RecordSearchProvider.js → HitSearchProvider.js} +17 -12
  17. package/components/app/providers/{RecordSearchProvider.test.js → HitSearchProvider.test.js} +70 -51
  18. package/components/app/providers/ModalProvider.d.ts +0 -1
  19. package/components/app/providers/ParameterProvider.d.ts +2 -9
  20. package/components/app/providers/ParameterProvider.js +240 -165
  21. package/components/app/providers/ParameterProvider.test.js +14 -307
  22. package/components/elements/PluginTypography.d.ts +1 -2
  23. package/components/elements/PluginTypography.js +2 -3
  24. package/components/elements/UserList.d.ts +2 -5
  25. package/components/elements/UserList.js +5 -14
  26. package/components/elements/addons/search/phrase/Phrase.js +1 -1
  27. package/components/elements/display/ChipPopper.d.ts +1 -1
  28. package/components/elements/display/HowlerCard.js +1 -1
  29. package/components/elements/display/Modal.js +0 -2
  30. package/components/elements/display/icons/BundleButton.d.ts +6 -0
  31. package/components/elements/display/icons/BundleButton.js +32 -0
  32. package/components/elements/hit/HitActions.js +4 -4
  33. package/components/elements/hit/HitBanner.js +48 -28
  34. package/components/elements/hit/HitCard.d.ts +0 -1
  35. package/components/elements/hit/HitCard.js +6 -6
  36. package/components/elements/{record/RecordComments.d.ts → hit/HitComments.d.ts} +4 -5
  37. package/components/elements/{record/RecordComments.js → hit/HitComments.js} +28 -29
  38. package/components/elements/{ObjectDetails.js → hit/HitDetails.js} +17 -17
  39. package/components/elements/hit/HitLabels.js +2 -2
  40. package/components/elements/hit/{HitPreview.d.ts → HitQuickSearch.d.ts} +3 -3
  41. package/components/elements/hit/{HitPreview.js → HitQuickSearch.js} +4 -10
  42. package/components/elements/hit/HitRelated.d.ts +6 -0
  43. package/components/elements/hit/HitRelated.js +7 -0
  44. package/components/elements/hit/HitSummary.d.ts +1 -2
  45. package/components/elements/hit/HitSummary.js +5 -6
  46. package/components/elements/{record/RecordWorklog.d.ts → hit/HitWorklog.d.ts} +3 -4
  47. package/components/elements/{record/RecordWorklog.js → hit/HitWorklog.js} +13 -15
  48. package/components/elements/hit/aggregate/HitGraph.js +8 -8
  49. package/components/elements/hit/outlines/DefaultOutline.js +1 -1
  50. package/components/elements/view/ViewTitle.d.ts +0 -1
  51. package/components/elements/view/ViewTitle.js +2 -9
  52. package/components/hooks/useHitActions.d.ts +1 -1
  53. package/components/hooks/useHitActions.js +4 -4
  54. package/components/hooks/{useRecordSelection.d.ts → useHitSelection.d.ts} +2 -2
  55. package/components/hooks/{useRecordSelection.js → useHitSelection.js} +33 -12
  56. package/components/hooks/useMyPreferences.js +1 -10
  57. package/components/hooks/useMySearch.js +2 -2
  58. package/components/hooks/useMySitemap.js +1 -4
  59. package/components/hooks/useMyTheme.js +2 -9
  60. package/components/hooks/useParamState.test.js +4 -3
  61. package/components/routes/action/edit/ActionEditor.js +2 -2
  62. package/components/routes/action/view/ActionSearch.js +1 -1
  63. package/components/routes/advanced/QueryBuilder.js +1 -1
  64. package/components/routes/advanced/QueryEditor.js +3 -3
  65. package/components/routes/advanced/historyCompletionProvider.js +3 -3
  66. package/components/routes/analytics/AnalyticDetails.js +2 -2
  67. package/components/routes/analytics/AnalyticSearch.js +1 -1
  68. package/components/routes/dossiers/DossierEditor.js +2 -2
  69. package/components/routes/dossiers/DossierEditor.test.js +1 -1
  70. package/components/routes/help/ApiDocumentation.js +1 -1
  71. package/components/routes/help/BundleDocumentation.d.ts +3 -0
  72. package/components/routes/help/BundleDocumentation.js +12 -0
  73. package/components/routes/help/HitBannerDocumentation.js +0 -1
  74. package/components/routes/help/HitDocumentation.js +3 -1
  75. package/components/routes/help/markdown/en/bundles.md.js +1 -0
  76. package/components/routes/help/markdown/fr/bundles.md.js +1 -0
  77. package/components/routes/hits/search/BundleParentMenu.d.ts +6 -0
  78. package/components/routes/hits/search/BundleParentMenu.js +32 -0
  79. package/components/routes/hits/search/BundleScroller.d.ts +2 -0
  80. package/components/routes/hits/search/BundleScroller.js +6 -0
  81. package/components/routes/hits/search/{RecordBrowser.js → HitBrowser.js} +9 -9
  82. package/components/{elements/record/RecordContextMenu.d.ts → routes/hits/search/HitContextMenu.d.ts} +3 -3
  83. package/components/routes/hits/search/HitContextMenu.js +227 -0
  84. package/components/{elements/record/RecordContextMenu.test.js → routes/hits/search/HitContextMenu.test.js} +39 -94
  85. package/components/routes/hits/search/{RecordQuery.d.ts → HitQuery.d.ts} +2 -2
  86. package/components/routes/hits/search/{RecordQuery.js → HitQuery.js} +6 -6
  87. package/components/routes/hits/search/InformationPane.d.ts +0 -1
  88. package/components/routes/hits/search/InformationPane.js +60 -47
  89. package/components/routes/hits/search/LayoutSettings.js +3 -3
  90. package/components/routes/hits/search/QuerySettings.js +1 -2
  91. package/components/routes/hits/search/QuerySettings.test.js +9 -14
  92. package/components/routes/hits/search/SearchPane.js +49 -26
  93. package/components/routes/hits/search/ViewLink.js +3 -3
  94. package/components/routes/hits/search/ViewLink.test.js +8 -8
  95. package/components/routes/hits/search/grid/AddColumnModal.js +4 -5
  96. package/components/routes/hits/search/grid/EnhancedCell.d.ts +1 -2
  97. package/components/routes/hits/search/grid/EnhancedCell.js +2 -2
  98. package/components/routes/hits/search/grid/HitGrid.js +18 -20
  99. package/components/routes/hits/search/grid/{RecordRow.d.ts → HitRow.d.ts} +2 -3
  100. package/components/routes/hits/search/grid/{RecordRow.js → HitRow.js} +8 -10
  101. package/components/routes/hits/view/HitViewer.js +13 -12
  102. package/components/routes/home/ViewCard.js +41 -47
  103. package/components/{elements/MarkdownEditor.js → routes/overviews/OverviewEditor.js} +3 -3
  104. package/components/routes/overviews/OverviewViewer.js +2 -2
  105. package/components/routes/views/ViewComposer.js +19 -46
  106. package/locales/en/translation.json +3 -88
  107. package/locales/fr/translation.json +3 -86
  108. package/models/WithMetadata.d.ts +1 -2
  109. package/models/entities/generated/{ThreatEnrichment.d.ts → Enrichment.d.ts} +1 -1
  110. package/models/entities/generated/Hit.d.ts +0 -1
  111. package/models/entities/generated/Howler.d.ts +4 -0
  112. package/models/entities/generated/Rule.d.ts +10 -2
  113. package/models/entities/generated/Threat.d.ts +2 -2
  114. package/models/entities/generated/View.d.ts +0 -1
  115. package/package.json +1 -18
  116. package/plugins/clue/components/ClueTypography.js +2 -2
  117. package/plugins/clue/utils.d.ts +1 -2
  118. package/tests/server-handlers.js +1 -6
  119. package/tests/utils.d.ts +0 -4
  120. package/tests/utils.js +0 -20
  121. package/utils/constants.d.ts +3 -3
  122. package/utils/hitFunctions.d.ts +1 -2
  123. package/utils/hitFunctions.js +4 -4
  124. package/utils/viewUtils.js +0 -3
  125. package/api/search/case.d.ts +0 -4
  126. package/api/search/case.js +0 -8
  127. package/api/v2/case/index.d.ts +0 -8
  128. package/api/v2/case/index.js +0 -20
  129. package/api/v2/case/items.d.ts +0 -6
  130. package/api/v2/case/items.js +0 -18
  131. package/api/v2/index.d.ts +0 -4
  132. package/api/v2/index.js +0 -6
  133. package/api/v2/search/facet.d.ts +0 -3
  134. package/api/v2/search/facet.js +0 -12
  135. package/api/v2/search/index.d.ts +0 -5
  136. package/api/v2/search/index.js +0 -24
  137. package/components/app/providers/RecordProvider.d.ts +0 -23
  138. package/components/elements/ContextMenu.d.ts +0 -56
  139. package/components/elements/ContextMenu.js +0 -109
  140. package/components/elements/ContextMenu.test.js +0 -215
  141. package/components/elements/ObjectDetails.d.ts +0 -6
  142. package/components/elements/case/CaseCard.d.ts +0 -12
  143. package/components/elements/case/CaseCard.js +0 -42
  144. package/components/elements/case/CasePreview.d.ts +0 -6
  145. package/components/elements/case/CasePreview.js +0 -17
  146. package/components/elements/case/StatusIcon.d.ts +0 -5
  147. package/components/elements/case/StatusIcon.js +0 -13
  148. package/components/elements/hit/elements/AnalyticLink.d.ts +0 -8
  149. package/components/elements/hit/elements/AnalyticLink.js +0 -22
  150. package/components/elements/hit/related/RelatedRecords.js +0 -63
  151. package/components/elements/observable/ObservableCard.d.ts +0 -6
  152. package/components/elements/observable/ObservableCard.js +0 -22
  153. package/components/elements/observable/ObservablePreview.d.ts +0 -6
  154. package/components/elements/observable/ObservablePreview.js +0 -12
  155. package/components/elements/record/RecordContextMenu.js +0 -247
  156. package/components/elements/record/RecordContextMenu.test.d.ts +0 -1
  157. package/components/elements/record/RecordRelated.d.ts +0 -7
  158. package/components/elements/record/RecordRelated.js +0 -34
  159. package/components/hooks/useRelatedRecords.d.ts +0 -13
  160. package/components/hooks/useRelatedRecords.js +0 -32
  161. package/components/routes/cases/CaseViewer.d.ts +0 -2
  162. package/components/routes/cases/CaseViewer.js +0 -22
  163. package/components/routes/cases/Cases.d.ts +0 -2
  164. package/components/routes/cases/Cases.js +0 -101
  165. package/components/routes/cases/constants.d.ts +0 -5
  166. package/components/routes/cases/constants.js +0 -5
  167. package/components/routes/cases/detail/AlertPanel.d.ts +0 -6
  168. package/components/routes/cases/detail/AlertPanel.js +0 -33
  169. package/components/routes/cases/detail/CaseAssets.d.ts +0 -11
  170. package/components/routes/cases/detail/CaseAssets.js +0 -104
  171. package/components/routes/cases/detail/CaseAssets.test.d.ts +0 -1
  172. package/components/routes/cases/detail/CaseAssets.test.js +0 -167
  173. package/components/routes/cases/detail/CaseDashboard.d.ts +0 -7
  174. package/components/routes/cases/detail/CaseDashboard.js +0 -54
  175. package/components/routes/cases/detail/CaseDetails.d.ts +0 -6
  176. package/components/routes/cases/detail/CaseDetails.js +0 -61
  177. package/components/routes/cases/detail/CaseOverview.d.ts +0 -7
  178. package/components/routes/cases/detail/CaseOverview.js +0 -43
  179. package/components/routes/cases/detail/CaseSidebar.d.ts +0 -8
  180. package/components/routes/cases/detail/CaseSidebar.js +0 -50
  181. package/components/routes/cases/detail/CaseTask.d.ts +0 -11
  182. package/components/routes/cases/detail/CaseTask.js +0 -57
  183. package/components/routes/cases/detail/CaseTimeline.d.ts +0 -12
  184. package/components/routes/cases/detail/CaseTimeline.js +0 -106
  185. package/components/routes/cases/detail/CaseTimeline.test.d.ts +0 -1
  186. package/components/routes/cases/detail/CaseTimeline.test.js +0 -227
  187. package/components/routes/cases/detail/ItemPage.d.ts +0 -6
  188. package/components/routes/cases/detail/ItemPage.js +0 -99
  189. package/components/routes/cases/detail/RelatedCasePanel.d.ts +0 -6
  190. package/components/routes/cases/detail/RelatedCasePanel.js +0 -31
  191. package/components/routes/cases/detail/TaskPanel.d.ts +0 -7
  192. package/components/routes/cases/detail/TaskPanel.js +0 -52
  193. package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +0 -12
  194. package/components/routes/cases/detail/aggregates/CaseAggregate.js +0 -19
  195. package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +0 -6
  196. package/components/routes/cases/detail/aggregates/SourceAggregate.js +0 -30
  197. package/components/routes/cases/detail/assets/Asset.d.ts +0 -14
  198. package/components/routes/cases/detail/assets/Asset.js +0 -12
  199. package/components/routes/cases/detail/assets/Asset.test.d.ts +0 -1
  200. package/components/routes/cases/detail/assets/Asset.test.js +0 -72
  201. package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +0 -14
  202. package/components/routes/cases/detail/sidebar/CaseFolder.js +0 -136
  203. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.d.ts +0 -34
  204. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.js +0 -105
  205. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.d.ts +0 -1
  206. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.js +0 -351
  207. package/components/routes/cases/detail/sidebar/types.d.ts +0 -3
  208. package/components/routes/cases/detail/sidebar/utils.d.ts +0 -3
  209. package/components/routes/cases/detail/sidebar/utils.js +0 -25
  210. package/components/routes/cases/hooks/useCase.d.ts +0 -13
  211. package/components/routes/cases/hooks/useCase.js +0 -51
  212. package/components/routes/cases/modals/AddToCaseModal.d.ts +0 -7
  213. package/components/routes/cases/modals/AddToCaseModal.js +0 -62
  214. package/components/routes/cases/modals/RenameItemModal.d.ts +0 -9
  215. package/components/routes/cases/modals/RenameItemModal.js +0 -48
  216. package/components/routes/cases/modals/ResolveModal.d.ts +0 -7
  217. package/components/routes/cases/modals/ResolveModal.js +0 -115
  218. package/components/routes/cases/modals/ResolveModal.test.d.ts +0 -1
  219. package/components/routes/cases/modals/ResolveModal.test.js +0 -384
  220. package/components/routes/hits/search/shared/IndexPicker.d.ts +0 -2
  221. package/components/routes/hits/search/shared/IndexPicker.js +0 -20
  222. package/components/routes/observables/ObservableViewer.d.ts +0 -7
  223. package/components/routes/observables/ObservableViewer.js +0 -27
  224. package/models/entities/generated/AttachmentsFile.d.ts +0 -12
  225. package/models/entities/generated/Case.d.ts +0 -28
  226. package/models/entities/generated/DestinationOriginal.d.ts +0 -19
  227. package/models/entities/generated/EmailAttachment.d.ts +0 -8
  228. package/models/entities/generated/EmailParent.d.ts +0 -19
  229. package/models/entities/generated/Enrichments.d.ts +0 -7
  230. package/models/entities/generated/EnrichmentsIndicator.d.ts +0 -21
  231. package/models/entities/generated/HttpResponse.d.ts +0 -11
  232. package/models/entities/generated/Item.d.ts +0 -9
  233. package/models/entities/generated/Observable.d.ts +0 -85
  234. package/models/entities/generated/ObservableCloud.d.ts +0 -20
  235. package/models/entities/generated/ObservableDestination.d.ts +0 -23
  236. package/models/entities/generated/ObservableEmail.d.ts +0 -30
  237. package/models/entities/generated/ObservableFile.d.ts +0 -36
  238. package/models/entities/generated/ObservableHowler.d.ts +0 -43
  239. package/models/entities/generated/ObservableHttp.d.ts +0 -11
  240. package/models/entities/generated/ObservableObserver.d.ts +0 -21
  241. package/models/entities/generated/ObservableOrganization.d.ts +0 -7
  242. package/models/entities/generated/ObservableProcess.d.ts +0 -34
  243. package/models/entities/generated/ObservableSource.d.ts +0 -23
  244. package/models/entities/generated/ObservableThreat.d.ts +0 -21
  245. package/models/entities/generated/ObservableTls.d.ts +0 -12
  246. package/models/entities/generated/ObserverIngress.d.ts +0 -9
  247. package/models/entities/generated/Task.d.ts +0 -10
  248. package/utils/typeUtils.d.ts +0 -7
  249. package/utils/typeUtils.js +0 -27
  250. /package/components/app/providers/{RecordSearchProvider.test.d.ts → HitSearchProvider.test.d.ts} +0 -0
  251. /package/components/elements/hit/{related/RelatedRecords.d.ts → HitDetails.d.ts} +0 -0
  252. /package/components/routes/hits/search/{RecordBrowser.d.ts → HitBrowser.d.ts} +0 -0
  253. /package/components/{elements/ContextMenu.test.d.ts → routes/hits/search/HitContextMenu.test.d.ts} +0 -0
  254. /package/components/{elements/MarkdownEditor.d.ts → routes/overviews/OverviewEditor.d.ts} +0 -0
@@ -1,34 +1,38 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Box, Chip, Divider, Grid, Stack, Tooltip, Typography, avatarClasses, iconButtonClasses, useTheme } from '@mui/material';
3
+ import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
3
4
  import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
4
5
  import { uniq } from 'lodash-es';
5
6
  import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
6
- import { useCallback, useContext, useMemo } from 'react';
7
+ import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
7
8
  import { Trans, useTranslation } from 'react-i18next';
8
9
  import { usePluginStore } from 'react-pluggable';
10
+ import { Link } from 'react-router-dom';
9
11
  import { ESCALATION_COLORS, PROVIDER_COLORS } from '@cccsaurora/howler-ui/utils/constants';
10
12
  import { stringToColor } from '@cccsaurora/howler-ui/utils/utils';
11
13
  import PluginTypography from '../PluginTypography';
12
- import AnalyticLink from './elements/AnalyticLink';
13
14
  import Assigned from './elements/Assigned';
14
15
  import EscalationChip from './elements/EscalationChip';
15
16
  import HitTimestamp from './elements/HitTimestamp';
16
17
  import HitBannerTooltip from './HitBannerTooltip';
17
18
  import { HitLayout } from './HitLayout';
18
- import RelatedRecords from './related/RelatedRecords';
19
19
  const HitBanner = ({ hit, layout = HitLayout.NORMAL, showAssigned = true }) => {
20
20
  const { t } = useTranslation();
21
21
  const { config } = useContext(ApiConfigContext);
22
22
  const theme = useTheme();
23
23
  const pluginStore = usePluginStore();
24
+ const { getMatchingAnalytic } = useMatchers();
25
+ const [analyticId, setAnalyticId] = useState();
24
26
  const compressed = useMemo(() => layout === HitLayout.DENSE, [layout]);
25
27
  const textVariant = useMemo(() => (layout === HitLayout.COMFY ? 'body1' : 'caption'), [layout]);
26
- const providerColor = useMemo(() => {
27
- if (!hit?.event.provider) {
28
- return PROVIDER_COLORS.unknown;
28
+ useEffect(() => {
29
+ if (!hit?.howler.analytic) {
30
+ return;
29
31
  }
30
- return PROVIDER_COLORS[hit?.event.provider] ?? stringToColor(hit?.event.provider);
31
- }, [hit?.event.provider]);
32
+ getMatchingAnalytic(hit).then(analytic => setAnalyticId(analytic?.analytic_id));
33
+ // eslint-disable-next-line react-hooks/exhaustive-deps
34
+ }, [hit?.howler.analytic]);
35
+ const providerColor = useMemo(() => PROVIDER_COLORS[hit.event?.provider ?? 'unknown'] ?? stringToColor(hit.event.provider), [hit.event?.provider]);
32
36
  const mitreId = useMemo(() => {
33
37
  if (hit.threat?.framework?.toLowerCase().startsWith('mitre')) {
34
38
  return;
@@ -48,23 +52,35 @@ const HitBanner = ({ hit, layout = HitLayout.NORMAL, showAssigned = true }) => {
48
52
  }
49
53
  return `/api/static/mitre/${mitreId}.svg`;
50
54
  }, [mitreId]);
51
- const leftBox = useMemo(() => (_jsx(HitBannerTooltip, { hit: hit, children: _jsxs(Box, { sx: {
52
- gridColumn: { xs: 'span 3', sm: 'span 1' },
53
- minWidth: '90px',
54
- backgroundColor: providerColor,
55
- color: theme.palette.getContrastText(providerColor),
56
- alignSelf: 'start',
57
- borderRadius: theme.shape.borderRadius,
58
- p: compressed ? 0.5 : 1,
59
- pt: 2,
60
- pl: 1
61
- }, display: "flex", flexDirection: "column", children: [_jsx(Typography, { variant: compressed ? 'caption' : 'body1', style: { wordBreak: 'break-all' }, children: hit.organization?.name ?? _jsx(Trans, { i18nKey: "unknown" }) }), iconUrl && (_jsx(Box, { sx: {
62
- width: '40px',
63
- height: '40px',
64
- mask: `url("${iconUrl}")`,
65
- maskSize: 'cover',
66
- background: theme.palette.getContrastText(providerColor)
67
- } }))] }) })), [compressed, hit, iconUrl, providerColor, theme.palette, theme.shape.borderRadius]);
55
+ const leftBox = useMemo(() => {
56
+ if (hit.howler.is_bundle) {
57
+ return (_jsx(Box, { sx: {
58
+ alignSelf: 'stretch',
59
+ backgroundColor: providerColor,
60
+ borderRadius: theme.shape.borderRadius,
61
+ minWidth: '15px'
62
+ } }));
63
+ }
64
+ else {
65
+ return (_jsx(HitBannerTooltip, { hit: hit, children: _jsxs(Box, { sx: {
66
+ gridColumn: { xs: 'span 3', sm: 'span 1' },
67
+ minWidth: '90px',
68
+ backgroundColor: providerColor,
69
+ color: theme.palette.getContrastText(providerColor),
70
+ alignSelf: 'start',
71
+ borderRadius: theme.shape.borderRadius,
72
+ p: compressed ? 0.5 : 1,
73
+ pt: 2,
74
+ pl: 1
75
+ }, display: "flex", flexDirection: "column", children: [_jsx(Typography, { variant: compressed ? 'caption' : 'body1', style: { wordBreak: 'break-all' }, children: hit.organization?.name ?? _jsx(Trans, { i18nKey: "unknown" }) }), iconUrl && (_jsx(Box, { sx: {
76
+ width: '40px',
77
+ height: '40px',
78
+ mask: `url("${iconUrl}")`,
79
+ maskSize: 'cover',
80
+ background: theme.palette.getContrastText(providerColor)
81
+ } }))] }) }));
82
+ }
83
+ }, [compressed, hit, iconUrl, providerColor, theme.palette, theme.shape.borderRadius]);
68
84
  /**
69
85
  * The tooltips are necessary only when in the most compressed format
70
86
  */
@@ -72,7 +88,7 @@ const HitBanner = ({ hit, layout = HitLayout.NORMAL, showAssigned = true }) => {
72
88
  const _children = (_jsxs(Stack, { direction: "row", spacing: 1, flex: 1, children: [_jsxs(Typography, { variant: textVariant, noWrap: compressed, textOverflow: compressed ? 'ellipsis' : 'wrap', ...typographyProps, sx: [
73
89
  { display: 'flex', flexDirection: 'row' },
74
90
  ...(Array.isArray(typographyProps?.sx) ? typographyProps?.sx : [typographyProps?.sx])
75
- ], children: [t(i18nKey), ":"] }), (Array.isArray(value) ? value : [value]).map(val => (_jsx(PluginTypography, { component: "span", context: "banner", variant: textVariant, noWrap: compressed, textOverflow: compressed ? 'ellipsis' : 'wrap', ...typographyProps, value: val, field: field, obj: hit }, val)))] }));
91
+ ], children: [t(i18nKey), ":"] }), (Array.isArray(value) ? value : [value]).map(val => (_jsx(PluginTypography, { component: "span", context: "banner", variant: textVariant, noWrap: compressed, textOverflow: compressed ? 'ellipsis' : 'wrap', ...typographyProps, value: val, field: field, hit: hit }, val)))] }));
76
92
  return compressed ? (_jsx(Tooltip, { title: Array.isArray(value) ? (_jsx("div", { children: value.map(_indicator => (_jsx("p", { style: { margin: 0, padding: 0 }, children: _indicator }, _indicator))) })) : (value), children: _children })) : (_children);
77
93
  }, [compressed, hit, t, textVariant]);
78
94
  return (_jsxs(Box, { display: "grid", gridTemplateColumns: "minmax(0, auto) minmax(0, 1fr) minmax(0, auto)", alignItems: "stretch", sx: { width: '100%', ml: 0, overflow: 'hidden' }, children: [leftBox, _jsxs(Stack, { sx: {
@@ -82,7 +98,11 @@ const HitBanner = ({ hit, layout = HitLayout.NORMAL, showAssigned = true }) => {
82
98
  }, spacing: layout !== HitLayout.COMFY ? 1 : 2, divider: _jsx(Divider, { orientation: "horizontal", sx: [
83
99
  layout !== HitLayout.COMFY && { marginTop: '4px !important' },
84
100
  { mr: `${theme.spacing(-1)} !important` }
85
- ] }), children: [_jsx(AnalyticLink, { hit: hit }), hit.howler?.rationale && (_jsxs(Typography, { flex: 1, variant: textVariant, color: ESCALATION_COLORS[hit.howler.escalation] + '.main', sx: { fontWeight: 'bold' }, children: [t('hit.header.rationale'), ": ", hit.howler.rationale] })), hit.howler?.outline && (_jsxs(_Fragment, { children: [_jsxs(Grid, { container: true, spacing: layout !== HitLayout.COMFY ? 1 : 2, sx: { ml: `${theme.spacing(-1)} !important` }, children: [hit.howler.outline.threat && (_jsx(Grid, { item: true, children: _jsx(Wrapper, { i18nKey: "hit.header.threat", value: hit.howler.outline.threat, field: "howler.outline.threat" }) })), hit.howler.outline.target && (_jsx(Grid, { item: true, children: _jsx(Wrapper, { i18nKey: "hit.header.target", value: hit.howler.outline.target, field: "howler.outline.target" }) }))] }), hit.howler.outline.indicators?.length > 0 && (_jsxs(Stack, { direction: "row", spacing: 1, children: [_jsxs(Typography, { component: "span", variant: textVariant, children: [t('hit.header.indicators'), ":"] }), _jsx(Grid, { container: true, spacing: 0.5, sx: { mt: `${theme.spacing(-0.5)} !important`, ml: `${theme.spacing(0.25)} !important` }, children: uniq(hit.howler.outline.indicators).map((_indicator, index) => {
101
+ ] }), children: [_jsxs(Typography, { variant: compressed ? 'body1' : 'h6', fontWeight: compressed && 'bold', sx: { alignSelf: 'start', '& a': { color: 'text.primary' } }, children: [analyticId ? (_jsx(Link, { to: `/analytics/${analyticId}`, onAuxClick: e => {
102
+ e.stopPropagation();
103
+ }, onClick: e => {
104
+ e.stopPropagation();
105
+ }, children: hit.howler.analytic })) : (hit.howler.analytic), hit.howler.detection && ': ', hit.howler.detection] }), hit.howler?.rationale && (_jsxs(Typography, { flex: 1, variant: textVariant, color: ESCALATION_COLORS[hit.howler.escalation] + '.main', sx: { fontWeight: 'bold' }, children: [t('hit.header.rationale'), ": ", hit.howler.rationale] })), hit.howler?.outline && (_jsxs(_Fragment, { children: [_jsxs(Grid, { container: true, spacing: layout !== HitLayout.COMFY ? 1 : 2, sx: { ml: `${theme.spacing(-1)} !important` }, children: [hit.howler.outline.threat && (_jsx(Grid, { item: true, children: _jsx(Wrapper, { i18nKey: "hit.header.threat", value: hit.howler.outline.threat, field: "howler.outline.threat" }) })), hit.howler.outline.target && (_jsx(Grid, { item: true, children: _jsx(Wrapper, { i18nKey: "hit.header.target", value: hit.howler.outline.target, field: "howler.outline.target" }) }))] }), hit.howler.outline.indicators?.length > 0 && (_jsxs(Stack, { direction: "row", spacing: 1, children: [_jsxs(Typography, { component: "span", variant: textVariant, children: [t('hit.header.indicators'), ":"] }), _jsx(Grid, { container: true, spacing: 0.5, sx: { mt: `${theme.spacing(-0.5)} !important`, ml: `${theme.spacing(0.25)} !important` }, children: uniq(hit.howler.outline.indicators).map((_indicator, index) => {
86
106
  return (_jsx(Grid, { item: true, children: _jsxs(Stack, { direction: "row", children: [_jsx(PluginTypography, { context: "indicators", variant: textVariant, value: _indicator, children: _indicator }), index < hit.howler.outline.indicators.length - 1 && (_jsx(Typography, { variant: textVariant, children: ',' }))] }) }, _indicator));
87
107
  }) })] })), hit.howler.outline.summary && (_jsx(Wrapper, { i18nKey: "hit.header.summary", value: hit.howler.outline.summary, paragraph: true, textOverflow: "wrap", sx: [compressed && { marginTop: `0 !important` }], field: "howler.outline.summary" }))] }))] }), _jsxs(Stack, { direction: "column", spacing: layout !== HitLayout.COMFY ? 0.5 : 1, alignSelf: "stretch", sx: [
88
108
  { minWidth: 0, alignItems: { sm: 'end', md: 'start' }, flex: 1, pl: 1 },
@@ -96,6 +116,6 @@ const HitBanner = ({ hit, layout = HitLayout.NORMAL, showAssigned = true }) => {
96
116
  width: theme.spacing(3)
97
117
  }
98
118
  }
99
- ], children: [_jsx(HitTimestamp, { hit: hit, layout: layout }), showAssigned && _jsx(Assigned, { hit: hit, layout: layout }), _jsxs(Stack, { direction: "row", spacing: layout !== HitLayout.COMFY ? 0.5 : 1, children: [_jsx(EscalationChip, { hit: hit, layout: layout }), ['in-progress', 'on-hold'].includes(hit.howler.status) && (_jsx(Chip, { sx: { width: 'fit-content', display: 'inline-flex' }, label: hit.howler.status, color: "primary" }))] }), hit.howler.related && _jsx(RelatedRecords, { hit: hit }), howlerPluginStore.plugins.flatMap(plugin => pluginStore.executeFunction(`${plugin}.status`, { hit, layout }))] })] }));
119
+ ], children: [_jsx(HitTimestamp, { hit: hit, layout: layout }), showAssigned && _jsx(Assigned, { hit: hit, layout: layout }), _jsxs(Stack, { direction: "row", spacing: layout !== HitLayout.COMFY ? 0.5 : 1, children: [_jsx(EscalationChip, { hit: hit, layout: layout }), ['in-progress', 'on-hold'].includes(hit.howler.status) && (_jsx(Chip, { sx: { width: 'fit-content', display: 'inline-flex' }, label: hit.howler.status, size: layout !== HitLayout.COMFY ? 'small' : 'medium', color: "primary" })), hit.howler.is_bundle && (_jsx(Chip, { size: layout !== HitLayout.COMFY ? 'small' : 'medium', label: t('hit.header.bundlesize', { hits: hit.howler.hits.length }) }))] }), howlerPluginStore.plugins.flatMap(plugin => pluginStore.executeFunction(`${plugin}.status`, { hit, layout }))] })] }));
100
120
  };
101
121
  export default HitBanner;
@@ -3,6 +3,5 @@ declare const _default: import("react").NamedExoticComponent<{
3
3
  id?: string;
4
4
  layout: HitLayout;
5
5
  readOnly?: boolean;
6
- elevation?: number;
7
6
  }>;
8
7
  export default _default;
@@ -1,24 +1,24 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { CardContent, Skeleton } from '@mui/material';
3
- import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
3
+ import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
4
4
  import { memo, useEffect } from 'react';
5
5
  import { useContextSelector } from 'use-context-selector';
6
6
  import HowlerCard from '../display/HowlerCard';
7
7
  import HitBanner from './HitBanner';
8
8
  import HitLabels from './HitLabels';
9
9
  import HitOutline from './HitOutline';
10
- const HitCard = ({ id, layout, readOnly = true, elevation }) => {
11
- const getRecord = useContextSelector(RecordContext, ctx => ctx.getRecord);
12
- const hit = useContextSelector(RecordContext, ctx => ctx.records[id]);
10
+ const HitCard = ({ id, layout, readOnly = true }) => {
11
+ const getHit = useContextSelector(HitContext, ctx => ctx.getHit);
12
+ const hit = useContextSelector(HitContext, ctx => ctx.hits[id]);
13
13
  useEffect(() => {
14
14
  if (!hit) {
15
- getRecord(id);
15
+ getHit(id);
16
16
  }
17
17
  // eslint-disable-next-line react-hooks/exhaustive-deps
18
18
  }, [id]);
19
19
  if (!hit) {
20
20
  return _jsx(Skeleton, { variant: "rounded", height: "200px" });
21
21
  }
22
- return (_jsx(HowlerCard, { id: hit?.howler.id, tabIndex: 0, sx: { position: 'relative' }, elevation: elevation, children: _jsxs(CardContent, { children: [_jsx(HitBanner, { hit: hit, layout: layout }), _jsx(HitOutline, { hit: hit, layout: layout }), _jsx(HitLabels, { hit: hit, readOnly: readOnly })] }) }));
22
+ return (_jsx(HowlerCard, { tabIndex: 0, sx: { position: 'relative' }, children: _jsxs(CardContent, { children: [_jsx(HitBanner, { hit: hit, layout: layout }), _jsx(HitOutline, { hit: hit, layout: layout }), _jsx(HitLabels, { hit: hit, readOnly: readOnly })] }) }));
23
23
  };
24
24
  export default memo(HitCard);
@@ -1,12 +1,11 @@
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
- interface RecordCommentsProps {
6
- record: Hit | Observable;
4
+ interface HitCommentsProps {
5
+ hit: Hit;
7
6
  users: {
8
7
  [id: string]: HowlerUser;
9
8
  };
10
9
  }
11
- declare const RecordComments: FC<RecordCommentsProps>;
12
- export default RecordComments;
10
+ declare const HitComments: FC<HitCommentsProps>;
11
+ export default HitComments;
@@ -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]) => {
@@ -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))