@oneuptime/common 12.0.4 → 12.0.5

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 (374) hide show
  1. package/Models/DatabaseModels/AIRun.ts +38 -1
  2. package/Models/DatabaseModels/AlertFeed.ts +30 -0
  3. package/Models/DatabaseModels/Dashboard.ts +28 -0
  4. package/Models/DatabaseModels/Incident.ts +2 -0
  5. package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
  6. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
  7. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  8. package/Models/DatabaseModels/IncidentFeed.ts +30 -0
  9. package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
  10. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  11. package/Models/DatabaseModels/LogSavedView.ts +33 -0
  12. package/Models/DatabaseModels/Project.ts +231 -9
  13. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
  14. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
  15. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  16. package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.ts +4 -4
  17. package/Models/DatabaseModels/StatusPage.ts +28 -0
  18. package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
  19. package/Server/API/AIAgentDataAPI.ts +44 -55
  20. package/Server/API/AIInvestigationAPI.ts +175 -17
  21. package/Server/API/DashboardAPI.ts +10 -10
  22. package/Server/API/StatusPageAPI.ts +38 -19
  23. package/Server/EnvironmentConfig.ts +16 -0
  24. package/Server/Infrastructure/ClickhouseConfig.ts +15 -1
  25. package/Server/Infrastructure/ClickhouseDatabase.ts +1 -1
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.ts +27 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.ts +89 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.ts +27 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.ts +40 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.ts +26 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  32. package/Server/Services/AIRunService.ts +78 -10
  33. package/Server/Services/AIService.ts +94 -22
  34. package/Server/Services/AlertFeedService.ts +5 -0
  35. package/Server/Services/AnalyticsDatabaseService.ts +29 -0
  36. package/Server/Services/DatabaseService.ts +43 -2
  37. package/Server/Services/IncidentFeedService.ts +5 -0
  38. package/Server/Services/LlmLogService.ts +46 -2
  39. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +6 -0
  40. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +4 -0
  41. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +8 -5
  42. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +90 -16
  43. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +2 -0
  44. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +2 -0
  45. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +223 -22
  46. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +25 -24
  47. package/Server/Utils/AI/SRE/ConfidenceSignal.ts +182 -71
  48. package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +252 -43
  49. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +25 -24
  50. package/Server/Utils/AI/SRE/InstrumentationTaskTrigger.ts +47 -13
  51. package/Server/Utils/AI/SRE/InvestigationGrader.ts +9 -5
  52. package/Server/Utils/AI/SRE/InvestigationQueue.ts +141 -24
  53. package/Server/Utils/AI/SRE/InvestigationSubjectLock.ts +66 -0
  54. package/Server/Utils/AI/SRE/PostedRootCause.ts +94 -3
  55. package/Server/Utils/AI/SRE/README.md +1 -1
  56. package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +132 -10
  57. package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +24 -0
  58. package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +29 -0
  59. package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +56 -0
  60. package/Server/Utils/Monitor/MonitorMetricUtil.ts +46 -0
  61. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -0
  62. package/Server/Utils/PasswordHash.ts +28 -4
  63. package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +251 -0
  64. package/Tests/App/Dashboard/EventStatusPanel.test.tsx +747 -0
  65. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +480 -0
  66. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1184 -0
  67. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +500 -0
  68. package/Tests/App/Dashboard/PortMonitorCriteriaFilter.test.ts +93 -0
  69. package/Tests/App/StatusPage/PublicStatusPageAPIErrorHandling.test.ts +117 -0
  70. package/Tests/App/StatusPage/StatusPageModelAPIPolymorphism.test.ts +223 -0
  71. package/Tests/Models/DatabaseModels/PermissionCatalogueCoverage.test.ts +243 -0
  72. package/Tests/Server/API/AIAgentDataFixFromIncidentContext.test.ts +266 -0
  73. package/Tests/Server/API/AIInvestigationAPI.test.ts +594 -0
  74. package/Tests/Server/API/AIInvestigationCreateFixTask.test.ts +80 -0
  75. package/Tests/Server/API/DashboardMasterPasswordAPI.test.ts +172 -1
  76. package/Tests/Server/API/StatusPageMasterPasswordAPI.test.ts +335 -0
  77. package/Tests/Server/Services/AIRunCodeFixClaim.test.ts +59 -5
  78. package/Tests/Server/Services/AIRunHumanVerdict.test.ts +127 -58
  79. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +396 -20
  80. package/Tests/Server/Services/AddMasterPasswordSaltMigration.test.ts +283 -0
  81. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +25 -1
  82. package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +45 -35
  83. package/Tests/Server/Services/DatabaseServiceUpdateColumnsWithoutHooks.test.ts +54 -0
  84. package/Tests/Server/Services/FeedAIRunAssociation.test.ts +125 -0
  85. package/Tests/Server/Services/FixFromIncidentTaskTrigger.test.ts +418 -57
  86. package/Tests/Server/Services/InstrumentationTaskTrigger.test.ts +173 -14
  87. package/Tests/Server/Services/MasterPasswordScrypt.test.ts +567 -0
  88. package/Tests/Server/Services/SeparateIncidentAlertAiSettingsMigration.test.ts +194 -0
  89. package/Tests/Server/Utils/AI/AIAlertGating.test.ts +37 -4
  90. package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +330 -29
  91. package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +25 -0
  92. package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +293 -5
  93. package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +59 -1
  94. package/Tests/Server/Utils/AI/FixFromIncidentAutoTrigger.test.ts +342 -19
  95. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +214 -30
  96. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +2 -0
  97. package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +65 -17
  98. package/Tests/Server/Utils/AI/InvestigationQueueRemediationExecution.test.ts +3 -0
  99. package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +313 -20
  100. package/Tests/Server/Utils/AI/PostedRootCause.test.ts +254 -0
  101. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +4 -0
  102. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +2 -0
  103. package/Tests/Server/Utils/AI/SubjectCodeFixRunDedupe.test.ts +44 -0
  104. package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +30 -0
  105. package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +212 -0
  106. package/Tests/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.test.ts +401 -0
  107. package/Tests/Server/Utils/Monitor/MonitorMetricUtilPortTimings.test.ts +243 -0
  108. package/Tests/Server/Utils/PasswordHash.test.ts +30 -0
  109. package/Tests/Types/Dashboard/DashboardTemplates.test.ts +111 -0
  110. package/Tests/Types/Monitor/CephMetricCatalog.test.ts +104 -0
  111. package/Tests/Types/Monitor/CriteriaFilter.test.ts +4 -0
  112. package/Tests/Types/Monitor/DockerMetricCatalog.test.ts +109 -0
  113. package/Tests/Types/Monitor/DockerSwarmMetricCatalog.test.ts +118 -0
  114. package/Tests/Types/Monitor/HostMetricCatalog.test.ts +104 -0
  115. package/Tests/Types/Monitor/IotMetricCatalog.test.ts +104 -0
  116. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +118 -0
  117. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +42 -0
  118. package/Tests/Types/Monitor/MonitorStep.test.ts +38 -0
  119. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +75 -0
  120. package/Tests/Types/Monitor/PodmanMetricCatalog.test.ts +109 -0
  121. package/Tests/Types/Monitor/ProxmoxMetricCatalog.test.ts +107 -0
  122. package/Tests/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.test.ts +151 -0
  123. package/Tests/Types/Permission.test.ts +137 -0
  124. package/Tests/Types/Rum/SessionReplayMaskingMode.test.ts +101 -0
  125. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +5 -5
  126. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +3 -5
  127. package/Tests/UI/Components/ErrorBoundary.test.tsx +158 -0
  128. package/Tests/UI/Components/FeedItemSafeMode.test.tsx +74 -0
  129. package/Tests/UI/Components/MoreMenu.test.tsx +756 -0
  130. package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +62 -0
  131. package/Tests/UI/Monitor/PortMonitorView.test.tsx +237 -0
  132. package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +91 -0
  133. package/Tests/UI/Utils/DownloadFile.test.ts +103 -0
  134. package/Tests/UI/Utils/ErrorSupportBundle.test.ts +582 -0
  135. package/Tests/UI/Utils/Project.test.ts +31 -0
  136. package/Tests/UI/Utils/StatusPageModelAPIInjection.test.ts +245 -0
  137. package/Tests/UI/Utils/UseDashboardGridDnd.test.tsx +820 -0
  138. package/Tests/Utils/Dashboard/DashboardViewConfig.test.ts +329 -0
  139. package/Tests/Utils/Dashboard/GridLayout.test.ts +951 -0
  140. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +117 -0
  141. package/Tests/Utils/RecordingRuleExpression.test.ts +327 -0
  142. package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +186 -0
  143. package/Tests/Utils/StatusPage/GroupTree.test.ts +456 -0
  144. package/Tests/Utils/StatusPage/OverviewGroupHierarchyVisibility.test.ts +588 -0
  145. package/Tests/Utils/Telemetry/SavedViewTimeRange.test.ts +244 -0
  146. package/Tests/Utils/Telemetry/TelemetryQueryTimeRange.test.ts +825 -0
  147. package/Types/AI/AIRunCodeFixRecommendation.ts +17 -0
  148. package/Types/AI/CodeFixTaskContext.ts +63 -3
  149. package/Types/AI/CodeFixTaskType.ts +3 -3
  150. package/Types/HashedString.ts +4 -3
  151. package/Types/Monitor/CriteriaFilter.ts +4 -0
  152. package/Types/Monitor/DomainMonitor/DomainLookupMethod.ts +23 -0
  153. package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +7 -0
  154. package/Types/Monitor/MonitorCriteriaInstance.ts +24 -7
  155. package/Types/Monitor/MonitorMetricType.ts +8 -0
  156. package/Types/Monitor/MonitorStep.ts +10 -1
  157. package/Types/Monitor/MonitorStepDomainMonitor.ts +21 -0
  158. package/Types/Monitor/PortMonitor/PortMonitorTimings.ts +10 -0
  159. package/Types/Permission.ts +23 -175
  160. package/Types/Probe/ProbeMonitorResponse.ts +6 -0
  161. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +2 -3
  162. package/UI/Components/ErrorBoundary.tsx +138 -11
  163. package/UI/Components/Feed/FeedItem.tsx +9 -2
  164. package/UI/Components/ModelDetail/CardModelDetail.tsx +0 -12
  165. package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
  166. package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +12 -2
  167. package/UI/Components/MoreMenu/MoreMenu.tsx +285 -97
  168. package/UI/Components/MoreMenu/MoreMenuItem.tsx +8 -5
  169. package/UI/Utils/DownloadFile.ts +16 -8
  170. package/UI/Utils/ErrorSupportBundle.ts +805 -0
  171. package/UI/Utils/ModelAPI/ModelAPI.ts +19 -8
  172. package/UI/Utils/Project.ts +4 -0
  173. package/UI/Utils/StatusPage.ts +8 -2
  174. package/UI/Utils/UseDashboardGridDnd.ts +917 -0
  175. package/Utils/Dashboard/DashboardViewConfig.ts +80 -36
  176. package/Utils/Dashboard/GridLayout.ts +523 -0
  177. package/Utils/Metrics/RecordingRuleExpression.ts +19 -17
  178. package/Utils/Monitor/MonitorMetricType.ts +44 -2
  179. package/Utils/StatusPage/GroupNestingLayout.ts +69 -0
  180. package/Utils/StatusPage/GroupTree.ts +157 -66
  181. package/Utils/StatusPage/ResourceUptime.ts +37 -2
  182. package/Utils/Telemetry/SavedViewTimeRange.ts +114 -0
  183. package/Utils/Telemetry/TelemetryQueryTimeRange.ts +334 -0
  184. package/build/dist/Models/DatabaseModels/AIRun.js +39 -1
  185. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  186. package/build/dist/Models/DatabaseModels/AlertFeed.js +32 -0
  187. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  188. package/build/dist/Models/DatabaseModels/Dashboard.js +29 -0
  189. package/build/dist/Models/DatabaseModels/Dashboard.js.map +1 -1
  190. package/build/dist/Models/DatabaseModels/Incident.js +2 -0
  191. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
  193. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
  195. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.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/IncidentFeed.js +32 -0
  199. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  200. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
  201. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  202. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  203. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/LogSavedView.js +33 -0
  205. package/build/dist/Models/DatabaseModels/LogSavedView.js.map +1 -1
  206. package/build/dist/Models/DatabaseModels/Project.js +241 -11
  207. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  208. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
  209. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  210. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
  211. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  212. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  213. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  214. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js +4 -4
  215. package/build/dist/Models/DatabaseModels/StatusPage.js +29 -0
  216. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
  218. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  219. package/build/dist/Server/API/AIAgentDataAPI.js +28 -46
  220. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  221. package/build/dist/Server/API/AIInvestigationAPI.js +111 -11
  222. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  223. package/build/dist/Server/API/DashboardAPI.js +8 -5
  224. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  225. package/build/dist/Server/API/StatusPageAPI.js +29 -11
  226. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  227. package/build/dist/Server/EnvironmentConfig.js +14 -0
  228. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  229. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +12 -1
  230. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  231. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +1 -1
  232. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js +22 -0
  234. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js.map +1 -0
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js +36 -0
  236. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js.map +1 -0
  237. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js +18 -0
  238. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js.map +1 -0
  239. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js +19 -0
  240. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js.map +1 -0
  241. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js +19 -0
  242. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js.map +1 -0
  243. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  244. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  245. package/build/dist/Server/Services/AIRunService.js +58 -12
  246. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  247. package/build/dist/Server/Services/AIService.js +74 -21
  248. package/build/dist/Server/Services/AIService.js.map +1 -1
  249. package/build/dist/Server/Services/AlertFeedService.js +3 -0
  250. package/build/dist/Server/Services/AlertFeedService.js.map +1 -1
  251. package/build/dist/Server/Services/AnalyticsDatabaseService.js +29 -1
  252. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  253. package/build/dist/Server/Services/DatabaseService.js +21 -2
  254. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  255. package/build/dist/Server/Services/IncidentFeedService.js +3 -0
  256. package/build/dist/Server/Services/IncidentFeedService.js.map +1 -1
  257. package/build/dist/Server/Services/LlmLogService.js +22 -1
  258. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  259. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +3 -0
  260. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  261. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +4 -0
  262. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
  263. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +8 -5
  264. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
  265. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +63 -24
  266. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  267. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +2 -0
  268. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  269. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +2 -0
  270. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  271. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +156 -28
  272. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  273. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +21 -19
  274. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  275. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +128 -49
  276. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -1
  277. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +170 -31
  278. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -1
  279. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +21 -19
  280. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  281. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js +33 -9
  282. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js.map +1 -1
  283. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +9 -5
  284. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
  285. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +121 -41
  286. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
  287. package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js +46 -0
  288. package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js.map +1 -0
  289. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js +61 -14
  290. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js.map +1 -1
  291. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +79 -1
  292. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
  293. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js +21 -0
  294. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js.map +1 -1
  295. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +20 -3
  296. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
  297. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +43 -0
  298. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
  299. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +33 -0
  300. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  301. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -0
  302. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  303. package/build/dist/Server/Utils/PasswordHash.js +25 -4
  304. package/build/dist/Server/Utils/PasswordHash.js.map +1 -1
  305. package/build/dist/Types/AI/AIRunCodeFixRecommendation.js +18 -0
  306. package/build/dist/Types/AI/AIRunCodeFixRecommendation.js.map +1 -0
  307. package/build/dist/Types/AI/CodeFixTaskContext.js +23 -3
  308. package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -1
  309. package/build/dist/Types/AI/CodeFixTaskType.js +3 -3
  310. package/build/dist/Types/HashedString.js +4 -3
  311. package/build/dist/Types/HashedString.js.map +1 -1
  312. package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
  313. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  314. package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js +24 -0
  315. package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js.map +1 -0
  316. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +24 -7
  317. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  318. package/build/dist/Types/Monitor/MonitorMetricType.js +7 -0
  319. package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
  320. package/build/dist/Types/Monitor/MonitorStep.js +6 -1
  321. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  322. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js +15 -0
  323. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js.map +1 -1
  324. package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js +2 -0
  325. package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js.map +1 -0
  326. package/build/dist/Types/Permission.js +23 -155
  327. package/build/dist/Types/Permission.js.map +1 -1
  328. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +2 -5
  329. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  330. package/build/dist/UI/Components/ErrorBoundary.js +67 -8
  331. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  332. package/build/dist/UI/Components/Feed/FeedItem.js +2 -2
  333. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  334. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +0 -12
  335. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  336. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
  337. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  338. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +12 -2
  339. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
  340. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +193 -65
  341. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  342. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +1 -1
  343. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  344. package/build/dist/UI/Utils/DownloadFile.js +15 -8
  345. package/build/dist/UI/Utils/DownloadFile.js.map +1 -1
  346. package/build/dist/UI/Utils/ErrorSupportBundle.js +476 -0
  347. package/build/dist/UI/Utils/ErrorSupportBundle.js.map +1 -0
  348. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -8
  349. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  350. package/build/dist/UI/Utils/Project.js +3 -0
  351. package/build/dist/UI/Utils/Project.js.map +1 -1
  352. package/build/dist/UI/Utils/StatusPage.js +4 -4
  353. package/build/dist/UI/Utils/StatusPage.js.map +1 -1
  354. package/build/dist/UI/Utils/UseDashboardGridDnd.js +551 -0
  355. package/build/dist/UI/Utils/UseDashboardGridDnd.js.map +1 -0
  356. package/build/dist/Utils/Dashboard/DashboardViewConfig.js +42 -26
  357. package/build/dist/Utils/Dashboard/DashboardViewConfig.js.map +1 -1
  358. package/build/dist/Utils/Dashboard/GridLayout.js +338 -0
  359. package/build/dist/Utils/Dashboard/GridLayout.js.map +1 -0
  360. package/build/dist/Utils/Metrics/RecordingRuleExpression.js +19 -17
  361. package/build/dist/Utils/Metrics/RecordingRuleExpression.js.map +1 -1
  362. package/build/dist/Utils/Monitor/MonitorMetricType.js +38 -6
  363. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  364. package/build/dist/Utils/StatusPage/GroupNestingLayout.js +46 -0
  365. package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -1
  366. package/build/dist/Utils/StatusPage/GroupTree.js +79 -34
  367. package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -1
  368. package/build/dist/Utils/StatusPage/ResourceUptime.js +22 -1
  369. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  370. package/build/dist/Utils/Telemetry/SavedViewTimeRange.js +79 -0
  371. package/build/dist/Utils/Telemetry/SavedViewTimeRange.js.map +1 -0
  372. package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js +236 -0
  373. package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js.map +1 -0
  374. package/package.json +1 -1
