@oneuptime/common 14.0.7 → 14.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1064) hide show
  1. package/Models/DatabaseModels/Incident.ts +267 -1
  2. package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
  3. package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
  4. package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
  5. package/Models/DatabaseModels/Index.ts +2 -0
  6. package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
  7. package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
  8. package/Models/DatabaseModels/Project.ts +55 -5
  9. package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
  10. package/Models/DatabaseModels/RunnerJob.ts +75 -0
  11. package/Models/DatabaseModels/StatusPage.ts +55 -0
  12. package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
  13. package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
  14. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
  15. package/Server/API/AIChatAPI.ts +6 -3
  16. package/Server/API/AIInvestigationAPI.ts +17 -10
  17. package/Server/API/AutoRemediationAPI.ts +207 -25
  18. package/Server/API/IncidentAPI.ts +162 -0
  19. package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
  20. package/Server/API/MicrosoftTeamsAPI.ts +93 -14
  21. package/Server/API/ProxmoxResourceAPI.ts +244 -14
  22. package/Server/API/StatusPageAPI.ts +487 -171
  23. package/Server/API/StatusPageSubscriberAPI.ts +63 -15
  24. package/Server/Infrastructure/GlobalCache.ts +32 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
  36. package/Server/Infrastructure/Semaphore.ts +10 -0
  37. package/Server/Middleware/IdentityRateLimit.ts +108 -3
  38. package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
  39. package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
  40. package/Server/Middleware/TelemetryIngest.ts +41 -18
  41. package/Server/Services/AIService.ts +70 -1
  42. package/Server/Services/AlertService.ts +352 -11
  43. package/Server/Services/AlertSeverityService.ts +65 -15
  44. package/Server/Services/AutoRemediationRuleService.ts +1 -1
  45. package/Server/Services/DatabaseService.ts +93 -10
  46. package/Server/Services/IncidentCustomFieldService.ts +276 -1
  47. package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
  48. package/Server/Services/IncidentEpisodeService.ts +15 -0
  49. package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
  50. package/Server/Services/IncidentPublicNoteService.ts +30 -1
  51. package/Server/Services/IncidentService.ts +1788 -142
  52. package/Server/Services/IncidentSeverityService.ts +66 -15
  53. package/Server/Services/IncidentStateTimelineService.ts +13 -0
  54. package/Server/Services/IncidentTemplateService.ts +109 -0
  55. package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
  56. package/Server/Services/KubernetesAiAgentService.ts +1634 -0
  57. package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
  58. package/Server/Services/KubernetesClusterService.ts +129 -21
  59. package/Server/Services/ProjectService.ts +32 -0
  60. package/Server/Services/ProxmoxResourceService.ts +322 -5
  61. package/Server/Services/RunnerJobService.ts +241 -104
  62. package/Server/Services/RunnerService.ts +1 -1
  63. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
  64. package/Server/Services/ScheduledMaintenanceService.ts +805 -19
  65. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
  66. package/Server/Services/SmsService.ts +10 -0
  67. package/Server/Services/StatusPageService.ts +65 -8
  68. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
  69. package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
  70. package/Server/Services/StatusPageSubscriberService.ts +1161 -64
  71. package/Server/Services/TeamComplianceSettingService.ts +1125 -9
  72. package/Server/Types/Markdown.ts +153 -6
  73. package/Server/Types/Workflow/Components/Email.ts +61 -2
  74. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
  75. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
  76. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
  77. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
  78. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
  79. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
  80. package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
  81. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
  82. package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
  83. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
  84. package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
  85. package/Server/Utils/DataSource/EgressGuard.ts +66 -21
  86. package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
  87. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
  88. package/Server/Utils/SSRFProtection.ts +76 -16
  89. package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
  90. package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
  91. package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
  92. package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
  93. package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
  94. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
  95. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
  96. package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
  97. package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
  98. package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
  99. package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
  100. package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
  101. package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
  102. package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
  103. package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
  104. package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
  105. package/Server/Utils/StatusPageResource.ts +51 -20
  106. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
  107. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
  108. package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
  109. package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
  110. package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
  111. package/Server/Utils/VM/VMAPI.ts +1 -0
  112. package/Server/Utils/VM/VMRunner.ts +10 -0
  113. package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
  114. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
  115. package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
  116. package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
  117. package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
  118. package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
  119. package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
  120. package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
  121. package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
  122. package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
  123. package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
  124. package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
  125. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
  126. package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
  127. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
  128. package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
  129. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
  130. package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
  131. package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
  132. package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
  133. package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
  134. package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
  135. package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
  136. package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
  137. package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
  138. package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
  139. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
  140. package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
  141. package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
  142. package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
  143. package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
  144. package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
  145. package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
  146. package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
  147. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
  148. package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
  149. package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
  150. package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
  151. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
  152. package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
  153. package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
  154. package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
  155. package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
  156. package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
  157. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
  158. package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
  159. package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
  160. package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
  161. package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
  162. package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
  163. package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
  164. package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
  165. package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
  166. package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
  167. package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
  168. package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
  169. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
  170. package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
  171. package/Tests/App/Dashboard/EventNotes.test.tsx +778 -2
  172. package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
  173. package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
  174. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +828 -17
  175. package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
  176. package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
  177. package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
  178. package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
  179. package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
  180. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
  181. package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
  182. package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
  183. package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
  184. package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
  185. package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
  186. package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
  187. package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
  188. package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
  189. package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
  190. package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
  191. package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
  192. package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
  193. package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
  194. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
  195. package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
  196. package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
  197. package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
  198. package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
  199. package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
  200. package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
  201. package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
  202. package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
  203. package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
  204. package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
  205. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
  206. package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
  207. package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
  208. package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
  209. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
  210. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
  211. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
  212. package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
  213. package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
  214. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
  215. package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
  216. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
  217. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
  218. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
  219. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
  220. package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
  221. package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
  222. package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
  223. package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
  224. package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
  225. package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
  226. package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
  227. package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
  228. package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
  229. package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
  230. package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
  231. package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
  232. package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
  233. package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
  234. package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
  235. package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
  236. package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
  237. package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
  238. package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
  239. package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
  240. package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
  241. package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
  242. package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
  243. package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
  244. package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
  245. package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
  246. package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
  247. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
  248. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
  249. package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
  250. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
  251. package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
  252. package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
  253. package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
  254. package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
  255. package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
  256. package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
  257. package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
  258. package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
  259. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
  260. package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
  261. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
  262. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
  263. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
  264. package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
  265. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
  266. package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
  267. package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
  268. package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
  269. package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
  270. package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
  271. package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
  272. package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
  273. package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
  274. package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
  275. package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
  276. package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
  277. package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
  278. package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
  279. package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
  280. package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
  281. package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
  282. package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
  283. package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
  284. package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
  285. package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
  286. package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
  287. package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
  288. package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
  289. package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
  290. package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
  291. package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
  292. package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
  293. package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
  294. package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
  295. package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
  296. package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
  297. package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
  298. package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
  299. package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
  300. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
  301. package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
  302. package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
  303. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
  304. package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
  305. package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
  306. package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
  307. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
  308. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
  309. package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
  310. package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
  311. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
  312. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
  313. package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
  314. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
  315. package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
  316. package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
  317. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
  318. package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
  319. package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
  320. package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
  321. package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
  322. package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
  323. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
  324. package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
  325. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
  326. package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
  327. package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
  328. package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
  329. package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
  330. package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
  331. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
  332. package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
  333. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
  334. package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
  335. package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
  336. package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
  337. package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
  338. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
  339. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
  340. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
  341. package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
  342. package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
  343. package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
  344. package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
  345. package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
  346. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
  347. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
  348. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
  349. package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
  350. package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
  351. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
  352. package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
  353. package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
  354. package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
  355. package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
  356. package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
  357. package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
  358. package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
  359. package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
  360. package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
  361. package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
  362. package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
  363. package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
  364. package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
  365. package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
  366. package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
  367. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
  368. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
  369. package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
  370. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
  371. package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
  372. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
  373. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
  374. package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
  375. package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
  376. package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
  377. package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
  378. package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
  379. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
  380. package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
  381. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
  382. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
  383. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
  384. package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
  385. package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
  386. package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
  387. package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
  388. package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
  389. package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
  390. package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
  391. package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
  392. package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
  393. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
  394. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
  395. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
  396. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
  397. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
  398. package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
  399. package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
  400. package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
  401. package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
  402. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
  403. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
  404. package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
  405. package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
  406. package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
  407. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
  408. package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
  409. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
  410. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
  411. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
  412. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
  413. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
  414. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
  415. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
  416. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
  417. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
  418. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
  419. package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
  420. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
  421. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
  422. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
  423. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
  424. package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
  425. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
  426. package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
  427. package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
  428. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
  429. package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
  430. package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
  431. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
  432. package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
  433. package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
  434. package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
  435. package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
  436. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
  437. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
  438. package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
  439. package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
  440. package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
  441. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
  442. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
  443. package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
  444. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
  445. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
  446. package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
  447. package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
  448. package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
  449. package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
  450. package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
  451. package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
  452. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
  453. package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
  454. package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
  455. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
  456. package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
  457. package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
  458. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
  459. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
  460. package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
  461. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
  462. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
  463. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
  464. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
  465. package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
  466. package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
  467. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
  468. package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
  469. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
  470. package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
  471. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
  472. package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
  473. package/Tests/Types/SafeHtml.test.ts +102 -0
  474. package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
  475. package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
  476. package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
  477. package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
  478. package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
  479. package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
  480. package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
  481. package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
  482. package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
  483. package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
  484. package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
  485. package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
  486. package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
  487. package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
  488. package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
  489. package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
  490. package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
  491. package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
  492. package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
  493. package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
  494. package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
  495. package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
  496. package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
  497. package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
  498. package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
  499. package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
  500. package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
  501. package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
  502. package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
  503. package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
  504. package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
  505. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
  506. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
  507. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
  508. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
  509. package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
  510. package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
  511. package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
  512. package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
  513. package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
  514. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
  515. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
  516. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
  517. package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
  518. package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
  519. package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
  520. package/Tests/UI/Components/Input.test.tsx +80 -1
  521. package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
  522. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
  523. package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
  524. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
  525. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
  526. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
  527. package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
  528. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
  529. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
  530. package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
  531. package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
  532. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
  533. package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
  534. package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
  535. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
  536. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
  537. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
  538. package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
  539. package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
  540. package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
  541. package/Tests/UI/Components/Toggle.test.tsx +66 -0
  542. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
  543. package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
  544. package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
  545. package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
  546. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
  547. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
  548. package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
  549. package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
  550. package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
  551. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
  552. package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
  553. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
  554. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
  555. package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
  556. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
  557. package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
  558. package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
  559. package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
  560. package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
  561. package/Types/AI/AIRunType.ts +9 -4
  562. package/Types/AI/InvestigationNotStartedReason.ts +6 -0
  563. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
  564. package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
  565. package/Types/CustomField/CustomFieldOrder.ts +55 -0
  566. package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
  567. package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
  568. package/Types/CustomField/CustomFieldType.ts +14 -0
  569. package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
  570. package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
  571. package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
  572. package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
  573. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
  574. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
  575. package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
  576. package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
  577. package/Types/SafeHtml.ts +93 -0
  578. package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
  579. package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
  580. package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
  581. package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
  582. package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
  583. package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
  584. package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
  585. package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
  586. package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
  587. package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
  588. package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
  589. package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
  590. package/Types/Team/ComplianceNotificationChannel.ts +22 -0
  591. package/Types/Team/ComplianceRule.ts +434 -0
  592. package/Types/Team/ComplianceRuleType.ts +21 -0
  593. package/Types/Team/TeamComplianceStatus.ts +81 -0
  594. package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
  595. package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
  596. package/UI/Components/ActionButton/RowActions.tsx +238 -0
  597. package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
  598. package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
  599. package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
  600. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
  601. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
  602. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
  603. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
  604. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
  605. package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
  606. package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
  607. package/UI/Components/Charts/Line/LineChart.tsx +32 -2
  608. package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
  609. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
  610. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
  611. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
  612. package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
  613. package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
  614. package/UI/Components/Charts/Utils/XAxis.ts +75 -0
  615. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
  616. package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
  617. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
  618. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
  619. package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
  620. package/UI/Components/Forms/Fields/FormField.tsx +10 -0
  621. package/UI/Components/Forms/ModelForm.tsx +70 -8
  622. package/UI/Components/Forms/Validation.ts +1 -1
  623. package/UI/Components/Input/Input.tsx +90 -57
  624. package/UI/Components/List/ListRow.tsx +8 -74
  625. package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
  626. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
  627. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
  628. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
  629. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
  630. package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
  631. package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
  632. package/UI/Components/ModelTable/BaseModelTable.tsx +21 -9
  633. package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
  634. package/UI/Components/ModelTable/ModelTable.tsx +6 -7
  635. package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
  636. package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
  637. package/UI/Components/OrderedStatesList/Item.tsx +7 -72
  638. package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
  639. package/UI/Components/Table/TableBody.tsx +8 -2
  640. package/UI/Components/Table/TableRow.tsx +20 -140
  641. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
  642. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
  643. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
  644. package/UI/Components/TelemetryViewer/types.ts +2 -0
  645. package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
  646. package/UI/Components/Toggle/Toggle.tsx +26 -0
  647. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  648. package/UI/Styles/Theme.css +14 -2
  649. package/UI/Utils/SensitiveUrlToken.ts +10 -5
  650. package/Utils/API.ts +247 -2
  651. package/Utils/AiRemediation/CommandPolicy.ts +1 -1
  652. package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
  653. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
  654. package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
  655. package/Utils/Markdown/MarkdownEscape.ts +65 -0
  656. package/Utils/ProbeNetworkFailureUtil.ts +673 -0
  657. package/build/dist/Models/DatabaseModels/Incident.js +267 -1
  658. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  659. package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
  660. package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
  661. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
  662. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  663. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
  664. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  665. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  666. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  667. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
  668. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
  669. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
  670. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  671. package/build/dist/Models/DatabaseModels/Project.js +56 -5
  672. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  673. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
  674. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
  675. package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
  676. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  677. package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
  678. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  679. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
  680. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  681. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
  682. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  683. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  684. package/build/dist/Server/API/AIChatAPI.js +6 -1
  685. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  686. package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
  687. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  688. package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
  689. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  690. package/build/dist/Server/API/IncidentAPI.js +100 -1
  691. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  692. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
  693. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
  694. package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
  695. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  696. package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
  697. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
  698. package/build/dist/Server/API/StatusPageAPI.js +417 -176
  699. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  700. package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
  701. package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
  702. package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
  703. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  704. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
  705. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
  706. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
  707. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
  708. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
  709. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
  710. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
  711. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
  712. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
  713. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
  714. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
  715. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
  716. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
  717. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
  718. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
  719. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
  720. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
  721. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
  722. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
  723. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
  724. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
  725. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  726. package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
  727. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  728. package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
  729. package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
  730. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
  731. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
  732. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
  733. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
  734. package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
  735. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  736. package/build/dist/Server/Services/AIService.js +59 -1
  737. package/build/dist/Server/Services/AIService.js.map +1 -1
  738. package/build/dist/Server/Services/AlertService.js +248 -13
  739. package/build/dist/Server/Services/AlertService.js.map +1 -1
  740. package/build/dist/Server/Services/AlertSeverityService.js +51 -15
  741. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  742. package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
  743. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  744. package/build/dist/Server/Services/DatabaseService.js +56 -7
  745. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  746. package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
  747. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  748. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
  749. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  750. package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
  751. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  752. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
  753. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  754. package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
  755. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  756. package/build/dist/Server/Services/IncidentService.js +1251 -72
  757. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  758. package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
  759. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  760. package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
  761. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  762. package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
  763. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  764. package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
  765. package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
  766. package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
  767. package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
  768. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
  769. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
  770. package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
  771. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  772. package/build/dist/Server/Services/ProjectService.js +22 -0
  773. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  774. package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
  775. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  776. package/build/dist/Server/Services/RunnerJobService.js +178 -92
  777. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  778. package/build/dist/Server/Services/RunnerService.js +1 -1
  779. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  780. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
  781. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  782. package/build/dist/Server/Services/ScheduledMaintenanceService.js +582 -20
  783. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  784. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
  785. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  786. package/build/dist/Server/Services/SmsService.js +2 -22
  787. package/build/dist/Server/Services/SmsService.js.map +1 -1
  788. package/build/dist/Server/Services/StatusPageService.js +47 -9
  789. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  790. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
  791. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  792. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
  793. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
  794. package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
  795. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  796. package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
  797. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  798. package/build/dist/Server/Types/Markdown.js +110 -6
  799. package/build/dist/Server/Types/Markdown.js.map +1 -1
  800. package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
  801. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  802. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
  803. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
  804. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
  805. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  806. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
  807. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  808. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
  809. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  810. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
  811. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  812. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
  813. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  814. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
  815. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
  816. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
  817. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
  818. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
  819. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
  820. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  821. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
  822. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
  823. package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
  824. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  825. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
  826. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
  827. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
  828. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  829. package/build/dist/Server/Utils/SSRFProtection.js +48 -6
  830. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  831. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
  832. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
  833. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
  834. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
  835. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
  836. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
  837. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
  838. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
  839. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
  840. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
  841. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
  842. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
  843. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
  844. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
  845. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
  846. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
  847. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
  848. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
  849. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
  850. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
  851. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
  852. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
  853. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
  854. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
  855. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
  856. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
  857. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
  858. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
  859. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
  860. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
  861. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
  862. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
  863. package/build/dist/Server/Utils/StatusPageResource.js +39 -7
  864. package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
  865. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
  866. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  867. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
  868. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  869. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
  870. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
  871. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
  872. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
  873. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
  874. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  875. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  876. package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
  877. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  878. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
  879. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
  880. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
  881. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  882. package/build/dist/Types/AI/AIRunType.js +9 -4
  883. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  884. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
  885. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  886. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
  887. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
  888. package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
  889. package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
  890. package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
  891. package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
  892. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
  893. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
  894. package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
  895. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  896. package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
  897. package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
  898. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
  899. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
  900. package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
  901. package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
  902. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
  903. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  904. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
  905. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
  906. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
  907. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
  908. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
  909. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  910. package/build/dist/Types/SafeHtml.js +84 -0
  911. package/build/dist/Types/SafeHtml.js.map +1 -0
  912. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
  913. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
  914. package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
  915. package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
  916. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
  917. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
  918. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
  919. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
  920. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
  921. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
  922. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
  923. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
  924. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
  925. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
  926. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
  927. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
  928. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
  929. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
  930. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
  931. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
  932. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
  933. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
  934. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
  935. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
  936. package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
  937. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
  938. package/build/dist/Types/Team/ComplianceRule.js +304 -0
  939. package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
  940. package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
  941. package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
  942. package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
  943. package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
  944. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
  945. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  946. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
  947. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
  948. package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
  949. package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
  950. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
  951. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
  952. package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
  953. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  954. package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
  955. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  956. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
  957. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  958. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
  959. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  960. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
  961. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  962. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
  963. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  964. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
  965. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
  966. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
  967. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
  968. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
  969. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
  970. package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
  971. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  972. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
  973. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
  974. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
  975. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
  976. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
  977. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
  978. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
  979. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
  980. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
  981. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
  982. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
  983. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  984. package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
  985. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  986. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
  987. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
  988. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
  989. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
  990. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
  991. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
  992. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
  993. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  994. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
  995. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  996. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
  997. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  998. package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
  999. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  1000. package/build/dist/UI/Components/Forms/Validation.js +1 -1
  1001. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  1002. package/build/dist/UI/Components/Input/Input.js +69 -54
  1003. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  1004. package/build/dist/UI/Components/List/ListRow.js +4 -39
  1005. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  1006. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
  1007. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  1008. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
  1009. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  1010. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
  1011. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  1012. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
  1013. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  1014. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  1015. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  1016. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  1017. package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
  1018. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +12 -2
  1019. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  1020. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
  1021. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  1022. package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
  1023. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  1024. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
  1025. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  1026. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
  1027. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  1028. package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
  1029. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  1030. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
  1031. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  1032. package/build/dist/UI/Components/Table/TableBody.js +6 -2
  1033. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  1034. package/build/dist/UI/Components/Table/TableRow.js +12 -75
  1035. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  1036. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
  1037. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  1038. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
  1039. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  1040. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
  1041. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  1042. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
  1043. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
  1044. package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
  1045. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  1046. package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
  1047. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  1048. package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
  1049. package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
  1050. package/build/dist/Utils/API.js +177 -1
  1051. package/build/dist/Utils/API.js.map +1 -1
  1052. package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
  1053. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
  1054. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
  1055. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
  1056. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
  1057. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
  1058. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
  1059. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
  1060. package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
  1061. package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
  1062. package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
  1063. package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
  1064. package/package.json +1 -1
