@cccsaurora/howler-ui 2.18.0-dev.757 → 2.18.0-dev.762

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 (283) hide show
  1. package/api/index.d.ts +2 -0
  2. package/api/index.js +4 -2
  3. package/api/search/case.d.ts +4 -0
  4. package/api/search/case.js +8 -0
  5. package/api/search/facet/hit.d.ts +1 -3
  6. package/api/search/facet/index.d.ts +3 -1
  7. package/api/search/index.d.ts +2 -1
  8. package/api/search/index.js +2 -1
  9. package/api/v2/case/index.d.ts +8 -0
  10. package/api/v2/case/index.js +20 -0
  11. package/api/v2/case/items.d.ts +6 -0
  12. package/api/v2/case/items.js +18 -0
  13. package/api/v2/index.d.ts +4 -0
  14. package/api/v2/index.js +6 -0
  15. package/api/v2/search/facet.d.ts +3 -0
  16. package/api/v2/search/facet.js +12 -0
  17. package/api/v2/search/index.d.ts +5 -0
  18. package/api/v2/search/index.js +24 -0
  19. package/commons/components/leftnav/LeftNavDrawer.js +1 -1
  20. package/components/app/App.js +36 -8
  21. package/components/app/hooks/useMatchers.d.ts +1 -1
  22. package/components/app/hooks/useMatchers.js +23 -11
  23. package/components/app/hooks/useMatchers.test.js +22 -22
  24. package/components/app/hooks/useTitle.js +3 -3
  25. package/components/app/providers/FavouritesProvider.js +2 -2
  26. package/components/app/providers/ModalProvider.d.ts +1 -0
  27. package/components/app/providers/ParameterProvider.d.ts +9 -2
  28. package/components/app/providers/ParameterProvider.js +165 -240
  29. package/components/app/providers/ParameterProvider.test.js +346 -94
  30. package/components/app/providers/RecordProvider.d.ts +23 -0
  31. package/components/app/providers/{HitProvider.js → RecordProvider.js} +41 -41
  32. package/components/app/providers/{HitSearchProvider.d.ts → RecordSearchProvider.d.ts} +6 -6
  33. package/components/app/providers/{HitSearchProvider.js → RecordSearchProvider.js} +12 -17
  34. package/components/app/providers/{HitSearchProvider.test.js → RecordSearchProvider.test.js} +52 -71
  35. package/components/app/providers/UserListProvider.js +28 -8
  36. package/components/elements/ContextMenu.d.ts +56 -0
  37. package/components/elements/ContextMenu.js +109 -0
  38. package/components/elements/ContextMenu.test.js +215 -0
  39. package/components/{routes/overviews/OverviewEditor.js → elements/MarkdownEditor.js} +3 -3
  40. package/components/elements/ObjectDetails.d.ts +6 -0
  41. package/components/elements/{hit/HitDetails.js → ObjectDetails.js} +17 -17
  42. package/components/elements/PluginTypography.d.ts +2 -1
  43. package/components/elements/PluginTypography.js +3 -2
  44. package/components/elements/UserList.d.ts +5 -2
  45. package/components/elements/UserList.js +18 -8
  46. package/components/elements/addons/search/phrase/Phrase.js +1 -1
  47. package/components/elements/case/CaseCard.d.ts +12 -0
  48. package/components/elements/case/CaseCard.js +42 -0
  49. package/components/elements/case/CasePreview.d.ts +6 -0
  50. package/components/elements/case/CasePreview.js +17 -0
  51. package/components/elements/case/StatusIcon.d.ts +5 -0
  52. package/components/elements/case/StatusIcon.js +13 -0
  53. package/components/elements/display/ChipPopper.d.ts +1 -1
  54. package/components/elements/display/HowlerCard.js +1 -1
  55. package/components/elements/display/Modal.js +2 -0
  56. package/components/elements/hit/HitActions.js +4 -4
  57. package/components/elements/hit/HitBanner.d.ts +1 -0
  58. package/components/elements/hit/HitBanner.js +29 -49
  59. package/components/elements/hit/HitCard.d.ts +2 -0
  60. package/components/elements/hit/HitCard.js +7 -7
  61. package/components/elements/hit/HitLabels.js +2 -2
  62. package/components/elements/hit/HitOutline.d.ts +1 -0
  63. package/components/elements/hit/HitOutline.js +3 -3
  64. package/components/elements/hit/{HitQuickSearch.d.ts → HitPreview.d.ts} +3 -3
  65. package/components/elements/hit/{HitQuickSearch.js → HitPreview.js} +10 -4
  66. package/components/elements/hit/HitSummary.d.ts +2 -1
  67. package/components/elements/hit/HitSummary.js +6 -5
  68. package/components/elements/hit/aggregate/HitGraph.js +8 -8
  69. package/components/elements/hit/elements/AnalyticLink.d.ts +9 -0
  70. package/components/elements/hit/elements/AnalyticLink.js +22 -0
  71. package/components/elements/hit/outlines/DefaultOutline.js +1 -1
  72. package/components/elements/hit/related/RelatedRecords.js +63 -0
  73. package/components/elements/observable/ObservableCard.d.ts +6 -0
  74. package/components/elements/observable/ObservableCard.js +22 -0
  75. package/components/elements/observable/ObservablePreview.d.ts +6 -0
  76. package/components/elements/observable/ObservablePreview.js +12 -0
  77. package/components/elements/{hit/HitComments.d.ts → record/RecordComments.d.ts} +5 -4
  78. package/components/elements/{hit/HitComments.js → record/RecordComments.js} +29 -28
  79. package/components/{routes/hits/search/HitContextMenu.d.ts → elements/record/RecordContextMenu.d.ts} +3 -3
  80. package/components/elements/record/RecordContextMenu.js +256 -0
  81. package/components/elements/record/RecordContextMenu.test.d.ts +1 -0
  82. package/components/{routes/hits/search/HitContextMenu.test.js → elements/record/RecordContextMenu.test.js} +94 -39
  83. package/components/elements/record/RecordRelated.d.ts +7 -0
  84. package/components/elements/record/RecordRelated.js +34 -0
  85. package/components/elements/{hit/HitWorklog.d.ts → record/RecordWorklog.d.ts} +4 -3
  86. package/components/elements/{hit/HitWorklog.js → record/RecordWorklog.js} +15 -13
  87. package/components/elements/view/ViewTitle.d.ts +1 -0
  88. package/components/elements/view/ViewTitle.js +9 -2
  89. package/components/hooks/useHitActions.d.ts +1 -1
  90. package/components/hooks/useHitActions.js +4 -4
  91. package/components/hooks/useMyPreferences.js +10 -1
  92. package/components/hooks/useMySearch.js +2 -2
  93. package/components/hooks/useMySitemap.js +4 -1
  94. package/components/hooks/useMyTheme.js +9 -2
  95. package/components/hooks/{useHitSelection.d.ts → useRecordSelection.d.ts} +2 -2
  96. package/components/hooks/{useHitSelection.js → useRecordSelection.js} +12 -33
  97. package/components/hooks/useRelatedRecords.d.ts +13 -0
  98. package/components/hooks/useRelatedRecords.js +32 -0
  99. package/components/routes/action/edit/ActionEditor.js +2 -2
  100. package/components/routes/action/view/ActionSearch.js +1 -1
  101. package/components/routes/advanced/QueryBuilder.js +1 -1
  102. package/components/routes/advanced/QueryEditor.js +3 -3
  103. package/components/routes/advanced/historyCompletionProvider.js +3 -3
  104. package/components/routes/analytics/AnalyticDetails.js +2 -2
  105. package/components/routes/analytics/AnalyticSearch.js +1 -1
  106. package/components/routes/cases/CaseViewer.d.ts +2 -0
  107. package/components/routes/cases/CaseViewer.js +22 -0
  108. package/components/routes/cases/Cases.d.ts +2 -0
  109. package/components/routes/cases/Cases.js +101 -0
  110. package/components/routes/cases/constants.d.ts +5 -0
  111. package/components/routes/cases/constants.js +5 -0
  112. package/components/routes/cases/detail/AlertPanel.d.ts +6 -0
  113. package/components/routes/cases/detail/AlertPanel.js +33 -0
  114. package/components/routes/cases/detail/CaseAssets.d.ts +11 -0
  115. package/components/routes/cases/detail/CaseAssets.js +104 -0
  116. package/components/routes/cases/detail/CaseAssets.test.d.ts +1 -0
  117. package/components/routes/cases/detail/CaseAssets.test.js +167 -0
  118. package/components/routes/cases/detail/CaseDashboard.d.ts +7 -0
  119. package/components/routes/cases/detail/CaseDashboard.js +66 -0
  120. package/components/routes/cases/detail/CaseDetails.d.ts +6 -0
  121. package/components/routes/cases/detail/CaseDetails.js +61 -0
  122. package/components/routes/cases/detail/CaseOverview.d.ts +7 -0
  123. package/components/routes/cases/detail/CaseOverview.js +43 -0
  124. package/components/routes/cases/detail/CaseSidebar.d.ts +8 -0
  125. package/components/routes/cases/detail/CaseSidebar.js +107 -0
  126. package/components/routes/cases/detail/CaseSidebar.test.d.ts +1 -0
  127. package/components/routes/cases/detail/CaseSidebar.test.js +246 -0
  128. package/components/routes/cases/detail/CaseTask.d.ts +11 -0
  129. package/components/routes/cases/detail/CaseTask.js +57 -0
  130. package/components/routes/cases/detail/CaseTimeline.d.ts +12 -0
  131. package/components/routes/cases/detail/CaseTimeline.js +106 -0
  132. package/components/routes/cases/detail/CaseTimeline.test.d.ts +1 -0
  133. package/components/routes/cases/detail/CaseTimeline.test.js +320 -0
  134. package/components/routes/cases/detail/ItemPage.d.ts +6 -0
  135. package/components/routes/cases/detail/ItemPage.js +99 -0
  136. package/components/routes/cases/detail/RelatedCasePanel.d.ts +6 -0
  137. package/components/routes/cases/detail/RelatedCasePanel.js +34 -0
  138. package/components/routes/cases/detail/TaskPanel.d.ts +7 -0
  139. package/components/routes/cases/detail/TaskPanel.js +52 -0
  140. package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +11 -0
  141. package/components/routes/cases/detail/aggregates/CaseAggregate.js +24 -0
  142. package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +6 -0
  143. package/components/routes/cases/detail/aggregates/SourceAggregate.js +26 -0
  144. package/components/routes/cases/detail/assets/Asset.d.ts +14 -0
  145. package/components/routes/cases/detail/assets/Asset.js +12 -0
  146. package/components/routes/cases/detail/assets/Asset.test.d.ts +1 -0
  147. package/components/routes/cases/detail/assets/Asset.test.js +72 -0
  148. package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +20 -0
  149. package/components/routes/cases/detail/sidebar/CaseFolder.js +83 -0
  150. package/components/routes/cases/detail/sidebar/CaseFolder.test.d.ts +1 -0
  151. package/components/routes/cases/detail/sidebar/CaseFolder.test.js +295 -0
  152. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.d.ts +34 -0
  153. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.js +103 -0
  154. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.d.ts +1 -0
  155. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.js +363 -0
  156. package/components/routes/cases/detail/sidebar/FolderEntry.d.ts +25 -0
  157. package/components/routes/cases/detail/sidebar/FolderEntry.js +88 -0
  158. package/components/routes/cases/detail/sidebar/FolderEntry.test.d.ts +1 -0
  159. package/components/routes/cases/detail/sidebar/FolderEntry.test.js +206 -0
  160. package/components/routes/cases/detail/sidebar/RootDropZone.d.ts +5 -0
  161. package/components/routes/cases/detail/sidebar/RootDropZone.js +33 -0
  162. package/components/routes/cases/detail/sidebar/types.d.ts +9 -0
  163. package/components/routes/cases/detail/sidebar/utils.d.ts +3 -0
  164. package/components/routes/cases/detail/sidebar/utils.js +29 -0
  165. package/components/routes/cases/detail/sidebar/utils.test.d.ts +1 -0
  166. package/components/routes/cases/detail/sidebar/utils.test.js +82 -0
  167. package/components/routes/cases/hooks/useCase.d.ts +13 -0
  168. package/components/routes/cases/hooks/useCase.js +51 -0
  169. package/components/routes/cases/modals/AddToCaseModal.d.ts +7 -0
  170. package/components/routes/cases/modals/AddToCaseModal.js +59 -0
  171. package/components/routes/cases/modals/AddToCaseModal.test.d.ts +1 -0
  172. package/components/routes/cases/modals/AddToCaseModal.test.js +313 -0
  173. package/components/routes/cases/modals/CaseRecordRow.d.ts +9 -0
  174. package/components/routes/cases/modals/CaseRecordRow.js +15 -0
  175. package/components/routes/cases/modals/CreateCaseModal.d.ts +7 -0
  176. package/components/routes/cases/modals/CreateCaseModal.js +55 -0
  177. package/components/routes/cases/modals/CreateCaseModal.test.d.ts +1 -0
  178. package/components/routes/cases/modals/CreateCaseModal.test.js +358 -0
  179. package/components/routes/cases/modals/RenameItemModal.d.ts +9 -0
  180. package/components/routes/cases/modals/RenameItemModal.js +48 -0
  181. package/components/routes/cases/modals/ResolveModal.d.ts +7 -0
  182. package/components/routes/cases/modals/ResolveModal.js +115 -0
  183. package/components/routes/cases/modals/ResolveModal.test.d.ts +1 -0
  184. package/components/routes/cases/modals/ResolveModal.test.js +384 -0
  185. package/components/routes/cases/modals/hooks.d.ts +7 -0
  186. package/components/routes/cases/modals/hooks.js +44 -0
  187. package/components/routes/cases/modals/types.d.ts +5 -0
  188. package/components/routes/dossiers/DossierEditor.js +2 -2
  189. package/components/routes/dossiers/DossierEditor.test.js +1 -1
  190. package/components/routes/help/ApiDocumentation.js +1 -1
  191. package/components/routes/help/HitBannerDocumentation.js +1 -0
  192. package/components/routes/help/HitDocumentation.js +1 -3
  193. package/components/routes/hits/search/InformationPane.d.ts +1 -0
  194. package/components/routes/hits/search/InformationPane.js +47 -60
  195. package/components/routes/hits/search/LayoutSettings.js +3 -3
  196. package/components/routes/hits/search/QuerySettings.js +2 -1
  197. package/components/routes/hits/search/QuerySettings.test.js +14 -9
  198. package/components/routes/hits/search/{HitBrowser.js → RecordBrowser.js} +9 -9
  199. package/components/routes/hits/search/{HitQuery.d.ts → RecordQuery.d.ts} +2 -2
  200. package/components/routes/hits/search/{HitQuery.js → RecordQuery.js} +6 -6
  201. package/components/routes/hits/search/SearchPane.js +26 -49
  202. package/components/routes/hits/search/ViewLink.js +3 -3
  203. package/components/routes/hits/search/ViewLink.test.js +8 -8
  204. package/components/routes/hits/search/grid/AddColumnModal.js +5 -4
  205. package/components/routes/hits/search/grid/EnhancedCell.d.ts +2 -1
  206. package/components/routes/hits/search/grid/EnhancedCell.js +2 -2
  207. package/components/routes/hits/search/grid/HitGrid.js +20 -18
  208. package/components/routes/hits/search/grid/{HitRow.d.ts → RecordRow.d.ts} +3 -2
  209. package/components/routes/hits/search/grid/{HitRow.js → RecordRow.js} +10 -8
  210. package/components/routes/hits/search/shared/IndexPicker.d.ts +2 -0
  211. package/components/routes/hits/search/shared/IndexPicker.js +20 -0
  212. package/components/routes/hits/view/HitViewer.js +12 -13
  213. package/components/routes/home/ViewCard.js +47 -41
  214. package/components/routes/observables/ObservableViewer.d.ts +7 -0
  215. package/components/routes/observables/ObservableViewer.js +27 -0
  216. package/components/routes/overviews/OverviewViewer.js +2 -2
  217. package/components/routes/views/ViewComposer.js +46 -19
  218. package/locales/en/translation.json +100 -3
  219. package/locales/fr/translation.json +98 -3
  220. package/models/WithMetadata.d.ts +2 -1
  221. package/models/entities/generated/AttachmentsFile.d.ts +12 -0
  222. package/models/entities/generated/Case.d.ts +28 -0
  223. package/models/entities/generated/DestinationOriginal.d.ts +19 -0
  224. package/models/entities/generated/EmailAttachment.d.ts +8 -0
  225. package/models/entities/generated/EmailParent.d.ts +19 -0
  226. package/models/entities/generated/Enrichments.d.ts +7 -0
  227. package/models/entities/generated/EnrichmentsIndicator.d.ts +21 -0
  228. package/models/entities/generated/Hit.d.ts +1 -0
  229. package/models/entities/generated/Howler.d.ts +0 -4
  230. package/models/entities/generated/HttpResponse.d.ts +11 -0
  231. package/models/entities/generated/Item.d.ts +9 -0
  232. package/models/entities/generated/Observable.d.ts +85 -0
  233. package/models/entities/generated/ObservableCloud.d.ts +20 -0
  234. package/models/entities/generated/ObservableDestination.d.ts +23 -0
  235. package/models/entities/generated/ObservableEmail.d.ts +30 -0
  236. package/models/entities/generated/ObservableFile.d.ts +36 -0
  237. package/models/entities/generated/ObservableHowler.d.ts +43 -0
  238. package/models/entities/generated/ObservableHttp.d.ts +11 -0
  239. package/models/entities/generated/ObservableObserver.d.ts +21 -0
  240. package/models/entities/generated/ObservableOrganization.d.ts +7 -0
  241. package/models/entities/generated/ObservableProcess.d.ts +34 -0
  242. package/models/entities/generated/ObservableSource.d.ts +23 -0
  243. package/models/entities/generated/ObservableThreat.d.ts +21 -0
  244. package/models/entities/generated/ObservableTls.d.ts +12 -0
  245. package/models/entities/generated/ObserverIngress.d.ts +9 -0
  246. package/models/entities/generated/Rule.d.ts +2 -10
  247. package/models/entities/generated/Task.d.ts +10 -0
  248. package/models/entities/generated/Threat.d.ts +2 -2
  249. package/models/entities/generated/{Enrichment.d.ts → ThreatEnrichment.d.ts} +1 -1
  250. package/models/entities/generated/View.d.ts +1 -0
  251. package/package.json +18 -1
  252. package/plugins/clue/components/ClueTypography.js +2 -2
  253. package/plugins/clue/utils.d.ts +2 -1
  254. package/tests/mocks.d.ts +11 -1
  255. package/tests/mocks.js +12 -7
  256. package/tests/server-handlers.js +6 -1
  257. package/tests/utils.d.ts +4 -0
  258. package/tests/utils.js +20 -0
  259. package/utils/constants.d.ts +3 -3
  260. package/utils/hitFunctions.d.ts +2 -1
  261. package/utils/hitFunctions.js +4 -4
  262. package/utils/typeUtils.d.ts +7 -0
  263. package/utils/typeUtils.js +27 -0
  264. package/utils/viewUtils.js +3 -0
  265. package/components/app/providers/HitProvider.d.ts +0 -22
  266. package/components/elements/display/icons/BundleButton.d.ts +0 -6
  267. package/components/elements/display/icons/BundleButton.js +0 -32
  268. package/components/elements/hit/HitRelated.d.ts +0 -6
  269. package/components/elements/hit/HitRelated.js +0 -7
  270. package/components/routes/help/BundleDocumentation.d.ts +0 -3
  271. package/components/routes/help/BundleDocumentation.js +0 -12
  272. package/components/routes/help/markdown/en/bundles.md.js +0 -1
  273. package/components/routes/help/markdown/fr/bundles.md.js +0 -1
  274. package/components/routes/hits/search/BundleParentMenu.d.ts +0 -6
  275. package/components/routes/hits/search/BundleParentMenu.js +0 -32
  276. package/components/routes/hits/search/BundleScroller.d.ts +0 -2
  277. package/components/routes/hits/search/BundleScroller.js +0 -6
  278. package/components/routes/hits/search/HitContextMenu.js +0 -227
  279. /package/components/app/providers/{HitSearchProvider.test.d.ts → RecordSearchProvider.test.d.ts} +0 -0
  280. /package/components/{routes/hits/search/HitContextMenu.test.d.ts → elements/ContextMenu.test.d.ts} +0 -0
  281. /package/components/{routes/overviews/OverviewEditor.d.ts → elements/MarkdownEditor.d.ts} +0 -0
  282. /package/components/elements/hit/{HitDetails.d.ts → related/RelatedRecords.d.ts} +0 -0
  283. /package/components/routes/hits/search/{HitBrowser.d.ts → RecordBrowser.d.ts} +0 -0
