@oneuptime/common 11.7.2 → 11.7.4

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 (414) hide show
  1. package/Models/AnalyticsModels/KubernetesCostAllocation.ts +103 -0
  2. package/Models/DatabaseModels/AlertEpisodeStateTimeline.ts +1 -0
  3. package/Models/DatabaseModels/AlertStateTimeline.ts +1 -0
  4. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  5. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  6. package/Models/DatabaseModels/Index.ts +5 -0
  7. package/Models/DatabaseModels/LogDropFilter.ts +89 -0
  8. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +13 -3
  9. package/Models/DatabaseModels/RecommendationDismissal.ts +463 -0
  10. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  11. package/Models/DatabaseModels/StatusPageGroup.ts +95 -0
  12. package/Models/DatabaseModels/TableView.ts +40 -0
  13. package/Models/DatabaseModels/TraceDropFilter.ts +89 -0
  14. package/Models/DatabaseModels/WorkflowVariable.ts +5 -1
  15. package/Server/API/AlertAPI.ts +32 -17
  16. package/Server/API/BaseAPI.ts +31 -2
  17. package/Server/API/BaseAnalyticsAPI.ts +36 -2
  18. package/Server/API/CommonAPI.ts +57 -0
  19. package/Server/API/IncidentAPI.ts +60 -33
  20. package/Server/API/IncidentEpisodeAPI.ts +31 -16
  21. package/Server/API/MonitorAPI.ts +30 -0
  22. package/Server/API/MonitorTemplateAPI.ts +8 -0
  23. package/Server/API/ProbeAPI.ts +196 -15
  24. package/Server/API/ProjectAPI.ts +79 -1
  25. package/Server/API/ScheduledMaintenanceAPI.ts +33 -18
  26. package/Server/API/StatusPageAPI.ts +74 -37
  27. package/Server/API/WorkspaceNotificationRuleAPI.ts +34 -4
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.ts +13 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.ts +527 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.ts +31 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.ts +55 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.ts +53 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  34. package/Server/Services/AlertEpisodeService.ts +47 -0
  35. package/Server/Services/AlertService.ts +125 -0
  36. package/Server/Services/BillingService.ts +98 -0
  37. package/Server/Services/DatabaseService.ts +184 -20
  38. package/Server/Services/IncidentEpisodeService.ts +47 -0
  39. package/Server/Services/IncidentService.ts +123 -0
  40. package/Server/Services/IncidentTemplateService.ts +123 -1
  41. package/Server/Services/Index.ts +2 -0
  42. package/Server/Services/LogDropFilterService.ts +133 -0
  43. package/Server/Services/MonitorProbeService.ts +94 -4
  44. package/Server/Services/MonitorService.ts +48 -43
  45. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +70 -0
  46. package/Server/Services/ProbeService.ts +66 -0
  47. package/Server/Services/ProjectService.ts +131 -7
  48. package/Server/Services/RecommendationDismissalService.ts +52 -0
  49. package/Server/Services/RunbookAgentJobService.ts +68 -7
  50. package/Server/Services/ScheduledMaintenanceService.ts +103 -0
  51. package/Server/Services/ScheduledMaintenanceTemplateService.ts +90 -0
  52. package/Server/Services/StatusPageGroupService.ts +186 -0
  53. package/Server/Services/TraceDropFilterService.ts +133 -0
  54. package/Server/Types/Workflow/Components/API/Delete.ts +12 -0
  55. package/Server/Types/Workflow/Components/API/Get.ts +12 -0
  56. package/Server/Types/Workflow/Components/API/Patch.ts +12 -0
  57. package/Server/Types/Workflow/Components/API/Post.ts +12 -0
  58. package/Server/Types/Workflow/Components/API/Put.ts +13 -0
  59. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +212 -0
  60. package/Server/Utils/DropFilterValidation.ts +127 -0
  61. package/Server/Utils/Errors.ts +5 -0
  62. package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
  63. package/Server/Utils/Monitor/MonitorIncident.ts +31 -2
  64. package/Server/Utils/Telemetry/AppMetrics.ts +31 -0
  65. package/Tests/Models/AnalyticsModels/KubernetesCostAllocation.test.ts +16 -0
  66. package/Tests/Models/DatabaseModels/MonitorProbeColumnAccessControl.test.ts +196 -0
  67. package/Tests/Models/RecommendationDismissalModel.test.ts +269 -0
  68. package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +320 -0
  69. package/Tests/Server/API/BaseAnalyticsAPIUpdatePayloadValidation.test.ts +293 -0
  70. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +164 -0
  71. package/Tests/Server/API/ProbeAPI.test.ts +422 -53
  72. package/Tests/Server/API/ProjectAPI.test.ts +180 -0
  73. package/Tests/Server/API/ProjectAPIExtendTrialBillingDisabled.test.ts +138 -0
  74. package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +458 -0
  75. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +359 -0
  76. package/Tests/Server/Infrastructure/ProcessSignalDelivery.test.ts +427 -0
  77. package/Tests/Server/Services/BillingServiceExtendTrial.test.ts +439 -0
  78. package/Tests/Server/Services/CrossProjectReferenceWriteGuard.test.ts +810 -0
  79. package/Tests/Server/Services/DatabaseServiceAtomicAddToColumns.test.ts +269 -0
  80. package/Tests/Server/Services/DatabaseServiceEmptyUpdate.test.ts +277 -0
  81. package/Tests/Server/Services/DatabaseServiceSortColumnSelect.test.ts +201 -0
  82. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +81 -23
  83. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +398 -0
  84. package/Tests/Server/Services/MonitorProbeDeleteHooks.test.ts +280 -0
  85. package/Tests/Server/Services/MonitorProbeServiceProbeTenancy.test.ts +304 -0
  86. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +262 -0
  87. package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +614 -0
  88. package/Tests/Server/Services/RecommendationDismissalService.test.ts +306 -0
  89. package/Tests/Server/Services/RepairCrossProjectIncidentReferencesMigration.test.ts +528 -0
  90. package/Tests/Server/Services/StatusPageGroupNesting.test.ts +395 -0
  91. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +26 -21
  92. package/Tests/Server/Types/Database/Permissions/WorkflowVariableColumnPermission.test.ts +227 -0
  93. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +285 -0
  94. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +463 -0
  95. package/Tests/Server/Utils/DropFilterValidation.test.ts +269 -0
  96. package/Tests/Server/Utils/Telemetry/SpanUtil.test.ts +94 -0
  97. package/Tests/Types/DateUserTimezone.test.ts +52 -0
  98. package/Tests/Types/JSONFunctions.test.ts +103 -1
  99. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +177 -9
  100. package/Tests/Types/Monitor/MonitorStepDnsMonitor.test.ts +105 -0
  101. package/Tests/Types/Monitor/MonitorStepDnssecMonitor.test.ts +131 -0
  102. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +60 -0
  103. package/Tests/Types/Monitor/MonitorStepExternalStatusPageMonitor.test.ts +82 -0
  104. package/Tests/Types/Monitor/MonitorStepInfrastructureMonitors.test.ts +268 -0
  105. package/Tests/Types/Monitor/MonitorStepMetricMonitor.test.ts +386 -0
  106. package/Tests/Types/Monitor/MonitorStepProfileMonitor.test.ts +185 -0
  107. package/Tests/Types/Monitor/MonitorStepSnmpMonitor.test.ts +156 -0
  108. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +371 -0
  109. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +901 -0
  110. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +607 -0
  111. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +754 -0
  112. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +960 -0
  113. package/Tests/Types/RollingTime/RollingTimeUtil.test.ts +186 -0
  114. package/Tests/Types/Runbook/RunbookStepTimeout.test.ts +410 -0
  115. package/Tests/Types/Telemetry/DropFilterSampling.test.ts +241 -0
  116. package/Tests/Types/Workspace/NotificationRuleConditionUtil.test.ts +353 -0
  117. package/Tests/Types/Workspace/NotificationRuleUtil.test.ts +566 -0
  118. package/Tests/UI/Components/CodeEditor.test.tsx +174 -0
  119. package/Tests/UI/Components/ErrorBoundary.test.tsx +555 -24
  120. package/Tests/UI/Components/InfoCard.test.tsx +358 -0
  121. package/Tests/UI/Components/LogTimeRangePicker.test.tsx +138 -0
  122. package/Tests/UI/Components/Modal.test.tsx +43 -5
  123. package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +679 -0
  124. package/Tests/UI/Components/ModelTable/BaseModelTableSearchLabels.test.tsx +397 -0
  125. package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +999 -0
  126. package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +1678 -0
  127. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1094 -0
  128. package/Tests/UI/Components/Runbook/StepTimeoutInput.test.tsx +386 -0
  129. package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +671 -0
  130. package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +404 -0
  131. package/Tests/UI/Utils/DropdownAlignment.test.ts +306 -0
  132. package/Tests/UI/Utils/TableFilterUrlState.test.ts +292 -0
  133. package/Tests/Utils/Monitor/MonitorSummaryProbeUtil.test.ts +263 -0
  134. package/Tests/Utils/NetworkDiscovery/ScanTargetUtil.test.ts +583 -0
  135. package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +1072 -0
  136. package/Tests/Utils/StatusPage/GroupTree.test.ts +583 -0
  137. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +411 -0
  138. package/Tests/Utils/UserPreferences.test.ts +563 -0
  139. package/Types/Date.ts +15 -6
  140. package/Types/JSONFunctions.ts +7 -2
  141. package/Types/Kubernetes/KubernetesCostIngest.ts +37 -1
  142. package/Types/Monitor/KubernetesAlertTemplates.ts +205 -0
  143. package/Types/Monitor/MonitorStep.ts +10 -1
  144. package/Types/Monitor/MonitorStepMetricMonitor.ts +46 -1
  145. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +413 -0
  146. package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +168 -0
  147. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +175 -0
  148. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +743 -0
  149. package/Types/Permission.ts +45 -0
  150. package/Types/Recommendation/RecommendationType.ts +20 -0
  151. package/Types/Runbook/RunbookExecutionDeadline.ts +129 -0
  152. package/Types/Runbook/RunbookStepTimeout.ts +202 -0
  153. package/Types/Telemetry/DropFilterSampling.ts +92 -0
  154. package/UI/Components/AI/GenerateFromAIModal.tsx +0 -2
  155. package/UI/Components/Accordion/Accordion.tsx +10 -2
  156. package/UI/Components/BulkUpdate/BulkLabelActions.tsx +0 -1
  157. package/UI/Components/CodeEditor/CodeEditor.tsx +34 -20
  158. package/UI/Components/Date/RangeStartAndEndDateView.tsx +0 -1
  159. package/UI/Components/EditionLabel/EditionLabel.tsx +1 -27
  160. package/UI/Components/Error/PageError.tsx +0 -4
  161. package/UI/Components/ErrorBoundary.tsx +252 -15
  162. package/UI/Components/InfoCard/InfoCard.tsx +58 -1
  163. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +34 -1
  164. package/UI/Components/MasterPage/MasterPage.tsx +65 -0
  165. package/UI/Components/Modal/Modal.tsx +16 -60
  166. package/UI/Components/ModelFormModal/ModelFormModal.tsx +0 -8
  167. package/UI/Components/ModelTable/BaseModelTable.tsx +503 -71
  168. package/UI/Components/ModelTable/Column.ts +17 -0
  169. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +523 -0
  170. package/UI/Components/ModelTable/ColumnPreference.ts +482 -0
  171. package/UI/Components/ModelTable/CustomFieldColumns.tsx +326 -0
  172. package/UI/Components/ModelTable/TableView.tsx +24 -2
  173. package/UI/Components/ModelTable/useCustomFieldColumns.ts +150 -0
  174. package/UI/Components/Runbook/StepTimeoutInput.tsx +173 -0
  175. package/UI/Components/SideMenu/SideMenu.tsx +24 -4
  176. package/UI/Components/StatusPage/ResourceGroupSection.tsx +231 -0
  177. package/UI/Components/Table/Table.tsx +14 -1
  178. package/UI/Components/Table/TableRow.tsx +180 -175
  179. package/UI/Components/Table/Types/Column.ts +2 -0
  180. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +34 -1
  181. package/UI/Utils/DropdownAlignment.ts +162 -0
  182. package/UI/Utils/TableFilterUrlState.ts +59 -20
  183. package/Utils/API.ts +106 -2
  184. package/Utils/Monitor/MonitorSummaryProbeUtil.ts +130 -0
  185. package/Utils/NetworkDiscovery/ScanTargetUtil.ts +434 -0
  186. package/Utils/StatusPage/GroupNestingLayout.ts +343 -0
  187. package/Utils/StatusPage/GroupTree.ts +307 -0
  188. package/Utils/StatusPage/ResourceUptime.ts +90 -17
  189. package/Utils/UserPreferences.ts +53 -0
  190. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js +86 -0
  191. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js +1 -0
  193. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js +1 -0
  195. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js.map +1 -1
  196. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  197. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  199. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  200. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  201. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  202. package/build/dist/Models/DatabaseModels/LogDropFilter.js +91 -0
  203. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +14 -4
  205. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  206. package/build/dist/Models/DatabaseModels/RecommendationDismissal.js +484 -0
  207. package/build/dist/Models/DatabaseModels/RecommendationDismissal.js.map +1 -0
  208. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  209. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  210. package/build/dist/Models/DatabaseModels/StatusPageGroup.js +94 -0
  211. package/build/dist/Models/DatabaseModels/StatusPageGroup.js.map +1 -1
  212. package/build/dist/Models/DatabaseModels/TableView.js +40 -0
  213. package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
  214. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +91 -0
  215. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  216. package/build/dist/Models/DatabaseModels/WorkflowVariable.js +5 -1
  217. package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
  218. package/build/dist/Server/API/AlertAPI.js +22 -13
  219. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  220. package/build/dist/Server/API/BaseAPI.js +22 -1
  221. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  222. package/build/dist/Server/API/BaseAnalyticsAPI.js +24 -1
  223. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  224. package/build/dist/Server/API/CommonAPI.js +42 -0
  225. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  226. package/build/dist/Server/API/IncidentAPI.js +43 -26
  227. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  228. package/build/dist/Server/API/IncidentEpisodeAPI.js +21 -12
  229. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  230. package/build/dist/Server/API/MonitorAPI.js +23 -0
  231. package/build/dist/Server/API/MonitorAPI.js.map +1 -1
  232. package/build/dist/Server/API/MonitorTemplateAPI.js +4 -0
  233. package/build/dist/Server/API/MonitorTemplateAPI.js.map +1 -1
  234. package/build/dist/Server/API/ProbeAPI.js +131 -15
  235. package/build/dist/Server/API/ProbeAPI.js.map +1 -1
  236. package/build/dist/Server/API/ProjectAPI.js +62 -3
  237. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  238. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +23 -14
  239. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  240. package/build/dist/Server/API/StatusPageAPI.js +61 -33
  241. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  242. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js +26 -2
  243. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js.map +1 -1
  244. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js +12 -0
  245. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js.map +1 -0
  246. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js +397 -0
  247. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js.map +1 -0
  248. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js +16 -0
  249. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js.map +1 -0
  250. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js +30 -0
  251. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js.map +1 -0
  252. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js +34 -0
  253. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js.map +1 -0
  254. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  255. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  256. package/build/dist/Server/Services/AlertEpisodeService.js +39 -0
  257. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  258. package/build/dist/Server/Services/AlertService.js +95 -0
  259. package/build/dist/Server/Services/AlertService.js.map +1 -1
  260. package/build/dist/Server/Services/BillingService.js +71 -0
  261. package/build/dist/Server/Services/BillingService.js.map +1 -1
  262. package/build/dist/Server/Services/DatabaseService.js +140 -17
  263. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  264. package/build/dist/Server/Services/IncidentEpisodeService.js +39 -0
  265. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  266. package/build/dist/Server/Services/IncidentService.js +100 -0
  267. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  268. package/build/dist/Server/Services/IncidentTemplateService.js +97 -0
  269. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  270. package/build/dist/Server/Services/Index.js +2 -0
  271. package/build/dist/Server/Services/Index.js.map +1 -1
  272. package/build/dist/Server/Services/LogDropFilterService.js +93 -0
  273. package/build/dist/Server/Services/LogDropFilterService.js.map +1 -1
  274. package/build/dist/Server/Services/MonitorProbeService.js +73 -5
  275. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  276. package/build/dist/Server/Services/MonitorService.js +34 -39
  277. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  278. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +71 -0
  279. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  280. package/build/dist/Server/Services/ProbeService.js +64 -0
  281. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  282. package/build/dist/Server/Services/ProjectService.js +103 -7
  283. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  284. package/build/dist/Server/Services/RecommendationDismissalService.js +50 -0
  285. package/build/dist/Server/Services/RecommendationDismissalService.js.map +1 -0
  286. package/build/dist/Server/Services/RunbookAgentJobService.js +63 -5
  287. package/build/dist/Server/Services/RunbookAgentJobService.js.map +1 -1
  288. package/build/dist/Server/Services/ScheduledMaintenanceService.js +83 -0
  289. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  290. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +65 -0
  291. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -1
  292. package/build/dist/Server/Services/StatusPageGroupService.js +136 -0
  293. package/build/dist/Server/Services/StatusPageGroupService.js.map +1 -1
  294. package/build/dist/Server/Services/TraceDropFilterService.js +93 -0
  295. package/build/dist/Server/Services/TraceDropFilterService.js.map +1 -1
  296. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +11 -0
  297. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  298. package/build/dist/Server/Types/Workflow/Components/API/Get.js +11 -0
  299. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  300. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +11 -0
  301. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  302. package/build/dist/Server/Types/Workflow/Components/API/Post.js +11 -0
  303. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  304. package/build/dist/Server/Types/Workflow/Components/API/Put.js +12 -0
  305. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  306. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +132 -0
  307. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -0
  308. package/build/dist/Server/Utils/DropFilterValidation.js +80 -0
  309. package/build/dist/Server/Utils/DropFilterValidation.js.map +1 -0
  310. package/build/dist/Server/Utils/Errors.js +3 -0
  311. package/build/dist/Server/Utils/Errors.js.map +1 -1
  312. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +30 -12
  313. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  314. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +38 -18
  315. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  316. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +26 -0
  317. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
  318. package/build/dist/Types/Date.js +14 -4
  319. package/build/dist/Types/Date.js.map +1 -1
  320. package/build/dist/Types/JSONFunctions.js +8 -3
  321. package/build/dist/Types/JSONFunctions.js.map +1 -1
  322. package/build/dist/Types/Kubernetes/KubernetesCostIngest.js.map +1 -1
  323. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +192 -0
  324. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  325. package/build/dist/Types/Monitor/MonitorStep.js +6 -1
  326. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  327. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +32 -1
  328. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
  329. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +294 -0
  330. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -0
  331. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +110 -0
  332. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -0
  333. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +58 -0
  334. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -0
  335. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +451 -0
  336. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -0
  337. package/build/dist/Types/Permission.js +40 -0
  338. package/build/dist/Types/Permission.js.map +1 -1
  339. package/build/dist/Types/Recommendation/RecommendationType.js +21 -0
  340. package/build/dist/Types/Recommendation/RecommendationType.js.map +1 -0
  341. package/build/dist/Types/Runbook/RunbookExecutionDeadline.js +93 -0
  342. package/build/dist/Types/Runbook/RunbookExecutionDeadline.js.map +1 -0
  343. package/build/dist/Types/Runbook/RunbookStepTimeout.js +142 -0
  344. package/build/dist/Types/Runbook/RunbookStepTimeout.js.map +1 -0
  345. package/build/dist/Types/Telemetry/DropFilterSampling.js +75 -0
  346. package/build/dist/Types/Telemetry/DropFilterSampling.js.map +1 -0
  347. package/build/dist/UI/Components/AI/GenerateFromAIModal.js +1 -2
  348. package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -1
  349. package/build/dist/UI/Components/Accordion/Accordion.js +9 -2
  350. package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
  351. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +1 -1
  352. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
  353. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +27 -14
  354. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  355. package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js +1 -1
  356. package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js.map +1 -1
  357. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +2 -20
  358. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  359. package/build/dist/UI/Components/Error/PageError.js +1 -3
  360. package/build/dist/UI/Components/Error/PageError.js.map +1 -1
  361. package/build/dist/UI/Components/ErrorBoundary.js +145 -11
  362. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  363. package/build/dist/UI/Components/InfoCard/InfoCard.js +28 -1
  364. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  365. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +13 -2
  366. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  367. package/build/dist/UI/Components/MasterPage/MasterPage.js +50 -1
  368. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  369. package/build/dist/UI/Components/Modal/Modal.js +4 -32
  370. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  371. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  372. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +308 -40
  373. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  374. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +230 -0
  375. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -0
  376. package/build/dist/UI/Components/ModelTable/ColumnPreference.js +258 -0
  377. package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -0
  378. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +168 -0
  379. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -0
  380. package/build/dist/UI/Components/ModelTable/TableView.js +13 -2
  381. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  382. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +84 -0
  383. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -0
  384. package/build/dist/UI/Components/Runbook/StepTimeoutInput.js +101 -0
  385. package/build/dist/UI/Components/Runbook/StepTimeoutInput.js.map +1 -0
  386. package/build/dist/UI/Components/SideMenu/SideMenu.js +15 -5
  387. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  388. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js +73 -0
  389. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js.map +1 -0
  390. package/build/dist/UI/Components/Table/Table.js +15 -2
  391. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  392. package/build/dist/UI/Components/Table/TableRow.js +11 -6
  393. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  394. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +13 -2
  395. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  396. package/build/dist/UI/Utils/DropdownAlignment.js +98 -0
  397. package/build/dist/UI/Utils/DropdownAlignment.js.map +1 -0
  398. package/build/dist/UI/Utils/TableFilterUrlState.js +48 -15
  399. package/build/dist/UI/Utils/TableFilterUrlState.js.map +1 -1
  400. package/build/dist/Utils/API.js +39 -2
  401. package/build/dist/Utils/API.js.map +1 -1
  402. package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js +97 -0
  403. package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js.map +1 -0
  404. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +330 -0
  405. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -0
  406. package/build/dist/Utils/StatusPage/GroupNestingLayout.js +280 -0
  407. package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -0
  408. package/build/dist/Utils/StatusPage/GroupTree.js +214 -0
  409. package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -0
  410. package/build/dist/Utils/StatusPage/ResourceUptime.js +60 -16
  411. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  412. package/build/dist/Utils/UserPreferences.js +33 -0
  413. package/build/dist/Utils/UserPreferences.js.map +1 -1
  414. package/package.json +1 -1
