@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,970 @@
1
+ import LokiConnector, {
2
+ LokiQueryRangeData,
3
+ lokiMatrixToTableRows,
4
+ lokiMatrixToTimeSeries,
5
+ lokiNanosecondsToDate,
6
+ lokiStreamsToTableRows,
7
+ parseLokiMatrixPoint,
8
+ parseLokiNanoseconds,
9
+ parseLokiResponseData,
10
+ toLokiNanoseconds,
11
+ } from "../../../../Server/Utils/DataSource/Connectors/LokiConnector";
12
+ import DataSourceEgressGuard from "../../../../Server/Utils/DataSource/EgressGuard";
13
+ import DataSourceHttpFetch, {
14
+ DataSourceHttpRequest,
15
+ DataSourceHttpResponse,
16
+ } from "../../../../Server/Utils/DataSource/HttpFetch";
17
+ import {
18
+ DataSourceConnectionSettings,
19
+ DataSourceQueryWindow,
20
+ } from "../../../../Server/Utils/DataSource/Types";
21
+ import AggregatedResult from "../../../../Types/BaseDatabase/AggregatedResult";
22
+ import {
23
+ DATA_SOURCE_CONNECT_TIMEOUT_IN_MS,
24
+ DATA_SOURCE_MAX_SERIES,
25
+ DATA_SOURCE_MAX_TABLE_ROWS,
26
+ DATA_SOURCE_MAX_TIME_SERIES_ROWS,
27
+ DATA_SOURCE_QUERY_TIMEOUT_IN_MS,
28
+ DataSourceLimitsUtil,
29
+ } from "../../../../Types/DataSource/DataSourceLimits";
30
+ import DataSourceTableResult, {
31
+ DataSourceTableColumn,
32
+ DataSourceTableColumnType,
33
+ } from "../../../../Types/DataSource/DataSourceTableResult";
34
+ import DataSourceType from "../../../../Types/DataSource/DataSourceType";
35
+ import BadDataException from "../../../../Types/Exception/BadDataException";
36
+ import { afterEach, describe, expect, test } from "@jest/globals";
37
+
38
+ type MakeSettingsFunction = (
39
+ overrides?: Partial<DataSourceConnectionSettings>,
40
+ ) => DataSourceConnectionSettings;
41
+
42
+ const makeSettings: MakeSettingsFunction = (
43
+ overrides?: Partial<DataSourceConnectionSettings>,
44
+ ): DataSourceConnectionSettings => {
45
+ return {
46
+ dataSourceType: DataSourceType.Loki,
47
+ url: "http://loki.example.com:3100",
48
+ apiToken: "super-secret-token",
49
+ ...overrides,
50
+ };
51
+ };
52
+
53
+ type MakeResponseFunction = (bodyJson: unknown) => DataSourceHttpResponse;
54
+
55
+ const makeResponse: MakeResponseFunction = (
56
+ bodyJson: unknown,
57
+ ): DataSourceHttpResponse => {
58
+ return {
59
+ statusCode: 200,
60
+ bodyText: JSON.stringify(bodyJson),
61
+ bodyJson: bodyJson,
62
+ };
63
+ };
64
+
65
+ type SuccessBodyFunction = (
66
+ resultType: string,
67
+ result: Array<unknown>,
68
+ ) => unknown;
69
+
70
+ const successBody: SuccessBodyFunction = (
71
+ resultType: string,
72
+ result: Array<unknown>,
73
+ ): unknown => {
74
+ return {
75
+ status: "success",
76
+ data: { resultType: resultType, result: result },
77
+ };
78
+ };
79
+
80
+ const queryWindow: DataSourceQueryWindow = {
81
+ startDate: new Date("2024-01-01T00:00:00.000Z"),
82
+ endDate: new Date("2024-01-01T01:00:00.000Z"),
83
+ stepInSeconds: 60,
84
+ };
85
+
86
+ type SpyFetchFunction = (response: DataSourceHttpResponse) => jest.SpyInstance;
87
+
88
+ const spyFetchResolving: SpyFetchFunction = (
89
+ response: DataSourceHttpResponse,
90
+ ): jest.SpyInstance => {
91
+ return jest.spyOn(DataSourceHttpFetch, "fetch").mockResolvedValue(response);
92
+ };
93
+
94
+ type GetRequestFunction = (spy: jest.SpyInstance) => DataSourceHttpRequest;
95
+
96
+ const getRequest: GetRequestFunction = (
97
+ spy: jest.SpyInstance,
98
+ ): DataSourceHttpRequest => {
99
+ return spy.mock.calls[0]![0] as DataSourceHttpRequest;
100
+ };
101
+
102
+ afterEach(() => {
103
+ jest.restoreAllMocks();
104
+ });
105
+
106
+ describe("toLokiNanoseconds", () => {
107
+ test("converts millisecond epochs to exact nanosecond strings", () => {
108
+ expect(toLokiNanoseconds(new Date(1700000000123))).toBe(
109
+ "1700000000123000000",
110
+ );
111
+ });
112
+
113
+ test("keeps every digit where float math would round", () => {
114
+ /*
115
+ * Nanosecond epochs sit above 2^53, where doubles can no longer
116
+ * represent every integer — the conversion must go through
117
+ * BigInt/string math and produce the exact tail of six zeros for any
118
+ * millisecond input, including odd values whose *1e6 product is not
119
+ * exactly representable.
120
+ */
121
+ expect(toLokiNanoseconds(new Date(1699999999999))).toBe(
122
+ "1699999999999000000",
123
+ );
124
+ expect(toLokiNanoseconds(new Date(1699999999997))).toBe(
125
+ "1699999999997000000",
126
+ );
127
+ expect(toLokiNanoseconds(new Date(1123456789123))).toBe(
128
+ "1123456789123000000",
129
+ );
130
+ });
131
+
132
+ test("handles the epoch and single milliseconds", () => {
133
+ expect(toLokiNanoseconds(new Date(0))).toBe("0");
134
+ expect(toLokiNanoseconds(new Date(1))).toBe("1000000");
135
+ });
136
+ });
137
+
138
+ describe("parseLokiNanoseconds", () => {
139
+ test("parses 19-digit nanosecond strings exactly", () => {
140
+ expect(parseLokiNanoseconds("1700000000123456789")).toBe(
141
+ BigInt("1700000000123456789"),
142
+ );
143
+ });
144
+
145
+ test("accepts surrounding whitespace", () => {
146
+ expect(parseLokiNanoseconds(" 123 ")).toBe(BigInt(123));
147
+ });
148
+
149
+ test("rejects non-numeric and fractional strings", () => {
150
+ expect(parseLokiNanoseconds("abc")).toBeNull();
151
+ expect(parseLokiNanoseconds("12.5")).toBeNull();
152
+ expect(parseLokiNanoseconds("1e18")).toBeNull();
153
+ expect(parseLokiNanoseconds("")).toBeNull();
154
+ expect(parseLokiNanoseconds(null)).toBeNull();
155
+ expect(parseLokiNanoseconds(undefined)).toBeNull();
156
+ expect(parseLokiNanoseconds({})).toBeNull();
157
+ });
158
+
159
+ test("tolerates numeric input", () => {
160
+ expect(parseLokiNanoseconds(1000000)).toBe(BigInt(1000000));
161
+ expect(parseLokiNanoseconds(Infinity)).toBeNull();
162
+ });
163
+ });
164
+
165
+ describe("lokiNanosecondsToDate", () => {
166
+ test("converts nanoseconds to a millisecond-precision Date", () => {
167
+ const date: Date | null = lokiNanosecondsToDate("1700000000123456789");
168
+ expect(date).not.toBeNull();
169
+ expect(date!.toISOString()).toBe("2023-11-14T22:13:20.123Z");
170
+ });
171
+
172
+ test("truncates rather than rounds sub-millisecond digits", () => {
173
+ const date: Date | null = lokiNanosecondsToDate("1700000000123999999");
174
+ expect(date!.toISOString()).toBe("2023-11-14T22:13:20.123Z");
175
+ });
176
+
177
+ test("round-trips with toLokiNanoseconds", () => {
178
+ const original: Date = new Date("2024-06-15T12:34:56.789Z");
179
+ const roundTripped: Date | null = lokiNanosecondsToDate(
180
+ toLokiNanoseconds(original),
181
+ );
182
+ expect(roundTripped!.getTime()).toBe(original.getTime());
183
+ });
184
+
185
+ test("returns null for malformed input", () => {
186
+ expect(lokiNanosecondsToDate("not-a-number")).toBeNull();
187
+ expect(lokiNanosecondsToDate(undefined)).toBeNull();
188
+ });
189
+ });
190
+
191
+ describe("parseLokiResponseData", () => {
192
+ test("returns resultType and result for a success envelope", () => {
193
+ const parsed: LokiQueryRangeData = parseLokiResponseData(
194
+ successBody("matrix", [{ metric: {}, values: [] }]),
195
+ );
196
+ expect(parsed.resultType).toBe("matrix");
197
+ expect(parsed.result).toHaveLength(1);
198
+ });
199
+
200
+ test("throws for non-object bodies", () => {
201
+ expect(() => {
202
+ return parseLokiResponseData(undefined);
203
+ }).toThrow(BadDataException);
204
+ expect(() => {
205
+ return parseLokiResponseData("<html>login</html>");
206
+ }).toThrow("did not return a JSON object");
207
+ expect(() => {
208
+ return parseLokiResponseData([1, 2, 3]);
209
+ }).toThrow(BadDataException);
210
+ });
211
+
212
+ test("surfaces the Loki error text on status error", () => {
213
+ expect(() => {
214
+ return parseLokiResponseData({
215
+ status: "error",
216
+ error: "parse error at line 1",
217
+ });
218
+ }).toThrow('Loki query failed with status "error": parse error at line 1');
219
+ });
220
+
221
+ test("throws when the data object is missing or malformed", () => {
222
+ expect(() => {
223
+ return parseLokiResponseData({ status: "success" });
224
+ }).toThrow("missing the data object");
225
+ expect(() => {
226
+ return parseLokiResponseData({ status: "success", data: "nope" });
227
+ }).toThrow(BadDataException);
228
+ expect(() => {
229
+ return parseLokiResponseData({ status: "success", data: {} });
230
+ }).toThrow("missing data.resultType or data.result");
231
+ expect(() => {
232
+ return parseLokiResponseData({
233
+ status: "success",
234
+ data: { resultType: "matrix", result: "not-an-array" },
235
+ });
236
+ }).toThrow(BadDataException);
237
+ });
238
+ });
239
+
240
+ describe("parseLokiMatrixPoint", () => {
241
+ test("parses [seconds, stringValue] samples", () => {
242
+ const point: { timestamp: Date; value: number } | null =
243
+ parseLokiMatrixPoint([1700000000, "42.5"]);
244
+ expect(point!.timestamp.getTime()).toBe(1700000000000);
245
+ expect(point!.value).toBe(42.5);
246
+ });
247
+
248
+ test("handles fractional-second timestamps", () => {
249
+ const point: { timestamp: Date; value: number } | null =
250
+ parseLokiMatrixPoint([1700000000.5, "1"]);
251
+ expect(point!.timestamp.getTime()).toBe(1700000000500);
252
+ });
253
+
254
+ test("rejects NaN and infinite values", () => {
255
+ expect(parseLokiMatrixPoint([1700000000, "NaN"])).toBeNull();
256
+ expect(parseLokiMatrixPoint([1700000000, "+Inf"])).toBeNull();
257
+ expect(parseLokiMatrixPoint([1700000000, "-Inf"])).toBeNull();
258
+ });
259
+
260
+ test("rejects malformed samples", () => {
261
+ expect(parseLokiMatrixPoint(null)).toBeNull();
262
+ expect(parseLokiMatrixPoint([1700000000])).toBeNull();
263
+ expect(parseLokiMatrixPoint(["abc", "1"])).toBeNull();
264
+ expect(parseLokiMatrixPoint([0, "1"])).toBeNull();
265
+ expect(parseLokiMatrixPoint({})).toBeNull();
266
+ });
267
+ });
268
+
269
+ describe("lokiMatrixToTimeSeries", () => {
270
+ test("maps series labels into per-point attributes without __name__", () => {
271
+ const result: AggregatedResult = lokiMatrixToTimeSeries([
272
+ {
273
+ metric: { __name__: "rate", app: "api", level: "error" },
274
+ values: [
275
+ [1700000000, "1"],
276
+ [1700000060, "2"],
277
+ ],
278
+ },
279
+ {
280
+ metric: { app: "worker", level: "error" },
281
+ values: [[1700000000, "3"]],
282
+ },
283
+ ]);
284
+
285
+ expect(result.truncated).toBe(false);
286
+ expect(result.data).toHaveLength(3);
287
+ expect(result.data[0]!.timestamp.getTime()).toBe(1700000000000);
288
+ expect(result.data[0]!.value).toBe(1);
289
+ expect(result.data[0]!["attributes"]).toEqual({
290
+ app: "api",
291
+ level: "error",
292
+ });
293
+ expect(result.data[2]!["attributes"]).toEqual({
294
+ app: "worker",
295
+ level: "error",
296
+ });
297
+ });
298
+
299
+ test("omits attributes entirely for unlabeled series", () => {
300
+ const result: AggregatedResult = lokiMatrixToTimeSeries([
301
+ { metric: {}, values: [[1700000000, "5"]] },
302
+ ]);
303
+ expect(result.data).toHaveLength(1);
304
+ expect("attributes" in result.data[0]!).toBe(false);
305
+ });
306
+
307
+ test("returns empty data for an empty result", () => {
308
+ const result: AggregatedResult = lokiMatrixToTimeSeries([]);
309
+ expect(result.data).toEqual([]);
310
+ expect(result.truncated).toBe(false);
311
+ });
312
+
313
+ test("skips malformed series and points without failing", () => {
314
+ const result: AggregatedResult = lokiMatrixToTimeSeries([
315
+ null,
316
+ "garbage",
317
+ { metric: { app: "api" } },
318
+ { metric: { app: "api" }, values: "nope" },
319
+ {
320
+ metric: { app: "api" },
321
+ values: [[1700000000, "NaN"], [1700000060, "7"], "junk"],
322
+ },
323
+ ]);
324
+ expect(result.data).toHaveLength(1);
325
+ expect(result.data[0]!.value).toBe(7);
326
+ });
327
+
328
+ test("caps the number of series and flags truncation", () => {
329
+ const manySeries: Array<unknown> = [];
330
+ for (
331
+ let seriesIndex: number = 0;
332
+ seriesIndex < DATA_SOURCE_MAX_SERIES + 1;
333
+ seriesIndex++
334
+ ) {
335
+ manySeries.push({
336
+ metric: { instance: `host-${seriesIndex}` },
337
+ values: [[1700000000, "1"]],
338
+ });
339
+ }
340
+ const result: AggregatedResult = lokiMatrixToTimeSeries(manySeries);
341
+ expect(result.data).toHaveLength(DATA_SOURCE_MAX_SERIES);
342
+ expect(result.truncated).toBe(true);
343
+ });
344
+
345
+ test("caps total points and flags truncation", () => {
346
+ const values: Array<unknown> = [];
347
+ for (
348
+ let pointIndex: number = 0;
349
+ pointIndex < DATA_SOURCE_MAX_TIME_SERIES_ROWS + 1;
350
+ pointIndex++
351
+ ) {
352
+ values.push([1700000000 + pointIndex, "1"]);
353
+ }
354
+ const result: AggregatedResult = lokiMatrixToTimeSeries([
355
+ { metric: {}, values: values },
356
+ ]);
357
+ expect(result.data).toHaveLength(DATA_SOURCE_MAX_TIME_SERIES_ROWS);
358
+ expect(result.truncated).toBe(true);
359
+ });
360
+
361
+ test("does not flag truncation at exactly the caps", () => {
362
+ const exactSeries: Array<unknown> = [];
363
+ for (
364
+ let seriesIndex: number = 0;
365
+ seriesIndex < DATA_SOURCE_MAX_SERIES;
366
+ seriesIndex++
367
+ ) {
368
+ exactSeries.push({
369
+ metric: { instance: `host-${seriesIndex}` },
370
+ values: [[1700000000, "1"]],
371
+ });
372
+ }
373
+ const result: AggregatedResult = lokiMatrixToTimeSeries(exactSeries);
374
+ expect(result.data).toHaveLength(DATA_SOURCE_MAX_SERIES);
375
+ expect(result.truncated).toBe(false);
376
+ });
377
+ });
378
+
379
+ describe("lokiStreamsToTableRows", () => {
380
+ test("flattens stream entries into rows sorted newest-first", () => {
381
+ const rows: Array<Record<string, unknown>> = lokiStreamsToTableRows([
382
+ {
383
+ stream: { app: "api", level: "error" },
384
+ values: [
385
+ ["1700000000000000000", "older line"],
386
+ ["1700000002000000000", "newest line"],
387
+ ],
388
+ },
389
+ {
390
+ stream: { app: "worker" },
391
+ values: [["1700000001000000000", "middle line"]],
392
+ },
393
+ ]);
394
+
395
+ expect(rows).toHaveLength(3);
396
+ expect(rows[0]!["line"]).toBe("newest line");
397
+ expect(rows[1]!["line"]).toBe("middle line");
398
+ expect(rows[2]!["line"]).toBe("older line");
399
+ expect(rows[0]!["app"]).toBe("api");
400
+ expect(rows[0]!["level"]).toBe("error");
401
+ expect(rows[1]!["app"]).toBe("worker");
402
+ expect((rows[0]!["timestamp"] as Date).toISOString()).toBe(
403
+ new Date(1700000002000).toISOString(),
404
+ );
405
+ });
406
+
407
+ test("stream labels cannot clobber the timestamp and line cells", () => {
408
+ const rows: Array<Record<string, unknown>> = lokiStreamsToTableRows([
409
+ {
410
+ stream: { line: "fake", timestamp: "fake", app: "api" },
411
+ values: [["1700000000000000000", "real line"]],
412
+ },
413
+ ]);
414
+ expect(rows[0]!["line"]).toBe("real line");
415
+ expect(rows[0]!["timestamp"]).toBeInstanceOf(Date);
416
+ expect(rows[0]!["app"]).toBe("api");
417
+ });
418
+
419
+ test("skips malformed streams and entries", () => {
420
+ const rows: Array<Record<string, unknown>> = lokiStreamsToTableRows([
421
+ null,
422
+ { stream: {}, values: "nope" },
423
+ {
424
+ stream: { app: "api" },
425
+ values: [
426
+ ["not-nanoseconds", "dropped"],
427
+ ["1700000000000000000"],
428
+ ["1700000000000000000", "kept"],
429
+ "junk",
430
+ ],
431
+ },
432
+ ]);
433
+ expect(rows).toHaveLength(1);
434
+ expect(rows[0]!["line"]).toBe("kept");
435
+ });
436
+
437
+ test("orders by full nanosecond precision, not just milliseconds", () => {
438
+ const rows: Array<Record<string, unknown>> = lokiStreamsToTableRows([
439
+ {
440
+ stream: {},
441
+ values: [
442
+ ["1700000000000000001", "first ns"],
443
+ ["1700000000000000002", "second ns"],
444
+ ],
445
+ },
446
+ ]);
447
+ expect(rows[0]!["line"]).toBe("second ns");
448
+ expect(rows[1]!["line"]).toBe("first ns");
449
+ });
450
+ });
451
+
452
+ describe("lokiMatrixToTableRows", () => {
453
+ test("maps matrix samples to rows with labels", () => {
454
+ const rows: Array<Record<string, unknown>> = lokiMatrixToTableRows([
455
+ {
456
+ metric: { __name__: "rate", app: "api" },
457
+ values: [
458
+ [1700000000, "1.5"],
459
+ [1700000060, "2.5"],
460
+ ],
461
+ },
462
+ ]);
463
+ expect(rows).toHaveLength(2);
464
+ expect(rows[0]!["value"]).toBe(1.5);
465
+ expect(rows[0]!["app"]).toBe("api");
466
+ expect(rows[0]!["__name__"]).toBeUndefined();
467
+ expect((rows[0]!["timestamp"] as Date).getTime()).toBe(1700000000000);
468
+ });
469
+
470
+ test("labels cannot clobber the timestamp and value cells", () => {
471
+ const rows: Array<Record<string, unknown>> = lokiMatrixToTableRows([
472
+ {
473
+ metric: { value: "fake", timestamp: "fake" },
474
+ values: [[1700000000, "9"]],
475
+ },
476
+ ]);
477
+ expect(rows[0]!["value"]).toBe(9);
478
+ expect(rows[0]!["timestamp"]).toBeInstanceOf(Date);
479
+ });
480
+ });
481
+
482
+ describe("LokiConnector.testConnection", () => {
483
+ test("probes /loki/api/v1/labels over the last hour with auth headers", async () => {
484
+ const spy: jest.SpyInstance = spyFetchResolving(
485
+ makeResponse({ status: "success", data: ["app", "level"] }),
486
+ );
487
+
488
+ const connector: LokiConnector = new LokiConnector();
489
+ await connector.testConnection(makeSettings());
490
+
491
+ expect(spy).toHaveBeenCalledTimes(1);
492
+ const request: DataSourceHttpRequest = getRequest(spy);
493
+ expect(request.method).toBe("GET");
494
+ expect(
495
+ request.url.startsWith(
496
+ "http://loki.example.com:3100/loki/api/v1/labels?",
497
+ ),
498
+ ).toBe(true);
499
+ expect(request.timeoutInMs).toBe(DATA_SOURCE_CONNECT_TIMEOUT_IN_MS);
500
+ expect(request.headers!["Authorization"]).toBe("Bearer super-secret-token");
501
+
502
+ const parsedUrl: URL = new URL(request.url);
503
+ const startParam: string = parsedUrl.searchParams.get("start")!;
504
+ const endParam: string = parsedUrl.searchParams.get("end")!;
505
+ /*
506
+ * The probe window is exactly one hour, expressed in nanoseconds —
507
+ * 3600 * 1e9 apart, with no float drift in either bound.
508
+ */
509
+ expect(BigInt(endParam) - BigInt(startParam)).toBe(
510
+ BigInt(3600) * BigInt(1000000000),
511
+ );
512
+ expect(startParam.endsWith("000000")).toBe(true);
513
+ expect(endParam.endsWith("000000")).toBe(true);
514
+ });
515
+
516
+ test("strips trailing slashes from the base URL", async () => {
517
+ const spy: jest.SpyInstance = spyFetchResolving(
518
+ makeResponse({ status: "success", data: [] }),
519
+ );
520
+ const connector: LokiConnector = new LokiConnector();
521
+ await connector.testConnection(
522
+ makeSettings({ url: "http://loki.example.com:3100///" }),
523
+ );
524
+ const request: DataSourceHttpRequest = getRequest(spy);
525
+ expect(
526
+ request.url.startsWith(
527
+ "http://loki.example.com:3100/loki/api/v1/labels?",
528
+ ),
529
+ ).toBe(true);
530
+ });
531
+
532
+ test("sends basic auth when username/password are configured", async () => {
533
+ const spy: jest.SpyInstance = spyFetchResolving(
534
+ makeResponse({ status: "success", data: [] }),
535
+ );
536
+ const connector: LokiConnector = new LokiConnector();
537
+ await connector.testConnection(
538
+ makeSettings({
539
+ apiToken: undefined,
540
+ username: "bob",
541
+ password: "hunter2secret",
542
+ }),
543
+ );
544
+ const request: DataSourceHttpRequest = getRequest(spy);
545
+ const expected: string = `Basic ${Buffer.from("bob:hunter2secret").toString("base64")}`;
546
+ expect(request.headers!["Authorization"]).toBe(expected);
547
+ });
548
+
549
+ test("rejects responses that are not a Loki success envelope", async () => {
550
+ spyFetchResolving({
551
+ statusCode: 200,
552
+ bodyText: "<html>gateway</html>",
553
+ bodyJson: undefined,
554
+ });
555
+ const connector: LokiConnector = new LokiConnector();
556
+ await expect(connector.testConnection(makeSettings())).rejects.toThrow(
557
+ "did not respond like a Grafana Loki API",
558
+ );
559
+ });
560
+
561
+ test("rejects a JSON body whose status is not success", async () => {
562
+ spyFetchResolving(makeResponse({ status: "error", data: [] }));
563
+ const connector: LokiConnector = new LokiConnector();
564
+ await expect(connector.testConnection(makeSettings())).rejects.toThrow(
565
+ BadDataException,
566
+ );
567
+ });
568
+
569
+ test("requires a URL and never dials without one", async () => {
570
+ const spy: jest.SpyInstance = jest.spyOn(DataSourceHttpFetch, "fetch");
571
+ const connector: LokiConnector = new LokiConnector();
572
+ await expect(
573
+ connector.testConnection(makeSettings({ url: undefined })),
574
+ ).rejects.toThrow("requires a URL");
575
+ expect(spy).not.toHaveBeenCalled();
576
+ });
577
+
578
+ test("egress guard blocks loopback targets before any socket is opened", async () => {
579
+ /*
580
+ * Real DataSourceHttpFetch.fetch path: the egress guard rejects
581
+ * loopback literals during URL validation, so no network is touched.
582
+ */
583
+ const connector: LokiConnector = new LokiConnector();
584
+ await expect(
585
+ connector.testConnection(makeSettings({ url: "http://127.0.0.1:3100" })),
586
+ ).rejects.toThrow("loopback");
587
+ });
588
+
589
+ test("egress guard runs on the configured hostname before connecting", async () => {
590
+ const guardSpy: jest.SpyInstance = jest
591
+ .spyOn(DataSourceEgressGuard, "assertHostnameAllowed")
592
+ .mockRejectedValue(
593
+ new BadDataException(
594
+ "Data source host loki.internal is not allowed: private network address.",
595
+ ) as never,
596
+ );
597
+ const connector: LokiConnector = new LokiConnector();
598
+ await expect(
599
+ connector.testConnection(
600
+ makeSettings({ url: "http://loki.internal:3100" }),
601
+ ),
602
+ ).rejects.toThrow("private network address");
603
+ expect(guardSpy).toHaveBeenCalledWith("loki.internal", undefined);
604
+ });
605
+ });
606
+
607
+ describe("LokiConnector.queryTimeSeries", () => {
608
+ test("issues a query_range GET with nanosecond bounds, step and limit", async () => {
609
+ const spy: jest.SpyInstance = spyFetchResolving(
610
+ makeResponse(successBody("matrix", [])),
611
+ );
612
+ const connector: LokiConnector = new LokiConnector();
613
+ await connector.queryTimeSeries(
614
+ makeSettings(),
615
+ 'sum(rate({app="api"} [5m]))',
616
+ queryWindow,
617
+ );
618
+
619
+ const request: DataSourceHttpRequest = getRequest(spy);
620
+ expect(request.method).toBe("GET");
621
+ expect(
622
+ request.url.startsWith(
623
+ "http://loki.example.com:3100/loki/api/v1/query_range?",
624
+ ),
625
+ ).toBe(true);
626
+ expect(request.timeoutInMs).toBe(DATA_SOURCE_QUERY_TIMEOUT_IN_MS);
627
+ expect(request.headers!["Authorization"]).toBe("Bearer super-secret-token");
628
+
629
+ const parsedUrl: URL = new URL(request.url);
630
+ expect(parsedUrl.searchParams.get("query")).toBe(
631
+ 'sum(rate({app="api"} [5m]))',
632
+ );
633
+ expect(parsedUrl.searchParams.get("start")).toBe(
634
+ toLokiNanoseconds(queryWindow.startDate),
635
+ );
636
+ expect(parsedUrl.searchParams.get("end")).toBe(
637
+ toLokiNanoseconds(queryWindow.endDate),
638
+ );
639
+ expect(parsedUrl.searchParams.get("step")).toBe("60s");
640
+ expect(parsedUrl.searchParams.get("limit")).toBe(
641
+ (DATA_SOURCE_MAX_TIME_SERIES_ROWS + 1).toString(),
642
+ );
643
+ });
644
+
645
+ test("derives the step from the window when none is given", async () => {
646
+ const spy: jest.SpyInstance = spyFetchResolving(
647
+ makeResponse(successBody("matrix", [])),
648
+ );
649
+ const windowWithoutStep: DataSourceQueryWindow = {
650
+ startDate: queryWindow.startDate,
651
+ endDate: queryWindow.endDate,
652
+ };
653
+ const connector: LokiConnector = new LokiConnector();
654
+ await connector.queryTimeSeries(
655
+ makeSettings(),
656
+ 'count_over_time({app="api"} [5m])',
657
+ windowWithoutStep,
658
+ );
659
+ const expectedStep: number = DataSourceLimitsUtil.getStepInSeconds(
660
+ windowWithoutStep.startDate,
661
+ windowWithoutStep.endDate,
662
+ );
663
+ const parsedUrl: URL = new URL(getRequest(spy).url);
664
+ expect(parsedUrl.searchParams.get("step")).toBe(`${expectedStep}s`);
665
+ });
666
+
667
+ test("normalizes a matrix response into AggregatedResult", async () => {
668
+ spyFetchResolving(
669
+ makeResponse(
670
+ successBody("matrix", [
671
+ {
672
+ metric: { app: "api" },
673
+ values: [
674
+ [1704067200, "1"],
675
+ [1704067260, "2"],
676
+ ],
677
+ },
678
+ ]),
679
+ ),
680
+ );
681
+ const connector: LokiConnector = new LokiConnector();
682
+ const result: AggregatedResult = await connector.queryTimeSeries(
683
+ makeSettings(),
684
+ 'sum(rate({app="api"} [5m]))',
685
+ queryWindow,
686
+ );
687
+ expect(result.truncated).toBe(false);
688
+ expect(result.data).toHaveLength(2);
689
+ expect(result.data[0]!.timestamp).toBeInstanceOf(Date);
690
+ expect(result.data[0]!.value).toBe(1);
691
+ expect(result.data[0]!["attributes"]).toEqual({ app: "api" });
692
+ });
693
+
694
+ test("returns an empty result for a matrix with no series", async () => {
695
+ spyFetchResolving(makeResponse(successBody("matrix", [])));
696
+ const connector: LokiConnector = new LokiConnector();
697
+ const result: AggregatedResult = await connector.queryTimeSeries(
698
+ makeSettings(),
699
+ 'sum(rate({app="api"} [5m]))',
700
+ queryWindow,
701
+ );
702
+ expect(result.data).toEqual([]);
703
+ expect(result.truncated).toBe(false);
704
+ });
705
+
706
+ test("tells the user to wrap raw log queries in a metric function", async () => {
707
+ spyFetchResolving(
708
+ makeResponse(
709
+ successBody("streams", [
710
+ { stream: { app: "api" }, values: [["1700000000000000000", "x"]] },
711
+ ]),
712
+ ),
713
+ );
714
+ const connector: LokiConnector = new LokiConnector();
715
+ await expect(
716
+ connector.queryTimeSeries(makeSettings(), '{app="api"}', queryWindow),
717
+ ).rejects.toThrow("rate() or count_over_time()");
718
+ });
719
+
720
+ test("rejects unexpected result types", async () => {
721
+ spyFetchResolving(makeResponse(successBody("vector", [])));
722
+ const connector: LokiConnector = new LokiConnector();
723
+ await expect(
724
+ connector.queryTimeSeries(makeSettings(), "vector(1)", queryWindow),
725
+ ).rejects.toThrow('unexpected result type "vector"');
726
+ });
727
+
728
+ test("rejects an empty query without dialing", async () => {
729
+ const spy: jest.SpyInstance = jest.spyOn(DataSourceHttpFetch, "fetch");
730
+ const connector: LokiConnector = new LokiConnector();
731
+ await expect(
732
+ connector.queryTimeSeries(makeSettings(), " ", queryWindow),
733
+ ).rejects.toThrow("Query is required");
734
+ expect(spy).not.toHaveBeenCalled();
735
+ });
736
+
737
+ test("surfaces Loki error envelopes with their message", async () => {
738
+ spyFetchResolving(
739
+ makeResponse({ status: "error", error: "parse error at char 5" }),
740
+ );
741
+ const connector: LokiConnector = new LokiConnector();
742
+ await expect(
743
+ connector.queryTimeSeries(makeSettings(), "sum(rate(", queryWindow),
744
+ ).rejects.toThrow("parse error at char 5");
745
+ });
746
+
747
+ test("propagates HTTP-level BadDataExceptions from the fetch layer", async () => {
748
+ jest
749
+ .spyOn(DataSourceHttpFetch, "fetch")
750
+ .mockRejectedValue(
751
+ new BadDataException(
752
+ "Data source responded with HTTP 400: parse error",
753
+ ) as never,
754
+ );
755
+ const connector: LokiConnector = new LokiConnector();
756
+ await expect(
757
+ connector.queryTimeSeries(makeSettings(), "sum(rate(", queryWindow),
758
+ ).rejects.toThrow("Data source responded with HTTP 400: parse error");
759
+ });
760
+
761
+ test("never leaks the password in error messages", async () => {
762
+ jest
763
+ .spyOn(DataSourceHttpFetch, "fetch")
764
+ .mockRejectedValue(
765
+ new Error(
766
+ "auth failed with password hunter2secret for user bob",
767
+ ) as never,
768
+ );
769
+ const connector: LokiConnector = new LokiConnector();
770
+ const settings: DataSourceConnectionSettings = makeSettings({
771
+ apiToken: undefined,
772
+ username: "bob",
773
+ password: "hunter2secret",
774
+ });
775
+
776
+ let thrownMessage: string = "";
777
+ try {
778
+ await connector.queryTimeSeries(
779
+ settings,
780
+ 'sum(rate({a="b"}[1m]))',
781
+ queryWindow,
782
+ );
783
+ } catch (error) {
784
+ thrownMessage = (error as Error).message;
785
+ }
786
+ expect(thrownMessage).not.toContain("hunter2secret");
787
+ expect(thrownMessage).toContain("***");
788
+ });
789
+
790
+ test("never leaks the API token in error messages", async () => {
791
+ jest
792
+ .spyOn(DataSourceHttpFetch, "fetch")
793
+ .mockRejectedValue(
794
+ new Error("401 unauthorized for token super-secret-token") as never,
795
+ );
796
+ const connector: LokiConnector = new LokiConnector();
797
+
798
+ let thrownMessage: string = "";
799
+ try {
800
+ await connector.queryTimeSeries(
801
+ makeSettings(),
802
+ 'sum(rate({a="b"}[1m]))',
803
+ queryWindow,
804
+ );
805
+ } catch (error) {
806
+ thrownMessage = (error as Error).message;
807
+ }
808
+ expect(thrownMessage).not.toContain("super-secret-token");
809
+ expect(thrownMessage).toContain("***");
810
+ });
811
+
812
+ test("uses the injected fetch function instead of the static fetch", async () => {
813
+ const staticSpy: jest.SpyInstance = jest.spyOn(
814
+ DataSourceHttpFetch,
815
+ "fetch",
816
+ );
817
+ const injectedFetch: jest.Mock = jest
818
+ .fn()
819
+ .mockResolvedValue(makeResponse(successBody("matrix", [])) as never);
820
+ const connector: LokiConnector = new LokiConnector(injectedFetch as never);
821
+ const result: AggregatedResult = await connector.queryTimeSeries(
822
+ makeSettings(),
823
+ 'sum(rate({app="api"} [5m]))',
824
+ queryWindow,
825
+ );
826
+ expect(result.data).toEqual([]);
827
+ expect(injectedFetch).toHaveBeenCalledTimes(1);
828
+ expect(staticSpy).not.toHaveBeenCalled();
829
+ });
830
+ });
831
+
832
+ describe("LokiConnector.queryTable", () => {
833
+ test("passes the table row cap + 1 as the limit parameter", async () => {
834
+ const spy: jest.SpyInstance = spyFetchResolving(
835
+ makeResponse(successBody("streams", [])),
836
+ );
837
+ const connector: LokiConnector = new LokiConnector();
838
+ await connector.queryTable(makeSettings(), '{app="api"}', queryWindow);
839
+ const parsedUrl: URL = new URL(getRequest(spy).url);
840
+ expect(parsedUrl.searchParams.get("limit")).toBe(
841
+ (DATA_SOURCE_MAX_TABLE_ROWS + 1).toString(),
842
+ );
843
+ });
844
+
845
+ test("maps streams to rows with ISO timestamps, lines and labels", async () => {
846
+ spyFetchResolving(
847
+ makeResponse(
848
+ successBody("streams", [
849
+ {
850
+ stream: { app: "api", level: "error" },
851
+ values: [
852
+ ["1700000000123456789", "first error"],
853
+ ["1700000060123456789", "second error"],
854
+ ],
855
+ },
856
+ ]),
857
+ ),
858
+ );
859
+ const connector: LokiConnector = new LokiConnector();
860
+ const result: DataSourceTableResult = await connector.queryTable(
861
+ makeSettings(),
862
+ '{app="api"}',
863
+ queryWindow,
864
+ );
865
+
866
+ expect(result.truncated).toBe(false);
867
+ expect(result.rows).toHaveLength(2);
868
+ /* Newest first. */
869
+ expect(result.rows[0]!["line"]).toBe("second error");
870
+ expect(result.rows[0]!["timestamp"]).toBe("2023-11-14T22:14:20.123Z");
871
+ expect(result.rows[0]!["app"]).toBe("api");
872
+ expect(result.rows[1]!["line"]).toBe("first error");
873
+ expect(result.rows[1]!["timestamp"]).toBe("2023-11-14T22:13:20.123Z");
874
+
875
+ const timestampColumn: DataSourceTableColumn | undefined =
876
+ result.columns.find((column: DataSourceTableColumn) => {
877
+ return column.key === "timestamp";
878
+ });
879
+ expect(timestampColumn!.type).toBe(DataSourceTableColumnType.Date);
880
+ const lineColumn: DataSourceTableColumn | undefined = result.columns.find(
881
+ (column: DataSourceTableColumn) => {
882
+ return column.key === "line";
883
+ },
884
+ );
885
+ expect(lineColumn!.type).toBe(DataSourceTableColumnType.Text);
886
+ });
887
+
888
+ test("clamps stream rows at the table cap and flags truncation", async () => {
889
+ const values: Array<unknown> = [];
890
+ for (
891
+ let entryIndex: number = 0;
892
+ entryIndex < DATA_SOURCE_MAX_TABLE_ROWS + 1;
893
+ entryIndex++
894
+ ) {
895
+ const nanoseconds: string = (
896
+ BigInt("1700000000000000000") +
897
+ BigInt(entryIndex) * BigInt(1000000)
898
+ ).toString();
899
+ values.push([nanoseconds, `line ${entryIndex}`]);
900
+ }
901
+ spyFetchResolving(
902
+ makeResponse(
903
+ successBody("streams", [{ stream: { app: "api" }, values: values }]),
904
+ ),
905
+ );
906
+ const connector: LokiConnector = new LokiConnector();
907
+ const result: DataSourceTableResult = await connector.queryTable(
908
+ makeSettings(),
909
+ '{app="api"}',
910
+ queryWindow,
911
+ );
912
+ expect(result.rows).toHaveLength(DATA_SOURCE_MAX_TABLE_ROWS);
913
+ expect(result.truncated).toBe(true);
914
+ });
915
+
916
+ test("maps matrix results to rows with numeric value column", async () => {
917
+ spyFetchResolving(
918
+ makeResponse(
919
+ successBody("matrix", [
920
+ {
921
+ metric: { app: "api" },
922
+ values: [
923
+ [1704067200, "1.5"],
924
+ [1704067260, "2.5"],
925
+ ],
926
+ },
927
+ ]),
928
+ ),
929
+ );
930
+ const connector: LokiConnector = new LokiConnector();
931
+ const result: DataSourceTableResult = await connector.queryTable(
932
+ makeSettings(),
933
+ 'sum(rate({app="api"} [5m]))',
934
+ queryWindow,
935
+ );
936
+
937
+ expect(result.rows).toHaveLength(2);
938
+ expect(result.rows[0]!["value"]).toBe(1.5);
939
+ expect(result.rows[0]!["app"]).toBe("api");
940
+ expect(typeof result.rows[0]!["timestamp"]).toBe("string");
941
+
942
+ const valueColumn: DataSourceTableColumn | undefined = result.columns.find(
943
+ (column: DataSourceTableColumn) => {
944
+ return column.key === "value";
945
+ },
946
+ );
947
+ expect(valueColumn!.type).toBe(DataSourceTableColumnType.Number);
948
+ });
949
+
950
+ test("returns an empty table for empty results", async () => {
951
+ spyFetchResolving(makeResponse(successBody("streams", [])));
952
+ const connector: LokiConnector = new LokiConnector();
953
+ const result: DataSourceTableResult = await connector.queryTable(
954
+ makeSettings(),
955
+ '{app="api"}',
956
+ queryWindow,
957
+ );
958
+ expect(result.columns).toEqual([]);
959
+ expect(result.rows).toEqual([]);
960
+ expect(result.truncated).toBe(false);
961
+ });
962
+
963
+ test("rejects unexpected result types for tables", async () => {
964
+ spyFetchResolving(makeResponse(successBody("vector", [])));
965
+ const connector: LokiConnector = new LokiConnector();
966
+ await expect(
967
+ connector.queryTable(makeSettings(), "vector(1)", queryWindow),
968
+ ).rejects.toThrow('unexpected result type "vector"');
969
+ });
970
+ });