@@ -716,6 +716,8 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
716
716
  showCurrentStatus: true,
717
717
  isCurrentlyDown: false,
718
718
  uptimePercent: 99.9,
719
+
720
+ resourceCountInSubtree: 1,
719
721
  }),
720
722
  ).toBe(StatusPageGroupRollupKind.UptimePercent);
721
723
  });
@@ -731,6 +733,8 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
731
733
  showCurrentStatus: true,
732
734
  isCurrentlyDown: true,
733
735
  uptimePercent: 99.9,
736
+
737
+ resourceCountInSubtree: 1,
734
738
  }),
735
739
  ).toBe(StatusPageGroupRollupKind.CurrentStatus);
736
740
  });
@@ -746,6 +750,8 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
746
750
  showCurrentStatus: true,
747
751
  isCurrentlyDown: false,
748
752
  uptimePercent: null,
753
+
754
+ resourceCountInSubtree: 1,
749
755
  }),
750
756
  ).toBe(StatusPageGroupRollupKind.None);
751
757
  });
@@ -757,6 +763,8 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
757
763
  showCurrentStatus: true,
758
764
  isCurrentlyDown: false,
759
765
  uptimePercent: 100,
766
+
767
+ resourceCountInSubtree: 1,
760
768
  }),
761
769
  ).toBe(StatusPageGroupRollupKind.CurrentStatus);
