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

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 +5 -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
@@ -1 +1 @@
1
- export default "# Retention in Howler\n\nIn order to comply with organizational policies, Howler is configured to purge stale alerts after a specific amount of\ntime. On this instance, that duration is `duration`.\n\n## How Retention Works\n\nHowler uses an automated retention job that runs on a configurable schedule (typically nightly) to remove\nalerts that have exceeded their retention period. The system evaluates two criteria for deletion:\n\n1. **Standard Retention**: Alerts are deleted when `event.created` exceeds the configured retention period\n2. **Custom Expiry**: Alerts are deleted when the `howler.expiry` field indicates the alert should expire\n\nAn alert will be removed when **either** condition is met - whichever comes first.\n\n## Custom Expiry (`howler.expiry`)\n\nThe `howler.expiry` field allows detection engineers to set custom retention periods for specific alerts\nduring ingestion. This field overrides the standard retention calculation and is commonly used when:\n\n- Clients have requested shorter data retention periods than the deployment default\n- Specific operations require time-limited data storage (e.g., a cybersecurity operation where data can\n only be retained for two weeks after ingest)\n- Regulatory requirements mandate earlier deletion for certain types of data\n\n```alert\nThe howler.expiry field can only shorten retention periods, not extend them. No matter\nwhat, alerts cannot be retained longer than the system-wide retention cutoff based on event.created.\n```\n\n## Configuration\n\nAdministrators can configure retention settings in the system configuration:\n\n```yaml\nsystem:\n type: staging\n retention:\n limit_amount: 120 # Retention period duration\n limit_unit: days # Time unit (days, hours, etc.)\n crontab: \"0 0 * * *\" # Schedule (nightly at midnight)\n enabled: true # Whether retention is active\n```\n\n## User Interface\n\nTo communicate retention timing to users, see the example alert below:\n\n`alert`\n\nIn the top right, hovering over the timestamp will outline how long users have before the alert is\nremoved. In order to ensure compliance with policy, ensure that `event.created` matches the date the\nunderlying data was collected, allowing Howler to ensure data is purged in time.\n"
1
+ export default "# Retention in Howler\n\nIn order to comply with organizational policies, Howler is configured to purge stale alerts after a specific amount of\ntime. On this instance, that duration is `duration`.\n\n## How Retention Works\n\nHowler uses an automated retention job that runs on a configurable schedule (typically nightly) to remove\nalerts that have exceeded their retention period. The system evaluates two criteria for deletion:\n\n1. **Standard Retention**: Alerts are deleted when `event.created` exceeds the configured retention period\n2. **Custom Expiry**: Alerts are deleted when the `howler.expiry` field indicates the alert should expire\n\nAn alert will be removed when **either** condition is met - whichever comes first.\n\n## Custom Expiry (`howler.expiry`)\n\nThe `howler.expiry` field allows detection engineers to set custom retention periods for specific alerts\nduring ingestion. This field overrides the standard retention calculation and is commonly used when:\n\n- Clients have requested shorter data retention periods than the deployment default\n- Specific operations require time-limited data storage (e.g., a cybersecurity operation where data can\n only be retained for two weeks after ingest)\n- Regulatory requirements mandate earlier deletion for certain types of data\n\n```alert\nThe howler.expiry field can only shorten retention periods, not extend them. No matter\nwhat, alerts cannot be retained longer than the system-wide retention cutoff based on event.created.\n```\n\n## Configuration\n\nAdministrators can configure retention settings in the system configuration:\n\n```yaml\nsystem:\n type: staging\n retention:\n limit_amount: 120 # Retention period duration\n limit_unit: days # Time unit (days, hours, etc.)\n crontab: '0 0 * * *' # Schedule (nightly at midnight)\n enabled: true # Whether retention is active\n```\n\n## User Interface\n\nTo communicate retention timing to users, see the example alert below:\n\n`alert`\n\nIn the top right, hovering over the timestamp will outline how long users have before the alert is\nremoved. In order to ensure compliance with policy, ensure that `event.created` matches the date the\nunderlying data was collected, allowing Howler to ensure data is purged in time.\n"
@@ -1 +1 @@
1
- export default "# Documentation du client Howler\n\nCette documentation explique comment interagir avec l'API howler en utilisant le client howler dans les environnements de d\u00e9veloppement Java et Python. Nous d\u00e9crirons le processus de base de la cr\u00e9ation d'un nouveau hit dans chaque environnement ainsi que la recherche dans howler des hits correspondant \u00e0 votre requ\u00eate.\n\n## Mise en route\n\n### Installation\n\nAfin d'utiliser le client howler, vous devez le lister comme une d\u00e9pendance dans votre projet.\n\n#### **Python**\n\nIl suffit de l'installer \u00e0 l'aide de pip:\n\n```bash\npip install howler-client\n```\n\nVous pouvez \u00e9galement l'ajouter \u00e0 votre requirements.txt, ou \u00e0 tout autre syst\u00e8me de gestion des d\u00e9pendances que vous utilisez.\n\n### Authentification\n\nComme indiqu\u00e9 dans la [Documentation sur l'authentification](/help/auth), les utilisateurs peuvent s'authentifier de diff\u00e9rentes mani\u00e8res. Pour interfacer avec le client howler, cependant, le flux sugg\u00e9r\u00e9 est d'utiliser une cl\u00e9 API. Avant de commencer, g\u00e9n\u00e9rons une cl\u00e9.\n\n1. Ouvrez l'interface utilisateur Howler avec laquelle vous souhaitez vous interfacer.\n2. Connectez-vous, puis cliquez sur votre profil en haut \u00e0 droite.\n3. Dans le menu utilisateur, cliquez sur Param\u00e8tres.\n4. Sous S\u00e9curit\u00e9 de l'utilisateur, appuyez sur l'ic\u00f4ne (+) sur la ligne Cl\u00e9s API.\n5. Nommez votre cl\u00e9 et donnez-lui les autorisations n\u00e9cessaires.\n6. Appuyez sur Create (Cr\u00e9er) et copiez la cha\u00eene fournie dans un endroit s\u00fbr. \\*\\*Vous ne reverrez plus cette cha\u00eene.\n\nCette cl\u00e9 API sera fournie \u00e0 votre code par la suite.\n\n## Client Python\n\nPour se connecter \u00e0 howler en utilisant le client python, il y a un processus assez simple \u00e0 suivre:\n\n```python\nfrom howler_client import get_client\n\nUSERNAME = 'user' # Obtenez-le \u00e0 partir de la page des param\u00e8tres de l'utilisateur de l'interface utilisateur de Howler.\nAPIKEY = 'apikey_name:apikey_data'\n\napikey = (USERNAME, APIKEY)\n\nhowler = get_client(\"$CURRENT_URL\", apikey=apikey)\n```\n\nVoil\u00e0, c'est fait ! Vous pouvez maintenant utiliser l'objet `howler` pour interagir avec le serveur. A quoi cela ressemble-t-il en r\u00e9alit\u00e9?\n\n### Cr\u00e9er des hits en Python\n\nPour le client python, vous pouvez cr\u00e9er des hits en utilisant les fonctions `howler.hit.create` ou `howler.hit.create_from_map`.\n\n#### `create`\n\nCette fonction prend un seul argument - soit un hit unique, soit une liste de hits, conforme au [Howler Schema] (/help/hit?tab=schema). Voici un exemple simple :\n\n```python\n# Quelques donn\u00e9es bidons au format Howler Schema\nexemple_hit = {\n \"howler\" : {\n \"analytic\" : \"exemple\",\n \"score\" : 10.0\n },\n \"event\" : {\n \"reason\" : \"Exemple hit\"\n }\n}\n\nhowler.hit.create(example_hit)\n```\n\nVous pouvez \u00e9galement ing\u00e9rer des donn\u00e9es dans un format plat :\n\n```python\nexample_hit = {\n \"howler.analytic\": \"example\",\n \"howler.score\": 10.0,\n \"event.reason\": \"Example hit\"\n}\n\nhowler.hit.create(example_hit)\n```\n\n#### `create_from_map`\n\nThis function takes in three arguments:\n\n- `tool name`: Le nom de l'outil d'analyse qui cr\u00e9e le hit\n- `map`: Une correspondance entre les donn\u00e9es brutes que vous avez et le sch\u00e9ma howler\n - Le format est un dictionnaire o\u00f9 les cl\u00e9s sont le chemin aplati des donn\u00e9es brutes, et les valeurs sont une liste de chemins aplatis pour les champs de Howler dans lesquels les donn\u00e9es seront copi\u00e9es.\n- `documents`: Les donn\u00e9es brutes que vous voulez ajouter \u00e0 Howler.\n\nVoici un exemple simple:\n\n```python\n# La correspondance entre nos donn\u00e9es et le sch\u00e9ma de Howler\nhwl_map = {\n \"file.sha256\": [\"file.hash.sha256\", \"howler.hash\"],\n \"file.name\": [\"file.name\"],\n \"src_ip\": [\"source.ip\", \"related.ip\"],\n \"dest_ip\": [\"destination.ip\", \"related.ip\"],\n \"time.created\": [\"event.start\"],\n}\n\n# Quelques fausses donn\u00e9es dans un format personnalis\u00e9 que nous voulons ajouter \u00e0 howler\nexample_hit = {\n \"src_ip\": \"0.0.0.0\",\n \"dest_ip\": \"8.8.8.8\",\n \"file\": {\n \"name\": \"hello.exe\",\n \"sha256\": sha256(str(\"hello.exe\").encode()).hexdigest()\n },\n \"time\": {\n \"created\": datetime.now().isoformat()\n },\n}\n\n# Notez que le troisi\u00e8me argument est de type liste!\nhowler.hit.create_from_map(\"example_ingestor\", hwl_map, [example_hit])\n```\n\n### Interroger les hits en Python\n\nL'interrogation des hits avec le client python howler se fait en utilisant la fonction `howler.search.hit`. Elle poss\u00e8de un certain nombre d'arguments obligatoires et optionnels:\n\n- Obligatoire:\n - `query`: requ\u00eate lucene (cha\u00eene)\n- Facultatif: `filters`: requ\u00eate lucene (cha\u00eene):\n - `filters`: Requ\u00eates lucene additionnelles utilis\u00e9es pour filtrer les donn\u00e9es (liste de cha\u00eenes)\n - `fl`: Liste des champs \u00e0 retourner (cha\u00eene de champs s\u00e9par\u00e9s par des virgules)\n - `offset`: Offset auquel les \u00e9l\u00e9ments de la requ\u00eate doivent commencer (entier)\n - `rows`: Nombre d'enregistrements \u00e0 retourner (entier)\n - `sort`: Champ utilis\u00e9 pour le tri avec direction (cha\u00eene: ex. 'id desc')\n - `timeout`: Nombre maximum de millisecondes d'ex\u00e9cution de la requ\u00eate (entier)\n - `use_archive`: Interroge \u00e9galement l'archive\n - `track_total_hits`: Nombre de hits \u00e0 suivre (par d\u00e9faut: 10k)\n\nVoici quelques exemples de requ\u00eates:\n\n```python\n# Rechercher tous les hits cr\u00e9\u00e9s par assemblyline, afficher les 50 premiers et ne renvoyer que leurs identifiants.\nhowler.search.hit(\"howler.analytic:assemblyline\", fl=\"howler.id\", rows=50)\n\n# Recherche de toutes les occurrences r\u00e9solues cr\u00e9\u00e9es au cours des cinq derniers jours, avec indication de leur identifiant et de l'analyste qui les a cr\u00e9\u00e9es. N'en afficher que dix, d\u00e9cal\u00e9s de 40\nhowler.search.hit(\"howler.status:resolved\", filters=['event.created:[now-5d TO now]'] fl=\"howler.id,howler.analytic\", rows=10, offset=40)\n\n# Recherche de tous les r\u00e9sultats, d\u00e9lai d'attente si la requ\u00eate prend plus de 100 ms\nhowler.search.hit(\"howler.id:*\", track_total_hits=100000000, timeout=100, use_archive=True)\n```\n\n### Mise \u00e0 jour des r\u00e9sultats\n\nAfin de mettre \u00e0 jour les hits, il existe un certain nombre de fonctions support\u00e9es :\n\n- `howler.hit.update(...)`\n- `howler.hit.update_by_query(...)`\n- `howler.hit.overwrite(...)`\n\n#### `update()`\n\nSi vous souhaitez mettre \u00e0 jour un hit de mani\u00e8re transactionnelle, vous pouvez utiliser le code suivant:\n\n```python\nhit_to_update = client.search.hit(\"howler.id:*\", rows=1, sort=\"event.created desc\")[\"items\"][0]\n\nresult = client.hit.update(hit_to_update[\"howler\"][\"id\"], [(UPDATE_SET, \"howler.score\", hit_to_update[\"howler\"][\"score\"] + 100)])\n```\n\nLes op\u00e9rations suivantes peuvent \u00eatre ex\u00e9cut\u00e9es pour mettre \u00e0 jour un r\u00e9sultat.\n\n**Op\u00e9rations de liste:**\n\n- `UPDATE_APPEND` : Utilis\u00e9 pour ajouter une valeur \u00e0 une liste donn\u00e9e\n- `UPDATE_APPEND_IF_MISSING` : Utilis\u00e9 pour ajouter une valeur \u00e0 une liste donn\u00e9e si la valeur n'est pas d\u00e9j\u00e0 dans la liste.\n- `UPDATE_REMOVE` : Supprime une valeur donn\u00e9e d'une liste\n\n**Op\u00e9rations num\u00e9riques:**\n\n- `UPDATE_DEC` : Diminue une valeur num\u00e9rique de la quantit\u00e9 sp\u00e9cifi\u00e9e.\n- `UPDATE_INC` : Incr\u00e9mente une valeur num\u00e9rique de la quantit\u00e9 sp\u00e9cifi\u00e9e.\n- `UPDATE_MAX` : Fixe une valeur num\u00e9rique au maximum de la valeur existante et de la valeur sp\u00e9cifi\u00e9e.\n- `UPDATE_MIN` : Fixe une valeur num\u00e9rique au minimum de la valeur existante et de la valeur sp\u00e9cifi\u00e9e.\n\n**Op\u00e9rations polyvalentes:**\n\n- `UPDATE_SET` : Fixe la valeur d'un champ \u00e0 la valeur donn\u00e9e.\n- `UPDATE_DELETE` : Supprime la valeur d'un champ donn\u00e9\n\n#### `update_by_query()`\n\nCette fonction vous permet de mettre \u00e0 jour un grand nombre d'occurrences \u00e0 l'aide d'une requ\u00eate :\n\n```python\nclient.hit.update_by_query(f'howler.analytic : \"Exemple d\\'analytic\"', [(UPDATE_INC, \"howler.score\", 100)])\n```\n\nLes m\u00eames op\u00e9rations que dans `update()` peuvent \u00eatre utilis\u00e9es.\n\n### `overwrite()`\n\nCette fonction vous permet d'\u00e9craser directement un hit avec un objet hit partiel. C'est la plus facile \u00e0 utiliser, mais elle perd une partie de la validation et du traitement suppl\u00e9mentaire des fonctions de mise \u00e0 jour.\n\n```python\nhit_to_update = client.search.hit(\"howler.id:*\", rows=1, sort=\"event.created desc\")[\"items\"][0]\n\nresult = client.hit.overwrite(hit_to_update[\"howler\"][\"id\"], {\"source.ip\" : \"127.0.0.1\", \u201cdestination.ip\u201d : \"8.8.8.8\"})\n```\n"
1
+ export default "# Documentation du client Howler\n\nCette documentation explique comment interagir avec l'API howler en utilisant le client howler dans les environnements de d\u00e9veloppement Java et Python. Nous d\u00e9crirons le processus de base de la cr\u00e9ation d'un nouveau hit dans chaque environnement ainsi que la recherche dans howler des hits correspondant \u00e0 votre requ\u00eate.\n\n## Mise en route\n\n### Installation\n\nAfin d'utiliser le client howler, vous devez le lister comme une d\u00e9pendance dans votre projet.\n\n#### **Python**\n\nIl suffit de l'installer \u00e0 l'aide de pip:\n\n```bash\npip install howler-client\n```\n\nVous pouvez \u00e9galement l'ajouter \u00e0 votre requirements.txt, ou \u00e0 tout autre syst\u00e8me de gestion des d\u00e9pendances que vous utilisez.\n\n### Authentification\n\nComme indiqu\u00e9 dans la [Documentation sur l'authentification](/help/auth), les utilisateurs peuvent s'authentifier de diff\u00e9rentes mani\u00e8res. Pour interfacer avec le client howler, cependant, le flux sugg\u00e9r\u00e9 est d'utiliser une cl\u00e9 API. Avant de commencer, g\u00e9n\u00e9rons une cl\u00e9.\n\n1. Ouvrez l'interface utilisateur Howler avec laquelle vous souhaitez vous interfacer.\n2. Connectez-vous, puis cliquez sur votre profil en haut \u00e0 droite.\n3. Dans le menu utilisateur, cliquez sur Param\u00e8tres.\n4. Sous S\u00e9curit\u00e9 de l'utilisateur, appuyez sur l'ic\u00f4ne (+) sur la ligne Cl\u00e9s API.\n5. Nommez votre cl\u00e9 et donnez-lui les autorisations n\u00e9cessaires.\n6. Appuyez sur Create (Cr\u00e9er) et copiez la cha\u00eene fournie dans un endroit s\u00fbr. \\*\\*Vous ne reverrez plus cette cha\u00eene.\n\nCette cl\u00e9 API sera fournie \u00e0 votre code par la suite.\n\n## Client Python\n\nPour se connecter \u00e0 howler en utilisant le client python, il y a un processus assez simple \u00e0 suivre:\n\n```python\nfrom howler_client import get_client\n\nUSERNAME = 'user' # Obtenez-le \u00e0 partir de la page des param\u00e8tres de l'utilisateur de l'interface utilisateur de Howler.\nAPIKEY = 'apikey_name:apikey_data'\n\napikey = (USERNAME, APIKEY)\n\nhowler = get_client(\"$CURRENT_URL\", apikey=apikey)\n```\n\nVoil\u00e0, c'est fait ! Vous pouvez maintenant utiliser l'objet `howler` pour interagir avec le serveur. A quoi cela ressemble-t-il en r\u00e9alit\u00e9?\n\n### Cr\u00e9er des hits en Python\n\nPour le client python, vous pouvez cr\u00e9er des hits en utilisant les fonctions `howler.hit.create` ou `howler.hit.create_from_map`.\n\n#### `create`\n\nCette fonction prend un seul argument - soit un hit unique, soit une liste de hits, conforme au [Howler Schema] (/help/hit?tab=schema). Voici un exemple simple :\n\n```python\n# Quelques donn\u00e9es bidons au format Howler Schema\nexemple_hit = {\n \"howler\" : {\n \"analytic\" : \"exemple\",\n \"score\" : 10.0\n },\n \"event\" : {\n \"reason\" : \"Exemple hit\"\n }\n}\n\nhowler.hit.create(example_hit)\n```\n\nVous pouvez \u00e9galement ing\u00e9rer des donn\u00e9es dans un format plat :\n\n```python\nexample_hit = {\n \"howler.analytic\": \"example\",\n \"howler.score\": 10.0,\n \"event.reason\": \"Example hit\"\n}\n\nhowler.hit.create(example_hit)\n```\n\n#### `create_from_map`\n\nThis function takes in three arguments:\n\n- `tool name`: Le nom de l'outil d'analyse qui cr\u00e9e le hit\n- `map`: Une correspondance entre les donn\u00e9es brutes que vous avez et le sch\u00e9ma howler\n - Le format est un dictionnaire o\u00f9 les cl\u00e9s sont le chemin aplati des donn\u00e9es brutes, et les valeurs sont une liste de chemins aplatis pour les champs de Howler dans lesquels les donn\u00e9es seront copi\u00e9es.\n- `documents`: Les donn\u00e9es brutes que vous voulez ajouter \u00e0 Howler.\n\nVoici un exemple simple:\n\n```python\n# La correspondance entre nos donn\u00e9es et le sch\u00e9ma de Howler\nhwl_map = {\n \"file.sha256\": [\"file.hash.sha256\", \"howler.hash\"],\n \"file.name\": [\"file.name\"],\n \"src_ip\": [\"source.ip\", \"related.ip\"],\n \"dest_ip\": [\"destination.ip\", \"related.ip\"],\n \"time.created\": [\"event.start\"],\n}\n\n# Quelques fausses donn\u00e9es dans un format personnalis\u00e9 que nous voulons ajouter \u00e0 howler\nexample_hit = {\n \"src_ip\": \"0.0.0.0\",\n \"dest_ip\": \"8.8.8.8\",\n \"file\": {\n \"name\": \"hello.exe\",\n \"sha256\": sha256(str(\"hello.exe\").encode()).hexdigest()\n },\n \"time\": {\n \"created\": datetime.now().isoformat()\n },\n}\n\n# Notez que le troisi\u00e8me argument est de type liste!\nhowler.hit.create_from_map(\"example_ingestor\", hwl_map, [example_hit])\n```\n\n### Interroger les hits en Python\n\nL'interrogation des hits avec le client python howler se fait en utilisant la fonction `howler.search.hit`. Elle poss\u00e8de un certain nombre d'arguments obligatoires et optionnels:\n\n- Obligatoire:\n - `query`: requ\u00eate lucene (cha\u00eene)\n- Facultatif: `filters`: requ\u00eate lucene (cha\u00eene):\n - `filters`: Requ\u00eates lucene additionnelles utilis\u00e9es pour filtrer les donn\u00e9es (liste de cha\u00eenes)\n - `fl`: Liste des champs \u00e0 retourner (cha\u00eene de champs s\u00e9par\u00e9s par des virgules)\n - `offset`: Offset auquel les \u00e9l\u00e9ments de la requ\u00eate doivent commencer (entier)\n - `rows`: Nombre d'enregistrements \u00e0 retourner (entier)\n - `sort`: Champ utilis\u00e9 pour le tri avec direction (cha\u00eene: ex. 'id desc')\n - `timeout`: Nombre maximum de millisecondes d'ex\u00e9cution de la requ\u00eate (entier)\n - `track_total_hits`: Nombre de hits \u00e0 suivre (par d\u00e9faut: 10k)\n\nVoici quelques exemples de requ\u00eates:\n\n```python\n# Rechercher tous les hits cr\u00e9\u00e9s par assemblyline, afficher les 50 premiers et ne renvoyer que leurs identifiants.\nhowler.search.hit(\"howler.analytic:assemblyline\", fl=\"howler.id\", rows=50)\n\n# Recherche de toutes les occurrences r\u00e9solues cr\u00e9\u00e9es au cours des cinq derniers jours, avec indication de leur identifiant et de l'analyste qui les a cr\u00e9\u00e9es. N'en afficher que dix, d\u00e9cal\u00e9s de 40\nhowler.search.hit(\"howler.status:resolved\", filters=['event.created:[now-5d TO now]'] fl=\"howler.id,howler.analytic\", rows=10, offset=40)\n\n# Recherche de tous les r\u00e9sultats, d\u00e9lai d'attente si la requ\u00eate prend plus de 100 ms\nhowler.search.hit(\"howler.id:*\", track_total_hits=100000000, timeout=100)\n```\n\n### Mise \u00e0 jour des r\u00e9sultats\n\nAfin de mettre \u00e0 jour les hits, il existe un certain nombre de fonctions support\u00e9es :\n\n- `howler.hit.update(...)`\n- `howler.hit.update_by_query(...)`\n- `howler.hit.overwrite(...)`\n\n#### `update()`\n\nSi vous souhaitez mettre \u00e0 jour un hit de mani\u00e8re transactionnelle, vous pouvez utiliser le code suivant:\n\n```python\nhit_to_update = client.search.hit(\"howler.id:*\", rows=1, sort=\"event.created desc\")[\"items\"][0]\n\nresult = client.hit.update(hit_to_update[\"howler\"][\"id\"], [(UPDATE_SET, \"howler.score\", hit_to_update[\"howler\"][\"score\"] + 100)])\n```\n\nLes op\u00e9rations suivantes peuvent \u00eatre ex\u00e9cut\u00e9es pour mettre \u00e0 jour un r\u00e9sultat.\n\n**Op\u00e9rations de liste:**\n\n- `UPDATE_APPEND` : Utilis\u00e9 pour ajouter une valeur \u00e0 une liste donn\u00e9e\n- `UPDATE_APPEND_IF_MISSING` : Utilis\u00e9 pour ajouter une valeur \u00e0 une liste donn\u00e9e si la valeur n'est pas d\u00e9j\u00e0 dans la liste.\n- `UPDATE_REMOVE` : Supprime une valeur donn\u00e9e d'une liste\n\n**Op\u00e9rations num\u00e9riques:**\n\n- `UPDATE_DEC` : Diminue une valeur num\u00e9rique de la quantit\u00e9 sp\u00e9cifi\u00e9e.\n- `UPDATE_INC` : Incr\u00e9mente une valeur num\u00e9rique de la quantit\u00e9 sp\u00e9cifi\u00e9e.\n- `UPDATE_MAX` : Fixe une valeur num\u00e9rique au maximum de la valeur existante et de la valeur sp\u00e9cifi\u00e9e.\n- `UPDATE_MIN` : Fixe une valeur num\u00e9rique au minimum de la valeur existante et de la valeur sp\u00e9cifi\u00e9e.\n\n**Op\u00e9rations polyvalentes:**\n\n- `UPDATE_SET` : Fixe la valeur d'un champ \u00e0 la valeur donn\u00e9e.\n- `UPDATE_DELETE` : Supprime la valeur d'un champ donn\u00e9\n\n#### `update_by_query()`\n\nCette fonction vous permet de mettre \u00e0 jour un grand nombre d'occurrences \u00e0 l'aide d'une requ\u00eate :\n\n```python\nclient.hit.update_by_query(f'howler.analytic : \"Exemple d\\'analytic\"', [(UPDATE_INC, \"howler.score\", 100)])\n```\n\nLes m\u00eames op\u00e9rations que dans `update()` peuvent \u00eatre utilis\u00e9es.\n\n### `overwrite()`\n\nCette fonction vous permet d'\u00e9craser directement un hit avec un objet hit partiel. C'est la plus facile \u00e0 utiliser, mais elle perd une partie de la validation et du traitement suppl\u00e9mentaire des fonctions de mise \u00e0 jour.\n\n```python\nhit_to_update = client.search.hit(\"howler.id:*\", rows=1, sort=\"event.created desc\")[\"items\"][0]\n\nresult = client.hit.overwrite(hit_to_update[\"howler\"][\"id\"], {\"source.ip\" : \"127.0.0.1\", \u201cdestination.ip\u201d : \"8.8.8.8\"})\n```\n"
@@ -1 +1 @@
1
- export default "# R\u00e9tention dans Howler\n\nAfin de se conformer aux politiques organisationnelles, Howler est configur\u00e9 pour purger les alertes\np\u00e9rim\u00e9es apr\u00e8s une p\u00e9riode de temps sp\u00e9cifique. Dans cette instance, cette dur\u00e9e est `duration`.\n\n## Comment fonctionne la r\u00e9tention\n\nHowler utilise un travail de r\u00e9tention automatis\u00e9 qui s'ex\u00e9cute selon un calendrier configurable\n(g\u00e9n\u00e9ralement nocturne) pour supprimer les alertes qui ont d\u00e9pass\u00e9 leur p\u00e9riode de r\u00e9tention. Le syst\u00e8me\n\u00e9value deux crit\u00e8res de suppression :\n\n1. **R\u00e9tention standard** : Les alertes sont supprim\u00e9es lorsque `event.created` d\u00e9passe la p\u00e9riode de\n r\u00e9tention configur\u00e9e\n2. **Expiration personnalis\u00e9e** : Les alertes sont supprim\u00e9es lorsque le champ `howler.expiry` indique\n que l'alerte doit expirer\n\nUne alerte sera supprim\u00e9e lorsque **l'une ou l'autre** condition est remplie - selon celle qui arrive en\npremier.\n\n## Expiration personnalis\u00e9e (`howler.expiry`)\n\nLe champ `howler.expiry` permet aux ing\u00e9nieurs de d\u00e9tection de d\u00e9finir des p\u00e9riodes de r\u00e9tention\npersonnalis\u00e9es pour des alertes sp\u00e9cifiques lors de l'ingestion. Ce champ remplace le calcul de\nr\u00e9tention standard et est couramment utilis\u00e9 quand :\n\n- Les clients ont demand\u00e9 des p\u00e9riodes de r\u00e9tention de donn\u00e9es plus courtes que la valeur par d\u00e9faut\n du d\u00e9ploiement\n- Des op\u00e9rations sp\u00e9cifiques n\u00e9cessitent un stockage de donn\u00e9es \u00e0 dur\u00e9e limit\u00e9e (par ex., une op\u00e9ration\n de cybers\u00e9curit\u00e9 o\u00f9 les donn\u00e9es ne peuvent \u00eatre conserv\u00e9es que deux semaines apr\u00e8s ingestion)\n- Les exigences r\u00e9glementaires imposent une suppression plus pr\u00e9coce pour certains types de donn\u00e9es\n\n```alert\nLe champ howler.expiry ne peut que raccourcir les p\u00e9riodes de r\u00e9tention, pas les\nprolonger. Quoi qu'il arrive, les alertes ne peuvent pas \u00eatre conserv\u00e9es plus longtemps que la limite de\nr\u00e9tention syst\u00e8me bas\u00e9e sur event.created.\n```\n\n## Configuration\n\nLes administrateurs peuvent configurer les param\u00e8tres de r\u00e9tention dans la configuration syst\u00e8me :\n\n```yaml\nsystem:\n type: staging\n retention:\n limit_amount: 120 # Dur\u00e9e de la p\u00e9riode de r\u00e9tention\n limit_unit: days # Unit\u00e9 de temps (days, hours, etc.)\n crontab: \"0 0 * * *\" # Calendrier (nocturne \u00e0 minuit)\n enabled: true # Si la r\u00e9tention est active\n```\n\n## Interface utilisateur\n\nAfin de communiquer le d\u00e9lai de r\u00e9tention aux utilisateurs, voir l'exemple d'alerte ci-dessous :\n\n`alert`\n\nEn haut \u00e0 droite, le survol de l'horodatage indique le temps dont dispose l'utilisateur avant que\nl'alerte ne soit supprim\u00e9e. Afin de se conformer aux politiques, assurez-vous que `event.created`\ncorrespond \u00e0 la date \u00e0 laquelle les donn\u00e9es sous-jacentes ont \u00e9t\u00e9 collect\u00e9es, permettant \u00e0 Howler de\ns'assurer que les donn\u00e9es sont purg\u00e9es \u00e0 temps.\n"
1
+ export default "# R\u00e9tention dans Howler\n\nAfin de se conformer aux politiques organisationnelles, Howler est configur\u00e9 pour purger les alertes\np\u00e9rim\u00e9es apr\u00e8s une p\u00e9riode de temps sp\u00e9cifique. Dans cette instance, cette dur\u00e9e est `duration`.\n\n## Comment fonctionne la r\u00e9tention\n\nHowler utilise un travail de r\u00e9tention automatis\u00e9 qui s'ex\u00e9cute selon un calendrier configurable\n(g\u00e9n\u00e9ralement nocturne) pour supprimer les alertes qui ont d\u00e9pass\u00e9 leur p\u00e9riode de r\u00e9tention. Le syst\u00e8me\n\u00e9value deux crit\u00e8res de suppression :\n\n1. **R\u00e9tention standard** : Les alertes sont supprim\u00e9es lorsque `event.created` d\u00e9passe la p\u00e9riode de\n r\u00e9tention configur\u00e9e\n2. **Expiration personnalis\u00e9e** : Les alertes sont supprim\u00e9es lorsque le champ `howler.expiry` indique\n que l'alerte doit expirer\n\nUne alerte sera supprim\u00e9e lorsque **l'une ou l'autre** condition est remplie - selon celle qui arrive en\npremier.\n\n## Expiration personnalis\u00e9e (`howler.expiry`)\n\nLe champ `howler.expiry` permet aux ing\u00e9nieurs de d\u00e9tection de d\u00e9finir des p\u00e9riodes de r\u00e9tention\npersonnalis\u00e9es pour des alertes sp\u00e9cifiques lors de l'ingestion. Ce champ remplace le calcul de\nr\u00e9tention standard et est couramment utilis\u00e9 quand :\n\n- Les clients ont demand\u00e9 des p\u00e9riodes de r\u00e9tention de donn\u00e9es plus courtes que la valeur par d\u00e9faut\n du d\u00e9ploiement\n- Des op\u00e9rations sp\u00e9cifiques n\u00e9cessitent un stockage de donn\u00e9es \u00e0 dur\u00e9e limit\u00e9e (par ex., une op\u00e9ration\n de cybers\u00e9curit\u00e9 o\u00f9 les donn\u00e9es ne peuvent \u00eatre conserv\u00e9es que deux semaines apr\u00e8s ingestion)\n- Les exigences r\u00e9glementaires imposent une suppression plus pr\u00e9coce pour certains types de donn\u00e9es\n\n```alert\nLe champ howler.expiry ne peut que raccourcir les p\u00e9riodes de r\u00e9tention, pas les\nprolonger. Quoi qu'il arrive, les alertes ne peuvent pas \u00eatre conserv\u00e9es plus longtemps que la limite de\nr\u00e9tention syst\u00e8me bas\u00e9e sur event.created.\n```\n\n## Configuration\n\nLes administrateurs peuvent configurer les param\u00e8tres de r\u00e9tention dans la configuration syst\u00e8me :\n\n```yaml\nsystem:\n type: staging\n retention:\n limit_amount: 120 # Dur\u00e9e de la p\u00e9riode de r\u00e9tention\n limit_unit: days # Unit\u00e9 de temps (days, hours, etc.)\n crontab: '0 0 * * *' # Calendrier (nocturne \u00e0 minuit)\n enabled: true # Si la r\u00e9tention est active\n```\n\n## Interface utilisateur\n\nAfin de communiquer le d\u00e9lai de r\u00e9tention aux utilisateurs, voir l'exemple d'alerte ci-dessous :\n\n`alert`\n\nEn haut \u00e0 droite, le survol de l'horodatage indique le temps dont dispose l'utilisateur avant que\nl'alerte ne soit supprim\u00e9e. Afin de se conformer aux politiques, assurez-vous que `event.created`\ncorrespond \u00e0 la date \u00e0 laquelle les donn\u00e9es sous-jacentes ont \u00e9t\u00e9 collect\u00e9es, permettant \u00e0 Howler de\ns'assurer que les donn\u00e9es sont purg\u00e9es \u00e0 temps.\n"
@@ -1,5 +1,6 @@
1
1
  import type { FC } from 'react';
