@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,960 @@
1
+ import {
2
+ ClickhouseAppInstance,
3
+ ClickhouseClient,
4
+ } from "../../../../Server/Infrastructure/ClickhouseDatabase";
5
+ import {
6
+ attachProjectNames,
7
+ buildProjectIngestionQuery,
8
+ buildSignalIngestionQuery,
9
+ buildTelemetryStorageSizeQuery,
10
+ getTelemetryIngestionByProject,
11
+ getTelemetryIngestionBySignal,
12
+ MAX_PROJECTS_PER_SIGNAL,
13
+ mergeProjectIngestionRows,
14
+ sumCountsOrNull,
15
+ TELEMETRY_INGESTION_SIGNALS,
16
+ TelemetryIngestionSignal,
17
+ TelemetryIngestionSignalSpec,
18
+ TelemetryProjectIngestion,
19
+ TelemetryProjectIngestionResult,
20
+ TelemetryProjectSignalIngestion,
21
+ TelemetrySignalIngestion,
22
+ TelemetrySignalIngestionResult,
23
+ TelemetrySignalProjectRows,
24
+ toCountOrNull,
25
+ } from "../../../../Server/Utils/InstanceHealth/TelemetryIngestion";
26
+ import AnalyticsTableName from "../../../../Types/AnalyticsDatabase/AnalyticsTableName";
27
+ import { JSONObject } from "../../../../Types/JSON";
28
+ import { afterEach, describe, expect, jest, test } from "@jest/globals";
29
+ import "../../TestingUtils/Init";
30
+
31
+ /*
32
+ * The telemetry ingestion probes behind the master-admin Telemetry page.
33
+ *
34
+ * Three things are worth a test here and nothing else really is:
35
+ *
36
+ * 1. The SQL. These queries run against multi-billion-row tables, and the only
37
+ * reason they are affordable is that they filter on the column each table is
38
+ * PARTITIONED and primary-key ordered by, and group by projectId — the
39
+ * leading primary-key column. Swap `time` for `createdAt`, or group by any
40
+ * other column, and the page still renders correct numbers in development
41
+ * while melting a production cluster. The assertions below pin the shape
42
+ * that keeps them cheap.
43
+ *
44
+ * 2. null vs 0. A count we could not read must never render as a confident
45
+ * zero: an operator reads "0 traces" as "this tenant stopped sending
46
+ * traces", which is a very different incident from "we could not measure".
47
+ * Every merge and sum path is checked for that distinction.
48
+ *
49
+ * 3. Independent degradation. One missing table, one failed query or an absent
50
+ * ClickHouse must not blank the signals that DID answer.
51
+ */
52
+
53
+ function makeClient(
54
+ query: (options: { query: string }) => Promise<{ data: Array<JSONObject> }>,
55
+ ): ClickhouseClient {
56
+ return {
57
+ query: async (options: { query: string }): Promise<unknown> => {
58
+ const result: { data: Array<JSONObject> } = await query(options);
59
+
60
+ return {
61
+ json: (): Promise<{ data: Array<JSONObject> }> => {
62
+ return Promise.resolve(result);
63
+ },
64
+ };
65
+ },
66
+ } as unknown as ClickhouseClient;
67
+ }
68
+
69
+ function specFor(
70
+ telemetryType: TelemetryIngestionSignal,
71
+ ): TelemetryIngestionSignalSpec {
72
+ const spec: TelemetryIngestionSignalSpec | undefined =
73
+ TELEMETRY_INGESTION_SIGNALS.find(
74
+ (candidate: TelemetryIngestionSignalSpec): boolean => {
75
+ return candidate.telemetryType === telemetryType;
76
+ },
77
+ );
78
+
79
+ if (!spec) {
80
+ throw new Error(`No telemetry signal registered for ${telemetryType}`);
81
+ }
82
+
83
+ return spec;
84
+ }
85
+
86
+ function projectRows(data: {
87
+ telemetryType: string;
88
+ available?: boolean | undefined;
89
+ truncated?: boolean | undefined;
90
+ rows: Array<{
91
+ projectId: string;
92
+ lastMinute: number | null;
93
+ lastHour: number | null;
94
+ lastDay: number | null;
95
+ }>;
96
+ }): TelemetrySignalProjectRows {
97
+ return {
98
+ telemetryType: data.telemetryType,
99
+ available: data.available ?? true,
100
+ truncated: data.truncated ?? false,
101
+ rows: data.rows,
102
+ };
103
+ }
104
+
105
+ function signalOf(
106
+ project: TelemetryProjectIngestion,
107
+ telemetryType: string,
108
+ ): TelemetryProjectSignalIngestion {
109
+ const signal: TelemetryProjectSignalIngestion | undefined =
110
+ project.signals.find(
111
+ (candidate: TelemetryProjectSignalIngestion): boolean => {
112
+ return candidate.telemetryType === telemetryType;
113
+ },
114
+ );
115
+
116
+ if (!signal) {
117
+ throw new Error(`Project ${project.projectId} has no ${telemetryType} row`);
118
+ }
119
+
120
+ return signal;
121
+ }
122
+
123
+ describe("telemetry signal registry", () => {
124
+ test("covers exactly the three signals OneUptime ingests", () => {
125
+ expect(
126
+ TELEMETRY_INGESTION_SIGNALS.map(
127
+ (spec: TelemetryIngestionSignalSpec): string => {
128
+ return spec.telemetryType;
129
+ },
130
+ ),
131
+ ).toEqual(["Logs", "Metrics", "Traces"]);
132
+ });
133
+
134
+ test("points each signal at its own analytics table", () => {
135
+ expect(specFor(TelemetryIngestionSignal.Logs).table).toBe(
136
+ AnalyticsTableName.Log,
137
+ );
138
+ expect(specFor(TelemetryIngestionSignal.Metrics).table).toBe(
139
+ AnalyticsTableName.Metric,
140
+ );
141
+ expect(specFor(TelemetryIngestionSignal.Traces).table).toBe(
142
+ AnalyticsTableName.Span,
143
+ );
144
+ });
145
+
146
+ /*
147
+ * Spans are ordered on startTime, not `time` — a copy-paste of the log spec
148
+ * would filter on a column the table does not have and drop traces entirely.
149
+ */
150
+ test("counts each signal on the event-time column its table is ordered by", () => {
151
+ expect(specFor(TelemetryIngestionSignal.Logs).timeColumn).toBe("time");
152
+ expect(specFor(TelemetryIngestionSignal.Metrics).timeColumn).toBe("time");
153
+ expect(specFor(TelemetryIngestionSignal.Traces).timeColumn).toBe(
154
+ "startTime",
155
+ );
156
+ });
157
+
158
+ /*
159
+ * `createdAt` is the true write time but is unindexed and unpartitioned on
160
+ * these tables, so filtering on it forces a full-table scan. No spec may name
161
+ * it, no matter how tempting "when did we actually write this" sounds.
162
+ */
163
+ test("never counts on the unindexed write-time column", () => {
164
+ for (const spec of TELEMETRY_INGESTION_SIGNALS) {
165
+ expect(spec.timeColumn).not.toBe("createdAt");
166
+ }
167
+ });
168
+ });
169
+
170
+ describe("buildSignalIngestionQuery", () => {
171
+ const query: string = buildSignalIngestionQuery({
172
+ spec: specFor(TelemetryIngestionSignal.Logs),
173
+ databaseName: "oneuptime",
174
+ });
175
+
176
+ test("bounds the scan to the last day so only the last day's partitions are read", () => {
177
+ expect(query).toContain("WHERE `time` >= now() - INTERVAL 1 DAY");
178
+ });
179
+
180
+ // A future-dated event would otherwise inflate every window it lands in.
181
+ test("bounds the scan at the present too", () => {
182
+ expect(query).toContain("`time` <= now()");
183
+ });
184
+
185
+ /*
186
+ * The smaller windows are countIf projections of the SAME scan. Issuing them
187
+ * as separate queries would triple the read for identical numbers.
188
+ */
189
+ test("derives the minute and hour windows from the one day-bounded scan", () => {
190
+ expect(query).toContain(
191
+ "countIf(`time` >= now() - INTERVAL 1 MINUTE) AS last_minute",
192
+ );
193
+ expect(query).toContain(
194
+ "countIf(`time` >= now() - INTERVAL 1 HOUR) AS last_hour",
195
+ );
196
+ expect(query).toContain("count() AS last_day");
197
+ expect(query.match(/FROM/g)).toHaveLength(1);
198
+ });
199
+
200
+ test("reads the app-facing distributed table, which spans every shard", () => {
201
+ expect(query).toContain("FROM `oneuptime`.`LogItemV3`");
202
+ expect(query).not.toContain("LogItemV3Local");
203
+ });
204
+
205
+ test("uses the span's own start-time column for traces", () => {
206
+ const traceQuery: string = buildSignalIngestionQuery({
207
+ spec: specFor(TelemetryIngestionSignal.Traces),
208
+ databaseName: "oneuptime",
209
+ });
210
+
211
+ expect(traceQuery).toContain(
212
+ "countIf(`startTime` >= now() - INTERVAL 1 MINUTE)",
213
+ );
214
+ expect(traceQuery).toContain("FROM `oneuptime`.`SpanItemV3`");
215
+ });
216
+
217
+ test("counts rows only — no telemetry row data is selected", () => {
218
+ expect(query).not.toContain("SELECT *");
219
+ expect(query).not.toContain("body");
220
+ expect(query).not.toContain("attributes");
221
+ });
222
+ });
223
+
224
+ describe("buildProjectIngestionQuery", () => {
225
+ const query: string = buildProjectIngestionQuery({
226
+ spec: specFor(TelemetryIngestionSignal.Logs),
227
+ databaseName: "oneuptime",
228
+ limit: 500,
229
+ });
230
+
231
+ /*
232
+ * projectId is the LEADING primary-key column of all three telemetry tables.
233
+ * It is the one dimension these tables can be grouped on without a full scan
234
+ * of the day's partitions — grouping by service, host or attribute would not
235
+ * be affordable here.
236
+ */
237
+ test("groups on the leading primary-key column", () => {
238
+ expect(query).toContain("GROUP BY projectId");
239
+ expect(query).toContain("projectId AS project_id");
240
+ });
241
+
242
+ test("keeps the same day-bounded single scan as the by-signal query", () => {
243
+ expect(query).toContain("WHERE `time` >= now() - INTERVAL 1 DAY");
244
+ expect(query).toContain("`time` <= now()");
245
+ expect(query).toContain(
246
+ "countIf(`time` >= now() - INTERVAL 1 MINUTE) AS last_minute",
247
+ );
248
+ expect(query.match(/FROM/g)).toHaveLength(1);
249
+ });
250
+
251
+ /*
252
+ * If the cap ever bites it must drop the SMALLEST tenants, not an arbitrary
253
+ * slice — and projectId as the tie-break keeps the list from reshuffling
254
+ * between two polls that return identical numbers.
255
+ */
256
+ test("orders by the widest window so a cap drops the smallest tenants", () => {
257
+ expect(query).toContain("ORDER BY last_day DESC, project_id ASC");
258
+ });
259
+
260
+ test("caps the number of tenants returned", () => {
261
+ expect(query).toContain("LIMIT 500");
262
+ });
263
+
264
+ test("never emits a fractional or zero limit", () => {
265
+ expect(
266
+ buildProjectIngestionQuery({
267
+ spec: specFor(TelemetryIngestionSignal.Logs),
268
+ databaseName: "oneuptime",
269
+ limit: 10.7,
270
+ }),
271
+ ).toContain("LIMIT 10");
272
+
273
+ expect(
274
+ buildProjectIngestionQuery({
275
+ spec: specFor(TelemetryIngestionSignal.Logs),
276
+ databaseName: "oneuptime",
277
+ limit: 0,
278
+ }),
279
+ ).toContain("LIMIT 1");
280
+ });
281
+
282
+ test("selects no telemetry row data, only the tenant id and counts", () => {
283
+ expect(query).not.toContain("SELECT *");
284
+ expect(query).not.toContain("body");
285
+ });
286
+ });
287
+
288
+ describe("buildTelemetryStorageSizeQuery", () => {
289
+ const query: string = buildTelemetryStorageSizeQuery({
290
+ clusterName: "oneuptime",
291
+ databaseName: "oneuptime",
292
+ storageTableNames: ["LogItemV3Local", "MetricItemV3Local"],
293
+ });
294
+
295
+ /*
296
+ * The app-facing names are Distributed wrappers that hold no parts of their
297
+ * own. Sizing them would match nothing and leave every "Actual size" blank.
298
+ */
299
+ test("sizes the local storage tables, not the distributed wrappers", () => {
300
+ expect(query).toContain("'LogItemV3Local', 'MetricItemV3Local'");
301
+ });
302
+
303
+ /*
304
+ * cluster() hits ONE replica per shard. clusterAllReplicas would sum the same
305
+ * shard once per replica and overreport the footprint by the replication
306
+ * factor.
307
+ */
308
+ test("reads one replica per shard so replicas are not double-counted", () => {
309
+ expect(query).toContain("cluster('oneuptime', system.parts)");
310
+ expect(query).not.toContain("clusterAllReplicas");
311
+ });
312
+
313
+ test("counts only active parts, so replaced parts are not added twice", () => {
314
+ expect(query).toContain("WHERE active");
315
+ });
316
+
317
+ /*
318
+ * data_uncompressed_bytes is the actual data volume the page claims to show;
319
+ * bytes_on_disk is the compressed footprint and is a different number.
320
+ */
321
+ test("reports the uncompressed volume the page labels 'actual size'", () => {
322
+ expect(query).toContain(
323
+ "sum(data_uncompressed_bytes) AS uncompressed_bytes",
324
+ );
325
+ expect(query).not.toContain("bytes_on_disk");
326
+ });
327
+
328
+ test("escapes quotes in the cluster and database names", () => {
329
+ expect(
330
+ buildTelemetryStorageSizeQuery({
331
+ clusterName: "it's",
332
+ databaseName: "db's",
333
+ storageTableNames: ["a'b"],
334
+ }),
335
+ ).toContain("cluster('it''s', system.parts)");
336
+ expect(
337
+ buildTelemetryStorageSizeQuery({
338
+ clusterName: "it's",
339
+ databaseName: "db's",
340
+ storageTableNames: ["a'b"],
341
+ }),
342
+ ).toContain("database = 'db''s'");
343
+ expect(
344
+ buildTelemetryStorageSizeQuery({
345
+ clusterName: "it's",
346
+ databaseName: "db's",
347
+ storageTableNames: ["a'b"],
348
+ }),
349
+ ).toContain("'a''b'");
350
+ });
351
+ });
352
+
353
+ describe("toCountOrNull", () => {
354
+ test("reads ClickHouse's stringified UInt64 counts as numbers", () => {
355
+ expect(toCountOrNull("4210")).toBe(4210);
356
+ expect(toCountOrNull(0)).toBe(0);
357
+ });
358
+
359
+ // Absent is not zero: the page prints "—" for one and "0" for the other.
360
+ test("keeps an unreadable count unknown rather than calling it zero", () => {
361
+ expect(toCountOrNull(null)).toBeNull();
362
+ expect(toCountOrNull(undefined)).toBeNull();
363
+ expect(toCountOrNull("")).toBeNull();
364
+ expect(toCountOrNull("not-a-number")).toBeNull();
365
+ expect(toCountOrNull(Infinity)).toBeNull();
366
+ });
367
+ });
368
+
369
+ describe("sumCountsOrNull", () => {
370
+ test("adds the counts it knows", () => {
371
+ expect(sumCountsOrNull([1, 2, 3])).toBe(6);
372
+ });
373
+
374
+ /*
375
+ * A partial total is still the most useful number available — dropping the
376
+ * whole row because one signal is unreadable hides the two that answered.
377
+ */
378
+ test("totals what is known when only some inputs are missing", () => {
379
+ expect(sumCountsOrNull([5, null, 7])).toBe(12);
380
+ });
381
+
382
+ test("stays unknown when nothing is known", () => {
383
+ expect(sumCountsOrNull([null, null])).toBeNull();
384
+ expect(sumCountsOrNull([])).toBeNull();
385
+ });
386
+
387
+ // Zero is a real total and must not be mistaken for "nothing was known".
388
+ test("reports a genuine zero as zero", () => {
389
+ expect(sumCountsOrNull([0, 0])).toBe(0);
390
+ });
391
+ });
392
+
393
+ describe("mergeProjectIngestionRows", () => {
394
+ test("folds the three signals into one row per project", () => {
395
+ const projects: Array<TelemetryProjectIngestion> =
396
+ mergeProjectIngestionRows([
397
+ projectRows({
398
+ telemetryType: "Logs",
399
+ rows: [
400
+ { projectId: "p1", lastMinute: 1, lastHour: 10, lastDay: 100 },
401
+ ],
402
+ }),
403
+ projectRows({
404
+ telemetryType: "Metrics",
405
+ rows: [
406
+ { projectId: "p1", lastMinute: 2, lastHour: 20, lastDay: 200 },
407
+ ],
408
+ }),
409
+ projectRows({
410
+ telemetryType: "Traces",
411
+ rows: [
412
+ { projectId: "p1", lastMinute: 3, lastHour: 30, lastDay: 300 },
413
+ ],
414
+ }),
415
+ ]);
416
+
417
+ expect(projects).toHaveLength(1);
418
+ expect(projects[0]!.projectId).toBe("p1");
419
+ expect(projects[0]!.lastMinute).toBe(6);
420
+ expect(projects[0]!.lastHour).toBe(60);
421
+ expect(projects[0]!.lastDay).toBe(600);
422
+ });
423
+
424
+ /*
425
+ * A tenant sending only logs really did send zero metrics. That is a fact, not
426
+ * a gap, and the row has to say 0 — otherwise every logs-only instance shows a
427
+ * page full of dashes.
428
+ */
429
+ test("a project missing from a successful signal ingested none of it", () => {
430
+ const projects: Array<TelemetryProjectIngestion> =
431
+ mergeProjectIngestionRows([
432
+ projectRows({
433
+ telemetryType: "Logs",
434
+ rows: [{ projectId: "p1", lastMinute: 1, lastHour: 1, lastDay: 1 }],
435
+ }),
436
+ projectRows({ telemetryType: "Metrics", rows: [] }),
437
+ projectRows({ telemetryType: "Traces", rows: [] }),
438
+ ]);
439
+
440
+ expect(signalOf(projects[0]!, "Metrics").lastDay).toBe(0);
441
+ expect(signalOf(projects[0]!, "Traces").lastDay).toBe(0);
442
+ expect(projects[0]!.lastDay).toBe(1);
443
+ });
444
+
445
+ /*
446
+ * The opposite case, and the one that matters most: when the QUERY failed we
447
+ * know nothing about that signal for anybody. Reporting 0 there would read as
448
+ * "every tenant stopped sending traces" — an incident that never happened.
449
+ */
450
+ test("a signal whose query failed stays unknown for every project", () => {
451
+ const projects: Array<TelemetryProjectIngestion> =
452
+ mergeProjectIngestionRows([
453
+ projectRows({
454
+ telemetryType: "Logs",
455
+ rows: [{ projectId: "p1", lastMinute: 1, lastHour: 1, lastDay: 1 }],
456
+ }),
457
+ projectRows({
458
+ telemetryType: "Traces",
459
+ available: false,
460
+ rows: [],
461
+ }),
462
+ ]);
463
+
464
+ expect(signalOf(projects[0]!, "Traces").lastDay).toBeNull();
465
+ expect(signalOf(projects[0]!, "Traces").lastHour).toBeNull();
466
+ expect(signalOf(projects[0]!, "Traces").lastMinute).toBeNull();
467
+ // The total still reports the signals that did answer.
468
+ expect(projects[0]!.lastDay).toBe(1);
469
+ });
470
+
471
+ /*
472
+ * Rows returned by a signal that reported unavailable are not trustworthy
473
+ * (a partial read before a timeout), so they must not create project rows.
474
+ */
475
+ test("ignores rows carried by a signal that reported itself unavailable", () => {
476
+ const projects: Array<TelemetryProjectIngestion> =
477
+ mergeProjectIngestionRows([
478
+ projectRows({
479
+ telemetryType: "Logs",
480
+ available: false,
481
+ rows: [{ projectId: "p1", lastMinute: 1, lastHour: 1, lastDay: 1 }],
482
+ }),
483
+ ]);
484
+
485
+ expect(projects).toEqual([]);
486
+ });
487
+
488
+ test("orders the biggest tenant first", () => {
489
+ const projects: Array<TelemetryProjectIngestion> =
490
+ mergeProjectIngestionRows([
491
+ projectRows({
492
+ telemetryType: "Logs",
493
+ rows: [
494
+ { projectId: "small", lastMinute: 0, lastHour: 1, lastDay: 5 },
495
+ { projectId: "big", lastMinute: 0, lastHour: 9, lastDay: 900 },
496
+ { projectId: "medium", lastMinute: 0, lastHour: 4, lastDay: 40 },
497
+ ],
498
+ }),
499
+ ]);
500
+
501
+ expect(
502
+ projects.map((project: TelemetryProjectIngestion): string => {
503
+ return project.projectId;
504
+ }),
505
+ ).toEqual(["big", "medium", "small"]);
506
+ });
507
+
508
+ /*
509
+ * Two tenants with identical volumes must not swap places between polls —
510
+ * a table that reshuffles under a cursor is unusable.
511
+ */
512
+ test("breaks ties on project id so the order is stable between refreshes", () => {
513
+ const projects: Array<TelemetryProjectIngestion> =
514
+ mergeProjectIngestionRows([
515
+ projectRows({
516
+ telemetryType: "Logs",
517
+ rows: [
518
+ { projectId: "b", lastMinute: 0, lastHour: 0, lastDay: 10 },
519
+ { projectId: "a", lastMinute: 0, lastHour: 0, lastDay: 10 },
520
+ ],
521
+ }),
522
+ ]);
523
+
524
+ expect(
525
+ projects.map((project: TelemetryProjectIngestion): string => {
526
+ return project.projectId;
527
+ }),
528
+ ).toEqual(["a", "b"]);
529
+ });
530
+
531
+ test("every project carries one entry per signal, in registry order", () => {
532
+ const projects: Array<TelemetryProjectIngestion> =
533
+ mergeProjectIngestionRows([
534
+ projectRows({
535
+ telemetryType: "Logs",
536
+ rows: [{ projectId: "p1", lastMinute: 0, lastHour: 0, lastDay: 1 }],
537
+ }),
538
+ projectRows({ telemetryType: "Metrics", rows: [] }),
539
+ projectRows({ telemetryType: "Traces", rows: [] }),
540
+ ]);
541
+
542
+ expect(
543
+ projects[0]!.signals.map(
544
+ (signal: TelemetryProjectSignalIngestion): string => {
545
+ return signal.telemetryType;
546
+ },
547
+ ),
548
+ ).toEqual(["Logs", "Metrics", "Traces"]);
549
+ });
550
+ });
551
+
552
+ describe("attachProjectNames", () => {
553
+ const projects: Array<TelemetryProjectIngestion> = mergeProjectIngestionRows([
554
+ projectRows({
555
+ telemetryType: "Logs",
556
+ rows: [
557
+ { projectId: "known", lastMinute: 0, lastHour: 0, lastDay: 20 },
558
+ { projectId: "deleted", lastMinute: 0, lastHour: 0, lastDay: 10 },
559
+ ],
560
+ }),
561
+ ]);
562
+
563
+ test("labels a project with its name", () => {
564
+ const named: Array<TelemetryProjectIngestion> = attachProjectNames(
565
+ projects,
566
+ new Map<string, string>([["known", "Acme"]]),
567
+ );
568
+
569
+ expect(named[0]!.projectName).toBe("Acme");
570
+ });
571
+
572
+ /*
573
+ * Telemetry outlives its project: retention keeps the rows for days after the
574
+ * project row is gone. That volume is real and still has to be attributable,
575
+ * so an unresolved project keeps a null name rather than being dropped.
576
+ */
577
+ test("keeps a project whose name no longer resolves", () => {
578
+ const named: Array<TelemetryProjectIngestion> = attachProjectNames(
579
+ projects,
580
+ new Map<string, string>([["known", "Acme"]]),
581
+ );
582
+
583
+ expect(named).toHaveLength(2);
584
+ expect(named[1]!.projectId).toBe("deleted");
585
+ expect(named[1]!.projectName).toBeNull();
586
+ expect(named[1]!.lastDay).toBe(10);
587
+ });
588
+
589
+ test("leaves the counts untouched", () => {
590
+ const named: Array<TelemetryProjectIngestion> = attachProjectNames(
591
+ projects,
592
+ new Map<string, string>(),
593
+ );
594
+
595
+ expect(named[0]!.lastDay).toBe(20);
596
+ expect(named[0]!.signals).toEqual(projects[0]!.signals);
597
+ });
598
+ });
599
+
600
+ describe("getTelemetryIngestionBySignal", () => {
601
+ afterEach(() => {
602
+ jest.restoreAllMocks();
603
+ });
604
+
605
+ test("reports not connected when ClickHouse is absent, without throwing", async () => {
606
+ jest
607
+ .spyOn(ClickhouseAppInstance, "getDataSource")
608
+ .mockReturnValue(null as unknown as ClickhouseClient);
609
+
610
+ const result: TelemetrySignalIngestionResult =
611
+ await getTelemetryIngestionBySignal();
612
+
613
+ expect(result.connected).toBe(false);
614
+ expect(result.tables).toEqual([]);
615
+ });
616
+
617
+ test("reads each signal's counts and footprint", async () => {
618
+ jest.spyOn(ClickhouseAppInstance, "getDataSource").mockReturnValue(
619
+ makeClient(
620
+ async (options: {
621
+ query: string;
622
+ }): Promise<{ data: Array<JSONObject> }> => {
623
+ if (options.query.includes("system.parts")) {
624
+ return {
625
+ data: [{ table: "LogItemV3Local", uncompressed_bytes: "2048" }],
626
+ };
627
+ }
628
+
629
+ return {
630
+ data: [{ last_minute: "1", last_hour: "60", last_day: "1440" }],
631
+ };
632
+ },
633
+ ),
634
+ );
635
+
636
+ const result: TelemetrySignalIngestionResult =
637
+ await getTelemetryIngestionBySignal();
638
+
639
+ expect(result.connected).toBe(true);
640
+ expect(result.tables).toHaveLength(3);
641
+
642
+ const logs: TelemetrySignalIngestion = result.tables[0]!;
643
+ expect(logs.telemetryType).toBe("Logs");
644
+ expect(logs.lastMinute).toBe(1);
645
+ expect(logs.lastHour).toBe(60);
646
+ expect(logs.lastDay).toBe(1440);
647
+ expect(logs.uncompressedBytes).toBe(2048);
648
+ expect(logs.available).toBe(true);
649
+ });
650
+
651
+ /*
652
+ * An instance that only ingests logs has no metric or span table. That must
653
+ * cost the missing signal its row's numbers and nothing else.
654
+ */
655
+ test("a failing table leaves the signals that answered intact", async () => {
656
+ jest.spyOn(ClickhouseAppInstance, "getDataSource").mockReturnValue(
657
+ makeClient(
658
+ async (options: {
659
+ query: string;
660
+ }): Promise<{ data: Array<JSONObject> }> => {
661
+ if (options.query.includes("SpanItemV3")) {
662
+ throw new Error("UNKNOWN_TABLE");
663
+ }
664
+
665
+ if (options.query.includes("system.parts")) {
666
+ return { data: [] };
667
+ }
668
+
669
+ return {
670
+ data: [{ last_minute: "1", last_hour: "2", last_day: "3" }],
671
+ };
672
+ },
673
+ ),
674
+ );
675
+
676
+ const result: TelemetrySignalIngestionResult =
677
+ await getTelemetryIngestionBySignal();
678
+
679
+ const traces: TelemetrySignalIngestion = result.tables[2]!;
680
+ expect(traces.telemetryType).toBe("Traces");
681
+ expect(traces.available).toBe(false);
682
+ expect(traces.lastDay).toBeNull();
683
+ expect(result.tables[0]!.lastDay).toBe(3);
684
+ });
685
+
686
+ // The counts are the point; a blank size column is a far smaller loss.
687
+ test("a failing size query still reports the ingestion counts", async () => {
688
+ jest.spyOn(ClickhouseAppInstance, "getDataSource").mockReturnValue(
689
+ makeClient(
690
+ async (options: {
691
+ query: string;
692
+ }): Promise<{ data: Array<JSONObject> }> => {
693
+ if (options.query.includes("system.parts")) {
694
+ throw new Error("ACCESS_DENIED");
695
+ }
696
+
697
+ return {
698
+ data: [{ last_minute: "1", last_hour: "2", last_day: "3" }],
699
+ };
700
+ },
701
+ ),
702
+ );
703
+
704
+ const result: TelemetrySignalIngestionResult =
705
+ await getTelemetryIngestionBySignal();
706
+
707
+ expect(result.tables[0]!.uncompressedBytes).toBeNull();
708
+ expect(result.tables[0]!.lastDay).toBe(3);
709
+ });
710
+ });
711
+
712
+ describe("getTelemetryIngestionByProject", () => {
713
+ afterEach(() => {
714
+ jest.restoreAllMocks();
715
+ });
716
+
717
+ test("reports not connected when ClickHouse is absent, without throwing", async () => {
718
+ jest
719
+ .spyOn(ClickhouseAppInstance, "getDataSource")
720
+ .mockReturnValue(null as unknown as ClickhouseClient);
721
+
722
+ const result: TelemetryProjectIngestionResult =
723
+ await getTelemetryIngestionByProject();
724
+
725
+ expect(result.connected).toBe(false);
726
+ expect(result.projects).toEqual([]);
727
+ });
728
+
729
+ test("merges the per-signal groupings into per-project rows", async () => {
730
+ jest.spyOn(ClickhouseAppInstance, "getDataSource").mockReturnValue(
731
+ makeClient(
732
+ async (options: {
733
+ query: string;
734
+ }): Promise<{ data: Array<JSONObject> }> => {
735
+ if (options.query.includes("LogItemV3")) {
736
+ return {
737
+ data: [
738
+ {
739
+ project_id: "p1",
740
+ last_minute: "1",
741
+ last_hour: "5",
742
+ last_day: "50",
743
+ },
744
+ ],
745
+ };
746
+ }
747
+
748
+ if (options.query.includes("MetricItemV3")) {
749
+ return {
750
+ data: [
751
+ {
752
+ project_id: "p1",
753
+ last_minute: "2",
754
+ last_hour: "6",
755
+ last_day: "60",
756
+ },
757
+ {
758
+ project_id: "p2",
759
+ last_minute: "0",
760
+ last_hour: "0",
761
+ last_day: "5",
762
+ },
763
+ ],
764
+ };
765
+ }
766
+
767
+ return { data: [] };
768
+ },
769
+ ),
770
+ );
771
+
772
+ const result: TelemetryProjectIngestionResult =
773
+ await getTelemetryIngestionByProject();
774
+
775
+ expect(result.connected).toBe(true);
776
+ expect(
777
+ result.projects.map((project: TelemetryProjectIngestion): string => {
778
+ return project.projectId;
779
+ }),
780
+ ).toEqual(["p1", "p2"]);
781
+ expect(result.projects[0]!.lastDay).toBe(110);
782
+ // p2 sent metrics only, so its logs really are zero.
783
+ expect(signalOf(result.projects[1]!, "Logs").lastDay).toBe(0);
784
+ });
785
+
786
+ test("names are left for the caller to resolve", async () => {
787
+ jest.spyOn(ClickhouseAppInstance, "getDataSource").mockReturnValue(
788
+ makeClient(async (): Promise<{ data: Array<JSONObject> }> => {
789
+ return {
790
+ data: [
791
+ {
792
+ project_id: "p1",
793
+ last_minute: "0",
794
+ last_hour: "0",
795
+ last_day: "1",
796
+ },
797
+ ],
798
+ };
799
+ }),
800
+ );
801
+
802
+ const result: TelemetryProjectIngestionResult =
803
+ await getTelemetryIngestionByProject();
804
+
805
+ expect(result.projects[0]!.projectName).toBeNull();
806
+ });
807
+
808
+ test("a failing signal is reported as unavailable, not as zero", async () => {
809
+ jest.spyOn(ClickhouseAppInstance, "getDataSource").mockReturnValue(
810
+ makeClient(
811
+ async (options: {
812
+ query: string;
813
+ }): Promise<{ data: Array<JSONObject> }> => {
814
+ if (options.query.includes("SpanItemV3")) {
815
+ throw new Error("UNKNOWN_TABLE");
816
+ }
817
+
818
+ return {
819
+ data: [
820
+ {
821
+ project_id: "p1",
822
+ last_minute: "0",
823
+ last_hour: "0",
824
+ last_day: "1",
825
+ },
826
+ ],
827
+ };
828
+ },
829
+ ),
830
+ );
831
+
832
+ const result: TelemetryProjectIngestionResult =
833
+ await getTelemetryIngestionByProject();
834
+
835
+ expect(result.signals).toEqual([
836
+ { telemetryType: "Logs", available: true, truncated: false },
837
+ { telemetryType: "Metrics", available: true, truncated: false },
838
+ { telemetryType: "Traces", available: false, truncated: false },
839
+ ]);
840
+ expect(signalOf(result.projects[0]!, "Traces").lastDay).toBeNull();
841
+ });
842
+
843
+ /*
844
+ * Silently dropping tenants off the bottom of a list an operator reads as
845
+ * "everyone who is ingesting" is the failure this flag exists to prevent.
846
+ */
847
+ test("flags truncation when a signal fills the cap", async () => {
848
+ jest.spyOn(ClickhouseAppInstance, "getDataSource").mockReturnValue(
849
+ makeClient(
850
+ async (options: {
851
+ query: string;
852
+ }): Promise<{ data: Array<JSONObject> }> => {
853
+ if (!options.query.includes("LogItemV3")) {
854
+ return { data: [] };
855
+ }
856
+
857
+ return {
858
+ data: Array.from(
859
+ { length: MAX_PROJECTS_PER_SIGNAL },
860
+ (_unused: unknown, index: number): JSONObject => {
861
+ return {
862
+ project_id: `p${index}`,
863
+ last_minute: "0",
864
+ last_hour: "0",
865
+ last_day: "1",
866
+ };
867
+ },
868
+ ),
869
+ };
870
+ },
871
+ ),
872
+ );
873
+
874
+ const result: TelemetryProjectIngestionResult =
875
+ await getTelemetryIngestionByProject();
876
+
877
+ expect(result.truncated).toBe(true);
878
+ expect(result.projects).toHaveLength(MAX_PROJECTS_PER_SIGNAL);
879
+ });
880
+
881
+ test("does not flag truncation when every signal came back under the cap", async () => {
882
+ jest.spyOn(ClickhouseAppInstance, "getDataSource").mockReturnValue(
883
+ makeClient(async (): Promise<{ data: Array<JSONObject> }> => {
884
+ return {
885
+ data: [
886
+ {
887
+ project_id: "p1",
888
+ last_minute: "0",
889
+ last_hour: "0",
890
+ last_day: "1",
891
+ },
892
+ ],
893
+ };
894
+ }),
895
+ );
896
+
897
+ const result: TelemetryProjectIngestionResult =
898
+ await getTelemetryIngestionByProject();
899
+
900
+ expect(result.truncated).toBe(false);
901
+ });
902
+
903
+ test("survives ClickHouse failing entirely", async () => {
904
+ jest.spyOn(ClickhouseAppInstance, "getDataSource").mockReturnValue(
905
+ makeClient(async (): Promise<{ data: Array<JSONObject> }> => {
906
+ throw new Error("connection refused");
907
+ }),
908
+ );
909
+
910
+ const result: TelemetryProjectIngestionResult =
911
+ await getTelemetryIngestionByProject();
912
+
913
+ expect(result.connected).toBe(true);
914
+ expect(result.projects).toEqual([]);
915
+ expect(
916
+ result.signals.every((signal: { available: boolean }): boolean => {
917
+ return !signal.available;
918
+ }),
919
+ ).toBe(true);
920
+ });
921
+ });
922
+
923
+ describe("query timeouts", () => {
924
+ afterEach(() => {
925
+ jest.restoreAllMocks();
926
+ });
927
+
928
+ /*
929
+ * These are dashboard polls against the largest tables in the system. Without
930
+ * a server-side cap, one refresh on a struggling cluster can hold a query slot
931
+ * for as long as the HTTP client will wait.
932
+ */
933
+ test("every probe query is capped with max_execution_time", async () => {
934
+ const settings: Array<unknown> = [];
935
+
936
+ jest.spyOn(ClickhouseAppInstance, "getDataSource").mockReturnValue({
937
+ query: (options: {
938
+ clickhouse_settings?: { max_execution_time?: number };
939
+ }): Promise<unknown> => {
940
+ settings.push(options.clickhouse_settings?.max_execution_time);
941
+
942
+ return Promise.resolve({
943
+ json: (): Promise<{ data: Array<JSONObject> }> => {
944
+ return Promise.resolve({ data: [] });
945
+ },
946
+ });
947
+ },
948
+ } as unknown as ClickhouseClient);
949
+
950
+ await getTelemetryIngestionBySignal();
951
+ await getTelemetryIngestionByProject();
952
+
953
+ expect(settings.length).toBeGreaterThan(0);
954
+ expect(
955
+ settings.every((value: unknown): boolean => {
956
+ return typeof value === "number" && value > 0;
957
+ }),
958
+ ).toBe(true);
959
+ });
960
+ });