762
770
  });
@@ -768,6 +776,8 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
768
776
  showCurrentStatus: false,
769
777
  isCurrentlyDown: false,
770
778
  uptimePercent: 100,
779
+
780
+ resourceCountInSubtree: 1,
771
781
  }),
772
782
  ).toBe(StatusPageGroupRollupKind.None);
773
783
  });
@@ -779,6 +789,8 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
779
789
  showCurrentStatus: false,
780
790
  isCurrentlyDown: true,
781
791
  uptimePercent: 99.9,
792
+
793
+ resourceCountInSubtree: 1,
782
794
  }),
783
795
  ).toBe(StatusPageGroupRollupKind.None);
784
796
  });
@@ -790,9 +802,49 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
790
802
  showCurrentStatus: true,
791
803
  isCurrentlyDown: false,
792
804
  uptimePercent: 0,
805
+
806
+ resourceCountInSubtree: 1,
793
807
  }),
794
808
  ).toBe(StatusPageGroupRollupKind.UptimePercent);
795
809
  });
810
+
811
+ /*
812
+ * A hierarchy is drawn while it is still being filled in, so groups with
813
+ * nothing under them yet are on the page. The rolled up status defaults to
814
+ * Operational when it finds no resources to look at, and publishing that
815
+ * would be telling visitors that a group containing no monitors is healthy.
816
+ */
817
+ test("a group with nothing under it shows no rollup, whatever it is configured for", () => {
818
+ for (const showUptimePercent of [true, false]) {
819
+ for (const showCurrentStatus of [true, false]) {
820
+ expect(
821
+ StatusPageGroupNestingLayoutUtil.getRollupKind({
822
+ showUptimePercent: showUptimePercent,
823
+ showCurrentStatus: showCurrentStatus,
824
+ isCurrentlyDown: false,
825
+ uptimePercent: null,
826
+ resourceCountInSubtree: 0,
827
+ }),
828
+ ).toBe(StatusPageGroupRollupKind.None);
829
+ }
830
+ }
831
+ });
832
+
833
+ /*
834
+ * The count is of the whole subtree, so a holder group whose own resource
835
+ * list is empty still reports for the monitors nested under it.
836
+ */
837
+ test("a holder group reports the resources nested below it", () => {
838
+ expect(
839
+ StatusPageGroupNestingLayoutUtil.getRollupKind({
840
+ showUptimePercent: false,
841
+ showCurrentStatus: true,
842
+ isCurrentlyDown: false,
843
+ uptimePercent: null,
844
+ resourceCountInSubtree: 12,
845
+ }),
846
+ ).toBe(StatusPageGroupRollupKind.CurrentStatus);
847
+ });
796
848
  });
