@cccsaurora/howler-ui 2.19.0-cases.863 → 2.19.0-dev.1023

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 (435) hide show
  1. package/api/action/index.d.ts +5 -4
  2. package/api/action/index.js +8 -8
  3. package/api/analytic/index.d.ts +3 -2
  4. package/api/analytic/index.js +4 -4
  5. package/api/analytic/owner.d.ts +2 -1
  6. package/api/analytic/owner.js +2 -2
  7. package/api/analytic/rules.d.ts +2 -1
  8. package/api/analytic/rules.js +2 -2
  9. package/api/dossier/index.d.ts +4 -3
  10. package/api/dossier/index.js +6 -6
  11. package/api/hit/index.d.ts +3 -2
  12. package/api/hit/index.js +4 -4
  13. package/api/hit/labels.d.ts +3 -2
  14. package/api/hit/labels.js +4 -4
  15. package/api/hit/overwrite.d.ts +2 -1
  16. package/api/hit/overwrite.js +2 -2
  17. package/api/hit/transition.d.ts +2 -1
  18. package/api/hit/transition.js +2 -2
  19. package/api/index.d.ts +5 -8
  20. package/api/index.js +10 -18
  21. package/api/overview/index.d.ts +4 -3
  22. package/api/overview/index.js +6 -6
  23. package/api/search/facet/hit.d.ts +3 -1
  24. package/api/search/facet/index.d.ts +1 -3
  25. package/api/search/index.d.ts +1 -2
  26. package/api/search/index.js +1 -2
  27. package/api/search/overview.d.ts +2 -2
  28. package/api/template/index.d.ts +4 -3
  29. package/api/template/index.js +6 -6
  30. package/api/user/index.d.ts +2 -1
  31. package/api/user/index.js +2 -2
  32. package/api/view/index.d.ts +4 -3
  33. package/api/view/index.js +6 -6
  34. package/commons/components/leftnav/LeftNavDrawer.js +1 -1
  35. package/components/app/App.d.ts +1 -0
  36. package/components/app/App.js +20 -42
  37. package/components/app/hooks/useMatchers.d.ts +1 -1
  38. package/components/app/hooks/useMatchers.js +11 -23
  39. package/components/app/hooks/useMatchers.test.js +22 -22
  40. package/components/app/hooks/useTitle.js +5 -5
  41. package/components/app/providers/FavouritesProvider.js +2 -6
  42. package/components/app/providers/HitProvider.d.ts +22 -0
  43. package/components/app/providers/{RecordProvider.js → HitProvider.js} +42 -42
  44. package/components/app/providers/{RecordSearchProvider.d.ts → HitSearchProvider.d.ts} +6 -6
  45. package/components/app/providers/{RecordSearchProvider.js → HitSearchProvider.js} +17 -12
  46. package/components/app/providers/{RecordSearchProvider.test.js → HitSearchProvider.test.js} +71 -52
  47. package/components/app/providers/ModalProvider.d.ts +1 -1
  48. package/components/app/providers/ModalProvider.js +5 -1
  49. package/components/app/providers/ParameterProvider.d.ts +2 -9
  50. package/components/app/providers/ParameterProvider.js +240 -165
  51. package/components/app/providers/ParameterProvider.test.js +94 -346
  52. package/components/app/providers/SearchResponseProvider.d.ts +21 -0
  53. package/components/app/providers/SearchResponseProvider.js +81 -0
  54. package/components/app/providers/SearchResponseProvider.test.js +406 -0
  55. package/components/app/providers/SocketProvider.d.ts +2 -11
  56. package/components/app/providers/SocketProvider.js +5 -18
  57. package/components/app/providers/UserListProvider.js +8 -28
  58. package/components/app/providers/ViewProvider.test.js +4 -1
  59. package/components/elements/PluginTypography.d.ts +1 -2
  60. package/components/elements/PluginTypography.js +2 -3
  61. package/components/elements/UserList.d.ts +2 -5
  62. package/components/elements/UserList.js +8 -18
  63. package/components/elements/addons/buttons/CustomButton.test.js +106 -0
  64. package/components/elements/addons/layout/FlexOne.test.js +30 -0
  65. package/components/elements/addons/lists/TuiListBase.js +4 -0
  66. package/components/elements/addons/lists/TuiListElement.js +6 -3
  67. package/components/elements/addons/lists/index.d.ts +1 -0
  68. package/components/elements/addons/search/SearchPagination.d.ts +2 -1
  69. package/components/elements/addons/search/SearchPagination.js +4 -4
  70. package/components/elements/addons/search/SearchPagination.test.js +80 -0
  71. package/components/elements/addons/search/SearchTotal.test.js +47 -0
  72. package/components/elements/addons/search/phrase/Phrase.js +1 -1
  73. package/components/elements/display/ChipPopper.d.ts +1 -1
  74. package/components/elements/display/ChipPopper.js +5 -5
  75. package/components/elements/display/DocumentationButton.test.js +84 -0
  76. package/components/elements/display/DynamicTabs.test.js +93 -0
  77. package/components/elements/display/HowlerAvatar.js +2 -1
  78. package/components/elements/display/HowlerCard.js +1 -1
  79. package/components/elements/display/HowlerCard.test.js +55 -0
  80. package/components/elements/display/Image.test.js +79 -0
  81. package/components/elements/display/ItemManager.d.ts +2 -2
  82. package/components/elements/display/ItemManager.js +1 -1
  83. package/components/elements/display/Markdown.js +9 -2
  84. package/components/elements/display/Modal.js +0 -2
  85. package/components/elements/display/QueryResultText.test.js +48 -0
  86. package/components/elements/display/TextDivider.test.js +43 -0
  87. package/components/elements/display/TypingIndicator.test.js +33 -0
  88. package/components/elements/display/icons/BundleButton.d.ts +6 -0
  89. package/components/elements/display/icons/BundleButton.js +32 -0
  90. package/components/elements/display/modals/ConfirmDeleteModal.d.ts +4 -0
  91. package/components/elements/display/modals/ConfirmDeleteModal.js +4 -2
  92. package/components/elements/hit/HitActions.js +4 -4
  93. package/components/elements/hit/HitBanner.d.ts +0 -1
  94. package/components/elements/hit/HitBanner.js +49 -31
  95. package/components/elements/hit/HitCard.d.ts +0 -2
  96. package/components/elements/hit/HitCard.js +7 -7
  97. package/components/elements/{record/RecordComments.d.ts → hit/HitComments.d.ts} +4 -5
  98. package/components/elements/{record/RecordComments.js → hit/HitComments.js} +28 -29
  99. package/components/elements/{ObjectDetails.js → hit/HitDetails.js} +17 -17
  100. package/components/elements/hit/HitLabels.js +2 -2
  101. package/components/elements/hit/HitOutline.d.ts +0 -1
  102. package/components/elements/hit/HitOutline.js +3 -3
  103. package/components/elements/hit/{HitPreview.d.ts → HitQuickSearch.d.ts} +3 -3
  104. package/components/elements/hit/{HitPreview.js → HitQuickSearch.js} +4 -10
  105. package/components/elements/hit/HitRelated.d.ts +6 -0
  106. package/components/elements/hit/HitRelated.js +7 -0
  107. package/components/elements/hit/HitSummary.d.ts +1 -2
  108. package/components/elements/hit/HitSummary.js +8 -8
  109. package/components/elements/{record/RecordWorklog.d.ts → hit/HitWorklog.d.ts} +3 -4
  110. package/components/elements/{record/RecordWorklog.js → hit/HitWorklog.js} +13 -15
  111. package/components/elements/hit/aggregate/HitGraph.js +8 -8
  112. package/components/elements/hit/elements/{Assigned.d.ts → HitUsers.d.ts} +5 -2
  113. package/components/elements/hit/elements/HitUsers.js +33 -0
  114. package/components/elements/hit/outlines/DefaultOutline.js +1 -1
  115. package/components/elements/view/ViewTitle.d.ts +0 -1
  116. package/components/elements/view/ViewTitle.js +2 -9
  117. package/components/hooks/useHitActions.d.ts +1 -1
  118. package/components/hooks/useHitActions.js +4 -4
  119. package/components/hooks/{useRecordSelection.d.ts → useHitSelection.d.ts} +2 -2
  120. package/components/hooks/{useRecordSelection.js → useHitSelection.js} +33 -12
  121. package/components/hooks/useMyApi.d.ts +1 -1
  122. package/components/hooks/useMyPreferences.js +3 -11
  123. package/components/hooks/useMySearch.js +2 -2
  124. package/components/hooks/useMySitemap.js +1 -4
  125. package/components/hooks/useMyTheme.js +2 -9
  126. package/components/routes/403.test.js +27 -0
  127. package/components/routes/404.test.js +26 -0
  128. package/components/routes/ErrorBoundary.test.js +43 -0
  129. package/components/routes/ErrorOccured.test.js +35 -0
  130. package/components/routes/Logout.test.js +59 -0
  131. package/components/routes/action/edit/ActionEditor.js +2 -2
  132. package/components/routes/action/useMyActionFunctions.js +5 -2
  133. package/components/routes/action/view/ActionDetails.js +10 -2
  134. package/components/routes/action/view/ActionSearch.js +30 -19
  135. package/components/routes/advanced/QueryBuilder.js +1 -1
  136. package/components/routes/advanced/QueryEditor.js +3 -3
  137. package/components/routes/advanced/historyCompletionProvider.js +3 -3
  138. package/components/routes/analytics/AnalyticDetails.js +12 -8
  139. package/components/routes/analytics/AnalyticSearch.js +7 -7
  140. package/components/routes/dossiers/DossierEditor.js +2 -2
  141. package/components/routes/dossiers/DossierEditor.test.js +1 -1
  142. package/components/routes/dossiers/Dossiers.js +25 -19
  143. package/components/routes/help/ApiDocumentation.js +1 -1
  144. package/components/routes/help/BundleDocumentation.d.ts +3 -0
  145. package/components/routes/help/BundleDocumentation.js +12 -0
  146. package/components/routes/help/HitBannerDocumentation.js +0 -1
  147. package/components/routes/help/HitDocumentation.js +3 -1
  148. package/components/routes/help/markdown/en/bundles.md.js +1 -0
  149. package/components/routes/help/markdown/en/client.md.js +1 -1
  150. package/components/routes/help/markdown/fr/bundles.md.js +1 -0
  151. package/components/routes/help/markdown/fr/client.md.js +1 -1
  152. package/components/routes/hits/search/BundleParentMenu.d.ts +6 -0
  153. package/components/routes/hits/search/BundleParentMenu.js +32 -0
  154. package/components/routes/hits/search/BundleScroller.d.ts +2 -0
  155. package/components/routes/hits/search/BundleScroller.js +6 -0
  156. package/components/routes/hits/search/{RecordBrowser.js → HitBrowser.js} +9 -9
  157. package/components/{elements/record/RecordContextMenu.d.ts → routes/hits/search/HitContextMenu.d.ts} +3 -3
  158. package/components/routes/hits/search/HitContextMenu.js +239 -0
  159. package/components/{elements/record/RecordContextMenu.test.js → routes/hits/search/HitContextMenu.test.js} +43 -98
  160. package/components/routes/hits/search/{RecordQuery.d.ts → HitQuery.d.ts} +2 -2
  161. package/components/routes/hits/search/{RecordQuery.js → HitQuery.js} +6 -6
  162. package/components/routes/hits/search/InformationPane.d.ts +0 -1
  163. package/components/routes/hits/search/InformationPane.js +63 -50
  164. package/components/routes/hits/search/LayoutSettings.js +3 -3
  165. package/components/routes/hits/search/QuerySettings.js +1 -2
  166. package/components/routes/hits/search/QuerySettings.test.js +9 -14
  167. package/components/routes/hits/search/SearchPane.js +49 -26
  168. package/components/routes/hits/search/ViewLink.js +3 -3
  169. package/components/routes/hits/search/ViewLink.test.js +8 -8
  170. package/components/routes/hits/search/grid/AddColumnModal.js +4 -5
  171. package/components/routes/hits/search/grid/EnhancedCell.d.ts +1 -2
  172. package/components/routes/hits/search/grid/EnhancedCell.js +2 -2
  173. package/components/routes/hits/search/grid/HitGrid.js +18 -20
  174. package/components/routes/hits/search/grid/{RecordRow.d.ts → HitRow.d.ts} +2 -3
  175. package/components/routes/hits/search/grid/{RecordRow.js → HitRow.js} +9 -11
  176. package/components/routes/hits/search/shared/CustomSpan.js +7 -7
  177. package/components/routes/hits/search/shared/CustomSpan.test.js +170 -0
  178. package/components/routes/hits/view/HitViewer.js +13 -12
  179. package/components/routes/home/AddNewCard.test.js +86 -0
  180. package/components/routes/home/ViewCard.js +41 -47
  181. package/components/routes/home/ViewRefresh.test.js +79 -0
  182. package/components/routes/overviews/OverviewCard.test.js +108 -0
  183. package/components/{elements/MarkdownEditor.js → routes/overviews/OverviewEditor.js} +3 -3
  184. package/components/routes/overviews/OverviewViewer.js +18 -10
  185. package/components/routes/overviews/Overviews.js +25 -19
  186. package/components/routes/settings/LocalSection.js +2 -1
  187. package/components/routes/settings/SettingsSection.test.d.ts +1 -0
  188. package/components/routes/settings/SettingsSection.test.js +19 -0
  189. package/components/routes/templates/TemplateCard.d.ts +3 -1
  190. package/components/routes/templates/TemplateCard.js +8 -4
  191. package/components/routes/templates/TemplateCard.test.d.ts +1 -0
  192. package/components/routes/templates/TemplateCard.test.js +115 -0
  193. package/components/routes/templates/TemplateViewer.js +62 -16
  194. package/components/routes/templates/Templates.js +35 -13
  195. package/components/routes/views/ViewComposer.js +19 -46
  196. package/components/routes/views/Views.js +21 -14
  197. package/locales/en/translation.json +21 -118
  198. package/locales/fr/translation.json +23 -120
  199. package/models/WithMetadata.d.ts +1 -2
  200. package/models/entities/generated/{ThreatEnrichment.d.ts → Enrichment.d.ts} +1 -1
  201. package/models/entities/generated/Hit.d.ts +1 -1
  202. package/models/entities/generated/Howler.d.ts +6 -0
  203. package/models/entities/generated/Rule.d.ts +9 -6
  204. package/models/entities/generated/Threat.d.ts +2 -2
  205. package/models/entities/generated/UserUser.d.ts +2 -0
  206. package/models/entities/generated/View.d.ts +0 -1
  207. package/package.json +125 -145
  208. package/plugins/clue/components/ClueTypography.js +2 -2
  209. package/plugins/clue/utils.d.ts +1 -2
  210. package/rest/FetchClient.test.d.ts +1 -0
  211. package/rest/FetchClient.test.js +81 -0
  212. package/setupTests.js +5 -0
  213. package/tests/mocks.d.ts +1 -11
  214. package/tests/mocks.js +7 -12
  215. package/tests/server-handlers.js +1 -6
  216. package/tests/utils.d.ts +0 -4
  217. package/tests/utils.js +0 -20
  218. package/utils/Throttler.test.d.ts +1 -0
  219. package/utils/Throttler.test.js +135 -0
  220. package/utils/actionUtils.test.d.ts +1 -0
  221. package/utils/actionUtils.test.js +140 -0
  222. package/utils/constants.d.ts +4 -4
  223. package/utils/constants.js +1 -6
  224. package/utils/hitFunctions.d.ts +1 -2
  225. package/utils/hitFunctions.js +4 -4
  226. package/utils/hitFunctions.test.d.ts +1 -0
  227. package/utils/hitFunctions.test.js +52 -0
  228. package/utils/localStorage.test.d.ts +1 -0
  229. package/utils/localStorage.test.js +96 -0
  230. package/utils/menuUtils.test.d.ts +1 -0
  231. package/utils/menuUtils.test.js +198 -0
  232. package/utils/sessionStorage.test.d.ts +1 -0
  233. package/utils/sessionStorage.test.js +81 -0
  234. package/utils/socketUtils.d.ts +0 -14
  235. package/utils/socketUtils.js +1 -17
  236. package/utils/socketUtils.test.js +16 -49
  237. package/utils/stringUtils.test.d.ts +1 -0
  238. package/utils/stringUtils.test.js +159 -0
  239. package/utils/utils.test.d.ts +1 -0
  240. package/utils/utils.test.js +292 -0
  241. package/utils/viewUtils.js +0 -3
  242. package/utils/viewUtils.test.d.ts +1 -0
  243. package/utils/viewUtils.test.js +45 -0
  244. package/utils/xsrf.test.d.ts +1 -0
  245. package/utils/xsrf.test.js +41 -0
  246. package/api/search/case.d.ts +0 -4
  247. package/api/search/case.js +0 -8
  248. package/api/socket/index.d.ts +0 -3
  249. package/api/socket/index.js +0 -6
  250. package/api/socket/viewers.d.ts +0 -2
  251. package/api/socket/viewers.js +0 -8
  252. package/api/socket/viewers.test.js +0 -44
  253. package/api/v2/case/index.d.ts +0 -9
  254. package/api/v2/case/index.js +0 -21
  255. package/api/v2/case/items.d.ts +0 -6
  256. package/api/v2/case/items.js +0 -18
  257. package/api/v2/case/rules.d.ts +0 -6
  258. package/api/v2/case/rules.js +0 -18
  259. package/api/v2/index.d.ts +0 -4
  260. package/api/v2/index.js +0 -6
  261. package/api/v2/search/facet.d.ts +0 -3
  262. package/api/v2/search/facet.js +0 -12
  263. package/api/v2/search/index.d.ts +0 -5
  264. package/api/v2/search/index.js +0 -24
  265. package/components/app/providers/RecordProvider.d.ts +0 -23
  266. package/components/elements/ContextMenu.d.ts +0 -56
  267. package/components/elements/ContextMenu.js +0 -109
  268. package/components/elements/ContextMenu.test.js +0 -215
  269. package/components/elements/ObjectDetails.d.ts +0 -6
  270. package/components/elements/case/CaseCard.d.ts +0 -12
  271. package/components/elements/case/CaseCard.js +0 -42
  272. package/components/elements/case/CasePreview.d.ts +0 -6
  273. package/components/elements/case/CasePreview.js +0 -17
  274. package/components/elements/case/StatusIcon.d.ts +0 -5
  275. package/components/elements/case/StatusIcon.js +0 -13
  276. package/components/elements/hit/elements/AnalyticLink.d.ts +0 -9
  277. package/components/elements/hit/elements/AnalyticLink.js +0 -22
  278. package/components/elements/hit/elements/Assigned.js +0 -34
  279. package/components/elements/hit/elements/Assigned.test.js +0 -65
  280. package/components/elements/hit/related/RelatedRecords.js +0 -63
  281. package/components/elements/observable/ObservableCard.d.ts +0 -6
  282. package/components/elements/observable/ObservableCard.js +0 -22
  283. package/components/elements/observable/ObservablePreview.d.ts +0 -6
  284. package/components/elements/observable/ObservablePreview.js +0 -12
  285. package/components/elements/record/RecordContextMenu.js +0 -268
  286. package/components/elements/record/RecordRelated.d.ts +0 -7
  287. package/components/elements/record/RecordRelated.js +0 -34
  288. package/components/hooks/useRelatedRecords.d.ts +0 -13
  289. package/components/hooks/useRelatedRecords.js +0 -32
  290. package/components/routes/cases/CaseViewer.d.ts +0 -2
  291. package/components/routes/cases/CaseViewer.js +0 -44
  292. package/components/routes/cases/CaseViewer.test.js +0 -133
  293. package/components/routes/cases/Cases.d.ts +0 -2
  294. package/components/routes/cases/Cases.js +0 -148
  295. package/components/routes/cases/constants.d.ts +0 -6
  296. package/components/routes/cases/constants.js +0 -6
  297. package/components/routes/cases/detail/AlertPanel.d.ts +0 -6
  298. package/components/routes/cases/detail/AlertPanel.js +0 -33
  299. package/components/routes/cases/detail/CaseAssets.d.ts +0 -11
  300. package/components/routes/cases/detail/CaseAssets.js +0 -104
  301. package/components/routes/cases/detail/CaseAssets.test.js +0 -167
  302. package/components/routes/cases/detail/CaseDashboard.d.ts +0 -7
  303. package/components/routes/cases/detail/CaseDashboard.js +0 -66
  304. package/components/routes/cases/detail/CaseDetails.d.ts +0 -6
  305. package/components/routes/cases/detail/CaseDetails.js +0 -70
  306. package/components/routes/cases/detail/CaseOverview.d.ts +0 -7
  307. package/components/routes/cases/detail/CaseOverview.js +0 -43
  308. package/components/routes/cases/detail/CaseRules.d.ts +0 -7
  309. package/components/routes/cases/detail/CaseRules.js +0 -57
  310. package/components/routes/cases/detail/CaseRules.test.js +0 -221
  311. package/components/routes/cases/detail/CaseSidebar.d.ts +0 -8
  312. package/components/routes/cases/detail/CaseSidebar.js +0 -107
  313. package/components/routes/cases/detail/CaseSidebar.test.js +0 -266
  314. package/components/routes/cases/detail/CaseTask.d.ts +0 -11
  315. package/components/routes/cases/detail/CaseTask.js +0 -66
  316. package/components/routes/cases/detail/CaseTimeline.d.ts +0 -12
  317. package/components/routes/cases/detail/CaseTimeline.js +0 -106
  318. package/components/routes/cases/detail/CaseTimeline.test.js +0 -320
  319. package/components/routes/cases/detail/CreateRuleDialog.d.ts +0 -9
  320. package/components/routes/cases/detail/CreateRuleDialog.js +0 -163
  321. package/components/routes/cases/detail/CreateRuleDialog.test.js +0 -259
  322. package/components/routes/cases/detail/ItemPage.d.ts +0 -6
  323. package/components/routes/cases/detail/ItemPage.js +0 -95
  324. package/components/routes/cases/detail/RelatedCasePanel.d.ts +0 -6
  325. package/components/routes/cases/detail/RelatedCasePanel.js +0 -34
  326. package/components/routes/cases/detail/TaskPanel.d.ts +0 -7
  327. package/components/routes/cases/detail/TaskPanel.js +0 -52
  328. package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +0 -11
  329. package/components/routes/cases/detail/aggregates/CaseAggregate.js +0 -24
  330. package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +0 -6
  331. package/components/routes/cases/detail/aggregates/SourceAggregate.js +0 -26
  332. package/components/routes/cases/detail/assets/Asset.d.ts +0 -14
  333. package/components/routes/cases/detail/assets/Asset.js +0 -12
  334. package/components/routes/cases/detail/assets/Asset.test.js +0 -72
  335. package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +0 -20
  336. package/components/routes/cases/detail/sidebar/CaseFolder.js +0 -83
  337. package/components/routes/cases/detail/sidebar/CaseFolder.test.js +0 -295
  338. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.d.ts +0 -34
  339. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.js +0 -103
  340. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.js +0 -363
  341. package/components/routes/cases/detail/sidebar/FolderEntry.d.ts +0 -25
  342. package/components/routes/cases/detail/sidebar/FolderEntry.js +0 -88
  343. package/components/routes/cases/detail/sidebar/FolderEntry.test.js +0 -206
  344. package/components/routes/cases/detail/sidebar/RootDropZone.d.ts +0 -5
  345. package/components/routes/cases/detail/sidebar/RootDropZone.js +0 -33
  346. package/components/routes/cases/detail/sidebar/types.d.ts +0 -9
  347. package/components/routes/cases/detail/sidebar/utils.d.ts +0 -3
  348. package/components/routes/cases/detail/sidebar/utils.js +0 -29
  349. package/components/routes/cases/detail/sidebar/utils.test.js +0 -82
  350. package/components/routes/cases/hooks/useCase.d.ts +0 -13
  351. package/components/routes/cases/hooks/useCase.js +0 -69
  352. package/components/routes/cases/hooks/useCase.test.js +0 -141
  353. package/components/routes/cases/modals/AddToCaseModal.d.ts +0 -7
  354. package/components/routes/cases/modals/AddToCaseModal.js +0 -59
  355. package/components/routes/cases/modals/AddToCaseModal.test.js +0 -313
  356. package/components/routes/cases/modals/CaseRecordRow.d.ts +0 -9
  357. package/components/routes/cases/modals/CaseRecordRow.js +0 -15
  358. package/components/routes/cases/modals/CreateCaseModal.d.ts +0 -7
  359. package/components/routes/cases/modals/CreateCaseModal.js +0 -55
  360. package/components/routes/cases/modals/CreateCaseModal.test.js +0 -358
  361. package/components/routes/cases/modals/RenameItemModal.d.ts +0 -9
  362. package/components/routes/cases/modals/RenameItemModal.js +0 -48
  363. package/components/routes/cases/modals/ResolveModal.d.ts +0 -7
  364. package/components/routes/cases/modals/ResolveModal.js +0 -115
  365. package/components/routes/cases/modals/ResolveModal.test.js +0 -394
  366. package/components/routes/cases/modals/hooks.d.ts +0 -7
  367. package/components/routes/cases/modals/hooks.js +0 -44
  368. package/components/routes/cases/modals/types.d.ts +0 -5
  369. package/components/routes/cases/search/CaseAssigneeFilter.d.ts +0 -6
  370. package/components/routes/cases/search/CaseAssigneeFilter.js +0 -33
  371. package/components/routes/cases/search/CaseAssigneeFilter.test.js +0 -127
  372. package/components/routes/cases/search/CaseDateFilter.d.ts +0 -13
  373. package/components/routes/cases/search/CaseDateFilter.js +0 -26
  374. package/components/routes/cases/search/CaseDateFilter.test.js +0 -115
  375. package/components/routes/cases/search/CaseStatusFilter.d.ts +0 -6
  376. package/components/routes/cases/search/CaseStatusFilter.js +0 -13
  377. package/components/routes/cases/search/CaseStatusFilter.test.js +0 -86
  378. package/components/routes/hits/search/shared/IndexPicker.d.ts +0 -2
  379. package/components/routes/hits/search/shared/IndexPicker.js +0 -20
  380. package/components/routes/observables/ObservableViewer.d.ts +0 -7
  381. package/components/routes/observables/ObservableViewer.js +0 -27
  382. package/models/entities/generated/AttachmentsFile.d.ts +0 -12
  383. package/models/entities/generated/Case.d.ts +0 -28
  384. package/models/entities/generated/DestinationOriginal.d.ts +0 -19
  385. package/models/entities/generated/EmailAttachment.d.ts +0 -8
  386. package/models/entities/generated/EmailParent.d.ts +0 -19
  387. package/models/entities/generated/Enrichments.d.ts +0 -7
  388. package/models/entities/generated/EnrichmentsIndicator.d.ts +0 -21
  389. package/models/entities/generated/HttpResponse.d.ts +0 -11
  390. package/models/entities/generated/Item.d.ts +0 -9
  391. package/models/entities/generated/Observable.d.ts +0 -85
  392. package/models/entities/generated/ObservableCloud.d.ts +0 -20
  393. package/models/entities/generated/ObservableDestination.d.ts +0 -23
  394. package/models/entities/generated/ObservableEmail.d.ts +0 -30
  395. package/models/entities/generated/ObservableFile.d.ts +0 -36
  396. package/models/entities/generated/ObservableHowler.d.ts +0 -42
  397. package/models/entities/generated/ObservableHttp.d.ts +0 -11
  398. package/models/entities/generated/ObservableObserver.d.ts +0 -21
  399. package/models/entities/generated/ObservableOrganization.d.ts +0 -7
  400. package/models/entities/generated/ObservableProcess.d.ts +0 -34
  401. package/models/entities/generated/ObservableSource.d.ts +0 -23
  402. package/models/entities/generated/ObservableThreat.d.ts +0 -21
  403. package/models/entities/generated/ObservableTls.d.ts +0 -12
  404. package/models/entities/generated/ObserverIngress.d.ts +0 -9
  405. package/models/entities/generated/Task.d.ts +0 -10
  406. package/models/socket/CaseUpdate.d.ts +0 -5
  407. package/models/socket/ViewersUpdate.d.ts +0 -4
  408. package/utils/typeUtils.d.ts +0 -7
  409. package/utils/typeUtils.js +0 -27
  410. /package/{api/socket/viewers.test.d.ts → components/app/providers/HitSearchProvider.test.d.ts} +0 -0
  411. /package/components/app/providers/{RecordSearchProvider.test.d.ts → SearchResponseProvider.test.d.ts} +0 -0
  412. /package/components/elements/{ContextMenu.test.d.ts → addons/buttons/CustomButton.test.d.ts} +0 -0
  413. /package/components/elements/{hit/elements/Assigned.test.d.ts → addons/layout/FlexOne.test.d.ts} +0 -0
  414. /package/components/elements/{record/RecordContextMenu.test.d.ts → addons/search/SearchPagination.test.d.ts} +0 -0
  415. /package/components/{routes/cases/CaseViewer.test.d.ts → elements/addons/search/SearchTotal.test.d.ts} +0 -0
  416. /package/components/{routes/cases/detail/CaseAssets.test.d.ts → elements/display/DocumentationButton.test.d.ts} +0 -0
  417. /package/components/{routes/cases/detail/CaseRules.test.d.ts → elements/display/DynamicTabs.test.d.ts} +0 -0
  418. /package/components/{routes/cases/detail/CaseSidebar.test.d.ts → elements/display/HowlerCard.test.d.ts} +0 -0
  419. /package/components/{routes/cases/detail/CaseTimeline.test.d.ts → elements/display/Image.test.d.ts} +0 -0
  420. /package/components/{routes/cases/detail/CreateRuleDialog.test.d.ts → elements/display/QueryResultText.test.d.ts} +0 -0
  421. /package/components/{routes/cases/detail/assets/Asset.test.d.ts → elements/display/TextDivider.test.d.ts} +0 -0
  422. /package/components/{routes/cases/detail/sidebar/CaseFolder.test.d.ts → elements/display/TypingIndicator.test.d.ts} +0 -0
  423. /package/components/elements/hit/{related/RelatedRecords.d.ts → HitDetails.d.ts} +0 -0
  424. /package/components/routes/{cases/detail/sidebar/CaseFolderContextMenu.test.d.ts → 403.test.d.ts} +0 -0
  425. /package/components/routes/{cases/detail/sidebar/FolderEntry.test.d.ts → 404.test.d.ts} +0 -0
  426. /package/components/routes/{cases/detail/sidebar/utils.test.d.ts → ErrorBoundary.test.d.ts} +0 -0
  427. /package/components/routes/{cases/hooks/useCase.test.d.ts → ErrorOccured.test.d.ts} +0 -0
  428. /package/components/routes/{cases/modals/AddToCaseModal.test.d.ts → Logout.test.d.ts} +0 -0
  429. /package/components/routes/hits/search/{RecordBrowser.d.ts → HitBrowser.d.ts} +0 -0
  430. /package/components/routes/{cases/modals/CreateCaseModal.test.d.ts → hits/search/HitContextMenu.test.d.ts} +0 -0
  431. /package/components/routes/{cases/modals/ResolveModal.test.d.ts → hits/search/shared/CustomSpan.test.d.ts} +0 -0
  432. /package/components/routes/{cases/search/CaseAssigneeFilter.test.d.ts → home/AddNewCard.test.d.ts} +0 -0
  433. /package/components/routes/{cases/search/CaseDateFilter.test.d.ts → home/ViewRefresh.test.d.ts} +0 -0
  434. /package/components/routes/{cases/search/CaseStatusFilter.test.d.ts → overviews/OverviewCard.test.d.ts} +0 -0
  435. /package/components/{elements/MarkdownEditor.d.ts → routes/overviews/OverviewEditor.d.ts} +0 -0
