@oneuptime/common 14.0.8 → 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 (976) 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 +4 -0
  9. package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
  10. package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
  11. package/Models/DatabaseModels/PodmanHost.ts +213 -0
  12. package/Models/DatabaseModels/Project.ts +55 -5
  13. package/Models/DatabaseModels/ProxmoxCluster.ts +213 -0
  14. package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
  15. package/Models/DatabaseModels/ResourceAiAgent.ts +419 -0
  16. package/Models/DatabaseModels/RunnerJob.ts +219 -0
  17. package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
  18. package/Models/DatabaseModels/VMwareVCenter.ts +213 -0
  19. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
  20. package/Server/API/AIChatAPI.ts +6 -3
  21. package/Server/API/AIInvestigationAPI.ts +17 -10
  22. package/Server/API/AutoRemediationAPI.ts +598 -25
  23. package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
  24. package/Server/API/MicrosoftTeamsAPI.ts +93 -14
  25. package/Server/API/ProxmoxResourceAPI.ts +244 -14
  26. package/Server/API/ResourceAiAccessAPI.ts +1669 -0
  27. package/Server/Infrastructure/GlobalCache.ts +32 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.ts +423 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  33. package/Server/Infrastructure/Semaphore.ts +22 -0
  34. package/Server/Middleware/TelemetryIngest.ts +51 -18
  35. package/Server/Services/AIService.ts +70 -1
  36. package/Server/Services/AlertService.ts +352 -11
  37. package/Server/Services/AlertSeverityService.ts +65 -15
  38. package/Server/Services/AnalyticsDatabaseService.ts +45 -1
  39. package/Server/Services/AutoRemediationRuleEngineService.ts +940 -112
  40. package/Server/Services/AutoRemediationRuleService.ts +1 -1
  41. package/Server/Services/CephClusterService.ts +109 -1
  42. package/Server/Services/DatabaseServerService.ts +116 -1
  43. package/Server/Services/DockerHostService.ts +109 -1
  44. package/Server/Services/DockerSwarmClusterService.ts +113 -1
  45. package/Server/Services/HostService.ts +126 -4
  46. package/Server/Services/IncidentService.ts +280 -104
  47. package/Server/Services/IncidentSeverityService.ts +66 -15
  48. package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
  49. package/Server/Services/KubernetesAiAgentService.ts +1634 -0
  50. package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
  51. package/Server/Services/KubernetesClusterService.ts +129 -21
  52. package/Server/Services/MetricRecordingRuleService.ts +47 -0
  53. package/Server/Services/PodmanHostService.ts +109 -1
  54. package/Server/Services/ProjectService.ts +32 -0
  55. package/Server/Services/ProxmoxClusterService.ts +110 -1
  56. package/Server/Services/ProxmoxResourceService.ts +322 -5
  57. package/Server/Services/ResourceAiAccessService.ts +1439 -0
  58. package/Server/Services/ResourceAiAgentJobService.ts +202 -0
  59. package/Server/Services/ResourceAiAgentService.ts +2432 -0
  60. package/Server/Services/RunnerJobService.ts +852 -108
  61. package/Server/Services/RunnerService.ts +1 -1
  62. package/Server/Services/ScheduledMaintenanceService.ts +737 -1
  63. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
  64. package/Server/Services/TeamComplianceSettingService.ts +1125 -9
  65. package/Server/Services/TelemetryUsageBillingService.ts +28 -0
  66. package/Server/Services/TraceRecordingRuleService.ts +33 -0
  67. package/Server/Services/VMwareVCenterService.ts +110 -1
  68. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
  69. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
  70. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
  71. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1475 -51
  72. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +1000 -43
  73. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +25 -1
  74. package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +577 -0
  75. package/Server/Utils/AI/ResourceAccess/ResourceAccessContext.ts +271 -0
  76. package/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.ts +45 -0
  77. package/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.ts +964 -0
  78. package/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.ts +333 -0
  79. package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +873 -0
  80. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +120 -7
  81. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +72 -5
  82. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +72 -5
  83. package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
  84. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
  85. package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
  86. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +402 -14
  87. package/Server/Utils/AutoRemediation/RemediationVerifier.ts +35 -2
  88. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +9 -1
  89. package/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.ts +920 -0
  90. package/Server/Utils/SessionReplay/SessionReplayUsage.ts +84 -6
  91. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
  92. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
  93. package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
  94. package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
  95. package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
  96. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +35 -15
  97. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +33 -15
  98. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +21 -1
  99. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +298 -224
  100. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +35 -15
  101. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +389 -175
  102. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +1073 -213
  103. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.ts +163 -0
  104. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.ts +396 -0
  105. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.ts +418 -0
  106. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.ts +152 -0
  107. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.ts +251 -0
  108. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.ts +214 -0
  109. package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
  110. package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
  111. package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
  112. package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
  113. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
  114. package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
  115. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
  116. package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
  117. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
  118. package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
  119. package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
  120. package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
  121. package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
  122. package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
  123. package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
  124. package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
  125. package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
  126. package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
  127. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +184 -19
  128. package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
  129. package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
  130. package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
  131. package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
  132. package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
  133. package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
  134. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
  135. package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
  136. package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
  137. package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
  138. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
  139. package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
  140. package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
  141. package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
  142. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +18 -6
  143. package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
  144. package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
  145. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
  146. package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
  147. package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
  148. package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
  149. package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
  150. package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
  151. package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
  152. package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
  153. package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
  154. package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
  155. package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
  156. package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
  157. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
  158. package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
  159. package/Tests/App/Dashboard/EventNotes.test.tsx +2 -2
  160. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +587 -13
  161. package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
  162. package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
  163. package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
  164. package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
  165. package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
  166. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
  167. package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
  168. package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
  169. package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
  170. package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
  171. package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
  172. package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
  173. package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
  174. package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
  175. package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
  176. package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
  177. package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
  178. package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
  179. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
  180. package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
  181. package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
  182. package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
  183. package/Tests/App/Dashboard/InvestigationInfrastructureTools.test.tsx +506 -0
  184. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
  185. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
  186. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
  187. package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
  188. package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
  189. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
  190. package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
  191. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
  192. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
  193. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
  194. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
  195. package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
  196. package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
  197. package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
  198. package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
  199. package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
  200. package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
  201. package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
  202. package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
  203. package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
  204. package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
  205. package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
  206. package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
  207. package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
  208. package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
  209. package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
  210. package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
  211. package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
  212. package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
  213. package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
  214. package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
  215. package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
  216. package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
  217. package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
  218. package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
  219. package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
  220. package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
  221. package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
  222. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
  223. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
  224. package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
  225. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
  226. package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
  227. package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
  228. package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
  229. package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
  230. package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
  231. package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
  232. package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
  233. package/Tests/App/Dashboard/RemediationSuggestionCardDescription.test.tsx +209 -0
  234. package/Tests/App/Dashboard/RemediationSuggestionCardResource.test.tsx +317 -0
  235. package/Tests/App/Dashboard/ResourceAiAccessSettingsUtil.test.ts +921 -0
  236. package/Tests/App/Dashboard/ResourceAiAgentInstall.test.ts +860 -0
  237. package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +1721 -0
  238. package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +1002 -0
  239. package/Tests/App/Dashboard/ResourceAiInsightsPage.test.tsx +931 -0
  240. package/Tests/App/Dashboard/ResourceAiNavigation.test.tsx +575 -0
  241. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
  242. package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
  243. package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +38 -7
  244. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
  245. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
  246. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
  247. package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
  248. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
  249. package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
  250. package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
  251. package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
  252. package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
  253. package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
  254. package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
  255. package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
  256. package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
  257. package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
  258. package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
  259. package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
  260. package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
  261. package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
  262. package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
  263. package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
  264. package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
  265. package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
  266. package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
  267. package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
  268. package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
  269. package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
  270. package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
  271. package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
  272. package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
  273. package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
  274. package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
  275. package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
  276. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
  277. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +33 -1
  278. package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
  279. package/Tests/Models/DatabaseModels/ResourceAiAccessColumns.test.ts +738 -0
  280. package/Tests/Models/DatabaseModels/ResourceAiAgentModel.test.ts +842 -0
  281. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
  282. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
  283. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
  284. package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
  285. package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
  286. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
  287. package/Tests/Server/API/AutoRemediationApproveResourceRound.test.ts +835 -0
  288. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
  289. package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
  290. package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
  291. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
  292. package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
  293. package/Tests/Server/API/ResourceAiAccessAPI.test.ts +2730 -0
  294. package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
  295. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +60 -0
  296. package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
  297. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
  298. package/Tests/Server/Infrastructure/Postgres/AddResourceAiAgentsMigration.test.ts +659 -0
  299. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
  300. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +31 -0
  301. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +119 -4
  302. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +196 -2
  303. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +123 -2
  304. package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
  305. package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
  306. package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
  307. package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
  308. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
  309. package/Tests/Server/Services/AutoRemediationResourceRuleEngine.test.ts +1279 -0
  310. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
  311. package/Tests/Server/Services/DatabaseServerService.test.ts +55 -0
  312. package/Tests/Server/Services/GroupTelemetryUsageExcludeNames.test.ts +299 -0
  313. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +44 -0
  314. package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
  315. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
  316. package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
  317. package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
  318. package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
  319. package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
  320. package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
  321. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
  322. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
  323. package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
  324. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
  325. package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
  326. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
  327. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
  328. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +45 -6
  329. package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
  330. package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
  331. package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
  332. package/Tests/Server/Services/RecordingRuleReservedMetricName.test.ts +171 -0
  333. package/Tests/Server/Services/ResourceAiAccessChangeAuthorization.test.ts +256 -0
  334. package/Tests/Server/Services/ResourceAiAccessService.test.ts +1373 -0
  335. package/Tests/Server/Services/ResourceAiAgentJobService.test.ts +375 -0
  336. package/Tests/Server/Services/ResourceAiAgentServiceHelpers.test.ts +1114 -0
  337. package/Tests/Server/Services/ResourceAiAgentServiceLifecycle.test.ts +1050 -0
  338. package/Tests/Server/Services/ResourceAiAgentServiceRegister.test.ts +2251 -0
  339. package/Tests/Server/Services/ResourceAiSettingsCreate.test.ts +373 -0
  340. package/Tests/Server/Services/ResourceAiSettingsPermission.test.ts +1042 -0
  341. package/Tests/Server/Services/ResourceServiceDeleteCleansUpAi.test.ts +319 -0
  342. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +153 -15
  343. package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
  344. package/Tests/Server/Services/RunnerJobEnqueueResourceCommand.test.ts +986 -0
  345. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
  346. package/Tests/Server/Services/RunnerJobResourceCommandLane.test.ts +211 -0
  347. package/Tests/Server/Services/RunnerJobResourceCommandTimeoutAndRedaction.test.ts +352 -0
  348. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
  349. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
  350. package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
  351. package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
  352. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
  353. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
  354. package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +218 -0
  355. package/Tests/Server/TestingUtils/Services/FakeRunnerJobCount.ts +145 -0
  356. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
  357. package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
  358. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
  359. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
  360. package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +453 -0
  361. package/Tests/Server/Utils/AI/InvestigationInfrastructureReport.test.ts +721 -0
  362. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
  363. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
  364. package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +94 -1
  365. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
  366. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
  367. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
  368. package/Tests/Server/Utils/AI/RemediationCommandToolsResource.test.ts +1530 -0
  369. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
  370. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
  371. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
  372. package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
  373. package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +1249 -0
  374. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
  375. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +117 -0
  376. package/Tests/Server/Utils/AI/RemediationResourceCopyParity.test.ts +463 -0
  377. package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +722 -0
  378. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAccessContext.test.ts +266 -0
  379. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.test.ts +1220 -0
  380. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.test.ts +569 -0
  381. package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +830 -0
  382. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
  383. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorResource.test.ts +717 -0
  384. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
  385. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
  386. package/Tests/Server/Utils/AutoRemediation/RemediationVerifierResourceFollowUp.test.ts +338 -0
  387. package/Tests/Server/Utils/Monitor/Criteria/SessionReplayBudgetTemplateCriteria.test.ts +422 -0
  388. package/Tests/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.test.ts +1647 -0
  389. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +143 -1
  390. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
  391. package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
  392. package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
  393. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
  394. package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
  395. package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
  396. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
  397. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +75 -3
  398. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsAccountNotLinked.test.ts +1093 -0
  399. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.test.ts +1049 -0
  400. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.test.ts +1676 -0
  401. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCards.test.ts +2884 -0
  402. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.test.ts +2490 -0
  403. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmit.test.ts +2293 -0
  404. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmitServerTimezone.test.ts +386 -0
  405. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.test.ts +1477 -0
  406. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.test.ts +1967 -0
  407. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsStripHtmlTags.test.ts +92 -0
  408. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsSubmittedFormRemoval.test.ts +1819 -0
  409. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.test.ts +1033 -0
  410. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezoneServerZone.test.ts +610 -0
  411. package/Tests/Server/Utils/Workspace/MicrosoftTeamsBotMessageHandling.test.ts +2610 -0
  412. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
  413. package/Tests/Server/Utils/Workspace/MicrosoftTeamsCreateCommandsEndToEnd.test.ts +4672 -0
  414. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
  415. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
  416. package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +190 -65
  417. package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +10 -4
  418. package/Tests/Types/AutoRemediation/AiRemediationCommandPlanResourceCommand.test.ts +305 -0
  419. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
  420. package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
  421. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +277 -13
  422. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +113 -0
  423. package/Tests/Types/Monitor/RumAlertTemplates.test.ts +773 -2
  424. package/Tests/Types/ResourceAiAgent/AiResourceType.test.ts +419 -0
  425. package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +517 -0
  426. package/Tests/Types/Runbook/RunbookStepType.test.ts +147 -8
  427. package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
  428. package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
  429. package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
  430. package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
  431. package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
  432. package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
  433. package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
  434. package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
  435. package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
  436. package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
  437. package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
  438. package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
  439. package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
  440. package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
  441. package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
  442. package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
  443. package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
  444. package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
  445. package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
  446. package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
  447. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
  448. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
  449. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
  450. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
  451. package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
  452. package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
  453. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
  454. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
  455. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
  456. package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
  457. package/Tests/UI/Components/Input.test.tsx +80 -1
  458. package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
  459. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
  460. package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
  461. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
  462. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
  463. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
  464. package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
  465. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
  466. package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
  467. package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
  468. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
  469. package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
  470. package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
  471. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
  472. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
  473. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
  474. package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
  475. package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
  476. package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
  477. package/Tests/UI/Components/Toggle.test.tsx +66 -0
  478. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
  479. package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
  480. package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
  481. package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +769 -1
  482. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
  483. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
  484. package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
  485. package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
  486. package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
  487. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
  488. package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
  489. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
  490. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
  491. package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
  492. package/Tests/Utils/AiRemediation/Resource/CephCommandPolicy.test.ts +1653 -0
  493. package/Tests/Utils/AiRemediation/Resource/CephOutputRedaction.test.ts +204 -0
  494. package/Tests/Utils/AiRemediation/Resource/DatabaseCommandPolicy.test.ts +1077 -0
  495. package/Tests/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.test.ts +558 -0
  496. package/Tests/Utils/AiRemediation/Resource/DatabaseQueryRedactor.test.ts +834 -0
  497. package/Tests/Utils/AiRemediation/Resource/DockerCliGrammar.test.ts +747 -0
  498. package/Tests/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.test.ts +1259 -0
  499. package/Tests/Utils/AiRemediation/Resource/DockerOutputRedaction.test.ts +386 -0
  500. package/Tests/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.test.ts +782 -0
  501. package/Tests/Utils/AiRemediation/Resource/GovcCommandPolicy.test.ts +2259 -0
  502. package/Tests/Utils/AiRemediation/Resource/HostCommandPolicy.test.ts +2019 -0
  503. package/Tests/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.test.ts +2497 -0
  504. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicy.test.ts +1305 -0
  505. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.test.ts +585 -0
  506. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactor.test.ts +334 -0
  507. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactorCopyParity.test.ts +98 -0
  508. package/Tests/Utils/AiRemediation/Resource/ResourcePolicyImportClosure.test.ts +391 -0
  509. package/Tests/Utils/AiRemediation/ResourceAiAgentPolicyCopyParity.test.ts +497 -0
  510. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
  511. package/Tests/Utils/SessionReplay/SessionReplayBudgetMetricType.test.ts +383 -0
  512. package/Types/AI/AIRunType.ts +9 -4
  513. package/Types/AI/InvestigationNotStartedReason.ts +6 -0
  514. package/Types/AI/ResourceAiAccessApi.ts +140 -0
  515. package/Types/AI/ResourceAiAccessPermissions.ts +126 -0
  516. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +197 -18
  517. package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
  518. package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
  519. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
  520. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
  521. package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
  522. package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
  523. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +55 -22
  524. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +35 -3
  525. package/Types/Monitor/RumAlertTemplates.ts +351 -4
  526. package/Types/ResourceAiAgent/AiResourceType.ts +310 -0
  527. package/Types/ResourceAiAgent/ResourceAiAccess.ts +574 -0
  528. package/Types/Rum/SessionReplayBudgetMetricType.ts +47 -0
  529. package/Types/Runbook/RunbookStepType.ts +29 -0
  530. package/Types/Team/ComplianceNotificationChannel.ts +22 -0
  531. package/Types/Team/ComplianceRule.ts +434 -0
  532. package/Types/Team/ComplianceRuleType.ts +21 -0
  533. package/Types/Team/TeamComplianceStatus.ts +81 -0
  534. package/Types/Telemetry/TelemetryIngestSurface.ts +40 -0
  535. package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
  536. package/UI/Components/ActionButton/RowActions.tsx +238 -0
  537. package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
  538. package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
  539. package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
  540. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
  541. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
  542. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
  543. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
  544. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
  545. package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
  546. package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
  547. package/UI/Components/Charts/Line/LineChart.tsx +32 -2
  548. package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
  549. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
  550. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
  551. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
  552. package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
  553. package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
  554. package/UI/Components/Charts/Utils/XAxis.ts +75 -0
  555. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
  556. package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
  557. package/UI/Components/Forms/Fields/FormField.tsx +10 -0
  558. package/UI/Components/Input/Input.tsx +90 -57
  559. package/UI/Components/List/ListRow.tsx +8 -74
  560. package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
  561. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
  562. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
  563. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
  564. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
  565. package/UI/Components/ModelTable/BaseModelTable.tsx +12 -1
  566. package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
  567. package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
  568. package/UI/Components/OrderedStatesList/Item.tsx +7 -72
  569. package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
  570. package/UI/Components/Table/TableBody.tsx +8 -2
  571. package/UI/Components/Table/TableRow.tsx +20 -140
  572. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
  573. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
  574. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
  575. package/UI/Components/TelemetryViewer/types.ts +2 -0
  576. package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
  577. package/UI/Components/Toggle/Toggle.tsx +26 -0
  578. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  579. package/UI/Styles/Theme.css +14 -2
  580. package/Utils/AI/InvestigationReport.ts +121 -15
  581. package/Utils/AiRemediation/CommandPolicy.ts +1 -1
  582. package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
  583. package/Utils/AiRemediation/Resource/CephCommandPolicy.ts +1936 -0
  584. package/Utils/AiRemediation/Resource/DatabaseCommandPolicy.ts +166 -0
  585. package/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.ts +1532 -0
  586. package/Utils/AiRemediation/Resource/DatabaseQueryRedactor.ts +1394 -0
  587. package/Utils/AiRemediation/Resource/DockerCliGrammar.ts +1839 -0
  588. package/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.ts +490 -0
  589. package/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.ts +687 -0
  590. package/Utils/AiRemediation/Resource/GovcCommandPolicy.ts +2096 -0
  591. package/Utils/AiRemediation/Resource/HostCommandPolicy.ts +2982 -0
  592. package/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.ts +1679 -0
  593. package/Utils/AiRemediation/Resource/ResourceCommandPolicy.ts +851 -0
  594. package/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.ts +593 -0
  595. package/Utils/AiRemediation/Resource/ResourceOutputRedactor.ts +2812 -0
  596. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
  597. package/Utils/SessionReplay/SessionReplayBudgetMetricType.ts +184 -0
  598. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
  599. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
  600. package/build/dist/Models/DatabaseModels/CephCluster.js +219 -0
  601. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  602. package/build/dist/Models/DatabaseModels/DatabaseServer.js +219 -0
  603. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
  604. package/build/dist/Models/DatabaseModels/DockerHost.js +219 -0
  605. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  606. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +219 -0
  607. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
  608. package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
  609. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  610. package/build/dist/Models/DatabaseModels/Host.js +219 -0
  611. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  612. package/build/dist/Models/DatabaseModels/Index.js +4 -0
  613. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  614. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
  615. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
  616. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
  617. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  618. package/build/dist/Models/DatabaseModels/PodmanHost.js +219 -0
  619. package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
  620. package/build/dist/Models/DatabaseModels/Project.js +56 -5
  621. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  622. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +219 -0
  623. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  624. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
  625. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
  626. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js +439 -0
  627. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js.map +1 -0
  628. package/build/dist/Models/DatabaseModels/RunnerJob.js +219 -0
  629. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  630. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
  631. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  632. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +219 -0
  633. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
  634. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  635. package/build/dist/Server/API/AIChatAPI.js +6 -1
  636. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  637. package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
  638. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  639. package/build/dist/Server/API/AutoRemediationAPI.js +374 -24
  640. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  641. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
  642. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
  643. package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
  644. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  645. package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
  646. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
  647. package/build/dist/Server/API/ResourceAiAccessAPI.js +1119 -0
  648. package/build/dist/Server/API/ResourceAiAccessAPI.js.map +1 -0
  649. package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
  650. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  651. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
  652. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
  653. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
  654. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
  655. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
  656. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
  657. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js +168 -0
  658. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js.map +1 -0
  659. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  660. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  661. package/build/dist/Server/Infrastructure/Semaphore.js +6 -0
  662. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  663. package/build/dist/Server/Middleware/TelemetryIngest.js +41 -19
  664. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  665. package/build/dist/Server/Services/AIService.js +59 -1
  666. package/build/dist/Server/Services/AIService.js.map +1 -1
  667. package/build/dist/Server/Services/AlertService.js +248 -13
  668. package/build/dist/Server/Services/AlertService.js.map +1 -1
  669. package/build/dist/Server/Services/AlertSeverityService.js +51 -15
  670. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  671. package/build/dist/Server/Services/AnalyticsDatabaseService.js +26 -1
  672. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  673. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +601 -3
  674. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  675. package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
  676. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  677. package/build/dist/Server/Services/CephClusterService.js +104 -0
  678. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  679. package/build/dist/Server/Services/DatabaseServerService.js +99 -0
  680. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -1
  681. package/build/dist/Server/Services/DockerHostService.js +104 -0
  682. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  683. package/build/dist/Server/Services/DockerSwarmClusterService.js +104 -0
  684. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  685. package/build/dist/Server/Services/HostService.js +114 -2
  686. package/build/dist/Server/Services/HostService.js.map +1 -1
  687. package/build/dist/Server/Services/IncidentService.js +199 -72
  688. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  689. package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
  690. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  691. package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
  692. package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
  693. package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
  694. package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
  695. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
  696. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
  697. package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
  698. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  699. package/build/dist/Server/Services/MetricRecordingRuleService.js +46 -0
  700. package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -1
  701. package/build/dist/Server/Services/PodmanHostService.js +104 -0
  702. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  703. package/build/dist/Server/Services/ProjectService.js +22 -0
  704. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  705. package/build/dist/Server/Services/ProxmoxClusterService.js +104 -0
  706. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  707. package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
  708. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  709. package/build/dist/Server/Services/ResourceAiAccessService.js +1028 -0
  710. package/build/dist/Server/Services/ResourceAiAccessService.js.map +1 -0
  711. package/build/dist/Server/Services/ResourceAiAgentJobService.js +173 -0
  712. package/build/dist/Server/Services/ResourceAiAgentJobService.js.map +1 -0
  713. package/build/dist/Server/Services/ResourceAiAgentService.js +1687 -0
  714. package/build/dist/Server/Services/ResourceAiAgentService.js.map +1 -0
  715. package/build/dist/Server/Services/RunnerJobService.js +522 -97
  716. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  717. package/build/dist/Server/Services/RunnerService.js +1 -1
  718. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  719. package/build/dist/Server/Services/ScheduledMaintenanceService.js +535 -3
  720. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  721. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
  722. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  723. package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
  724. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  725. package/build/dist/Server/Services/TelemetryUsageBillingService.js +26 -0
  726. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  727. package/build/dist/Server/Services/TraceRecordingRuleService.js +37 -0
  728. package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -1
  729. package/build/dist/Server/Services/VMwareVCenterService.js +104 -0
  730. package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
  731. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
  732. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
  733. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
  734. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  735. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
  736. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  737. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1056 -43
  738. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  739. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +683 -42
  740. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  741. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +21 -1
  742. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  743. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +330 -0
  744. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -0
  745. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js +160 -0
  746. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js.map +1 -0
  747. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js +33 -0
  748. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js.map +1 -0
  749. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js +640 -0
  750. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js.map +1 -0
  751. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js +226 -0
  752. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js.map +1 -0
  753. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +600 -0
  754. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -0
  755. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +89 -9
  756. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  757. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +55 -4
  758. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  759. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +55 -4
  760. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  761. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
  762. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
  763. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
  764. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
  765. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
  766. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +294 -15
  767. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  768. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +29 -1
  769. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
  770. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +9 -1
  771. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  772. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js +627 -0
  773. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js.map +1 -0
  774. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +60 -5
  775. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -1
  776. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
  777. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  778. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
  779. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  780. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
  781. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
  782. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
  783. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
  784. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
  785. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  786. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +19 -15
  787. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  788. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +19 -15
  789. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  790. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +17 -1
  791. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  792. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +183 -209
  793. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  794. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +19 -15
  795. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  796. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +233 -167
  797. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
  798. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +768 -152
  799. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  800. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js +129 -0
  801. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js.map +1 -0
  802. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js +266 -0
  803. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js.map +1 -0
  804. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js +258 -0
  805. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js.map +1 -0
  806. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js +104 -0
  807. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js.map +1 -0
  808. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js +183 -0
  809. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js.map +1 -0
  810. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js +123 -0
  811. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js.map +1 -0
  812. package/build/dist/Types/AI/AIRunType.js +9 -4
  813. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  814. package/build/dist/Types/AI/ResourceAiAccessApi.js +30 -0
  815. package/build/dist/Types/AI/ResourceAiAccessApi.js.map +1 -0
  816. package/build/dist/Types/AI/ResourceAiAccessPermissions.js +98 -0
  817. package/build/dist/Types/AI/ResourceAiAccessPermissions.js.map +1 -0
  818. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +126 -45
  819. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  820. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
  821. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
  822. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
  823. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  824. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
  825. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
  826. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
  827. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
  828. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
  829. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  830. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +47 -21
  831. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  832. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +5 -0
  833. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  834. package/build/dist/Types/Monitor/RumAlertTemplates.js +199 -3
  835. package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
  836. package/build/dist/Types/ResourceAiAgent/AiResourceType.js +242 -0
  837. package/build/dist/Types/ResourceAiAgent/AiResourceType.js.map +1 -0
  838. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +275 -0
  839. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -0
  840. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js +45 -0
  841. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js.map +1 -0
  842. package/build/dist/Types/Runbook/RunbookStepType.js +27 -0
  843. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  844. package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
  845. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
  846. package/build/dist/Types/Team/ComplianceRule.js +304 -0
  847. package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
  848. package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
  849. package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
  850. package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
  851. package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
  852. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +36 -0
  853. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  854. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
  855. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
  856. package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
  857. package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
  858. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
  859. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
  860. package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
  861. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  862. package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
  863. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  864. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
  865. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  866. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
  867. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  868. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
  869. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  870. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
  871. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  872. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
  873. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
  874. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
  875. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
  876. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
  877. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
  878. package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
  879. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  880. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
  881. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
  882. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
  883. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
  884. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
  885. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
  886. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
  887. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
  888. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
  889. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
  890. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
  891. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  892. package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
  893. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  894. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
  895. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
  896. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
  897. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  898. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
  899. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  900. package/build/dist/UI/Components/Input/Input.js +69 -54
  901. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  902. package/build/dist/UI/Components/List/ListRow.js +4 -39
  903. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  904. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
  905. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  906. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
  907. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  908. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
  909. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  910. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
  911. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  912. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  913. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  914. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +10 -0
  915. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  916. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
  917. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  918. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
  919. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  920. package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
  921. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  922. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
  923. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  924. package/build/dist/UI/Components/Table/TableBody.js +6 -2
  925. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  926. package/build/dist/UI/Components/Table/TableRow.js +12 -75
  927. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  928. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
  929. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  930. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
  931. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  932. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
  933. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  934. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
  935. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
  936. package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
  937. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  938. package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
  939. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  940. package/build/dist/Utils/AI/InvestigationReport.js +71 -11
  941. package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
  942. package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
  943. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
  944. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
  945. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
  946. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js +1462 -0
  947. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js.map +1 -0
  948. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js +122 -0
  949. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js.map +1 -0
  950. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js +1139 -0
  951. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js.map +1 -0
  952. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js +1060 -0
  953. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js.map +1 -0
  954. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js +1301 -0
  955. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js.map +1 -0
  956. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js +370 -0
  957. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js.map +1 -0
  958. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js +498 -0
  959. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js.map +1 -0
  960. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js +1565 -0
  961. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js.map +1 -0
  962. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js +2206 -0
  963. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js.map +1 -0
  964. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js +1129 -0
  965. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js.map +1 -0
  966. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js +565 -0
  967. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js.map +1 -0
  968. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js +408 -0
  969. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js.map +1 -0
  970. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js +1910 -0
  971. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js.map +1 -0
  972. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
  973. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
  974. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js +160 -0
  975. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js.map +1 -0
  976. package/package.json +1 -1