@@ -0,0 +1,384 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /// <reference types="vitest" />
3
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
4
+ import userEvent, {} from '@testing-library/user-event';
5
+ import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
6
+ import { ModalContext } from '@cccsaurora/howler-ui/components/app/providers/ModalProvider';
7
+ import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
8
+ import i18n from '@cccsaurora/howler-ui/i18n';
9
+ import {} from 'react';
10
+ import { I18nextProvider } from 'react-i18next';
11
+ import { MemoryRouter } from 'react-router-dom';
12
+ import { createMockCase, createMockHit } from '@cccsaurora/howler-ui/tests/utils';
13
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
14
+ import ResolveModal from './ResolveModal';
15
+ // ---------------------------------------------------------------------------
16
+ // Hoisted mocks
17
+ // ---------------------------------------------------------------------------
18
+ const mockAssess = vi.hoisted(() => vi.fn().mockResolvedValue(undefined));
19
+ const mockDispatchApi = vi.hoisted(() => vi.fn());
20
+ const mockUpdateCase = vi.hoisted(() => vi.fn().mockResolvedValue(undefined));
21
+ const mockClose = vi.hoisted(() => vi.fn());
22
+ const mockLoadRecords = vi.hoisted(() => vi.fn());
23
+ vi.mock('components/hooks/useMyApi', () => ({
24
+ default: () => ({ dispatchApi: mockDispatchApi })
25
+ }));
26
+ vi.mock('components/hooks/useHitActions', () => ({
27
+ default: () => ({ assess: mockAssess })
28
+ }));
29
+ vi.mock('../hooks/useCase', () => ({
30
+ default: () => ({ update: mockUpdateCase })
31
+ }));
32
+ vi.mock('components/elements/hit/elements/AnalyticLink', () => ({
33
+ default: ({ hit }) => _jsx("span", { "data-testid": `analytic-${hit.howler.id}`, children: hit.howler.analytic })
34
+ }));
35
+ vi.mock('components/elements/hit/elements/EscalationChip', () => ({
36
+ default: () => null
37
+ }));
38
+ vi.mock('components/elements/hit/HitCard', () => ({
39
+ default: ({ id }) => _jsx("div", { children: `HitCard:${id}` })
40
+ }));
41
+ vi.mock('api', () => ({
42
+ default: {
43
+ search: {
44
+ hit: {
45
+ post: (params) => params
46
+ }
47
+ }
48
+ }
49
+ }));
50
+ vi.mock('commons/components/app/hooks/useAppUser', () => ({
51
+ useAppUser: () => ({ user: { username: 'test-user' } })
52
+ }));
53
+ // ---------------------------------------------------------------------------
54
+ // Fixtures
55
+ // ---------------------------------------------------------------------------
56
+ const mockConfig = {
57
+ lookups: {
58
+ 'howler.assessment': ['legitimate', 'false_positive', 'non_issue']
59
+ }
60
+ };
61
+ const makeUnresolvedHit = (id) => createMockHit({ howler: { id, status: 'open', analytic: `analytic-${id}` } });
62
+ const makeResolvedHit = (id) => createMockHit({ howler: { id, status: 'resolved', analytic: `analytic-${id}` } });
63
+ const HIT_1 = makeUnresolvedHit('hit-1');
64
+ const HIT_2 = makeUnresolvedHit('hit-2');
65
+ const HIT_RESOLVED = makeResolvedHit('hit-resolved');
66
+ const caseWithHits = (hitIds) => createMockCase({
67
+ case_id: 'case-1',
68
+ items: hitIds.map(id => ({ type: 'hit', value: id }))
69
+ });
70
+ // ---------------------------------------------------------------------------
71
+ // Wrapper factory
72
+ // ---------------------------------------------------------------------------
73
+ const createWrapper = (records = {}) => {
74
+ const Wrapper = ({ children }) => (_jsx(I18nextProvider, { i18n: i18n, children: _jsx(ModalContext.Provider, { value: { close: mockClose, open: vi.fn(), setContent: vi.fn() }, children: _jsx(ApiConfigContext.Provider, { value: { config: mockConfig, setConfig: vi.fn() }, children: _jsx(RecordContext.Provider, { value: { records, loadRecords: mockLoadRecords }, children: _jsx(MemoryRouter, { children: children }) }) }) }) }));
75
+ return Wrapper;
76
+ };
77
+ // ---------------------------------------------------------------------------
78
+ // Helpers
79
+ // ---------------------------------------------------------------------------
80
+ /**
81
+ * Clicks a MUI Checkbox — operates on the ButtonBase parent of the hidden input
82
+ * so that user-event's special checkbox-input code path is bypassed and the
83
+ * component's own onClick handler fires correctly.
84
+ */
85
+ const clickCheckbox = (checkbox) => {
86
+ fireEvent.click(checkbox.parentElement);
87
+ };
88
+ /** Fills in assessment and rationale so the confirm button becomes enabled. */
89
+ const fillForm = async (user, assessment = 'legitimate', rationale = 'Test rationale') => {
90
+ const rationaleInput = screen.getByPlaceholderText(i18n.t('modal.rationale.label'));
91
+ await user.clear(rationaleInput);
92
+ await user.type(rationaleInput, rationale);
93
+ // Typing into the combobox triggers MUI Autocomplete's onInputChange which
94
+ // opens the listbox — a plain click does not reliably open it in jsdom.
95
+ // The combobox has no accessible label (only a placeholder), so we query by
96
+ // role alone — there is exactly one combobox in the modal.
97
+ const assessmentInput = screen.getByRole('combobox');
98
+ await user.type(assessmentInput, assessment.slice(0, 3));
99
+ const option = await screen.findByRole('option', { name: assessment });
100
+ await user.click(option);
101
+ };
102
+ // ---------------------------------------------------------------------------
103
+ // Tests
104
+ // ---------------------------------------------------------------------------
105
+ describe('ResolveModal', () => {
106
+ let user;
107
+ let mockOnConfirm;
108
+ beforeEach(() => {
109
+ user = userEvent.setup();
110
+ mockOnConfirm = vi.fn();
111
+ vi.clearAllMocks();
112
+ // Default: resolve immediately with an empty items list
113
+ mockDispatchApi.mockResolvedValue({ items: [] });
114
+ });
115
+ // -------------------------------------------------------------------------
116
+ // Initial render
117
+ // -------------------------------------------------------------------------
118
+ describe('initial render', () => {
119
+ it('shows the modal title', () => {
120
+ render(_jsx(ResolveModal, { case: caseWithHits([]), onConfirm: mockOnConfirm }), {
121
+ wrapper: createWrapper()
122
+ });
123
+ expect(screen.getByText(i18n.t('modal.cases.resolve'))).toBeInTheDocument();
124
+ });
125
+ it('shows the modal description', () => {
126
+ render(_jsx(ResolveModal, { case: caseWithHits([]), onConfirm: mockOnConfirm }), {
127
+ wrapper: createWrapper()
128
+ });
129
+ expect(screen.getByText(i18n.t('modal.cases.resolve.description'))).toBeInTheDocument();
130
+ });
131
+ it('renders a cancel button', () => {
132
+ render(_jsx(ResolveModal, { case: caseWithHits([]), onConfirm: mockOnConfirm }), {
133
+ wrapper: createWrapper()
134
+ });
135
+ expect(screen.getByRole('button', { name: i18n.t('cancel') })).toBeInTheDocument();
136
+ });
137
+ it('renders a confirm button', () => {
138
+ render(_jsx(ResolveModal, { case: caseWithHits([]), onConfirm: mockOnConfirm }), {
139
+ wrapper: createWrapper()
140
+ });
141
+ expect(screen.getByRole('button', { name: i18n.t('confirm') })).toBeInTheDocument();
142
+ });
143
+ it('shows the "Resolved Alerts" accordion', () => {
144
+ render(_jsx(ResolveModal, { case: caseWithHits([]), onConfirm: mockOnConfirm }), {
145
+ wrapper: createWrapper()
146
+ });
147
+ expect(screen.getByText(i18n.t('modal.cases.alerts.resolved'))).toBeInTheDocument();
148
+ });
149
+ });
150
+ // -------------------------------------------------------------------------
151
+ // Loading state
152
+ // -------------------------------------------------------------------------
153
+ describe('loading state', () => {
154
+ it('shows a LinearProgress while the API call is pending', () => {
155
+ mockDispatchApi.mockReturnValue(new Promise(() => { })); // never resolves
156
+ const { container } = render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
157
+ wrapper: createWrapper()
158
+ });
159
+ const progress = container.querySelector('.MuiLinearProgress-root');
160
+ expect(progress).toBeInTheDocument();
161
+ // opacity is 1 while loading
162
+ expect(progress).toHaveStyle({ opacity: '1' });
163
+ });
164
+ it('disables the confirm button while loading', () => {
165
+ mockDispatchApi.mockReturnValue(new Promise(() => { }));
166
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
167
+ wrapper: createWrapper()
168
+ });
169
+ expect(screen.getByRole('button', { name: i18n.t('confirm') })).toBeDisabled();
170
+ });
171
+ it('shows a CircularProgress inside the confirm button while loading', () => {
172
+ mockDispatchApi.mockReturnValue(new Promise(() => { }));
173
+ const { container } = render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
174
+ wrapper: createWrapper()
175
+ });
176
+ expect(container.querySelector('.MuiCircularProgress-root')).toBeInTheDocument();
177
+ });
178
+ it('fades out the LinearProgress after loading completes', async () => {
179
+ const { container } = render(_jsx(ResolveModal, { case: caseWithHits([]), onConfirm: mockOnConfirm }), {
180
+ wrapper: createWrapper()
181
+ });
182
+ await waitFor(() => {
183
+ const progress = container.querySelector('.MuiLinearProgress-root');
184
+ expect(progress).toHaveStyle({ opacity: '0' });
185
+ });
186
+ });
187
+ });
188
+ // -------------------------------------------------------------------------
189
+ // Hit rendering
190
+ // -------------------------------------------------------------------------
191
+ describe('hit rendering', () => {
192
+ it('renders unresolved hits with checkboxes after loading', async () => {
193
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1', 'hit-2']), onConfirm: mockOnConfirm }), {
194
+ wrapper: createWrapper({ 'hit-1': HIT_1, 'hit-2': HIT_2 })
195
+ });
196
+ await waitFor(() => {
197
+ expect(screen.getAllByRole('checkbox')).toHaveLength(2);
198
+ });
199
+ });
200
+ it('does not show a checkbox for resolved hits', async () => {
201
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1', 'hit-resolved']), onConfirm: mockOnConfirm }), {
202
+ wrapper: createWrapper({ 'hit-1': HIT_1, 'hit-resolved': HIT_RESOLVED })
203
+ });
204
+ await waitFor(() => {
205
+ // only the single unresolved hit gets a checkbox
206
+ expect(screen.getAllByRole('checkbox')).toHaveLength(1);
207
+ });
208
+ });
209
+ it('calls loadRecords with the API search results', async () => {
210
+ const items = [HIT_1];
211
+ mockDispatchApi.mockResolvedValueOnce({ items });
212
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
213
+ wrapper: createWrapper()
214
+ });
215
+ await waitFor(() => {
216
+ expect(mockLoadRecords).toHaveBeenCalledWith(items);
217
+ });
218
+ });
219
+ });
220
+ // -------------------------------------------------------------------------
221
+ // Confirm button disabled states
222
+ // -------------------------------------------------------------------------
223
+ describe('confirm button enablement', () => {
224
+ it('is disabled when no hits are selected', async () => {
225
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
226
+ wrapper: createWrapper({ 'hit-1': HIT_1 })
227
+ });
228
+ // Wait for the hit to load (checkbox appears) but don't select it
229
+ await screen.findAllByRole('checkbox');
230
+ await fillForm(user);
231
+ // No hit selected → selectedHitIds.size === 0 → button still disabled
232
+ expect(screen.getByRole('button', { name: i18n.t('confirm') })).toBeDisabled();
233
+ });
234
+ it('is disabled when assessment is missing', async () => {
235
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
236
+ wrapper: createWrapper({ 'hit-1': HIT_1 })
237
+ });
238
+ const [checkbox] = await screen.findAllByRole('checkbox');
239
+ clickCheckbox(checkbox);
240
+ await waitFor(() => expect(checkbox).toBeChecked());
241
+ const rationaleInput = screen.getByPlaceholderText(i18n.t('modal.rationale.label'));
242
+ await user.type(rationaleInput, 'some reason');
243
+ // no assessment chosen → still disabled
244
+ expect(screen.getByRole('button', { name: i18n.t('confirm') })).toBeDisabled();
245
+ });
246
+ it('is disabled when rationale is empty', async () => {
247
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
248
+ wrapper: createWrapper({ 'hit-1': HIT_1 })
249
+ });
250
+ const [checkbox] = await screen.findAllByRole('checkbox');
251
+ clickCheckbox(checkbox);
252
+ await waitFor(() => expect(checkbox).toBeChecked());
253
+ // fill only assessment, no rationale
254
+ const assessmentInput = screen.getByRole('combobox');
255
+ await user.type(assessmentInput, 'leg');
256
+ const option = await screen.findByRole('option', { name: 'legitimate' });
257
+ await user.click(option);
258
+ expect(screen.getByRole('button', { name: i18n.t('confirm') })).toBeDisabled();
259
+ });
260
+ it('is enabled when a hit is selected + assessment + rationale are provided', async () => {
261
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
262
+ wrapper: createWrapper({ 'hit-1': HIT_1 })
263
+ });
264
+ const [checkbox] = await screen.findAllByRole('checkbox');
265
+ clickCheckbox(checkbox);
266
+ await waitFor(() => expect(checkbox).toBeChecked());
267
+ await fillForm(user);
268
+ expect(screen.getByRole('button', { name: i18n.t('confirm') })).toBeEnabled();
269
+ });
270
+ });
271
+ // -------------------------------------------------------------------------
272
+ // Checkbox / selection toggling
273
+ // -------------------------------------------------------------------------
274
+ describe('hit selection', () => {
275
+ it('checks a hit when its checkbox is clicked', async () => {
276
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
277
+ wrapper: createWrapper({ 'hit-1': HIT_1 })
278
+ });
279
+ const [checkbox] = await screen.findAllByRole('checkbox');
280
+ expect(checkbox).not.toBeChecked();
281
+ clickCheckbox(checkbox);
282
+ await waitFor(() => expect(checkbox).toBeChecked());
283
+ });
284
+ it('unchecks a hit when its checkbox is clicked a second time', async () => {
285
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
286
+ wrapper: createWrapper({ 'hit-1': HIT_1 })
287
+ });
288
+ const [checkbox] = await screen.findAllByRole('checkbox');
289
+ clickCheckbox(checkbox);
290
+ await waitFor(() => expect(checkbox).toBeChecked());
291
+ clickCheckbox(checkbox);
292
+ await waitFor(() => expect(checkbox).not.toBeChecked());
293
+ });
294
+ it('tracks each hit independently when there are multiple', async () => {
295
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1', 'hit-2']), onConfirm: mockOnConfirm }), {
296
+ wrapper: createWrapper({ 'hit-1': HIT_1, 'hit-2': HIT_2 })
297
+ });
298
+ const checkboxes = await screen.findAllByRole('checkbox');
299
+ expect(checkboxes).toHaveLength(2);
300
+ clickCheckbox(checkboxes[0]);
301
+ await waitFor(() => {
302
+ expect(checkboxes[0]).toBeChecked();
303
+ expect(checkboxes[1]).not.toBeChecked();
304
+ });
305
+ });
306
+ });
307
+ // -------------------------------------------------------------------------
308
+ // Confirm action
309
+ // -------------------------------------------------------------------------
310
+ describe('confirm action', () => {
311
+ it('calls assess with the chosen assessment, true, and rationale', async () => {
312
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
313
+ wrapper: createWrapper({ 'hit-1': HIT_1 })
314
+ });
315
+ const [checkbox] = await screen.findAllByRole('checkbox');
316
+ clickCheckbox(checkbox);
317
+ await waitFor(() => expect(checkbox).toBeChecked());
318
+ await fillForm(user, 'legitimate', 'My rationale');
319
+ await user.click(screen.getByRole('button', { name: i18n.t('confirm') }));
320
+ await waitFor(() => {
321
+ expect(mockAssess).toHaveBeenCalledWith('legitimate', true, 'My rationale');
322
+ });
323
+ });
324
+ it('clears the selection after confirm completes', async () => {
325
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
326
+ wrapper: createWrapper({ 'hit-1': HIT_1 })
327
+ });
328
+ const [checkbox] = await screen.findAllByRole('checkbox');
329
+ clickCheckbox(checkbox);
330
+ await waitFor(() => expect(checkbox).toBeChecked());
331
+ await fillForm(user);
332
+ await user.click(screen.getByRole('button', { name: i18n.t('confirm') }));
333
+ await waitFor(() => {
334
+ expect(checkbox).not.toBeChecked();
335
+ });
336
+ });
337
+ });
338
+ // -------------------------------------------------------------------------
339
+ // Cancel button
340
+ // -------------------------------------------------------------------------
341
+ describe('cancel button', () => {
342
+ it('calls close() when cancel is clicked', async () => {
343
+ // Use a case with an unresolved hit so the auto-resolve effect does not fire
344
+ // and call close() before we even click cancel.
345
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
346
+ wrapper: createWrapper({ 'hit-1': HIT_1 })
347
+ });
348
+ // Wait for loading to finish so no unexpected state transitions happen mid-click
349
+ await screen.findAllByRole('checkbox');
350
+ await user.click(screen.getByRole('button', { name: i18n.t('cancel') }));
351
+ expect(mockClose).toHaveBeenCalledTimes(1);
352
+ });
353
+ });
354
+ // -------------------------------------------------------------------------
355
+ // Auto-resolve when all hits are already resolved
356
+ // -------------------------------------------------------------------------
357
+ describe('auto-resolve', () => {
358
+ it('calls updateCase, onConfirm, and close when no unresolved hits remain after loading', async () => {
359
+ // All hits in the case are already resolved
360
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-resolved']), onConfirm: mockOnConfirm }), {
361
+ wrapper: createWrapper({ 'hit-resolved': HIT_RESOLVED })
362
+ });
363
+ // dispatchApi resolves → loading becomes false → unresolvedHits.length === 0 → auto-close
364
+ await waitFor(() => {
365
+ expect(mockUpdateCase).toHaveBeenCalledWith({ status: 'resolved' });
366
+ });
367
+ await waitFor(() => {
368
+ expect(mockOnConfirm).toHaveBeenCalledTimes(1);
369
+ expect(mockClose).toHaveBeenCalledTimes(1);
370
+ });
371
+ });
372
+ it('does NOT auto-resolve while hits are still unresolved', async () => {
373
+ render(_jsx(ResolveModal, { case: caseWithHits(['hit-1']), onConfirm: mockOnConfirm }), {
374
+ wrapper: createWrapper({ 'hit-1': HIT_1 })
375
+ });
376
+ // Wait for loading to complete: the unresolved hit's checkbox appears once the
377
+ // dispatchApi call settles and the LinearProgress opacity drops to 0.
378
+ await screen.findAllByRole('checkbox');
379
+ // Should not have auto-resolved
380
+ expect(mockUpdateCase).not.toHaveBeenCalled();
381
+ expect(mockClose).not.toHaveBeenCalled();
382
+ });
383
+ });
384
+ });
@@ -0,0 +1,7 @@
1
+ import type { Case } from '@cccsaurora/howler-ui/models/entities/generated/Case';
2
+ import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
3
+ import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
4
+ import type { RecordEntry } from './types';
5
+ export declare const defaultTitle: (record: Hit | Observable) => string;
6
+ export declare const useFolderOptions: (selectedCase: Case | null) => string[];
7
+ export declare const useRecordEntries: (records: (Hit | Observable)[]) => readonly [RecordEntry[], (index: number, field: "title" | "path", value: string) => void];
@@ -0,0 +1,44 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+ export const defaultTitle = (record) => {
3
+ if (record.__index === 'hit') {
4
+ return `${record.howler.analytic} (${record.howler.id})`;
5
+ }
6
+ return `Observable (${record.howler.id})`;
7
+ };
8
+ export const useFolderOptions = (selectedCase) => {
9
+ return useMemo(() => {
10
+ if (!selectedCase?.items) {
11
+ return [];
12
+ }
13
+ const paths = new Set();
14
+ for (const item of selectedCase.items) {
15
+ if (!item.path) {
16
+ continue;
17
+ }
18
+ const parts = item.path.split('/');
19
+ parts.pop();
20
+ for (let i = 1; i <= parts.length; i++) {
21
+ paths.add(parts.slice(0, i).join('/'));
22
+ }
23
+ }
24
+ return Array.from(paths);
25
+ }, [selectedCase]);
26
+ };
27
+ // ---------------------------------------------------------------------------
28
+ // Hook
29
+ // ---------------------------------------------------------------------------
30
+ export const useRecordEntries = (records) => {
31
+ const [entries, setEntries] = useState(() => (records ?? []).map(record => ({
32
+ record,
33
+ path: '',
34
+ title: defaultTitle(record)
35
+ })));
36
+ const updateEntry = useCallback((index, field, value) => {
37
+ setEntries(prev => {
38
+ const next = [...prev];
39
+ next[index] = { ...next[index], [field]: value };
40
+ return next;
41
+ });
42
+ }, []);
43
+ return [entries, updateEntry];
44
+ };
@@ -0,0 +1,5 @@
1
+ export interface RecordEntry {
2
+ record: Hit | Observable;
3
+ path: string;
4
+ title: string;
5
+ }
@@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
13
13
  import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
