@cccsaurora/howler-ui 2.19.0-cases.862 → 2.19.0-dev.1000

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 (397) hide show
  1. package/api/index.d.ts +0 -4
  2. package/api/index.js +2 -10
  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.d.ts +1 -0
  9. package/components/app/App.js +20 -42
  10. package/components/app/hooks/useMatchers.d.ts +1 -1
  11. package/components/app/hooks/useMatchers.js +11 -23
  12. package/components/app/hooks/useMatchers.test.js +22 -22
  13. package/components/app/hooks/useTitle.js +5 -5
  14. package/components/app/providers/FavouritesProvider.js +2 -6
  15. package/components/app/providers/HitProvider.d.ts +22 -0
  16. package/components/app/providers/{RecordProvider.js → HitProvider.js} +42 -42
  17. package/components/app/providers/{RecordSearchProvider.d.ts → HitSearchProvider.d.ts} +6 -6
  18. package/components/app/providers/{RecordSearchProvider.js → HitSearchProvider.js} +17 -12
  19. package/components/app/providers/{RecordSearchProvider.test.js → HitSearchProvider.test.js} +71 -52
  20. package/components/app/providers/ModalProvider.d.ts +1 -1
  21. package/components/app/providers/ModalProvider.js +5 -1
  22. package/components/app/providers/ParameterProvider.d.ts +2 -9
  23. package/components/app/providers/ParameterProvider.js +240 -165
  24. package/components/app/providers/ParameterProvider.test.js +94 -346
  25. package/components/app/providers/SocketProvider.d.ts +2 -11
  26. package/components/app/providers/SocketProvider.js +5 -18
  27. package/components/app/providers/UserListProvider.js +8 -28
  28. package/components/elements/PluginTypography.d.ts +1 -2
  29. package/components/elements/PluginTypography.js +2 -3
  30. package/components/elements/UserList.d.ts +2 -5
  31. package/components/elements/UserList.js +8 -18
  32. package/components/elements/addons/buttons/CustomButton.test.js +106 -0
  33. package/components/elements/addons/layout/FlexOne.test.js +30 -0
  34. package/components/elements/addons/lists/TuiListBase.js +4 -0
  35. package/components/elements/addons/lists/TuiListElement.js +6 -3
  36. package/components/elements/addons/lists/index.d.ts +1 -0
  37. package/components/elements/addons/search/SearchPagination.test.js +80 -0
  38. package/components/elements/addons/search/SearchTotal.test.js +45 -0
  39. package/components/elements/addons/search/phrase/Phrase.js +1 -1
  40. package/components/elements/display/ChipPopper.d.ts +1 -1
  41. package/components/elements/display/ChipPopper.js +5 -5
  42. package/components/elements/display/DocumentationButton.test.js +84 -0
  43. package/components/elements/display/DynamicTabs.test.js +93 -0
  44. package/components/elements/display/HowlerAvatar.js +2 -1
  45. package/components/elements/display/HowlerCard.js +1 -1
  46. package/components/elements/display/HowlerCard.test.js +55 -0
  47. package/components/elements/display/Image.test.js +79 -0
  48. package/components/elements/display/Markdown.js +9 -2
  49. package/components/elements/display/Modal.js +0 -2
  50. package/components/elements/display/QueryResultText.test.js +48 -0
  51. package/components/elements/display/TextDivider.test.js +43 -0
  52. package/components/elements/display/TypingIndicator.test.js +33 -0
  53. package/components/elements/display/icons/BundleButton.d.ts +6 -0
  54. package/components/elements/display/icons/BundleButton.js +32 -0
  55. package/components/elements/display/modals/ConfirmDeleteModal.d.ts +4 -0
  56. package/components/elements/display/modals/ConfirmDeleteModal.js +4 -2
  57. package/components/elements/hit/HitActions.js +4 -4
  58. package/components/elements/hit/HitBanner.d.ts +0 -1
  59. package/components/elements/hit/HitBanner.js +49 -31
  60. package/components/elements/hit/HitCard.d.ts +0 -2
  61. package/components/elements/hit/HitCard.js +7 -7
  62. package/components/elements/{record/RecordComments.d.ts → hit/HitComments.d.ts} +4 -5
  63. package/components/elements/{record/RecordComments.js → hit/HitComments.js} +28 -29
  64. package/components/elements/{ObjectDetails.js → hit/HitDetails.js} +17 -17
  65. package/components/elements/hit/HitLabels.js +2 -2
  66. package/components/elements/hit/HitOutline.d.ts +0 -1
  67. package/components/elements/hit/HitOutline.js +3 -3
  68. package/components/elements/hit/{HitPreview.d.ts → HitQuickSearch.d.ts} +3 -3
  69. package/components/elements/hit/{HitPreview.js → HitQuickSearch.js} +4 -10
  70. package/components/elements/hit/HitRelated.d.ts +6 -0
  71. package/components/elements/hit/HitRelated.js +7 -0
  72. package/components/elements/hit/HitSummary.d.ts +1 -2
  73. package/components/elements/hit/HitSummary.js +8 -8
  74. package/components/elements/{record/RecordWorklog.d.ts → hit/HitWorklog.d.ts} +3 -4
  75. package/components/elements/{record/RecordWorklog.js → hit/HitWorklog.js} +13 -15
  76. package/components/elements/hit/aggregate/HitGraph.js +8 -8
  77. package/components/elements/hit/elements/{Assigned.d.ts → HitUsers.d.ts} +5 -2
  78. package/components/elements/hit/elements/HitUsers.js +33 -0
  79. package/components/elements/hit/outlines/DefaultOutline.js +1 -1
  80. package/components/elements/view/ViewTitle.d.ts +0 -1
  81. package/components/elements/view/ViewTitle.js +2 -9
  82. package/components/hooks/useHitActions.d.ts +1 -1
  83. package/components/hooks/useHitActions.js +4 -4
  84. package/components/hooks/{useRecordSelection.d.ts → useHitSelection.d.ts} +2 -2
  85. package/components/hooks/{useRecordSelection.js → useHitSelection.js} +33 -12
  86. package/components/hooks/useMyPreferences.js +3 -11
  87. package/components/hooks/useMySearch.js +2 -2
  88. package/components/hooks/useMySitemap.js +1 -4
  89. package/components/hooks/useMyTheme.js +2 -9
  90. package/components/routes/403.test.js +27 -0
  91. package/components/routes/404.test.js +26 -0
  92. package/components/routes/ErrorBoundary.test.js +43 -0
  93. package/components/routes/ErrorOccured.test.js +35 -0
  94. package/components/routes/Logout.test.js +59 -0
  95. package/components/routes/action/edit/ActionEditor.js +2 -2
  96. package/components/routes/action/view/ActionDetails.js +4 -2
  97. package/components/routes/action/view/ActionSearch.js +12 -7
  98. package/components/routes/advanced/QueryBuilder.js +1 -1
  99. package/components/routes/advanced/QueryEditor.js +3 -3
  100. package/components/routes/advanced/historyCompletionProvider.js +3 -3
  101. package/components/routes/analytics/AnalyticDetails.js +12 -8
  102. package/components/routes/analytics/AnalyticSearch.js +1 -1
  103. package/components/routes/dossiers/DossierEditor.js +2 -2
  104. package/components/routes/dossiers/DossierEditor.test.js +1 -1
  105. package/components/routes/dossiers/Dossiers.js +15 -11
  106. package/components/routes/help/ApiDocumentation.js +1 -1
  107. package/components/routes/help/BundleDocumentation.d.ts +3 -0
  108. package/components/routes/help/BundleDocumentation.js +12 -0
  109. package/components/routes/help/HitBannerDocumentation.js +0 -1
  110. package/components/routes/help/HitDocumentation.js +3 -1
  111. package/components/routes/help/markdown/en/bundles.md.js +1 -0
  112. package/components/routes/help/markdown/en/client.md.js +1 -1
  113. package/components/routes/help/markdown/fr/bundles.md.js +1 -0
  114. package/components/routes/help/markdown/fr/client.md.js +1 -1
  115. package/components/routes/hits/search/BundleParentMenu.d.ts +6 -0
  116. package/components/routes/hits/search/BundleParentMenu.js +32 -0
  117. package/components/routes/hits/search/BundleScroller.d.ts +2 -0
  118. package/components/routes/hits/search/BundleScroller.js +6 -0
  119. package/components/routes/hits/search/{RecordBrowser.js → HitBrowser.js} +9 -9
  120. package/components/{elements/record/RecordContextMenu.d.ts → routes/hits/search/HitContextMenu.d.ts} +3 -3
  121. package/components/routes/hits/search/HitContextMenu.js +239 -0
  122. package/components/{elements/record/RecordContextMenu.test.js → routes/hits/search/HitContextMenu.test.js} +43 -98
  123. package/components/routes/hits/search/{RecordQuery.d.ts → HitQuery.d.ts} +2 -2
  124. package/components/routes/hits/search/{RecordQuery.js → HitQuery.js} +6 -6
  125. package/components/routes/hits/search/InformationPane.d.ts +0 -1
  126. package/components/routes/hits/search/InformationPane.js +63 -50
  127. package/components/routes/hits/search/LayoutSettings.js +3 -3
  128. package/components/routes/hits/search/QuerySettings.js +1 -2
  129. package/components/routes/hits/search/QuerySettings.test.js +9 -14
  130. package/components/routes/hits/search/SearchPane.js +49 -26
  131. package/components/routes/hits/search/ViewLink.js +3 -3
  132. package/components/routes/hits/search/ViewLink.test.js +8 -8
  133. package/components/routes/hits/search/grid/AddColumnModal.js +4 -5
  134. package/components/routes/hits/search/grid/EnhancedCell.d.ts +1 -2
  135. package/components/routes/hits/search/grid/EnhancedCell.js +2 -2
  136. package/components/routes/hits/search/grid/HitGrid.js +18 -20
  137. package/components/routes/hits/search/grid/{RecordRow.d.ts → HitRow.d.ts} +2 -3
  138. package/components/routes/hits/search/grid/{RecordRow.js → HitRow.js} +9 -11
  139. package/components/routes/hits/search/shared/CustomSpan.js +7 -7
  140. package/components/routes/hits/search/shared/CustomSpan.test.js +170 -0
  141. package/components/routes/hits/view/HitViewer.js +13 -12
  142. package/components/routes/home/AddNewCard.test.js +68 -0
  143. package/components/routes/home/ViewCard.js +41 -47
  144. package/components/routes/home/ViewRefresh.test.js +79 -0
  145. package/components/routes/overviews/OverviewCard.test.js +108 -0
  146. package/components/{elements/MarkdownEditor.js → routes/overviews/OverviewEditor.js} +3 -3
  147. package/components/routes/overviews/OverviewViewer.js +14 -10
  148. package/components/routes/overviews/Overviews.js +15 -11
  149. package/components/routes/settings/LocalSection.js +2 -1
  150. package/components/routes/settings/SettingsSection.test.js +19 -0
  151. package/components/routes/templates/TemplateCard.d.ts +3 -1
  152. package/components/routes/templates/TemplateCard.js +8 -4
  153. package/components/routes/templates/TemplateCard.test.d.ts +1 -0
  154. package/components/routes/templates/TemplateCard.test.js +115 -0
  155. package/components/routes/templates/TemplateViewer.js +57 -16
  156. package/components/routes/templates/Templates.js +23 -6
  157. package/components/routes/views/ViewComposer.js +19 -46
  158. package/components/routes/views/Views.js +8 -4
  159. package/locales/en/translation.json +17 -117
  160. package/locales/fr/translation.json +19 -119
  161. package/models/WithMetadata.d.ts +1 -2
  162. package/models/entities/generated/{ThreatEnrichment.d.ts → Enrichment.d.ts} +1 -1
  163. package/models/entities/generated/Hit.d.ts +1 -1
  164. package/models/entities/generated/Howler.d.ts +6 -0
  165. package/models/entities/generated/Rule.d.ts +9 -6
  166. package/models/entities/generated/Threat.d.ts +2 -2
  167. package/models/entities/generated/UserUser.d.ts +2 -0
  168. package/models/entities/generated/View.d.ts +0 -1
  169. package/package.json +112 -132
  170. package/plugins/clue/components/ClueTypography.js +2 -2
  171. package/plugins/clue/utils.d.ts +1 -2
  172. package/rest/FetchClient.test.d.ts +1 -0
  173. package/rest/FetchClient.test.js +81 -0
  174. package/setupTests.js +5 -0
  175. package/tests/mocks.d.ts +1 -11
  176. package/tests/mocks.js +7 -12
  177. package/tests/server-handlers.js +1 -6
  178. package/tests/utils.d.ts +0 -4
  179. package/tests/utils.js +0 -20
  180. package/utils/Throttler.test.d.ts +1 -0
  181. package/utils/Throttler.test.js +135 -0
  182. package/utils/actionUtils.test.d.ts +1 -0
  183. package/utils/actionUtils.test.js +140 -0
  184. package/utils/constants.d.ts +4 -4
  185. package/utils/constants.js +1 -6
  186. package/utils/hitFunctions.d.ts +1 -2
  187. package/utils/hitFunctions.js +4 -4
  188. package/utils/hitFunctions.test.d.ts +1 -0
  189. package/utils/hitFunctions.test.js +52 -0
  190. package/utils/localStorage.test.d.ts +1 -0
  191. package/utils/localStorage.test.js +96 -0
  192. package/utils/menuUtils.test.d.ts +1 -0
  193. package/utils/menuUtils.test.js +198 -0
  194. package/utils/sessionStorage.test.d.ts +1 -0
  195. package/utils/sessionStorage.test.js +81 -0
  196. package/utils/socketUtils.d.ts +0 -14
  197. package/utils/socketUtils.js +1 -17
  198. package/utils/socketUtils.test.js +16 -49
  199. package/utils/stringUtils.test.d.ts +1 -0
  200. package/utils/stringUtils.test.js +159 -0
  201. package/utils/utils.test.d.ts +1 -0
  202. package/utils/utils.test.js +292 -0
  203. package/utils/viewUtils.js +0 -3
  204. package/utils/viewUtils.test.d.ts +1 -0
  205. package/utils/viewUtils.test.js +45 -0
  206. package/utils/xsrf.test.d.ts +1 -0
  207. package/utils/xsrf.test.js +41 -0
  208. package/api/search/case.d.ts +0 -4
  209. package/api/search/case.js +0 -8
  210. package/api/socket/index.d.ts +0 -3
  211. package/api/socket/index.js +0 -6
  212. package/api/socket/viewers.d.ts +0 -2
  213. package/api/socket/viewers.js +0 -8
  214. package/api/socket/viewers.test.js +0 -44
  215. package/api/v2/case/index.d.ts +0 -9
  216. package/api/v2/case/index.js +0 -21
  217. package/api/v2/case/items.d.ts +0 -6
  218. package/api/v2/case/items.js +0 -18
  219. package/api/v2/case/rules.d.ts +0 -6
  220. package/api/v2/case/rules.js +0 -18
  221. package/api/v2/index.d.ts +0 -4
  222. package/api/v2/index.js +0 -6
  223. package/api/v2/search/facet.d.ts +0 -3
  224. package/api/v2/search/facet.js +0 -12
  225. package/api/v2/search/index.d.ts +0 -5
  226. package/api/v2/search/index.js +0 -24
  227. package/components/app/providers/RecordProvider.d.ts +0 -23
  228. package/components/elements/ContextMenu.d.ts +0 -56
  229. package/components/elements/ContextMenu.js +0 -109
  230. package/components/elements/ContextMenu.test.js +0 -215
  231. package/components/elements/ObjectDetails.d.ts +0 -6
  232. package/components/elements/case/CaseCard.d.ts +0 -12
  233. package/components/elements/case/CaseCard.js +0 -42
  234. package/components/elements/case/CasePreview.d.ts +0 -6
  235. package/components/elements/case/CasePreview.js +0 -17
  236. package/components/elements/case/StatusIcon.d.ts +0 -5
  237. package/components/elements/case/StatusIcon.js +0 -13
  238. package/components/elements/hit/elements/AnalyticLink.d.ts +0 -9
  239. package/components/elements/hit/elements/AnalyticLink.js +0 -22
  240. package/components/elements/hit/elements/Assigned.js +0 -34
  241. package/components/elements/hit/elements/Assigned.test.js +0 -65
  242. package/components/elements/hit/related/RelatedRecords.js +0 -63
  243. package/components/elements/observable/ObservableCard.d.ts +0 -6
  244. package/components/elements/observable/ObservableCard.js +0 -22
  245. package/components/elements/observable/ObservablePreview.d.ts +0 -6
  246. package/components/elements/observable/ObservablePreview.js +0 -12
  247. package/components/elements/record/RecordContextMenu.js +0 -268
  248. package/components/elements/record/RecordRelated.d.ts +0 -7
  249. package/components/elements/record/RecordRelated.js +0 -34
  250. package/components/hooks/useRelatedRecords.d.ts +0 -13
  251. package/components/hooks/useRelatedRecords.js +0 -32
  252. package/components/routes/cases/CaseViewer.d.ts +0 -2
  253. package/components/routes/cases/CaseViewer.js +0 -44
  254. package/components/routes/cases/CaseViewer.test.js +0 -133
  255. package/components/routes/cases/Cases.d.ts +0 -2
  256. package/components/routes/cases/Cases.js +0 -148
  257. package/components/routes/cases/constants.d.ts +0 -6
  258. package/components/routes/cases/constants.js +0 -6
  259. package/components/routes/cases/detail/AlertPanel.d.ts +0 -6
  260. package/components/routes/cases/detail/AlertPanel.js +0 -33
  261. package/components/routes/cases/detail/CaseAssets.d.ts +0 -11
  262. package/components/routes/cases/detail/CaseAssets.js +0 -104
  263. package/components/routes/cases/detail/CaseAssets.test.js +0 -167
  264. package/components/routes/cases/detail/CaseDashboard.d.ts +0 -7
  265. package/components/routes/cases/detail/CaseDashboard.js +0 -66
  266. package/components/routes/cases/detail/CaseDetails.d.ts +0 -6
  267. package/components/routes/cases/detail/CaseDetails.js +0 -70
  268. package/components/routes/cases/detail/CaseOverview.d.ts +0 -7
  269. package/components/routes/cases/detail/CaseOverview.js +0 -43
  270. package/components/routes/cases/detail/CaseRules.d.ts +0 -7
  271. package/components/routes/cases/detail/CaseRules.js +0 -57
  272. package/components/routes/cases/detail/CaseRules.test.js +0 -221
  273. package/components/routes/cases/detail/CaseSidebar.d.ts +0 -8
  274. package/components/routes/cases/detail/CaseSidebar.js +0 -107
  275. package/components/routes/cases/detail/CaseSidebar.test.js +0 -266
  276. package/components/routes/cases/detail/CaseTask.d.ts +0 -11
  277. package/components/routes/cases/detail/CaseTask.js +0 -66
  278. package/components/routes/cases/detail/CaseTimeline.d.ts +0 -12
  279. package/components/routes/cases/detail/CaseTimeline.js +0 -106
  280. package/components/routes/cases/detail/CaseTimeline.test.js +0 -320
  281. package/components/routes/cases/detail/CreateRuleDialog.d.ts +0 -9
  282. package/components/routes/cases/detail/CreateRuleDialog.js +0 -163
  283. package/components/routes/cases/detail/CreateRuleDialog.test.js +0 -259
  284. package/components/routes/cases/detail/ItemPage.d.ts +0 -6
  285. package/components/routes/cases/detail/ItemPage.js +0 -95
  286. package/components/routes/cases/detail/RelatedCasePanel.d.ts +0 -6
  287. package/components/routes/cases/detail/RelatedCasePanel.js +0 -34
  288. package/components/routes/cases/detail/TaskPanel.d.ts +0 -7
  289. package/components/routes/cases/detail/TaskPanel.js +0 -52
  290. package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +0 -11
  291. package/components/routes/cases/detail/aggregates/CaseAggregate.js +0 -24
  292. package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +0 -6
  293. package/components/routes/cases/detail/aggregates/SourceAggregate.js +0 -26
  294. package/components/routes/cases/detail/assets/Asset.d.ts +0 -14
  295. package/components/routes/cases/detail/assets/Asset.js +0 -12
  296. package/components/routes/cases/detail/assets/Asset.test.js +0 -72
  297. package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +0 -20
  298. package/components/routes/cases/detail/sidebar/CaseFolder.js +0 -83
  299. package/components/routes/cases/detail/sidebar/CaseFolder.test.js +0 -295
  300. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.d.ts +0 -34
  301. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.js +0 -103
  302. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.js +0 -363
  303. package/components/routes/cases/detail/sidebar/FolderEntry.d.ts +0 -25
  304. package/components/routes/cases/detail/sidebar/FolderEntry.js +0 -88
  305. package/components/routes/cases/detail/sidebar/FolderEntry.test.js +0 -206
  306. package/components/routes/cases/detail/sidebar/RootDropZone.d.ts +0 -5
  307. package/components/routes/cases/detail/sidebar/RootDropZone.js +0 -33
  308. package/components/routes/cases/detail/sidebar/types.d.ts +0 -9
  309. package/components/routes/cases/detail/sidebar/utils.d.ts +0 -3
  310. package/components/routes/cases/detail/sidebar/utils.js +0 -29
  311. package/components/routes/cases/detail/sidebar/utils.test.js +0 -82
  312. package/components/routes/cases/hooks/useCase.d.ts +0 -13
  313. package/components/routes/cases/hooks/useCase.js +0 -69
  314. package/components/routes/cases/hooks/useCase.test.js +0 -141
  315. package/components/routes/cases/modals/AddToCaseModal.d.ts +0 -7
  316. package/components/routes/cases/modals/AddToCaseModal.js +0 -59
  317. package/components/routes/cases/modals/AddToCaseModal.test.js +0 -313
  318. package/components/routes/cases/modals/CaseRecordRow.d.ts +0 -9
  319. package/components/routes/cases/modals/CaseRecordRow.js +0 -15
  320. package/components/routes/cases/modals/CreateCaseModal.d.ts +0 -7
  321. package/components/routes/cases/modals/CreateCaseModal.js +0 -55
  322. package/components/routes/cases/modals/CreateCaseModal.test.js +0 -358
  323. package/components/routes/cases/modals/RenameItemModal.d.ts +0 -9
  324. package/components/routes/cases/modals/RenameItemModal.js +0 -48
  325. package/components/routes/cases/modals/ResolveModal.d.ts +0 -7
  326. package/components/routes/cases/modals/ResolveModal.js +0 -115
  327. package/components/routes/cases/modals/ResolveModal.test.js +0 -394
  328. package/components/routes/cases/modals/hooks.d.ts +0 -7
  329. package/components/routes/cases/modals/hooks.js +0 -44
  330. package/components/routes/cases/modals/types.d.ts +0 -5
  331. package/components/routes/cases/search/CaseAssigneeFilter.d.ts +0 -6
  332. package/components/routes/cases/search/CaseAssigneeFilter.js +0 -33
  333. package/components/routes/cases/search/CaseAssigneeFilter.test.js +0 -127
  334. package/components/routes/cases/search/CaseDateFilter.d.ts +0 -13
  335. package/components/routes/cases/search/CaseDateFilter.js +0 -26
  336. package/components/routes/cases/search/CaseDateFilter.test.js +0 -115
  337. package/components/routes/cases/search/CaseStatusFilter.d.ts +0 -6
  338. package/components/routes/cases/search/CaseStatusFilter.js +0 -13
  339. package/components/routes/cases/search/CaseStatusFilter.test.js +0 -86
  340. package/components/routes/hits/search/shared/IndexPicker.d.ts +0 -2
  341. package/components/routes/hits/search/shared/IndexPicker.js +0 -20
  342. package/components/routes/observables/ObservableViewer.d.ts +0 -7
  343. package/components/routes/observables/ObservableViewer.js +0 -27
  344. package/models/entities/generated/AttachmentsFile.d.ts +0 -12
  345. package/models/entities/generated/Case.d.ts +0 -28
  346. package/models/entities/generated/DestinationOriginal.d.ts +0 -19
  347. package/models/entities/generated/EmailAttachment.d.ts +0 -8
  348. package/models/entities/generated/EmailParent.d.ts +0 -19
  349. package/models/entities/generated/Enrichments.d.ts +0 -7
  350. package/models/entities/generated/EnrichmentsIndicator.d.ts +0 -21
  351. package/models/entities/generated/HttpResponse.d.ts +0 -11
  352. package/models/entities/generated/Item.d.ts +0 -9
  353. package/models/entities/generated/Observable.d.ts +0 -85
  354. package/models/entities/generated/ObservableCloud.d.ts +0 -20
  355. package/models/entities/generated/ObservableDestination.d.ts +0 -23
  356. package/models/entities/generated/ObservableEmail.d.ts +0 -30
  357. package/models/entities/generated/ObservableFile.d.ts +0 -36
  358. package/models/entities/generated/ObservableHowler.d.ts +0 -42
  359. package/models/entities/generated/ObservableHttp.d.ts +0 -11
  360. package/models/entities/generated/ObservableObserver.d.ts +0 -21
  361. package/models/entities/generated/ObservableOrganization.d.ts +0 -7
  362. package/models/entities/generated/ObservableProcess.d.ts +0 -34
  363. package/models/entities/generated/ObservableSource.d.ts +0 -23
  364. package/models/entities/generated/ObservableThreat.d.ts +0 -21
  365. package/models/entities/generated/ObservableTls.d.ts +0 -12
  366. package/models/entities/generated/ObserverIngress.d.ts +0 -9
  367. package/models/entities/generated/Task.d.ts +0 -10
  368. package/models/socket/CaseUpdate.d.ts +0 -5
  369. package/models/socket/ViewersUpdate.d.ts +0 -4
  370. package/utils/typeUtils.d.ts +0 -7
  371. package/utils/typeUtils.js +0 -27
  372. /package/{api/socket/viewers.test.d.ts → components/app/providers/HitSearchProvider.test.d.ts} +0 -0
  373. /package/components/{app/providers/RecordSearchProvider.test.d.ts → elements/addons/buttons/CustomButton.test.d.ts} +0 -0
  374. /package/components/elements/{ContextMenu.test.d.ts → addons/layout/FlexOne.test.d.ts} +0 -0
  375. /package/components/elements/{hit/elements/Assigned.test.d.ts → addons/search/SearchPagination.test.d.ts} +0 -0
  376. /package/components/elements/{record/RecordContextMenu.test.d.ts → addons/search/SearchTotal.test.d.ts} +0 -0
  377. /package/components/{routes/cases/CaseViewer.test.d.ts → elements/display/DocumentationButton.test.d.ts} +0 -0
  378. /package/components/{routes/cases/detail/CaseAssets.test.d.ts → elements/display/DynamicTabs.test.d.ts} +0 -0
  379. /package/components/{routes/cases/detail/CaseRules.test.d.ts → elements/display/HowlerCard.test.d.ts} +0 -0
  380. /package/components/{routes/cases/detail/CaseSidebar.test.d.ts → elements/display/Image.test.d.ts} +0 -0
  381. /package/components/{routes/cases/detail/CaseTimeline.test.d.ts → elements/display/QueryResultText.test.d.ts} +0 -0
  382. /package/components/{routes/cases/detail/CreateRuleDialog.test.d.ts → elements/display/TextDivider.test.d.ts} +0 -0
  383. /package/components/{routes/cases/detail/assets/Asset.test.d.ts → elements/display/TypingIndicator.test.d.ts} +0 -0
  384. /package/components/elements/hit/{related/RelatedRecords.d.ts → HitDetails.d.ts} +0 -0
  385. /package/components/routes/{cases/detail/sidebar/CaseFolder.test.d.ts → 403.test.d.ts} +0 -0
  386. /package/components/routes/{cases/detail/sidebar/CaseFolderContextMenu.test.d.ts → 404.test.d.ts} +0 -0
  387. /package/components/routes/{cases/detail/sidebar/FolderEntry.test.d.ts → ErrorBoundary.test.d.ts} +0 -0
  388. /package/components/routes/{cases/detail/sidebar/utils.test.d.ts → ErrorOccured.test.d.ts} +0 -0
  389. /package/components/routes/{cases/hooks/useCase.test.d.ts → Logout.test.d.ts} +0 -0
  390. /package/components/routes/hits/search/{RecordBrowser.d.ts → HitBrowser.d.ts} +0 -0
  391. /package/components/routes/{cases/modals/AddToCaseModal.test.d.ts → hits/search/HitContextMenu.test.d.ts} +0 -0
  392. /package/components/routes/{cases/modals/CreateCaseModal.test.d.ts → hits/search/shared/CustomSpan.test.d.ts} +0 -0
  393. /package/components/routes/{cases/modals/ResolveModal.test.d.ts → home/AddNewCard.test.d.ts} +0 -0
  394. /package/components/routes/{cases/search/CaseAssigneeFilter.test.d.ts → home/ViewRefresh.test.d.ts} +0 -0
  395. /package/components/routes/{cases/search/CaseDateFilter.test.d.ts → overviews/OverviewCard.test.d.ts} +0 -0
  396. /package/components/{elements/MarkdownEditor.d.ts → routes/overviews/OverviewEditor.d.ts} +0 -0
  397. /package/components/routes/{cases/search/CaseStatusFilter.test.d.ts → settings/SettingsSection.test.d.ts} +0 -0
