@oneuptime/common 12.0.5 → 12.0.6

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 (528) hide show
  1. package/Models/DatabaseModels/AIRun.ts +33 -0
  2. package/Models/DatabaseModels/DataSource.ts +744 -0
  3. package/Models/DatabaseModels/Index.ts +3 -0
  4. package/Models/DatabaseModels/ProjectCallSMSConfig.ts +10 -2
  5. package/Models/DatabaseModels/User.ts +2 -0
  6. package/Models/DatabaseModels/UserOnCallLog.ts +2 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +2 -0
  8. package/Models/DatabaseModels/UserSession.ts +2 -0
  9. package/Models/DatabaseModels/UserTotpAuth.ts +6 -4
  10. package/Models/DatabaseModels/UserWebAuthn.ts +5 -3
  11. package/Server/API/AIInsightAPI.ts +47 -4
  12. package/Server/API/AIInvestigationAPI.ts +25 -0
  13. package/Server/API/DashboardAPI.ts +176 -492
  14. package/Server/API/DataSourceAPI.ts +268 -0
  15. package/Server/API/StatusPageAPI.ts +11 -1
  16. package/Server/API/UserAPI.ts +119 -0
  17. package/Server/Infrastructure/ClickhouseConfig.ts +59 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.ts +39 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.ts +24 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.ts +76 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  22. package/Server/Infrastructure/Queue.ts +26 -0
  23. package/Server/Infrastructure/QueueWorker.ts +17 -0
  24. package/Server/Middleware/TelemetryIngest.ts +8 -3
  25. package/Server/Services/AIInsightService.ts +132 -12
  26. package/Server/Services/AIRunService.ts +34 -0
  27. package/Server/Services/AIService.ts +17 -2
  28. package/Server/Services/AlertService.ts +38 -32
  29. package/Server/Services/AnalyticsDatabaseService.ts +8 -4
  30. package/Server/Services/DataSourceService.ts +200 -0
  31. package/Server/Services/HostService.ts +92 -0
  32. package/Server/Services/IncidentService.ts +45 -0
  33. package/Server/Services/Index.ts +2 -0
  34. package/Server/Services/MonitorService.ts +49 -0
  35. package/Server/Services/MonitorStatusService.ts +166 -0
  36. package/Server/Services/OpenTelemetryIngestService.ts +255 -184
  37. package/Server/Services/RumSessionPinService.ts +7 -21
  38. package/Server/Services/StatusPageSubscriberService.ts +13 -0
  39. package/Server/Services/UserService.ts +280 -9
  40. package/Server/Static/Vendor/README.md +62 -0
  41. package/Server/Static/Vendor/fonts/InterVariable.woff2 +0 -0
  42. package/Server/Static/Vendor/highlight/highlight.min.js +1244 -0
  43. package/Server/Static/Vendor/highlight/languages/apache.min.js +15 -0
  44. package/Server/Static/Vendor/highlight/languages/bash.min.js +21 -0
  45. package/Server/Static/Vendor/highlight/languages/c.min.js +41 -0
  46. package/Server/Static/Vendor/highlight/languages/cpp.min.js +47 -0
  47. package/Server/Static/Vendor/highlight/languages/csharp.min.js +49 -0
  48. package/Server/Static/Vendor/highlight/languages/css.min.js +31 -0
  49. package/Server/Static/Vendor/highlight/languages/diff.min.js +9 -0
  50. package/Server/Static/Vendor/highlight/languages/dns.min.js +11 -0
  51. package/Server/Static/Vendor/highlight/languages/dockerfile.min.js +8 -0
  52. package/Server/Static/Vendor/highlight/languages/go.min.js +20 -0
  53. package/Server/Static/Vendor/highlight/languages/graphql.min.js +12 -0
  54. package/Server/Static/Vendor/highlight/languages/http.min.js +14 -0
  55. package/Server/Static/Vendor/highlight/languages/ini.min.js +15 -0
  56. package/Server/Static/Vendor/highlight/languages/java.min.js +38 -0
  57. package/Server/Static/Vendor/highlight/languages/javascript.min.js +81 -0
  58. package/Server/Static/Vendor/highlight/languages/json.min.js +8 -0
  59. package/Server/Static/Vendor/highlight/languages/kotlin.min.js +46 -0
  60. package/Server/Static/Vendor/highlight/languages/lua.min.js +15 -0
  61. package/Server/Static/Vendor/highlight/languages/makefile.min.js +14 -0
  62. package/Server/Static/Vendor/highlight/languages/nginx.min.js +21 -0
  63. package/Server/Static/Vendor/highlight/languages/perl.min.js +41 -0
  64. package/Server/Static/Vendor/highlight/languages/php.min.js +59 -0
  65. package/Server/Static/Vendor/highlight/languages/powershell.min.js +39 -0
  66. package/Server/Static/Vendor/highlight/languages/python.min.js +42 -0
  67. package/Server/Static/Vendor/highlight/languages/r.min.js +26 -0
  68. package/Server/Static/Vendor/highlight/languages/ruby.min.js +54 -0
  69. package/Server/Static/Vendor/highlight/languages/rust.min.js +27 -0
  70. package/Server/Static/Vendor/highlight/languages/scala.min.js +28 -0
  71. package/Server/Static/Vendor/highlight/languages/sql.min.js +21 -0
  72. package/Server/Static/Vendor/highlight/languages/swift.min.js +68 -0
  73. package/Server/Static/Vendor/highlight/languages/typescript.min.js +99 -0
  74. package/Server/Static/Vendor/highlight/languages/xml.min.js +29 -0
  75. package/Server/Static/Vendor/highlight/languages/yaml.min.js +28 -0
  76. package/Server/Static/Vendor/highlight/styles/github-dark.min.css +10 -0
  77. package/Server/Static/Vendor/highlight/styles/vs2015.min.css +1 -0
  78. package/Server/Static/Vendor/tailwind/tailwind-3.4.5.js +21028 -0
  79. package/Server/Types/Database/Permissions/BasePermission.ts +7 -0
  80. package/Server/Types/Database/Permissions/DeletePermission.ts +2 -0
  81. package/Server/Types/Database/Permissions/ReadPermission.ts +3 -0
  82. package/Server/Types/Database/Permissions/TenantPermission.ts +101 -16
  83. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +2 -2
  84. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +90 -11
  85. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +1 -0
  86. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +1 -0
  87. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.ts +125 -0
  88. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +25 -15
  89. package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +256 -57
  90. package/Server/Utils/AI/SRE/Insights/InsightStore.ts +76 -18
  91. package/Server/Utils/AI/SRE/InvestigationTldr.ts +352 -0
  92. package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +7 -2
  93. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +1793 -0
  94. package/Server/Utils/Dashboard/PublicDashboardViewConfig.ts +65 -0
  95. package/Server/Utils/DataSource/ConnectorRegistry.ts +53 -0
  96. package/Server/Utils/DataSource/Connectors/ClickHouseConnector.ts +338 -0
  97. package/Server/Utils/DataSource/Connectors/ElasticsearchConnector.ts +717 -0
  98. package/Server/Utils/DataSource/Connectors/LokiConnector.ts +606 -0
  99. package/Server/Utils/DataSource/Connectors/MySqlConnector.ts +307 -0
  100. package/Server/Utils/DataSource/Connectors/PostgresConnector.ts +260 -0
  101. package/Server/Utils/DataSource/Connectors/PrometheusConnector.ts +555 -0
  102. package/Server/Utils/DataSource/Connectors/RestApiConnector.ts +597 -0
  103. package/Server/Utils/DataSource/Connectors/SqlServerConnector.ts +318 -0
  104. package/Server/Utils/DataSource/EgressGuard.ts +446 -0
  105. package/Server/Utils/DataSource/HttpFetch.ts +221 -0
  106. package/Server/Utils/DataSource/SqlQueryGuard.ts +383 -0
  107. package/Server/Utils/DataSource/SqlResultShaper.ts +372 -0
  108. package/Server/Utils/DataSource/Types.ts +93 -0
  109. package/Server/Utils/Database/PostgresErrorTranslator.ts +172 -17
  110. package/Server/Utils/InProcessMemo.ts +141 -0
  111. package/Server/Utils/InstanceHealth/TelemetryIngestion.ts +628 -0
  112. package/Server/Utils/Monitor/MonitorMetricUtil.ts +58 -0
  113. package/Server/Utils/Monitor/MonitorResource.ts +14 -0
  114. package/Server/Utils/Response.ts +12 -1
  115. package/Server/Utils/SSRFProtection.ts +237 -0
  116. package/Server/Utils/StartServer.ts +8 -0
  117. package/Server/Utils/StatusPageSubscriberWebhook.ts +30 -0
  118. package/Server/Utils/Telemetry/CaptureSpan.ts +30 -18
  119. package/Server/Utils/Telemetry/IngestionTimestamp.ts +75 -0
  120. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +99 -7
  121. package/Server/Utils/Telemetry/Telemetry.ts +112 -1
  122. package/Server/Utils/Telemetry.ts +104 -3
  123. package/Server/Utils/VendorAssets.ts +165 -0
  124. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +28 -5
  125. package/Server/Utils/Workspace/Slack/Slack.ts +5 -0
  126. package/Server/Views/Partials/Head.ejs +21 -7
  127. package/Tests/App/AdminDashboard/AdminProjectsIdFilter.test.tsx +207 -0
  128. package/Tests/App/AdminDashboard/AdminUsersIdFilter.test.tsx +216 -0
  129. package/Tests/App/Dashboard/AIInsightViewActions.test.tsx +422 -0
  130. package/Tests/App/Dashboard/AddWidgetModal.test.tsx +10 -3
  131. package/Tests/App/Dashboard/AlertsSideMenu.test.tsx +387 -0
  132. package/Tests/App/Dashboard/ChatActivityFeed.test.tsx +199 -0
  133. package/Tests/App/Dashboard/DataSourceChartSyntheticConfig.test.tsx +736 -0
  134. package/Tests/App/Dashboard/DataSourceQueryEditor.test.tsx +868 -0
  135. package/Tests/App/Dashboard/DataSourceWidgetCatalog.test.ts +440 -0
  136. package/Tests/App/Dashboard/DataSourceWidgetFetching.test.tsx +2109 -0
  137. package/Tests/App/Dashboard/DataSourceWidgetViews.test.tsx +726 -0
  138. package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +447 -0
  139. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +514 -3
  140. package/Tests/App/Dashboard/ProductMenuBreadcrumbs.test.tsx +391 -0
  141. package/Tests/App/Dashboard/ScheduledMaintenanceSideMenu.test.tsx +326 -0
  142. package/Tests/App/Dashboard/SideMenuHarness.tsx +187 -0
  143. package/Tests/App/Dashboard/WidgetCatalog.test.ts +6 -1
  144. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +1858 -0
  145. package/Tests/Models/DatabaseModels/DataSourceModelSecrets.test.ts +70 -0
  146. package/Tests/Server/API/AIInvestigationAPI.test.ts +187 -0
  147. package/Tests/Server/API/DashboardPublicDataSourceStrip.test.ts +686 -0
  148. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +585 -823
  149. package/Tests/Server/API/DataSourceAPIRoutes.test.ts +486 -0
  150. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +291 -0
  151. package/Tests/Server/API/UserAuthenticationAPI.test.ts +641 -0
  152. package/Tests/Server/Infrastructure/ClickhouseKeepAliveOptions.test.ts +441 -0
  153. package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +140 -0
  154. package/Tests/Server/Services/AIInsightService.test.ts +266 -11
  155. package/Tests/Server/Services/AIRunAnalysisTldrPersistence.test.ts +160 -0
  156. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +4 -0
  157. package/Tests/Server/Services/AlertMetricResourceAttributes.test.ts +352 -0
  158. package/Tests/Server/Services/DataSourceServiceSettings.test.ts +244 -0
  159. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +397 -0
  160. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +41 -2
  161. package/Tests/Server/Services/IncidentMetricResourceAttributes.test.ts +408 -0
  162. package/Tests/Server/Services/MonitorStatusServiceDeleteReferenceCleanup.test.ts +351 -0
  163. package/Tests/Server/Services/MonitorStatusServiceValidation.test.ts +171 -0
  164. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +398 -0
  165. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +19 -0
  166. package/Tests/Server/Services/ProjectCallSMSConfigCredentialUniqueness.test.ts +305 -0
  167. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +36 -1
  168. package/Tests/Server/Services/RumSessionPinUniqueViolationRecovery.test.ts +163 -0
  169. package/Tests/Server/Services/UserAuthenticationService.test.ts +973 -0
  170. package/Tests/Server/Types/Database/ObjectIdColumnQuery.test.ts +262 -0
  171. package/Tests/Server/Types/Database/Permissions/TenantPermission.test.ts +358 -0
  172. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +2 -2
  173. package/Tests/Server/Utils/AI/Insights/ExceptionSpikeDetector.test.ts +6 -1
  174. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +121 -4
  175. package/Tests/Server/Utils/AI/Insights/NewExceptionDetector.test.ts +361 -10
  176. package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +292 -0
  177. package/Tests/Server/Utils/AI/InvestigationTldr.test.ts +623 -0
  178. package/Tests/Server/Utils/AI/InvestigationTldrWiring.test.ts +98 -0
  179. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +857 -0
  180. package/Tests/Server/Utils/Dashboard/PublicDashboardViewConfig.test.ts +480 -0
  181. package/Tests/Server/Utils/DataSource/ClickHouseConnector.test.ts +759 -0
  182. package/Tests/Server/Utils/DataSource/ConnectorRegistry.test.ts +37 -0
  183. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +645 -0
  184. package/Tests/Server/Utils/DataSource/ElasticsearchConnector.test.ts +1206 -0
  185. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +616 -0
  186. package/Tests/Server/Utils/DataSource/LokiConnector.test.ts +970 -0
  187. package/Tests/Server/Utils/DataSource/MySqlConnector.test.ts +935 -0
  188. package/Tests/Server/Utils/DataSource/PostgresConnector.test.ts +854 -0
  189. package/Tests/Server/Utils/DataSource/PrometheusConnector.test.ts +881 -0
  190. package/Tests/Server/Utils/DataSource/RestApiConnector.test.ts +1020 -0
  191. package/Tests/Server/Utils/DataSource/SqlQueryGuard.test.ts +425 -0
  192. package/Tests/Server/Utils/DataSource/SqlResultShaper.test.ts +555 -0
  193. package/Tests/Server/Utils/DataSource/SqlServerConnector.test.ts +934 -0
  194. package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +228 -7
  195. package/Tests/Server/Utils/InProcessMemo.test.ts +292 -0
  196. package/Tests/Server/Utils/InstanceHealth/TelemetryIngestion.test.ts +960 -0
  197. package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +103 -0
  198. package/Tests/Server/Utils/Monitor/MonitorMetricResourceAttributes.test.ts +336 -0
  199. package/Tests/Server/Utils/OfflineAssetHygiene.test.ts +521 -0
  200. package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +143 -0
  201. package/Tests/Server/Utils/SSRFProtection.test.ts +193 -0
  202. package/Tests/Server/Utils/StatusPageSubscriberWebhook.test.ts +101 -0
  203. package/Tests/Server/Utils/TailwindBuildCopy.test.ts +225 -0
  204. package/Tests/Server/Utils/Telemetry/ResourceHeartbeatL1Memo.test.ts +355 -0
  205. package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +128 -0
  206. package/Tests/Server/Utils/Telemetry/TelemetryUtilAttributeKeys.test.ts +381 -0
  207. package/Tests/Server/Utils/Telemetry.test.ts +87 -0
  208. package/Tests/Server/Utils/VendorAssetIntegrity.test.ts +343 -0
  209. package/Tests/Server/Utils/VendorAssets.test.ts +671 -0
  210. package/Tests/Server/Views/OfflineRender.test.ts +297 -0
  211. package/Tests/Types/Dashboard/DataSourceComponentTypes.test.ts +618 -0
  212. package/Tests/Types/DateClickhouseFormat.test.ts +192 -0
  213. package/Tests/Types/Password.test.ts +363 -0
  214. package/Tests/UI/Components/ConfirmModal.test.tsx +7 -0
  215. package/Tests/UI/Components/ErrorBoundary.test.tsx +53 -0
  216. package/Tests/UI/Components/Modal.test.tsx +281 -1
  217. package/Tests/UI/Components/ObjectIdFilterPipeline.test.tsx +396 -0
  218. package/Tests/UI/Components/StatusPage/ResourceGroupNavigator.test.tsx +1147 -0
  219. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +116 -1
  220. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  221. package/Tests/Utils/Analytics.test.ts +23 -0
  222. package/Tests/Utils/Dashboard/Components/DashboardDataSourceComponents.test.ts +640 -0
  223. package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +138 -0
  224. package/Tests/Utils/DataSource/DataSourceValueReducer.test.ts +526 -0
  225. package/Tests/Utils/Metrics/MetricResourceAttributeUtil.test.ts +691 -0
  226. package/Tests/Utils/StatusPage/GroupHierarchyView.test.ts +1452 -0
  227. package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +931 -0
  228. package/Tests/Utils/Telemetry/CaptureSpanExportGating.test.ts +288 -0
  229. package/Tests/Utils/Telemetry/IngestionTimestamp.test.ts +170 -0
  230. package/Types/AI/AIInsightEvidence.ts +6 -0
  231. package/Types/Analytics/RevenueEvent.ts +31 -0
  232. package/Types/Dashboard/DashboardComponentType.ts +31 -0
  233. package/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.ts +27 -0
  234. package/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.ts +32 -0
  235. package/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.ts +30 -0
  236. package/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.ts +55 -0
  237. package/Types/DataSource/DataSourceLimits.ts +62 -0
  238. package/Types/DataSource/DataSourceQueryConfig.ts +41 -0
  239. package/Types/DataSource/DataSourceTableResult.ts +38 -0
  240. package/Types/DataSource/DataSourceType.ts +213 -0
  241. package/Types/Date.ts +43 -3
  242. package/Types/Password.ts +64 -0
  243. package/Types/Permission.ts +46 -0
  244. package/Types/UserAuthenticationStatus.ts +34 -0
  245. package/UI/Components/Button/Button.tsx +1 -1
  246. package/UI/Components/ErrorBoundary.tsx +459 -108
  247. package/UI/Components/Icon/Icon.tsx +10 -9
  248. package/UI/Components/Modal/ConfirmModal.tsx +6 -1
  249. package/UI/Components/Modal/Modal.tsx +267 -35
  250. package/UI/Components/Modal/ModalFooter.tsx +6 -1
  251. package/UI/Components/MoreMenu/MoreMenu.tsx +19 -4
  252. package/UI/Components/MoreMenu/MoreMenuItem.tsx +6 -1
  253. package/UI/Components/StatusPage/ResourceGrid.tsx +314 -0
  254. package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +808 -0
  255. package/UI/Components/StatusPage/ResourceList.tsx +570 -0
  256. package/UI/Styles/Theme.css +18 -0
  257. package/UI/Utils/API/RequestOptions.ts +7 -0
  258. package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +1 -0
  259. package/UI/Utils/ModelAPI/ModelAPI.ts +1 -0
  260. package/UI/Utils/TestDataSource.ts +61 -0
  261. package/UI/esbuild-config.js +45 -0
  262. package/Utils/Analytics.ts +15 -0
  263. package/Utils/Dashboard/Components/DashboardDataSourceChartComponent.ts +88 -0
  264. package/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.ts +123 -0
  265. package/Utils/Dashboard/Components/DashboardDataSourceShared.ts +16 -0
  266. package/Utils/Dashboard/Components/DashboardDataSourceTableComponent.ts +72 -0
  267. package/Utils/Dashboard/Components/DashboardDataSourceValueComponent.ts +126 -0
  268. package/Utils/Dashboard/Components/Index.ts +28 -0
  269. package/Utils/DataSource/DataSourceQueryText.ts +94 -0
  270. package/Utils/DataSource/DataSourceValueReducer.ts +156 -0
  271. package/Utils/Metrics/MetricResourceAttributeUtil.ts +380 -0
  272. package/Utils/NetworkDiscovery/ScanTargetUtil.ts +8 -6
  273. package/Utils/StatusPage/GroupHierarchyView.ts +931 -0
  274. package/Utils/StatusPage/ResourceExplorer.ts +853 -0
  275. package/build/dist/Models/DatabaseModels/AIRun.js +34 -0
  276. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  277. package/build/dist/Models/DatabaseModels/DataSource.js +774 -0
  278. package/build/dist/Models/DatabaseModels/DataSource.js.map +1 -0
  279. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  280. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  281. package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js +14 -4
  282. package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js.map +1 -1
  283. package/build/dist/Models/DatabaseModels/User.js +2 -0
  284. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  285. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +2 -0
  286. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  287. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +2 -0
  288. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  289. package/build/dist/Models/DatabaseModels/UserSession.js +2 -0
  290. package/build/dist/Models/DatabaseModels/UserSession.js.map +1 -1
  291. package/build/dist/Models/DatabaseModels/UserTotpAuth.js +6 -4
  292. package/build/dist/Models/DatabaseModels/UserTotpAuth.js.map +1 -1
  293. package/build/dist/Models/DatabaseModels/UserWebAuthn.js +5 -3
  294. package/build/dist/Models/DatabaseModels/UserWebAuthn.js.map +1 -1
  295. package/build/dist/Server/API/AIInsightAPI.js +34 -4
  296. package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
  297. package/build/dist/Server/API/AIInvestigationAPI.js +23 -0
  298. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  299. package/build/dist/Server/API/DashboardAPI.js +119 -433
  300. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  301. package/build/dist/Server/API/DataSourceAPI.js +173 -0
  302. package/build/dist/Server/API/DataSourceAPI.js.map +1 -0
  303. package/build/dist/Server/API/StatusPageAPI.js +11 -1
  304. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  305. package/build/dist/Server/API/UserAPI.js +94 -2
  306. package/build/dist/Server/API/UserAPI.js.map +1 -1
  307. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +49 -0
  308. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  309. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js +20 -0
  310. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js.map +1 -0
  311. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js +19 -0
  312. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js.map +1 -0
  313. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js +64 -0
  314. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js.map +1 -0
  315. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  316. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  317. package/build/dist/Server/Infrastructure/Queue.js +27 -0
  318. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  319. package/build/dist/Server/Infrastructure/QueueWorker.js +13 -0
  320. package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
  321. package/build/dist/Server/Middleware/TelemetryIngest.js +8 -3
  322. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  323. package/build/dist/Server/Services/AIInsightService.js +110 -10
  324. package/build/dist/Server/Services/AIInsightService.js.map +1 -1
  325. package/build/dist/Server/Services/AIRunService.js +34 -0
  326. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  327. package/build/dist/Server/Services/AIService.js +16 -2
  328. package/build/dist/Server/Services/AIService.js.map +1 -1
  329. package/build/dist/Server/Services/AlertService.js +28 -35
  330. package/build/dist/Server/Services/AlertService.js.map +1 -1
  331. package/build/dist/Server/Services/AnalyticsDatabaseService.js +8 -4
  332. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  333. package/build/dist/Server/Services/DataSourceService.js +159 -0
  334. package/build/dist/Server/Services/DataSourceService.js.map +1 -0
  335. package/build/dist/Server/Services/HostService.js +74 -0
  336. package/build/dist/Server/Services/HostService.js.map +1 -1
  337. package/build/dist/Server/Services/IncidentService.js +35 -24
  338. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  339. package/build/dist/Server/Services/Index.js +2 -0
  340. package/build/dist/Server/Services/Index.js.map +1 -1
  341. package/build/dist/Server/Services/MonitorService.js +45 -0
  342. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  343. package/build/dist/Server/Services/MonitorStatusService.js +129 -0
  344. package/build/dist/Server/Services/MonitorStatusService.js.map +1 -1
  345. package/build/dist/Server/Services/OpenTelemetryIngestService.js +222 -159
  346. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  347. package/build/dist/Server/Services/RumSessionPinService.js +7 -15
  348. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  349. package/build/dist/Server/Services/StatusPageSubscriberService.js +10 -0
  350. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  351. package/build/dist/Server/Services/UserService.js +241 -10
  352. package/build/dist/Server/Services/UserService.js.map +1 -1
  353. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +6 -0
  354. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  355. package/build/dist/Server/Types/Database/Permissions/DeletePermission.js +1 -0
  356. package/build/dist/Server/Types/Database/Permissions/DeletePermission.js.map +1 -1
  357. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +2 -0
  358. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
  359. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +68 -12
  360. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
  361. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +2 -2
  362. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  363. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +68 -8
  364. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  365. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +1 -0
  366. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  367. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +1 -0
  368. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  369. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js +97 -0
  370. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js.map +1 -0
  371. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +17 -15
  372. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
  373. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +178 -54
  374. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
  375. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js +65 -16
  376. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js.map +1 -1
  377. package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js +303 -0
  378. package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js.map +1 -0
  379. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -1
  380. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +1261 -0
  381. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -0
  382. package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js +47 -0
  383. package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js.map +1 -0
  384. package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js +42 -0
  385. package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js.map +1 -0
  386. package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js +187 -0
  387. package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js.map +1 -0
  388. package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js +493 -0
  389. package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js.map +1 -0
  390. package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js +399 -0
  391. package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js.map +1 -0
  392. package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js +195 -0
  393. package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js.map +1 -0
  394. package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js +149 -0
  395. package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js.map +1 -0
  396. package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js +362 -0
  397. package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js.map +1 -0
  398. package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js +342 -0
  399. package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js.map +1 -0
  400. package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js +186 -0
  401. package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js.map +1 -0
  402. package/build/dist/Server/Utils/DataSource/EgressGuard.js +305 -0
  403. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -0
  404. package/build/dist/Server/Utils/DataSource/HttpFetch.js +134 -0
  405. package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -0
  406. package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js +331 -0
  407. package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js.map +1 -0
  408. package/build/dist/Server/Utils/DataSource/SqlResultShaper.js +280 -0
  409. package/build/dist/Server/Utils/DataSource/SqlResultShaper.js.map +1 -0
  410. package/build/dist/Server/Utils/DataSource/Types.js +2 -0
  411. package/build/dist/Server/Utils/DataSource/Types.js.map +1 -0
  412. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +103 -9
  413. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -1
  414. package/build/dist/Server/Utils/InProcessMemo.js +113 -0
  415. package/build/dist/Server/Utils/InProcessMemo.js.map +1 -0
  416. package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js +406 -0
  417. package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js.map +1 -0
  418. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +34 -0
  419. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  420. package/build/dist/Server/Utils/Monitor/MonitorResource.js +14 -0
  421. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  422. package/build/dist/Server/Utils/Response.js +9 -1
  423. package/build/dist/Server/Utils/Response.js.map +1 -1
  424. package/build/dist/Server/Utils/SSRFProtection.js +192 -0
  425. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -0
  426. package/build/dist/Server/Utils/StartServer.js +7 -0
  427. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  428. package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js +24 -0
  429. package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js.map +1 -1
  430. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js +25 -11
  431. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js.map +1 -1
  432. package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js +32 -0
  433. package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js.map +1 -0
  434. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +92 -5
  435. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -1
  436. package/build/dist/Server/Utils/Telemetry/Telemetry.js +92 -6
  437. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  438. package/build/dist/Server/Utils/Telemetry.js +93 -6
  439. package/build/dist/Server/Utils/Telemetry.js.map +1 -1
  440. package/build/dist/Server/Utils/VendorAssets.js +121 -0
  441. package/build/dist/Server/Utils/VendorAssets.js.map +1 -0
  442. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -4
  443. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  444. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +5 -0
  445. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  446. package/build/dist/Types/Analytics/RevenueEvent.js +26 -0
  447. package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -0
  448. package/build/dist/Types/Dashboard/DashboardComponentType.js +27 -0
  449. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  450. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js +2 -0
  451. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js.map +1 -0
  452. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js +2 -0
  453. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js.map +1 -0
  454. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js +2 -0
  455. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js.map +1 -0
  456. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js +16 -0
  457. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js.map +1 -0
  458. package/build/dist/Types/DataSource/DataSourceLimits.js +47 -0
  459. package/build/dist/Types/DataSource/DataSourceLimits.js.map +1 -0
  460. package/build/dist/Types/DataSource/DataSourceQueryConfig.js +10 -0
  461. package/build/dist/Types/DataSource/DataSourceQueryConfig.js.map +1 -0
  462. package/build/dist/Types/DataSource/DataSourceTableResult.js +14 -0
  463. package/build/dist/Types/DataSource/DataSourceTableResult.js.map +1 -0
  464. package/build/dist/Types/DataSource/DataSourceType.js +183 -0
  465. package/build/dist/Types/DataSource/DataSourceType.js.map +1 -0
  466. package/build/dist/Types/Date.js +38 -3
  467. package/build/dist/Types/Date.js.map +1 -1
  468. package/build/dist/Types/Password.js +56 -0
  469. package/build/dist/Types/Password.js.map +1 -0
  470. package/build/dist/Types/Permission.js +42 -0
  471. package/build/dist/Types/Permission.js.map +1 -1
  472. package/build/dist/Types/UserAuthenticationStatus.js +2 -0
  473. package/build/dist/Types/UserAuthenticationStatus.js.map +1 -0
  474. package/build/dist/UI/Components/ErrorBoundary.js +388 -72
  475. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  476. package/build/dist/UI/Components/Icon/Icon.js +10 -4
  477. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  478. package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
  479. package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
  480. package/build/dist/UI/Components/Modal/Modal.js +178 -19
  481. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  482. package/build/dist/UI/Components/Modal/ModalFooter.js +3 -1
  483. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  484. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +9 -6
  485. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  486. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +7 -1
  487. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  488. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +119 -0
  489. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -0
  490. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +354 -0
  491. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -0
  492. package/build/dist/UI/Components/StatusPage/ResourceList.js +203 -0
  493. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -0
  494. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +1 -6
  495. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
  496. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +3 -8
  497. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  498. package/build/dist/UI/Utils/TestDataSource.js +39 -0
  499. package/build/dist/UI/Utils/TestDataSource.js.map +1 -0
  500. package/build/dist/Utils/Analytics.js +4 -0
  501. package/build/dist/Utils/Analytics.js.map +1 -1
  502. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js +79 -0
  503. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js.map +1 -0
  504. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js +111 -0
  505. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js.map +1 -0
  506. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js +15 -0
  507. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js.map +1 -0
  508. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js +62 -0
  509. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js.map +1 -0
  510. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js +112 -0
  511. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js.map +1 -0
  512. package/build/dist/Utils/Dashboard/Components/Index.js +16 -0
  513. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  514. package/build/dist/Utils/DataSource/DataSourceQueryText.js +64 -0
  515. package/build/dist/Utils/DataSource/DataSourceQueryText.js.map +1 -0
  516. package/build/dist/Utils/DataSource/DataSourceValueReducer.js +124 -0
  517. package/build/dist/Utils/DataSource/DataSourceValueReducer.js.map +1 -0
  518. package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js +281 -0
  519. package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js.map +1 -0
  520. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +8 -6
  521. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -1
  522. package/build/dist/Utils/StatusPage/GroupHierarchyView.js +560 -0
  523. package/build/dist/Utils/StatusPage/GroupHierarchyView.js.map +1 -0
  524. package/build/dist/Utils/StatusPage/ResourceExplorer.js +519 -0
  525. package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -0
  526. package/jest.config.json +1 -0
  527. package/package.json +5 -1
  528. package/tsconfig.json +12 -10