@@ -0,0 +1,86 @@
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': {
29
+ view_id: 'view-1',
30
+ title: 'My View',
31
+ query: 'howler.status:open',
32
+ sort: 'event.created desc',
33
+ span: 'date.range.1.month',
34
+ type: 'personal',
35
+ owner: 'testuser',
36
+ settings: { advance_on_triage: false }
37
+ },
38
+ 'view-2': {
39
+ view_id: 'view-2',
40
+ title: 'Second View',
41
+ query: 'howler.status:open',
42
+ sort: 'event.created desc',
43
+ span: 'date.range.1.month',
44
+ type: 'personal',
45
+ owner: 'testuser',
46
+ settings: { advance_on_triage: false }
47
+ }
48
+ },
49
+ fetchViews: mockFetchViews
50
+ })
51
+ }));
52
+ const Wrapper = ({ children }) => (_jsx(I18nextProvider, { i18n: i18n, children: children }));
53
+ describe('AddNewCard', () => {
54
+ const mockAddCard = vi.fn();
55
+ beforeEach(() => {
56
+ vi.clearAllMocks();
57
+ mockApiSearchAnalyticPost.mockResolvedValue({
58
+ items: [{ analytic_id: 'analytic-1', name: 'Test Analytic', description: 'Test description' }],
59
+ total: 1
60
+ });
61
+ });
62
+ it('should render the add card title', async () => {
63
+ render(_jsx(AddNewCard, { dashboard: [], addCard: mockAddCard }), { wrapper: Wrapper });
64
+ await waitFor(() => {
65
+ expect(screen.getByText('Add New Panel')).toBeInTheDocument();
66
+ });
67
+ });
68
+ it('should render the description', async () => {
69
+ render(_jsx(AddNewCard, { dashboard: [], addCard: mockAddCard }), { wrapper: Wrapper });
70
+ await waitFor(() => {
71
+ expect(screen.getByText(/Add an additional panel/)).toBeInTheDocument();
72
+ });
73
+ });
74
+ it('should have the create button disabled initially', async () => {
75
+ render(_jsx(AddNewCard, { dashboard: [], addCard: mockAddCard }), { wrapper: Wrapper });
76
+ await waitFor(() => {
77
+ expect(screen.getByRole('button', { name: /Create/i })).toBeDisabled();
78
+ });
79
+ });
80
+ it('should fetch analytics on mount', async () => {
81
+ render(_jsx(AddNewCard, { dashboard: [], addCard: mockAddCard }), { wrapper: Wrapper });
82
+ await waitFor(() => {
83
+ expect(mockApiSearchAnalyticPost).toHaveBeenCalledWith(expect.objectContaining({ query: '*:*' }));
84
+ });
85
+ });
86
+ });
@@ -3,80 +3,74 @@ import { OpenInNew } from '@mui/icons-material';
3
3
  import { Card, CardContent, IconButton, Skeleton, Stack, Typography } from '@mui/material';