@@ -3,24 +3,23 @@ import { Add, Check, Settings, TableChart } from '@mui/icons-material';
3
3
  import { Autocomplete, Chip, Divider, Grid, IconButton, Stack, TextField } from '@mui/material';
4
4
  import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
5
5
  import { FieldContext } from '@cccsaurora/howler-ui/components/app/providers/FieldProvider';
6
- import { RecordSearchContext } from '@cccsaurora/howler-ui/components/app/providers/RecordSearchProvider';
6
+ import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
7
7
  import ChipPopper from '@cccsaurora/howler-ui/components/elements/display/ChipPopper';
8
- import { sortBy, uniq } from 'lodash-es';
8
+ import { has, sortBy, uniq } from 'lodash-es';
9
9
  import { memo, useContext, useEffect, useMemo, useState } from 'react';
10
10
  import { useTranslation } from 'react-i18next';
11
11
  import { useContextSelector } from 'use-context-selector';
12
- import { isHit } from '@cccsaurora/howler-ui/utils/typeUtils';
13
12
  const AddColumnModal = ({ addColumn, columns }) => {
14
13
  const { t } = useTranslation();
15
14
  const { hitFields } = useContext(FieldContext);
16
- const response = useContextSelector(RecordSearchContext, ctx => ctx.response);
15
+ const response = useContextSelector(HitSearchContext, ctx => ctx.response);
17
16
  const { getMatchingTemplate } = useMatchers();
18
17
  const [columnToAdd, setColumnToAdd] = useState(null);
19
18
  const options = useMemo(() => hitFields.map(field => field.key), [hitFields]);
20
19
  const [suggestions, setSuggestions] = useState([]);
21
20
  useEffect(() => {
22
21
  (async () => {
23
- setSuggestions(uniq((await Promise.all((response?.items ?? []).filter(isHit).map(async (_hit) => (await getMatchingTemplate(_hit))?.keys ?? []))).flat()));
22
+ setSuggestions(uniq((await Promise.all((response?.items ?? []).map(async (_hit) => (has(_hit, '__template') ? _hit.__template?.keys : (await getMatchingTemplate(_hit))?.keys) ?? []))).flat()));
24
23
  })();
25
24
  }, [getMatchingTemplate, response?.items]);
26
25
  return (_jsx(ChipPopper, { icon: _jsx(TableChart, {}), deleteIcon: _jsx(Settings, {}), toggleOnDelete: true, slotProps: { chip: { size: 'small' } }, children: _jsxs(Stack, { spacing: 1, p: 1, width: "500px", children: [_jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Autocomplete, { sx: { flex: 1 }, size: "small", options: options, value: columnToAdd, renderInput: params => _jsx(TextField, { fullWidth: true, placeholder: t('hit.fields'), ...params }), onChange: (_ev, value) => setColumnToAdd(value) }), _jsx(IconButton, { disabled: !columnToAdd, onClick: () => {
@@ -1,8 +1,7 @@
1
1
  import { type SxProps } from '@mui/material';
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
  declare const _default: import("react").NamedExoticComponent<{
5
- record: Hit | Observable;
4
+ hit: Hit;
6
5
  value: string;
7
6
  sx?: SxProps;
8
7
  className: string;
@@ -4,7 +4,7 @@ import { Stack, TableCell } from '@mui/material';
4
4
  import PluginTypography from '@cccsaurora/howler-ui/components/elements/PluginTypography';
5
5
  import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
- const EnhancedCell = ({ record, value: rawValue, sx = {}, className, field }) => {
7
+ const EnhancedCell = ({ hit, value: rawValue, sx = {}, className, field }) => {
8
8
  const { t } = useTranslation();
9
9
  if (!rawValue) {
10
10
  return _jsx(TableCell, { style: { borderBottom: 'none' }, children: t('none') });
@@ -13,6 +13,6 @@ const EnhancedCell = ({ record, value: rawValue, sx = {}, className, field }) =>
13
13
  return (_jsx(TableCell, { sx: { borderBottom: 'none', borderRight: 'thin solid', borderRightColor: 'divider', fontSize: '0.8rem' }, children: _jsx(Stack, { direction: "row", className: className, spacing: 0.5, sx: [
14
14
  { display: 'flex', justifyContent: 'start', width: '100%', overflow: 'hidden' },
15
15
  ...(Array.isArray(sx) ? sx : [sx])
16
- ], children: values.map((value, index) => (_jsx(PluginTypography, { context: "table", sx: { fontSize: 'inherit', textOverflow: 'ellipsis' }, value: value, field: field, obj: record, children: value }, value + index))) }) }));
16
+ ], children: values.map((value, index) => (_jsx(PluginTypography, { context: "table", sx: { fontSize: 'inherit', textOverflow: 'ellipsis' }, value: value, field: field, hit: hit, children: value }, value + index))) }) }));
17
17
  };
18
18
  export default memo(EnhancedCell);
@@ -4,37 +4,36 @@ import { arrayMove, SortableContext, sortableKeyboardCoordinates } from '@dnd-ki
4
4
  import { FormatIndentDecrease, FormatIndentIncrease, Info, List, Search, TableChart } from '@mui/icons-material';
5
5
  import { IconButton, LinearProgress, Paper, Stack, Table, TableBody, TableCell, TableHead, TableRow, ToggleButton, ToggleButtonGroup, Typography, useTheme } from '@mui/material';
6
6
  import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
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 SearchTotal from '@cccsaurora/howler-ui/components/elements/addons/search/SearchTotal';
11
11
  import DevelopmentBanner from '@cccsaurora/howler-ui/components/elements/display/features/DevelopmentBanner';
12
- import RecordContextMenu from '@cccsaurora/howler-ui/components/elements/record/RecordContextMenu';
12
+ import useHitSelection from '@cccsaurora/howler-ui/components/hooks/useHitSelection';
13
13
  import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
14
- import useRecordSelection from '@cccsaurora/howler-ui/components/hooks/useRecordSelection';
15
14
  import { uniq } from 'lodash-es';
16
15
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
17
16
  import { useTranslation } from 'react-i18next';
18
17
  import { useContextSelector } from 'use-context-selector';
19
18
  import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
20
- import { isHit } from '@cccsaurora/howler-ui/utils/typeUtils';
19
+ import HitContextMenu from '../HitContextMenu';
20
+ import HitQuery from '../HitQuery';
21
21
  import QuerySettings from '../QuerySettings';
22
- import RecordQuery from '../RecordQuery';
23
22
  import AddColumnModal from './AddColumnModal';
24
23
  import ColumnHeader from './ColumnHeader';
25
- import RecordRow from './RecordRow';
24
+ import HitRow from './HitRow';
26
25
  const HitGrid = () => {
27
26
  const { t } = useTranslation();
28
27
  const theme = useTheme();
29
28
  const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
30
- const { onClick } = useRecordSelection();
29
+ const { onClick } = useHitSelection();
31
30
  const { getMatchingAnalytic } = useMatchers();
32
- const search = useContextSelector(RecordSearchContext, ctx => ctx.search);
33
- const displayType = useContextSelector(RecordSearchContext, ctx => ctx.displayType);
34
- const setDisplayType = useContextSelector(RecordSearchContext, ctx => ctx.setDisplayType);
35
- const response = useContextSelector(RecordSearchContext, ctx => ctx.response);
36
- const searching = useContextSelector(RecordSearchContext, ctx => ctx.searching);
37
- const selectedHits = useContextSelector(RecordContext, ctx => ctx.selectedRecords);
31
+ const search = useContextSelector(HitSearchContext, ctx => ctx.search);
32
+ const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
33
+ const setDisplayType = useContextSelector(HitSearchContext, ctx => ctx.setDisplayType);
34
+ const response = useContextSelector(HitSearchContext, ctx => ctx.response);
35
+ const searching = useContextSelector(HitSearchContext, ctx => ctx.searching);
36
+ const selectedHits = useContextSelector(HitContext, ctx => ctx.selectedHits);
38
37
  const query = useContextSelector(ParameterContext, ctx => ctx.query);
39
38
  const selected = useContextSelector(ParameterContext, ctx => ctx.selected);
40
39
  const [collapseMainColumn, setCollapseMainColumn] = useMyLocalStorageItem(StorageKey.GRID_COLLAPSE_COLUMN, false);
@@ -57,11 +56,10 @@ const HitGrid = () => {
57
56
  return false;
58
57
  }, [selected, selectedHits]);
59
58
  useEffect(() => {
60
- response?.items.forEach(record => {
61
- if (!isHit(record) || analyticIds[record.howler.analytic]) {
62
- return;
59
+ response?.items.forEach(hit => {
60
+ if (!analyticIds[hit.howler.analytic]) {
61
+ getMatchingAnalytic(hit).then(_analytic => setAnalyticIds(_analyticIds => ({ ..._analyticIds, [hit.howler.analytic]: _analytic.analytic_id })));
63
62
  }
64
- getMatchingAnalytic(record).then(_analytic => setAnalyticIds(_analyticIds => ({ ..._analyticIds, [record.howler.analytic]: _analytic.analytic_id })));
65
63
  });
66
64
  // eslint-disable-next-line react-hooks/exhaustive-deps
67
65
  }, [analyticIds, response]);
@@ -119,7 +117,7 @@ const HitGrid = () => {
119
117
  }
120
118
  return selectedElement.id;
121
119
  }, []);
122
- return (_jsxs(Stack, { spacing: 1, p: 2, width: "100%", sx: { overflow: 'hidden', height: `calc(100vh - ${theme.spacing(showSelectBar ? 13 : 8)})` }, children: [_jsx(DevelopmentBanner, {}), _jsxs(Stack, { direction: "row", justifyContent: "space-between", children: [_jsx(Typography, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5, textAlign: 'left' }, variant: "body2", children: t('hit.search.prompt') }), response && (_jsx(SearchTotal, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5 }, variant: "body2", offset: response.offset, pageLength: response.rows, total: response.total }))] }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsxs(Stack, { position: "relative", flex: 1, children: [_jsx(RecordQuery, { searching: searching, triggerSearch: search, compact: true }), searching && (_jsx(LinearProgress, { sx: {
120
+ return (_jsxs(Stack, { spacing: 1, p: 2, width: "100%", sx: { overflow: 'hidden', height: `calc(100vh - ${theme.spacing(showSelectBar ? 13 : 8)})` }, children: [_jsx(DevelopmentBanner, {}), _jsxs(Stack, { direction: "row", justifyContent: "space-between", children: [_jsx(Typography, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5, textAlign: 'left' }, variant: "body2", children: t('hit.search.prompt') }), response && (_jsx(SearchTotal, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5 }, variant: "body2", offset: response.offset, pageLength: response.rows, total: response.total }))] }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsxs(Stack, { position: "relative", flex: 1, children: [_jsx(HitQuery, { searching: searching, triggerSearch: search, compact: true }), searching && (_jsx(LinearProgress, { sx: {
123
121
  position: 'absolute',
124
122
  left: 0,
125
123
  right: 0,
@@ -129,6 +127,6 @@ const HitGrid = () => {
129
127
  } }))] }), _jsxs(ToggleButtonGroup, { exclusive: true, value: displayType, onChange: (__, value) => setDisplayType(value), size: "small", children: [_jsx(ToggleButton, { value: "list", children: _jsx(List, {}) }), _jsx(ToggleButton, { value: "grid", children: _jsx(TableChart, {}) })] })] }), _jsxs(Stack, { direction: "row", spacing: 1, width: "100%", alignItems: "center", children: [_jsx(QuerySettings, { boxSx: { flex: 1 } }), _jsx(AddColumnModal, { columns: columns, addColumn: key => setColumns(uniq([...columns, key])) })] }), _jsxs(Stack, { component: Paper, spacing: 1, width: "100%", height: "100%", sx: { overflow: 'auto', flex: 1 }, onScroll: onScroll, children: [_jsxs(Table, { sx: { '& td,th': { px: 1, py: 0.25, whiteSpace: 'nowrap' } }, children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { sx: {
130
128
  borderRight: 'thin solid',
131
129
  borderRightColor: 'divider'
132
- }, children: _jsx(IconButton, { onClick: () => setCollapseMainColumn(!collapseMainColumn), children: collapseMainColumn ? (_jsx(FormatIndentIncrease, { fontSize: "small" })) : (_jsx(FormatIndentDecrease, { fontSize: "small" })) }) }), _jsx(DndContext, { sensors: sensors, collisionDetection: pointerWithin, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: columns, children: columns.map(col => (_jsx(ColumnHeader, { col: col, width: columnWidths[col], onMouseDown: onMouseDown, setColumns: setColumns }, col))) }) }), _jsx(TableCell, { sx: { width: '100%' } })] }) }), _jsxs(RecordContextMenu, { Component: TableBody, getSelectedId: getSelectedId, children: [response?.items.map(hit => (_jsx(RecordRow, { record: hit, analyticIds: analyticIds, columns: columns, columnWidths: columnWidths, collapseMainColumn: collapseMainColumn, onClick: onClick }, hit.howler.id))), _jsx(TableRow, { children: _jsx(TableCell, { colSpan: columns.length + 2, children: _jsx(Stack, { alignItems: "center", justifyContent: "center", py: 0.5, px: 1, children: _jsx(IconButton, { onClick: () => search(query, true), children: _jsx(Search, {}) }) }) }) })] })] }), (response?.total ?? 0) < 1 && (_jsx(Stack, { direction: "row", spacing: 1, alignItems: "center", p: 1, justifyContent: "center", flex: 1, children: _jsxs(Typography, { variant: "h3", color: "text.secondary", display: "flex", flexDirection: "row", alignItems: "center", children: [_jsx(Info, { fontSize: "inherit", sx: { color: 'text.secondary', mr: 1 } }), _jsx("span", { children: t('app.list.empty') })] }) }))] })] }));
130
+ }, children: _jsx(IconButton, { onClick: () => setCollapseMainColumn(!collapseMainColumn), children: collapseMainColumn ? (_jsx(FormatIndentIncrease, { fontSize: "small" })) : (_jsx(FormatIndentDecrease, { fontSize: "small" })) }) }), _jsx(DndContext, { sensors: sensors, collisionDetection: pointerWithin, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: columns, children: columns.map(col => (_jsx(ColumnHeader, { col: col, width: columnWidths[col], onMouseDown: onMouseDown, setColumns: setColumns }, col))) }) }), _jsx(TableCell, { sx: { width: '100%' } })] }) }), _jsxs(HitContextMenu, { Component: TableBody, getSelectedId: getSelectedId, children: [response?.items.map(hit => (_jsx(HitRow, { hit: hit, analyticIds: analyticIds, columns: columns, columnWidths: columnWidths, collapseMainColumn: collapseMainColumn, onClick: onClick }, hit.howler.id))), _jsx(TableRow, { children: _jsx(TableCell, { colSpan: columns.length + 2, children: _jsx(Stack, { alignItems: "center", justifyContent: "center", py: 0.5, px: 1, children: _jsx(IconButton, { onClick: () => search(query, true), children: _jsx(Search, {}) }) }) }) })] })] }), (response?.total ?? 0) < 1 && (_jsx(Stack, { direction: "row", spacing: 1, alignItems: "center", p: 1, justifyContent: "center", flex: 1, children: _jsxs(Typography, { variant: "h3", color: "text.secondary", display: "flex", flexDirection: "row", alignItems: "center", children: [_jsx(Info, { fontSize: "inherit", sx: { color: 'text.secondary', mr: 1 } }), _jsx("span", { children: t('app.list.empty') })] }) }))] })] }));
133
131
  };