@@ -10,6 +10,14 @@ import QueryHelper from "../Types/Database/QueryHelper";
10
10
  import DatabaseService from "./DatabaseService";
11
11
  import IncidentCustomField from "../../Models/DatabaseModels/IncidentCustomField";
12
12
  import CustomFieldMappingService from "./CustomFieldMappingService";
13
+ import IncidentCustomFieldService from "./IncidentCustomFieldService";
14
+ import { CustomFieldDefinition } from "../../Types/CustomField/CustomFieldDefinition";
15
+ import { mergeTemplateCustomFields } from "../../Types/CustomField/CustomFieldTemplateMerge";
16
+ import {
17
+ CustomFieldValueValidationError,
18
+ formatCustomFieldValueValidationErrors,
19
+ validateCustomFieldValues,
20
+ } from "../../Types/CustomField/CustomFieldValueValidator";
13
21
  import AIRunService from "./AIRunService";
14
22
  import AIRunType from "../../Types/AI/AIRunType";
15
23
  import AIRunStatus from "../../Types/AI/AIRunStatus";
@@ -48,6 +56,18 @@ import DatabaseBaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/Dat
48
56
  import SloRecordReferenceValidator from "../Utils/Slo/SloRecordReferenceValidator";
49
57
  import UserNotificationEventType from "../../Types/UserNotification/UserNotificationEventType";
50
58
  import StatusPageSubscriberNotificationStatus from "../../Types/StatusPage/StatusPageSubscriberNotificationStatus";
59
+ import IncidentCreatedRenotify from "../../Types/StatusPage/IncidentCreatedRenotify";
60
+ import IncidentCreatedResend from "../../Types/StatusPage/IncidentCreatedResend";
61
+ import IncidentScopeAddedPagesNotification, {
62
+ IncidentScopeAddedPagesNotificationAction,
63
+ StatusPageScopeChange,
64
+ } from "../../Types/StatusPage/IncidentScopeAddedPagesNotification";
65
+ import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
66
+ import StatusPage from "../../Models/DatabaseModels/StatusPage";
67
+ import StatusPageService from "./StatusPageService";
68
+ import StatusPageReadAccess from "../Utils/StatusPage/StatusPageReadAccess";
69
+ import SubscriberNotificationResendAccess from "../Utils/StatusPage/SubscriberNotificationResendAccess";
70
+ import Select from "../Types/Database/Select";
51
71
  import DockerHost from "../../Models/DatabaseModels/DockerHost";
52
72
  import PodmanHost from "../../Models/DatabaseModels/PodmanHost";
53
73
  import Host from "../../Models/DatabaseModels/Host";
@@ -73,6 +93,7 @@ import ServiceType from "../../Types/Telemetry/ServiceType";
73
93
  import OneUptimeDate from "../../Types/Date";
74
94
  import TelemetryUtil from "../Utils/Telemetry/Telemetry";
75
95
  import MetricResourceAttributeUtil from "../../Utils/Metrics/MetricResourceAttributeUtil";
96
+ import { escapeMarkdownInline } from "../../Utils/Markdown/MarkdownEscape";
76
97
  import logger, { LogAttributes } from "../Utils/Logger";
77
98
  import ProductAnalytics from "../Utils/ProductAnalytics";
78
99
  import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
@@ -123,12 +144,56 @@ import {
123
144
  INCIDENT_ALERT_IDS_TO_LINK_KEY,
124
145
  } from "../../Types/Incident/IncidentAlertLink";
125
146
 
147
+ /*
148
+ * How an update changed an incident's status page scope, for its feed item.
149
+ * notificationQueued: the update also queued the 'created' notification for
150
+ * the added pages (IncidentScopeAddedPagesNotification).
151
+ * notificationAlreadyQueued: that notification was Pending or being sent, so
152
+ * the update left it alone; onUpdateSuccess checks it did not finish without
153
+ * the added pages in the meantime.
154
+ */
155
+ type StatusPageScopeCarryForward = StatusPageScopeChange & {
156
+ isScoped: boolean;
157
+ notificationQueued: boolean;
158
+ notificationAlreadyQueued?: boolean | undefined;
159
+ };
160
+
161
+ /*
162
+ * What adding status pages does to one matched incident's 'created'
163
+ * notification, and the record of told pages to write if it is queued with
164
+ * none (IncidentScopeAddedPagesNotification.getRecordToSeedOnQueue).
165
+ */
166
+ interface AddedStatusPagesDecision {
167
+ incidentId: string;
168
+ action: IncidentScopeAddedPagesNotificationAction;
169
+ recordToSeed: Array<string> | undefined;
170
+ }
171
+
126
172
  // key is incidentId for this dictionary.
127
173
  type UpdateCarryForward = Dictionary<{
174
+ /*
175
+ * The monitors the update takes off and puts on the incident. Both are
176
+ * empty unless the update sends the monitor list.
177
+ */
128
178
  monitorsRemoved: Array<Monitor>;
129
179
  monitorsAdded: Array<Monitor>;
180
+ /*
181
+ * Whether the incident was resolved before the update, which decides if
182
+ * the monitors taken off it are restored. Read before the write, since the
183
+ * same update may resolve or reopen it, and only when a monitor is taken
184
+ * off (undefined otherwise).
185
+ */
186
+ isResolvedBeforeUpdate?: boolean | undefined;
187
+ // The monitor status the incident put its monitors in before the update.
130
188
  oldChangeMonitorStatusIdTo: ObjectID | undefined;
189
+ // The monitor status the update writes; undefined when it writes none.
131
190
  newMonitorChangeStatusIdTo: ObjectID | undefined;
191
+ /*
192
+ * The update sets the incident's monitor status to nothing, so the
193
+ * incident no longer puts its monitors, old or added, in any status.
194
+ */
195
+ isChangeMonitorStatusToCleared?: boolean | undefined;
196
+ statusPageScopeChange?: StatusPageScopeCarryForward | undefined;
132
197
  }>;
133
198
 
134
199
  /*
@@ -476,9 +541,10 @@ export class Service extends DatabaseService<Model> {
476
541
  updateBy: UpdateBy<Model>,
477
542
  ): Promise<OnUpdate<Model>> {
478
543
  /*
479
- * get monitors for this incident.
480
- * if the monitors are removed then change them to operational state.
481
- * then change all of the monitors in this incident to the changeMonitorStatusToId.
544
+ * Records which monitors the update takes off and puts on each incident,
545
+ * the monitor status it writes, and whether the incident was resolved
546
+ * before it, for onUpdateSuccess to decide what that does to the
547
+ * monitors (updateMonitorsForIncidentEdit).
482
548
  */
483
549
 
