@oneuptime/common 12.0.8 → 12.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 (769) hide show
  1. package/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.ts +1 -1
  2. package/Models/AnalyticsModels/MetricItemAggMV1mByService.ts +1 -1
  3. package/Models/DatabaseModels/AIConversation.ts +32 -0
  4. package/Models/DatabaseModels/AIConversationMessage.ts +41 -0
  5. package/Models/DatabaseModels/AlertEpisodeMember.ts +27 -0
  6. package/Models/DatabaseModels/IncidentEpisodeMember.ts +28 -0
  7. package/Models/DatabaseModels/Index.ts +12 -4
  8. package/Models/DatabaseModels/{TelemetryEntity.ts → InventoryItem.ts} +139 -9
  9. package/Models/DatabaseModels/InventoryItemCustomField.ts +434 -0
  10. package/Models/DatabaseModels/{TelemetryEntityRelationship.ts → InventoryItemRelationship.ts} +8 -8
  11. package/Models/DatabaseModels/NetworkDevice.ts +141 -0
  12. package/Models/DatabaseModels/NetworkDeviceLink.ts +699 -0
  13. package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +467 -0
  14. package/Models/DatabaseModels/NetworkTopologySuppression.ts +429 -0
  15. package/Models/DatabaseModels/OnCallDutyPolicyFeed.ts +9 -0
  16. package/Models/DatabaseModels/Project.ts +78 -0
  17. package/Models/DatabaseModels/UserCall.ts +42 -0
  18. package/Models/DatabaseModels/UserEmail.ts +44 -0
  19. package/Models/DatabaseModels/UserNotificationRule.ts +425 -55
  20. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +24 -2
  21. package/Models/DatabaseModels/UserPush.ts +49 -0
  22. package/Models/DatabaseModels/UserSMS.ts +43 -0
  23. package/Models/DatabaseModels/UserTelegram.ts +59 -0
  24. package/Models/DatabaseModels/UserWebhook.ts +52 -0
  25. package/Models/DatabaseModels/UserWhatsApp.ts +41 -0
  26. package/Models/DatabaseModels/WorkflowVariable.ts +12 -0
  27. package/Server/API/AIChatAPI.ts +315 -1
  28. package/Server/API/DashboardAPI.ts +217 -1
  29. package/Server/API/OnCallReadinessAPI.ts +841 -0
  30. package/Server/API/TeamComplianceAPI.ts +69 -17
  31. package/Server/API/TelemetryAPI.ts +220 -12
  32. package/Server/EnvironmentConfig.ts +52 -0
  33. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +22 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +4 -4
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +5 -5
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.ts +35 -0
  37. package/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.ts +82 -0
  38. package/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.ts +91 -0
  39. package/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.ts +47 -0
  40. package/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.ts +255 -0
  41. package/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.ts +107 -0
  42. package/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.ts +90 -0
  43. package/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.ts +39 -0
  44. package/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.ts +33 -0
  45. package/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.ts +59 -0
  46. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
  47. package/Server/Infrastructure/Queue.ts +78 -13
  48. package/Server/Middleware/PublicDashboardRateLimit.ts +593 -0
  49. package/Server/Services/AIService.ts +7 -0
  50. package/Server/Services/AlertEpisodeStateTimelineService.ts +29 -0
  51. package/Server/Services/AlertSeverityService.ts +63 -0
  52. package/Server/Services/DashboardService.ts +9 -10
  53. package/Server/Services/DatabaseService.ts +32 -2
  54. package/Server/Services/IncidentEpisodeStateTimelineService.ts +29 -0
  55. package/Server/Services/IncidentSeverityService.ts +76 -0
  56. package/Server/Services/Index.ts +12 -4
  57. package/Server/Services/InventoryItemCustomFieldService.ts +9 -0
  58. package/Server/Services/{TelemetryEntityRelationshipService.ts → InventoryItemRelationshipService.ts} +4 -4
  59. package/Server/Services/{TelemetryEntityService.ts → InventoryItemService.ts} +89 -20
  60. package/Server/Services/LogAggregationService.ts +45 -8
  61. package/Server/Services/MetricAggregationService.ts +121 -0
  62. package/Server/Services/MetricService.ts +7 -7
  63. package/Server/Services/NetworkDeviceLinkRuleService.ts +10 -0
  64. package/Server/Services/NetworkDeviceLinkService.ts +84 -0
  65. package/Server/Services/NetworkDeviceService.ts +140 -0
  66. package/Server/Services/NetworkSiteService.ts +77 -25
  67. package/Server/Services/NetworkTopologySuppressionService.ts +84 -0
  68. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +41 -29
  69. package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +8 -0
  70. package/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +62 -13
  71. package/Server/Services/OnCallDutyPolicyScheduleService.ts +61 -1
  72. package/Server/Services/OnCallNotificationAlertingService.ts +742 -0
  73. package/Server/Services/OnCallReadinessService.ts +2803 -0
  74. package/Server/Services/OnCallSetupReminderService.ts +955 -0
  75. package/Server/Services/ProfileAggregationService.ts +123 -0
  76. package/Server/Services/StatusPageService.ts +9 -10
  77. package/Server/Services/TeamComplianceService.ts +429 -252
  78. package/Server/Services/UserCallService.ts +26 -1
  79. package/Server/Services/UserEmailService.ts +26 -1
  80. package/Server/Services/UserNotificationRuleAdminService.ts +1183 -0
  81. package/Server/Services/UserNotificationRuleService.ts +3812 -333
  82. package/Server/Services/UserOnCallLogService.ts +561 -48
  83. package/Server/Services/UserPushService.ts +29 -0
  84. package/Server/Services/UserService.ts +11 -0
  85. package/Server/Services/UserSmsService.ts +26 -1
  86. package/Server/Services/UserTelegramService.ts +24 -1
  87. package/Server/Services/UserWebhookService.ts +28 -1
  88. package/Server/Services/UserWhatsAppService.ts +24 -1
  89. package/Server/Types/Database/Permissions/BasePermission.ts +19 -0
  90. package/Server/Types/Database/Permissions/CreatePermission.ts +164 -0
  91. package/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.ts +340 -0
  92. package/Server/Types/Database/Permissions/QueryPermission.ts +48 -0
  93. package/Server/Types/Database/Permissions/TenantPermission.ts +8 -1
  94. package/Server/Types/Workflow/Components/API/Delete.ts +1 -1
  95. package/Server/Types/Workflow/Components/API/Get.ts +1 -1
  96. package/Server/Types/Workflow/Components/API/Patch.ts +1 -1
  97. package/Server/Types/Workflow/Components/API/Post.ts +1 -1
  98. package/Server/Types/Workflow/Components/API/Put.ts +1 -1
  99. package/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +29 -5
  100. package/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +18 -10
  101. package/Server/Types/Workflow/Components/BaseModel/ModelArguments.ts +55 -0
  102. package/Server/Types/Workflow/Components/Conditions/IfElse.ts +3 -17
  103. package/Server/Types/Workflow/Components/Email.ts +25 -7
  104. package/Server/Types/Workflow/Components/JavaScript.ts +10 -3
  105. package/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +1 -1
  106. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +643 -48
  107. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +32 -3
  108. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +20 -6
  109. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +7 -0
  110. package/Server/Utils/AI/Toolbox/AIActionTools.ts +2 -2
  111. package/Server/Utils/AI/Toolbox/AIMetaTools.ts +863 -0
  112. package/Server/Utils/AI/Toolbox/AlertTools.ts +177 -15
  113. package/Server/Utils/AI/Toolbox/IncidentTools.ts +191 -10
  114. package/Server/Utils/AI/Toolbox/Index.ts +48 -0
  115. package/Server/Utils/AI/Toolbox/MonitorTools.ts +298 -11
  116. package/Server/Utils/AI/Toolbox/NoteWriteTools.ts +295 -0
  117. package/Server/Utils/AI/Toolbox/OnCallTools.ts +1246 -0
  118. package/Server/Utils/AI/Toolbox/RunbookTools.ts +424 -0
  119. package/Server/Utils/AI/Toolbox/SloTools.ts +456 -0
  120. package/Server/Utils/AI/Toolbox/StatusPageTools.ts +559 -0
  121. package/Server/Utils/AI/Toolbox/TeamTools.ts +327 -0
  122. package/Server/Utils/AI/Toolbox/TimelineTools.ts +615 -0
  123. package/Server/Utils/AI/Toolbox/WorkflowProbeTools.ts +664 -0
  124. package/Server/Utils/ClientIp.ts +221 -0
  125. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +47 -0
  126. package/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.ts +163 -0
  127. package/Server/Utils/Dashboard/PublicDashboardSloWidget.ts +147 -0
  128. package/Server/Utils/Express.ts +12 -17
  129. package/Server/Utils/LLM/LLMService.ts +85 -8
  130. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +204 -10
  131. package/Server/Utils/SSRFProtection.ts +98 -23
  132. package/Server/Utils/StartServer.ts +12 -3
  133. package/Server/Utils/Telemetry/EntityRegistry.ts +205 -18
  134. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +25 -25
  135. package/Server/Utils/Telemetry/TelemetryEntity.ts +160 -52
  136. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +7 -3
  137. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +2146 -0
  138. package/Tests/App/Dashboard/CreateWorkflowModal.test.tsx +561 -0
  139. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +2470 -0
  140. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +1897 -0
  141. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +3606 -0
  142. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +784 -0
  143. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +1119 -0
  144. package/Tests/App/Dashboard/SloWidgetFetching.test.tsx +531 -0
  145. package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +1312 -0
  146. package/Tests/App/Dashboard/UserSettingsSetupChecklistPage.test.tsx +1390 -0
  147. package/Tests/Models/InventoryItemModel.test.ts +174 -0
  148. package/Tests/Models/InventoryItemNaming.test.ts +302 -0
  149. package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +437 -0
  150. package/Tests/Server/API/DashboardPublicRateLimit.test.ts +659 -0
  151. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +18 -0
  152. package/Tests/Server/API/DashboardPublicSloAPI.test.ts +880 -0
  153. package/Tests/Server/API/Helpers.ts +24 -15
  154. package/Tests/Server/API/OnCallReadinessAPI.test.ts +2680 -0
  155. package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +915 -0
  156. package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +533 -0
  157. package/Tests/Server/Infrastructure/Postgres/InventoryItemArchiveMigration.test.ts +213 -0
  158. package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +432 -0
  159. package/Tests/Server/Infrastructure/Queue.test.ts +293 -0
  160. package/Tests/Server/Middleware/PublicDashboardRateLimit.test.ts +1645 -0
  161. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +2848 -0
  162. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1393 -0
  163. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +1802 -0
  164. package/Tests/Server/Services/EpisodeStateTimelineNote.test.ts +304 -0
  165. package/Tests/Server/Services/InventoryItemDisplayName.test.ts +339 -0
  166. package/Tests/Server/Services/{TelemetryEntityManualCreate.test.ts → InventoryItemManualCreate.test.ts} +27 -27
  167. package/Tests/Server/Services/IpAllowlistSpoofing.test.ts +450 -0
  168. package/Tests/Server/Services/LogAggregationService.test.ts +235 -1
  169. package/Tests/Server/Services/MetricAggregationService.test.ts +231 -0
  170. package/Tests/Server/Services/MetricEntityMVKeyParity.test.ts +80 -29
  171. package/Tests/Server/Services/MetricServiceAggregate.test.ts +30 -30
  172. package/Tests/Server/Services/NetworkSiteService.test.ts +18 -3
  173. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +1728 -0
  174. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +2402 -0
  175. package/Tests/Server/Services/OnCallDutyPolicyExecutionLogTimelineGapFeed.test.ts +394 -0
  176. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +1744 -0
  177. package/Tests/Server/Services/OnCallReadinessService.test.ts +4295 -0
  178. package/Tests/Server/Services/OnCallSetupReminder.test.ts +1272 -0
  179. package/Tests/Server/Services/OnCallWeeklyReadinessDigest.test.ts +1021 -0
  180. package/Tests/Server/Services/ProfileAggregationService.test.ts +296 -0
  181. package/Tests/Server/Services/SeverityCreationRuleBackfill.test.ts +1536 -0
  182. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +1818 -0
  183. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +1845 -0
  184. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +1394 -0
  185. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +1166 -0
  186. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1468 -0
  187. package/Tests/Server/Services/UserOnCallLogNoNotificationRules.test.ts +1457 -0
  188. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +1546 -0
  189. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +529 -0
  190. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +1219 -0
  191. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +1089 -0
  192. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +2 -1
  193. package/Tests/Server/Types/Workflow/Components/BaseModelDatabaseComponents.test.ts +190 -0
  194. package/Tests/Server/Types/Workflow/Components/ChatWebhookComponents.test.ts +44 -14
  195. package/Tests/Server/Types/Workflow/Components/Email.test.ts +151 -0
  196. package/Tests/Server/Types/Workflow/Components/IfElse.test.ts +98 -0
  197. package/Tests/Server/Types/Workflow/Components/JavaScript.test.ts +51 -0
  198. package/Tests/Server/Utils/AI/AIMetaTools.test.ts +586 -0
  199. package/Tests/Server/Utils/AI/AlertMonitorFilters.test.ts +582 -0
  200. package/Tests/Server/Utils/AI/ChatAgentRunner.test.ts +726 -0
  201. package/Tests/Server/Utils/AI/IncidentToolsFilters.test.ts +315 -0
  202. package/Tests/Server/Utils/AI/LLMServiceStopReason.test.ts +314 -0
  203. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +26 -3
  204. package/Tests/Server/Utils/AI/NoteWriteTools.test.ts +268 -0
  205. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +169 -0
  206. package/Tests/Server/Utils/AI/OnCallTools.test.ts +664 -0
  207. package/Tests/Server/Utils/AI/RunbookTools.test.ts +325 -0
  208. package/Tests/Server/Utils/AI/SloTools.test.ts +306 -0
  209. package/Tests/Server/Utils/AI/StatusPageTools.test.ts +391 -0
  210. package/Tests/Server/Utils/AI/TeamTools.test.ts +257 -0
  211. package/Tests/Server/Utils/AI/TimelineTools.test.ts +472 -0
  212. package/Tests/Server/Utils/AI/WorkflowProbeTools.test.ts +428 -0
  213. package/Tests/Server/Utils/ClientIp.test.ts +438 -0
  214. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +171 -0
  215. package/Tests/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.test.ts +383 -0
  216. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +23 -25
  217. package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +6 -0
  218. package/Tests/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.test.ts +307 -0
  219. package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +468 -0
  220. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorTelemetryDeepLinks.test.ts +460 -0
  221. package/Tests/Server/Utils/ResponseRateLimitStatusCodes.test.ts +137 -0
  222. package/Tests/Server/Utils/SSRFProtectionBypasses.test.ts +40 -8
  223. package/Tests/Server/Utils/SSRFProtectionUserInfo.test.ts +351 -0
  224. package/Tests/Server/Utils/Telemetry/EntityRegistryRetirement.test.ts +531 -0
  225. package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +15 -15
  226. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +356 -39
  227. package/Tests/Types/IP/IP.test.ts +263 -0
  228. package/Tests/Types/IP/IPWhitelist.test.ts +197 -0
  229. package/Tests/Types/IP/IPv6.test.ts +12 -1
  230. package/Tests/Types/Monitor/SnmpOid.test.ts +64 -0
  231. package/Tests/Types/NetworkDevice/NetworkDeviceMonitoringMethod.test.ts +110 -0
  232. package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +106 -0
  233. package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeDifferential.test.ts +511 -0
  234. package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageEndToEnd.test.ts +489 -0
  235. package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageGapTolerance.test.ts +479 -0
  236. package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageState.test.ts +822 -0
  237. package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +6 -6
  238. package/Tests/Types/Workflow/BaseModelComponents.test.ts +429 -0
  239. package/Tests/Types/Workflow/Components/BaseModel.test.ts +225 -0
  240. package/Tests/Types/Workflow/IntegrationCredentialMetadata.test.ts +100 -0
  241. package/Tests/Types/Workflow/TemplateSyntax.test.ts +112 -0
  242. package/Tests/Types/Workflow/Templates.test.ts +916 -101
  243. package/Tests/UI/Components/ActiveFilterChipsOpenRoute.test.tsx +82 -0
  244. package/Tests/UI/Components/ComponentsModal.test.tsx +564 -7
  245. package/Tests/UI/Components/KeyboardShortcut.test.tsx +95 -0
  246. package/Tests/UI/Components/LogDetailsPanelCrossSignal.test.tsx +448 -0
  247. package/Tests/UI/Components/LogsTableCrossLinks.test.tsx +263 -0
  248. package/Tests/UI/Components/PendingProjectInvitations.test.tsx +913 -0
  249. package/Tests/UI/Components/SimpleLogViewer.test.tsx +228 -0
  250. package/Tests/UI/Components/TableRowSelectability.test.tsx +312 -0
  251. package/Tests/UI/Components/Workflow/GraphLint.test.ts +72 -0
  252. package/Tests/UI/Components/Workflow/GraphLintSummary.test.ts +755 -0
  253. package/Tests/UI/Components/Workflow/ModelColumnEditor.test.ts +242 -7
  254. package/Tests/UI/Components/Workflow/ModelColumnEditorServerContract.test.ts +15 -7
  255. package/Tests/UI/Components/Workflow/ModelSchema.test.ts +242 -28
  256. package/Tests/UI/Components/Workflow/RunStatusWatcher.test.ts +59 -1
  257. package/Tests/UI/Components/Workflow/StepTraceViewer.test.tsx +256 -0
  258. package/Tests/UI/Components/Workflow/UseRunWatch.test.tsx +665 -0
  259. package/Tests/UI/Components/Workflow/WorkflowIssuesModal.test.tsx +485 -0
  260. package/Tests/UI/Components/Workflow/WorkflowLogModal.test.tsx +478 -0
  261. package/Tests/UI/Components/Workflow/WorkflowStatusBar.test.tsx +379 -0
  262. package/Tests/UI/EsbuildConfig.test.ts +607 -0
  263. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
  264. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +11 -4
  265. package/Tests/UI/Utils/ModelAPICreateMiscData.test.ts +94 -0
  266. package/Tests/UI/Utils/Platform.test.ts +147 -0
  267. package/Tests/UI/Utils/ProjectInvitationDisplay.test.ts +357 -0
  268. package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +198 -0
  269. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +487 -0
  270. package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +698 -0
  271. package/Tests/__mocks__/bullmq.js +55 -0
  272. package/Types/AI/AIChatMessageStatus.ts +8 -1
  273. package/Types/AI/AIChatTypes.ts +12 -0
  274. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +88 -0
  275. package/Types/Exception/ExceptionCode.ts +2 -0
  276. package/Types/Exception/ServiceUnavailableException.ts +8 -0
  277. package/Types/Exception/TooManyRequestsException.ts +8 -0
  278. package/Types/IP/IP.ts +93 -47
  279. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +49 -3
  280. package/Types/NetworkDevice/NetworkDeviceMonitoringMethod.ts +55 -0
  281. package/Types/OnCallDutyPolicy/Layer.ts +203 -149
  282. package/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.ts +13 -0
  283. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +155 -10
  284. package/Types/Permission.ts +193 -0
  285. package/Types/Telemetry/EntityRelationshipType.ts +1 -1
  286. package/Types/Telemetry/EntitySource.ts +1 -1
  287. package/Types/Telemetry/EntityType.ts +1 -1
  288. package/Types/Telemetry/EntityTypeGroups.ts +1 -1
  289. package/Types/Workflow/Components/BaseModel.ts +75 -29
  290. package/Types/Workflow/Components/Discord.ts +1 -0
  291. package/Types/Workflow/Components/Email.ts +12 -3
  292. package/Types/Workflow/Components/JavaScript.ts +7 -0
  293. package/Types/Workflow/Components/MicrosoftTeams.ts +3 -2
  294. package/Types/Workflow/Components/Slack.ts +1 -0
  295. package/Types/Workflow/Components/Telegram.ts +1 -0
  296. package/Types/Workflow/TemplateSyntax.ts +44 -0
  297. package/Types/Workflow/Templates.ts +2097 -45
  298. package/UI/Components/Calendar/Calendar.css +43 -0
  299. package/UI/Components/Calendar/Calendar.tsx +8 -0
  300. package/UI/Components/Card/Card.tsx +2 -2
  301. package/UI/Components/Checkbox/Checkbox.tsx +16 -0
  302. package/UI/Components/Dictionary/Dictionary.tsx +48 -9
  303. package/UI/Components/FormModal/BasicFormModal.tsx +2 -1
  304. package/UI/Components/Header/HeaderIconDropdownButton.tsx +53 -3
  305. package/UI/Components/Input/Input.tsx +1 -0
  306. package/UI/Components/KeyboardShortcut/KeyboardKey.ts +185 -0
  307. package/UI/Components/KeyboardShortcut/KeyboardShortcut.tsx +87 -0
  308. package/UI/Components/LogsViewer/LogsViewer.tsx +28 -0
  309. package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +31 -0
  310. package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +18 -18
  311. package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +363 -14
  312. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +11 -0
  313. package/UI/Components/LogsViewer/components/LogsTable.tsx +155 -12
  314. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +29 -0
  315. package/UI/Components/LogsViewer/types.ts +23 -0
  316. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +9 -2
  317. package/UI/Components/Navbar/NavBarMenuModal.tsx +15 -30
  318. package/UI/Components/ProjectInvitations/PendingProjectInvitations.tsx +442 -0
  319. package/UI/Components/SimpleLogViewer/SimpleLogViewer.tsx +23 -1
  320. package/UI/Components/Table/Table.tsx +49 -16
  321. package/UI/Components/Table/TableBody.tsx +53 -28
  322. package/UI/Components/Table/TableHeader.tsx +13 -0
  323. package/UI/Components/Table/TableRow.tsx +58 -26
  324. package/UI/Components/Workflow/ArgumentsForm.tsx +155 -9
  325. package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +26 -0
  326. package/UI/Components/Workflow/ComponentSettingsModal.tsx +10 -1
  327. package/UI/Components/Workflow/ComponentValuePickerModal.tsx +135 -7
  328. package/UI/Components/Workflow/ComponentsModal.tsx +116 -22
  329. package/UI/Components/Workflow/DocumentationViewer.tsx +59 -9
  330. package/UI/Components/Workflow/GraphLint.ts +42 -5
  331. package/UI/Components/Workflow/GraphLintSummary.ts +390 -0
  332. package/UI/Components/Workflow/ModelColumnEditor.tsx +154 -22
  333. package/UI/Components/Workflow/ModelSchema.ts +115 -33
  334. package/UI/Components/Workflow/RunStatusWatcher.ts +1 -1
  335. package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
  336. package/UI/Components/Workflow/UseRunWatch.ts +212 -0
  337. package/UI/Components/Workflow/VariableModal.tsx +6 -2
  338. package/UI/Components/Workflow/Workflow.tsx +49 -3
  339. package/UI/Components/Workflow/WorkflowIssuesModal.tsx +255 -0
  340. package/UI/Components/Workflow/WorkflowLogModal.tsx +128 -0
  341. package/UI/Components/Workflow/WorkflowStatusBar.tsx +224 -0
  342. package/UI/Utils/AIChatExport/ConversationMarkdown.ts +10 -0
  343. package/UI/Utils/ModelAPI/ModelAPI.ts +7 -1
  344. package/UI/Utils/Platform.ts +149 -0
  345. package/UI/Utils/ProjectInvitationDisplay.ts +118 -0
  346. package/UI/esbuild-config.js +22 -1
  347. package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +187 -0
  348. package/Utils/Monitor/NetworkTopologyUtil.ts +888 -164
  349. package/Utils/Telemetry/CrossSignalScope.ts +502 -0
  350. package/Utils/Telemetry/EntityKey.ts +5 -5
  351. package/Utils/Telemetry/EntityRelationship.ts +1 -1
  352. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js +1 -1
  353. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js +1 -1
  354. package/build/dist/Models/DatabaseModels/AIConversation.js +32 -0
  355. package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
  356. package/build/dist/Models/DatabaseModels/AIConversationMessage.js +42 -0
  357. package/build/dist/Models/DatabaseModels/AIConversationMessage.js.map +1 -1
  358. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +28 -0
  359. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
  360. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +29 -0
  361. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
  362. package/build/dist/Models/DatabaseModels/Index.js +12 -4
  363. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  364. package/build/dist/Models/DatabaseModels/{TelemetryEntity.js → InventoryItem.js} +167 -31
  365. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -0
  366. package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js +454 -0
  367. package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js.map +1 -0
  368. package/build/dist/Models/DatabaseModels/{TelemetryEntityRelationship.js → InventoryItemRelationship.js} +27 -27
  369. package/build/dist/Models/DatabaseModels/InventoryItemRelationship.js.map +1 -0
  370. package/build/dist/Models/DatabaseModels/NetworkDevice.js +141 -0
  371. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  372. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +719 -0
  373. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -0
  374. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +475 -0
  375. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -0
  376. package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js +446 -0
  377. package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js.map +1 -0
  378. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js +9 -0
  379. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js.map +1 -1
  380. package/build/dist/Models/DatabaseModels/Project.js +80 -0
  381. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  382. package/build/dist/Models/DatabaseModels/UserCall.js +46 -2
  383. package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
  384. package/build/dist/Models/DatabaseModels/UserEmail.js +48 -2
  385. package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
  386. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +424 -55
  387. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  388. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +24 -2
  389. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  390. package/build/dist/Models/DatabaseModels/UserPush.js +51 -1
  391. package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
  392. package/build/dist/Models/DatabaseModels/UserSMS.js +47 -2
  393. package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
  394. package/build/dist/Models/DatabaseModels/UserTelegram.js +65 -3
  395. package/build/dist/Models/DatabaseModels/UserTelegram.js.map +1 -1
  396. package/build/dist/Models/DatabaseModels/UserWebhook.js +56 -2
  397. package/build/dist/Models/DatabaseModels/UserWebhook.js.map +1 -1
  398. package/build/dist/Models/DatabaseModels/UserWhatsApp.js +45 -2
  399. package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
  400. package/build/dist/Models/DatabaseModels/WorkflowVariable.js +12 -0
  401. package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
  402. package/build/dist/Server/API/AIChatAPI.js +237 -1
  403. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  404. package/build/dist/Server/API/DashboardAPI.js +165 -13
  405. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  406. package/build/dist/Server/API/OnCallReadinessAPI.js +599 -0
  407. package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -0
  408. package/build/dist/Server/API/TeamComplianceAPI.js +68 -9
  409. package/build/dist/Server/API/TeamComplianceAPI.js.map +1 -1
  410. package/build/dist/Server/API/TelemetryAPI.js +129 -18
  411. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  412. package/build/dist/Server/EnvironmentConfig.js +45 -0
  413. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  414. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +22 -0
  415. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  416. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +4 -4
  417. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js +18 -0
  418. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js.map +1 -0
  419. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js +39 -0
  420. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js.map +1 -0
  421. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js +38 -0
  422. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js.map +1 -0
  423. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js +22 -0
  424. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js.map +1 -0
  425. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js +150 -0
  426. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js.map +1 -0
  427. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js +57 -0
  428. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js.map +1 -0
  429. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js +69 -0
  430. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js.map +1 -0
  431. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js +32 -0
  432. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js.map +1 -0
  433. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js +26 -0
  434. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js.map +1 -0
  435. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js +48 -0
  436. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js.map +1 -0
  437. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
  438. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  439. package/build/dist/Server/Infrastructure/Queue.js +72 -13
  440. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  441. package/build/dist/Server/Middleware/PublicDashboardRateLimit.js +399 -0
  442. package/build/dist/Server/Middleware/PublicDashboardRateLimit.js.map +1 -0
  443. package/build/dist/Server/Services/AIService.js +1 -0
  444. package/build/dist/Server/Services/AIService.js.map +1 -1
  445. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +24 -3
  446. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -1
  447. package/build/dist/Server/Services/AlertSeverityService.js +54 -0
  448. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  449. package/build/dist/Server/Services/DashboardService.js +10 -9
  450. package/build/dist/Server/Services/DashboardService.js.map +1 -1
  451. package/build/dist/Server/Services/DatabaseService.js +24 -2
  452. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  453. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +24 -3
  454. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  455. package/build/dist/Server/Services/IncidentSeverityService.js +67 -0
  456. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  457. package/build/dist/Server/Services/Index.js +12 -4
  458. package/build/dist/Server/Services/Index.js.map +1 -1
  459. package/build/dist/Server/Services/InventoryItemCustomFieldService.js +9 -0
  460. package/build/dist/Server/Services/InventoryItemCustomFieldService.js.map +1 -0
  461. package/build/dist/Server/Services/{TelemetryEntityRelationshipService.js → InventoryItemRelationshipService.js} +6 -6
  462. package/build/dist/Server/Services/InventoryItemRelationshipService.js.map +1 -0
  463. package/build/dist/Server/Services/{TelemetryEntityService.js → InventoryItemService.js} +68 -23
  464. package/build/dist/Server/Services/InventoryItemService.js.map +1 -0
  465. package/build/dist/Server/Services/LogAggregationService.js +27 -8
  466. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  467. package/build/dist/Server/Services/MetricAggregationService.js +80 -0
  468. package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
  469. package/build/dist/Server/Services/MetricService.js +6 -6
  470. package/build/dist/Server/Services/MetricService.js.map +1 -1
  471. package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js +9 -0
  472. package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js.map +1 -0
  473. package/build/dist/Server/Services/NetworkDeviceLinkService.js +71 -0
  474. package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -0
  475. package/build/dist/Server/Services/NetworkDeviceService.js +113 -0
  476. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  477. package/build/dist/Server/Services/NetworkSiteService.js +53 -13
  478. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  479. package/build/dist/Server/Services/NetworkTopologySuppressionService.js +85 -0
  480. package/build/dist/Server/Services/NetworkTopologySuppressionService.js.map +1 -0
  481. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +48 -32
  482. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  483. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +8 -0
  484. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
  485. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js +51 -12
  486. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js.map +1 -1
  487. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +57 -13
  488. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  489. package/build/dist/Server/Services/OnCallNotificationAlertingService.js +548 -0
  490. package/build/dist/Server/Services/OnCallNotificationAlertingService.js.map +1 -0
  491. package/build/dist/Server/Services/OnCallReadinessService.js +1961 -0
  492. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -0
  493. package/build/dist/Server/Services/OnCallSetupReminderService.js +738 -0
  494. package/build/dist/Server/Services/OnCallSetupReminderService.js.map +1 -0
  495. package/build/dist/Server/Services/ProfileAggregationService.js +68 -4
  496. package/build/dist/Server/Services/ProfileAggregationService.js.map +1 -1
  497. package/build/dist/Server/Services/StatusPageService.js +11 -10
  498. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  499. package/build/dist/Server/Services/TeamComplianceService.js +312 -160
  500. package/build/dist/Server/Services/TeamComplianceService.js.map +1 -1
  501. package/build/dist/Server/Services/UserCallService.js +24 -1
  502. package/build/dist/Server/Services/UserCallService.js.map +1 -1
  503. package/build/dist/Server/Services/UserEmailService.js +24 -1
  504. package/build/dist/Server/Services/UserEmailService.js.map +1 -1
  505. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +858 -0
  506. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -0
  507. package/build/dist/Server/Services/UserNotificationRuleService.js +2830 -175
  508. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  509. package/build/dist/Server/Services/UserOnCallLogService.js +488 -43
  510. package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
  511. package/build/dist/Server/Services/UserPushService.js +26 -0
  512. package/build/dist/Server/Services/UserPushService.js.map +1 -1
  513. package/build/dist/Server/Services/UserService.js +10 -0
  514. package/build/dist/Server/Services/UserService.js.map +1 -1
  515. package/build/dist/Server/Services/UserSmsService.js +24 -1
  516. package/build/dist/Server/Services/UserSmsService.js.map +1 -1
  517. package/build/dist/Server/Services/UserTelegramService.js +22 -1
  518. package/build/dist/Server/Services/UserTelegramService.js.map +1 -1
  519. package/build/dist/Server/Services/UserWebhookService.js +25 -1
  520. package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
  521. package/build/dist/Server/Services/UserWhatsAppService.js +22 -1
  522. package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
  523. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +12 -1
  524. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  525. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +126 -0
  526. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  527. package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js +254 -0
  528. package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js.map +1 -0
  529. package/build/dist/Server/Types/Database/Permissions/QueryPermission.js +47 -2
  530. package/build/dist/Server/Types/Database/Permissions/QueryPermission.js.map +1 -1
  531. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +7 -0
  532. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
  533. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +1 -1
  534. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  535. package/build/dist/Server/Types/Workflow/Components/API/Get.js +1 -1
  536. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  537. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +1 -1
  538. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  539. package/build/dist/Server/Types/Workflow/Components/API/Post.js +1 -1
  540. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  541. package/build/dist/Server/Types/Workflow/Components/API/Put.js +1 -1
  542. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  543. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js +21 -5
  544. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js.map +1 -1
  545. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js +14 -10
  546. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js.map +1 -1
  547. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js +31 -0
  548. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js.map +1 -1
  549. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +3 -9
  550. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
  551. package/build/dist/Server/Types/Workflow/Components/Email.js +15 -4
  552. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  553. package/build/dist/Server/Types/Workflow/Components/JavaScript.js +7 -2
  554. package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
  555. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js +1 -1
  556. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js.map +1 -1
  557. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +514 -56
  558. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  559. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +20 -3
  560. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  561. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +19 -6
  562. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
  563. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +7 -0
  564. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  565. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js +2 -2
  566. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -1
  567. package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js +692 -0
  568. package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js.map +1 -0
  569. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +148 -12
  570. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -1
  571. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +157 -10
  572. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
  573. package/build/dist/Server/Utils/AI/Toolbox/Index.js +37 -0
  574. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  575. package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js +259 -14
  576. package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js.map +1 -1
  577. package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js +235 -0
  578. package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js.map +1 -0
  579. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +1000 -0
  580. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -0
  581. package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js +356 -0
  582. package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js.map +1 -0
  583. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +394 -0
  584. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -0
  585. package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js +465 -0
  586. package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js.map +1 -0
  587. package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js +280 -0
  588. package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js.map +1 -0
  589. package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js +527 -0
  590. package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js.map +1 -0
  591. package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js +548 -0
  592. package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js.map +1 -0
  593. package/build/dist/Server/Utils/ClientIp.js +137 -0
  594. package/build/dist/Server/Utils/ClientIp.js.map +1 -0
  595. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +38 -0
  596. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
  597. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js +89 -0
  598. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js.map +1 -0
  599. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js +77 -0
  600. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js.map +1 -0
  601. package/build/dist/Server/Utils/Express.js +12 -12
  602. package/build/dist/Server/Utils/Express.js.map +1 -1
  603. package/build/dist/Server/Utils/LLM/LLMService.js +70 -7
  604. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  605. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +121 -11
  606. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  607. package/build/dist/Server/Utils/SSRFProtection.js +82 -21
  608. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  609. package/build/dist/Server/Utils/StartServer.js +12 -4
  610. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  611. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +165 -18
  612. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  613. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +11 -11
  614. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  615. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +122 -47
  616. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  617. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +7 -3
  618. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  619. package/build/dist/Types/AI/AIChatMessageStatus.js +8 -1
  620. package/build/dist/Types/AI/AIChatMessageStatus.js.map +1 -1
  621. package/build/dist/Types/AI/AIChatTypes.js +12 -0
  622. package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
  623. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +60 -0
  624. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -0
  625. package/build/dist/Types/Exception/ExceptionCode.js +2 -0
  626. package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
  627. package/build/dist/Types/Exception/ServiceUnavailableException.js +8 -0
  628. package/build/dist/Types/Exception/ServiceUnavailableException.js.map +1 -0
  629. package/build/dist/Types/Exception/TooManyRequestsException.js +8 -0
  630. package/build/dist/Types/Exception/TooManyRequestsException.js.map +1 -0
  631. package/build/dist/Types/IP/IP.js +87 -43
  632. package/build/dist/Types/IP/IP.js.map +1 -1
  633. package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js +50 -0
  634. package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js.map +1 -0
  635. package/build/dist/Types/OnCallDutyPolicy/Layer.js +186 -123
  636. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  637. package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js +13 -0
  638. package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js.map +1 -1
  639. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +105 -11
  640. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
  641. package/build/dist/Types/Permission.js +174 -0
  642. package/build/dist/Types/Permission.js.map +1 -1
  643. package/build/dist/Types/Telemetry/EntityRelationshipType.js +1 -1
  644. package/build/dist/Types/Telemetry/EntitySource.js +1 -1
  645. package/build/dist/Types/Telemetry/EntityType.js +1 -1
  646. package/build/dist/Types/Telemetry/EntityTypeGroups.js +1 -1
  647. package/build/dist/Types/Telemetry/EntityTypeGroups.js.map +1 -1
  648. package/build/dist/Types/Workflow/Components/BaseModel.js +66 -28
  649. package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
  650. package/build/dist/Types/Workflow/Components/Discord.js +1 -0
  651. package/build/dist/Types/Workflow/Components/Discord.js.map +1 -1
  652. package/build/dist/Types/Workflow/Components/Email.js +12 -3
  653. package/build/dist/Types/Workflow/Components/Email.js.map +1 -1
  654. package/build/dist/Types/Workflow/Components/JavaScript.js +7 -0
  655. package/build/dist/Types/Workflow/Components/JavaScript.js.map +1 -1
  656. package/build/dist/Types/Workflow/Components/MicrosoftTeams.js +3 -2
  657. package/build/dist/Types/Workflow/Components/MicrosoftTeams.js.map +1 -1
  658. package/build/dist/Types/Workflow/Components/Slack.js +1 -0
  659. package/build/dist/Types/Workflow/Components/Slack.js.map +1 -1
  660. package/build/dist/Types/Workflow/Components/Telegram.js +1 -0
  661. package/build/dist/Types/Workflow/Components/Telegram.js.map +1 -1
  662. package/build/dist/Types/Workflow/TemplateSyntax.js +12 -0
  663. package/build/dist/Types/Workflow/TemplateSyntax.js.map +1 -1
  664. package/build/dist/Types/Workflow/Templates.js +1931 -38
  665. package/build/dist/Types/Workflow/Templates.js.map +1 -1
  666. package/build/dist/UI/Components/Calendar/Calendar.js +1 -1
  667. package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
  668. package/build/dist/UI/Components/Checkbox/Checkbox.js +1 -1
  669. package/build/dist/UI/Components/Checkbox/Checkbox.js.map +1 -1
  670. package/build/dist/UI/Components/Dictionary/Dictionary.js +25 -11
  671. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  672. package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
  673. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +27 -4
  674. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  675. package/build/dist/UI/Components/Input/Input.js +1 -0
  676. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  677. package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js +163 -0
  678. package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js.map +1 -0
  679. package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js +47 -0
  680. package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js.map +1 -0
  681. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +4 -4
  682. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  683. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +11 -1
  684. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
  685. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +10 -8
  686. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -1
  687. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +227 -14
  688. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
  689. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +5 -0
  690. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  691. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +69 -5
  692. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
  693. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +8 -0
  694. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  695. package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
  696. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +9 -2
  697. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
  698. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +8 -21
  699. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  700. package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js +251 -0
  701. package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js.map +1 -0
  702. package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js +9 -2
  703. package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js.map +1 -1
  704. package/build/dist/UI/Components/Table/Table.js +27 -15
  705. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  706. package/build/dist/UI/Components/Table/TableBody.js +24 -18
  707. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  708. package/build/dist/UI/Components/Table/TableHeader.js +9 -1
  709. package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
  710. package/build/dist/UI/Components/Table/TableRow.js +29 -21
  711. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  712. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +130 -12
  713. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  714. package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js +10 -1
  715. package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js.map +1 -1
  716. package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js +4 -4
  717. package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js.map +1 -1
  718. package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js +57 -7
  719. package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js.map +1 -1
  720. package/build/dist/UI/Components/Workflow/ComponentsModal.js +53 -18
  721. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  722. package/build/dist/UI/Components/Workflow/DocumentationViewer.js +19 -6
  723. package/build/dist/UI/Components/Workflow/DocumentationViewer.js.map +1 -1
  724. package/build/dist/UI/Components/Workflow/GraphLint.js +33 -4
  725. package/build/dist/UI/Components/Workflow/GraphLint.js.map +1 -1
  726. package/build/dist/UI/Components/Workflow/GraphLintSummary.js +231 -0
  727. package/build/dist/UI/Components/Workflow/GraphLintSummary.js.map +1 -0
  728. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +116 -21
  729. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
  730. package/build/dist/UI/Components/Workflow/ModelSchema.js +72 -34
  731. package/build/dist/UI/Components/Workflow/ModelSchema.js.map +1 -1
  732. package/build/dist/UI/Components/Workflow/RunStatusWatcher.js +1 -1
  733. package/build/dist/UI/Components/Workflow/RunStatusWatcher.js.map +1 -1
  734. package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
  735. package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
  736. package/build/dist/UI/Components/Workflow/UseRunWatch.js +123 -0
  737. package/build/dist/UI/Components/Workflow/UseRunWatch.js.map +1 -0
  738. package/build/dist/UI/Components/Workflow/VariableModal.js +3 -2
  739. package/build/dist/UI/Components/Workflow/VariableModal.js.map +1 -1
  740. package/build/dist/UI/Components/Workflow/Workflow.js +41 -1
  741. package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
  742. package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js +99 -0
  743. package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js.map +1 -0
  744. package/build/dist/UI/Components/Workflow/WorkflowLogModal.js +56 -0
  745. package/build/dist/UI/Components/Workflow/WorkflowLogModal.js.map +1 -0
  746. package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js +92 -0
  747. package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js.map +1 -0
  748. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +9 -0
  749. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -1
  750. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +1 -1
  751. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  752. package/build/dist/UI/Utils/Platform.js +118 -0
  753. package/build/dist/UI/Utils/Platform.js.map +1 -0
  754. package/build/dist/UI/Utils/ProjectInvitationDisplay.js +106 -0
  755. package/build/dist/UI/Utils/ProjectInvitationDisplay.js.map +1 -0
  756. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +108 -0
  757. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -0
  758. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +642 -136
  759. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  760. package/build/dist/Utils/Telemetry/CrossSignalScope.js +328 -0
  761. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -0
  762. package/build/dist/Utils/Telemetry/EntityKey.js +5 -5
  763. package/build/dist/Utils/Telemetry/EntityRelationship.js +1 -1
  764. package/jest.config.json +1 -0
  765. package/package.json +1 -1
  766. package/build/dist/Models/DatabaseModels/TelemetryEntity.js.map +0 -1
  767. package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +0 -1
  768. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +0 -1
  769. package/build/dist/Server/Services/TelemetryEntityService.js.map +0 -1