134
132
  export default HitGrid;
@@ -1,11 +1,10 @@
1
1
  import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
2
- import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
3
2
  declare const _default: import("react").NamedExoticComponent<{
4
- record: Hit | Observable;
3
+ hit: Hit;
5
4
  analyticIds: Record<string, string>;
6
5
  columns: string[];
7
6
  columnWidths: Record<string, string>;
8
7
  collapseMainColumn: boolean;
9
- onClick: (e: React.MouseEvent<HTMLDivElement>, record: Hit | Observable) => void;
8
+ onClick: (e: React.MouseEvent<HTMLDivElement>, hit: Hit) => void;
10
9
  }>;
11
10
  export default _default;
@@ -1,27 +1,25 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { KeyboardArrowUp } from '@mui/icons-material';
3
3
  import { Box, Collapse, IconButton, lighten, Stack, TableCell, TableRow, Typography, useTheme } from '@mui/material';
4
+ import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
4
5
  import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
5
- import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
6
- import Assigned from '@cccsaurora/howler-ui/components/elements/hit/elements/Assigned';
7
6
  import EscalationChip from '@cccsaurora/howler-ui/components/elements/hit/elements/EscalationChip';
7
+ import HitUsers from '@cccsaurora/howler-ui/components/elements/hit/elements/HitUsers';
8
8
  import HitCard from '@cccsaurora/howler-ui/components/elements/hit/HitCard';
