@oneuptime/common 12.0.3 → 12.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (516) hide show
  1. package/Models/DatabaseModels/AIRun.ts +38 -1
  2. package/Models/DatabaseModels/AlertFeed.ts +30 -0
  3. package/Models/DatabaseModels/CephCluster.ts +14 -0
  4. package/Models/DatabaseModels/CodeRepository.ts +14 -6
  5. package/Models/DatabaseModels/Dashboard.ts +28 -0
  6. package/Models/DatabaseModels/DockerHost.ts +14 -0
  7. package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
  8. package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
  9. package/Models/DatabaseModels/Incident.ts +2 -0
  10. package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
  11. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
  12. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  13. package/Models/DatabaseModels/IncidentFeed.ts +30 -0
  14. package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
  15. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  16. package/Models/DatabaseModels/IoTFleet.ts +14 -0
  17. package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
  18. package/Models/DatabaseModels/LogSavedView.ts +33 -0
  19. package/Models/DatabaseModels/NetworkDevice.ts +14 -0
  20. package/Models/DatabaseModels/NetworkInterface.ts +18 -0
  21. package/Models/DatabaseModels/Project.ts +247 -11
  22. package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
  23. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
  24. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
  25. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  26. package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.ts +4 -4
  27. package/Models/DatabaseModels/Service.ts +30 -0
  28. package/Models/DatabaseModels/StatusPage.ts +28 -0
  29. package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
  30. package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
  31. package/Models/DatabaseModels/User.ts +30 -0
  32. package/Server/API/AIAgentDataAPI.ts +75 -55
  33. package/Server/API/AIInvestigationAPI.ts +175 -17
  34. package/Server/API/DashboardAPI.ts +429 -22
  35. package/Server/API/GitHubAPI.ts +119 -283
  36. package/Server/API/StatusPageAPI.ts +38 -19
  37. package/Server/API/UserAPI.ts +263 -1
  38. package/Server/EnvironmentConfig.ts +23 -3
  39. package/Server/Infrastructure/ClickhouseConfig.ts +15 -1
  40. package/Server/Infrastructure/ClickhouseDatabase.ts +1 -1
  41. package/Server/Infrastructure/GlobalCache.ts +37 -0
  42. package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
  43. package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
  44. package/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.ts +27 -0
  45. package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
  46. package/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.ts +89 -0
  47. package/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.ts +27 -0
  48. package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
  49. package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
  50. package/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.ts +40 -0
  51. package/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.ts +26 -0
  52. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
  53. package/Server/Services/AIRunService.ts +78 -10
  54. package/Server/Services/AIService.ts +94 -22
  55. package/Server/Services/AlertFeedService.ts +5 -0
  56. package/Server/Services/AnalyticsDatabaseService.ts +101 -1
  57. package/Server/Services/CodeRepositoryService.ts +105 -2
  58. package/Server/Services/DatabaseService.ts +227 -3
  59. package/Server/Services/IncidentFeedService.ts +5 -0
  60. package/Server/Services/LlmLogService.ts +46 -2
  61. package/Server/Services/OpenTelemetryIngestService.ts +230 -26
  62. package/Server/Services/UserService.ts +184 -0
  63. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +6 -0
  64. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +4 -0
  65. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +8 -5
  66. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +90 -16
  67. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +2 -0
  68. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +2 -0
  69. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +223 -22
  70. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +25 -24
  71. package/Server/Utils/AI/SRE/ConfidenceSignal.ts +182 -71
  72. package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +252 -43
  73. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +25 -24
  74. package/Server/Utils/AI/SRE/InstrumentationTaskTrigger.ts +47 -13
  75. package/Server/Utils/AI/SRE/InvestigationGrader.ts +9 -5
  76. package/Server/Utils/AI/SRE/InvestigationQueue.ts +141 -24
  77. package/Server/Utils/AI/SRE/InvestigationSubjectLock.ts +66 -0
  78. package/Server/Utils/AI/SRE/PostedRootCause.ts +94 -3
  79. package/Server/Utils/AI/SRE/README.md +1 -1
  80. package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +132 -10
  81. package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
  82. package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +24 -0
  83. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
  84. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
  85. package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
  86. package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +29 -0
  87. package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +56 -0
  88. package/Server/Utils/Monitor/MonitorMetricUtil.ts +46 -0
  89. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -0
  90. package/Server/Utils/PasswordHash.ts +330 -0
  91. package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +251 -0
  92. package/Tests/App/Dashboard/EventStatusPanel.test.tsx +747 -0
  93. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +480 -0
  94. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1184 -0
  95. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +500 -0
  96. package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
  97. package/Tests/App/Dashboard/PortMonitorCriteriaFilter.test.ts +93 -0
  98. package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
  99. package/Tests/App/StatusPage/PublicStatusPageAPIErrorHandling.test.ts +117 -0
  100. package/Tests/App/StatusPage/StatusPageModelAPIPolymorphism.test.ts +223 -0
  101. package/Tests/Models/DatabaseModels/PermissionCatalogueCoverage.test.ts +243 -0
  102. package/Tests/Server/API/AIAgentDataFixFromIncidentContext.test.ts +266 -0
  103. package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
  104. package/Tests/Server/API/AIInvestigationAPI.test.ts +594 -0
  105. package/Tests/Server/API/AIInvestigationCreateFixTask.test.ts +80 -0
  106. package/Tests/Server/API/DashboardMasterPasswordAPI.test.ts +172 -1
  107. package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
  108. package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
  109. package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
  110. package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
  111. package/Tests/Server/API/Helpers.ts +6 -1
  112. package/Tests/Server/API/StatusPageMasterPasswordAPI.test.ts +335 -0
  113. package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
  114. package/Tests/Server/Infrastructure/GlobalCache.test.ts +128 -0
  115. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
  116. package/Tests/Server/Services/AIRunCodeFixClaim.test.ts +59 -5
  117. package/Tests/Server/Services/AIRunHumanVerdict.test.ts +127 -58
  118. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +396 -20
  119. package/Tests/Server/Services/AddMasterPasswordSaltMigration.test.ts +283 -0
  120. package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
  121. package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
  122. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +30 -1
  123. package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
  124. package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
  125. package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +880 -0
  126. package/Tests/Server/Services/DatabaseServiceUpdateColumnsWithoutHooks.test.ts +54 -0
  127. package/Tests/Server/Services/FeedAIRunAssociation.test.ts +125 -0
  128. package/Tests/Server/Services/FixFromIncidentTaskTrigger.test.ts +418 -57
  129. package/Tests/Server/Services/InstrumentationTaskTrigger.test.ts +173 -14
  130. package/Tests/Server/Services/MasterPasswordScrypt.test.ts +567 -0
  131. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
  132. package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
  133. package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
  134. package/Tests/Server/Services/SeparateIncidentAlertAiSettingsMigration.test.ts +194 -0
  135. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
  136. package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
  137. package/Tests/Server/Utils/AI/AIAlertGating.test.ts +37 -4
  138. package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +330 -29
  139. package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +25 -0
  140. package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +293 -5
  141. package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +59 -1
  142. package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
  143. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
  144. package/Tests/Server/Utils/AI/FixFromIncidentAutoTrigger.test.ts +342 -19
  145. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +214 -30
  146. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +2 -0
  147. package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +65 -17
  148. package/Tests/Server/Utils/AI/InvestigationQueueRemediationExecution.test.ts +3 -0
  149. package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +313 -20
  150. package/Tests/Server/Utils/AI/PostedRootCause.test.ts +254 -0
  151. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +4 -0
  152. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +2 -0
  153. package/Tests/Server/Utils/AI/SubjectCodeFixRunDedupe.test.ts +44 -0
  154. package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +30 -0
  155. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
  156. package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
  157. package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
  158. package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
  159. package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +212 -0
  160. package/Tests/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.test.ts +401 -0
  161. package/Tests/Server/Utils/Monitor/MonitorMetricUtilPortTimings.test.ts +243 -0
  162. package/Tests/Server/Utils/PasswordHash.test.ts +585 -0
  163. package/Tests/Types/Dashboard/DashboardTemplates.test.ts +111 -0
  164. package/Tests/Types/Database/ColumnLength.test.ts +7 -1
  165. package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
  166. package/Tests/Types/Monitor/CephMetricCatalog.test.ts +104 -0
  167. package/Tests/Types/Monitor/CriteriaFilter.test.ts +4 -0
  168. package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
  169. package/Tests/Types/Monitor/DockerMetricCatalog.test.ts +109 -0
  170. package/Tests/Types/Monitor/DockerSwarmMetricCatalog.test.ts +118 -0
  171. package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
  172. package/Tests/Types/Monitor/HostMetricCatalog.test.ts +104 -0
  173. package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
  174. package/Tests/Types/Monitor/IotMetricCatalog.test.ts +104 -0
  175. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +118 -0
  176. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +42 -0
  177. package/Tests/Types/Monitor/MonitorStep.test.ts +38 -0
  178. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +75 -0
  179. package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
  180. package/Tests/Types/Monitor/PodmanMetricCatalog.test.ts +109 -0
  181. package/Tests/Types/Monitor/ProxmoxMetricCatalog.test.ts +107 -0
  182. package/Tests/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.test.ts +151 -0
  183. package/Tests/Types/Permission.test.ts +137 -0
  184. package/Tests/Types/Rum/SessionReplayMaskingMode.test.ts +101 -0
  185. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +5 -5
  186. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +3 -5
  187. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
  188. package/Tests/UI/Components/ErrorBoundary.test.tsx +158 -0
  189. package/Tests/UI/Components/FeedItemSafeMode.test.tsx +74 -0
  190. package/Tests/UI/Components/MoreMenu.test.tsx +756 -0
  191. package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +62 -0
  192. package/Tests/UI/Monitor/PortMonitorView.test.tsx +237 -0
  193. package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +91 -0
  194. package/Tests/UI/Utils/DownloadFile.test.ts +103 -0
  195. package/Tests/UI/Utils/ErrorSupportBundle.test.ts +582 -0
  196. package/Tests/UI/Utils/Project.test.ts +31 -0
  197. package/Tests/UI/Utils/StatusPageModelAPIInjection.test.ts +245 -0
  198. package/Tests/UI/Utils/UseDashboardGridDnd.test.tsx +820 -0
  199. package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
  200. package/Tests/Utils/Dashboard/DashboardViewConfig.test.ts +329 -0
  201. package/Tests/Utils/Dashboard/GridLayout.test.ts +951 -0
  202. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +117 -0
  203. package/Tests/Utils/RecordingRuleExpression.test.ts +327 -0
  204. package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +186 -0
  205. package/Tests/Utils/StatusPage/GroupTree.test.ts +456 -0
  206. package/Tests/Utils/StatusPage/OverviewGroupHierarchyVisibility.test.ts +588 -0
  207. package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
  208. package/Tests/Utils/Telemetry/SavedViewTimeRange.test.ts +244 -0
  209. package/Tests/Utils/Telemetry/TelemetryQueryTimeRange.test.ts +825 -0
  210. package/Types/AI/AIRunCodeFixRecommendation.ts +17 -0
  211. package/Types/AI/CodeFixTaskContext.ts +63 -3
  212. package/Types/AI/CodeFixTaskType.ts +3 -3
  213. package/Types/Database/ColumnLength.ts +9 -1
  214. package/Types/Database/TableColumn.ts +11 -0
  215. package/Types/Database/UnsynchronizedIndex.ts +48 -0
  216. package/Types/HashedString.ts +140 -4
  217. package/Types/Monitor/CriteriaFilter.ts +4 -0
  218. package/Types/Monitor/DomainMonitor/DomainLookupMethod.ts +23 -0
  219. package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +7 -0
  220. package/Types/Monitor/MonitorCriteriaInstance.ts +24 -7
  221. package/Types/Monitor/MonitorMetricType.ts +8 -0
  222. package/Types/Monitor/MonitorStep.ts +10 -1
  223. package/Types/Monitor/MonitorStepDomainMonitor.ts +21 -0
  224. package/Types/Monitor/PortMonitor/PortMonitorTimings.ts +10 -0
  225. package/Types/Permission.ts +23 -175
  226. package/Types/Probe/ProbeMonitorResponse.ts +6 -0
  227. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +2 -3
  228. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
  229. package/UI/Components/ErrorBoundary.tsx +138 -11
  230. package/UI/Components/Feed/FeedItem.tsx +9 -2
  231. package/UI/Components/ModelDetail/CardModelDetail.tsx +0 -12
  232. package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
  233. package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +12 -2
  234. package/UI/Components/MoreMenu/MoreMenu.tsx +285 -97
  235. package/UI/Components/MoreMenu/MoreMenuItem.tsx +8 -5
  236. package/UI/Components/Navbar/NavBar.tsx +12 -2
  237. package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
  238. package/UI/Utils/DownloadFile.ts +16 -8
  239. package/UI/Utils/ErrorSupportBundle.ts +805 -0
  240. package/UI/Utils/ModelAPI/ModelAPI.ts +19 -8
  241. package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
  242. package/UI/Utils/Project.ts +4 -0
  243. package/UI/Utils/StatusPage.ts +8 -2
  244. package/UI/Utils/UseDashboardGridDnd.ts +917 -0
  245. package/Utils/Dashboard/DashboardViewConfig.ts +80 -36
  246. package/Utils/Dashboard/GridLayout.ts +523 -0
  247. package/Utils/Metrics/RecordingRuleExpression.ts +19 -17
  248. package/Utils/Monitor/MonitorMetricType.ts +44 -2
  249. package/Utils/StatusPage/GroupNestingLayout.ts +69 -0
  250. package/Utils/StatusPage/GroupTree.ts +157 -66
  251. package/Utils/StatusPage/ResourceUptime.ts +37 -2
  252. package/Utils/TeamMembersByProject.ts +237 -0
  253. package/Utils/Telemetry/SavedViewTimeRange.ts +114 -0
  254. package/Utils/Telemetry/TelemetryQueryTimeRange.ts +334 -0
  255. package/build/dist/Models/DatabaseModels/AIRun.js +39 -1
  256. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  257. package/build/dist/Models/DatabaseModels/AlertFeed.js +32 -0
  258. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  259. package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
  260. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  261. package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
  262. package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
  263. package/build/dist/Models/DatabaseModels/Dashboard.js +29 -0
  264. package/build/dist/Models/DatabaseModels/Dashboard.js.map +1 -1
  265. package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
  266. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  267. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
  268. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
  269. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
  270. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
  271. package/build/dist/Models/DatabaseModels/Incident.js +2 -0
  272. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  273. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
  274. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  275. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
  276. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  277. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  278. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  279. package/build/dist/Models/DatabaseModels/IncidentFeed.js +32 -0
  280. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  281. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
  282. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  283. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  284. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  285. package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
  286. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
  287. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
  288. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  289. package/build/dist/Models/DatabaseModels/LogSavedView.js +33 -0
  290. package/build/dist/Models/DatabaseModels/LogSavedView.js.map +1 -1
  291. package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
  292. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  293. package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
  294. package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
  295. package/build/dist/Models/DatabaseModels/Project.js +257 -13
  296. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  297. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
  298. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  299. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
  300. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  301. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
  302. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  303. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  304. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  305. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js +4 -4
  306. package/build/dist/Models/DatabaseModels/Service.js +28 -1
  307. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  308. package/build/dist/Models/DatabaseModels/StatusPage.js +29 -0
  309. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  310. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
  311. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  312. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
  313. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
  314. package/build/dist/Models/DatabaseModels/User.js +32 -0
  315. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  316. package/build/dist/Server/API/AIAgentDataAPI.js +49 -48
  317. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  318. package/build/dist/Server/API/AIInvestigationAPI.js +111 -11
  319. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  320. package/build/dist/Server/API/DashboardAPI.js +312 -15
  321. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  322. package/build/dist/Server/API/GitHubAPI.js +94 -167
  323. package/build/dist/Server/API/GitHubAPI.js.map +1 -1
  324. package/build/dist/Server/API/StatusPageAPI.js +29 -11
  325. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  326. package/build/dist/Server/API/UserAPI.js +187 -2
  327. package/build/dist/Server/API/UserAPI.js.map +1 -1
  328. package/build/dist/Server/EnvironmentConfig.js +21 -3
  329. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  330. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +12 -1
  331. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  332. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +1 -1
  333. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
  334. package/build/dist/Server/Infrastructure/GlobalCache.js +29 -0
  335. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  336. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
  337. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
  338. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
  339. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
  340. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js +22 -0
  341. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js.map +1 -0
  342. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
  343. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
  344. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js +36 -0
  345. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js.map +1 -0
  346. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js +18 -0
  347. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js.map +1 -0
  348. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
  349. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
  350. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
  351. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
  352. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js +19 -0
  353. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js.map +1 -0
  354. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js +19 -0
  355. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js.map +1 -0
  356. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
  357. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  358. package/build/dist/Server/Services/AIRunService.js +58 -12
  359. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  360. package/build/dist/Server/Services/AIService.js +74 -21
  361. package/build/dist/Server/Services/AIService.js.map +1 -1
  362. package/build/dist/Server/Services/AlertFeedService.js +3 -0
  363. package/build/dist/Server/Services/AlertFeedService.js.map +1 -1
  364. package/build/dist/Server/Services/AnalyticsDatabaseService.js +86 -2
  365. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  366. package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
  367. package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
  368. package/build/dist/Server/Services/DatabaseService.js +180 -3
  369. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  370. package/build/dist/Server/Services/IncidentFeedService.js +3 -0
  371. package/build/dist/Server/Services/IncidentFeedService.js.map +1 -1
  372. package/build/dist/Server/Services/LlmLogService.js +22 -1
  373. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  374. package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
  375. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  376. package/build/dist/Server/Services/UserService.js +165 -0
  377. package/build/dist/Server/Services/UserService.js.map +1 -1
  378. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +3 -0
  379. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  380. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +4 -0
  381. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
  382. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +8 -5
  383. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
  384. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +63 -24
  385. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  386. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +2 -0
  387. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  388. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +2 -0
  389. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  390. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +156 -28
  391. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  392. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +21 -19
  393. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  394. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +128 -49
  395. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -1
  396. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +170 -31
  397. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -1
  398. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +21 -19
  399. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  400. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js +33 -9
  401. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js.map +1 -1
  402. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +9 -5
  403. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
  404. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +121 -41
  405. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
  406. package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js +46 -0
  407. package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js.map +1 -0
  408. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js +61 -14
  409. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js.map +1 -1
  410. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +79 -1
  411. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
  412. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
  413. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
  414. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js +21 -0
  415. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js.map +1 -1
  416. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
  417. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  418. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
  419. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  420. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
  421. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
  422. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +20 -3
  423. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
  424. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +43 -0
  425. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
  426. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +33 -0
  427. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  428. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -0
  429. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  430. package/build/dist/Server/Utils/PasswordHash.js +258 -0
  431. package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
  432. package/build/dist/Types/AI/AIRunCodeFixRecommendation.js +18 -0
  433. package/build/dist/Types/AI/AIRunCodeFixRecommendation.js.map +1 -0
  434. package/build/dist/Types/AI/CodeFixTaskContext.js +23 -3
  435. package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -1
  436. package/build/dist/Types/AI/CodeFixTaskType.js +3 -3
  437. package/build/dist/Types/Database/ColumnLength.js +9 -1
  438. package/build/dist/Types/Database/ColumnLength.js.map +1 -1
  439. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  440. package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
  441. package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
  442. package/build/dist/Types/HashedString.js +96 -5
  443. package/build/dist/Types/HashedString.js.map +1 -1
  444. package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
  445. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  446. package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js +24 -0
  447. package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js.map +1 -0
  448. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +24 -7
  449. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  450. package/build/dist/Types/Monitor/MonitorMetricType.js +7 -0
  451. package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
  452. package/build/dist/Types/Monitor/MonitorStep.js +6 -1
  453. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  454. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js +15 -0
  455. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js.map +1 -1
  456. package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js +2 -0
  457. package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js.map +1 -0
  458. package/build/dist/Types/Permission.js +23 -155
  459. package/build/dist/Types/Permission.js.map +1 -1
  460. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +2 -5
  461. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  462. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
  463. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  464. package/build/dist/UI/Components/ErrorBoundary.js +67 -8
  465. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  466. package/build/dist/UI/Components/Feed/FeedItem.js +2 -2
  467. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  468. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +0 -12
  469. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  470. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
  471. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  472. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +12 -2
  473. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
  474. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +193 -65
  475. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  476. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +1 -1
  477. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  478. package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
  479. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  480. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
  481. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  482. package/build/dist/UI/Utils/DownloadFile.js +15 -8
  483. package/build/dist/UI/Utils/DownloadFile.js.map +1 -1
  484. package/build/dist/UI/Utils/ErrorSupportBundle.js +476 -0
  485. package/build/dist/UI/Utils/ErrorSupportBundle.js.map +1 -0
  486. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -8
  487. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  488. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
  489. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
  490. package/build/dist/UI/Utils/Project.js +3 -0
  491. package/build/dist/UI/Utils/Project.js.map +1 -1
  492. package/build/dist/UI/Utils/StatusPage.js +4 -4
  493. package/build/dist/UI/Utils/StatusPage.js.map +1 -1
  494. package/build/dist/UI/Utils/UseDashboardGridDnd.js +551 -0
  495. package/build/dist/UI/Utils/UseDashboardGridDnd.js.map +1 -0
  496. package/build/dist/Utils/Dashboard/DashboardViewConfig.js +42 -26
  497. package/build/dist/Utils/Dashboard/DashboardViewConfig.js.map +1 -1
  498. package/build/dist/Utils/Dashboard/GridLayout.js +338 -0
  499. package/build/dist/Utils/Dashboard/GridLayout.js.map +1 -0
  500. package/build/dist/Utils/Metrics/RecordingRuleExpression.js +19 -17
  501. package/build/dist/Utils/Metrics/RecordingRuleExpression.js.map +1 -1
  502. package/build/dist/Utils/Monitor/MonitorMetricType.js +38 -6
  503. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  504. package/build/dist/Utils/StatusPage/GroupNestingLayout.js +46 -0
  505. package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -1
  506. package/build/dist/Utils/StatusPage/GroupTree.js +79 -34
  507. package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -1
  508. package/build/dist/Utils/StatusPage/ResourceUptime.js +22 -1
  509. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  510. package/build/dist/Utils/TeamMembersByProject.js +145 -0
  511. package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
  512. package/build/dist/Utils/Telemetry/SavedViewTimeRange.js +79 -0
  513. package/build/dist/Utils/Telemetry/SavedViewTimeRange.js.map +1 -0
  514. package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js +236 -0
  515. package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js.map +1 -0
  516. package/package.json +1 -1