14
14
  import { useContextSelector } from 'use-context-selector';
15
15
  import QueryResultText from '../../elements/display/QueryResultText';
16
- import HitQuery from '../hits/search/HitQuery';
16
+ import RecordQuery from '../hits/search/RecordQuery';
17
17
  import LeadForm from './LeadForm';
18
18
  import PivotForm from './PivotForm';
19
19
  const DossierEditor = () => {
@@ -188,6 +188,6 @@ const DossierEditor = () => {
188
188
  fontSize: '0.9em',
189
189
  fontStyle: 'italic',
190
190
  mb: 0.5
191
- }), variant: "body2", children: t('hit.search.prompt') }), _jsx(HitQuery, { disabled: !dossier || loading, onChange: (_val, isDirty) => setSearchDirty(isDirty), triggerSearch: query => setDossier(_dossier => ({ ..._dossier, query })) }), searchTotal >= 0 && _jsx(QueryResultText, { count: searchTotal, query: dossier.query })] }) }), _jsxs(Tabs, { value: tab, onChange: (_ev, value) => setTab(value), children: [_jsx(Tab, { label: t('route.dossiers.manager.tabs.leads'), value: "leads" }), _jsx(Tab, { label: t('route.dossiers.manager.tabs.pivots'), value: "pivots" })] }), tab === 'leads' && _jsx(LeadForm, { dossier: dossier, setDossier: setDossier, loading: loading }), tab === 'pivots' && _jsx(PivotForm, { dossier: dossier, setDossier: setDossier, loading: loading })] })] }) }));
191
+ }), variant: "body2", children: t('hit.search.prompt') }), _jsx(RecordQuery, { disabled: !dossier || loading, onChange: (_val, isDirty) => setSearchDirty(isDirty), triggerSearch: query => setDossier(_dossier => ({ ..._dossier, query })) }), searchTotal >= 0 && _jsx(QueryResultText, { count: searchTotal, query: dossier.query })] }) }), _jsxs(Tabs, { value: tab, onChange: (_ev, value) => setTab(value), children: [_jsx(Tab, { label: t('route.dossiers.manager.tabs.leads'), value: "leads" }), _jsx(Tab, { label: t('route.dossiers.manager.tabs.pivots'), value: "pivots" })] }), tab === 'leads' && _jsx(LeadForm, { dossier: dossier, setDossier: setDossier, loading: loading }), tab === 'pivots' && _jsx(PivotForm, { dossier: dossier, setDossier: setDossier, loading: loading })] })] }) }));
192
192
  };