9
9
  import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
10
- import ObservableCard from '@cccsaurora/howler-ui/components/elements/observable/ObservableCard';
11
10
  import { get } from 'lodash-es';
12
11
  import { memo, useState } from 'react';
13
12
  import { useTranslation } from 'react-i18next';
14
13
  import { Link } from 'react-router-dom';
15
14
  import { useContextSelector } from 'use-context-selector';
16
- import { isHit } from '@cccsaurora/howler-ui/utils/typeUtils';
17
15
  import EnhancedCell from './EnhancedCell';
18
- const RecordRow = ({ record, analyticIds, columns, columnWidths, collapseMainColumn, onClick }) => {
16
+ const HitRow = ({ hit, analyticIds, columns, columnWidths, collapseMainColumn, onClick }) => {
19
17
  const theme = useTheme();
20
18
  const { t } = useTranslation();
21
- const selectedHits = useContextSelector(RecordContext, ctx => ctx.selectedRecords);
19
+ const selectedHits = useContextSelector(HitContext, ctx => ctx.selectedHits);
22
20
  const selected = useContextSelector(ParameterContext, ctx => ctx.selected);
23
21
  const [expandRow, setExpandRow] = useState(false);
24
- return (_jsxs(_Fragment, { children: [_jsxs(TableRow, { id: record.howler.id, onClick: ev => onClick(ev, record), sx: [
22
+ return (_jsxs(_Fragment, { children: [_jsxs(TableRow, { id: hit.howler.id, onClick: ev => onClick(ev, hit), sx: [
25
23
  {
26
24
  transition: theme.transitions.create('background-color'),
27
25
  '&:hover': {
@@ -29,10 +27,10 @@ const RecordRow = ({ record, analyticIds, columns, columnWidths, collapseMainCol
29
27
  backgroundColor: theme.palette.background.paper
30
28
  }
31
29
  },
32
- selectedHits.some(_hit => _hit.howler.id === record.howler.id) && {
30
+ selectedHits.some(_hit => _hit.howler.id === hit.howler.id) && {
33
31
  backgroundColor: lighten(theme.palette.background.paper, 0.15)
34
32
  },
35
- selected === record.howler.id && {
33
+ selected === hit.howler.id && {
36
34
  backgroundColor: lighten(theme.palette.background.paper, 0.25)
37
35
  }
38
36
  ], children: [_jsx(TableCell, { sx: {
@@ -47,6 +45,6 @@ const RecordRow = ({ record, analyticIds, columns, columnWidths, collapseMainCol
47
45
  e.preventDefault();
48
46
  e.stopPropagation();
49
47
  setExpandRow(_expanded => !_expanded);
50
- }, children: _jsx(KeyboardArrowUp, {}) }), _jsx(Collapse, { in: !collapseMainColumn, orientation: "horizontal", unmountOnExit: true, children: _jsxs(Stack, { direction: "row", spacing: 1, flexWrap: "nowrap", children: [isHit(record) && _jsx(EscalationChip, { hit: record, layout: HitLayout.DENSE, hideLabel: true }), _jsxs(Typography, { sx: { textWrap: 'nowrap', whiteSpace: 'nowrap', fontSize: 'inherit' }, children: [analyticIds[record.howler.analytic] ? (_jsx(Link, { to: `/analytics/${analyticIds[record.howler.analytic]}`, onClick: e => e.stopPropagation(), children: record.howler.analytic })) : (record.howler.analytic), record.howler.detection && ': ', record.howler.detection] }), isHit(record) && record.howler.assignment !== 'unassigned' && (_jsx(Assigned, { hit: record, layout: HitLayout.DENSE, hideLabel: true }))] }) })] }) }), columns.map(col => (_jsx(EnhancedCell, { record: record, className: `col-${col.replaceAll('.', '-')}`, value: get(record, col) ?? t('none'), sx: columnWidths[col] ? { width: columnWidths[col] } : { width: '220px', maxWidth: '300px' }, field: col }, col)))] }, record.howler.id), _jsx(TableRow, { onClick: ev => onClick(ev, record), children: _jsx(TableCell, { colSpan: columns.length + 2, style: { paddingBottom: 0, paddingTop: 0 }, children: _jsx(Collapse, { in: expandRow, unmountOnExit: true, children: _jsx(Box, { width: "100%", maxWidth: "1200px", margin: 1, children: isHit(record) ? (_jsx(HitCard, { id: record.howler.id, layout: HitLayout.NORMAL })) : (_jsx(ObservableCard, { id: record.howler.id, observable: record })) }) }) }) })] }));
48
+ }, children: _jsx(KeyboardArrowUp, {}) }), _jsx(Collapse, { in: !collapseMainColumn, orientation: "horizontal", unmountOnExit: true, children: _jsxs(Stack, { direction: "row", spacing: 1, flexWrap: "nowrap", children: [_jsx(EscalationChip, { hit: hit, layout: HitLayout.DENSE, hideLabel: true }), _jsxs(Typography, { sx: { textWrap: 'nowrap', whiteSpace: 'nowrap', fontSize: 'inherit' }, children: [analyticIds[hit.howler.analytic] ? (_jsx(Link, { to: `/analytics/${analyticIds[hit.howler.analytic]}`, onClick: e => e.stopPropagation(), children: hit.howler.analytic })) : (hit.howler.analytic), hit.howler.detection && ': ', hit.howler.detection] }), _jsx(HitUsers, { hit: hit, layout: HitLayout.DENSE, hideLabel: true })] }) })] }) }), columns.map(col => (_jsx(EnhancedCell, { hit: hit, className: `col-${col.replaceAll('.', '-')}`, value: get(hit, col) ?? t('none'), sx: columnWidths[col] ? { width: columnWidths[col] } : { width: '220px', maxWidth: '300px' }, field: col }, col)))] }, hit.howler.id), _jsx(TableRow, { onClick: ev => onClick(ev, hit), children: _jsx(TableCell, { colSpan: columns.length + 2, style: { paddingBottom: 0, paddingTop: 0 }, children: _jsx(Collapse, { in: expandRow, unmountOnExit: true, children: _jsx(Box, { width: "100%", maxWidth: "1200px", margin: 1, children: _jsx(HitCard, { id: hit.howler.id, layout: HitLayout.NORMAL }) }) }) }) })] }));
51
49
  };