484
550
  updateBy.query = applyIncidentSelfPrivacyFilter(
@@ -490,12 +556,36 @@ export class Service extends DatabaseService<Model> {
490
556
  updateBy.data.isVisibleOnStatusPage = false;
491
557
  }
492
558
 
559
+ this.stripServiceOwnedScopeColumns(updateBy);
560
+
561
+ /*
562
+ * Before anything below adds values of its own to customFields (the
563
+ * mapped values copied from monitors), so only what the caller wrote is
564
+ * checked.
565
+ */
566
+ await this.validateCustomFieldValuesOnUpdate(updateBy);
567
+
568
+ // Before the hooks below add a Pending of their own.
569
+ const isCreatedNotificationResendRequested: boolean =
570
+ this.isCreatedNotificationResendRequested(updateBy);
571
+
493
572
  await this.validateProjectScopedReferences(updateBy);
494
573
 
495
574
  const carryForward: UpdateCarryForward = {};
496
575
 
576
+ const data: Dictionary<unknown> = updateBy.data as Dictionary<unknown>;
577
+
578
+ /*
579
+ * Only an update that sends the monitor list changes which monitors the
580
+ * incident holds. One that sends just the monitor status (the API, the
581
+ * CLI or a workflow) takes no monitor off: reading the missing list as
582
+ * empty would restore every monitor, and walking it threw. A list set to
583
+ * null is sent: TypeORM clears a many-to-many set to null, like [].
584
+ */
585
+ const isMonitorListUpdated: boolean = data["monitors"] !== undefined;
586
+
497
587
  if (
498
- updateBy.data.monitors ||
588
+ isMonitorListUpdated ||
499
589
  updateBy.data.changeMonitorStatusTo ||
500
590
  updateBy.data.changeMonitorStatusToId
501
591
  ) {
@@ -513,105 +603,1308 @@ export class Service extends DatabaseService<Model> {
513
603
  props: updateBy.props,
514
604
  });
515
605
 
606
+ /*
607
+ * The monitor status the update writes, in any shape the API accepts
608
+ * (an id, a bare uuid string or a relation object), so the status an
609
+ * update writes is never mistaken for one it leaves alone.
610
+ */
611
+ const monitorStatusIdInUpdate: ObjectID | string | undefined =
612
+ resolveReferenceId(data["changeMonitorStatusToId"]) ||
613
+ resolveReferenceId(data["changeMonitorStatusTo"]);
614
+
615
+ const newMonitorChangeStatusIdTo: ObjectID | undefined =
616
+ monitorStatusIdInUpdate
617
+ ? new ObjectID(monitorStatusIdInUpdate.toString())
618
+ : undefined;
619
+
620
+ const isChangeMonitorStatusToCleared: boolean =
621
+ !newMonitorChangeStatusIdTo &&
622
+ (data["changeMonitorStatusToId"] === null ||
623
+ data["changeMonitorStatusTo"] === null);
624
+
625
+ const monitorIdsAfterUpdate: Array<string> = isMonitorListUpdated
626
+ ? this.getMonitorIdsInUpdate(data["monitors"])
627
+ : [];
628
+
516
629
  for (const incident of incidentsToUpdate) {
630
+ const storedMonitors: Array<Monitor> = incident.monitors || [];
631
+
632
+ const storedMonitorIds: Array<string> = storedMonitors.map(
633
+ (monitor: Monitor): string => {
634
+ return this.getMonitorIdForComparison(monitor);
635
+ },
636
+ );
637
+
638
+ const monitorsRemoved: Array<Monitor> = isMonitorListUpdated
639
+ ? storedMonitors.filter((monitor: Monitor): boolean => {
640
+ return !monitorIdsAfterUpdate.includes(
641
+ this.getMonitorIdForComparison(monitor),
642
+ );
643
+ })
644
+ : [];
645
+
517
646
  carryForward[incident.id!.toString()] = {
518
- monitorsRemoved: [],
519
- monitorsAdded: [],
647
+ monitorsRemoved: monitorsRemoved,
648
+ /*
649
+ * Read now, before the write: an update that resolves the incident
650
+ * and takes a monitor off must still restore that monitor, and one
651
+ * that reopens a resolved incident must not restore it twice.
652
+ * After the write, the state no longer tells these cases apart.
653
+ */
654
+ isResolvedBeforeUpdate:
655
+ monitorsRemoved.length > 0
656
+ ? await this.isIncidentResolved({ incidentId: incident.id! })
657
+ : undefined,
658
+ monitorsAdded: monitorIdsAfterUpdate
659
+ .filter((monitorId: string): boolean => {
660
+ return !storedMonitorIds.includes(monitorId);
661
+ })
662
+ .map((monitorId: string): Monitor => {
663
+ return new Monitor(new ObjectID(monitorId));
664
+ }),
520
665
  oldChangeMonitorStatusIdTo: incident.changeMonitorStatusToId,
521
- newMonitorChangeStatusIdTo:
522
- (updateBy.data.changeMonitorStatusToId as ObjectID) ||
523
- (updateBy.data.changeMonitorStatusTo as unknown as MonitorStatus)
524
- ?._id ||
525
- undefined,
666
+ newMonitorChangeStatusIdTo: newMonitorChangeStatusIdTo,
667
+ isChangeMonitorStatusToCleared: isChangeMonitorStatusToCleared,
526
668
  };
669
+ }
670
+ }
527
671
 
