@oneuptime/common 14.0.9 → 14.0.10

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 (383) hide show
  1. package/Models/DatabaseModels/AutoRemediationSuggestion.ts +60 -0
  2. package/Models/DatabaseModels/CephCluster.ts +213 -0
  3. package/Models/DatabaseModels/DatabaseServer.ts +213 -0
  4. package/Models/DatabaseModels/DockerHost.ts +213 -0
  5. package/Models/DatabaseModels/DockerSwarmCluster.ts +213 -0
  6. package/Models/DatabaseModels/GlobalConfig.ts +1 -1
  7. package/Models/DatabaseModels/Host.ts +213 -0
  8. package/Models/DatabaseModels/Index.ts +2 -0
  9. package/Models/DatabaseModels/PodmanHost.ts +213 -0
  10. package/Models/DatabaseModels/ProxmoxCluster.ts +213 -0
  11. package/Models/DatabaseModels/ResourceAiAgent.ts +419 -0
  12. package/Models/DatabaseModels/RunnerJob.ts +144 -0
  13. package/Models/DatabaseModels/VMwareVCenter.ts +213 -0
  14. package/Server/API/AutoRemediationAPI.ts +392 -1
  15. package/Server/API/ResourceAiAccessAPI.ts +1669 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.ts +423 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  18. package/Server/Infrastructure/Semaphore.ts +22 -0
  19. package/Server/Middleware/TelemetryIngest.ts +15 -5
  20. package/Server/Services/AnalyticsDatabaseService.ts +45 -1
  21. package/Server/Services/AutoRemediationRuleEngineService.ts +940 -112
  22. package/Server/Services/CephClusterService.ts +109 -1
  23. package/Server/Services/DatabaseServerService.ts +116 -1
  24. package/Server/Services/DockerHostService.ts +109 -1
  25. package/Server/Services/DockerSwarmClusterService.ts +113 -1
  26. package/Server/Services/HostService.ts +126 -4
  27. package/Server/Services/MetricRecordingRuleService.ts +47 -0
  28. package/Server/Services/PodmanHostService.ts +109 -1
  29. package/Server/Services/ProxmoxClusterService.ts +110 -1
  30. package/Server/Services/ResourceAiAccessService.ts +1439 -0
  31. package/Server/Services/ResourceAiAgentJobService.ts +202 -0
  32. package/Server/Services/ResourceAiAgentService.ts +2432 -0
  33. package/Server/Services/RunnerJobService.ts +611 -4
  34. package/Server/Services/TelemetryUsageBillingService.ts +28 -0
  35. package/Server/Services/TraceRecordingRuleService.ts +33 -0
  36. package/Server/Services/VMwareVCenterService.ts +110 -1
  37. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1434 -32
  38. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +935 -21
  39. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +25 -1
  40. package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +577 -0
  41. package/Server/Utils/AI/ResourceAccess/ResourceAccessContext.ts +271 -0
  42. package/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.ts +45 -0
  43. package/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.ts +964 -0
  44. package/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.ts +333 -0
  45. package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +873 -0
  46. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +72 -2
  47. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +72 -5
  48. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +72 -5
  49. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +396 -13
  50. package/Server/Utils/AutoRemediation/RemediationVerifier.ts +35 -2
  51. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +9 -1
  52. package/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.ts +920 -0
  53. package/Server/Utils/SessionReplay/SessionReplayUsage.ts +84 -6
  54. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +35 -15
  55. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +33 -15
  56. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +21 -1
  57. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +298 -224
  58. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +35 -15
  59. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +389 -175
  60. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +366 -143
  61. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.ts +163 -0
  62. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.ts +396 -0
  63. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.ts +418 -0
  64. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.ts +152 -0
  65. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.ts +251 -0
  66. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.ts +214 -0
  67. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +93 -0
  68. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +18 -6
  69. package/Tests/App/Dashboard/InvestigationInfrastructureTools.test.tsx +506 -0
  70. package/Tests/App/Dashboard/RemediationSuggestionCardDescription.test.tsx +209 -0
  71. package/Tests/App/Dashboard/RemediationSuggestionCardResource.test.tsx +317 -0
  72. package/Tests/App/Dashboard/ResourceAiAccessSettingsUtil.test.ts +921 -0
  73. package/Tests/App/Dashboard/ResourceAiAgentInstall.test.ts +860 -0
  74. package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +1721 -0
  75. package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +1002 -0
  76. package/Tests/App/Dashboard/ResourceAiInsightsPage.test.tsx +931 -0
  77. package/Tests/App/Dashboard/ResourceAiNavigation.test.tsx +575 -0
  78. package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +38 -7
  79. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +33 -1
  80. package/Tests/Models/DatabaseModels/ResourceAiAccessColumns.test.ts +738 -0
  81. package/Tests/Models/DatabaseModels/ResourceAiAgentModel.test.ts +842 -0
  82. package/Tests/Server/API/AutoRemediationApproveResourceRound.test.ts +835 -0
  83. package/Tests/Server/API/ResourceAiAccessAPI.test.ts +2730 -0
  84. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +60 -0
  85. package/Tests/Server/Infrastructure/Postgres/AddResourceAiAgentsMigration.test.ts +659 -0
  86. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +31 -0
  87. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +63 -5
  88. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +103 -5
  89. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +19 -1
  90. package/Tests/Server/Services/AutoRemediationResourceRuleEngine.test.ts +1279 -0
  91. package/Tests/Server/Services/DatabaseServerService.test.ts +55 -0
  92. package/Tests/Server/Services/GroupTelemetryUsageExcludeNames.test.ts +299 -0
  93. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +44 -0
  94. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +45 -6
  95. package/Tests/Server/Services/RecordingRuleReservedMetricName.test.ts +171 -0
  96. package/Tests/Server/Services/ResourceAiAccessChangeAuthorization.test.ts +256 -0
  97. package/Tests/Server/Services/ResourceAiAccessService.test.ts +1373 -0
  98. package/Tests/Server/Services/ResourceAiAgentJobService.test.ts +375 -0
  99. package/Tests/Server/Services/ResourceAiAgentServiceHelpers.test.ts +1114 -0
  100. package/Tests/Server/Services/ResourceAiAgentServiceLifecycle.test.ts +1050 -0
  101. package/Tests/Server/Services/ResourceAiAgentServiceRegister.test.ts +2251 -0
  102. package/Tests/Server/Services/ResourceAiSettingsCreate.test.ts +373 -0
  103. package/Tests/Server/Services/ResourceAiSettingsPermission.test.ts +1042 -0
  104. package/Tests/Server/Services/ResourceServiceDeleteCleansUpAi.test.ts +319 -0
  105. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +105 -0
  106. package/Tests/Server/Services/RunnerJobEnqueueResourceCommand.test.ts +986 -0
  107. package/Tests/Server/Services/RunnerJobResourceCommandLane.test.ts +211 -0
  108. package/Tests/Server/Services/RunnerJobResourceCommandTimeoutAndRedaction.test.ts +352 -0
  109. package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +218 -0
  110. package/Tests/Server/TestingUtils/Services/FakeRunnerJobCount.ts +145 -0
  111. package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +453 -0
  112. package/Tests/Server/Utils/AI/InvestigationInfrastructureReport.test.ts +721 -0
  113. package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +94 -1
  114. package/Tests/Server/Utils/AI/RemediationCommandToolsResource.test.ts +1530 -0
  115. package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +1249 -0
  116. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +117 -0
  117. package/Tests/Server/Utils/AI/RemediationResourceCopyParity.test.ts +463 -0
  118. package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +722 -0
  119. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAccessContext.test.ts +266 -0
  120. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.test.ts +1220 -0
  121. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.test.ts +569 -0
  122. package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +830 -0
  123. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorResource.test.ts +717 -0
  124. package/Tests/Server/Utils/AutoRemediation/RemediationVerifierResourceFollowUp.test.ts +338 -0
  125. package/Tests/Server/Utils/Monitor/Criteria/SessionReplayBudgetTemplateCriteria.test.ts +422 -0
  126. package/Tests/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.test.ts +1647 -0
  127. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +143 -1
  128. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +33 -3
  129. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsAccountNotLinked.test.ts +1093 -0
  130. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.test.ts +1049 -0
  131. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.test.ts +1676 -0
  132. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCards.test.ts +2884 -0
  133. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.test.ts +2490 -0
  134. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmit.test.ts +2293 -0
  135. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmitServerTimezone.test.ts +386 -0
  136. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.test.ts +1477 -0
  137. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.test.ts +1967 -0
  138. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsStripHtmlTags.test.ts +92 -0
  139. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsSubmittedFormRemoval.test.ts +1819 -0
  140. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.test.ts +1033 -0
  141. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezoneServerZone.test.ts +610 -0
  142. package/Tests/Server/Utils/Workspace/MicrosoftTeamsBotMessageHandling.test.ts +2610 -0
  143. package/Tests/Server/Utils/Workspace/MicrosoftTeamsCreateCommandsEndToEnd.test.ts +4672 -0
  144. package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +190 -65
  145. package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +10 -4
  146. package/Tests/Types/AutoRemediation/AiRemediationCommandPlanResourceCommand.test.ts +305 -0
  147. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +277 -13
  148. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +113 -0
  149. package/Tests/Types/Monitor/RumAlertTemplates.test.ts +773 -2
  150. package/Tests/Types/ResourceAiAgent/AiResourceType.test.ts +419 -0
  151. package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +517 -0
  152. package/Tests/Types/Runbook/RunbookStepType.test.ts +147 -8
  153. package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +769 -1
  154. package/Tests/Utils/AiRemediation/Resource/CephCommandPolicy.test.ts +1653 -0
  155. package/Tests/Utils/AiRemediation/Resource/CephOutputRedaction.test.ts +204 -0
  156. package/Tests/Utils/AiRemediation/Resource/DatabaseCommandPolicy.test.ts +1077 -0
  157. package/Tests/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.test.ts +558 -0
  158. package/Tests/Utils/AiRemediation/Resource/DatabaseQueryRedactor.test.ts +834 -0
  159. package/Tests/Utils/AiRemediation/Resource/DockerCliGrammar.test.ts +747 -0
  160. package/Tests/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.test.ts +1259 -0
  161. package/Tests/Utils/AiRemediation/Resource/DockerOutputRedaction.test.ts +386 -0
  162. package/Tests/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.test.ts +782 -0
  163. package/Tests/Utils/AiRemediation/Resource/GovcCommandPolicy.test.ts +2259 -0
  164. package/Tests/Utils/AiRemediation/Resource/HostCommandPolicy.test.ts +2019 -0
  165. package/Tests/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.test.ts +2497 -0
  166. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicy.test.ts +1305 -0
  167. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.test.ts +585 -0
  168. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactor.test.ts +334 -0
  169. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactorCopyParity.test.ts +98 -0
  170. package/Tests/Utils/AiRemediation/Resource/ResourcePolicyImportClosure.test.ts +391 -0
  171. package/Tests/Utils/AiRemediation/ResourceAiAgentPolicyCopyParity.test.ts +497 -0
  172. package/Tests/Utils/SessionReplay/SessionReplayBudgetMetricType.test.ts +383 -0
  173. package/Types/AI/ResourceAiAccessApi.ts +140 -0
  174. package/Types/AI/ResourceAiAccessPermissions.ts +126 -0
  175. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +183 -2
  176. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +55 -22
  177. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +35 -3
  178. package/Types/Monitor/RumAlertTemplates.ts +351 -4
  179. package/Types/ResourceAiAgent/AiResourceType.ts +310 -0
  180. package/Types/ResourceAiAgent/ResourceAiAccess.ts +574 -0
  181. package/Types/Rum/SessionReplayBudgetMetricType.ts +47 -0
  182. package/Types/Runbook/RunbookStepType.ts +29 -0
  183. package/Types/Telemetry/TelemetryIngestSurface.ts +14 -2
  184. package/Utils/AI/InvestigationReport.ts +121 -15
  185. package/Utils/AiRemediation/Resource/CephCommandPolicy.ts +1936 -0
  186. package/Utils/AiRemediation/Resource/DatabaseCommandPolicy.ts +166 -0
  187. package/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.ts +1532 -0
  188. package/Utils/AiRemediation/Resource/DatabaseQueryRedactor.ts +1394 -0
  189. package/Utils/AiRemediation/Resource/DockerCliGrammar.ts +1839 -0
  190. package/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.ts +490 -0
  191. package/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.ts +687 -0
  192. package/Utils/AiRemediation/Resource/GovcCommandPolicy.ts +2096 -0
  193. package/Utils/AiRemediation/Resource/HostCommandPolicy.ts +2982 -0
  194. package/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.ts +1679 -0
  195. package/Utils/AiRemediation/Resource/ResourceCommandPolicy.ts +851 -0
  196. package/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.ts +593 -0
  197. package/Utils/AiRemediation/Resource/ResourceOutputRedactor.ts +2812 -0
  198. package/Utils/SessionReplay/SessionReplayBudgetMetricType.ts +184 -0
  199. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
  200. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
  201. package/build/dist/Models/DatabaseModels/CephCluster.js +219 -0
  202. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  203. package/build/dist/Models/DatabaseModels/DatabaseServer.js +219 -0
  204. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
  205. package/build/dist/Models/DatabaseModels/DockerHost.js +219 -0
  206. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  207. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +219 -0
  208. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
  209. package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
  210. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  211. package/build/dist/Models/DatabaseModels/Host.js +219 -0
  212. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  213. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  214. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  215. package/build/dist/Models/DatabaseModels/PodmanHost.js +219 -0
  216. package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +219 -0
  218. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  219. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js +439 -0
  220. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js.map +1 -0
  221. package/build/dist/Models/DatabaseModels/RunnerJob.js +145 -0
  222. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  223. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +219 -0
  224. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
  225. package/build/dist/Server/API/AutoRemediationAPI.js +231 -1
  226. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  227. package/build/dist/Server/API/ResourceAiAccessAPI.js +1119 -0
  228. package/build/dist/Server/API/ResourceAiAccessAPI.js.map +1 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js +168 -0
  230. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js.map +1 -0
  231. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  232. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Semaphore.js +6 -0
  234. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  235. package/build/dist/Server/Middleware/TelemetryIngest.js +12 -5
  236. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  237. package/build/dist/Server/Services/AnalyticsDatabaseService.js +26 -1
  238. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  239. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +601 -3
  240. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  241. package/build/dist/Server/Services/CephClusterService.js +104 -0
  242. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  243. package/build/dist/Server/Services/DatabaseServerService.js +99 -0
  244. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -1
  245. package/build/dist/Server/Services/DockerHostService.js +104 -0
  246. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  247. package/build/dist/Server/Services/DockerSwarmClusterService.js +104 -0
  248. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  249. package/build/dist/Server/Services/HostService.js +114 -2
  250. package/build/dist/Server/Services/HostService.js.map +1 -1
  251. package/build/dist/Server/Services/MetricRecordingRuleService.js +46 -0
  252. package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -1
  253. package/build/dist/Server/Services/PodmanHostService.js +104 -0
  254. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  255. package/build/dist/Server/Services/ProxmoxClusterService.js +104 -0
  256. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  257. package/build/dist/Server/Services/ResourceAiAccessService.js +1028 -0
  258. package/build/dist/Server/Services/ResourceAiAccessService.js.map +1 -0
  259. package/build/dist/Server/Services/ResourceAiAgentJobService.js +173 -0
  260. package/build/dist/Server/Services/ResourceAiAgentJobService.js.map +1 -0
  261. package/build/dist/Server/Services/ResourceAiAgentService.js +1687 -0
  262. package/build/dist/Server/Services/ResourceAiAgentService.js.map +1 -0
  263. package/build/dist/Server/Services/RunnerJobService.js +344 -5
  264. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  265. package/build/dist/Server/Services/TelemetryUsageBillingService.js +26 -0
  266. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  267. package/build/dist/Server/Services/TraceRecordingRuleService.js +37 -0
  268. package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -1
  269. package/build/dist/Server/Services/VMwareVCenterService.js +104 -0
  270. package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
  271. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1029 -30
  272. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  273. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +633 -28
  274. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  275. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +21 -1
  276. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  277. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +330 -0
  278. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -0
  279. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js +160 -0
  280. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js.map +1 -0
  281. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js +33 -0
  282. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js.map +1 -0
  283. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js +640 -0
  284. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js.map +1 -0
  285. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js +226 -0
  286. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js.map +1 -0
  287. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +600 -0
  288. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -0
  289. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +50 -4
  290. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  291. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +55 -4
  292. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  293. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +55 -4
  294. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  295. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +290 -13
  296. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  297. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +29 -1
  298. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
  299. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +9 -1
  300. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  301. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js +627 -0
  302. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js.map +1 -0
  303. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +60 -5
  304. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -1
  305. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +19 -15
  306. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  307. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +19 -15
  308. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  309. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +17 -1
  310. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  311. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +183 -209
  312. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  313. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +19 -15
  314. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  315. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +233 -167
  316. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
  317. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +263 -110
  318. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  319. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js +129 -0
  320. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js.map +1 -0
  321. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js +266 -0
  322. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js.map +1 -0
  323. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js +258 -0
  324. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js.map +1 -0
  325. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js +104 -0
  326. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js.map +1 -0
  327. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js +183 -0
  328. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js.map +1 -0
  329. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js +123 -0
  330. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js.map +1 -0
  331. package/build/dist/Types/AI/ResourceAiAccessApi.js +30 -0
  332. package/build/dist/Types/AI/ResourceAiAccessApi.js.map +1 -0
  333. package/build/dist/Types/AI/ResourceAiAccessPermissions.js +98 -0
  334. package/build/dist/Types/AI/ResourceAiAccessPermissions.js.map +1 -0
  335. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +116 -29
  336. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  337. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +47 -21
  338. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  339. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +5 -0
  340. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  341. package/build/dist/Types/Monitor/RumAlertTemplates.js +199 -3
  342. package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
  343. package/build/dist/Types/ResourceAiAgent/AiResourceType.js +242 -0
  344. package/build/dist/Types/ResourceAiAgent/AiResourceType.js.map +1 -0
  345. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +275 -0
  346. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -0
  347. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js +45 -0
  348. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js.map +1 -0
  349. package/build/dist/Types/Runbook/RunbookStepType.js +27 -0
  350. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  351. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +13 -2
  352. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  353. package/build/dist/Utils/AI/InvestigationReport.js +71 -11
  354. package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
  355. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js +1462 -0
  356. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js.map +1 -0
  357. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js +122 -0
  358. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js.map +1 -0
  359. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js +1139 -0
  360. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js.map +1 -0
  361. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js +1060 -0
  362. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js.map +1 -0
  363. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js +1301 -0
  364. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js.map +1 -0
  365. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js +370 -0
  366. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js.map +1 -0
  367. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js +498 -0
  368. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js.map +1 -0
  369. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js +1565 -0
  370. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js.map +1 -0
  371. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js +2206 -0
  372. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js.map +1 -0
  373. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js +1129 -0
  374. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js.map +1 -0
  375. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js +565 -0
  376. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js.map +1 -0
  377. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js +408 -0
  378. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js.map +1 -0
  379. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js +1910 -0
  380. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js.map +1 -0
  381. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js +160 -0
  382. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js.map +1 -0
  383. package/package.json +1 -1