52
- export default memo(RecordRow);
50
+ export default memo(HitRow);
@@ -11,16 +11,16 @@ import { useContextSelector } from 'use-context-selector';
11
11
  const CustomSpan = () => {
12
12
  const { t } = useTranslation();
13
13
  const span = useContextSelector(ParameterContext, ctx => ctx.span);
14
- const defaultStartDate = dayjs().subtract(2, 'days');
15
- const defaultEndDate = dayjs().subtract(1, 'day');
16
- const startDate = useContextSelector(ParameterContext, ctx => ctx.startDate ? dayjs(ctx.startDate) : defaultStartDate);
17
14
  const setCustomSpan = useContextSelector(ParameterContext, ctx => ctx.setCustomSpan);
18
- const endDate = useContextSelector(ParameterContext, ctx => (ctx.endDate ? dayjs(ctx.endDate) : defaultEndDate));
15
+ const startDate = useContextSelector(ParameterContext, ctx => (ctx.startDate ? dayjs(ctx.startDate) : null));
16
+ const endDate = useContextSelector(ParameterContext, ctx => (ctx.endDate ? dayjs(ctx.endDate) : null));
19
17
  useEffect(() => {
20
- if (span?.endsWith('custom')) {
21
- setCustomSpan(startDate.toISOString(), endDate.toISOString());
18
+ if (span?.endsWith('custom') && (!startDate || !endDate)) {
19
+ const _startDate = startDate ?? dayjs().subtract(2, 'day');
20
+ const _endDate = endDate ?? dayjs().subtract(1, 'day');
21
+ setCustomSpan(dayjs.min(_startDate, _endDate).toISOString(), dayjs.max(_startDate, _endDate).toISOString());
22
22
  }
23
23
  }, [endDate, setCustomSpan, span, startDate]);
24
- return span?.endsWith('custom') ? (_jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: _jsxs(Stack, { direction: "row", spacing: 1, useFlexGap: true, flexWrap: "wrap", children: [_jsx(DateTimePicker, { sx: { minWidth: '175px', flexGrow: 1, marginTop: 1 }, slotProps: { textField: { size: 'small' } }, label: t('date.select.start'), value: startDate ? dayjs(startDate) : dayjs().subtract(1, 'days'), maxDate: endDate, onChange: (newStartDate) => setCustomSpan(newStartDate.toISOString(), endDate.toISOString()), ampm: false, disableFuture: true }), _jsx(DateTimePicker, { sx: { minWidth: '175px', flexGrow: 1, marginTop: 1 }, slotProps: { textField: { size: 'small' } }, label: t('date.select.end'), value: endDate, minDate: startDate, onChange: (newEndDate) => setCustomSpan(startDate.toISOString(), newEndDate.toISOString()), ampm: false, disableFuture: true })] }) })) : null;
24
+ return span?.endsWith('custom') ? (_jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: _jsxs(Stack, { direction: "row", spacing: 1, useFlexGap: true, flexWrap: "wrap", children: [_jsx(DateTimePicker, { sx: { minWidth: '175px', flexGrow: 1, marginTop: 1 }, slotProps: { textField: { size: 'small' } }, label: t('date.select.start'), value: startDate ? dayjs(startDate) : dayjs().subtract(1, 'days'), maxDateTime: endDate, onChange: (newStartDate) => setCustomSpan(newStartDate.toISOString(), endDate.toISOString()), ampm: false, disableFuture: true }), _jsx(DateTimePicker, { sx: { minWidth: '175px', flexGrow: 1, marginTop: 1 }, slotProps: { textField: { size: 'small' } }, label: t('date.select.end'), value: endDate, minDateTime: startDate, onChange: (newEndDate) => setCustomSpan(startDate.toISOString(), newEndDate.toISOString()), ampm: false, disableFuture: true })] }) })) : null;
25
25
  };