@@ -8,6 +8,8 @@ var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
10
  import DatabaseConfig from "../DatabaseConfig";
11
+ import DatabaseRequestType from "../Types/BaseDatabase/DatabaseRequestType";
12
+ import TenantPermission from "../Types/Database/Permissions/TenantPermission";
11
13
  import Markdown, { MarkdownContentType } from "../Types/Markdown";
12
14
  import CallService from "./CallService";
13
15
  import DatabaseService from "./DatabaseService";
@@ -21,12 +23,23 @@ import TelegramService from "./TelegramService";
21
23
  import WebhookService from "./WebhookService";
22
24
  import WhatsAppService from "./WhatsAppService";
23
25
  import UserEmailService from "./UserEmailService";
26
+ import UserCallService from "./UserCallService";
27
+ import UserPushService from "./UserPushService";
28
+ import UserSmsService from "./UserSmsService";
29
+ import UserTelegramService from "./UserTelegramService";
30
+ import UserWebhookService from "./UserWebhookService";
31
+ import UserWhatsAppService from "./UserWhatsAppService";
32
+ import ProjectService from "./ProjectService";
33
+ import UserNotificationRuleAdminService from "./UserNotificationRuleAdminService";
34
+ import OnCallReadinessService, { ReadinessMethodType, ReadinessStatus, ResponderSource, } from "./OnCallReadinessService";
24
35
  import UserOnCallLogService from "./UserOnCallLogService";
25
36
  import UserOnCallLogTimelineService from "./UserOnCallLogTimelineService";
26
37
  import { AppApiRoute } from "../../ServiceRoute";
27
38
  import Route from "../../Types/API/Route";
28
39
  import URL from "../../Types/API/URL";
