@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,1072 @@
1
+ import StatusPageGroupNestingLayoutUtil, {
2
+ StatusPageGroupRollupKind,
3
+ } from "../../../Utils/StatusPage/GroupNestingLayout";
4
+ import ObjectID from "../../../Types/ObjectID";
5
+ import StatusPageGroup from "../../../Models/DatabaseModels/StatusPageGroup";
6
+ import StatusPageResource from "../../../Models/DatabaseModels/StatusPageResource";
7
+ import StatusPageGroupViewMode from "../../../Types/StatusPage/StatusPageGroupViewMode";
8
+ import Monitor from "../../../Models/DatabaseModels/Monitor";
9
+ import { describe, expect, test } from "@jest/globals";
10
+
11
+ /*
12
+ * Every measurement below is taken from the class strings the util actually
13
+ * emits, not from a number written down beside them. A number kept alongside the
14
+ * classes can drift from them silently - and did: an earlier version of these
15
+ * tests passed unchanged with the real indent inflated six fold.
16
+ *
17
+ * Tailwind's default scale: a spacing step is 0.25rem, so `pl-5` is 20px and
18
+ * `ml-2.5` is 10px. Arbitrary values (`pl-[34px]`) are read literally. Where a
19
+ * class has an `sm:` variant, the variant is the wide viewport value.
20
+ */
21
+ const TAILWIND_FONT_SIZE_PX: Record<string, number> = {
22
+ "text-xs": 12,
23
+ "text-sm": 14,
24
+ "text-base": 16,
25
+ "text-lg": 18,
26
+ "text-xl": 20,
27
+ "text-2xl": 24,
28
+ };
29
+
30
+ function tokensOf(className: string): Array<string> {
31
+ return className.split(/\s+/).filter((token: string) => {
32
+ return token.length > 0;
33
+ });
34
+ }
35
+
36
+ /*
37
+ * The value that wins at the wide viewport: the `sm:` variant when there is one,
38
+ * otherwise the unprefixed class.
39
+ */
40
+ function wideValueOf(
41
+ className: string,
42
+ prefix: string,
43
+ read: (value: string) => number | null,
44
+ ): number | null {
45
+ let unprefixed: number | null = null;
46
+ let atSm: number | null = null;
47
+
48
+ for (const token of tokensOf(className)) {
49
+ if (token.startsWith(`sm:${prefix}`)) {
50
+ atSm = read(token.substring(`sm:${prefix}`.length));
51
+ } else if (token.startsWith(prefix)) {
52
+ unprefixed = read(token.substring(prefix.length));
53
+ }
54
+ }
55
+
56
+ return atSm === null ? unprefixed : atSm;
57
+ }
58
+
59
+ function readSpacing(value: string): number | null {
60
+ const arbitrary: RegExpMatchArray | null = value.match(
61
+ /^\[(\d+(?:\.\d+)?)px]$/,
62
+ );
63
+
64
+ if (arbitrary) {
65
+ return Number(arbitrary[1]);
66
+ }
67
+
68
+ const step: number = Number(value);
69
+
70
+ return Number.isFinite(step) ? step * 4 : null;
71
+ }
72
+
73
+ /* The px value of `pl-*` / `ml-*` / `pt-*` in a class string. */
74
+ function spacingPx(className: string, prefix: string): number {
75
+ return wideValueOf(className, prefix, readSpacing) || 0;
76
+ }
77
+
78
+ /* The px font size a class string renders at, at the wide viewport. */
79
+ function fontSizePx(className: string): number {
80
+ for (const token of tokensOf(className)) {
81
+ const bare: string = token.startsWith("sm:") ? token.substring(3) : token;
82
+
83
+ if (TAILWIND_FONT_SIZE_PX[bare] !== undefined && token.startsWith("sm:")) {
84
+ return TAILWIND_FONT_SIZE_PX[bare]!;
85
+ }
86
+ }
87
+
88
+ for (const token of tokensOf(className)) {
89
+ if (TAILWIND_FONT_SIZE_PX[token] !== undefined) {
90
+ return TAILWIND_FONT_SIZE_PX[token]!;
91
+ }
92
+ }
93
+
94
+ return 0;
95
+ }
96
+
97
+ /*
98
+ * How far one nesting step moves its contents right: the rail's own offset, the
99
+ * 1px rail itself, and the padding that clears it. Read off the body class, so
100
+ * changing the class changes the number.
101
+ */
102
+ function nestingStepPx(depth: number): number {
103
+ const bodyClassName: string =
104
+ StatusPageGroupNestingLayoutUtil.getBodyClassName({ depth: depth });
105
+
106
+ if (!bodyClassName.includes("border-l")) {
107
+ return spacingPx(bodyClassName, "ml-") + spacingPx(bodyClassName, "pl-");
108
+ }
109
+
110
+ return spacingPx(bodyClassName, "ml-") + 1 + spacingPx(bodyClassName, "pl-");
111
+ }
112
+
113
+ /* Where a group title's text starts: the chevron box plus the gap after it. */
114
+ function titleTextOffsetPx(): number {
115
+ return (
116
+ spacingPx(StatusPageGroupNestingLayoutUtil.getChevronBoxClassName(), "w-") +
117
+ spacingPx(StatusPageGroupNestingLayoutUtil.getHeaderRowClassName(), "gap-")
118
+ );
119
+ }
120
+
121
+ /*
122
+ * Contract under test - how the overview page draws nested resource groups.
123
+ *
124
+ * The nesting used to be drawn as a card inside a card inside a card, and at
125
+ * three levels deep that puts four borders around one uptime bar, squeezes the
126
+ * bars off the right of a phone, and renders a leaf resource larger than the
127
+ * group heading above it. Every rule below is one of those failures turned into
128
+ * an assertion:
129
+ *
130
+ * - only the top level is a card; nothing nested adds a surface,
131
+ * - a group title is never smaller than the resources inside it,
132
+ * - the indent stops growing before a deep tree runs out of width,
133
+ * - and no shape of bad data (a negative depth, a parent pointer cycle) may
134
+ * break the layout or hang the walk.
135
+ */
136
+
137
+ const CORPORATE: ObjectID = new ObjectID(
138
+ "11111111-1111-4111-8111-111111111111",
139
+ );
140
+ const REGION_ONE: ObjectID = new ObjectID(
141
+ "22222222-2222-4222-8222-222222222222",
142
+ );
143
+ const REGION_TWO: ObjectID = new ObjectID(
144
+ "33333333-3333-4333-8333-333333333333",
145
+ );
146
+ const MARKET: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
147
+ const UNIT: ObjectID = new ObjectID("55555555-5555-4555-8555-555555555555");
148
+
149
+ /* Depths well past anything the write path allows. */
150
+ const ABSURD_DEPTHS: Array<number> = [5, 6, 9, 10, 11, 25, 100, 1000];
151
+
152
+ function makeGroup(data: {
153
+ id: ObjectID;
154
+ name: string;
155
+ parentId?: ObjectID | undefined;
156
+ order?: number | undefined;
157
+ viewMode?: StatusPageGroupViewMode | undefined;
158
+ }): StatusPageGroup {
159
+ const group: StatusPageGroup = new StatusPageGroup();
160
+ group._id = data.id.toString();
161
+ group.name = data.name;
162
+
163
+ if (data.parentId) {
164
+ group.parentStatusPageGroupId = data.parentId;
165
+ }
166
+
167
+ if (data.order !== undefined) {
168
+ group.order = data.order;
169
+ }
170
+
171
+ if (data.viewMode) {
172
+ group.viewMode = data.viewMode;
173
+ }
174
+
175
+ return group;
176
+ }
177
+
178
+ function makeResource(data: {
179
+ id: ObjectID;
180
+ name: string;
181
+ groupId?: ObjectID | undefined;
182
+ showStatusHistoryChart?: boolean | undefined;
183
+ withMonitor?: boolean | undefined;
184
+ monitorGroupId?: ObjectID | undefined;
185
+ }): StatusPageResource {
186
+ const resource: StatusPageResource = new StatusPageResource();
187
+ resource._id = data.id.toString();
188
+ resource.displayName = data.name;
189
+
190
+ if (data.groupId) {
191
+ resource.statusPageGroupId = data.groupId;
192
+ }
193
+
194
+ if (data.showStatusHistoryChart !== undefined) {
195
+ resource.showStatusHistoryChart = data.showStatusHistoryChart;
196
+ }
197
+
198
+ /*
199
+ * The page only draws a resource that resolves to a monitor or a monitor
200
+ * group, so a fixture without one is how "nothing to draw" is expressed.
201
+ */
202
+ if (data.withMonitor !== false && !data.monitorGroupId) {
203
+ const monitor: Monitor = new Monitor();
204
+ monitor._id = data.id.toString();
205
+ resource.monitor = monitor;
206
+ }
207
+
208
+ if (data.monitorGroupId) {
209
+ resource.monitorGroupId = data.monitorGroupId;
210
+ }
211
+
212
+ return resource;
213
+ }
214
+
215
+ /*
216
+ * Corporate
217
+ * Region 1000
218
+ * Market 1001
219
+ * Unit 0152
220
+ * Region 2000
221
+ */
222
+ function makeHierarchy(): Array<StatusPageGroup> {
223
+ return [
224
+ makeGroup({ id: CORPORATE, name: "Corporate", order: 1 }),
225
+ makeGroup({
226
+ id: REGION_ONE,
227
+ name: "Region 1000",
228
+ parentId: CORPORATE,
229
+ order: 2,
230
+ }),
231
+ makeGroup({
232
+ id: MARKET,
233
+ name: "Market 1001",
234
+ parentId: REGION_ONE,
235
+ order: 3,
236
+ }),
237
+ makeGroup({ id: UNIT, name: "Unit 0152", parentId: MARKET, order: 4 }),
238
+ makeGroup({
239
+ id: REGION_TWO,
240
+ name: "Region 2000",
241
+ parentId: CORPORATE,
242
+ order: 5,
243
+ }),
244
+ ];
245
+ }
246
+
247
+ describe("StatusPageGroupNestingLayoutUtil", () => {
248
+ describe("getVisualDepth", () => {
249
+ test("passes a normal depth straight through", () => {
250
+ expect(
251
+ StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: 0 }),
252
+ ).toBe(0);
253
+ expect(
254
+ StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: 1 }),
255
+ ).toBe(1);
256
+ expect(
257
+ StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: 3 }),
258
+ ).toBe(3);
259
+ });
260
+
261
+ test("clamps at MaxIndentedDepth so a deep tree keeps its width", () => {
262
+ for (const depth of ABSURD_DEPTHS) {
263
+ expect(
264
+ StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: depth }),
265
+ ).toBe(StatusPageGroupNestingLayoutUtil.MaxIndentedDepth);
266
+ }
267
+ });
268
+
269
+ /*
270
+ * Depth comes from a recursive render today, but a bad number must degrade
271
+ * to the top level rather than produce a class like `pl-NaN`.
272
+ */
273
+ test.each([[-1], [-10], [Number.NEGATIVE_INFINITY], [Number.NaN]])(
274
+ "treats %p as the top level",
275
+ (depth: number) => {
276
+ expect(
277
+ StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: depth }),
278
+ ).toBe(0);
279
+ },
280
+ );
281
+
282
+ test("treats positive infinity as the top level rather than returning it", () => {
283
+ expect(
284
+ StatusPageGroupNestingLayoutUtil.getVisualDepth({
285
+ depth: Number.POSITIVE_INFINITY,
286
+ }),
287
+ ).toBe(0);
288
+ });
289
+
290
+ test("floors a fractional depth", () => {
291
+ expect(
292
+ StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: 2.9 }),
293
+ ).toBe(2);
294
+ expect(
295
+ StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: 0.9 }),
296
+ ).toBe(0);
297
+ });
298
+
299
+ test("never decreases as depth increases", () => {
300
+ let previous: number = StatusPageGroupNestingLayoutUtil.getVisualDepth({
301
+ depth: 0,
302
+ });
303
+
304
+ for (let depth: number = 1; depth <= 30; depth++) {
305
+ const current: number = StatusPageGroupNestingLayoutUtil.getVisualDepth(
306
+ {
307
+ depth: depth,
308
+ },
309
+ );
310
+
311
+ expect(current).toBeGreaterThanOrEqual(previous);
312
+ previous = current;
313
+ }
314
+ });
315
+ });
316
+
317
+ describe("isRootLevel", () => {
318
+ test("only the top level, and anything that degrades to it, is root", () => {
319
+ expect(StatusPageGroupNestingLayoutUtil.isRootLevel({ depth: 0 })).toBe(
320
+ true,
321
+ );
322
+ expect(StatusPageGroupNestingLayoutUtil.isRootLevel({ depth: -5 })).toBe(
323
+ true,
324
+ );
325
+ expect(
326
+ StatusPageGroupNestingLayoutUtil.isRootLevel({ depth: Number.NaN }),
327
+ ).toBe(true);
328
+ expect(StatusPageGroupNestingLayoutUtil.isRootLevel({ depth: 1 })).toBe(
329
+ false,
330
+ );
331
+ expect(StatusPageGroupNestingLayoutUtil.isRootLevel({ depth: 12 })).toBe(
332
+ false,
333
+ );
334
+ });
335
+ });
336
+
337
+ describe("title sizing", () => {
338
+ /*
339
+ * The regression this guards: resource names rendered at text-lg while a
340
+ * nested group heading rendered at text-sm, so the leaf looked more
341
+ * important than the branch it hung off. Both sides are measured off the
342
+ * class strings that render, so shrinking either one fails here.
343
+ */
344
+ test("a group title is never smaller than the resources inside it", () => {
345
+ const resourcePx: number = fontSizePx(
346
+ StatusPageGroupNestingLayoutUtil.getResourceTitleClassName(),
347
+ );
348
+
349
+ expect(resourcePx).toBeGreaterThan(0);
350
+
351
+ for (let depth: number = 0; depth <= 30; depth++) {
352
+ expect(
353
+ fontSizePx(
354
+ StatusPageGroupNestingLayoutUtil.getTitleClassName({
355
+ depth: depth,
356
+ }),
357
+ ),
358
+ ).toBeGreaterThanOrEqual(resourcePx);
359
+ }
360
+ });
361
+
362
+ test("title size never grows as the hierarchy gets deeper", () => {
363
+ let previous: number = fontSizePx(
364
+ StatusPageGroupNestingLayoutUtil.getTitleClassName({ depth: 0 }),
365
+ );
366
+
367
+ expect(previous).toBeGreaterThan(0);
368
+
369
+ for (let depth: number = 1; depth <= 30; depth++) {
370
+ const current: number = fontSizePx(
371
+ StatusPageGroupNestingLayoutUtil.getTitleClassName({ depth: depth }),
372
+ );
373
+
374
+ expect(current).toBeGreaterThan(0);
375
+ expect(current).toBeLessThanOrEqual(previous);
376
+ previous = current;
377
+ }
378
+ });
379
+
380
+ test("the top level outranks everything below it", () => {
381
+ expect(
382
+ fontSizePx(
383
+ StatusPageGroupNestingLayoutUtil.getTitleClassName({ depth: 0 }),
384
+ ),
385
+ ).toBeGreaterThan(
386
+ fontSizePx(
387
+ StatusPageGroupNestingLayoutUtil.getTitleClassName({ depth: 1 }),
388
+ ),
389
+ );
390
+ });
391
+
392
+ /*
393
+ * A group title is heavier than a resource name at the same size - that is
394
+ * what separates a heading from a reading when the rail is the only other
395
+ * cue.
396
+ */
397
+ test("a group title is heavier than a resource name", () => {
398
+ expect(
399
+ StatusPageGroupNestingLayoutUtil.getResourceTitleClassName(),
400
+ ).toContain("font-medium");
401
+
402
+ for (let depth: number = 0; depth <= 12; depth++) {
403
+ const className: string =
404
+ StatusPageGroupNestingLayoutUtil.getTitleClassName({ depth: depth });
405
+
406
+ expect(className).toContain("font-semibold");
407
+ expect(className).toContain("text-gray-900");
408
+ }
409
+ });
410
+
411
+ test("nested titles are all styled the same - depth is shown by the rail", () => {
412
+ const atOne: string = StatusPageGroupNestingLayoutUtil.getTitleClassName({
413
+ depth: 1,
414
+ });
415
+
416
+ for (let depth: number = 2; depth <= 12; depth++) {
417
+ expect(
418
+ StatusPageGroupNestingLayoutUtil.getTitleClassName({ depth: depth }),
419
+ ).toBe(atOne);
420
+ }
421
+ });
422
+ });
423
+
424
+ describe("getContainerClassName", () => {
425
+ test("the top level is a card", () => {
426
+ const className: string =
427
+ StatusPageGroupNestingLayoutUtil.getContainerClassName({ depth: 0 });
428
+
429
+ expect(className).toContain("bg-white");
430
+ expect(className).toContain("rounded-xl");
431
+ expect(className).toContain("shadow");
432
+ });
433
+
434
+ /*
435
+ * The headline regression: a nested group must not introduce a surface of
436
+ * its own. Asserted as the absence of each surface class rather than as an
437
+ * empty string, so a future non-surface class (`relative`, say) does not
438
+ * fail the test while a returning card still does.
439
+ */
440
+ test("nothing nested adds a card, a border or a background", () => {
441
+ for (let depth: number = 1; depth <= 12; depth++) {
442
+ const className: string =
443
+ StatusPageGroupNestingLayoutUtil.getContainerClassName({
444
+ depth: depth,
445
+ });
446
+
447
+ expect(className).not.toMatch(/\bbg-/);
448
+ expect(className).not.toMatch(/\bborder/);
449
+ expect(className).not.toMatch(/\brounded/);
450
+ expect(className).not.toMatch(/\bshadow/);
451
+ expect(className).not.toMatch(/\bp[xytblr]?-/);
452
+ }
453
+ });
454
+ });
455
+
456
+ describe("getBodyClassName", () => {
457
+ test("the top level body has no rail - the card edge is the boundary", () => {
458
+ const className: string =
459
+ StatusPageGroupNestingLayoutUtil.getBodyClassName({ depth: 0 });
460
+
461
+ expect(className).not.toContain("border-l");
462
+ expect(className).not.toContain("pl-");
463
+ expect(StatusPageGroupNestingLayoutUtil.showRail({ depth: 0 })).toBe(
464
+ false,
465
+ );
466
+ });
467
+
468
+ test("every nested body hangs off exactly one hairline rail", () => {
469
+ for (let depth: number = 1; depth <= 12; depth++) {
470
+ const className: string =
471
+ StatusPageGroupNestingLayoutUtil.getBodyClassName({ depth: depth });
472
+
473
+ expect(className).toContain("border-l");
474
+ // border-l, not border-l-2: one hairline, however deep it goes.
475
+ expect(className).not.toContain("border-l-2");
476
+ expect(className).toContain("border-gray-200");
477
+ expect(className).toMatch(/pl-/);
478
+ expect(
479
+ StatusPageGroupNestingLayoutUtil.showRail({ depth: depth }),
480
+ ).toBe(true);
481
+ }
482
+ });
483
+
484
+ test("the indent tightens past MaxIndentedDepth instead of growing", () => {
485
+ expect(
486
+ nestingStepPx(StatusPageGroupNestingLayoutUtil.MaxIndentedDepth),
487
+ ).toBeLessThan(nestingStepPx(1));
488
+ });
489
+
490
+ /*
491
+ * A sub group is meant to read as hanging off its parent's title, not off
492
+ * the parent's chevron, so one nesting step moves its contents to where the
493
+ * parent's title text starts. Both sides are measured off the classes that
494
+ * render: the chevron box and header gap on one side, the body's margin,
495
+ * rail and padding on the other.
496
+ */
497
+ test("a nesting step lands a sub group's contents at its parent's title", () => {
498
+ const offset: number = titleTextOffsetPx();
499
+
500
+ expect(offset).toBeGreaterThan(0);
501
+ expect(nestingStepPx(1)).toBeGreaterThan(offset - 3);
502
+ expect(nestingStepPx(1)).toBeLessThanOrEqual(offset);
503
+ });
504
+
505
+ test("the top level costs no indent at all", () => {
506
+ expect(nestingStepPx(0)).toBe(0);
507
+ });
508
+
509
+ test("every nesting step costs something, so the rails stay apart", () => {
510
+ for (let depth: number = 1; depth <= 12; depth++) {
511
+ expect(nestingStepPx(depth)).toBeGreaterThan(0);
512
+ }
513
+ });
514
+
515
+ test("the indent never grows as the hierarchy gets deeper", () => {
516
+ let previous: number = nestingStepPx(1);
517
+
518
+ for (let depth: number = 2; depth <= 30; depth++) {
519
+ const current: number = nestingStepPx(depth);
520
+
521
+ expect(current).toBeLessThanOrEqual(previous);
522
+ previous = current;
523
+ }
524
+ });
525
+
526
+ /*
527
+ * The write path caps nesting at StatusPageGroupTreeUtil.MaxNestingDepth
528
+ * (10), so a fully nested resource is indented by the sum of ten steps. The
529
+ * status page content column is max-w-5xl minus padding, about 984px, and
530
+ * the uptime bars have to stay readable inside what is left.
531
+ */
532
+ test("the deepest nesting the write path allows costs a quarter of the column", () => {
533
+ let total: number = 0;
534
+
535
+ for (let depth: number = 1; depth <= 10; depth++) {
536
+ total += nestingStepPx(depth);
537
+ }
538
+
539
+ expect(total).toBeGreaterThan(0);
540
+ expect(total).toBeLessThan(250);
541
+ });
542
+
543
+ /* Every step past the cap has to be cheap, or depth 20 runs off the page. */
544
+ test("a step past the cap costs no more than 20px", () => {
545
+ expect(
546
+ nestingStepPx(StatusPageGroupNestingLayoutUtil.MaxIndentedDepth + 5),
547
+ ).toBeLessThanOrEqual(20);
548
+ });
549
+
550
+ test("the rail is placed under its parent's chevron, not flush left", () => {
551
+ expect(
552
+ spacingPx(
553
+ StatusPageGroupNestingLayoutUtil.getBodyClassName({ depth: 1 }),
554
+ "ml-",
555
+ ),
556
+ ).toBeGreaterThan(0);
557
+ });
558
+
559
+ test("depths beyond the cap all render the same tightened indent", () => {
560
+ const atCap: string = StatusPageGroupNestingLayoutUtil.getBodyClassName({
561
+ depth: StatusPageGroupNestingLayoutUtil.MaxIndentedDepth,
562
+ });
563
+
564
+ for (const depth of ABSURD_DEPTHS) {
565
+ expect(
566
+ StatusPageGroupNestingLayoutUtil.getBodyClassName({ depth: depth }),
567
+ ).toBe(atCap);
568
+ }
569
+ });
570
+
571
+ test("a bad depth falls back to the top level body", () => {
572
+ expect(
573
+ StatusPageGroupNestingLayoutUtil.getBodyClassName({
574
+ depth: Number.NaN,
575
+ }),
576
+ ).toBe(StatusPageGroupNestingLayoutUtil.getBodyClassName({ depth: 0 }));
577
+ });
578
+ });
579
+
580
+ describe("getHeaderClassName", () => {
581
+ test("the header is a focusable control that pushes its two ends apart", () => {
582
+ for (let depth: number = 0; depth <= 12; depth++) {
583
+ const className: string =
584
+ StatusPageGroupNestingLayoutUtil.getHeaderClassName({ depth: depth });
585
+
586
+ expect(tokensOf(className)).toContain("flex");
587
+ expect(className).toContain("justify-between");
588
+ expect(className).toContain("focus-visible:ring-2");
589
+ }
590
+ });
591
+
592
+ /*
593
+ * The hover background bleeds to the edge of the content column via equal
594
+ * and opposite margin and padding, and the width has to pay for both margins
595
+ * or the box is over-constrained: the browser then drops margin-right (CSS
596
+ * 2.1 10.3.3) and every rollup lands 16px short of the card edge the resource
597
+ * readings sit on. `w-full` is exactly that mistake.
598
+ */
599
+ test("the bleed is symmetric and paid for, so the rollup lines up with the readings below", () => {
600
+ for (let depth: number = 0; depth <= 12; depth++) {
601
+ const className: string =
602
+ StatusPageGroupNestingLayoutUtil.getHeaderClassName({ depth: depth });
603
+
604
+ const bleedPerSide: number = spacingPx(className, "-mx-");
605
+
606
+ expect(bleedPerSide).toBeGreaterThan(0);
607
+ expect(spacingPx(className, "px-")).toBe(bleedPerSide);
608
+
609
+ /* A button shrink wraps without a width, even as a flex container. */
610
+ expect(tokensOf(className)).not.toContain("w-full");
611
+ expect(className).toContain(
612
+ `w-[calc(100%+${(bleedPerSide * 2) / 16}rem)]`,
613
+ );
614
+ }
615
+ });
616
+ });
617
+
618
+ describe("spacing and chrome tokens", () => {
619
+ /*
620
+ * Measured, not restated: the description's indent has to equal the chevron
621
+ * box plus the gap after it, both of which are classes on the header.
622
+ */
623
+ test("the description lines up with the title, not the chevron", () => {
624
+ const offset: number = titleTextOffsetPx();
625
+
626
+ expect(offset).toBeGreaterThan(0);
627
+ expect(
628
+ spacingPx(
629
+ StatusPageGroupNestingLayoutUtil.getDescriptionClassName(),
630
+ "pl-",
631
+ ),
632
+ ).toBe(offset);
633
+ });
634
+
635
+ test("the ungrouped card is the same surface as a top level group", () => {
636
+ const card: string =
637
+ StatusPageGroupNestingLayoutUtil.getUngroupedResourcesCardClassName();
638
+
639
+ expect(card).toContain("bg-white");
640
+ expect(card).toContain("rounded-xl");
641
+ expect(card).toContain("shadow");
642
+ });
643
+
644
+ /*
645
+ * A rollup and a resource reading are the same kind of number, so they are
646
+ * set at the same size - measured off both class strings rather than
647
+ * asserting the literals one of them happens to contain.
648
+ */
649
+ test("a rollup is sized like the resource readings below it", () => {
650
+ expect(
651
+ fontSizePx(StatusPageGroupNestingLayoutUtil.getRollupClassName()),
652
+ ).toBe(
653
+ fontSizePx(
654
+ StatusPageGroupNestingLayoutUtil.getResourceTitleClassName(),
655
+ ),
656
+ );
657
+ expect(StatusPageGroupNestingLayoutUtil.getRollupClassName()).toContain(
658
+ "flex-shrink-0",
659
+ );
660
+ });
661
+
662
+ test("a rollup is marked with a dot so it does not read as a resource's own", () => {
663
+ const dot: string =
664
+ StatusPageGroupNestingLayoutUtil.getRollupDotClassName();
665
+
666
+ expect(dot).toContain("rounded-full");
667
+ expect(dot).toContain("flex-shrink-0");
668
+ expect(
669
+ StatusPageGroupNestingLayoutUtil.getRollupLabelClassName(),
670
+ ).toContain("tabular-nums");
671
+ });
672
+
673
+ test("the sub group list is spaced at every depth", () => {
674
+ for (let depth: number = 0; depth <= 12; depth++) {
675
+ expect(
676
+ StatusPageGroupNestingLayoutUtil.getSubGroupListClassName({
677
+ depth: depth,
678
+ }),
679
+ ).toMatch(/space-y-/);
680
+ }
681
+ });
682
+
683
+ /*
684
+ * justify-between is inert without flex, and the axis exists precisely to
685
+ * push "90 days ago" and "Today" to opposite ends.
686
+ */
687
+ test("the time axis pushes its two ends apart", () => {
688
+ const axis: string =
689
+ StatusPageGroupNestingLayoutUtil.getTimeAxisClassName();
690
+
691
+ expect(tokensOf(axis)).toContain("flex");
692
+ expect(tokensOf(axis)).toContain("justify-between");
693
+ });
694
+
695
+ test("the resource list carries its own rhythm", () => {
696
+ expect(
697
+ StatusPageGroupNestingLayoutUtil.getResourceListClassName(),
698
+ ).toMatch(/space-y-/);
699
+ });
700
+
701
+ test("the sub group count badge is a quiet pill", () => {
702
+ const badge: string =
703
+ StatusPageGroupNestingLayoutUtil.getSubGroupCountBadgeClassName();
704
+
705
+ expect(badge).toContain("rounded-full");
706
+ expect(badge).toContain("text-gray-500");
707
+ expect(badge).toContain("flex-shrink-0");
708
+ });
709
+ });
710
+
711
+ describe("getRollupKind", () => {
712
+ test("a healthy group configured for uptime shows its uptime percent", () => {
713
+ expect(
714
+ StatusPageGroupNestingLayoutUtil.getRollupKind({
715
+ showUptimePercent: true,
716
+ showCurrentStatus: true,
717
+ isCurrentlyDown: false,
718
+ uptimePercent: 99.9,
719
+ }),
720
+ ).toBe(StatusPageGroupRollupKind.UptimePercent);
721
+ });
722
+
723
+ /*
724
+ * "99.9% uptime" next to a group that is down right now is not what a
725
+ * visitor came to read.
726
+ */
727
+ test("a group that is down shows its status, not a percent", () => {
728
+ expect(
729
+ StatusPageGroupNestingLayoutUtil.getRollupKind({
730
+ showUptimePercent: true,
731
+ showCurrentStatus: true,
732
+ isCurrentlyDown: true,
733
+ uptimePercent: 99.9,
734
+ }),
735
+ ).toBe(StatusPageGroupRollupKind.CurrentStatus);
736
+ });
737
+
738
+ /*
739
+ * A group with nothing under it has no uptime to average. Showing a zero
740
+ * would read as an outage.
741
+ */
742
+ test("a group with no uptime to report shows nothing rather than a zero", () => {
743
+ expect(
744
+ StatusPageGroupNestingLayoutUtil.getRollupKind({
745
+ showUptimePercent: true,
746
+ showCurrentStatus: true,
747
+ isCurrentlyDown: false,
748
+ uptimePercent: null,
749
+ }),
750
+ ).toBe(StatusPageGroupRollupKind.None);
751
+ });
752
+
753
+ test("a group configured for status only shows its status", () => {
754
+ expect(
755
+ StatusPageGroupNestingLayoutUtil.getRollupKind({
756
+ showUptimePercent: false,
757
+ showCurrentStatus: true,
758
+ isCurrentlyDown: false,
759
+ uptimePercent: 100,
760
+ }),
761
+ ).toBe(StatusPageGroupRollupKind.CurrentStatus);
762
+ });
763
+
764
+ test("a group configured for neither shows nothing", () => {
765
+ expect(
766
+ StatusPageGroupNestingLayoutUtil.getRollupKind({
767
+ showUptimePercent: false,
768
+ showCurrentStatus: false,
769
+ isCurrentlyDown: false,
770
+ uptimePercent: 100,
771
+ }),
772
+ ).toBe(StatusPageGroupRollupKind.None);
773
+ });
774
+
775
+ test("a group that is down and shows no status shows nothing", () => {
776
+ expect(
777
+ StatusPageGroupNestingLayoutUtil.getRollupKind({
778
+ showUptimePercent: true,
779
+ showCurrentStatus: false,
780
+ isCurrentlyDown: true,
781
+ uptimePercent: 99.9,
782
+ }),
783
+ ).toBe(StatusPageGroupRollupKind.None);
784
+ });
785
+
786
+ test("a zero percent uptime is still a percent, not nothing", () => {
787
+ expect(
788
+ StatusPageGroupNestingLayoutUtil.getRollupKind({
789
+ showUptimePercent: true,
790
+ showCurrentStatus: true,
791
+ isCurrentlyDown: false,
792
+ uptimePercent: 0,
793
+ }),
794
+ ).toBe(StatusPageGroupRollupKind.UptimePercent);
795
+ });
796
+ });
797
+
798
+ describe("shouldShowSubGroupDivider", () => {
799
+ /*
800
+ * Without the line, a group's last resource reads as the first row of the
801
+ * first sub group below it.
802
+ */
803
+ test("a line is drawn only between own resources and sub groups", () => {
804
+ expect(
805
+ StatusPageGroupNestingLayoutUtil.shouldShowSubGroupDivider({
806
+ hasOwnResources: true,
807
+ subGroupCount: 1,
808
+ }),
809
+ ).toBe(true);
810
+ expect(
811
+ StatusPageGroupNestingLayoutUtil.shouldShowSubGroupDivider({
812
+ hasOwnResources: true,
813
+ subGroupCount: 0,
814
+ }),
815
+ ).toBe(false);
816
+ expect(
817
+ StatusPageGroupNestingLayoutUtil.shouldShowSubGroupDivider({
818
+ hasOwnResources: false,
819
+ subGroupCount: 3,
820
+ }),
821
+ ).toBe(false);
822
+ expect(
823
+ StatusPageGroupNestingLayoutUtil.shouldShowSubGroupDivider({
824
+ hasOwnResources: false,
825
+ subGroupCount: 0,
826
+ }),
827
+ ).toBe(false);
828
+ });
829
+
830
+ test("the divider is a hairline with padding, not a margin", () => {
831
+ const divider: string =
832
+ StatusPageGroupNestingLayoutUtil.getSubGroupDividerClassName();
833
+
834
+ expect(divider).toContain("border-t");
835
+ expect(divider).toMatch(/pt-/);
836
+ });
837
+ });
838
+
839
+ describe("shouldRenderOwnResources", () => {
840
+ /*
841
+ * A group that exists only to hold sub groups is not empty - the sub groups
842
+ * are its content - so it must not render the "no resources" message.
843
+ */
844
+ test("a holder group with sub groups renders no resource list of its own", () => {
845
+ expect(
846
+ StatusPageGroupNestingLayoutUtil.shouldRenderOwnResources({
847
+ ownResourceCount: 0,
848
+ subGroupCount: 2,
849
+ }),
850
+ ).toBe(false);
851
+ });
852
+
853
+ test("a group with resources always renders them", () => {
854
+ expect(
855
+ StatusPageGroupNestingLayoutUtil.shouldRenderOwnResources({
856
+ ownResourceCount: 1,
857
+ subGroupCount: 0,
858
+ }),
859
+ ).toBe(true);
860
+ expect(
861
+ StatusPageGroupNestingLayoutUtil.shouldRenderOwnResources({
862
+ ownResourceCount: 4,
863
+ subGroupCount: 3,
864
+ }),
865
+ ).toBe(true);
866
+ });
867
+
868
+ /*
869
+ * A truly empty group still renders the list, because that is where the
870
+ * "no resources added to this group" message lives.
871
+ */
872
+ test("a group with neither resources nor sub groups still renders the list", () => {
873
+ expect(
874
+ StatusPageGroupNestingLayoutUtil.shouldRenderOwnResources({
875
+ ownResourceCount: 0,
876
+ subGroupCount: 0,
877
+ }),
878
+ ).toBe(true);
879
+ });
880
+ });
881
+ describe("shouldRenderTimeAxis", () => {
882
+ const CHARTED: ObjectID = new ObjectID(
883
+ "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
884
+ );
885
+ const UNCHARTED: ObjectID = new ObjectID(
886
+ "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
887
+ );
888
+
889
+ test("a group with a charted resource of its own draws one axis", () => {
890
+ const groups: Array<StatusPageGroup> = makeHierarchy();
891
+
892
+ expect(
893
+ StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
894
+ statusPageGroup: groups[0]!,
895
+ statusPageResources: [
896
+ makeResource({
897
+ id: CHARTED,
898
+ name: "Corporate API",
899
+ groupId: CORPORATE,
900
+ showStatusHistoryChart: true,
901
+ }),
902
+ ],
903
+ }),
904
+ ).toBe(true);
905
+ });
906
+
907
+ /*
908
+ * The axis belongs next to the bars it labels. A sub group's bars are
909
+ * indented past this group's, and a sub group may be collapsed, so this
910
+ * group's axis must not claim to describe them.
911
+ */
912
+ test("a descendant's charted resource does not put an axis on the parent", () => {
913
+ const groups: Array<StatusPageGroup> = makeHierarchy();
914
+
915
+ expect(
916
+ StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
917
+ statusPageGroup: groups[0]!,
918
+ statusPageResources: [
919
+ makeResource({
920
+ id: CHARTED,
921
+ name: "Unit 0152 API",
922
+ groupId: UNIT,
923
+ showStatusHistoryChart: true,
924
+ }),
925
+ ],
926
+ }),
927
+ ).toBe(false);
928
+ });
929
+
930
+ test("a group whose resources draw no history chart draws no axis", () => {
931
+ const groups: Array<StatusPageGroup> = makeHierarchy();
932
+
933
+ expect(
934
+ StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
935
+ statusPageGroup: groups[0]!,
936
+ statusPageResources: [
937
+ makeResource({
938
+ id: UNCHARTED,
939
+ name: "Corporate API",
940
+ groupId: CORPORATE,
941
+ showStatusHistoryChart: false,
942
+ }),
943
+ ],
944
+ }),
945
+ ).toBe(false);
946
+ });
947
+
948
+ /*
949
+ * A grid group draws a matrix of cells rather than bars over a window, so a
950
+ * "90 days ago ... Today" axis would be labelling something that is not
951
+ * there.
952
+ */
953
+ test("a grid view group draws no axis", () => {
954
+ const gridGroup: StatusPageGroup = makeGroup({
955
+ id: CORPORATE,
956
+ name: "Corporate",
957
+ order: 1,
958
+ viewMode: StatusPageGroupViewMode.Grid,
959
+ });
960
+
961
+ expect(
962
+ StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
963
+ statusPageGroup: gridGroup,
964
+ statusPageResources: [
965
+ makeResource({
966
+ id: CHARTED,
967
+ name: "Corporate API",
968
+ groupId: CORPORATE,
969
+ showStatusHistoryChart: true,
970
+ }),
971
+ ],
972
+ }),
973
+ ).toBe(false);
974
+ });
975
+
976
+ test("a list view group draws one", () => {
977
+ const listGroup: StatusPageGroup = makeGroup({
978
+ id: CORPORATE,
979
+ name: "Corporate",
980
+ order: 1,
981
+ viewMode: StatusPageGroupViewMode.List,
982
+ });
983
+
984
+ expect(
985
+ StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
986
+ statusPageGroup: listGroup,
987
+ statusPageResources: [
988
+ makeResource({
989
+ id: CHARTED,
990
+ name: "Corporate API",
991
+ groupId: CORPORATE,
992
+ showStatusHistoryChart: true,
993
+ }),
994
+ ],
995
+ }),
996
+ ).toBe(true);
997
+ });
998
+
999
+ test("the ungrouped block draws an axis for its own resources", () => {
1000
+ expect(
1001
+ StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
1002
+ statusPageGroup: null,
1003
+ statusPageResources: [
1004
+ makeResource({
1005
+ id: CHARTED,
1006
+ name: "Ungrouped",
1007
+ showStatusHistoryChart: true,
1008
+ }),
1009
+ ],
1010
+ }),
1011
+ ).toBe(true);
1012
+ });
1013
+
1014
+ test("a grouped resource does not put an axis on the ungrouped block", () => {
1015
+ expect(
1016
+ StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
1017
+ statusPageGroup: null,
1018
+ statusPageResources: [
1019
+ makeResource({
1020
+ id: CHARTED,
1021
+ name: "Corporate API",
1022
+ groupId: CORPORATE,
1023
+ showStatusHistoryChart: true,
1024
+ }),
1025
+ ],
1026
+ }),
1027
+ ).toBe(false);
1028
+ });
1029
+
1030
+ test("a resource with nothing to draw cannot bring an axis with it", () => {
1031
+ expect(
1032
+ StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
1033
+ statusPageGroup: null,
1034
+ statusPageResources: [
1035
+ makeResource({
1036
+ id: CHARTED,
1037
+ name: "Ungrouped",
1038
+ showStatusHistoryChart: true,
1039
+ withMonitor: false,
1040
+ }),
1041
+ ],
1042
+ }),
1043
+ ).toBe(false);
1044
+ });
1045
+
1046
+ test("a monitor group resource can bring an axis with it", () => {
1047
+ expect(
1048
+ StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
1049
+ statusPageGroup: null,
1050
+ statusPageResources: [
1051
+ makeResource({
1052
+ id: CHARTED,
1053
+ name: "Ungrouped",
1054
+ showStatusHistoryChart: true,
1055
+ withMonitor: false,
1056
+ monitorGroupId: UNCHARTED,
1057
+ }),
1058
+ ],
1059
+ }),
1060
+ ).toBe(true);
1061
+ });
1062
+
1063
+ test("an empty page has no axis", () => {
1064
+ expect(
1065
+ StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
1066
+ statusPageGroup: null,
1067
+ statusPageResources: [],
1068
+ }),
1069
+ ).toBe(false);
1070
+ });
1071
+ });
1072
+ });