26
26
  export default memo(CustomSpan);
@@ -0,0 +1,170 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from 'react';
3
+ vi.mock('use-context-selector', async () => {
4
+ const actual = await vi.importActual('use-context-selector');
5
+ return {
6
+ ...actual,
7
+ createContext,
8
+ useContextSelector: (_context, selector) => selector(useContext(_context))
9
+ };
10
+ });
11
+ vi.mock('react-i18next', () => ({
12
+ useTranslation: () => ({ t: (key) => key })
13
+ }));
14
+ import { render, screen, waitFor } from '@testing-library/react';
15
+ import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
16
+ import dayjs from 'dayjs';
17
+ import minMax from 'dayjs/plugin/minMax';
18
+ import {} from 'react';
19
+ dayjs.extend(minMax);
20
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
21
+ // Import after mocks
22
+ import CustomSpan from './CustomSpan';
23
+ const mockSetCustomSpan = vi.fn();
24
+ const defaultCtx = {
25
+ span: 'date.range.custom',
26
+ startDate: undefined,
27
+ endDate: undefined,
28
+ setCustomSpan: mockSetCustomSpan
29
+ };
30
+ const makeWrapper = (ctx) => function ({ children }) {
31
+ return (_jsx(ParameterContext.Provider, { value: { ...defaultCtx, ...ctx }, children: children }));
32
+ };
33
+ describe('CustomSpan', () => {
34
+ beforeEach(() => {
35
+ vi.clearAllMocks();
36
+ });
37
+ describe('useEffect – date initialisation', () => {
38
+ it('should set both dates to defaults when both are null', async () => {
39
+ render(_jsx(CustomSpan, {}), {
40
+ wrapper: makeWrapper({ span: 'date.range.custom', startDate: undefined, endDate: undefined })
41
+ });
42
+ await waitFor(() => expect(mockSetCustomSpan).toHaveBeenCalledOnce());
43
+ const [start, end] = mockSetCustomSpan.mock.calls[0];
44
+ const startDate = dayjs(start);
45
+ const endDate = dayjs(end);
46
+ expect(startDate.isBefore(endDate)).toBe(true);
47
+ expect(dayjs().diff(startDate, 'hour')).toBeGreaterThanOrEqual(47);
48
+ expect(dayjs().diff(startDate, 'hour')).toBeLessThanOrEqual(49);
49
+ expect(dayjs().diff(endDate, 'hour')).toBeGreaterThanOrEqual(23);
50
+ expect(dayjs().diff(endDate, 'hour')).toBeLessThanOrEqual(25);
51
+ });
52
+ it('should set default start date when only endDate is provided', async () => {
53
+ // Use a recent end date that's after the default start (now - 2 days)
54
+ const existingEnd = dayjs().subtract(1, 'hour').toISOString();
55
+ render(_jsx(CustomSpan, {}), {
56
+ wrapper: makeWrapper({ span: 'date.range.custom', startDate: undefined, endDate: existingEnd })
57
+ });
58
+ await waitFor(() => expect(mockSetCustomSpan).toHaveBeenCalledOnce());
59
+ const [start, end] = mockSetCustomSpan.mock.calls[0];
60
+ expect(end).toBe(existingEnd);
61
+ expect(dayjs(start).isBefore(dayjs(end))).toBe(true);
62
+ });
63
+ it('should set default end date when only startDate is provided', async () => {
64
+ // Use a recent start date that's before the default end (now - 1 day)
65
+ const existingStart = dayjs().subtract(3, 'day').toISOString();
66
+ render(_jsx(CustomSpan, {}), {
67
+ wrapper: makeWrapper({ span: 'date.range.custom', startDate: existingStart, endDate: undefined })
68
+ });
69
+ await waitFor(() => expect(mockSetCustomSpan).toHaveBeenCalledOnce());
70
+ const [start, end] = mockSetCustomSpan.mock.calls[0];
71
+ expect(start).toBe(existingStart);
72
+ expect(dayjs(end).isAfter(dayjs(start))).toBe(true);
73
+ });
74
+ it('should NOT call setCustomSpan when both dates are already set', () => {
75
+ render(_jsx(CustomSpan, {}), {
76
+ wrapper: makeWrapper({
77
+ span: 'date.range.custom',
78
+ startDate: dayjs().subtract(3, 'day').toISOString(),
79
+ endDate: dayjs().subtract(1, 'hour').toISOString()
80
+ })
81
+ });
82
+ expect(mockSetCustomSpan).not.toHaveBeenCalled();
83
+ });
84
+ it('should NOT call setCustomSpan when span is not custom', () => {
85
+ render(_jsx(CustomSpan, {}), {
86
+ wrapper: makeWrapper({ span: 'date.range.1.month', startDate: undefined, endDate: undefined })
87
+ });
88
+ expect(mockSetCustomSpan).not.toHaveBeenCalled();
89
+ });
90
+ it('should NOT call setCustomSpan when span is undefined', () => {
91
+ render(_jsx(CustomSpan, {}), {
92
+ wrapper: makeWrapper({ span: undefined, startDate: undefined, endDate: undefined })
93
+ });
94
+ expect(mockSetCustomSpan).not.toHaveBeenCalled();
95
+ });
96
+ it('should ensure startDate is before endDate even if existing startDate is after default endDate', async () => {
97
+ const futureStart = dayjs().add(1, 'hour').toISOString();
98
+ render(_jsx(CustomSpan, {}), {
99
+ wrapper: makeWrapper({ span: 'date.range.custom', startDate: futureStart, endDate: undefined })
100
+ });
101
+ await waitFor(() => expect(mockSetCustomSpan).toHaveBeenCalledOnce());
102
+ const [start, end] = mockSetCustomSpan.mock.calls[0];
103
+ expect(dayjs(start).isBefore(dayjs(end))).toBe(true);
104
+ });
105
+ });
106
+ describe('rendering', () => {
107
+ it('should render nothing when span is not custom', () => {
108
+ const { container } = render(_jsx(CustomSpan, {}), {
109
+ wrapper: makeWrapper({ span: 'date.range.1.month' })
110
+ });
111
+ expect(container.innerHTML).toBe('');
112
+ });
113
+ it('should render nothing when span is undefined', () => {
114
+ const { container } = render(_jsx(CustomSpan, {}), {
115
+ wrapper: makeWrapper({ span: undefined })
116
+ });
117
+ expect(container.innerHTML).toBe('');
118
+ });
119
+ it('should render two date pickers when span is custom', () => {
120
+ render(_jsx(CustomSpan, {}), {
121
+ wrapper: makeWrapper({
122
+ span: 'date.range.custom',
123
+ startDate: dayjs().subtract(3, 'day').toISOString(),
124
+ endDate: dayjs().subtract(1, 'hour').toISOString()
125
+ })
126
+ });
127
+ expect(screen.getByLabelText('date.select.start')).toBeInTheDocument();
128
+ expect(screen.getByLabelText('date.select.end')).toBeInTheDocument();
129
+ });
130
+ it('should render pickers for any span ending with "custom"', () => {
131
+ render(_jsx(CustomSpan, {}), {
132
+ wrapper: makeWrapper({
133
+ span: 'my.prefix.custom',
134
+ startDate: dayjs().subtract(3, 'day').toISOString(),
135
+ endDate: dayjs().subtract(1, 'hour').toISOString()
136
+ })
137
+ });
138
+ expect(screen.getByLabelText('date.select.start')).toBeInTheDocument();
139
+ expect(screen.getByLabelText('date.select.end')).toBeInTheDocument();
140
+ });
141
+ });
142
+ describe('onChange handlers', () => {
143
+ it('should not call setCustomSpan when both dates are present on render', () => {
144
+ render(_jsx(CustomSpan, {}), {
145
+ wrapper: makeWrapper({
146
+ span: 'date.range.custom',
147
+ startDate: dayjs().subtract(3, 'day').toISOString(),
148
+ endDate: dayjs().subtract(1, 'hour').toISOString()
149
+ })
150
+ });
151
+ const startPicker = screen.getByLabelText('date.select.start');
152
+ expect(startPicker).toBeInTheDocument();
153
+ expect(mockSetCustomSpan).not.toHaveBeenCalled();
154
+ });
155
+ it('should render start picker with fallback value when startDate is null', async () => {
156
+ render(_jsx(CustomSpan, {}), {
157
+ wrapper: makeWrapper({
158
+ span: 'date.range.custom',
159
+ startDate: undefined,
160
+ endDate: dayjs().subtract(1, 'hour').toISOString()
161
+ })
162
+ });
163
+ // When startDate is null, the picker's value falls back to dayjs().subtract(1, 'days')
164
+ const startInput = screen.getByLabelText('date.select.start');
165
+ expect(startInput).toBeInTheDocument();
166
+ // The input should have a value (the fallback), not be empty
167
+ expect(startInput.value).not.toBe('');
168
+ });
169
+ });
170
+ });
@@ -4,22 +4,23 @@ import { Code, Comment, DataObject, History, LinkSharp, QueryStats, ViewAgenda }
4
4
  import { Badge, Box, CardContent, Collapse, IconButton, Skeleton, Stack, Tab, Tabs, Tooltip, useMediaQuery, useTheme } from '@mui/material';