4
4
  import api from '@cccsaurora/howler-ui/api';
5
5
  import AppListEmpty from '@cccsaurora/howler-ui/commons/components/display/AppListEmpty';
6
- import { useRecordContextSelector } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
6
+ import { useHitContextSelector } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
7
7
  import { ViewContext } from '@cccsaurora/howler-ui/components/app/providers/ViewProvider';
8
8
  import HitBanner from '@cccsaurora/howler-ui/components/elements/hit/HitBanner';
9
9
  import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
10
- import ObservableCard from '@cccsaurora/howler-ui/components/elements/observable/ObservableCard';
11
- import RecordContextMenu from '@cccsaurora/howler-ui/components/elements/record/RecordContextMenu';
12
10
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
11
+ import HitContextMenu from '@cccsaurora/howler-ui/components/routes/hits/search/HitContextMenu';
13
12
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
14
13
  import { useTranslation } from 'react-i18next';
15
14
  import { Link, useNavigate } from 'react-router-dom';
16
15
  import { useContextSelector } from 'use-context-selector';
17
- import { isObservable } from '@cccsaurora/howler-ui/utils/typeUtils';
18
16
  import { buildViewUrl } from '@cccsaurora/howler-ui/utils/viewUtils';
19
- // Custom hook to select records by IDs with proper memoization
20
- const useSelectRecordsByIds = (recordIds) => {
21
- const recordIdsRef = useRef(recordIds);
17
+ // Custom hook to select hits by IDs with proper memoization
18
+ const useSelectHitsByIds = (hitIds) => {
19
+ const hitIdsRef = useRef(hitIds);
22
20
  const prevResultRef = useRef([]);
23
- const prevRecordIdsRef = useRef([]);
24
- // Keep ref up to date with latest recordIds
25
- recordIdsRef.current = recordIds;
21
+ const prevHitIdsRef = useRef([]);
22
+ // Keep ref up to date with latest hitIds
23
+ hitIdsRef.current = hitIds;
26
24
  const selector = useCallback(ctx => {
27
- const currentRecordIds = recordIdsRef.current;
28
- // Fast path: if recordIds array didn't change, check if record objects changed
29
- if (prevRecordIdsRef.current.length === currentRecordIds.length &&
30
- currentRecordIds.every((id, i) => id === prevRecordIdsRef.current[i])) {
31
- // RecordIds unchanged - check if any record objects changed by reference
32
- const anyRecordChanged = currentRecordIds.some((id, i) => ctx.records[id] !== prevResultRef.current[i]);
33
- if (!anyRecordChanged) {
25
+ const currentHitIds = hitIdsRef.current;
26
+ // Fast path: if hitIds array didn't change, check if hit objects changed
27
+ if (prevHitIdsRef.current.length === currentHitIds.length &&
28
+ currentHitIds.every((id, i) => id === prevHitIdsRef.current[i])) {
29
+ // HitIds unchanged - check if any hit objects changed by reference
30
+ const anyHitChanged = currentHitIds.some((id, i) => ctx.hits[id] !== prevResultRef.current[i]);
31
+ if (!anyHitChanged) {
34
32
  return prevResultRef.current;
35
33
  }
36
34
  }
37
35
  // Something changed - rebuild the array
38
- const currentRecords = currentRecordIds.map(id => ctx.records[id]).filter(Boolean);
39
- prevRecordIdsRef.current = currentRecordIds;
40
- prevResultRef.current = currentRecords;
41
- return currentRecords;
36
+ const currentHits = currentHitIds.map(id => ctx.hits[id]).filter(Boolean);
37
+ prevHitIdsRef.current = currentHitIds;
38
+ prevResultRef.current = currentHits;
39
+ return currentHits;
42
40
  }, []); // Empty deps - selector never changes
43
- return useRecordContextSelector(selector);
41
+ return useHitContextSelector(selector);
44
42
  };
45
43
  // Utility functions
46
44
  const normalize = (val) => (val == null ? '' : String(val));
47
45
  // Have to normalize the fields as websockets and api return null and undefined respectively. This causes false positives when comparing signatures if not normalized to a consistent value. We also stringify non-primitive values to ensure changes are detected.
48
- const createRecordSignature = (record) => {
49
- if (!record) {
46
+ const createHitSignature = (hit) => {
47
+ if (!hit)
50
48
  return '';
51
- }
52
- if (isObservable(record)) {
53
- return record.howler?.id;
54
- }
55
- return `${record.howler?.id}:${normalize(record.howler?.status)}:${normalize(record.howler?.assignment)}:${normalize(record.howler?.assessment)}`;
49
+ return `${hit.howler?.id}:${normalize(hit.howler?.status)}:${normalize(hit.howler?.assignment)}:${normalize(hit.howler?.assessment)}`;
56
50
  };
57
- const createSignatureFromRecords = (records) => {
58
- if (records.length === 0)
51
+ const createSignatureFromHits = (hits) => {
52
+ if (hits.length === 0)
59
53
  return '';
60
- return records.map(createRecordSignature).join('|');
54
+ return hits.map(createHitSignature).join('|');
61
55
  };
62
56
  const DEBOUNCE_TIME = 1000; // 1 second debounce for signature changes
63
57
  const ViewCard = ({ viewId, limit, refreshTick, onRefreshComplete }) => {
64
58
  const navigate = useNavigate();
65
59
  const { t } = useTranslation();
66
60
  const { dispatchApi } = useMyApi();
67
- const [recordIds, setRecordIds] = useState([]);
61
+ const [hitIds, setHitIds] = useState([]);
68
62
  const [loading, setLoading] = useState(false);
69
63
  const debounceTimerRef = useRef(null);
70
64
  const isRefreshing = useRef(false);
71
65
  const lastSignature = useRef('');
72
66
  const view = useContextSelector(ViewContext, ctx => ctx.views[viewId]);
73
67
  const fetchViews = useContextSelector(ViewContext, ctx => ctx.fetchViews);
74
- const loadRecords = useRecordContextSelector(ctx => ctx.loadRecords);
68
+ const loadHits = useHitContextSelector(ctx => ctx.loadHits);
75
69
  // Subscribe to hits from HitProvider cache based on current hitIds in the view
76
70
  // Uses memoized selector to avoid unnecessary re-renders on unrelated hit updates
77
- const records = useSelectRecordsByIds(recordIds);
71
+ const hits = useSelectHitsByIds(hitIds);
78
72
  // Create a stable signature that only changes when relevant fields change
79
- const recordsSignature = useMemo(() => createSignatureFromRecords(records), [records]);
73
+ const hitsSignature = useMemo(() => createSignatureFromHits(hits), [hits]);
80
74
  const refreshView = useCallback(async () => {
81
75
  if (!view?.query || isRefreshing.current) {
82
76
  onRefreshComplete?.();
@@ -84,21 +78,21 @@ const ViewCard = ({ viewId, limit, refreshTick, onRefreshComplete }) => {
84
78
  }
85
79
  isRefreshing.current = true;
86
80
  try {
87
- const res = await dispatchApi(api.v2.search.post(view.indexes, {
81
+ const res = await dispatchApi(api.search.hit.post({
88
82
  query: view.query,
89
83
  rows: limit,
90
84
  metadata: ['analytic']
91
85
  }));
92
- const fetchedRecords = res.items ?? [];
93
- loadRecords(fetchedRecords);
94
- setRecordIds(fetchedRecords.map(r => r.howler.id));
95
- lastSignature.current = createSignatureFromRecords(fetchedRecords);
86
+ const fetchedHits = res.items ?? [];
87
+ loadHits(fetchedHits);
88
+ setHitIds(fetchedHits.map(h => h.howler.id));
89
+ lastSignature.current = createSignatureFromHits(fetchedHits);
96
90
  }
97
91
  finally {
98
92
  isRefreshing.current = false;
99
93
  onRefreshComplete?.();
100
94
  }
101
- }, [dispatchApi, limit, view?.query, view?.indexes, loadRecords, onRefreshComplete]);
95
+ }, [dispatchApi, limit, view?.query, loadHits, onRefreshComplete]);
102
96
  const debouncedRefresh = useCallback(() => {
103
97
  if (debounceTimerRef.current) {
104
98
  clearTimeout(debounceTimerRef.current);
@@ -131,16 +125,16 @@ const ViewCard = ({ viewId, limit, refreshTick, onRefreshComplete }) => {
131
125
  }, [view?.query, limit, refreshView]);
132
126
  // Monitor hits currently in the view for changes that might affect query results
133
127
  useEffect(() => {
134
- if (!recordsSignature || recordIds.length === 0 || !lastSignature.current) {
135
- lastSignature.current = recordsSignature;
128
+ if (!hitsSignature || hitIds.length === 0 || !lastSignature.current) {
129
+ lastSignature.current = hitsSignature;
136
130
  return;
137
131
  }
138
132
  // Check if signature actually changed
139
- if (lastSignature.current === recordsSignature) {
133
+ if (lastSignature.current === hitsSignature) {
140
134
  return;
141
135
  }
142
136
  debouncedRefresh();
143
- }, [recordsSignature, recordIds, debouncedRefresh]);
137
+ }, [hitsSignature, hitIds, debouncedRefresh]);
144
138
  useEffect(() => {
145
139
  return () => {
146
140
  if (debounceTimerRef.current) {
@@ -157,6 +151,6 @@ const ViewCard = ({ viewId, limit, refreshTick, onRefreshComplete }) => {
157
151
  }
158
152
  return selectedElement.id;
159
153
  }, []);
160
- return (_jsx(Card, { variant: "outlined", sx: { height: '100%' }, children: _jsxs(Stack, { spacing: 1, sx: { p: 1, minHeight: 100 }, children: [_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { variant: "h6", children: t(view?.title) || _jsx(Skeleton, { variant: "text", height: "2em", width: "100px" }) }), _jsx(IconButton, { size: "small", component: Link, disabled: !view, to: view ? buildViewUrl(view) : '', onClick: () => onClick(view.query), children: _jsx(OpenInNew, { fontSize: "small" }) })] }), loading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { height: 150, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 160, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 140, width: "100%", variant: "rounded" })] })) : records.length > 0 ? (_jsx(RecordContextMenu, { getSelectedId: getSelectedId, children: records.map((r) => (_jsx(Card, { id: r.howler.id, variant: "outlined", sx: { cursor: 'pointer' }, onClick: () => navigate(`/hits/${r.howler.id}`), children: _jsx(CardContent, { children: r.__index == 'hit' ? (_jsx(HitBanner, { layout: HitLayout.DENSE, hit: r })) : (_jsx(ObservableCard, { observable: r })) }) }, r.howler.id))) })) : (_jsx(AppListEmpty, {}))] }) }));
154
+ return (_jsx(Card, { variant: "outlined", sx: { height: '100%' }, children: _jsxs(Stack, { spacing: 1, sx: { p: 1, minHeight: 100 }, children: [_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { variant: "h6", children: t(view?.title) || _jsx(Skeleton, { variant: "text", height: "2em", width: "100px" }) }), _jsx(IconButton, { size: "small", component: Link, disabled: !view, to: view ? buildViewUrl(view) : '', onClick: () => onClick(view.query), children: _jsx(OpenInNew, { fontSize: "small" }) })] }), loading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { height: 150, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 160, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 140, width: "100%", variant: "rounded" })] })) : hits.length > 0 ? (_jsx(HitContextMenu, { getSelectedId: getSelectedId, children: hits.map(h => (_jsx(Card, { id: h.howler.id, variant: "outlined", sx: { cursor: 'pointer' }, onClick: () => navigate((h.howler.is_bundle ? '/bundles/' : '/hits/') + h.howler.id), children: _jsx(CardContent, { children: _jsx(HitBanner, { layout: HitLayout.DENSE, hit: h }) }) }, h.howler.id))) })) : (_jsx(AppListEmpty, {}))] }) }));
161
155
  };
