@cccsaurora/howler-ui 2.18.0 → 2.19.0-cases.1051

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 (468) 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 +9 -4
  20. package/api/index.js +18 -10
  21. package/api/overview/index.d.ts +4 -3
  22. package/api/overview/index.js +6 -6
  23. package/api/search/case.d.ts +4 -0
  24. package/api/search/case.js +8 -0
  25. package/api/search/facet/hit.d.ts +1 -3
  26. package/api/search/facet/index.d.ts +3 -1
  27. package/api/search/index.d.ts +2 -1
  28. package/api/search/index.js +2 -1
  29. package/api/search/overview.d.ts +2 -2
  30. package/api/socket/index.d.ts +3 -0
  31. package/api/socket/index.js +6 -0
  32. package/api/socket/viewers.d.ts +2 -0
  33. package/api/socket/viewers.js +8 -0
  34. package/api/socket/viewers.test.js +44 -0
  35. package/api/template/index.d.ts +4 -3
  36. package/api/template/index.js +6 -6
  37. package/api/user/index.d.ts +2 -1
  38. package/api/user/index.js +2 -2
  39. package/api/v2/case/index.d.ts +9 -0
  40. package/api/v2/case/index.js +21 -0
  41. package/api/v2/case/items.d.ts +6 -0
  42. package/api/v2/case/items.js +18 -0
  43. package/api/v2/case/rules.d.ts +6 -0
  44. package/api/v2/case/rules.js +18 -0
  45. package/api/v2/index.d.ts +4 -0
  46. package/api/v2/index.js +6 -0
  47. package/api/v2/search/facet.d.ts +3 -0
  48. package/api/v2/search/facet.js +12 -0
  49. package/api/v2/search/index.d.ts +5 -0
  50. package/api/v2/search/index.js +24 -0
  51. package/api/view/index.d.ts +4 -3
  52. package/api/view/index.js +6 -6
  53. package/commons/components/leftnav/LeftNavDrawer.js +1 -1
  54. package/components/app/App.d.ts +1 -0
  55. package/components/app/App.js +64 -13
  56. package/components/app/hooks/useMatchers.d.ts +1 -1
  57. package/components/app/hooks/useMatchers.js +23 -11
  58. package/components/app/hooks/useMatchers.test.js +22 -22
  59. package/components/app/hooks/useTitle.js +5 -5
  60. package/components/app/providers/FavouritesProvider.js +2 -6
  61. package/components/app/providers/ModalProvider.d.ts +2 -0
  62. package/components/app/providers/ModalProvider.js +5 -1
  63. package/components/app/providers/ParameterProvider.d.ts +9 -2
  64. package/components/app/providers/ParameterProvider.js +165 -240
  65. package/components/app/providers/ParameterProvider.test.js +346 -94
  66. package/components/app/providers/RecordProvider.d.ts +23 -0
  67. package/components/app/providers/{HitProvider.js → RecordProvider.js} +42 -42
  68. package/components/app/providers/{HitSearchProvider.d.ts → RecordSearchProvider.d.ts} +6 -6
  69. package/components/app/providers/{HitSearchProvider.js → RecordSearchProvider.js} +12 -17
  70. package/components/app/providers/{HitSearchProvider.test.js → RecordSearchProvider.test.js} +52 -71
  71. package/components/app/providers/SearchResponseProvider.d.ts +21 -0
  72. package/components/app/providers/SearchResponseProvider.js +81 -0
  73. package/components/app/providers/SearchResponseProvider.test.d.ts +1 -0
  74. package/components/app/providers/SearchResponseProvider.test.js +406 -0
  75. package/components/app/providers/SocketProvider.d.ts +11 -2
  76. package/components/app/providers/SocketProvider.js +18 -5
  77. package/components/app/providers/UserListProvider.js +28 -8
  78. package/components/app/providers/ViewProvider.test.js +4 -1
  79. package/components/elements/ContextMenu.d.ts +56 -0
  80. package/components/elements/ContextMenu.js +109 -0
  81. package/components/elements/ContextMenu.test.d.ts +1 -0
  82. package/components/elements/ContextMenu.test.js +215 -0
  83. package/components/{routes/overviews/OverviewEditor.js → elements/MarkdownEditor.js} +3 -3
  84. package/components/elements/ObjectDetails.d.ts +6 -0
  85. package/components/elements/{hit/HitDetails.js → ObjectDetails.js} +17 -17
  86. package/components/elements/PluginTypography.d.ts +2 -1
  87. package/components/elements/PluginTypography.js +3 -2
  88. package/components/elements/UserList.d.ts +5 -2
  89. package/components/elements/UserList.js +18 -8
  90. package/components/elements/addons/buttons/CustomButton.test.d.ts +1 -0
  91. package/components/elements/addons/buttons/CustomButton.test.js +106 -0
  92. package/components/elements/addons/layout/FlexOne.test.d.ts +1 -0
  93. package/components/elements/addons/layout/FlexOne.test.js +30 -0
  94. package/components/elements/addons/lists/TuiListBase.js +4 -0
  95. package/components/elements/addons/lists/TuiListElement.js +6 -3
  96. package/components/elements/addons/lists/index.d.ts +1 -0
  97. package/components/elements/addons/search/SearchPagination.d.ts +2 -1
  98. package/components/elements/addons/search/SearchPagination.js +4 -4
  99. package/components/elements/addons/search/SearchPagination.test.d.ts +1 -0
  100. package/components/elements/addons/search/SearchPagination.test.js +80 -0
  101. package/components/elements/addons/search/SearchTotal.test.d.ts +1 -0
  102. package/components/elements/addons/search/SearchTotal.test.js +47 -0
  103. package/components/elements/addons/search/phrase/Phrase.js +1 -1
  104. package/components/elements/case/CaseCard.d.ts +12 -0
  105. package/components/elements/case/CaseCard.js +42 -0
  106. package/components/elements/case/CasePreview.d.ts +6 -0
  107. package/components/elements/case/CasePreview.js +17 -0
  108. package/components/elements/case/StatusIcon.d.ts +5 -0
  109. package/components/elements/case/StatusIcon.js +13 -0
  110. package/components/elements/display/ChipPopper.d.ts +1 -1
  111. package/components/elements/display/ChipPopper.js +6 -5
  112. package/components/elements/display/DocumentationButton.test.d.ts +1 -0
  113. package/components/elements/display/DocumentationButton.test.js +84 -0
  114. package/components/elements/display/DynamicTabs.test.d.ts +1 -0
  115. package/components/elements/display/DynamicTabs.test.js +93 -0
  116. package/components/elements/display/HowlerAvatar.js +2 -1
  117. package/components/elements/display/HowlerCard.js +1 -1
  118. package/components/elements/display/HowlerCard.test.d.ts +1 -0
  119. package/components/elements/display/HowlerCard.test.js +51 -0
  120. package/components/elements/display/Image.test.d.ts +1 -0
  121. package/components/elements/display/Image.test.js +79 -0
  122. package/components/elements/display/ItemManager.d.ts +2 -2
  123. package/components/elements/display/ItemManager.js +1 -1
  124. package/components/elements/display/Markdown.js +9 -2
  125. package/components/elements/display/Modal.js +2 -0
  126. package/components/elements/display/QueryResultText.test.d.ts +1 -0
  127. package/components/elements/display/QueryResultText.test.js +48 -0
  128. package/components/elements/display/TextDivider.test.d.ts +1 -0
  129. package/components/elements/display/TextDivider.test.js +43 -0
  130. package/components/elements/display/TypingIndicator.test.d.ts +1 -0
  131. package/components/elements/display/TypingIndicator.test.js +33 -0
  132. package/components/elements/display/modals/ConfirmDeleteModal.d.ts +4 -0
  133. package/components/elements/display/modals/ConfirmDeleteModal.js +4 -2
  134. package/components/elements/hit/HitActions.js +4 -4
  135. package/components/elements/hit/HitBanner.d.ts +1 -0
  136. package/components/elements/hit/HitBanner.js +34 -51
  137. package/components/elements/hit/HitCard.d.ts +2 -0
  138. package/components/elements/hit/HitCard.js +7 -7
  139. package/components/elements/hit/HitLabels.js +2 -2
  140. package/components/elements/hit/HitOutline.d.ts +1 -0
  141. package/components/elements/hit/HitOutline.js +3 -3
  142. package/components/elements/hit/{HitQuickSearch.d.ts → HitPreview.d.ts} +3 -3
  143. package/components/elements/hit/{HitQuickSearch.js → HitPreview.js} +10 -4
  144. package/components/elements/hit/HitSummary.d.ts +2 -1
  145. package/components/elements/hit/HitSummary.js +9 -7
  146. package/components/elements/hit/aggregate/HitGraph.js +8 -8
  147. package/components/elements/hit/elements/AnalyticLink.d.ts +9 -0
  148. package/components/elements/hit/elements/AnalyticLink.js +22 -0
  149. package/components/elements/hit/elements/Assigned.d.ts +2 -0
  150. package/components/elements/hit/elements/Assigned.js +21 -16
  151. package/components/elements/hit/elements/Assigned.test.d.ts +1 -0
  152. package/components/elements/hit/elements/Assigned.test.js +65 -0
  153. package/components/elements/hit/outlines/DefaultOutline.js +1 -1
  154. package/components/elements/hit/related/RelatedRecords.js +63 -0
  155. package/components/elements/observable/ObservableCard.d.ts +6 -0
  156. package/components/elements/observable/ObservableCard.js +22 -0
  157. package/components/elements/observable/ObservablePreview.d.ts +6 -0
  158. package/components/elements/observable/ObservablePreview.js +12 -0
  159. package/components/elements/{hit/HitComments.d.ts → record/RecordComments.d.ts} +5 -4
  160. package/components/elements/{hit/HitComments.js → record/RecordComments.js} +29 -28
  161. package/components/{routes/hits/search/HitContextMenu.d.ts → elements/record/RecordContextMenu.d.ts} +3 -3
  162. package/components/elements/record/RecordContextMenu.js +268 -0
  163. package/components/elements/record/RecordContextMenu.test.d.ts +1 -0
  164. package/components/{routes/hits/search/HitContextMenu.test.js → elements/record/RecordContextMenu.test.js} +190 -39
  165. package/components/elements/record/RecordRelated.d.ts +7 -0
  166. package/components/elements/record/RecordRelated.js +34 -0
  167. package/components/elements/{hit/HitWorklog.d.ts → record/RecordWorklog.d.ts} +4 -3
  168. package/components/elements/{hit/HitWorklog.js → record/RecordWorklog.js} +15 -13
  169. package/components/elements/view/ViewTitle.d.ts +1 -0
  170. package/components/elements/view/ViewTitle.js +9 -2
  171. package/components/hooks/useHitActions.d.ts +1 -1
  172. package/components/hooks/useHitActions.js +4 -4
  173. package/components/hooks/useMyApi.d.ts +1 -1
  174. package/components/hooks/useMyPreferences.js +12 -2
  175. package/components/hooks/useMySearch.js +2 -2
  176. package/components/hooks/useMySitemap.js +4 -1
  177. package/components/hooks/useMyTheme.js +9 -2
  178. package/components/hooks/{useHitSelection.d.ts → useRecordSelection.d.ts} +2 -2
  179. package/components/hooks/{useHitSelection.js → useRecordSelection.js} +12 -33
  180. package/components/hooks/useRelatedRecords.d.ts +13 -0
  181. package/components/hooks/useRelatedRecords.js +32 -0
  182. package/components/routes/403.d.ts +3 -0
  183. package/components/routes/403.js +10 -0
  184. package/components/routes/403.test.d.ts +1 -0
  185. package/components/routes/403.test.js +27 -0
  186. package/components/routes/404.test.d.ts +1 -0
  187. package/components/routes/404.test.js +26 -0
  188. package/components/routes/ErrorBoundary.test.d.ts +1 -0
  189. package/components/routes/ErrorBoundary.test.js +43 -0
  190. package/components/routes/ErrorOccured.test.d.ts +1 -0
  191. package/components/routes/ErrorOccured.test.js +35 -0
  192. package/components/routes/Logout.test.d.ts +1 -0
  193. package/components/routes/Logout.test.js +59 -0
  194. package/components/routes/action/edit/ActionEditor.js +3 -3
  195. package/components/routes/action/useMyActionFunctions.js +9 -3
  196. package/components/routes/action/view/ActionDetails.js +15 -2
  197. package/components/routes/action/view/ActionSearch.js +32 -20
  198. package/components/routes/action/view/markdown/integrations.en.md.js +1 -1
  199. package/components/routes/action/view/markdown/integrations.fr.md.js +1 -1
  200. package/components/routes/advanced/QueryBuilder.js +1 -1
  201. package/components/routes/advanced/QueryEditor.js +3 -3
  202. package/components/routes/advanced/historyCompletionProvider.js +3 -3
  203. package/components/routes/analytics/AnalyticDetails.js +12 -8
  204. package/components/routes/analytics/AnalyticSearch.js +7 -7
  205. package/components/routes/cases/CaseViewer.d.ts +2 -0
  206. package/components/routes/cases/CaseViewer.js +44 -0
  207. package/components/routes/cases/CaseViewer.test.d.ts +1 -0
  208. package/components/routes/cases/CaseViewer.test.js +133 -0
  209. package/components/routes/cases/Cases.d.ts +2 -0
  210. package/components/routes/cases/Cases.js +147 -0
  211. package/components/routes/cases/constants.d.ts +6 -0
  212. package/components/routes/cases/constants.js +6 -0
  213. package/components/routes/cases/detail/AlertPanel.d.ts +6 -0
  214. package/components/routes/cases/detail/AlertPanel.js +33 -0
  215. package/components/routes/cases/detail/CaseAssets.d.ts +11 -0
  216. package/components/routes/cases/detail/CaseAssets.js +104 -0
  217. package/components/routes/cases/detail/CaseAssets.test.d.ts +1 -0
  218. package/components/routes/cases/detail/CaseAssets.test.js +167 -0
  219. package/components/routes/cases/detail/CaseDashboard.d.ts +7 -0
  220. package/components/routes/cases/detail/CaseDashboard.js +66 -0
  221. package/components/routes/cases/detail/CaseDetails.d.ts +6 -0
  222. package/components/routes/cases/detail/CaseDetails.js +70 -0
  223. package/components/routes/cases/detail/CaseOverview.d.ts +7 -0
  224. package/components/routes/cases/detail/CaseOverview.js +43 -0
  225. package/components/routes/cases/detail/CaseRules.d.ts +7 -0
  226. package/components/routes/cases/detail/CaseRules.js +57 -0
  227. package/components/routes/cases/detail/CaseRules.test.d.ts +1 -0
  228. package/components/routes/cases/detail/CaseRules.test.js +221 -0
  229. package/components/routes/cases/detail/CaseSidebar.d.ts +8 -0
  230. package/components/routes/cases/detail/CaseSidebar.js +107 -0
  231. package/components/routes/cases/detail/CaseSidebar.test.d.ts +1 -0
  232. package/components/routes/cases/detail/CaseSidebar.test.js +266 -0
  233. package/components/routes/cases/detail/CaseTask.d.ts +11 -0
  234. package/components/routes/cases/detail/CaseTask.js +66 -0
  235. package/components/routes/cases/detail/CaseTimeline.d.ts +12 -0
  236. package/components/routes/cases/detail/CaseTimeline.js +106 -0
  237. package/components/routes/cases/detail/CaseTimeline.test.d.ts +1 -0
  238. package/components/routes/cases/detail/CaseTimeline.test.js +320 -0
  239. package/components/routes/cases/detail/CreateRuleDialog.d.ts +9 -0
  240. package/components/routes/cases/detail/CreateRuleDialog.js +163 -0
  241. package/components/routes/cases/detail/CreateRuleDialog.test.d.ts +1 -0
  242. package/components/routes/cases/detail/CreateRuleDialog.test.js +259 -0
  243. package/components/routes/cases/detail/ItemPage.d.ts +6 -0
  244. package/components/routes/cases/detail/ItemPage.js +95 -0
  245. package/components/routes/cases/detail/RelatedCasePanel.d.ts +6 -0
  246. package/components/routes/cases/detail/RelatedCasePanel.js +34 -0
  247. package/components/routes/cases/detail/TaskPanel.d.ts +7 -0
  248. package/components/routes/cases/detail/TaskPanel.js +52 -0
  249. package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +11 -0
  250. package/components/routes/cases/detail/aggregates/CaseAggregate.js +24 -0
  251. package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +6 -0
  252. package/components/routes/cases/detail/aggregates/SourceAggregate.js +26 -0
  253. package/components/routes/cases/detail/assets/Asset.d.ts +14 -0
  254. package/components/routes/cases/detail/assets/Asset.js +12 -0
  255. package/components/routes/cases/detail/assets/Asset.test.d.ts +1 -0
  256. package/components/routes/cases/detail/assets/Asset.test.js +72 -0
  257. package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +20 -0
  258. package/components/routes/cases/detail/sidebar/CaseFolder.js +83 -0
  259. package/components/routes/cases/detail/sidebar/CaseFolder.test.d.ts +1 -0
  260. package/components/routes/cases/detail/sidebar/CaseFolder.test.js +295 -0
  261. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.d.ts +34 -0
  262. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.js +103 -0
  263. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.d.ts +1 -0
  264. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.js +363 -0
  265. package/components/routes/cases/detail/sidebar/FolderEntry.d.ts +25 -0
  266. package/components/routes/cases/detail/sidebar/FolderEntry.js +88 -0
  267. package/components/routes/cases/detail/sidebar/FolderEntry.test.d.ts +1 -0
  268. package/components/routes/cases/detail/sidebar/FolderEntry.test.js +206 -0
  269. package/components/routes/cases/detail/sidebar/RootDropZone.d.ts +5 -0
  270. package/components/routes/cases/detail/sidebar/RootDropZone.js +33 -0
  271. package/components/routes/cases/detail/sidebar/types.d.ts +9 -0
  272. package/components/routes/cases/detail/sidebar/utils.d.ts +3 -0
  273. package/components/routes/cases/detail/sidebar/utils.js +29 -0
  274. package/components/routes/cases/detail/sidebar/utils.test.d.ts +1 -0
  275. package/components/routes/cases/detail/sidebar/utils.test.js +82 -0
  276. package/components/routes/cases/hooks/useCase.d.ts +13 -0
  277. package/components/routes/cases/hooks/useCase.js +69 -0
  278. package/components/routes/cases/hooks/useCase.test.d.ts +1 -0
  279. package/components/routes/cases/hooks/useCase.test.js +141 -0
  280. package/components/routes/cases/modals/AddToCaseModal.d.ts +7 -0
  281. package/components/routes/cases/modals/AddToCaseModal.js +59 -0
  282. package/components/routes/cases/modals/AddToCaseModal.test.d.ts +1 -0
  283. package/components/routes/cases/modals/AddToCaseModal.test.js +313 -0
  284. package/components/routes/cases/modals/CaseRecordRow.d.ts +9 -0
  285. package/components/routes/cases/modals/CaseRecordRow.js +15 -0
  286. package/components/routes/cases/modals/CreateCaseModal.d.ts +7 -0
  287. package/components/routes/cases/modals/CreateCaseModal.js +55 -0
  288. package/components/routes/cases/modals/CreateCaseModal.test.d.ts +1 -0
  289. package/components/routes/cases/modals/CreateCaseModal.test.js +358 -0
  290. package/components/routes/cases/modals/RenameItemModal.d.ts +9 -0
  291. package/components/routes/cases/modals/RenameItemModal.js +48 -0
  292. package/components/routes/cases/modals/ResolveModal.d.ts +7 -0
  293. package/components/routes/cases/modals/ResolveModal.js +116 -0
  294. package/components/routes/cases/modals/ResolveModal.test.d.ts +1 -0
  295. package/components/routes/cases/modals/ResolveModal.test.js +454 -0
  296. package/components/routes/cases/modals/hooks.d.ts +7 -0
  297. package/components/routes/cases/modals/hooks.js +44 -0
  298. package/components/routes/cases/modals/types.d.ts +5 -0
  299. package/components/routes/cases/search/CaseAssigneeFilter.d.ts +6 -0
  300. package/components/routes/cases/search/CaseAssigneeFilter.js +33 -0
  301. package/components/routes/cases/search/CaseAssigneeFilter.test.d.ts +1 -0
  302. package/components/routes/cases/search/CaseAssigneeFilter.test.js +127 -0
  303. package/components/routes/cases/search/CaseDateFilter.d.ts +13 -0
  304. package/components/routes/cases/search/CaseDateFilter.js +26 -0
  305. package/components/routes/cases/search/CaseDateFilter.test.d.ts +1 -0
  306. package/components/routes/cases/search/CaseDateFilter.test.js +115 -0
  307. package/components/routes/cases/search/CaseStatusFilter.d.ts +6 -0
  308. package/components/routes/cases/search/CaseStatusFilter.js +13 -0
  309. package/components/routes/cases/search/CaseStatusFilter.test.d.ts +1 -0
  310. package/components/routes/cases/search/CaseStatusFilter.test.js +86 -0
  311. package/components/routes/dossiers/DossierEditor.js +2 -2
  312. package/components/routes/dossiers/DossierEditor.test.js +1 -1
  313. package/components/routes/dossiers/Dossiers.js +25 -19
  314. package/components/routes/help/ActionIntroductionDocumentation.js +1 -1
  315. package/components/routes/help/ApiDocumentation.js +1 -1
  316. package/components/routes/help/HitBannerDocumentation.js +1 -0
  317. package/components/routes/help/HitDocumentation.js +1 -3
  318. package/components/routes/help/markdown/en/client.md.js +1 -1
  319. package/components/routes/help/markdown/en/retention.md.js +1 -1
  320. package/components/routes/help/markdown/fr/client.md.js +1 -1
  321. package/components/routes/help/markdown/fr/retention.md.js +1 -1
  322. package/components/routes/hits/search/InformationPane.d.ts +1 -0
  323. package/components/routes/hits/search/InformationPane.js +50 -63
  324. package/components/routes/hits/search/LayoutSettings.js +3 -3
  325. package/components/routes/hits/search/QuerySettings.js +2 -1
  326. package/components/routes/hits/search/QuerySettings.test.js +14 -9
  327. package/components/routes/hits/search/{HitBrowser.js → RecordBrowser.js} +9 -9
  328. package/components/routes/hits/search/{HitQuery.d.ts → RecordQuery.d.ts} +2 -2
  329. package/components/routes/hits/search/{HitQuery.js → RecordQuery.js} +6 -6
  330. package/components/routes/hits/search/SearchPane.js +26 -49
  331. package/components/routes/hits/search/ViewLink.js +3 -3
  332. package/components/routes/hits/search/ViewLink.test.js +8 -8
  333. package/components/routes/hits/search/grid/AddColumnModal.js +5 -4
  334. package/components/routes/hits/search/grid/EnhancedCell.d.ts +2 -1
  335. package/components/routes/hits/search/grid/EnhancedCell.js +2 -2
  336. package/components/routes/hits/search/grid/HitGrid.js +20 -18
  337. package/components/routes/hits/search/grid/{HitRow.d.ts → RecordRow.d.ts} +3 -2
  338. package/components/routes/hits/search/grid/{HitRow.js → RecordRow.js} +10 -8
  339. package/components/routes/hits/search/shared/CustomSpan.js +7 -7
  340. package/components/routes/hits/search/shared/CustomSpan.test.d.ts +1 -0
  341. package/components/routes/hits/search/shared/CustomSpan.test.js +170 -0
  342. package/components/routes/hits/search/shared/IndexPicker.d.ts +2 -0
  343. package/components/routes/hits/search/shared/IndexPicker.js +20 -0
  344. package/components/routes/hits/view/HitViewer.js +12 -13
  345. package/components/routes/home/AddNewCard.js +1 -1
  346. package/components/routes/home/AddNewCard.test.d.ts +1 -0
  347. package/components/routes/home/AddNewCard.test.js +86 -0
  348. package/components/routes/home/ViewCard.js +47 -41
  349. package/components/routes/home/ViewRefresh.test.d.ts +1 -0
  350. package/components/routes/home/ViewRefresh.test.js +79 -0
  351. package/components/routes/observables/ObservableViewer.d.ts +7 -0
  352. package/components/routes/observables/ObservableViewer.js +27 -0
  353. package/components/routes/overviews/OverviewCard.test.d.ts +1 -0
  354. package/components/routes/overviews/OverviewCard.test.js +108 -0
  355. package/components/routes/overviews/OverviewViewer.js +18 -10
  356. package/components/routes/overviews/Overviews.js +25 -19
  357. package/components/routes/overviews/template/en.md.js +1 -1
  358. package/components/routes/overviews/template/fr.md.js +1 -1
  359. package/components/routes/settings/LocalSection.js +2 -1
  360. package/components/routes/settings/SettingsSection.test.d.ts +1 -0
  361. package/components/routes/settings/SettingsSection.test.js +19 -0
  362. package/components/routes/templates/TemplateCard.d.ts +3 -1
  363. package/components/routes/templates/TemplateCard.js +8 -4
  364. package/components/routes/templates/TemplateCard.test.d.ts +1 -0
  365. package/components/routes/templates/TemplateCard.test.js +115 -0
  366. package/components/routes/templates/TemplateViewer.js +62 -16
  367. package/components/routes/templates/Templates.js +35 -13
  368. package/components/routes/views/ViewComposer.js +46 -19
  369. package/components/routes/views/Views.js +21 -14
  370. package/locales/en/translation.json +146 -8
  371. package/locales/fr/translation.json +144 -8
  372. package/models/WithMetadata.d.ts +2 -1
  373. package/models/entities/generated/ApiType.d.ts +1 -1
  374. package/models/entities/generated/AttachmentsFile.d.ts +12 -0
  375. package/models/entities/generated/Case.d.ts +28 -0
  376. package/models/entities/generated/DestinationOriginal.d.ts +19 -0
  377. package/models/entities/generated/EmailAttachment.d.ts +8 -0
  378. package/models/entities/generated/EmailParent.d.ts +19 -0
  379. package/models/entities/generated/Enrichments.d.ts +7 -0
  380. package/models/entities/generated/EnrichmentsIndicator.d.ts +21 -0
  381. package/models/entities/generated/Hit.d.ts +2 -0
  382. package/models/entities/generated/Howler.d.ts +1 -5
  383. package/models/entities/generated/HttpResponse.d.ts +11 -0
  384. package/models/entities/generated/Item.d.ts +9 -0
  385. package/models/entities/generated/Observable.d.ts +85 -0
  386. package/models/entities/generated/ObservableCloud.d.ts +20 -0
  387. package/models/entities/generated/ObservableDestination.d.ts +23 -0
  388. package/models/entities/generated/ObservableEmail.d.ts +30 -0
  389. package/models/entities/generated/ObservableFile.d.ts +36 -0
  390. package/models/entities/generated/ObservableHowler.d.ts +42 -0
  391. package/models/entities/generated/ObservableHttp.d.ts +11 -0
  392. package/models/entities/generated/ObservableObserver.d.ts +21 -0
  393. package/models/entities/generated/ObservableOrganization.d.ts +7 -0
  394. package/models/entities/generated/ObservableProcess.d.ts +34 -0
  395. package/models/entities/generated/ObservableSource.d.ts +23 -0
  396. package/models/entities/generated/ObservableThreat.d.ts +21 -0
  397. package/models/entities/generated/ObservableTls.d.ts +12 -0
  398. package/models/entities/generated/ObserverIngress.d.ts +9 -0
  399. package/models/entities/generated/Rule.d.ts +6 -9
  400. package/models/entities/generated/Task.d.ts +10 -0
  401. package/models/entities/generated/Threat.d.ts +2 -2
  402. package/models/entities/generated/{Enrichment.d.ts → ThreatEnrichment.d.ts} +1 -1
  403. package/models/entities/generated/UserUser.d.ts +2 -0
  404. package/models/entities/generated/View.d.ts +1 -0
  405. package/models/socket/CaseUpdate.d.ts +5 -0
  406. package/models/socket/ViewersUpdate.d.ts +4 -0
  407. package/package.json +147 -132
  408. package/plugins/clue/components/ClueTypography.js +2 -2
  409. package/plugins/clue/utils.d.ts +2 -1
  410. package/rest/FetchClient.test.d.ts +1 -0
  411. package/rest/FetchClient.test.js +81 -0
  412. package/setupTests.js +5 -0
  413. package/tests/mocks.d.ts +11 -1
  414. package/tests/mocks.js +12 -7
  415. package/tests/server-handlers.js +6 -1
  416. package/tests/utils.d.ts +4 -0
  417. package/tests/utils.js +20 -0
  418. package/utils/Throttler.test.d.ts +1 -0
  419. package/utils/Throttler.test.js +135 -0
  420. package/utils/actionUtils.test.d.ts +1 -0
  421. package/utils/actionUtils.test.js +140 -0
  422. package/utils/constants.d.ts +5 -3
  423. package/utils/constants.js +7 -0
  424. package/utils/hitFunctions.d.ts +2 -1
  425. package/utils/hitFunctions.js +4 -4
  426. package/utils/hitFunctions.test.d.ts +1 -0
  427. package/utils/hitFunctions.test.js +52 -0
  428. package/utils/localStorage.test.d.ts +1 -0
  429. package/utils/localStorage.test.js +96 -0
  430. package/utils/menuUtils.js +1 -1
  431. package/utils/menuUtils.test.d.ts +1 -0
  432. package/utils/menuUtils.test.js +198 -0
  433. package/utils/sessionStorage.test.d.ts +1 -0
  434. package/utils/sessionStorage.test.js +81 -0
  435. package/utils/socketUtils.d.ts +14 -0
  436. package/utils/socketUtils.js +17 -1
  437. package/utils/socketUtils.test.d.ts +1 -0
  438. package/utils/socketUtils.test.js +59 -0
  439. package/utils/stringUtils.test.d.ts +1 -0
  440. package/utils/stringUtils.test.js +159 -0
  441. package/utils/typeUtils.d.ts +7 -0
  442. package/utils/typeUtils.js +27 -0
  443. package/utils/utils.test.d.ts +1 -0
  444. package/utils/utils.test.js +292 -0
  445. package/utils/viewUtils.js +3 -0
  446. package/utils/viewUtils.test.d.ts +1 -0
  447. package/utils/viewUtils.test.js +45 -0
  448. package/utils/xsrf.test.d.ts +1 -0
  449. package/utils/xsrf.test.js +41 -0
  450. package/components/app/providers/HitProvider.d.ts +0 -22
  451. package/components/elements/display/icons/BundleButton.d.ts +0 -6
  452. package/components/elements/display/icons/BundleButton.js +0 -32
  453. package/components/elements/hit/HitRelated.d.ts +0 -6
  454. package/components/elements/hit/HitRelated.js +0 -7
  455. package/components/routes/help/BundleDocumentation.d.ts +0 -3
  456. package/components/routes/help/BundleDocumentation.js +0 -12
  457. package/components/routes/help/markdown/en/bundles.md.js +0 -1
  458. package/components/routes/help/markdown/fr/bundles.md.js +0 -1
  459. package/components/routes/hits/search/BundleParentMenu.d.ts +0 -6
  460. package/components/routes/hits/search/BundleParentMenu.js +0 -32
  461. package/components/routes/hits/search/BundleScroller.d.ts +0 -2
  462. package/components/routes/hits/search/BundleScroller.js +0 -6
  463. package/components/routes/hits/search/HitContextMenu.js +0 -229
  464. /package/{components/app/providers/HitSearchProvider.test.d.ts → api/socket/viewers.test.d.ts} +0 -0
  465. /package/components/{routes/hits/search/HitContextMenu.test.d.ts → app/providers/RecordSearchProvider.test.d.ts} +0 -0
  466. /package/components/{routes/overviews/OverviewEditor.d.ts → elements/MarkdownEditor.d.ts} +0 -0
  467. /package/components/elements/hit/{HitDetails.d.ts → related/RelatedRecords.d.ts} +0 -0
  468. /package/components/routes/hits/search/{HitBrowser.d.ts → RecordBrowser.d.ts} +0 -0