5
5
  import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCenter';
6
6
  import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
7
- import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
7
+ import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
8
8
  import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
9
9
  import HowlerCard from '@cccsaurora/howler-ui/components/elements/display/HowlerCard';
10
+ import BundleButton from '@cccsaurora/howler-ui/components/elements/display/icons/BundleButton';
10
11
  import SocketBadge from '@cccsaurora/howler-ui/components/elements/display/icons/SocketBadge';
11
12
  import JSONViewer from '@cccsaurora/howler-ui/components/elements/display/json/JSONViewer';
12
13
  import HitActions from '@cccsaurora/howler-ui/components/elements/hit/HitActions';
13
14
  import HitBanner from '@cccsaurora/howler-ui/components/elements/hit/HitBanner';
15
+ import HitComments from '@cccsaurora/howler-ui/components/elements/hit/HitComments';
16
+ import HitDetails from '@cccsaurora/howler-ui/components/elements/hit/HitDetails';
14
17
  import HitLabels from '@cccsaurora/howler-ui/components/elements/hit/HitLabels';
15
18
  import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
16
19
  import HitLinks from '@cccsaurora/howler-ui/components/elements/hit/HitLinks';
17
20
  import HitOutline from '@cccsaurora/howler-ui/components/elements/hit/HitOutline';
18
21
  import HitOverview from '@cccsaurora/howler-ui/components/elements/hit/HitOverview';
19
- import ObjectDetails from '@cccsaurora/howler-ui/components/elements/ObjectDetails';
20
- import RecordComments from '@cccsaurora/howler-ui/components/elements/record/RecordComments';
21
- import RecordRelated from '@cccsaurora/howler-ui/components/elements/record/RecordRelated';
22
- import RecordWorklog from '@cccsaurora/howler-ui/components/elements/record/RecordWorklog';
22
+ import HitRelated from '@cccsaurora/howler-ui/components/elements/hit/HitRelated';
23
+ import HitWorklog from '@cccsaurora/howler-ui/components/elements/hit/HitWorklog';
23
24
  import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
24
25
  import useMyUserList from '@cccsaurora/howler-ui/components/hooks/useMyUserList';
25
26
  import { useCallback, useEffect, useMemo, useState } from 'react';