193
193
  export default memo(DossierEditor);
@@ -61,7 +61,7 @@ vi.mock('commons/components/pages/PageCenter', () => ({
61
61
  vi.mock('../../elements/display/QueryResultText', () => ({
62
62
  default: ({ count, query }) => (_jsxs("div", { id: "query-result-text", children: [count, " ", ' results for ', " ", query] }))
63
63
  }));
64
- vi.mock('../hits/search/HitQuery', () => ({
64
+ vi.mock('../hits/search/RecordQuery', () => ({
65
65
  default: ({ onChange, triggerSearch, disabled }) => {
66
66
  return (_jsxs("div", { id: "hit-query", children: [_jsx("input", { id: "query-input", disabled: disabled, onChange: e => {
67
67
  onChange?.(e.target.value, false);
@@ -49,7 +49,7 @@ const ApiDocumentation = () => {
49
49
  .replace(/(\S+)\s+=>\s+(.+)/g, '\n`$1`: $2\n')
50
50
  .replace(/(Data Block:\n)([\s\S]+)(Result Example:)/, (__, p1, p2, p3) => `${p1}\`\`\`\n${p2.trim()}\n\`\`\`\n${p3}`)
51
51
  .replace(/(Result Example:\n)([\s\S]+)$/, (__, p1, p2) => `${p1}\`\`\`\n${p2.trim()}\n\`\`\``) }));
52
- return (_jsxs(Fragment, { children: [_jsxs(TableRow, { style: { marginBottom: '1rem' }, sx: [isLg && { '& > td': { borderBottom: 0 } }], children: [_jsx(TableCell, { children: _jsxs(Stack, { direction: "column", spacing: 1, alignItems: "start", children: [_jsx(Typography, { children: endpoint.name }), _jsx("code", { children: endpoint.path }), _jsxs(Stack, { direction: "row", spacing: 1, children: [endpoint.complete ? (_jsx(Chip, { size: "small", label: "Stable", color: "success" })) : (_jsx(Chip, { size: "small", label: "Unstable", color: "error" })), endpoint.protected ? (_jsx(Chip, { size: "small", label: "Protected", color: "warning" })) : (_jsx(Chip, { size: "small", label: "Unprotected" }))] }), _jsx(Stack, { spacing: 1, direction: "row", children: endpoint.methods.map(m => (_jsx(Chip, { size: "small", label: m }, m))) }), endpoint.ui_only && _jsx(Chip, { size: "small", label: "UI Only" })] }) }), _jsx(TableCell, { children: _jsx(Stack, { spacing: 1, direction: "row", children: endpoint.required_type.map(type => (_jsx(Chip, { size: "small", label: type, color: user.roles?.includes(type) ? 'success' : 'default' }, type))) }) }), _jsx(TableCell, { children: _jsx(Stack, { spacing: 1, direction: "row", children: endpoint.required_priv.map((p) => (_jsx(Chip, { size: "small", label: t(APIKEY_LABELS[p]) }, p))) }) }), !isLg && _jsx(TableCell, { children: documentationCell })] }), isLg && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 3, sx: { '& pre': { whiteSpace: 'pre-wrap' } }, children: documentationCell }) }))] }, endpoint.id));
52
+ return (_jsxs(Fragment, { children: [_jsxs(TableRow, { style: { marginBottom: '1rem' }, sx: [isLg && { '& > td': { borderBottom: 0 } }], children: [_jsx(TableCell, { children: _jsxs(Stack, { direction: "column", spacing: 1, alignItems: "start", children: [_jsx(Typography, { children: endpoint.name }), _jsx("code", { children: endpoint.path }), _jsxs(Stack, { direction: "row", spacing: 1, children: [endpoint.complete ? (_jsx(Chip, { label: "Stable", color: "success" })) : (_jsx(Chip, { label: "Unstable", color: "error" })), endpoint.protected ? (_jsx(Chip, { label: "Protected", color: "warning" })) : (_jsx(Chip, { label: "Unprotected" }))] }), _jsx(Stack, { spacing: 1, direction: "row", children: endpoint.methods.map(m => (_jsx(Chip, { size: "small", label: m }, m))) }), endpoint.ui_only && _jsx(Chip, { label: "UI Only" })] }) }), _jsx(TableCell, { children: _jsx(Stack, { spacing: 1, direction: "row", children: endpoint.required_type.map(type => (_jsx(Chip, { size: "small", label: type, color: user.roles?.includes(type) ? 'success' : 'default' }, type))) }) }), _jsx(TableCell, { children: _jsx(Stack, { spacing: 1, direction: "row", children: endpoint.required_priv.map((p) => (_jsx(Chip, { size: "small", label: t(APIKEY_LABELS[p]) }, p))) }) }), !isLg && _jsx(TableCell, { children: documentationCell })] }), isLg && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 3, sx: { '& pre': { whiteSpace: 'pre-wrap' } }, children: documentationCell }) }))] }, endpoint.id));
53
53
  }) })] }) })] }) }));