@@ -0,0 +1,931 @@
1
+ import StatusPageGroup from "../../Models/DatabaseModels/StatusPageGroup";
2
+ import StatusPageGroupTreeUtil, {
3
+ StatusPageGroupTreeIndex,
4
+ StatusPageGroupTreeNode,
5
+ } from "./GroupTree";
6
+
7
+ /*
8
+ * The admin side of the status page group hierarchy.
9
+ *
10
+ * Groups nest (Corporate Unit -> Region -> Market -> Site) and the page that
11
+ * manages them used to be a flat table with a "Parent Group" column. A table
12
+ * can tell you that "Market 1001" sits under "Region 1000"; it cannot show you
13
+ * the shape of the thing you are building, which is the only question an
14
+ * operator actually has in front of a hierarchy. So the page draws a tree, and
15
+ * everything the tree needs to know - which rows are visible, how deep each one
16
+ * sits, which guide rails are drawn beside it, which way it may be moved - is
17
+ * worked out here rather than inline in the component.
18
+ *
19
+ * It lives next to GroupNestingLayout (which does the same job for the public
20
+ * status page) for the same reason: this is the part that has to hold at depth 1
21
+ * and still hold at depth 9 against data written straight to the database, and
22
+ * that is worth pinning down in tests rather than in a render.
23
+ */
24
+
25
+ /*
26
+ * Ancestors are joined with this everywhere in the admin (Resources, Monitor
27
+ * Rules, the parent picker), so a path reads the same wherever it is shown.
28
+ */
29
+ export const STATUS_PAGE_GROUP_PATH_SEPARATOR: string = " › ";
30
+
31
+ export interface StatusPageGroupHierarchyRow {
32
+ statusPageGroup: StatusPageGroup;
33
+ /*
34
+ * "" for a group with no id of its own, which is how the rest of the tree
35
+ * utils read a missing id too.
36
+ */
37
+ id: string;
38
+ name: string;
39
+ /* 0 for a top level group, 1 for its children, and so on. */
40
+ depth: number;
41
+
42
+ /*
43
+ * Whether the group has sub groups AT ALL, as opposed to whether any of them
44
+ * survive the current search. The badge counts the real hierarchy - a search
45
+ * narrows what is on screen, it does not change what the group contains.
46
+ */
47
+ hasSubGroups: boolean;
48
+ subGroupCount: number;
49
+ descendantCount: number;
50
+
51
+ /*
52
+ * Whether this row has children in the CURRENT view. A group whose sub groups
53
+ * were all filtered out has nothing to disclose, so it draws no chevron.
54
+ */
55
+ hasVisibleSubGroups: boolean;
56
+ isExpanded: boolean;
57
+
58
+ /*
59
+ * One entry per indent column left of this row's own connector, outermost
60
+ * first. True means the ancestor that owns that column still has a sibling
61
+ * below this row, so its guide rail continues past it; false means the rail
62
+ * has already ended and the column is blank.
63
+ *
64
+ * Trimmed from the left at MaxIndentColumns - see getIndentColumnCount.
65
+ */
66
+ ancestorRails: Array<boolean>;
67
+ /* Ends this row's own connector in an elbow rather than a tee. */
68
+ isLastVisibleSibling: boolean;
69
+
70
+ /*
71
+ * This row's place among the siblings that are actually drawn, 0 based. A
72
+ * tree tells a screen reader "3 of 7 at level 2", and the counts it quotes
73
+ * have to be the ones on screen or they describe a tree nobody can see.
74
+ */
75
+ visibleSiblingIndex: number;
76
+ visibleSiblingCount: number;
77
+
78
+ /* Ancestor names, top level first, excluding the group itself. */
79
+ ancestorNames: Array<string>;
80
+
81
+ /*
82
+ * True when the group itself matches the current search. Rows that are only
83
+ * on screen because a descendant matched are drawn quieter - they are context,
84
+ * not results.
85
+ */
86
+ isSearchMatch: boolean;
87
+
88
+ /*
89
+ * The group's REAL neighbours under the same parent, not its neighbours in the
90
+ * filtered view. Reordering writes to `order`, which is a property of the
91
+ * hierarchy rather than of whatever the operator happens to have typed into
92
+ * the search box, so a move must always mean the same thing.
93
+ */
94
+ previousSiblingId: string | null;
95
+ nextSiblingId: string | null;
96
+ canMoveUp: boolean;
97
+ canMoveDown: boolean;
98
+ }
99
+
100
+ /*
101
+ * One level of the hierarchy: every group that shares a parent, and where each
102
+ * of them sits in that level. Worked out once per parent by getRows so that a
103
+ * fifteen hundred group level is not re-derived for every one of its rows.
104
+ */
105
+ interface SiblingLevel {
106
+ siblings: Array<StatusPageGroup>;
107
+ positionById: Map<string, number>;
108
+ }
109
+
110
+ export interface StatusPageGroupHierarchySummary {
111
+ totalCount: number;
112
+ topLevelCount: number;
113
+ nestedCount: number;
114
+ /*
115
+ * How many levels the hierarchy actually uses - 1 for a flat list, 0 when
116
+ * there are no groups. Deliberately 1-based: it is shown to a human as
117
+ * "3 levels", and a hierarchy that is one level deep is not zero levels deep.
118
+ */
119
+ levelCount: number;
120
+ }
121
+
122
+ export default class StatusPageGroupHierarchyViewUtil {
123
+ /*
124
+ * How many indent columns a row may draw before the tree stops stepping
125
+ * right. Nesting is capped on write at StatusPageGroupTreeUtil.MaxNestingDepth
126
+ * (10, so the deepest legal row sits at depth 9 and needs 9 columns), but rows
127
+ * written straight to the database are not capped, and a row that indents
128
+ * itself off the right of the card is worse than a row whose outermost rails
129
+ * are elided.
130
+ */
131
+ public static readonly MaxIndentColumns: number = 10;
132
+
133
+ public static getIndentColumnCount(data: { depth: number }): number {
134
+ if (!Number.isFinite(data.depth)) {
135
+ return 0;
136
+ }
137
+
138
+ const depth: number = Math.floor(data.depth);
139
+
140
+ if (depth <= 0) {
141
+ return 0;
142
+ }
143
+
144
+ return Math.min(depth, this.MaxIndentColumns);
145
+ }
146
+
147
+ /*
148
+ * The rows to render, in tree order, with everything each row needs to draw
149
+ * itself. Collapsed subtrees are not in the result at all - the component
150
+ * renders a flat list, so "collapsed" and "absent" have to be the same thing.
151
+ */
152
+ public static getRows(data: {
153
+ statusPageGroups: Array<StatusPageGroup>;
154
+ expandedGroupIds?: Set<string> | undefined;
155
+ searchText?: string | undefined;
156
+ }): Array<StatusPageGroupHierarchyRow> {
157
+ const index: StatusPageGroupTreeIndex = StatusPageGroupTreeUtil.buildIndex({
158
+ statusPageGroups: data.statusPageGroups,
159
+ });
160
+
161
+ const tree: Array<StatusPageGroupTreeNode> =
162
+ StatusPageGroupTreeUtil.buildTree({
163
+ statusPageGroups: data.statusPageGroups,
164
+ index: index,
165
+ });
166
+
167
+ const searchTerm: string = this.normalizeSearchText(data.searchText);
168
+ const isSearching: boolean = searchTerm.length > 0;
169
+
170
+ const matchedIds: Set<string> = isSearching
171
+ ? this.getMatchingGroupIds({
172
+ statusPageGroups: data.statusPageGroups,
173
+ searchText: searchTerm,
174
+ })
175
+ : new Set<string>();
176
+
177
+ const visibleIds: Set<string> | null = isSearching
178
+ ? this.getSearchVisibleGroupIds({
179
+ statusPageGroups: data.statusPageGroups,
180
+ searchText: searchTerm,
181
+ index: index,
182
+ })
183
+ : null;
184
+
185
+ /*
186
+ * An ancestor of a match is opened whatever the operator collapsed earlier:
187
+ * a search that hides its own results behind a collapsed parent is a search
188
+ * that found nothing as far as the operator can tell.
189
+ */
190
+ const forcedExpandedIds: Set<string> = isSearching
191
+ ? this.getAncestorIdsOfMatches({
192
+ statusPageGroups: data.statusPageGroups,
193
+ matchedIds: matchedIds,
194
+ index: index,
195
+ })
196
+ : new Set<string>();
197
+
198
+ const expandedGroupIds: Set<string> =
199
+ data.expandedGroupIds || new Set<string>();
200
+
201
+ const rows: Array<StatusPageGroupHierarchyRow> = [];
202
+
203
+ /*
204
+ * Where each group sits among its siblings, worked out once per parent
205
+ * rather than once per row.
206
+ *
207
+ * Without it, a status page whose groups are all at the top level asks for
208
+ * the same fifteen hundred long sibling list fifteen hundred times and
209
+ * scans it each time - two and a quarter million comparisons and fifteen
210
+ * hundred copies of the array, which is seconds of work on every keystroke
211
+ * and every group opened.
212
+ */
213
+ const siblingsByParentKey: Map<string, SiblingLevel> = new Map<
214
+ string,
215
+ SiblingLevel
216
+ >();
217
+
218
+ type GetSiblingLevelFunction = (
219
+ statusPageGroup: StatusPageGroup,
220
+ ) => SiblingLevel;
221
+
222
+ const getSiblingLevel: GetSiblingLevelFunction = (
223
+ statusPageGroup: StatusPageGroup,
224
+ ): SiblingLevel => {
225
+ const parentId: string | null =
226
+ StatusPageGroupTreeUtil.getParentId(statusPageGroup);
227
+
228
+ /*
229
+ * The same key getSiblingGroups resolves to: a group whose parent is not
230
+ * on this status page is a root, and its siblings are the other roots.
231
+ */
232
+ const parentKey: string =
233
+ parentId && index.byId.has(parentId) ? parentId : "";
234
+
235
+ const cached: SiblingLevel | undefined =
236
+ siblingsByParentKey.get(parentKey);
237
+
238
+ if (cached) {
239
+ return cached;
240
+ }
241
+
242
+ const siblings: Array<StatusPageGroup> = this.getSiblingGroups({
243
+ statusPageGroup: statusPageGroup,
244
+ statusPageGroups: data.statusPageGroups,
245
+ index: index,
246
+ });
247
+
248
+ const positionById: Map<string, number> = new Map<string, number>();
249
+
250
+ siblings.forEach((sibling: StatusPageGroup, position: number): void => {
251
+ positionById.set(this.getGroupId(sibling), position);
252
+ });
253
+
254
+ const level: SiblingLevel = {
255
+ siblings: siblings,
256
+ positionById: positionById,
257
+ };
258
+
259
+ siblingsByParentKey.set(parentKey, level);
260
+
261
+ return level;
262
+ };
263
+
264
+ type WalkFunction = (
265
+ nodes: Array<StatusPageGroupTreeNode>,
266
+ ancestorRails: Array<boolean>,
267
+ ancestorNames: Array<string>,
268
+ ) => void;
269
+
270
+ const walk: WalkFunction = (
271
+ nodes: Array<StatusPageGroupTreeNode>,
272
+ ancestorRails: Array<boolean>,
273
+ ancestorNames: Array<string>,
274
+ ): void => {
275
+ /*
276
+ * Filtered before the positions below are worked out, so "is this the last
277
+ * one" is asked of the rows that are actually drawn. Skipping a whole node
278
+ * is safe: a group is visible whenever anything in its subtree matched, so
279
+ * an invisible node cannot be hiding a visible descendant.
280
+ */
281
+ const visibleNodes: Array<StatusPageGroupTreeNode> = visibleIds
282
+ ? nodes.filter((node: StatusPageGroupTreeNode) => {
283
+ return visibleIds.has(this.getGroupId(node.group));
284
+ })
285
+ : nodes;
286
+
287
+ visibleNodes.forEach(
288
+ (node: StatusPageGroupTreeNode, position: number): void => {
289
+ const groupId: string = this.getGroupId(node.group);
290
+ const isLastVisibleSibling: boolean =
291
+ position === visibleNodes.length - 1;
292
+
293
+ const visibleChildren: Array<StatusPageGroupTreeNode> = visibleIds
294
+ ? node.children.filter((child: StatusPageGroupTreeNode) => {
295
+ return visibleIds.has(this.getGroupId(child.group));
296
+ })
297
+ : node.children;
298
+
299
+ const hasVisibleSubGroups: boolean = visibleChildren.length > 0;
300
+
301
+ const isExpanded: boolean =
302
+ hasVisibleSubGroups &&
303
+ (expandedGroupIds.has(groupId) || forcedExpandedIds.has(groupId));
304
+
305
+ const neighbours: {
306
+ previousSiblingId: string | null;
307
+ nextSiblingId: string | null;
308
+ } = this.getSiblingNeighbours({
309
+ statusPageGroup: node.group,
310
+ statusPageGroups: data.statusPageGroups,
311
+ index: index,
312
+ siblingLevel: getSiblingLevel(node.group),
313
+ });
314
+
315
+ rows.push({
316
+ statusPageGroup: node.group,
317
+ id: groupId,
318
+ name: node.group.name || "",
319
+ depth: node.depth,
320
+ hasSubGroups: node.children.length > 0,
321
+ subGroupCount: node.children.length,
322
+ descendantCount: this.countDescendants({ node: node }),
323
+ hasVisibleSubGroups: hasVisibleSubGroups,
324
+ isExpanded: isExpanded,
325
+ ancestorRails: this.trimRails({
326
+ rails: ancestorRails,
327
+ depth: node.depth,
328
+ }),
329
+ isLastVisibleSibling: isLastVisibleSibling,
330
+ visibleSiblingIndex: position,
331
+ visibleSiblingCount: visibleNodes.length,
332
+ ancestorNames: [...ancestorNames],
333
+ isSearchMatch: !isSearching || matchedIds.has(groupId),
334
+ previousSiblingId: neighbours.previousSiblingId,
335
+ nextSiblingId: neighbours.nextSiblingId,
336
+ canMoveUp: neighbours.previousSiblingId !== null,
337
+ canMoveDown: neighbours.nextSiblingId !== null,
338
+ });
339
+
340
+ if (isExpanded) {
341
+ walk(
342
+ node.children,
343
+ [...ancestorRails, !isLastVisibleSibling],
344
+ [...ancestorNames, node.group.name || ""],
345
+ );
346
+ }
347
+ },
348
+ );
349
+ };
350
+
351
+ walk(tree, [], []);
352
+
353
+ return rows;
354
+ }
355
+
356
+ /*
357
+ * Groups whose name or description contains the search text. Case and
358
+ * surrounding whitespace are ignored - an operator hunting for a group they
359
+ * half remember should not have to match its capitalisation.
360
+ */
361
+ public static getMatchingGroupIds(data: {
362
+ statusPageGroups: Array<StatusPageGroup>;
363
+ searchText?: string | undefined;
364
+ }): Set<string> {
365
+ const searchTerm: string = this.normalizeSearchText(data.searchText);
366
+
367
+ const matchedIds: Set<string> = new Set<string>();
368
+
369
+ if (!searchTerm) {
370
+ return matchedIds;
371
+ }
372
+
373
+ for (const group of data.statusPageGroups) {
374
+ if (
375
+ this.matchesSearch({ statusPageGroup: group, searchText: searchTerm })
376
+ ) {
377
+ matchedIds.add(this.getGroupId(group));
378
+ }
379
+ }
380
+
381
+ return matchedIds;
382
+ }
383
+
384
+ public static matchesSearch(data: {
385
+ statusPageGroup: StatusPageGroup;
386
+ searchText?: string | undefined;
387
+ }): boolean {
388
+ const searchTerm: string = this.normalizeSearchText(data.searchText);
389
+
390
+ if (!searchTerm) {
391
+ return true;
392
+ }
393
+
394
+ const haystack: string = [
395
+ data.statusPageGroup.name || "",
396
+ data.statusPageGroup.description || "",
397
+ ]
398
+ .join(" ")
399
+ .toLowerCase();
400
+
401
+ return haystack.includes(searchTerm);
402
+ }
403
+
404
+ /*
405
+ * Everything a search puts on screen: the matches, the ancestors that give
406
+ * each match its place in the hierarchy, and the matches' own subtrees.
407
+ *
408
+ * The subtrees are there because a hierarchy is what the operator is looking
409
+ * at. Searching "Region 1000" and being shown a Region with no contents would
410
+ * be answering a different question from the one that was asked.
411
+ */
412
+ public static getSearchVisibleGroupIds(data: {
413
+ statusPageGroups: Array<StatusPageGroup>;
414
+ searchText?: string | undefined;
415
+ index?: StatusPageGroupTreeIndex | undefined;
416
+ }): Set<string> {
417
+ const index: StatusPageGroupTreeIndex = this.resolveIndex(data);
418
+
419
+ const matchedIds: Set<string> = this.getMatchingGroupIds({
420
+ statusPageGroups: data.statusPageGroups,
421
+ searchText: data.searchText,
422
+ });
423
+
424
+ const visibleIds: Set<string> = new Set<string>();
425
+
426
+ for (const group of data.statusPageGroups) {
427
+ if (!matchedIds.has(this.getGroupId(group))) {
428
+ continue;
429
+ }
430
+
431
+ visibleIds.add(this.getGroupId(group));
432
+
433
+ for (const ancestor of StatusPageGroupTreeUtil.getAncestorGroups({
434
+ statusPageGroup: group,
435
+ statusPageGroups: data.statusPageGroups,
436
+ index: index,
437
+ })) {
438
+ visibleIds.add(this.getGroupId(ancestor));
439
+ }
440
+
441
+ for (const descendant of StatusPageGroupTreeUtil.getDescendantGroups({
442
+ statusPageGroup: group,
443
+ statusPageGroups: data.statusPageGroups,
444
+ index: index,
445
+ })) {
446
+ visibleIds.add(this.getGroupId(descendant));
447
+ }
448
+ }
449
+
450
+ return visibleIds;
451
+ }
452
+
453
+ /* Every group that has sub groups - what "Expand all" turns on. */
454
+ public static getExpandableGroupIds(data: {
455
+ statusPageGroups: Array<StatusPageGroup>;
456
+ index?: StatusPageGroupTreeIndex | undefined;
457
+ }): Set<string> {
458
+ const index: StatusPageGroupTreeIndex = this.resolveIndex(data);
459
+
460
+ const expandableIds: Set<string> = new Set<string>();
461
+
462
+ type VisitFunction = (nodes: Array<StatusPageGroupTreeNode>) => void;
463
+
464
+ const visit: VisitFunction = (
465
+ nodes: Array<StatusPageGroupTreeNode>,
466
+ ): void => {
467
+ for (const node of nodes) {
468
+ if (node.children.length > 0) {
469
+ expandableIds.add(this.getGroupId(node.group));
470
+ }
471
+
472
+ visit(node.children);
473
+ }
474
+ };
475
+
476
+ visit(
477
+ StatusPageGroupTreeUtil.buildTree({
478
+ statusPageGroups: data.statusPageGroups,
479
+ index: index,
480
+ }),
481
+ );
482
+
483
+ return expandableIds;
484
+ }
485
+
486
+ /*
487
+ * What the page opens with.
488
+ *
489
+ * Everything down to maxAutoExpandDepth is open, and deeper levels start
490
+ * shut. A hierarchy that is fully expanded on arrival is unreadable at any
491
+ * real size, and one that is fully collapsed hides the fact that it nests at
492
+ * all - which is the single thing this page exists to show.
493
+ */
494
+ public static getDefaultExpandedGroupIds(data: {
495
+ statusPageGroups: Array<StatusPageGroup>;
496
+ maxAutoExpandDepth?: number | undefined;
497
+ index?: StatusPageGroupTreeIndex | undefined;
498
+ }): Set<string> {
499
+ const index: StatusPageGroupTreeIndex = this.resolveIndex(data);
500
+
501
+ const maxAutoExpandDepth: number =
502
+ data.maxAutoExpandDepth === undefined ? 1 : data.maxAutoExpandDepth;
503
+
504
+ const expandedIds: Set<string> = new Set<string>();
505
+
506
+ type VisitFunction = (nodes: Array<StatusPageGroupTreeNode>) => void;
507
+
508
+ const visit: VisitFunction = (
509
+ nodes: Array<StatusPageGroupTreeNode>,
510
+ ): void => {
511
+ for (const node of nodes) {
512
+ if (node.children.length > 0 && node.depth <= maxAutoExpandDepth) {
513
+ expandedIds.add(this.getGroupId(node.group));
514
+ }
515
+
516
+ visit(node.children);
517
+ }
518
+ };
519
+
520
+ visit(
521
+ StatusPageGroupTreeUtil.buildTree({
522
+ statusPageGroups: data.statusPageGroups,
523
+ index: index,
524
+ }),
525
+ );
526
+
527
+ return expandedIds;
528
+ }
529
+
530
+ public static getSummary(data: {
531
+ statusPageGroups: Array<StatusPageGroup>;
532
+ index?: StatusPageGroupTreeIndex | undefined;
533
+ }): StatusPageGroupHierarchySummary {
534
+ const index: StatusPageGroupTreeIndex = this.resolveIndex(data);
535
+
536
+ let topLevelCount: number = 0;
537
+ let deepestDepth: number = -1;
538
+
539
+ type VisitFunction = (nodes: Array<StatusPageGroupTreeNode>) => void;
540
+
541
+ const visit: VisitFunction = (
542
+ nodes: Array<StatusPageGroupTreeNode>,
543
+ ): void => {
544
+ for (const node of nodes) {
545
+ if (node.depth === 0) {
546
+ topLevelCount++;
547
+ }
548
+
549
+ if (node.depth > deepestDepth) {
550
+ deepestDepth = node.depth;
551
+ }
552
+
553
+ visit(node.children);
554
+ }
555
+ };
556
+
557
+ visit(
558
+ StatusPageGroupTreeUtil.buildTree({
559
+ statusPageGroups: data.statusPageGroups,
560
+ index: index,
561
+ }),
562
+ );
563
+
564
+ const totalCount: number = data.statusPageGroups.length;
565
+
566
+ return {
567
+ totalCount: totalCount,
568
+ topLevelCount: topLevelCount,
569
+ nestedCount: totalCount - topLevelCount,
570
+ levelCount: deepestDepth + 1,
571
+ };
572
+ }
573
+
574
+ /* "Corporate › Region 1000 › Market 1001", the group itself last. */
575
+ public static getGroupPathLabel(data: {
576
+ statusPageGroup: StatusPageGroup;
577
+ statusPageGroups: Array<StatusPageGroup>;
578
+ index?: StatusPageGroupTreeIndex | undefined;
579
+ }): string {
580
+ const ancestors: Array<StatusPageGroup> =
581
+ StatusPageGroupTreeUtil.getAncestorGroups({
582
+ statusPageGroup: data.statusPageGroup,
583
+ statusPageGroups: data.statusPageGroups,
584
+ index: this.resolveIndex(data),
585
+ }).reverse();
586
+
587
+ return [
588
+ ...ancestors.map((ancestor: StatusPageGroup) => {
589
+ return ancestor.name || "";
590
+ }),
591
+ data.statusPageGroup.name || "",
592
+ ].join(STATUS_PAGE_GROUP_PATH_SEPARATOR);
593
+ }
594
+
595
+ /*
596
+ * The groups that may legally be picked as a parent, in tree order.
597
+ *
598
+ * StatusPageGroupService is the only place that can decide this for real - it
599
+ * is the one that sees the tree at write time - so this is not validation, it
600
+ * is the picker refusing to offer a choice it already knows the API will
601
+ * reject. The three rules it applies are the same three the service enforces:
602
+ * a group is not its own parent, a group does not move under its own sub
603
+ * group, and the moved subtree still has to fit inside MaxNestingDepth.
604
+ */
605
+ public static getParentGroupCandidates(data: {
606
+ statusPageGroups: Array<StatusPageGroup>;
607
+ /*
608
+ * The group being edited. Omitted when creating, where nothing is excluded
609
+ * on identity grounds and the subtree being placed is empty.
610
+ */
611
+ statusPageGroupId?: string | undefined;
612
+ index?: StatusPageGroupTreeIndex | undefined;
613
+ }): Array<StatusPageGroup> {
614
+ const index: StatusPageGroupTreeIndex = this.resolveIndex(data);
615
+
616
+ const statusPageGroupId: string = data.statusPageGroupId || "";
617
+
618
+ const group: StatusPageGroup | undefined = statusPageGroupId
619
+ ? index.byId.get(statusPageGroupId)
620
+ : undefined;
621
+
622
+ const excludedIds: Set<string> = new Set<string>();
623
+ let subtreeHeight: number = 0;
624
+
625
+ if (group) {
626
+ excludedIds.add(statusPageGroupId);
627
+
628
+ for (const descendant of StatusPageGroupTreeUtil.getDescendantGroups({
629
+ statusPageGroup: group,
630
+ statusPageGroups: data.statusPageGroups,
631
+ index: index,
632
+ })) {
633
+ excludedIds.add(this.getGroupId(descendant));
634
+ }
635
+
636
+ subtreeHeight = this.getSubtreeHeight({
637
+ statusPageGroup: group,
638
+ statusPageGroups: data.statusPageGroups,
639
+ index: index,
640
+ });
641
+ }
642
+
643
+ const candidates: Array<StatusPageGroup> = [];
644
+
645
+ type VisitFunction = (nodes: Array<StatusPageGroupTreeNode>) => void;
646
+
647
+ const visit: VisitFunction = (
648
+ nodes: Array<StatusPageGroupTreeNode>,
649
+ ): void => {
650
+ for (const node of nodes) {
651
+ const nodeId: string = this.getGroupId(node.group);
652
+
653
+ if (excludedIds.has(nodeId)) {
654
+ /*
655
+ * Its whole subtree is excluded too, and buildTree already nested
656
+ * them under it, so there is nothing below worth walking.
657
+ */
658
+ continue;
659
+ }
660
+
661
+ if (
662
+ node.depth + 1 + subtreeHeight <
663
+ StatusPageGroupTreeUtil.MaxNestingDepth
664
+ ) {
665
+ candidates.push(node.group);
666
+ }
667
+
668
+ visit(node.children);
669
+ }
670
+ };
671
+
672
+ visit(
673
+ StatusPageGroupTreeUtil.buildTree({
674
+ statusPageGroups: data.statusPageGroups,
675
+ index: index,
676
+ }),
677
+ );
678
+
679
+ return candidates;
680
+ }
681
+
682
+ /*
683
+ * How many levels hang below this group. 0 for a leaf, 1 for a group whose
684
+ * children are all leaves. This is the part of a move the server also has to
685
+ * account for: a group carries its subtree with it.
686
+ */
687
+ public static getSubtreeHeight(data: {
688
+ statusPageGroup: StatusPageGroup;
689
+ statusPageGroups: Array<StatusPageGroup>;
690
+ index?: StatusPageGroupTreeIndex | undefined;
691
+ }): number {
692
+ const index: StatusPageGroupTreeIndex = this.resolveIndex(data);
693
+
694
+ const ownDepth: number = StatusPageGroupTreeUtil.getDepth({
695
+ statusPageGroup: data.statusPageGroup,
696
+ statusPageGroups: data.statusPageGroups,
697
+ index: index,
698
+ });
699
+
700
+ let height: number = 0;
701
+
702
+ for (const descendant of StatusPageGroupTreeUtil.getDescendantGroups({
703
+ statusPageGroup: data.statusPageGroup,
704
+ statusPageGroups: data.statusPageGroups,
705
+ index: index,
706
+ })) {
707
+ const relativeDepth: number =
708
+ StatusPageGroupTreeUtil.getDepth({
709
+ statusPageGroup: descendant,
710
+ statusPageGroups: data.statusPageGroups,
711
+ index: index,
712
+ }) - ownDepth;
713
+
714
+ if (relativeDepth > height) {
715
+ height = relativeDepth;
716
+ }
717
+ }
718
+
719
+ return height;
720
+ }
721
+
722
+ /*
723
+ * The `order` a group has to be given to swap places with the sibling above
724
+ * or below it.
725
+ *
726
+ * `order` is one flat sequence across the whole status page rather than one
727
+ * per parent, so a sibling's own order is the correct target: the service
728
+ * renumbers everything between the two, which is what keeps every other
729
+ * group's relative position intact.
730
+ */
731
+ public static getReorderTargetOrder(data: {
732
+ statusPageGroups: Array<StatusPageGroup>;
733
+ statusPageGroupId: string;
734
+ direction: "up" | "down";
735
+ index?: StatusPageGroupTreeIndex | undefined;
736
+ }): number | null {
737
+ const index: StatusPageGroupTreeIndex = this.resolveIndex(data);
738
+
739
+ const group: StatusPageGroup | undefined = index.byId.get(
740
+ data.statusPageGroupId,
741
+ );
742
+
743
+ if (!group) {
744
+ return null;
745
+ }
746
+
747
+ const neighbours: {
748
+ previousSiblingId: string | null;
749
+ nextSiblingId: string | null;
750
+ } = this.getSiblingNeighbours({
751
+ statusPageGroup: group,
752
+ statusPageGroups: data.statusPageGroups,
753
+ index: index,
754
+ });
755
+
756
+ const neighbourId: string | null =
757
+ data.direction === "up"
758
+ ? neighbours.previousSiblingId
759
+ : neighbours.nextSiblingId;
760
+
761
+ if (!neighbourId) {
762
+ return null;
763
+ }
764
+
765
+ const neighbour: StatusPageGroup | undefined = index.byId.get(neighbourId);
766
+
767
+ if (!neighbour || typeof neighbour.order !== "number") {
768
+ return null;
769
+ }
770
+
771
+ return neighbour.order;
772
+ }
773
+
774
+ /*
775
+ * The groups drawn beside this one under the same parent, in render order.
776
+ *
777
+ * Top level rows read their neighbours from getRootGroups rather than from
778
+ * the parent buckets, because that is the list buildTree put them in: an
779
+ * orphan (a group whose parent was not fetched, or was deleted from under it)
780
+ * renders at the top level, and reading the bucket for its dangling parent id
781
+ * would tell it that it has no neighbours at all.
782
+ */
783
+ public static getSiblingGroups(data: {
784
+ statusPageGroup: StatusPageGroup;
785
+ statusPageGroups: Array<StatusPageGroup>;
786
+ index?: StatusPageGroupTreeIndex | undefined;
787
+ }): Array<StatusPageGroup> {
788
+ const index: StatusPageGroupTreeIndex = this.resolveIndex(data);
789
+
790
+ const parentId: string | null = StatusPageGroupTreeUtil.getParentId(
791
+ data.statusPageGroup,
792
+ );
793
+
794
+ if (parentId && index.byId.has(parentId)) {
795
+ return StatusPageGroupTreeUtil.getChildGroups({
796
+ statusPageGroupId: parentId,
797
+ statusPageGroups: data.statusPageGroups,
798
+ index: index,
799
+ });
800
+ }
801
+
802
+ return StatusPageGroupTreeUtil.getRootGroups({
803
+ statusPageGroups: data.statusPageGroups,
804
+ index: index,
805
+ });
806
+ }
807
+
808
+ private static getSiblingNeighbours(data: {
809
+ statusPageGroup: StatusPageGroup;
810
+ statusPageGroups: Array<StatusPageGroup>;
811
+ index: StatusPageGroupTreeIndex;
812
+ /*
813
+ * This group's whole sibling level, when the caller has already worked it
814
+ * out - which it has whenever it is drawing more than one row of it. See
815
+ * getRows.
816
+ */
817
+ siblingLevel?: SiblingLevel | undefined;
818
+ }): { previousSiblingId: string | null; nextSiblingId: string | null } {
819
+ const siblings: Array<StatusPageGroup> =
820
+ data.siblingLevel?.siblings ||
821
+ this.getSiblingGroups({
822
+ statusPageGroup: data.statusPageGroup,
823
+ statusPageGroups: data.statusPageGroups,
824
+ index: data.index,
825
+ });
826
+
827
+ const groupId: string = this.getGroupId(data.statusPageGroup);
828
+
829
+ const cachedPosition: number | undefined =
830
+ data.siblingLevel?.positionById.get(groupId);
831
+
832
+ const position: number =
833
+ cachedPosition === undefined
834
+ ? siblings.findIndex((sibling: StatusPageGroup) => {
835
+ return this.getGroupId(sibling) === groupId;
836
+ })
837
+ : cachedPosition;
838
+
839
+ if (position < 0) {
840
+ return { previousSiblingId: null, nextSiblingId: null };
841
+ }
842
+
843
+ const previous: StatusPageGroup | undefined = siblings[position - 1];
844
+ const next: StatusPageGroup | undefined = siblings[position + 1];
845
+
846
+ return {
847
+ previousSiblingId: previous ? this.getGroupId(previous) : null,
848
+ nextSiblingId: next ? this.getGroupId(next) : null,
849
+ };
850
+ }
851
+
852
+ private static getAncestorIdsOfMatches(data: {
853
+ statusPageGroups: Array<StatusPageGroup>;
854
+ matchedIds: Set<string>;
855
+ index: StatusPageGroupTreeIndex;
856
+ }): Set<string> {
857
+ const ancestorIds: Set<string> = new Set<string>();
858
+
859
+ for (const group of data.statusPageGroups) {
860
+ if (!data.matchedIds.has(this.getGroupId(group))) {
861
+ continue;
862
+ }
863
+
864
+ for (const ancestor of StatusPageGroupTreeUtil.getAncestorGroups({
865
+ statusPageGroup: group,
866
+ statusPageGroups: data.statusPageGroups,
867
+ index: data.index,
868
+ })) {
869
+ ancestorIds.add(this.getGroupId(ancestor));
870
+ }
871
+ }
872
+
873
+ return ancestorIds;
874
+ }
875
+
876
+ private static countDescendants(data: {
877
+ node: StatusPageGroupTreeNode;
878
+ }): number {
879
+ let count: number = 0;
880
+
881
+ for (const child of data.node.children) {
882
+ count += 1 + this.countDescendants({ node: child });
883
+ }
884
+
885
+ return count;
886
+ }
887
+
888
+ /*
889
+ * Past MaxIndentColumns the outermost rails are dropped rather than the
890
+ * nearest ones: the columns closest to the row are the ones that say where it
891
+ * sits, and a row that has walked off the right of the card says nothing at
892
+ * all.
893
+ */
894
+ private static trimRails(data: {
895
+ rails: Array<boolean>;
896
+ depth: number;
897
+ }): Array<boolean> {
898
+ const columnCount: number = this.getIndentColumnCount({
899
+ depth: data.depth,
900
+ });
901
+
902
+ /* One column is this row's own connector; the rest are ancestor rails. */
903
+ const railCount: number = Math.max(0, columnCount - 1);
904
+
905
+ if (data.rails.length <= railCount) {
906
+ return [...data.rails];
907
+ }
908
+
909
+ return data.rails.slice(data.rails.length - railCount);
910
+ }
911
+
912
+ private static normalizeSearchText(searchText?: string | undefined): string {
913
+ return (searchText || "").trim().toLowerCase();
914
+ }
915
+
916
+ private static getGroupId(statusPageGroup: StatusPageGroup): string {
917
+ return statusPageGroup._id?.toString() || "";
918
+ }
919
+
920
+ private static resolveIndex(data: {
921
+ statusPageGroups: Array<StatusPageGroup>;
922
+ index?: StatusPageGroupTreeIndex | undefined;
923
+ }): StatusPageGroupTreeIndex {
924
+ return (
925
+ data.index ||
926
+ StatusPageGroupTreeUtil.buildIndex({
927
+ statusPageGroups: data.statusPageGroups,
928
+ })
929
+ );
930
+ }
931
+ }