@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,192 @@
1
+ import OneUptimeDate from "../../Types/Date";
2
+ import moment from "moment-timezone";
3
+ import { afterAll, describe, expect, test } from "@jest/globals";
4
+
5
+ /*
6
+ * Run the whole suite in a non-UTC, DST-observing zone. Under TZ=UTC the
7
+ * local and UTC Date getters agree, so a getUTC* -> get* regression in the
8
+ * hand-rolled formatter would be invisible to every parity assertion below.
9
+ * America/New_York makes any local-getter mutation diverge from the
10
+ * moment-UTC oracle deterministically (including across DST transitions in
11
+ * the fuzz sweep). Node re-reads process.env.TZ on Date operations; the
12
+ * original value is restored afterAll so co-resident suites in the same
13
+ * worker keep the process default.
14
+ */
15
+ const ORIGINAL_TZ: string | undefined = process.env["TZ"];
16
+ process.env["TZ"] = "America/New_York";
17
+
18
+ afterAll(() => {
19
+ if (ORIGINAL_TZ === undefined) {
20
+ delete process.env["TZ"];
21
+ } else {
22
+ process.env["TZ"] = ORIGINAL_TZ;
23
+ }
24
+ });
25
+
26
+ /*
27
+ * Contract under test — the ClickHouse DateTime formatters that stamp every
28
+ * ingested telemetry row (createdAt, time, retentionDate, startTime/endTime).
29
+ *
30
+ * These used to be moment(...).utc().format("YYYY-MM-DD HH:mm:ss"), which
31
+ * allocates a moment wrapper and parses a format-token string PER CALL — and
32
+ * they are called several times per log record / span / metric datapoint.
33
+ * They are now a hand-rolled UTC formatter. The replacement must be
34
+ * byte-identical to what moment produced, because these strings are written
35
+ * straight into ClickHouse DateTime columns and compared/parsed downstream.
36
+ *
37
+ * The parity tests below therefore compare the new implementation against a
38
+ * live moment call, not against hardcoded strings, across edge cases and a
39
+ * deterministic fuzz sweep.
40
+ */
41
+
42
+ function momentReference(date: Date): string {
43
+ return moment(date).utc().format("YYYY-MM-DD HH:mm:ss");
44
+ }
45
+
46
+ describe("OneUptimeDate.toClickhouseDateTime", () => {
47
+ const edgeCases: Array<{ name: string; date: Date }> = [
48
+ { name: "unix epoch", date: new Date(0) },
49
+ { name: "one ms before epoch", date: new Date(-1) },
50
+ { name: "one second before epoch", date: new Date(-1000) },
51
+ {
52
+ name: "pre-1970 date",
53
+ date: new Date(Date.UTC(1955, 10, 5, 6, 15, 30, 123)),
54
+ },
55
+ {
56
+ name: "leap day",
57
+ date: new Date(Date.UTC(2024, 1, 29, 23, 59, 59, 999)),
58
+ },
59
+ {
60
+ name: "year rollover instant",
61
+ date: new Date(Date.UTC(2025, 11, 31, 23, 59, 59, 999)),
62
+ },
63
+ {
64
+ name: "first instant of a year",
65
+ date: new Date(Date.UTC(2026, 0, 1, 0, 0, 0, 0)),
66
+ },
67
+ {
68
+ name: "single-digit month/day/hour/minute/second",
69
+ date: new Date(Date.UTC(2026, 2, 4, 5, 6, 7, 8)),
70
+ },
71
+ {
72
+ name: "three-digit year pads to 4 digits",
73
+ date: new Date(Date.UTC(999, 0, 2, 3, 4, 5)),
74
+ },
75
+ {
76
+ name: "far future",
77
+ date: new Date(Date.UTC(2999, 11, 31, 12, 0, 0)),
78
+ },
79
+ ];
80
+
81
+ for (const { name, date } of edgeCases) {
82
+ test(`matches the moment implementation it replaced: ${name}`, () => {
83
+ expect(OneUptimeDate.toClickhouseDateTime(date)).toBe(
84
+ momentReference(date),
85
+ );
86
+ });
87
+ }
88
+
89
+ test("fuzz sweep: parity with moment across ±100 years", () => {
90
+ /*
91
+ * Deterministic LCG so a failure is reproducible. 2000 samples spread
92
+ * across ~200 years of epoch millis, including sub-second components.
93
+ */
94
+ let seed: number = 42;
95
+ const nextRandom: () => number = (): number => {
96
+ seed = (seed * 1103515245 + 12345) % 2147483648;
97
+ return seed / 2147483648;
98
+ };
99
+
100
+ const hundredYearsMs: number = 100 * 365.25 * 24 * 3600 * 1000;
101
+
102
+ for (let i: number = 0; i < 2000; i++) {
103
+ const ms: number = Math.floor((nextRandom() * 2 - 1) * hundredYearsMs);
104
+ const date: Date = new Date(ms);
105
+ expect(OneUptimeDate.toClickhouseDateTime(date)).toBe(
106
+ momentReference(date),
107
+ );
108
+ }
109
+ });
110
+
111
+ test("accepts ISO string input like the moment version did", () => {
112
+ const iso: string = "2026-08-10T13:45:59.500Z";
113
+ expect(OneUptimeDate.toClickhouseDateTime(iso)).toBe(
114
+ momentReference(new Date(iso)),
115
+ );
116
+ });
117
+
118
+ test("keeps moment's 'Invalid date' sentinel for NaN dates", () => {
119
+ const invalid: Date = new Date(NaN);
120
+ expect(OneUptimeDate.toClickhouseDateTime(invalid)).toBe(
121
+ momentReference(invalid),
122
+ );
123
+ expect(OneUptimeDate.toClickhouseDateTime(invalid)).toBe("Invalid date");
124
+ });
125
+
126
+ test("output is always UTC regardless of process timezone", () => {
127
+ // 2026-06-15T00:30:00Z is a different calendar day in UTC-negative zones.
128
+ const date: Date = new Date(Date.UTC(2026, 5, 15, 0, 30, 0));
129
+ expect(OneUptimeDate.toClickhouseDateTime(date)).toBe(
130
+ "2026-06-15 00:30:00",
131
+ );
132
+ });
133
+ });
134
+
135
+ describe("OneUptimeDate.toClickhouseDateTime64", () => {
136
+ /*
137
+ * Nano timestamps arrive as JS numbers, so a modern epoch date times 1e6
138
+ * exceeds Number.MAX_SAFE_INTEGER and the sub-second digits are already
139
+ * imprecise before this function sees them (pre-existing caller
140
+ * behavior). Exercise the extraction arithmetic with early-epoch dates
141
+ * whose nano values are exactly representable.
142
+ */
143
+ test("extracts sub-second nanos from the unix-nano timestamp", () => {
144
+ const date: Date = new Date(Date.UTC(1970, 0, 20, 12, 0, 5, 0));
145
+ // 123456789 sub-second nanoseconds — exactly representable here.
146
+ const nano: number = date.getTime() * 1_000_000 + 123_456_789;
147
+ expect(OneUptimeDate.toClickhouseDateTime64(date, nano)).toBe(
148
+ "1970-01-20 12:00:05.123456789",
149
+ );
150
+ });
151
+
152
+ test("pads short sub-second nano fractions to 9 digits", () => {
153
+ const date: Date = new Date(Date.UTC(1970, 0, 20, 12, 0, 5, 0));
154
+ const nano: number = date.getTime() * 1_000_000 + 7;
155
+ expect(OneUptimeDate.toClickhouseDateTime64(date, nano)).toBe(
156
+ "1970-01-20 12:00:05.000000007",
157
+ );
158
+ });
159
+
160
+ test("falls back to Date milliseconds when nano timestamp is absent", () => {
161
+ const date: Date = new Date(Date.UTC(2026, 7, 10, 12, 0, 5, 123));
162
+ expect(OneUptimeDate.toClickhouseDateTime64(date)).toBe(
163
+ "2026-08-10 12:00:05.123000000",
164
+ );
165
+ });
166
+
167
+ test("falls back to Date milliseconds when nano timestamp is zero", () => {
168
+ // The implementation treats only nanoTimestamp > 0 as authoritative.
169
+ const date: Date = new Date(Date.UTC(2026, 7, 10, 12, 0, 5, 42));
170
+ expect(OneUptimeDate.toClickhouseDateTime64(date, 0)).toBe(
171
+ "2026-08-10 12:00:05.042000000",
172
+ );
173
+ });
174
+
175
+ test("base portion matches the moment implementation it replaced", () => {
176
+ const date: Date = new Date(Date.UTC(1999, 11, 31, 23, 59, 59, 999));
177
+ const formatted: string = OneUptimeDate.toClickhouseDateTime64(date);
178
+ expect(formatted.slice(0, 19)).toBe(momentReference(date));
179
+ });
180
+ });
181
+
182
+ describe("OneUptimeDate.getCurrentDate", () => {
183
+ test("returns the current wall-clock time as a plain Date", () => {
184
+ const before: number = Date.now();
185
+ const current: Date = OneUptimeDate.getCurrentDate();
186
+ const after: number = Date.now();
187
+
188
+ expect(current).toBeInstanceOf(Date);
189
+ expect(current.getTime()).toBeGreaterThanOrEqual(before);
190
+ expect(current.getTime()).toBeLessThanOrEqual(after);
191
+ });
192
+ });
@@ -0,0 +1,363 @@
1
+ import {
2
+ MAXIMUM_PASSWORD_LENGTH,
3
+ MINIMUM_PASSWORD_LENGTH,
4
+ getPasswordValidationError,
5
+ } from "../../Types/Password";
6
+ import { describe, expect, test } from "@jest/globals";
7
+ import fs from "fs";
8
+ import path from "path";
9
+
10
+ /*
11
+ * The password policy, pinned.
12
+ *
13
+ * Before this module existed the only thing enforcing a minimum password
14
+ * length in OneUptime was `minLength: 6` typed inline into each browser form.
15
+ * A password that arrives from anywhere other than that form — the master-admin
16
+ * "set this user's password" endpoint being the obvious one — skipped the check
17
+ * entirely. So the value of this module is not that it is clever; it is that
18
+ * exactly one place decides what "acceptable" means, and both the form and the
19
+ * server ask it.
20
+ *
21
+ * That makes two classes of silent breakage worth guarding:
22
+ *
23
+ * 1. The RULES drifting. Every branch here is a rejection an operator or a
24
+ * user will read, and the order the branches run in is load-bearing (a
25
+ * six-space password is long enough and still not a password). Loosening
26
+ * one by accident is invisible until a bad password is already hashed and
27
+ * stored.
28
+ * 2. The rules drifting APART. If the Admin Dashboard form goes back to
29
+ * hardcoding a number, then retuning the policy here silently stops
30
+ * retuning the form, and the browser starts accepting what the server
31
+ * rejects — which surfaces to the operator as an opaque failed request.
32
+ * The last describe block below reads that form's source to pin it.
33
+ *
34
+ * The length cases are derived from the constants rather than written as 6 and
35
+ * 100, so that raising the minimum moves these tests with the policy instead of
36
+ * turning them into a wall of failures that has to be hand-edited (and might be
37
+ * hand-edited wrongly).
38
+ */
39
+
40
+ describe("getPasswordValidationError rejects a missing password", () => {
41
+ /*
42
+ * undefined, null and "" all mean "the operator submitted the form without
43
+ * typing anything", and each arrives from a different layer: undefined from a
44
+ * JSON body with no `password` key at all, null from a client that sent one
45
+ * explicitly, "" from an empty text input. They have to be one message
46
+ * because to the person reading it they are one mistake.
47
+ */
48
+ test("undefined, null and the empty string all report the password as required", () => {
49
+ const expected: string = "Password is required.";
50
+
51
+ expect(getPasswordValidationError(undefined)).toBe(expected);
52
+ expect(getPasswordValidationError(null)).toBe(expected);
53
+ expect(getPasswordValidationError("")).toBe(expected);
54
+ });
55
+ });
56
+
57
+ describe("getPasswordValidationError rejects a blank password", () => {
58
+ test("a single space and a tab/newline run are blank, not passwords", () => {
59
+ expect(getPasswordValidationError(" ")).toBe("Password cannot be blank.");
60
+ expect(getPasswordValidationError("\t\n")).toBe(
61
+ "Password cannot be blank.",
62
+ );
63
+ });
64
+
65
+ /*
66
+ * The case the blank check exists for.
67
+ *
68
+ * Six spaces satisfies the length rule on its own — nothing below this line
69
+ * would reject it — so without the blank branch a user could be given a
70
+ * password of pure whitespace that they can neither see nor reliably retype.
71
+ * If the blank check were ever reordered below the length check, or dropped
72
+ * because "the length check covers it", this is the case that would start
73
+ * passing silently, and the shorter whitespace strings above would not catch
74
+ * it because they fail the length rule anyway.
75
+ */
76
+ test("a whitespace-only value long enough to pass the length rule is still rejected", () => {
77
+ const sixSpaces: string = " ".repeat(MINIMUM_PASSWORD_LENGTH);
78
+
79
+ expect(sixSpaces.length).toBeGreaterThanOrEqual(MINIMUM_PASSWORD_LENGTH);
80
+ expect(getPasswordValidationError(sixSpaces)).toBe(
81
+ "Password cannot be blank.",
82
+ );
83
+ });
84
+
85
+ /*
86
+ * Precedence in the other direction: blankness is reported before length even
87
+ * when the value is also too long. "Password cannot be more than 100
88
+ * characters." would be a baffling thing to show someone who pressed the
89
+ * space bar.
90
+ */
91
+ test("blankness is reported ahead of the maximum-length rule", () => {
92
+ const overlongWhitespace: string = " ".repeat(MAXIMUM_PASSWORD_LENGTH + 1);
93
+
94
+ expect(getPasswordValidationError(overlongWhitespace)).toBe(
95
+ "Password cannot be blank.",
96
+ );
97
+ });
98
+ });
99
+
100
+ describe("getPasswordValidationError enforces the length bounds", () => {
101
+ /*
102
+ * The four values either side of the two boundaries. Off-by-one here is the
103
+ * classic way a policy quietly becomes a different policy: a `<=` where a `<`
104
+ * belongs locks out everyone whose existing password is exactly at the
105
+ * minimum, and nothing else in the system would notice.
106
+ */
107
+ test("one character below the minimum is rejected", () => {
108
+ const tooShort: string = "a".repeat(MINIMUM_PASSWORD_LENGTH - 1);
109
+
110
+ expect(getPasswordValidationError(tooShort)).toBe(
111
+ `Password cannot be less than ${MINIMUM_PASSWORD_LENGTH} characters.`,
112
+ );
113
+ });
114
+
115
+ test("exactly the minimum is accepted", () => {
116
+ const atMinimum: string = "a".repeat(MINIMUM_PASSWORD_LENGTH);
117
+
118
+ expect(getPasswordValidationError(atMinimum)).toBeNull();
119
+ });
120
+
121
+ test("exactly the maximum is accepted", () => {
122
+ const atMaximum: string = "a".repeat(MAXIMUM_PASSWORD_LENGTH);
123
+
124
+ expect(getPasswordValidationError(atMaximum)).toBeNull();
125
+ });
126
+
127
+ test("one character above the maximum is rejected", () => {
128
+ const tooLong: string = "a".repeat(MAXIMUM_PASSWORD_LENGTH + 1);
129
+
130
+ expect(getPasswordValidationError(tooLong)).toBe(
131
+ `Password cannot be more than ${MAXIMUM_PASSWORD_LENGTH} characters.`,
132
+ );
133
+ });
134
+
135
+ /*
136
+ * Length is counted on the RAW value, never a trimmed one.
137
+ *
138
+ * This looks like a bug the first time you read it — " ab " is only two
139
+ * real characters — and the temptation is to trim before measuring. Doing so
140
+ * would be worse than lax: whatever the user typed is what gets hashed, so
141
+ * trimming for the length check while hashing the untrimmed value measures
142
+ * one string and stores another, and trimming for BOTH stores a secret the
143
+ * user never typed and then fails to match it when they type it again at
144
+ * login. Leading and trailing spaces in a password are the user's business.
145
+ */
146
+ test("length is measured before trimming, so padded short passwords are accepted", () => {
147
+ const padded: string = " abcd ";
148
+
149
+ expect(padded.length).toBe(8);
150
+ expect(padded.trim().length).toBe(4);
151
+ expect(padded.trim().length).toBeLessThan(MINIMUM_PASSWORD_LENGTH);
152
+
153
+ expect(getPasswordValidationError(padded)).toBeNull();
154
+ });
155
+
156
+ /*
157
+ * The mirror of the above: padding does not rescue a value that is genuinely
158
+ * too short in raw characters either. Nothing is trimmed, in either
159
+ * direction.
160
+ */
161
+ test("a padded value that is still short raw is rejected on length", () => {
162
+ const shortEvenPadded: string = " ab ";
163
+
164
+ expect(shortEvenPadded.length).toBeLessThan(MINIMUM_PASSWORD_LENGTH);
165
+ expect(getPasswordValidationError(shortEvenPadded)).toBe(
166
+ `Password cannot be less than ${MINIMUM_PASSWORD_LENGTH} characters.`,
167
+ );
168
+ });
169
+ });
170
+
171
+ describe("the rejection messages tell the operator what to do", () => {
172
+ /*
173
+ * These messages are surfaced verbatim — the browser form shows them and the
174
+ * server wraps them in the exception it returns. A message that says only
175
+ * "Password is invalid." forces the operator to guess, and guessing at
176
+ * somebody else's password policy is exactly the loop this feature exists to
177
+ * remove. So each length message must actually contain its own limit, and
178
+ * must not quote the other one.
179
+ */
180
+ test("the too-short message names the minimum and not the maximum", () => {
181
+ const message: string | null = getPasswordValidationError(
182
+ "a".repeat(MINIMUM_PASSWORD_LENGTH - 1),
183
+ );
184
+
185
+ expect(message).toContain(String(MINIMUM_PASSWORD_LENGTH));
186
+ expect(message).not.toContain(String(MAXIMUM_PASSWORD_LENGTH));
187
+ });
188
+
189
+ test("the too-long message names the maximum", () => {
190
+ const message: string | null = getPasswordValidationError(
191
+ "a".repeat(MAXIMUM_PASSWORD_LENGTH + 1),
192
+ );
193
+
194
+ expect(message).toContain(String(MAXIMUM_PASSWORD_LENGTH));
195
+ });
196
+
197
+ /*
198
+ * Every rejection is a complete sentence a human can read, not a code. Cheap
199
+ * to assert, and it catches the refactor that starts returning an enum or a
200
+ * key while leaving the `string | null` signature in place.
201
+ */
202
+ test("every rejection is a non-empty sentence", () => {
203
+ const rejected: Array<string | undefined | null> = [
204
+ undefined,
205
+ null,
206
+ "",
207
+ " ",
208
+ "a".repeat(MINIMUM_PASSWORD_LENGTH - 1),
209
+ "a".repeat(MAXIMUM_PASSWORD_LENGTH + 1),
210
+ ];
211
+
212
+ rejected.forEach((value: string | undefined | null): void => {
213
+ const message: string | null = getPasswordValidationError(value);
214
+
215
+ expect(typeof message).toBe("string");
216
+ expect((message as string).length).toBeGreaterThan(0);
217
+ expect(message as string).toMatch(/\.$/);
218
+ });
219
+ });
220
+ });
221
+
222
+ describe("getPasswordValidationError accepts real passwords", () => {
223
+ test("an ordinary passphrase returns null", () => {
224
+ expect(getPasswordValidationError("correct-horse-battery")).toBeNull();
225
+ });
226
+
227
+ /*
228
+ * null specifically, not undefined or false — callers branch on it (`const
229
+ * error = getPasswordValidationError(p); if (error) { throw ... }`), and the
230
+ * server-side guard turns a non-null return straight into a BadDataException.
231
+ */
232
+ test("acceptance is signalled with null, not undefined", () => {
233
+ const result: string | null = getPasswordValidationError(
234
+ "a-perfectly-fine-password",
235
+ );
236
+
237
+ expect(result).toBeNull();
238
+ expect(result).not.toBeUndefined();
239
+ });
240
+
241
+ test("passwords full of punctuation and symbols are not second-guessed", () => {
242
+ const accepted: Array<string> = [
243
+ "!@#$%^&*()",
244
+ "p@ssw0rd!",
245
+ " spaces inside are fine ",
246
+ "Ünïcödé-påsswörd",
247
+ ];
248
+
249
+ accepted.forEach((value: string): void => {
250
+ expect(getPasswordValidationError(value)).toBeNull();
251
+ });
252
+ });
253
+
254
+ /*
255
+ * Pinning, not endorsing: `.length` is UTF-16 code units, so a password of
256
+ * three astral-plane emoji counts as six and clears the minimum. That is the
257
+ * behaviour every other OneUptime password form has always had (they all used
258
+ * the browser's own minLength, which counts the same way), so this module
259
+ * matching it is the point. Recorded here so that if someone ever switches to
260
+ * grapheme counting it is a deliberate, visible change rather than a silent
261
+ * one.
262
+ */
263
+ test("length counts UTF-16 code units, matching every other password form", () => {
264
+ const threeEmoji: string = "🔐🔐🔐";
265
+
266
+ expect(threeEmoji.length).toBe(MINIMUM_PASSWORD_LENGTH);
267
+ expect(getPasswordValidationError(threeEmoji)).toBeNull();
268
+ });
269
+ });
270
+
271
+ describe("the exported constants are usable as a policy", () => {
272
+ test("both bounds are plain numbers", () => {
273
+ expect(typeof MINIMUM_PASSWORD_LENGTH).toBe("number");
274
+ expect(typeof MAXIMUM_PASSWORD_LENGTH).toBe("number");
275
+
276
+ expect(Number.isInteger(MINIMUM_PASSWORD_LENGTH)).toBe(true);
277
+ expect(Number.isInteger(MAXIMUM_PASSWORD_LENGTH)).toBe(true);
278
+ });
279
+
280
+ /*
281
+ * A minimum at or above the maximum would make every password unacceptable
282
+ * while each individual branch still looked correct in review — the kind of
283
+ * mistake that only shows up when nobody can set a password any more.
284
+ */
285
+ test("the minimum is positive and strictly below the maximum", () => {
286
+ expect(MINIMUM_PASSWORD_LENGTH).toBeGreaterThan(0);
287
+ expect(MINIMUM_PASSWORD_LENGTH).toBeLessThan(MAXIMUM_PASSWORD_LENGTH);
288
+ });
289
+ });
290
+
291
+ /*
292
+ * The whole reason this module sits in Common/Types rather than Common/Server:
293
+ * the browser can import it, so the form and the endpoint cannot disagree.
294
+ *
295
+ * That only holds while the form actually imports it. A form that hardcodes
296
+ * `minLength: 6` again keeps working today and starts lying the moment the
297
+ * policy is retuned here — the browser would go on accepting six characters
298
+ * that the server now rejects, and the operator would see a request fail with
299
+ * no field marked. Nothing in a type-check or a render test catches that, so it
300
+ * is asserted against the form's source text.
301
+ */
302
+ describe("the Admin Dashboard set-password form shares this policy", () => {
303
+ const SET_PASSWORD_FORM: string = path.join(
304
+ __dirname,
305
+ "..",
306
+ "..",
307
+ "..",
308
+ "App",
309
+ "FeatureSet",
310
+ "AdminDashboard",
311
+ "src",
312
+ "Pages",
313
+ "Users",
314
+ "View",
315
+ "Authentication.tsx",
316
+ );
317
+
318
+ /*
319
+ * Comments in that file discuss the policy in prose (and the module under
320
+ * test is itself heavily commented), so a bare `toContain` on the raw text
321
+ * could be satisfied by a comment while the code hardcoded a number. Strip
322
+ * comments first and assert only on code. Line comments are only stripped at
323
+ * a line start or after whitespace, so a "//" inside a URL literal survives.
324
+ */
325
+ const readFormCode: () => string = (): string => {
326
+ const raw: string = fs.readFileSync(SET_PASSWORD_FORM, "utf8");
327
+
328
+ return raw
329
+ .replace(/\/\*[\s\S]*?\*\//g, "")
330
+ .replace(/(^|\s)\/\/.*$/gm, "$1");
331
+ };
332
+
333
+ test("the form imports the minimum from this module", () => {
334
+ const code: string = readFormCode();
335
+
336
+ expect(code).toMatch(
337
+ /import\s*\{[^}]*MINIMUM_PASSWORD_LENGTH[^}]*\}\s*from\s*["']Common\/Types\/Password["']/,
338
+ );
339
+ });
340
+
341
+ test("both password fields validate against the imported constant", () => {
342
+ const code: string = readFormCode();
343
+
344
+ const usages: number = (
345
+ code.match(/minLength\s*:\s*MINIMUM_PASSWORD_LENGTH/g) || []
346
+ ).length;
347
+
348
+ // The new password field and the confirmation field.
349
+ expect(usages).toBe(2);
350
+ });
351
+
352
+ /*
353
+ * The assertion that actually bites: no numeric literal may appear as a
354
+ * minLength anywhere in the form, whatever number it is. Written against the
355
+ * digit rather than against "6" so that re-hardcoding the current value under
356
+ * a future, different policy still fails.
357
+ */
358
+ test("no minLength in the form is a hardcoded number", () => {
359
+ const code: string = readFormCode();
360
+
361
+ expect(code).not.toMatch(/minLength\s*:\s*\d/);
362
+ });
363
+ });
@@ -3,6 +3,13 @@ import ConfirmModal, {
3
3
  ComponentProps,
4
4
  } from "../../../UI/Components/Modal/ConfirmModal";