@@ -0,0 +1,1184 @@
1
+ import {
2
+ afterEach,
3
+ beforeEach,
4
+ describe,
5
+ expect,
6
+ jest,
7
+ test,
8
+ } from "@jest/globals";
9
+ import "@testing-library/jest-dom";
10
+ import {
11
+ act,
12
+ cleanup,
13
+ fireEvent,
14
+ render,
15
+ screen,
16
+ } from "@testing-library/react";
17
+ import * as React from "react";
18
+ import getJestMockFunction, { MockFunction } from "../../MockType";
19
+
20
+ /*
21
+ * InvestigationPanel joins three independently changing pieces of state: the
22
+ * live AIRun, the report posted just after completion, and the persisted
23
+ * recommendation that authorizes a code-fix action. These tests drive real
24
+ * React effects and timers so completion, navigation, and request races stay
25
+ * covered rather than testing only static snapshots.
26
+ */
27
+
28
+ const postMock: MockFunction = getJestMockFunction();
29
+ const getFriendlyMessageMock: MockFunction = getJestMockFunction();
30
+ const getCommonHeadersMock: MockFunction = getJestMockFunction();
31
+ const markdownViewerMock: MockFunction = getJestMockFunction();
32
+ const activityFeedMock: MockFunction = getJestMockFunction();
33
+
34
+ jest.mock("../../../UI/Utils/API/API", () => {
35
+ return {
36
+ __esModule: true,
37
+ default: {
38
+ post: (...args: Array<unknown>) => {
39
+ return postMock(...args);
40
+ },
41
+ getFriendlyMessage: (...args: Array<unknown>) => {
42
+ return getFriendlyMessageMock(...args);
43
+ },
44
+ },
45
+ };
46
+ });
47
+
48
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
49
+ return {
50
+ __esModule: true,
51
+ default: {
52
+ getCommonHeaders: (...args: Array<unknown>) => {
53
+ return getCommonHeadersMock(...args);
54
+ },
55
+ },
56
+ };
57
+ });
58
+
59
+ /*
60
+ * Record MarkdownViewer's props so safeMode remains an asserted part of the
61
+ * contract even when the Common Jest config replaces its markdown renderer.
62
+ */
63
+ jest.mock("../../../UI/Components/Markdown.tsx/MarkdownViewer", () => {
64
+ return {
65
+ __esModule: true,
66
+ default: (props: MarkdownViewerProps): React.ReactElement => {
67
+ markdownViewerMock(props);
68
+ return React.createElement(
69
+ "div",
70
+ { "data-testid": "investigation-markdown" },
71
+ props.text,
72
+ );
73
+ },
74
+ };
75
+ });
76
+
77
+ jest.mock(
78
+ "../../../../App/FeatureSet/Dashboard/src/Components/AIChat/ChatActivityFeed",
79
+ () => {
80
+ return {
81
+ __esModule: true,
82
+ default: (props: ActivityFeedProps): React.ReactElement => {
83
+ activityFeedMock(props);
84
+ return React.createElement("div", {
85
+ "data-testid": "investigation-activity",
86
+ });
87
+ },
88
+ };
89
+ },
90
+ );
91
+
92
+ import InvestigationPanel, {
93
+ InvestigationSubjectType,
94
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/AI/InvestigationPanel";
95
+ import AIRunEvent from "../../../Models/DatabaseModels/AIRunEvent";
96
+ import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
97
+ import AIRunCodeFixRecommendation from "../../../Types/AI/AIRunCodeFixRecommendation";
98
+ import AIRunEventType from "../../../Types/AI/AIRunEventType";
99
+ import AIRunStatus from "../../../Types/AI/AIRunStatus";
100
+ import { JSONArray, JSONObject } from "../../../Types/JSON";
101
+ import ObjectID from "../../../Types/ObjectID";
102
+
103
+ interface MarkdownViewerProps {
104
+ text: string;
105
+ safeMode?: boolean | undefined;
106
+ }
107
+
108
+ interface ActivityFeedProps {
109
+ events: Array<AIRunEvent>;
110
+ title?: string | undefined;
111
+ showLiveIndicator?: boolean | undefined;
112
+ maxVisibleSteps?: number | undefined;
113
+ }
114
+
115
+ interface InvestigationPayloadOptions {
116
+ status: AIRunStatus;
117
+ runId?: string | undefined;
118
+ events?: JSONArray | undefined;
119
+ analysisMarkdown?: string | null | undefined;
120
+ isAnalysisPending?: boolean | undefined;
121
+ errorMessage?: string | null | undefined;
122
+ toolCallCount?: number | undefined;
123
+ totalTokens?: number | undefined;
124
+ humanVerdict?: string | null | undefined;
125
+ codeFixRecommendation?: AIRunCodeFixRecommendation | undefined;
126
+ completedAt?: string | undefined;
127
+ }
128
+
129
+ interface ApiResponse {
130
+ data: JSONObject;
131
+ }
132
+
133
+ interface PostRequest {
134
+ url: { toString: () => string };
135
+ data: JSONObject;
136
+ }
137
+
138
+ interface Deferred<T> {
139
+ promise: Promise<T>;
140
+ resolve: (value: T) => void;
141
+ }
142
+
143
+ const POLL_INTERVAL_MS: number = 2500;
144
+ const SETTLED_POLL_INTERVAL_MS: number = 30_000;
145
+ const RECOMMENDATION_SETTLEMENT_MAX_AGE_MS: number = 3 * 60 * 1000;
146
+ const MAX_RECOMMENDATION_POLL_RESPONSES: number = Math.ceil(
147
+ RECOMMENDATION_SETTLEMENT_MAX_AGE_MS / POLL_INTERVAL_MS,
148
+ );
149
+ const COMPLETED_AT: string = "2026-08-07T12:00:00.000Z";
150
+ const OLD_COMPLETED_AT: string = "2026-08-07T11:56:59.000Z";
151
+ const RUN_ID: string = "11111111-1111-4111-8111-111111111111";
152
+ const FIX_RUN_ID: string = "22222222-2222-4222-8222-222222222222";
153
+ const NEXT_RUN_ID: string = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
154
+ const INCIDENT_ID: ObjectID = new ObjectID(
155
+ "33333333-3333-4333-8333-333333333333",
156
+ );
157
+ const ALERT_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
158
+ const EVENT_ID: string = "55555555-5555-4555-8555-555555555555";
159
+ const ANALYSIS: string =
160
+ "## Root cause\n\nThe database connection pool was exhausted.";
161
+
162
+ const activityEvent: JSONObject = {
163
+ _id: EVENT_ID,
164
+ sequence: 1,
165
+ eventType: AIRunEventType.ToolCallStarted,
166
+ toolName: "search_logs",
167
+ createdAt: new Date("2026-08-07T10:00:00.000Z"),
168
+ };
169
+
170
+ function investigationPayload(
171
+ options: InvestigationPayloadOptions,
172
+ ): JSONObject {
173
+ const run: JSONObject = {
174
+ _id: options.runId || RUN_ID,
175
+ status: options.status,
176
+ errorMessage: options.errorMessage ?? null,
177
+ toolCallCount: options.toolCallCount ?? 0,
178
+ totalTokens: options.totalTokens ?? 0,
179
+ humanVerdict: options.humanVerdict ?? null,
180
+ };
181
+
182
+ if (options.codeFixRecommendation !== undefined) {
183
+ run["codeFixRecommendation"] = options.codeFixRecommendation;
184
+ }
185
+
186
+ if (options.completedAt !== undefined) {
187
+ run["completedAt"] = options.completedAt;
188
+ }
189
+
190
+ return {
191
+ run,
192
+ events: options.events || [],
193
+ analysisMarkdown: options.analysisMarkdown ?? null,
194
+ isAnalysisPending: options.isAnalysisPending === true,
195
+ };
196
+ }
197
+
198
+ function successfulResponse(payload: JSONObject): ApiResponse {
199
+ return { data: payload };
200
+ }
201
+
202
+ function noInvestigationResponse(): ApiResponse {
203
+ return {
204
+ data: {
205
+ run: null,
206
+ events: [],
207
+ analysisMarkdown: null,
208
+ isAnalysisPending: false,
209
+ },
210
+ };
211
+ }
212
+
213
+ function completedResponse(
214
+ overrides: Partial<InvestigationPayloadOptions> = {},
215
+ ): ApiResponse {
216
+ return successfulResponse(
217
+ investigationPayload({
218
+ status: AIRunStatus.Completed,
219
+ analysisMarkdown: ANALYSIS,
220
+ events: [activityEvent],
221
+ toolCallCount: 2,
222
+ totalTokens: 1234,
223
+ codeFixRecommendation: AIRunCodeFixRecommendation.Recommended,
224
+ completedAt: COMPLETED_AT,
225
+ ...overrides,
226
+ }),
227
+ );
228
+ }
229
+
230
+ function createDeferred<T>(): Deferred<T> {
231
+ let resolvePromise: ((value: T) => void) | undefined;
232
+ const promise: Promise<T> = new Promise<T>((resolve: (value: T) => void) => {
233
+ resolvePromise = resolve;
234
+ });
235
+
236
+ return {
237
+ promise,
238
+ resolve: (value: T): void => {
239
+ resolvePromise!(value);
240
+ },
241
+ };
242
+ }
243
+
244
+ function renderPanel(data?: {
245
+ subjectType?: InvestigationSubjectType | undefined;
246
+ subjectId?: ObjectID | undefined;
247
+ onAnalysisAvailable?: (() => void) | undefined;
248
+ onStatusChange?: ((status: AIRunStatus | null) => void) | undefined;
249
+ }): ReturnType<typeof render> {
250
+ return render(
251
+ <InvestigationPanel
252
+ subjectType={data?.subjectType || "incident"}
253
+ subjectId={data?.subjectId || INCIDENT_ID}
254
+ onAnalysisAvailable={data?.onAnalysisAvailable}
255
+ onStatusChange={data?.onStatusChange}
256
+ />,
257
+ );
258
+ }
259
+
260
+ /* Let the awaits inside fetchData and the following React effects settle. */
261
+ async function flush(): Promise<void> {
262
+ await act(async (): Promise<void> => {
263
+ await Promise.resolve();
264
+ await Promise.resolve();
265
+ await Promise.resolve();
266
+ await Promise.resolve();
267
+ });
268
+ }
269
+
270
+ async function tick(milliseconds: number): Promise<void> {
271
+ await act(async (): Promise<void> => {
272
+ jest.advanceTimersByTime(milliseconds);
273
+ await Promise.resolve();
274
+ await Promise.resolve();
275
+ await Promise.resolve();
276
+ await Promise.resolve();
277
+ });
278
+ }
279
+
280
+ async function advanceFastPolls(count: number = 1): Promise<void> {
281
+ for (let index: number = 0; index < count; index++) {
282
+ await tick(POLL_INTERVAL_MS);
283
+ }
284
+ }
285
+
286
+ async function resolveDeferred<T>(
287
+ deferred: Deferred<T>,
288
+ value: T,
289
+ ): Promise<void> {
290
+ await act(async (): Promise<void> => {
291
+ deferred.resolve(value);
292
+ await Promise.resolve();
293
+ await Promise.resolve();
294
+ await Promise.resolve();
295
+ await Promise.resolve();
296
+ });
297
+ }
298
+
299
+ function postRequestAt(index: number): PostRequest {
300
+ return postMock.mock.calls[index]![0] as PostRequest;
301
+ }
302
+
303
+ function requestPath(index: number): string {
304
+ return postRequestAt(index).url.toString();
305
+ }
306
+
307
+ function fixButton(): HTMLElement | null {
308
+ return screen.queryByRole("button", {
309
+ name: "Open Fix PR from this analysis",
310
+ });
311
+ }
312
+
313
+ function lastActivityProps(): ActivityFeedProps {
314
+ const calls: Array<Array<ActivityFeedProps>> = activityFeedMock.mock
315
+ .calls as Array<Array<ActivityFeedProps>>;
316
+ return calls[calls.length - 1]![0]!;
317
+ }
318
+
319
+ beforeEach(() => {
320
+ jest.useFakeTimers();
321
+ jest.setSystemTime(new Date(COMPLETED_AT));
322
+ getCommonHeadersMock.mockReturnValue({});
323
+ getFriendlyMessageMock.mockImplementation((error: unknown): string => {
324
+ if (
325
+ typeof error === "object" &&
326
+ error !== null &&
327
+ "message" in error &&
328
+ typeof (error as { message?: unknown }).message === "string"
329
+ ) {
330
+ return (error as { message: string }).message;
331
+ }
332
+
333
+ return "Request failed";
334
+ });
335
+ });
336
+
337
+ afterEach(() => {
338
+ cleanup();
339
+ jest.clearAllTimers();
340
+ jest.useRealTimers();
341
+ postMock.mockReset();
342
+ getFriendlyMessageMock.mockReset();
343
+ getCommonHeadersMock.mockReset();
344
+ markdownViewerMock.mockReset();
345
+ activityFeedMock.mockReset();
346
+ });
347
+
348
+ describe("InvestigationPanel report lifecycle", () => {
349
+ test("renders nothing and briefly discovers a run when none exists yet", async () => {
350
+ postMock.mockResolvedValue(noInvestigationResponse() as never);
351
+
352
+ const { container } = renderPanel();
353
+ await flush();
354
+
355
+ expect(container).toBeEmptyDOMElement();
356
+ expect(jest.getTimerCount()).toBe(1);
357
+
358
+ await advanceFastPolls(4);
359
+ expect(postMock).toHaveBeenCalledTimes(5);
360
+ expect(jest.getTimerCount()).toBe(1);
361
+ });
362
+
363
+ test("shows live activity and requests the incident endpoint", async () => {
364
+ postMock.mockResolvedValue(
365
+ successfulResponse(
366
+ investigationPayload({
367
+ status: AIRunStatus.Running,
368
+ events: [activityEvent],
369
+ }),
370
+ ) as never,
371
+ );
372
+
373
+ renderPanel();
374
+ await flush();
375
+
376
+ expect(screen.getByText("Investigating…")).toBeInTheDocument();
377
+ expect(screen.getByTestId("investigation-activity")).toBeInTheDocument();
378
+ expect(lastActivityProps().events).toHaveLength(1);
379
+ expect(lastActivityProps().showLiveIndicator).toBe(true);
380
+ expect(
381
+ document.querySelector('[class~="motion-safe:animate-ping"]'),
382
+ ).not.toBeNull();
383
+
384
+ const request: PostRequest = postRequestAt(0);
385
+ expect(request.url.toString()).toContain("/ai-investigation/incident");
386
+ expect(request.data).toEqual({ incidentId: INCIDENT_ID.toString() });
387
+ expect(getCommonHeadersMock).toHaveBeenCalledTimes(1);
388
+ });
389
+
390
+ test("renders the completed report safely and demotes activity to a disclosure", async () => {
391
+ const onAnalysisAvailable: MockFunction = getJestMockFunction();
392
+ postMock.mockResolvedValue(completedResponse() as never);
393
+
394
+ renderPanel({ onAnalysisAvailable });
395
+ await flush();
396
+
397
+ expect(screen.getByText("Investigation complete")).toBeInTheDocument();
398
+ expect(screen.getByLabelText("Investigation report")).toBeInTheDocument();
399
+ expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
400
+ "The database connection pool was exhausted.",
401
+ );
402
+ expect(markdownViewerMock).toHaveBeenCalledWith({
403
+ text: ANALYSIS,
404
+ safeMode: true,
405
+ });
406
+ expect(screen.getByText("Investigation activity")).toBeInTheDocument();
407
+ expect(lastActivityProps()).toEqual(
408
+ expect.objectContaining({
409
+ title: "Completed activity",
410
+ showLiveIndicator: false,
411
+ maxVisibleSteps: 10,
412
+ }),
413
+ );
414
+
415
+ const usage: HTMLElement = screen.getByLabelText("Investigation usage");
416
+ expect(usage).toHaveTextContent("2 telemetry queries");
417
+ expect(usage).toHaveTextContent("1,234 tokens");
418
+ expect(fixButton()).toBeEnabled();
419
+ expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
420
+ expect(screen.getByRole("button", { name: "Rejected" })).toBeEnabled();
421
+ expect(onAnalysisAvailable).toHaveBeenCalledTimes(1);
422
+ expect(jest.getTimerCount()).toBe(1);
423
+ });
424
+
425
+ test("keeps polling across Completed until the same run report appears", async () => {
426
+ const onAnalysisAvailable: MockFunction = getJestMockFunction();
427
+ postMock
428
+ .mockResolvedValueOnce(
429
+ completedResponse({
430
+ analysisMarkdown: null,
431
+ isAnalysisPending: true,
432
+ }) as never,
433
+ )
434
+ .mockResolvedValueOnce(
435
+ completedResponse({
436
+ analysisMarkdown: ANALYSIS,
437
+ isAnalysisPending: false,
438
+ }) as never,
439
+ );
440
+
441
+ renderPanel({ onAnalysisAvailable });
442
+ await flush();
443
+
444
+ expect(screen.getByText("Preparing investigation report…")).toBeVisible();
445
+ expect(screen.getByText("Preparing the final report")).toBeVisible();
446
+ expect(screen.queryByTestId("investigation-markdown")).toBeNull();
447
+ expect(onAnalysisAvailable).not.toHaveBeenCalled();
448
+
449
+ /* Status, run id, events and recommendation remain identical. */
450
+ await tick(POLL_INTERVAL_MS);
451
+
452
+ expect(screen.getByText("Investigation complete")).toBeVisible();
453
+ expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
454
+ "The database connection pool was exhausted.",
455
+ );
456
+ expect(postMock).toHaveBeenCalledTimes(2);
457
+ expect(onAnalysisAvailable).toHaveBeenCalledTimes(1);
458
+
459
+ await tick(POLL_INTERVAL_MS * 4);
460
+ expect(postMock).toHaveBeenCalledTimes(2);
461
+ });
462
+
463
+ test("does not overlap a slow active-run poll", async () => {
464
+ const slowPoll: Deferred<ApiResponse> = createDeferred<ApiResponse>();
465
+ postMock
466
+ .mockResolvedValueOnce(
467
+ successfulResponse(
468
+ investigationPayload({ status: AIRunStatus.Running }),
469
+ ) as never,
470
+ )
471
+ .mockReturnValueOnce(slowPoll.promise as never)
472
+ .mockResolvedValue(completedResponse() as never);
473
+
474
+ const view: ReturnType<typeof render> = renderPanel();
475
+ await flush();
476
+ await tick(POLL_INTERVAL_MS);
477
+
478
+ expect(postMock).toHaveBeenCalledTimes(2);
479
+ expect(screen.getByText("Investigating…")).toBeVisible();
480
+
481
+ view.rerender(
482
+ <InvestigationPanel
483
+ subjectType="incident"
484
+ subjectId={new ObjectID(INCIDENT_ID.toString())}
485
+ />,
486
+ );
487
+ await flush();
488
+ await tick(POLL_INTERVAL_MS * 3);
489
+ expect(postMock).toHaveBeenCalledTimes(2);
490
+
491
+ await resolveDeferred(slowPoll, completedResponse());
492
+ expect(screen.getByText("Investigation complete")).toBeVisible();
493
+ expect(postMock).toHaveBeenCalledTimes(2);
494
+ });
495
+
496
+ test("ignores a previous subject response after route navigation", async () => {
497
+ const previousSubject: Deferred<ApiResponse> =
498
+ createDeferred<ApiResponse>();
499
+ const statuses: Array<AIRunStatus | null> = [];
500
+ postMock
501
+ .mockReturnValueOnce(previousSubject.promise as never)
502
+ .mockResolvedValueOnce(completedResponse() as never);
503
+
504
+ const view: ReturnType<typeof render> = renderPanel({
505
+ onStatusChange: (status: AIRunStatus | null): void => {
506
+ statuses.push(status);
507
+ },
508
+ });
509
+ await flush();
510
+
511
+ view.rerender(
512
+ <InvestigationPanel
513
+ subjectType="alert"
514
+ subjectId={ALERT_ID}
515
+ onStatusChange={(status: AIRunStatus | null): void => {
516
+ statuses.push(status);
517
+ }}
518
+ />,
519
+ );
520
+ await flush();
521
+
522
+ expect(screen.getByText("Investigation complete")).toBeVisible();
523
+ expect(postRequestAt(1).data).toEqual({ alertId: ALERT_ID.toString() });
524
+
525
+ await resolveDeferred(
526
+ previousSubject,
527
+ successfulResponse(investigationPayload({ status: AIRunStatus.Running })),
528
+ );
529
+
530
+ expect(screen.getByText("Investigation complete")).toBeVisible();
531
+ expect(screen.queryByText("Investigating…")).toBeNull();
532
+ expect(statuses[statuses.length - 1]).toBe(AIRunStatus.Completed);
533
+ });
534
+
535
+ test("does not notify a new subject with the previous report", async () => {
536
+ const nextSubject: Deferred<ApiResponse> = createDeferred<ApiResponse>();
537
+ const onAnalysisAvailable: MockFunction = getJestMockFunction();
538
+ const nextAnalysis: string =
539
+ "## Alert root cause\n\nThe upstream dependency rejected requests.";
540
+ postMock
541
+ .mockResolvedValueOnce(completedResponse() as never)
542
+ .mockReturnValueOnce(nextSubject.promise as never);
543
+
544
+ const view: ReturnType<typeof render> = renderPanel({
545
+ onAnalysisAvailable,
546
+ });
547
+ await flush();
548
+ expect(onAnalysisAvailable).toHaveBeenCalledTimes(1);
549
+ onAnalysisAvailable.mockClear();
550
+
551
+ view.rerender(
552
+ <InvestigationPanel
553
+ subjectType="alert"
554
+ subjectId={ALERT_ID}
555
+ onAnalysisAvailable={onAnalysisAvailable}
556
+ />,
557
+ );
558
+ await flush();
559
+
560
+ expect(view.container).toBeEmptyDOMElement();
561
+ expect(onAnalysisAvailable).not.toHaveBeenCalled();
562
+
563
+ await resolveDeferred(
564
+ nextSubject,
565
+ completedResponse({ runId: NEXT_RUN_ID, analysisMarkdown: nextAnalysis }),
566
+ );
567
+
568
+ expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
569
+ "The upstream dependency rejected requests.",
570
+ );
571
+ expect(onAnalysisAvailable).toHaveBeenCalledTimes(1);
572
+ });
573
+
574
+ test("shows the no-report outcome and disables analysis-only actions", async () => {
575
+ postMock.mockResolvedValue(
576
+ completedResponse({
577
+ analysisMarkdown: null,
578
+ isAnalysisPending: false,
579
+ events: [],
580
+ toolCallCount: 0,
581
+ totalTokens: 0,
582
+ }) as never,
583
+ );
584
+
585
+ renderPanel();
586
+ await flush();
587
+
588
+ expect(
589
+ screen.getByText("Investigation completed without a report"),
590
+ ).toBeVisible();
591
+ expect(
592
+ screen.getByText("No investigation report was published."),
593
+ ).toBeVisible();
594
+ expect(fixButton()).toBeDisabled();
595
+ expect(screen.getByRole("button", { name: "Confirmed" })).toBeDisabled();
596
+ expect(screen.getByRole("button", { name: "Rejected" })).toBeDisabled();
597
+ });
598
+
599
+ test("shows a terminal failure without completed actions", async () => {
600
+ postMock.mockResolvedValue(
601
+ successfulResponse(
602
+ investigationPayload({
603
+ status: AIRunStatus.Error,
604
+ errorMessage: "The model provider timed out.",
605
+ events: [activityEvent],
606
+ }),
607
+ ) as never,
608
+ );
609
+
610
+ renderPanel();
611
+ await flush();
612
+
613
+ expect(screen.getByText("Investigation did not finish")).toBeVisible();
614
+ expect(screen.getByText("The model provider timed out.")).toBeVisible();
615
+ expect(lastActivityProps()).toEqual(
616
+ expect.objectContaining({
617
+ title: "Investigation activity",
618
+ showLiveIndicator: false,
619
+ }),
620
+ );
621
+ expect(fixButton()).toBeNull();
622
+ expect(screen.queryByText("Rate this investigation")).toBeNull();
623
+ });
624
+
625
+ test("uses the alert endpoint and alert id", async () => {
626
+ postMock.mockResolvedValue(
627
+ successfulResponse(
628
+ investigationPayload({ status: AIRunStatus.Running }),
629
+ ) as never,
630
+ );
631
+
632
+ renderPanel({ subjectType: "alert", subjectId: ALERT_ID });
633
+ await flush();
634
+
635
+ expect(requestPath(0)).toContain("/ai-investigation/alert");
636
+ expect(postRequestAt(0).data).toEqual({ alertId: ALERT_ID.toString() });
637
+ });
638
+ });
639
+
640
+ describe("InvestigationPanel code-fix recommendation", () => {
641
+ test("shows the fix action only for an explicit Recommended decision", async () => {
642
+ postMock.mockResolvedValue(completedResponse() as never);
643
+
644
+ renderPanel();
645
+ await flush();
646
+
647
+ expect(fixButton()).toBeEnabled();
648
+ expect(screen.getByText("Act on this investigation")).toBeVisible();
649
+ expect(screen.getByText("Rate this investigation")).toBeVisible();
650
+ });
651
+
652
+ test("hides every fix-task element for NotRecommended but retains verdict controls", async () => {
653
+ postMock.mockResolvedValue(
654
+ completedResponse({
655
+ codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
656
+ }) as never,
657
+ );
658
+
659
+ renderPanel();
660
+ await flush();
661
+
662
+ expect(fixButton()).toBeNull();
663
+ expect(screen.queryByText("Act on this investigation")).toBeNull();
664
+ expect(screen.queryByText(/Fix task created/)).toBeNull();
665
+ expect(screen.getByText("Rate this investigation")).toBeVisible();
666
+ expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
667
+ expect(screen.getByRole("button", { name: "Rejected" })).toBeEnabled();
668
+ });
669
+
670
+ test("fails closed for a legacy row with no recommendation or completion time", async () => {
671
+ postMock.mockResolvedValue(
672
+ completedResponse({
673
+ codeFixRecommendation: undefined,
674
+ completedAt: undefined,
675
+ }) as never,
676
+ );
677
+
678
+ renderPanel();
679
+ await flush();
680
+
681
+ expect(fixButton()).toBeNull();
682
+ expect(screen.getByText("Rate this investigation")).toBeVisible();
683
+ expect(jest.getTimerCount()).toBe(1);
684
+
685
+ await advanceFastPolls(4);
686
+ expect(postMock).toHaveBeenCalledTimes(1);
687
+ });
688
+
689
+ test("fails closed at fast cadence for an old row with no recommendation", async () => {
690
+ postMock.mockResolvedValue(
691
+ completedResponse({
692
+ codeFixRecommendation: undefined,
693
+ completedAt: OLD_COMPLETED_AT,
694
+ }) as never,
695
+ );
696
+
697
+ renderPanel();
698
+ await flush();
699
+
700
+ expect(fixButton()).toBeNull();
701
+ expect(jest.getTimerCount()).toBe(1);
702
+
703
+ await advanceFastPolls(4);
704
+ expect(postMock).toHaveBeenCalledTimes(1);
705
+ });
706
+
707
+ test("polls a recent missing decision and reveals the action when it settles", async () => {
708
+ postMock
709
+ .mockResolvedValueOnce(
710
+ completedResponse({ codeFixRecommendation: undefined }) as never,
711
+ )
712
+ .mockResolvedValueOnce(completedResponse() as never);
713
+
714
+ renderPanel();
715
+ await flush();
716
+
717
+ expect(fixButton()).toBeNull();
718
+ expect(screen.getByText("Rate this investigation")).toBeVisible();
719
+ expect(jest.getTimerCount()).toBe(2);
720
+
721
+ await advanceFastPolls();
722
+
723
+ expect(postMock).toHaveBeenCalledTimes(2);
724
+ expect(fixButton()).toBeEnabled();
725
+ expect(jest.getTimerCount()).toBe(1);
726
+ });
727
+
728
+ test("applies Pending to Recommended when no other signature field changes", async () => {
729
+ postMock
730
+ .mockResolvedValueOnce(
731
+ completedResponse({
732
+ codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
733
+ }) as never,
734
+ )
735
+ .mockResolvedValueOnce(completedResponse() as never);
736
+
737
+ renderPanel();
738
+ await flush();
739
+
740
+ expect(screen.getByText("Investigation complete")).toBeVisible();
741
+ expect(fixButton()).toBeNull();
742
+ expect(postMock).toHaveBeenCalledTimes(1);
743
+ expect(jest.getTimerCount()).toBe(2);
744
+
745
+ await advanceFastPolls();
746
+
747
+ expect(postMock).toHaveBeenCalledTimes(2);
748
+ expect(fixButton()).toBeEnabled();
749
+ expect(jest.getTimerCount()).toBe(1);
750
+ });
751
+
752
+ test("does not present explicit Pending as an active investigation", async () => {
753
+ postMock.mockResolvedValue(
754
+ completedResponse({
755
+ codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
756
+ }) as never,
757
+ );
758
+
759
+ renderPanel();
760
+ await flush();
761
+
762
+ expect(screen.getByText("Investigation complete")).toBeVisible();
763
+ expect(screen.queryByText("Investigating…")).toBeNull();
764
+ expect(
765
+ document.querySelector('[class~="motion-safe:animate-ping"]'),
766
+ ).toBeNull();
767
+ expect(fixButton()).toBeNull();
768
+ });
769
+
770
+ test("tolerates a browser clock behind the server while Pending settles", async () => {
771
+ jest.setSystemTime(new Date("2026-08-07T11:59:55.000Z"));
772
+ postMock
773
+ .mockResolvedValueOnce(
774
+ completedResponse({
775
+ codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
776
+ }) as never,
777
+ )
778
+ .mockResolvedValueOnce(completedResponse() as never);
779
+
780
+ renderPanel();
781
+ await flush();
782
+ expect(fixButton()).toBeNull();
783
+
784
+ await advanceFastPolls();
785
+ expect(fixButton()).toBeEnabled();
786
+ });
787
+
788
+ test("tolerates a browser clock ahead of the server for explicit Pending", async () => {
789
+ jest.setSystemTime(new Date("2026-08-07T12:05:00.000Z"));
790
+ postMock
791
+ .mockResolvedValueOnce(
792
+ completedResponse({
793
+ codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
794
+ }) as never,
795
+ )
796
+ .mockResolvedValueOnce(completedResponse() as never);
797
+
798
+ renderPanel();
799
+ await flush();
800
+ expect(fixButton()).toBeNull();
801
+
802
+ await advanceFastPolls();
803
+ expect(fixButton()).toBeEnabled();
804
+ });
805
+
806
+ test("bounds a permanently Pending decision by time and response count", async () => {
807
+ postMock.mockResolvedValue(
808
+ completedResponse({
809
+ codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
810
+ }) as never,
811
+ );
812
+
813
+ renderPanel();
814
+ await flush();
815
+
816
+ expect(fixButton()).toBeNull();
817
+ expect(jest.getTimerCount()).toBe(2);
818
+
819
+ await advanceFastPolls(MAX_RECOMMENDATION_POLL_RESPONSES);
820
+
821
+ const callsAtSettlement: number = postMock.mock.calls.length;
822
+ expect(callsAtSettlement).toBeGreaterThan(1);
823
+ expect(callsAtSettlement).toBeLessThanOrEqual(
824
+ MAX_RECOMMENDATION_POLL_RESPONSES + 1,
825
+ );
826
+ expect(fixButton()).toBeNull();
827
+ expect(screen.queryByRole("alert")).toBeNull();
828
+ expect(jest.getTimerCount()).toBe(1);
829
+
830
+ await advanceFastPolls(4);
831
+ expect(postMock).toHaveBeenCalledTimes(callsAtSettlement);
832
+ });
833
+
834
+ test("keeps Pending polls sequential when a response is slow", async () => {
835
+ const slowPending: Deferred<ApiResponse> = createDeferred<ApiResponse>();
836
+ postMock
837
+ .mockResolvedValueOnce(
838
+ completedResponse({
839
+ codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
840
+ }) as never,
841
+ )
842
+ .mockReturnValueOnce(slowPending.promise as never)
843
+ .mockResolvedValueOnce(completedResponse() as never);
844
+
845
+ renderPanel();
846
+ await flush();
847
+ await advanceFastPolls();
848
+ expect(postMock).toHaveBeenCalledTimes(2);
849
+
850
+ await advanceFastPolls(5);
851
+ expect(postMock).toHaveBeenCalledTimes(2);
852
+
853
+ await resolveDeferred(
854
+ slowPending,
855
+ completedResponse({
856
+ codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
857
+ }),
858
+ );
859
+ await advanceFastPolls();
860
+
861
+ expect(postMock).toHaveBeenCalledTimes(3);
862
+ expect(fixButton()).toBeEnabled();
863
+ });
864
+
865
+ test("stops failed Pending polls at the independent deadline", async () => {
866
+ const failedPoll: HTTPErrorResponse = new HTTPErrorResponse(
867
+ 503,
868
+ { message: "temporarily unavailable" },
869
+ {},
870
+ );
871
+ postMock
872
+ .mockResolvedValueOnce(
873
+ completedResponse({
874
+ codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
875
+ }) as never,
876
+ )
877
+ .mockResolvedValue(failedPoll as never);
878
+
879
+ renderPanel();
880
+ await flush();
881
+ await advanceFastPolls(MAX_RECOMMENDATION_POLL_RESPONSES);
882
+
883
+ const callsAtDeadline: number = postMock.mock.calls.length;
884
+ expect(callsAtDeadline).toBeGreaterThan(1);
885
+ expect(fixButton()).toBeNull();
886
+ expect(screen.queryByRole("alert")).toBeNull();
887
+
888
+ await advanceFastPolls(4);
889
+ expect(postMock).toHaveBeenCalledTimes(callsAtDeadline);
890
+ });
891
+
892
+ test("a hung Pending request never overlaps before or after the deadline", async () => {
893
+ const hungPoll: Deferred<ApiResponse> = createDeferred<ApiResponse>();
894
+ postMock
895
+ .mockResolvedValueOnce(
896
+ completedResponse({
897
+ codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
898
+ }) as never,
899
+ )
900
+ .mockReturnValueOnce(hungPoll.promise as never);
901
+
902
+ renderPanel();
903
+ await flush();
904
+ await advanceFastPolls();
905
+ expect(postMock).toHaveBeenCalledTimes(2);
906
+
907
+ await tick(RECOMMENDATION_SETTLEMENT_MAX_AGE_MS);
908
+ expect(fixButton()).toBeNull();
909
+ expect(postMock).toHaveBeenCalledTimes(2);
910
+
911
+ /* Settled cadence must reuse the still-hung request, not overlap it. */
912
+ await tick(SETTLED_POLL_INTERVAL_MS);
913
+ expect(postMock).toHaveBeenCalledTimes(2);
914
+ });
915
+ });
916
+
917
+ describe("InvestigationPanel completed actions", () => {
918
+ test("creates a run-bound fix task and replaces the action with success", async () => {
919
+ postMock
920
+ .mockResolvedValueOnce(completedResponse() as never)
921
+ .mockResolvedValueOnce(
922
+ successfulResponse({ aiRunId: FIX_RUN_ID }) as never,
923
+ );
924
+
925
+ renderPanel();
926
+ await flush();
927
+ fireEvent.click(fixButton()!);
928
+ await flush();
929
+
930
+ expect(screen.getByRole("alert")).toHaveTextContent("Fix task created");
931
+ expect(fixButton()).toBeNull();
932
+ expect(requestPath(1)).toContain("/ai-investigation/create-fix-task");
933
+ expect(postRequestAt(1).data).toEqual({
934
+ subjectType: "incident",
935
+ subjectId: INCIDENT_ID.toString(),
936
+ investigationRunId: RUN_ID,
937
+ });
938
+ expect(screen.getByText("View task progress")).toBeVisible();
939
+ });
940
+
941
+ test("keeps the Recommended action and shows a friendly task error", async () => {
942
+ postMock
943
+ .mockResolvedValueOnce(completedResponse() as never)
944
+ .mockResolvedValueOnce(
945
+ new HTTPErrorResponse(
946
+ 400,
947
+ { message: "No connected repository exists." },
948
+ {},
949
+ ) as never,
950
+ );
951
+
952
+ renderPanel();
953
+ await flush();
954
+ fireEvent.click(fixButton()!);
955
+ await flush();
956
+
957
+ expect(screen.getByRole("alert")).toHaveTextContent(
958
+ "Could not create the fix task",
959
+ );
960
+ expect(screen.getByRole("alert")).toHaveTextContent(
961
+ "No connected repository exists",
962
+ );
963
+ expect(fixButton()).toBeEnabled();
964
+ });
965
+
966
+ test("clears settled fix-task state after subject navigation", async () => {
967
+ const nextAnalysis: string =
968
+ "## Alert root cause\n\nA deployment removed the required credential.";
969
+ postMock
970
+ .mockResolvedValueOnce(completedResponse() as never)
971
+ .mockResolvedValueOnce(
972
+ successfulResponse({ aiRunId: FIX_RUN_ID }) as never,
973
+ )
974
+ .mockResolvedValueOnce(
975
+ completedResponse({
976
+ runId: NEXT_RUN_ID,
977
+ analysisMarkdown: nextAnalysis,
978
+ }) as never,
979
+ );
980
+
981
+ const view: ReturnType<typeof render> = renderPanel();
982
+ await flush();
983
+ fireEvent.click(fixButton()!);
984
+ await flush();
985
+ expect(screen.getByRole("alert")).toHaveTextContent("Fix task created");
986
+
987
+ view.rerender(
988
+ <InvestigationPanel subjectType="alert" subjectId={ALERT_ID} />,
989
+ );
990
+ await flush();
991
+
992
+ expect(screen.queryByText(/Fix task created/)).toBeNull();
993
+ expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
994
+ "A deployment removed the required credential.",
995
+ );
996
+ expect(fixButton()).toBeEnabled();
997
+ });
998
+
999
+ test("ignores a fix-task result that resolves on another subject", async () => {
1000
+ const staleTask: Deferred<ApiResponse> = createDeferred<ApiResponse>();
1001
+ postMock
1002
+ .mockResolvedValueOnce(completedResponse() as never)
1003
+ .mockReturnValueOnce(staleTask.promise as never)
1004
+ .mockResolvedValueOnce(
1005
+ completedResponse({ runId: NEXT_RUN_ID }) as never,
1006
+ );
1007
+
1008
+ const view: ReturnType<typeof render> = renderPanel();
1009
+ await flush();
1010
+ fireEvent.click(fixButton()!);
1011
+ await flush();
1012
+
1013
+ view.rerender(
1014
+ <InvestigationPanel subjectType="alert" subjectId={ALERT_ID} />,
1015
+ );
1016
+ await flush();
1017
+ await resolveDeferred(
1018
+ staleTask,
1019
+ successfulResponse({ aiRunId: FIX_RUN_ID }),
1020
+ );
1021
+
1022
+ expect(screen.queryByRole("alert")).toBeNull();
1023
+ expect(fixButton()).toBeEnabled();
1024
+ expect(screen.getByText("Investigation complete")).toBeVisible();
1025
+ });
1026
+
1027
+ test("resets fix-task and verdict state when the same subject gets a new run", async () => {
1028
+ const nextAnalysis: string =
1029
+ "## New root cause\n\nA later investigation found a certificate rollover.";
1030
+ postMock
1031
+ .mockResolvedValueOnce(completedResponse() as never)
1032
+ .mockResolvedValueOnce(
1033
+ successfulResponse({ aiRunId: FIX_RUN_ID }) as never,
1034
+ )
1035
+ .mockResolvedValueOnce(successfulResponse({}) as never)
1036
+ .mockResolvedValueOnce(
1037
+ completedResponse({
1038
+ runId: NEXT_RUN_ID,
1039
+ analysisMarkdown: nextAnalysis,
1040
+ }) as never,
1041
+ );
1042
+
1043
+ renderPanel();
1044
+ await flush();
1045
+ fireEvent.click(fixButton()!);
1046
+ await flush();
1047
+ fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
1048
+ await flush();
1049
+
1050
+ expect(screen.getByRole("alert")).toHaveTextContent("Fix task created");
1051
+ expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
1052
+
1053
+ await tick(SETTLED_POLL_INTERVAL_MS);
1054
+
1055
+ expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
1056
+ "A later investigation found a certificate rollover.",
1057
+ );
1058
+ expect(screen.queryByText(/Fix task created/)).toBeNull();
1059
+ expect(screen.queryByText(/You confirmed this analysis/)).toBeNull();
1060
+ expect(fixButton()).toBeEnabled();
1061
+ expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
1062
+ });
1063
+
1064
+ test("binds a verdict to the displayed run even without a fix recommendation", async () => {
1065
+ postMock
1066
+ .mockResolvedValueOnce(
1067
+ completedResponse({
1068
+ codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
1069
+ }) as never,
1070
+ )
1071
+ .mockResolvedValueOnce(successfulResponse({}) as never);
1072
+
1073
+ renderPanel();
1074
+ await flush();
1075
+ fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
1076
+ await flush();
1077
+
1078
+ expect(requestPath(1)).toContain("/ai-investigation/verdict");
1079
+ expect(postRequestAt(1).data).toEqual({
1080
+ subjectType: "incident",
1081
+ subjectId: INCIDENT_ID.toString(),
1082
+ investigationRunId: RUN_ID,
1083
+ verdict: "Confirmed",
1084
+ });
1085
+ expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
1086
+ });
1087
+
1088
+ test("retains an existing verdict when a fix is not recommended", async () => {
1089
+ postMock.mockResolvedValue(
1090
+ completedResponse({
1091
+ codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
1092
+ humanVerdict: "Confirmed",
1093
+ }) as never,
1094
+ );
1095
+
1096
+ renderPanel();
1097
+ await flush();
1098
+
1099
+ expect(fixButton()).toBeNull();
1100
+ expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
1101
+ expect(screen.getByText("Change")).toBeVisible();
1102
+ expect(screen.getByText("Rate this investigation")).toBeVisible();
1103
+ });
1104
+
1105
+ test("a GET started before verdict save cannot overwrite the saved verdict", async () => {
1106
+ const stalePoll: Deferred<ApiResponse> = createDeferred<ApiResponse>();
1107
+ postMock
1108
+ .mockResolvedValueOnce(completedResponse() as never)
1109
+ .mockReturnValueOnce(stalePoll.promise as never)
1110
+ .mockResolvedValueOnce(successfulResponse({}) as never);
1111
+
1112
+ renderPanel();
1113
+ await flush();
1114
+ await tick(SETTLED_POLL_INTERVAL_MS);
1115
+ expect(postMock).toHaveBeenCalledTimes(2);
1116
+
1117
+ fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
1118
+ await flush();
1119
+ expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
1120
+
1121
+ await resolveDeferred(stalePoll, completedResponse({ humanVerdict: null }));
1122
+
1123
+ expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
1124
+ expect(screen.queryByRole("button", { name: "Confirmed" })).toBeNull();
1125
+ });
1126
+
1127
+ test("rolls back an optimistic verdict when save fails", async () => {
1128
+ postMock
1129
+ .mockResolvedValueOnce(completedResponse() as never)
1130
+ .mockResolvedValueOnce(
1131
+ new HTTPErrorResponse(
1132
+ 500,
1133
+ { message: "Verdict storage is unavailable." },
1134
+ {},
1135
+ ) as never,
1136
+ );
1137
+
1138
+ renderPanel();
1139
+ await flush();
1140
+ fireEvent.click(screen.getByRole("button", { name: "Rejected" }));
1141
+ await flush();
1142
+
1143
+ expect(screen.getByRole("alert")).toHaveTextContent(
1144
+ "Could not save your verdict",
1145
+ );
1146
+ expect(screen.getByRole("alert")).toHaveTextContent(
1147
+ "Verdict storage is unavailable.",
1148
+ );
1149
+ expect(screen.queryByText(/You rejected this analysis/)).toBeNull();
1150
+ expect(screen.getByRole("button", { name: "Rejected" })).toBeEnabled();
1151
+ });
1152
+
1153
+ test("ignores a verdict result that resolves after navigation", async () => {
1154
+ const staleVerdict: Deferred<ApiResponse> = createDeferred<ApiResponse>();
1155
+ postMock
1156
+ .mockResolvedValueOnce(
1157
+ completedResponse({
1158
+ codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
1159
+ }) as never,
1160
+ )
1161
+ .mockReturnValueOnce(staleVerdict.promise as never)
1162
+ .mockResolvedValueOnce(
1163
+ completedResponse({
1164
+ runId: NEXT_RUN_ID,
1165
+ codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
1166
+ }) as never,
1167
+ );
1168
+
1169
+ const view: ReturnType<typeof render> = renderPanel();
1170
+ await flush();
1171
+ fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
1172
+ await flush();
1173
+
1174
+ view.rerender(
1175
+ <InvestigationPanel subjectType="alert" subjectId={ALERT_ID} />,
1176
+ );
1177
+ await flush();
1178
+ await resolveDeferred(staleVerdict, successfulResponse({}));
1179
+
1180
+ expect(screen.queryByText(/You confirmed this analysis/)).toBeNull();
1181
+ expect(screen.queryByRole("alert")).toBeNull();
1182
+ expect(screen.getByRole("button", { name: "Confirmed" })).toBeEnabled();
1183
+ });
1184
+ });