162
156
  export default ViewCard;
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /// <reference types="vitest" />
3
+ import { act, render, screen } 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 { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
8
+ import ViewRefresh, {} from './ViewRefresh';
9
+ const Wrapper = ({ children }) => (_jsx(I18nextProvider, { i18n: i18n, children: children }));
10
+ describe('ViewRefresh', () => {
11
+ beforeEach(() => {
12
+ vi.useFakeTimers();
13
+ });
14
+ afterEach(() => {
15
+ vi.useRealTimers();
16
+ });
17
+ it('should render the refresh button', () => {
18
+ const onRefresh = vi.fn();
19
+ render(_jsx(ViewRefresh, { refreshRate: 30, viewCardCount: 2, onRefresh: onRefresh }), { wrapper: Wrapper });
20
+ expect(screen.getByRole('button')).toBeInTheDocument();
21
+ });
22
+ it('should show a progress indicator initially', () => {
23
+ const onRefresh = vi.fn();
24
+ render(_jsx(ViewRefresh, { refreshRate: 30, viewCardCount: 2, onRefresh: onRefresh }), { wrapper: Wrapper });
25
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
26
+ });
27
+ it('should trigger refresh when progress reaches 100%', async () => {
28
+ const onRefresh = vi.fn();
29
+ render(_jsx(ViewRefresh, { refreshRate: 30, viewCardCount: 2, onRefresh: onRefresh }), { wrapper: Wrapper });
30
+ // Progress increments by 1 every refreshRate*10ms = 300ms.
31
+ // Each increment triggers a re-render and a new setTimeout.
32
+ // We advance one tick at a time inside act to let React process each state update.
33
+ for (let i = 0; i < 101; i++) {
34
+ await act(async () => {
35
+ vi.advanceTimersByTime(300);
36
+ });
37
+ }
38
+ expect(onRefresh).toHaveBeenCalled();
39
+ });
40
+ it('should clear refreshing state when all cards report back via ref', async () => {
41
+ const onRefresh = vi.fn();
42
+ const ref = React.createRef();
43
+ render(_jsx(ViewRefresh, { ref: ref, refreshRate: 30, viewCardCount: 2, onRefresh: onRefresh }), { wrapper: Wrapper });
44
+ // Advance to 100% to trigger refresh
45
+ for (let i = 0; i < 101; i++) {
46
+ await act(async () => {
47
+ vi.advanceTimersByTime(300);
48
+ });
49
+ }
50
+ expect(onRefresh).toHaveBeenCalled();
51
+ // Simulate both cards completing
52
+ act(() => {
53
+ ref.current?.handleRefreshComplete();
54
+ ref.current?.handleRefreshComplete();
55
+ });
56
+ // After all cards complete, the progress should reset (button re-enabled)
57
+ expect(screen.getByRole('button')).not.toBeDisabled();
58
+ });
59
+ it('should trigger refresh via manual click', () => {
60
+ const onRefresh = vi.fn();
61
+ render(_jsx(ViewRefresh, { refreshRate: 30, viewCardCount: 2, onRefresh: onRefresh }), { wrapper: Wrapper });
62
+ // Click the refresh button directly using fireEvent (avoids userEvent timer issues)
63
+ const button = screen.getByRole('button');
64
+ act(() => {
65
+ button.click();
66
+ });
67
+ expect(onRefresh).toHaveBeenCalled();
68
+ });
69
+ it('should not call onRefresh when viewCardCount is 0 and button is clicked', () => {
70
+ const onRefresh = vi.fn();
71
+ render(_jsx(ViewRefresh, { refreshRate: 30, viewCardCount: 0, onRefresh: onRefresh }), { wrapper: Wrapper });
72
+ const button = screen.getByRole('button');
73
+ act(() => {
74
+ button.click();
75
+ });
76
+ // onRefresh should not be called because viewCardCount is 0
77
+ expect(onRefresh).not.toHaveBeenCalled();
78
+ });
79
+ });
@@ -0,0 +1,108 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /// <reference types="vitest" />
3
+ import { render, screen } from '@testing-library/react';
4
+ import userEvent, {} from '@testing-library/user-event';
5
+ import i18n from '@cccsaurora/howler-ui/i18n';
6
+ import { I18nextProvider } from 'react-i18next';
7
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
8
+ import OverviewCard from './OverviewCard';
9
+ vi.mock('components/elements/display/HowlerAvatar', () => ({
10
+ default: ({ userId }) => _jsx("div", { id: "howler-avatar", children: userId })
11
+ }));
12
+ vi.mock('components/elements/addons/layout/FlexOne', () => ({
13
+ default: () => _jsx("div", { id: "flex-one" })
14
+ }));
15
+ const Wrapper = ({ children }) => (_jsx(I18nextProvider, { i18n: i18n, children: children }));
16
+ describe('OverviewCard', () => {
17
+ let user;
18
+ beforeEach(() => {
19
+ user = userEvent.setup();
20
+ vi.clearAllMocks();
21
+ });
22
+ it('should render overview analytic and detection', () => {
23
+ const overview = {
24
+ overview_id: 'ov-1',
25
+ analytic: 'Test Analytic',
26
+ detection: 'Test Detection',
27
+ content: 'line 1\nline 2\nline 3',
28
+ owner: 'testuser'
29
+ };
30
+ render(_jsx(OverviewCard, { overview: overview }), { wrapper: Wrapper });
31
+ expect(screen.getByText(/Test Analytic/)).toBeInTheDocument();
32
+ expect(screen.getByText(/Test Detection/)).toBeInTheDocument();
33
+ });
34
+ it('should render "All" when detection is null', () => {
35
+ const overview = {
36
+ overview_id: 'ov-1',
37
+ analytic: 'Test Analytic',
38
+ detection: null,
39
+ content: 'line 1',
40
+ owner: 'testuser'
41
+ };
42
+ render(_jsx(OverviewCard, { overview: overview }), { wrapper: Wrapper });
43
+ expect(screen.getByText(/All/)).toBeInTheDocument();
44
+ });
45
+ it('should display content preview (max 3 lines)', () => {
46
+ const overview = {
47
+ overview_id: 'ov-1',
48
+ analytic: 'Test Analytic',
49
+ detection: null,
50
+ content: 'first line\nsecond line\nthird line\nfourth line\nfifth line',
51
+ owner: 'testuser'
52
+ };
53
+ render(_jsx(OverviewCard, { overview: overview }), { wrapper: Wrapper });
54
+ expect(screen.getByText(/first line/)).toBeInTheDocument();
55
+ expect(screen.getByText(/second line/)).toBeInTheDocument();
56
+ expect(screen.getByText(/third line/)).toBeInTheDocument();
57
+ });
58
+ it('should render the owner avatar', () => {
59
+ const overview = {
60
+ overview_id: 'ov-1',
61
+ analytic: 'Test Analytic',
62
+ detection: null,
63
+ content: 'content',
64
+ owner: 'testuser'
65
+ };
66
+ render(_jsx(OverviewCard, { overview: overview }), { wrapper: Wrapper });
67
+ expect(screen.getByTestId('howler-avatar')).toBeInTheDocument();
68
+ expect(screen.getByText('testuser')).toBeInTheDocument();
69
+ });
70
+ it('should not show delete button when onDelete is not provided', () => {
71
+ const overview = {
72
+ overview_id: 'ov-1',
73
+ analytic: 'Test Analytic',
74
+ detection: null,
75
+ content: 'content',
76
+ owner: 'testuser'
77
+ };
78
+ render(_jsx(OverviewCard, { overview: overview }), { wrapper: Wrapper });
79
+ expect(document.querySelector('[data-testid="DeleteIcon"]')).not.toBeInTheDocument();
80
+ });
81
+ it('should show delete button and call onDelete when clicked', async () => {
82
+ const mockOnDelete = vi.fn();
83
+ const overview = {
84
+ overview_id: 'ov-1',
85
+ analytic: 'Test Analytic',
86
+ detection: null,
87
+ content: 'content',
88
+ owner: 'testuser'
89
+ };
90
+ render(_jsx(OverviewCard, { overview: overview, onDelete: mockOnDelete }), { wrapper: Wrapper });
91
+ const deleteButton = document.querySelector('[data-testid="DeleteIcon"]').closest('button');
92
+ await user.click(deleteButton);
93
+ expect(mockOnDelete).toHaveBeenCalledWith(expect.anything(), 'ov-1');
94
+ });
95
+ it('should apply custom className', () => {
96
+ const overview = {
97
+ overview_id: 'ov-1',
98
+ analytic: 'Test Analytic',
99
+ detection: null,
100
+ content: 'content',
101
+ owner: 'testuser'
102
+ };
103
+ const { container } = render(_jsx(OverviewCard, { overview: overview, className: "custom-class" }), {
104
+ wrapper: Wrapper
105
+ });
106
+ expect(container.querySelector('.custom-class')).toBeInTheDocument();
107
+ });
108
+ });
@@ -4,8 +4,8 @@ import { useTheme } from '@mui/material';
4
4
  import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
5
5
  import ThemedEditor from '@cccsaurora/howler-ui/components/elements/ThemedEditor';
6
6
  import { memo, useCallback, useContext, useEffect, useMemo } from 'react';
7
- import { conf, language } from '../routes/overviews/markdownExtendedTokenProvider';
8
- const MarkdownEditor = ({ content, setContent, onMount, fontSize = 16, height = '100%', width = '100%', editorOptions = {} }) => {
7
+ import { conf, language } from './markdownExtendedTokenProvider';
8
+ const OverviewEditor = ({ content, setContent, onMount, fontSize = 16, height = '100%', width = '100%', editorOptions = {} }) => {
9
9
  const theme = useTheme();
10
10
  const monaco = useMonaco();
11
11
  const { config } = useContext(ApiConfigContext);
@@ -53,4 +53,4 @@ const MarkdownEditor = ({ content, setContent, onMount, fontSize = 16, height =
53
53
  }), [fontSize, editorOptions]);
54
54
  return (_jsx(ThemedEditor, { height: height, width: width, theme: theme.palette.mode === 'light' ? 'howler' : 'howler-dark', value: content, onChange: value => setContent(value), beforeMount: beforeEditorMount, onMount: onMount, options: options }));
55
55
  };
56
- export default memo(MarkdownEditor);
56
+ export default memo(OverviewEditor);
@@ -8,14 +8,16 @@ import { Check, DarkMode, Delete, SsidChart, WbSunny } from '@mui/icons-material
8
8
  import { useApp } from '@cccsaurora/howler-ui/commons/components/app/hooks';
9
9
  import AppInfoPanel from '@cccsaurora/howler-ui/commons/components/display/AppInfoPanel';
10
10
  import useThemeBuilder from '@cccsaurora/howler-ui/commons/components/utils/hooks/useThemeBuilder';
11
+ import { ModalContext } from '@cccsaurora/howler-ui/components/app/providers/ModalProvider';
11
12
  import { OverviewContext } from '@cccsaurora/howler-ui/components/app/providers/OverviewProvider';
12
13
  import HitOverview from '@cccsaurora/howler-ui/components/elements/hit/HitOverview';
13
14
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
15
+ import useMySnackbar from '@cccsaurora/howler-ui/components/hooks/useMySnackbar';
14
16
  import useMyTheme from '@cccsaurora/howler-ui/components/hooks/useMyTheme';
15
17
  import { useSearchParams } from 'react-router-dom';
16
18
  import hitsData from '@cccsaurora/howler-ui/utils/hit.json';
17
19
  import { sanitizeLuceneQuery } from '@cccsaurora/howler-ui/utils/stringUtils';
18
- import MarkdownEditor from '../../elements/MarkdownEditor';
20
+ import OverviewEditor from './OverviewEditor';
19
21
  import { useStartingTemplate } from './startingTemplate';
20
22
  const OverviewViewer = () => {
21
23
  const theme = useTheme();
@@ -25,6 +27,8 @@ const OverviewViewer = () => {
25
27
  const [params, setParams] = useSearchParams();
26
28
  const { getOverviews } = useContext(OverviewContext);
27
29
  const { dispatchApi } = useMyApi();
30
+ const { withConfirmDeleteModal } = useContext(ModalContext);
31
+ const { showSuccessMessage } = useMySnackbar();
28
32
  const [overviewList, setOverviewList] = useState([]);
29
33
  const [selectedOverview, setSelectedOverview] = useState(null);
30
34
  const [content, setContent] = useState('');
@@ -124,15 +128,19 @@ const OverviewViewer = () => {
124
128
  replace: true
125
129
  });
126
130
  }, [analytic, detection, params, setParams]);