5
5
  import { describe, expect, it, jest, beforeEach } from "@jest/globals";
6
+ /*
7
+ * The main entry, not "/extend-expect": the latter no longer ships type
8
+ * declarations. Without this import the jest-dom matchers below have no types
9
+ * of their own, and the suite only compiled when it happened to share a
10
+ * program with a file that did import them.
11
+ */
12
+ import "@testing-library/jest-dom";
6
13
  import { fireEvent, render, screen } from "@testing-library/react";
7
14
  import React from "react";
8
15
 
@@ -222,6 +222,18 @@ describe("ErrorBoundary", () => {
222
222
  expect(screen.queryByRole("alert")).not.toBeNull();
223
223
  });
224
224
 
225
+ test("leads with a plain-language heading", () => {
226
+ render(
227
+ <ErrorBoundary>
228
+ <ThrowOnRender />
229
+ </ErrorBoundary>,
230
+ );
231
+
232
+ expect(
233
+ screen.queryByRole("heading", { name: "Something went wrong" }),
234
+ ).not.toBeNull();
235
+ });
236
+
225
237
  test("shows the underlying error message for support", () => {
226
238
  render(
227
239
  <ErrorBoundary>
@@ -234,6 +246,26 @@ describe("ErrorBoundary", () => {
234
246
  ).toContain("reading 'queryConfigs'");
235
247
  });
236
248
 
249
+ /*
250
+ * The stack-shaped message is for support, not for the person who just hit
251
+ * the error. It stays in the DOM (so it can be read and copied) but behind
252
+ * a closed disclosure so the recovery actions lead.
253
+ */
254
+ test("keeps the raw error message behind a closed disclosure", () => {
255
+ render(
256
+ <ErrorBoundary>
257
+ <ThrowOnRender message="Cannot read properties of undefined (reading 'queryConfigs')" />
258
+ </ErrorBoundary>,
259
+ );
260
+
261
+ const disclosure: HTMLDetailsElement | null = screen
262
+ .getByTestId("error-boundary-error-message")
263
+ .closest("details");
264
+
265
+ expect(disclosure).not.toBeNull();
266
+ expect(disclosure!.open).toBe(false);
267
+ });
268
+
237
269
  test("offers a support bundle and explains what it contains", () => {
238
270
  render(
239
271
  <ErrorBoundary>
@@ -247,6 +279,27 @@ describe("ErrorBoundary", () => {
247
279
  expect(screen.queryByText(/scrubbed page URL/)).not.toBeNull();
248
280
  expect(screen.queryByText(/never reads cookies/)).not.toBeNull();
249
281
  });
282
+
283
+ /*
284
+ * A downloaded bundle is only useful if the user knows where to send it,
285
+ * so the address is on the fallback rather than a page they have to find.
286
+ */
287
+ test("says where to send the support bundle", () => {
288
+ render(
289
+ <ErrorBoundary>
290
+ <ThrowOnRender />
291
+ </ErrorBoundary>,
292
+ );
293
+
294
+ const supportEmailLink: HTMLAnchorElement = screen.getByTestId(
295
+ "error-boundary-support-email",
296
+ ) as HTMLAnchorElement;
297
+
298
+ expect(supportEmailLink.textContent).toBe("support@oneuptime.com");
299
+ expect(supportEmailLink.getAttribute("href")).toBe(
300
+ "mailto:support@oneuptime.com",
301
+ );
302
+ });
250
303
  });
251
304
 
252
305
  describe("reporting", () => {