2
2
  declare const InformationPane: FC<{
3
+ selected?: string;
3
4
  onClose?: () => void;
4
5
  }>;
5
6
  export default InformationPane;
@@ -4,29 +4,28 @@ import { Badge, Box, Divider, IconButton, Skeleton, Stack, Tab, Tabs, Tooltip, u
4
4
  import TuiIconButton from '@cccsaurora/howler-ui/components/elements/addons/buttons/CustomIconButton';
5
5
  import { Icon } from '@iconify/react';
6
6
  import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
7
- import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
8
7
  import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
8
+ import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
9
9
  import { SocketContext } from '@cccsaurora/howler-ui/components/app/providers/SocketProvider';
10
10
  import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
11
11
  import VSBox from '@cccsaurora/howler-ui/components/elements/addons/layout/vsbox/VSBox';
12
12
  import VSBoxContent from '@cccsaurora/howler-ui/components/elements/addons/layout/vsbox/VSBoxContent';
13
13
  import VSBoxHeader from '@cccsaurora/howler-ui/components/elements/addons/layout/vsbox/VSBoxHeader';
14
14
  import Phrase from '@cccsaurora/howler-ui/components/elements/addons/search/phrase/Phrase';
15
- import BundleButton from '@cccsaurora/howler-ui/components/elements/display/icons/BundleButton';
16
15
  import SocketBadge from '@cccsaurora/howler-ui/components/elements/display/icons/SocketBadge';
17
16
  import JSONViewer from '@cccsaurora/howler-ui/components/elements/display/json/JSONViewer';
18
17
  import HitActions from '@cccsaurora/howler-ui/components/elements/hit/HitActions';
19
18
  import HitBanner from '@cccsaurora/howler-ui/components/elements/hit/HitBanner';
20
- import HitComments from '@cccsaurora/howler-ui/components/elements/hit/HitComments';
21
- import HitDetails from '@cccsaurora/howler-ui/components/elements/hit/HitDetails';
22
19
  import HitLabels from '@cccsaurora/howler-ui/components/elements/hit/HitLabels';
23
20
  import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
24
21
  import HitLinks from '@cccsaurora/howler-ui/components/elements/hit/HitLinks';
25
22
  import HitOutline from '@cccsaurora/howler-ui/components/elements/hit/HitOutline';
26
23
  import HitOverview from '@cccsaurora/howler-ui/components/elements/hit/HitOverview';
27
- import HitRelated from '@cccsaurora/howler-ui/components/elements/hit/HitRelated';
28
24
  import HitSummary from '@cccsaurora/howler-ui/components/elements/hit/HitSummary';
29
- import HitWorklog from '@cccsaurora/howler-ui/components/elements/hit/HitWorklog';
25
+ import ObjectDetails from '@cccsaurora/howler-ui/components/elements/ObjectDetails';
26
+ import RecordComments from '@cccsaurora/howler-ui/components/elements/record/RecordComments';
27
+ import RecordRelated from '@cccsaurora/howler-ui/components/elements/record/RecordRelated';
28
+ import RecordWorklog from '@cccsaurora/howler-ui/components/elements/record/RecordWorklog';
30
29
  import useMyUserList from '@cccsaurora/howler-ui/components/hooks/useMyUserList';
31
30
  import ErrorBoundary from '@cccsaurora/howler-ui/components/routes/ErrorBoundary';
32
31
  import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
@@ -37,17 +36,18 @@ import { useLocation } from 'react-router-dom';
37
36
  import { useContextSelector } from 'use-context-selector';
38
37
  import { getUserList } from '@cccsaurora/howler-ui/utils/hitFunctions';
39
38
  import { validateRegex } from '@cccsaurora/howler-ui/utils/stringUtils';
39
+ import { isHit } from '@cccsaurora/howler-ui/utils/typeUtils';
40
40
  import { tryParse } from '@cccsaurora/howler-ui/utils/utils';
41
41
  import LeadRenderer from '../view/LeadRenderer';
42
- const InformationPane = ({ onClose }) => {
42
+ const InformationPane = ({ onClose, selected: _selected }) => {
43
43
  const { t, i18n } = useTranslation();
44
44
  const theme = useTheme();
45
45
  const location = useLocation();
46
- const { emit, isOpen } = useContext(SocketContext);
46
+ const { emit, open } = useContext(SocketContext);
47
47
  const { getMatchingOverview, getMatchingDossiers, getMatchingAnalytic } = useMatchers();
48
- const selected = useContextSelector(ParameterContext, ctx => ctx.selected);
48
+ const selected = useContextSelector(ParameterContext, ctx => ctx?.selected) ?? _selected;
49
49
  const pluginStore = usePluginStore();
50
- const getHit = useContextSelector(HitContext, ctx => ctx.getHit);
50
+ const getRecord = useContextSelector(RecordContext, ctx => ctx.getRecord);
51
51
  const [userIds, setUserIds] = useState(new Set());
52
52
  const [analytic, setAnalytic] = useState();
53
53
  const [hasOverview, setHasOverview] = useState(false);
@@ -58,7 +58,7 @@ const InformationPane = ({ onClose }) => {
58
58
  const [_dossiers, setDossiers] = useState(null);
59
59
  const dossiers = useMemo(() => _dossiers ?? [], [_dossiers]);
60
60
  const users = useMyUserList(userIds);
61
- const hit = useContextSelector(HitContext, ctx => ctx.hits[selected]);
61
+ const record = useContextSelector(RecordContext, ctx => ctx.records[selected]);
62
62
  howlerPluginStore.plugins.forEach(plugin => {
63
63
  pluginStore.executeFunction(`${plugin}.on`, 'viewing');
64
64
  });
@@ -66,14 +66,14 @@ const InformationPane = ({ onClose }) => {
66
66
  if (!selected) {
67
67
  return;
68
68
  }
69
- if (!hit?.howler.data) {
69
+ if (!record?.howler.data) {
70
70
  setLoading(true);
71
- getHit(selected, true).finally(() => setLoading(false));
71
+ getRecord(selected, true).finally(() => setLoading(false));
72
72
  return;
73
73
  }
74
- setUserIds(getUserList(hit));
74
+ setUserIds(getUserList(record));
75
75
  // eslint-disable-next-line react-hooks/exhaustive-deps
76
- }, [getHit, selected]);
76
+ }, [getRecord, selected]);
77
77
  useEffect(() => {
78
78
  if (selected) {
79
79
  setAnalytic(null);
@@ -82,25 +82,22 @@ const InformationPane = ({ onClose }) => {
82
82
  }
83
83
  }, [selected]);
84
84
  useEffect(() => {
85
- if (hit && !analytic) {
86
- getMatchingAnalytic(hit).then(setAnalytic);
85
+ if (isHit(record) && !analytic) {
86
+ getMatchingAnalytic(record).then(setAnalytic);
87
87
  }
88
- }, [analytic, getMatchingAnalytic, hit]);
88
+ }, [analytic, getMatchingAnalytic, record]);
89
89
  useEffect(() => {
90
- if (hit && !_dossiers) {
91
- getMatchingDossiers(hit).then(setDossiers);
90
+ if (isHit(record) && !_dossiers) {
91
+ getMatchingDossiers(record).then(setDossiers);
92
92
  }
93
- }, [_dossiers, getMatchingDossiers, hit]);
93
+ }, [_dossiers, getMatchingDossiers, record]);
94
94
  useEffect(() => {
95
- getMatchingOverview(hit).then(_overview => setHasOverview(!!_overview));
96
- }, [getMatchingOverview, hit]);
97
- useEffect(() => {
98
- if (tab === 'hit_aggregate' && !hit?.howler.is_bundle) {
99
- setTab('overview');
95
+ if (isHit(record)) {
96
+ getMatchingOverview(record).then(_overview => setHasOverview(!!_overview));
100
97
  }
101
- }, [hit?.howler.is_bundle, tab]);
98
+ }, [getMatchingOverview, record]);
102
99
  useEffect(() => {
103
- if (selected && isOpen()) {
100
+ if (selected && open) {
104
101
  emit({
105
102
  broadcast: false,
106
103
  action: 'viewing',
@@ -112,7 +109,7 @@ const InformationPane = ({ onClose }) => {
112
109
  id: selected
113
110
  });
114
111
  }
115
- }, [emit, selected, isOpen]);
112
+ }, [emit, selected, open]);
116
113
  useEffect(() => {
117
114
  if (hasOverview && tab === 'details') {
118
115
  setTab('overview');
@@ -122,48 +119,37 @@ const InformationPane = ({ onClose }) => {
122
119
  }
123
120
  // eslint-disable-next-line react-hooks/exhaustive-deps
124
121
  }, [hasOverview]);
125
- /**
126
- * What to show as the header? If loading a skeleton, then it depends on bundle or not. Bundles don't
127
- * show anything while normal hits do
128
- */
129
- const header = useMemo(() => {
130
- if (loading && !hit?.howler?.is_bundle) {
131
- return _jsx(Skeleton, { variant: "rounded", height: 152 });
132
- }
133
- else if (!!hit && !hit.howler.is_bundle) {
134
- return _jsx(HitBanner, { layout: HitLayout.DENSE, hit: hit });
135
- }
136
- else {
137
- return null;
138
- }
139
- }, [hit, loading]);
140
122
  const tabContent = useMemo(() => {
141
123
  if (!tab) {
142
124
  return;
143
125
  }
126
+ const defaultContent = {
127
+ details: () => (_jsx(Box, { pr: 2, children: _jsx(ObjectDetails, { obj: record }) })),
128
+ comments: () => _jsx(RecordComments, { record: record, users: users }),
129
+ raw: () => _jsx(JSONViewer, { data: !loading && record, hideSearch: true, filter: filter }),
130
+ data: () => (_jsx(JSONViewer, { data: !loading && record?.howler?.data?.map(entry => tryParse(entry)), collapse: false, hideSearch: true, filter: filter })),
131
+ related: () => _jsx(RecordRelated, { record: record }),
132
+ worklog: () => _jsx(RecordWorklog, { record: !loading && record, users: users })
133
+ };
134
+ if (!isHit(record)) {
135
+ return defaultContent[tab]?.();
136
+ }
144
137
  return {
145
- overview: () => _jsx(HitOverview, { hit: hit }),
146
- details: () => _jsx(HitDetails, { hit: hit }),
147
- hit_comments: () => _jsx(HitComments, { hit: hit, users: users }),
148
- hit_raw: () => _jsx(JSONViewer, { data: !loading && hit, hideSearch: true, filter: filter }),
149
- hit_data: () => (_jsx(JSONViewer, { data: !loading && hit?.howler?.data?.map(entry => tryParse(entry)), collapse: false, hideSearch: true, filter: filter })),
150
- hit_worklog: () => _jsx(HitWorklog, { hit: !loading && hit, users: users }),
138
+ ...defaultContent,
139
+ overview: () => _jsx(HitOverview, { hit: record }),
151
140
  hit_aggregate: () => _jsx(HitSummary, {}),
152
- hit_related: () => _jsx(HitRelated, { hit: hit }),
153
- ...Object.fromEntries((hit?.howler.dossier ?? []).map((lead, index) => [
141
+ ...Object.fromEntries((record?.howler.dossier ?? []).map((lead, index) => [
154
142
  'lead:' + index,
155
- () => _jsx(LeadRenderer, { lead: lead, hit: hit })
143
+ () => _jsx(LeadRenderer, { lead: lead, hit: record })
156
144
  ])),
157
145
  ...Object.fromEntries(dossiers.flatMap((_dossier, dossierIndex) => (_dossier.leads ?? []).map((_lead, leadIndex) => [
158
146
  `external-lead:${dossierIndex}:${leadIndex}`,
159
- () => _jsx(LeadRenderer, { lead: _lead, hit: hit })
147
+ () => _jsx(LeadRenderer, { lead: _lead, hit: record })
160
148
  ])))
161
149
  }[tab]?.();
162
- }, [dossiers, filter, hit, loading, tab, users]);
150
+ }, [dossiers, filter, record, loading, tab, users]);
163
151
  const hasError = useMemo(() => !validateRegex(filter), [filter]);
164
- return (_jsxs(VSBox, { top: 10, sx: { height: '100%', flex: 1 }, children: [_jsxs(Stack, { direction: "column", flex: 1, sx: { overflowY: 'auto', flexGrow: 1 }, position: "relative", spacing: 1, ml: 2, children: [_jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, flexShrink: 0, pr: 2, sx: [hit?.howler?.is_bundle && { position: 'absolute', top: 1, right: 0, zIndex: 1100 }], children: [_jsx(FlexOne, {}), onClose && !location.pathname.startsWith('/bundles') && (_jsx(TuiIconButton, { size: "small", onClick: onClose, tooltip: t('hit.panel.details.exit'), children: _jsx(Clear, {}) })), _jsx(SocketBadge, { size: "small" }), analytic && (_jsx(TuiIconButton, { size: "small", tooltip: t('hit.panel.analytic.open'), disabled: !analytic || loading, route: `/analytics/${analytic.analytic_id}`, children: _jsx(QueryStats, {}) })), hit?.howler.bundles?.length > 0 && _jsx(BundleButton, { ids: hit.howler.bundles, disabled: loading }), !!hit && !hit.howler.is_bundle && (_jsx(TuiIconButton, { tooltip: t('hit.panel.open'), href: `/hits/${selected}`, disabled: !hit || loading, size: "small", target: "_blank", children: _jsx(OpenInNew, {}) }))] }), _jsx(Box, { pr: 2, children: header }), !!hit &&
165
- !hit.howler.is_bundle &&
166
- (!loading ? (_jsxs(_Fragment, { children: [_jsx(HitOutline, { hit: hit, layout: HitLayout.DENSE, forceAllFields: true }), _jsx(HitLabels, { hit: hit })] })) : (_jsx(Skeleton, { height: 124 }))), _jsx(HitLinks, { hit: hit, analytic: analytic, dossiers: dossiers }), _jsxs(VSBoxHeader, { ml: -1, mr: -1, pb: 1, sx: { top: '0px' }, children: [_jsxs(Tabs, { value: tab === 'overview' && !hasOverview ? 'details' : tab, sx: {
152
+ return (_jsxs(VSBox, { top: 10, sx: { height: '100%', flex: 1 }, children: [_jsxs(Stack, { direction: "column", flex: 1, sx: { overflowY: 'auto', flexGrow: 1 }, position: "relative", spacing: 1, ml: 2, children: [_jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, flexShrink: 0, pr: 2, children: [_jsx(FlexOne, {}), onClose && !location.pathname.startsWith('/bundles') && (_jsx(TuiIconButton, { size: "small", onClick: onClose, tooltip: t('hit.panel.details.exit'), children: _jsx(Clear, {}) })), _jsx(SocketBadge, { size: "small" }), analytic && (_jsx(TuiIconButton, { size: "small", tooltip: t('analytic.open'), disabled: !analytic || loading, route: `/analytics/${analytic.analytic_id}`, children: _jsx(QueryStats, {}) })), !!record && (_jsx(TuiIconButton, { tooltip: t(`${record.__index}.open`), href: `/${record.__index}s/${selected}`, disabled: !record || loading, size: "small", target: "_blank", children: _jsx(OpenInNew, {}) }))] }), isHit(record) && (_jsxs(_Fragment, { children: [_jsx(Box, { pr: 2, children: _jsx(HitBanner, { layout: HitLayout.DENSE, hit: record }) }), !loading && (_jsxs(_Fragment, { children: [_jsx(HitOutline, { hit: record, layout: HitLayout.DENSE, forceAllFields: true }), _jsx(HitLabels, { hit: record })] })), _jsx(HitLinks, { hit: record, analytic: analytic, dossiers: dossiers })] })), loading && (_jsxs(_Fragment, { children: [_jsx(Skeleton, { variant: "rounded", height: 152 }), _jsx(Skeleton, { height: 124 })] })), _jsxs(VSBoxHeader, { ml: -1, mr: -1, pb: 1, sx: { top: '0px' }, children: [_jsxs(Tabs, { value: tab === 'overview' && !hasOverview ? 'details' : tab, sx: {
167
153
  display: 'flex',
168
154
  flexDirection: 'row',
169
155
  pr: 2,
@@ -185,17 +171,18 @@ const InformationPane = ({ onClose }) => {
185
171
  right: 0
186
172
  }
187
173
  }
188
- }, variant: "scrollable", children: [_jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.comments'), children: _jsx(Badge, { sx: {
174
+ }, variant: "scrollable", children: [_jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('viewer.comments'), children: _jsx(Badge, { sx: {
189
175
  '& > .MuiBadge-badge': {
190
176
  backgroundColor: theme.palette.divider,
191
177
  zIndex: 1,
192
178
  right: theme.spacing(-0.5)
193
179
  },
194
180
  '& > svg': { zIndex: 2 }
195
- }, badgeContent: hit?.howler.comment?.length ?? 0, children: _jsx(Comment, {}) }) }), value: "hit_comments", onClick: () => setTab('hit_comments') }), hit?.howler?.is_bundle && (_jsx(Tab, { label: t('hit.viewer.aggregate'), value: "hit_aggregate", onClick: () => setTab('hit_aggregate') })), hasOverview && (_jsx(Tab, { label: t('hit.viewer.overview'), value: "overview", onClick: () => setTab('overview') })), _jsx(Tab, { label: t('hit.viewer.details'), value: "details", onClick: () => setTab('details') }), hit?.howler.dossier?.map((lead, index) => (_jsx(Tab
196
- // eslint-disable-next-line react/no-array-index-key
197
- , { label: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [lead.icon && _jsx(Icon, { icon: lead.icon }), _jsx("span", { children: i18n.language === 'en' ? lead.label.en : lead.label.fr })] }), value: 'lead:' + index, onClick: () => setTab('lead:' + index) }, 'lead:' + index))), dossiers.flatMap((_dossier, dossierIndex) => (_dossier.leads ?? []).map((_lead, leadIndex) => (_jsx(Tab
181
+ }, badgeContent: record?.howler.comment?.length ?? 0, children: _jsx(Comment, {}) }) }), value: "comments", onClick: () => setTab('comments') }), isHit(record) && hasOverview && (_jsx(Tab, { label: t('hit.viewer.overview'), value: "overview", onClick: () => setTab('overview') })), _jsx(Tab, { label: t('hit.viewer.details'), value: "details", onClick: () => setTab('details') }), isHit(record) &&
182
+ record?.howler.dossier?.map((lead, index) => (_jsx(Tab
183
+ // eslint-disable-next-line react/no-array-index-key
184
+ , { label: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [lead.icon && _jsx(Icon, { icon: lead.icon }), _jsx("span", { children: i18n.language === 'en' ? lead.label.en : lead.label.fr })] }), value: 'lead:' + index, onClick: () => setTab('lead:' + index) }, 'lead:' + index))), dossiers.flatMap((_dossier, dossierIndex) => (_dossier.leads ?? []).map((_lead, leadIndex) => (_jsx(Tab
198
185
  // eslint-disable-next-line react/no-array-index-key
199
- , { label: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_lead.icon && _jsx(Icon, { icon: _lead.icon }), _jsx("span", { children: i18n.language === 'en' ? _lead.label.en : _lead.label.fr })] }), value: `external-lead:${dossierIndex}:${leadIndex}`, onClick: () => setTab(`external-lead:${dossierIndex}:${leadIndex}`) }, `external-lead:${dossierIndex}:${leadIndex}`)))), _jsx(FlexOne, {}), _jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.data'), children: _jsx(DataObject, {}) }), value: "hit_data", onClick: () => setTab('hit_data'), disabled: !hit?.howler?.data }), _jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.json'), children: _jsx(Code, {}) }), value: "hit_raw", onClick: () => setTab('hit_raw') }), _jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.worklog'), children: _jsx(History, {}) }), value: "hit_worklog", onClick: () => setTab('hit_worklog') }), _jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.related'), children: _jsx(LinkSharp, {}) }), value: "hit_related", onClick: () => setTab('hit_related') })] }), ['hit_raw', 'hit_data'].includes(tab) && (_jsx(Phrase, { sx: { mt: 1, pr: 1 }, value: filter, onChange: setFilter, error: hasError, label: t('json.viewer.search.label'), placeholder: t('json.viewer.search.prompt'), endAdornment: _jsx(IconButton, { onClick: () => setFilter(''), children: _jsx(Clear, {}) }) }))] }), _jsx(ErrorBoundary, { children: _jsx(VSBoxContent, { mr: -1, ml: -1, height: "100%", children: _jsx(Stack, { height: "100%", flex: 1, children: tabContent }) }) })] }), !!hit && hit?.howler && (_jsxs(Box, { pr: 2, bgcolor: theme.palette.background.default, position: "relative", children: [_jsx(Divider, { orientation: "horizontal" }), _jsx(HitActions, { hit: hit })] }))] }));
186
+ , { label: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_lead.icon && _jsx(Icon, { icon: _lead.icon }), _jsx("span", { children: i18n.language === 'en' ? _lead.label.en : _lead.label.fr })] }), value: `external-lead:${dossierIndex}:${leadIndex}`, onClick: () => setTab(`external-lead:${dossierIndex}:${leadIndex}`) }, `external-lead:${dossierIndex}:${leadIndex}`)))), _jsx(FlexOne, {}), _jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.data'), children: _jsx(DataObject, {}) }), value: "data", onClick: () => setTab('data'), disabled: !record?.howler?.data }), _jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.json'), children: _jsx(Code, {}) }), value: "raw", onClick: () => setTab('raw') }), _jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.worklog'), children: _jsx(History, {}) }), value: "worklog", onClick: () => setTab('worklog') }), _jsx(Tab, { sx: { px: 2, minWidth: 0 }, label: _jsx(Tooltip, { title: t('hit.viewer.related'), children: _jsx(LinkSharp, {}) }), value: "related", onClick: () => setTab('related') })] }), ['raw', 'data'].includes(tab) && (_jsx(Phrase, { sx: { mt: 1, pr: 1 }, value: filter, onChange: setFilter, error: hasError, label: t('json.viewer.search.label'), placeholder: t('json.viewer.search.prompt'), endAdornment: _jsx(IconButton, { onClick: () => setFilter(''), children: _jsx(Clear, {}) }) }))] }), _jsx(ErrorBoundary, { children: _jsx(VSBoxContent, { mr: -1, ml: -1, height: "100%", children: _jsx(Stack, { height: "100%", flex: 1, children: tabContent }) }) })] }), isHit(record) && (_jsxs(Box, { pr: 2, bgcolor: theme.palette.background.default, position: "relative", children: [_jsx(Divider, { orientation: "horizontal" }), _jsx(HitActions, { hit: record })] }))] }));
200
187
  };