@@ -0,0 +1,406 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { act, renderHook } from '@testing-library/react';
3
+ import { useContext } from 'react';
4
+ import SearchResponseProvider, { SearchResponseContext } from './SearchResponseProvider';
5
+ const TEST_PAGE_SIZE = 25;
6
+ const TEST_TOTAL_COUNT = 100;
7
+ const makeWrapper = (initialResponse) => {
8
+ const Wrapper = ({ children }) => (_jsx(SearchResponseProvider, { idField: "id", initialResponse: initialResponse, children: children }));
9
+ return Wrapper;
10
+ };
11
+ const renderProvider = (initialResponse) => {
12
+ return renderHook(() => useContext(SearchResponseContext), {
13
+ wrapper: makeWrapper(initialResponse)
14
+ });
15
+ };
16
+ describe('push', () => {
17
+ it("should add an item to the response if it doesn't exist", () => {
18
+ const hook = renderProvider({
19
+ items: [{ id: '0', name: 'existing' }],
20
+ offset: 0,
21
+ rows: TEST_PAGE_SIZE,
22
+ total: TEST_TOTAL_COUNT,
23
+ removeCount: 0
24
+ });
25
+ act(() => {
26
+ hook.result.current.push({ id: '1', name: 'test' });
27
+ });
28
+ expect(hook.result.current.response).toEqual({
29
+ items: [
30
+ { id: '0', name: 'existing' },
31
+ { id: '1', name: 'test' }
32
+ ],
33
+ offset: 0,
34
+ rows: TEST_PAGE_SIZE,
35
+ total: TEST_TOTAL_COUNT + 1,
36
+ removeCount: 0
37
+ });
38
+ });
39
+ it('should replace an item in the response if it already exists', () => {
40
+ const hook = renderProvider({
41
+ items: [{ id: '0', name: 'old' }],
42
+ offset: 0,
43
+ rows: TEST_PAGE_SIZE,
44
+ total: TEST_TOTAL_COUNT,
45
+ removeCount: 0
46
+ });
47
+ act(() => {
48
+ hook.result.current.push({ id: '0', name: 'new' });
49
+ });
50
+ expect(hook.result.current.response).toEqual({
51
+ items: [{ id: '0', name: 'new' }],
52
+ offset: 0,
53
+ rows: TEST_PAGE_SIZE,
54
+ total: TEST_TOTAL_COUNT,
55
+ removeCount: 0
56
+ });
57
+ });
58
+ it('should update total but not add item if the response has the maximum number of items', () => {
59
+ const items = Array.from({ length: TEST_PAGE_SIZE }, (_, i) => ({ id: i.toString(), name: `item${i}` }));
60
+ const newItem = { id: TEST_PAGE_SIZE.toString(), name: 'test' };
61
+ const hook = renderProvider({
62
+ items,
63
+ offset: 0,
64
+ rows: TEST_PAGE_SIZE,
65
+ total: TEST_TOTAL_COUNT,
66
+ removeCount: 0
67
+ });
68
+ act(() => {
69
+ hook.result.current.push(newItem);
70
+ });
71
+ expect(hook.result.current.response?.items).not.toContainEqual(newItem);
72
+ expect(hook.result.current.response).toEqual({
73
+ items,
74
+ offset: 0,
75
+ rows: TEST_PAGE_SIZE,
76
+ total: TEST_TOTAL_COUNT + 1,
77
+ removeCount: 0
78
+ });
79
+ });
80
+ it('should decrement removeCount if the item does not exist already', () => {
81
+ const hook = renderProvider({
82
+ items: [{ id: '0', name: 'existing' }],
83
+ offset: 0,
84
+ rows: TEST_PAGE_SIZE,
85
+ total: TEST_TOTAL_COUNT,
86
+ removeCount: 1
87
+ });
88
+ act(() => {
89
+ hook.result.current.push({ id: '1', name: 'test' });
90
+ });
91
+ expect(hook.result.current.response).not.toBeNull();
92
+ expect(hook.result.current.response.removeCount).toBe(0);
93
+ });
94
+ it('should not decrement removeCount if the item already exists', () => {
95
+ const hook = renderProvider({
96
+ items: [{ id: '0', name: 'existing' }],
97
+ offset: 0,
98
+ rows: TEST_PAGE_SIZE,
99
+ total: TEST_TOTAL_COUNT,
100
+ removeCount: 1
101
+ });
102
+ act(() => {
103
+ hook.result.current.push({ id: '0', name: 'new' });
104
+ });
105
+ expect(hook.result.current.response).not.toBeNull();
106
+ expect(hook.result.current.response.removeCount).toBe(1);
107
+ });
108
+ it('should keep response null if it is uninitiated', () => {
109
+ const hook = renderProvider();
110
+ act(() => {
111
+ hook.result.current.push({ id: '0', name: 'test' });
112
+ });
113
+ expect(hook.result.current.response).toBeNull();
114
+ });
115
+ });
116
+ describe('remove', () => {
117
+ it('should remove an item from the response if it exists', () => {
118
+ const hook = renderProvider({
119
+ items: [
120
+ { id: '0', name: 'item0' },
121
+ { id: '1', name: 'item1' }
122
+ ],
123
+ offset: 0,
124
+ rows: TEST_PAGE_SIZE,
125
+ total: TEST_TOTAL_COUNT,
126
+ removeCount: 0
127
+ });
128
+ act(() => {
129
+ hook.result.current.remove('0');
130
+ });
131
+ expect(hook.result.current.response).toEqual({
132
+ items: [{ id: '1', name: 'item1' }],
133
+ offset: 0,
134
+ rows: TEST_PAGE_SIZE,
135
+ total: TEST_TOTAL_COUNT - 1,
136
+ removeCount: 1
137
+ });
138
+ });
139
+ it('should ignore the remove if the item does not exist', () => {
140
+ const hook = renderProvider({
141
+ items: [{ id: '0', name: 'item' }],
142
+ offset: 0,
143
+ rows: TEST_PAGE_SIZE,
144
+ total: TEST_TOTAL_COUNT,
145
+ removeCount: 0
146
+ });
147
+ act(() => {
148
+ hook.result.current.remove('1');
149
+ });
150
+ expect(hook.result.current.response).toEqual({
151
+ items: [{ id: '0', name: 'item' }],
152
+ offset: 0,
153
+ rows: TEST_PAGE_SIZE,
154
+ total: TEST_TOTAL_COUNT,
155
+ removeCount: 0
156
+ });
157
+ });
158
+ it('should keep response null if it is uninitiated', () => {
159
+ const hook = renderProvider();
160
+ act(() => {
161
+ hook.result.current.remove('0');
162
+ });
163
+ expect(hook.result.current.response).toBeNull();
164
+ });
165
+ });
166
+ describe('replace', () => {
167
+ it('should replace an item in the response if it exists', () => {
168
+ const hook = renderProvider({
169
+ items: [{ id: '0', name: 'old' }],
170
+ offset: 0,
171
+ rows: TEST_PAGE_SIZE,
172
+ total: TEST_TOTAL_COUNT,
173
+ removeCount: 0
174
+ });
175
+ act(() => {
176
+ hook.result.current.replace('0', { id: undefined, name: 'new' });
177
+ });
178
+ expect(hook.result.current.response).toEqual({
179
+ items: [{ id: '0', name: 'new' }],
180
+ offset: 0,
181
+ rows: TEST_PAGE_SIZE,
182
+ total: TEST_TOTAL_COUNT,
183
+ removeCount: 0
184
+ });
185
+ });
186
+ it('should ignore the replace if the item does not exist', () => {
187
+ const hook = renderProvider({
188
+ items: [{ id: '0', name: 'item' }],
189
+ offset: 0,
190
+ rows: TEST_PAGE_SIZE,
191
+ total: TEST_TOTAL_COUNT,
192
+ removeCount: 0
193
+ });
194
+ act(() => {
195
+ hook.result.current.replace('1', { id: undefined, name: 'new' });
196
+ });
197
+ expect(hook.result.current.response).not.toContainEqual({ id: '1', name: 'new' });
198
+ expect(hook.result.current.response).toEqual({
199
+ items: [{ id: '0', name: 'item' }],
200
+ offset: 0,
201
+ rows: TEST_PAGE_SIZE,
202
+ total: TEST_TOTAL_COUNT,
203
+ removeCount: 0
204
+ });
205
+ });
206
+ it('should keep response null if it is uninitiated', () => {
207
+ const hook = renderProvider();
208
+ act(() => {
209
+ hook.result.current.replace('0', { id: '0', name: 'new' });
210
+ });
211
+ expect(hook.result.current.response).toBeNull();
212
+ });
213
+ it('should throw error if item id does not match the id provided', () => {
214
+ const hook = renderProvider({
215
+ items: [{ id: '0', name: 'old' }],
216
+ offset: 0,
217
+ rows: TEST_PAGE_SIZE,
218
+ total: TEST_TOTAL_COUNT,
219
+ removeCount: 0
220
+ });
221
+ expect(() => act(() => {
222
+ hook.result.current.replace('0', { id: '1', name: 'new' });
223
+ })).toThrow(/id does not match/);
224
+ });
225
+ });
226
+ describe('request', () => {
227
+ const apiSearchMock = vi.fn();
228
+ beforeEach(() => {
229
+ apiSearchMock.mockReset();
230
+ });
231
+ it('should update the response with the result of the request', async () => {
232
+ const hook = renderProvider({
233
+ items: [{ id: '0', name: 'item' }],
234
+ offset: 0,
235
+ rows: TEST_PAGE_SIZE,
236
+ total: TEST_TOTAL_COUNT,
237
+ removeCount: 0
238
+ });
239
+ const request = {
240
+ query: 'test',
241
+ rows: TEST_PAGE_SIZE,
242
+ offset: 0
243
+ };
244
+ apiSearchMock.mockResolvedValue({
245
+ items: [{ id: '1', name: 'new' }],
246
+ offset: request.offset,
247
+ rows: TEST_PAGE_SIZE,
248
+ total: TEST_TOTAL_COUNT
249
+ });
250
+ await act(async () => {
251
+ await hook.result.current.request(apiSearchMock, request);
252
+ });
253
+ expect(apiSearchMock).toHaveBeenCalledWith(request);
254
+ expect(hook.result.current.response).toEqual({
255
+ items: [{ id: '1', name: 'new' }],
256
+ offset: request.offset,
257
+ rows: TEST_PAGE_SIZE,
258
+ total: TEST_TOTAL_COUNT,
259
+ removeCount: 0
260
+ });
261
+ });
262
+ it.for([
263
+ { description: 'before', offset: 0 },
264
+ { description: 'the same as', offset: TEST_PAGE_SIZE }
265
+ ])('should reset removeCount if the offset is $description the current offset', async ({ offset }) => {
266
+ const hook = renderProvider({
267
+ items: [{ id: '0', name: 'item' }],
268
+ offset: TEST_PAGE_SIZE,
269
+ rows: TEST_PAGE_SIZE,
270
+ total: TEST_TOTAL_COUNT,
271
+ removeCount: 5
272
+ });
273
+ const request = {
274
+ query: 'test',
275
+ rows: TEST_PAGE_SIZE,
276
+ offset: offset
277
+ };
278
+ apiSearchMock.mockResolvedValue({
279
+ items: [{ id: '1', name: 'new' }],
280
+ offset: request.offset,
281
+ rows: TEST_PAGE_SIZE,
282
+ total: TEST_TOTAL_COUNT
283
+ });
284
+ await act(async () => {
285
+ await hook.result.current.request(apiSearchMock, request);
286
+ });
287
+ expect(apiSearchMock).toHaveBeenCalledWith(request);
288
+ expect(hook.result.current.response).toEqual({
289
+ items: [{ id: '1', name: 'new' }],
290
+ offset: request.offset,
291
+ rows: TEST_PAGE_SIZE,
292
+ total: TEST_TOTAL_COUNT,
293
+ removeCount: 0
294
+ });
295
+ });
296
+ it('should not reset removeCount if the offset is after the current offset', async () => {
297
+ const hook = renderProvider({
298
+ items: [{ id: '0', name: 'item' }],
299
+ offset: TEST_PAGE_SIZE,
300
+ rows: TEST_PAGE_SIZE,
301
+ total: TEST_TOTAL_COUNT,
302
+ removeCount: 5
303
+ });
304
+ const request = {
305
+ query: 'test',
306
+ rows: TEST_PAGE_SIZE,
307
+ offset: TEST_PAGE_SIZE * 2
308
+ };
309
+ apiSearchMock.mockResolvedValue({
310
+ items: [{ id: '1', name: 'new' }],
311
+ offset: request.offset,
312
+ rows: TEST_PAGE_SIZE,
313
+ total: TEST_TOTAL_COUNT
314
+ });
315
+ await act(async () => {
316
+ await hook.result.current.request(apiSearchMock, request);
317
+ });
318
+ expect(apiSearchMock).toHaveBeenCalledWith(request);
319
+ expect(hook.result.current.response).toEqual({
320
+ items: [{ id: '1', name: 'new' }],
321
+ offset: request.offset,
322
+ rows: TEST_PAGE_SIZE,
323
+ total: TEST_TOTAL_COUNT,
324
+ removeCount: 5
325
+ });
326
+ });
327
+ it('should keep response undefined if the request fails', async () => {
328
+ const hook = renderProvider();
329
+ const request = {
330
+ query: 'test',
331
+ rows: TEST_PAGE_SIZE,
332
+ offset: 0
333
+ };
334
+ apiSearchMock.mockRejectedValue(new Error('Request failed'));
335
+ await act(async () => {
336
+ await hook.result.current.request(apiSearchMock, request).catch(() => { });
337
+ });
338
+ expect(apiSearchMock).toHaveBeenCalledWith(request);
339
+ expect(hook.result.current.response).toBeNull();
340
+ });
341
+ it('should keep response unchanged if the request fails', async () => {
342
+ const initialResponse = {
343
+ items: [{ id: '0', name: 'item' }],
344
+ offset: 0,
345
+ rows: TEST_PAGE_SIZE,
346
+ total: TEST_TOTAL_COUNT,
347
+ removeCount: 5
348
+ };
349
+ const hook = renderProvider(initialResponse);
350
+ const request = {
351
+ query: 'test',
352
+ rows: TEST_PAGE_SIZE,
353
+ offset: 0
354
+ };
355
+ apiSearchMock.mockRejectedValue(new Error('Request failed'));
356
+ await act(async () => {
357
+ await hook.result.current.request(apiSearchMock, request).catch(() => { });
358
+ });
359
+ expect(apiSearchMock).toHaveBeenCalledWith(request);
360
+ expect(hook.result.current.response).toEqual(initialResponse);
361
+ });
362
+ it('should throw an error if the request throws an error', async () => {
363
+ const hook = renderProvider();
364
+ const request = {
365
+ query: 'test',
366
+ rows: TEST_PAGE_SIZE,
367
+ offset: 0
368
+ };
369
+ apiSearchMock.mockRejectedValue(new Error('Request failed'));
370
+ await expect(act(async () => {
371
+ await hook.result.current.request(apiSearchMock, request);
372
+ })).rejects.toThrow('Request failed');
373
+ });
374
+ });
375
+ describe('getSearchRequestData', () => {
376
+ it.for([
377
+ { description: 'no remove count', removeCount: 0 },
378
+ { description: 'with remove count', removeCount: 5 }
379
+ ])('should modify offset if it is provided - $description', ({ removeCount }) => {
380
+ const hook = renderProvider({
381
+ items: [],
382
+ offset: 0,
383
+ rows: TEST_PAGE_SIZE,
384
+ total: TEST_TOTAL_COUNT,
385
+ removeCount: removeCount
386
+ });
387
+ const modifiedRequest = hook.result.current.getSearchRequestData({ offset: TEST_PAGE_SIZE });
388
+ expect(modifiedRequest.offset).toBe(TEST_PAGE_SIZE - removeCount);
389
+ });
390
+ it('should not modify offset if it is not provided', () => {
391
+ const hook = renderProvider({
392
+ items: [],
393
+ offset: 0,
394
+ rows: TEST_PAGE_SIZE,
395
+ total: TEST_TOTAL_COUNT,
396
+ removeCount: 5
397
+ });
398
+ const modifiedRequest = hook.result.current.getSearchRequestData({ query: 'value' });
399
+ expect(modifiedRequest.offset).not.toBeDefined();
400
+ });
401
+ it('should not modify offset if response is uninitialised', () => {
402
+ const hook = renderProvider();
403
+ const modifiedRequest = hook.result.current.getSearchRequestData({ offset: TEST_PAGE_SIZE });
404
+ expect(modifiedRequest.offset).toBe(TEST_PAGE_SIZE);
405
+ });
406
+ });
@@ -48,9 +48,18 @@ interface SocketContextType {
48
48
  */
49
49
  reconnect: () => void;
50
50
  /**
51
- * Helper function to tell if the socket is open
51
+ * Helper to tell if the socket is open
52
52
  */
53
- isOpen: () => boolean;
53
+ open: boolean;
54
+ /**
55
+ * A map of entity IDs to their current viewers.
56
+ */
57
+ viewers: Record<string, string[]>;
58
+ /**
59
+ * Fetch the current viewers for an entity via REST, then keep in sync via socket.
60
+ * @param entityId The entity ID to fetch viewers for
61
+ */
62
+ fetchViewers: (entityId: string) => Promise<void>;
54
63
  }