@@ -0,0 +1,671 @@
1
+ import ResourceGroupSection from "../../../../UI/Components/StatusPage/ResourceGroupSection";
2
+ import StatusPageGroupNestingLayoutUtil from "../../../../Utils/StatusPage/GroupNestingLayout";
3
+ import "@testing-library/jest-dom";
4
+ import { fireEvent, render, screen } from "@testing-library/react";
5
+ import userEvent from "@testing-library/user-event";
6
+ import { UserEvent } from "@testing-library/user-event/dist/types/setup/setup";
7
+ import React, { ReactElement } from "react";
8
+ import { describe, expect, test } from "@jest/globals";
9
+
10
+ /*
11
+ * Contract under test - one level of the status page resource group hierarchy.
12
+ *
13
+ * The nesting this replaces drew every level as a card inside a card inside a
14
+ * card: at three levels deep a visitor saw four borders wrapped around one
15
+ * uptime bar, the bars lost the width they need on a phone, and the header was a
16
+ * div with role="button" wrapping markdown that may contain links. So the
17
+ * assertions here are about structure rather than looks:
18
+ *
19
+ * - only the top level is a card, and nothing nested adds a surface,
20
+ * - nested levels hang off a rail,
21
+ * - the header is a real button that reports its expanded state,
22
+ * - the rolled up reading stays visible when the level is expanded,
23
+ * - and a description never ends up inside the button.
24
+ */
25
+
26
+ function renderSection(
27
+ props: Partial<React.ComponentProps<typeof ResourceGroupSection>> = {},
28
+ ): ReturnType<typeof render> {
29
+ return render(
30
+ <ResourceGroupSection
31
+ depth={props.depth === undefined ? 0 : props.depth}
32
+ name={props.name === undefined ? "Corporate" : props.name}
33
+ description={props.description}
34
+ subGroupCount={props.subGroupCount}
35
+ subGroupCountLabel={props.subGroupCountLabel}
36
+ rollupLabel={props.rollupLabel}
37
+ rollupColor={props.rollupColor}
38
+ isInitiallyExpanded={props.isInitiallyExpanded}
39
+ resourcesElement={props.resourcesElement}
40
+ subGroupsElement={props.subGroupsElement}
41
+ hasOwnResources={props.hasOwnResources}
42
+ testId={props.testId}
43
+ />,
44
+ );
45
+ }
46
+
47
+ function header(): HTMLElement {
48
+ return screen.getByTestId("status-page-group-header");
49
+ }
50
+
51
+ describe("ResourceGroupSection", () => {
52
+ describe("the header", () => {
53
+ test("renders the group name", () => {
54
+ renderSection({ name: "Region 1000" });
55
+
56
+ expect(screen.getByText("Region 1000")).toBeInTheDocument();
57
+ });
58
+
59
+ /*
60
+ * It used to be a div with role="button". A real button gets Enter, Space,
61
+ * focus and the accessible name for free.
62
+ */
63
+ test("is a real button, not a div wearing a role", () => {
64
+ renderSection();
65
+
66
+ expect(header().tagName).toBe("BUTTON");
67
+ expect(header()).toHaveAttribute("type", "button");
68
+ });
69
+
70
+ test("reports its collapsed and expanded state", () => {
71
+ renderSection();
72
+
73
+ expect(header()).toHaveAttribute("aria-expanded", "false");
74
+
75
+ fireEvent.click(header());
76
+
77
+ expect(header()).toHaveAttribute("aria-expanded", "true");
78
+ });
79
+
80
+ test("toggles the body open and shut", () => {
81
+ renderSection({
82
+ resourcesElement: <div>Corporate API</div>,
83
+ });
84
+
85
+ expect(screen.queryByText("Corporate API")).not.toBeInTheDocument();
86
+
87
+ fireEvent.click(header());
88
+ expect(screen.getByText("Corporate API")).toBeInTheDocument();
89
+
90
+ fireEvent.click(header());
91
+ expect(screen.queryByText("Corporate API")).not.toBeInTheDocument();
92
+ });
93
+
94
+ /*
95
+ * aria-controls may only name an element that exists, and the body is not
96
+ * rendered while the section is shut.
97
+ */
98
+ test("points aria-controls at the body only while the body exists", () => {
99
+ renderSection({ resourcesElement: <div>Corporate API</div> });
100
+
101
+ expect(header()).not.toHaveAttribute("aria-controls");
102
+
103
+ fireEvent.click(header());
104
+
105
+ const bodyId: string | null = header().getAttribute("aria-controls");
106
+
107
+ expect(bodyId).toBeTruthy();
108
+ expect(screen.getByTestId("status-page-group-body")).toHaveAttribute(
109
+ "id",
110
+ bodyId,
111
+ );
112
+ });
113
+
114
+ /*
115
+ * A native button is focusable and activates on Enter and Space without
116
+ * anything being wired up. The div with role="button" it replaces needed
117
+ * tabIndex and a hand-rolled keydown handler to get halfway there.
118
+ */
119
+ test("is reachable by keyboard without a tabindex of its own", () => {
120
+ renderSection();
121
+
122
+ header().focus();
123
+
124
+ expect(header()).toHaveFocus();
125
+ expect(header()).not.toHaveAttribute("tabindex");
126
+ });
127
+
128
+ test("opens on Enter", async () => {
129
+ const user: UserEvent = userEvent.setup();
130
+
131
+ renderSection({ resourcesElement: <div>Corporate API</div> });
132
+
133
+ header().focus();
134
+ await user.keyboard("{Enter}");
135
+
136
+ expect(header()).toHaveAttribute("aria-expanded", "true");
137
+ expect(screen.getByText("Corporate API")).toBeInTheDocument();
138
+ });
139
+
140
+ test("opens on Space", async () => {
141
+ const user: UserEvent = userEvent.setup();
142
+
143
+ renderSection({ resourcesElement: <div>Corporate API</div> });
144
+
145
+ header().focus();
146
+ await user.keyboard("{ }");
147
+
148
+ expect(header()).toHaveAttribute("aria-expanded", "true");
149
+ });
150
+ });
151
+
152
+ describe("expansion state", () => {
153
+ test("is shut by default", () => {
154
+ renderSection({ resourcesElement: <div>Corporate API</div> });
155
+
156
+ expect(
157
+ screen.queryByTestId("status-page-group-body"),
158
+ ).not.toBeInTheDocument();
159
+ });
160
+
161
+ test("opens when the group is configured to expand by default", () => {
162
+ renderSection({
163
+ isInitiallyExpanded: true,
164
+ resourcesElement: <div>Corporate API</div>,
165
+ });
166
+
167
+ expect(screen.getByText("Corporate API")).toBeInTheDocument();
168
+ });
169
+
170
+ /*
171
+ * Groups arrive with the page payload, so the flag can flip after the first
172
+ * render.
173
+ */
174
+ test("follows isInitiallyExpanded when it arrives late", () => {
175
+ const { rerender } = render(
176
+ <ResourceGroupSection depth={0} name="Corporate" />,
177
+ );
178
+
179
+ expect(header()).toHaveAttribute("aria-expanded", "false");
180
+
181
+ rerender(
182
+ <ResourceGroupSection
183
+ depth={0}
184
+ name="Corporate"
185
+ isInitiallyExpanded={true}
186
+ />,
187
+ );
188
+
189
+ expect(header()).toHaveAttribute("aria-expanded", "true");
190
+ });
191
+
192
+ /*
193
+ * The hazard the effect is keyed on a boolean for: isExpandedByDefault is an
194
+ * optional column, so it routinely arrives undefined and settles to false.
195
+ * Keyed on the prop, that identity change fires the effect and slams shut a
196
+ * section the visitor had just opened.
197
+ */
198
+ test("undefined settling to false does not shut a section the visitor opened", () => {
199
+ const { rerender } = render(
200
+ <ResourceGroupSection depth={0} name="Corporate" />,
201
+ );
202
+
203
+ fireEvent.click(header());
204
+ expect(header()).toHaveAttribute("aria-expanded", "true");
205
+
206
+ rerender(
207
+ <ResourceGroupSection
208
+ depth={0}
209
+ name="Corporate"
210
+ isInitiallyExpanded={false}
211
+ />,
212
+ );
213
+
214
+ expect(header()).toHaveAttribute("aria-expanded", "true");
215
+ });
216
+
217
+ /*
218
+ * A re-render that does not change the flag must not collapse a section the
219
+ * visitor just opened.
220
+ */
221
+ test("keeps a section the visitor opened open across a re-render", () => {
222
+ const { rerender } = render(
223
+ <ResourceGroupSection depth={0} name="Corporate" />,
224
+ );
225
+
226
+ fireEvent.click(header());
227
+ expect(header()).toHaveAttribute("aria-expanded", "true");
228
+
229
+ rerender(<ResourceGroupSection depth={0} name="Corporate updated" />);
230
+
231
+ expect(header()).toHaveAttribute("aria-expanded", "true");
232
+ });
233
+ });
234
+
235
+ describe("the top level", () => {
236
+ test("is a card", () => {
237
+ renderSection({ depth: 0 });
238
+
239
+ const container: HTMLElement = screen.getByTestId("status-page-group");
240
+
241
+ expect(container).toHaveClass("bg-white");
242
+ expect(container).toHaveClass("rounded-xl");
243
+ expect(container).toHaveClass("shadow");
244
+ });
245
+
246
+ test("carries its depth for anything reading the DOM", () => {
247
+ renderSection({ depth: 0 });
248
+
249
+ expect(screen.getByTestId("status-page-group")).toHaveAttribute(
250
+ "data-depth",
251
+ "0",
252
+ );
253
+ });
254
+
255
+ test("its body has no rail - the card edge is the boundary", () => {
256
+ renderSection({
257
+ depth: 0,
258
+ isInitiallyExpanded: true,
259
+ resourcesElement: <div>Corporate API</div>,
260
+ });
261
+
262
+ expect(
263
+ screen.getByTestId("status-page-group-body").className,
264
+ ).not.toMatch(/border-l/);
265
+ });
266
+ });
267
+
268
+ describe("a nested level", () => {
269
+ /*
270
+ * The headline regression. A nested group is a row on a rail, not another
271
+ * surface - no background, no border, no rounding, no shadow.
272
+ */
273
+ test.each([[1], [2], [3], [4], [7]])(
274
+ "at depth %i adds no card of its own",
275
+ (depth: number) => {
276
+ renderSection({ depth: depth });
277
+
278
+ const container: HTMLElement = screen.getByTestId(
279
+ "status-page-nested-group",
280
+ );
281
+
282
+ /*
283
+ * Asserted as the absence of each surface rather than as an empty class
284
+ * string, so a future non-surface utility does not fail this while a
285
+ * returning card still does.
286
+ */
287
+ expect(container.className).not.toMatch(/\bbg-/);
288
+ expect(container.className).not.toMatch(/\bborder/);
289
+ expect(container.className).not.toMatch(/\brounded/);
290
+ expect(container.className).not.toMatch(/\bshadow/);
291
+ expect(container.className).not.toMatch(/\bp[xytblr]?-\d/);
292
+ },
293
+ );
294
+
295
+ test("hangs its body off exactly one hairline rail", () => {
296
+ renderSection({
297
+ depth: 1,
298
+ isInitiallyExpanded: true,
299
+ resourcesElement: <div>Region 1000 API</div>,
300
+ });
301
+
302
+ const body: HTMLElement = screen.getByTestId("status-page-group-body");
303
+
304
+ expect(body).toHaveClass("border-l");
305
+ expect(body).toHaveClass("border-gray-200");
306
+ expect(body.className).not.toMatch(/border-l-2/);
307
+ });
308
+
309
+ test("carries its depth for anything reading the DOM", () => {
310
+ renderSection({ depth: 3 });
311
+
312
+ expect(screen.getByTestId("status-page-nested-group")).toHaveAttribute(
313
+ "data-depth",
314
+ "3",
315
+ );
316
+ });
317
+
318
+ test("a title is never smaller than the resources it contains", () => {
319
+ renderSection({ depth: 5, name: "Unit 0152" });
320
+
321
+ const title: HTMLElement = screen.getByText("Unit 0152");
322
+
323
+ expect(title.className).toContain("text-base");
324
+ expect(title.className).not.toMatch(/(^|\s)text-sm(\s|$)/);
325
+ });
326
+
327
+ /*
328
+ * Wrapping was tried here and is worse: the title is the only shrinkable
329
+ * item in the header row, so beside a badge and a rollup it collapsed to a
330
+ * ~30px column and broke the name one character per line on a phone.
331
+ */
332
+ test("a long title ellipsizes and keeps the full name reachable", () => {
333
+ renderSection({ depth: 1, name: "Corporate Unit Northwest" });
334
+
335
+ const title: HTMLElement = screen.getByText("Corporate Unit Northwest");
336
+
337
+ expect(title.className).toContain("truncate");
338
+ expect(title.className).toContain("min-w-0");
339
+ expect(title.className).not.toContain("break-words");
340
+ expect(title).toHaveAttribute("title", "Corporate Unit Northwest");
341
+ });
342
+ });
343
+
344
+ describe("the rolled up reading", () => {
345
+ test("is rendered when the group has one", () => {
346
+ renderSection({ rollupLabel: "99.9% uptime" });
347
+
348
+ expect(screen.getByText("99.9% uptime")).toBeInTheDocument();
349
+ });
350
+
351
+ /*
352
+ * It used to live in the accordion's rightElement, which is hidden while the
353
+ * accordion is open - so expanding a level hid the number you expanded it to
354
+ * compare against.
355
+ */
356
+ test("stays visible while the level is expanded", () => {
357
+ renderSection({ rollupLabel: "99.9% uptime" });
358
+
359
+ fireEvent.click(header());
360
+
361
+ expect(screen.getByText("99.9% uptime")).toBeInTheDocument();
362
+ });
363
+
364
+ test("leaves no empty slot behind when the group shows neither status nor uptime", () => {
365
+ renderSection({});
366
+
367
+ expect(
368
+ screen.queryByTestId("status-page-group-rollup"),
369
+ ).not.toBeInTheDocument();
370
+ });
371
+
372
+ /*
373
+ * The dot is what stops a group rollup reading as one more resource figure -
374
+ * "100% uptime" on a group and on a resource are otherwise identical.
375
+ */
376
+ test("carries a status dot in the rolled up status colour", () => {
377
+ renderSection({ rollupLabel: "Operational", rollupColor: "#2ab57d" });
378
+
379
+ const dot: HTMLElement = screen.getByTestId(
380
+ "status-page-group-rollup-dot",
381
+ );
382
+
383
+ expect(dot).toHaveStyle({ backgroundColor: "#2ab57d" });
384
+ expect(dot).toHaveAttribute("aria-hidden", "true");
385
+ expect(screen.getByText("Operational")).toHaveStyle({
386
+ color: "#2ab57d",
387
+ });
388
+ });
389
+
390
+ test("the dot is decoration - the reading itself is the text", () => {
391
+ renderSection({ rollupLabel: "Operational", rollupColor: "#2ab57d" });
392
+
393
+ expect(screen.getByTestId("status-page-group-rollup")).toHaveTextContent(
394
+ "Operational",
395
+ );
396
+ });
397
+
398
+ test("renders without a colour rather than crashing", () => {
399
+ renderSection({ rollupLabel: "Operational" });
400
+
401
+ expect(screen.getByText("Operational")).toBeInTheDocument();
402
+ expect(
403
+ screen.getByTestId("status-page-group-rollup-dot"),
404
+ ).toBeInTheDocument();
405
+ });
406
+ });
407
+
408
+ describe("the sub group count", () => {
409
+ test("is shown when the caller supplies a label", () => {
410
+ renderSection({ subGroupCountLabel: "3 groups", subGroupCount: 3 });
411
+
412
+ expect(
413
+ screen.getByTestId("status-page-group-sub-group-count"),
414
+ ).toHaveTextContent("3 groups");
415
+ });
416
+
417
+ test("is absent for a group with no sub groups", () => {
418
+ renderSection({ subGroupCount: 0 });
419
+
420
+ expect(
421
+ screen.queryByTestId("status-page-group-sub-group-count"),
422
+ ).not.toBeInTheDocument();
423
+ });
424
+ });
425
+
426
+ describe("the description", () => {
427
+ /*
428
+ * The markdown itself renders through a lazily imported viewer, so what is
429
+ * asserted here is this component's part: that a group with a description
430
+ * gets the slot, aligned with the title, and a group without one does not.
431
+ */
432
+ test("is rendered when the group has one", () => {
433
+ renderSection({ description: "Everything in the corporate unit." });
434
+
435
+ const description: HTMLElement = screen.getByTestId(
436
+ "status-page-group-description",
437
+ );
438
+
439
+ expect(description).toBeInTheDocument();
440
+ expect(description.className).toBe(
441
+ StatusPageGroupNestingLayoutUtil.getDescriptionClassName(),
442
+ );
443
+ });
444
+
445
+ test("is absent for a group with no description", () => {
446
+ renderSection({});
447
+
448
+ expect(
449
+ screen.queryByTestId("status-page-group-description"),
450
+ ).not.toBeInTheDocument();
451
+ });
452
+
453
+ /*
454
+ * A description is markdown and may contain a link. A link inside a button
455
+ * is not something a browser or a screen reader can make sense of, so the
456
+ * description sits outside the header.
457
+ */
458
+ test("never ends up inside the header button", () => {
459
+ renderSection({ description: "Everything in the corporate unit." });
460
+
461
+ expect(
462
+ header().contains(screen.getByTestId("status-page-group-description")),
463
+ ).toBe(false);
464
+ });
465
+
466
+ test("is announced with the header rather than read as part of its name", () => {
467
+ renderSection({ description: "Everything in the corporate unit." });
468
+
469
+ expect(header().getAttribute("aria-describedby")).toBe(
470
+ screen.getByTestId("status-page-group-description").getAttribute("id"),
471
+ );
472
+ });
473
+
474
+ test("adds no describedby when there is no description", () => {
475
+ renderSection({});
476
+
477
+ expect(header()).not.toHaveAttribute("aria-describedby");
478
+ });
479
+ });
480
+
481
+ describe("the body", () => {
482
+ test("renders a group's own resources above its sub groups", () => {
483
+ renderSection({
484
+ isInitiallyExpanded: true,
485
+ hasOwnResources: true,
486
+ subGroupCount: 1,
487
+ resourcesElement: <div>Corporate API</div>,
488
+ subGroupsElement: <div>Region 1000</div>,
489
+ });
490
+
491
+ const body: HTMLElement = screen.getByTestId("status-page-group-body");
492
+ const text: string = body.textContent || "";
493
+
494
+ expect(text.indexOf("Corporate API")).toBeGreaterThanOrEqual(0);
495
+ expect(text.indexOf("Corporate API")).toBeLessThan(
496
+ text.indexOf("Region 1000"),
497
+ );
498
+ });
499
+
500
+ /*
501
+ * Without the line, a group's last resource reads as the first row of the
502
+ * first sub group below it.
503
+ */
504
+ test("draws a line between own resources and sub groups", () => {
505
+ renderSection({
506
+ isInitiallyExpanded: true,
507
+ hasOwnResources: true,
508
+ subGroupCount: 1,
509
+ resourcesElement: <div>Corporate API</div>,
510
+ subGroupsElement: <div>Region 1000</div>,
511
+ });
512
+
513
+ expect(screen.getByTestId("status-page-sub-group-list")).toHaveClass(
514
+ "border-t",
515
+ );
516
+ });
517
+
518
+ test("draws no line for a group that only holds sub groups", () => {
519
+ renderSection({
520
+ isInitiallyExpanded: true,
521
+ hasOwnResources: false,
522
+ subGroupCount: 2,
523
+ subGroupsElement: <div>Region 1000</div>,
524
+ });
525
+
526
+ expect(
527
+ screen.getByTestId("status-page-sub-group-list").className,
528
+ ).not.toMatch(/border-t/);
529
+ });
530
+
531
+ test("has no sub group list when there are no sub groups", () => {
532
+ renderSection({
533
+ isInitiallyExpanded: true,
534
+ hasOwnResources: true,
535
+ resourcesElement: <div>Corporate API</div>,
536
+ });
537
+
538
+ expect(
539
+ screen.queryByTestId("status-page-sub-group-list"),
540
+ ).not.toBeInTheDocument();
541
+ });
542
+
543
+ test("an expanded group with nothing in it still opens", () => {
544
+ renderSection({ isInitiallyExpanded: true });
545
+
546
+ expect(screen.getByTestId("status-page-group-body")).toBeInTheDocument();
547
+ });
548
+ });
549
+
550
+ describe("a real hierarchy", () => {
551
+ /*
552
+ * Corporate -> Region -> Market -> Site -> Rack -> Node, all expanded. This
553
+ * is the shape that used to produce six stacked cards.
554
+ */
555
+ type RenderNestedFunction = (depth: number) => ReactElement;
556
+
557
+ const renderNested: RenderNestedFunction = (
558
+ depth: number,
559
+ ): ReactElement => {
560
+ return (
561
+ <ResourceGroupSection
562
+ depth={depth}
563
+ name={`Level ${depth}`}
564
+ isInitiallyExpanded={true}
565
+ subGroupCount={depth < 5 ? 1 : 0}
566
+ testId={`level-${depth}`}
567
+ subGroupsElement={depth < 5 ? renderNested(depth + 1) : undefined}
568
+ resourcesElement={depth === 5 ? <div>Leaf API</div> : undefined}
569
+ hasOwnResources={depth === 5}
570
+ />
571
+ );
572
+ };
573
+
574
+ test("renders every level and reaches the leaf", () => {
575
+ render(renderNested(0));
576
+
577
+ for (let depth: number = 0; depth <= 5; depth++) {
578
+ expect(screen.getByTestId(`level-${depth}`)).toBeInTheDocument();
579
+ }
580
+
581
+ expect(screen.getByText("Leaf API")).toBeInTheDocument();
582
+ });
583
+
584
+ test("only one card is drawn, however deep the hierarchy goes", () => {
585
+ const { container } = render(renderNested(0));
586
+
587
+ const cards: Array<Element> = Array.from(
588
+ container.querySelectorAll("[data-testid^='level-']"),
589
+ ).filter((element: Element) => {
590
+ return element.className.includes("bg-white");
591
+ });
592
+
593
+ expect(cards).toHaveLength(1);
594
+ expect(cards[0]!.getAttribute("data-testid")).toBe("level-0");
595
+ });
596
+
597
+ test("every level below the top hangs off its own rail", () => {
598
+ render(renderNested(0));
599
+
600
+ for (let depth: number = 1; depth <= 5; depth++) {
601
+ const level: HTMLElement = screen.getByTestId(`level-${depth}`);
602
+ const body: Element | null = level.querySelector(
603
+ "[data-testid='status-page-group-body']",
604
+ );
605
+
606
+ expect(body).not.toBeNull();
607
+ expect(body!.className).toContain("border-l");
608
+ }
609
+ });
610
+
611
+ /*
612
+ * The indent has to stop growing at some point or a deep tree runs the
613
+ * uptime bars off the right of a phone.
614
+ */
615
+ test("the indent stops growing once the cap is reached", () => {
616
+ render(renderNested(0));
617
+
618
+ const bodyClassAt: (depth: number) => string = (
619
+ depth: number,
620
+ ): string => {
621
+ return (
622
+ screen
623
+ .getByTestId(`level-${depth}`)
624
+ .querySelector("[data-testid='status-page-group-body']")
625
+ ?.className || ""
626
+ );
627
+ };
628
+
629
+ expect(bodyClassAt(1)).toBe(
630
+ StatusPageGroupNestingLayoutUtil.getBodyClassName({ depth: 1 }),
631
+ );
632
+ expect(
633
+ bodyClassAt(StatusPageGroupNestingLayoutUtil.MaxIndentedDepth + 1),
634
+ ).toBe(bodyClassAt(StatusPageGroupNestingLayoutUtil.MaxIndentedDepth));
635
+ /*
636
+ * The step is smaller past the cap, which is what keeps a ten level tree
637
+ * inside the content column. The px arithmetic lives in the util's own
638
+ * suite; here it is enough that the two are not the same class.
639
+ */
640
+ expect(
641
+ bodyClassAt(StatusPageGroupNestingLayoutUtil.MaxIndentedDepth),
642
+ ).not.toBe(bodyClassAt(1));
643
+ });
644
+
645
+ test("collapsing a level hides everything under it", () => {
646
+ render(renderNested(0));
647
+
648
+ expect(screen.getByText("Leaf API")).toBeInTheDocument();
649
+
650
+ fireEvent.click(
651
+ screen
652
+ .getByTestId("level-2")
653
+ .querySelector(
654
+ "[data-testid='status-page-group-header']",
655
+ ) as HTMLElement,
656
+ );
657
+
658
+ expect(screen.queryByText("Leaf API")).not.toBeInTheDocument();
659
+ expect(screen.getByTestId("level-2")).toBeInTheDocument();
660
+ });
661
+ });
662
+
663
+ describe("test ids", () => {
664
+ test("the caller can override the default", () => {
665
+ renderSection({ testId: "my-group" });
666
+
667
+ expect(screen.getByTestId("my-group")).toBeInTheDocument();
668
+ expect(screen.queryByTestId("status-page-group")).not.toBeInTheDocument();
669
+ });
670
+ });
671
+ });