201
188
  export default InformationPane;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ArrowDropDown, InfoOutlined, List, Settings, TableChart, ViewComfy, ViewCompact, ViewModule } from '@mui/icons-material';
3
3
  import { Checkbox, Divider, FormLabel, Stack, TextField, ToggleButton, ToggleButtonGroup, Tooltip } from '@mui/material';
4
- import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
4
+ import { RecordSearchContext } from '@cccsaurora/howler-ui/components/app/providers/RecordSearchProvider';
5
5
  import ChipPopper from '@cccsaurora/howler-ui/components/elements/display/ChipPopper';
6
6
  import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
7
7
  import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
@@ -10,8 +10,8 @@ import { useContextSelector } from 'use-context-selector';
10
10
  import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
11
11
  const LayoutSettings = () => {
12
12
  const { t } = useTranslation();
13
- const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
14
- const setDisplayType = useContextSelector(HitSearchContext, ctx => ctx.setDisplayType);
13
+ const displayType = useContextSelector(RecordSearchContext, ctx => ctx.displayType);
14
+ const setDisplayType = useContextSelector(RecordSearchContext, ctx => ctx.setDisplayType);
15
15
  const [hitLayout, setHitLayout] = useMyLocalStorageItem(StorageKey.HIT_LAYOUT, false);
16
16
  const [templateFieldCount, setTemplateFieldCount] = useMyLocalStorageItem(StorageKey.TEMPLATE_FIELD_COUNT, null);
17
17
  return (_jsx(ChipPopper, { icon: _jsx(Tooltip, { title: t('search.layout.settings'), children: _jsx(Settings, {}) }), deleteIcon: _jsx(ArrowDropDown, {}), toggleOnDelete: true, disablePortal: false, slotProps: { chip: { size: 'medium', 'aria-label': t('search.layout.settings') } }, placement: "bottom-end", children: _jsxs(Stack, { spacing: 1, alignItems: "start", children: [_jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", alignSelf: "stretch", children: [_jsx(FormLabel, { id: "display_type", children: t('page.settings.local.hits.display_type') }), _jsx("div", { style: { flex: 1 } }), _jsx(Tooltip, { title: t('page.settings.local.hits.display_type.description'), children: _jsx(InfoOutlined, { fontSize: "inherit" }) })] }), _jsxs(ToggleButtonGroup, { exclusive: true, value: displayType, onChange: (__, value) => setDisplayType(value), size: "small", "aria-labelledby": "display_type", children: [_jsx(ToggleButton, { value: "list", children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(List, {}), _jsx("span", { children: t('page.settings.local.hits.display_type.list') })] }) }), _jsx(ToggleButton, { value: "grid", children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(TableChart, {}), _jsx("span", { children: t('page.settings.local.hits.display_type.grid') })] }) })] }), _jsx(Divider, { flexItem: true }), _jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", alignSelf: "stretch", children: [_jsx(FormLabel, { id: "layout", children: t('page.settings.local.hits.layout') }), _jsx("div", { style: { flex: 1 } }), _jsx(Tooltip, { title: t('page.settings.local.hits.layout.description'), children: _jsx(InfoOutlined, { fontSize: "inherit" }) })] }), _jsxs(ToggleButtonGroup, { exclusive: true, size: "small", value: hitLayout, onChange: (_, value) => setHitLayout(value), "aria-labelledby": "layout", children: [_jsx(ToggleButton, { value: HitLayout.DENSE, children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(ViewCompact, {}), _jsx("span", { children: t('page.settings.local.hits.layout.dense') })] }) }), _jsx(ToggleButton, { value: HitLayout.NORMAL, children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(ViewModule, {}), _jsx("span", { children: t('page.settings.local.hits.layout.normal') })] }) }), _jsx(ToggleButton, { value: HitLayout.COMFY, children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(ViewComfy, {}), _jsx("span", { children: t('page.settings.local.hits.layout.comfy') })] }) })] }), _jsx(Divider, { flexItem: true }), _jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", alignSelf: "stretch", children: [_jsx(FormLabel, { id: "field_count", children: t('page.settings.local.hits.field_count') }), _jsx("div", { style: { flex: 1 } }), _jsx(Tooltip, { title: t('page.settings.local.hits.field_count.description'), children: _jsx(InfoOutlined, { fontSize: "inherit" }) })] }), _jsxs(Stack, { direction: "row", spacing: 0.5, alignSelf: "stretch", children: [_jsx(Checkbox, { checked: templateFieldCount !== null, onChange: (_, checked) => setTemplateFieldCount(checked ? 3 : null), size: "small" }), _jsx(TextField, { type: "number", size: "small", disabled: templateFieldCount === null, value: templateFieldCount ?? 3, fullWidth: true, onChange: e => {