@@ -0,0 +1,931 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ afterEach,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ jest,
8
+ test,
9
+ } from "@jest/globals";
10
+ import {
11
+ act,
12
+ cleanup,
13
+ fireEvent,
14
+ render,
15
+ screen,
16
+ waitFor,
17
+ within,
18
+ } from "@testing-library/react";
19
+ import React from "react";
20
+ import {
21
+ MemoryRouter,
22
+ NavigateFunction,
23
+ Route as PageRoute,
24
+ Routes,
25
+ useNavigate,
26
+ } from "react-router-dom";
27
+ import ResourceAiInsightsPage from "../../../../App/FeatureSet/Dashboard/src/Components/ResourceAiAgent/ResourceAiInsightsPage";
28
+ import {
29
+ RESOURCE_AI_INSIGHTS_EMPTY_TITLE,
30
+ RESOURCE_AI_INSIGHTS_PAGE_TITLE,
31
+ RESOURCE_COMMAND_JOB_ORIGIN_LABELS,
32
+ ResourceAiInsights,
33
+ describeResourceCommandJobOrigin,
34
+ describeResourceFixType,
35
+ describeResourceInvestigationSubject,
36
+ getResourceAiInsightsEmptyDescription,
37
+ getResourceAiInsightsPageSubtitle,
38
+ getResourceCommandsEmptyMessage,
39
+ getResourceFixStatusLook,
40
+ getResourceInvestigationStatusLook,
41
+ getResourceInvestigationSummary,
42
+ parseResourceAiInsights,
43
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/ResourceAiAgent/ResourceAiInsights";
44
+ import {
45
+ ResourceAiAgentDescriptor,
46
+ getResourceAiAgentDescriptor,
47
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/ResourceAiAgent/ResourceAiAgentDescriptors";
48
+ import RouteMap from "../../../../App/FeatureSet/Dashboard/src/Utils/RouteMap";
49
+ import RunnerJob from "../../../Models/DatabaseModels/RunnerJob";
50
+ import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
51
+ import HTTPResponse from "../../../Types/API/HTTPResponse";
52
+ import Route from "../../../Types/API/Route";
53
+ import AIRunStatus from "../../../Types/AI/AIRunStatus";
54
+ import AutoRemediationSuggestionStatus from "../../../Types/AutoRemediation/AutoRemediationSuggestionStatus";
55
+ import AutoRemediationSuggestionType from "../../../Types/AutoRemediation/AutoRemediationSuggestionType";
56
+ import ListResult from "../../../Types/BaseDatabase/ListResult";
57
+ import {
58
+ Gray500,
59
+ Green500,
60
+ Red500,
61
+ Yellow500,
62
+ } from "../../../Types/BrandColors";
63
+ import { JSONObject } from "../../../Types/JSON";
64
+ import ObjectID from "../../../Types/ObjectID";
65
+ import Permission from "../../../Types/Permission";
66
+ import AiResourceType, {
67
+ ALL_AI_RESOURCE_TYPES,
68
+ } from "../../../Types/ResourceAiAgent/AiResourceType";
69
+ import {
70
+ ResourceAiAccessStatus,
71
+ ResourceAiRemediationMode,
72
+ } from "../../../Types/ResourceAiAgent/ResourceAiAccess";
73
+ import RunbookStepType from "../../../Types/Runbook/RunbookStepType";
74
+ import RunnerJobOrigin from "../../../Types/Runbook/RunnerJobOrigin";
75
+ import RunnerJobStatus from "../../../Types/Runbook/RunnerJobStatus";
76
+ import API from "../../../UI/Utils/API/API";
77
+ import ModelAPI from "../../../UI/Utils/ModelAPI/ModelAPI";
78
+ import PermissionUtil from "../../../UI/Utils/Permission";
79
+ import User from "../../../UI/Utils/User";
80
+ import { goTo, PROJECT_ID } from "./SideMenuHarness";
81
+
82
+ jest.mock("react-i18next", () => {
83
+ return {
84
+ useTranslation: () => {
85
+ return {
86
+ t: (value: string): string => {
87
+ return value;
88
+ },
89
+ };
90
+ },
91
+ };
92
+ });
93
+
94
+ /*
95
+ * A resource's AI Insights page (AI → Insights) renders for real on its
96
+ * real route, with the insights and status routes, the model API and the
97
+ * permission snapshot stubbed. It answers "what did OneUptime AI do on this
98
+ * resource?": the investigations of incidents and alerts here, the fixes
99
+ * AI proposed or applied, and every command it ran through the resource's
100
+ * AI agent — a RunnerJob table filtered on this resource's ResourceCommand
101
+ * jobs, with a permission fallback.
102
+ */
103
+
104
+ const WAIT_TIMEOUT: number = 20000;
105
+
106
+ const RESOURCE_ID: string = "44444444-0000-4000-8000-000000000004";
107
+ const OTHER_RESOURCE_ID: string = "44444444-0000-4000-8000-000000000005";
108
+ const INCIDENT_ID: string = "aaaaaaaa-0000-4000-8000-000000000001";
109
+ const ALERT_ID: string = "bbbbbbbb-0000-4000-8000-000000000002";
110
+ const RUN_ID: string = "66666666-0000-4000-8000-000000000006";
111
+ const SECOND_RUN_ID: string = "66666666-0000-4000-8000-000000000007";
112
+ const FIX_ID: string = "77777777-0000-4000-8000-000000000001";
113
+ const SECOND_FIX_ID: string = "77777777-0000-4000-8000-000000000002";
114
+
115
+ const INSIGHTS_ROUTE: string = "/resource-ai-access/insights";
116
+ const STATUS_ROUTE: string = "/resource-ai-access/status";
117
+
118
+ const CEPH: ResourceAiAgentDescriptor = getResourceAiAgentDescriptor(
119
+ AiResourceType.CephCluster,
120
+ );
121
+
122
+ const BASE_PERMISSIONS: Array<Permission> = [
123
+ Permission.Public,
124
+ Permission.User,
125
+ Permission.CurrentUser,
126
+ ];
127
+
128
+ function makeStatus(
129
+ overrides: Partial<ResourceAiAccessStatus> = {},
130
+ ): ResourceAiAccessStatus {
131
+ return {
132
+ resourceType: AiResourceType.CephCluster,
133
+ resourceId: RESOURCE_ID,
134
+ resourceName: "ceph-prod",
135
+ isAiInvestigationEnabled: true,
136
+ aiRemediationMode: ResourceAiRemediationMode.Disabled,
137
+ aiCommandAllowlist: [],
138
+ agent: null,
139
+ gaps: [],
140
+ isInvestigationReady: true,
141
+ isRemediationReady: false,
142
+ ...overrides,
143
+ };
144
+ }
145
+
146
+ function incidentInvestigation(): JSONObject {
147
+ return {
148
+ aiRunId: RUN_ID,
149
+ status: AIRunStatus.Completed,
150
+ analysisTldr: "osd.3 went down after its disk filled.",
151
+ createdAt: "2026-09-22T09:00:00.000Z",
152
+ completedAt: "2026-09-22T09:04:00.000Z",
153
+ incident: {
154
+ id: INCIDENT_ID,
155
+ title: "HEALTH_WARN on ceph-prod",
156
+ number: 42,
157
+ },
158
+ };
159
+ }
160
+
161
+ function alertInvestigation(): JSONObject {
162
+ return {
163
+ aiRunId: SECOND_RUN_ID,
164
+ status: AIRunStatus.Running,
165
+ createdAt: "2026-09-22T09:30:00.000Z",
166
+ alert: { id: ALERT_ID, title: "PGs degraded" },
167
+ };
168
+ }
169
+
170
+ function fullInsights(): JSONObject {
171
+ return {
172
+ resourceType: AiResourceType.CephCluster,
173
+ resourceId: RESOURCE_ID,
174
+ investigations: [incidentInvestigation(), alertInvestigation()],
175
+ fixes: [
176
+ {
177
+ id: FIX_ID,
178
+ status: AutoRemediationSuggestionStatus.Suggested,
179
+ executionMode: "Suggest",
180
+ suggestionType: AutoRemediationSuggestionType.CommandPlan,
181
+ rationale: "Mark osd.3 out so its PGs recover elsewhere.",
182
+ createdAt: "2026-09-22T09:05:00.000Z",
183
+ incidentId: INCIDENT_ID,
184
+ },
185
+ {
186
+ id: SECOND_FIX_ID,
187
+ status: AutoRemediationSuggestionStatus.AutoExecuted,
188
+ suggestionType: AutoRemediationSuggestionType.CommandPlan,
189
+ createdAt: "2026-09-22T09:35:00.000Z",
190
+ alertId: ALERT_ID,
191
+ },
192
+ ],
193
+ commandCounts: { investigation: 1717, remediation: 2929 },
194
+ };
195
+ }
196
+
197
+ function emptyInsights(): JSONObject {
198
+ return {
199
+ resourceType: AiResourceType.CephCluster,
200
+ resourceId: RESOURCE_ID,
201
+ investigations: [],
202
+ fixes: [],
203
+ commandCounts: { investigation: 0, remediation: 0 },
204
+ };
205
+ }
206
+
207
+ type Answer = () => Promise<HTTPResponse<JSONObject> | HTTPErrorResponse>;
208
+
209
+ function ok(body: unknown): Answer {
210
+ return async (): Promise<HTTPResponse<JSONObject>> => {
211
+ return new HTTPResponse<JSONObject>(200, body as JSONObject, {});
212
+ };
213
+ }
214
+
215
+ function httpError(statusCode: number, message: string): Answer {
216
+ return async (): Promise<HTTPErrorResponse> => {
217
+ return new HTTPErrorResponse(statusCode, { message }, {});
218
+ };
219
+ }
220
+
221
+ let postSpy: ReturnType<typeof jest.spyOn>;
222
+ let getListSpy: ReturnType<typeof jest.spyOn>;
223
+ let jobs: Array<RunnerJob> = [];
224
+ let insightsAnswers: Array<Answer> = [];
225
+ let statusAnswer: Answer = ok(makeStatus());
226
+
227
+ function serve(insights: Answer | Array<Answer>, status?: Answer): void {
228
+ insightsAnswers = Array.isArray(insights) ? [...insights] : [insights];
229
+ if (status) {
230
+ statusAnswer = status;
231
+ }
232
+ }
233
+
234
+ function postsTo(route: string): Array<JSONObject> {
235
+ return postSpy.mock.calls
236
+ .map((call: Array<unknown>): JSONObject => {
237
+ return (call[0] || {}) as JSONObject;
238
+ })
239
+ .filter((request: JSONObject): boolean => {
240
+ return String(request["url"]).endsWith(route);
241
+ });
242
+ }
243
+
244
+ interface ListRequest {
245
+ modelType?: unknown;
246
+ query?: Record<string, unknown> | undefined;
247
+ select?: Record<string, unknown> | undefined;
248
+ }
249
+
250
+ function jobListRequest(): ListRequest | undefined {
251
+ return getListSpy.mock.calls
252
+ .map((call: Array<unknown>): ListRequest => {
253
+ return (call[0] || {}) as ListRequest;
254
+ })
255
+ .find((request: ListRequest): boolean => {
256
+ return request.modelType === RunnerJob;
257
+ });
258
+ }
259
+
260
+ function makeJob(
261
+ id: string,
262
+ overrides: Partial<Record<keyof RunnerJob, unknown>> = {},
263
+ ): RunnerJob {
264
+ return Object.assign(
265
+ new RunnerJob(),
266
+ {
267
+ _id: id,
268
+ createdAt: new Date("2026-09-22T09:59:00Z"),
269
+ origin: RunnerJobOrigin.AiInvestigation,
270
+ stepType: RunbookStepType.ResourceCommand,
271
+ status: RunnerJobStatus.Succeeded,
272
+ payload: { displayCommand: `ceph osd tree ${id}` },
273
+ exitCode: 0,
274
+ },
275
+ overrides,
276
+ );
277
+ }
278
+
279
+ function grant(permissions: Array<Permission>): void {
280
+ jest.spyOn(User, "isMasterAdmin").mockReturnValue(false);
281
+ jest.spyOn(PermissionUtil, "getAllPermissions").mockReturnValue(permissions);
282
+ jest.spyOn(PermissionUtil, "getGlobalPermissions").mockReturnValue(null);
283
+ jest.spyOn(PermissionUtil, "getProjectPermissions").mockReturnValue({
284
+ projectId: new ObjectID(PROJECT_ID),
285
+ userId: ObjectID.generate(),
286
+ permissions: permissions.map((permission: Permission) => {
287
+ return {
288
+ permission: permission,
289
+ labelIds: [],
290
+ _type: "UserPermission",
291
+ };
292
+ }),
293
+ _type: "UserTenantAccessPermission",
294
+ } as unknown as ReturnType<typeof PermissionUtil.getProjectPermissions>);
295
+ }
296
+
297
+ let navigate: NavigateFunction | undefined;
298
+
299
+ function NavigationProbe(): React.ReactElement {
300
+ navigate = useNavigate();
301
+ return <></>;
302
+ }
303
+
304
+ function insightsPath(
305
+ descriptor: ResourceAiAgentDescriptor,
306
+ resourceId: string = RESOURCE_ID,
307
+ ): string {
308
+ return RouteMap[descriptor.insightsPage]!.toString()
309
+ .replace(":projectId", PROJECT_ID)
310
+ .replace(":id", resourceId);
311
+ }
312
+
313
+ function openInsightsPage(descriptor: ResourceAiAgentDescriptor = CEPH): void {
314
+ const path: string = insightsPath(descriptor);
315
+ goTo(path);
316
+
317
+ render(
318
+ <MemoryRouter initialEntries={[path]}>
319
+ <NavigationProbe />
320
+ <Routes>
321
+ <PageRoute
322
+ path={String(RouteMap[descriptor.insightsPage])}
323
+ element={
324
+ <ResourceAiInsightsPage
325
+ descriptor={descriptor}
326
+ pageRoute={RouteMap[descriptor.insightsPage] as Route}
327
+ currentProject={null}
328
+ hasPaymentMethod={true}
329
+ />
330
+ }
331
+ />
332
+ </Routes>
333
+ </MemoryRouter>,
334
+ );
335
+ }
336
+
337
+ async function findText(text: string | RegExp): Promise<HTMLElement> {
338
+ return await screen.findByText(text, {}, { timeout: WAIT_TIMEOUT });
339
+ }
340
+
341
+ async function findTestId(testId: string): Promise<HTMLElement> {
342
+ return await screen.findByTestId(testId, {}, { timeout: WAIT_TIMEOUT });
343
+ }
344
+
345
+ function hrefOf(element: HTMLElement): string {
346
+ return element.closest("a")?.getAttribute("href") || "";
347
+ }
348
+
349
+ beforeEach(() => {
350
+ window.localStorage.clear();
351
+ window.sessionStorage.clear();
352
+ grant([...BASE_PERMISSIONS, Permission.ProjectMember]);
353
+
354
+ statusAnswer = ok(makeStatus());
355
+ insightsAnswers = [ok(fullInsights())];
356
+
357
+ postSpy = jest.spyOn(API, "post");
358
+ postSpy.mockImplementation(
359
+ async (
360
+ request: unknown,
361
+ ): Promise<HTTPResponse<JSONObject> | HTTPErrorResponse> => {
362
+ const url: string = String((request as JSONObject)["url"]);
363
+ if (url.endsWith(INSIGHTS_ROUTE)) {
364
+ const answer: Answer =
365
+ insightsAnswers.length > 1
366
+ ? insightsAnswers.shift()!
367
+ : insightsAnswers[0]!;
368
+ return await answer();
369
+ }
370
+ if (url.endsWith(STATUS_ROUTE)) {
371
+ return await statusAnswer();
372
+ }
373
+ throw new Error(`Unexpected request to ${url}`);
374
+ },
375
+ );
376
+
377
+ jobs = [];
378
+ getListSpy = jest.spyOn(ModelAPI, "getList");
379
+ getListSpy.mockImplementation(
380
+ async (args: unknown): Promise<ListResult<RunnerJob>> => {
381
+ const request: ListRequest = args as ListRequest;
382
+ if (request.modelType === RunnerJob) {
383
+ return { data: jobs, count: jobs.length, skip: 0, limit: 10 };
384
+ }
385
+ return { data: [], count: 0, skip: 0, limit: 10 };
386
+ },
387
+ );
388
+ jest.spyOn(ModelAPI, "getCommonHeaders").mockReturnValue({});
389
+ });
390
+
391
+ afterEach(() => {
392
+ cleanup();
393
+ jest.restoreAllMocks();
394
+ });
395
+
396
+ describe("parseResourceAiInsights", () => {
397
+ test("reads the shape the insights route returns", () => {
398
+ const parsed: ResourceAiInsights | null =
399
+ parseResourceAiInsights(fullInsights());
400
+
401
+ expect(parsed?.investigations).toEqual([
402
+ {
403
+ aiRunId: RUN_ID,
404
+ status: AIRunStatus.Completed,
405
+ analysisTldr: "osd.3 went down after its disk filled.",
406
+ createdAt: "2026-09-22T09:00:00.000Z",
407
+ completedAt: "2026-09-22T09:04:00.000Z",
408
+ incident: {
409
+ id: INCIDENT_ID,
410
+ title: "HEALTH_WARN on ceph-prod",
411
+ number: 42,
412
+ },
413
+ alert: null,
414
+ },
415
+ {
416
+ aiRunId: SECOND_RUN_ID,
417
+ status: AIRunStatus.Running,
418
+ analysisTldr: null,
419
+ createdAt: "2026-09-22T09:30:00.000Z",
420
+ completedAt: null,
421
+ incident: null,
422
+ alert: { id: ALERT_ID, title: "PGs degraded" },
423
+ },
424
+ ]);
425
+ expect(parsed?.fixes[1]).toEqual({
426
+ id: SECOND_FIX_ID,
427
+ status: AutoRemediationSuggestionStatus.AutoExecuted,
428
+ executionMode: null,
429
+ suggestionType: AutoRemediationSuggestionType.CommandPlan,
430
+ rationale: null,
431
+ createdAt: "2026-09-22T09:35:00.000Z",
432
+ incidentId: null,
433
+ alertId: ALERT_ID,
434
+ approvedAt: null,
435
+ });
436
+ });
437
+
438
+ test.each([
439
+ ["null", null],
440
+ ["a string", "insights"],
441
+ ["an array", [incidentInvestigation()]],
442
+ ["an object with neither list", { commandCounts: {} }],
443
+ ])("refuses %s", (_label: string, value: unknown) => {
444
+ expect(parseResourceAiInsights(value)).toBeNull();
445
+ });
446
+
447
+ test("drops rows without an id, and reads ids in the serialized envelope", () => {
448
+ expect(
449
+ parseResourceAiInsights({
450
+ investigations: [
451
+ { status: AIRunStatus.Completed },
452
+ { aiRunId: { _type: "ObjectID", value: RUN_ID } },
453
+ "not a row",
454
+ ],
455
+ }),
456
+ ).toEqual({
457
+ investigations: [
458
+ {
459
+ aiRunId: RUN_ID,
460
+ status: null,
461
+ analysisTldr: null,
462
+ createdAt: null,
463
+ completedAt: null,
464
+ incident: null,
465
+ alert: null,
466
+ },
467
+ ],
468
+ fixes: [],
469
+ });
470
+ });
471
+ });
472
+
473
+ describe("the words on each row", () => {
474
+ test("every investigation and fix status has its label and colour", () => {
475
+ const palette: Array<unknown> = [Green500, Red500, Yellow500, Gray500];
476
+ for (const status of Object.values(AIRunStatus)) {
477
+ expect(
478
+ getResourceInvestigationStatusLook(status).label.length,
479
+ ).toBeGreaterThan(0);
480
+ expect(palette).toContain(
481
+ getResourceInvestigationStatusLook(status).color,
482
+ );
483
+ }
484
+ for (const status of Object.values(AutoRemediationSuggestionStatus)) {
485
+ expect(getResourceFixStatusLook(status).label.length).toBeGreaterThan(0);
486
+ expect(palette).toContain(getResourceFixStatusLook(status).color);
487
+ }
488
+ expect(getResourceInvestigationStatusLook(AIRunStatus.Running).label).toBe(
489
+ "Investigating",
490
+ );
491
+ expect(getResourceInvestigationStatusLook(AIRunStatus.Stale).label).toBe(
492
+ "Timed out",
493
+ );
494
+ expect(getResourceInvestigationStatusLook(AIRunStatus.Completed)).toEqual({
495
+ label: "Completed",
496
+ color: Green500,
497
+ });
498
+ expect(getResourceInvestigationStatusLook(AIRunStatus.Error)).toEqual({
499
+ label: "Failed",
500
+ color: Red500,
501
+ });
502
+ expect(
503
+ getResourceFixStatusLook(AutoRemediationSuggestionStatus.Suggested),
504
+ ).toEqual({ label: "Waiting for approval", color: Yellow500 });
505
+ // A status a newer server added shows as it is, in a neutral pill.
506
+ expect(getResourceInvestigationStatusLook("Brand new")).toEqual({
507
+ label: "Brand new",
508
+ color: Gray500,
509
+ });
510
+ expect(getResourceFixStatusLook("Brand new")).toEqual({
511
+ label: "Brand new",
512
+ color: Gray500,
513
+ });
514
+ });
515
+
516
+ test("the kind of fix, the subject and the finding", () => {
517
+ expect(
518
+ describeResourceFixType(AutoRemediationSuggestionType.CommandPlan),
519
+ ).toBe("Command plan");
520
+ expect(describeResourceFixType(AutoRemediationSuggestionType.Runbook)).toBe(
521
+ "Runbook",
522
+ );
523
+ expect(describeResourceFixType(null)).toBeNull();
524
+
525
+ const parsed: ResourceAiInsights = parseResourceAiInsights(fullInsights())!;
526
+ expect(
527
+ describeResourceInvestigationSubject(parsed.investigations[0]!),
528
+ ).toEqual({
529
+ text: "Incident #42: HEALTH_WARN on ceph-prod",
530
+ incidentId: INCIDENT_ID,
531
+ alertId: null,
532
+ });
533
+ expect(
534
+ describeResourceInvestigationSubject(parsed.investigations[1]!).text,
535
+ ).toBe("Alert: PGs degraded");
536
+ expect(getResourceInvestigationSummary(parsed.investigations[1]!)).toBe(
537
+ "Still investigating.",
538
+ );
539
+ expect(
540
+ getResourceInvestigationSummary({
541
+ ...parsed.investigations[1]!,
542
+ status: AIRunStatus.Error,
543
+ }),
544
+ ).toBe("No summary was recorded.");
545
+ });
546
+
547
+ test("a command's reason: an investigation, a connection test or a fix", () => {
548
+ expect(
549
+ describeResourceCommandJobOrigin({
550
+ origin: RunnerJobOrigin.AiInvestigation,
551
+ aiRunId: new ObjectID(RUN_ID),
552
+ }),
553
+ ).toBe("Investigation (read-only)");
554
+ expect(
555
+ describeResourceCommandJobOrigin({
556
+ origin: RunnerJobOrigin.AiInvestigation,
557
+ }),
558
+ ).toBe("Connection test (read-only)");
559
+ expect(
560
+ describeResourceCommandJobOrigin({
561
+ origin: RunnerJobOrigin.AiRemediation,
562
+ }),
563
+ ).toBe("Fix");
564
+ expect(describeResourceCommandJobOrigin({})).toBe("");
565
+ expect(RESOURCE_COMMAND_JOB_ORIGIN_LABELS).toEqual({
566
+ [RunnerJobOrigin.AiInvestigation]: "Investigation or connection test",
567
+ [RunnerJobOrigin.AiRemediation]: "Fix",
568
+ });
569
+ });
570
+ });
571
+
572
+ describe("the AI Insights page", () => {
573
+ test("shows its title and the resource's subtitle", async () => {
574
+ openInsightsPage();
575
+
576
+ expect(
577
+ screen.getByText(RESOURCE_AI_INSIGHTS_PAGE_TITLE),
578
+ ).toBeInTheDocument();
579
+ expect(
580
+ screen.getByText(getResourceAiInsightsPageSubtitle(CEPH)),
581
+ ).toBeInTheDocument();
582
+ expect(await findText("Investigations")).toBeInTheDocument();
583
+ });
584
+
585
+ test("asks the insights and status routes about this resource", async () => {
586
+ openInsightsPage();
587
+ await findText("Investigations");
588
+
589
+ expect(postsTo(INSIGHTS_ROUTE)).toHaveLength(1);
590
+ expect(postsTo(INSIGHTS_ROUTE)[0]!["data"]).toEqual({
591
+ resourceType: AiResourceType.CephCluster,
592
+ resourceId: RESOURCE_ID,
593
+ });
594
+ expect(String(postsTo(INSIGHTS_ROUTE)[0]!["url"])).toContain(
595
+ "/api/resource-ai-access/insights",
596
+ );
597
+ await waitFor(
598
+ () => {
599
+ expect(postsTo(STATUS_ROUTE)).toHaveLength(1);
600
+ },
601
+ { timeout: WAIT_TIMEOUT },
602
+ );
603
+ });
604
+
605
+ test("lists investigations and fixes, linked to their incident or alert", async () => {
606
+ openInsightsPage();
607
+ await findText("Investigations");
608
+
609
+ const investigations: Array<HTMLElement> = screen.getAllByTestId(
610
+ "ai-insights-investigation",
611
+ );
612
+ expect(investigations).toHaveLength(2);
613
+ expect(
614
+ hrefOf(
615
+ within(investigations[0]!).getByText(
616
+ "Incident #42: HEALTH_WARN on ceph-prod",
617
+ ),
618
+ ),
619
+ ).toBe(`/dashboard/${PROJECT_ID}/incidents/${INCIDENT_ID}`);
620
+ expect(
621
+ within(investigations[0]!).getByText(
622
+ "osd.3 went down after its disk filled.",
623
+ ),
624
+ ).toBeInTheDocument();
625
+ expect(
626
+ hrefOf(within(investigations[1]!).getByText("Alert: PGs degraded")),
627
+ ).toBe(`/dashboard/${PROJECT_ID}/alerts/${ALERT_ID}`);
628
+
629
+ const fixes: Array<HTMLElement> = screen.getAllByTestId("ai-insights-fix");
630
+ expect(fixes).toHaveLength(2);
631
+ expect(
632
+ within(fixes[0]!).getByText("Waiting for approval"),
633
+ ).toBeInTheDocument();
634
+ expect(within(fixes[0]!).getByText("Command plan")).toBeInTheDocument();
635
+ expect(hrefOf(within(fixes[0]!).getByText("Open incident"))).toBe(
636
+ `/dashboard/${PROJECT_ID}/incidents/${INCIDENT_ID}`,
637
+ );
638
+ expect(
639
+ within(fixes[1]!).getByText("No reason was recorded."),
640
+ ).toBeInTheDocument();
641
+ expect(hrefOf(within(fixes[1]!).getByText("Open alert"))).toBe(
642
+ `/dashboard/${PROJECT_ID}/alerts/${ALERT_ID}`,
643
+ );
644
+ // No KPI tiles: the counts are not rendered.
645
+ expect(screen.queryByText("1717")).not.toBeInTheDocument();
646
+ });
647
+
648
+ test("renders server text as text, never as markup", async () => {
649
+ serve(
650
+ ok({
651
+ investigations: [
652
+ {
653
+ aiRunId: RUN_ID,
654
+ analysisTldr: "<img src='x' onerror='window.pwned=1'>",
655
+ incident: {
656
+ id: INCIDENT_ID,
657
+ title: "<script>window.pwned=1</script>",
658
+ number: 1,
659
+ },
660
+ },
661
+ ],
662
+ fixes: [],
663
+ }),
664
+ );
665
+ openInsightsPage();
666
+
667
+ expect(
668
+ await findText("Incident #1: <script>window.pwned=1</script>"),
669
+ ).toBeInTheDocument();
670
+ expect(document.querySelector("img[src='x']")).toBeNull();
671
+ });
672
+
673
+ test("one empty state, pointing at the AI agent page when AI cannot run commands here", async () => {
674
+ serve(ok(emptyInsights()), ok(makeStatus({ isInvestigationReady: false })));
675
+ openInsightsPage();
676
+
677
+ expect(
678
+ await findText(RESOURCE_AI_INSIGHTS_EMPTY_TITLE),
679
+ ).toBeInTheDocument();
680
+ expect(
681
+ screen.getByText(getResourceAiInsightsEmptyDescription(CEPH)),
682
+ ).toBeInTheDocument();
683
+ expect(screen.queryByText("Investigations")).not.toBeInTheDocument();
684
+
685
+ const hint: HTMLElement = await findTestId("ai-insights-agent-hint");
686
+ expect(hint).toHaveTextContent(
687
+ "OneUptime AI can't run commands on this Ceph cluster right now.",
688
+ );
689
+ expect(hrefOf(within(hint).getByText("Open the AI agent page"))).toBe(
690
+ `/dashboard/${PROJECT_ID}/ceph/${RESOURCE_ID}/ai/agent`,
691
+ );
692
+ });
693
+
694
+ test("no pointer when AI is ready, or when the status cannot be read", async () => {
695
+ serve(ok(emptyInsights()));
696
+ openInsightsPage();
697
+ await findText(RESOURCE_AI_INSIGHTS_EMPTY_TITLE);
698
+ await waitFor(
699
+ () => {
700
+ expect(postsTo(STATUS_ROUTE)).toHaveLength(1);
701
+ },
702
+ { timeout: WAIT_TIMEOUT },
703
+ );
704
+ expect(
705
+ screen.queryByTestId("ai-insights-agent-hint"),
706
+ ).not.toBeInTheDocument();
707
+ cleanup();
708
+
709
+ serve(ok(emptyInsights()), httpError(500, "status is down"));
710
+ openInsightsPage();
711
+ await findText(RESOURCE_AI_INSIGHTS_EMPTY_TITLE);
712
+ expect(
713
+ screen.queryByTestId("ai-insights-agent-hint"),
714
+ ).not.toBeInTheDocument();
715
+ });
716
+
717
+ test("the pointer sits above the lists when there is history but AI is not ready", async () => {
718
+ serve(ok(fullInsights()), ok(makeStatus({ isInvestigationReady: false })));
719
+ openInsightsPage();
720
+
721
+ const hint: HTMLElement = await findTestId("ai-insights-agent-hint");
722
+ expect(screen.getByText("Investigations")).toBeInTheDocument();
723
+ expect(
724
+ hint.compareDocumentPosition(screen.getByText("Investigations")) &
725
+ Node.DOCUMENT_POSITION_FOLLOWING,
726
+ ).toBeTruthy();
727
+ });
728
+
729
+ test("shows the server's error and retries on request", async () => {
730
+ serve([
731
+ httpError(500, "The insights service is unavailable."),
732
+ ok(fullInsights()),
733
+ ]);
734
+ openInsightsPage();
735
+
736
+ expect(
737
+ await findText("The insights service is unavailable."),
738
+ ).toBeInTheDocument();
739
+ fireEvent.click(
740
+ within(screen.getByTestId("ai-insights-error")).getByTestId(
741
+ "refresh-button",
742
+ ),
743
+ );
744
+
745
+ expect(await findText("Investigations")).toBeInTheDocument();
746
+ expect(postsTo(INSIGHTS_ROUTE)).toHaveLength(2);
747
+ });
748
+
749
+ test("explains a body it cannot read", async () => {
750
+ serve(ok({ unexpected: true }));
751
+ openInsightsPage();
752
+
753
+ expect(
754
+ await findText("The server returned AI insights this page cannot read."),
755
+ ).toBeInTheDocument();
756
+ });
757
+
758
+ test("drops a late answer about the resource the user navigated away from", async () => {
759
+ let answerFirst: (() => void) | undefined;
760
+ let calls: number = 0;
761
+ postSpy.mockImplementation(
762
+ async (
763
+ request: unknown,
764
+ ): Promise<HTTPResponse<JSONObject> | HTTPErrorResponse> => {
765
+ const url: string = String((request as JSONObject)["url"]);
766
+ if (url.endsWith(STATUS_ROUTE)) {
767
+ return new HTTPResponse<JSONObject>(
768
+ 200,
769
+ makeStatus() as unknown as JSONObject,
770
+ {},
771
+ );
772
+ }
773
+ calls++;
774
+ if (calls === 1) {
775
+ await new Promise<void>((resolve: () => void) => {
776
+ answerFirst = resolve;
777
+ });
778
+ return new HTTPResponse<JSONObject>(200, fullInsights(), {});
779
+ }
780
+ return new HTTPResponse<JSONObject>(
781
+ 200,
782
+ { investigations: [alertInvestigation()], fixes: [] },
783
+ {},
784
+ );
785
+ },
786
+ );
787
+ openInsightsPage();
788
+
789
+ await waitFor(
790
+ () => {
791
+ expect(answerFirst).toBeDefined();
792
+ },
793
+ { timeout: WAIT_TIMEOUT },
794
+ );
795
+
796
+ act(() => {
797
+ navigate!(insightsPath(CEPH, OTHER_RESOURCE_ID));
798
+ });
799
+
800
+ expect(await findText("Alert: PGs degraded")).toBeInTheDocument();
801
+ expect(
802
+ postsTo(INSIGHTS_ROUTE).map((request: JSONObject): unknown => {
803
+ return (request["data"] as JSONObject)["resourceId"];
804
+ }),
805
+ ).toEqual([RESOURCE_ID, OTHER_RESOURCE_ID]);
806
+
807
+ await act(async () => {
808
+ answerFirst!();
809
+ await Promise.resolve();
810
+ });
811
+
812
+ expect(
813
+ screen.queryByText("Incident #42: HEALTH_WARN on ceph-prod"),
814
+ ).not.toBeInTheDocument();
815
+ });
816
+ });
817
+
818
+ describe("the commands table", () => {
819
+ test("lists this resource's ResourceCommand jobs with every column its cells read", async () => {
820
+ jobs = [
821
+ makeJob("investigation", { aiRunId: new ObjectID(RUN_ID) }),
822
+ makeJob("connection-test"),
823
+ makeJob("fix", {
824
+ origin: RunnerJobOrigin.AiRemediation,
825
+ aiRunId: new ObjectID(RUN_ID),
826
+ status: RunnerJobStatus.Failed,
827
+ exitCode: 1,
828
+ errorMessage: "Refused by the Ceph AI agent: this agent is read-only",
829
+ payload: { displayCommand: "ceph osd out 3" },
830
+ }),
831
+ ];
832
+ openInsightsPage();
833
+
834
+ expect(await findText("ceph osd tree investigation")).toBeInTheDocument();
835
+ expect(
836
+ screen.getByText("ceph osd tree connection-test"),
837
+ ).toBeInTheDocument();
838
+ const fixRow: HTMLElement | null = screen
839
+ .getByText("ceph osd out 3")
840
+ .closest("tr");
841
+ expect(within(fixRow!).getByText("Fix")).toBeInTheDocument();
842
+ expect(within(fixRow!).getByText("Failed (exit 1)")).toBeInTheDocument();
843
+ expect(
844
+ within(fixRow!).getByText(
845
+ "Refused by the Ceph AI agent: this agent is read-only",
846
+ ),
847
+ ).toBeInTheDocument();
848
+ expect(screen.getByText("Investigation (read-only)")).toBeInTheDocument();
849
+ expect(screen.getByText("Connection test (read-only)")).toBeInTheDocument();
850
+
851
+ const request: ListRequest | undefined = jobListRequest();
852
+ expect(request?.select).toEqual(
853
+ expect.objectContaining({
854
+ createdAt: true,
855
+ payload: true,
856
+ origin: true,
857
+ status: true,
858
+ aiRunId: true,
859
+ exitCode: true,
860
+ errorMessage: true,
861
+ }),
862
+ );
863
+ // Summaries only: never the command output.
864
+ expect(request?.select).not.toHaveProperty("output");
865
+ expect(request?.query).toEqual(
866
+ expect.objectContaining({
867
+ resourceType: AiResourceType.CephCluster,
868
+ stepType: RunbookStepType.ResourceCommand,
869
+ }),
870
+ );
871
+ expect(String(request?.query?.["resourceId"])).toBe(RESOURCE_ID);
872
+ // Never a Kubernetes cluster's kubectl jobs.
873
+ expect(request?.query).not.toHaveProperty("kubernetesClusterId");
874
+ });
875
+
876
+ test("is titled for the resource's tool and says when nothing ran yet", async () => {
877
+ openInsightsPage();
878
+
879
+ expect(
880
+ await findText(getResourceCommandsEmptyMessage(CEPH)),
881
+ ).toBeInTheDocument();
882
+ expect(screen.getAllByText("ceph commands").length).toBeGreaterThan(0);
883
+ });
884
+
885
+ for (const permission of [
886
+ Permission.SettingsAdmin,
887
+ Permission.SettingsViewer,
888
+ Permission.ReadCephCluster,
889
+ ]) {
890
+ test(`explains instead of failing for ${permission}`, async () => {
891
+ grant([...BASE_PERMISSIONS, permission]);
892
+ openInsightsPage();
893
+
894
+ const note: HTMLElement = await findTestId(
895
+ "resource-command-jobs-permission-note",
896
+ );
897
+ expect(note).toHaveTextContent("Runbook Viewer");
898
+ expect(jobListRequest()).toBeUndefined();
899
+ // The summaries still show: they come from the resource's own read gate.
900
+ expect(await findText("Investigations")).toBeInTheDocument();
901
+ });
902
+ }
903
+ });
904
+
905
+ describe("every resource type", () => {
906
+ test.each(ALL_AI_RESOURCE_TYPES)(
907
+ "%s: asks about its own type and filters the table on it",
908
+ async (type: AiResourceType) => {
909
+ const descriptor: ResourceAiAgentDescriptor =
910
+ getResourceAiAgentDescriptor(type);
911
+ openInsightsPage(descriptor);
912
+
913
+ expect(
914
+ await findText(getResourceCommandsEmptyMessage(descriptor)),
915
+ ).toBeInTheDocument();
916
+ expect(postsTo(INSIGHTS_ROUTE)[0]!["data"]).toEqual({
917
+ resourceType: type,
918
+ resourceId: RESOURCE_ID,
919
+ });
920
+ expect(jobListRequest()?.query).toEqual(
921
+ expect.objectContaining({
922
+ resourceType: type,
923
+ stepType: RunbookStepType.ResourceCommand,
924
+ }),
925
+ );
926
+ expect(
927
+ screen.getAllByText(descriptor.commandsCardTitle).length,
928
+ ).toBeGreaterThan(0);
929
+ },
930
+ );
931
+ });