@oneuptime/common 14.0.8 → 14.0.9

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 (639) hide show
  1. package/Models/DatabaseModels/Index.ts +2 -0
  2. package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
  3. package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
  4. package/Models/DatabaseModels/Project.ts +55 -5
  5. package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
  6. package/Models/DatabaseModels/RunnerJob.ts +75 -0
  7. package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
  8. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
  9. package/Server/API/AIChatAPI.ts +6 -3
  10. package/Server/API/AIInvestigationAPI.ts +17 -10
  11. package/Server/API/AutoRemediationAPI.ts +207 -25
  12. package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
  13. package/Server/API/MicrosoftTeamsAPI.ts +93 -14
  14. package/Server/API/ProxmoxResourceAPI.ts +244 -14
  15. package/Server/Infrastructure/GlobalCache.ts +32 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  20. package/Server/Middleware/TelemetryIngest.ts +41 -18
  21. package/Server/Services/AIService.ts +70 -1
  22. package/Server/Services/AlertService.ts +352 -11
  23. package/Server/Services/AlertSeverityService.ts +65 -15
  24. package/Server/Services/AutoRemediationRuleService.ts +1 -1
  25. package/Server/Services/IncidentService.ts +280 -104
  26. package/Server/Services/IncidentSeverityService.ts +66 -15
  27. package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
  28. package/Server/Services/KubernetesAiAgentService.ts +1634 -0
  29. package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
  30. package/Server/Services/KubernetesClusterService.ts +129 -21
  31. package/Server/Services/ProjectService.ts +32 -0
  32. package/Server/Services/ProxmoxResourceService.ts +322 -5
  33. package/Server/Services/RunnerJobService.ts +241 -104
  34. package/Server/Services/RunnerService.ts +1 -1
  35. package/Server/Services/ScheduledMaintenanceService.ts +737 -1
  36. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
  37. package/Server/Services/TeamComplianceSettingService.ts +1125 -9
  38. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
  39. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
  40. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
  41. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
  42. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
  43. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
  44. package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
  45. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
  46. package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
  47. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
  48. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
  49. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
  50. package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
  51. package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
  52. package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
  53. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
  54. package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
  55. package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
  56. package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
  57. package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
  58. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
  59. package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
  60. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
  61. package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
  62. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
  63. package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
  64. package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
  65. package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
  66. package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
  67. package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
  68. package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
  69. package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
  70. package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
  71. package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
  72. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
  73. package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
  74. package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
  75. package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
  76. package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
  77. package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
  78. package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
  79. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
  80. package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
  81. package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
  82. package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
  83. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
  84. package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
  85. package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
  86. package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
  87. package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
  88. package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
  89. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
  90. package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
  91. package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
  92. package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
  93. package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
  94. package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
  95. package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
  96. package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
  97. package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
  98. package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
  99. package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
  100. package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
  101. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
  102. package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
  103. package/Tests/App/Dashboard/EventNotes.test.tsx +2 -2
  104. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +587 -13
  105. package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
  106. package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
  107. package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
  108. package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
  109. package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
  110. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
  111. package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
  112. package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
  113. package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
  114. package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
  115. package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
  116. package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
  117. package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
  118. package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
  119. package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
  120. package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
  121. package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
  122. package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
  123. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
  124. package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
  125. package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
  126. package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
  127. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
  128. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
  129. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
  130. package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
  131. package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
  132. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
  133. package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
  134. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
  135. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
  136. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
  137. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
  138. package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
  139. package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
  140. package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
  141. package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
  142. package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
  143. package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
  144. package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
  145. package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
  146. package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
  147. package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
  148. package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
  149. package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
  150. package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
  151. package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
  152. package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
  153. package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
  154. package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
  155. package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
  156. package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
  157. package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
  158. package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
  159. package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
  160. package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
  161. package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
  162. package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
  163. package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
  164. package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
  165. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
  166. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
  167. package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
  168. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
  169. package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
  170. package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
  171. package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
  172. package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
  173. package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
  174. package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
  175. package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
  176. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
  177. package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
  178. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
  179. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
  180. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
  181. package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
  182. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
  183. package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
  184. package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
  185. package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
  186. package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
  187. package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
  188. package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
  189. package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
  190. package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
  191. package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
  192. package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
  193. package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
  194. package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
  195. package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
  196. package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
  197. package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
  198. package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
  199. package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
  200. package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
  201. package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
  202. package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
  203. package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
  204. package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
  205. package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
  206. package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
  207. package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
  208. package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
  209. package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
  210. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
  211. package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
  212. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
  213. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
  214. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
  215. package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
  216. package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
  217. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
  218. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
  219. package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
  220. package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
  221. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
  222. package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
  223. package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
  224. package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
  225. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
  226. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
  227. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
  228. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
  229. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
  230. package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
  231. package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
  232. package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
  233. package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
  234. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
  235. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
  236. package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
  237. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
  238. package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
  239. package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
  240. package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
  241. package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
  242. package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
  243. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
  244. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
  245. package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
  246. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
  247. package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
  248. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
  249. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
  250. package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
  251. package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
  252. package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
  253. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
  254. package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
  255. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
  256. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
  257. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
  258. package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
  259. package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
  260. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
  261. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
  262. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
  263. package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
  264. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
  265. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
  266. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
  267. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
  268. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
  269. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
  270. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
  271. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
  272. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
  273. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
  274. package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
  275. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
  276. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
  277. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
  278. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
  279. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
  280. package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
  281. package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
  282. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
  283. package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
  284. package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
  285. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
  286. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
  287. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
  288. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
  289. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
  290. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
  291. package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
  292. package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
  293. package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
  294. package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
  295. package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
  296. package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
  297. package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
  298. package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
  299. package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
  300. package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
  301. package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
  302. package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
  303. package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
  304. package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
  305. package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
  306. package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
  307. package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
  308. package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
  309. package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
  310. package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
  311. package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
  312. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
  313. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
  314. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
  315. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
  316. package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
  317. package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
  318. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
  319. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
  320. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
  321. package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
  322. package/Tests/UI/Components/Input.test.tsx +80 -1
  323. package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
  324. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
  325. package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
  326. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
  327. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
  328. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
  329. package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
  330. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
  331. package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
  332. package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
  333. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
  334. package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
  335. package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
  336. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
  337. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
  338. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
  339. package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
  340. package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
  341. package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
  342. package/Tests/UI/Components/Toggle.test.tsx +66 -0
  343. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
  344. package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
  345. package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
  346. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
  347. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
  348. package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
  349. package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
  350. package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
  351. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
  352. package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
  353. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
  354. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
  355. package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
  356. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
  357. package/Types/AI/AIRunType.ts +9 -4
  358. package/Types/AI/InvestigationNotStartedReason.ts +6 -0
  359. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
  360. package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
  361. package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
  362. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
  363. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
  364. package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
  365. package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
  366. package/Types/Team/ComplianceNotificationChannel.ts +22 -0
  367. package/Types/Team/ComplianceRule.ts +434 -0
  368. package/Types/Team/ComplianceRuleType.ts +21 -0
  369. package/Types/Team/TeamComplianceStatus.ts +81 -0
  370. package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
  371. package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
  372. package/UI/Components/ActionButton/RowActions.tsx +238 -0
  373. package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
  374. package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
  375. package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
  376. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
  377. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
  378. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
  379. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
  380. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
  381. package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
  382. package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
  383. package/UI/Components/Charts/Line/LineChart.tsx +32 -2
  384. package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
  385. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
  386. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
  387. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
  388. package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
  389. package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
  390. package/UI/Components/Charts/Utils/XAxis.ts +75 -0
  391. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
  392. package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
  393. package/UI/Components/Forms/Fields/FormField.tsx +10 -0
  394. package/UI/Components/Input/Input.tsx +90 -57
  395. package/UI/Components/List/ListRow.tsx +8 -74
  396. package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
  397. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
  398. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
  399. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
  400. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
  401. package/UI/Components/ModelTable/BaseModelTable.tsx +12 -1
  402. package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
  403. package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
  404. package/UI/Components/OrderedStatesList/Item.tsx +7 -72
  405. package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
  406. package/UI/Components/Table/TableBody.tsx +8 -2
  407. package/UI/Components/Table/TableRow.tsx +20 -140
  408. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
  409. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
  410. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
  411. package/UI/Components/TelemetryViewer/types.ts +2 -0
  412. package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
  413. package/UI/Components/Toggle/Toggle.tsx +26 -0
  414. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  415. package/UI/Styles/Theme.css +14 -2
  416. package/Utils/AiRemediation/CommandPolicy.ts +1 -1
  417. package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
  418. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
  419. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  420. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  421. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
  422. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
  423. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
  424. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  425. package/build/dist/Models/DatabaseModels/Project.js +56 -5
  426. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  427. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
  428. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
  429. package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
  430. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  431. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
  432. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  433. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  434. package/build/dist/Server/API/AIChatAPI.js +6 -1
  435. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  436. package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
  437. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  438. package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
  439. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  440. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
  441. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
  442. package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
  443. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  444. package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
  445. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
  446. package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
  447. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  448. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
  449. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
  450. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
  451. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
  452. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
  453. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
  454. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  455. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  456. package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
  457. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  458. package/build/dist/Server/Services/AIService.js +59 -1
  459. package/build/dist/Server/Services/AIService.js.map +1 -1
  460. package/build/dist/Server/Services/AlertService.js +248 -13
  461. package/build/dist/Server/Services/AlertService.js.map +1 -1
  462. package/build/dist/Server/Services/AlertSeverityService.js +51 -15
  463. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  464. package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
  465. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  466. package/build/dist/Server/Services/IncidentService.js +199 -72
  467. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  468. package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
  469. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  470. package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
  471. package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
  472. package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
  473. package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
  474. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
  475. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
  476. package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
  477. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  478. package/build/dist/Server/Services/ProjectService.js +22 -0
  479. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  480. package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
  481. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  482. package/build/dist/Server/Services/RunnerJobService.js +178 -92
  483. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  484. package/build/dist/Server/Services/RunnerService.js +1 -1
  485. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  486. package/build/dist/Server/Services/ScheduledMaintenanceService.js +535 -3
  487. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  488. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
  489. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  490. package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
  491. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  492. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
  493. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
  494. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
  495. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  496. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
  497. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  498. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
  499. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  500. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
  501. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  502. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
  503. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  504. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
  505. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
  506. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
  507. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
  508. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
  509. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
  510. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  511. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
  512. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  513. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
  514. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  515. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
  516. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
  517. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
  518. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
  519. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
  520. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  521. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
  522. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  523. package/build/dist/Types/AI/AIRunType.js +9 -4
  524. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  525. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
  526. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  527. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
  528. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
  529. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
  530. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  531. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
  532. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
  533. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
  534. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
  535. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
  536. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  537. package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
  538. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
  539. package/build/dist/Types/Team/ComplianceRule.js +304 -0
  540. package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
  541. package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
  542. package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
  543. package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
  544. package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
  545. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
  546. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  547. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
  548. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
  549. package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
  550. package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
  551. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
  552. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
  553. package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
  554. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  555. package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
  556. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  557. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
  558. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  559. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
  560. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  561. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
  562. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  563. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
  564. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  565. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
  566. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
  567. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
  568. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
  569. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
  570. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
  571. package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
  572. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  573. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
  574. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
  575. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
  576. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
  577. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
  578. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
  579. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
  580. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
  581. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
  582. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
  583. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
  584. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  585. package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
  586. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  587. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
  588. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
  589. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
  590. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  591. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
  592. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  593. package/build/dist/UI/Components/Input/Input.js +69 -54
  594. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  595. package/build/dist/UI/Components/List/ListRow.js +4 -39
  596. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  597. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
  598. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  599. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
  600. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  601. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
  602. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  603. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
  604. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  605. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  606. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  607. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +10 -0
  608. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  609. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
  610. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  611. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
  612. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  613. package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
  614. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  615. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
  616. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  617. package/build/dist/UI/Components/Table/TableBody.js +6 -2
  618. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  619. package/build/dist/UI/Components/Table/TableRow.js +12 -75
  620. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  621. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
  622. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  623. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
  624. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  625. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
  626. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  627. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
  628. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
  629. package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
  630. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  631. package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
  632. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  633. package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
  634. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
  635. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
  636. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
  637. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
  638. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
  639. package/package.json +1 -1