@@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next';
9
9
  import { useContextSelector } from 'use-context-selector';
10
10
  import HitFilter from './shared/HitFilter';
11
11
  import HitSort from './shared/HitSort';
12
+ import IndexPicker from './shared/IndexPicker';
12
13
  import SearchSpan from './shared/SearchSpan';
13
14
  import ViewLink from './ViewLink';
14
15
  const QuerySettings = ({ boxSx }) => {
@@ -25,6 +26,6 @@ const QuerySettings = ({ boxSx }) => {
25
26
  await fetchViews();
26
27
  addView('');
27
28
  };
28
- return (_jsx(Box, { sx: boxSx ?? { position: 'relative', maxWidth: '1200px' }, children: _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsxs(Grid, { container: true, spacing: 1, sx: theme => ({ ml: `${theme.spacing(-1)} !important`, mt: `${theme.spacing(-1)} !important` }), children: [_jsx(Grid, { item: true, children: _jsx(HitSort, {}) }), _jsx(Grid, { item: true, children: _jsx(SearchSpan, {}) }), currentViews?.map((view, id) => (_jsx(Grid, { item: true, children: _jsx(ViewLink, { id: id, viewId: view }) }, view))), filters?.map((filter, id) => (_jsx(Grid, { item: true, children: _jsx(HitFilter, { id: id, value: filter }) }, filter)))] }), _jsx(ChipPopper, { icon: _jsx(Add, {}), deleteIcon: _jsx(ArrowDropDown, {}), toggleOnDelete: true, closeOnClick: true, slotProps: { chip: { size: 'small', color: 'primary' }, paper: { sx: { p: 1 } } }, children: _jsxs(Stack, { spacing: 1, children: [_jsxs(Button, { id: "add-filter", "aria-label": t('hit.search.filter.add'), variant: "outlined", onClick: () => addFilter('howler.assessment:*'), disabled: filters?.some(filter => filter.endsWith('*')), sx: { display: 'flex', pl: 1 }, children: [_jsx(Add, { fontSize: "small", sx: { mr: 1 } }), _jsx("div", { style: { flex: 1 } }), _jsx("span", { children: t('hit.search.filter.add') })] }), _jsxs(Button, { id: "add-view", "aria-label": t('hit.search.view.add'), variant: "outlined", onClick: onAddView, disabled: !allowAddViews, sx: { display: 'flex', pl: 1 }, children: [_jsx(Add, { fontSize: "small", sx: { mr: 1 } }), _jsx("div", { style: { flex: 1 } }), _jsx("span", { children: t('hit.search.view.add') })] })] }) })] }) }));
29
+ return (_jsx(Box, { sx: boxSx ?? { position: 'relative', maxWidth: '1200px' }, children: _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsxs(Grid, { container: true, spacing: 1, sx: theme => ({ ml: `${theme.spacing(-1)} !important`, mt: `${theme.spacing(-1)} !important` }), children: [_jsx(Grid, { item: true, children: _jsx(IndexPicker, {}) }), _jsx(Grid, { item: true, children: _jsx(HitSort, {}) }), _jsx(Grid, { item: true, children: _jsx(SearchSpan, {}) }), currentViews?.map((view, id) => (_jsx(Grid, { item: true, children: _jsx(ViewLink, { id: id, viewId: view }) }, view))), filters?.map((filter, id) => (_jsx(Grid, { item: true, children: _jsx(HitFilter, { id: id, value: filter }) }, filter)))] }), _jsx(ChipPopper, { icon: _jsx(Add, {}), deleteIcon: _jsx(ArrowDropDown, {}), toggleOnDelete: true, closeOnClick: true, slotProps: { chip: { size: 'small', color: 'primary' }, paper: { sx: { p: 1 } } }, children: _jsxs(Stack, { spacing: 1, children: [_jsxs(Button, { id: "add-filter", "aria-label": t('hit.search.filter.add'), variant: "outlined", onClick: () => addFilter('howler.assessment:*'), disabled: filters?.some(filter => filter.endsWith('*')), sx: { display: 'flex', pl: 1 }, children: [_jsx(Add, { fontSize: "small", sx: { mr: 1 } }), _jsx("div", { style: { flex: 1 } }), _jsx("span", { children: t('hit.search.filter.add') })] }), _jsxs(Button, { id: "add-view", "aria-label": t('hit.search.view.add'), variant: "outlined", onClick: onAddView, disabled: !allowAddViews, sx: { display: 'flex', pl: 1 }, children: [_jsx(Add, { fontSize: "small", sx: { mr: 1 } }), _jsx("div", { style: { flex: 1 } }), _jsx("span", { children: t('hit.search.view.add') })] })] }) })] }) }));
29
30
  };