528
- for (const monitor of incident.monitors || []) {
529
- // check if this monitor is actually removed.
530
- let isRemoved: boolean = true;
672
+ // Set notification status based on shouldStatusPageSubscribersBeNotifiedOnIncidentCreated if it's being updated
673
+ if (
674
+ updateBy.data.shouldStatusPageSubscribersBeNotifiedOnIncidentCreated !==
675
+ undefined
676
+ ) {
677
+ if (
678
+ updateBy.data.shouldStatusPageSubscribersBeNotifiedOnIncidentCreated ===
679
+ false
680
+ ) {
681
+ updateBy.data.subscriberNotificationStatusOnIncidentCreated =
682
+ StatusPageSubscriberNotificationStatus.Skipped;
683
+ updateBy.data.subscriberNotificationStatusMessage =
684
+ "Notifications skipped as subscribers are not to be notified for this incident.";
685
+ } else if (
686
+ updateBy.data.shouldStatusPageSubscribersBeNotifiedOnIncidentCreated ===
687
+ true
688
+ ) {
689
+ updateBy.data.subscriberNotificationStatusOnIncidentCreated =
690
+ StatusPageSubscriberNotificationStatus.Pending;
691
+ }
692
+ }
531
693
 
532
- for (const updatedMonitor of updateBy.data
533
- ?.monitors as unknown as Array<Monitor>) {
534
- if (
535
- updatedMonitor._id &&
536
- updatedMonitor._id.toString() === monitor._id?.toString()
537
- ) {
538
- isRemoved = false;
539
- break;
540
- }
541
- }
694
+ await this.queueCreatedNotificationResendToAllStatusPagesIfRequested(
695
+ updateBy,
696
+ );
542
697
 
543
- if (isRemoved) {
544
- carryForward[incident.id!.toString()]?.monitorsRemoved?.push(
545
- monitor,
546
- );
698
+ /*
699
+ * Retry or Resend - a user's Pending - over a notification that is
700
+ * being sent would let a second run send it alongside, or be
701
+ * overwritten when the send settles (SubscriberNotificationResendAccess).
702
+ * 'Resend to all pages' above has refused that already, with its own
703
+ * reason; the hooks below queue their own Pending only from a settled
704
+ * state.
705
+ */
706
+ await SubscriberNotificationResendAccess.assertNotQueuedWhileBeingSent({
707
+ modelType: Model,
708
+ service: this,
709
+ updateBy: updateBy,
710
+ statusColumns: [
711
+ "subscriberNotificationStatusOnIncidentCreated",
712
+ "subscriberNotificationStatusOnPostmortemPublished",
713
+ ],
714
+ });
715
+
716
+ await this.clearCreatedNotificationRecordOnResend({
717
+ updateBy: updateBy,
718
+ isResendRequested: isCreatedNotificationResendRequested,
719
+ });
720
+
721
+ await this.queueCreatedNotificationOnPublishIfRequested(updateBy);
722
+
723
+ await this.applyStatusPageScopeToUpdate(updateBy, carryForward);
724
+
725
+ /*
726
+ * Re-apply mapped custom field values. Covers the Custom Fields modal
727
+ * saving the whole bag back over a mapped value, and the incident's
728
+ * monitors being added or removed — both change what a mapped field should
729
+ * hold, and folding the answer into this payload keeps it one write with a
730
+ * truthful audit entry.
731
+ */
732
+ await CustomFieldMappingService.applyMappingsToUpdate({
733
+ definitionModelType: IncidentCustomField,
734
+ updateBy: updateBy,
735
+ });
736
+
737
+ return {
738
+ updateBy: updateBy,
739
+ carryForward: carryForward,
740
+ };
741
+ }
742
+
743
+ /*
744
+ * The ids of the monitors an update's list holds, deduplicated and
745
+ * lowercased like getMonitorIdForComparison. The list reaches the hook as
746
+ * models, `{ _id }` objects, ObjectIDs or bare uuid strings (API update),
747
+ * and comparing only `_id` read every bare id as a monitor taken off and
748
+ * put back on again.
749
+ */
750
+ private getMonitorIdsInUpdate(monitors: unknown): Array<string> {
751
+ const monitorIds: Array<string> = [];
752
+
753
+ for (const monitorId of resolveReferenceIds(monitors)) {
754
+ const normalizedMonitorId: string = monitorId
755
+ .toString()
756
+ .trim()
757
+ .toLowerCase();
758
+
759
+ if (!monitorIds.includes(normalizedMonitorId)) {
760
+ monitorIds.push(normalizedMonitorId);
761
+ }
762
+ }
763
+
764
+ return monitorIds;
765
+ }
766
+
767
+ // A monitor's id as getMonitorIdsInUpdate writes it, for comparing the two.
768
+ private getMonitorIdForComparison(monitor: Monitor): string {
769
+ return (monitor._id?.toString() || "").trim().toLowerCase();
770
+ }
771
+
772
+ /*
773
+ * The incidents an update will write to, as they are stored, for the hooks
774
+ * below that decide what else joins the update.
775
+ *
776
+ * Read as root: the answer only decides which columns join this update, and
777
+ * the update itself is still checked against the caller's permissions
778
+ * afterwards. Reading with the caller's props would fail the whole edit for
779
+ * a role that may edit incidents but not read these columns. The query
780
+ * already carries the caller's privacy filter (applyIncidentSelfPrivacyFilter),
781
+ * but not their tenant: the update's permission check adds it only after
782
+ * this hook runs. So a non-root caller's read is limited to their own
783
+ * project here, or a request for another project's incident id would be
784
+ * answered with that incident's state (a refusal that depends on it)
785
+ * instead of the usual "nothing updated".
786
+ */
787
+ private async findIncidentsForUpdateHook(data: {
788
+ updateBy: UpdateBy<Model>;
789
+ select: Select<Model>;
790
+ }): Promise<Array<Model>> {
791
+ const updateBy: UpdateBy<Model> = data.updateBy;
792
+
793
+ const query: Query<Model> =
794
+ !updateBy.props.isRoot && updateBy.props.tenantId
795
+ ? {
796
+ ...updateBy.query,
797
+ projectId: updateBy.props.tenantId,
547
798
  }
548
- }
799
+ : updateBy.query;
549
800
 
550
- if (updateBy.data.monitors && updateBy.data.monitors.length > 0) {
551
- for (const monitor of updateBy.data
552
- ?.monitors as unknown as Array<Monitor>) {
553
- // check if this monitor is actually added.
554
- let isAdded: boolean = true;
555
-
556
- for (const existingMonitor of incident.monitors || []) {
557
- if (
558
- existingMonitor._id &&
559
- existingMonitor._id.toString() === monitor._id?.toString()
560
- ) {
561
- isAdded = false;
562
- break;
563
- }
564
- }
801
+ return await this.findBy({
802
+ query: query,
803
+ select: data.select,
804
+ limit: LIMIT_MAX,
805
+ skip: 0,
806
+ props: {
807
+ isRoot: true,
808
+ },
809
+ });
810
+ }
811
+
812
+ /*
813
+ * CUSTOM FIELD VALUES must fit their fields: a Number field holds a number,
814
+ * a Dropdown one of its options (CustomFieldValueValidator has the rules).
815
+ *
816
+ * Only for writes a user or an API key makes. Root writes - the server's
817
+ * own, such as values copied from monitors or an incident template - are
818
+ * trusted, and the mapped values are added after this runs, so they never
819
+ * reach it. Only the keys a write changes are checked, so a value stored
820
+ * before these checks, or a dropdown option removed since, does not stop
821
+ * someone saving the rest of the card.
822
+ *
823
+ * "Required on create" is not enforced here: incidents created by
824
+ * monitors, the API, Slack, Microsoft Teams or AI cannot fill in a form.
825
+ */
826
+ private async getCustomFieldDefinitionsForValidation(
827
+ projectId: ObjectID,
828
+ ): Promise<Array<CustomFieldDefinition>> {
829
+ const definitions: Array<IncidentCustomField> =
830
+ await IncidentCustomFieldService.findBy({
831
+ query: {
832
+ projectId: projectId,
833
+ },
834
+ select: {
835
+ name: true,
836
+ customFieldType: true,
837
+ dropdownOptions: true,
838
+ },
839
+ limit: LIMIT_PER_PROJECT,
840
+ skip: 0,
841
+ props: {
842
+ isRoot: true,
843
+ },
844
+ });
845
+
846
+ const result: Array<CustomFieldDefinition> = [];
847
+
848
+ for (const definition of definitions) {
849
+ if (typeof definition.name !== "string") {
850
+ continue;
851
+ }
852
+
853
+ result.push({
854
+ name: definition.name,
855
+ customFieldType: definition.customFieldType,
856
+ dropdownOptions: definition.dropdownOptions,
857
+ });
858
+ }
859
+
860
+ return result;
861
+ }
862
+
863
+ private hasCustomFieldValuesToValidate(customFields: unknown): boolean {
864
+ return (
865
+ customFields !== null &&
866
+ typeof customFields === "object" &&
867
+ !Array.isArray(customFields) &&
868
+ Object.keys(customFields as JSONObject).length > 0
869
+ );
870
+ }
871
+
872
+ private throwIfCustomFieldValuesInvalid(
873
+ errors: Array<CustomFieldValueValidationError>,
874
+ ): void {
875
+ if (errors.length > 0) {
876
+ throw new BadDataException(
877
+ formatCustomFieldValueValidationErrors(errors),
878
+ );
879
+ }
880
+ }
881
+
882
+ private async validateCustomFieldValuesOnCreate(data: {
883
+ createBy: CreateBy<Model>;
884
+ projectId: ObjectID;
885
+ }): Promise<void> {
886
+ const customFields: unknown = data.createBy.data.customFields;
887
+
888
+ if (
889
+ data.createBy.props.isRoot ||
890
+ !data.projectId ||
891
+ !this.hasCustomFieldValuesToValidate(customFields)
892
+ ) {
893
+ return;
894
+ }
895
+
896
+ this.throwIfCustomFieldValuesInvalid(
897
+ validateCustomFieldValues({
898
+ definitions: await this.getCustomFieldDefinitionsForValidation(
899
+ data.projectId,
900
+ ),
901
+ customFields: customFields,
902
+ storedCustomFields: {},
903
+ }),
904
+ );
905
+ }
906
+
907
+ /*
908
+ * Checked against each incident the update writes, because what counts as
909
+ * changed depends on what that incident already holds.
910
+ */
911
+ private async validateCustomFieldValuesOnUpdate(
912
+ updateBy: UpdateBy<Model>,
913
+ ): Promise<void> {
914
+ const customFields: unknown = updateBy.data.customFields;
915
+
916
+ if (
917
+ updateBy.props.isRoot ||
918
+ !this.hasCustomFieldValuesToValidate(customFields)
919
+ ) {
920
+ return;
921
+ }
922
+
923
+ const incidents: Array<Model> = await this.findIncidentsForUpdateHook({
924
+ updateBy: updateBy,
925
+ select: {
926
+ _id: true,
927
+ projectId: true,
928
+ customFields: true,
929
+ },
930
+ });
931
+
932
+ const definitionsByProject: Map<
933
+ string,
934
+ Array<CustomFieldDefinition>
935
+ > = new Map<string, Array<CustomFieldDefinition>>();
936
+
937
+ for (const incident of incidents) {
938
+ if (!incident.projectId) {
939
+ continue;
940
+ }
941
+
942
+ const projectKey: string = incident.projectId.toString();
943
+
944
+ if (!definitionsByProject.has(projectKey)) {
945
+ definitionsByProject.set(
946
+ projectKey,
947
+ await this.getCustomFieldDefinitionsForValidation(incident.projectId),
948
+ );
949
+ }
950
+
951
+ this.throwIfCustomFieldValuesInvalid(
952
+ validateCustomFieldValues({
953
+ definitions: definitionsByProject.get(projectKey)!,
954
+ customFields: customFields,
955
+ storedCustomFields: incident.customFields,
956
+ }),
957
+ );
958
+ }
959
+ }
960
+
961
+ /*
962
+ * isScopedToStatusPages and statusPagesNotifiedOnCreation are this
963
+ * service's to write. A client value for either is dropped before any hook
964
+ * runs, so what the hooks write is all that is written: the flag is derived
965
+ * from statusPages (applyStatusPageScopeToUpdate), and the record is the
966
+ * notification job's - a client must not be able to seed it and so keep
967
+ * pages from ever hearing about the incident, or clear it and have them
968
+ * told twice. Root (internal) callers keep the record they send.
969
+ */
970
+ private stripServiceOwnedScopeColumns(updateBy: UpdateBy<Model>): void {
971
+ const data: Dictionary<unknown> = updateBy.data as Dictionary<unknown>;
972
+
973
+ delete data["isScopedToStatusPages"];
974
+
975
+ if (!updateBy.props.isRoot) {
976
+ delete data["statusPagesNotifiedOnCreation"];
977
+ }
978
+ }
979
+
980
+ /*
981
+ * Whether an update itself asks for the 'created' notification to go out
982
+ * again: it sets the status to Pending - the API route of resending it,
983
+ * and the dashboard's Retry - turns notifying on creation on, which
984
+ * onBeforeUpdate maps to Pending, or asks for it to be sent to every
985
+ * status page again (IncidentCreatedResend, the dashboard's Resend). Read
986
+ * before any hook adds a Pending of its own.
987
+ */
988
+ private isCreatedNotificationResendRequested(
989
+ updateBy: UpdateBy<Model>,
990
+ ): boolean {
991
+ return (
992
+ updateBy.data.subscriberNotificationStatusOnIncidentCreated ===
993
+ StatusPageSubscriberNotificationStatus.Pending ||
994
+ updateBy.data.shouldStatusPageSubscribersBeNotifiedOnIncidentCreated ===
995
+ true ||
996
+ IncidentCreatedResend.isRequested(updateBy.miscDataProps)
997
+ );
998
+ }
999
+
1000
+ /*
1001
+ * 'Resend to all pages' (see IncidentCreatedResend): the 'created'
1002
+ * notification goes back to Pending with a message saying so, and
1003
+ * clearCreatedNotificationRecordOnResend empties its record of told pages
1004
+ * in the same write, so the job sends it to every page the incident
1005
+ * reaches now - after a failure too, where a plain Retry would resume.
1006
+ *
1007
+ * Everything is written into the caller's own update, so it lands with the
1008
+ * rest of it or not at all, and the column check that runs after this hook
1009
+ * still decides whether the caller may write the status. That check is
1010
+ * also made here first, before the incidents are read: the refusals below
1011
+ * depend on the notification's state, and a caller who may not send it
1012
+ * again is told only that. The incidents are then read with the caller's
1013
+ * own permissions (and the privacy filter already on the query), so an
1014
+ * incident they cannot see never decides the answer; when they can see
1015
+ * none of those the update matches, nothing is queued.
1016
+ *
1017
+ * Refused - with the reason, rather than silently dropped, because asking
1018
+ * for it is the whole point of the request - when any matched incident's
1019
+ * notification did not go out (Skipped) or is on its way (Pending,
1020
+ * InProgress), and when the same update sets the status to anything but
1021
+ * Pending.
1022
+ */
1023
+ private async queueCreatedNotificationResendToAllStatusPagesIfRequested(
1024
+ updateBy: UpdateBy<Model>,
1025
+ ): Promise<void> {
1026
+ if (!IncidentCreatedResend.isRequested(updateBy.miscDataProps)) {
1027
+ return;
1028
+ }
1029
+
1030
+ const requestedStatus: unknown =
1031
+ updateBy.data.subscriberNotificationStatusOnIncidentCreated;
1032
+
1033
+ if (
1034
+ requestedStatus !== undefined &&
1035
+ requestedStatus !== StatusPageSubscriberNotificationStatus.Pending
1036
+ ) {
1037
+ throw new BadDataException(
1038
+ IncidentCreatedResend.conflictingStatusMessage,
1039
+ );
1040
+ }
1041
+
1042
+ SubscriberNotificationResendAccess.assertCallerMayUpdateColumns({
1043
+ modelType: Model,
1044
+ columns: {
1045
+ subscriberNotificationStatusOnIncidentCreated:
1046
+ StatusPageSubscriberNotificationStatus.Pending,
1047
+ subscriberNotificationStatusMessage:
1048
+ IncidentCreatedResend.queuedMessage,
1049
+ statusPagesNotifiedOnCreation: [],
1050
+ },
1051
+ props: updateBy.props,
1052
+ refusal: IncidentCreatedResend.noPermissionMessage,
1053
+ });
1054
+
1055
+ const incidents: Array<Model> = await this.findBy({
1056
+ query: updateBy.query,
1057
+ select: {
1058
+ _id: true,
1059
+ subscriberNotificationStatusOnIncidentCreated: true,
1060
+ },
1061
+ limit: LIMIT_MAX,
1062
+ skip: 0,
1063
+ props: updateBy.props,
1064
+ });
1065
+
1066
+ for (const incident of incidents) {
1067
+ const refusal: string | null = IncidentCreatedResend.getRefusalReason(
1068
+ incident.subscriberNotificationStatusOnIncidentCreated,
1069
+ );
1070
+
1071
+ if (refusal) {
1072
+ throw new BadDataException(refusal);
1073
+ }
1074
+ }
1075
+
1076
+ if (incidents.length === 0) {
1077
+ return;
1078
+ }
1079
+
1080
+ updateBy.data.subscriberNotificationStatusOnIncidentCreated =
1081
+ StatusPageSubscriberNotificationStatus.Pending;
1082
+
1083
+ if (updateBy.data.subscriberNotificationStatusMessage === undefined) {
1084
+ updateBy.data.subscriberNotificationStatusMessage =
1085
+ IncidentCreatedResend.queuedMessage;
1086
+ }
1087
+ }
1088
+
1089
+ /*
1090
+ * A resend asked for by the update itself (see
1091
+ * isCreatedNotificationResendRequested) goes to every status page, as it
1092
+ * always did. The job skips the pages in its record of told pages
1093
+ * (Incident.statusPagesNotifiedOnCreation), so without this a resend of a
1094
+ * notification that went out would reach nobody: the record is emptied in
1095
+ * the same write.
1096
+ *
1097
+ * It is kept in three cases:
1098
+ * - Failed: Retry resumes where the failed send stopped, so the pages it
1099
+ * told are not told twice - unless the update asks for it to go to every
1100
+ * page again (IncidentCreatedResend), which is what that request is for;
1101
+ * - Pending or InProgress: the notification is on its way already, and its
1102
+ * record is what keeps a send queued for added pages from reaching the
1103
+ * pages told before (IncidentCreatedResend refuses these);
1104
+ * - a root caller that writes the record itself.
1105
+ *
1106
+ * An update matching several incidents empties the record when any of them
1107
+ * would otherwise be resent to nobody, because the write applies the same
1108
+ * data to all of them.
1109
+ */
1110
+ private async clearCreatedNotificationRecordOnResend(data: {
1111
+ updateBy: UpdateBy<Model>;
1112
+ isResendRequested: boolean;
1113
+ }): Promise<void> {
1114
+ const updateBy: UpdateBy<Model> = data.updateBy;
1115
+
1116
+ if (!data.isResendRequested) {
1117
+ return;
1118
+ }
1119
+
1120
+ if (
1121
+ (updateBy.data as Dictionary<unknown>)[
1122
+ "statusPagesNotifiedOnCreation"
1123
+ ] !== undefined
1124
+ ) {
1125
+ return;
1126
+ }
1127
+
1128
+ if (IncidentCreatedResend.isRequested(updateBy.miscDataProps)) {
1129
+ /*
1130
+ * Queued for every page by
1131
+ * queueCreatedNotificationResendToAllStatusPagesIfRequested, which
1132
+ * left the status alone when the caller can see none of the incidents
1133
+ * the update matches: nothing to read to decide.
1134
+ */
1135
+ if (
1136
+ updateBy.data.subscriberNotificationStatusOnIncidentCreated ===
1137
+ StatusPageSubscriberNotificationStatus.Pending
1138
+ ) {
1139
+ updateBy.data.statusPagesNotifiedOnCreation = [];
1140
+ }
1141
+
1142
+ return;
1143
+ }
1144
+
1145
+ const incidents: Array<Model> = await this.findIncidentsForUpdateHook({
1146
+ updateBy: updateBy,
1147
+ select: {
1148
+ _id: true,
1149
+ subscriberNotificationStatusOnIncidentCreated: true,
1150
+ },
1151
+ });
1152
+
1153
+ const someIncidentWouldReachNobody: boolean = incidents.some(
1154
+ (incident: Model): boolean => {
1155
+ return ![
1156
+ StatusPageSubscriberNotificationStatus.Failed,
1157
+ StatusPageSubscriberNotificationStatus.Pending,
1158
+ StatusPageSubscriberNotificationStatus.InProgress,
1159
+ ].includes(
1160
+ incident.subscriberNotificationStatusOnIncidentCreated as StatusPageSubscriberNotificationStatus,
1161
+ );
1162
+ },
1163
+ );
1164
+
1165
+ if (someIncidentWouldReachNobody) {
1166
+ updateBy.data.statusPagesNotifiedOnCreation = [];
1167
+ }
1168
+ }
1169
+
1170
+ /*
1171
+ * Publishing a hidden incident can announce it: when the editor turns
1172
+ * 'Visible on Status Page' on and ticks "Notify subscribers that this
1173
+ * incident was created" (see IncidentCreatedRenotify), the incident's
1174
+ * 'created' notification goes back to Pending, and the
1175
+ * Incident:SendNotificationToSubscribers job sends it now that the incident
1176
+ * is visible - to the pages its record of told pages does not list, so an
1177
+ * incident whose notification went out before it was hidden tells only the
1178
+ * pages added while it was hidden.
1179
+ *
1180
+ * It only ever re-queues a notification with someone left to tell, for an
1181
+ * incident that is hidden today, not private, and meant to notify on
1182
+ * creation, so a stray request cannot email subscribers twice or break a
1183
+ * deliberate "don't notify". The status is written into this same update,
1184
+ * so it lands together with the visibility change or not at all. The
1185
+ * column (and its message) are computed, but their update ACL lists the
1186
+ * incident roles, so a non-root edit still passes the column check that
1187
+ * runs after this hook.
1188
+ *
1189
+ * An update that sets the status itself - the API route of resetting it to
1190
+ * Pending, or the notify-on-create flag above - is left alone. An update
1191
+ * that matches several incidents queues only when every one of them
1192
+ * qualifies, because the write applies the same data to all of them.
1193
+ */
1194
+ private async queueCreatedNotificationOnPublishIfRequested(
1195
+ updateBy: UpdateBy<Model>,
1196
+ ): Promise<void> {
1197
+ if (!IncidentCreatedRenotify.isRequested(updateBy.miscDataProps)) {
1198
+ return;
1199
+ }
1200
+
1201
+ if (updateBy.data.isVisibleOnStatusPage !== true) {
1202
+ return;
1203
+ }
1204
+
1205
+ if (
1206
+ updateBy.data.subscriberNotificationStatusOnIncidentCreated !== undefined
1207
+ ) {
1208
+ return;
1209
+ }
1210
+
1211
+ const incidents: Array<Model> = await this.findIncidentsForUpdateHook({
1212
+ updateBy: updateBy,
1213
+ select: {
1214
+ _id: true,
1215
+ isVisibleOnStatusPage: true,
1216
+ isPrivate: true,
1217
+ subscriberNotificationStatusOnIncidentCreated: true,
1218
+ shouldStatusPageSubscribersBeNotifiedOnIncidentCreated: true,
1219
+ statusPagesNotifiedOnCreation: true,
1220
+ statusPages: {
1221
+ _id: true,
1222
+ },
1223
+ },
1224
+ });
1225
+
1226
+ if (incidents.length === 0) {
1227
+ return;
1228
+ }
1229
+
1230
+ const everyIncidentQualifies: boolean = incidents.every(
1231
+ (incident: Model): boolean => {
1232
+ return IncidentCreatedRenotify.canRenotifyOnPublish({
1233
+ isVisibleOnStatusPage: incident.isVisibleOnStatusPage,
1234
+ isPrivate:
1235
+ updateBy.data.isPrivate !== undefined &&
1236
+ updateBy.data.isPrivate !== null
1237
+ ? (updateBy.data.isPrivate as boolean)
1238
+ : incident.isPrivate,
1239
+ subscriberNotificationStatusOnIncidentCreated:
1240
+ incident.subscriberNotificationStatusOnIncidentCreated,
1241
+ shouldStatusPageSubscribersBeNotifiedOnIncidentCreated:
1242
+ incident.shouldStatusPageSubscribersBeNotifiedOnIncidentCreated,
1243
+ // The pages it is limited to once this update is written.
1244
+ statusPages:
1245
+ updateBy.data.statusPages !== undefined
1246
+ ? updateBy.data.statusPages
1247
+ : incident.statusPages,
1248
+ statusPagesNotifiedOnCreation: incident.statusPagesNotifiedOnCreation,
1249
+ });
1250
+ },
1251
+ );
1252
+
1253
+ if (!everyIncidentQualifies) {
1254
+ logger.debug(
1255
+ `Not re-queueing the incident created notification on publish: ${incidents.length} incident(s) matched and not all of them were hidden, set to notify subscribers and had subscribers left to tell.`,
1256
+ );
1257
+ return;
1258
+ }
1259
+
1260
+ updateBy.data.subscriberNotificationStatusOnIncidentCreated =
1261
+ StatusPageSubscriberNotificationStatus.Pending;
1262
+ updateBy.data.subscriberNotificationStatusMessage =
1263
+ IncidentCreatedRenotify.queuedMessage;
1264
+ }
1265
+
1266
+ /*
1267
+ * The status pages a new incident is limited to (Incident.statusPages), and
1268
+ * the two columns that follow from them.
1269
+ *
1270
+ * isScopedToStatusPages is derived from the list, whatever the caller sent
1271
+ * for it: the column is computed, which exempts it from the column check on
1272
+ * create, so a client value would otherwise be stored as is - and a scoped
1273
+ * flag with no pages hides the incident from every status page.
1274
+ *
1275
+ * statusPagesNotifiedOnCreation is the created-notification job's record of
1276
+ * the pages it told. A client must not be able to seed it and so keep pages
1277
+ * from ever hearing about the incident; only root (internal) callers keep
1278
+ * what they send.
1279
+ *
1280
+ * The pages a non-root caller picks must be pages they can read (see
1281
+ * StatusPageReadAccess), except the pages of an incident template they can
1282
+ * read: the dashboard fills a template's pages in for everyone who declares
1283
+ * from it, which is what the template is for. Pages copied from a template
1284
+ * on the server are not the caller's pick. That is checked once the pages
1285
+ * are known to be the project's (assertCallerCanPickStatusPagesOnCreate).
1286
+ *
1287
+ * `isScopedToNothingByTemplate`: declared through the API from a template
1288
+ * whose status pages were all deleted. The incident is scoped with no
1289
+ * pages - hidden from every status page - rather than unscoped.
1290
+ */
1291
+ private applyStatusPageScopeToCreate(data: {
1292
+ createBy: CreateBy<Model>;
1293
+ isScopedToNothingByTemplate?: boolean | undefined;
1294
+ }): void {
1295
+ const createBy: CreateBy<Model> = data.createBy;
1296
+
1297
+ if (!createBy.props.isRoot) {
1298
+ delete createBy.data.statusPagesNotifiedOnCreation;
1299
+ }
1300
+
1301
+ const statusPageIds: Array<string> =
1302
+ IncidentScopeAddedPagesNotification.normalizeStatusPageIds(
1303
+ createBy.data.statusPages,
1304
+ );
1305
+
1306
+ if (
1307
+ createBy.data.statusPages !== undefined &&
1308
+ createBy.data.statusPages !== null
1309
+ ) {
1310
+ createBy.data.statusPages = this.toStatusPageStubs(statusPageIds);
1311
+ }
1312
+
1313
+ createBy.data.isScopedToStatusPages =
1314
+ statusPageIds.length > 0 || data.isScopedToNothingByTemplate === true;
1315
+ }
1316
+
1317
+ /*
1318
+ * The pages a non-root caller picks for a new incident must be pages they
1319
+ * can read (see applyStatusPageScopeToCreate). Runs after the project check,
1320
+ * so another project's page, or one that does not exist, is reported as
1321
+ * such rather than as a page the caller cannot read.
1322
+ */
1323
+ private async assertCallerCanPickStatusPagesOnCreate(data: {
1324
+ createBy: CreateBy<Model>;
1325
+ projectId: ObjectID;
1326
+ statusPagesFromCaller: boolean;
1327
+ }): Promise<void> {
1328
+ const createBy: CreateBy<Model> = data.createBy;
1329
+
1330
+ const statusPageIds: Array<string> =
1331
+ IncidentScopeAddedPagesNotification.normalizeStatusPageIds(
1332
+ createBy.data.statusPages,
1333
+ );
1334
+
1335
+ if (
1336
+ createBy.props.isRoot ||
1337
+ !data.statusPagesFromCaller ||
1338
+ statusPageIds.length === 0
1339
+ ) {
1340
+ return;
1341
+ }
1342
+
1343
+ const readableStatusPageIds: Array<string> =
1344
+ await StatusPageReadAccess.getReadableStatusPageIds({
1345
+ statusPageIds: statusPageIds,
1346
+ props: createBy.props,
1347
+ });
1348
+
1349
+ let unreadableStatusPageIds: Array<string> = statusPageIds.filter(
1350
+ (id: string): boolean => {
1351
+ return !readableStatusPageIds.includes(id);
1352
+ },
1353
+ );
1354
+
1355
+ if (unreadableStatusPageIds.length > 0) {
1356
+ const templateStatusPageIds: Array<string> =
1357
+ await this.getStatusPageIdsOfTemplatesReadableByCaller({
1358
+ projectId: data.projectId,
1359
+ props: createBy.props,
1360
+ });
1361
+
1362
+ unreadableStatusPageIds = unreadableStatusPageIds.filter(
1363
+ (id: string): boolean => {
1364
+ return !templateStatusPageIds.includes(id);
1365
+ },
1366
+ );
1367
+ }
1368
+
1369
+ if (unreadableStatusPageIds.length > 0) {
1370
+ throw new NotAuthorizedException(
1371
+ StatusPageReadAccess.getRefusalMessage("incident"),
1372
+ );
1373
+ }
1374
+ }
1375
+
1376
+ /*
1377
+ * The status pages the incident templates the caller can read are limited
1378
+ * to. Such a page may be picked for a new incident without reading it:
1379
+ * declaring from the template picks it anyway. The templates are read with
1380
+ * the caller's permissions (and labels); their pages as root. A caller who
1381
+ * cannot read templates at all gets none.
1382
+ */
1383
+ private async getStatusPageIdsOfTemplatesReadableByCaller(data: {
1384
+ projectId: ObjectID;
1385
+ props: DatabaseCommonInteractionProps;
1386
+ }): Promise<Array<string>> {
1387
+ let templates: Array<IncidentTemplate> = [];
1388
+
1389
+ try {
1390
+ templates = await IncidentTemplateService.findBy({
1391
+ query: {
1392
+ projectId: data.projectId,
1393
+ },
1394
+ select: {
1395
+ _id: true,
1396
+ },
1397
+ limit: LIMIT_MAX,
1398
+ skip: 0,
1399
+ props: data.props,
1400
+ });
1401
+ } catch (err) {
1402
+ // No access to templates (or not on a plan that has them): no pages.
1403
+ logger.debug(
1404
+ `Could not read the caller's incident templates while checking the status pages of a new incident: ${err}`,
1405
+ );
1406
+ return [];
1407
+ }
1408
+
1409
+ if (templates.length === 0) {
1410
+ return [];
1411
+ }
1412
+
1413
+ const templatesWithPages: Array<IncidentTemplate> =
1414
+ await IncidentTemplateService.findBy({
1415
+ query: {
1416
+ _id: QueryHelper.any(
1417
+ templates.map((template: IncidentTemplate): string => {
1418
+ return template.id!.toString();
1419
+ }),
1420
+ ),
1421
+ projectId: data.projectId,
1422
+ },
1423
+ select: {
1424
+ _id: true,
1425
+ statusPages: {
1426
+ _id: true,
1427
+ },
1428
+ },
1429
+ limit: LIMIT_MAX,
1430
+ skip: 0,
1431
+ props: {
1432
+ isRoot: true,
1433
+ },
1434
+ });
1435
+
1436
+ return IncidentScopeAddedPagesNotification.normalizeStatusPageIds(
1437
+ templatesWithPages.flatMap(
1438
+ (template: IncidentTemplate): Array<StatusPage> => {
1439
+ return template.statusPages || [];
1440
+ },
1441
+ ),
1442
+ );
1443
+ }
1444
+
1445
+ /*
1446
+ * The status pages an incident is limited to, on update. Everything that
1447
+ * follows from a write to Incident.statusPages happens here, in the
1448
+ * caller's own update, so the list, the flag and a re-queued notification
1449
+ * land together or not at all:
1450
+ *
1451
+ * - isScopedToStatusPages is derived from the list the update writes (a
1452
+ * value the caller sent for it was dropped by
1453
+ * stripServiceOwnedScopeColumns). Nothing else ever sets it, and it is
1454
+ * never recomputed when join rows disappear: deleting the only page an
1455
+ * incident is scoped to leaves it scoped to nothing, hidden from every
1456
+ * page, rather than widened to all of them;
1457
+ * - a non-root caller's list is topped up with the pages the incident is
1458
+ * scoped to that the caller cannot read. Status pages are label-scoped,
1459
+ * so an editor may see only some of an incident's pages, and a list write
1460
+ * replaces the whole list (the dashboard sends back what the editor
1461
+ * sees). Without this, saving the incident would silently drop every page
1462
+ * the editor was not shown;
1463
+ * - the pages a non-root caller adds must be pages they can read (see
1464
+ * StatusPageReadAccess);
1465
+ * - with IncidentScopeAddedPagesNotification requested, the 'created'
1466
+ * notification is queued again for the pages the update adds;
1467
+ * - what was added and removed is carried forward for the feed item
1468
+ * onUpdateSuccess writes, and so is whether the notification was on its
1469
+ * way already, which onUpdateSuccess checks again once the pages are
1470
+ * written.
1471
+ *
1472
+ * The flag and the notification columns are computed, but their update
1473
+ * access control lists every role that may edit statusPages, so a non-root
1474
+ * edit passes the column check that runs after this hook.
1475
+ */
1476
+ private async applyStatusPageScopeToUpdate(
1477
+ updateBy: UpdateBy<Model>,
1478
+ carryForward: UpdateCarryForward,
1479
+ ): Promise<void> {
1480
+ const data: Dictionary<unknown> = updateBy.data as Dictionary<unknown>;
1481
+
1482
+ if (data["statusPages"] === undefined) {
1483
+ return;
1484
+ }
1485
+
1486
+ const incidents: Array<Model> = await this.findIncidentsForUpdateHook({
1487
+ updateBy: updateBy,
1488
+ select: {
1489
+ _id: true,
1490
+ statusPages: {
1491
+ _id: true,
1492
+ },
1493
+ isVisibleOnStatusPage: true,
1494
+ isPrivate: true,
1495
+ subscriberNotificationStatusOnIncidentCreated: true,
1496
+ shouldStatusPageSubscribersBeNotifiedOnIncidentCreated: true,
1497
+ statusPagesNotifiedOnCreation: true,
1498
+ },
1499
+ });
1500
+
1501
+ const requestedStatusPageIds: Array<string> =
1502
+ IncidentScopeAddedPagesNotification.normalizeStatusPageIds(
1503
+ data["statusPages"],
1504
+ );
1505
+
1506
+ const hiddenStatusPageIds: Array<string> = updateBy.props.isRoot
1507
+ ? []
1508
+ : await this.getScopedStatusPageIdsHiddenFromCaller({
1509
+ incidents: incidents,
1510
+ props: updateBy.props,
1511
+ });
1512
+
1513
+ const statusPageIds: Array<string> = [
1514
+ ...requestedStatusPageIds,
1515
+ ...hiddenStatusPageIds.filter((id: string) => {
1516
+ return !requestedStatusPageIds.includes(id);
1517
+ }),
1518
+ ];
1519
+
1520
+ if (!updateBy.props.isRoot) {
1521
+ await StatusPageReadAccess.assertCallerCanPickStatusPages({
1522
+ statusPageIds: this.getStatusPageIdsAddedToAny({
1523
+ incidents: incidents,
1524
+ statusPageIds: statusPageIds,
1525
+ }),
1526
+ props: updateBy.props,
1527
+ subject: "incident",
1528
+ });
1529
+ }
1530
+
1531
+ data["statusPages"] = this.toStatusPageStubs(statusPageIds);
1532
+ data["isScopedToStatusPages"] = statusPageIds.length > 0;
1533
+
1534
+ const decisions: Array<AddedStatusPagesDecision> = [];
1535
+
1536
+ for (const incident of incidents) {
1537
+ const incidentId: string = incident.id!.toString();
1538
+
1539
+ const change: StatusPageScopeChange =
1540
+ IncidentScopeAddedPagesNotification.getScopeChange({
1541
+ before: incident.statusPages,
1542
+ after: statusPageIds,
1543
+ });
1544
+
1545
+ const action: IncidentScopeAddedPagesNotificationAction =
1546
+ IncidentScopeAddedPagesNotification.getAction({
1547
+ addedStatusPageIds: change.addedStatusPageIds,
1548
+ incident: {
1549
+ subscriberNotificationStatusOnIncidentCreated:
1550
+ incident.subscriberNotificationStatusOnIncidentCreated,
1551
+ shouldStatusPageSubscribersBeNotifiedOnIncidentCreated:
1552
+ incident.shouldStatusPageSubscribersBeNotifiedOnIncidentCreated,
1553
+ isVisibleOnStatusPage: this.getValueAfterUpdate(
1554
+ updateBy.data.isVisibleOnStatusPage,
1555
+ incident.isVisibleOnStatusPage,
1556
+ ),
1557
+ isPrivate: this.getValueAfterUpdate(
1558
+ updateBy.data.isPrivate,
1559
+ incident.isPrivate,
1560
+ ),
1561
+ statusPagesNotifiedOnCreation:
1562
+ incident.statusPagesNotifiedOnCreation,
1563
+ },
1564
+ });
1565
+
1566
+ carryForward[incidentId] = {
1567
+ monitorsRemoved: [],
1568
+ monitorsAdded: [],
1569
+ oldChangeMonitorStatusIdTo: undefined,
1570
+ newMonitorChangeStatusIdTo: undefined,
1571
+ ...carryForward[incidentId],
1572
+ statusPageScopeChange: {
1573
+ ...change,
1574
+ isScoped: statusPageIds.length > 0,
1575
+ notificationQueued: false,
1576
+ /*
1577
+ * On its way already, and nothing in this update sets the status:
1578
+ * onUpdateSuccess makes sure the send did not finish without the
1579
+ * added pages in the meantime.
1580
+ */
1581
+ notificationAlreadyQueued:
1582
+ action ===
1583
+ IncidentScopeAddedPagesNotificationAction.AlreadyQueued &&
1584
+ updateBy.data.subscriberNotificationStatusOnIncidentCreated ===
1585
+ undefined,
1586
+ },
1587
+ };
1588
+
1589
+ decisions.push({
1590
+ incidentId: incidentId,
1591
+ action: action,
1592
+ recordToSeed:
1593
+ action === IncidentScopeAddedPagesNotificationAction.Queue
1594
+ ? IncidentScopeAddedPagesNotification.getRecordToSeedOnQueue({
1595
+ statusPagesNotifiedOnCreation:
1596
+ incident.statusPagesNotifiedOnCreation,
1597
+ statusPageIdsBeforeUpdate: incident.statusPages,
1598
+ })
1599
+ : undefined,
1600
+ });
1601
+ }
1602
+
1603
+ this.queueCreatedNotificationForAddedStatusPagesIfRequested({
1604
+ updateBy: updateBy,
1605
+ decisions: decisions,
1606
+ carryForward: carryForward,
1607
+ });
1608
+ }
1609
+
1610
+ /*
1611
+ * The pages an update writing `statusPageIds` adds to at least one of the
1612
+ * incidents it matches.
1613
+ */
1614
+ private getStatusPageIdsAddedToAny(data: {
1615
+ incidents: Array<Model>;
1616
+ statusPageIds: Array<string>;
1617
+ }): Array<string> {
1618
+ const added: Array<string> = [];
1619
+
1620
+ for (const incident of data.incidents) {
1621
+ for (const id of IncidentScopeAddedPagesNotification.getScopeChange({
1622
+ before: incident.statusPages,
1623
+ after: data.statusPageIds,
1624
+ }).addedStatusPageIds) {
1625
+ if (!added.includes(id)) {
1626
+ added.push(id);
1627
+ }
1628
+ }
1629
+ }
1630
+
1631
+ return added;
1632
+ }
1633
+
1634
+ /*
1635
+ * Adding status pages to an incident's scope can tell them the incident was
1636
+ * created: with IncidentScopeAddedPagesNotification requested, the 'created'
1637
+ * notification goes back to Pending, and the job sends it to the pages it
1638
+ * has no record of telling (Incident.statusPagesNotifiedOnCreation). See
1639
+ * IncidentScopeAddedPagesNotification.getAction for when it applies.
1640
+ *
1641
+ * A notification that was never sent has no record yet, so the pages the
1642
+ * incident was limited to before are written in as the record in the same
1643
+ * write (getRecordToSeedOnQueue): the job then tells only the added pages.
1644
+ *
1645
+ * An update that sets the status itself - the API route of resetting it to
1646
+ * Pending, the notify-on-create flag, or publishing a hidden incident - is
1647
+ * left alone. An update that matches several incidents queues only when it
1648
+ * may for every one of them, because the write applies the same data to
1649
+ * all. For the same reason it cannot write a record that differs between
1650
+ * them, or overwrite the record of an incident that has one, and is refused
1651
+ * instead.
1652
+ */
1653
+ private queueCreatedNotificationForAddedStatusPagesIfRequested(data: {
1654
+ updateBy: UpdateBy<Model>;
1655
+ decisions: Array<AddedStatusPagesDecision>;
1656
+ carryForward: UpdateCarryForward;
1657
+ }): void {
1658
+ const updateBy: UpdateBy<Model> = data.updateBy;
1659
+
1660
+ if (
1661
+ !IncidentScopeAddedPagesNotification.isRequested(updateBy.miscDataProps)
1662
+ ) {
1663
+ return;
1664
+ }
1665
+
1666
+ if (
1667
+ updateBy.data.subscriberNotificationStatusOnIncidentCreated !== undefined
1668
+ ) {
1669
+ return;
1670
+ }
1671
+
1672
+ const actions: Array<IncidentScopeAddedPagesNotificationAction> =
1673
+ data.decisions.map(
1674
+ (
1675
+ decision: AddedStatusPagesDecision,
1676
+ ): IncidentScopeAddedPagesNotificationAction => {
1677
+ return decision.action;
1678
+ },
1679
+ );
1680
+
1681
+ if (!actions.includes(IncidentScopeAddedPagesNotificationAction.Queue)) {
1682
+ return;
1683
+ }
1684
+
1685
+ const everyIncidentMayQueue: boolean = actions.every(
1686
+ (action: IncidentScopeAddedPagesNotificationAction): boolean => {
1687
+ return (
1688
+ action === IncidentScopeAddedPagesNotificationAction.Queue ||
1689
+ action === IncidentScopeAddedPagesNotificationAction.AlreadyQueued
1690
+ );
1691
+ },
1692
+ );
1693
+
1694
+ if (!everyIncidentMayQueue) {
1695
+ logger.debug(
1696
+ `Not queueing the incident created notification for added status pages: ${actions.length} incident(s) matched and not all of them gained status pages and may be announced.`,
1697
+ );
1698
+ return;
1699
+ }
1700
+
1701
+ const recordsToSeed: Array<Array<string>> = data.decisions
1702
+ .map((decision: AddedStatusPagesDecision): Array<string> | undefined => {
1703
+ return decision.recordToSeed;
1704
+ })
1705
+ .filter((record: Array<string> | undefined): boolean => {
1706
+ return record !== undefined;
1707
+ }) as Array<Array<string>>;
1708
+
1709
+ if (recordsToSeed.length > 0) {
1710
+ const recordToSeed: Array<string> = recordsToSeed[0]!;
1711
+
1712
+ const everyIncidentTakesTheSameRecord: boolean =
1713
+ recordsToSeed.length === data.decisions.length &&
1714
+ recordsToSeed.every((record: Array<string>): boolean => {
1715
+ return (
1716
+ [...record].sort().join(",") === [...recordToSeed].sort().join(",")
1717
+ );
1718
+ });
1719
+
1720
+ if (!everyIncidentTakesTheSameRecord) {
1721
+ throw new BadDataException(
1722
+ "These incidents cannot be sent the incident-created notification for their added status pages together: they were limited to different status pages, or not all of them were ever announced. Change them one at a time.",
1723
+ );
1724
+ }
1725
+
1726
+ updateBy.data.statusPagesNotifiedOnCreation = recordToSeed;
1727
+ }
1728
+
1729
+ updateBy.data.subscriberNotificationStatusOnIncidentCreated =
1730
+ StatusPageSubscriberNotificationStatus.Pending;
1731
+ updateBy.data.subscriberNotificationStatusMessage =
1732
+ IncidentScopeAddedPagesNotification.queuedMessage;
1733
+
1734
+ for (const decision of data.decisions) {
1735
+ const scopeChange: StatusPageScopeCarryForward | undefined =
1736
+ data.carryForward[decision.incidentId]?.statusPageScopeChange;
1737
+
1738
+ if (
1739
+ scopeChange &&
1740
+ decision.action === IncidentScopeAddedPagesNotificationAction.Queue
1741
+ ) {
1742
+ scopeChange.notificationQueued = true;
1743
+ }
1744
+ }
1745
+ }
1746
+
1747
+ /*
1748
+ * An update that added pages while the 'created' notification was Pending
1749
+ * or being sent left the status alone: the queued send reads the scope when
1750
+ * it goes out, and a running send reads it again once it has finished (see
1751
+ * the Incident:SendNotificationToSubscribers job). One case slips between
1752
+ * the two: the whole send started after this update read the status and
1753
+ * finished before it wrote the pages. Once the pages are written, this
1754
+ * checks for exactly that - the send finished, and an added page is not in
1755
+ * its record - and queues the notification again for the pages it missed.
1756
+ *
1757
+ * Never throws: the update is already written.
1758
+ */
1759
+ private async requeueCreatedNotificationIfAddedPagesWereMissed(data: {
1760
+ incidentId: ObjectID;
1761
+ change: StatusPageScopeCarryForward | undefined;
1762
+ }): Promise<void> {
1763
+ if (
1764
+ !data.change?.notificationAlreadyQueued ||
1765
+ data.change.addedStatusPageIds.length === 0
1766
+ ) {
1767
+ return;
1768
+ }
1769
+
1770
+ try {
1771
+ const incident: Model | null = await this.findOneById({
1772
+ id: data.incidentId,
1773
+ select: {
1774
+ subscriberNotificationStatusOnIncidentCreated: true,
1775
+ statusPagesNotifiedOnCreation: true,
1776
+ },
1777
+ props: {
1778
+ isRoot: true,
1779
+ },
1780
+ });
1781
+
1782
+ if (
1783
+ incident?.subscriberNotificationStatusOnIncidentCreated !==
1784
+ StatusPageSubscriberNotificationStatus.Success
1785
+ ) {
1786
+ // Still on its way (it will see the pages), or settled otherwise.
1787
+ return;
1788
+ }
1789
+
1790
+ const recorded: Array<string> =
1791
+ IncidentScopeAddedPagesNotification.getRecordedStatusPageIds(
1792
+ incident.statusPagesNotifiedOnCreation,
1793
+ ) || [];
1794
+
1795
+ const missed: boolean = data.change.addedStatusPageIds.some(
1796
+ (id: string): boolean => {
1797
+ return !recorded.includes(id);
1798
+ },
1799
+ );
1800
+
1801
+ if (!missed) {
1802
+ return;
1803
+ }
1804
+
1805
+ await this.updateOneById({
1806
+ id: data.incidentId,
1807
+ data: {
1808
+ subscriberNotificationStatusOnIncidentCreated:
1809
+ StatusPageSubscriberNotificationStatus.Pending,
1810
+ subscriberNotificationStatusMessage:
1811
+ IncidentScopeAddedPagesNotification.queuedMessage,
1812
+ },
1813
+ props: {
1814
+ isRoot: true,
1815
+ ignoreHooks: true,
1816
+ },
1817
+ });
1818
+ } catch (err) {
1819
+ logger.error(
1820
+ `Failed to check whether the incident created notification reached the status pages added to the incident: ${err}`,
1821
+ {
1822
+ incidentId: data.incidentId?.toString(),
1823
+ } as LogAttributes,
1824
+ );
1825
+ }
1826
+ }
1827
+
1828
+ /*
1829
+ * The pages the matched incidents are scoped to that the caller cannot
1830
+ * read, which a list write from them must keep (see
1831
+ * applyStatusPageScopeToUpdate). A bulk update writes the same list onto
1832
+ * every incident it matches, so it can only keep them when every matched
1833
+ * incident holds the same hidden pages; otherwise it would scope some of
1834
+ * them to pages they were never scoped to, and it is refused.
1835
+ */
1836
+ private async getScopedStatusPageIdsHiddenFromCaller(data: {
1837
+ incidents: Array<Model>;
1838
+ props: DatabaseCommonInteractionProps;
1839
+ }): Promise<Array<string>> {
1840
+ const heldByIncident: Array<Array<string>> = data.incidents.map(
1841
+ (incident: Model) => {
1842
+ return IncidentScopeAddedPagesNotification.normalizeStatusPageIds(
1843
+ incident.statusPages,
1844
+ );
1845
+ },
1846
+ );
1847
+
1848
+ const heldIds: Array<string> =
1849
+ IncidentScopeAddedPagesNotification.normalizeStatusPageIds(
1850
+ heldByIncident.flat(),
1851
+ );
1852
+
1853
+ if (heldIds.length === 0) {
1854
+ return [];
1855
+ }
1856
+
1857
+ const readableIds: Array<string> =
1858
+ await StatusPageReadAccess.getReadableStatusPageIds({
1859
+ statusPageIds: heldIds,
1860
+ props: data.props,
1861
+ });
1862
+
1863
+ const hiddenByIncident: Array<Array<string>> = heldByIncident.map(
1864
+ (held: Array<string>) => {
1865
+ return held
1866
+ .filter((id: string) => {
1867
+ return !readableIds.includes(id);
1868
+ })
1869
+ .sort();
1870
+ },
1871
+ );
1872
+
1873
+ const hiddenIds: Array<string> = hiddenByIncident[0] || [];
1874
+
1875
+ const everyIncidentHidesTheSame: boolean = hiddenByIncident.every(
1876
+ (hidden: Array<string>) => {
1877
+ return hidden.join(",") === hiddenIds.join(",");
1878
+ },
1879
+ );
565
1880
 
566
- if (isAdded) {
567
- // this monitor is added.
568
- carryForward[incident.id!.toString()]?.monitorsAdded?.push(
569
- monitor,
570
- );
571
- }
572
- }
573
- }
574
- }
1881
+ if (!everyIncidentHidesTheSame) {
1882
+ throw new BadDataException(
1883
+ "These incidents are limited to different status pages that you do not have access to, so their status pages cannot be changed together. Change them one at a time.",
1884
+ );
575
1885
  }