797
849
 
798
850
  describe("shouldShowSubGroupDivider", () => {
@@ -878,6 +930,140 @@ describe("StatusPageGroupNestingLayoutUtil", () => {
878
930
  ).toBe(true);
879
931
  });
880
932
  });
933
+
934
+ /*
935
+ * The overview page used to gate its whole resources-and-groups block on
936
+ * `statusPageResources.length > 0`. That made a status page carrying a fully
937
+ * built group hierarchy and no monitors render nothing at all: the operator
938
+ * created their groups, saw them listed in the dashboard, and got an empty
939
+ * public page until the first monitor was attached to one of them.
940
+ *
941
+ * Groups are content. These two gates are what says so, and they have to
942
+ * agree with each other - a page that renders its hierarchy must not also
943
+ * render an empty state above it saying there is nothing here.
944
+ */
945
+ describe("shouldRenderResourcesSection", () => {
946
+ test("renders for a page with groups and no resources at all", () => {
947
+ expect(
948
+ StatusPageGroupNestingLayoutUtil.shouldRenderResourcesSection({
949
+ statusPageResourceCount: 0,
950
+ statusPageGroupCount: 1,
951
+ }),
952
+ ).toBe(true);
953
+ });
954
+
955
+ /* The size the bug was reported at. */
956
+ test("renders for a large hierarchy with no resources", () => {
957
+ expect(
958
+ StatusPageGroupNestingLayoutUtil.shouldRenderResourcesSection({
959
+ statusPageResourceCount: 0,
960
+ statusPageGroupCount: 1506,
961
+ }),
962
+ ).toBe(true);
963
+ });
964
+
965
+ test("renders for a page with resources and no groups", () => {
966
+ expect(
967
+ StatusPageGroupNestingLayoutUtil.shouldRenderResourcesSection({
968
+ statusPageResourceCount: 3,
969
+ statusPageGroupCount: 0,
970
+ }),
971
+ ).toBe(true);
972
+ });
973
+
974
+ test("renders for a page with both", () => {
975
+ expect(
976
+ StatusPageGroupNestingLayoutUtil.shouldRenderResourcesSection({
977
+ statusPageResourceCount: 3,
978
+ statusPageGroupCount: 4,
979
+ }),
980
+ ).toBe(true);
981
+ });
982
+
983
+ test("renders nothing for a page with neither", () => {
984
+ expect(
985
+ StatusPageGroupNestingLayoutUtil.shouldRenderResourcesSection({
986
+ statusPageResourceCount: 0,
987
+ statusPageGroupCount: 0,
988
+ }),
989
+ ).toBe(false);
990
+ });
991
+ });
992
+
993
+ describe("shouldRenderOverviewEmptyState", () => {
994
+ type EmptyStateInput = Parameters<
995
+ typeof StatusPageGroupNestingLayoutUtil.shouldRenderOverviewEmptyState
996
+ >[0];
997
+
998
+ function emptyPage(overrides: Partial<EmptyStateInput> = {}): boolean {
999
+ return StatusPageGroupNestingLayoutUtil.shouldRenderOverviewEmptyState({
1000
+ statusPageResourceCount: 0,
1001
+ statusPageGroupCount: 0,
1002
+ activeIncidentCount: 0,
1003
+ activeEpisodeCount: 0,
1004
+ activeScheduledMaintenanceCount: 0,
1005
+ activeAnnouncementCount: 0,
1006
+ ...overrides,
1007
+ });
1008
+ }
1009
+
1010
+ test("a page with nothing on it shows the empty state", () => {
1011
+ expect(emptyPage()).toBe(true);
1012
+ });
1013
+
1014
+ /*
1015
+ * The half of the bug the visitor actually saw: 1506 groups on the page,
1016
+ * and an "all clear, nothing to see" panel rendered instead of them.
1017
+ */
1018
+ test("a page with groups and no resources does not show the empty state", () => {
1019
+ expect(emptyPage({ statusPageGroupCount: 1506 })).toBe(false);
1020
+ });
1021
+
1022
+ test("a page with resources does not show the empty state", () => {
1023
+ expect(emptyPage({ statusPageResourceCount: 1 })).toBe(false);
1024
+ });
1025
+
1026
+ test.each([
1027
+ ["an active incident", "activeIncidentCount"],
1028
+ ["an active episode", "activeEpisodeCount"],
1029
+ ["a scheduled maintenance event", "activeScheduledMaintenanceCount"],
1030
+ ["an announcement", "activeAnnouncementCount"],
1031
+ ])(
1032
+ "%s suppresses the empty state on its own",
1033
+ (_name: string, key: string) => {
1034
+ expect(emptyPage({ [key]: 1 } as Partial<EmptyStateInput>)).toBe(false);
1035
+ },
1036
+ );
1037
+
1038
+ /*
1039
+ * The two gates are read off the same two counts, and the page renders
1040
+ * both of them. If they ever disagree the visitor sees an empty state
1041
+ * stacked on top of the hierarchy it claims is not there, so the
1042
+ * relationship is asserted over every combination rather than sampled.
1043
+ */
1044
+ test("never contradicts shouldRenderResourcesSection", () => {
1045
+ const counts: Array<number> = [0, 1, 2, 1506];
1046
+
1047
+ for (const statusPageResourceCount of counts) {
1048
+ for (const statusPageGroupCount of counts) {
1049
+ const rendersSection: boolean =
1050
+ StatusPageGroupNestingLayoutUtil.shouldRenderResourcesSection({
1051
+ statusPageResourceCount: statusPageResourceCount,
1052
+ statusPageGroupCount: statusPageGroupCount,
1053
+ });
1054
+
1055
+ const rendersEmptyState: boolean = emptyPage({
1056
+ statusPageResourceCount: statusPageResourceCount,
1057
+ statusPageGroupCount: statusPageGroupCount,
1058
+ });
1059
+
1060
+ expect(rendersSection && rendersEmptyState).toBe(false);
1061
+ expect(rendersSection || rendersEmptyState).toBe(true);
1062
+ }
1063
+ }
1064
+ });
1065
+ });
1066
+
881
1067
  describe("shouldRenderTimeAxis", () => {
882
1068
  const CHARTED: ObjectID = new ObjectID(
883
1069
  "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
@@ -1,4 +1,5 @@
1
1
  import StatusPageGroupTreeUtil, {
2
+ StatusPageGroupTreeIndex,
2
3
  StatusPageGroupTreeNode,
3
4
  } from "../../../Utils/StatusPage/GroupTree";
4
5
  import ObjectID from "../../../Types/ObjectID";
@@ -90,6 +91,36 @@ function namesOf(groups: Array<StatusPageGroup>): Array<string> {
90
91
  });
91
92
  }
92
93
 
94
+ /*
95
+ * Counts how many times the walk under test asks a group who its parent is.
96
+ * That read is the primitive every tree helper is built on, so counting it is
97
+ * a deterministic stand-in for "did this scan the whole list per node?" - a
98
+ * wall clock assertion would just be a flake on a loaded CI box.
99
+ */
100
+ function watchParentReads(groups: Array<StatusPageGroup>): () => number {
101
+ let reads: number = 0;
102
+
103
+ const watch: (group: StatusPageGroup) => void = (
104
+ group: StatusPageGroup,
105
+ ): void => {
106
+ const actualParentId: ObjectID | undefined = group.parentStatusPageGroupId;
107
+
108
+ Object.defineProperty(group, "parentStatusPageGroupId", {
109
+ configurable: true,
110
+ get: (): ObjectID | undefined => {
111
+ reads++;
112
+ return actualParentId;
113
+ },
114
+ });
115
+ };
116
+
117
+ groups.forEach(watch);
118
+
119
+ return (): number => {
120
+ return reads;
121
+ };
122
+ }
123
+
93
124
  describe("StatusPageGroupTreeUtil", () => {
94
125
  describe("getParentId", () => {
95
126
  test("returns null for a top level group", () => {
@@ -580,4 +611,429 @@ describe("StatusPageGroupTreeUtil", () => {
580
611
  ).toHaveLength(0);
581
612
  });
582
613
  });
614
+
615
+ /*
616
+ * The index is an optimisation, and the only thing that makes an
617
+ * optimisation safe is that it cannot change an answer. Every helper is
618
+ * therefore asserted to give byte-identical results with and without one,
619
+ * across the same bad-data shapes the rest of this file covers.
620
+ */
621
+ describe("buildIndex", () => {
622
+ test("buckets children under their parent, in order", () => {
623
+ const groups: Array<StatusPageGroup> = makeHierarchy();
624
+
625
+ const index: StatusPageGroupTreeIndex =
626
+ StatusPageGroupTreeUtil.buildIndex({ statusPageGroups: groups });
627
+
628
+ expect(
629
+ namesOf(index.childrenByParentId.get(CORPORATE.toString()) || []),
630
+ ).toEqual(["Region 1000", "Region 2000"]);
631
+ expect(namesOf(index.childrenByParentId.get(null) || [])).toEqual([
632
+ "Corporate",
633
+ ]);
634
+ expect(index.byId.size).toBe(groups.length);
635
+ expect(index.byId.get(UNIT.toString())?.name).toBe("Unit 0152");
636
+ });
637
+
638
+ /*
639
+ * `null` rather than "" is the key for parentless groups on purpose: a
640
+ * group with no id of its own reads as "" everywhere in this class, and a
641
+ * string sentinel would make it adopt every root on the page.
642
+ */
643
+ test("a group with no id does not adopt the parentless groups", () => {
644
+ const idless: StatusPageGroup = new StatusPageGroup();
645
+ idless.name = "No id";
646
+
647
+ const groups: Array<StatusPageGroup> = [...makeHierarchy(), idless];
648
+
649
+ expect(
650
+ StatusPageGroupTreeUtil.getChildGroups({
651
+ statusPageGroupId: "",
652
+ statusPageGroups: groups,
653
+ }),
654
+ ).toHaveLength(0);
655
+ });
656
+
657
+ test("mutating the array getChildGroups returns leaves the index intact", () => {
658
+ const groups: Array<StatusPageGroup> = makeHierarchy();
659
+ const index: StatusPageGroupTreeIndex =
660
+ StatusPageGroupTreeUtil.buildIndex({ statusPageGroups: groups });
661
+
662
+ const children: Array<StatusPageGroup> =
663
+ StatusPageGroupTreeUtil.getChildGroups({
664
+ statusPageGroupId: CORPORATE.toString(),
665
+ statusPageGroups: groups,
666
+ index: index,
667
+ });
668
+
669
+ children.pop();
670
+
671
+ expect(
672
+ namesOf(
673
+ StatusPageGroupTreeUtil.getChildGroups({
674
+ statusPageGroupId: CORPORATE.toString(),
675
+ statusPageGroups: groups,
676
+ index: index,
677
+ }),
678
+ ),
679
+ ).toEqual(["Region 1000", "Region 2000"]);
680
+ });
681
+
682
+ describe("answers are identical with and without a supplied index", () => {
683
+ function cycle(): Array<StatusPageGroup> {
684
+ return [
685
+ makeGroup({
686
+ id: CORPORATE,
687
+ name: "A",
688
+ parentId: REGION_ONE,
689
+ order: 1,
690
+ }),
691
+ makeGroup({
692
+ id: REGION_ONE,
693
+ name: "B",
694
+ parentId: CORPORATE,
695
+ order: 2,
696
+ }),
697
+ ];
698
+ }
699
+
700
+ function orphanAndSelfParent(): Array<StatusPageGroup> {
701
+ return [
702
+ makeGroup({ id: CORPORATE, name: "Self", parentId: CORPORATE }),
703
+ makeGroup({ id: REGION_ONE, name: "Orphan", parentId: MISSING }),
704
+ makeGroup({ id: MARKET, name: "Unordered" }),
705
+ ];
706
+ }
707
+
708
+ const SHAPES: Array<{
709
+ name: string;
710
+ groups: () => Array<StatusPageGroup>;
711
+ }> = [
712
+ { name: "a normal hierarchy", groups: makeHierarchy },
713
+ { name: "a cycle", groups: cycle },
714
+ { name: "orphans and self parents", groups: orphanAndSelfParent },
715
+ {
716
+ name: "an empty list",
717
+ groups: (): Array<StatusPageGroup> => {
718
+ return [];
719
+ },
720
+ },
721
+ ];
722
+
723
+ test.each(SHAPES)(
724
+ "$name",
725
+ ({ groups }: { groups: () => Array<StatusPageGroup> }) => {
726
+ const list: Array<StatusPageGroup> = groups();
727
+ const index: StatusPageGroupTreeIndex =
728
+ StatusPageGroupTreeUtil.buildIndex({ statusPageGroups: list });
729
+
730
+ expect(
731
+ namesOf(
732
+ StatusPageGroupTreeUtil.getRootGroups({
733
+ statusPageGroups: list,
734
+ index: index,
735
+ }),
736
+ ),
737
+ ).toEqual(
738
+ namesOf(
739
+ StatusPageGroupTreeUtil.getRootGroups({ statusPageGroups: list }),
740
+ ),
741
+ );
742
+
743
+ const flatten: (
744
+ nodes: Array<StatusPageGroupTreeNode>,
745
+ ) => Array<string> = (
746
+ nodes: Array<StatusPageGroupTreeNode>,
747
+ ): Array<string> => {
748
+ return nodes.flatMap((node: StatusPageGroupTreeNode) => {
749
+ return [
750
+ `${node.group.name}@${node.depth}`,
751
+ ...flatten(node.children),
752
+ ];
753
+ });
754
+ };
755
+
756
+ expect(
757
+ flatten(
758
+ StatusPageGroupTreeUtil.buildTree({
759
+ statusPageGroups: list,
760
+ index: index,
761
+ }),
762
+ ),
763
+ ).toEqual(
764
+ flatten(
765
+ StatusPageGroupTreeUtil.buildTree({ statusPageGroups: list }),
766
+ ),
767
+ );
768
+
769
+ for (const group of list) {
770
+ expect(
771
+ namesOf(
772
+ StatusPageGroupTreeUtil.getGroupAndDescendants({
773
+ statusPageGroup: group,
774
+ statusPageGroups: list,
775
+ index: index,
776
+ }),
777
+ ),
778
+ ).toEqual(
779
+ namesOf(
780
+ StatusPageGroupTreeUtil.getGroupAndDescendants({
781
+ statusPageGroup: group,
782
+ statusPageGroups: list,
783
+ }),
784
+ ),
785
+ );
786
+
787
+ expect(
788
+ namesOf(
789
+ StatusPageGroupTreeUtil.getAncestorGroups({
790
+ statusPageGroup: group,
791
+ statusPageGroups: list,
792
+ index: index,
793
+ }),
794
+ ),
795
+ ).toEqual(
796
+ namesOf(
797
+ StatusPageGroupTreeUtil.getAncestorGroups({
798
+ statusPageGroup: group,
799
+ statusPageGroups: list,
800
+ }),
801
+ ),
802
+ );
803
+
804
+ expect(
805
+ StatusPageGroupTreeUtil.getDepth({
806
+ statusPageGroup: group,
807
+ statusPageGroups: list,
808
+ index: index,
809
+ }),
810
+ ).toBe(
811
+ StatusPageGroupTreeUtil.getDepth({
812
+ statusPageGroup: group,
813
+ statusPageGroups: list,
814
+ }),
815
+ );
816
+ }
817
+ },
818
+ );
819
+ });
820
+ });
821
+
822
+ /*
823
+ * The bug this file's fix belongs to was filed against a status page with
824
+ * 1506 groups. Nothing below asserts a wall clock time - a test that fails
825
+ * on a loaded CI box is worse than no test - but the shape of the work is
826
+ * pinned: every group has to come back, at the right depth, and the walk has
827
+ * to stay near linear rather than degrading into a scan of the whole list
828
+ * per node.
829
+ */
830
+ describe("at the size the bug was reported at", () => {
831
+ const CORPORATE_UNITS: number = 6;
832
+ const REGIONS_PER_UNIT: number = 5;
833
+ const MARKETS_PER_REGION: number = 5;
834
+ const SITES_PER_MARKET: number = 10;
835
+
836
+ function makeLargeHierarchy(): Array<StatusPageGroup> {
837
+ const groups: Array<StatusPageGroup> = [];
838
+ let order: number = 0;
839
+
840
+ const idFor: (label: string) => ObjectID = (label: string): ObjectID => {
841
+ return new ObjectID(label);
842
+ };
843
+
844
+ for (let unit: number = 0; unit < CORPORATE_UNITS; unit++) {
845
+ const unitId: ObjectID = idFor(`unit-${unit}`);
846
+ groups.push(
847
+ makeGroup({ id: unitId, name: `Unit ${unit}`, order: order++ }),
848
+ );
849
+
850
+ for (let region: number = 0; region < REGIONS_PER_UNIT; region++) {
851
+ const regionId: ObjectID = idFor(`unit-${unit}-region-${region}`);
852
+ groups.push(
853
+ makeGroup({
854
+ id: regionId,
855
+ name: `Region ${unit}-${region}`,
856
+ parentId: unitId,
857
+ order: order++,
858
+ }),
859
+ );
860
+
861
+ for (let market: number = 0; market < MARKETS_PER_REGION; market++) {
862
+ const marketId: ObjectID = idFor(
863
+ `unit-${unit}-region-${region}-market-${market}`,
864
+ );
865
+ groups.push(
866
+ makeGroup({
867
+ id: marketId,
868
+ name: `Market ${unit}-${region}-${market}`,
869
+ parentId: regionId,
870
+ order: order++,
871
+ }),
872
+ );
873
+
874
+ for (let site: number = 0; site < SITES_PER_MARKET; site++) {
875
+ groups.push(
876
+ makeGroup({
877
+ id: idFor(
878
+ `unit-${unit}-region-${region}-market-${market}-site-${site}`,
879
+ ),
880
+ name: `Site ${unit}-${region}-${market}-${site}`,
881
+ parentId: marketId,
882
+ order: order++,
883
+ }),
884
+ );
885
+ }
886
+ }
887
+ }
888
+ }
889
+
890
+ return groups;
891
+ }
892
+
893
+ const EXPECTED_TOTAL: number =
894
+ CORPORATE_UNITS *
895
+ (1 +
896
+ REGIONS_PER_UNIT * (1 + MARKETS_PER_REGION * (1 + SITES_PER_MARKET)));
897
+
898
+ test("the fixture really is the reported size", () => {
899
+ expect(makeLargeHierarchy()).toHaveLength(EXPECTED_TOTAL);
900
+ expect(EXPECTED_TOTAL).toBeGreaterThan(1500);
901
+ });
902
+
903
+ test("every one of the groups reaches the tree, at the right depth", () => {
904
+ const groups: Array<StatusPageGroup> = makeLargeHierarchy();
905
+
906
+ const depthByName: Map<string, number> = new Map<string, number>();
907
+
908
+ const visit: (nodes: Array<StatusPageGroupTreeNode>) => void = (
909
+ nodes: Array<StatusPageGroupTreeNode>,
910
+ ): void => {
911
+ for (const node of nodes) {
912
+ depthByName.set(node.group.name || "", node.depth);
913
+ visit(node.children);
914
+ }
915
+ };
916
+
917
+ visit(StatusPageGroupTreeUtil.buildTree({ statusPageGroups: groups }));
918
+
919
+ expect(depthByName.size).toBe(groups.length);
920
+
921
+ for (const group of groups) {
922
+ const name: string = group.name || "";
923
+ const expectedDepth: number = name.startsWith("Unit ")
924
+ ? 0
925
+ : name.startsWith("Region ")
926
+ ? 1
927
+ : name.startsWith("Market ")
928
+ ? 2
929
+ : 3;
930
+
931
+ expect(depthByName.get(name)).toBe(expectedDepth);
932
+ }
933
+ });
934
+
935
+ test("a subtree rollup covers exactly its own branch", () => {
936
+ const groups: Array<StatusPageGroup> = makeLargeHierarchy();
937
+ const index: StatusPageGroupTreeIndex =
938
+ StatusPageGroupTreeUtil.buildIndex({ statusPageGroups: groups });
939
+
940
+ const oneUnit: StatusPageGroup = groups.find((group: StatusPageGroup) => {
941
+ return group.name === "Unit 3";
942
+ })!;
943
+
944
+ const subtree: Array<StatusPageGroup> =
945
+ StatusPageGroupTreeUtil.getGroupAndDescendants({
946
+ statusPageGroup: oneUnit,
947
+ statusPageGroups: groups,
948
+ index: index,
949
+ });
950
+
951
+ expect(subtree).toHaveLength(
952
+ 1 +
953
+ REGIONS_PER_UNIT * (1 + MARKETS_PER_REGION * (1 + SITES_PER_MARKET)),
954
+ );
955
+
956
+ for (const group of subtree) {
957
+ expect(group.name).toMatch(/(Unit 3|[A-Za-z]+ 3-)/);
958
+ }
959
+ });
960
+
961
+ /*
962
+ * The regression guard for the walk itself. Before the index, answering
963
+ * "who are this group's children?" filtered the whole list, so one full
964
+ * pass over the tree touched every group once per node - at this size,
965
+ * millions of comparisons. Counting parent reads rather than milliseconds
966
+ * keeps this deterministic: a scan-per-node implementation lands at
967
+ * groups x nodes, which is three orders of magnitude above the bound here.
968
+ */
969
+ test("building the tree does not re-scan the group list per node", () => {
970
+ const groups: Array<StatusPageGroup> = makeLargeHierarchy();
971
+ const countParentReads: () => number = watchParentReads(groups);
972
+
973
+ StatusPageGroupTreeUtil.buildTree({ statusPageGroups: groups });
974
+
975
+ // One bucketing pass plus the root scan, not one scan per node.
976
+ expect(countParentReads()).toBeLessThan(groups.length * 4);
977
+ });
978
+
979
+ test("a rollup per group stays near linear when the index is shared", () => {
980
+ const groups: Array<StatusPageGroup> = makeLargeHierarchy();
981
+ const index: StatusPageGroupTreeIndex =
982
+ StatusPageGroupTreeUtil.buildIndex({ statusPageGroups: groups });
983
+
984
+ const countParentReads: () => number = watchParentReads(groups);
985
+
986
+ for (const group of groups) {
987
+ StatusPageGroupTreeUtil.getGroupAndDescendants({
988
+ statusPageGroup: group,
989
+ statusPageGroups: groups,
990
+ index: index,
991
+ });
992
+ }
993
+
994
+ /*
995
+ * A shared index means a subtree walk reads no parent pointers at all.
996
+ * Without one this loop is groups x groups reads - over two million.
997
+ */
998
+ expect(countParentReads()).toBe(0);
999
+ });
1000
+
1001
+ /*
1002
+ * Nesting is capped on write, but rows written straight to the database
1003
+ * are not, and a chain that deep must not blow the recursion in buildTree.
1004
+ */
1005
+ test("a long chain of nested groups still builds", () => {
1006
+ const groups: Array<StatusPageGroup> = [];
1007
+ let previousId: ObjectID | undefined = undefined;
1008
+
1009
+ for (let i: number = 0; i < 1000; i++) {
1010
+ const id: ObjectID = new ObjectID(`chain-${i}`);
1011
+ groups.push(
1012
+ makeGroup({
1013
+ id: id,
1014
+ name: `Level ${i}`,
1015
+ parentId: previousId,
1016
+ order: i,
1017
+ }),
1018
+ );
1019
+ previousId = id;
1020
+ }
1021
+
1022
+ const tree: Array<StatusPageGroupTreeNode> =
1023
+ StatusPageGroupTreeUtil.buildTree({ statusPageGroups: groups });
1024
+
1025
+ expect(tree).toHaveLength(1);
1026
+
1027
+ let node: StatusPageGroupTreeNode = tree[0]!;
1028
+ let depth: number = 0;
1029
+
1030
+ while (node.children.length > 0) {
1031
+ node = node.children[0]!;
1032
+ depth++;
1033
+ }
1034
+
1035
+ expect(depth).toBe(999);
1036
+ expect(node.group.name).toBe("Level 999");
1037
+ });
1038
+ });
583
1039
  });