30
31
  export default memo(QuerySettings);
@@ -16,6 +16,9 @@ vi.mock('./shared/HitSort', () => ({
16
16
  vi.mock('./shared/SearchSpan', () => ({
17
17
  default: () => _jsx("div", { id: "search-span", children: "SearchSpan" })
18
18
  }));
19
+ vi.mock('./shared/IndexPicker', () => ({
20
+ default: () => _jsx("div", { id: "index-picker", children: "IndexPicker" })
21
+ }));
19
22
  vi.mock('./ViewLink', () => ({
20
23
  default: ({ id, viewId }) => (_jsxs("div", { id: `view-link-${id}`, "data-view-id": viewId, children: ["ViewLink ", id, ": ", viewId] }))
21
24
  }));
@@ -33,6 +36,7 @@ const mockFetchViews = vi.fn();
33
36
  let mockParameterContext = {
34
37
  filters: [],
35
38
  views: [],
39
+ indexes: [],
36
40
  addFilter: mockAddFilter,
37
41
  addView: mockAddView
38
42
  };
@@ -270,16 +274,16 @@ describe('QuerySettings', () => {
270
274
  mockParameterContext.views = ['view-1'];
271
275
  const { container } = render(_jsx(QuerySettings, {}), { wrapper: Wrapper });
272
276
  const gridItems = container.querySelectorAll('[class*="MuiGrid-item"]');
273
- // HitSort + SearchSpan + 1 view + 2 filters = 5 items
274
- expect(gridItems.length).toBe(5);
277
+ // IndexPicker + HitSort + SearchSpan + 1 view + 2 filters = 5 items
278
+ expect(gridItems.length).toBe(6);
275
279
  });
276
280
  it('should render correct number of items with multiple views', () => {
277
281
  mockParameterContext.filters = ['filter1'];
278
282
  mockParameterContext.views = ['view-1', 'view-2', 'view-3'];
279
283
  const { container } = render(_jsx(QuerySettings, {}), { wrapper: Wrapper });
280
284
  const gridItems = container.querySelectorAll('[class*="MuiGrid-item"]');
281
- // HitSort + SearchSpan + 3 views + 1 filter = 6 items
282
- expect(gridItems.length).toBe(6);
285
+ // IndexPicker + HitSort + SearchSpan + 3 views + 1 filter = 6 items
286
+ expect(gridItems.length).toBe(7);
283
287
  });
284
288
  });
285
289
  describe('Edge Cases', () => {
@@ -508,11 +512,12 @@ describe('QuerySettings', () => {
508
512
  mockParameterContext.views = ['view-1'];
509
513
  const { container } = render(_jsx(QuerySettings, {}), { wrapper: Wrapper });
510
514
  const gridItems = container.querySelectorAll('[class*="MuiGrid-item"]');
511
- // Order: HitSort, SearchSpan, ViewLink(s), HitFilter(s)
512
- expect(gridItems[0]).toContainElement(screen.getByTestId('hit-sort'));
513
- expect(gridItems[1]).toContainElement(screen.getByTestId('search-span'));
514
- expect(gridItems[2]).toContainElement(screen.getByTestId('view-link-0'));
515
- expect(gridItems[3]).toContainElement(screen.getByTestId('hit-filter-0'));
515
+ // Order: IndexPicker, HitSort, SearchSpan, ViewLink(s), HitFilter(s)
516
+ expect(gridItems[0]).toContainElement(screen.getByTestId('index-picker'));
517
+ expect(gridItems[1]).toContainElement(screen.getByTestId('hit-sort'));
518
+ expect(gridItems[2]).toContainElement(screen.getByTestId('search-span'));
519
+ expect(gridItems[3]).toContainElement(screen.getByTestId('view-link-0'));
520
+ expect(gridItems[4]).toContainElement(screen.getByTestId('hit-filter-0'));
516
521
  });
517
522
  it('should pass correct props to ViewLink components', () => {
518
523
  mockParameterContext.views = ['view-1', 'view-2'];
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ChevronLeft, Close, ManageSearch } from '@mui/icons-material';
3
3
  import { Box, Card, Checkbox, Collapse, Drawer, Fab, IconButton, Stack, Tooltip, Typography, useMediaQuery, useTheme } from '@mui/material';
4
- import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
5
- import HitSearchProvider, { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
6
4
  import ParameterProvider, { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
5
+ import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
6
+ import RecordSearchProvider, { RecordSearchContext } from '@cccsaurora/howler-ui/components/app/providers/RecordSearchProvider';
7
7
  import { ViewContext } from '@cccsaurora/howler-ui/components/app/providers/ViewProvider';
8
8
  import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
9
9
  import FlexPort from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexPort';
@@ -32,14 +32,14 @@ const HitBrowser = () => {
32
32
  const setQuery = useContextSelector(ParameterContext, ctx => ctx.setQuery);
33
33
  const setOffset = useContextSelector(ParameterContext, ctx => ctx.setOffset);
34
34
  const selectedViews = useContextSelector(ParameterContext, ctx => ctx.views);
35
- const selectedHits = useContextSelector(HitContext, ctx => ctx.selectedHits);
36
- const addHitToSelection = useContextSelector(HitContext, ctx => ctx.addHitToSelection);
37
- const removeHitFromSelection = useContextSelector(HitContext, ctx => ctx.removeHitFromSelection);
38
- const clearSelectedHits = useContextSelector(HitContext, ctx => ctx.clearSelectedHits);
35
+ const selectedHits = useContextSelector(RecordContext, ctx => ctx.selectedRecords);
36
+ const addHitToSelection = useContextSelector(RecordContext, ctx => ctx.addRecordToSelection);
37
+ const removeHitFromSelection = useContextSelector(RecordContext, ctx => ctx.removeRecordFromSelection);
38
+ const clearSelectedHits = useContextSelector(RecordContext, ctx => ctx.clearSelectedRecords);
39
39
  const searchPaneWidth = useMyLocalStorageItem(StorageKey.SEARCH_PANE_WIDTH, null)[0];
40
40
  const forceDrawer = useMyLocalStorageItem(StorageKey.FORCE_DRAWER, false)[0];
41
- const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
42
- const response = useContextSelector(HitSearchContext, ctx => ctx.response);
41
+ const displayType = useContextSelector(RecordSearchContext, ctx => ctx.displayType);
42
+ const response = useContextSelector(RecordSearchContext, ctx => ctx.response);
43
43
  const location = useLocation();
44
44
  const routeParams = useParams();
45
45
  const [show, setShow] = useState(!!selected);
@@ -128,6 +128,6 @@ const HitBrowser = () => {
128
128
  }, children: _jsx(ChevronLeft, { sx: { transition: 'rotate 250ms', rotate: show ? '180deg' : '0deg' } }) }))] }));