576
1886
 
577
- // Set notification status based on shouldStatusPageSubscribersBeNotifiedOnIncidentCreated if it's being updated
578
- if (
579
- updateBy.data.shouldStatusPageSubscribersBeNotifiedOnIncidentCreated !==
580
- undefined
581
- ) {
582
- if (
583
- updateBy.data.shouldStatusPageSubscribersBeNotifiedOnIncidentCreated ===
584
- false
585
- ) {
586
- updateBy.data.subscriberNotificationStatusOnIncidentCreated =
587
- StatusPageSubscriberNotificationStatus.Skipped;
588
- updateBy.data.subscriberNotificationStatusMessage =
589
- "Notifications skipped as subscribers are not to be notified for this incident.";
590
- } else if (
591
- updateBy.data.shouldStatusPageSubscribersBeNotifiedOnIncidentCreated ===
592
- true
593
- ) {
594
- updateBy.data.subscriberNotificationStatusOnIncidentCreated =
595
- StatusPageSubscriberNotificationStatus.Pending;
596
- }
597
- }
1887
+ return hiddenIds;
1888
+ }
598
1889
 
599
- /*
600
- * Re-apply mapped custom field values. Covers the Custom Fields modal
601
- * saving the whole bag back over a mapped value, and the incident's
602
- * monitors being added or removed — both change what a mapped field should
603
- * hold, and folding the answer into this payload keeps it one write with a
604
- * truthful audit entry.
605
- */
606
- await CustomFieldMappingService.applyMappingsToUpdate({
607
- definitionModelType: IncidentCustomField,
608
- updateBy: updateBy,
1890
+ private toStatusPageStubs(statusPageIds: Array<string>): Array<StatusPage> {
1891
+ return statusPageIds.map((statusPageId: string) => {
1892
+ const statusPage: StatusPage = new StatusPage();
1893
+ statusPage._id = statusPageId;
1894
+ return statusPage;
609
1895
  });
1896
+ }
610
1897
 
611
- return {
612
- updateBy: updateBy,
613
- carryForward: carryForward,
614
- };
1898
+ // A column's value once an update is written: the update's, else the stored.
1899
+ private getValueAfterUpdate<T>(
1900
+ updatedValue: unknown,
1901
+ storedValue: T | undefined,
1902
+ ): T | undefined {
1903
+ if (updatedValue !== undefined && updatedValue !== null) {
1904
+ return updatedValue as T;
1905
+ }
1906
+
1907
+ return storedValue;
615
1908
  }
616
1909
 
617
1910
  /*
@@ -764,6 +2057,16 @@ export class Service extends DatabaseService<Model> {
764
2057
  modelName: "On-Call Policy",
765
2058
  service: OnCallDutyPolicyService,
766
2059
  },
2060
+ /*
2061
+ * The status pages the incident is limited to. Another project's page
2062
+ * here would put that project's page name into this incident's feed and
2063
+ * scope it to a page it can never show on.
2064
+ */
2065
+ {
2066
+ column: "statusPages",
2067
+ modelName: "Status Page",
2068
+ service: StatusPageService,
2069
+ },
767
2070
  ...getAffectedResourceRelations(this.getModel()),
768
2071
  ];
769
2072
  }