55
64
  export declare const SocketContext: import("react").Context<SocketContextType>;
56
65
  declare const SocketProvider: React.FC<PropsWithChildren>;
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  /* eslint-disable no-console */
3
3
  import api from '@cccsaurora/howler-ui/api';
4
+ import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
4
5
  import useMyLocalStorage from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
5
- import { createContext, useCallback, useEffect, useRef, useState } from 'react';
6
+ import { createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
7
  import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
7
8
  import { getStored, setAxiosCache, setStored } from '@cccsaurora/howler-ui/utils/sessionStorage';
8
- import { isHitUpdate } from '@cccsaurora/howler-ui/utils/socketUtils';
9
+ import { isHitUpdate, isViewersUpdate } from '@cccsaurora/howler-ui/utils/socketUtils';
9
10
  /**
10
11
  * Enum to help track the status of the Websocket, since the corresponding websocket enums are directly on the object
11
12
  */
@@ -19,6 +20,7 @@ export var Status;
19
20
  export const SocketContext = createContext(null);
20
21
  const SocketProvider = ({ children }) => {
21
22
  const { get } = useMyLocalStorage();
23
+ const { dispatchApi } = useMyApi();
22
24
  // In order to persist the connection through state changes, we use a ref
23
25
  const socket = useRef();
24
26
  // Due to react setState race conditions, listeners are also stored in a ref
@@ -31,6 +33,8 @@ const SocketProvider = ({ children }) => {
31
33
  const [retry, setRetry] = useState(true);
32
34
  // Track the number of failed attempts when connecting to the server
33
35
  const [failedAttempts, setFailedAttempts] = useState(0);
36
+ // Track active viewers per entity ID
37
+ const [viewers, setViewers] = useState({});
34
38
  const onClose = useCallback(e => {
35
39
  // https://www.rfc-editor.org/rfc/rfc6455:
36
40
  // 1006 is a reserved value and MUST NOT be set as a status code in a
@@ -183,7 +187,7 @@ const SocketProvider = ({ children }) => {
183
187
  const addListener = useCallback((key, callback) => {
184
188
  // If a listener with the same key already exists, remove it.
185
189
  if (listeners.current[key]) {
186
- socket.current?.removeEventListener('message', listeners[key]);
190
+ socket.current?.removeEventListener('message', listeners.current[key]);
187
191
  }
188
192
  // We wrap the callback so that all the listeners don't need to JSON.parse the data
189
193
  const wrapped = ev => {
@@ -200,6 +204,9 @@ const SocketProvider = ({ children }) => {
200
204
  api_status_code: parsedData.status
201
205
  });
202
206
  }
207
+ if (isViewersUpdate(parsedData)) {
208
+ setViewers(prev => ({ ...prev, [parsedData.id]: parsedData.viewers }));
209
+ }
203
210
  callback(parsedData);
204
211
  };
205
212
  socket.current?.addEventListener('message', wrapped);
@@ -219,8 +226,14 @@ const SocketProvider = ({ children }) => {
219
226
  }
220
227
  socket.current?.send(data);
221
228
  }, [status]);
222
- const isOpen = useCallback(() => status === Status.OPEN, [status]);
229
+ const open = useMemo(() => status === Status.OPEN, [status]);
223
230
  const reconnect = useCallback(() => setRetry(true), []);
224
- return (_jsx(SocketContext.Provider, { value: { addListener, removeListener, emit, status, reconnect, isOpen }, children: children }));
231
+ const fetchViewers = useCallback(async (entityId) => {
232
+ const result = await dispatchApi(api.socket.viewers.get(entityId), { throwError: false });
233
+ if (result) {
234
+ setViewers(prev => ({ ...prev, [entityId]: result }));
235
+ }
236
+ }, [dispatchApi]);
237
+ return (_jsx(SocketContext.Provider, { value: { addListener, removeListener, emit, status, reconnect, open, viewers, fetchViewers }, children: children }));
225
238
  };
226
239
  export default SocketProvider;
@@ -1,11 +1,15 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import api from '@cccsaurora/howler-ui/api';
3
3
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
4
- import { createContext, useCallback, useState } from 'react';
4
+ import { createContext, useCallback, useEffect, useRef, useState } from 'react';
5
5
  export const UserListContext = createContext(null);
6
6
  const UserListProvider = ({ children }) => {
7
7
  const { dispatchApi } = useMyApi();
8
8
  const [users, setUsers] = useState({});
9
+ const usersRef = useRef(users);
10
+ usersRef.current = users;
11
+ const pendingIds = useRef(new Set());
12
+ const debounceTimer = useRef(null);
9
13
  const searchUsers = useCallback(async (query) => {
10
14
  const newUsers = (await dispatchApi(api.search.user.post({ query, rows: 1000 }), {
11
15
  throwError: false,
@@ -17,14 +21,30 @@ const UserListProvider = ({ children }) => {
17
21
  ...newUsers
18
22
  }));
19
23
  }, [dispatchApi]);
20
- const fetchUsers = useCallback(async (ids) => {
21
- ids.delete('Unknown');
22
- const idsToGet = Array.from(ids.values()).filter(id => !Object.keys(users).includes(id));
23
- if (idsToGet.length <= 0) {
24
- return;
24
+ const fetchUsers = useCallback((ids) => {
25
+ const nextIds = new Set(ids);
26
+ nextIds.delete('Unknown');
27
+ nextIds.forEach(id => pendingIds.current.add(id));
28
+ if (debounceTimer.current) {
29
+ clearTimeout(debounceTimer.current);
25
30
  }
26
- await searchUsers(`id:${[...idsToGet].join(' OR ')}`);
27
- }, [searchUsers, users]);
31
+ debounceTimer.current = setTimeout(async () => {
32
+ const idsToGet = Array.from(pendingIds.current).filter(id => !Object.keys(usersRef.current).includes(id));
33
+ pendingIds.current = new Set();
34
+ debounceTimer.current = null;
35
+ if (idsToGet.length <= 0) {
36
+ return;
37
+ }
38
+ await searchUsers(`id:${idsToGet.join(' OR ')}`);
39
+ }, 200);
40
+ }, [searchUsers]);
41
+ useEffect(() => {
42
+ return () => {
43
+ if (debounceTimer.current) {
44
+ clearTimeout(debounceTimer.current);
45
+ }
46
+ };
47
+ }, []);
28
48
  return _jsx(UserListContext.Provider, { value: { users, fetchUsers, searchUsers }, children: children });
29
49
  };
30
50
  export default UserListProvider;
@@ -151,7 +151,10 @@ describe('ViewContext', () => {
151
151
  it('should allow users to edit views', async () => {
152
152
  const result = await act(async () => hook.result.current('example_view_id', { query: DEFAULT_QUERY }));
153
153
  expect(hput).toHaveBeenCalledOnce();
154
- expect(hput).toBeCalledWith('/api/v1/view/example_view_id', { query: DEFAULT_QUERY });
154
+ const [url, body, ...rest] = vi.mocked(hput).mock.calls[0];
155
+ expect(url).toBe('/api/v1/view/example_view_id');
156
+ expect(body).toEqual({ query: DEFAULT_QUERY });
157
+ expect(rest.every(v => v === undefined)).toBe(true);
155
158
  expect(result).toEqual(MOCK_RESPONSES['/api/v1/view/example_view_id']);
156
159
  });
157
160
  });
@@ -0,0 +1,56 @@
1
+ import { type SxProps } from '@mui/material';
2
+ import type { ElementType, FC, MouseEventHandler, PropsWithChildren, ReactNode } from 'react';
3
+ export type ContextMenuDivider = {
4
+ kind: 'divider';
5
+ id: string;
6
+ sx?: SxProps;
7
+ };
8
+ export type ContextMenuLeafItem = {
9
+ kind: 'item';
10
+ id: string;
11
+ icon?: ReactNode;
12
+ label: ReactNode;
13
+ disabled?: boolean;
14
+ onClick?: () => void;
15
+ /** When provided the item renders as a router Link instead of a button. */
16
+ to?: string;
17
+ };
18
+ export type ContextMenuSubItem = {
19
+ key: string;
20
+ label: ReactNode;
21
+ disabled?: boolean;
22
+ onClick?: () => void;
23
+ };
24
+ export type ContextMenuSubmenuItem = {
25
+ kind: 'submenu';
26
+ /**
27
+ * Identifier for this submenu. Used to derive:
28
+ * - the MenuItem's DOM id (`${id}-menu-item`)
29
+ * - the submenu Paper's DOM id (`${id}-submenu`)
30
+ */
31
+ id: string;
32
+ icon?: ReactNode;
33
+ label: ReactNode;
34
+ disabled?: boolean;
35
+ items: ContextMenuSubItem[];
36
+ };
37
+ export type ContextMenuEntry = ContextMenuDivider | ContextMenuLeafItem | ContextMenuSubmenuItem;
38
+ interface ContextMenuProps {
39
+ items: ContextMenuEntry[];
40
+ /** Called after the menu opens, with the triggering event. */
41
+ onOpen?: MouseEventHandler<HTMLElement>;
42
+ /** Called when the menu closes. */
43
+ onClose?: () => void;
44
+ /** Wraps children + menu in this element. Defaults to Box. */
45
+ Component?: ElementType;
46
+ /** id applied to the wrapper element */
47
+ id?: string;
48
+ }
49
+ /**
50
+ * Generic context menu component that renders a MUI Menu from a declarative
51
+ * items structure supporting leaf items, dividers, and single-level submenus.
52
+ *
53
+ * Submenus appear on hover and are positioned to avoid screen overflow.
54
+ */
55
+ declare const ContextMenu: FC<PropsWithChildren<ContextMenuProps>>;
56
+ export default ContextMenu;