54
54
  };
55
55
  export default ApiDocumentation;
@@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next';
9
9
  const HitBannerDocumentation = () => {
10
10
  const { t } = useTranslation();
11
11
  const dummyHit = useMemo(() => ({
12
+ __index: 'hit',
12
13
  timestamp: '2023-02-11T15:10:31.585826Z',
13
14
  howler: {
14
15
  id: 'howler.id',
@@ -5,7 +5,6 @@ import { useScrollRestoration } from '@cccsaurora/howler-ui/components/hooks/use
5
5
  import { useCallback, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { useSearchParams } from 'react-router-dom';
8
- import BundleDocumentation from './BundleDocumentation';
9
8
  import HelpTabs from './components/HelpTabs';
10
9
  import HitBannerDocumentation from './HitBannerDocumentation';
11
10
  import HitLabelsDocumentation from './HitLabelsDocumentation';
@@ -23,8 +22,7 @@ const HitDocumentation = () => {
23
22
  searchParams.set('tab', _tab);
24
23
  setSearchParams(new URLSearchParams(searchParams));
25
24
  }, [searchParams, setSearchParams]);
26
- return (_jsx(PageCenter, { margin: 4, width: "100%", maxWidth: "1750px", textAlign: "left", children: _jsxs(Stack, { sx: { flexDirection: useHorizontal ? 'column' : 'row', '& h1': { mt: 0 } }, children: [_jsxs(HelpTabs, { value: tab, children: [_jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.schema.title') }), value: "schema", onClick: () => onChange('schema') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.banner.title') }), value: "header", onClick: () => onChange('header') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.bundle.title') }), value: "bundle", onClick: () => onChange('bundle') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.links.title') }), value: "links", onClick: () => onChange('links') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.labels.title') }), value: "labels", onClick: () => onChange('labels') })] }), _jsx(Box, { children: {
27
- bundle: () => _jsx(BundleDocumentation, {}),
25
+ return (_jsx(PageCenter, { margin: 4, width: "100%", maxWidth: "1750px", textAlign: "left", children: _jsxs(Stack, { sx: { flexDirection: useHorizontal ? 'column' : 'row', '& h1': { mt: 0 } }, children: [_jsxs(HelpTabs, { value: tab, children: [_jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.schema.title') }), value: "schema", onClick: () => onChange('schema') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.banner.title') }), value: "header", onClick: () => onChange('header') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.links.title') }), value: "links", onClick: () => onChange('links') }), _jsx(Tab, { label: _jsx(Typography, { variant: "caption", children: t('help.hit.labels.title') }), value: "labels", onClick: () => onChange('labels') })] }), _jsx(Box, { children: {
28
26
  header: () => _jsx(HitBannerDocumentation, {}),
29
27
  links: () => _jsx(HitLinksDocumentation, {}),
30
28
  labels: () => _jsx(HitLabelsDocumentation, {}),
@@ -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;