@@ -811,6 +2114,23 @@ export class Service extends DatabaseService<Model> {
811
2114
  const projectId: ObjectID =
812
2115
  createBy.props.tenantId || createBy.data.projectId!;
813
2116
 
2117
+ /*
2118
+ * The values the caller sent, before a template or a mapping adds any:
2119
+ * those are not the caller's to get wrong.
2120
+ */
2121
+ await this.validateCustomFieldValuesOnCreate({
2122
+ createBy: createBy,
2123
+ projectId: projectId,
2124
+ });
2125
+
2126
+ /*
2127
+ * Status pages the caller picked, as opposed to ones copied from a
2128
+ * template below (see applyStatusPageScopeToCreate).
2129
+ */
2130
+ const statusPagesFromCaller: boolean =
2131
+ createBy.data.statusPages !== undefined &&
2132
+ createBy.data.statusPages !== null;
2133
+
814
2134
  /*
815
2135
  * Declaring the incident from alerts. The alert ids are checked here,
816
2136
  * before the incident number is taken and before anything is written, so
@@ -886,6 +2206,9 @@ export class Service extends DatabaseService<Model> {
886
2206
  // Determine the initial incident state
887
2207
  let initialIncidentStateId: ObjectID | undefined = undefined;
888
2208
 
2209
+ // Declared from a template whose status pages were all deleted.
2210
+ let isScopedToNothingByTemplate: boolean = false;
2211
+
889
2212
  // If currentIncidentStateId is already provided (manual selection), use it
890
2213
  if (createBy.data.currentIncidentStateId) {
891
2214
  initialIncidentStateId = createBy.data.currentIncidentStateId;
@@ -941,6 +2264,9 @@ export class Service extends DatabaseService<Model> {
941
2264
  services: { _id: true },
942
2265
  onCallDutyPolicies: { _id: true },
943
2266
  labels: { _id: true },
2267
+ statusPages: { _id: true },
2268
+ isScopedToStatusPages: true,
2269
+ customFields: true,
944
2270
  },
945
2271
  props: {
946
2272
  isRoot: true,
@@ -1092,9 +2418,50 @@ export class Service extends DatabaseService<Model> {
1092
2418
  createBy.data.labels = stubs;
1093
2419
  }
1094
2420
  }
2421
+ // Applying a template that has status pages scopes the incident.
2422
+ if (createBy.data.statusPages === undefined) {
2423
+ const stubs: Array<StatusPage> | undefined = stubBy(
2424
+ StatusPage,
2425
+ incidentTemplate.statusPages,
2426
+ );
2427
+ if (stubs && stubs.length > 0) {
2428
+ createBy.data.statusPages = stubs;
2429
+ } else if (incidentTemplate.isScopedToStatusPages) {
2430
+ /*
2431
+ * A template limited to status pages that have all been deleted
2432
+ * since. Its incidents stay limited - to nothing - like an
2433
+ * incident whose pages were deleted, rather than reaching every
2434
+ * page that lists their monitors.
2435
+ */
2436
+ isScopedToNothingByTemplate = true;
2437
+ }
2438
+ }
2439
+
2440
+ /*
2441
+ * The template's custom field values fill in the fields the caller
2442
+ * left out; a value the caller sent always wins. Merged, not
2443
+ * replaced, so the caller's own values are never dropped. They are
2444
+ * the template's, so the value check above (the caller's values
2445
+ * only) does not see them, and the mapping below still has the last
2446
+ * word on a mapped field.
2447
+ */
2448
+ const customFieldsWithTemplate: JSONObject | undefined =
2449
+ mergeTemplateCustomFields({
2450
+ templateCustomFields: incidentTemplate.customFields,
2451
+ customFields: createBy.data.customFields,
2452
+ });
2453
+
2454
+ if (customFieldsWithTemplate) {
2455
+ createBy.data.customFields = customFieldsWithTemplate;
2456
+ }
1095
2457
  }