129
129
  };
130
130
  const HitBrowserProvider = () => {
131
- return (_jsx(ParameterProvider, { children: _jsx(HitSearchProvider, { children: _jsx(HitBrowser, {}) }) }));
131
+ return (_jsx(ParameterProvider, { children: _jsx(RecordSearchProvider, { children: _jsx(HitBrowser, {}) }) }));
132
132
  };
133
133
  export default HitBrowserProvider;
@@ -1,9 +1,9 @@
1
- export type HitQueryProps = {
1
+ export type RecordQueryProps = {
2
2
  triggerSearch: (query: string) => void;
3
3
  onChange?: (query: string, isDirty: boolean) => void;
4
4
  searching?: boolean;
5
5
  disabled?: boolean;
6
6
  compact?: boolean;
7
7
  };
8
- declare const _default: import("react").NamedExoticComponent<HitQueryProps>;
8
+ declare const _default: import("react").NamedExoticComponent<RecordQueryProps>;
9
9
  export default _default;
@@ -5,7 +5,7 @@ import { Badge, Box, Card, Skeleton, Tooltip, alpha, useTheme } from '@mui/mater
5
5
  import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
6
6
  import TuiIconButton from '@cccsaurora/howler-ui/components/elements/addons/buttons/CustomIconButton';
7
7
  import QueryEditor from '@cccsaurora/howler-ui/components/routes/advanced/QueryEditor';
8
- import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
8
+ import { RecordSearchContext } from '@cccsaurora/howler-ui/components/app/providers/RecordSearchProvider';
9
9
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
10
10
  import { useTranslation } from 'react-i18next';
11
11
  import { useLocation } from 'react-router-dom';
@@ -13,8 +13,8 @@ import { useContextSelector } from 'use-context-selector';
13
13
  import { DEFAULT_QUERY } from '@cccsaurora/howler-ui/utils/constants';
14
14
  import { sanitizeMultilineLucene } from '@cccsaurora/howler-ui/utils/stringUtils';
15
15
  const DEFAULT_MULTILINE_HEIGHT = 250;
16
- const PROMPT_CONTEXT = 'isHitQuery && !suggestWidgetVisible && !renameInputVisible && !inSnippetMode && !quickFixWidgetVisible';
17
- const HitQuery = ({ searching = false, disabled = false, compact = false, triggerSearch, onChange }) => {
16
+ const PROMPT_CONTEXT = 'isRecordQuery && !suggestWidgetVisible && !renameInputVisible && !inSnippetMode && !quickFixWidgetVisible';
17
+ const RecordQuery = ({ searching = false, disabled = false, compact = false, triggerSearch, onChange }) => {
18
18
  const { t } = useTranslation();
19
19
  const location = useLocation();
20
20
  const theme = useTheme();
@@ -22,7 +22,7 @@ const HitQuery = ({ searching = false, disabled = false, compact = false, trigge
22
22
  const savedQuery = useContextSelector(ParameterContext, ctx => ctx.query || DEFAULT_QUERY);
23
23
  const prevQuery = useRef(null);
24
24
  const [query, setQuery] = useState(new URLSearchParams(window.location.search).get('query') || DEFAULT_QUERY);
25
- const fzfSearch = useContextSelector(HitSearchContext, ctx => ctx?.fzfSearch ?? false);
25
+ const fzfSearch = useContextSelector(RecordSearchContext, ctx => ctx?.fzfSearch ?? false);
26
26
  const [loaded, setLoaded] = useState(false);
27
27
  const [multiline, setMultiline] = useState(false);
28
28
  const [y, setY] = useState(0);
@@ -90,7 +90,7 @@ const HitQuery = ({ searching = false, disabled = false, compact = false, trigge
90
90
  window.addEventListener('mouseup', onMouseUp);
91
91
  }, [onMouseMove, onMouseUp]);
92
92
  const onMount = useCallback((ed) => {
93
- ed.createContextKey('isHitQuery', true);
93
+ ed.createContextKey('isRecordQuery', true);
94
94
  setLoaded(true);
95
95
  }, []);
96
96
  const options = useMemo(() => ({
@@ -148,4 +148,4 @@ const HitQuery = ({ searching = false, disabled = false, compact = false, trigge
148
148
  zIndex: 1000
149
149
  }, onMouseDown: onMouseDown }))] }));
150
150
  };
151
- export default memo(HitQuery);
151
+ export default memo(RecordQuery);