127
- const onDelete = useCallback(async () => {
128
- await dispatchApi(api.overview.del(selectedOverview.overview_id), {
129
- logError: false,
130
- showError: true,
131
- throwError: false
131
+ const onDelete = useCallback(() => {
132
+ withConfirmDeleteModal(async () => {
133
+ await dispatchApi(api.overview.del(selectedOverview.overview_id), {
134
+ logError: false,
135
+ showError: true,
136
+ throwError: true
137
+ });
138
+ setOverviewList(l => l.filter(v => v.overview_id !== selectedOverview.overview_id));
139
+ setSelectedOverview(null);
140
+ setContent('');
141
+ showSuccessMessage(t('route.overviews.manager.delete.success'));
132
142
  });
133
- setSelectedOverview(null);
134
- setContent('');
135
- }, [dispatchApi, selectedOverview?.overview_id]);
143
+ }, [dispatchApi, selectedOverview?.overview_id, withConfirmDeleteModal, showSuccessMessage, t]);
136
144
  const onSave = useCallback(async () => {
137
145
  if (analytic && detection) {
138
146
  try {
@@ -188,7 +196,7 @@ const OverviewViewer = () => {
188
196
  right: `calc(50% + 7px - ${x}px)`,
189
197
  mr: -2.4,
190
198
  pr: 1.5
191
- }, children: _jsx(MarkdownEditor, { height: "100%", content: content, setContent: setContent }) }) }), _jsx(Box, { sx: {
199
+ }, children: _jsx(OverviewEditor, { height: "100%", content: content, setContent: setContent }) }) }), _jsx(Box, { sx: {
192
200
  position: 'absolute',
193
201
  top: 0,
194
202
  bottom: 0,
@@ -2,6 +2,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Article } from '@mui/icons-material';
3
3
  import { Typography } from '@mui/material';
4
4
  import api from '@cccsaurora/howler-ui/api';
5
+ import { ModalContext } from '@cccsaurora/howler-ui/components/app/providers/ModalProvider';
6
+ import SearchResponseProvider, { SearchResponseContext } from '@cccsaurora/howler-ui/components/app/providers/SearchResponseProvider';
5
7
  import { TuiListProvider } from '@cccsaurora/howler-ui/components/elements/addons/lists';
6
8
  import { TuiListMethodContext } from '@cccsaurora/howler-ui/components/elements/addons/lists/TuiListProvider';
7
9
  import ItemManager from '@cccsaurora/howler-ui/components/elements/display/ItemManager';
@@ -18,14 +20,15 @@ const OverviewsBase = () => {
18
20
  const navigate = useNavigate();
19
21
  const { dispatchApi } = useMyApi();
20
22
  const { showSuccessMessage } = useMySnackbar();
23
+ const { withConfirmDeleteModal } = useContext(ModalContext);
21
24
  const [searchParams, setSearchParams] = useSearchParams();
22
25
  const { load } = useContext(TuiListMethodContext);
23
26
  const pageCount = useMyLocalStorageItem(StorageKey.PAGE_COUNT, 25)[0];
24
27
  const [phrase, setPhrase] = useState('');
25
28
  const [offset, setOffset] = useState(parseInt(searchParams.get('offset')) || 0);
26
- const [response, setResponse] = useState(null);
27
29
  const [hasError, setHasError] = useState(false);
28
30
  const [loading, setLoading] = useState(false);
31
+ const { response, request, remove, getSearchRequestData } = useContext(SearchResponseContext);
29
32
  const onSearch = useCallback(async () => {
30
33
  try {
31
34
  setLoading(true);
@@ -40,11 +43,11 @@ const OverviewsBase = () => {
40
43
  // Check for the actual search query
41
44
  const query = phrase ? `*:*${phrase}*` : '*:*';
42
45
  // Ensure the overview should be visible and/or matches the type we are filtering for
43
- setResponse(await dispatchApi(api.search.overview.post({
46
+ await request(api.search.overview.post, {
44
47
  query,
45
48
  rows: pageCount,
46
49
  offset
47
- })));
50
+ });
48
51
  }
49
52
  catch (e) {
50
53
  setHasError(true);
@@ -52,7 +55,7 @@ const OverviewsBase = () => {
52
55
  finally {
53
56
  setLoading(false);
54
57
  }
55
- }, [phrase, setSearchParams, searchParams, dispatchApi, pageCount, offset]);
58
+ }, [phrase, setSearchParams, searchParams, request, pageCount, offset]);
56
59
  // Load the items into list when response changes.
57
60
  // This hook should only trigger when the 'response' changes.
58
61
  useEffect(() => {
@@ -68,24 +71,27 @@ const OverviewsBase = () => {
68
71
  }, [response, load]);
69
72
  const onPageChange = useCallback((_offset) => {
70
73
  if (_offset !== offset) {
71
- searchParams.set('offset', _offset.toString());
74
+ const modifiedRequest = getSearchRequestData({ offset: _offset });
75
+ searchParams.set('offset', modifiedRequest.offset.toString());
72
76
  setSearchParams(searchParams, { replace: true });
73
- setOffset(_offset);
77
+ setOffset(modifiedRequest.offset);
74
78
  }
75
- }, [offset, searchParams, setSearchParams]);
76
- const onDelete = useCallback(async (e, id) => {
79
+ }, [offset, searchParams, setSearchParams, getSearchRequestData]);
80
+ const onDelete = useCallback((e, id) => {
77
81
  e.preventDefault();
78
82
  e.stopPropagation();
79
- try {
80
- await dispatchApi(api.overview.del(id), { throwError: false, showError: true });
81
- await onSearch();
82
- showSuccessMessage(t('route.overviews.manager.delete.success'));
83
- }
84
- catch (_err) {
85
- // eslint-disable-next-line no-console
86
- console.warn(_err);
87
- }
88
- }, [dispatchApi, onSearch, showSuccessMessage, t]);
83
+ withConfirmDeleteModal(async () => {
84
+ try {
85
+ await dispatchApi(api.overview.del(id), { throwError: true, showError: true });
86
+ remove(id);
87
+ showSuccessMessage(t('route.overviews.manager.delete.success'));
88
+ }
89
+ catch (_err) {
90
+ // eslint-disable-next-line no-console
91
+ console.warn(_err);
92
+ }
93
+ });
94
+ }, [dispatchApi, remove, withConfirmDeleteModal, showSuccessMessage, t]);
89
95
  useEffect(() => {
90
96
  onSearch();
91
97
  if (!searchParams.has('offset')) {
@@ -111,6 +117,6 @@ const OverviewsBase = () => {
111
117
  return (_jsx(ItemManager, { onSearch: onSearch, onPageChange: onPageChange, phrase: phrase, setPhrase: setPhrase, hasError: hasError, searching: loading, aboveSearch: _jsx(Typography, { sx: theme => ({ fontStyle: 'italic', color: theme.palette.text.disabled, mb: 0.5 }), variant: "body2", children: t('route.overviews.search.prompt') }), renderer: ({ item }, classRenderer) => renderer(item.item, classRenderer()), response: response, onSelect: (item) => navigate(`/overviews/view?analytic=${item.item.analytic}${item.item.detection ? '&detection=' + item.item.detection : ''}`), onCreate: () => navigate('/overviews/view'), createPrompt: "route.overviews.create", searchPrompt: "route.overviews.manager.search", createIcon: _jsx(Article, { sx: { mr: 1 } }) }));
112
118
  };
113
119
  const Overviews = () => {
114
- return (_jsx(TuiListProvider, { children: _jsx(OverviewsBase, {}) }));
120
+ return (_jsx(TuiListProvider, { children: _jsx(SearchResponseProvider, { idField: "overview_id", children: _jsx(OverviewsBase, {}) }) }));
115
121
  };
116
122
  export default Overviews;