@@ -43,8 +44,8 @@ const HitViewer = () => {
43
44
  const isUnderLg = useMediaQuery(theme.breakpoints.down('lg'));
44
45
  const [orientation, setOrientation] = useMyLocalStorageItem(StorageKey.VIEWER_ORIENTATION, Orientation.VERTICAL);
45
46
  const { getMatchingOverview, getMatchingDossiers, getMatchingAnalytic } = useMatchers();
46
- const getHit = useContextSelector(RecordContext, ctx => ctx.getRecord);
47
- const hit = useContextSelector(RecordContext, ctx => ctx.records[params.id]);
47
+ const getHit = useContextSelector(HitContext, ctx => ctx.getHit);
48
+ const hit = useContextSelector(HitContext, ctx => ctx.hits[params.id]);
48
49
  const [userIds, setUserIds] = useState(new Set());
49
50
  const users = useMyUserList(userIds);
50
51
  const [tab, setTab] = useState('details');
@@ -93,12 +94,12 @@ const HitViewer = () => {
93
94
  }
94
95
  return {
95
96
  overview: () => _jsx(HitOverview, { hit: hit }),
96
- details: () => _jsx(ObjectDetails, { obj: hit }),
97
- hit_comments: () => _jsx(RecordComments, { record: hit, users: users }),
97
+ details: () => _jsx(HitDetails, { hit: hit }),
98
+ hit_comments: () => _jsx(HitComments, { hit: hit, users: users }),
98
99
  hit_raw: () => _jsx(JSONViewer, { data: hit }),
99
100
  hit_data: () => _jsx(JSONViewer, { data: hit?.howler?.data?.map(entry => tryParse(entry)), collapse: false }),
100
- hit_worklog: () => _jsx(RecordWorklog, { record: hit, users: users }),
101
- hit_related: () => _jsx(RecordRelated, { record: hit }),
101
+ hit_worklog: () => _jsx(HitWorklog, { hit: hit, users: users }),
102
+ hit_related: () => _jsx(HitRelated, { hit: hit }),
102
103
  ...Object.fromEntries(hit?.howler.dossier?.map((lead, index) => ['lead:' + index, () => _jsx(LeadRenderer, { lead: lead, hit: hit })]) ?? []),
103
104
  ...Object.fromEntries(dossiers.flatMap((_dossier, dossierIndex) => (_dossier.leads ?? []).map((_lead, leadIndex) => [
104
105
  `external-lead:${dossierIndex}:${leadIndex}`,
@@ -131,7 +132,7 @@ const HitViewer = () => {
131
132
  position: 'absolute',
132
133
  top: theme.spacing(2),
133
134
  right: theme.spacing(-6)
134
- }, children: [_jsx(Tooltip, { title: t('hit.panel.view.layout'), children: _jsx(IconButton, { onClick: onOrientationChange, children: _jsx(ViewAgenda, { sx: { transition: 'rotate 250ms', rotate: orientation === 'vertical' ? '90deg' : '0deg' } }) }) }), _jsx(SocketBadge, { size: "medium" }), analytic && (_jsx(Tooltip, { title: t('analytic.open'), children: _jsx(IconButton, { onClick: () => navigate(`/analytics/${analytic.analytic_id}`), children: _jsx(QueryStats, {}) }) }))] }))] }), _jsx(HowlerCard, { sx: [orientation === 'horizontal' && { height: '0px' }], children: _jsx(CardContent, { sx: { padding: 1, position: 'relative' }, children: _jsx(HitActions, { hit: hit, orientation: "vertical" }) }) }), _jsx(Box, { sx: { gridColumn: '1 / span 2', mb: 1 }, children: _jsxs(Tabs, { value: tab === 'overview' && !hasOverview ? 'details' : tab, sx: { display: 'flex', flexDirection: 'row', pr: 2, alignItems: 'center' }, children: [hasOverview && (_jsx(Tab, { label: t('hit.viewer.overview'), value: "overview", onClick: () => setTab('overview') })), _jsx(Tab, { label: t('hit.viewer.details'), value: "details", onClick: () => setTab('details') }), hit?.howler.dossier?.map((lead, index) => (_jsx(Tab
135
+ }, children: [_jsx(Tooltip, { title: t('hit.panel.view.layout'), children: _jsx(IconButton, { onClick: onOrientationChange, children: _jsx(ViewAgenda, { sx: { transition: 'rotate 250ms', rotate: orientation === 'vertical' ? '90deg' : '0deg' } }) }) }), _jsx(SocketBadge, { size: "medium" }), analytic && (_jsx(Tooltip, { title: t('hit.panel.analytic.open'), children: _jsx(IconButton, { onClick: () => navigate(`/analytics/${analytic.analytic_id}`), children: _jsx(QueryStats, {}) }) })), hit?.howler.bundles?.length > 0 && _jsx(BundleButton, { ids: hit.howler.bundles })] }))] }), _jsx(HowlerCard, { sx: [orientation === 'horizontal' && { height: '0px' }], children: _jsx(CardContent, { sx: { padding: 1, position: 'relative' }, children: _jsx(HitActions, { hit: hit, orientation: "vertical" }) }) }), _jsx(Box, { sx: { gridColumn: '1 / span 2', mb: 1 }, children: _jsxs(Tabs, { value: tab === 'overview' && !hasOverview ? 'details' : tab, sx: { display: 'flex', flexDirection: 'row', pr: 2, alignItems: 'center' }, children: [hit?.howler?.is_bundle && (_jsx(Tab, { label: t('hit.viewer.aggregate'), value: "hit_aggregate", onClick: () => setTab('hit_aggregate') })), hasOverview && (_jsx(Tab, { label: t('hit.viewer.overview'), value: "overview", onClick: () => setTab('overview') })), _jsx(Tab, { label: t('hit.viewer.details'), value: "details", onClick: () => setTab('details') }), hit?.howler.dossier?.map((lead, index) => (_jsx(Tab
135
136
  // eslint-disable-next-line react/no-array-index-key
136
137
  , { label: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [lead.icon && _jsx(Icon, { icon: lead.icon }), _jsx("span", { children: i18n.language === 'en' ? lead.label.en : lead.label.fr })] }), value: 'lead:' + index, onClick: () => setTab('lead:' + index) }, 'lead:' + index))), dossiers.flatMap((_dossier, dossierIndex) => (_dossier.leads ?? []).map((_lead, leadIndex) => (_jsx(Tab
137
138
  // eslint-disable-next-line react/no-array-index-key
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /// <reference types="vitest" />
3
+ import { render, screen, waitFor } from '@testing-library/react';
4
+ import i18n from '@cccsaurora/howler-ui/i18n';
5
+ import React from 'react';
6
+ import { I18nextProvider } from 'react-i18next';
7
+ import { setupContextSelectorMock } from '@cccsaurora/howler-ui/tests/mocks';
8
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
9
+ import AddNewCard from './AddNewCard';
10
+ setupContextSelectorMock();
11
+ const mockApiSearchAnalyticPost = vi.fn();
12
+ vi.mock('api', () => ({
13
+ default: {
14
+ search: {
15
+ analytic: {
16
+ post: (...args) => mockApiSearchAnalyticPost(...args)
17
+ }
18
+ }
19
+ }
20
+ }));
21
+ vi.mock('components/elements/addons/buttons/CustomButton', () => ({
22
+ default: ({ children, disabled, onClick, ...props }) => (_jsx("button", { disabled: disabled, onClick: onClick, ...props, children: children }))
23
+ }));
24
+ const mockFetchViews = vi.hoisted(() => vi.fn().mockResolvedValue(undefined));
25
+ vi.mock('components/app/providers/ViewProvider', () => ({
26
+ ViewContext: React.createContext({
27
+ views: {
28
+ 'view-1': { view_id: 'view-1', title: 'My View', query: 'howler.status:open', sort: 'event.created desc', span: 'date.range.1.month', type: 'personal', owner: 'testuser', settings: { advance_on_triage: false } },
29
+ 'view-2': { view_id: 'view-2', title: 'Second View', query: 'howler.status:open', sort: 'event.created desc', span: 'date.range.1.month', type: 'personal', owner: 'testuser', settings: { advance_on_triage: false } }
30
+ },
31
+ fetchViews: mockFetchViews
32
+ })
33
+ }));
34
+ const Wrapper = ({ children }) => (_jsx(I18nextProvider, { i18n: i18n, children: children }));
35
+ describe('AddNewCard', () => {
36
+ const mockAddCard = vi.fn();
37
+ beforeEach(() => {
38
+ vi.clearAllMocks();
39
+ mockApiSearchAnalyticPost.mockResolvedValue({
40
+ items: [{ analytic_id: 'analytic-1', name: 'Test Analytic', description: 'Test description' }],
41
+ total: 1
42
+ });
43
+ });
44
+ it('should render the add card title', async () => {
45
+ render(_jsx(AddNewCard, { dashboard: [], addCard: mockAddCard }), { wrapper: Wrapper });
46
+ await waitFor(() => {
47
+ expect(screen.getByText('Add New Panel')).toBeInTheDocument();
48
+ });
49
+ });
50
+ it('should render the description', async () => {
51
+ render(_jsx(AddNewCard, { dashboard: [], addCard: mockAddCard }), { wrapper: Wrapper });
52
+ await waitFor(() => {
53
+ expect(screen.getByText(/Add an additional panel/)).toBeInTheDocument();
54
+ });
55
+ });
56
+ it('should have the create button disabled initially', async () => {
57
+ render(_jsx(AddNewCard, { dashboard: [], addCard: mockAddCard }), { wrapper: Wrapper });
58
+ await waitFor(() => {
59
+ expect(screen.getByRole('button', { name: /Create/i })).toBeDisabled();
60
+ });
61
+ });
62
+ it('should fetch analytics on mount', async () => {
63
+ render(_jsx(AddNewCard, { dashboard: [], addCard: mockAddCard }), { wrapper: Wrapper });
64
+ await waitFor(() => {
65
+ expect(mockApiSearchAnalyticPost).toHaveBeenCalledWith(expect.objectContaining({ query: '*:*' }));
66
+ });
67
+ });
68
+ });