1096
2458
  }
1097
2459
 
2460
+ this.applyStatusPageScopeToCreate({
2461
+ createBy: createBy,
2462
+ isScopedToNothingByTemplate: isScopedToNothingByTemplate,
2463
+ });
2464
+
1098
2465
  // If no custom state is provided or found, fall back to default created state
1099
2466
  if (!initialIncidentStateId) {
1100
2467
  const incidentState: IncidentState | null =
@@ -1182,6 +2549,13 @@ export class Service extends DatabaseService<Model> {
1182
2549
  },
1183
2550
  );
1184
2551
 
2552
+ // Before the counter increment too, like the checks above.
2553
+ await this.assertCallerCanPickStatusPagesOnCreate({
2554
+ createBy: createBy,
2555
+ projectId: projectId,
2556
+ statusPagesFromCaller: statusPagesFromCaller,
2557
+ });
2558
+
1185
2559
  const incidentCounterResult: {
1186
2560
  counter: number;
1187
2561
  prefix: string | undefined;
@@ -1313,6 +2687,8 @@ export class Service extends DatabaseService<Model> {
1313
2687
  * The resources named under "Resources Affected" are read by the feed
1314
2688
  * builder itself (LinkedAffectedResources), one relation at a time.
1315
2689
  */
2690
+ // The created feed item names the status pages a scoped incident is limited to.
2691
+ isScopedToStatusPages: true,
1316
2692
  },
1317
2693
  props: {
1318
2694
  isRoot: true,
@@ -1975,6 +3351,44 @@ export class Service extends DatabaseService<Model> {
1975
3351
  }
1976
3352
  }
1977
3353
 
3354
+ /*
3355
+ * The status pages a scoped incident is limited to, as a Markdown list for
3356
+ * its created feed item. Read on their own rather than in onCreateSuccess's
3357
+ * select: a find that selects several many-to-many lists returns a row for
3358
+ * every combination of them.
3359
+ */
3360
+ private async getScopedStatusPagesMarkdown(incident: Model): Promise<string> {
3361
+ const incidentWithStatusPages: Model | null = await this.findOneById({
3362
+ id: incident.id!,
3363
+ select: {
3364
+ statusPages: {
3365
+ _id: true,
3366
+ name: true,
3367
+ },
3368
+ },
3369
+ props: {
3370
+ isRoot: true,
3371
+ },
3372
+ });
3373
+
3374
+ const statusPages: Array<StatusPage> =
3375
+ incidentWithStatusPages?.statusPages || [];
3376
+
3377
+ if (statusPages.length === 0) {
3378
+ // Scoped to pages that have all been deleted since: hidden everywhere.
3379
+ return `- None: the status pages it was limited to have been deleted, so it is not shown on any status page.\n`;
3380
+ }
3381
+
3382
+ let markdown: string = "";
3383
+
3384
+ for (const statusPage of statusPages) {
3385
+ // A page name is free text: kept from turning into a link or an image.
3386
+ markdown += `- [${escapeMarkdownInline(statusPage.name || "Untitled status page")}](${(await StatusPageService.getStatusPageLinkInDashboard(incident.projectId!, statusPage.id!)).toString()})\n`;
3387
+ }
3388
+
3389
+ return markdown;
3390
+ }
3391
+
1978
3392
  @CaptureSpan()
1979
3393
  private async createIncidentFeedAsync(incident: Model): Promise<void> {
1980
3394
  try {
@@ -2029,6 +3443,12 @@ ${incident.description || "No description provided."}
2029
3443
  feedInfoInMarkdown += `\n\n`;
2030
3444
  }
2031
3445
 
3446
+ if (incident.isScopedToStatusPages) {
3447
+ feedInfoInMarkdown += `📣 **Limited to Status Pages**:\n`;
3448
+ feedInfoInMarkdown += await this.getScopedStatusPagesMarkdown(incident);
3449
+ feedInfoInMarkdown += `\n\n`;
3450
+ }
3451
+
2032
3452
  if (incident.rootCause) {
2033
3453
  feedInfoInMarkdown += `\n
2034
3454
  📄 **Root Cause**:
@@ -2969,14 +4389,8 @@ ${incidentSeverity.name}
2969
4389
  onUpdate.carryForward;
2970
4390
 
2971
4391
  if (carryForward) {
2972
- const incidentCarryForward:
2973
- | {
2974
- monitorsRemoved: Array<Monitor>;
2975
- monitorsAdded: Array<Monitor>;
2976
- oldChangeMonitorStatusIdTo: ObjectID | undefined;
2977
- newMonitorChangeStatusIdTo: ObjectID | undefined;
2978
- }
2979
- | undefined = carryForward[incidentId.toString()];
4392
+ const incidentCarryForward: UpdateCarryForward[string] | undefined =
4393
+ carryForward[incidentId.toString()];
2980
4394
 
2981
4395
  if (incidentCarryForward) {
2982
4396
  if (incidentCarryForward.monitorsRemoved.length > 0) {
@@ -3002,12 +4416,6 @@ ${incidentSeverity.name}
3002
4416
  },
3003
4417
  });
3004
4418
 
3005
- // change these monitors back to operational state.
3006
- await this.markMonitorsActiveForMonitoring(
3007
- projectId!,
3008
- incidentCarryForward.monitorsRemoved,
3009
- );
3010
-
3011
4419
  feedInfoInMarkdown += `\n\n**🗑️ Monitors Removed**:\n`;
3012
4420
 
3013
4421
  for (const monitor of monitorsRemoved) {
@@ -3050,9 +4458,28 @@ ${incidentSeverity.name}
3050
4458
  shouldAddIncidentFeed = true;
3051
4459
  }
3052
4460
 
4461
+ const statusPageScopeMarkdown: string =
4462
+ await this.getStatusPageScopeFeedMarkdown({
4463
+ projectId: projectId,
4464
+ incidentId: incidentId,
4465
+ change: incidentCarryForward.statusPageScopeChange,
4466
+ });
4467
+
4468
+ if (statusPageScopeMarkdown) {
4469
+ feedInfoInMarkdown += statusPageScopeMarkdown;
4470
+ shouldAddIncidentFeed = true;
4471
+ }
4472
+
4473
+ await this.requeueCreatedNotificationIfAddedPagesWereMissed({
4474
+ incidentId: incidentId,
4475
+ change: incidentCarryForward.statusPageScopeChange,
4476
+ });
4477
+
4478
+ // Saving the status the incident already had changes nothing.
3053
4479
  if (
3054
4480
  incidentCarryForward.oldChangeMonitorStatusIdTo &&
3055
- incidentCarryForward.newMonitorChangeStatusIdTo
4481
+ incidentCarryForward.newMonitorChangeStatusIdTo &&
4482
+ this.isMonitorStatusChangedByUpdate(incidentCarryForward)
3056
4483
  ) {
3057
4484
  const oldMonitorStatus: MonitorStatus | null =
3058
4485
  await MonitorStatusService.findOneBy({
@@ -3086,45 +4513,13 @@ ${incidentSeverity.name}
3086
4513
  }
3087
4514
  }
3088
4515
 
3089
- const changeNewMonitorStatusTo: ObjectID | undefined =
3090
- incidentCarryForward.newMonitorChangeStatusIdTo ||
3091
- incidentCarryForward.oldChangeMonitorStatusIdTo;
3092
-
3093
- if (incidentCarryForward.monitorsAdded?.length > 0) {
3094
- await this.disableActiveMonitoringIfManualIncident(incidentId);
3095
- }
3096
-
3097
- if (changeNewMonitorStatusTo) {
3098
- const incident: Model | null = await this.findOneById({
3099
- id: incidentId,
3100
- select: {
3101
- projectId: true,
3102
- monitors: {
3103
- _id: true,
3104
- },
3105
- },
3106
- props: {
3107
- isRoot: true,
3108
- },
3109
- });
3110
-
3111
- const monitorsForThisIncident: Array<Monitor> =
3112
- incident?.monitors || [];
3113
-
3114
- await MonitorService.changeMonitorStatus(
3115
- projectId!,
3116
- monitorsForThisIncident.map((monitor: Monitor) => {
3117
- return new ObjectID(monitor._id?.toString() || "");
3118
- }),
3119
- changeNewMonitorStatusTo,
3120
- true, // notifyMonitorOwners
3121
- "Status was changed because Incident " +
3122
- incidentNumberDisplay +
3123
- " was updated.",
3124
- undefined,
3125
- onUpdate.updateBy.props,
3126
- );
3127
- }
4516
+ await this.updateMonitorsForIncidentEdit({
4517
+ projectId: projectId,
4518
+ incidentId: incidentId,
4519
+ incidentNumberDisplay: incidentNumberDisplay,
4520
+ carryForward: incidentCarryForward,
4521
+ props: onUpdate.updateBy.props,
4522
+ });
3128
4523
  }
3129
4524
  }
3130
4525
 
@@ -3147,6 +4542,257 @@ ${incidentSeverity.name}
3147
4542
  return onUpdate;
3148
4543
  }
3149
4544
 
4545
+ /*
4546
+ * Whether an update puts the incident's monitors in a different status
4547
+ * from the one it held: it writes a status, and not the same one again.
4548
+ */
4549
+ private isMonitorStatusChangedByUpdate(
4550
+ carryForward: UpdateCarryForward[string],
4551
+ ): boolean {
4552
+ if (!carryForward.newMonitorChangeStatusIdTo) {
4553
+ return false;
4554
+ }
4555
+
4556
+ return (
4557
+ carryForward.newMonitorChangeStatusIdTo.toString().toLowerCase() !==
4558
+ (carryForward.oldChangeMonitorStatusIdTo?.toString() || "").toLowerCase()
4559
+ );
4560
+ }
4561
+
4562
+ /*
4563
+ * What an edit of an incident's monitors, or of the status it puts them
4564
+ * in, does to the monitors themselves. The feed item records the edit
4565
+ * either way; this only decides which monitors change.
4566
+ *
4567
+ * The monitors taken off the incident are restored, unless another open
4568
+ * incident still holds them (markMonitorsActiveForMonitoring), when the
4569
+ * incident was open before the update. That includes an update that
4570
+ * resolves it as well: resolving restores only the monitors it still
4571
+ * holds (IncidentStateTimelineService), so this is the only restore the
4572
+ * ones taken off get. A monitor taken off an incident that was resolved
4573
+ * already was restored when it resolved, and restoring it again could
4574
+ * overwrite a status set since (maintenance, or a manual monitor's status
4575
+ * set by hand).
4576
+ *
4577
+ * A resolved incident, as it stands after the update, does nothing else
4578
+ * to its monitors. Nothing would clear what it did: an added monitor of a
4579
+ * manual incident would never be probed again, and the incident's status
4580
+ * would sit on its monitors as false downtime.
4581
+ *
4582
+ * An open incident:
4583
+ * - stops probing the monitors of a manual incident when one is added,
4584
+ * as creating it did (disableActiveMonitoringIfManualIncident);
4585
+ * - puts the monitors added in its status, and all of its monitors only
4586
+ * when the edit changes that status. Every save used to put all of them
4587
+ * in it again, overwriting a status a probe had set since.
4588
+ */
4589
+ private async updateMonitorsForIncidentEdit(data: {
4590
+ projectId: ObjectID;
4591
+ incidentId: ObjectID;
4592
+ incidentNumberDisplay: string;
4593
+ carryForward: UpdateCarryForward[string];
4594
+ props: DatabaseCommonInteractionProps;
4595
+ }): Promise<void> {
4596
+ const carryForward: UpdateCarryForward[string] = data.carryForward;
4597
+
4598
+ if (
4599
+ carryForward.monitorsRemoved.length > 0 &&
4600
+ !carryForward.isResolvedBeforeUpdate
4601
+ ) {
4602
+ // change these monitors back to operational state.
4603
+ await this.markMonitorsActiveForMonitoring(
4604
+ data.projectId,
4605
+ carryForward.monitorsRemoved,
4606
+ );
4607
+ }
4608
+
4609
+ const isMonitorStatusChanged: boolean =
4610
+ this.isMonitorStatusChangedByUpdate(carryForward);
4611
+
4612
+ if (carryForward.monitorsAdded.length === 0 && !isMonitorStatusChanged) {
4613
+ return;
4614
+ }
4615
+
4616
+ /*
4617
+ * Read after the update is written, so an edit that resolves the
4618
+ * incident as well counts as resolved: changing the state restored the
4619
+ * monitors it holds, the added ones included.
4620
+ */
4621
+ if (await this.isIncidentResolved({ incidentId: data.incidentId })) {
4622
+ return;
4623
+ }
4624
+
4625
+ if (carryForward.monitorsAdded.length > 0) {
4626
+ await this.disableActiveMonitoringIfManualIncident(data.incidentId);
4627
+ }
4628
+
4629
+ // The status the incident puts its monitors in after the update.
4630
+ const monitorStatusIdToApply: ObjectID | undefined =
4631
+ carryForward.isChangeMonitorStatusToCleared
4632
+ ? undefined
4633
+ : carryForward.newMonitorChangeStatusIdTo ||
4634
+ carryForward.oldChangeMonitorStatusIdTo;
4635
+
4636
+ if (
4637
+ !monitorStatusIdToApply ||
4638
+ (!isMonitorStatusChanged && carryForward.monitorsAdded.length === 0)
4639
+ ) {
4640
+ return;
4641
+ }
4642
+
4643
+ const addedMonitorIds: Array<string> = carryForward.monitorsAdded.map(
4644
+ (monitor: Monitor): string => {
4645
+ return this.getMonitorIdForComparison(monitor);
4646
+ },
4647
+ );
4648
+
4649
+ /*
4650
+ * The monitors the incident holds now, so a status is only ever put on
4651
+ * a monitor the update left on it.
4652
+ */
4653
+ const incident: Model | null = await this.findOneById({
4654
+ id: data.incidentId,
4655
+ select: {
4656
+ monitors: {
4657
+ _id: true,
4658
+ },
4659
+ },
4660
+ props: {
4661
+ isRoot: true,
4662
+ },
4663
+ });
4664
+
4665
+ const monitorIdsToChange: Array<ObjectID> = (incident?.monitors || [])
4666
+ .filter((monitor: Monitor): boolean => {
4667
+ return (
4668
+ isMonitorStatusChanged ||
4669
+ addedMonitorIds.includes(this.getMonitorIdForComparison(monitor))
4670
+ );
4671
+ })
4672
+ .map((monitor: Monitor): ObjectID => {
4673
+ return new ObjectID(monitor._id?.toString() || "");
4674
+ });
4675
+
4676
+ if (monitorIdsToChange.length === 0) {
4677
+ return;
4678
+ }
4679
+
4680
+ await MonitorService.changeMonitorStatus(
4681
+ data.projectId,
4682
+ monitorIdsToChange,
4683
+ monitorStatusIdToApply,
4684
+ true, // notifyMonitorOwners
4685
+ "Status was changed because Incident " +
4686
+ data.incidentNumberDisplay +
4687
+ " was updated.",
4688
+ undefined,
4689
+ data.props,
4690
+ );
4691
+ }
4692
+
4693
+ /*
4694
+ * The part of an update's feed item that records a change of the status
4695
+ * pages the incident is limited to: the pages added and removed, whether
4696
+ * the incident is now limited at all, and whether the added pages will be
4697
+ * sent the 'created' notification. Empty when the scope did not change.
4698
+ *
4699
+ * Page names are read as root, like the monitor names above. The pages were
4700
+ * validated as this project's when they were written, and the read is
4701
+ * filtered on the project anyway. Never throws: the update is already
4702
+ * written, and a missing line in the feed must not turn it into an error.
4703
+ */
4704
+ private async getStatusPageScopeFeedMarkdown(data: {
4705
+ projectId: ObjectID;
4706
+ incidentId: ObjectID;
4707
+ change: StatusPageScopeCarryForward | undefined;
4708
+ }): Promise<string> {
4709
+ const change: StatusPageScopeCarryForward | undefined = data.change;
4710
+
4711
+ if (
4712
+ !change ||
4713
+ (change.addedStatusPageIds.length === 0 &&
4714
+ change.removedStatusPageIds.length === 0)
4715
+ ) {
4716
+ return "";
4717
+ }
4718
+
4719
+ try {
4720
+ const statusPages: Array<StatusPage> = await StatusPageService.findBy({
4721
+ query: {
4722
+ _id: QueryHelper.any([
4723
+ ...change.addedStatusPageIds,
4724
+ ...change.removedStatusPageIds,
4725
+ ]),
4726
+ projectId: data.projectId,
4727
+ },
4728
+ select: {
4729
+ _id: true,
4730
+ name: true,
4731
+ },
4732
+ limit: LIMIT_MAX,
4733
+ skip: 0,
4734
+ props: {
4735
+ isRoot: true,
4736
+ },
4737
+ });
4738
+
4739
+ const statusPageLines: (ids: Array<string>) => Promise<string> = async (
4740
+ ids: Array<string>,
4741
+ ): Promise<string> => {
4742
+ let lines: string = "";
4743
+
4744
+ for (const id of ids) {
4745
+ const statusPage: StatusPage | undefined = statusPages.find(
4746
+ (page: StatusPage) => {
4747
+ return page.id?.toString().toLowerCase() === id;
4748
+ },
4749
+ );
4750
+
4751
+ if (!statusPage) {
4752
+ // Deleted since: its name is gone with it.
4753
+ lines += `- A deleted status page\n`;
4754
+ continue;
4755
+ }
4756
+
4757
+ // A page name is free text: kept from turning into a link or an image.
4758
+ lines += `- [${escapeMarkdownInline(statusPage.name || "Untitled status page")}](${(await StatusPageService.getStatusPageLinkInDashboard(data.projectId, statusPage.id!)).toString()})\n`;
4759
+ }
4760
+
4761
+ return lines;
4762
+ };
4763
+
4764
+ let markdown: string = "";
4765
+
4766
+ if (change.addedStatusPageIds.length > 0) {
4767
+ markdown += `\n\n**📣 Status Pages Added**:\n${await statusPageLines(change.addedStatusPageIds)}`;
4768
+ }
4769
+
4770
+ if (change.removedStatusPageIds.length > 0) {
4771
+ markdown += `\n\n**🔕 Status Pages Removed**:\n${await statusPageLines(change.removedStatusPageIds)}`;
4772
+ }
4773
+
4774
+ markdown += change.isScoped
4775
+ ? `\n\nThis incident is shown on, and notifies the subscribers of, only its selected status pages that list its monitors.\n`
4776
+ : `\n\nThis incident is no longer limited to specific status pages: it is shown on every status page that lists its monitors, except pages that only show incidents limited to them.\n`;
4777
+
4778
+ if (change.notificationQueued) {
4779
+ markdown += `\nSubscribers of the added status pages will be sent the notification that this incident was created.\n`;
4780
+ }
4781
+
4782
+ return markdown;
4783
+ } catch (err) {
4784
+ logger.error(
4785
+ `Failed to describe the status page scope change in the incident feed: ${err}`,
4786
+ {
4787
+ projectId: data.projectId?.toString(),
4788
+ incidentId: data.incidentId?.toString(),
4789
+ } as LogAttributes,
4790
+ );
4791
+
4792
+ return "";
4793
+ }
4794
+ }
4795
+
3150
4796
  @CaptureSpan()
3151
4797
  public async doesMonitorHasMoreActiveManualIncidents(
3152
4798
  monitorId: ObjectID,