29
- import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
40
+ import AuditLogAction from "../../Types/AuditLog/AuditLogAction";
41
+ import SortOrder from "../../Types/BaseDatabase/SortOrder";
42
+ import LIMIT_MAX, { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
30
43
  import QueryHelper from "../Types/Database/QueryHelper";
31
44
  import Email from "../../Types/Email";
32
45
  import EmailTemplateType from "../../Types/Email/EmailTemplateType";
@@ -49,19 +62,268 @@ import AlertSeverityService from "./AlertSeverityService";
49
62
  import AlertEpisode from "../../Models/DatabaseModels/AlertEpisode";
50
63
  import AlertEpisodeService from "./AlertEpisodeService";
51
64
  import AlertEpisodeMemberService from "./AlertEpisodeMemberService";
65
+ import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
52
66
  import IncidentEpisodeService from "./IncidentEpisodeService";
67
+ import IncidentEpisodeMemberService from "./IncidentEpisodeMemberService";
53
68
  import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
54
69
  import PushNotificationService from "./PushNotificationService";
55
70
  import NotificationRuleEventType from "../../Types/Workspace/NotificationRules/EventType";
56
71
  import PushNotificationUtil from "../Utils/PushNotificationUtil";
57
72
  import logger from "../Utils/Logger";
58
73
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
74
+ /*
75
+ * Why the fallback returns an outcome and not just a boolean.
76
+ *
77
+ * Its caller (UserOnCallLogService.onCreateSuccess) has to pick a
78
+ * UserNotificationExecutionStatus out of the answer, and
79
+ * UserNotificationExecutionStatus.Error is TERMINAL — ExecutePendingExecutions
80
+ * selects Executing and TimeoutStuckExecutions selects Started, so nothing
81
+ * anywhere re-selects an Error log. That makes the two ways of not notifying
82
+ * somebody opposites rather than synonyms: "this responder has nothing we can
83
+ * page them on" is a real, permanent misconfiguration worth burning the log
84
+ * for, while "the send raised" is a bad minute that a terminal status would
85
+ * turn into a permanently dropped page. Both are `notified: false`, so the
86
+ * difference has to survive the return or the caller cannot act on it.
87
+ */
88
+ export var FallbackNotificationOutcome;
89
+ (function (FallbackNotificationOutcome) {
90
+ /*
91
+ * A page was handed to at least one sender. Nothing below observes what the
92
+ * sender then did with it — every send in deliverNotificationForRule is
93
+ * fire-and-forget — so this means dispatched, not received.
94
+ */
95
+ FallbackNotificationOutcome["Delivered"] = "Delivered";
96
+ /*
97
+ * There was nothing to try. The responder has no verified method the
98
+ * fallback may use and no webhook, or the only paid channels they have are
99
+ * switched off at the project level. Permanent: a retry finds the same
100
+ * nothing, and only a human adding a notification method changes it.
101
+ */
102
+ FallbackNotificationOutcome["NoUsableNotificationMethod"] = "NoUsableNotificationMethod";
103
+ /*
104
+ * There was something to try and none of it went out: a send raised, or a
105
+ * chosen channel had no template for this event type, or another run already
106
+ * holds the fallback claim on this log and owns the outcome. All three are
107
+ * transient from the caller's point of view — none of them is evidence that
108
+ * the responder is unreachable, so none of them justifies a terminal status.
109
+ */
110
+ FallbackNotificationOutcome["DeliveryFailed"] = "DeliveryFailed";
111
+ })(FallbackNotificationOutcome || (FallbackNotificationOutcome = {}));
112
+ /*
113
+ * The fallback is not tied to any UserNotificationRule row — there is no rule,
114
+ * which is the whole reason it runs — so it claims the on-call log under this
115
+ * reserved literal instead of a rule id. `executedNotificationRules` is a jsonb
116
+ * map keyed by arbitrary text, so the literal sits beside real rule uuids and
117
+ * can never collide with one.
118
+ */
119
+ export const FALLBACK_NOTIFICATION_CLAIM_KEY = "__fallback__";
120
+ /*
121
+ * ---------------------------------------------------------------------------
122
+ * DELETION IMPACT — "what would I lose by deleting this?", asked BEFORE the
123
+ * delete.
124
+ *
125
+ * Two writes a responder makes about their own configuration can take away the
126
+ * only thing standing between a page and nobody hearing it, and neither one
127
+ * looks like that from the screen it is made on:
128
+ *
129
+ * - Deleting a RULE can remove the LAST rule covering one
130
+ * (ruleType x severity) cell. The rule table is a list of rows, not a
131
+ * coverage grid, so "this is the only thing left for Sev1 incidents" is
132
+ * visible nowhere at the moment somebody clicks delete.
133
+ *
134
+ * - Deleting a METHOD CASCADES. Every method foreign key on
135
+ * UserNotificationRule is onDelete: "CASCADE" — and each method service
136
+ * deletes the rows in its own onBeforeDelete as well, so the cascade
137
+ * happens whether or not the database does it — which means removing one
138
+ * phone number destroys every rule that pointed at it. The delete dialog
139
+ * for a phone number mentions notification rules nowhere at all. This is
140
+ * the more dangerous of the two by a distance, because the loss is not even
141
+ * the thing being deleted.
142
+ *
143
+ * Everything here is ADVISORY and is deliberately shaped as a QUESTION the
144
+ * caller asks first, not as a hook that throws. The deletion still goes through
145
+ * the ordinary CRUD path afterwards and nothing below can stop it, for two
146
+ * reasons. The first is that this is the user's own configuration and they are
147
+ * entitled to it — turning "I do not want to be woken by Sev4 alerts" into
148
+ * something a human needs permission for is a worse product than the accident
149
+ * it prevents. The second is that a throwing hook would break the LEGITIMATE
150
+ * deletes too: a user leaving a project, an admin retiring a decommissioned
151
+ * number, a team cleaning up after a migration. The goal is not that nobody
152
+ * does this. It is that nobody does it by accident.
153
+ *
154
+ * "Is this person on call anywhere" is answered by OnCallReadinessService and
155
+ * is never re-derived here. A second answer to that question that disagreed
156
+ * with the readiness page would be worse than no answer: an admin who is told
157
+ * "you are not on call" by a delete dialog and "NotReachable on 3 policies" by
158
+ * the readiness table has no way to know which one to believe, and will end up
159
+ * believing the reassuring one.
160
+ * ---------------------------------------------------------------------------
161
+ */
162
+ /*
163
+ * The channel vocabulary is ReadinessMethodType, re-exported under the name the
164
+ * deletion API uses. Sharing one enum with readiness (which in turn shares its
165
+ * literals with the fallback's `channelsUsed`) means an operator reading
166
+ * "Telegram" in a delete warning, "Telegram" in the readiness table and
167
+ * "notified via fallback (Telegram)" in an execution log is reading the same
168
+ * word about the same thing. It is also what lets a notification-method service
169
+ * name its own channel without importing the readiness module.
170
+ */
171
+ export { ReadinessMethodType as NotificationMethodChannel };
172
+ /**
173
+ * Whether anything will still be able to page this user once the deletion has
174
+ * happened.
175
+ *
176
+ * Four values rather than a boolean because two of the four are things this
177
+ * preview knows FOR CERTAIN and two are not, and collapsing them would mean
178
+ * either inventing a false green or crying wolf at everybody.
179
+ *
180
+ * The certainty comes from one structural fact: a method must be verified to be
181
+ * used at all, and of the seven channels, Push, Email and Webhook have no
182
+ * project switch that can turn them off (see OnCallReadinessService.
183
+ * isChannelEnabled — the first two are zero-cost and the third is somebody
184
+ * else's endpoint). So "no verified method survives" is definitely unreachable,
185
+ * and "a verified Push/Email/Webhook survives" is definitely reachable. What is
186
+ * left over — a user whose surviving methods are all on the four paid channels
187
+ * — depends on project settings this preview does not read, and says so.
188
+ */
189
+ export var PostDeletionReachability;
190
+ (function (PostDeletionReachability) {
191
+ /** A verified method on a channel no project setting can disable survives. */
192
+ PostDeletionReachability["Reachable"] = "Reachable";
193
+ /**
194
+ * Verified methods survive, but every one of them is on a paid channel
195
+ * (SMS, Call, WhatsApp, Telegram) that the project can switch off. Whether
196
+ * this user can still be paged is a project setting, and the readiness page
197
+ * is the surface that knows.
198
+ */
199
+ PostDeletionReachability["DependsOnProjectSettings"] = "DependsOnProjectSettings";
200
+ /**
201
+ * Nothing verified survives. This deletion is the one that takes away the
202
+ * last way of reaching this person — no rule, and no fallback either, since
203
+ * the fallback needs a verified method too.
204
+ */
205
+ PostDeletionReachability["NotReachable"] = "NotReachable";
206
+ /**
207
+ * They could not be paged before this deletion either. Worth its own value
208
+ * rather than being folded into NotReachable: the sentence an admin needs is
209
+ * "this was already broken", not "you are about to break it".
210
+ */
211
+ PostDeletionReachability["AlreadyNotReachable"] = "AlreadyNotReachable";
212
+ /**
213
+ * Readiness had no answer — the user is not a member of this project, or has
214
+ * no User row. Never guessed at, because a guess here is exactly the false
215
+ * green this whole feature exists to prevent.
216
+ */
217
+ PostDeletionReachability["Unknown"] = "Unknown";
218
+ })(PostDeletionReachability || (PostDeletionReachability = {}));
219
+ var SeverityKind;
220
+ (function (SeverityKind) {
221
+ SeverityKind["Incident"] = "Incident";
222
+ SeverityKind["Alert"] = "Alert";
223
+ })(SeverityKind || (SeverityKind = {}));
224
+ /*
225
+ * Which severity column scopes which rule type. This is the same table
226
+ * OnCallReadinessService keeps as RULE_TYPE_SCOPES and it has to stay in
227
+ * agreement with it: an alert rule matched against an incident severity id
228
+ * matches nothing at runtime, so a preview that paired them would report a cell
229
+ * as covered by a rule that can never fire — the exact shape of Gap G, where
230
+ * episode rules were written with a NULL severity and were unreachable and
231
+ * invisible at the same time. The severity is always taken from the column the
232
+ * RULE TYPE dictates, never from whichever one happens to be populated.
233
+ */
234
+ const PAGING_RULE_TYPE_SCOPES = [
235
+ {
236
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
237
+ severityKind: SeverityKind.Incident,
238
+ severityColumn: "incidentSeverityId",
239
+ subjectNoun: "incidents",
240
+ },
241
+ {
242
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE,
243
+ severityKind: SeverityKind.Incident,
244
+ severityColumn: "incidentSeverityId",
245
+ subjectNoun: "incident episodes",
246
+ },
247
+ {
248
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
249
+ severityKind: SeverityKind.Alert,
250
+ severityColumn: "alertSeverityId",
251
+ subjectNoun: "alerts",
252
+ },
253
+ {
254
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE,
255
+ severityKind: SeverityKind.Alert,
256
+ severityColumn: "alertSeverityId",
257
+ subjectNoun: "alert episodes",
258
+ },
259
+ ];
260
+ /*
261
+ * The two rule types that are about the user's shift rather than about anything
262
+ * that fired. They carry no severity, so they are one cell each.
263
+ */
264
+ const HANDOFF_RULE_TYPES = [
265
+ NotificationRuleType.WHEN_USER_GOES_ON_CALL,
266
+ NotificationRuleType.WHEN_USER_GOES_OFF_CALL,
267
+ ];
268
+ /**
269
+ * The three channels no project setting can switch off. Push and Email are
270
+ * zero-cost and Webhook is somebody else's endpoint, so nothing gates them —
271
+ * which is what makes "a verified one of these survives" a CERTAIN answer to
272
+ * "can this person still be paged" rather than a hopeful one. Kept in step with
273
+ * OnCallReadinessService.isChannelEnabled, which returns true for exactly these
274
+ * three unconditionally.
275
+ *
276
+ * A FUNCTION rather than a module-level constant, and that is load-bearing
277
+ * rather than stylistic: this module and OnCallReadinessService import each
278
+ * other, so whichever one is loaded second sees the other's exports still
279
+ * empty. Reading ReadinessMethodType while this module is being evaluated
280
+ * therefore throws on ONE of the two load orders and not the other — a crash
281
+ * that depends on which file some unrelated caller happened to import first,
282
+ * which is about the worst possible failure to debug. Read on call, both orders
283
+ * are long since settled. The same rule applies to every enum below that comes
284
+ * from OnCallReadinessService.
285
+ */
286
+ const channelsWithNoProjectSwitch = () => {
287
+ return [
288
+ ReadinessMethodType.Push,
289
+ ReadinessMethodType.Email,
290
+ ReadinessMethodType.Webhook,
291
+ ];
292
+ };
293
+ /**
294
+ * How each responder source reads in a sentence. The enum values are single
295
+ * words chosen for a chip; a warning has room to say what they mean, and
296
+ * "Override" on its own tells a user nothing about why they are on call.
297
+ *
298
+ * The map is built inside the call for the module-evaluation reason above —
299
+ * a computed key is read at definition time, so a module-level Record would
300
+ * carry exactly the same load-order crash. Typed as a full Record so that a new
301
+ * ResponderSource fails to compile here rather than rendering as a blank.
302
+ */
303
+ const responderSourceProse = (source) => {
304
+ const prose = {
305
+ [ResponderSource.Direct]: "directly on an escalation rule",
306
+ [ResponderSource.Team]: "through a team",
307
+ [ResponderSource.Schedule]: "through a schedule",
308
+ [ResponderSource.Override]: "through an override",
309
+ };
310
+ return prose[source];
311
+ };
312
+ /*
313
+ * Rows per page for the rule read, and a ceiling on how many pages one preview
314
+ * may take. LIMIT_PER_PROJECT is the largest read the database layer will
315
+ * serve, so it is the biggest page that survives a round trip. One user's rules
316
+ * are bounded by (rule types x severities x methods) and land far below one
317
+ * page in any real project; the loop exists so that the one project where that
318
+ * is not true gets a truthful answer instead of a silently truncated one.
319
+ */
320
+ const DELETION_IMPACT_PAGE_SIZE = LIMIT_PER_PROJECT;
321
+ const MAX_DELETION_IMPACT_PAGES = 50;
59
322
  export class Service extends DatabaseService {
60
323
  constructor() {
61
324
  super(Model);
62
325
  }
63
326
  async executeNotificationRuleItem(userNotificationRuleId, options) {
64
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49;
65
327
  /*
66
328
  * Atomically claim this rule for this on-call log BEFORE sending, so two
67
329
  * overlapping cron runs cannot both mark the rule un-executed and both
@@ -83,36 +345,51 @@ export class Service extends DatabaseService {
83
345
  select: {
84
346
  _id: true,
85
347
  userId: true,
348
+ /*
349
+ * Every method relation also selects its OWN userId, which none of the
350
+ * channel blocks below read. It is here for the ownership check that
351
+ * runs before delivery: the address a page is sent to comes from these
352
+ * relations, while whose page it is comes from the rule's userId, and
353
+ * nothing in the ORM ever compares the two. See
354
+ * getNotificationMethodsNotOwnedByRuleOwner.
355
+ */
86
356
  userCall: {
87
357
  phone: true,
88
358
  isVerified: true,
359
+ userId: true,
89
360
  },
90
361
  userSms: {
91
362
  phone: true,
92
363
  isVerified: true,
364
+ userId: true,
93
365
  },
94
366
  userWhatsApp: {
95
367
  phone: true,
96
368
  isVerified: true,
369
+ userId: true,
97
370
  },
98
371
  userTelegram: {
99
372
  telegramChatId: true,
100
373
  telegramUserHandle: true,
101
374
  isVerified: true,
375
+ userId: true,
102
376
  },
103
377
  userWebhook: {
104
378
  webhookUrl: true,
105
379
  name: true,
106
380
  secret: true,
381
+ userId: true,
107
382
  },
108
383
  userEmail: {
109
384
  email: true,
110
385
  isVerified: true,
386
+ userId: true,
111
387
  },
112
388
  userPush: {
113
389
  deviceToken: true,
114
390
  deviceType: true,
115
391
  isVerified: true,
392
+ userId: true,
116
393
  },
117
394
  },
118
395
  props: {
@@ -123,19 +400,121 @@ export class Service extends DatabaseService {
123
400
  throw new BadDataException("Notification rule item not found.");
124
401
  }
125
402
  /*
126
- * If the project has a default Twilio config set, use it for all
127
- * team-member SMS and Calls in this rule. Otherwise the global config
128
- * is used by the notification service.
403
+ * The last line of defence, and the only one that survives every write path
404
+ * including ones that do not exist yet.
405
+ *
406
+ * The write-side guards in UserNotificationRuleAdminService stop a rule
407
+ * whose ownership column and method relation name different people from
408
+ * being SAVED. This stops one that somehow exists from being ACTED ON: a
409
+ * row written before those guards landed, one written by internal code
410
+ * running as root, or one written through a path a future change forgets to
411
+ * route through them. Without it, a single bad row silently redirects a
412
+ * responder's pages for as long as nobody thinks to compare two columns
413
+ * that no screen shows side by side.
129
414
  */
130
- const projectTwilioConfig = await ProjectCallSMSConfigService.getProjectDefaultTwilioConfig(options.projectId);
415
+ const mismatchedChannels = this.getNotificationMethodsNotOwnedByRuleOwner(notificationRuleItem);
416
+ if (mismatchedChannels.length > 0) {
417
+ await this.recordMismatchedNotificationMethod(notificationRuleItem, options, mismatchedChannels);
418
+ return;
419
+ }
420
+ await this.deliverNotificationForRule(notificationRuleItem, options);
421
+ }
422
+ /*
423
+ * Which of a rule's method relations are owned by somebody other than the
424
+ * rule itself.
425
+ *
426
+ * Only a method whose userId was actually LOADED and actually DISAGREES is
427
+ * reported. An unselected column arrives as `undefined`, and reading absence
428
+ * as disagreement would turn this guard into a page-dropping machine on every
429
+ * caller that does not select userId — precisely the failure this whole epic
430
+ * exists to eliminate. Silence here means "no evidence of a mismatch", which
431
+ * is the only safe default for a check that can suppress a page.
432
+ */
433
+ getNotificationMethodsNotOwnedByRuleOwner(notificationRuleItem) {
434
+ var _a, _b, _c, _d, _e, _f, _g;
435
+ const ruleOwnerUserId = notificationRuleItem.userId;
436
+ if (!ruleOwnerUserId) {
437
+ /*
438
+ * An unowned rule cannot be paged for anybody in the first place — the
439
+ * caller found it by id, not by owner — so there is no owner to compare
440
+ * against and nothing to report.
441
+ */
442
+ return [];
443
+ }
444
+ const methodOwners = [
445
+ { label: "Email", ownerUserId: (_a = notificationRuleItem.userEmail) === null || _a === void 0 ? void 0 : _a.userId },
446
+ { label: "SMS", ownerUserId: (_b = notificationRuleItem.userSms) === null || _b === void 0 ? void 0 : _b.userId },
447
+ { label: "Call", ownerUserId: (_c = notificationRuleItem.userCall) === null || _c === void 0 ? void 0 : _c.userId },
448
+ {
449
+ label: "WhatsApp",
450
+ ownerUserId: (_d = notificationRuleItem.userWhatsApp) === null || _d === void 0 ? void 0 : _d.userId,
451
+ },
452
+ {
453
+ label: "Telegram",
454
+ ownerUserId: (_e = notificationRuleItem.userTelegram) === null || _e === void 0 ? void 0 : _e.userId,
455
+ },
456
+ { label: "Push", ownerUserId: (_f = notificationRuleItem.userPush) === null || _f === void 0 ? void 0 : _f.userId },
457
+ {
458
+ label: "Webhook",
459
+ ownerUserId: (_g = notificationRuleItem.userWebhook) === null || _g === void 0 ? void 0 : _g.userId,
460
+ },
461
+ ];
462
+ const mismatched = [];
463
+ for (const methodOwner of methodOwners) {
464
+ if (methodOwner.ownerUserId &&
465
+ methodOwner.ownerUserId.toString() !== ruleOwnerUserId.toString()) {
466
+ mismatched.push(methodOwner.label);
467
+ }
468
+ }
469
+ return mismatched;
470
+ }
471
+ /*
472
+ * Refuse the whole rule, not merely the offending channel.
473
+ *
474
+ * A rule with a foreign method on it is not a rule with one bad field; it is
475
+ * a row somebody wrote to redirect a page, and delivering its other channels
476
+ * would let the row keep working well enough to escape notice. The timeline
477
+ * row is the point: it is the surface a responder and an operator both read,
478
+ * and it names the channel so the mismatch can be found and repaired rather
479
+ * than merely felt as a page that never arrived.
480
+ */
481
+ async recordMismatchedNotificationMethod(notificationRuleItem, options, mismatchedChannels) {
482
+ var _a, _b;
483
+ logger.error(`Notification rule ${(_a = notificationRuleItem.id) === null || _a === void 0 ? void 0 : _a.toString()} was not executed: its ${mismatchedChannels.join(", ")} notification method does not belong to the user the rule belongs to (${(_b = notificationRuleItem.userId) === null || _b === void 0 ? void 0 : _b.toString()}).`);
484
+ const logTimelineItem = this.buildLogTimelineItem(notificationRuleItem, options);
485
+ logTimelineItem.status = UserNotificationStatus.Error;
486
+ logTimelineItem.statusMessage = `Notification not sent because the ${mismatchedChannels.join(", ")} notification method on this rule belongs to a different user. Please review this notification rule.`;
487
+ await UserOnCallLogTimelineService.create({
488
+ data: logTimelineItem,
489
+ props: {
490
+ isRoot: true,
491
+ },
492
+ });
493
+ }
494
+ /*
495
+ * Build the timeline row every channel block stamps its status onto.
496
+ *
497
+ * Callers keep ONE instance and mutate it, because after the first create()
498
+ * the instance carries an _id and a second create() with it UPDATEs the row
499
+ * it already wrote instead of inserting a new one. Anything that needs a row
500
+ * genuinely independent of the delivery attempts (the fell-through guard
501
+ * below) must therefore call this again for a fresh instance rather than
502
+ * reuse the one the channel blocks have been writing to.
503
+ */
504
+ buildLogTimelineItem(notificationRuleItem, options) {
131
505
  const logTimelineItem = new UserOnCallLogTimeline();
132
506
  logTimelineItem.projectId = options.projectId;
133
507
  logTimelineItem.userNotificationLogId = options.userNotificationLogId;
134
- logTimelineItem.userNotificationRuleId = userNotificationRuleId;
135
- logTimelineItem.userNotificationLogId = options.userNotificationLogId;
136
508
  logTimelineItem.userId = notificationRuleItem.userId;
137
509
  logTimelineItem.userNotificationEventType =
138
510
  options.userNotificationEventType;
511
+ /*
512
+ * The fallback delivers through rules it builds in memory and never saves,
513
+ * so there is not always a rule id to point the row at.
514
+ */
515
+ if (notificationRuleItem.id) {
516
+ logTimelineItem.userNotificationRuleId = notificationRuleItem.id;
517
+ }
139
518
  if (options.userBelongsToTeamId) {
140
519
  logTimelineItem.userBelongsToTeamId = options.userBelongsToTeamId;
141
520
  }
@@ -168,6 +547,43 @@ export class Service extends DatabaseService {
168
547
  logTimelineItem.onCallDutyPolicyExecutionLogTimelineId =
169
548
  options.onCallDutyPolicyExecutionLogTimelineId;
170
549
  }
550
+ return logTimelineItem;
551
+ }
552
+ /*
553
+ * The delivery half of executeNotificationRuleItem: given a rule that is
554
+ * already loaded with its method relations, decide what to send on which
555
+ * channel and hand it to the senders.
556
+ *
557
+ * It is split out from the public method so executeFallbackNotification can
558
+ * reuse it with a rule it assembled in memory and never persisted. The claim
559
+ * and the rule lookup that the public method does first are meaningless for a
560
+ * rule that does not exist in the database; everything from here down is
561
+ * exactly what the fallback needs.
562
+ *
563
+ * Returns whether a page was actually handed to a sender, which the fallback
564
+ * needs and the normal path ignores. Resolving without throwing is NOT the
565
+ * same as having sent something: a rule whose channel has no block for this
566
+ * event type falls all the way through to the guard at the bottom, writes an
567
+ * Error row and sends nothing. A caller that read "did not throw" as "paged"
568
+ * would name a channel the responder never heard from.
569
+ */
570
+ async deliverNotificationForRule(notificationRuleItem, options) {
571
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55;
572
+ /*
573
+ * If the project has a default Twilio config set, use it for all
574
+ * team-member SMS and Calls in this rule. Otherwise the global config
575
+ * is used by the notification service.
576
+ */
577
+ const projectTwilioConfig = await ProjectCallSMSConfigService.getProjectDefaultTwilioConfig(options.projectId);
578
+ const logTimelineItem = this.buildLogTimelineItem(notificationRuleItem, options);
579
+ /*
580
+ * Which channels this rule could actually deliver on, and whether any block
581
+ * below matched the event type. If a channel is contactable but no branch
582
+ * claimed the event, the page vanishes without a trace — the guard at the
583
+ * end of this method turns that into a visible Error row.
584
+ */
585
+ const contactableChannels = this.getContactableChannelNames(notificationRuleItem);
586
+ let deliveryAttempted = false;
171
587
  // add status and status message and save.
172
588
  let incident = null;
173
589
  let alert = null;
@@ -294,6 +710,7 @@ export class Service extends DatabaseService {
294
710
  UserNotificationEventType.AlertCreated &&
295
711
  alert) {
296
712
  // create an error log.
713
+ deliveryAttempted = true;
297
714
  logTimelineItem.status = UserNotificationStatus.Sending;
298
715
  logTimelineItem.statusMessage = `Sending email to ${(_c = notificationRuleItem.userEmail) === null || _c === void 0 ? void 0 : _c.email.toString()}`;
299
716
  logTimelineItem.userEmailId = notificationRuleItem.userEmail.id;
@@ -333,6 +750,7 @@ export class Service extends DatabaseService {
333
750
  UserNotificationEventType.IncidentCreated &&
334
751
  incident) {
335
752
  // create an error log.
753
+ deliveryAttempted = true;
336
754
  logTimelineItem.status = UserNotificationStatus.Sending;
337
755
  logTimelineItem.statusMessage = `Sending email to ${(_e = notificationRuleItem.userEmail) === null || _e === void 0 ? void 0 : _e.email.toString()}`;
338
756
  logTimelineItem.userEmailId = notificationRuleItem.userEmail.id;
@@ -371,6 +789,7 @@ export class Service extends DatabaseService {
371
789
  if (options.userNotificationEventType ===
372
790
  UserNotificationEventType.AlertEpisodeCreated &&
373
791
  alertEpisode) {
792
+ deliveryAttempted = true;
374
793
  logTimelineItem.status = UserNotificationStatus.Sending;
375
794
  logTimelineItem.statusMessage = `Sending email to ${(_g = notificationRuleItem.userEmail) === null || _g === void 0 ? void 0 : _g.email.toString()}`;
376
795
  logTimelineItem.userEmailId = notificationRuleItem.userEmail.id;
@@ -404,13 +823,55 @@ export class Service extends DatabaseService {
404
823
  });
405
824
  });
406
825
  }
826
+ // send email for incident episode
827
+ if (options.userNotificationEventType ===
828
+ UserNotificationEventType.IncidentEpisodeCreated &&
829
+ incidentEpisode) {
830
+ deliveryAttempted = true;
831
+ logTimelineItem.status = UserNotificationStatus.Sending;
832
+ logTimelineItem.statusMessage = `Sending email to ${(_j = notificationRuleItem.userEmail) === null || _j === void 0 ? void 0 : _j.email.toString()}`;
833
+ logTimelineItem.userEmailId = notificationRuleItem.userEmail.id;
834
+ const updatedLog = await UserOnCallLogTimelineService.create({
835
+ data: logTimelineItem,
836
+ props: {
837
+ isRoot: true,
838
+ },
839
+ });
840
+ const emailMessage = await this.generateEmailTemplateForIncidentEpisodeCreated((_k = notificationRuleItem.userEmail) === null || _k === void 0 ? void 0 : _k.email, incidentEpisode, updatedLog.id);
841
+ /*
842
+ * No incidentEpisodeId is passed: MailService.sendMail accepts the key
843
+ * in its options type but never serialises it onto the request body, so
844
+ * passing it would look like a link that does not exist.
845
+ */
846
+ MailService.sendMail(emailMessage, {
847
+ userOnCallLogTimelineId: updatedLog.id,
848
+ projectId: options.projectId,
849
+ userId: notificationRuleItem.userId,
850
+ onCallPolicyId: options.onCallPolicyId,
851
+ onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
852
+ teamId: options.userBelongsToTeamId,
853
+ onCallDutyPolicyExecutionLogTimelineId: options.onCallDutyPolicyExecutionLogTimelineId,
854
+ onCallScheduleId: options.onCallScheduleId,
855
+ }).catch(async (err) => {
856
+ await UserOnCallLogTimelineService.updateOneById({
857
+ id: updatedLog.id,
858
+ data: {
859
+ status: UserNotificationStatus.Error,
860
+ statusMessage: err.message || "Error sending email.",
861
+ },
862
+ props: {
863
+ isRoot: true,
864
+ },
865
+ });
866
+ });
867
+ }
407
868
  }
408
869
  // if you have an email but is not verified, then create a log.
409
- if (((_j = notificationRuleItem.userEmail) === null || _j === void 0 ? void 0 : _j.email) &&
410
- !((_k = notificationRuleItem.userEmail) === null || _k === void 0 ? void 0 : _k.isVerified)) {
870
+ if (((_l = notificationRuleItem.userEmail) === null || _l === void 0 ? void 0 : _l.email) &&
871
+ !((_m = notificationRuleItem.userEmail) === null || _m === void 0 ? void 0 : _m.isVerified)) {
411
872
  // create an error log.
412
873
  logTimelineItem.status = UserNotificationStatus.Error;
413
- logTimelineItem.statusMessage = `Email notification not sent because email ${(_l = notificationRuleItem.userEmail) === null || _l === void 0 ? void 0 : _l.email.toString()} is not verified.`;
874
+ logTimelineItem.statusMessage = `Email notification not sent because email ${(_o = notificationRuleItem.userEmail) === null || _o === void 0 ? void 0 : _o.email.toString()} is not verified.`;
414
875
  await UserOnCallLogTimelineService.create({
415
876
  data: logTimelineItem,
416
877
  props: {
@@ -419,15 +880,16 @@ export class Service extends DatabaseService {
419
880
  });
420
881
  }
421
882
  // send sms.
422
- if (((_m = notificationRuleItem.userSms) === null || _m === void 0 ? void 0 : _m.phone) &&
423
- ((_o = notificationRuleItem.userSms) === null || _o === void 0 ? void 0 : _o.isVerified)) {
883
+ if (((_p = notificationRuleItem.userSms) === null || _p === void 0 ? void 0 : _p.phone) &&
884
+ ((_q = notificationRuleItem.userSms) === null || _q === void 0 ? void 0 : _q.isVerified)) {
424
885
  //send sms for alert
425
886
  if (options.userNotificationEventType ===
426
887
  UserNotificationEventType.AlertCreated &&
427
888
  alert) {
428
889
  // create an error log.
890
+ deliveryAttempted = true;
429
891
  logTimelineItem.status = UserNotificationStatus.Sending;
430
- logTimelineItem.statusMessage = `Sending SMS to ${(_p = notificationRuleItem.userSms) === null || _p === void 0 ? void 0 : _p.phone.toString()}.`;
892
+ logTimelineItem.statusMessage = `Sending SMS to ${(_r = notificationRuleItem.userSms) === null || _r === void 0 ? void 0 : _r.phone.toString()}.`;
431
893
  logTimelineItem.userSmsId = notificationRuleItem.userSms.id;
432
894
  const updatedLog = await UserOnCallLogTimelineService.create({
433
895
  data: logTimelineItem,
@@ -466,8 +928,9 @@ export class Service extends DatabaseService {
466
928
  UserNotificationEventType.IncidentCreated &&
467
929
  incident) {
468
930
  // create an error log.
931
+ deliveryAttempted = true;
469
932
  logTimelineItem.status = UserNotificationStatus.Sending;
470
- logTimelineItem.statusMessage = `Sending SMS to ${(_q = notificationRuleItem.userSms) === null || _q === void 0 ? void 0 : _q.phone.toString()}.`;
933
+ logTimelineItem.statusMessage = `Sending SMS to ${(_s = notificationRuleItem.userSms) === null || _s === void 0 ? void 0 : _s.phone.toString()}.`;
471
934
  logTimelineItem.userSmsId = notificationRuleItem.userSms.id;
472
935
  const updatedLog = await UserOnCallLogTimelineService.create({
473
936
  data: logTimelineItem,
@@ -505,8 +968,9 @@ export class Service extends DatabaseService {
505
968
  if (options.userNotificationEventType ===
506
969
  UserNotificationEventType.AlertEpisodeCreated &&
507
970
  alertEpisode) {
971
+ deliveryAttempted = true;
508
972
  logTimelineItem.status = UserNotificationStatus.Sending;
509
- logTimelineItem.statusMessage = `Sending SMS to ${(_r = notificationRuleItem.userSms) === null || _r === void 0 ? void 0 : _r.phone.toString()}.`;
973
+ logTimelineItem.statusMessage = `Sending SMS to ${(_t = notificationRuleItem.userSms) === null || _t === void 0 ? void 0 : _t.phone.toString()}.`;
510
974
  logTimelineItem.userSmsId = notificationRuleItem.userSms.id;
511
975
  const updatedLog = await UserOnCallLogTimelineService.create({
512
976
  data: logTimelineItem,
@@ -539,12 +1003,54 @@ export class Service extends DatabaseService {
539
1003
  });
540
1004
  });
541
1005
  }
1006
+ // send sms for incident episode
1007
+ if (options.userNotificationEventType ===
1008
+ UserNotificationEventType.IncidentEpisodeCreated &&
1009
+ incidentEpisode) {
1010
+ deliveryAttempted = true;
1011
+ logTimelineItem.status = UserNotificationStatus.Sending;
1012
+ logTimelineItem.statusMessage = `Sending SMS to ${(_u = notificationRuleItem.userSms) === null || _u === void 0 ? void 0 : _u.phone.toString()}.`;
1013
+ logTimelineItem.userSmsId = notificationRuleItem.userSms.id;
1014
+ const updatedLog = await UserOnCallLogTimelineService.create({
1015
+ data: logTimelineItem,
1016
+ props: {
1017
+ isRoot: true,
1018
+ },
1019
+ });
1020
+ const smsMessage = await this.generateSmsTemplateForIncidentEpisodeCreated(notificationRuleItem.userSms.phone, incidentEpisode, updatedLog.id);
1021
+ /*
1022
+ * SmsService accepts incidentEpisodeId but drops it on the floor when
1023
+ * building the request body, so it is deliberately not passed here.
1024
+ */
1025
+ SmsService.sendSms(smsMessage, {
1026
+ projectId: incidentEpisode.projectId,
1027
+ customTwilioConfig: projectTwilioConfig,
1028
+ userOnCallLogTimelineId: updatedLog.id,
1029
+ userId: notificationRuleItem.userId,
1030
+ onCallPolicyId: options.onCallPolicyId,
1031
+ onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
1032
+ teamId: options.userBelongsToTeamId,
1033
+ onCallDutyPolicyExecutionLogTimelineId: options.onCallDutyPolicyExecutionLogTimelineId,
1034
+ onCallScheduleId: options.onCallScheduleId,
1035
+ }).catch(async (err) => {
1036
+ await UserOnCallLogTimelineService.updateOneById({
1037
+ id: updatedLog.id,
1038
+ data: {
1039
+ status: UserNotificationStatus.Error,
1040
+ statusMessage: err.message || "Error sending SMS.",
1041
+ },
1042
+ props: {
1043
+ isRoot: true,
1044
+ },
1045
+ });
1046
+ });
1047
+ }
542
1048
  }
543
- if (((_s = notificationRuleItem.userSms) === null || _s === void 0 ? void 0 : _s.phone) &&
544
- !((_t = notificationRuleItem.userSms) === null || _t === void 0 ? void 0 : _t.isVerified)) {
1049
+ if (((_v = notificationRuleItem.userSms) === null || _v === void 0 ? void 0 : _v.phone) &&
1050
+ !((_w = notificationRuleItem.userSms) === null || _w === void 0 ? void 0 : _w.isVerified)) {
545
1051
  // create a log.
546
1052
  logTimelineItem.status = UserNotificationStatus.Error;
547
- logTimelineItem.statusMessage = `SMS not sent because phone ${(_u = notificationRuleItem.userSms) === null || _u === void 0 ? void 0 : _u.phone.toString()} is not verified.`;
1053
+ logTimelineItem.statusMessage = `SMS not sent because phone ${(_x = notificationRuleItem.userSms) === null || _x === void 0 ? void 0 : _x.phone.toString()} is not verified.`;
548
1054
  await UserOnCallLogTimelineService.create({
549
1055
  data: logTimelineItem,
550
1056
  props: {
@@ -552,13 +1058,14 @@ export class Service extends DatabaseService {
552
1058
  },
553
1059
  });
554
1060
  }
555
- if (((_v = notificationRuleItem.userWhatsApp) === null || _v === void 0 ? void 0 : _v.phone) &&
556
- ((_w = notificationRuleItem.userWhatsApp) === null || _w === void 0 ? void 0 : _w.isVerified)) {
1061
+ if (((_y = notificationRuleItem.userWhatsApp) === null || _y === void 0 ? void 0 : _y.phone) &&
1062
+ ((_z = notificationRuleItem.userWhatsApp) === null || _z === void 0 ? void 0 : _z.isVerified)) {
557
1063
  if (options.userNotificationEventType ===
558
1064
  UserNotificationEventType.AlertCreated &&
559
1065
  alert) {
1066
+ deliveryAttempted = true;
560
1067
  logTimelineItem.status = UserNotificationStatus.Sending;
561
- logTimelineItem.statusMessage = `Sending WhatsApp message to ${(_x = notificationRuleItem.userWhatsApp) === null || _x === void 0 ? void 0 : _x.phone.toString()}.`;
1068
+ logTimelineItem.statusMessage = `Sending WhatsApp message to ${(_0 = notificationRuleItem.userWhatsApp) === null || _0 === void 0 ? void 0 : _0.phone.toString()}.`;
562
1069
  logTimelineItem.userWhatsAppId = notificationRuleItem.userWhatsApp.id;
563
1070
  const updatedLog = await UserOnCallLogTimelineService.create({
564
1071
  data: logTimelineItem,
@@ -593,8 +1100,9 @@ export class Service extends DatabaseService {
593
1100
  if (options.userNotificationEventType ===
594
1101
  UserNotificationEventType.IncidentCreated &&
595
1102
  incident) {
1103
+ deliveryAttempted = true;
596
1104
  logTimelineItem.status = UserNotificationStatus.Sending;
597
- logTimelineItem.statusMessage = `Sending WhatsApp message to ${(_y = notificationRuleItem.userWhatsApp) === null || _y === void 0 ? void 0 : _y.phone.toString()}.`;
1105
+ logTimelineItem.statusMessage = `Sending WhatsApp message to ${(_1 = notificationRuleItem.userWhatsApp) === null || _1 === void 0 ? void 0 : _1.phone.toString()}.`;
598
1106
  logTimelineItem.userWhatsAppId = notificationRuleItem.userWhatsApp.id;
599
1107
  const updatedLog = await UserOnCallLogTimelineService.create({
600
1108
  data: logTimelineItem,
@@ -630,8 +1138,9 @@ export class Service extends DatabaseService {
630
1138
  if (options.userNotificationEventType ===
631
1139
  UserNotificationEventType.AlertEpisodeCreated &&
632
1140
  alertEpisode) {
1141
+ deliveryAttempted = true;
633
1142
  logTimelineItem.status = UserNotificationStatus.Sending;
634
- logTimelineItem.statusMessage = `Sending WhatsApp message to ${(_z = notificationRuleItem.userWhatsApp) === null || _z === void 0 ? void 0 : _z.phone.toString()}.`;
1143
+ logTimelineItem.statusMessage = `Sending WhatsApp message to ${(_2 = notificationRuleItem.userWhatsApp) === null || _2 === void 0 ? void 0 : _2.phone.toString()}.`;
635
1144
  logTimelineItem.userWhatsAppId = notificationRuleItem.userWhatsApp.id;
636
1145
  const updatedLog = await UserOnCallLogTimelineService.create({
637
1146
  data: logTimelineItem,
@@ -663,11 +1172,52 @@ export class Service extends DatabaseService {
663
1172
  });
664
1173
  });
665
1174
  }
1175
+ // send WhatsApp for incident episode
1176
+ if (options.userNotificationEventType ===
1177
+ UserNotificationEventType.IncidentEpisodeCreated &&
1178
+ incidentEpisode) {
1179
+ deliveryAttempted = true;
1180
+ logTimelineItem.status = UserNotificationStatus.Sending;
1181
+ logTimelineItem.statusMessage = `Sending WhatsApp message to ${(_3 = notificationRuleItem.userWhatsApp) === null || _3 === void 0 ? void 0 : _3.phone.toString()}.`;
1182
+ logTimelineItem.userWhatsAppId = notificationRuleItem.userWhatsApp.id;
1183
+ const updatedLog = await UserOnCallLogTimelineService.create({
1184
+ data: logTimelineItem,
1185
+ props: {
1186
+ isRoot: true,
1187
+ },
1188
+ });
1189
+ const whatsAppMessage = await this.generateWhatsAppTemplateForIncidentEpisodeCreated(notificationRuleItem.userWhatsApp.phone, incidentEpisode, updatedLog.id);
1190
+ /*
1191
+ * WhatsAppService accepts incidentEpisodeId but never writes it onto
1192
+ * the request body, so it is deliberately not passed here.
1193
+ */
1194
+ WhatsAppService.sendWhatsAppMessage(whatsAppMessage, {
1195
+ projectId: incidentEpisode.projectId,
1196
+ userOnCallLogTimelineId: updatedLog.id,
1197
+ userId: notificationRuleItem.userId,
1198
+ onCallPolicyId: options.onCallPolicyId,
1199
+ onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
1200
+ teamId: options.userBelongsToTeamId,
1201
+ onCallDutyPolicyExecutionLogTimelineId: options.onCallDutyPolicyExecutionLogTimelineId,
1202
+ onCallScheduleId: options.onCallScheduleId,
1203
+ }).catch(async (err) => {
1204
+ await UserOnCallLogTimelineService.updateOneById({
1205
+ id: updatedLog.id,
1206
+ data: {
1207
+ status: UserNotificationStatus.Error,
1208
+ statusMessage: err.message || "Error sending WhatsApp message.",
1209
+ },
1210
+ props: {
1211
+ isRoot: true,
1212
+ },
1213
+ });
1214
+ });
1215
+ }
666
1216
  }
667
- if (((_0 = notificationRuleItem.userWhatsApp) === null || _0 === void 0 ? void 0 : _0.phone) &&
668
- !((_1 = notificationRuleItem.userWhatsApp) === null || _1 === void 0 ? void 0 : _1.isVerified)) {
1217
+ if (((_4 = notificationRuleItem.userWhatsApp) === null || _4 === void 0 ? void 0 : _4.phone) &&
1218
+ !((_5 = notificationRuleItem.userWhatsApp) === null || _5 === void 0 ? void 0 : _5.isVerified)) {
669
1219
  logTimelineItem.status = UserNotificationStatus.Error;
670
- logTimelineItem.statusMessage = `WhatsApp message not sent because phone ${(_2 = notificationRuleItem.userWhatsApp) === null || _2 === void 0 ? void 0 : _2.phone.toString()} is not verified.`;
1220
+ logTimelineItem.statusMessage = `WhatsApp message not sent because phone ${(_6 = notificationRuleItem.userWhatsApp) === null || _6 === void 0 ? void 0 : _6.phone.toString()} is not verified.`;
671
1221
  logTimelineItem.userWhatsAppId = notificationRuleItem.userWhatsApp.id;
672
1222
  await UserOnCallLogTimelineService.create({
673
1223
  data: logTimelineItem,
@@ -677,11 +1227,12 @@ export class Service extends DatabaseService {
677
1227
  });
678
1228
  }
679
1229
  // send Telegram.
680
- if (((_3 = notificationRuleItem.userTelegram) === null || _3 === void 0 ? void 0 : _3.telegramChatId) &&
681
- ((_4 = notificationRuleItem.userTelegram) === null || _4 === void 0 ? void 0 : _4.isVerified)) {
1230
+ if (((_7 = notificationRuleItem.userTelegram) === null || _7 === void 0 ? void 0 : _7.telegramChatId) &&
1231
+ ((_8 = notificationRuleItem.userTelegram) === null || _8 === void 0 ? void 0 : _8.isVerified)) {
682
1232
  if (options.userNotificationEventType ===
683
1233
  UserNotificationEventType.AlertCreated &&
684
1234
  alert) {
1235
+ deliveryAttempted = true;
685
1236
  logTimelineItem.status = UserNotificationStatus.Sending;
686
1237
  logTimelineItem.statusMessage = `Sending Telegram message.`;
687
1238
  logTimelineItem.userTelegramId = notificationRuleItem.userTelegram.id;
@@ -723,6 +1274,7 @@ export class Service extends DatabaseService {
723
1274
  if (options.userNotificationEventType ===
724
1275
  UserNotificationEventType.IncidentCreated &&
725
1276
  incident) {
1277
+ deliveryAttempted = true;
726
1278
  logTimelineItem.status = UserNotificationStatus.Sending;
727
1279
  logTimelineItem.statusMessage = `Sending Telegram message.`;
728
1280
  logTimelineItem.userTelegramId = notificationRuleItem.userTelegram.id;
@@ -764,6 +1316,7 @@ export class Service extends DatabaseService {
764
1316
  if (options.userNotificationEventType ===
765
1317
  UserNotificationEventType.AlertEpisodeCreated &&
766
1318
  alertEpisode) {
1319
+ deliveryAttempted = true;
767
1320
  logTimelineItem.status = UserNotificationStatus.Sending;
768
1321
  logTimelineItem.statusMessage = `Sending Telegram message.`;
769
1322
  logTimelineItem.userTelegramId = notificationRuleItem.userTelegram.id;
@@ -802,9 +1355,54 @@ export class Service extends DatabaseService {
802
1355
  });
803
1356
  });
804
1357
  }
1358
+ if (options.userNotificationEventType ===
1359
+ UserNotificationEventType.IncidentEpisodeCreated &&
1360
+ incidentEpisode) {
1361
+ deliveryAttempted = true;
1362
+ logTimelineItem.status = UserNotificationStatus.Sending;
1363
+ logTimelineItem.statusMessage = `Sending Telegram message.`;
1364
+ logTimelineItem.userTelegramId = notificationRuleItem.userTelegram.id;
1365
+ const updatedLog = await UserOnCallLogTimelineService.create({
1366
+ data: logTimelineItem,
1367
+ props: {
1368
+ isRoot: true,
1369
+ },
1370
+ });
1371
+ const telegramMessage = {
1372
+ to: notificationRuleItem.userTelegram.telegramChatId,
1373
+ body: await this.generateTelegramBodyForIncidentEpisodeCreated(incidentEpisode, updatedLog.id),
1374
+ parseMode: "HTML",
1375
+ disableWebPagePreview: true,
1376
+ };
1377
+ /*
1378
+ * TelegramService accepts incidentEpisodeId but never writes it onto
1379
+ * the request body, so it is deliberately not passed here.
1380
+ */
1381
+ TelegramService.sendTelegramMessage(telegramMessage, {
1382
+ projectId: incidentEpisode.projectId,
1383
+ userOnCallLogTimelineId: updatedLog.id,
1384
+ userId: notificationRuleItem.userId,
1385
+ onCallPolicyId: options.onCallPolicyId,
1386
+ onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
1387
+ teamId: options.userBelongsToTeamId,
1388
+ onCallDutyPolicyExecutionLogTimelineId: options.onCallDutyPolicyExecutionLogTimelineId,
1389
+ onCallScheduleId: options.onCallScheduleId,
1390
+ }).catch(async (err) => {
1391
+ await UserOnCallLogTimelineService.updateOneById({
1392
+ id: updatedLog.id,
1393
+ data: {
1394
+ status: UserNotificationStatus.Error,
1395
+ statusMessage: err.message || "Error sending Telegram message.",
1396
+ },
1397
+ props: {
1398
+ isRoot: true,
1399
+ },
1400
+ });
1401
+ });
1402
+ }
805
1403
  }
806
1404
  if (notificationRuleItem.userTelegram &&
807
- !((_5 = notificationRuleItem.userTelegram) === null || _5 === void 0 ? void 0 : _5.isVerified)) {
1405
+ !((_9 = notificationRuleItem.userTelegram) === null || _9 === void 0 ? void 0 : _9.isVerified)) {
808
1406
  logTimelineItem.status = UserNotificationStatus.Error;
809
1407
  logTimelineItem.statusMessage = `Telegram message not sent because the Telegram account is not verified.`;
810
1408
  logTimelineItem.userTelegramId = notificationRuleItem.userTelegram.id;
@@ -816,11 +1414,12 @@ export class Service extends DatabaseService {
816
1414
  });
817
1415
  }
818
1416
  // send webhook.
819
- if ((_6 = notificationRuleItem.userWebhook) === null || _6 === void 0 ? void 0 : _6.webhookUrl) {
1417
+ if ((_10 = notificationRuleItem.userWebhook) === null || _10 === void 0 ? void 0 : _10.webhookUrl) {
820
1418
  const webhookUrl = notificationRuleItem.userWebhook.webhookUrl;
821
1419
  const webhookSecret = notificationRuleItem.userWebhook.secret;
822
1420
  const userWebhookId = notificationRuleItem.userWebhook.id;
823
1421
  const dispatchWebhook = async (params) => {
1422
+ deliveryAttempted = true;
824
1423
  logTimelineItem.status = UserNotificationStatus.Sending;
825
1424
  logTimelineItem.statusMessage = `Sending webhook to ${webhookUrl}.`;
826
1425
  logTimelineItem.userWebhookId = userWebhookId;
@@ -865,19 +1464,19 @@ export class Service extends DatabaseService {
865
1464
  payload: {
866
1465
  eventType: "on-call.alert.created",
867
1466
  timestamp: new Date().toISOString(),
868
- projectId: ((_7 = alert.projectId) === null || _7 === void 0 ? void 0 : _7.toString()) || "",
1467
+ projectId: ((_11 = alert.projectId) === null || _11 === void 0 ? void 0 : _11.toString()) || "",
869
1468
  userId: notificationRuleItem.userId.toString(),
870
1469
  alert: {
871
- id: ((_8 = alert.id) === null || _8 === void 0 ? void 0 : _8.toString()) || "",
1470
+ id: ((_12 = alert.id) === null || _12 === void 0 ? void 0 : _12.toString()) || "",
872
1471
  title: alert.title || "",
873
1472
  description: alert.description || "",
874
1473
  alertNumber: alert.alertNumber || null,
875
1474
  alertNumberWithPrefix: alert.alertNumberWithPrefix || null,
876
- severity: ((_9 = alert.alertSeverity) === null || _9 === void 0 ? void 0 : _9.name) || null,
877
- state: ((_10 = alert.currentAlertState) === null || _10 === void 0 ? void 0 : _10.name) || null,
1475
+ severity: ((_13 = alert.alertSeverity) === null || _13 === void 0 ? void 0 : _13.name) || null,
1476
+ state: ((_14 = alert.currentAlertState) === null || _14 === void 0 ? void 0 : _14.name) || null,
878
1477
  },
879
- onCallPolicyId: ((_11 = options.onCallPolicyId) === null || _11 === void 0 ? void 0 : _11.toString()) || null,
880
- onCallPolicyEscalationRuleId: ((_12 = options.onCallPolicyEscalationRuleId) === null || _12 === void 0 ? void 0 : _12.toString()) || null,
1478
+ onCallPolicyId: ((_15 = options.onCallPolicyId) === null || _15 === void 0 ? void 0 : _15.toString()) || null,
1479
+ onCallPolicyEscalationRuleId: ((_16 = options.onCallPolicyEscalationRuleId) === null || _16 === void 0 ? void 0 : _16.toString()) || null,
881
1480
  },
882
1481
  });
883
1482
  }
@@ -891,19 +1490,19 @@ export class Service extends DatabaseService {
891
1490
  payload: {
892
1491
  eventType: "on-call.incident.created",
893
1492
  timestamp: new Date().toISOString(),
894
- projectId: ((_13 = incident.projectId) === null || _13 === void 0 ? void 0 : _13.toString()) || "",
1493
+ projectId: ((_17 = incident.projectId) === null || _17 === void 0 ? void 0 : _17.toString()) || "",
895
1494
  userId: notificationRuleItem.userId.toString(),
896
1495
  incident: {
897
- id: ((_14 = incident.id) === null || _14 === void 0 ? void 0 : _14.toString()) || "",
1496
+ id: ((_18 = incident.id) === null || _18 === void 0 ? void 0 : _18.toString()) || "",
898
1497
  title: incident.title || "",
899
1498
  description: incident.description || "",
900
1499
  incidentNumber: incident.incidentNumber || null,
901
1500
  incidentNumberWithPrefix: incident.incidentNumberWithPrefix || null,
902
- severity: ((_15 = incident.incidentSeverity) === null || _15 === void 0 ? void 0 : _15.name) || null,
903
- state: ((_16 = incident.currentIncidentState) === null || _16 === void 0 ? void 0 : _16.name) || null,
1501
+ severity: ((_19 = incident.incidentSeverity) === null || _19 === void 0 ? void 0 : _19.name) || null,
1502
+ state: ((_20 = incident.currentIncidentState) === null || _20 === void 0 ? void 0 : _20.name) || null,
904
1503
  },
905
- onCallPolicyId: ((_17 = options.onCallPolicyId) === null || _17 === void 0 ? void 0 : _17.toString()) || null,
906
- onCallPolicyEscalationRuleId: ((_18 = options.onCallPolicyEscalationRuleId) === null || _18 === void 0 ? void 0 : _18.toString()) || null,
1504
+ onCallPolicyId: ((_21 = options.onCallPolicyId) === null || _21 === void 0 ? void 0 : _21.toString()) || null,
1505
+ onCallPolicyEscalationRuleId: ((_22 = options.onCallPolicyEscalationRuleId) === null || _22 === void 0 ? void 0 : _22.toString()) || null,
907
1506
  },
908
1507
  });
909
1508
  }
@@ -916,19 +1515,19 @@ export class Service extends DatabaseService {
916
1515
  payload: {
917
1516
  eventType: "on-call.alertEpisode.created",
918
1517
  timestamp: new Date().toISOString(),
919
- projectId: ((_19 = alertEpisode.projectId) === null || _19 === void 0 ? void 0 : _19.toString()) || "",
1518
+ projectId: ((_23 = alertEpisode.projectId) === null || _23 === void 0 ? void 0 : _23.toString()) || "",
920
1519
  userId: notificationRuleItem.userId.toString(),
921
1520
  alertEpisode: {
922
- id: ((_20 = alertEpisode.id) === null || _20 === void 0 ? void 0 : _20.toString()) || "",
1521
+ id: ((_24 = alertEpisode.id) === null || _24 === void 0 ? void 0 : _24.toString()) || "",
923
1522
  title: alertEpisode.title || "",
924
1523
  description: alertEpisode.description || "",
925
1524
  episodeNumber: alertEpisode.episodeNumber || null,
926
1525
  episodeNumberWithPrefix: alertEpisode.episodeNumberWithPrefix || null,
927
- severity: ((_21 = alertEpisode.alertSeverity) === null || _21 === void 0 ? void 0 : _21.name) || null,
928
- state: ((_22 = alertEpisode.currentAlertState) === null || _22 === void 0 ? void 0 : _22.name) || null,
1526
+ severity: ((_25 = alertEpisode.alertSeverity) === null || _25 === void 0 ? void 0 : _25.name) || null,
1527
+ state: ((_26 = alertEpisode.currentAlertState) === null || _26 === void 0 ? void 0 : _26.name) || null,
929
1528
  },
930
- onCallPolicyId: ((_23 = options.onCallPolicyId) === null || _23 === void 0 ? void 0 : _23.toString()) || null,
931
- onCallPolicyEscalationRuleId: ((_24 = options.onCallPolicyEscalationRuleId) === null || _24 === void 0 ? void 0 : _24.toString()) || null,
1529
+ onCallPolicyId: ((_27 = options.onCallPolicyId) === null || _27 === void 0 ? void 0 : _27.toString()) || null,
1530
+ onCallPolicyEscalationRuleId: ((_28 = options.onCallPolicyEscalationRuleId) === null || _28 === void 0 ? void 0 : _28.toString()) || null,
932
1531
  },
933
1532
  });
934
1533
  }
@@ -941,33 +1540,34 @@ export class Service extends DatabaseService {
941
1540
  payload: {
942
1541
  eventType: "on-call.incidentEpisode.created",
943
1542
  timestamp: new Date().toISOString(),
944
- projectId: ((_25 = incidentEpisode.projectId) === null || _25 === void 0 ? void 0 : _25.toString()) || "",
1543
+ projectId: ((_29 = incidentEpisode.projectId) === null || _29 === void 0 ? void 0 : _29.toString()) || "",
945
1544
  userId: notificationRuleItem.userId.toString(),
946
1545
  incidentEpisode: {
947
- id: ((_26 = incidentEpisode.id) === null || _26 === void 0 ? void 0 : _26.toString()) || "",
1546
+ id: ((_30 = incidentEpisode.id) === null || _30 === void 0 ? void 0 : _30.toString()) || "",
948
1547
  title: incidentEpisode.title || "",
949
1548
  description: incidentEpisode.description || "",
950
1549
  episodeNumber: incidentEpisode.episodeNumber || null,
951
1550
  episodeNumberWithPrefix: incidentEpisode.episodeNumberWithPrefix || null,
952
- severity: ((_27 = incidentEpisode.incidentSeverity) === null || _27 === void 0 ? void 0 : _27.name) || null,
953
- state: ((_28 = incidentEpisode.currentIncidentState) === null || _28 === void 0 ? void 0 : _28.name) || null,
1551
+ severity: ((_31 = incidentEpisode.incidentSeverity) === null || _31 === void 0 ? void 0 : _31.name) || null,
1552
+ state: ((_32 = incidentEpisode.currentIncidentState) === null || _32 === void 0 ? void 0 : _32.name) || null,
954
1553
  },
955
- onCallPolicyId: ((_29 = options.onCallPolicyId) === null || _29 === void 0 ? void 0 : _29.toString()) || null,
956
- onCallPolicyEscalationRuleId: ((_30 = options.onCallPolicyEscalationRuleId) === null || _30 === void 0 ? void 0 : _30.toString()) || null,
1554
+ onCallPolicyId: ((_33 = options.onCallPolicyId) === null || _33 === void 0 ? void 0 : _33.toString()) || null,
1555
+ onCallPolicyEscalationRuleId: ((_34 = options.onCallPolicyEscalationRuleId) === null || _34 === void 0 ? void 0 : _34.toString()) || null,
957
1556
  },
958
1557
  });
959
1558
  }
960
1559
  }
961
1560
  // send call.
962
- if (((_31 = notificationRuleItem.userCall) === null || _31 === void 0 ? void 0 : _31.phone) &&
963
- ((_32 = notificationRuleItem.userCall) === null || _32 === void 0 ? void 0 : _32.isVerified)) {
1561
+ if (((_35 = notificationRuleItem.userCall) === null || _35 === void 0 ? void 0 : _35.phone) &&
1562
+ ((_36 = notificationRuleItem.userCall) === null || _36 === void 0 ? void 0 : _36.isVerified)) {
964
1563
  // send call for alert
965
1564
  if (options.userNotificationEventType ===
966
1565
  UserNotificationEventType.AlertCreated &&
967
1566
  alert) {
968
1567
  // create an error log.
1568
+ deliveryAttempted = true;
969
1569
  logTimelineItem.status = UserNotificationStatus.Sending;
970
- logTimelineItem.statusMessage = `Making a call to ${(_33 = notificationRuleItem.userCall) === null || _33 === void 0 ? void 0 : _33.phone.toString()}.`;
1570
+ logTimelineItem.statusMessage = `Making a call to ${(_37 = notificationRuleItem.userCall) === null || _37 === void 0 ? void 0 : _37.phone.toString()}.`;
971
1571
  logTimelineItem.userCallId = notificationRuleItem.userCall.id;
972
1572
  const updatedLog = await UserOnCallLogTimelineService.create({
973
1573
  data: logTimelineItem,
@@ -975,7 +1575,7 @@ export class Service extends DatabaseService {
975
1575
  isRoot: true,
976
1576
  },
977
1577
  });
978
- const callRequest = await this.generateCallTemplateForAlertCreated((_34 = notificationRuleItem.userCall) === null || _34 === void 0 ? void 0 : _34.phone, alert, updatedLog.id);
1578
+ const callRequest = await this.generateCallTemplateForAlertCreated((_38 = notificationRuleItem.userCall) === null || _38 === void 0 ? void 0 : _38.phone, alert, updatedLog.id);
979
1579
  // send call.
980
1580
  CallService.makeCall(callRequest, {
981
1581
  projectId: alert.projectId,
@@ -1005,8 +1605,9 @@ export class Service extends DatabaseService {
1005
1605
  UserNotificationEventType.IncidentCreated &&
1006
1606
  incident) {
1007
1607
  // send call for incident
1608
+ deliveryAttempted = true;
1008
1609
  logTimelineItem.status = UserNotificationStatus.Sending;
1009
- logTimelineItem.statusMessage = `Making a call to ${(_35 = notificationRuleItem.userCall) === null || _35 === void 0 ? void 0 : _35.phone.toString()}.`;
1610
+ logTimelineItem.statusMessage = `Making a call to ${(_39 = notificationRuleItem.userCall) === null || _39 === void 0 ? void 0 : _39.phone.toString()}.`;
1010
1611
  logTimelineItem.userCallId = notificationRuleItem.userCall.id;
1011
1612
  const updatedLog = await UserOnCallLogTimelineService.create({
1012
1613
  data: logTimelineItem,
@@ -1014,7 +1615,7 @@ export class Service extends DatabaseService {
1014
1615
  isRoot: true,
1015
1616
  },
1016
1617
  });
1017
- const callRequest = await this.generateCallTemplateForIncidentCreated((_36 = notificationRuleItem.userCall) === null || _36 === void 0 ? void 0 : _36.phone, incident, updatedLog.id);
1618
+ const callRequest = await this.generateCallTemplateForIncidentCreated((_40 = notificationRuleItem.userCall) === null || _40 === void 0 ? void 0 : _40.phone, incident, updatedLog.id);
1018
1619
  // send call.
1019
1620
  CallService.makeCall(callRequest, {
1020
1621
  projectId: incident.projectId,
@@ -1044,8 +1645,9 @@ export class Service extends DatabaseService {
1044
1645
  if (options.userNotificationEventType ===
1045
1646
  UserNotificationEventType.AlertEpisodeCreated &&
1046
1647
  alertEpisode) {
1648
+ deliveryAttempted = true;
1047
1649
  logTimelineItem.status = UserNotificationStatus.Sending;
1048
- logTimelineItem.statusMessage = `Making a call to ${(_37 = notificationRuleItem.userCall) === null || _37 === void 0 ? void 0 : _37.phone.toString()}.`;
1650
+ logTimelineItem.statusMessage = `Making a call to ${(_41 = notificationRuleItem.userCall) === null || _41 === void 0 ? void 0 : _41.phone.toString()}.`;
1049
1651
  logTimelineItem.userCallId = notificationRuleItem.userCall.id;
1050
1652
  const updatedLog = await UserOnCallLogTimelineService.create({
1051
1653
  data: logTimelineItem,
@@ -1053,7 +1655,7 @@ export class Service extends DatabaseService {
1053
1655
  isRoot: true,
1054
1656
  },
1055
1657
  });
1056
- const callRequest = await this.generateCallTemplateForAlertEpisodeCreated((_38 = notificationRuleItem.userCall) === null || _38 === void 0 ? void 0 : _38.phone, alertEpisode, updatedLog.id);
1658
+ const callRequest = await this.generateCallTemplateForAlertEpisodeCreated((_42 = notificationRuleItem.userCall) === null || _42 === void 0 ? void 0 : _42.phone, alertEpisode, updatedLog.id);
1057
1659
  CallService.makeCall(callRequest, {
1058
1660
  projectId: alertEpisode.projectId,
1059
1661
  customTwilioConfig: projectTwilioConfig,
@@ -1078,12 +1680,54 @@ export class Service extends DatabaseService {
1078
1680
  });
1079
1681
  });
1080
1682
  }
1683
+ // send call for incident episode
1684
+ if (options.userNotificationEventType ===
1685
+ UserNotificationEventType.IncidentEpisodeCreated &&
1686
+ incidentEpisode) {
1687
+ deliveryAttempted = true;
1688
+ logTimelineItem.status = UserNotificationStatus.Sending;
1689
+ logTimelineItem.statusMessage = `Making a call to ${(_43 = notificationRuleItem.userCall) === null || _43 === void 0 ? void 0 : _43.phone.toString()}.`;
1690
+ logTimelineItem.userCallId = notificationRuleItem.userCall.id;
1691
+ const updatedLog = await UserOnCallLogTimelineService.create({
1692
+ data: logTimelineItem,
1693
+ props: {
1694
+ isRoot: true,
1695
+ },
1696
+ });
1697
+ const callRequest = await this.generateCallTemplateForIncidentEpisodeCreated((_44 = notificationRuleItem.userCall) === null || _44 === void 0 ? void 0 : _44.phone, incidentEpisode, updatedLog.id);
1698
+ /*
1699
+ * CallService accepts incidentEpisodeId but never writes it onto the
1700
+ * request body, so it is deliberately not passed here.
1701
+ */
1702
+ CallService.makeCall(callRequest, {
1703
+ projectId: incidentEpisode.projectId,
1704
+ customTwilioConfig: projectTwilioConfig,
1705
+ userOnCallLogTimelineId: updatedLog.id,
1706
+ userId: notificationRuleItem.userId,
1707
+ onCallPolicyId: options.onCallPolicyId,
1708
+ onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
1709
+ teamId: options.userBelongsToTeamId,
1710
+ onCallDutyPolicyExecutionLogTimelineId: options.onCallDutyPolicyExecutionLogTimelineId,
1711
+ onCallScheduleId: options.onCallScheduleId,
1712
+ }).catch(async (err) => {
1713
+ await UserOnCallLogTimelineService.updateOneById({
1714
+ id: updatedLog.id,
1715
+ data: {
1716
+ status: UserNotificationStatus.Error,
1717
+ statusMessage: err.message || "Error making call.",
1718
+ },
1719
+ props: {
1720
+ isRoot: true,
1721
+ },
1722
+ });
1723
+ });
1724
+ }
1081
1725
  }
1082
- if (((_39 = notificationRuleItem.userCall) === null || _39 === void 0 ? void 0 : _39.phone) &&
1083
- !((_40 = notificationRuleItem.userCall) === null || _40 === void 0 ? void 0 : _40.isVerified)) {
1726
+ if (((_45 = notificationRuleItem.userCall) === null || _45 === void 0 ? void 0 : _45.phone) &&
1727
+ !((_46 = notificationRuleItem.userCall) === null || _46 === void 0 ? void 0 : _46.isVerified)) {
1084
1728
  // create a log.
1085
1729
  logTimelineItem.status = UserNotificationStatus.Error;
1086
- logTimelineItem.statusMessage = `Call not sent because phone ${(_41 = notificationRuleItem.userCall) === null || _41 === void 0 ? void 0 : _41.phone.toString()} is not verified.`;
1730
+ logTimelineItem.statusMessage = `Call not sent because phone ${(_47 = notificationRuleItem.userCall) === null || _47 === void 0 ? void 0 : _47.phone.toString()} is not verified.`;
1087
1731
  await UserOnCallLogTimelineService.create({
1088
1732
  data: logTimelineItem,
1089
1733
  props: {
@@ -1092,13 +1736,14 @@ export class Service extends DatabaseService {
1092
1736
  });
1093
1737
  }
1094
1738
  // send push notification.
1095
- if (((_42 = notificationRuleItem.userPush) === null || _42 === void 0 ? void 0 : _42.deviceToken) &&
1096
- ((_43 = notificationRuleItem.userPush) === null || _43 === void 0 ? void 0 : _43.isVerified)) {
1739
+ if (((_48 = notificationRuleItem.userPush) === null || _48 === void 0 ? void 0 : _48.deviceToken) &&
1740
+ ((_49 = notificationRuleItem.userPush) === null || _49 === void 0 ? void 0 : _49.isVerified)) {
1097
1741
  // send push notification for alert
1098
1742
  if (options.userNotificationEventType ===
1099
1743
  UserNotificationEventType.AlertCreated &&
1100
1744
  alert) {
1101
1745
  // create a log.
1746
+ deliveryAttempted = true;
1102
1747
  logTimelineItem.status = UserNotificationStatus.Sending;
1103
1748
  logTimelineItem.statusMessage = `Sending push notification to device.`;
1104
1749
  logTimelineItem.userPushId = notificationRuleItem.userPush.id;
@@ -1108,7 +1753,7 @@ export class Service extends DatabaseService {
1108
1753
  isRoot: true,
1109
1754
  },
1110
1755
  });
1111
- const pushMessage = PushNotificationUtil.createAlertCreatedNotification(Object.assign(Object.assign(Object.assign({ alertTitle: alert.title, projectName: ((_44 = alert.project) === null || _44 === void 0 ? void 0 : _44.name) || "OneUptime", alertViewLink: (await AlertService.getAlertLinkInDashboard(alert.projectId, alert.id)).toString() }, (alert.alertNumber !== undefined && {
1756
+ const pushMessage = PushNotificationUtil.createAlertCreatedNotification(Object.assign(Object.assign(Object.assign({ alertTitle: alert.title, projectName: ((_50 = alert.project) === null || _50 === void 0 ? void 0 : _50.name) || "OneUptime", alertViewLink: (await AlertService.getAlertLinkInDashboard(alert.projectId, alert.id)).toString() }, (alert.alertNumber !== undefined && {
1112
1757
  alertNumber: alert.alertNumber,
1113
1758
  })), (alert.alertNumberWithPrefix && {
1114
1759
  alertNumberWithPrefix: alert.alertNumberWithPrefix,
@@ -1151,6 +1796,7 @@ export class Service extends DatabaseService {
1151
1796
  UserNotificationEventType.IncidentCreated &&
1152
1797
  incident) {
1153
1798
  // create a log.
1799
+ deliveryAttempted = true;
1154
1800
  logTimelineItem.status = UserNotificationStatus.Sending;
1155
1801
  logTimelineItem.statusMessage = `Sending push notification to device.`;
1156
1802
  logTimelineItem.userPushId = notificationRuleItem.userPush.id;
@@ -1160,7 +1806,7 @@ export class Service extends DatabaseService {
1160
1806
  isRoot: true,
1161
1807
  },
1162
1808
  });
1163
- const pushMessage = PushNotificationUtil.createIncidentCreatedNotification(Object.assign(Object.assign(Object.assign({ incidentTitle: incident.title, projectName: ((_45 = incident.project) === null || _45 === void 0 ? void 0 : _45.name) || "OneUptime", incidentViewLink: (await IncidentService.getIncidentLinkInDashboard(incident.projectId, incident.id)).toString() }, (incident.incidentNumber !== undefined && {
1809
+ const pushMessage = PushNotificationUtil.createIncidentCreatedNotification(Object.assign(Object.assign(Object.assign({ incidentTitle: incident.title, projectName: ((_51 = incident.project) === null || _51 === void 0 ? void 0 : _51.name) || "OneUptime", incidentViewLink: (await IncidentService.getIncidentLinkInDashboard(incident.projectId, incident.id)).toString() }, (incident.incidentNumber !== undefined && {
1164
1810
  incidentNumber: incident.incidentNumber,
1165
1811
  })), (incident.incidentNumberWithPrefix && {
1166
1812
  incidentNumberWithPrefix: incident.incidentNumberWithPrefix,
@@ -1202,6 +1848,7 @@ export class Service extends DatabaseService {
1202
1848
  if (options.userNotificationEventType ===
1203
1849
  UserNotificationEventType.AlertEpisodeCreated &&
1204
1850
  alertEpisode) {
1851
+ deliveryAttempted = true;
1205
1852
  logTimelineItem.status = UserNotificationStatus.Sending;
1206
1853
  logTimelineItem.statusMessage = `Sending push notification to device.`;
1207
1854
  logTimelineItem.userPushId = notificationRuleItem.userPush.id;
@@ -1211,7 +1858,7 @@ export class Service extends DatabaseService {
1211
1858
  isRoot: true,
1212
1859
  },
1213
1860
  });
1214
- const pushMessage = PushNotificationUtil.createAlertEpisodeCreatedNotification(Object.assign(Object.assign(Object.assign({ alertEpisodeTitle: alertEpisode.title, projectName: ((_46 = alertEpisode.project) === null || _46 === void 0 ? void 0 : _46.name) || "OneUptime", alertEpisodeViewLink: (await AlertEpisodeService.getEpisodeLinkInDashboard(alertEpisode.projectId, alertEpisode.id)).toString() }, (alertEpisode.episodeNumber !== undefined && {
1861
+ const pushMessage = PushNotificationUtil.createAlertEpisodeCreatedNotification(Object.assign(Object.assign(Object.assign({ alertEpisodeTitle: alertEpisode.title, projectName: ((_52 = alertEpisode.project) === null || _52 === void 0 ? void 0 : _52.name) || "OneUptime", alertEpisodeViewLink: (await AlertEpisodeService.getEpisodeLinkInDashboard(alertEpisode.projectId, alertEpisode.id)).toString() }, (alertEpisode.episodeNumber !== undefined && {
1215
1862
  episodeNumber: alertEpisode.episodeNumber,
1216
1863
  })), (alertEpisode.episodeNumberWithPrefix && {
1217
1864
  episodeNumberWithPrefix: alertEpisode.episodeNumberWithPrefix,
@@ -1252,6 +1899,7 @@ export class Service extends DatabaseService {
1252
1899
  if (options.userNotificationEventType ===
1253
1900
  UserNotificationEventType.IncidentEpisodeCreated &&
1254
1901
  incidentEpisode) {
1902
+ deliveryAttempted = true;
1255
1903
  logTimelineItem.status = UserNotificationStatus.Sending;
1256
1904
  logTimelineItem.statusMessage = `Sending push notification to device.`;
1257
1905
  logTimelineItem.userPushId = notificationRuleItem.userPush.id;
@@ -1261,7 +1909,7 @@ export class Service extends DatabaseService {
1261
1909
  isRoot: true,
1262
1910
  },
1263
1911
  });
1264
- const pushMessage = PushNotificationUtil.createIncidentEpisodeCreatedNotification(Object.assign(Object.assign(Object.assign({ incidentEpisodeTitle: incidentEpisode.title, projectName: ((_47 = incidentEpisode.project) === null || _47 === void 0 ? void 0 : _47.name) || "OneUptime", incidentEpisodeViewLink: (await IncidentEpisodeService.getEpisodeLinkInDashboard(incidentEpisode.projectId, incidentEpisode.id)).toString() }, (incidentEpisode.episodeNumber !== undefined && {
1912
+ const pushMessage = PushNotificationUtil.createIncidentEpisodeCreatedNotification(Object.assign(Object.assign(Object.assign({ incidentEpisodeTitle: incidentEpisode.title, projectName: ((_53 = incidentEpisode.project) === null || _53 === void 0 ? void 0 : _53.name) || "OneUptime", incidentEpisodeViewLink: (await IncidentEpisodeService.getEpisodeLinkInDashboard(incidentEpisode.projectId, incidentEpisode.id)).toString() }, (incidentEpisode.episodeNumber !== undefined && {
1265
1913
  episodeNumber: incidentEpisode.episodeNumber,
1266
1914
  })), (incidentEpisode.episodeNumberWithPrefix && {
1267
1915
  episodeNumberWithPrefix: incidentEpisode.episodeNumberWithPrefix,
@@ -1298,8 +1946,8 @@ export class Service extends DatabaseService {
1298
1946
  });
1299
1947
  }
1300
1948
  }
1301
- if (((_48 = notificationRuleItem.userPush) === null || _48 === void 0 ? void 0 : _48.deviceToken) &&
1302
- !((_49 = notificationRuleItem.userPush) === null || _49 === void 0 ? void 0 : _49.isVerified)) {
1949
+ if (((_54 = notificationRuleItem.userPush) === null || _54 === void 0 ? void 0 : _54.deviceToken) &&
1950
+ !((_55 = notificationRuleItem.userPush) === null || _55 === void 0 ? void 0 : _55.isVerified)) {
1303
1951
  // create a log.
1304
1952
  logTimelineItem.status = UserNotificationStatus.Error;
1305
1953
  logTimelineItem.statusMessage = `Push notification not sent because device is not verified.`;
@@ -1310,29 +1958,420 @@ export class Service extends DatabaseService {
1310
1958
  },
1311
1959
  });
1312
1960
  }
1313
- }
1314
- async generateCallTemplateForAlertCreated(to, alert, userOnCallLogTimelineId) {
1315
- const host = await DatabaseConfig.getHost();
1316
- const httpProtocol = await DatabaseConfig.getHttpProtocol();
1317
- const alertIdentifier = alert.alertNumber !== undefined
1318
- ? `Alert number ${alert.alertNumber}, ${alert.title || "Alert"}`
1319
- : alert.title || "Alert";
1320
- const callRequest = {
1321
- to: to,
1322
- data: [
1323
- {
1324
- sayMessage: "This is a call from One Uptime",
1325
- },
1326
- {
1327
- sayMessage: "A new alert has been created",
1328
- },
1329
- {
1330
- sayMessage: alertIdentifier,
1961
+ /*
1962
+ * The fell-through guard.
1963
+ *
1964
+ * Gap F was a whole class of lost pages: a contactable channel, an event
1965
+ * type that no block in that channel branched on, and therefore neither a
1966
+ * send nor an error row — the responder was simply never told, and nothing
1967
+ * anywhere recorded that. Rather than trust that every future event type
1968
+ * gets wired into all seven blocks, make the omission loud.
1969
+ *
1970
+ * The row is built fresh instead of reusing logTimelineItem: that instance
1971
+ * picks up an _id as soon as any block has created a row with it, and a
1972
+ * second create() with it would UPDATE that row rather than insert this one.
1973
+ */
1974
+ if (contactableChannels.length > 0 && !deliveryAttempted) {
1975
+ const statusMessage = `No notification template for ${options.userNotificationEventType} on ${contactableChannels.join(", ")}.`;
1976
+ const fellThroughRow = this.buildLogTimelineItem(notificationRuleItem, options);
1977
+ fellThroughRow.status = UserNotificationStatus.Error;
1978
+ fellThroughRow.statusMessage = statusMessage;
1979
+ await UserOnCallLogTimelineService.create({
1980
+ data: fellThroughRow,
1981
+ props: {
1982
+ isRoot: true,
1331
1983
  },
1332
- {
1333
- introMessage: "To acknowledge this alert press 1",
1334
- numDigits: 1,
1335
- timeoutInSeconds: 10,
1984
+ });
1985
+ logger.error(`${statusMessage} User on-call log: ${options.userNotificationLogId.toString()}`);
1986
+ }
1987
+ return deliveryAttempted;
1988
+ }
1989
+ /*
1990
+ * The channels this rule could actually reach the user on, by display name.
1991
+ *
1992
+ * These are the same gates each channel block opens with, so an empty list
1993
+ * means "this rule can contact nobody" — a rule whose method was
1994
+ * cascade-deleted, say — and a non-empty one means a page was expected to go
1995
+ * out. Webhooks have no verification concept at all (UserWebhook has no
1996
+ * isVerified column), so presence of a URL is the whole gate there.
1997
+ */
1998
+ getContactableChannelNames(notificationRuleItem) {
1999
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
2000
+ const channels = [];
2001
+ if (((_a = notificationRuleItem.userEmail) === null || _a === void 0 ? void 0 : _a.email) &&
2002
+ ((_b = notificationRuleItem.userEmail) === null || _b === void 0 ? void 0 : _b.isVerified)) {
2003
+ channels.push("Email");
2004
+ }
2005
+ if (((_c = notificationRuleItem.userSms) === null || _c === void 0 ? void 0 : _c.phone) &&
2006
+ ((_d = notificationRuleItem.userSms) === null || _d === void 0 ? void 0 : _d.isVerified)) {
2007
+ channels.push("SMS");
2008
+ }
2009
+ if (((_e = notificationRuleItem.userWhatsApp) === null || _e === void 0 ? void 0 : _e.phone) &&
2010
+ ((_f = notificationRuleItem.userWhatsApp) === null || _f === void 0 ? void 0 : _f.isVerified)) {
2011
+ channels.push("WhatsApp");
2012
+ }
2013
+ if (((_g = notificationRuleItem.userTelegram) === null || _g === void 0 ? void 0 : _g.telegramChatId) &&
2014
+ ((_h = notificationRuleItem.userTelegram) === null || _h === void 0 ? void 0 : _h.isVerified)) {
2015
+ channels.push("Telegram");
2016
+ }
2017
+ if ((_j = notificationRuleItem.userWebhook) === null || _j === void 0 ? void 0 : _j.webhookUrl) {
2018
+ channels.push("Webhook");
2019
+ }
2020
+ if (((_k = notificationRuleItem.userCall) === null || _k === void 0 ? void 0 : _k.phone) &&
2021
+ ((_l = notificationRuleItem.userCall) === null || _l === void 0 ? void 0 : _l.isVerified)) {
2022
+ channels.push("Call");
2023
+ }
2024
+ if (((_m = notificationRuleItem.userPush) === null || _m === void 0 ? void 0 : _m.deviceToken) &&
2025
+ ((_o = notificationRuleItem.userPush) === null || _o === void 0 ? void 0 : _o.isVerified)) {
2026
+ channels.push("Push");
2027
+ }
2028
+ return channels;
2029
+ }
2030
+ /*
2031
+ * Page a responder who has NO notification rule matching what just fired.
2032
+ *
2033
+ * Zero matching rules is indistinguishable from "never configured" unless the
2034
+ * user said otherwise, so the caller (UserOnCallLogService.onCreateSuccess)
2035
+ * checks for an explicit opt-out row first and only reaches here when the
2036
+ * silence looks accidental. Reaching a human on whatever they have verified
2037
+ * beats honouring a configuration they never made.
2038
+ *
2039
+ * Nothing here observes delivery success: every send below is fire-and-forget
2040
+ * (see deliverNotificationForRule), so `notified` means "a page was handed to
2041
+ * the sender", not "a phone rang".
2042
+ *
2043
+ * The three ways this can end are spelled out in FallbackNotificationOutcome,
2044
+ * and the caller must branch on them rather than on `notified` alone: only
2045
+ * NoUsableNotificationMethod describes a responder who cannot be reached, and
2046
+ * only that one is safe to record as a terminal status.
2047
+ */
2048
+ async executeFallbackNotification(options) {
2049
+ /*
2050
+ * Claim the log under the reserved fallback key before doing anything, so
2051
+ * two overlapping cron ticks cannot both fall back and double-page the same
2052
+ * responder for one escalation.
2053
+ */
2054
+ const claimed = await UserOnCallLogService.claimNotificationExecution({
2055
+ userOnCallLogId: options.userOnCallLogId,
2056
+ claimKey: FALLBACK_NOTIFICATION_CLAIM_KEY,
2057
+ });
2058
+ if (!claimed) {
2059
+ /*
2060
+ * A concurrent run already fell back for this log; it owns everything
2061
+ * that happens next, including the log's final status. Reported as the
2062
+ * transient outcome rather than as "no usable method", because the
2063
+ * caller's response to the latter is a terminal Error — which would
2064
+ * stamp "this responder is unreachable" over a page that is in flight.
2065
+ */
2066
+ return {
2067
+ outcome: FallbackNotificationOutcome.DeliveryFailed,
2068
+ notified: false,
2069
+ channelsUsed: [],
2070
+ };
2071
+ }
2072
+ const fallbackRules = await this.chooseFallbackChannels(options);
2073
+ if (fallbackRules.length === 0) {
2074
+ logger.warn(`On-call fallback found no usable notification method for user ${options.userId.toString()} in project ${options.projectId.toString()} (${options.severityName} ${options.ruleType}). The page cannot be delivered.`);
2075
+ return {
2076
+ outcome: FallbackNotificationOutcome.NoUsableNotificationMethod,
2077
+ notified: false,
2078
+ channelsUsed: [],
2079
+ };
2080
+ }
2081
+ const channelsUsed = [];
2082
+ let anAttemptFailed = false;
2083
+ /*
2084
+ * One delivery call per channel, never a loop inside one call: the timeline
2085
+ * row is a single mutable object inside deliverNotificationForRule, and a
2086
+ * second create() with it would UPDATE the row the first channel wrote
2087
+ * instead of inserting a second one — the second page would vanish from the
2088
+ * timeline and, worse, overwrite the first one's status.
2089
+ */
2090
+ for (const fallbackRule of fallbackRules) {
2091
+ try {
2092
+ const dispatched = await this.deliverNotificationForRule(fallbackRule.rule, options);
2093
+ /*
2094
+ * Only a genuine dispatch earns a place in channelsUsed. The channel
2095
+ * names in here are read back to the operator as "notified via fallback
2096
+ * (Push, Email)", so a name added merely because the call resolved is a
2097
+ * lie in the one place somebody looks to find out whether the responder
2098
+ * was reached — and deliverNotificationForRule resolves perfectly
2099
+ * happily when no block claimed the event type.
2100
+ */
2101
+ if (dispatched) {
2102
+ channelsUsed.push(fallbackRule.channelName);
2103
+ }
2104
+ else {
2105
+ anAttemptFailed = true;
2106
+ logger.error(`On-call fallback dispatched nothing on ${fallbackRule.channelName} for user ${options.userId.toString()}: no notification template matched ${options.userNotificationEventType}.`);
2107
+ }
2108
+ }
2109
+ catch (err) {
2110
+ anAttemptFailed = true;
2111
+ logger.error(`On-call fallback failed to deliver on ${fallbackRule.channelName} for user ${options.userId.toString()}.`);
2112
+ logger.error(err);
2113
+ }
2114
+ }
2115
+ if (channelsUsed.length > 0) {
2116
+ return {
2117
+ outcome: FallbackNotificationOutcome.Delivered,
2118
+ notified: true,
2119
+ channelsUsed: channelsUsed,
2120
+ };
2121
+ }
2122
+ /*
2123
+ * There were channels to try and not one of them carried a page. That is
2124
+ * emphatically not the "responder has no notification method" case —
2125
+ * chooseFallbackChannels returns only verified, project-enabled methods, so
2126
+ * the responder is reachable and today simply failed to be reached.
2127
+ *
2128
+ * anAttemptFailed is necessarily true on this line, since every path
2129
+ * through the loop that does not push a channel sets it. It is read rather
2130
+ * than assumed so that a future channel that can finish without either
2131
+ * dispatching or failing degrades into the transient outcome instead of
2132
+ * silently telling the operator the responder has nothing configured.
2133
+ */
2134
+ return {
2135
+ outcome: anAttemptFailed
2136
+ ? FallbackNotificationOutcome.DeliveryFailed
2137
+ : FallbackNotificationOutcome.NoUsableNotificationMethod,
2138
+ notified: false,
2139
+ channelsUsed: [],
2140
+ };
2141
+ }
2142
+ /*
2143
+ * Pick what to page the user on, and build an unsaved rule for each choice.
2144
+ *
2145
+ * Zero-cost channels win: push and email reach the most people for no money
2146
+ * and no billing surprise, and there is no reason to pick between them, so a
2147
+ * user who has both gets both. Only a user with neither is worth spending on,
2148
+ * and then just once, in escalating-intrusiveness order.
2149
+ *
2150
+ * Paid channels are additionally gated on the project's own enable flags.
2151
+ * SmsService and CallService enforce those at send time, but WhatsApp and
2152
+ * Telegram only check them when a method is created — so a project that
2153
+ * switched WhatsApp off would still be billed by a fallback that did not look.
2154
+ */
2155
+ async chooseFallbackChannels(options) {
2156
+ const chosen = [];
2157
+ const userPush = await UserPushService.findOneBy({
2158
+ query: {
2159
+ projectId: options.projectId,
2160
+ userId: options.userId,
2161
+ isVerified: true,
2162
+ },
2163
+ select: {
2164
+ _id: true,
2165
+ deviceToken: true,
2166
+ deviceType: true,
2167
+ isVerified: true,
2168
+ },
2169
+ props: {
2170
+ isRoot: true,
2171
+ },
2172
+ });
2173
+ if (userPush) {
2174
+ const rule = this.buildUnsavedFallbackRule(options);
2175
+ rule.userPush = userPush;
2176
+ rule.userPushId = userPush.id;
2177
+ chosen.push({ channelName: "Push", rule: rule });
2178
+ }
2179
+ const userEmail = await UserEmailService.findOneBy({
2180
+ query: {
2181
+ projectId: options.projectId,
2182
+ userId: options.userId,
2183
+ isVerified: true,
2184
+ },
2185
+ select: {
2186
+ _id: true,
2187
+ email: true,
2188
+ isVerified: true,
2189
+ },
2190
+ props: {
2191
+ isRoot: true,
2192
+ },
2193
+ });
2194
+ if (userEmail) {
2195
+ const rule = this.buildUnsavedFallbackRule(options);
2196
+ rule.userEmail = userEmail;
2197
+ rule.userEmailId = userEmail.id;
2198
+ chosen.push({ channelName: "Email", rule: rule });
2199
+ }
2200
+ if (chosen.length > 0) {
2201
+ return chosen;
2202
+ }
2203
+ const project = await ProjectService.findOneById({
2204
+ id: options.projectId,
2205
+ select: {
2206
+ enableSmsNotifications: true,
2207
+ enableCallNotifications: true,
2208
+ enableWhatsAppNotifications: true,
2209
+ enableTelegramNotifications: true,
2210
+ },
2211
+ props: {
2212
+ isRoot: true,
2213
+ },
2214
+ });
2215
+ if (project === null || project === void 0 ? void 0 : project.enableSmsNotifications) {
2216
+ const userSms = await UserSmsService.findOneBy({
2217
+ query: {
2218
+ projectId: options.projectId,
2219
+ userId: options.userId,
2220
+ isVerified: true,
2221
+ },
2222
+ select: {
2223
+ _id: true,
2224
+ phone: true,
2225
+ isVerified: true,
2226
+ },
2227
+ props: {
2228
+ isRoot: true,
2229
+ },
2230
+ });
2231
+ if (userSms) {
2232
+ const rule = this.buildUnsavedFallbackRule(options);
2233
+ rule.userSms = userSms;
2234
+ rule.userSmsId = userSms.id;
2235
+ return [{ channelName: "SMS", rule: rule }];
2236
+ }
2237
+ }
2238
+ if (project === null || project === void 0 ? void 0 : project.enableCallNotifications) {
2239
+ const userCall = await UserCallService.findOneBy({
2240
+ query: {
2241
+ projectId: options.projectId,
2242
+ userId: options.userId,
2243
+ isVerified: true,
2244
+ },
2245
+ select: {
2246
+ _id: true,
2247
+ phone: true,
2248
+ isVerified: true,
2249
+ },
2250
+ props: {
2251
+ isRoot: true,
2252
+ },
2253
+ });
2254
+ if (userCall) {
2255
+ const rule = this.buildUnsavedFallbackRule(options);
2256
+ rule.userCall = userCall;
2257
+ rule.userCallId = userCall.id;
2258
+ return [{ channelName: "Call", rule: rule }];
2259
+ }
2260
+ }
2261
+ if (project === null || project === void 0 ? void 0 : project.enableWhatsAppNotifications) {
2262
+ const userWhatsApp = await UserWhatsAppService.findOneBy({
2263
+ query: {
2264
+ projectId: options.projectId,
2265
+ userId: options.userId,
2266
+ isVerified: true,
2267
+ },
2268
+ select: {
2269
+ _id: true,
2270
+ phone: true,
2271
+ isVerified: true,
2272
+ },
2273
+ props: {
2274
+ isRoot: true,
2275
+ },
2276
+ });
2277
+ if (userWhatsApp) {
2278
+ const rule = this.buildUnsavedFallbackRule(options);
2279
+ rule.userWhatsApp = userWhatsApp;
2280
+ rule.userWhatsAppId = userWhatsApp.id;
2281
+ return [{ channelName: "WhatsApp", rule: rule }];
2282
+ }
2283
+ }
2284
+ if (project === null || project === void 0 ? void 0 : project.enableTelegramNotifications) {
2285
+ const userTelegram = await UserTelegramService.findOneBy({
2286
+ query: {
2287
+ projectId: options.projectId,
2288
+ userId: options.userId,
2289
+ isVerified: true,
2290
+ },
2291
+ select: {
2292
+ _id: true,
2293
+ telegramChatId: true,
2294
+ telegramUserHandle: true,
2295
+ isVerified: true,
2296
+ },
2297
+ props: {
2298
+ isRoot: true,
2299
+ },
2300
+ });
2301
+ if (userTelegram) {
2302
+ const rule = this.buildUnsavedFallbackRule(options);
2303
+ rule.userTelegram = userTelegram;
2304
+ rule.userTelegramId = userTelegram.id;
2305
+ return [{ channelName: "Telegram", rule: rule }];
2306
+ }
2307
+ }
2308
+ /*
2309
+ * A webhook costs the project nothing and has no verification concept at
2310
+ * all (UserWebhook has no isVerified column), so its presence is the whole
2311
+ * test, and there is no project flag to consult.
2312
+ */
2313
+ const userWebhook = await UserWebhookService.findOneBy({
2314
+ query: {
2315
+ projectId: options.projectId,
2316
+ userId: options.userId,
2317
+ },
2318
+ select: {
2319
+ _id: true,
2320
+ webhookUrl: true,
2321
+ name: true,
2322
+ secret: true,
2323
+ },
2324
+ props: {
2325
+ isRoot: true,
2326
+ },
2327
+ });
2328
+ if (userWebhook) {
2329
+ const rule = this.buildUnsavedFallbackRule(options);
2330
+ rule.userWebhook = userWebhook;
2331
+ rule.userWebhookId = userWebhook.id;
2332
+ return [{ channelName: "Webhook", rule: rule }];
2333
+ }
2334
+ return chosen;
2335
+ }
2336
+ /*
2337
+ * A UserNotificationRule that exists only for the length of one delivery.
2338
+ *
2339
+ * It is never saved: the user did not ask for this rule, and persisting it
2340
+ * would silently rewrite their configuration behind their back. The method
2341
+ * relation is populated as a loaded entity rather than just its FK because
2342
+ * deliverNotificationForRule reads the relation (userEmail.email,
2343
+ * userEmail.isVerified) and never dereferences the id.
2344
+ */
2345
+ buildUnsavedFallbackRule(options) {
2346
+ const rule = new Model();
2347
+ rule.projectId = options.projectId;
2348
+ rule.userId = options.userId;
2349
+ rule.ruleType = options.ruleType;
2350
+ rule.notifyAfterMinutes = 0;
2351
+ return rule;
2352
+ }
2353
+ async generateCallTemplateForAlertCreated(to, alert, userOnCallLogTimelineId) {
2354
+ const host = await DatabaseConfig.getHost();
2355
+ const httpProtocol = await DatabaseConfig.getHttpProtocol();
2356
+ const alertIdentifier = alert.alertNumber !== undefined
2357
+ ? `Alert number ${alert.alertNumber}, ${alert.title || "Alert"}`
2358
+ : alert.title || "Alert";
2359
+ const callRequest = {
2360
+ to: to,
2361
+ data: [
2362
+ {
2363
+ sayMessage: "This is a call from One Uptime",
2364
+ },
2365
+ {
2366
+ sayMessage: "A new alert has been created",
2367
+ },
2368
+ {
2369
+ sayMessage: alertIdentifier,
2370
+ },
2371
+ {
2372
+ introMessage: "To acknowledge this alert press 1",
2373
+ numDigits: 1,
2374
+ timeoutInSeconds: 10,
1336
2375
  noInputMessage: "You have not entered any input. Good bye",
1337
2376
  onInputCallRequest: {
1338
2377
  "1": {
@@ -1430,6 +2469,47 @@ export class Service extends DatabaseService {
1430
2469
  };
1431
2470
  return callRequest;
1432
2471
  }
2472
+ async generateCallTemplateForIncidentEpisodeCreated(to, incidentEpisode, userOnCallLogTimelineId) {
2473
+ const host = await DatabaseConfig.getHost();
2474
+ const httpProtocol = await DatabaseConfig.getHttpProtocol();
2475
+ const episodeIdentifier = incidentEpisode.episodeNumberWithPrefix
2476
+ ? `Incident episode ${incidentEpisode.episodeNumberWithPrefix}, ${incidentEpisode.title || "Incident Episode"}`
2477
+ : incidentEpisode.episodeNumber !== undefined
2478
+ ? `Incident episode number ${incidentEpisode.episodeNumber}, ${incidentEpisode.title || "Incident Episode"}`
2479
+ : incidentEpisode.title || "Incident Episode";
2480
+ const callRequest = {
2481
+ to: to,
2482
+ data: [
2483
+ {
2484
+ sayMessage: "This is a call from One Uptime",
2485
+ },
2486
+ {
2487
+ sayMessage: "A new incident episode has been created",
2488
+ },
2489
+ {
2490
+ sayMessage: episodeIdentifier,
2491
+ },
2492
+ {
2493
+ introMessage: "To acknowledge this incident episode press 1",
2494
+ numDigits: 1,
2495
+ timeoutInSeconds: 10,
2496
+ noInputMessage: "You have not entered any input. Good bye",
2497
+ onInputCallRequest: {
2498
+ "1": {
2499
+ sayMessage: "You have acknowledged this incident episode. Good bye",
2500
+ },
2501
+ default: {
2502
+ sayMessage: "Invalid input. Good bye",
2503
+ },
2504
+ },
2505
+ responseUrl: new URL(httpProtocol, host, new Route(AppApiRoute.toString())
2506
+ .addRoute(new UserOnCallLogTimeline().crudApiPath)
2507
+ .addRoute("/call/gather-input/" + userOnCallLogTimelineId.toString())),
2508
+ },
2509
+ ],
2510
+ };
2511
+ return callRequest;
2512
+ }
1433
2513
  async generateSmsTemplateForAlertCreated(to, alert, userOnCallLogTimelineId) {
1434
2514
  const host = await DatabaseConfig.getHost();
1435
2515
  const httpProtocol = await DatabaseConfig.getHttpProtocol();
@@ -1480,6 +2560,19 @@ export class Service extends DatabaseService {
1480
2560
  };
1481
2561
  return sms;
1482
2562
  }
2563
+ async generateSmsTemplateForIncidentEpisodeCreated(to, incidentEpisode, userOnCallLogTimelineId) {
2564
+ const url = await this.buildOnCallAcknowledgeShortUrl(userOnCallLogTimelineId);
2565
+ const episodeIdentifier = incidentEpisode.episodeNumberWithPrefix
2566
+ ? `${incidentEpisode.episodeNumberWithPrefix} (${incidentEpisode.title || "Incident Episode"})`
2567
+ : incidentEpisode.episodeNumber !== undefined
2568
+ ? `#${incidentEpisode.episodeNumber} (${incidentEpisode.title || "Incident Episode"})`
2569
+ : incidentEpisode.title || "Incident Episode";
2570
+ const sms = {
2571
+ to,
2572
+ message: `This is a message from OneUptime. A new incident episode has been created: ${episodeIdentifier}. To acknowledge this incident episode, please click on the following link ${url.toString()}`,
2573
+ };
2574
+ return sms;
2575
+ }
1483
2576
  async buildOnCallAcknowledgeShortUrl(userOnCallLogTimelineId) {
1484
2577
  const host = await DatabaseConfig.getHost();
1485
2578
  const httpProtocol = await DatabaseConfig.getHttpProtocol();
@@ -1557,6 +2650,27 @@ export class Service extends DatabaseService {
1557
2650
  lines.push("", `✅ <a href="${this.escapeTelegramHtml(ackUrl.toString())}">Tap to acknowledge</a>`);
1558
2651
  return lines.join("\n");
1559
2652
  }
2653
+ async generateTelegramBodyForIncidentEpisodeCreated(incidentEpisode, userOnCallLogTimelineId) {
2654
+ const ackUrl = await this.buildOnCallAcknowledgeShortUrl(userOnCallLogTimelineId);
2655
+ const episodeIdentifier = incidentEpisode.episodeNumberWithPrefix
2656
+ ? `${incidentEpisode.episodeNumberWithPrefix} — ${incidentEpisode.title || "Incident Episode"}`
2657
+ : incidentEpisode.episodeNumber !== undefined
2658
+ ? `#${incidentEpisode.episodeNumber} — ${incidentEpisode.title || "Incident Episode"}`
2659
+ : incidentEpisode.title || "Incident Episode";
2660
+ const lines = [
2661
+ "🔥 <b>New incident episode assigned to you</b>",
2662
+ "",
2663
+ `📋 <b>${this.escapeTelegramHtml(episodeIdentifier)}</b>`,
2664
+ "",
2665
+ "👤 You're getting this because you're on call.",
2666
+ ];
2667
+ if (incidentEpisode.projectId && incidentEpisode.id) {
2668
+ const dashboardUrl = await IncidentEpisodeService.getEpisodeLinkInDashboard(incidentEpisode.projectId, incidentEpisode.id);
2669
+ lines.push("", `🔎 <a href="${this.escapeTelegramHtml(dashboardUrl.toString())}">View incident episode in OneUptime</a>`);
2670
+ }
2671
+ lines.push("", `✅ <a href="${this.escapeTelegramHtml(ackUrl.toString())}">Tap to acknowledge</a>`);
2672
+ return lines.join("\n");
2673
+ }
1560
2674
  async generateWhatsAppTemplateForAlertCreated(to, alert, userOnCallLogTimelineId) {
1561
2675
  var _a;
1562
2676
  const host = await DatabaseConfig.getHost();
@@ -1646,6 +2760,32 @@ export class Service extends DatabaseService {
1646
2760
  templateLanguageCode: WhatsAppTemplateLanguage[templateKey],
1647
2761
  };
1648
2762
  }
2763
+ async generateWhatsAppTemplateForIncidentEpisodeCreated(to, incidentEpisode, userOnCallLogTimelineId) {
2764
+ var _a;
2765
+ const acknowledgeUrl = await this.buildOnCallAcknowledgeShortUrl(userOnCallLogTimelineId);
2766
+ const episodeLinkOnDashboard = incidentEpisode.projectId && incidentEpisode.id
2767
+ ? (await IncidentEpisodeService.getEpisodeLinkInDashboard(incidentEpisode.projectId, incidentEpisode.id)).toString()
2768
+ : acknowledgeUrl.toString();
2769
+ const templateKey = WhatsAppTemplateIds.IncidentEpisodeCreated;
2770
+ const templateVariables = {
2771
+ project_name: ((_a = incidentEpisode.project) === null || _a === void 0 ? void 0 : _a.name) || "OneUptime",
2772
+ episode_title: incidentEpisode.title || "",
2773
+ acknowledge_url: acknowledgeUrl.toString(),
2774
+ episode_number: incidentEpisode.episodeNumberWithPrefix ||
2775
+ (incidentEpisode.episodeNumber !== undefined
2776
+ ? incidentEpisode.episodeNumber.toString()
2777
+ : ""),
2778
+ episode_link: episodeLinkOnDashboard,
2779
+ };
2780
+ const body = renderWhatsAppTemplate(templateKey, templateVariables);
2781
+ return {
2782
+ to,
2783
+ body,
2784
+ templateKey,
2785
+ templateVariables,
2786
+ templateLanguageCode: WhatsAppTemplateLanguage[templateKey],
2787
+ };
2788
+ }
1649
2789
  async generateEmailTemplateForAlertCreated(to, alert, userOnCallLogTimelineId) {
1650
2790
  const host = await DatabaseConfig.getHost();
1651
2791
  const httpProtocol = await DatabaseConfig.getHttpProtocol();
@@ -1822,30 +2962,169 @@ export class Service extends DatabaseService {
1822
2962
  };
1823
2963
  return emailMessage;
1824
2964
  }
1825
- async startUserNotificationRulesExecution(userId, options) {
1826
- // add user notification log.
1827
- const userOnCallLog = new UserOnCallLog();
1828
- userOnCallLog.userId = userId;
1829
- userOnCallLog.projectId = options.projectId;
1830
- if (options.triggeredByIncidentId) {
1831
- userOnCallLog.triggeredByIncidentId = options.triggeredByIncidentId;
1832
- }
1833
- if (options.triggeredByAlertId) {
1834
- userOnCallLog.triggeredByAlertId = options.triggeredByAlertId;
1835
- }
1836
- if (options.triggeredByAlertEpisodeId) {
1837
- userOnCallLog.triggeredByAlertEpisodeId =
1838
- options.triggeredByAlertEpisodeId;
1839
- }
1840
- if (options.triggeredByIncidentEpisodeId) {
1841
- userOnCallLog.triggeredByIncidentEpisodeId =
1842
- options.triggeredByIncidentEpisodeId;
1843
- }
1844
- userOnCallLog.userNotificationEventType = options.userNotificationEventType;
1845
- if (options.onCallPolicyExecutionLogId) {
1846
- userOnCallLog.onCallDutyPolicyExecutionLogId =
1847
- options.onCallPolicyExecutionLogId;
1848
- }
2965
+ async generateEmailTemplateForIncidentEpisodeCreated(to, incidentEpisode, userOnCallLogTimelineId) {
2966
+ const host = await DatabaseConfig.getHost();
2967
+ const httpProtocol = await DatabaseConfig.getHttpProtocol();
2968
+ // Fetch incidents that are members of this episode
2969
+ const episodeMembers = await IncidentEpisodeMemberService.findBy({
2970
+ query: {
2971
+ incidentEpisodeId: incidentEpisode.id,
2972
+ },
2973
+ select: {
2974
+ incidentId: true,
2975
+ },
2976
+ props: {
2977
+ isRoot: true,
2978
+ },
2979
+ limit: LIMIT_PER_PROJECT,
2980
+ skip: 0,
2981
+ });
2982
+ // Get the incident IDs
2983
+ const incidentIds = episodeMembers
2984
+ .map((member) => {
2985
+ return member.incidentId;
2986
+ })
2987
+ .filter((id) => {
2988
+ return id !== undefined;
2989
+ });
2990
+ // Fetch full incident data with monitors
2991
+ const incidents = incidentIds.length > 0
2992
+ ? await IncidentService.findBy({
2993
+ query: {
2994
+ _id: QueryHelper.any(incidentIds),
2995
+ },
2996
+ select: {
2997
+ _id: true,
2998
+ title: true,
2999
+ incidentNumber: true,
3000
+ incidentNumberWithPrefix: true,
3001
+ monitors: {
3002
+ _id: true,
3003
+ name: true,
3004
+ },
3005
+ },
3006
+ props: {
3007
+ isRoot: true,
3008
+ },
3009
+ limit: LIMIT_PER_PROJECT,
3010
+ skip: 0,
3011
+ })
3012
+ : [];
3013
+ /*
3014
+ * Unique monitors across every incident in the episode. An incident carries
3015
+ * a list of monitors (unlike an alert, which has exactly one), so this
3016
+ * flattens rather than reading a single relation.
3017
+ */
3018
+ const monitorNames = new Set();
3019
+ for (const incident of incidents) {
3020
+ for (const monitor of incident.monitors || []) {
3021
+ if (monitor.name) {
3022
+ monitorNames.add(monitor.name);
3023
+ }
3024
+ }
3025
+ }
3026
+ const resourcesAffected = monitorNames.size > 0
3027
+ ? Array.from(monitorNames).join(", ")
3028
+ : "No resources identified";
3029
+ // Build incidents list HTML with proper email styling
3030
+ let incidentsListHtml = "";
3031
+ if (incidents.length > 0) {
3032
+ const incidentRows = [];
3033
+ for (const incident of incidents) {
3034
+ const incidentTitle = incident.title || "Untitled Incident";
3035
+ const incidentNumber = incident.incidentNumberWithPrefix ||
3036
+ (incident.incidentNumber ? `#${incident.incidentNumber}` : "");
3037
+ const incidentLink = (await IncidentService.getIncidentLinkInDashboard(incidentEpisode.projectId, incident.id)).toString();
3038
+ const monitorName = (incident.monitors || [])
3039
+ .map((monitor) => {
3040
+ return monitor.name || "";
3041
+ })
3042
+ .filter((name) => {
3043
+ return name.length > 0;
3044
+ })
3045
+ .join(", ") || "";
3046
+ incidentRows.push(`
3047
+ <tr>
3048
+ <td style="padding: 12px 16px; border-bottom: 1px solid #e2e8f0;">
3049
+ <table cellpadding="0" cellspacing="0" width="100%">
3050
+ <tr>
3051
+ <td style="vertical-align: middle;">
3052
+ <span style="display: inline-block; background-color: #fee2e2; color: #991b1b; font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 4px; margin-right: 8px;">${incidentNumber}</span>
3053
+ <a href="${incidentLink}" style="color: #2563eb; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; font-weight: 500; text-decoration: none;">${incidentTitle}</a>
3054
+ ${monitorName ? `<span style="display: block; color: #64748b; font-size: 12px; margin-top: 4px;">Monitor: ${monitorName}</span>` : ""}
3055
+ </td>
3056
+ <td style="text-align: right; vertical-align: middle;">
3057
+ <a href="${incidentLink}" style="color: #2563eb; font-size: 12px; text-decoration: none;">View →</a>
3058
+ </td>
3059
+ </tr>
3060
+ </table>
3061
+ </td>
3062
+ </tr>
3063
+ `);
3064
+ }
3065
+ if (incidentRows.length > 0) {
3066
+ incidentsListHtml = `
3067
+ <table cellpadding="0" cellspacing="0" width="100%" style="background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); border-radius: 8px; border: 1px solid #e2e8f0; margin: 8px 0 16px 0;">
3068
+ <tbody>
3069
+ ${incidentRows.join("")}
3070
+ </tbody>
3071
+ </table>
3072
+ `;
3073
+ }
3074
+ }
3075
+ const episodeNumber = incidentEpisode.episodeNumberWithPrefix ||
3076
+ (incidentEpisode.episodeNumber
3077
+ ? `#${incidentEpisode.episodeNumber}`
3078
+ : "");
3079
+ const vars = {
3080
+ incidentEpisodeTitle: incidentEpisode.title,
3081
+ episodeNumber: episodeNumber,
3082
+ projectName: incidentEpisode.project.name,
3083
+ currentState: incidentEpisode.currentIncidentState.name,
3084
+ incidentEpisodeDescription: await Markdown.convertToHTML(incidentEpisode.description || "", MarkdownContentType.Email),
3085
+ incidentEpisodeSeverity: incidentEpisode.incidentSeverity.name,
3086
+ resourcesAffected: resourcesAffected,
3087
+ rootCause: incidentEpisode.rootCause ||
3088
+ "No root cause identified for this incident episode",
3089
+ incidentsList: incidentsListHtml,
3090
+ incidentsCount: incidents.length.toString(),
3091
+ incidentEpisodeViewLink: (await IncidentEpisodeService.getEpisodeLinkInDashboard(incidentEpisode.projectId, incidentEpisode.id)).toString(),
3092
+ acknowledgeIncidentEpisodeLink: new URL(httpProtocol, host, new Route(AppApiRoute.toString())
3093
+ .addRoute(new UserOnCallLogTimeline().crudApiPath)
3094
+ .addRoute("/acknowledge-page/" + userOnCallLogTimelineId.toString())).toString(),
3095
+ };
3096
+ const emailMessage = {
3097
+ toEmail: to,
3098
+ templateType: EmailTemplateType.AcknowledgeIncidentEpisode,
3099
+ vars: vars,
3100
+ subject: `ACTION REQUIRED: Incident Episode ${episodeNumber} created - ${incidentEpisode.title}`,
3101
+ };
3102
+ return emailMessage;
3103
+ }
3104
+ async startUserNotificationRulesExecution(userId, options) {
3105
+ // add user notification log.
3106
+ const userOnCallLog = new UserOnCallLog();
3107
+ userOnCallLog.userId = userId;
3108
+ userOnCallLog.projectId = options.projectId;
3109
+ if (options.triggeredByIncidentId) {
3110
+ userOnCallLog.triggeredByIncidentId = options.triggeredByIncidentId;
3111
+ }
3112
+ if (options.triggeredByAlertId) {
3113
+ userOnCallLog.triggeredByAlertId = options.triggeredByAlertId;
3114
+ }
3115
+ if (options.triggeredByAlertEpisodeId) {
3116
+ userOnCallLog.triggeredByAlertEpisodeId =
3117
+ options.triggeredByAlertEpisodeId;
3118
+ }
3119
+ if (options.triggeredByIncidentEpisodeId) {
3120
+ userOnCallLog.triggeredByIncidentEpisodeId =
3121
+ options.triggeredByIncidentEpisodeId;
3122
+ }
3123
+ userOnCallLog.userNotificationEventType = options.userNotificationEventType;
3124
+ if (options.onCallPolicyExecutionLogId) {
3125
+ userOnCallLog.onCallDutyPolicyExecutionLogId =
3126
+ options.onCallPolicyExecutionLogId;
3127
+ }
1849
3128
  if (options.onCallPolicyId) {
1850
3129
  userOnCallLog.onCallDutyPolicyId = options.onCallPolicyId;
1851
3130
  }
@@ -1933,33 +3212,612 @@ export class Service extends DatabaseService {
1933
3212
  });
1934
3213
  }
1935
3214
  async onBeforeCreate(createBy) {
1936
- if (!createBy.data.userCallId &&
1937
- !createBy.data.userCall &&
1938
- !createBy.data.userEmail &&
1939
- !createBy.data.userSms &&
1940
- !createBy.data.userSmsId &&
1941
- !createBy.data.userWhatsApp &&
1942
- !createBy.data.userWhatsAppId &&
1943
- !createBy.data.userTelegram &&
1944
- !createBy.data.userTelegramId &&
1945
- !createBy.data.userWebhook &&
1946
- !createBy.data.userWebhookId &&
1947
- !createBy.data.userEmailId &&
1948
- !createBy.data.userPushId &&
1949
- !createBy.data.userPush) {
1950
- throw new BadDataException("Call, SMS, WhatsApp, Telegram, Webhook, Email, or Push notification is required");
1951
- }
3215
+ const carrier = createBy.data;
3216
+ /*
3217
+ * THE OWNERSHIP COLUMN IS REDUCED TO ONE SPELLING BEFORE ANYTHING READS IT,
3218
+ * and it has to happen here, first, rather than inside the guard below.
3219
+ *
3220
+ * `userId` and `user` are two decorated members over one join column. Every
3221
+ * check from this line down — the roster check, the method-ownership check,
3222
+ * the create invariants, CreatePermission's own ownership gate, and the
3223
+ * audit line after the write — asks "who does this row belong to", and each
3224
+ * of them would otherwise have to answer it from two disagreeing sources.
3225
+ * A payload carrying `user: { _id: <somebody else> }` and no `userId` is
3226
+ * the concrete failure: the guard reads the scalar, finds nothing, falls
3227
+ * back to the actor and validates a self-write, while TypeORM writes the
3228
+ * relation's id and the row belongs to somebody else entirely.
3229
+ *
3230
+ * So: refuse a payload whose two spellings disagree, then fold the survivor
3231
+ * into the scalar. After these two lines `createBy.data.userId` is the
3232
+ * single, authoritative owner, and it is the value that will be persisted.
3233
+ *
3234
+ * Deliberately NOT behind the root short-circuit that guards the checks
3235
+ * below. This is a reduction of the payload rather than a permission
3236
+ * decision, and an internal caller writing an ambiguous row would be just
3237
+ * as ambiguous a row.
3238
+ */
3239
+ UserNotificationRuleAdminService.assertOneRuleOwner(carrier);
3240
+ UserNotificationRuleAdminService.collapseRuleOwnerRelationOnCreate(carrier);
3241
+ await this.assertWriteIsPermittedForRuleOwner(createBy);
3242
+ /*
3243
+ * Ambiguity is refused, then removed — in that order, and only after the
3244
+ * ownership guard above has had its say. A payload that names two different
3245
+ * methods for one channel is a payload nobody legitimately sends, and one
3246
+ * that names the same method twice is folded down to a single spelling so
3247
+ * that the invariants below, and the ORM after them, are reading the one
3248
+ * value that will actually be written.
3249
+ */
3250
+ UserNotificationRuleAdminService.assertOneMethodPerNotificationChannel(carrier);
3251
+ UserNotificationRuleAdminService.collapseNotificationMethodRelationsOnCreate(carrier);
3252
+ const hasNotificationMethod = UserNotificationRuleAdminService.carriesAnyNotificationMethod(carrier);
3253
+ this.assertRuleIsCoherent({
3254
+ isOptOut: Boolean(createBy.data.isOptOut),
3255
+ hasNotificationMethod: hasNotificationMethod,
3256
+ });
1952
3257
  return {
1953
3258
  createBy,
1954
3259
  carryForward: null,
1955
3260
  };
1956
3261
  }
3262
+ /**
3263
+ * The two invariants that decide whether a rule row means anything, enforced
3264
+ * from one place because create and update can each break both of them.
3265
+ *
3266
+ * An opt-out row is how a user says "deliberately do not page me for this rule
3267
+ * type at this severity". It carries the rule type and the severity and
3268
+ * nothing else — a method on it would be self-contradictory (reach me here;
3269
+ * also never reach me), and its whole purpose is to make silence explicit so
3270
+ * that every OTHER zero-rule case can be treated as misconfiguration and
3271
+ * rescued by the fallback. A rule that is NOT opt-out and names no method is
3272
+ * the mirror failure: it looks like coverage on every screen and delivers
3273
+ * nothing.
3274
+ *
3275
+ * The wording of both messages is load-bearing — the dashboard and the API
3276
+ * docs quote them — so they are written once here rather than once per path.
3277
+ */
3278
+ assertRuleIsCoherent(data) {
3279
+ if (data.isOptOut && data.hasNotificationMethod) {
3280
+ throw new BadDataException("An opt-out notification rule cannot have a notification method. Remove the notification method, or turn off opt-out.");
3281
+ }
3282
+ if (!data.isOptOut && !data.hasNotificationMethod) {
3283
+ throw new BadDataException("Call, SMS, WhatsApp, Telegram, Webhook, Email, or Push notification is required");
3284
+ }
3285
+ }
3286
+ /**
3287
+ * The create-path half of the on-behalf-of guards (audit R1 and R3).
3288
+ *
3289
+ * CreatePermission.checkCreateOwnership decides WHETHER a caller may name
3290
+ * somebody else in the ownership column: CurrentUser-only callers may not, a
3291
+ * caller holding a real role permission in the model's create list may. That
3292
+ * check is deliberately permission-shaped and value-blind past that point —
3293
+ * it has no notion of a project roster and no notion of what the rest of the
3294
+ * row says. Both of those are checked here, because both of them are how the
3295
+ * widened permission turns into somebody else's pages.
3296
+ *
3297
+ * Root and master-admin writes are exempt, matching the short-circuit at the
3298
+ * top of CreatePermission. Every internal seeder (default rules on method
3299
+ * verification, invitation acceptance, migrations) builds the ownership
3300
+ * column and the method reference from one and the same userId, so the guard
3301
+ * could only ever cost them a query per row; and the delivery-time check in
3302
+ * executeNotificationRuleItem is the backstop that keeps even an internally
3303
+ * written bad row from being acted on.
3304
+ */
3305
+ async assertWriteIsPermittedForRuleOwner(createBy) {
3306
+ if (createBy.props.isRoot || createBy.props.isMasterAdmin) {
3307
+ return;
3308
+ }
3309
+ /*
3310
+ * `createBy.data.userId` alone is enough HERE, and only because
3311
+ * onBeforeCreate has already folded the `user` relation into it. Read on
3312
+ * its own — before that reduction existed — this line was a bypass: a
3313
+ * payload spelling the owner as `user: { _id: <somebody else> }` left the
3314
+ * scalar empty, fell through to props.userId, and every check below was
3315
+ * answered about the actor while the row was written for the victim. If
3316
+ * that fold is ever moved or removed, this line becomes wrong again.
3317
+ *
3318
+ * The fallback to props.userId is a different thing and stays: an omitted
3319
+ * ownership column means "for myself". CreatePermission stamps props.userId
3320
+ * onto it, but it does so AFTER this hook has run, so the value is not on
3321
+ * the model yet and reading data.userId alone would treat every ordinary
3322
+ * self-service create as an unowned row.
3323
+ */
3324
+ const ruleOwnerUserId = createBy.data.userId || createBy.props.userId;
3325
+ if (!ruleOwnerUserId) {
3326
+ throw new BadDataException("A notification rule must belong to a user. Sign in as the user this rule is for, or name the user the rule belongs to.");
3327
+ }
3328
+ const actorUserId = createBy.props.userId;
3329
+ const isWritingForSomebodyElse = !actorUserId || actorUserId.toString() !== ruleOwnerUserId.toString();
3330
+ if (isWritingForSomebodyElse) {
3331
+ /*
3332
+ * R1. Holding an administrative permission is a claim about a PROJECT, so
3333
+ * it can only ever authorise writing for users of that project. Without
3334
+ * this, one throwaway project where the caller is an admin would license
3335
+ * writing notification rules for any user id in the installation.
3336
+ */
3337
+ await UserNotificationRuleAdminService.assertTargetUserIsProjectMember({
3338
+ targetUserId: ruleOwnerUserId,
3339
+ props: createBy.props,
3340
+ });
3341
+ }
3342
+ /*
3343
+ * R3, and note that it runs for a self-write too. "userId is me, but the
3344
+ * email row I am pointing at is yours" is the mirror image of the hijack —
3345
+ * it does not steal my pages, it copies them to your inbox — and it was
3346
+ * writable long before this phase widened anything.
3347
+ */
3348
+ const references = UserNotificationRuleAdminService.collectNotificationMethodReferences(createBy.data);
3349
+ await UserNotificationRuleAdminService.assertNotificationMethodsBelongToUser({
3350
+ ownerUserId: ruleOwnerUserId,
3351
+ references: references,
3352
+ });
3353
+ }
3354
+ /*
3355
+ * R6 for the create path.
3356
+ *
3357
+ * Keyed on the actor the SERVER resolved (props.userId) against the userId
3358
+ * the row was actually PERSISTED with — read off createdItem, after
3359
+ * CreatePermission has had its say and after the insert. Nothing in the
3360
+ * request body reaches this comparison, because the body is the thing being
3361
+ * audited.
3362
+ */
3363
+ async onCreateSuccess(onCreate, createdItem) {
3364
+ const actorUserId = onCreate.createBy.props.userId;
3365
+ const ruleOwnerUserId = createdItem.userId;
3366
+ if (actorUserId &&
3367
+ ruleOwnerUserId &&
3368
+ actorUserId.toString() !== ruleOwnerUserId.toString()) {
3369
+ await UserNotificationRuleAdminService.recordAdminRuleChange({
3370
+ action: AuditLogAction.Create,
3371
+ actorUserId: actorUserId,
3372
+ ownerUserId: ruleOwnerUserId,
3373
+ projectId: createdItem.projectId || onCreate.createBy.props.tenantId,
3374
+ ruleId: createdItem.id,
3375
+ after: createdItem,
3376
+ notifyOwner: true,
3377
+ props: onCreate.createBy.props,
3378
+ });
3379
+ }
3380
+ return createdItem;
3381
+ }
3382
+ /**
3383
+ * Narrow a caller-supplied query to the rows that caller is actually entitled
3384
+ * to write, for use by the write hooks.
3385
+ *
3386
+ * WHY THIS EXISTS AT ALL. DatabaseService runs the hooks BEFORE the permission
3387
+ * layer: _updateBy calls onBeforeUpdate and only then
3388
+ * ModelPermission.checkUpdateQueryPermissions; _deleteBy calls onBeforeDelete
3389
+ * and only then checkDeleteQueryPermission. So a hook that reads
3390
+ * `updateBy.query` is reading the RAW request — no tenant predicate, no
3391
+ * ownership predicate — and the hooks below read it with `isRoot` props on
3392
+ * top, because the question they ask is a question about the database's state
3393
+ * rather than about the caller's visibility. Left there, a caller could point
3394
+ * the guard at rows in another project entirely: the guard would validate
3395
+ * against them, the audit trail would name their owners, and the write itself
3396
+ * would touch a completely different set.
3397
+ *
3398
+ * WHY NOT JUST CALL ModelPermission. That is the obvious fix and it is the
3399
+ * wrong one. checkUpdateQueryPermissions does two jobs — it narrows the query
3400
+ * AND it authorises the request — and running it here would run the second
3401
+ * job twice, moving every table- and column-level rejection into the hook and
3402
+ * duplicating the team lookups behind the tenant scope on every write. The
3403
+ * hook does not need to authorise anything; _updateBy authorises it a few
3404
+ * lines later and is the authority. What the hook needs is only that the row
3405
+ * set it reasons about is no wider than the row set the write can reach.
3406
+ *
3407
+ * WHAT IS REPRODUCED, AND WHY THAT IS THE WHOLE OF IT. For this model the
3408
+ * narrowing is exactly two predicates: the tenant column
3409
+ * (TenantPermission.addTenantScopeToQuery for a member,
3410
+ * PermissionUtil.addTenantScopeToQueryAsRoot on the delete path for root) and,
3411
+ * when Permission.CurrentUser is the ONLY thing letting the caller through,
3412
+ * the ownership column. Nothing else applies: UserNotificationRule declares no
3413
+ * access-control column, is not an operational resource and has no
3414
+ * @OwnedThrough, so addAccessControlIdsToQuery and addOwnedScopeToQuery are
3415
+ * both no-ops on it. IF ANY OF THAT CHANGES ON THE MODEL, THIS MUST CHANGE
3416
+ * WITH IT — a narrowing the permission layer applies and this does not is a
3417
+ * guard validating rows the write never touches.
3418
+ *
3419
+ * Root and master-admin queries are returned untouched. They are entitled to
3420
+ * every row, and narrowing them would make the guard read FEWER rows than the
3421
+ * write reaches, which is the one direction it must never be wrong in.
3422
+ */
3423
+ narrowQueryToCallerEntitlement(query, props, requestType) {
3424
+ if (props.isRoot || props.isMasterAdmin) {
3425
+ return query;
3426
+ }
3427
+ const scopedQuery = Object.assign({}, query);
3428
+ const tenantColumn = this.getModel().getTenantColumn();
3429
+ if (tenantColumn && props.tenantId && !props.isMultiTenantRequest) {
3430
+ scopedQuery[tenantColumn] = props.tenantId;
3431
+ }
3432
+ const userColumn = this.getModel().getUserColumn();
3433
+ if (userColumn &&
3434
+ props.userId &&
3435
+ TenantPermission.isAccessGrantedOnlyByCurrentUser(this.modelType, props, requestType)) {
3436
+ /*
3437
+ * Set rather than merged. A CurrentUser-only caller whose query names
3438
+ * somebody else is rejected outright by addCurrentUserScopeToQuery a
3439
+ * moment from now, so the only thing that matters here is that the guard
3440
+ * never reads rows that rejection would have protected.
3441
+ */
3442
+ scopedQuery[userColumn] = props.userId;
3443
+ }
3444
+ return scopedQuery;
3445
+ }
3446
+ /**
3447
+ * The update-path half of R3, plus the read that R6 needs.
3448
+ *
3449
+ * The rule's owner is re-read FROM THE DATABASE here and never taken from
3450
+ * updateBy.data. That is the whole point of the hook: on update the caller
3451
+ * controls the body, so a userId in it is a claim ("this row is mine") made
3452
+ * by exactly the party the guard exists to doubt. The persisted value is the
3453
+ * only one that decides whose pages the row selects, so it is the only one
3454
+ * worth comparing a method's owner against.
3455
+ *
3456
+ * The lookup runs with isRoot rather than the caller's own props on purpose.
3457
+ * Scoping it to what the CALLER can READ would let a caller who cannot see a
3458
+ * row edit it unchecked — the query would simply return nothing and the loop
3459
+ * below would have nothing to reject. Read permission and write permission are
3460
+ * different lists, and it is the write one that decides what this hook has to
3461
+ * answer for.
3462
+ *
3463
+ * The QUERY, on the other hand, is narrowed first. Root props remove the
3464
+ * caller's visibility from the answer; they must not also remove the caller's
3465
+ * ENTITLEMENT from it, and this hook runs before ModelPermission has applied
3466
+ * either. See narrowQueryToCallerEntitlement for why the narrowing is
3467
+ * reproduced here rather than delegated.
3468
+ *
3469
+ * The rows are carried forward so onUpdateSuccess can audit against the
3470
+ * owner as it stood BEFORE the write, without a second read and without
3471
+ * trusting anything the request said.
3472
+ */
3473
+ async onBeforeUpdate(updateBy) {
3474
+ var _a;
3475
+ const patch = updateBy.data;
3476
+ const references = UserNotificationRuleAdminService.collectNotificationMethodReferences(patch);
3477
+ const isInternalWrite = Boolean(updateBy.props.isRoot || updateBy.props.isMasterAdmin);
3478
+ /*
3479
+ * Three reasons to read the affected rows, and only one of them is R3. An
3480
+ * actor id means this write might be somebody editing somebody else's
3481
+ * configuration, which R6 has to be able to report on even when no method FK
3482
+ * is being touched; and a patch that touches `isOptOut` or any method column
3483
+ * can break a row-level invariant that is only visible once the patch is
3484
+ * laid over the row it is being applied to.
3485
+ */
3486
+ const touchesRuleCoherence = patch["isOptOut"] !== undefined ||
3487
+ UserNotificationRuleAdminService.mentionsAnyNotificationMethodColumn(patch);
3488
+ const needsAffectedRules = (references.length > 0 && !isInternalWrite) ||
3489
+ touchesRuleCoherence ||
3490
+ Boolean(updateBy.props.userId);
3491
+ if (!needsAffectedRules) {
3492
+ return {
3493
+ updateBy,
3494
+ carryForward: null,
3495
+ };
3496
+ }
3497
+ const affectedRules = await this.findBy({
3498
+ query: this.narrowQueryToCallerEntitlement(updateBy.query, updateBy.props, DatabaseRequestType.Update),
3499
+ select: {
3500
+ _id: true,
3501
+ userId: true,
3502
+ projectId: true,
3503
+ ruleType: true,
3504
+ notifyAfterMinutes: true,
3505
+ isOptOut: true,
3506
+ incidentSeverityId: true,
3507
+ alertSeverityId: true,
3508
+ userEmailId: true,
3509
+ userSmsId: true,
3510
+ userCallId: true,
3511
+ userWhatsAppId: true,
3512
+ userTelegramId: true,
3513
+ userPushId: true,
3514
+ userWebhookId: true,
3515
+ },
3516
+ limit: LIMIT_MAX,
3517
+ skip: 0,
3518
+ props: {
3519
+ isRoot: true,
3520
+ ignoreHooks: true,
3521
+ },
3522
+ });
3523
+ if (references.length > 0 && !isInternalWrite) {
3524
+ /*
3525
+ * One validation per DISTINCT owner rather than per row. A bulk update
3526
+ * across twenty of one user's rules asks the same question twenty times,
3527
+ * and each question costs a lookup per referenced method.
3528
+ */
3529
+ const validatedOwnerIds = new Set();
3530
+ for (const affectedRule of affectedRules) {
3531
+ const ownerKey = ((_a = affectedRule.userId) === null || _a === void 0 ? void 0 : _a.toString()) || "";
3532
+ if (validatedOwnerIds.has(ownerKey)) {
3533
+ continue;
3534
+ }
3535
+ validatedOwnerIds.add(ownerKey);
3536
+ await UserNotificationRuleAdminService.assertNotificationMethodsBelongToUser({
3537
+ ownerUserId: affectedRule.userId,
3538
+ references: references,
3539
+ });
3540
+ }
3541
+ }
3542
+ /*
3543
+ * Ambiguity is refused here as it is on create, but NOT folded away. The
3544
+ * relation members are `update: []` on this model while the `*Id` members
3545
+ * are open to an administrator, so rewriting one spelling into the other
3546
+ * would smuggle a column write past the very ColumnPermission check that
3547
+ * runs immediately after this hook. Refusal leaves nothing for the ORM to
3548
+ * choose between without moving a value across a permission boundary.
3549
+ */
3550
+ UserNotificationRuleAdminService.assertOneMethodPerNotificationChannel(patch);
3551
+ if (touchesRuleCoherence) {
3552
+ /*
3553
+ * The create-time invariants, re-checked per affected row.
3554
+ *
3555
+ * They were enforced only on create, which left update as a way to reach
3556
+ * the states create refuses: flip `isOptOut` on a rule that carries an
3557
+ * email and you have a row that says both "reach me here" and "never
3558
+ * reach me"; null the last method on a rule that is not opt-out and you
3559
+ * have a row that looks like coverage on every screen and delivers
3560
+ * nothing — indistinguishable, to the fallback, from a deliberate choice
3561
+ * to stay silent. Neither is visible from the patch alone, which is why
3562
+ * this waits until the affected rows have been read.
3563
+ */
3564
+ for (const affectedRule of affectedRules) {
3565
+ const methodIdsAfterPatch = UserNotificationRuleAdminService.getNotificationMethodIdsAfterPatch({
3566
+ patch: patch,
3567
+ currentRow: affectedRule,
3568
+ });
3569
+ const isOptOutAfterPatch = patch["isOptOut"] !== undefined
3570
+ ? Boolean(patch["isOptOut"])
3571
+ : Boolean(affectedRule.isOptOut);
3572
+ this.assertRuleIsCoherent({
3573
+ isOptOut: isOptOutAfterPatch,
3574
+ hasNotificationMethod: methodIdsAfterPatch.length > 0,
3575
+ });
3576
+ }
3577
+ }
3578
+ return {
3579
+ updateBy,
3580
+ carryForward: {
3581
+ affectedRules: affectedRules,
3582
+ },
3583
+ };
3584
+ }
3585
+ /*
3586
+ * R6 for the update path.
3587
+ *
3588
+ * Every row whose PERSISTED owner is somebody other than the actor gets an
3589
+ * audit entry; the owner gets at most one mail no matter how many of their
3590
+ * rules one request touched, because twenty copies of "an admin changed your
3591
+ * rules" is a message people learn to delete rather than read.
3592
+ *
3593
+ * `updatedItemIds` is the set of rows the write ACTUALLY touched, and the
3594
+ * carried-forward rows are filtered down to it. The two can differ: the hook
3595
+ * read every row the (narrowed) query matched, while _updateBy applies the
3596
+ * caller's own skip/limit and drops rows that were hard-deleted between the
3597
+ * two. Reporting an unchanged row would put a change in the audit trail that
3598
+ * never happened and mail somebody about it.
3599
+ */
3600
+ async onUpdateSuccess(onUpdate, updatedItemIds) {
3601
+ var _a;
3602
+ const actorUserId = onUpdate.updateBy.props.userId;
3603
+ if (!actorUserId) {
3604
+ return onUpdate;
3605
+ }
3606
+ const affectedRules = ((_a = onUpdate.carryForward) === null || _a === void 0 ? void 0 : _a.affectedRules) || [];
3607
+ const updatedIds = new Set(updatedItemIds.map((id) => {
3608
+ return id.toString();
3609
+ }));
3610
+ await this.reportAdministrativeChange({
3611
+ action: AuditLogAction.Update,
3612
+ actorUserId: actorUserId,
3613
+ rules: affectedRules.filter((rule) => {
3614
+ return Boolean(rule.id && updatedIds.has(rule.id.toString()));
3615
+ }),
3616
+ updatedFields: onUpdate.updateBy.data,
3617
+ props: onUpdate.updateBy.props,
3618
+ });
3619
+ return onUpdate;
3620
+ }
3621
+ /**
3622
+ * R6, factored out because create, update and delete all owe the same debt.
3623
+ *
3624
+ * One audit entry per ROW, because that is what an investigator reconstructs
3625
+ * a timeline from, and at most one mail per PERSON, because one request that
3626
+ * touches twenty of somebody's rules is still one thing that happened to
3627
+ * them.
3628
+ *
3629
+ * Rules the actor owns are skipped: configuring your own paging is not an
3630
+ * administrative act and does not need announcing to yourself.
3631
+ */
3632
+ async reportAdministrativeChange(data) {
3633
+ const notifiedOwnerIds = new Set();
3634
+ for (const rule of data.rules) {
3635
+ const ruleOwnerUserId = rule.userId;
3636
+ if (!ruleOwnerUserId ||
3637
+ ruleOwnerUserId.toString() === data.actorUserId.toString()) {
3638
+ continue;
3639
+ }
3640
+ const ownerKey = ruleOwnerUserId.toString();
3641
+ const isFirstRuleForThisOwner = !notifiedOwnerIds.has(ownerKey);
3642
+ notifiedOwnerIds.add(ownerKey);
3643
+ await UserNotificationRuleAdminService.recordAdminRuleChange({
3644
+ action: data.action,
3645
+ actorUserId: data.actorUserId,
3646
+ ownerUserId: ruleOwnerUserId,
3647
+ projectId: rule.projectId || data.props.tenantId,
3648
+ ruleId: rule.id,
3649
+ before: rule,
3650
+ updatedFields: data.updatedFields,
3651
+ notifyOwner: isFirstRuleForThisOwner,
3652
+ props: data.props,
3653
+ });
3654
+ }
3655
+ }
3656
+ /**
3657
+ * The delete-path guard, and the reason it is a guard at all.
3658
+ *
3659
+ * Deleting somebody's notification rules is the most destructive of the three
3660
+ * write verbs and, until this hook existed, the only unguarded one: the model
3661
+ * opened `delete` to the administrative permissions, and nothing here noticed.
3662
+ * An admin — or anyone who had got hold of an admin session — could remove a
3663
+ * responder's entire paging configuration and leave no record and no warning.
3664
+ * The person it happened to would find out during an incident.
3665
+ *
3666
+ * What a delete guard can and cannot be. There is no R3 analogue: a deleted
3667
+ * row routes nothing anywhere, so there is no method-versus-owner pair left to
3668
+ * disagree. Nor is there an R1 analogue: refusing to delete the rules of
3669
+ * somebody who is no longer on the roster would block exactly the cleanup an
3670
+ * admin performs after a member leaves. What is left, and what actually
3671
+ * matters, is EVIDENTIARY — establish from the database who owned these rows
3672
+ * before they cease to exist, because after the write nothing can answer that
3673
+ * question and the request body was never allowed to.
3674
+ *
3675
+ * The rows must be read here rather than in onDeleteSuccess for the same
3676
+ * reason: _deleteBy hands the success hook only the ids it deleted, and by
3677
+ * then the rows are gone.
3678
+ *
3679
+ * The query is narrowed first. onBeforeDelete runs BEFORE
3680
+ * ModelPermission.checkDeleteQueryPermission, exactly as onBeforeUpdate runs
3681
+ * before its update counterpart, so the raw query carries neither the tenant
3682
+ * predicate nor the ownership predicate — see narrowQueryToCallerEntitlement.
3683
+ * Without it, a member could point this read at another project's rules and
3684
+ * have their owners written into the audit trail and mailed a warning about a
3685
+ * deletion that never touched them.
3686
+ */
3687
+ async onBeforeDelete(deleteBy) {
3688
+ if (!deleteBy.props.userId) {
3689
+ /*
3690
+ * No actor, nothing to attribute. Workers and migrations delete rules
3691
+ * (project teardown, method removal cascades) and an "an administrator
3692
+ * deleted your rules" mail for every one of those is how the message
3693
+ * that matters gets filtered away.
3694
+ */
3695
+ return {
3696
+ deleteBy,
3697
+ carryForward: null,
3698
+ };
3699
+ }
3700
+ const deletedRules = await this.findBy({
3701
+ query: this.narrowQueryToCallerEntitlement(deleteBy.query, deleteBy.props, DatabaseRequestType.Delete),
3702
+ select: {
3703
+ _id: true,
3704
+ userId: true,
3705
+ projectId: true,
3706
+ ruleType: true,
3707
+ notifyAfterMinutes: true,
3708
+ isOptOut: true,
3709
+ incidentSeverityId: true,
3710
+ alertSeverityId: true,
3711
+ userEmailId: true,
3712
+ userSmsId: true,
3713
+ userCallId: true,
3714
+ userWhatsAppId: true,
3715
+ userTelegramId: true,
3716
+ userPushId: true,
3717
+ userWebhookId: true,
3718
+ },
3719
+ limit: LIMIT_MAX,
3720
+ skip: 0,
3721
+ props: {
3722
+ isRoot: true,
3723
+ ignoreHooks: true,
3724
+ },
3725
+ });
3726
+ return {
3727
+ deleteBy,
3728
+ carryForward: {
3729
+ deletedRules: deletedRules,
3730
+ },
3731
+ };
3732
+ }
3733
+ /*
3734
+ * R6 for the delete path.
3735
+ *
3736
+ * Keyed the same way as the other two: the actor the SERVER resolved against
3737
+ * the owner the DATABASE recorded, read before the rows were removed. The
3738
+ * snapshot carried forward is now the only description of what those rules
3739
+ * were, so it is what the audit entry is built from.
3740
+ */
3741
+ async onDeleteSuccess(onDelete, deletedItemIds) {
3742
+ var _a;
3743
+ const actorUserId = onDelete.deleteBy.props.userId;
3744
+ if (!actorUserId) {
3745
+ return onDelete;
3746
+ }
3747
+ const deletedRules = ((_a = onDelete.carryForward) === null || _a === void 0 ? void 0 : _a.deletedRules) || [];
3748
+ const deletedIds = new Set(deletedItemIds.map((id) => {
3749
+ return id.toString();
3750
+ }));
3751
+ await this.reportAdministrativeChange({
3752
+ action: AuditLogAction.Delete,
3753
+ actorUserId: actorUserId,
3754
+ /*
3755
+ * Only rows the delete actually removed. The hook read every row the
3756
+ * narrowed query matched; _deleteBy then applied the caller's own
3757
+ * skip/limit on top, so the two sets are not always the same and a
3758
+ * warning about a rule that still exists is a false alarm.
3759
+ */
3760
+ rules: deletedRules.filter((rule) => {
3761
+ return Boolean(rule.id && deletedIds.has(rule.id.toString()));
3762
+ }),
3763
+ props: onDelete.deleteBy.props,
3764
+ });
3765
+ return onDelete;
3766
+ }
1957
3767
  async addDefaultNotificationRulesForVerifiedMethod(data) {
1958
3768
  const { projectId, userId, notificationMethod } = data;
1959
- await this.createIncidentOnCallRules(projectId, userId, notificationMethod);
1960
- await this.createAlertOnCallRules(projectId, userId, notificationMethod);
1961
- await this.createSingleRule(projectId, userId, notificationMethod, NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE);
1962
- await this.createSingleRule(projectId, userId, notificationMethod, NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE);
3769
+ /*
3770
+ * Read each severity list once and reuse it for both rule types it drives.
3771
+ * Incident severities scope both ON_CALL_EXECUTED_INCIDENT and
3772
+ * ON_CALL_EXECUTED_INCIDENT_EPISODE; alert severities do the same for their
3773
+ * two.
3774
+ */
3775
+ const incidentSeverityIds = await this.getIncidentSeverityIds(projectId);
3776
+ const alertSeverityIds = await this.getAlertSeverityIds(projectId);
3777
+ await this.createSeverityScopedRules({
3778
+ projectId,
3779
+ userId,
3780
+ notificationMethod,
3781
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
3782
+ severityIds: incidentSeverityIds,
3783
+ severityColumn: "incidentSeverityId",
3784
+ });
3785
+ await this.createSeverityScopedRules({
3786
+ projectId,
3787
+ userId,
3788
+ notificationMethod,
3789
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
3790
+ severityIds: alertSeverityIds,
3791
+ severityColumn: "alertSeverityId",
3792
+ });
3793
+ /*
3794
+ * The two episode rule types are severity-scoped as well, and used not to
3795
+ * be. UserOnCallLogService counts episode rules filtered by a concrete
3796
+ * severity id, and the episode rule pages in User Settings scope their
3797
+ * tables the same way — so a NULL-severity episode rule matched no page and
3798
+ * appeared in no table. Users got "defaults" that were unreachable and
3799
+ * invisible at the same time.
3800
+ */
3801
+ await this.createSeverityScopedRules({
3802
+ projectId,
3803
+ userId,
3804
+ notificationMethod,
3805
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE,
3806
+ severityIds: alertSeverityIds,
3807
+ severityColumn: "alertSeverityId",
3808
+ });
3809
+ await this.createSeverityScopedRules({
3810
+ projectId,
3811
+ userId,
3812
+ notificationMethod,
3813
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE,
3814
+ severityIds: incidentSeverityIds,
3815
+ severityColumn: "incidentSeverityId",
3816
+ });
3817
+ /*
3818
+ * These two are about the user's shift, not about anything that fired, so
3819
+ * they legitimately have no severity and stay single rules.
3820
+ */
1963
3821
  await this.createSingleRule(projectId, userId, notificationMethod, NotificationRuleType.WHEN_USER_GOES_ON_CALL);
1964
3822
  await this.createSingleRule(projectId, userId, notificationMethod, NotificationRuleType.WHEN_USER_GOES_OFF_CALL);
1965
3823
  }
@@ -2011,7 +3869,7 @@ export class Service extends DatabaseService {
2011
3869
  }
2012
3870
  return query;
2013
3871
  }
2014
- async createIncidentOnCallRules(projectId, userId, notificationMethod) {
3872
+ async getIncidentSeverityIds(projectId) {
2015
3873
  const incidentSeverities = await IncidentSeverityService.findBy({
2016
3874
  query: {
2017
3875
  projectId,
@@ -2025,33 +3883,11 @@ export class Service extends DatabaseService {
2025
3883
  _id: true,
2026
3884
  },
2027
3885
  });
2028
- for (const incidentSeverity of incidentSeverities) {
2029
- const existingRule = await this.findOneBy({
2030
- query: Object.assign(Object.assign({ projectId,
2031
- userId }, this.getNotificationMethodQuery(notificationMethod)), { incidentSeverityId: incidentSeverity.id, ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT }),
2032
- props: {
2033
- isRoot: true,
2034
- },
2035
- });
2036
- if (existingRule) {
2037
- continue;
2038
- }
2039
- const rule = new Model();
2040
- rule.projectId = projectId;
2041
- rule.userId = userId;
2042
- this.applyNotificationMethod(rule, notificationMethod);
2043
- rule.incidentSeverityId = incidentSeverity.id;
2044
- rule.notifyAfterMinutes = 0;
2045
- rule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_INCIDENT;
2046
- await this.create({
2047
- data: rule,
2048
- props: {
2049
- isRoot: true,
2050
- },
2051
- });
2052
- }
3886
+ return incidentSeverities.map((severity) => {
3887
+ return severity.id;
3888
+ });
2053
3889
  }
2054
- async createAlertOnCallRules(projectId, userId, notificationMethod) {
3890
+ async getAlertSeverityIds(projectId) {
2055
3891
  const alertSeverities = await AlertSeverityService.findBy({
2056
3892
  query: {
2057
3893
  projectId,
@@ -2065,10 +3901,20 @@ export class Service extends DatabaseService {
2065
3901
  _id: true,
2066
3902
  },
2067
3903
  });
2068
- for (const alertSeverity of alertSeverities) {
3904
+ return alertSeverities.map((severity) => {
3905
+ return severity.id;
3906
+ });
3907
+ }
3908
+ /*
3909
+ * Seed one rule per severity for a severity-scoped rule type, skipping any
3910
+ * (method, severity, ruleType) triple the user already has. The duplicate
3911
+ * check is keyed on the same columns the write sets, so re-verifying a method
3912
+ * never doubles a user's rules — and therefore never doubles their pages.
3913
+ */
3914
+ async createSeverityScopedRules(data) {
3915
+ for (const severityId of data.severityIds) {
2069
3916
  const existingRule = await this.findOneBy({
2070
- query: Object.assign(Object.assign({ projectId,
2071
- userId }, this.getNotificationMethodQuery(notificationMethod)), { alertSeverityId: alertSeverity.id, ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT }),
3917
+ query: Object.assign(Object.assign({ projectId: data.projectId, userId: data.userId }, this.getNotificationMethodQuery(data.notificationMethod)), { [data.severityColumn]: severityId, ruleType: data.ruleType }),
2072
3918
  props: {
2073
3919
  isRoot: true,
2074
3920
  },
@@ -2077,12 +3923,12 @@ export class Service extends DatabaseService {
2077
3923
  continue;
2078
3924
  }
2079
3925
  const rule = new Model();
2080
- rule.projectId = projectId;
2081
- rule.userId = userId;
2082
- this.applyNotificationMethod(rule, notificationMethod);
2083
- rule.alertSeverityId = alertSeverity.id;
3926
+ rule.projectId = data.projectId;
3927
+ rule.userId = data.userId;
3928
+ this.applyNotificationMethod(rule, data.notificationMethod);
3929
+ rule[data.severityColumn] = severityId;
2084
3930
  rule.notifyAfterMinutes = 0;
2085
- rule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_ALERT;
3931
+ rule.ruleType = data.ruleType;
2086
3932
  await this.create({
2087
3933
  data: rule,
2088
3934
  props: {
@@ -2147,6 +3993,728 @@ export class Service extends DatabaseService {
2147
3993
  },
2148
3994
  });
2149
3995
  }
3996
+ /**
3997
+ * What this user would lose by deleting these notification rules.
3998
+ *
3999
+ * Ask this BEFORE deleting. It reads and returns; it writes nothing and
4000
+ * refuses nothing, and the caller is expected to go ahead and delete anyway
4001
+ * if that is what the human wants after reading it.
4002
+ *
4003
+ * The rule ids are INTERSECTED with the rules this user actually has in this
4004
+ * project rather than trusted — the read is scoped by (projectId, userId) in
4005
+ * the query itself, so an id belonging to somebody else, or to another
4006
+ * project, simply matches nothing and contributes nothing to the answer.
4007
+ * That is the only place row scoping can come from: a column access list
4008
+ * cannot restrict WHICH ROWS a caller sees, because Permission.CurrentUser is
4009
+ * auto-granted to every authenticated caller and so never means "only my own
4010
+ * row".
4011
+ *
4012
+ * An empty id list is legal and returns a zero-deletion impact, which is
4013
+ * still worth something: it carries this user's responder status, their
4014
+ * reachability and whether the project's fallback is on.
4015
+ *
4016
+ * ---------------------------------------------------------------------------
4017
+ * NOT WIRED TODAY. READ THIS BEFORE ASSUMING IT GUARDS ANYTHING.
4018
+ *
4019
+ * This method and getNotificationMethodDeletionImpact below have NO production
4020
+ * caller. The delete guard that actually ships is client-side, in
4021
+ * App/FeatureSet/Dashboard/src/Components/NotificationMethods/NotificationMethod.tsx
4022
+ * (useNotificationMethodDeleteGuard + DeletionImpactModal), and it computes the
4023
+ * same answer in the browser from the deleting user's OWN rules.
4024
+ *
4025
+ * That is adequate for the case that ships: a person deleting their own
4026
+ * notification method or their own rule can read all of their own rules, and
4027
+ * one user's rule set is small and bounded. It is NOT adequate for an
4028
+ * administrator computing the impact of deleting somebody ELSE's
4029
+ * configuration, which is what this exists for - and that is Phase 3, which is
4030
+ * not merged.
4031
+ *
4032
+ * So this is deliberately-retained, currently-unreachable code, kept because
4033
+ * the admin path needs exactly it and because it holds one thing the browser
4034
+ * copy cannot: it reads EVERY rule for the user rather than a page, and it
4035
+ * derives a rule's severity from the column its RULE TYPE dictates rather than
4036
+ * whichever column happens to be populated.
4037
+ *
4038
+ * Retaining unreachable code is a real cost and this comment is the price of
4039
+ * it: nothing here enforces anything server-side today. A deletion is not
4040
+ * validated, refused or even observed by this service. If you are reading this
4041
+ * because you assumed the server checked, it does not.
4042
+ * ---------------------------------------------------------------------------
4043
+ */
4044
+ async getRuleDeletionImpact(data) {
4045
+ const targetRuleIds = new Set(data.notificationRuleIds.map((ruleId) => {
4046
+ return ruleId.toString();
4047
+ }));
4048
+ return this.computeDeletionImpact({
4049
+ projectId: data.projectId,
4050
+ userId: data.userId,
4051
+ isBeingDeleted: (rule) => {
4052
+ var _a;
4053
+ return targetRuleIds.has(((_a = rule.id) === null || _a === void 0 ? void 0 : _a.toString()) || "");
4054
+ },
4055
+ deletedMethod: undefined,
4056
+ });
4057
+ }
4058
+ /**
4059
+ * What this user would lose by deleting one notification method.
4060
+ *
4061
+ * This is the dangerous one. Deleting a method is not a small write: every
4062
+ * method foreign key on UserNotificationRule is onDelete: "CASCADE", and each
4063
+ * method service deletes the same rows itself in onBeforeDelete, so removing
4064
+ * one phone number takes every rule that pointed at it with it. Somebody
4065
+ * tidying up an old number has no reason to expect that, and nothing on the
4066
+ * screen tells them.
4067
+ *
4068
+ * The method row is looked up scoped by projectId, and the userId comes from
4069
+ * the ROW rather than from the caller. Both matter: the projectId scope is
4070
+ * what stops a caller probing method ids from other projects, and taking the
4071
+ * userId from the row is what stops a caller asking for one user's method
4072
+ * under another user's name and getting an answer that belongs to neither.
4073
+ */
4074
+ async getNotificationMethodDeletionImpact(data) {
4075
+ const method = await this.resolveNotificationMethod(data);
4076
+ return this.computeDeletionImpact({
4077
+ projectId: data.projectId,
4078
+ userId: method.userId,
4079
+ isBeingDeleted: (rule) => {
4080
+ var _a;
4081
+ return (((_a = this.getRuleMethodId(rule, data.methodType)) === null || _a === void 0 ? void 0 : _a.toString()) ===
4082
+ data.methodId.toString());
4083
+ },
4084
+ deletedMethod: method,
4085
+ });
4086
+ }
4087
+ /**
4088
+ * Read the method row being deleted, scoped to the project, and reduce it to
4089
+ * the three things the preview needs: whose it is, which channel it is on,
4090
+ * and whether it was ever verified.
4091
+ *
4092
+ * Webhooks report isVerified: true with no column behind it. UserWebhook has
4093
+ * no verification concept at all — its presence IS the whole test, which is
4094
+ * how the fallback treats it and how readiness reports it — so calling it
4095
+ * unverified here would tell somebody that deleting the one channel
4096
+ * guaranteed to work costs them nothing.
4097
+ */
4098
+ async resolveNotificationMethod(data) {
4099
+ const query = {
4100
+ _id: data.methodId,
4101
+ projectId: data.projectId,
4102
+ };
4103
+ const props = {
4104
+ isRoot: true,
4105
+ };
4106
+ let row = null;
4107
+ let isVerified = false;
4108
+ if (data.methodType === ReadinessMethodType.Email) {
4109
+ row = await UserEmailService.findOneBy({
4110
+ query: query,
4111
+ select: { _id: true, userId: true, isVerified: true },
4112
+ props: props,
4113
+ });
4114
+ isVerified = Boolean(row === null || row === void 0 ? void 0 : row.isVerified);
4115
+ }
4116
+ else if (data.methodType === ReadinessMethodType.SMS) {
4117
+ row = await UserSmsService.findOneBy({
4118
+ query: query,
4119
+ select: { _id: true, userId: true, isVerified: true },
4120
+ props: props,
4121
+ });
4122
+ isVerified = Boolean(row === null || row === void 0 ? void 0 : row.isVerified);
4123
+ }
4124
+ else if (data.methodType === ReadinessMethodType.Call) {
4125
+ row = await UserCallService.findOneBy({
4126
+ query: query,
4127
+ select: { _id: true, userId: true, isVerified: true },
4128
+ props: props,
4129
+ });
4130
+ isVerified = Boolean(row === null || row === void 0 ? void 0 : row.isVerified);
4131
+ }
4132
+ else if (data.methodType === ReadinessMethodType.Push) {
4133
+ row = await UserPushService.findOneBy({
4134
+ query: query,
4135
+ select: { _id: true, userId: true, isVerified: true },
4136
+ props: props,
4137
+ });
4138
+ isVerified = Boolean(row === null || row === void 0 ? void 0 : row.isVerified);
4139
+ }
4140
+ else if (data.methodType === ReadinessMethodType.WhatsApp) {
4141
+ row = await UserWhatsAppService.findOneBy({
4142
+ query: query,
4143
+ select: { _id: true, userId: true, isVerified: true },
4144
+ props: props,
4145
+ });
4146
+ isVerified = Boolean(row === null || row === void 0 ? void 0 : row.isVerified);
4147
+ }
4148
+ else if (data.methodType === ReadinessMethodType.Telegram) {
4149
+ row = await UserTelegramService.findOneBy({
4150
+ query: query,
4151
+ select: { _id: true, userId: true, isVerified: true },
4152
+ props: props,
4153
+ });
4154
+ isVerified = Boolean(row === null || row === void 0 ? void 0 : row.isVerified);
4155
+ }
4156
+ else if (data.methodType === ReadinessMethodType.Webhook) {
4157
+ row = await UserWebhookService.findOneBy({
4158
+ query: query,
4159
+ select: { _id: true, userId: true },
4160
+ props: props,
4161
+ });
4162
+ isVerified = Boolean(row);
4163
+ }
4164
+ else {
4165
+ throw new BadDataException(`${data.methodType} is not a notification method`);
4166
+ }
4167
+ if (!row || !row.userId) {
4168
+ throw new BadDataException("Notification method not found");
4169
+ }
4170
+ return {
4171
+ methodType: data.methodType,
4172
+ userId: row.userId,
4173
+ isVerified: isVerified,
4174
+ };
4175
+ }
4176
+ /**
4177
+ * The foreign key a rule uses to point at a method of this channel. One
4178
+ * lookup table rather than seven inline comparisons, so a rule can never be
4179
+ * tested against the wrong column — which would report a WhatsApp rule as
4180
+ * surviving the deletion of the SMS number it does not use, or worse, the
4181
+ * reverse.
4182
+ */
4183
+ getRuleMethodId(rule, methodType) {
4184
+ if (methodType === ReadinessMethodType.Email) {
4185
+ return rule.userEmailId;
4186
+ }
4187
+ if (methodType === ReadinessMethodType.SMS) {
4188
+ return rule.userSmsId;
4189
+ }
4190
+ if (methodType === ReadinessMethodType.Call) {
4191
+ return rule.userCallId;
4192
+ }
4193
+ if (methodType === ReadinessMethodType.Push) {
4194
+ return rule.userPushId;
4195
+ }
4196
+ if (methodType === ReadinessMethodType.WhatsApp) {
4197
+ return rule.userWhatsAppId;
4198
+ }
4199
+ if (methodType === ReadinessMethodType.Telegram) {
4200
+ return rule.userTelegramId;
4201
+ }
4202
+ if (methodType === ReadinessMethodType.Webhook) {
4203
+ return rule.userWebhookId;
4204
+ }
4205
+ return undefined;
4206
+ }
4207
+ /**
4208
+ * The before/after picture both entry points share.
4209
+ *
4210
+ * Deliberately shaped as "read every rule this user has, then ask a predicate
4211
+ * which of them go" rather than "count the rules that go". Coverage is a
4212
+ * property of what is LEFT, so the rules that survive are as load-bearing as
4213
+ * the ones that do not: a cell with two rules on two methods loses nothing
4214
+ * when one of them goes, and the only way to know that is to have read both.
4215
+ */
4216
+ async computeDeletionImpact(data) {
4217
+ const cellStates = new Map();
4218
+ const handoffStates = new Map();
4219
+ let rulesDeletedCount = 0;
4220
+ /*
4221
+ * Folded as each page arrives rather than accumulated and folded after.
4222
+ * Every rule collapses into one of a few dozen cells, so holding the rows
4223
+ * would mean carrying the whole table in memory to produce a map orders of
4224
+ * magnitude smaller — and the one project where that matters is exactly the
4225
+ * project where this read takes more than one page.
4226
+ */
4227
+ const foldRule = (rule) => {
4228
+ const isDeleted = data.isBeingDeleted(rule);
4229
+ if (isDeleted) {
4230
+ rulesDeletedCount++;
4231
+ }
4232
+ /*
4233
+ * `isOptOut === true`, never `=== false`. The column is nullable and was
4234
+ * added long after these rows started existing, so it is NULL on every
4235
+ * rule in every existing install; testing for false would classify all of
4236
+ * them as neither rules nor opt-outs and report a fully configured user
4237
+ * as having nothing to lose. This is the exact dual of the predicate
4238
+ * readiness folds with, and it has to stay that way.
4239
+ */
4240
+ const isOptOut = rule.isOptOut === true;
4241
+ const scope = PAGING_RULE_TYPE_SCOPES.find((candidate) => {
4242
+ return candidate.ruleType === rule.ruleType;
4243
+ });
4244
+ if (scope) {
4245
+ const severityId = rule[scope.severityColumn];
4246
+ /*
4247
+ * A severity-scoped rule with a NULL severity matches no page at
4248
+ * runtime, so it covers no cell and losing it costs nothing. Counting
4249
+ * it would promise coverage that never existed.
4250
+ */
4251
+ if (!severityId) {
4252
+ return;
4253
+ }
4254
+ const key = `${scope.ruleType}|${severityId.toString()}`;
4255
+ const state = cellStates.get(key) || {
4256
+ ruleType: scope.ruleType,
4257
+ severityKind: scope.severityKind,
4258
+ severityId: severityId.toString(),
4259
+ rulesBefore: 0,
4260
+ rulesRemoved: 0,
4261
+ hasOptOut: false,
4262
+ };
4263
+ if (isOptOut) {
4264
+ /*
4265
+ * Only a SURVIVING opt-out makes the silence deliberate. Deleting the
4266
+ * opt-out along with the last rule leaves neither, and that cell is a
4267
+ * real gap however it was created.
4268
+ */
4269
+ if (!isDeleted) {
4270
+ state.hasOptOut = true;
4271
+ }
4272
+ }
4273
+ else {
4274
+ state.rulesBefore++;
4275
+ if (isDeleted) {
4276
+ state.rulesRemoved++;
4277
+ }
4278
+ }
4279
+ cellStates.set(key, state);
4280
+ return;
4281
+ }
4282
+ const handoffType = HANDOFF_RULE_TYPES.find((candidate) => {
4283
+ return candidate === rule.ruleType;
4284
+ });
4285
+ if (!handoffType) {
4286
+ // Not a rule type anything pages on. It covers nothing, so it loses nothing.
4287
+ return;
4288
+ }
4289
+ const handoffState = handoffStates.get(handoffType) || {
4290
+ rulesBefore: 0,
4291
+ rulesRemoved: 0,
4292
+ hasOptOut: false,
4293
+ };
4294
+ if (isOptOut) {
4295
+ if (!isDeleted) {
4296
+ handoffState.hasOptOut = true;
4297
+ }
4298
+ }
4299
+ else {
4300
+ handoffState.rulesBefore++;
4301
+ if (isDeleted) {
4302
+ handoffState.rulesRemoved++;
4303
+ }
4304
+ }
4305
+ handoffStates.set(handoffType, handoffState);
4306
+ };
4307
+ const isTruncated = await this.readEveryNotificationRuleForUser({
4308
+ projectId: data.projectId,
4309
+ userId: data.userId,
4310
+ consume: (rows) => {
4311
+ for (const rule of rows) {
4312
+ foldRule(rule);
4313
+ }
4314
+ },
4315
+ });
4316
+ const lostCells = Array.from(cellStates.values()).filter((state) => {
4317
+ return (state.rulesBefore > 0 &&
4318
+ state.rulesRemoved === state.rulesBefore &&
4319
+ !state.hasOptOut);
4320
+ });
4321
+ const handoffNotificationsLost = HANDOFF_RULE_TYPES.filter((ruleType) => {
4322
+ const state = handoffStates.get(ruleType);
4323
+ return Boolean(state &&
4324
+ state.rulesBefore > 0 &&
4325
+ state.rulesRemoved === state.rulesBefore &&
4326
+ !state.hasOptOut);
4327
+ });
4328
+ /*
4329
+ * Severity names are read only when something was actually lost. The common
4330
+ * case for this call is "nothing you care about goes", and that case should
4331
+ * not cost two extra round trips to name cells nobody will be shown.
4332
+ */
4333
+ const severityNames = await this.loadSeverityNamesForCells(data.projectId, lostCells);
4334
+ const coverageLost = this.buildCoverageLossCells(lostCells, severityNames);
4335
+ /*
4336
+ * "Is this person on call anywhere" comes from OnCallReadinessService and
4337
+ * from nowhere else. getReadinessForUsers rather than getReadinessForUser
4338
+ * because the plural form OMITS a user who is not a member of the project
4339
+ * instead of throwing for them: somebody being removed from a project is
4340
+ * one of the perfectly legitimate reasons their methods are being deleted,
4341
+ * and that must produce an honest "unknown" rather than an exception in
4342
+ * front of an admin doing housekeeping.
4343
+ */
4344
+ const readinessList = await OnCallReadinessService.getReadinessForUsers([data.userId], data.projectId);
4345
+ const readiness = readinessList[0];
4346
+ const verifiedMethods = ((readiness === null || readiness === void 0 ? void 0 : readiness.methods) || []).filter((method) => {
4347
+ return method.isVerified;
4348
+ });
4349
+ const remainingVerifiedMethods = [
4350
+ ...verifiedMethods,
4351
+ ];
4352
+ if (data.deletedMethod && data.deletedMethod.isVerified) {
4353
+ /*
4354
+ * Readiness returns one entry per method ROW, and the row being deleted is
4355
+ * identified by its CHANNEL rather than by its id. Removing exactly ONE
4356
+ * entry of that channel is what makes the count right for a user with two
4357
+ * verified SMS numbers who is deleting one of them: the other survives,
4358
+ * and so does the entry.
4359
+ *
4360
+ * ReadinessMethod does now carry methodId, so an exact match is available;
4361
+ * matching on the channel is kept because it is equivalent HERE and not
4362
+ * because the id is missing. The only consumer of this list is
4363
+ * resolveReachability, which reads its LENGTH and its methodTypes and
4364
+ * nothing else, so which of two same-channel entries is dropped cannot
4365
+ * change the answer. Anything added below that cares about a specific row
4366
+ * must match on methodId instead — this equivalence is a property of the
4367
+ * current consumer, not a licence.
4368
+ */
4369
+ const index = remainingVerifiedMethods.findIndex((method) => {
4370
+ var _a;
4371
+ return method.methodType === ((_a = data.deletedMethod) === null || _a === void 0 ? void 0 : _a.methodType);
4372
+ });
4373
+ if (index >= 0) {
4374
+ remainingVerifiedMethods.splice(index, 1);
4375
+ }
4376
+ }
4377
+ const reachability = this.resolveReachability(readiness, remainingVerifiedMethods);
4378
+ const project = await ProjectService.findOneById({
4379
+ id: data.projectId,
4380
+ select: {
4381
+ _id: true,
4382
+ disableOnCallNotificationFallback: true,
4383
+ },
4384
+ props: {
4385
+ isRoot: true,
4386
+ },
4387
+ });
4388
+ /*
4389
+ * Read exactly as readiness reads it, including what a missing project row
4390
+ * means. Agreeing with the readiness page matters more here than picking
4391
+ * the louder default independently would: two surfaces that disagree about
4392
+ * whether pages are dropped teach people to trust neither.
4393
+ */
4394
+ const isFallbackEnabled = !(project === null || project === void 0 ? void 0 : project.disableOnCallNotificationFallback);
4395
+ return {
4396
+ projectId: data.projectId,
4397
+ userId: data.userId,
4398
+ isOnCallResponder: Boolean(readiness && readiness.reachedVia.length > 0),
4399
+ reachedVia: (readiness === null || readiness === void 0 ? void 0 : readiness.reachedVia) || [],
4400
+ rulesDeletedCount: rulesDeletedCount,
4401
+ coverageLost: coverageLost,
4402
+ handoffNotificationsLost: handoffNotificationsLost,
4403
+ reachability: reachability,
4404
+ verifiedMethodCountAfterDeletion: remainingVerifiedMethods.length,
4405
+ isFallbackEnabled: isFallbackEnabled,
4406
+ isTruncated: isTruncated,
4407
+ warnings: this.buildDeletionWarnings({
4408
+ deletedMethod: data.deletedMethod,
4409
+ rulesDeletedCount: rulesDeletedCount,
4410
+ readiness: readiness,
4411
+ reachability: reachability,
4412
+ remainingVerifiedMethods: remainingVerifiedMethods,
4413
+ coverageLost: coverageLost,
4414
+ handoffNotificationsLost: handoffNotificationsLost,
4415
+ isFallbackEnabled: isFallbackEnabled,
4416
+ isTruncated: isTruncated,
4417
+ }),
4418
+ };
4419
+ }
4420
+ /**
4421
+ * Every notification rule this user has in this project, one page at a time.
4422
+ *
4423
+ * Returns whether the read was TRUNCATED rather than throwing or silently
4424
+ * stopping. A truncated read here is not symmetric in its consequences:
4425
+ * unread rules that would have survived make this over-warn, which costs a
4426
+ * moment of an admin's attention, while unread rules that would have been
4427
+ * DELETED make it under-warn, which is the whole failure this feature exists
4428
+ * to prevent. Either way the caller is told.
4429
+ *
4430
+ * The sort is `_id` ascending because OFFSET paging over a query with no
4431
+ * total order can return one row twice and skip another — and the default
4432
+ * sort would be `createdAt DESC`, which is emphatically not unique for a
4433
+ * user whose default rules were all written in one transaction.
4434
+ */
4435
+ async readEveryNotificationRuleForUser(data) {
4436
+ let skip = 0;
4437
+ for (let page = 0; page < MAX_DELETION_IMPACT_PAGES; page++) {
4438
+ const rows = await this.findBy({
4439
+ query: {
4440
+ projectId: data.projectId,
4441
+ userId: data.userId,
4442
+ },
4443
+ select: {
4444
+ _id: true,
4445
+ ruleType: true,
4446
+ incidentSeverityId: true,
4447
+ alertSeverityId: true,
4448
+ isOptOut: true,
4449
+ userEmailId: true,
4450
+ userSmsId: true,
4451
+ userCallId: true,
4452
+ userPushId: true,
4453
+ userWhatsAppId: true,
4454
+ userTelegramId: true,
4455
+ userWebhookId: true,
4456
+ },
4457
+ sort: {
4458
+ _id: SortOrder.Ascending,
4459
+ },
4460
+ limit: DELETION_IMPACT_PAGE_SIZE,
4461
+ skip: skip,
4462
+ props: {
4463
+ isRoot: true,
4464
+ },
4465
+ });
4466
+ data.consume(rows);
4467
+ if (rows.length < DELETION_IMPACT_PAGE_SIZE) {
4468
+ return false;
4469
+ }
4470
+ skip += rows.length;
4471
+ }
4472
+ logger.error(`UserNotificationRuleService stopped reading notification rules for user ${data.userId.toString()} in project ${data.projectId.toString()} after ${MAX_DELETION_IMPACT_PAGES} pages of ${DELETION_IMPACT_PAGE_SIZE} rows. The deletion impact for this user is INCOMPLETE and may understate what the deletion removes.`);
4473
+ return true;
4474
+ }
4475
+ /**
4476
+ * Display names for the severities of the cells that are actually lost, in
4477
+ * the project's own severity order.
4478
+ *
4479
+ * Not paged, unlike the rule read: severity lists are a handful of rows per
4480
+ * project by construction (they are a UI-managed enumeration, not user data),
4481
+ * and LIMIT_PER_PROJECT is three orders of magnitude past any of them.
4482
+ */
4483
+ async loadSeverityNamesForCells(projectId, lostCells) {
4484
+ const severityNames = new Map();
4485
+ const needsIncident = lostCells.some((cell) => {
4486
+ return cell.severityKind === SeverityKind.Incident;
4487
+ });
4488
+ const needsAlert = lostCells.some((cell) => {
4489
+ return cell.severityKind === SeverityKind.Alert;
4490
+ });
4491
+ if (needsIncident) {
4492
+ const incidentSeverities = await IncidentSeverityService.findBy({
4493
+ query: {
4494
+ projectId: projectId,
4495
+ },
4496
+ select: {
4497
+ _id: true,
4498
+ name: true,
4499
+ },
4500
+ sort: {
4501
+ order: SortOrder.Ascending,
4502
+ },
4503
+ limit: LIMIT_PER_PROJECT,
4504
+ skip: 0,
4505
+ props: {
4506
+ isRoot: true,
4507
+ },
4508
+ });
4509
+ incidentSeverities.forEach((severity, index) => {
4510
+ if (!severity.id) {
4511
+ return;
4512
+ }
4513
+ severityNames.set(this.severityNameKey(SeverityKind.Incident, severity.id.toString()), {
4514
+ name: severity.name || "Unnamed Severity",
4515
+ rank: index,
4516
+ });
4517
+ });
4518
+ }
4519
+ if (needsAlert) {
4520
+ const alertSeverities = await AlertSeverityService.findBy({
4521
+ query: {
4522
+ projectId: projectId,
4523
+ },
4524
+ select: {
4525
+ _id: true,
4526
+ name: true,
4527
+ },
4528
+ sort: {
4529
+ order: SortOrder.Ascending,
4530
+ },
4531
+ limit: LIMIT_PER_PROJECT,
4532
+ skip: 0,
4533
+ props: {
4534
+ isRoot: true,
4535
+ },
4536
+ });
4537
+ alertSeverities.forEach((severity, index) => {
4538
+ if (!severity.id) {
4539
+ return;
4540
+ }
4541
+ severityNames.set(this.severityNameKey(SeverityKind.Alert, severity.id.toString()), {
4542
+ name: severity.name || "Unnamed Severity",
4543
+ rank: index,
4544
+ });
4545
+ });
4546
+ }
4547
+ return severityNames;
4548
+ }
4549
+ severityNameKey(kind, severityId) {
4550
+ /*
4551
+ * Keyed by KIND as well as by id. Incident and alert severities are
4552
+ * different tables with independently generated ids, and a map keyed on the
4553
+ * id alone would let one name a cell of the other kind if the two ever
4554
+ * collided — a one-in-a-uuid event that would be indistinguishable from a
4555
+ * mislabelled warning if it happened.
4556
+ */
4557
+ return `${kind}|${severityId}`;
4558
+ }
4559
+ buildCoverageLossCells(lostCells, severityNames) {
4560
+ const ordered = [...lostCells].sort((a, b) => {
4561
+ const ruleTypeDifference = this.pagingRuleTypeRank(a.ruleType) -
4562
+ this.pagingRuleTypeRank(b.ruleType);
4563
+ if (ruleTypeDifference !== 0) {
4564
+ return ruleTypeDifference;
4565
+ }
4566
+ /*
4567
+ * Severity order, not alphabetical. "Sev1, Sev2, Sev3" happens to sort
4568
+ * both ways; "Critical, High, Low" does not, and a warning that lists
4569
+ * severities in an order the user has never seen them in reads as a
4570
+ * different set of severities.
4571
+ */
4572
+ return (this.severityRank(a, severityNames) -
4573
+ this.severityRank(b, severityNames));
4574
+ });
4575
+ return ordered.map((cell) => {
4576
+ const ref = severityNames.get(this.severityNameKey(cell.severityKind, cell.severityId));
4577
+ return {
4578
+ ruleType: cell.ruleType,
4579
+ severityId: new ObjectID(cell.severityId),
4580
+ severityName: ref === null || ref === void 0 ? void 0 : ref.name,
4581
+ rulesRemoved: cell.rulesRemoved,
4582
+ };
4583
+ });
4584
+ }
4585
+ pagingRuleTypeRank(ruleType) {
4586
+ const index = PAGING_RULE_TYPE_SCOPES.findIndex((scope) => {
4587
+ return scope.ruleType === ruleType;
4588
+ });
4589
+ return index < 0 ? PAGING_RULE_TYPE_SCOPES.length : index;
4590
+ }
4591
+ severityRank(cell, severityNames) {
4592
+ const ref = severityNames.get(this.severityNameKey(cell.severityKind, cell.severityId));
4593
+ /*
4594
+ * A severity we could not name sorts last rather than first. It is the one
4595
+ * entry whose sentence will read "this severity", and burying it under the
4596
+ * ones that read properly costs nothing; leading with it looks like a bug.
4597
+ */
4598
+ return ref ? ref.rank : Number.MAX_SAFE_INTEGER;
4599
+ }
4600
+ resolveReachability(readiness, remainingVerifiedMethods) {
4601
+ if (!readiness) {
4602
+ return PostDeletionReachability.Unknown;
4603
+ }
4604
+ /*
4605
+ * Checked first, and it is not merely a nicety of wording. Readiness says
4606
+ * NotReachable when the user has no USABLE method — which includes the case
4607
+ * where every verified method they own is on a channel the project has
4608
+ * switched off — so this branch is also what keeps the branches below from
4609
+ * promising "still reachable" on the strength of a verified method that
4610
+ * nothing can send on.
4611
+ */
4612
+ if (readiness.status === ReadinessStatus.NotReachable) {
4613
+ return PostDeletionReachability.AlreadyNotReachable;
4614
+ }
4615
+ if (remainingVerifiedMethods.length === 0) {
4616
+ return PostDeletionReachability.NotReachable;
4617
+ }
4618
+ const unswitchableChannels = channelsWithNoProjectSwitch();
4619
+ const hasUnswitchableChannel = remainingVerifiedMethods.some((method) => {
4620
+ return unswitchableChannels.includes(method.methodType);
4621
+ });
4622
+ if (hasUnswitchableChannel) {
4623
+ return PostDeletionReachability.Reachable;
4624
+ }
4625
+ return PostDeletionReachability.DependsOnProjectSettings;
4626
+ }
4627
+ /**
4628
+ * The sentences a human reads in the confirmation dialog, most consequential
4629
+ * first.
4630
+ *
4631
+ * Every line names a specific thing that is lost AND what happens because of
4632
+ * it, which is the same contract OnCallReadinessService.buildReasons keeps
4633
+ * and for the same reason: "no rule for Sev4" is a shrug, "no rule for Sev4,
4634
+ * and those pages are dropped" is a decision. Nothing here says "are you
4635
+ * sure?" — the caller owns the question, and this owns the facts it is asked
4636
+ * about.
4637
+ */
4638
+ buildDeletionWarnings(data) {
4639
+ const warnings = [];
4640
+ /*
4641
+ * The cascade goes first because it is the part nobody clicked. Everything
4642
+ * below is a consequence of it, and a list that started with the
4643
+ * consequences would read as though the notification rules were being
4644
+ * deleted for no reason at all.
4645
+ */
4646
+ if (data.deletedMethod && data.rulesDeletedCount > 0) {
4647
+ warnings.push(`Deleting this ${data.deletedMethod.methodType} notification method also deletes ${data.rulesDeletedCount} notification ${data.rulesDeletedCount === 1 ? "rule" : "rules"} that use it - a notification rule cannot outlive the method it sends on.`);
4648
+ }
4649
+ if (data.reachability === PostDeletionReachability.NotReachable) {
4650
+ warnings.push("This is the last verified notification method on this account - after it there is nothing left to page this user on, and the on-call fallback has nothing to fall back to either.");
4651
+ }
4652
+ if (!data.readiness) {
4653
+ warnings.push("Whether this user is on call could not be determined - they may no longer be a member of this project.");
4654
+ }
4655
+ else if (data.readiness.reachedVia.length > 0) {
4656
+ const sources = data.readiness.reachedVia.map((source) => {
4657
+ return responderSourceProse(source);
4658
+ });
4659
+ warnings.push(`This user is on call in this project (${sources.join(", ")}), so anything lost here is a page that does not arrive.`);
4660
+ }
4661
+ else {
4662
+ /*
4663
+ * Said out loud rather than left as silence. "Not on call" is the one
4664
+ * answer that makes this whole dialog safe to click through, and an admin
4665
+ * who has to infer it from the absence of a warning will infer it wrongly
4666
+ * at least once.
4667
+ */
4668
+ warnings.push("This user is not on any on-call policy right now, so nothing here can cost a page today - it will if they are ever added to one.");
4669
+ }
4670
+ for (const scope of PAGING_RULE_TYPE_SCOPES) {
4671
+ const severityNames = data.coverageLost
4672
+ .filter((cell) => {
4673
+ return cell.ruleType === scope.ruleType;
4674
+ })
4675
+ .map((cell) => {
4676
+ return cell.severityName || "this severity";
4677
+ });
4678
+ if (severityNames.length === 0) {
4679
+ continue;
4680
+ }
4681
+ /*
4682
+ * One sentence per rule type listing its severities, not one per cell. A
4683
+ * user deleting a method that carried all their default rules would
4684
+ * otherwise be handed one line per (rule type x severity) — sixteen
4685
+ * near-identical sentences that nobody reads to the end of.
4686
+ */
4687
+ const subject = `After this, no rule covers ${severityNames.join(", ")} ${scope.subjectNoun}`;
4688
+ if (data.isFallbackEnabled) {
4689
+ warnings.push(`${subject} - those pages fall back to whatever this user has verified, which is not what they configured`);
4690
+ }
4691
+ else {
4692
+ warnings.push(`${subject} - those pages are dropped, because on-call fallback is disabled for this project`);
4693
+ }
4694
+ }
4695
+ if (data.handoffNotificationsLost.includes(NotificationRuleType.WHEN_USER_GOES_ON_CALL)) {
4696
+ warnings.push("This user will no longer be told when they go on call.");
4697
+ }
4698
+ if (data.handoffNotificationsLost.includes(NotificationRuleType.WHEN_USER_GOES_OFF_CALL)) {
4699
+ warnings.push("This user will no longer be told when they go off call.");
4700
+ }
4701
+ if (data.reachability === PostDeletionReachability.AlreadyNotReachable) {
4702
+ warnings.push("This user already has no usable notification method, so nothing can page them today either - this deletion is not what breaks it.");
4703
+ }
4704
+ if (data.reachability === PostDeletionReachability.DependsOnProjectSettings) {
4705
+ const channels = [];
4706
+ for (const method of data.remainingVerifiedMethods) {
4707
+ if (!channels.includes(method.methodType)) {
4708
+ channels.push(method.methodType);
4709
+ }
4710
+ }
4711
+ warnings.push(`Every verified method left on this account is on a channel the project can switch off (${channels.join(", ")}) - check On-Call > Readiness to confirm this user can still be paged.`);
4712
+ }
4713
+ if (data.isTruncated) {
4714
+ warnings.push("This preview is incomplete - there are more notification rules on this account than it could read, so the real loss may be larger than what is listed here.");
4715
+ }
4716
+ return warnings;
4717
+ }
2150
4718
  }
2151
4719
  __decorate([
2152
4720
  CaptureSpan(),
@@ -2154,6 +4722,12 @@ __decorate([
2154
4722
  __metadata("design:paramtypes", [ObjectID, Object]),
2155
4723
  __metadata("design:returntype", Promise)
2156
4724
  ], Service.prototype, "executeNotificationRuleItem", null);
4725
+ __decorate([
4726
+ CaptureSpan(),
4727
+ __metadata("design:type", Function),
4728
+ __metadata("design:paramtypes", [Object]),
4729
+ __metadata("design:returntype", Promise)
4730
+ ], Service.prototype, "executeFallbackNotification", null);
2157
4731
  __decorate([
2158
4732
  CaptureSpan(),
2159
4733
  __metadata("design:type", Function),
@@ -2178,6 +4752,14 @@ __decorate([
2178
4752
  ObjectID]),
2179
4753
  __metadata("design:returntype", Promise)
2180
4754
  ], Service.prototype, "generateCallTemplateForAlertEpisodeCreated", null);
4755
+ __decorate([
4756
+ CaptureSpan(),
4757
+ __metadata("design:type", Function),
4758
+ __metadata("design:paramtypes", [Phone,
4759
+ IncidentEpisode,
4760
+ ObjectID]),
4761
+ __metadata("design:returntype", Promise)
4762
+ ], Service.prototype, "generateCallTemplateForIncidentEpisodeCreated", null);
2181
4763
  __decorate([
2182
4764
  CaptureSpan(),
2183
4765
  __metadata("design:type", Function),
@@ -2202,6 +4784,14 @@ __decorate([
2202
4784
  ObjectID]),
2203
4785
  __metadata("design:returntype", Promise)
2204
4786
  ], Service.prototype, "generateSmsTemplateForAlertEpisodeCreated", null);
4787
+ __decorate([
4788
+ CaptureSpan(),
4789
+ __metadata("design:type", Function),
4790
+ __metadata("design:paramtypes", [Phone,
4791
+ IncidentEpisode,
4792
+ ObjectID]),
4793
+ __metadata("design:returntype", Promise)
4794
+ ], Service.prototype, "generateSmsTemplateForIncidentEpisodeCreated", null);
2205
4795
  __decorate([
2206
4796
  CaptureSpan(),
2207
4797
  __metadata("design:type", Function),
@@ -2223,6 +4813,13 @@ __decorate([
2223
4813
  ObjectID]),
2224
4814
  __metadata("design:returntype", Promise)
2225
4815
  ], Service.prototype, "generateTelegramBodyForAlertEpisodeCreated", null);
4816
+ __decorate([
4817
+ CaptureSpan(),
4818
+ __metadata("design:type", Function),
4819
+ __metadata("design:paramtypes", [IncidentEpisode,
4820
+ ObjectID]),
4821
+ __metadata("design:returntype", Promise)
4822
+ ], Service.prototype, "generateTelegramBodyForIncidentEpisodeCreated", null);
2226
4823
  __decorate([
2227
4824
  CaptureSpan(),
2228
4825
  __metadata("design:type", Function),
@@ -2247,6 +4844,14 @@ __decorate([
2247
4844
  ObjectID]),
2248
4845
  __metadata("design:returntype", Promise)
2249
4846
  ], Service.prototype, "generateWhatsAppTemplateForAlertEpisodeCreated", null);
4847
+ __decorate([
4848
+ CaptureSpan(),
4849
+ __metadata("design:type", Function),
4850
+ __metadata("design:paramtypes", [Phone,
4851
+ IncidentEpisode,
4852
+ ObjectID]),
4853
+ __metadata("design:returntype", Promise)
4854
+ ], Service.prototype, "generateWhatsAppTemplateForIncidentEpisodeCreated", null);
2250
4855
  __decorate([
2251
4856
  CaptureSpan(),
2252
4857
  __metadata("design:type", Function),
@@ -2271,6 +4876,14 @@ __decorate([
2271
4876
  ObjectID]),
2272
4877
  __metadata("design:returntype", Promise)
2273
4878
  ], Service.prototype, "generateEmailTemplateForAlertEpisodeCreated", null);
4879
+ __decorate([
4880
+ CaptureSpan(),
4881
+ __metadata("design:type", Function),
4882
+ __metadata("design:paramtypes", [Email,
4883
+ IncidentEpisode,
4884
+ ObjectID]),
4885
+ __metadata("design:returntype", Promise)
4886
+ ], Service.prototype, "generateEmailTemplateForIncidentEpisodeCreated", null);
2274
4887
  __decorate([
2275
4888
  CaptureSpan(),
2276
4889
  __metadata("design:type", Function),
@@ -2289,6 +4902,36 @@ __decorate([
2289
4902
  __metadata("design:paramtypes", [Object]),
2290
4903
  __metadata("design:returntype", Promise)
2291
4904
  ], Service.prototype, "onBeforeCreate", null);
4905
+ __decorate([
4906
+ CaptureSpan(),
4907
+ __metadata("design:type", Function),
4908
+ __metadata("design:paramtypes", [Object, Model]),
4909
+ __metadata("design:returntype", Promise)
4910
+ ], Service.prototype, "onCreateSuccess", null);
4911
+ __decorate([
4912
+ CaptureSpan(),
4913
+ __metadata("design:type", Function),
4914
+ __metadata("design:paramtypes", [Object]),
4915
+ __metadata("design:returntype", Promise)
4916
+ ], Service.prototype, "onBeforeUpdate", null);
4917
+ __decorate([
4918
+ CaptureSpan(),
4919
+ __metadata("design:type", Function),
4920
+ __metadata("design:paramtypes", [Object, Array]),
4921
+ __metadata("design:returntype", Promise)
4922
+ ], Service.prototype, "onUpdateSuccess", null);
4923
+ __decorate([
4924
+ CaptureSpan(),
4925
+ __metadata("design:type", Function),
4926
+ __metadata("design:paramtypes", [Object]),
4927
+ __metadata("design:returntype", Promise)
4928
+ ], Service.prototype, "onBeforeDelete", null);
4929
+ __decorate([
4930
+ CaptureSpan(),
4931
+ __metadata("design:type", Function),
4932
+ __metadata("design:paramtypes", [Object, Array]),
4933
+ __metadata("design:returntype", Promise)
4934
+ ], Service.prototype, "onDeleteSuccess", null);
2292
4935
  __decorate([
2293
4936
  CaptureSpan(),
2294
4937
  __metadata("design:type", Function),
@@ -2303,5 +4946,17 @@ __decorate([
2303
4946
  Email]),
2304
4947
  __metadata("design:returntype", Promise)
2305
4948
  ], Service.prototype, "addDefaultNotificationRuleForUser", null);
4949
+ __decorate([
4950
+ CaptureSpan(),
4951
+ __metadata("design:type", Function),
4952
+ __metadata("design:paramtypes", [Object]),
4953
+ __metadata("design:returntype", Promise)
4954
+ ], Service.prototype, "getRuleDeletionImpact", null);
4955
+ __decorate([
4956
+ CaptureSpan(),
4957
+ __metadata("design:type", Function),
4958
+ __metadata("design:paramtypes", [Object]),
4959
+ __metadata("design:returntype", Promise)
4960
+ ], Service.prototype, "getNotificationMethodDeletionImpact", null);
2306
4961
  export default new Service();
2307
4962
  //# sourceMappingURL=UserNotificationRuleService.js.map