@@ -0,0 +1,2237 @@
1
+ import AlertSeverity from "../../../Models/DatabaseModels/AlertSeverity";
2
+ import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
3
+ import Entities from "../../../Models/DatabaseModels/Index";
4
+ import IncidentSeverity from "../../../Models/DatabaseModels/IncidentSeverity";
5
+ import TeamComplianceSetting from "../../../Models/DatabaseModels/TeamComplianceSetting";
6
+ import EnterpriseEdition from "../../../Server/Enterprise/EnterpriseEdition";
7
+ import PostgresAppInstance from "../../../Server/Infrastructure/PostgresDatabase";
8
+ import AlertSeverityService from "../../../Server/Services/AlertSeverityService";
9
+ import IncidentSeverityService from "../../../Server/Services/IncidentSeverityService";
10
+ import TeamComplianceSettingService, {
11
+ DeletedSeverity,
12
+ DUPLICATE_COMPLIANCE_RULE_MESSAGE,
13
+ SEVERITIES_DELETED_ENABLE_MESSAGE,
14
+ } from "../../../Server/Services/TeamComplianceSettingService";
15
+ import UpdateBy from "../../../Server/Types/Database/UpdateBy";
16
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
17
+ import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
18
+ import { JSONObject } from "../../../Types/JSON";
19
+ import ObjectID from "../../../Types/ObjectID";
20
+ import PositiveNumber from "../../../Types/PositiveNumber";
21
+ import Permission, {
22
+ UserTenantAccessPermission,
23
+ } from "../../../Types/Permission";
24
+ import ComplianceNotificationChannel from "../../../Types/Team/ComplianceNotificationChannel";
25
+ import { ComplianceSeverityKind } from "../../../Types/Team/ComplianceRule";
26
+ import ComplianceRuleType from "../../../Types/Team/ComplianceRuleType";
27
+ import UserType from "../../../Types/UserType";
28
+ import { DataSource, Logger } from "typeorm";
29
+
30
+ /*
31
+ * Team compliance rules scoped by severity and channel
32
+ * (TeamComplianceSetting.notificationChannel / incidentSeverities /
33
+ * alertSeverities) against a migrated Postgres.
34
+ *
35
+ * Opt in with RUN_POSTGRES_TEAM_COMPLIANCE_TESTS=true against a database the
36
+ * registered migrations (1796000000000-AddTeamComplianceRuleScope included)
37
+ * have been applied to, e.g. from packages/Common:
38
+ *
39
+ * RUN_POSTGRES_TEAM_COMPLIANCE_TESTS=true \
40
+ * TEAM_COMPLIANCE_TEST_DATABASE_HOST=127.0.0.1 \
41
+ * TEAM_COMPLIANCE_TEST_DATABASE_PORT=5400 \
42
+ * DATABASE_PASSWORD=... node node_modules/.bin/jest --runInBand \
43
+ * Tests/Server/Services/TeamComplianceSettingPostgres.test.ts --forceExit
44
+ *
45
+ * Without the flag the suite is skipped, so the Common test job - whose
46
+ * Postgres is not migrated - never runs it.
47
+ *
48
+ * Why a real Postgres: the unit tests of TeamComplianceSettingService mock
49
+ * every read, so they cannot show that the two ManyToMany join tables the
50
+ * migration created are the ones the model writes, that severity ids in each
51
+ * shape a caller sends (id strings, `{_id}` JSON, models) land as join rows,
52
+ * that the service's validation reads - an IN over uuid ids, a relation
53
+ * select - are SQL Postgres accepts, that an update replaces (or clears) the
54
+ * join rows, or that the ON DELETE CASCADE of both foreign keys holds.
55
+ *
56
+ * Two halves, as in IncidentStatusPageScopePostgres.test.ts:
57
+ *
58
+ * - the migrated public tables are inspected as they are: the new column, the
59
+ * foreign keys of the two join tables, and the dropped unique index;
60
+ * - behaviour runs in a uniquely named schema holding structure-only clones
61
+ * (LIKE ... INCLUDING ALL) of the tables involved. The join tables' foreign
62
+ * keys are copied from the migrated tables' own definitions, so a cascade
63
+ * observed here is the cascade the migration declared. No row is ever
64
+ * written outside that schema, and the schema is dropped afterwards.
65
+ *
66
+ * The production TeamComplianceSettingService runs unchanged: its hooks, the
67
+ * permission layer (as root, and as signed-in users), and the relation save -
68
+ * and so do IncidentSeverityService and AlertSeverityService, whose deletes
69
+ * pause a rule left with no severity instead of leaving it to widen to every
70
+ * severity - and mark it, so it cannot be turned back on as it is or be
71
+ * mistaken for a duplicate. Only realtime and workflow triggers are stubbed,
72
+ * and - for the signed-in half - the enterprise licence check, which reads a
73
+ * licence no test database has. Every statement Postgres rejects is recorded through the
74
+ * DataSource's logger and fails the test, even when a caller catches it.
75
+ */
76
+ const describePostgres: typeof describe =
77
+ process.env["RUN_POSTGRES_TEAM_COMPLIANCE_TESTS"] === "true"
78
+ ? describe
79
+ : describe.skip;
80
+
81
+ const TABLES: Array<string> = [
82
+ "Project",
83
+ "Team",
84
+ "IncidentSeverity",
85
+ "AlertSeverity",
86
+ "TeamComplianceSetting",
87
+ "TeamComplianceSettingIncidentSeverity",
88
+ "TeamComplianceSettingAlertSeverity",
89
+ ];
90
+
91
+ const JOIN_TABLES: Array<string> = [
92
+ "TeamComplianceSettingIncidentSeverity",
93
+ "TeamComplianceSettingAlertSeverity",
94
+ ];
95
+
96
+ const INCIDENT_JOIN_TABLE: string = "TeamComplianceSettingIncidentSeverity";
97
+ const ALERT_JOIN_TABLE: string = "TeamComplianceSettingAlertSeverity";
98
+
99
+ const FOREIGN_SEVERITY_MESSAGE: string =
100
+ "One or more of the selected incident severities do not exist in this project.";
101
+ const FOREIGN_ALERT_SEVERITY_MESSAGE: string =
102
+ "One or more of the selected alert severities do not exist in this project.";
103
+
104
+ // What a severity delete writes into the options of a rule it emptied.
105
+ const MARKED: JSONObject = { severitiesDeleted: true };
106
+
107
+ interface ForeignKeyRow {
108
+ table: string;
109
+ name: string;
110
+ column: string;
111
+ referencedTable: string;
112
+ onDelete: string;
113
+ definition: string;
114
+ }
115
+
116
+ type SqlRow = Record<string, unknown>;
117
+
118
+ // A severity list in any shape a caller sends it.
119
+ type SeverityList = Array<
120
+ string | JSONObject | IncidentSeverity | AlertSeverity
121
+ >;
122
+
123
+ interface RuleInput {
124
+ ruleType: ComplianceRuleType;
125
+ notificationChannel?: ComplianceNotificationChannel | null | undefined;
126
+ incidentSeverities?: SeverityList | undefined;
127
+ alertSeverities?: SeverityList | undefined;
128
+ enabled?: boolean | undefined;
129
+ teamId?: ObjectID | undefined;
130
+ projectId?: ObjectID | undefined;
131
+ }
132
+
133
+ // A rule row as Postgres stores it, with its join rows.
134
+ interface StoredRule {
135
+ ruleType: string;
136
+ notificationChannel: string | null;
137
+ enabled: boolean;
138
+ incidentSeverityIds: Array<string>;
139
+ alertSeverityIds: Array<string>;
140
+ }
141
+
142
+ interface ReadSeverity {
143
+ id: string;
144
+ name: string | undefined;
145
+ color: string | undefined;
146
+ order: number | undefined;
147
+ }
148
+
149
+ /*
150
+ * Records every statement Postgres rejected. PostgresQueryRunner hands each
151
+ * failure to the logger before it throws, whatever the caller then does with
152
+ * the error.
153
+ */
154
+ class FailedQueryRecorder implements Logger {
155
+ public failures: Array<string> = [];
156
+
157
+ public logQuery(): void {
158
+ return;
159
+ }
160
+
161
+ public logQueryError(error: string | Error, query: string): void {
162
+ this.failures.push(
163
+ `${error instanceof Error ? error.message : error} in: ${query}`,
164
+ );
165
+ }
166
+
167
+ public logQuerySlow(): void {
168
+ return;
169
+ }
170
+
171
+ public logSchemaBuild(): void {
172
+ return;
173
+ }
174
+
175
+ public logMigration(): void {
176
+ return;
177
+ }
178
+
179
+ public log(): void {
180
+ return;
181
+ }
182
+ }
183
+
184
+ const ROOT: DatabaseCommonInteractionProps = { isRoot: true };
185
+
186
+ describePostgres(
187
+ "Team compliance rule scope (severities + channel) against a migrated Postgres",
188
+ () => {
189
+ const schema: string = `team_compliance_${ObjectID.generate()
190
+ .toString()
191
+ .replace(/-/g, "")}`;
192
+
193
+ const failedQueries: FailedQueryRecorder = new FailedQueryRecorder();
194
+
195
+ const projectId: ObjectID = ObjectID.generate();
196
+ const otherProjectId: ObjectID = ObjectID.generate();
197
+ const teamId: ObjectID = ObjectID.generate();
198
+ const otherTeamId: ObjectID = ObjectID.generate();
199
+ const otherProjectTeamId: ObjectID = ObjectID.generate();
200
+
201
+ // Incident severities of the project, most severe first.
202
+ const critical: ObjectID = ObjectID.generate();
203
+ const major: ObjectID = ObjectID.generate();
204
+ const minor: ObjectID = ObjectID.generate();
205
+
206
+ const criticalAlert: ObjectID = ObjectID.generate();
207
+ const warningAlert: ObjectID = ObjectID.generate();
208
+
209
+ // Severities of ANOTHER project.
210
+ const otherProjectCritical: ObjectID = ObjectID.generate();
211
+ const otherProjectCriticalAlert: ObjectID = ObjectID.generate();
212
+
213
+ let database: DataSource;
214
+ let migratedForeignKeys: Array<ForeignKeyRow> = [];
215
+
216
+ beforeAll(async () => {
217
+ database = new DataSource({
218
+ type: "postgres",
219
+ host: process.env["TEAM_COMPLIANCE_TEST_DATABASE_HOST"] || "localhost",
220
+ port: Number(
221
+ process.env["TEAM_COMPLIANCE_TEST_DATABASE_PORT"] || "5400",
222
+ ),
223
+ username: process.env["DATABASE_USERNAME"] || "postgres",
224
+ password: process.env["DATABASE_PASSWORD"] || "password",
225
+ database:
226
+ process.env["TEAM_COMPLIANCE_TEST_DATABASE_NAME"] ||
227
+ process.env["DATABASE_NAME"] ||
228
+ "oneuptimedb",
229
+ entities: Entities,
230
+ schema,
231
+ synchronize: false,
232
+ logging: ["error"],
233
+ logger: failedQueries,
234
+ extra: { options: `-c search_path=${schema},public` },
235
+ });
236
+ await database.initialize();
237
+
238
+ /*
239
+ * Read the migrated foreign keys before the clones exist, so their
240
+ * definitions name the referenced tables without a schema.
241
+ */
242
+ migratedForeignKeys = await database.query(
243
+ `SELECT t.relname AS "table",
244
+ c.conname AS "name",
245
+ a.attname AS "column",
246
+ r.relname AS "referencedTable",
247
+ c.confdeltype AS "onDelete",
248
+ pg_get_constraintdef(c.oid) AS "definition"
249
+ FROM pg_constraint c
250
+ JOIN pg_class t ON t.oid = c.conrelid
251
+ JOIN pg_namespace n ON n.oid = t.relnamespace
252
+ JOIN pg_class r ON r.oid = c.confrelid
253
+ JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = c.conkey[1]
254
+ WHERE n.nspname = 'public' AND t.relname = ANY($1) AND c.contype = 'f'
255
+ ORDER BY t.relname, a.attname`,
256
+ [JOIN_TABLES],
257
+ );
258
+
259
+ await database.query(`CREATE SCHEMA "${schema}"`);
260
+
261
+ for (const table of TABLES) {
262
+ await database.query(
263
+ `CREATE TABLE "${schema}"."${table}" (LIKE public."${table}" INCLUDING ALL)`,
264
+ );
265
+ }
266
+
267
+ for (const foreignKey of migratedForeignKeys) {
268
+ const definition: string = foreignKey.definition.replace(
269
+ /REFERENCES public\./g,
270
+ "REFERENCES ",
271
+ );
272
+ await database.query(
273
+ `ALTER TABLE "${schema}"."${foreignKey.table}" ADD CONSTRAINT "${foreignKey.name}" ${definition}`,
274
+ );
275
+ }
276
+
277
+ const currentSchema: Array<{ current_schema: string }> =
278
+ await database.query("SELECT current_schema()");
279
+ expect(currentSchema[0]?.current_schema).toBe(schema);
280
+
281
+ jest.spyOn(PostgresAppInstance, "isConnected").mockReturnValue(true);
282
+ jest
283
+ .spyOn(PostgresAppInstance, "getDataSource")
284
+ .mockReturnValue(database);
285
+
286
+ /*
287
+ * The only side effects of these paths that leave Postgres: realtime
288
+ * events go to the socket server, workflow triggers to the workflow
289
+ * service.
290
+ */
291
+ jest
292
+ .spyOn(TeamComplianceSettingService, "onTriggerRealtime")
293
+ .mockResolvedValue(undefined);
294
+ jest
295
+ .spyOn(TeamComplianceSettingService, "onTriggerWorkflow")
296
+ .mockResolvedValue(undefined);
297
+
298
+ for (const severityService of [
299
+ IncidentSeverityService,
300
+ AlertSeverityService,
301
+ ]) {
302
+ jest
303
+ .spyOn(severityService, "onTriggerRealtime")
304
+ .mockResolvedValue(undefined);
305
+ jest
306
+ .spyOn(severityService, "onTriggerWorkflow")
307
+ .mockResolvedValue(undefined);
308
+ }
309
+ });
310
+
311
+ beforeEach(async () => {
312
+ failedQueries.failures = [];
313
+
314
+ await database.query(
315
+ TABLES.map((table: string): string => {
316
+ return `DELETE FROM "${schema}"."${table}"`;
317
+ })
318
+ .reverse()
319
+ .join("; "),
320
+ );
321
+
322
+ for (const [id, name] of [
323
+ [projectId, "Compliance project"],
324
+ [otherProjectId, "Another project"],
325
+ ] as Array<[ObjectID, string]>) {
326
+ await insert("Project", {
327
+ _id: id.toString(),
328
+ name: name,
329
+ slug: `project-${id.toString()}`,
330
+ version: 1,
331
+ });
332
+ }
333
+
334
+ for (const [id, project, name] of [
335
+ [teamId, projectId, "Platform on-call"],
336
+ [otherTeamId, projectId, "Database on-call"],
337
+ [otherProjectTeamId, otherProjectId, "Someone else's team"],
338
+ ] as Array<[ObjectID, ObjectID, string]>) {
339
+ await insert("Team", {
340
+ _id: id.toString(),
341
+ projectId: project.toString(),
342
+ name: name,
343
+ slug: `team-${id.toString()}`,
344
+ version: 1,
345
+ });
346
+ }
347
+
348
+ for (const [id, project, name, color, order] of [
349
+ [critical, projectId, "Critical Incident", "#FF0000", 1],
350
+ [major, projectId, "Major Incident", "#FFA500", 2],
351
+ [minor, projectId, "Minor Incident", "#FFFF00", 3],
352
+ [otherProjectCritical, otherProjectId, "Their Critical", "#000000", 1],
353
+ ] as Array<[ObjectID, ObjectID, string, string, number]>) {
354
+ await insertSeverity("IncidentSeverity", {
355
+ id,
356
+ project,
357
+ name,
358
+ color,
359
+ order,
360
+ });
361
+ }
362
+
363
+ for (const [id, project, name, color, order] of [
364
+ [criticalAlert, projectId, "Critical Alert", "#DC2626", 1],
365
+ [warningAlert, projectId, "Warning", "#F59E0B", 2],
366
+ [
367
+ otherProjectCriticalAlert,
368
+ otherProjectId,
369
+ "Their Alert",
370
+ "#000000",
371
+ 1,
372
+ ],
373
+ ] as Array<[ObjectID, ObjectID, string, string, number]>) {
374
+ await insertSeverity("AlertSeverity", {
375
+ id,
376
+ project,
377
+ name,
378
+ color,
379
+ order,
380
+ });
381
+ }
382
+ });
383
+
384
+ afterEach(() => {
385
+ // A failed statement fails the test, even if a caller swallowed it.
386
+ expect(failedQueries.failures).toEqual([]);
387
+ });
388
+
389
+ afterAll(async () => {
390
+ jest.restoreAllMocks();
391
+ if (database?.isInitialized) {
392
+ await database.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
393
+ await database.destroy();
394
+ }
395
+ });
396
+
397
+ async function insert(table: string, row: SqlRow): Promise<void> {
398
+ const columns: Array<string> = Object.keys(row);
399
+ await database.query(
400
+ `INSERT INTO "${schema}"."${table}" (${columns
401
+ .map((column: string): string => {
402
+ return `"${column}"`;
403
+ })
404
+ .join(", ")}) VALUES (${columns
405
+ .map((_column: string, index: number): string => {
406
+ return `$${index + 1}`;
407
+ })
408
+ .join(", ")})`,
409
+ columns.map((column: string): unknown => {
410
+ return row[column];
411
+ }),
412
+ );
413
+ }
414
+
415
+ async function insertSeverity(
416
+ table: "IncidentSeverity" | "AlertSeverity",
417
+ data: {
418
+ id: ObjectID;
419
+ project: ObjectID;
420
+ name: string;
421
+ color: string;
422
+ order: number;
423
+ },
424
+ ): Promise<void> {
425
+ await insert(table, {
426
+ _id: data.id.toString(),
427
+ projectId: data.project.toString(),
428
+ name: data.name,
429
+ slug: `severity-${data.id.toString()}`,
430
+ color: data.color,
431
+ order: data.order,
432
+ version: 1,
433
+ });
434
+ }
435
+
436
+ function ids(list: Array<ObjectID>): Array<string> {
437
+ return list
438
+ .map((id: ObjectID): string => {
439
+ return id.toString();
440
+ })
441
+ .sort();
442
+ }
443
+
444
+ function upper(id: ObjectID): string {
445
+ return id.toString().toUpperCase();
446
+ }
447
+
448
+ // `{_id}` JSON, as BaseAPI.updateItem hands an EntityArray to the hooks.
449
+ function asJson(list: Array<ObjectID>): Array<JSONObject> {
450
+ return list.map((id: ObjectID): JSONObject => {
451
+ return { _id: id.toString() };
452
+ });
453
+ }
454
+
455
+ /*
456
+ * Models built from `{_id}` JSON by BaseModel.fromJSON: what
457
+ * BaseAPI.createItem hands the hooks for a ModelForm create.
458
+ */
459
+ function asIncidentModels(list: Array<ObjectID>): Array<IncidentSeverity> {
460
+ return BaseModel.fromJSONArray<IncidentSeverity>(
461
+ asJson(list),
462
+ IncidentSeverity,
463
+ );
464
+ }
465
+
466
+ function asStrings(list: Array<ObjectID>): Array<string> {
467
+ return list.map((id: ObjectID): string => {
468
+ return id.toString();
469
+ });
470
+ }
471
+
472
+ function buildRule(input: RuleInput): TeamComplianceSetting {
473
+ const setting: TeamComplianceSetting = new TeamComplianceSetting();
474
+ setting.projectId = input.projectId || projectId;
475
+ setting.teamId = input.teamId || teamId;
476
+ setting.ruleType = input.ruleType;
477
+ setting.enabled = input.enabled === undefined ? true : input.enabled;
478
+
479
+ /*
480
+ * The hooks see whatever shape the caller sends, so the severity lists
481
+ * go on the model untyped, exactly as they arrive.
482
+ */
483
+ const raw: Record<string, unknown> = setting as unknown as Record<
484
+ string,
485
+ unknown
486
+ >;
487
+
488
+ if (input.notificationChannel !== undefined) {
489
+ raw["notificationChannel"] = input.notificationChannel;
490
+ }
491
+
492
+ if (input.incidentSeverities !== undefined) {
493
+ raw["incidentSeverities"] = input.incidentSeverities;
494
+ }
495
+
496
+ if (input.alertSeverities !== undefined) {
497
+ raw["alertSeverities"] = input.alertSeverities;
498
+ }
499
+
500
+ return setting;
501
+ }
502
+
503
+ async function createRule(
504
+ input: RuleInput,
505
+ props: DatabaseCommonInteractionProps = ROOT,
506
+ ): Promise<ObjectID> {
507
+ const created: TeamComplianceSetting =
508
+ await TeamComplianceSettingService.create({
509
+ data: buildRule(input),
510
+ props: props,
511
+ });
512
+
513
+ expect(created.id).toBeTruthy();
514
+
515
+ return created.id!;
516
+ }
517
+
518
+ async function updateRule(
519
+ id: ObjectID,
520
+ data: JSONObject,
521
+ props: DatabaseCommonInteractionProps = ROOT,
522
+ ): Promise<void> {
523
+ await TeamComplianceSettingService.updateOneById({
524
+ id: id,
525
+ data: data as unknown as UpdateBy<TeamComplianceSetting>["data"],
526
+ props: props,
527
+ });
528
+ }
529
+
530
+ async function joinRows(
531
+ table: string,
532
+ settingId: ObjectID,
533
+ ): Promise<Array<string>> {
534
+ const column: string =
535
+ table === INCIDENT_JOIN_TABLE
536
+ ? "incidentSeverityId"
537
+ : "alertSeverityId";
538
+ const rows: Array<SqlRow> = await database.query(
539
+ `SELECT "${column}" AS "id" FROM "${schema}"."${table}" WHERE "teamComplianceSettingId" = $1 ORDER BY "${column}"`,
540
+ [settingId.toString()],
541
+ );
542
+ return rows.map((row: SqlRow): string => {
543
+ return String(row["id"]);
544
+ });
545
+ }
546
+
547
+ async function stored(settingId: ObjectID): Promise<StoredRule | null> {
548
+ const rows: Array<SqlRow> = await database.query(
549
+ `SELECT "ruleType", "notificationChannel", "enabled" FROM "${schema}"."TeamComplianceSetting" WHERE "_id" = $1`,
550
+ [settingId.toString()],
551
+ );
552
+
553
+ if (!rows[0]) {
554
+ return null;
555
+ }
556
+
557
+ return {
558
+ ruleType: String(rows[0]["ruleType"]),
559
+ notificationChannel:
560
+ rows[0]["notificationChannel"] === null
561
+ ? null
562
+ : String(rows[0]["notificationChannel"]),
563
+ enabled: rows[0]["enabled"] === true,
564
+ incidentSeverityIds: await joinRows(INCIDENT_JOIN_TABLE, settingId),
565
+ alertSeverityIds: await joinRows(ALERT_JOIN_TABLE, settingId),
566
+ };
567
+ }
568
+
569
+ // The rule's options column, as Postgres holds it.
570
+ async function storedOptions(settingId: ObjectID): Promise<unknown> {
571
+ const rows: Array<SqlRow> = await database.query(
572
+ `SELECT "options" FROM "${schema}"."TeamComplianceSetting" WHERE "_id" = $1`,
573
+ [settingId.toString()],
574
+ );
575
+
576
+ return rows[0]?.["options"];
577
+ }
578
+
579
+ /*
580
+ * Runs `deletes` side by side, holding each one's rule lookup until every
581
+ * one of them has looked: what two requests (or two App replicas)
582
+ * deleting at the same moment do. Without the hold, Promise.all may run
583
+ * one delete to completion before the other looks, which hides the race.
584
+ */
585
+ async function deleteSideBySide(
586
+ deletes: Array<() => Promise<unknown>>,
587
+ ): Promise<void> {
588
+ const lookup: (typeof TeamComplianceSettingService)["getRulesScopedToAnyOf"] =
589
+ TeamComplianceSettingService.getRulesScopedToAnyOf.bind(
590
+ TeamComplianceSettingService,
591
+ );
592
+
593
+ let arrived: number = 0;
594
+ let releaseAll: () => void = (): void => {
595
+ return;
596
+ };
597
+ const everyoneHasLooked: Promise<void> = new Promise<void>(
598
+ (resolve: () => void): void => {
599
+ releaseAll = resolve;
600
+ },
601
+ );
602
+
603
+ const spy: jest.SpyInstance = jest
604
+ .spyOn(TeamComplianceSettingService, "getRulesScopedToAnyOf")
605
+ .mockImplementation(
606
+ async (data: {
607
+ severityKind: ComplianceSeverityKind;
608
+ severities: Array<DeletedSeverity>;
609
+ }): Promise<Array<string>> => {
610
+ const found: Array<string> = await lookup(data);
611
+
612
+ arrived++;
613
+
614
+ if (arrived === deletes.length) {
615
+ releaseAll();
616
+ }
617
+
618
+ await everyoneHasLooked;
619
+
620
+ return found;
621
+ },
622
+ );
623
+
624
+ try {
625
+ await Promise.all(
626
+ deletes.map((run: () => Promise<unknown>): Promise<unknown> => {
627
+ return run();
628
+ }),
629
+ );
630
+ } finally {
631
+ spy.mockRestore();
632
+ }
633
+
634
+ expect(arrived).toBe(deletes.length);
635
+ }
636
+
637
+ async function countRules(): Promise<number> {
638
+ const rows: Array<{ count: string }> = await database.query(
639
+ `SELECT COUNT(*)::text AS "count" FROM "${schema}"."TeamComplianceSetting"`,
640
+ );
641
+ return Number(rows[0]!.count);
642
+ }
643
+
644
+ async function countJoinRows(): Promise<number> {
645
+ const rows: Array<{ count: string }> = await database.query(
646
+ `SELECT ((SELECT COUNT(*) FROM "${schema}"."${INCIDENT_JOIN_TABLE}") + (SELECT COUNT(*) FROM "${schema}"."${ALERT_JOIN_TABLE}"))::text AS "count"`,
647
+ );
648
+ return Number(rows[0]!.count);
649
+ }
650
+
651
+ function toReadSeverities(
652
+ list: Array<IncidentSeverity | AlertSeverity> | undefined,
653
+ ): Array<ReadSeverity> {
654
+ return (list || [])
655
+ .map((severity: IncidentSeverity | AlertSeverity): ReadSeverity => {
656
+ return {
657
+ id: severity.id!.toString(),
658
+ name: severity.name,
659
+ color: severity.color ? severity.color.toString() : undefined,
660
+ order: severity.order,
661
+ };
662
+ })
663
+ .sort((a: ReadSeverity, b: ReadSeverity): number => {
664
+ return (a.order || 0) - (b.order || 0);
665
+ });
666
+ }
667
+
668
+ // The rule read back the way the compliance page and the edit form do.
669
+ async function readRule(
670
+ id: ObjectID,
671
+ props: DatabaseCommonInteractionProps = ROOT,
672
+ ): Promise<TeamComplianceSetting | null> {
673
+ return await TeamComplianceSettingService.findOneById({
674
+ id: id,
675
+ select: {
676
+ _id: true,
677
+ ruleType: true,
678
+ enabled: true,
679
+ notificationChannel: true,
680
+ incidentSeverities: {
681
+ _id: true,
682
+ name: true,
683
+ color: true,
684
+ order: true,
685
+ },
686
+ alertSeverities: {
687
+ _id: true,
688
+ name: true,
689
+ color: true,
690
+ order: true,
691
+ },
692
+ },
693
+ props: props,
694
+ });
695
+ }
696
+
697
+ // A signed-in user holding `permissions` in `project`, owning nothing.
698
+ function memberProps(
699
+ project: ObjectID,
700
+ permissions: Array<Permission>,
701
+ ): DatabaseCommonInteractionProps {
702
+ const tenantPermission: UserTenantAccessPermission = {
703
+ projectId: project,
704
+ _type: "UserTenantAccessPermission",
705
+ permissions: permissions.map((permission: Permission) => {
706
+ return {
707
+ _type: "UserPermission",
708
+ permission: permission,
709
+ labelIds: [],
710
+ isBlockPermission: false,
711
+ };
712
+ }),
713
+ };
714
+
715
+ return {
716
+ tenantId: project,
717
+ userId: ObjectID.generate(),
718
+ userType: UserType.User,
719
+ userTenantAccessPermission: {
720
+ [project.toString()]: tenantPermission,
721
+ },
722
+ };
723
+ }
724
+
725
+ describe("the migrated tables", () => {
726
+ test("a severity join row goes with its rule or its severity", () => {
727
+ expect(
728
+ migratedForeignKeys.map((foreignKey: ForeignKeyRow) => {
729
+ return [
730
+ foreignKey.table,
731
+ foreignKey.column,
732
+ foreignKey.referencedTable,
733
+ foreignKey.onDelete,
734
+ ];
735
+ }),
736
+ ).toEqual([
737
+ // confdeltype: c = CASCADE
738
+ [
739
+ "TeamComplianceSettingAlertSeverity",
740
+ "alertSeverityId",
741
+ "AlertSeverity",
742
+ "c",
743
+ ],
744
+ [
745
+ "TeamComplianceSettingAlertSeverity",
746
+ "teamComplianceSettingId",
747
+ "TeamComplianceSetting",
748
+ "c",
749
+ ],
750
+ [
751
+ "TeamComplianceSettingIncidentSeverity",
752
+ "incidentSeverityId",
753
+ "IncidentSeverity",
754
+ "c",
755
+ ],
756
+ [
757
+ "TeamComplianceSettingIncidentSeverity",
758
+ "teamComplianceSettingId",
759
+ "TeamComplianceSetting",
760
+ "c",
761
+ ],
762
+ ]);
763
+ });
764
+
765
+ test("every existing rule reads as 'any channel', and one team may hold several rules of a type", async () => {
766
+ const columns: Array<SqlRow> = await database.query(
767
+ `SELECT data_type, is_nullable, column_default, character_maximum_length
768
+ FROM information_schema.columns
769
+ WHERE table_schema = 'public' AND table_name = 'TeamComplianceSetting' AND column_name = 'notificationChannel'`,
770
+ );
771
+
772
+ expect(columns).toEqual([
773
+ {
774
+ data_type: "character varying",
775
+ is_nullable: "YES",
776
+ column_default: null,
777
+ character_maximum_length: 100,
778
+ },
779
+ ]);
780
+
781
+ const uniqueIndexes: Array<SqlRow> = await database.query(
782
+ `SELECT indexname FROM pg_indexes
783
+ WHERE schemaname = 'public' AND tablename = 'TeamComplianceSetting'
784
+ AND indexdef LIKE 'CREATE UNIQUE INDEX%' AND indexdef LIKE '%"ruleType"%'`,
785
+ );
786
+
787
+ expect(uniqueIndexes).toEqual([]);
788
+ });
789
+ });
790
+
791
+ describe("creating a rule", () => {
792
+ test("severity ids sent as strings land as join rows, and read back with their name, colour and order", async () => {
793
+ const id: ObjectID = await createRule({
794
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
795
+ notificationChannel: ComplianceNotificationChannel.Call,
796
+ incidentSeverities: asStrings([major, critical]),
797
+ });
798
+
799
+ expect(await stored(id)).toEqual({
800
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
801
+ notificationChannel: ComplianceNotificationChannel.Call,
802
+ enabled: true,
803
+ incidentSeverityIds: ids([critical, major]),
804
+ alertSeverityIds: [],
805
+ });
806
+
807
+ const rule: TeamComplianceSetting | null = await readRule(id);
808
+
809
+ expect(rule?.notificationChannel).toBe(
810
+ ComplianceNotificationChannel.Call,
811
+ );
812
+ expect(toReadSeverities(rule?.incidentSeverities)).toEqual([
813
+ {
814
+ id: critical.toString(),
815
+ name: "Critical Incident",
816
+ color: "#FF0000",
817
+ order: 1,
818
+ },
819
+ {
820
+ id: major.toString(),
821
+ name: "Major Incident",
822
+ color: "#FFA500",
823
+ order: 2,
824
+ },
825
+ ]);
826
+ expect(rule?.alertSeverities || []).toEqual([]);
827
+ });
828
+
829
+ test("severity ids sent as {_id} JSON land as join rows", async () => {
830
+ const id: ObjectID = await createRule({
831
+ ruleType: ComplianceRuleType.HasAlertOnCallRules,
832
+ notificationChannel: ComplianceNotificationChannel.Push,
833
+ alertSeverities: asJson([criticalAlert]),
834
+ });
835
+
836
+ expect(await stored(id)).toEqual({
837
+ ruleType: ComplianceRuleType.HasAlertOnCallRules,
838
+ notificationChannel: ComplianceNotificationChannel.Push,
839
+ enabled: true,
840
+ incidentSeverityIds: [],
841
+ alertSeverityIds: ids([criticalAlert]),
842
+ });
843
+
844
+ const rule: TeamComplianceSetting | null = await readRule(id);
845
+
846
+ expect(toReadSeverities(rule?.alertSeverities)).toEqual([
847
+ {
848
+ id: criticalAlert.toString(),
849
+ name: "Critical Alert",
850
+ color: "#DC2626",
851
+ order: 1,
852
+ },
853
+ ]);
854
+ });
855
+
856
+ test("severities as the models BaseAPI builds from a ModelForm create land as join rows", async () => {
857
+ const setting: TeamComplianceSetting =
858
+ BaseModel.fromJSON<TeamComplianceSetting>(
859
+ {
860
+ ruleType: ComplianceRuleType.HasIncidentEpisodeOnCallRules,
861
+ notificationChannel: ComplianceNotificationChannel.SMS,
862
+ enabled: true,
863
+ incidentSeverities: asJson([critical, minor]),
864
+ },
865
+ TeamComplianceSetting,
866
+ ) as TeamComplianceSetting;
867
+ setting.projectId = projectId;
868
+ setting.teamId = teamId;
869
+
870
+ const created: TeamComplianceSetting =
871
+ await TeamComplianceSettingService.create({
872
+ data: setting,
873
+ props: ROOT,
874
+ });
875
+
876
+ expect(await stored(created.id!)).toEqual({
877
+ ruleType: ComplianceRuleType.HasIncidentEpisodeOnCallRules,
878
+ notificationChannel: ComplianceNotificationChannel.SMS,
879
+ enabled: true,
880
+ incidentSeverityIds: ids([critical, minor]),
881
+ alertSeverityIds: [],
882
+ });
883
+ });
884
+
885
+ test("no severities and no channel stores an 'any channel, every severity' rule with no join rows", async () => {
886
+ const id: ObjectID = await createRule({
887
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
888
+ });
889
+
890
+ expect(await stored(id)).toEqual({
891
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
892
+ notificationChannel: null,
893
+ enabled: true,
894
+ incidentSeverityIds: [],
895
+ alertSeverityIds: [],
896
+ });
897
+
898
+ const rule: TeamComplianceSetting | null = await readRule(id);
899
+
900
+ expect(rule?.incidentSeverities || []).toEqual([]);
901
+ expect(rule?.notificationChannel ?? null).toBeNull();
902
+ });
903
+
904
+ test("options the rule type does not use are not stored", async () => {
905
+ const methodRule: ObjectID = await createRule({
906
+ ruleType: ComplianceRuleType.HasNotificationCallMethod,
907
+ notificationChannel: ComplianceNotificationChannel.Push,
908
+ incidentSeverities: asStrings([critical]),
909
+ alertSeverities: asJson([criticalAlert]),
910
+ });
911
+
912
+ expect(await stored(methodRule)).toEqual({
913
+ ruleType: ComplianceRuleType.HasNotificationCallMethod,
914
+ notificationChannel: null,
915
+ enabled: true,
916
+ incidentSeverityIds: [],
917
+ alertSeverityIds: [],
918
+ });
919
+
920
+ const incidentRule: ObjectID = await createRule({
921
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
922
+ notificationChannel: ComplianceNotificationChannel.Call,
923
+ incidentSeverities: asStrings([critical]),
924
+ alertSeverities: asJson([criticalAlert]),
925
+ });
926
+
927
+ expect(await stored(incidentRule)).toEqual({
928
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
929
+ notificationChannel: ComplianceNotificationChannel.Call,
930
+ enabled: true,
931
+ incidentSeverityIds: ids([critical]),
932
+ alertSeverityIds: [],
933
+ });
934
+
935
+ const alertRule: ObjectID = await createRule({
936
+ ruleType: ComplianceRuleType.HasAlertEpisodeOnCallRules,
937
+ incidentSeverities: asStrings([critical]),
938
+ alertSeverities: asStrings([warningAlert]),
939
+ });
940
+
941
+ expect(await stored(alertRule)).toEqual({
942
+ ruleType: ComplianceRuleType.HasAlertEpisodeOnCallRules,
943
+ notificationChannel: null,
944
+ enabled: true,
945
+ incidentSeverityIds: [],
946
+ alertSeverityIds: ids([warningAlert]),
947
+ });
948
+ });
949
+
950
+ test("an exact duplicate is refused whatever the shape, order or case of its severity ids", async () => {
951
+ await createRule({
952
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
953
+ notificationChannel: ComplianceNotificationChannel.Call,
954
+ incidentSeverities: asStrings([critical, major]),
955
+ });
956
+
957
+ const duplicates: Array<SeverityList> = [
958
+ asStrings([critical, major]),
959
+ asStrings([major, critical]),
960
+ [upper(critical), upper(major)],
961
+ [upper(major), critical.toString()],
962
+ asJson([major, critical]),
963
+ asIncidentModels([major, critical]),
964
+ asStrings([critical, major, critical]),
965
+ ];
966
+
967
+ for (const incidentSeverities of duplicates) {
968
+ await expect(
969
+ createRule({
970
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
971
+ notificationChannel: ComplianceNotificationChannel.Call,
972
+ incidentSeverities: incidentSeverities,
973
+ }),
974
+ ).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
975
+ }
976
+
977
+ expect(await countRules()).toBe(1);
978
+ expect(await countJoinRows()).toBe(2);
979
+ });
980
+
981
+ /*
982
+ * The premise of getIds' lower-casing: Postgres parses a uuid without
983
+ * regard to case and hands it back lower case.
984
+ */
985
+ test("an upper-case id is stored as the lower-case uuid, and the lower-case spelling is then a duplicate", async () => {
986
+ const id: ObjectID = await createRule({
987
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
988
+ notificationChannel: ComplianceNotificationChannel.WhatsApp,
989
+ incidentSeverities: [upper(minor)],
990
+ });
991
+
992
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual(ids([minor]));
993
+
994
+ await expect(
995
+ createRule({
996
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
997
+ notificationChannel: ComplianceNotificationChannel.WhatsApp,
998
+ incidentSeverities: asStrings([minor]),
999
+ }),
1000
+ ).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
1001
+ });
1002
+
1003
+ test("the legacy 'any channel, every severity' rule and a method rule are still one per team", async () => {
1004
+ await createRule({
1005
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1006
+ });
1007
+
1008
+ await expect(
1009
+ createRule({
1010
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1011
+ notificationChannel: null,
1012
+ incidentSeverities: [],
1013
+ }),
1014
+ ).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
1015
+
1016
+ await createRule({
1017
+ ruleType: ComplianceRuleType.HasNotificationCallMethod,
1018
+ });
1019
+
1020
+ // The channel and severities a method rule does not use change nothing.
1021
+ await expect(
1022
+ createRule({
1023
+ ruleType: ComplianceRuleType.HasNotificationCallMethod,
1024
+ notificationChannel: ComplianceNotificationChannel.SMS,
1025
+ incidentSeverities: asStrings([critical]),
1026
+ }),
1027
+ ).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
1028
+
1029
+ expect(await countRules()).toBe(2);
1030
+ });
1031
+
1032
+ test("a different channel, severity set, rule type or team is a different rule", async () => {
1033
+ const scope: Array<ObjectID> = [critical, major];
1034
+
1035
+ await createRule({
1036
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1037
+ notificationChannel: ComplianceNotificationChannel.Call,
1038
+ incidentSeverities: asStrings(scope),
1039
+ });
1040
+
1041
+ const allowed: Array<RuleInput> = [
1042
+ // Push for the same severities.
1043
+ {
1044
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1045
+ notificationChannel: ComplianceNotificationChannel.Push,
1046
+ incidentSeverities: asStrings(scope),
1047
+ },
1048
+ // Any channel for the same severities.
1049
+ {
1050
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1051
+ incidentSeverities: asStrings(scope),
1052
+ },
1053
+ // Call for a subset.
1054
+ {
1055
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1056
+ notificationChannel: ComplianceNotificationChannel.Call,
1057
+ incidentSeverities: asStrings([critical]),
1058
+ },
1059
+ // Call for a superset.
1060
+ {
1061
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1062
+ notificationChannel: ComplianceNotificationChannel.Call,
1063
+ incidentSeverities: asStrings([critical, major, minor]),
1064
+ },
1065
+ // Call for every severity.
1066
+ {
1067
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1068
+ notificationChannel: ComplianceNotificationChannel.Call,
1069
+ },
1070
+ // The episode rule type.
1071
+ {
1072
+ ruleType: ComplianceRuleType.HasIncidentEpisodeOnCallRules,
1073
+ notificationChannel: ComplianceNotificationChannel.Call,
1074
+ incidentSeverities: asStrings(scope),
1075
+ },
1076
+ // The same rule on another team.
1077
+ {
1078
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1079
+ notificationChannel: ComplianceNotificationChannel.Call,
1080
+ incidentSeverities: asStrings(scope),
1081
+ teamId: otherTeamId,
1082
+ },
1083
+ ];
1084
+
1085
+ for (const input of allowed) {
1086
+ await createRule(input);
1087
+ }
1088
+
1089
+ expect(await countRules()).toBe(1 + allowed.length);
1090
+ });
1091
+
1092
+ test("a severity of another project, of the other kind, or that does not exist is refused and nothing is written", async () => {
1093
+ const refused: Array<{ input: RuleInput; message: string }> = [
1094
+ {
1095
+ input: {
1096
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1097
+ notificationChannel: ComplianceNotificationChannel.Call,
1098
+ incidentSeverities: asStrings([otherProjectCritical]),
1099
+ },
1100
+ message: FOREIGN_SEVERITY_MESSAGE,
1101
+ },
1102
+ {
1103
+ input: {
1104
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1105
+ notificationChannel: ComplianceNotificationChannel.Call,
1106
+ incidentSeverities: asJson([critical, otherProjectCritical]),
1107
+ },
1108
+ message: FOREIGN_SEVERITY_MESSAGE,
1109
+ },
1110
+ {
1111
+ input: {
1112
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1113
+ incidentSeverities: asStrings([criticalAlert]),
1114
+ },
1115
+ message: FOREIGN_SEVERITY_MESSAGE,
1116
+ },
1117
+ {
1118
+ input: {
1119
+ ruleType: ComplianceRuleType.HasAlertOnCallRules,
1120
+ alertSeverities: asStrings([otherProjectCriticalAlert]),
1121
+ },
1122
+ message:
1123
+ "One or more of the selected alert severities do not exist in this project.",
1124
+ },
1125
+ {
1126
+ input: {
1127
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1128
+ incidentSeverities: asStrings([ObjectID.generate()]),
1129
+ },
1130
+ message: FOREIGN_SEVERITY_MESSAGE,
1131
+ },
1132
+ {
1133
+ input: {
1134
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1135
+ incidentSeverities: ["not-a-uuid"],
1136
+ },
1137
+ message: 'Invalid ID format: "not-a-uuid".',
1138
+ },
1139
+ ];
1140
+
1141
+ for (const { input, message } of refused) {
1142
+ await expect(createRule(input)).rejects.toThrow(message);
1143
+ }
1144
+
1145
+ expect(await countRules()).toBe(0);
1146
+ expect(await countJoinRows()).toBe(0);
1147
+ });
1148
+
1149
+ /*
1150
+ * The relation save inserts one join row per list item, and the join
1151
+ * table's primary key refuses the same severity twice. The service
1152
+ * writes the list it checked - de-duplicated, lower-cased - so a
1153
+ * repeat, in any case and any shape, is one join row, on create and on
1154
+ * update (where a re-sent id in another case must also not be INSERTed
1155
+ * next to the stored one).
1156
+ */
1157
+ test("a repeated severity id (in any case, on create or update) is stored once", async () => {
1158
+ const id: ObjectID = await createRule({
1159
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1160
+ notificationChannel: ComplianceNotificationChannel.Call,
1161
+ incidentSeverities: [critical.toString(), upper(critical)],
1162
+ });
1163
+
1164
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual(
1165
+ ids([critical]),
1166
+ );
1167
+
1168
+ await updateRule(id, {
1169
+ incidentSeverities: asJson([major, major]),
1170
+ });
1171
+
1172
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual(ids([major]));
1173
+
1174
+ await updateRule(id, {
1175
+ incidentSeverities: [
1176
+ { _id: upper(major) },
1177
+ { id: critical.toString() },
1178
+ ],
1179
+ });
1180
+
1181
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual(
1182
+ ids([critical, major]),
1183
+ );
1184
+
1185
+ const alertRule: ObjectID = await createRule({
1186
+ ruleType: ComplianceRuleType.HasAlertOnCallRules,
1187
+ alertSeverities: [
1188
+ ...asJson([warningAlert, warningAlert]),
1189
+ upper(warningAlert),
1190
+ ],
1191
+ });
1192
+
1193
+ expect(await joinRows(ALERT_JOIN_TABLE, alertRule)).toEqual(
1194
+ ids([warningAlert]),
1195
+ );
1196
+ });
1197
+
1198
+ test("a severity list item is checked by the id the save writes: one read by its `id` cannot smuggle in another project's severity", async () => {
1199
+ const id: ObjectID = await createRule({
1200
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1201
+ notificationChannel: ComplianceNotificationChannel.Call,
1202
+ incidentSeverities: asStrings([critical]),
1203
+ });
1204
+
1205
+ /*
1206
+ * What BaseAPI.updateItem hands the hooks: the JSON body, deserialized
1207
+ * (an `{_type: "ObjectID"}` value becomes an ObjectID), not models.
1208
+ * The save reads an `_id` only when it is a string, and otherwise the
1209
+ * item's `id`.
1210
+ */
1211
+ for (const item of [
1212
+ { _id: 1, id: otherProjectCritical.toString() },
1213
+ { _id: true, id: otherProjectCritical.toString() },
1214
+ { _id: critical, id: otherProjectCritical.toString() },
1215
+ ]) {
1216
+ await expect(
1217
+ updateRule(id, { incidentSeverities: [item] as never }),
1218
+ ).rejects.toThrow(FOREIGN_SEVERITY_MESSAGE);
1219
+
1220
+ await expect(
1221
+ createRule({
1222
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1223
+ notificationChannel: ComplianceNotificationChannel.Push,
1224
+ incidentSeverities: [item as unknown as JSONObject],
1225
+ }),
1226
+ ).rejects.toThrow(FOREIGN_SEVERITY_MESSAGE);
1227
+ }
1228
+
1229
+ /*
1230
+ * An item the save would drop is refused: the rule would otherwise be
1231
+ * stored with no severity at all - a rule for every severity.
1232
+ */
1233
+ await expect(
1234
+ updateRule(id, { incidentSeverities: [{ _id: major }] as never }),
1235
+ ).rejects.toThrow(
1236
+ "Every incident severity of a compliance rule must be a severity id.",
1237
+ );
1238
+
1239
+ expect(await stored(id)).toEqual({
1240
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1241
+ notificationChannel: ComplianceNotificationChannel.Call,
1242
+ enabled: true,
1243
+ incidentSeverityIds: ids([critical]),
1244
+ alertSeverityIds: [],
1245
+ });
1246
+ expect(await countRules()).toBe(1);
1247
+ });
1248
+
1249
+ test("a refused create leaves no rule behind", async () => {
1250
+ /*
1251
+ * A severity that passes the service's check but not the join
1252
+ * table's foreign key - the check is told it exists - so the refusal
1253
+ * comes from Postgres, after the rule row was inserted in the same
1254
+ * save.
1255
+ */
1256
+ const severityCheck: jest.SpyInstance = jest
1257
+ .spyOn(IncidentSeverityService, "countBy")
1258
+ .mockResolvedValueOnce(new PositiveNumber(1));
1259
+
1260
+ try {
1261
+ await expect(
1262
+ createRule({
1263
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1264
+ notificationChannel: ComplianceNotificationChannel.Call,
1265
+ incidentSeverities: asStrings([ObjectID.generate()]),
1266
+ }),
1267
+ ).rejects.toThrow();
1268
+ } finally {
1269
+ severityCheck.mockRestore();
1270
+ failedQueries.failures = [];
1271
+ }
1272
+
1273
+ /*
1274
+ * The rule row and its join rows are one save: a rule that lost its
1275
+ * severities would read as "every severity" - a stricter rule than
1276
+ * the one asked for.
1277
+ */
1278
+ expect(await countRules()).toBe(0);
1279
+ expect(await countJoinRows()).toBe(0);
1280
+ });
1281
+ });
1282
+
1283
+ describe("updating a rule", () => {
1284
+ async function callForCriticalAndMajor(): Promise<ObjectID> {
1285
+ return await createRule({
1286
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1287
+ notificationChannel: ComplianceNotificationChannel.Call,
1288
+ incidentSeverities: asStrings([critical, major]),
1289
+ });
1290
+ }
1291
+
1292
+ test("toggling enabled leaves the scope alone; switching off reads nothing, switching on reads only the row", async () => {
1293
+ const id: ObjectID = await callForCriticalAndMajor();
1294
+
1295
+ const findBy: jest.SpyInstance = jest.spyOn(
1296
+ TeamComplianceSettingService,
1297
+ "findBy",
1298
+ );
1299
+ const incidentCount: jest.SpyInstance = jest.spyOn(
1300
+ IncidentSeverityService,
1301
+ "countBy",
1302
+ );
1303
+ const alertCount: jest.SpyInstance = jest.spyOn(
1304
+ AlertSeverityService,
1305
+ "countBy",
1306
+ );
1307
+
1308
+ try {
1309
+ await updateRule(id, { enabled: false });
1310
+
1311
+ expect(await stored(id)).toEqual({
1312
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1313
+ notificationChannel: ComplianceNotificationChannel.Call,
1314
+ enabled: false,
1315
+ incidentSeverityIds: ids([critical, major]),
1316
+ alertSeverityIds: [],
1317
+ });
1318
+
1319
+ expect(findBy).not.toHaveBeenCalled();
1320
+
1321
+ await updateRule(id, { enabled: true });
1322
+
1323
+ expect((await stored(id))?.enabled).toBe(true);
1324
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual(
1325
+ ids([critical, major]),
1326
+ );
1327
+
1328
+ // Whether a severity delete emptied it: one read of the row.
1329
+ expect(findBy).toHaveBeenCalledTimes(1);
1330
+ expect(incidentCount).not.toHaveBeenCalled();
1331
+ expect(alertCount).not.toHaveBeenCalled();
1332
+ } finally {
1333
+ findBy.mockRestore();
1334
+ incidentCount.mockRestore();
1335
+ alertCount.mockRestore();
1336
+ }
1337
+ });
1338
+
1339
+ test("changing an incident rule into a method rule clears its channel and severities", async () => {
1340
+ const id: ObjectID = await callForCriticalAndMajor();
1341
+
1342
+ await updateRule(id, {
1343
+ ruleType: ComplianceRuleType.HasNotificationCallMethod,
1344
+ });
1345
+
1346
+ expect(await stored(id)).toEqual({
1347
+ ruleType: ComplianceRuleType.HasNotificationCallMethod,
1348
+ notificationChannel: null,
1349
+ enabled: true,
1350
+ incidentSeverityIds: [],
1351
+ alertSeverityIds: [],
1352
+ });
1353
+
1354
+ const rule: TeamComplianceSetting | null = await readRule(id);
1355
+
1356
+ expect(rule?.incidentSeverities || []).toEqual([]);
1357
+ expect(rule?.notificationChannel ?? null).toBeNull();
1358
+ });
1359
+
1360
+ test("changing an incident rule into an alert rule drops the incident severities and keeps the channel", async () => {
1361
+ const id: ObjectID = await callForCriticalAndMajor();
1362
+
1363
+ await updateRule(id, {
1364
+ ruleType: ComplianceRuleType.HasAlertOnCallRules,
1365
+ alertSeverities: asJson([criticalAlert]),
1366
+ });
1367
+
1368
+ expect(await stored(id)).toEqual({
1369
+ ruleType: ComplianceRuleType.HasAlertOnCallRules,
1370
+ notificationChannel: ComplianceNotificationChannel.Call,
1371
+ enabled: true,
1372
+ incidentSeverityIds: [],
1373
+ alertSeverityIds: ids([criticalAlert]),
1374
+ });
1375
+ });
1376
+
1377
+ test("sending severities replaces the join rows, and an empty list clears them", async () => {
1378
+ const id: ObjectID = await callForCriticalAndMajor();
1379
+
1380
+ await updateRule(id, { incidentSeverities: asJson([minor]) });
1381
+
1382
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual(ids([minor]));
1383
+
1384
+ await updateRule(id, {
1385
+ incidentSeverities: asStrings([major, critical]),
1386
+ });
1387
+
1388
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual(
1389
+ ids([critical, major]),
1390
+ );
1391
+
1392
+ await updateRule(id, { incidentSeverities: [] });
1393
+
1394
+ expect(await stored(id)).toEqual({
1395
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1396
+ notificationChannel: ComplianceNotificationChannel.Call,
1397
+ enabled: true,
1398
+ incidentSeverityIds: [],
1399
+ alertSeverityIds: [],
1400
+ });
1401
+ });
1402
+
1403
+ test("changing only the channel leaves the severity join rows alone", async () => {
1404
+ const id: ObjectID = await callForCriticalAndMajor();
1405
+
1406
+ await updateRule(id, {
1407
+ notificationChannel: ComplianceNotificationChannel.Push,
1408
+ });
1409
+
1410
+ expect(await stored(id)).toEqual({
1411
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1412
+ notificationChannel: ComplianceNotificationChannel.Push,
1413
+ enabled: true,
1414
+ incidentSeverityIds: ids([critical, major]),
1415
+ alertSeverityIds: [],
1416
+ });
1417
+
1418
+ await updateRule(id, { notificationChannel: null });
1419
+
1420
+ expect((await stored(id))?.notificationChannel).toBeNull();
1421
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual(
1422
+ ids([critical, major]),
1423
+ );
1424
+ });
1425
+
1426
+ test("alert severities sent to an incident rule are dropped and its incident severities stay", async () => {
1427
+ const id: ObjectID = await callForCriticalAndMajor();
1428
+
1429
+ await updateRule(id, { alertSeverities: asJson([criticalAlert]) });
1430
+
1431
+ expect(await stored(id)).toEqual({
1432
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1433
+ notificationChannel: ComplianceNotificationChannel.Call,
1434
+ enabled: true,
1435
+ incidentSeverityIds: ids([critical, major]),
1436
+ alertSeverityIds: [],
1437
+ });
1438
+ });
1439
+
1440
+ test("re-saving a rule's own scope is not a duplicate of itself", async () => {
1441
+ const id: ObjectID = await callForCriticalAndMajor();
1442
+
1443
+ await updateRule(id, {
1444
+ notificationChannel: ComplianceNotificationChannel.Call,
1445
+ incidentSeverities: asJson([major, critical]),
1446
+ });
1447
+
1448
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual(
1449
+ ids([critical, major]),
1450
+ );
1451
+ });
1452
+
1453
+ test("a change that makes the rule a duplicate of a sibling, or points it at a foreign severity, is refused and changes nothing", async () => {
1454
+ const id: ObjectID = await callForCriticalAndMajor();
1455
+
1456
+ await createRule({
1457
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1458
+ notificationChannel: ComplianceNotificationChannel.Call,
1459
+ incidentSeverities: asStrings([critical]),
1460
+ });
1461
+
1462
+ await expect(
1463
+ updateRule(id, { incidentSeverities: asJson([critical]) }),
1464
+ ).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
1465
+
1466
+ await expect(
1467
+ updateRule(id, { incidentSeverities: [upper(critical)] }),
1468
+ ).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
1469
+
1470
+ await expect(
1471
+ updateRule(id, {
1472
+ incidentSeverities: asJson([critical, otherProjectCritical]),
1473
+ }),
1474
+ ).rejects.toThrow(FOREIGN_SEVERITY_MESSAGE);
1475
+
1476
+ expect(await stored(id)).toEqual({
1477
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1478
+ notificationChannel: ComplianceNotificationChannel.Call,
1479
+ enabled: true,
1480
+ incidentSeverityIds: ids([critical, major]),
1481
+ alertSeverityIds: [],
1482
+ });
1483
+ });
1484
+ });
1485
+
1486
+ describe("deleting", () => {
1487
+ test("deleting a severity removes its join row and keeps the rule", async () => {
1488
+ const id: ObjectID = await createRule({
1489
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1490
+ notificationChannel: ComplianceNotificationChannel.Call,
1491
+ incidentSeverities: asStrings([critical, major]),
1492
+ });
1493
+
1494
+ // What DatabaseService.deleteBy issues once a delete is permitted.
1495
+ await database.query(
1496
+ `DELETE FROM "${schema}"."IncidentSeverity" WHERE "_id" = $1`,
1497
+ [critical.toString()],
1498
+ );
1499
+
1500
+ expect(await stored(id)).toEqual({
1501
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1502
+ notificationChannel: ComplianceNotificationChannel.Call,
1503
+ enabled: true,
1504
+ incidentSeverityIds: ids([major]),
1505
+ alertSeverityIds: [],
1506
+ });
1507
+
1508
+ const rule: TeamComplianceSetting | null = await readRule(id);
1509
+
1510
+ expect(toReadSeverities(rule?.incidentSeverities)).toEqual([
1511
+ {
1512
+ id: major.toString(),
1513
+ name: "Major Incident",
1514
+ color: "#FFA500",
1515
+ order: 2,
1516
+ },
1517
+ ]);
1518
+ });
1519
+
1520
+ test("deleting an alert severity removes its join row too", async () => {
1521
+ const id: ObjectID = await createRule({
1522
+ ruleType: ComplianceRuleType.HasAlertOnCallRules,
1523
+ notificationChannel: ComplianceNotificationChannel.Push,
1524
+ alertSeverities: asStrings([criticalAlert, warningAlert]),
1525
+ });
1526
+
1527
+ await database.query(
1528
+ `DELETE FROM "${schema}"."AlertSeverity" WHERE "_id" = $1`,
1529
+ [warningAlert.toString()],
1530
+ );
1531
+
1532
+ expect(await joinRows(ALERT_JOIN_TABLE, id)).toEqual(
1533
+ ids([criticalAlert]),
1534
+ );
1535
+ });
1536
+
1537
+ /*
1538
+ * The raw deletes above show the cascade itself. Through the severity
1539
+ * services - the only way the product deletes a severity - a rule the
1540
+ * cascade leaves with no severity is paused rather than left to read
1541
+ * as a rule for every severity.
1542
+ */
1543
+ test("deleting a rule's only severity through the service pauses the rule; a rule with another severity left keeps it", async () => {
1544
+ const onlyCritical: ObjectID = await createRule({
1545
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1546
+ notificationChannel: ComplianceNotificationChannel.Call,
1547
+ incidentSeverities: asStrings([critical]),
1548
+ });
1549
+ const episodeOnlyCritical: ObjectID = await createRule({
1550
+ ruleType: ComplianceRuleType.HasIncidentEpisodeOnCallRules,
1551
+ notificationChannel: ComplianceNotificationChannel.SMS,
1552
+ incidentSeverities: asStrings([critical]),
1553
+ });
1554
+ const criticalAndMajor: ObjectID = await createRule({
1555
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1556
+ notificationChannel: ComplianceNotificationChannel.Push,
1557
+ incidentSeverities: asStrings([critical, major]),
1558
+ });
1559
+ const everySeverity: ObjectID = await createRule({
1560
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1561
+ notificationChannel: ComplianceNotificationChannel.Email,
1562
+ });
1563
+ const alertRule: ObjectID = await createRule({
1564
+ ruleType: ComplianceRuleType.HasAlertOnCallRules,
1565
+ alertSeverities: asStrings([criticalAlert]),
1566
+ });
1567
+
1568
+ await IncidentSeverityService.deleteOneById({
1569
+ id: critical,
1570
+ props: ROOT,
1571
+ });
1572
+
1573
+ expect(await stored(onlyCritical)).toEqual({
1574
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1575
+ notificationChannel: ComplianceNotificationChannel.Call,
1576
+ enabled: false,
1577
+ incidentSeverityIds: [],
1578
+ alertSeverityIds: [],
1579
+ });
1580
+ expect(await storedOptions(onlyCritical)).toEqual(MARKED);
1581
+ expect((await stored(episodeOnlyCritical))?.enabled).toBe(false);
1582
+ expect(await storedOptions(episodeOnlyCritical)).toEqual(MARKED);
1583
+ expect(await storedOptions(criticalAndMajor)).toBeNull();
1584
+ expect(await storedOptions(everySeverity)).toBeNull();
1585
+
1586
+ expect(await stored(criticalAndMajor)).toEqual({
1587
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1588
+ notificationChannel: ComplianceNotificationChannel.Push,
1589
+ enabled: true,
1590
+ incidentSeverityIds: ids([major]),
1591
+ alertSeverityIds: [],
1592
+ });
1593
+ expect((await stored(everySeverity))?.enabled).toBe(true);
1594
+ expect(await stored(alertRule)).toMatchObject({
1595
+ enabled: true,
1596
+ alertSeverityIds: ids([criticalAlert]),
1597
+ });
1598
+ });
1599
+
1600
+ test("deleting an alert rule's only severity through the service pauses it too", async () => {
1601
+ const onlyWarning: ObjectID = await createRule({
1602
+ ruleType: ComplianceRuleType.HasAlertEpisodeOnCallRules,
1603
+ notificationChannel: ComplianceNotificationChannel.Push,
1604
+ alertSeverities: asStrings([warningAlert]),
1605
+ });
1606
+ const incidentRule: ObjectID = await createRule({
1607
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1608
+ incidentSeverities: asStrings([critical]),
1609
+ });
1610
+
1611
+ await AlertSeverityService.deleteOneById({
1612
+ id: warningAlert,
1613
+ props: ROOT,
1614
+ });
1615
+
1616
+ expect(await stored(onlyWarning)).toMatchObject({
1617
+ enabled: false,
1618
+ alertSeverityIds: [],
1619
+ });
1620
+ expect((await stored(incidentRule))?.enabled).toBe(true);
1621
+ });
1622
+
1623
+ test.each([
1624
+ ["incident", false],
1625
+ ["alert", true],
1626
+ ])(
1627
+ "two %s severity deletes side by side - the two severities of one rule - pause and mark it",
1628
+ async (_label: string, isAlert: boolean) => {
1629
+ const both: ObjectID = isAlert
1630
+ ? await createRule({
1631
+ ruleType: ComplianceRuleType.HasAlertOnCallRules,
1632
+ notificationChannel: ComplianceNotificationChannel.Call,
1633
+ alertSeverities: asStrings([criticalAlert, warningAlert]),
1634
+ })
1635
+ : await createRule({
1636
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1637
+ notificationChannel: ComplianceNotificationChannel.Call,
1638
+ incidentSeverities: asStrings([critical, major]),
1639
+ });
1640
+
1641
+ // A rule with a severity neither delete touches.
1642
+ const keepsMinor: ObjectID = await createRule({
1643
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1644
+ notificationChannel: ComplianceNotificationChannel.Push,
1645
+ incidentSeverities: asStrings([critical, minor]),
1646
+ });
1647
+
1648
+ await deleteSideBySide(
1649
+ (isAlert ? [criticalAlert, warningAlert] : [critical, major]).map(
1650
+ (id: ObjectID): (() => Promise<unknown>) => {
1651
+ return (): Promise<unknown> => {
1652
+ return isAlert
1653
+ ? AlertSeverityService.deleteOneById({
1654
+ id: id,
1655
+ props: ROOT,
1656
+ })
1657
+ : IncidentSeverityService.deleteOneById({
1658
+ id: id,
1659
+ props: ROOT,
1660
+ });
1661
+ };
1662
+ },
1663
+ ),
1664
+ );
1665
+
1666
+ expect(await stored(both)).toMatchObject({
1667
+ enabled: false,
1668
+ incidentSeverityIds: [],
1669
+ alertSeverityIds: [],
1670
+ });
1671
+ expect(await storedOptions(both)).toEqual(MARKED);
1672
+
1673
+ expect(await stored(keepsMinor)).toMatchObject({
1674
+ enabled: true,
1675
+ incidentSeverityIds: isAlert
1676
+ ? ids([critical, minor])
1677
+ : ids([minor]),
1678
+ });
1679
+ expect(await storedOptions(keepsMinor)).toBeNull();
1680
+ },
1681
+ );
1682
+
1683
+ test("a rule an admin had paused is marked too, so switching it on later cannot widen it", async () => {
1684
+ const paused: ObjectID = await createRule({
1685
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1686
+ notificationChannel: ComplianceNotificationChannel.Call,
1687
+ incidentSeverities: asStrings([critical]),
1688
+ enabled: false,
1689
+ });
1690
+
1691
+ await IncidentSeverityService.deleteOneById({
1692
+ id: critical,
1693
+ props: ROOT,
1694
+ });
1695
+
1696
+ expect(await storedOptions(paused)).toEqual(MARKED);
1697
+
1698
+ await expect(updateRule(paused, { enabled: true })).rejects.toThrow(
1699
+ SEVERITIES_DELETED_ENABLE_MESSAGE,
1700
+ );
1701
+ expect((await stored(paused))?.enabled).toBe(false);
1702
+ });
1703
+
1704
+ /*
1705
+ * The rule a delete emptied is stored with no severities - exactly how
1706
+ * the team's real "every severity" rule of the same type and channel
1707
+ * is stored. It must neither block that rule's edit form as a
1708
+ * duplicate, nor be switched back on as a second copy of it.
1709
+ */
1710
+ test("a rule a delete emptied duplicates nothing, cannot be switched on as it is, and is re-scoped from its edit form", async () => {
1711
+ const everySeverity: ObjectID = await createRule({
1712
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1713
+ notificationChannel: ComplianceNotificationChannel.Call,
1714
+ });
1715
+ const onlyCritical: ObjectID = await createRule({
1716
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1717
+ notificationChannel: ComplianceNotificationChannel.Call,
1718
+ incidentSeverities: asStrings([critical]),
1719
+ });
1720
+
1721
+ await IncidentSeverityService.deleteOneById({
1722
+ id: critical,
1723
+ props: ROOT,
1724
+ });
1725
+
1726
+ // The every-severity rule's edit form sends its whole scope.
1727
+ await updateRule(everySeverity, {
1728
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1729
+ notificationChannel: ComplianceNotificationChannel.Call,
1730
+ incidentSeverities: [],
1731
+ alertSeverities: [],
1732
+ enabled: false,
1733
+ });
1734
+
1735
+ expect((await stored(everySeverity))?.enabled).toBe(false);
1736
+
1737
+ await expect(
1738
+ updateRule(onlyCritical, { enabled: true }),
1739
+ ).rejects.toThrow(SEVERITIES_DELETED_ENABLE_MESSAGE);
1740
+
1741
+ expect((await stored(onlyCritical))?.enabled).toBe(false);
1742
+ expect(await storedOptions(onlyCritical)).toEqual(MARKED);
1743
+
1744
+ // The emptied rule's edit form: new severities, switched back on.
1745
+ await updateRule(onlyCritical, {
1746
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1747
+ notificationChannel: ComplianceNotificationChannel.Call,
1748
+ incidentSeverities: asJson([major]),
1749
+ alertSeverities: [],
1750
+ enabled: true,
1751
+ });
1752
+
1753
+ expect(await stored(onlyCritical)).toEqual({
1754
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1755
+ notificationChannel: ComplianceNotificationChannel.Call,
1756
+ enabled: true,
1757
+ incidentSeverityIds: ids([major]),
1758
+ alertSeverityIds: [],
1759
+ });
1760
+ expect(await storedOptions(onlyCritical)).toBeNull();
1761
+
1762
+ // An ordinary rule again: it can now be switched off and on.
1763
+ await updateRule(onlyCritical, { enabled: false });
1764
+ await updateRule(onlyCritical, { enabled: true });
1765
+ expect((await stored(onlyCritical))?.enabled).toBe(true);
1766
+ });
1767
+
1768
+ test("re-scoping an emptied rule keeps every other option it carries", async () => {
1769
+ const onlyCritical: ObjectID = await createRule({
1770
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1771
+ notificationChannel: ComplianceNotificationChannel.Call,
1772
+ incidentSeverities: asStrings([critical]),
1773
+ });
1774
+
1775
+ await database.query(
1776
+ `UPDATE "${schema}"."TeamComplianceSetting" SET "options" = $1 WHERE "_id" = $2`,
1777
+ [JSON.stringify({ note: "keep me" }), onlyCritical.toString()],
1778
+ );
1779
+
1780
+ await IncidentSeverityService.deleteOneById({
1781
+ id: critical,
1782
+ props: ROOT,
1783
+ });
1784
+
1785
+ expect(await storedOptions(onlyCritical)).toEqual({
1786
+ note: "keep me",
1787
+ ...MARKED,
1788
+ });
1789
+
1790
+ await updateRule(onlyCritical, {
1791
+ incidentSeverities: asJson([minor]),
1792
+ });
1793
+
1794
+ expect(await storedOptions(onlyCritical)).toEqual({ note: "keep me" });
1795
+ });
1796
+
1797
+ test("a delete the database refuses - the severity is still in use - pauses nothing", async () => {
1798
+ const onlyCritical: ObjectID = await createRule({
1799
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1800
+ notificationChannel: ComplianceNotificationChannel.Call,
1801
+ incidentSeverities: asStrings([critical]),
1802
+ });
1803
+
1804
+ /*
1805
+ * Incidents reference their severity with ON DELETE NO ACTION, so a
1806
+ * severity any incident used cannot be deleted. A stand-in table
1807
+ * with the same kind of reference.
1808
+ */
1809
+ await database.query(
1810
+ `CREATE TABLE "${schema}"."SeverityInUse" ("incidentSeverityId" uuid NOT NULL REFERENCES "${schema}"."IncidentSeverity"("_id"))`,
1811
+ );
1812
+
1813
+ try {
1814
+ await database.query(
1815
+ `INSERT INTO "${schema}"."SeverityInUse" ("incidentSeverityId") VALUES ($1)`,
1816
+ [critical.toString()],
1817
+ );
1818
+
1819
+ await expect(
1820
+ IncidentSeverityService.deleteOneById({
1821
+ id: critical,
1822
+ props: ROOT,
1823
+ }),
1824
+ ).rejects.toThrow();
1825
+ } finally {
1826
+ await database.query(`DROP TABLE "${schema}"."SeverityInUse"`);
1827
+ failedQueries.failures = [];
1828
+ }
1829
+
1830
+ expect(await stored(onlyCritical)).toEqual({
1831
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1832
+ notificationChannel: ComplianceNotificationChannel.Call,
1833
+ enabled: true,
1834
+ incidentSeverityIds: ids([critical]),
1835
+ alertSeverityIds: [],
1836
+ });
1837
+ });
1838
+
1839
+ test("deleting a rule through the service removes its join rows and nobody else's", async () => {
1840
+ const id: ObjectID = await createRule({
1841
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1842
+ notificationChannel: ComplianceNotificationChannel.Call,
1843
+ incidentSeverities: asStrings([critical, major]),
1844
+ });
1845
+
1846
+ const sibling: ObjectID = await createRule({
1847
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1848
+ notificationChannel: ComplianceNotificationChannel.Push,
1849
+ incidentSeverities: asStrings([critical]),
1850
+ });
1851
+
1852
+ await TeamComplianceSettingService.deleteOneById({
1853
+ id: id,
1854
+ props: ROOT,
1855
+ });
1856
+
1857
+ expect(await stored(id)).toBeNull();
1858
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual([]);
1859
+ expect(await joinRows(INCIDENT_JOIN_TABLE, sibling)).toEqual(
1860
+ ids([critical]),
1861
+ );
1862
+ });
1863
+ });
1864
+
1865
+ /*
1866
+ * The dashboard's path: a signed-in project admin, through the permission
1867
+ * layer, with the request's project as the tenant.
1868
+ */
1869
+ describe("as a signed-in project admin", () => {
1870
+ let licenceCheck: jest.SpyInstance;
1871
+
1872
+ beforeAll(() => {
1873
+ licenceCheck = jest
1874
+ .spyOn(EnterpriseEdition, "assertFeatureAvailableSync")
1875
+ .mockReturnValue(undefined);
1876
+ });
1877
+
1878
+ afterAll(() => {
1879
+ licenceCheck.mockRestore();
1880
+ });
1881
+
1882
+ test("creates, reads back and rescopes a rule of their project", async () => {
1883
+ const admin: DatabaseCommonInteractionProps = memberProps(projectId, [
1884
+ Permission.ProjectAdmin,
1885
+ ]);
1886
+
1887
+ const id: ObjectID = await createRule(
1888
+ {
1889
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1890
+ notificationChannel: ComplianceNotificationChannel.Call,
1891
+ incidentSeverities: asIncidentModels([critical]),
1892
+ },
1893
+ admin,
1894
+ );
1895
+
1896
+ expect(await stored(id)).toEqual({
1897
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1898
+ notificationChannel: ComplianceNotificationChannel.Call,
1899
+ enabled: true,
1900
+ incidentSeverityIds: ids([critical]),
1901
+ alertSeverityIds: [],
1902
+ });
1903
+
1904
+ const rule: TeamComplianceSetting | null = await readRule(id, admin);
1905
+
1906
+ expect(toReadSeverities(rule?.incidentSeverities)).toEqual([
1907
+ {
1908
+ id: critical.toString(),
1909
+ name: "Critical Incident",
1910
+ color: "#FF0000",
1911
+ order: 1,
1912
+ },
1913
+ ]);
1914
+
1915
+ await updateRule(
1916
+ id,
1917
+ { incidentSeverities: asJson([critical, major]) },
1918
+ admin,
1919
+ );
1920
+
1921
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual(
1922
+ ids([critical, major]),
1923
+ );
1924
+
1925
+ await expect(
1926
+ createRule(
1927
+ {
1928
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1929
+ notificationChannel: ComplianceNotificationChannel.Call,
1930
+ incidentSeverities: asJson([major, critical]),
1931
+ },
1932
+ admin,
1933
+ ),
1934
+ ).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
1935
+ });
1936
+
1937
+ test("a signed-in delete by a project admin pauses the rule too, and still re-ranks the severities that remain", async () => {
1938
+ const admin: DatabaseCommonInteractionProps = memberProps(projectId, [
1939
+ Permission.ProjectAdmin,
1940
+ ]);
1941
+
1942
+ const onlyCritical: ObjectID = await createRule({
1943
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1944
+ notificationChannel: ComplianceNotificationChannel.Call,
1945
+ incidentSeverities: asStrings([critical]),
1946
+ });
1947
+
1948
+ await IncidentSeverityService.deleteOneById({
1949
+ id: critical,
1950
+ props: admin,
1951
+ });
1952
+
1953
+ expect((await stored(onlyCritical))?.enabled).toBe(false);
1954
+
1955
+ const orders: Array<SqlRow> = await database.query(
1956
+ `SELECT "_id", "order" FROM "${schema}"."IncidentSeverity" WHERE "projectId" = $1 ORDER BY "order"`,
1957
+ [projectId.toString()],
1958
+ );
1959
+
1960
+ expect(orders).toEqual([
1961
+ { _id: major.toString(), order: 1 },
1962
+ { _id: minor.toString(), order: 2 },
1963
+ ]);
1964
+ });
1965
+
1966
+ /*
1967
+ * The hooks read as root in the project the request names, and
1968
+ * DatabaseService only checks the caller's permission after them. They
1969
+ * check it first, so a caller with no say over the project's rules is
1970
+ * refused before any of their answers - "not a severity of this
1971
+ * project", "this team already has that rule" - can describe it.
1972
+ */
1973
+ test("a caller who may not change the project's rules learns nothing about its severities or rules", async () => {
1974
+ await createRule({
1975
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
1976
+ notificationChannel: ComplianceNotificationChannel.Call,
1977
+ incidentSeverities: asStrings([critical]),
1978
+ });
1979
+
1980
+ const outsiders: Array<DatabaseCommonInteractionProps> = [
1981
+ // Signed in elsewhere, naming this project.
1982
+ {
1983
+ ...memberProps(otherProjectId, [Permission.ProjectAdmin]),
1984
+ tenantId: projectId,
1985
+ },
1986
+ // A member who may only read the project's teams.
1987
+ memberProps(projectId, [
1988
+ Permission.ProjectMember,
1989
+ Permission.ReadProjectTeam,
1990
+ ]),
1991
+ ];
1992
+
1993
+ for (const props of outsiders) {
1994
+ // A foreign severity, and an exact duplicate: both refused alike.
1995
+ for (const severity of [otherProjectCritical, critical]) {
1996
+ await expect(
1997
+ createRule(
1998
+ {
1999
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
2000
+ notificationChannel: ComplianceNotificationChannel.Call,
2001
+ incidentSeverities: asStrings([severity]),
2002
+ },
2003
+ props,
2004
+ ),
2005
+ ).rejects.toThrow(NotAuthorizedException);
2006
+ }
2007
+ }
2008
+
2009
+ expect(await countRules()).toBe(1);
2010
+ });
2011
+
2012
+ test("a team editor creates and rescopes a rule; cleared options pass the permission check", async () => {
2013
+ const editor: DatabaseCommonInteractionProps = memberProps(projectId, [
2014
+ Permission.ReadProjectTeam,
2015
+ Permission.EditProjectTeam,
2016
+ ]);
2017
+
2018
+ // A method rule sent with options it does not take: cleared, allowed.
2019
+ const method: ObjectID = await createRule(
2020
+ {
2021
+ ruleType: ComplianceRuleType.HasNotificationCallMethod,
2022
+ notificationChannel: ComplianceNotificationChannel.SMS,
2023
+ incidentSeverities: asStrings([critical]),
2024
+ },
2025
+ editor,
2026
+ );
2027
+
2028
+ expect(await stored(method)).toEqual({
2029
+ ruleType: ComplianceRuleType.HasNotificationCallMethod,
2030
+ notificationChannel: null,
2031
+ enabled: true,
2032
+ incidentSeverityIds: [],
2033
+ alertSeverityIds: [],
2034
+ });
2035
+
2036
+ const id: ObjectID = await createRule(
2037
+ {
2038
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
2039
+ notificationChannel: ComplianceNotificationChannel.Call,
2040
+ incidentSeverities: asIncidentModels([critical]),
2041
+ alertSeverities: asStrings([criticalAlert]),
2042
+ },
2043
+ editor,
2044
+ );
2045
+
2046
+ await updateRule(
2047
+ id,
2048
+ { incidentSeverities: [upper(major), { _id: critical.toString() }] },
2049
+ editor,
2050
+ );
2051
+
2052
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual(
2053
+ ids([critical, major]),
2054
+ );
2055
+
2056
+ await updateRule(
2057
+ id,
2058
+ { ruleType: ComplianceRuleType.HasNotificationPushMethod },
2059
+ editor,
2060
+ );
2061
+
2062
+ expect(await stored(id)).toEqual({
2063
+ ruleType: ComplianceRuleType.HasNotificationPushMethod,
2064
+ notificationChannel: null,
2065
+ enabled: true,
2066
+ incidentSeverityIds: [],
2067
+ alertSeverityIds: [],
2068
+ });
2069
+
2070
+ // Edit Teams on its own creates, as it did before.
2071
+ await createRule(
2072
+ { ruleType: ComplianceRuleType.HasNotificationEmailMethod },
2073
+ memberProps(projectId, [Permission.EditProjectTeam]),
2074
+ );
2075
+
2076
+ expect(await countRules()).toBe(3);
2077
+ });
2078
+
2079
+ /*
2080
+ * A row whose stored rule type this build does not recognise - legacy
2081
+ * data, or written by a newer build before a rollback - still gets
2082
+ * whatever severity list an update sends written to it, so the list is
2083
+ * checked against the row's project by its own kind.
2084
+ */
2085
+ test("cannot link another project's severity to a rule of an unrecognised type", async () => {
2086
+ const admin: DatabaseCommonInteractionProps = memberProps(projectId, [
2087
+ Permission.ProjectAdmin,
2088
+ ]);
2089
+
2090
+ const id: ObjectID = await createRule({
2091
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
2092
+ });
2093
+
2094
+ await database.query(
2095
+ `UPDATE "${schema}"."TeamComplianceSetting" SET "ruleType" = 'LegacyRule' WHERE "_id" = $1`,
2096
+ [id.toString()],
2097
+ );
2098
+
2099
+ await expect(
2100
+ updateRule(
2101
+ id,
2102
+ { incidentSeverities: asJson([otherProjectCritical]) },
2103
+ admin,
2104
+ ),
2105
+ ).rejects.toThrow(FOREIGN_SEVERITY_MESSAGE);
2106
+
2107
+ await expect(
2108
+ updateRule(
2109
+ id,
2110
+ { alertSeverities: asJson([otherProjectCriticalAlert]) },
2111
+ admin,
2112
+ ),
2113
+ ).rejects.toThrow(FOREIGN_ALERT_SEVERITY_MESSAGE);
2114
+
2115
+ expect(await countJoinRows()).toBe(0);
2116
+
2117
+ // This project's own severities are still accepted.
2118
+ await updateRule(id, { incidentSeverities: asJson([critical]) }, admin);
2119
+
2120
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual(
2121
+ ids([critical]),
2122
+ );
2123
+ });
2124
+
2125
+ test("a project admin cannot switch a rule a delete emptied back on, but can re-scope it", async () => {
2126
+ const admin: DatabaseCommonInteractionProps = memberProps(projectId, [
2127
+ Permission.ProjectAdmin,
2128
+ ]);
2129
+
2130
+ const onlyCritical: ObjectID = await createRule({
2131
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
2132
+ notificationChannel: ComplianceNotificationChannel.Call,
2133
+ incidentSeverities: asStrings([critical]),
2134
+ });
2135
+
2136
+ await IncidentSeverityService.deleteOneById({
2137
+ id: critical,
2138
+ props: admin,
2139
+ });
2140
+
2141
+ await expect(
2142
+ updateRule(onlyCritical, { enabled: true }, admin),
2143
+ ).rejects.toThrow(SEVERITIES_DELETED_ENABLE_MESSAGE);
2144
+
2145
+ // A caller with no say in the project is refused before any read.
2146
+ await expect(
2147
+ updateRule(
2148
+ onlyCritical,
2149
+ { enabled: true },
2150
+ {
2151
+ ...memberProps(otherProjectId, [Permission.ProjectAdmin]),
2152
+ tenantId: projectId,
2153
+ },
2154
+ ),
2155
+ ).rejects.toThrow(NotAuthorizedException);
2156
+
2157
+ await updateRule(
2158
+ onlyCritical,
2159
+ { incidentSeverities: asJson([major]), enabled: true },
2160
+ admin,
2161
+ );
2162
+
2163
+ expect(await stored(onlyCritical)).toMatchObject({
2164
+ enabled: true,
2165
+ incidentSeverityIds: ids([major]),
2166
+ });
2167
+ expect(await storedOptions(onlyCritical)).toBeNull();
2168
+ });
2169
+
2170
+ test("cannot rescope another project's rule", async () => {
2171
+ const id: ObjectID = await createRule({
2172
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
2173
+ notificationChannel: ComplianceNotificationChannel.Call,
2174
+ incidentSeverities: asStrings([critical]),
2175
+ });
2176
+
2177
+ const otherAdmin: DatabaseCommonInteractionProps = memberProps(
2178
+ otherProjectId,
2179
+ [Permission.ProjectAdmin],
2180
+ );
2181
+
2182
+ await updateRule(
2183
+ id,
2184
+ { incidentSeverities: asJson([otherProjectCritical]) },
2185
+ otherAdmin,
2186
+ );
2187
+
2188
+ expect(await joinRows(INCIDENT_JOIN_TABLE, id)).toEqual(
2189
+ ids([critical]),
2190
+ );
2191
+ });
2192
+
2193
+ /*
2194
+ * The request's project is stamped on the payload before the hooks run
2195
+ * (DatabaseService._onBeforeCreate), so the severity check is made
2196
+ * against the admin's own project whatever projectId the payload names.
2197
+ */
2198
+ test("cannot attach another project's severity by naming that project in the payload", async () => {
2199
+ const admin: DatabaseCommonInteractionProps = memberProps(projectId, [
2200
+ Permission.ProjectAdmin,
2201
+ ]);
2202
+
2203
+ await expect(
2204
+ createRule(
2205
+ {
2206
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
2207
+ notificationChannel: ComplianceNotificationChannel.Call,
2208
+ incidentSeverities: asIncidentModels([otherProjectCritical]),
2209
+ projectId: otherProjectId,
2210
+ },
2211
+ admin,
2212
+ ),
2213
+ ).rejects.toThrow(FOREIGN_SEVERITY_MESSAGE);
2214
+
2215
+ expect(await countRules()).toBe(0);
2216
+ expect(await countJoinRows()).toBe(0);
2217
+
2218
+ const id: ObjectID = await createRule(
2219
+ {
2220
+ ruleType: ComplianceRuleType.HasIncidentOnCallRules,
2221
+ notificationChannel: ComplianceNotificationChannel.Call,
2222
+ incidentSeverities: asIncidentModels([critical]),
2223
+ projectId: otherProjectId,
2224
+ },
2225
+ admin,
2226
+ );
2227
+
2228
+ const rows: Array<SqlRow> = await database.query(
2229
+ `SELECT "projectId" FROM "${schema}"."TeamComplianceSetting" WHERE "_id" = $1`,
2230
+ [id.toString()],
2231
+ );
2232
+
2233
+ expect(rows).toEqual([{ projectId: projectId.toString() }]);
2234
+ });
2235
+ });
2236
+ },
2237
+ );