@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
@@ -0,0 +1,1457 @@
1
+ import UserOnCallLogService from "../../../Server/Services/UserOnCallLogService";
2
+ import UserNotificationRuleService, {
3
+ FallbackNotificationOutcome,
4
+ } from "../../../Server/Services/UserNotificationRuleService";
5
+ import OnCallDutyPolicyExecutionLogTimelineService from "../../../Server/Services/OnCallDutyPolicyExecutionLogTimelineService";
6
+ import IncidentService from "../../../Server/Services/IncidentService";
7
+ import AlertService from "../../../Server/Services/AlertService";
8
+ import AlertEpisodeService from "../../../Server/Services/AlertEpisodeService";
9
+ import IncidentEpisodeService from "../../../Server/Services/IncidentEpisodeService";
10
+ import ProjectService from "../../../Server/Services/ProjectService";
11
+ import UserService from "../../../Server/Services/UserService";
12
+ import Project from "../../../Models/DatabaseModels/Project";
13
+ import CreateBy from "../../../Server/Types/Database/CreateBy";
14
+ import UpdateBy from "../../../Server/Types/Database/UpdateBy";
15
+ import { OnCreate, OnUpdate } from "../../../Server/Types/Database/Hooks";
16
+ import Model from "../../../Models/DatabaseModels/UserOnCallLog";
17
+ import UserNotificationRule from "../../../Models/DatabaseModels/UserNotificationRule";
18
+ import ObjectID from "../../../Types/ObjectID";
19
+ import PositiveNumber from "../../../Types/PositiveNumber";
20
+ import BadDataException from "../../../Types/Exception/BadDataException";
21
+ import NotificationRuleType from "../../../Types/NotificationRule/NotificationRuleType";
22
+ import OnCallDutyExecutionLogTimelineStatus from "../../../Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus";
23
+ import UserNotificationEventType from "../../../Types/UserNotification/UserNotificationEventType";
24
+ import UserNotificationExecutionStatus from "../../../Types/UserNotification/UserNotificationExecutionStatus";
25
+ import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
26
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
27
+
28
+ /*
29
+ * UserOnCallLogService.onCreateSuccess is the moment a page is actually routed
30
+ * to a human. A UserOnCallLog row is written by the escalation machinery, and
31
+ * this hook decides whether the responder's phone rings.
32
+ *
33
+ * The branch these tests are mostly about USED to be a dead-end. When the
34
+ * responder had ZERO UserNotificationRule rows matching (ruleType x severity),
35
+ * the hook wrote `Error` plus a status message onto the log and onto the on-call
36
+ * timeline, and RETURNED. Nothing was sent. Nobody was emailed. The escalation
37
+ * timer kept running as if the person had simply chosen not to acknowledge. That
38
+ * is Gap C in Internal/Roadmap/OnCallNotificationReadiness.md.
39
+ *
40
+ * GAP C CLOSED IN PHASE 1. The zero-rule branch now asks three questions in
41
+ * order, and section (A) below pins all three:
42
+ *
43
+ * 1. Is there an isOptOut row for this exact (ruleType x severity)? Then the
44
+ * silence was asked for: status Completed, timeline Skipped, nothing sent.
45
+ * 2. Has the project set disableOnCallNotificationFallback? Then it wants the
46
+ * old behaviour, and it gets it verbatim - the same Error, the same
47
+ * sentence. That path is the reason the exact
48
+ * "No notification rules found for this user..." string is still pinned
49
+ * here, character for character.
50
+ * 3. Otherwise: the verified-method fallback. Delivered => status Executing
51
+ * plus "notified via fallback (<channels>)" and a Notification Sent
52
+ * timeline row. Nothing verified to deliver on => a loud, terminal Error
53
+ * that names the responder and says where to fix it.
54
+ *
55
+ * The rest of the file pins the surrounding machinery the fallback has to keep
56
+ * working: which severity id is threaded into the rule count for each of the
57
+ * four trigger kinds, the exact option bag handed to
58
+ * executeNotificationRuleItem, the event-type -> rule-type mapping, the
59
+ * execution-status -> timeline-status translation in onUpdateSuccess, and the
60
+ * unconditional `status = Scheduled` stamp in onBeforeCreate.
61
+ *
62
+ * No database is touched: every persistence call is a jest.spyOn stub, and the
63
+ * protected hooks are invoked directly through a cast, exactly as the
64
+ * neighbouring UserOnCallLog / TeamMember characterisation tests do.
65
+ */
66
+
67
+ const LOG_ID: ObjectID = new ObjectID("11111111-1111-4111-8111-111111111111");
68
+ const PROJECT_ID: ObjectID = new ObjectID(
69
+ "22222222-2222-4222-8222-222222222222",
70
+ );
71
+ const USER_ID: ObjectID = new ObjectID("33333333-3333-4333-8333-333333333333");
72
+ const TIMELINE_ID: ObjectID = new ObjectID(
73
+ "44444444-4444-4444-8444-444444444444",
74
+ );
75
+ const EXECUTION_LOG_ID: ObjectID = new ObjectID(
76
+ "55555555-5555-4555-8555-555555555555",
77
+ );
78
+ const POLICY_ID: ObjectID = new ObjectID(
79
+ "66666666-6666-4666-8666-666666666666",
80
+ );
81
+ const ESCALATION_RULE_ID: ObjectID = new ObjectID(
82
+ "77777777-7777-4777-8777-777777777777",
83
+ );
84
+ const TEAM_ID: ObjectID = new ObjectID("88888888-8888-4888-8888-888888888888");
85
+ const SCHEDULE_ID: ObjectID = new ObjectID(
86
+ "99999999-9999-4999-8999-999999999999",
87
+ );
88
+
89
+ const INCIDENT_ID: ObjectID = new ObjectID(
90
+ "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
91
+ );
92
+ const ALERT_ID: ObjectID = new ObjectID("bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb");
93
+ const ALERT_EPISODE_ID: ObjectID = new ObjectID(
94
+ "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
95
+ );
96
+ const INCIDENT_EPISODE_ID: ObjectID = new ObjectID(
97
+ "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
98
+ );
99
+
100
+ const INCIDENT_SEVERITY_ID: ObjectID = new ObjectID(
101
+ "e1e1e1e1-e1e1-4e1e-8e1e-e1e1e1e1e1e1",
102
+ );
103
+ const ALERT_SEVERITY_ID: ObjectID = new ObjectID(
104
+ "e2e2e2e2-e2e2-4e2e-8e2e-e2e2e2e2e2e2",
105
+ );
106
+ const EPISODE_ALERT_SEVERITY_ID: ObjectID = new ObjectID(
107
+ "e3e3e3e3-e3e3-4e3e-8e3e-e3e3e3e3e3e3",
108
+ );
109
+ const EPISODE_INCIDENT_SEVERITY_ID: ObjectID = new ObjectID(
110
+ "e4e4e4e4-e4e4-4e4e-8e4e-e4e4e4e4e4e4",
111
+ );
112
+
113
+ const RULE_A_ID: ObjectID = new ObjectID(
114
+ "f1f1f1f1-f1f1-4f1f-8f1f-f1f1f1f1f1f1",
115
+ );
116
+ const RULE_B_ID: ObjectID = new ObjectID(
117
+ "f2f2f2f2-f2f2-4f2f-8f2f-f2f2f2f2f2f2",
118
+ );
119
+
120
+ /*
121
+ * The literal the pre-fallback dead-end wrote, and still the literal written on
122
+ * the ONE path that asks for the old behaviour:
123
+ * project.disableOnCallNotificationFallback. Copied character-for-character from
124
+ * UserOnCallLogService's NO_NOTIFICATION_RULES_STATUS_MESSAGE - it is written to
125
+ * both the log and the timeline, and both copies must agree or they tell the
126
+ * operator different stories.
127
+ */
128
+ const NO_RULES_MESSAGE: string =
129
+ "No notification rules found for this user. User should add the rules in User Settings > On-Call Rules.";
130
+
131
+ /* The channel the stubbed fallback claims to have delivered on. */
132
+ const FALLBACK_CHANNEL: string = "Email";
133
+
134
+ /*
135
+ * A project row shaped the way the no-rule path reads it. `undefined` means the
136
+ * fallback is enabled, which is the product default.
137
+ */
138
+ function makeProject(disableOnCallNotificationFallback?: boolean): Project {
139
+ return {
140
+ _id: PROJECT_ID.toString(),
141
+ disableOnCallNotificationFallback: disableOnCallNotificationFallback,
142
+ } as unknown as Project;
143
+ }
144
+
145
+ interface StatusUpdateCall {
146
+ id: ObjectID;
147
+ data: {
148
+ status?: UserNotificationExecutionStatus | undefined;
149
+ statusMessage?: string | undefined;
150
+ };
151
+ props: { isRoot: boolean };
152
+ }
153
+
154
+ interface TimelineUpdateCall {
155
+ id: ObjectID;
156
+ data: {
157
+ status?: OnCallDutyExecutionLogTimelineStatus | undefined;
158
+ statusMessage?: string | undefined;
159
+ };
160
+ props: { isRoot: boolean };
161
+ }
162
+
163
+ interface RuleQueryCall {
164
+ query: {
165
+ userId: ObjectID;
166
+ projectId: ObjectID;
167
+ ruleType: NotificationRuleType;
168
+ notifyAfterMinutes?: number | undefined;
169
+ incidentSeverityId?: ObjectID | undefined;
170
+ alertSeverityId?: ObjectID | undefined;
171
+ };
172
+ skip: number;
173
+ limit: number;
174
+ props: { isRoot: boolean };
175
+ }
176
+
177
+ interface ExecuteOptions {
178
+ projectId: ObjectID;
179
+ userNotificationLogId: ObjectID;
180
+ userNotificationEventType: UserNotificationEventType;
181
+ triggeredByIncidentId?: ObjectID | undefined;
182
+ triggeredByAlertId?: ObjectID | undefined;
183
+ triggeredByAlertEpisodeId?: ObjectID | undefined;
184
+ triggeredByIncidentEpisodeId?: ObjectID | undefined;
185
+ onCallPolicyExecutionLogId?: ObjectID | undefined;
186
+ onCallPolicyId?: ObjectID | undefined;
187
+ onCallPolicyEscalationRuleId?: ObjectID | undefined;
188
+ userBelongsToTeamId?: ObjectID | undefined;
189
+ onCallDutyPolicyExecutionLogTimelineId?: ObjectID | undefined;
190
+ onCallScheduleId?: ObjectID | undefined;
191
+ }
192
+
193
+ // A persisted UserOnCallLog row as onCreateSuccess receives it.
194
+ function makeCreatedLog(overrides: Record<string, unknown> = {}): Model {
195
+ return {
196
+ id: LOG_ID,
197
+ _id: LOG_ID.toString(),
198
+ projectId: PROJECT_ID,
199
+ userId: USER_ID,
200
+ userNotificationEventType: UserNotificationEventType.IncidentCreated,
201
+ onCallDutyPolicyExecutionLogTimelineId: TIMELINE_ID,
202
+ onCallDutyPolicyExecutionLogId: EXECUTION_LOG_ID,
203
+ onCallDutyPolicyId: POLICY_ID,
204
+ onCallDutyPolicyEscalationRuleId: ESCALATION_RULE_ID,
205
+ userBelongsToTeamId: TEAM_ID,
206
+ onCallDutyScheduleId: SCHEDULE_ID,
207
+ ...overrides,
208
+ } as unknown as Model;
209
+ }
210
+
211
+ function makeRule(id: ObjectID): UserNotificationRule {
212
+ return {
213
+ id: id,
214
+ _id: id.toString(),
215
+ } as unknown as UserNotificationRule;
216
+ }
217
+
218
+ /*
219
+ * The four shapes of on-call log the escalation machinery can produce, each
220
+ * paired with the event type the caller sets and the severity id the hook is
221
+ * supposed to thread into the rule count.
222
+ */
223
+ const INCIDENT_LOG: Record<string, unknown> = {
224
+ triggeredByIncidentId: INCIDENT_ID,
225
+ userNotificationEventType: UserNotificationEventType.IncidentCreated,
226
+ };
227
+ const ALERT_LOG: Record<string, unknown> = {
228
+ triggeredByAlertId: ALERT_ID,
229
+ userNotificationEventType: UserNotificationEventType.AlertCreated,
230
+ };
231
+ const ALERT_EPISODE_LOG: Record<string, unknown> = {
232
+ triggeredByAlertEpisodeId: ALERT_EPISODE_ID,
233
+ userNotificationEventType: UserNotificationEventType.AlertEpisodeCreated,
234
+ };
235
+ const INCIDENT_EPISODE_LOG: Record<string, unknown> = {
236
+ triggeredByIncidentEpisodeId: INCIDENT_EPISODE_ID,
237
+ userNotificationEventType: UserNotificationEventType.IncidentEpisodeCreated,
238
+ };
239
+
240
+ // Call the protected hooks without widening the service's public surface.
241
+ function callOnCreateSuccess(createdItem: Model): Promise<Model> {
242
+ const onCreate: OnCreate<Model> = {
243
+ createBy: {
244
+ data: createdItem,
245
+ props: { isRoot: true },
246
+ } as unknown as CreateBy<Model>,
247
+ carryForward: null,
248
+ };
249
+
250
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
251
+ return (UserOnCallLogService as any).onCreateSuccess(onCreate, createdItem);
252
+ }
253
+
254
+ function callOnUpdateSuccess(
255
+ data: Record<string, unknown>,
256
+ ): Promise<OnUpdate<Model>> {
257
+ const onUpdate: OnUpdate<Model> = {
258
+ updateBy: {
259
+ query: { _id: LOG_ID.toString() },
260
+ data: data,
261
+ props: { isRoot: true },
262
+ limit: 1,
263
+ skip: 0,
264
+ } as unknown as UpdateBy<Model>,
265
+ carryForward: null,
266
+ };
267
+
268
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
269
+ return (UserOnCallLogService as any).onUpdateSuccess(onUpdate, [LOG_ID]);
270
+ }
271
+
272
+ function callOnBeforeCreate(
273
+ createBy: CreateBy<Model>,
274
+ ): Promise<OnCreate<Model>> {
275
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
276
+ return (UserOnCallLogService as any).onBeforeCreate(createBy);
277
+ }
278
+
279
+ let logUpdateSpy: jest.SpyInstance;
280
+ let timelineUpdateSpy: jest.SpyInstance;
281
+ let countBySpy: jest.SpyInstance;
282
+ let ruleFindBySpy: jest.SpyInstance;
283
+ let ruleFindOneBySpy: jest.SpyInstance;
284
+ let executeRuleSpy: jest.SpyInstance;
285
+ let fallbackSpy: jest.SpyInstance;
286
+ let projectFindOneByIdSpy: jest.SpyInstance;
287
+ let userFindOneByIdSpy: jest.SpyInstance;
288
+
289
+ function statusUpdates(): Array<StatusUpdateCall> {
290
+ return logUpdateSpy.mock.calls.map(
291
+ (call: Array<unknown>): StatusUpdateCall => {
292
+ return call[0] as StatusUpdateCall;
293
+ },
294
+ );
295
+ }
296
+
297
+ function timelineUpdates(): Array<TimelineUpdateCall> {
298
+ return timelineUpdateSpy.mock.calls.map(
299
+ (call: Array<unknown>): TimelineUpdateCall => {
300
+ return call[0] as TimelineUpdateCall;
301
+ },
302
+ );
303
+ }
304
+
305
+ beforeEach(() => {
306
+ logUpdateSpy = jest
307
+ .spyOn(UserOnCallLogService, "updateOneById")
308
+ .mockResolvedValue(undefined as never);
309
+
310
+ timelineUpdateSpy = jest
311
+ .spyOn(OnCallDutyPolicyExecutionLogTimelineService, "updateOneById")
312
+ .mockResolvedValue(undefined as never);
313
+
314
+ // Zero rules is the default for this file - it is the branch under test.
315
+ countBySpy = jest
316
+ .spyOn(UserNotificationRuleService, "countBy")
317
+ .mockResolvedValue(new PositiveNumber(0) as never);
318
+
319
+ ruleFindBySpy = jest
320
+ .spyOn(UserNotificationRuleService, "findBy")
321
+ .mockResolvedValue([] as never);
322
+
323
+ /*
324
+ * The opt-out lookup. "No opt-out row" is the default for this file: the
325
+ * interesting default is the one where a page SHOULD go out.
326
+ */
327
+ ruleFindOneBySpy = jest
328
+ .spyOn(UserNotificationRuleService, "findOneBy")
329
+ .mockResolvedValue(null as never);
330
+
331
+ executeRuleSpy = jest
332
+ .spyOn(UserNotificationRuleService, "executeNotificationRuleItem")
333
+ .mockResolvedValue(undefined as never);
334
+
335
+ // Fallback enabled (the product default), and it reaches somebody.
336
+ projectFindOneByIdSpy = jest
337
+ .spyOn(ProjectService, "findOneById")
338
+ .mockResolvedValue(makeProject() as never);
339
+
340
+ fallbackSpy = jest
341
+ .spyOn(UserNotificationRuleService, "executeFallbackNotification")
342
+ .mockResolvedValue({
343
+ notified: true,
344
+ channelsUsed: [FALLBACK_CHANNEL],
345
+ } as never);
346
+
347
+ /*
348
+ * Only ever read to put a name in a failure message, so a null user is a
349
+ * perfectly good default - the message degrades to "This responder".
350
+ */
351
+ userFindOneByIdSpy = jest
352
+ .spyOn(UserService, "findOneById")
353
+ .mockResolvedValue(null as never);
354
+
355
+ jest.spyOn(IncidentService, "findOneById").mockResolvedValue({
356
+ incidentSeverityId: INCIDENT_SEVERITY_ID,
357
+ } as never);
358
+ jest.spyOn(AlertService, "findOneById").mockResolvedValue({
359
+ alertSeverityId: ALERT_SEVERITY_ID,
360
+ } as never);
361
+ jest.spyOn(AlertEpisodeService, "findOneById").mockResolvedValue({
362
+ alertSeverityId: EPISODE_ALERT_SEVERITY_ID,
363
+ } as never);
364
+ jest.spyOn(IncidentEpisodeService, "findOneById").mockResolvedValue({
365
+ incidentSeverityId: EPISODE_INCIDENT_SEVERITY_ID,
366
+ } as never);
367
+ });
368
+
369
+ afterEach(() => {
370
+ jest.restoreAllMocks();
371
+ });
372
+
373
+ /*
374
+ * ------------------------------------------------------------------------- *
375
+ * (A) onCreateSuccess - the "no rules" branch, and the three answers it now
376
+ * has for it.
377
+ * -------------------------------------------------------------------------
378
+ */
379
+
380
+ describe("UserOnCallLogService.onCreateSuccess - zero matching notification rules", () => {
381
+ /*
382
+ * GAP C CLOSED - these assertions are the inverted ones.
383
+ *
384
+ * Every test in this describe block used to assert that a responder with no
385
+ * matching rule was silently dropped: log Error, timeline Error, nothing sent.
386
+ * They now assert the opposite - that the fallback carries the page and both
387
+ * records say so.
388
+ */
389
+
390
+ test("the log ends at Executing, not at a terminal Error", async () => {
391
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
392
+
393
+ const updates: Array<StatusUpdateCall> = statusUpdates();
394
+
395
+ /*
396
+ * Still exactly two writes, in order: the hook flips the freshly-created row
397
+ * to Started, then - having reached somebody through the fallback - to
398
+ * Executing, which is where the normal path leaves it too. The worker's next
399
+ * tick finds no outstanding rules and closes it out as Completed.
400
+ */
401
+ expect(updates).toHaveLength(2);
402
+ expect(updates[0]!.data.status).toBe(
403
+ UserNotificationExecutionStatus.Started,
404
+ );
405
+ expect(updates[1]!.data.status).toBe(
406
+ UserNotificationExecutionStatus.Executing,
407
+ );
408
+ expect(updates[1]!.id.toString()).toBe(LOG_ID.toString());
409
+ expect(updates[1]!.props.isRoot).toBe(true);
410
+ });
411
+
412
+ test("the log's statusMessage names the fallback and the channel it used", async () => {
413
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
414
+
415
+ const updates: Array<StatusUpdateCall> = statusUpdates();
416
+
417
+ expect(updates[1]!.data.statusMessage).toContain(
418
+ "No notification rule configured for",
419
+ );
420
+ expect(updates[1]!.data.statusMessage).toContain("notified via fallback");
421
+ expect(updates[1]!.data.statusMessage).toContain(FALLBACK_CHANNEL);
422
+ // The old dead-end sentence is not written on this path at all.
423
+ expect(updates[1]!.data.statusMessage).not.toBe(NO_RULES_MESSAGE);
424
+ });
425
+
426
+ test("the on-call timeline says Notification Sent with the SAME message", async () => {
427
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
428
+
429
+ const timeline: Array<TimelineUpdateCall> = timelineUpdates();
430
+ const updates: Array<StatusUpdateCall> = statusUpdates();
431
+
432
+ expect(timeline).toHaveLength(1);
433
+ expect(timeline[0]!.id.toString()).toBe(TIMELINE_ID.toString());
434
+ expect(timeline[0]!.data.status).toBe(
435
+ OnCallDutyExecutionLogTimelineStatus.NotificationSent,
436
+ );
437
+ /*
438
+ * The log and the timeline must keep telling the operator the same story -
439
+ * that was true of the dead-end's two copies and it stays true here.
440
+ */
441
+ expect(timeline[0]!.data.statusMessage).toBe(
442
+ updates[1]!.data.statusMessage,
443
+ );
444
+ expect(timeline[0]!.props.isRoot).toBe(true);
445
+ });
446
+
447
+ test("the opt-out lookup asks for exactly the cell the count came up empty for", async () => {
448
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
449
+
450
+ expect(ruleFindOneBySpy).toHaveBeenCalledTimes(1);
451
+ const query: Record<string, unknown> = (
452
+ ruleFindOneBySpy.mock.calls[0]![0] as { query: Record<string, unknown> }
453
+ ).query;
454
+
455
+ expect((query["userId"] as ObjectID).toString()).toBe(USER_ID.toString());
456
+ expect((query["projectId"] as ObjectID).toString()).toBe(
457
+ PROJECT_ID.toString(),
458
+ );
459
+ expect(query["ruleType"]).toBe(
460
+ NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
461
+ );
462
+ expect((query["incidentSeverityId"] as ObjectID).toString()).toBe(
463
+ INCIDENT_SEVERITY_ID.toString(),
464
+ );
465
+ expect(query["isOptOut"]).toBe(true);
466
+ });
467
+
468
+ test("an opt-out row means intentional silence: Completed, Skipped, nothing sent", async () => {
469
+ ruleFindOneBySpy.mockResolvedValue(makeRule(RULE_A_ID) as never);
470
+
471
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
472
+
473
+ const updates: Array<StatusUpdateCall> = statusUpdates();
474
+ const timeline: Array<TimelineUpdateCall> = timelineUpdates();
475
+
476
+ expect(updates[1]!.data.status).toBe(
477
+ UserNotificationExecutionStatus.Completed,
478
+ );
479
+ expect(updates[1]!.data.statusMessage).toContain("opted out");
480
+ expect(timeline[0]!.data.status).toBe(
481
+ OnCallDutyExecutionLogTimelineStatus.Skipped,
482
+ );
483
+
484
+ /*
485
+ * The point of the opt-out row: it is checked BEFORE the project setting and
486
+ * before any delivery, so nothing is dispatched and no project read is even
487
+ * needed.
488
+ */
489
+ expect(fallbackSpy).not.toHaveBeenCalled();
490
+ expect(projectFindOneByIdSpy).not.toHaveBeenCalled();
491
+ expect(executeRuleSpy).not.toHaveBeenCalled();
492
+ });
493
+
494
+ test("disableOnCallNotificationFallback restores the old dead-end, word for word", async () => {
495
+ projectFindOneByIdSpy.mockResolvedValue(makeProject(true) as never);
496
+
497
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
498
+
499
+ const updates: Array<StatusUpdateCall> = statusUpdates();
500
+ const timeline: Array<TimelineUpdateCall> = timelineUpdates();
501
+
502
+ expect(updates).toHaveLength(2);
503
+ expect(updates[1]!.data.status).toBe(UserNotificationExecutionStatus.Error);
504
+ expect(updates[1]!.data.statusMessage).toBe(NO_RULES_MESSAGE);
505
+ expect(updates[1]!.data.statusMessage).toContain(
506
+ "User Settings > On-Call Rules",
507
+ );
508
+
509
+ expect(timeline).toHaveLength(1);
510
+ expect(timeline[0]!.data.status).toBe(
511
+ OnCallDutyExecutionLogTimelineStatus.Error,
512
+ );
513
+ expect(timeline[0]!.data.statusMessage).toBe(NO_RULES_MESSAGE);
514
+
515
+ // A project that opted out of the fallback is never billed for one either.
516
+ expect(fallbackSpy).not.toHaveBeenCalled();
517
+ });
518
+
519
+ test("a responder with nothing verified ends as an Error that says what to do", async () => {
520
+ /*
521
+ * The outcome is what separates the two Error endings, so it has to be set
522
+ * here. notified:false alone means only "nothing went out"; it is
523
+ * NoUsableNotificationMethod that says the responder is unreachable rather
524
+ * than that a send blew up, and the two get different messages because they
525
+ * need different actions from whoever reads the log.
526
+ */
527
+ fallbackSpy.mockResolvedValue({
528
+ notified: false,
529
+ outcome: FallbackNotificationOutcome.NoUsableNotificationMethod,
530
+ channelsUsed: [],
531
+ } as never);
532
+
533
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
534
+
535
+ const updates: Array<StatusUpdateCall> = statusUpdates();
536
+ const timeline: Array<TimelineUpdateCall> = timelineUpdates();
537
+
538
+ expect(updates[1]!.data.status).toBe(UserNotificationExecutionStatus.Error);
539
+ expect(updates[1]!.data.statusMessage).toContain(
540
+ "has no verified notification method",
541
+ );
542
+ expect(updates[1]!.data.statusMessage).toContain(
543
+ "User Settings > Notification Methods",
544
+ );
545
+ expect(timeline[0]!.data.status).toBe(
546
+ OnCallDutyExecutionLogTimelineStatus.Error,
547
+ );
548
+
549
+ /*
550
+ * The responder is read TWICE on this path, and both reads are wanted.
551
+ * This function reads them for their name, to put a person rather than a
552
+ * uuid in the status message. OnCallNotificationAlertingService then reads
553
+ * them again for their email, because it emails the responder directly -
554
+ * and it has to do its own read regardless, since the
555
+ * disableOnCallNotificationFallback branch reaches it without ever
556
+ * computing a label. Threading the row through to save one query would
557
+ * couple a fire-and-forget notifier to the paging path it must never be
558
+ * able to slow down or break.
559
+ */
560
+ expect(userFindOneByIdSpy).toHaveBeenCalledTimes(2);
561
+ });
562
+
563
+ test("a fallback that throws is contained: the escalation is not taken down with it", async () => {
564
+ /*
565
+ * The caller is part-way through paging a whole escalation level. If this
566
+ * hook threw, every responder queued behind this one would be skipped.
567
+ */
568
+ fallbackSpy.mockRejectedValue(new Error("smtp exploded") as never);
569
+
570
+ const created: Model = makeCreatedLog(INCIDENT_LOG);
571
+
572
+ await expect(callOnCreateSuccess(created)).resolves.toBe(created);
573
+
574
+ const updates: Array<StatusUpdateCall> = statusUpdates();
575
+
576
+ /*
577
+ * Error, and deliberately so. The tempting alternative - leave the log
578
+ * Executing so "the next tick retries" - buys no retry at all: nothing
579
+ * re-enters this hook. What it would actually buy is
580
+ * ExecutePendingExecutions picking the log up, finding no due rules (there
581
+ * were none, which is why the fallback ran), and closing it out as
582
+ * Completed - which onUpdateSuccess renders on the escalation timeline as
583
+ * "Alert Sent" for a page nobody received.
584
+ */
585
+ expect(updates[1]!.data.status).toBe(UserNotificationExecutionStatus.Error);
586
+ expect(updates[1]!.data.statusMessage).toContain(
587
+ "the fallback delivery attempt failed",
588
+ );
589
+ expect(updates[1]!.data.statusMessage).toContain("was not delivered");
590
+ });
591
+
592
+ test("executeNotificationRuleItem is NEVER called - there is no rule to execute", async () => {
593
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
594
+
595
+ /*
596
+ * Unchanged, and it is not a contradiction of the fallback: the fallback
597
+ * builds its own unsaved rules and delivers them itself, precisely because
598
+ * there is no persisted rule to hand to executeNotificationRuleItem.
599
+ */
600
+ expect(executeRuleSpy).not.toHaveBeenCalled();
601
+ });
602
+
603
+ test("the immediate-rule lookup is never even attempted (early return)", async () => {
604
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
605
+
606
+ /*
607
+ * countBy answered zero, so the hook returns before the notifyAfterMinutes
608
+ * findBy. The fallback lives between those two points, and reaches for the
609
+ * opt-out row with findOneBy rather than widening this count.
610
+ */
611
+ expect(countBySpy).toHaveBeenCalledTimes(1);
612
+ expect(ruleFindBySpy).not.toHaveBeenCalled();
613
+ });
614
+
615
+ test("the hook returns the created item unchanged", async () => {
616
+ const created: Model = makeCreatedLog(INCIDENT_LOG);
617
+
618
+ const returned: Model = await callOnCreateSuccess(created);
619
+
620
+ expect(returned).toBe(created);
621
+ });
622
+
623
+ test("the timeline never says 'Alert Sent' - the fallback message is more specific", async () => {
624
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
625
+
626
+ const sawAlertSent: boolean = timelineUpdates().some(
627
+ (update: TimelineUpdateCall): boolean => {
628
+ return update.data.statusMessage === "Alert Sent";
629
+ },
630
+ );
631
+
632
+ /*
633
+ * The normal path's timeline message is the generic "Alert Sent". The
634
+ * fallback deliberately writes something an operator can act on instead, so
635
+ * a fallback delivery is never mistaken for a configured one.
636
+ */
637
+ expect(sawAlertSent).toBe(false);
638
+ });
639
+ });
640
+
641
+ describe("UserOnCallLogService.onCreateSuccess - severity threading per trigger kind", () => {
642
+ /*
643
+ * Each trigger kind loads its own parent entity and threads THAT entity's
644
+ * severity id into the rule count. Getting the wrong key here would count
645
+ * rules for a severity the incident does not have, which reads as "no rules" -
646
+ * and now sends the responder down the fallback path for a cell they are in
647
+ * fact configured for, which is a subtler bug than the old dead-end, not a
648
+ * milder one.
649
+ */
650
+
651
+ test.each<
652
+ [
653
+ string,
654
+ Record<string, unknown>,
655
+ NotificationRuleType,
656
+ "incidentSeverityId" | "alertSeverityId",
657
+ ObjectID,
658
+ ]
659
+ >([
660
+ [
661
+ "incident",
662
+ INCIDENT_LOG,
663
+ NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
664
+ "incidentSeverityId",
665
+ INCIDENT_SEVERITY_ID,
666
+ ],
667
+ [
668
+ "alert",
669
+ ALERT_LOG,
670
+ NotificationRuleType.ON_CALL_EXECUTED_ALERT,
671
+ "alertSeverityId",
672
+ ALERT_SEVERITY_ID,
673
+ ],
674
+ [
675
+ "alert episode",
676
+ ALERT_EPISODE_LOG,
677
+ NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE,
678
+ "alertSeverityId",
679
+ EPISODE_ALERT_SEVERITY_ID,
680
+ ],
681
+ [
682
+ "incident episode",
683
+ INCIDENT_EPISODE_LOG,
684
+ NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE,
685
+ "incidentSeverityId",
686
+ EPISODE_INCIDENT_SEVERITY_ID,
687
+ ],
688
+ ])(
689
+ "a %s-triggered log counts rules by its own severity, then falls back on zero",
690
+ async (
691
+ _label: string,
692
+ overrides: Record<string, unknown>,
693
+ expectedRuleType: NotificationRuleType,
694
+ severityKey: "incidentSeverityId" | "alertSeverityId",
695
+ expectedSeverityId: ObjectID,
696
+ ): Promise<void> => {
697
+ await callOnCreateSuccess(makeCreatedLog(overrides));
698
+
699
+ expect(countBySpy).toHaveBeenCalledTimes(1);
700
+ const call: RuleQueryCall = countBySpy.mock.calls[0]![0] as RuleQueryCall;
701
+
702
+ expect(call.query.userId.toString()).toBe(USER_ID.toString());
703
+ expect(call.query.projectId.toString()).toBe(PROJECT_ID.toString());
704
+ expect(call.query.ruleType).toBe(expectedRuleType);
705
+ expect(call.query[severityKey]!.toString()).toBe(
706
+ expectedSeverityId.toString(),
707
+ );
708
+ expect(call.skip).toBe(0);
709
+ expect(call.limit).toBe(LIMIT_PER_PROJECT);
710
+ expect(call.props.isRoot).toBe(true);
711
+
712
+ /*
713
+ * GAP C CLOSED - all four trigger kinds now reach the fallback, and each
714
+ * hands it ITS OWN rule type so the fallback reasons about the right cell.
715
+ */
716
+ const fallbackOptions: Record<string, unknown> = fallbackSpy.mock
717
+ .calls[0]![0] as Record<string, unknown>;
718
+ expect(fallbackOptions["ruleType"]).toBe(expectedRuleType);
719
+
720
+ const updates: Array<StatusUpdateCall> = statusUpdates();
721
+ expect(updates[updates.length - 1]!.data.status).toBe(
722
+ UserNotificationExecutionStatus.Executing,
723
+ );
724
+ expect(updates[updates.length - 1]!.data.statusMessage).toContain(
725
+ "notified via fallback",
726
+ );
727
+ expect(executeRuleSpy).not.toHaveBeenCalled();
728
+ },
729
+ );
730
+
731
+ test("the count query carries only the severity key for its own trigger kind", async () => {
732
+ await callOnCreateSuccess(makeCreatedLog(ALERT_LOG));
733
+
734
+ const call: RuleQueryCall = countBySpy.mock.calls[0]![0] as RuleQueryCall;
735
+
736
+ /*
737
+ * An alert-triggered log queries alertSeverityId ONLY - incidentSeverityId
738
+ * is not part of the object at all, so the count is not accidentally
739
+ * narrowed by an incident severity.
740
+ *
741
+ * isOptOut is the fifth key and is load-bearing: without it an opt-out row
742
+ * counts as a matching rule, the zero-rule branch is never entered, and the
743
+ * immediate-rule lookup then executes the opt-out as though it were a real
744
+ * rule - sending nothing while the escalation timeline records
745
+ * "Alert Sent". Removing it from this list would let that regression back
746
+ * in silently.
747
+ */
748
+ expect(Object.keys(call.query).sort()).toEqual([
749
+ "alertSeverityId",
750
+ "isOptOut",
751
+ "projectId",
752
+ "ruleType",
753
+ "userId",
754
+ ]);
755
+ });
756
+
757
+ test("a missing parent entity still counts with an undefined severity in the query", async () => {
758
+ /*
759
+ * findOneById returning null (deleted incident, or a read that raced the
760
+ * write) makes `incident?.incidentSeverityId` undefined. That is still
761
+ * passed straight through as the query value rather than being treated as an
762
+ * error - unchanged - so the count comes back zero.
763
+ *
764
+ * GAP C CLOSED - what changed is the consequence. The responder used to land
765
+ * in the silent dead-end; they now go to the fallback, which is the right
766
+ * answer for a race: somebody is on call for something, and losing the
767
+ * parent row is no reason to lose the page too.
768
+ */
769
+ jest.spyOn(IncidentService, "findOneById").mockResolvedValue(null as never);
770
+
771
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
772
+
773
+ const call: RuleQueryCall = countBySpy.mock.calls[0]![0] as RuleQueryCall;
774
+ expect(call.query.incidentSeverityId).toBeUndefined();
775
+
776
+ const updates: Array<StatusUpdateCall> = statusUpdates();
777
+ expect(updates[updates.length - 1]!.data.status).toBe(
778
+ UserNotificationExecutionStatus.Executing,
779
+ );
780
+ expect(fallbackSpy).toHaveBeenCalledTimes(1);
781
+ });
782
+
783
+ test("a log with no trigger id at all counts nothing and falls back", async () => {
784
+ /*
785
+ * ruleCount starts at PositiveNumber(0) and none of the four `if` blocks
786
+ * run, so countBy is never called and the hook falls straight into the
787
+ * zero-rule branch.
788
+ *
789
+ * GAP C CLOSED - and out the other side into the fallback rather than an
790
+ * Error.
791
+ */
792
+ await callOnCreateSuccess(makeCreatedLog());
793
+
794
+ expect(countBySpy).not.toHaveBeenCalled();
795
+ const updates: Array<StatusUpdateCall> = statusUpdates();
796
+ expect(updates[updates.length - 1]!.data.status).toBe(
797
+ UserNotificationExecutionStatus.Executing,
798
+ );
799
+ expect(executeRuleSpy).not.toHaveBeenCalled();
800
+ expect(fallbackSpy).toHaveBeenCalledTimes(1);
801
+ });
802
+ });
803
+
804
+ describe("UserOnCallLogService.onCreateSuccess - at least one matching rule", () => {
805
+ beforeEach(() => {
806
+ countBySpy.mockResolvedValue(new PositiveNumber(1) as never);
807
+ ruleFindBySpy.mockResolvedValue([makeRule(RULE_A_ID)] as never);
808
+ });
809
+
810
+ test("executeNotificationRuleItem is called once per immediate rule", async () => {
811
+ ruleFindBySpy.mockResolvedValue([
812
+ makeRule(RULE_A_ID),
813
+ makeRule(RULE_B_ID),
814
+ ] as never);
815
+
816
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
817
+
818
+ expect(executeRuleSpy).toHaveBeenCalledTimes(2);
819
+ expect((executeRuleSpy.mock.calls[0]![0] as ObjectID).toString()).toBe(
820
+ RULE_A_ID.toString(),
821
+ );
822
+ expect((executeRuleSpy.mock.calls[1]![0] as ObjectID).toString()).toBe(
823
+ RULE_B_ID.toString(),
824
+ );
825
+ });
826
+
827
+ test("the log ends at Executing (with no statusMessage)", async () => {
828
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
829
+
830
+ const updates: Array<StatusUpdateCall> = statusUpdates();
831
+
832
+ expect(updates).toHaveLength(2);
833
+ expect(updates[0]!.data.status).toBe(
834
+ UserNotificationExecutionStatus.Started,
835
+ );
836
+ expect(updates[1]!.data.status).toBe(
837
+ UserNotificationExecutionStatus.Executing,
838
+ );
839
+ expect(updates[1]!.data.statusMessage).toBeUndefined();
840
+ });
841
+
842
+ test("the timeline is set to NotificationSent / 'Alert Sent'", async () => {
843
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
844
+
845
+ const timeline: Array<TimelineUpdateCall> = timelineUpdates();
846
+
847
+ expect(timeline).toHaveLength(1);
848
+ expect(timeline[0]!.id.toString()).toBe(TIMELINE_ID.toString());
849
+ expect(timeline[0]!.data.status).toBe(
850
+ OnCallDutyExecutionLogTimelineStatus.NotificationSent,
851
+ );
852
+ expect(timeline[0]!.data.statusMessage).toBe("Alert Sent");
853
+ });
854
+
855
+ test("every field of the executeNotificationRuleItem option bag is forwarded", async () => {
856
+ const created: Model = makeCreatedLog({
857
+ ...INCIDENT_LOG,
858
+ triggeredByAlertId: ALERT_ID,
859
+ triggeredByAlertEpisodeId: ALERT_EPISODE_ID,
860
+ triggeredByIncidentEpisodeId: INCIDENT_EPISODE_ID,
861
+ });
862
+
863
+ await callOnCreateSuccess(created);
864
+
865
+ expect(executeRuleSpy).toHaveBeenCalledTimes(1);
866
+ const options: ExecuteOptions = executeRuleSpy.mock
867
+ .calls[0]![1] as ExecuteOptions;
868
+
869
+ /*
870
+ * Exactly thirteen keys - four of them renamed on the way across
871
+ * (onCallDutyPolicy* -> onCallPolicy*, onCallDutySchedule -> onCallSchedule).
872
+ * A dropped key here is a page that cannot be attributed back to its
873
+ * escalation rule.
874
+ */
875
+ expect(Object.keys(options).sort()).toEqual([
876
+ "onCallDutyPolicyExecutionLogTimelineId",
877
+ "onCallPolicyEscalationRuleId",
878
+ "onCallPolicyExecutionLogId",
879
+ "onCallPolicyId",
880
+ "onCallScheduleId",
881
+ "projectId",
882
+ "triggeredByAlertEpisodeId",
883
+ "triggeredByAlertId",
884
+ "triggeredByIncidentEpisodeId",
885
+ "triggeredByIncidentId",
886
+ "userBelongsToTeamId",
887
+ "userNotificationEventType",
888
+ "userNotificationLogId",
889
+ ]);
890
+
891
+ expect(options.userNotificationLogId.toString()).toBe(LOG_ID.toString());
892
+ expect(options.projectId.toString()).toBe(PROJECT_ID.toString());
893
+ expect(options.triggeredByIncidentId!.toString()).toBe(
894
+ INCIDENT_ID.toString(),
895
+ );
896
+ expect(options.triggeredByAlertId!.toString()).toBe(ALERT_ID.toString());
897
+ expect(options.triggeredByAlertEpisodeId!.toString()).toBe(
898
+ ALERT_EPISODE_ID.toString(),
899
+ );
900
+ expect(options.triggeredByIncidentEpisodeId!.toString()).toBe(
901
+ INCIDENT_EPISODE_ID.toString(),
902
+ );
903
+ expect(options.userNotificationEventType).toBe(
904
+ UserNotificationEventType.IncidentCreated,
905
+ );
906
+ expect(options.onCallPolicyExecutionLogId!.toString()).toBe(
907
+ EXECUTION_LOG_ID.toString(),
908
+ );
909
+ expect(options.onCallPolicyId!.toString()).toBe(POLICY_ID.toString());
910
+ expect(options.onCallPolicyEscalationRuleId!.toString()).toBe(
911
+ ESCALATION_RULE_ID.toString(),
912
+ );
913
+ expect(options.userBelongsToTeamId!.toString()).toBe(TEAM_ID.toString());
914
+ expect(options.onCallDutyPolicyExecutionLogTimelineId!.toString()).toBe(
915
+ TIMELINE_ID.toString(),
916
+ );
917
+ expect(options.onCallScheduleId!.toString()).toBe(SCHEDULE_ID.toString());
918
+ });
919
+
920
+ test("optional ids that are absent on the log are forwarded as undefined, not dropped", async () => {
921
+ const created: Model = makeCreatedLog({
922
+ ...INCIDENT_LOG,
923
+ onCallDutyPolicyId: undefined,
924
+ onCallDutyScheduleId: undefined,
925
+ userBelongsToTeamId: undefined,
926
+ });
927
+
928
+ await callOnCreateSuccess(created);
929
+
930
+ const options: ExecuteOptions = executeRuleSpy.mock
931
+ .calls[0]![1] as ExecuteOptions;
932
+
933
+ expect(Object.keys(options)).toContain("onCallPolicyId");
934
+ expect(options.onCallPolicyId).toBeUndefined();
935
+ expect(options.onCallScheduleId).toBeUndefined();
936
+ expect(options.userBelongsToTeamId).toBeUndefined();
937
+ // The two required ids are still there.
938
+ expect(options.userNotificationLogId.toString()).toBe(LOG_ID.toString());
939
+ });
940
+
941
+ test("the immediate-rule query pins notifyAfterMinutes 0 and both severity keys", async () => {
942
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
943
+
944
+ expect(ruleFindBySpy).toHaveBeenCalledTimes(1);
945
+ const call: RuleQueryCall = ruleFindBySpy.mock
946
+ .calls[0]![0] as RuleQueryCall;
947
+
948
+ expect(call.query.notifyAfterMinutes).toBe(0);
949
+ expect(call.query.ruleType).toBe(
950
+ NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
951
+ );
952
+ expect(call.query.incidentSeverityId!.toString()).toBe(
953
+ INCIDENT_SEVERITY_ID.toString(),
954
+ );
955
+ /*
956
+ * Unlike the countBy query, the findBy query always carries BOTH severity
957
+ * keys; the one that does not apply is present with an undefined value.
958
+ */
959
+ expect(Object.keys(call.query).sort()).toEqual([
960
+ "alertSeverityId",
961
+ "incidentSeverityId",
962
+ "isOptOut",
963
+ "notifyAfterMinutes",
964
+ "projectId",
965
+ "ruleType",
966
+ "userId",
967
+ ]);
968
+ expect(call.query.alertSeverityId).toBeUndefined();
969
+ expect(call.limit).toBe(LIMIT_PER_PROJECT);
970
+ expect(call.props.isRoot).toBe(true);
971
+ });
972
+
973
+ test("an alert-episode log resolves alertSeverityId from the episode", async () => {
974
+ await callOnCreateSuccess(makeCreatedLog(ALERT_EPISODE_LOG));
975
+
976
+ const call: RuleQueryCall = ruleFindBySpy.mock
977
+ .calls[0]![0] as RuleQueryCall;
978
+
979
+ expect(call.query.alertSeverityId!.toString()).toBe(
980
+ EPISODE_ALERT_SEVERITY_ID.toString(),
981
+ );
982
+ expect(call.query.incidentSeverityId).toBeUndefined();
983
+ });
984
+
985
+ test("when a log carries both an alert and an alert episode, the alert wins", async () => {
986
+ const created: Model = makeCreatedLog({
987
+ ...ALERT_LOG,
988
+ triggeredByAlertEpisodeId: ALERT_EPISODE_ID,
989
+ });
990
+
991
+ await callOnCreateSuccess(created);
992
+
993
+ const call: RuleQueryCall = ruleFindBySpy.mock
994
+ .calls[0]![0] as RuleQueryCall;
995
+
996
+ // `alert && alert.alertSeverityId ? ... : alertEpisode ...` - alert first.
997
+ expect(call.query.alertSeverityId!.toString()).toBe(
998
+ ALERT_SEVERITY_ID.toString(),
999
+ );
1000
+ });
1001
+
1002
+ test("when a log carries both an incident and an incident episode, the incident wins", async () => {
1003
+ const created: Model = makeCreatedLog({
1004
+ ...INCIDENT_LOG,
1005
+ triggeredByIncidentEpisodeId: INCIDENT_EPISODE_ID,
1006
+ });
1007
+
1008
+ await callOnCreateSuccess(created);
1009
+
1010
+ const call: RuleQueryCall = ruleFindBySpy.mock
1011
+ .calls[0]![0] as RuleQueryCall;
1012
+
1013
+ expect(call.query.incidentSeverityId!.toString()).toBe(
1014
+ INCIDENT_SEVERITY_ID.toString(),
1015
+ );
1016
+ });
1017
+
1018
+ test("rules exist but none are immediate: nothing is sent, yet the log still says Executing", async () => {
1019
+ /*
1020
+ * The rule count includes DELAYED rules, so a user whose only rule is
1021
+ * "call me after 5 minutes" passes the zero-rule guard, sends nothing now,
1022
+ * and is left Executing for the worker to pick up on a later tick.
1023
+ */
1024
+ ruleFindBySpy.mockResolvedValue([] as never);
1025
+
1026
+ await callOnCreateSuccess(makeCreatedLog(INCIDENT_LOG));
1027
+
1028
+ expect(executeRuleSpy).not.toHaveBeenCalled();
1029
+ const updates: Array<StatusUpdateCall> = statusUpdates();
1030
+ expect(updates[updates.length - 1]!.data.status).toBe(
1031
+ UserNotificationExecutionStatus.Executing,
1032
+ );
1033
+ expect(timelineUpdates()[0]!.data.statusMessage).toBe("Alert Sent");
1034
+ });
1035
+
1036
+ test("the created item is returned on the happy path too", async () => {
1037
+ const created: Model = makeCreatedLog(INCIDENT_LOG);
1038
+
1039
+ const returned: Model = await callOnCreateSuccess(created);
1040
+
1041
+ expect(returned).toBe(created);
1042
+ });
1043
+ });
1044
+
1045
+ /*
1046
+ * ------------------------------------------------------------------------- *
1047
+ * (B) getNotificationRuleType - event type to rule type.
1048
+ * -------------------------------------------------------------------------
1049
+ */
1050
+
1051
+ describe("UserOnCallLogService.getNotificationRuleType", () => {
1052
+ test.each<[UserNotificationEventType, NotificationRuleType]>([
1053
+ [
1054
+ UserNotificationEventType.IncidentCreated,
1055
+ NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
1056
+ ],
1057
+ [
1058
+ UserNotificationEventType.AlertCreated,
1059
+ NotificationRuleType.ON_CALL_EXECUTED_ALERT,
1060
+ ],
1061
+ [
1062
+ UserNotificationEventType.AlertEpisodeCreated,
1063
+ NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE,
1064
+ ],
1065
+ [
1066
+ UserNotificationEventType.IncidentEpisodeCreated,
1067
+ NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE,
1068
+ ],
1069
+ ])(
1070
+ "%s maps to %s",
1071
+ (
1072
+ eventType: UserNotificationEventType,
1073
+ expected: NotificationRuleType,
1074
+ ): void => {
1075
+ expect(UserOnCallLogService.getNotificationRuleType(eventType)).toBe(
1076
+ expected,
1077
+ );
1078
+ },
1079
+ );
1080
+
1081
+ test("every member of UserNotificationEventType is mapped (no silent hole)", () => {
1082
+ const eventTypes: Array<UserNotificationEventType> = Object.values(
1083
+ UserNotificationEventType,
1084
+ );
1085
+
1086
+ expect(eventTypes).toHaveLength(4);
1087
+
1088
+ for (const eventType of eventTypes) {
1089
+ expect(() => {
1090
+ return UserOnCallLogService.getNotificationRuleType(eventType);
1091
+ }).not.toThrow();
1092
+ }
1093
+ });
1094
+
1095
+ test("an unknown event type throws BadDataException", () => {
1096
+ expect(() => {
1097
+ return UserOnCallLogService.getNotificationRuleType(
1098
+ "Something Else" as UserNotificationEventType,
1099
+ );
1100
+ }).toThrow(BadDataException);
1101
+ });
1102
+
1103
+ test("the unknown-event-type message names the problem", () => {
1104
+ expect(() => {
1105
+ return UserOnCallLogService.getNotificationRuleType(
1106
+ undefined as unknown as UserNotificationEventType,
1107
+ );
1108
+ }).toThrow("Invalid user notification event type.");
1109
+ });
1110
+
1111
+ test("the two non-on-call rule types are never produced by this mapping", () => {
1112
+ /*
1113
+ * WHEN_USER_GOES_ON_CALL / OFF_CALL rules exist on the same table. If this
1114
+ * mapping ever returned one, a shift-change rule would be counted as
1115
+ * incident coverage - the exact confusion the plan flags in Gap E for
1116
+ * TeamComplianceService, which ignores ruleType altogether.
1117
+ */
1118
+ const produced: Array<NotificationRuleType> = Object.values(
1119
+ UserNotificationEventType,
1120
+ ).map((eventType: UserNotificationEventType): NotificationRuleType => {
1121
+ return UserOnCallLogService.getNotificationRuleType(eventType);
1122
+ });
1123
+
1124
+ expect(produced).not.toContain(NotificationRuleType.WHEN_USER_GOES_ON_CALL);
1125
+ expect(produced).not.toContain(
1126
+ NotificationRuleType.WHEN_USER_GOES_OFF_CALL,
1127
+ );
1128
+ expect(new Set(produced).size).toBe(4);
1129
+ });
1130
+ });
1131
+
1132
+ /*
1133
+ * ------------------------------------------------------------------------- *
1134
+ * (C) onUpdateSuccess - execution status to timeline status.
1135
+ * -------------------------------------------------------------------------
1136
+ */
1137
+
1138
+ describe("UserOnCallLogService.onUpdateSuccess - status translation", () => {
1139
+ let logFindBySpy: jest.SpyInstance;
1140
+
1141
+ beforeEach(() => {
1142
+ logFindBySpy = jest
1143
+ .spyOn(UserOnCallLogService, "findBy")
1144
+ .mockResolvedValue([
1145
+ {
1146
+ onCallDutyPolicyExecutionLogTimelineId: TIMELINE_ID,
1147
+ },
1148
+ ] as never);
1149
+ });
1150
+
1151
+ test.each<
1152
+ [UserNotificationExecutionStatus, OnCallDutyExecutionLogTimelineStatus]
1153
+ >([
1154
+ [
1155
+ UserNotificationExecutionStatus.Completed,
1156
+ OnCallDutyExecutionLogTimelineStatus.NotificationSent,
1157
+ ],
1158
+ [
1159
+ UserNotificationExecutionStatus.Error,
1160
+ OnCallDutyExecutionLogTimelineStatus.Error,
1161
+ ],
1162
+ [
1163
+ UserNotificationExecutionStatus.Executing,
1164
+ OnCallDutyExecutionLogTimelineStatus.Executing,
1165
+ ],
1166
+ [
1167
+ UserNotificationExecutionStatus.Scheduled,
1168
+ OnCallDutyExecutionLogTimelineStatus.Started,
1169
+ ],
1170
+ [
1171
+ UserNotificationExecutionStatus.Started,
1172
+ OnCallDutyExecutionLogTimelineStatus.Started,
1173
+ ],
1174
+ ])(
1175
+ "%s on the log becomes %s on the timeline",
1176
+ async (
1177
+ status: UserNotificationExecutionStatus,
1178
+ expected: OnCallDutyExecutionLogTimelineStatus,
1179
+ ): Promise<void> => {
1180
+ await callOnUpdateSuccess({ status: status });
1181
+
1182
+ expect(timelineUpdateSpy).toHaveBeenCalledTimes(1);
1183
+ const call: TimelineUpdateCall = timelineUpdates()[0]!;
1184
+ expect(call.data.status).toBe(expected);
1185
+ expect(call.id.toString()).toBe(TIMELINE_ID.toString());
1186
+ expect(call.props.isRoot).toBe(true);
1187
+ },
1188
+ );
1189
+
1190
+ test("Scheduled and Started collapse to the same timeline status", async () => {
1191
+ /*
1192
+ * Two distinct execution states share one timeline state, so an operator
1193
+ * reading the timeline cannot tell "queued" from "picked up". Pinned so a
1194
+ * later change that splits them is a deliberate one.
1195
+ */
1196
+ await callOnUpdateSuccess({
1197
+ status: UserNotificationExecutionStatus.Scheduled,
1198
+ });
1199
+ await callOnUpdateSuccess({
1200
+ status: UserNotificationExecutionStatus.Started,
1201
+ });
1202
+
1203
+ const written: Array<OnCallDutyExecutionLogTimelineStatus | undefined> =
1204
+ timelineUpdates().map(
1205
+ (
1206
+ call: TimelineUpdateCall,
1207
+ ): OnCallDutyExecutionLogTimelineStatus | undefined => {
1208
+ return call.data.status;
1209
+ },
1210
+ );
1211
+
1212
+ expect(written).toEqual([
1213
+ OnCallDutyExecutionLogTimelineStatus.Started,
1214
+ OnCallDutyExecutionLogTimelineStatus.Started,
1215
+ ]);
1216
+ });
1217
+
1218
+ test("every member of UserNotificationExecutionStatus has a case - the default is unreachable for valid members", async () => {
1219
+ /*
1220
+ * The enum has exactly five members and the switch has exactly five cases,
1221
+ * so `default: throw new BadDataException("Invalid status")` cannot be hit
1222
+ * by a value that came out of the enum. It is reachable only when a caller
1223
+ * pushes a string that was never a member (an API payload, a migration, or
1224
+ * a cast) into `data.status`.
1225
+ */
1226
+ const all: Array<UserNotificationExecutionStatus> = Object.values(
1227
+ UserNotificationExecutionStatus,
1228
+ );
1229
+
1230
+ expect(all.sort()).toEqual([
1231
+ UserNotificationExecutionStatus.Completed,
1232
+ UserNotificationExecutionStatus.Error,
1233
+ UserNotificationExecutionStatus.Executing,
1234
+ UserNotificationExecutionStatus.Scheduled,
1235
+ UserNotificationExecutionStatus.Started,
1236
+ ]);
1237
+
1238
+ for (const status of all) {
1239
+ await expect(
1240
+ callOnUpdateSuccess({ status: status }),
1241
+ ).resolves.toBeDefined();
1242
+ }
1243
+
1244
+ expect(timelineUpdateSpy).toHaveBeenCalledTimes(all.length);
1245
+ });
1246
+
1247
+ test("a status that is not an enum member throws BadDataException", async () => {
1248
+ await expect(
1249
+ callOnUpdateSuccess({ status: "Cancelled" }),
1250
+ ).rejects.toBeInstanceOf(BadDataException);
1251
+ });
1252
+
1253
+ test("the invalid-status throw happens AFTER the row lookup and writes no timeline", async () => {
1254
+ /*
1255
+ * findBy runs before the switch, so an invalid status costs a query and
1256
+ * then aborts the whole update hook with nothing written.
1257
+ */
1258
+ await expect(callOnUpdateSuccess({ status: "Cancelled" })).rejects.toThrow(
1259
+ "Invalid status",
1260
+ );
1261
+
1262
+ expect(logFindBySpy).toHaveBeenCalledTimes(1);
1263
+ expect(timelineUpdateSpy).not.toHaveBeenCalled();
1264
+ });
1265
+
1266
+ test("an update without a status is a no-op: no lookup, no timeline write", async () => {
1267
+ await callOnUpdateSuccess({ statusMessage: "just a note" });
1268
+
1269
+ expect(logFindBySpy).not.toHaveBeenCalled();
1270
+ expect(timelineUpdateSpy).not.toHaveBeenCalled();
1271
+ });
1272
+
1273
+ test("the statusMessage rides along to the timeline verbatim", async () => {
1274
+ await callOnUpdateSuccess({
1275
+ status: UserNotificationExecutionStatus.Error,
1276
+ statusMessage: NO_RULES_MESSAGE,
1277
+ });
1278
+
1279
+ expect(timelineUpdates()[0]!.data.statusMessage).toBe(NO_RULES_MESSAGE);
1280
+ });
1281
+
1282
+ test("a status update with no statusMessage writes undefined over whatever the timeline had", async () => {
1283
+ /*
1284
+ * `statusMessage: onUpdate.updateBy.data.statusMessage!` is unconditional,
1285
+ * so a bare status change blanks the timeline's existing message rather
1286
+ * than leaving it alone.
1287
+ */
1288
+ await callOnUpdateSuccess({
1289
+ status: UserNotificationExecutionStatus.Executing,
1290
+ });
1291
+
1292
+ const call: TimelineUpdateCall = timelineUpdates()[0]!;
1293
+ expect(Object.keys(call.data)).toContain("statusMessage");
1294
+ expect(call.data.statusMessage).toBeUndefined();
1295
+ });
1296
+
1297
+ test("every matched log row gets its own timeline update", async () => {
1298
+ const otherTimelineId: ObjectID = new ObjectID(
1299
+ "0a0a0a0a-0a0a-4a0a-8a0a-0a0a0a0a0a0a",
1300
+ );
1301
+
1302
+ logFindBySpy.mockResolvedValue([
1303
+ { onCallDutyPolicyExecutionLogTimelineId: TIMELINE_ID },
1304
+ { onCallDutyPolicyExecutionLogTimelineId: otherTimelineId },
1305
+ ] as never);
1306
+
1307
+ await callOnUpdateSuccess({
1308
+ status: UserNotificationExecutionStatus.Completed,
1309
+ });
1310
+
1311
+ expect(timelineUpdateSpy).toHaveBeenCalledTimes(2);
1312
+ expect(
1313
+ timelineUpdates().map((call: TimelineUpdateCall): string => {
1314
+ return call.id.toString();
1315
+ }),
1316
+ ).toEqual([TIMELINE_ID.toString(), otherTimelineId.toString()]);
1317
+ });
1318
+
1319
+ test("no matching log rows means no timeline updates at all", async () => {
1320
+ logFindBySpy.mockResolvedValue([] as never);
1321
+
1322
+ await callOnUpdateSuccess({
1323
+ status: UserNotificationExecutionStatus.Completed,
1324
+ });
1325
+
1326
+ expect(timelineUpdateSpy).not.toHaveBeenCalled();
1327
+ });
1328
+
1329
+ test("a log row with no timeline id still issues an update, with an undefined id", async () => {
1330
+ /*
1331
+ * `id: item.onCallDutyPolicyExecutionLogTimelineId!` is a non-null
1332
+ * assertion over a genuinely optional column. Today the call is made with
1333
+ * `undefined` and the failure surfaces downstream rather than here.
1334
+ */
1335
+ logFindBySpy.mockResolvedValue([{}] as never);
1336
+
1337
+ await callOnUpdateSuccess({
1338
+ status: UserNotificationExecutionStatus.Completed,
1339
+ });
1340
+
1341
+ expect(timelineUpdateSpy).toHaveBeenCalledTimes(1);
1342
+ expect(timelineUpdates()[0]!.id).toBeUndefined();
1343
+ });
1344
+
1345
+ test("the row lookup reuses the caller's query and selects only the timeline id", async () => {
1346
+ await callOnUpdateSuccess({
1347
+ status: UserNotificationExecutionStatus.Completed,
1348
+ });
1349
+
1350
+ const call: {
1351
+ query: Record<string, unknown>;
1352
+ select: Record<string, unknown>;
1353
+ skip: number;
1354
+ limit: number;
1355
+ props: { isRoot: boolean };
1356
+ } = logFindBySpy.mock.calls[0]![0] as {
1357
+ query: Record<string, unknown>;
1358
+ select: Record<string, unknown>;
1359
+ skip: number;
1360
+ limit: number;
1361
+ props: { isRoot: boolean };
1362
+ };
1363
+
1364
+ expect(call.query["_id"]).toBe(LOG_ID.toString());
1365
+ expect(call.select["onCallDutyPolicyExecutionLogTimelineId"]).toBe(true);
1366
+ expect(call.skip).toBe(0);
1367
+ expect(call.limit).toBe(LIMIT_PER_PROJECT);
1368
+ expect(call.props.isRoot).toBe(true);
1369
+ });
1370
+
1371
+ test("the hook returns the onUpdate it was handed", async () => {
1372
+ const returned: OnUpdate<Model> = await callOnUpdateSuccess({
1373
+ status: UserNotificationExecutionStatus.Completed,
1374
+ });
1375
+
1376
+ expect(returned.updateBy.data.status).toBe(
1377
+ UserNotificationExecutionStatus.Completed,
1378
+ );
1379
+ });
1380
+ });
1381
+
1382
+ /*
1383
+ * ------------------------------------------------------------------------- *
1384
+ * (D) onBeforeCreate - the status stamp.
1385
+ * -------------------------------------------------------------------------
1386
+ */
1387
+
1388
+ describe("UserOnCallLogService.onBeforeCreate - status is always Scheduled", () => {
1389
+ function createBy(data: Record<string, unknown>): CreateBy<Model> {
1390
+ return {
1391
+ data: data as unknown as Model,
1392
+ props: { isRoot: true },
1393
+ } as unknown as CreateBy<Model>;
1394
+ }
1395
+
1396
+ test("a create with no status is stamped Scheduled", async () => {
1397
+ const create: CreateBy<Model> = createBy({ projectId: PROJECT_ID });
1398
+
1399
+ await callOnBeforeCreate(create);
1400
+
1401
+ expect(create.data.status).toBe(UserNotificationExecutionStatus.Scheduled);
1402
+ });
1403
+
1404
+ test.each<[UserNotificationExecutionStatus]>([
1405
+ [UserNotificationExecutionStatus.Started],
1406
+ [UserNotificationExecutionStatus.Executing],
1407
+ [UserNotificationExecutionStatus.Completed],
1408
+ [UserNotificationExecutionStatus.Error],
1409
+ ])(
1410
+ "a caller asking for %s is overridden back to Scheduled",
1411
+ async (status: UserNotificationExecutionStatus): Promise<void> => {
1412
+ /*
1413
+ * A row created already Completed (or already Error) would never be
1414
+ * picked up by ExecutePendingExecutions, so this stamp is what guarantees
1415
+ * the escalation actually enters the pipeline.
1416
+ */
1417
+ const create: CreateBy<Model> = createBy({ status: status });
1418
+
1419
+ await callOnBeforeCreate(create);
1420
+
1421
+ expect(create.data.status).toBe(
1422
+ UserNotificationExecutionStatus.Scheduled,
1423
+ );
1424
+ },
1425
+ );
1426
+
1427
+ test("a nonsense status string is also overridden", async () => {
1428
+ const create: CreateBy<Model> = createBy({ status: "Whatever" });
1429
+
1430
+ await callOnBeforeCreate(create);
1431
+
1432
+ expect(create.data.status).toBe(UserNotificationExecutionStatus.Scheduled);
1433
+ });
1434
+
1435
+ test("the hook returns the same createBy with a null carryForward", async () => {
1436
+ const create: CreateBy<Model> = createBy({ projectId: PROJECT_ID });
1437
+
1438
+ const result: OnCreate<Model> = await callOnBeforeCreate(create);
1439
+
1440
+ expect(result.createBy).toBe(create);
1441
+ expect(result.carryForward).toBeNull();
1442
+ });
1443
+
1444
+ test("nothing else on the create payload is touched", async () => {
1445
+ const create: CreateBy<Model> = createBy({
1446
+ projectId: PROJECT_ID,
1447
+ userId: USER_ID,
1448
+ statusMessage: "set by caller",
1449
+ });
1450
+
1451
+ await callOnBeforeCreate(create);
1452
+
1453
+ expect(create.data.projectId).toBe(PROJECT_ID);
1454
+ expect(create.data.userId).toBe(USER_ID);
1455
+ expect(create.data.statusMessage).toBe("set by caller");
1456
+ });
1457
+ });