@@ -0,0 +1,884 @@
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
+ cleanup,
12
+ fireEvent,
13
+ render,
14
+ screen,
15
+ waitFor,
16
+ within,
17
+ } from "@testing-library/react";
18
+ import React from "react";
19
+ import { MemoryRouter } from "react-router-dom";
20
+ import AIFeatures, {
21
+ AI_FEATURES_CARD_TITLE,
22
+ AI_FEATURE_FIELDS,
23
+ canEditAiFeatures,
24
+ getAiFeaturesPermissionMessage,
25
+ getAiFeaturesUpdatePermissions,
26
+ } from "../../../../App/FeatureSet/Dashboard/src/Pages/Settings/AIFeatures";
27
+ import AICredits from "../../../../App/FeatureSet/Dashboard/src/Pages/Settings/AICredits";
28
+ import SettingsSideMenu from "../../../../App/FeatureSet/Dashboard/src/Pages/Settings/SideMenu";
29
+ import IncidentAISettings, {
30
+ POSTMORTEM_DRAFT_CARD_TITLE,
31
+ POSTMORTEM_DRAFT_FIELDS,
32
+ } from "../../../../App/FeatureSet/Dashboard/src/Pages/Incidents/Settings/IncidentAISettings";
33
+ import {
34
+ ProjectColumnsEditGate,
35
+ canUpdateProjectColumns,
36
+ getProjectColumnsEditGate,
37
+ getProjectColumnsPermissionMessage,
38
+ getProjectColumnsUpdatePermissions,
39
+ } from "../../../../App/FeatureSet/Dashboard/src/Pages/Settings/ProjectColumnEditGate";
40
+ import PageMap from "../../../../App/FeatureSet/Dashboard/src/Utils/PageMap";
41
+ import RouteMap, {
42
+ SettingsRoutePath,
43
+ } from "../../../../App/FeatureSet/Dashboard/src/Utils/RouteMap";
44
+ import { getSettingsBreadcrumbs } from "../../../../App/FeatureSet/Dashboard/src/Utils/Breadcrumbs/SettingsBreadcrumbs";
45
+ import Project from "../../../Models/DatabaseModels/Project";
46
+ import Route from "../../../Types/API/Route";
47
+ import ListResult from "../../../Types/BaseDatabase/ListResult";
48
+ import Link from "../../../Types/Link";
49
+ import ObjectID from "../../../Types/ObjectID";
50
+ import Permission from "../../../Types/Permission";
51
+ import ModelAPI from "../../../UI/Utils/ModelAPI/ModelAPI";
52
+ import PermissionUtil from "../../../UI/Utils/Permission";
53
+ import User from "../../../UI/Utils/User";
54
+ import {
55
+ MenuLink,
56
+ PROJECT_ID,
57
+ goTo,
58
+ linksIn,
59
+ renderMenu,
60
+ routeFor,
61
+ } from "./SideMenuHarness";
62
+
63
+ jest.mock("react-i18next", () => {
64
+ return {
65
+ useTranslation: () => {
66
+ return {
67
+ t: (value: string): string => {
68
+ return value;
69
+ },
70
+ };
71
+ },
72
+ };
73
+ });
74
+
75
+ /*
76
+ * Whether this install has billing on, pinned by the suite instead of read
77
+ * from the environment. The settings menu lists AI Credits only when
78
+ * BILLING_ENABLED is true, and that constant comes from process.env: CI's
79
+ * test-setup.sh writes BILLING_ENABLED=true into config.env and `npm test`
80
+ * exports it, while a bare `npx jest` leaves it unset. A menu assertion that
81
+ * inherited it would describe whichever machine ran it rather than the
82
+ * install the test names. A getter, so each test can choose.
83
+ */
84
+ let billingEnabledForTest: boolean = false;
85
+
86
+ jest.mock("../../../UI/Config", () => {
87
+ const actual: Record<string, unknown> = jest.requireActual(
88
+ "../../../UI/Config",
89
+ ) as Record<string, unknown>;
90
+
91
+ const mocked: Record<string, unknown> = { ...actual };
92
+
93
+ Object.defineProperty(mocked, "BILLING_ENABLED", {
94
+ get: (): boolean => {
95
+ return billingEnabledForTest;
96
+ },
97
+ });
98
+
99
+ return mocked;
100
+ });
101
+
102
+ /*
103
+ * The project's AI switches and where they live.
104
+ *
105
+ * Enable AI, Enable auto-remediation and Enable AI command execution used to
106
+ * sit on AI Credits (or, for auto-remediation, nowhere) — a page the settings
107
+ * menu lists only when billing is on, so on a self-hosted install the master
108
+ * switch was reachable only by typing its URL. They now live together on
109
+ * "Project Settings → AI Features", first in the AI section of the menu and
110
+ * outside the billing branch. The same pages are rendered for real here,
111
+ * with the model API and the permission snapshot stubbed.
112
+ *
113
+ * Also here: the incident AI settings page's new "Draft a postmortem
114
+ * automatically when an incident resolves" switch, which is its own card so
115
+ * it can never ride on (or be overwritten by) the investigation settings.
116
+ */
117
+
118
+ const WAIT_TIMEOUT: number = 20000;
119
+
120
+ const AI_FEATURES_PATH: string = `/dashboard/${PROJECT_ID}/settings/ai-features`;
121
+
122
+ const BASE_PERMISSIONS: Array<Permission> = [
123
+ Permission.Public,
124
+ Permission.User,
125
+ Permission.CurrentUser,
126
+ ];
127
+
128
+ const MOVED_SWITCH_TITLES: Array<string> = [
129
+ "Enable AI",
130
+ "Enable Auto-Remediation",
131
+ "Enable AI Command Execution (for Runners)",
132
+ ];
133
+
134
+ let project: Project;
135
+ let getItemSpy: ReturnType<typeof jest.spyOn>;
136
+ let createOrUpdateSpy: ReturnType<typeof jest.spyOn>;
137
+
138
+ function grant(permissions: Array<Permission>, isMasterAdmin?: boolean): void {
139
+ jest.spyOn(User, "isMasterAdmin").mockReturnValue(Boolean(isMasterAdmin));
140
+ jest.spyOn(PermissionUtil, "getAllPermissions").mockReturnValue(permissions);
141
+ jest.spyOn(PermissionUtil, "getGlobalPermissions").mockReturnValue(null);
142
+ jest.spyOn(PermissionUtil, "getProjectPermissions").mockReturnValue({
143
+ projectId: new ObjectID(PROJECT_ID),
144
+ userId: ObjectID.generate(),
145
+ permissions: permissions.map((permission: Permission) => {
146
+ return {
147
+ permission: permission,
148
+ labelIds: [],
149
+ _type: "UserPermission",
150
+ };
151
+ }),
152
+ _type: "UserTenantAccessPermission",
153
+ } as unknown as ReturnType<typeof PermissionUtil.getProjectPermissions>);
154
+ }
155
+
156
+ function renderPage(element: React.ReactElement, path: string): void {
157
+ goTo(path);
158
+ render(<MemoryRouter initialEntries={[path]}>{element}</MemoryRouter>);
159
+ }
160
+
161
+ function openAiFeatures(): void {
162
+ renderPage(
163
+ <AIFeatures
164
+ pageRoute={RouteMap[PageMap.SETTINGS_AI_FEATURES] as Route}
165
+ currentProject={null}
166
+ hasPaymentMethod={true}
167
+ />,
168
+ AI_FEATURES_PATH,
169
+ );
170
+ }
171
+
172
+ async function findText(text: string | RegExp): Promise<HTMLElement> {
173
+ return await screen.findByText(text, {}, { timeout: WAIT_TIMEOUT });
174
+ }
175
+
176
+ function itemRequests(): Array<{ select?: Record<string, unknown> }> {
177
+ return getItemSpy.mock.calls.map(
178
+ (call: Array<unknown>): { select?: Record<string, unknown> } => {
179
+ return (call[0] || {}) as { select?: Record<string, unknown> };
180
+ },
181
+ );
182
+ }
183
+
184
+ function postedProject(): Project {
185
+ return (createOrUpdateSpy.mock.calls[0]![0] as { model: Project }).model;
186
+ }
187
+
188
+ // A detail row's title, as ModelDetail renders it.
189
+ function detailTitle(title: string): HTMLElement {
190
+ return screen.getByText(title, { selector: "label > span" });
191
+ }
192
+
193
+ // The value a detail row shows, by its title.
194
+ function detailValue(title: string): string {
195
+ const row: HTMLElement | null =
196
+ detailTitle(title).closest("div.space-y-1")?.parentElement || null;
197
+ return row?.textContent || "";
198
+ }
199
+
200
+ async function openEditModal(buttonText: string): Promise<HTMLElement> {
201
+ const button: HTMLElement = await findText(buttonText);
202
+ fireEvent.click(button);
203
+ return await screen.findByRole("dialog", {}, { timeout: WAIT_TIMEOUT });
204
+ }
205
+
206
+ /*
207
+ * A form switch by its field title. The accessible name is the field label,
208
+ * which also carries "(Optional)" for a field that is not required.
209
+ */
210
+ function switchName(title: string): RegExp {
211
+ return new RegExp(
212
+ `^${title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}( \\(Optional\\))?$`,
213
+ );
214
+ }
215
+
216
+ async function waitForSwitch(
217
+ dialog: HTMLElement,
218
+ name: string,
219
+ checked: boolean,
220
+ ): Promise<HTMLElement> {
221
+ const toggle: HTMLElement = await within(dialog).findByRole(
222
+ "switch",
223
+ { name: switchName(name) },
224
+ { timeout: WAIT_TIMEOUT },
225
+ );
226
+ await waitFor(
227
+ () => {
228
+ expect(toggle).toHaveAttribute("aria-checked", String(checked));
229
+ },
230
+ { timeout: WAIT_TIMEOUT },
231
+ );
232
+ return toggle;
233
+ }
234
+
235
+ async function save(dialog: HTMLElement): Promise<void> {
236
+ fireEvent.click(within(dialog).getByTestId("modal-footer-submit-button"));
237
+ await waitFor(
238
+ () => {
239
+ expect(createOrUpdateSpy).toHaveBeenCalledTimes(1);
240
+ },
241
+ { timeout: WAIT_TIMEOUT },
242
+ );
243
+ }
244
+
245
+ beforeEach(() => {
246
+ window.localStorage.clear();
247
+ window.sessionStorage.clear();
248
+ grant([...BASE_PERMISSIONS, Permission.ProjectOwner]);
249
+
250
+ project = Object.assign(new Project(), {
251
+ _id: PROJECT_ID,
252
+ enableAi: true,
253
+ enableAutoRemediation: false,
254
+ enableAiCommandExecution: false,
255
+ enableAutomaticIncidentInvestigation: true,
256
+ enableAutomaticPostmortemDraft: false,
257
+ aiCurrentBalanceInUSDCents: 1250,
258
+ enableAutoRechargeAiBalance: false,
259
+ autoAiRechargeByBalanceInUSD: 20,
260
+ autoRechargeAiWhenCurrentBalanceFallsInUSD: 10,
261
+ });
262
+
263
+ getItemSpy = jest
264
+ .spyOn(ModelAPI, "getItem")
265
+ .mockImplementation(async (): Promise<Project> => {
266
+ return project;
267
+ });
268
+ jest
269
+ .spyOn(ModelAPI, "getList")
270
+ .mockImplementation(async (): Promise<ListResult<Project>> => {
271
+ return { data: [], count: 0, skip: 0, limit: 10 };
272
+ });
273
+ jest
274
+ .spyOn(ModelAPI, "count")
275
+ .mockImplementation(async (): Promise<number> => {
276
+ return 0;
277
+ });
278
+ createOrUpdateSpy = jest
279
+ .spyOn(ModelAPI, "createOrUpdate")
280
+ .mockImplementation(async (): Promise<never> => {
281
+ return { data: {} } as never;
282
+ });
283
+ });
284
+
285
+ afterEach(() => {
286
+ cleanup();
287
+ jest.restoreAllMocks();
288
+ billingEnabledForTest = false;
289
+ });
290
+
291
+ describe("who may change the AI features", () => {
292
+ test("follows the three columns' own update permissions", () => {
293
+ const project: Project = new Project();
294
+ const expected: Array<Permission> = (
295
+ project.getColumnAccessControlFor("enableAi")?.update || []
296
+ ).filter((permission: Permission): boolean => {
297
+ return AI_FEATURE_FIELDS.every((field: string): boolean => {
298
+ return (
299
+ project.getColumnAccessControlFor(field)?.update || []
300
+ ).includes(permission);
301
+ });
302
+ });
303
+
304
+ expect(AI_FEATURE_FIELDS).toEqual([
305
+ "enableAi",
306
+ "enableAutoRemediation",
307
+ "enableAiCommandExecution",
308
+ ]);
309
+ expect(getAiFeaturesUpdatePermissions()).toEqual(expected);
310
+ expect(getAiFeaturesUpdatePermissions()).toEqual([
311
+ Permission.ProjectOwner,
312
+ Permission.ManageProjectBilling,
313
+ ]);
314
+ });
315
+
316
+ test("is narrower than the Project table's update list", () => {
317
+ // Why the card cannot rely on CardModelDetail's table-level gate.
318
+ expect(new Project().getUpdatePermissions()).toEqual(
319
+ expect.arrayContaining([Permission.ProjectAdmin, Permission.EditProject]),
320
+ );
321
+ expect(getAiFeaturesUpdatePermissions()).not.toContain(
322
+ Permission.ProjectAdmin,
323
+ );
324
+ expect(getAiFeaturesUpdatePermissions()).not.toContain(
325
+ Permission.EditProject,
326
+ );
327
+ });
328
+
329
+ test.each([
330
+ [Permission.ProjectOwner, true],
331
+ [Permission.ManageProjectBilling, true],
332
+ [Permission.ProjectAdmin, false],
333
+ [Permission.EditProject, false],
334
+ [Permission.ProjectMember, false],
335
+ [Permission.Viewer, false],
336
+ ])("%s may change them: %s", (permission: Permission, allowed: boolean) => {
337
+ grant([...BASE_PERMISSIONS, permission]);
338
+ expect(canEditAiFeatures()).toBe(allowed);
339
+ });
340
+
341
+ test("a master admin may change them", () => {
342
+ grant([], true);
343
+ expect(canEditAiFeatures()).toBe(true);
344
+ });
345
+
346
+ test("the locked button's reason names the permissions", () => {
347
+ expect(getAiFeaturesPermissionMessage()).toBe(
348
+ "Changing these needs one of these permissions: Project Owner, Manage Billing.",
349
+ );
350
+ });
351
+ });
352
+
353
+ /*
354
+ * The shared gate the AI Features card and the postmortem draft card use in
355
+ * place of CardModelDetail's table-level one.
356
+ */
357
+ describe("the Project column edit gate", () => {
358
+ test("keeps the permissions every listed column allows, in the first column's order", () => {
359
+ expect(
360
+ getProjectColumnsUpdatePermissions([
361
+ "enableAi",
362
+ "enableAiCommandExecution",
363
+ ]),
364
+ ).toEqual([Permission.ProjectOwner, Permission.ManageProjectBilling]);
365
+ expect(
366
+ getProjectColumnsUpdatePermissions(["enableAutomaticPostmortemDraft"]),
367
+ ).toEqual([Permission.ProjectOwner, Permission.ProjectAdmin]);
368
+ });
369
+
370
+ test("columns with different update lists leave only what they share", () => {
371
+ // enableAi: Owner + Manage Billing; the postmortem switch: Owner + Admin.
372
+ expect(
373
+ getProjectColumnsUpdatePermissions([
374
+ "enableAi",
375
+ "enableAutomaticPostmortemDraft",
376
+ ]),
377
+ ).toEqual([Permission.ProjectOwner]);
378
+ });
379
+
380
+ test("no columns, or a column that declares nothing, lets nobody but a master admin in", () => {
381
+ expect(getProjectColumnsUpdatePermissions([])).toEqual([]);
382
+ expect(
383
+ getProjectColumnsUpdatePermissions(["notARealProjectColumn"]),
384
+ ).toEqual([]);
385
+ expect(canUpdateProjectColumns([])).toBe(false);
386
+
387
+ grant([], true);
388
+ expect(canUpdateProjectColumns([])).toBe(true);
389
+ });
390
+
391
+ test("names the permissions in the locked reason", () => {
392
+ expect(
393
+ getProjectColumnsPermissionMessage(["enableAutomaticPostmortemDraft"]),
394
+ ).toBe(
395
+ "Changing these needs one of these permissions: Project Owner, Project Admin.",
396
+ );
397
+ });
398
+
399
+ test("an allowed user gets the card's own button, not a locked one", () => {
400
+ const gate: ProjectColumnsEditGate = getProjectColumnsEditGate({
401
+ fields: ["enableAi"],
402
+ buttonTitle: "Edit",
403
+ });
404
+
405
+ expect(gate).toEqual({ isEditable: true, lockedButtons: [] });
406
+ });
407
+
408
+ test("anyone else gets one locked button that says why and does nothing", () => {
409
+ grant([...BASE_PERMISSIONS, Permission.ProjectAdmin]);
410
+
411
+ const gate: ProjectColumnsEditGate = getProjectColumnsEditGate({
412
+ fields: ["enableAi"],
413
+ buttonTitle: "Edit AI Features",
414
+ });
415
+
416
+ expect(gate.isEditable).toBe(false);
417
+ expect(gate.lockedButtons.length).toBe(1);
418
+ expect(gate.lockedButtons[0]).toMatchObject({
419
+ title: "Edit AI Features",
420
+ disabled: true,
421
+ tooltip:
422
+ "Changing these needs one of these permissions: Project Owner, Manage Billing.",
423
+ });
424
+ expect(() => {
425
+ gate.lockedButtons[0]!.onClick();
426
+ }).not.toThrow();
427
+ });
428
+
429
+ test("offers nothing before the permission snapshot has landed", () => {
430
+ grant([]);
431
+
432
+ expect(
433
+ getProjectColumnsEditGate({ fields: ["enableAi"], buttonTitle: "Edit" }),
434
+ ).toEqual({ isEditable: false, lockedButtons: [] });
435
+ });
436
+
437
+ test("a master admin may edit even with an empty snapshot", () => {
438
+ grant([], true);
439
+
440
+ expect(
441
+ getProjectColumnsEditGate({ fields: ["enableAi"], buttonTitle: "Edit" }),
442
+ ).toEqual({ isEditable: true, lockedButtons: [] });
443
+ });
444
+ });
445
+
446
+ describe("AI Features page", () => {
447
+ test("shows the three switches with the project's values", async () => {
448
+ openAiFeatures();
449
+
450
+ expect(await findText(AI_FEATURES_CARD_TITLE)).toBeInTheDocument();
451
+ expect(
452
+ screen.getByText("Turn OneUptime AI on or off for this project."),
453
+ ).toBeInTheDocument();
454
+
455
+ await waitFor(
456
+ () => {
457
+ expect(screen.getAllByText("Yes").length).toBe(1);
458
+ },
459
+ { timeout: WAIT_TIMEOUT },
460
+ );
461
+ expect(screen.getAllByText("No").length).toBe(2);
462
+
463
+ for (const title of MOVED_SWITCH_TITLES) {
464
+ expect(detailTitle(title)).toBeInTheDocument();
465
+ }
466
+ expect(detailValue("Enable AI")).toContain("Yes");
467
+ expect(detailValue("Enable Auto-Remediation")).toContain("No");
468
+ expect(detailValue("Enable AI Command Execution (for Runners)")).toContain(
469
+ "No",
470
+ );
471
+ });
472
+
473
+ test("reads the project it is on, and only the three switches", async () => {
474
+ openAiFeatures();
475
+ await findText("Edit AI Features");
476
+
477
+ await waitFor(
478
+ () => {
479
+ expect(itemRequests().length).toBeGreaterThan(0);
480
+ },
481
+ { timeout: WAIT_TIMEOUT },
482
+ );
483
+ const call: { id?: unknown; select?: Record<string, unknown> } = getItemSpy
484
+ .mock.calls[0]![0] as {
485
+ id?: unknown;
486
+ select?: Record<string, unknown>;
487
+ };
488
+ expect(String(call.id)).toBe(PROJECT_ID);
489
+ expect(Object.keys(call.select || {})).toEqual(
490
+ expect.arrayContaining([...AI_FEATURE_FIELDS]),
491
+ );
492
+ expect(call.select).not.toHaveProperty("aiCurrentBalanceInUSDCents");
493
+ });
494
+
495
+ test("a project owner edits all three in one form and saves exactly them", async () => {
496
+ openAiFeatures();
497
+
498
+ const dialog: HTMLElement = await openEditModal("Edit AI Features");
499
+ await waitForSwitch(dialog, "Enable AI", true);
500
+ const autoRemediation: HTMLElement = await waitForSwitch(
501
+ dialog,
502
+ "Enable Auto-Remediation",
503
+ false,
504
+ );
505
+ await waitForSwitch(
506
+ dialog,
507
+ "Enable AI Command Execution (for Runners)",
508
+ false,
509
+ );
510
+
511
+ expect(
512
+ within(dialog).getByText(/never proposes or applies a fix/),
513
+ ).toBeInTheDocument();
514
+ expect(
515
+ within(dialog).getByText(
516
+ /Kubernetes clusters connected through the Kubernetes AI agent do not need this/,
517
+ ),
518
+ ).toBeInTheDocument();
519
+ /*
520
+ * The switch also gates fixes on a cluster reached through an advanced
521
+ * Runner with a Kubernetes credential, so an owner of such a cluster must
522
+ * be told before switching it off.
523
+ */
524
+ expect(
525
+ within(dialog).getByText(
526
+ /Kubernetes clusters reached through a Runner with a Kubernetes credential/,
527
+ ),
528
+ ).toBeInTheDocument();
529
+ expect(
530
+ within(dialog).getByText(
531
+ /auto-remediation rules on the Runners you opt in/,
532
+ ),
533
+ ).toBeInTheDocument();
534
+
535
+ fireEvent.click(autoRemediation);
536
+ await save(dialog);
537
+
538
+ const posted: Project = postedProject();
539
+ expect(posted.enableAi).toBe(true);
540
+ expect(posted.enableAutoRemediation).toBe(true);
541
+ expect(posted.enableAiCommandExecution).toBe(false);
542
+ // Nothing else on the project rides along.
543
+ expect(posted.aiCurrentBalanceInUSDCents).toBeUndefined();
544
+ expect(posted.enableAutomaticIncidentInvestigation).toBeUndefined();
545
+ expect(posted.enableAutomaticPostmortemDraft).toBeUndefined();
546
+ });
547
+
548
+ test("someone with Manage Project Billing may edit", async () => {
549
+ grant([...BASE_PERMISSIONS, Permission.ManageProjectBilling]);
550
+ openAiFeatures();
551
+
552
+ const button: HTMLElement = await findText("Edit AI Features");
553
+ expect(button.closest("button")).not.toBeDisabled();
554
+ });
555
+
556
+ test("a master admin may edit", async () => {
557
+ grant([], true);
558
+ openAiFeatures();
559
+
560
+ const button: HTMLElement = await findText("Edit AI Features");
561
+ expect(button.closest("button")).not.toBeDisabled();
562
+ });
563
+
564
+ test.each([
565
+ [Permission.ProjectAdmin],
566
+ [Permission.ProjectMember],
567
+ [Permission.Viewer],
568
+ ])("is locked, with the reason, for %s", async (permission: Permission) => {
569
+ grant([...BASE_PERMISSIONS, permission]);
570
+ openAiFeatures();
571
+
572
+ await findText(AI_FEATURES_CARD_TITLE);
573
+ await waitFor(
574
+ () => {
575
+ expect(screen.getAllByText("No").length).toBe(2);
576
+ },
577
+ { timeout: WAIT_TIMEOUT },
578
+ );
579
+
580
+ const buttons: Array<HTMLElement> = screen
581
+ .getAllByText("Edit AI Features")
582
+ .map((text: HTMLElement): HTMLElement => {
583
+ return text.closest("button") as HTMLElement;
584
+ });
585
+ expect(buttons.length).toBe(1);
586
+ expect(buttons[0]).toBeDisabled();
587
+
588
+ fireEvent.click(buttons[0]!);
589
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
590
+ expect(createOrUpdateSpy).not.toHaveBeenCalled();
591
+ });
592
+
593
+ test("offers no button at all before the permission snapshot has landed", async () => {
594
+ grant([]);
595
+ openAiFeatures();
596
+
597
+ /*
598
+ * Telling somebody they need a permission they may well hold is worse
599
+ * than briefly not offering the button: neither the card's own gate nor
600
+ * the locked copy renders until the snapshot lands.
601
+ */
602
+ await findText(AI_FEATURES_CARD_TITLE);
603
+ await findText("Turn OneUptime AI on or off for this project.");
604
+ expect(screen.queryByText("Edit AI Features")).not.toBeInTheDocument();
605
+ });
606
+ });
607
+
608
+ describe("AI Credits page", () => {
609
+ function openAiCredits(): void {
610
+ renderPage(
611
+ <AICredits
612
+ pageRoute={RouteMap[PageMap.SETTINGS_AI_CREDITS] as Route}
613
+ currentProject={null}
614
+ hasPaymentMethod={true}
615
+ />,
616
+ `/dashboard/${PROJECT_ID}/settings/ai-credits`,
617
+ );
618
+ }
619
+
620
+ test("keeps the balance and recharge settings", async () => {
621
+ openAiCredits();
622
+
623
+ expect(await findText("Current Balance")).toBeInTheDocument();
624
+ expect(screen.getByText("Auto Recharge")).toBeInTheDocument();
625
+ expect(screen.getByText("Recharge Balance")).toBeInTheDocument();
626
+ });
627
+
628
+ test("no longer shows the switches that moved to AI Features", async () => {
629
+ openAiCredits();
630
+ await findText("Current Balance");
631
+
632
+ expect(screen.queryByText("Enable AI")).not.toBeInTheDocument();
633
+ expect(
634
+ screen.queryByText("Enable AI Command Execution"),
635
+ ).not.toBeInTheDocument();
636
+ expect(screen.queryByText("Edit AI Settings")).not.toBeInTheDocument();
637
+ expect(
638
+ screen.queryByText("Edit AI Command Execution Settings"),
639
+ ).not.toBeInTheDocument();
640
+
641
+ await waitFor(
642
+ () => {
643
+ expect(itemRequests().length).toBeGreaterThan(0);
644
+ },
645
+ { timeout: WAIT_TIMEOUT },
646
+ );
647
+ for (const request of itemRequests()) {
648
+ expect(request.select).not.toHaveProperty("enableAi");
649
+ expect(request.select).not.toHaveProperty("enableAiCommandExecution");
650
+ expect(request.select).not.toHaveProperty("enableAutoRemediation");
651
+ }
652
+ });
653
+ });
654
+
655
+ describe("settings menu, route and breadcrumbs", () => {
656
+ test("AI Features is the first item of the AI section, on an install without billing", async () => {
657
+ billingEnabledForTest = false;
658
+ goTo(AI_FEATURES_PATH);
659
+ await renderMenu(<SettingsSideMenu />);
660
+
661
+ const aiLinks: Array<MenuLink> = linksIn("AI");
662
+ expect(aiLinks[0]).toEqual({
663
+ title: "AI Features",
664
+ href: routeFor(PageMap.SETTINGS_AI_FEATURES),
665
+ });
666
+ // Billing is off: AI Credits is hidden, AI Features is not.
667
+ expect(
668
+ aiLinks.map((link: MenuLink): string => {
669
+ return link.title;
670
+ }),
671
+ ).not.toContain("AI Credits");
672
+ });
673
+
674
+ test("AI Features stays first on an install with billing, and AI Credits is listed after it", async () => {
675
+ billingEnabledForTest = true;
676
+ goTo(AI_FEATURES_PATH);
677
+ await renderMenu(<SettingsSideMenu />);
678
+
679
+ const aiLinks: Array<MenuLink> = linksIn("AI");
680
+ expect(aiLinks[0]).toEqual({
681
+ title: "AI Features",
682
+ href: routeFor(PageMap.SETTINGS_AI_FEATURES),
683
+ });
684
+ // Billing is on: the balance and recharge page joins the section.
685
+ expect(aiLinks.slice(1)).toContainEqual({
686
+ title: "AI Credits",
687
+ href: routeFor(PageMap.SETTINGS_AI_CREDITS),
688
+ });
689
+ });
690
+
691
+ test("lives at settings/ai-features", () => {
692
+ expect(SettingsRoutePath[PageMap.SETTINGS_AI_FEATURES]).toBe("ai-features");
693
+ expect(RouteMap[PageMap.SETTINGS_AI_FEATURES]?.toString()).toBe(
694
+ "/dashboard/:projectId/settings/ai-features",
695
+ );
696
+ expect(routeFor(PageMap.SETTINGS_AI_FEATURES)).toBe(AI_FEATURES_PATH);
697
+ });
698
+
699
+ test("has its own breadcrumbs", () => {
700
+ const links: Array<Link> | undefined = getSettingsBreadcrumbs(
701
+ RouteMap[PageMap.SETTINGS_AI_FEATURES]!.toString(),
702
+ );
703
+ expect(
704
+ (links || []).map((link: Link): string => {
705
+ return link.title;
706
+ }),
707
+ ).toEqual(["Project", "Settings", "AI Features"]);
708
+ });
709
+ });
710
+
711
+ describe("Incidents → Settings → AI", () => {
712
+ const POSTMORTEM_SWITCH: string =
713
+ "Draft a postmortem automatically when an incident resolves";
714
+
715
+ function openIncidentAiSettings(): void {
716
+ renderPage(
717
+ <IncidentAISettings
718
+ pageRoute={RouteMap[PageMap.INCIDENTS_SETTINGS_AI] as Route}
719
+ currentProject={null}
720
+ hasPaymentMethod={true}
721
+ />,
722
+ `/dashboard/${PROJECT_ID}/incidents/settings/ai`,
723
+ );
724
+ }
725
+
726
+ test("has its own card for the postmortem draft, off by default here", async () => {
727
+ openIncidentAiSettings();
728
+
729
+ expect(await findText("Automatic Postmortem Draft")).toBeInTheDocument();
730
+ expect(
731
+ screen.getByText(/This is separate from automatic investigation\./),
732
+ ).toBeInTheDocument();
733
+ await waitFor(
734
+ () => {
735
+ expect(detailTitle(POSTMORTEM_SWITCH)).toBeInTheDocument();
736
+ },
737
+ { timeout: WAIT_TIMEOUT },
738
+ );
739
+ await waitFor(
740
+ () => {
741
+ expect(detailValue(POSTMORTEM_SWITCH)).toContain("No");
742
+ },
743
+ { timeout: WAIT_TIMEOUT },
744
+ );
745
+ });
746
+
747
+ test("the investigation card no longer says anything about postmortems", async () => {
748
+ openIncidentAiSettings();
749
+
750
+ const investigationTitle: HTMLElement = await findText(
751
+ "Automatic Incident Investigation",
752
+ );
753
+ const card: HTMLElement | null = investigationTitle.closest(
754
+ '[data-testid="card"]',
755
+ );
756
+ expect(card).not.toBeNull();
757
+ expect(card!.textContent || "").not.toMatch(/postmortem/i);
758
+ });
759
+
760
+ test("turning the draft on saves only that switch", async () => {
761
+ openIncidentAiSettings();
762
+ await findText("Automatic Postmortem Draft");
763
+
764
+ const card: HTMLElement = (
765
+ await findText("Automatic Postmortem Draft")
766
+ ).closest('[data-testid="card"]') as HTMLElement;
767
+ fireEvent.click(within(card).getByText("Update"));
768
+ const dialog: HTMLElement = await screen.findByRole(
769
+ "dialog",
770
+ {},
771
+ { timeout: WAIT_TIMEOUT },
772
+ );
773
+ const toggle: HTMLElement = await waitForSwitch(
774
+ dialog,
775
+ POSTMORTEM_SWITCH,
776
+ false,
777
+ );
778
+ expect(
779
+ within(dialog).getByText(
780
+ /never replaces a postmortem that already exists/,
781
+ ),
782
+ ).toBeInTheDocument();
783
+
784
+ fireEvent.click(toggle);
785
+ await save(dialog);
786
+
787
+ const posted: Project = postedProject();
788
+ expect(posted.enableAutomaticPostmortemDraft).toBe(true);
789
+ expect(posted.enableAutomaticIncidentInvestigation).toBeUndefined();
790
+ expect(posted.incidentAiDailyAutonomousTokenLimit).toBeUndefined();
791
+ });
792
+
793
+ async function postmortemCard(): Promise<HTMLElement> {
794
+ return (await findText(POSTMORTEM_DRAFT_CARD_TITLE)).closest(
795
+ '[data-testid="card"]',
796
+ ) as HTMLElement;
797
+ }
798
+
799
+ test("the card is gated on exactly the column it writes", () => {
800
+ expect(POSTMORTEM_DRAFT_FIELDS).toEqual(["enableAutomaticPostmortemDraft"]);
801
+ expect(getProjectColumnsUpdatePermissions(POSTMORTEM_DRAFT_FIELDS)).toEqual(
802
+ new Project().getColumnAccessControlFor("enableAutomaticPostmortemDraft")
803
+ ?.update,
804
+ );
805
+ });
806
+
807
+ test.each([[Permission.ProjectOwner], [Permission.ProjectAdmin]])(
808
+ "%s gets a working Update button on the postmortem card",
809
+ async (permission: Permission) => {
810
+ grant([...BASE_PERMISSIONS, permission]);
811
+ openIncidentAiSettings();
812
+
813
+ const card: HTMLElement = await postmortemCard();
814
+ const buttons: Array<HTMLElement> = within(card)
815
+ .getAllByText("Update")
816
+ .map((text: HTMLElement): HTMLElement => {
817
+ return text.closest("button") as HTMLElement;
818
+ });
819
+ expect(buttons.length).toBe(1);
820
+ expect(buttons[0]).not.toBeDisabled();
821
+
822
+ fireEvent.click(buttons[0]!);
823
+ expect(
824
+ await screen.findByRole("dialog", {}, { timeout: WAIT_TIMEOUT }),
825
+ ).toBeInTheDocument();
826
+ },
827
+ );
828
+
829
+ /*
830
+ * The Project table's update list lets these two in, but the column does
831
+ * not: the card's own gate would give them a save the server refuses.
832
+ */
833
+ test.each([[Permission.ManageProjectBilling], [Permission.EditProject]])(
834
+ "%s sees the postmortem card's Update button locked, with the reason",
835
+ async (permission: Permission) => {
836
+ grant([...BASE_PERMISSIONS, permission]);
837
+ openIncidentAiSettings();
838
+
839
+ /*
840
+ * Neither role may read the column, so the card shows no value; the
841
+ * locked button is all there is to wait for.
842
+ */
843
+ const card: HTMLElement = await postmortemCard();
844
+ await within(card).findByText("Update", {}, { timeout: WAIT_TIMEOUT });
845
+
846
+ const buttons: Array<HTMLElement> = within(card)
847
+ .getAllByText("Update")
848
+ .map((text: HTMLElement): HTMLElement => {
849
+ return text.closest("button") as HTMLElement;
850
+ });
851
+ expect(buttons.length).toBe(1);
852
+ expect(buttons[0]).toBeDisabled();
853
+
854
+ fireEvent.click(buttons[0]!);
855
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
856
+ expect(createOrUpdateSpy).not.toHaveBeenCalled();
857
+ },
858
+ );
859
+
860
+ test("a viewer still reads the postmortem switch, behind a locked button", async () => {
861
+ grant([...BASE_PERMISSIONS, Permission.Viewer]);
862
+ openIncidentAiSettings();
863
+
864
+ const card: HTMLElement = await postmortemCard();
865
+ await waitFor(
866
+ () => {
867
+ expect(detailValue(POSTMORTEM_SWITCH)).toContain("No");
868
+ },
869
+ { timeout: WAIT_TIMEOUT },
870
+ );
871
+ const button: HTMLElement | null = within(card)
872
+ .getByText("Update")
873
+ .closest("button");
874
+ expect(button).toBeDisabled();
875
+ });
876
+
877
+ test("the postmortem card offers no button before the permission snapshot has landed", async () => {
878
+ grant([]);
879
+ openIncidentAiSettings();
880
+
881
+ const card: HTMLElement = await postmortemCard();
882
+ expect(within(card).queryByText("Update")).not.toBeInTheDocument();
883
+ });
884
+ });