@oneuptime/common 12.0.7 → 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 (873) 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} +184 -9
  9. package/Models/DatabaseModels/InventoryItemCustomField.ts +434 -0
  10. package/Models/DatabaseModels/{TelemetryEntityRelationship.ts → InventoryItemRelationship.ts} +33 -7
  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/WorkflowLog.ts +38 -0
  27. package/Models/DatabaseModels/WorkflowVariable.ts +12 -0
  28. package/Server/API/AIChatAPI.ts +315 -1
  29. package/Server/API/DashboardAPI.ts +217 -1
  30. package/Server/API/OnCallReadinessAPI.ts +841 -0
  31. package/Server/API/TeamComplianceAPI.ts +69 -17
  32. package/Server/API/TelemetryAPI.ts +220 -12
  33. package/Server/API/UserAPI.ts +16 -1
  34. package/Server/EnvironmentConfig.ts +52 -0
  35. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +22 -0
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +4 -4
  37. package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +5 -5
  38. package/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.ts +41 -0
  39. package/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.ts +17 -0
  40. package/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.ts +35 -0
  41. package/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.ts +82 -0
  42. package/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.ts +91 -0
  43. package/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.ts +47 -0
  44. package/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.ts +255 -0
  45. package/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.ts +107 -0
  46. package/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.ts +90 -0
  47. package/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.ts +39 -0
  48. package/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.ts +33 -0
  49. package/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.ts +59 -0
  50. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +24 -0
  51. package/Server/Infrastructure/Queue.ts +78 -13
  52. package/Server/Middleware/MasterAdminAuthorization.ts +11 -6
  53. package/Server/Middleware/PublicDashboardRateLimit.ts +593 -0
  54. package/Server/Services/AIService.ts +7 -0
  55. package/Server/Services/AlertEpisodeStateTimelineService.ts +29 -0
  56. package/Server/Services/AlertSeverityService.ts +63 -0
  57. package/Server/Services/DashboardService.ts +9 -10
  58. package/Server/Services/DatabaseService.ts +32 -2
  59. package/Server/Services/IncidentEpisodeStateTimelineService.ts +29 -0
  60. package/Server/Services/IncidentSeverityService.ts +76 -0
  61. package/Server/Services/Index.ts +12 -4
  62. package/Server/Services/InventoryItemCustomFieldService.ts +9 -0
  63. package/Server/Services/{TelemetryEntityRelationshipService.ts → InventoryItemRelationshipService.ts} +7 -4
  64. package/Server/Services/{TelemetryEntityService.ts → InventoryItemService.ts} +203 -21
  65. package/Server/Services/LogAggregationService.ts +45 -8
  66. package/Server/Services/MetricAggregationService.ts +121 -0
  67. package/Server/Services/MetricService.ts +7 -7
  68. package/Server/Services/NetworkDeviceLinkRuleService.ts +10 -0
  69. package/Server/Services/NetworkDeviceLinkService.ts +84 -0
  70. package/Server/Services/NetworkDeviceService.ts +140 -0
  71. package/Server/Services/NetworkSiteService.ts +77 -25
  72. package/Server/Services/NetworkTopologySuppressionService.ts +84 -0
  73. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +41 -29
  74. package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +8 -0
  75. package/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +62 -13
  76. package/Server/Services/OnCallDutyPolicyScheduleService.ts +61 -1
  77. package/Server/Services/OnCallNotificationAlertingService.ts +742 -0
  78. package/Server/Services/OnCallReadinessService.ts +2803 -0
  79. package/Server/Services/OnCallSetupReminderService.ts +955 -0
  80. package/Server/Services/ProfileAggregationService.ts +123 -0
  81. package/Server/Services/StatusPageService.ts +9 -10
  82. package/Server/Services/TeamComplianceService.ts +429 -252
  83. package/Server/Services/UserCallService.ts +26 -1
  84. package/Server/Services/UserEmailService.ts +26 -1
  85. package/Server/Services/UserNotificationRuleAdminService.ts +1183 -0
  86. package/Server/Services/UserNotificationRuleService.ts +3812 -333
  87. package/Server/Services/UserOnCallLogService.ts +561 -48
  88. package/Server/Services/UserPushService.ts +29 -0
  89. package/Server/Services/UserService.ts +11 -0
  90. package/Server/Services/UserSmsService.ts +26 -1
  91. package/Server/Services/UserTelegramService.ts +24 -1
  92. package/Server/Services/UserWebhookService.ts +28 -1
  93. package/Server/Services/UserWhatsAppService.ts +24 -1
  94. package/Server/Types/Database/Permissions/BasePermission.ts +19 -0
  95. package/Server/Types/Database/Permissions/CreatePermission.ts +164 -0
  96. package/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.ts +340 -0
  97. package/Server/Types/Database/Permissions/QueryPermission.ts +48 -0
  98. package/Server/Types/Database/Permissions/TenantPermission.ts +8 -1
  99. package/Server/Types/Workflow/Components/API/Delete.ts +1 -1
  100. package/Server/Types/Workflow/Components/API/Get.ts +1 -1
  101. package/Server/Types/Workflow/Components/API/Patch.ts +1 -1
  102. package/Server/Types/Workflow/Components/API/Post.ts +1 -1
  103. package/Server/Types/Workflow/Components/API/Put.ts +1 -1
  104. package/Server/Types/Workflow/Components/API/Utils.ts +44 -1
  105. package/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +29 -5
  106. package/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +18 -10
  107. package/Server/Types/Workflow/Components/BaseModel/ModelArguments.ts +55 -0
  108. package/Server/Types/Workflow/Components/Conditions/IfElse.ts +3 -17
  109. package/Server/Types/Workflow/Components/Email.ts +25 -7
  110. package/Server/Types/Workflow/Components/JavaScript.ts +10 -3
  111. package/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +1 -1
  112. package/Server/Types/Workflow/TriggerCode.ts +12 -0
  113. package/Server/Types/Workflow/Workflow.ts +5 -0
  114. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +643 -48
  115. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +32 -3
  116. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +20 -6
  117. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +7 -0
  118. package/Server/Utils/AI/Toolbox/AIActionTools.ts +2 -2
  119. package/Server/Utils/AI/Toolbox/AIMetaTools.ts +863 -0
  120. package/Server/Utils/AI/Toolbox/AlertTools.ts +177 -15
  121. package/Server/Utils/AI/Toolbox/IncidentTools.ts +191 -10
  122. package/Server/Utils/AI/Toolbox/Index.ts +48 -0
  123. package/Server/Utils/AI/Toolbox/MonitorTools.ts +298 -11
  124. package/Server/Utils/AI/Toolbox/NoteWriteTools.ts +295 -0
  125. package/Server/Utils/AI/Toolbox/OnCallTools.ts +1246 -0
  126. package/Server/Utils/AI/Toolbox/RunbookTools.ts +424 -0
  127. package/Server/Utils/AI/Toolbox/SloTools.ts +456 -0
  128. package/Server/Utils/AI/Toolbox/StatusPageTools.ts +559 -0
  129. package/Server/Utils/AI/Toolbox/TeamTools.ts +327 -0
  130. package/Server/Utils/AI/Toolbox/TimelineTools.ts +615 -0
  131. package/Server/Utils/AI/Toolbox/WorkflowProbeTools.ts +664 -0
  132. package/Server/Utils/ClientIp.ts +221 -0
  133. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +47 -0
  134. package/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.ts +163 -0
  135. package/Server/Utils/Dashboard/PublicDashboardSloWidget.ts +147 -0
  136. package/Server/Utils/Express.ts +12 -17
  137. package/Server/Utils/LLM/LLMService.ts +85 -8
  138. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +204 -10
  139. package/Server/Utils/SSRFProtection.ts +98 -23
  140. package/Server/Utils/StartServer.ts +12 -3
  141. package/Server/Utils/Telemetry/EntityRegistry.ts +205 -18
  142. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +689 -0
  143. package/Server/Utils/Telemetry/TelemetryEntity.ts +160 -52
  144. package/Server/Utils/VM/VMAPI.ts +56 -8
  145. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +7 -3
  146. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +2146 -0
  147. package/Tests/App/Dashboard/CreateWorkflowModal.test.tsx +561 -0
  148. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +2470 -0
  149. package/Tests/App/Dashboard/MonitorCriteriaAttributeFilter.test.tsx +574 -0
  150. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +1897 -0
  151. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +3606 -0
  152. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +784 -0
  153. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +1119 -0
  154. package/Tests/App/Dashboard/SloWidgetFetching.test.tsx +531 -0
  155. package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +1312 -0
  156. package/Tests/App/Dashboard/UserSettingsSetupChecklistPage.test.tsx +1390 -0
  157. package/Tests/Models/InventoryItemModel.test.ts +174 -0
  158. package/Tests/Models/InventoryItemNaming.test.ts +302 -0
  159. package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +437 -0
  160. package/Tests/Server/API/DashboardPublicRateLimit.test.ts +659 -0
  161. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +18 -0
  162. package/Tests/Server/API/DashboardPublicSloAPI.test.ts +880 -0
  163. package/Tests/Server/API/Helpers.ts +24 -15
  164. package/Tests/Server/API/OnCallReadinessAPI.test.ts +2680 -0
  165. package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +915 -0
  166. package/Tests/Server/API/UserProjectsAPI.test.ts +478 -4
  167. package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +533 -0
  168. package/Tests/Server/Infrastructure/Postgres/InventoryItemArchiveMigration.test.ts +213 -0
  169. package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +432 -0
  170. package/Tests/Server/Infrastructure/Queue.test.ts +293 -0
  171. package/Tests/Server/Middleware/PublicDashboardRateLimit.test.ts +1645 -0
  172. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +2848 -0
  173. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1393 -0
  174. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +1802 -0
  175. package/Tests/Server/Services/EpisodeStateTimelineNote.test.ts +304 -0
  176. package/Tests/Server/Services/InventoryItemDisplayName.test.ts +339 -0
  177. package/Tests/Server/Services/InventoryItemManualCreate.test.ts +248 -0
  178. package/Tests/Server/Services/IpAllowlistSpoofing.test.ts +450 -0
  179. package/Tests/Server/Services/LogAggregationService.test.ts +235 -1
  180. package/Tests/Server/Services/MetricAggregationService.test.ts +231 -0
  181. package/Tests/Server/Services/MetricEntityMVKeyParity.test.ts +80 -29
  182. package/Tests/Server/Services/MetricServiceAggregate.test.ts +30 -30
  183. package/Tests/Server/Services/NetworkSiteService.test.ts +18 -3
  184. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +1728 -0
  185. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +2402 -0
  186. package/Tests/Server/Services/OnCallDutyPolicyExecutionLogTimelineGapFeed.test.ts +394 -0
  187. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +1744 -0
  188. package/Tests/Server/Services/OnCallReadinessService.test.ts +4295 -0
  189. package/Tests/Server/Services/OnCallSetupReminder.test.ts +1272 -0
  190. package/Tests/Server/Services/OnCallWeeklyReadinessDigest.test.ts +1021 -0
  191. package/Tests/Server/Services/ProfileAggregationService.test.ts +296 -0
  192. package/Tests/Server/Services/SeverityCreationRuleBackfill.test.ts +1536 -0
  193. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +1818 -0
  194. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +1845 -0
  195. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +1394 -0
  196. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +1166 -0
  197. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1468 -0
  198. package/Tests/Server/Services/UserOnCallLogNoNotificationRules.test.ts +1457 -0
  199. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +1546 -0
  200. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +529 -0
  201. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +1219 -0
  202. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +1089 -0
  203. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +2 -1
  204. package/Tests/Server/Types/Workflow/Components/ApiComponentHeaders.test.ts +192 -0
  205. package/Tests/Server/Types/Workflow/Components/BaseModelDatabaseComponents.test.ts +190 -0
  206. package/Tests/Server/Types/Workflow/Components/ChatWebhookComponents.test.ts +44 -14
  207. package/Tests/Server/Types/Workflow/Components/Email.test.ts +151 -0
  208. package/Tests/Server/Types/Workflow/Components/IfElse.test.ts +98 -0
  209. package/Tests/Server/Types/Workflow/Components/JavaScript.test.ts +51 -0
  210. package/Tests/Server/Utils/AI/AIMetaTools.test.ts +586 -0
  211. package/Tests/Server/Utils/AI/AlertMonitorFilters.test.ts +582 -0
  212. package/Tests/Server/Utils/AI/ChatAgentRunner.test.ts +726 -0
  213. package/Tests/Server/Utils/AI/IncidentToolsFilters.test.ts +315 -0
  214. package/Tests/Server/Utils/AI/LLMServiceStopReason.test.ts +314 -0
  215. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +26 -3
  216. package/Tests/Server/Utils/AI/NoteWriteTools.test.ts +268 -0
  217. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +169 -0
  218. package/Tests/Server/Utils/AI/OnCallTools.test.ts +664 -0
  219. package/Tests/Server/Utils/AI/RunbookTools.test.ts +325 -0
  220. package/Tests/Server/Utils/AI/SloTools.test.ts +306 -0
  221. package/Tests/Server/Utils/AI/StatusPageTools.test.ts +391 -0
  222. package/Tests/Server/Utils/AI/TeamTools.test.ts +257 -0
  223. package/Tests/Server/Utils/AI/TimelineTools.test.ts +472 -0
  224. package/Tests/Server/Utils/AI/WorkflowProbeTools.test.ts +428 -0
  225. package/Tests/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.test.ts +152 -0
  226. package/Tests/Server/Utils/ClientIp.test.ts +438 -0
  227. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +171 -0
  228. package/Tests/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.test.ts +383 -0
  229. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +23 -25
  230. package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +6 -0
  231. package/Tests/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.test.ts +307 -0
  232. package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +468 -0
  233. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorTelemetryDeepLinks.test.ts +460 -0
  234. package/Tests/Server/Utils/ResponseRateLimitStatusCodes.test.ts +137 -0
  235. package/Tests/Server/Utils/SSRFProtectionBypasses.test.ts +40 -8
  236. package/Tests/Server/Utils/SSRFProtectionUserInfo.test.ts +351 -0
  237. package/Tests/Server/Utils/Telemetry/EntityRegistryRetirement.test.ts +531 -0
  238. package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +322 -0
  239. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +356 -39
  240. package/Tests/Server/Utils/VM/VMAPISubstitution.test.ts +243 -0
  241. package/Tests/Types/IP/IP.test.ts +263 -0
  242. package/Tests/Types/IP/IPWhitelist.test.ts +197 -0
  243. package/Tests/Types/IP/IPv6.test.ts +12 -1
  244. package/Tests/Types/Monitor/SnmpOid.test.ts +64 -0
  245. package/Tests/Types/NetworkDevice/NetworkDeviceMonitoringMethod.test.ts +110 -0
  246. package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +106 -0
  247. package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeDifferential.test.ts +511 -0
  248. package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageEndToEnd.test.ts +489 -0
  249. package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageGapTolerance.test.ts +479 -0
  250. package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageState.test.ts +822 -0
  251. package/Tests/Types/SerializableObjectDictionaryImportCycle.test.ts +197 -0
  252. package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +140 -0
  253. package/Tests/Types/Workflow/BaseModelComponents.test.ts +429 -0
  254. package/Tests/Types/Workflow/Components/BaseModel.test.ts +225 -0
  255. package/Tests/Types/Workflow/IntegrationCredentialMetadata.test.ts +100 -0
  256. package/Tests/Types/Workflow/StepTrace.test.ts +235 -0
  257. package/Tests/Types/Workflow/TemplateSyntax.test.ts +491 -0
  258. package/Tests/Types/Workflow/Templates.test.ts +1218 -0
  259. package/Tests/UI/Components/ActiveFilterChipsOpenRoute.test.tsx +82 -0
  260. package/Tests/UI/Components/ComponentsModal.test.tsx +564 -7
  261. package/Tests/UI/Components/CustomTimeRangeModal.test.tsx +459 -0
  262. package/Tests/UI/Components/DictionaryAttributeFilterRow.test.tsx +477 -0
  263. package/Tests/UI/Components/Forms/AnchoredFieldPopupKeyboard.test.tsx +382 -0
  264. package/Tests/UI/Components/Forms/ColorPickerPicking.test.tsx +569 -0
  265. package/Tests/UI/Components/Forms/ValidationJSON.test.ts +241 -0
  266. package/Tests/UI/Components/KeyboardShortcut.test.tsx +95 -0
  267. package/Tests/UI/Components/LogDetailsPanelCrossSignal.test.tsx +448 -0
  268. package/Tests/UI/Components/LogTimeRangePicker.test.tsx +42 -0
  269. package/Tests/UI/Components/LogsTableCrossLinks.test.tsx +263 -0
  270. package/Tests/UI/Components/ModalPortalDismissal.test.tsx +90 -0
  271. package/Tests/UI/Components/PendingProjectInvitations.test.tsx +913 -0
  272. package/Tests/UI/Components/SimpleLogViewer.test.tsx +228 -0
  273. package/Tests/UI/Components/TableRowSelectability.test.tsx +312 -0
  274. package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +49 -7
  275. package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +325 -0
  276. package/Tests/UI/Components/Workflow/GraphLint.test.ts +893 -0
  277. package/Tests/UI/Components/Workflow/GraphLintSummary.test.ts +755 -0
  278. package/Tests/UI/Components/Workflow/ModelColumnEditor.test.ts +522 -0
  279. package/Tests/UI/Components/Workflow/ModelColumnEditorServerContract.test.ts +193 -0
  280. package/Tests/UI/Components/Workflow/ModelSchema.test.ts +388 -0
  281. package/Tests/UI/Components/Workflow/RunStatusWatcher.test.ts +210 -0
  282. package/Tests/UI/Components/Workflow/StepTraceViewer.test.tsx +256 -0
  283. package/Tests/UI/Components/Workflow/UseRunWatch.test.tsx +665 -0
  284. package/Tests/UI/Components/Workflow/Utils.test.ts +192 -0
  285. package/Tests/UI/Components/Workflow/WorkflowIssuesModal.test.tsx +485 -0
  286. package/Tests/UI/Components/Workflow/WorkflowLogModal.test.tsx +478 -0
  287. package/Tests/UI/Components/Workflow/WorkflowStatusBar.test.tsx +379 -0
  288. package/Tests/UI/EsbuildConfig.test.ts +607 -0
  289. package/Tests/UI/Monitor/MonitorStepCriteriaView.test.tsx +432 -0
  290. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
  291. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +11 -4
  292. package/Tests/UI/Utils/ModelAPICreateMiscData.test.ts +94 -0
  293. package/Tests/UI/Utils/Platform.test.ts +147 -0
  294. package/Tests/UI/Utils/ProjectInvitationDisplay.test.ts +357 -0
  295. package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +198 -0
  296. package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +514 -0
  297. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +727 -0
  298. package/Tests/Utils/Schema/AnalyticsModelSchema.test.ts +469 -0
  299. package/Tests/Utils/Schema/ModelSchema.test.ts +268 -0
  300. package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +698 -0
  301. package/Tests/Utils/Telemetry/EntityKeyNonTelemetry.test.ts +193 -0
  302. package/Tests/__mocks__/bullmq.js +55 -0
  303. package/Types/AI/AIChatMessageStatus.ts +8 -1
  304. package/Types/AI/AIChatTypes.ts +12 -0
  305. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +88 -0
  306. package/Types/Exception/ExceptionCode.ts +2 -0
  307. package/Types/Exception/ServiceUnavailableException.ts +8 -0
  308. package/Types/Exception/TooManyRequestsException.ts +8 -0
  309. package/Types/IP/IP.ts +93 -47
  310. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +80 -3
  311. package/Types/NetworkDevice/NetworkDeviceMonitoringMethod.ts +55 -0
  312. package/Types/OnCallDutyPolicy/Layer.ts +203 -149
  313. package/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.ts +13 -0
  314. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +155 -10
  315. package/Types/Permission.ts +193 -0
  316. package/Types/SerializableObjectDictionary.ts +133 -39
  317. package/Types/Telemetry/EntityRelationshipType.ts +1 -1
  318. package/Types/Telemetry/EntitySource.ts +40 -0
  319. package/Types/Telemetry/EntityType.ts +28 -1
  320. package/Types/Telemetry/EntityTypeGroups.ts +65 -0
  321. package/Types/Workflow/Component.ts +29 -0
  322. package/Types/Workflow/Components/API.ts +35 -0
  323. package/Types/Workflow/Components/BaseModel.ts +77 -27
  324. package/Types/Workflow/Components/Discord.ts +1 -0
  325. package/Types/Workflow/Components/Email.ts +12 -3
  326. package/Types/Workflow/Components/JavaScript.ts +7 -0
  327. package/Types/Workflow/Components/MicrosoftTeams.ts +3 -2
  328. package/Types/Workflow/Components/Slack.ts +1 -0
  329. package/Types/Workflow/Components/Telegram.ts +1 -0
  330. package/Types/Workflow/StepTrace.ts +181 -0
  331. package/Types/Workflow/TemplateSyntax.ts +543 -0
  332. package/Types/Workflow/Templates.ts +2368 -0
  333. package/UI/Components/Calendar/Calendar.css +43 -0
  334. package/UI/Components/Calendar/Calendar.tsx +8 -0
  335. package/UI/Components/Card/Card.tsx +2 -2
  336. package/UI/Components/Checkbox/Checkbox.tsx +16 -0
  337. package/UI/Components/Date/CustomTimeRangeModal.tsx +278 -0
  338. package/UI/Components/Date/TimeRangePickerDropdown.tsx +250 -0
  339. package/UI/Components/Dictionary/Dictionary.tsx +71 -15
  340. package/UI/Components/FormModal/BasicFormModal.tsx +2 -1
  341. package/UI/Components/Forms/Fields/ColorPicker.tsx +66 -10
  342. package/UI/Components/Forms/Fields/IconPicker.tsx +48 -10
  343. package/UI/Components/Forms/Types/Field.ts +7 -0
  344. package/UI/Components/Forms/Validation.ts +63 -1
  345. package/UI/Components/Header/HeaderIconDropdownButton.tsx +53 -3
  346. package/UI/Components/Input/Input.tsx +32 -3
  347. package/UI/Components/KeyboardShortcut/KeyboardKey.ts +185 -0
  348. package/UI/Components/KeyboardShortcut/KeyboardShortcut.tsx +87 -0
  349. package/UI/Components/LogsViewer/LogsViewer.tsx +28 -0
  350. package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +31 -0
  351. package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +18 -18
  352. package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +363 -14
  353. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +11 -216
  354. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +11 -0
  355. package/UI/Components/LogsViewer/components/LogsTable.tsx +155 -12
  356. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +29 -0
  357. package/UI/Components/LogsViewer/types.ts +23 -0
  358. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +9 -2
  359. package/UI/Components/Modal/Modal.tsx +37 -4
  360. package/UI/Components/Navbar/NavBarMenuModal.tsx +15 -30
  361. package/UI/Components/ProjectInvitations/PendingProjectInvitations.tsx +442 -0
  362. package/UI/Components/SimpleLogViewer/SimpleLogViewer.tsx +23 -1
  363. package/UI/Components/Table/Table.tsx +49 -16
  364. package/UI/Components/Table/TableBody.tsx +53 -28
  365. package/UI/Components/Table/TableHeader.tsx +13 -0
  366. package/UI/Components/Table/TableRow.tsx +58 -26
  367. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +11 -210
  368. package/UI/Components/Workflow/ArgumentsForm.tsx +341 -8
  369. package/UI/Components/Workflow/Component.tsx +28 -26
  370. package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +26 -0
  371. package/UI/Components/Workflow/ComponentSettingsModal.tsx +79 -9
  372. package/UI/Components/Workflow/ComponentValuePickerModal.tsx +135 -7
  373. package/UI/Components/Workflow/ComponentsModal.tsx +116 -22
  374. package/UI/Components/Workflow/DocumentationViewer.tsx +59 -9
  375. package/UI/Components/Workflow/GraphLint.ts +628 -0
  376. package/UI/Components/Workflow/GraphLintSummary.ts +390 -0
  377. package/UI/Components/Workflow/ModelColumnEditor.tsx +528 -0
  378. package/UI/Components/Workflow/ModelSchema.ts +278 -0
  379. package/UI/Components/Workflow/RunForm.tsx +41 -7
  380. package/UI/Components/Workflow/RunStatusWatcher.ts +122 -0
  381. package/UI/Components/Workflow/StepTraceViewer.tsx +186 -0
  382. package/UI/Components/Workflow/UseRunWatch.ts +212 -0
  383. package/UI/Components/Workflow/Utils.ts +93 -1
  384. package/UI/Components/Workflow/VariableModal.tsx +6 -2
  385. package/UI/Components/Workflow/Workflow.tsx +126 -7
  386. package/UI/Components/Workflow/WorkflowIssuesModal.tsx +255 -0
  387. package/UI/Components/Workflow/WorkflowLogModal.tsx +128 -0
  388. package/UI/Components/Workflow/WorkflowStatusBar.tsx +224 -0
  389. package/UI/Types/LayeredDismissal.ts +31 -0
  390. package/UI/Types/UseAnchoredFieldPopup.ts +99 -0
  391. package/UI/Utils/AIChatExport/ConversationMarkdown.ts +10 -0
  392. package/UI/Utils/ModelAPI/ModelAPI.ts +7 -1
  393. package/UI/Utils/Platform.ts +149 -0
  394. package/UI/Utils/ProjectInvitationDisplay.ts +118 -0
  395. package/UI/esbuild-config.js +22 -1
  396. package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +187 -0
  397. package/Utils/Monitor/NetworkDeviceRoleUtil.ts +533 -0
  398. package/Utils/Monitor/NetworkTopologyUtil.ts +917 -155
  399. package/Utils/Telemetry/CrossSignalScope.ts +502 -0
  400. package/Utils/Telemetry/EntityKey.ts +71 -5
  401. package/Utils/Telemetry/EntityRelationship.ts +1 -1
  402. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js +1 -1
  403. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js +1 -1
  404. package/build/dist/Models/DatabaseModels/AIConversation.js +32 -0
  405. package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
  406. package/build/dist/Models/DatabaseModels/AIConversationMessage.js +42 -0
  407. package/build/dist/Models/DatabaseModels/AIConversationMessage.js.map +1 -1
  408. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +28 -0
  409. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
  410. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +29 -0
  411. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
  412. package/build/dist/Models/DatabaseModels/Index.js +12 -4
  413. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  414. package/build/dist/Models/DatabaseModels/{TelemetryEntity.js → InventoryItem.js} +212 -29
  415. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -0
  416. package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js +454 -0
  417. package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js.map +1 -0
  418. package/build/dist/Models/DatabaseModels/{TelemetryEntityRelationship.js → InventoryItemRelationship.js} +52 -25
  419. package/build/dist/Models/DatabaseModels/InventoryItemRelationship.js.map +1 -0
  420. package/build/dist/Models/DatabaseModels/NetworkDevice.js +141 -0
  421. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  422. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +719 -0
  423. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -0
  424. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +475 -0
  425. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -0
  426. package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js +446 -0
  427. package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js.map +1 -0
  428. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js +9 -0
  429. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js.map +1 -1
  430. package/build/dist/Models/DatabaseModels/Project.js +80 -0
  431. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  432. package/build/dist/Models/DatabaseModels/UserCall.js +46 -2
  433. package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
  434. package/build/dist/Models/DatabaseModels/UserEmail.js +48 -2
  435. package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
  436. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +424 -55
  437. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  438. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +24 -2
  439. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  440. package/build/dist/Models/DatabaseModels/UserPush.js +51 -1
  441. package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
  442. package/build/dist/Models/DatabaseModels/UserSMS.js +47 -2
  443. package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
  444. package/build/dist/Models/DatabaseModels/UserTelegram.js +65 -3
  445. package/build/dist/Models/DatabaseModels/UserTelegram.js.map +1 -1
  446. package/build/dist/Models/DatabaseModels/UserWebhook.js +56 -2
  447. package/build/dist/Models/DatabaseModels/UserWebhook.js.map +1 -1
  448. package/build/dist/Models/DatabaseModels/UserWhatsApp.js +45 -2
  449. package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
  450. package/build/dist/Models/DatabaseModels/WorkflowLog.js +39 -0
  451. package/build/dist/Models/DatabaseModels/WorkflowLog.js.map +1 -1
  452. package/build/dist/Models/DatabaseModels/WorkflowVariable.js +12 -0
  453. package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
  454. package/build/dist/Server/API/AIChatAPI.js +237 -1
  455. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  456. package/build/dist/Server/API/DashboardAPI.js +165 -13
  457. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  458. package/build/dist/Server/API/OnCallReadinessAPI.js +599 -0
  459. package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -0
  460. package/build/dist/Server/API/TeamComplianceAPI.js +68 -9
  461. package/build/dist/Server/API/TeamComplianceAPI.js.map +1 -1
  462. package/build/dist/Server/API/TelemetryAPI.js +129 -18
  463. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  464. package/build/dist/Server/API/UserAPI.js +16 -1
  465. package/build/dist/Server/API/UserAPI.js.map +1 -1
  466. package/build/dist/Server/EnvironmentConfig.js +45 -0
  467. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  468. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +22 -0
  469. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  470. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +4 -4
  471. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js +20 -0
  472. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js.map +1 -0
  473. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js +12 -0
  474. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js.map +1 -0
  475. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js +18 -0
  476. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js.map +1 -0
  477. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js +39 -0
  478. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js.map +1 -0
  479. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js +38 -0
  480. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js.map +1 -0
  481. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js +22 -0
  482. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js.map +1 -0
  483. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js +150 -0
  484. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js.map +1 -0
  485. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js +57 -0
  486. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js.map +1 -0
  487. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js +69 -0
  488. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js.map +1 -0
  489. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js +32 -0
  490. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js.map +1 -0
  491. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js +26 -0
  492. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js.map +1 -0
  493. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js +48 -0
  494. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js.map +1 -0
  495. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +24 -0
  496. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  497. package/build/dist/Server/Infrastructure/Queue.js +72 -13
  498. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  499. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -6
  500. package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
  501. package/build/dist/Server/Middleware/PublicDashboardRateLimit.js +399 -0
  502. package/build/dist/Server/Middleware/PublicDashboardRateLimit.js.map +1 -0
  503. package/build/dist/Server/Services/AIService.js +1 -0
  504. package/build/dist/Server/Services/AIService.js.map +1 -1
  505. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +24 -3
  506. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -1
  507. package/build/dist/Server/Services/AlertSeverityService.js +54 -0
  508. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  509. package/build/dist/Server/Services/DashboardService.js +10 -9
  510. package/build/dist/Server/Services/DashboardService.js.map +1 -1
  511. package/build/dist/Server/Services/DatabaseService.js +24 -2
  512. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  513. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +24 -3
  514. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  515. package/build/dist/Server/Services/IncidentSeverityService.js +67 -0
  516. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  517. package/build/dist/Server/Services/Index.js +12 -4
  518. package/build/dist/Server/Services/Index.js.map +1 -1
  519. package/build/dist/Server/Services/InventoryItemCustomFieldService.js +9 -0
  520. package/build/dist/Server/Services/InventoryItemCustomFieldService.js.map +1 -0
  521. package/build/dist/Server/Services/{TelemetryEntityRelationshipService.js → InventoryItemRelationshipService.js} +9 -6
  522. package/build/dist/Server/Services/InventoryItemRelationshipService.js.map +1 -0
  523. package/build/dist/Server/Services/{TelemetryEntityService.js → InventoryItemService.js} +158 -23
  524. package/build/dist/Server/Services/InventoryItemService.js.map +1 -0
  525. package/build/dist/Server/Services/LogAggregationService.js +27 -8
  526. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  527. package/build/dist/Server/Services/MetricAggregationService.js +80 -0
  528. package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
  529. package/build/dist/Server/Services/MetricService.js +6 -6
  530. package/build/dist/Server/Services/MetricService.js.map +1 -1
  531. package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js +9 -0
  532. package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js.map +1 -0
  533. package/build/dist/Server/Services/NetworkDeviceLinkService.js +71 -0
  534. package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -0
  535. package/build/dist/Server/Services/NetworkDeviceService.js +113 -0
  536. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  537. package/build/dist/Server/Services/NetworkSiteService.js +53 -13
  538. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  539. package/build/dist/Server/Services/NetworkTopologySuppressionService.js +85 -0
  540. package/build/dist/Server/Services/NetworkTopologySuppressionService.js.map +1 -0
  541. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +48 -32
  542. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  543. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +8 -0
  544. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
  545. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js +51 -12
  546. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js.map +1 -1
  547. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +57 -13
  548. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  549. package/build/dist/Server/Services/OnCallNotificationAlertingService.js +548 -0
  550. package/build/dist/Server/Services/OnCallNotificationAlertingService.js.map +1 -0
  551. package/build/dist/Server/Services/OnCallReadinessService.js +1961 -0
  552. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -0
  553. package/build/dist/Server/Services/OnCallSetupReminderService.js +738 -0
  554. package/build/dist/Server/Services/OnCallSetupReminderService.js.map +1 -0
  555. package/build/dist/Server/Services/ProfileAggregationService.js +68 -4
  556. package/build/dist/Server/Services/ProfileAggregationService.js.map +1 -1
  557. package/build/dist/Server/Services/StatusPageService.js +11 -10
  558. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  559. package/build/dist/Server/Services/TeamComplianceService.js +312 -160
  560. package/build/dist/Server/Services/TeamComplianceService.js.map +1 -1
  561. package/build/dist/Server/Services/UserCallService.js +24 -1
  562. package/build/dist/Server/Services/UserCallService.js.map +1 -1
  563. package/build/dist/Server/Services/UserEmailService.js +24 -1
  564. package/build/dist/Server/Services/UserEmailService.js.map +1 -1
  565. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +858 -0
  566. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -0
  567. package/build/dist/Server/Services/UserNotificationRuleService.js +2830 -175
  568. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  569. package/build/dist/Server/Services/UserOnCallLogService.js +488 -43
  570. package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
  571. package/build/dist/Server/Services/UserPushService.js +26 -0
  572. package/build/dist/Server/Services/UserPushService.js.map +1 -1
  573. package/build/dist/Server/Services/UserService.js +10 -0
  574. package/build/dist/Server/Services/UserService.js.map +1 -1
  575. package/build/dist/Server/Services/UserSmsService.js +24 -1
  576. package/build/dist/Server/Services/UserSmsService.js.map +1 -1
  577. package/build/dist/Server/Services/UserTelegramService.js +22 -1
  578. package/build/dist/Server/Services/UserTelegramService.js.map +1 -1
  579. package/build/dist/Server/Services/UserWebhookService.js +25 -1
  580. package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
  581. package/build/dist/Server/Services/UserWhatsAppService.js +22 -1
  582. package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
  583. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +12 -1
  584. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  585. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +126 -0
  586. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  587. package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js +254 -0
  588. package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js.map +1 -0
  589. package/build/dist/Server/Types/Database/Permissions/QueryPermission.js +47 -2
  590. package/build/dist/Server/Types/Database/Permissions/QueryPermission.js.map +1 -1
  591. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +7 -0
  592. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
  593. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +1 -1
  594. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  595. package/build/dist/Server/Types/Workflow/Components/API/Get.js +1 -1
  596. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  597. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +1 -1
  598. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  599. package/build/dist/Server/Types/Workflow/Components/API/Post.js +1 -1
  600. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  601. package/build/dist/Server/Types/Workflow/Components/API/Put.js +1 -1
  602. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  603. package/build/dist/Server/Types/Workflow/Components/API/Utils.js +28 -0
  604. package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
  605. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js +21 -5
  606. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js.map +1 -1
  607. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js +14 -10
  608. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js.map +1 -1
  609. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js +31 -0
  610. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js.map +1 -1
  611. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +3 -9
  612. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
  613. package/build/dist/Server/Types/Workflow/Components/Email.js +15 -4
  614. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  615. package/build/dist/Server/Types/Workflow/Components/JavaScript.js +7 -2
  616. package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
  617. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js +1 -1
  618. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js.map +1 -1
  619. package/build/dist/Server/Types/Workflow/TriggerCode.js.map +1 -1
  620. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +514 -56
  621. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  622. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +20 -3
  623. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  624. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +19 -6
  625. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
  626. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +7 -0
  627. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  628. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js +2 -2
  629. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -1
  630. package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js +692 -0
  631. package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js.map +1 -0
  632. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +148 -12
  633. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -1
  634. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +157 -10
  635. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
  636. package/build/dist/Server/Utils/AI/Toolbox/Index.js +37 -0
  637. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  638. package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js +259 -14
  639. package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js.map +1 -1
  640. package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js +235 -0
  641. package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js.map +1 -0
  642. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +1000 -0
  643. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -0
  644. package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js +356 -0
  645. package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js.map +1 -0
  646. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +394 -0
  647. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -0
  648. package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js +465 -0
  649. package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js.map +1 -0
  650. package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js +280 -0
  651. package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js.map +1 -0
  652. package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js +527 -0
  653. package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js.map +1 -0
  654. package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js +548 -0
  655. package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js.map +1 -0
  656. package/build/dist/Server/Utils/ClientIp.js +137 -0
  657. package/build/dist/Server/Utils/ClientIp.js.map +1 -0
  658. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +38 -0
  659. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
  660. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js +89 -0
  661. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js.map +1 -0
  662. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js +77 -0
  663. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js.map +1 -0
  664. package/build/dist/Server/Utils/Express.js +12 -12
  665. package/build/dist/Server/Utils/Express.js.map +1 -1
  666. package/build/dist/Server/Utils/LLM/LLMService.js +70 -7
  667. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  668. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +121 -11
  669. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  670. package/build/dist/Server/Utils/SSRFProtection.js +82 -21
  671. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  672. package/build/dist/Server/Utils/StartServer.js +12 -4
  673. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  674. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +165 -18
  675. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  676. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +507 -0
  677. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -0
  678. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +122 -47
  679. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  680. package/build/dist/Server/Utils/VM/VMAPI.js +51 -4
  681. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  682. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +7 -3
  683. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  684. package/build/dist/Types/AI/AIChatMessageStatus.js +8 -1
  685. package/build/dist/Types/AI/AIChatMessageStatus.js.map +1 -1
  686. package/build/dist/Types/AI/AIChatTypes.js +12 -0
  687. package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
  688. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +60 -0
  689. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -0
  690. package/build/dist/Types/Exception/ExceptionCode.js +2 -0
  691. package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
  692. package/build/dist/Types/Exception/ServiceUnavailableException.js +8 -0
  693. package/build/dist/Types/Exception/ServiceUnavailableException.js.map +1 -0
  694. package/build/dist/Types/Exception/TooManyRequestsException.js +8 -0
  695. package/build/dist/Types/Exception/TooManyRequestsException.js.map +1 -0
  696. package/build/dist/Types/IP/IP.js +87 -43
  697. package/build/dist/Types/IP/IP.js.map +1 -1
  698. package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js +50 -0
  699. package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js.map +1 -0
  700. package/build/dist/Types/OnCallDutyPolicy/Layer.js +186 -123
  701. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  702. package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js +13 -0
  703. package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js.map +1 -1
  704. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +105 -11
  705. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
  706. package/build/dist/Types/Permission.js +174 -0
  707. package/build/dist/Types/Permission.js.map +1 -1
  708. package/build/dist/Types/SerializableObjectDictionary.js +133 -39
  709. package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
  710. package/build/dist/Types/Telemetry/EntityRelationshipType.js +1 -1
  711. package/build/dist/Types/Telemetry/EntitySource.js +39 -0
  712. package/build/dist/Types/Telemetry/EntitySource.js.map +1 -0
  713. package/build/dist/Types/Telemetry/EntityType.js +28 -1
  714. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  715. package/build/dist/Types/Telemetry/EntityTypeGroups.js +57 -0
  716. package/build/dist/Types/Telemetry/EntityTypeGroups.js.map +1 -0
  717. package/build/dist/Types/Workflow/Component.js +21 -0
  718. package/build/dist/Types/Workflow/Component.js.map +1 -1
  719. package/build/dist/Types/Workflow/Components/API.js +35 -0
  720. package/build/dist/Types/Workflow/Components/API.js.map +1 -1
  721. package/build/dist/Types/Workflow/Components/BaseModel.js +68 -26
  722. package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
  723. package/build/dist/Types/Workflow/Components/Discord.js +1 -0
  724. package/build/dist/Types/Workflow/Components/Discord.js.map +1 -1
  725. package/build/dist/Types/Workflow/Components/Email.js +12 -3
  726. package/build/dist/Types/Workflow/Components/Email.js.map +1 -1
  727. package/build/dist/Types/Workflow/Components/JavaScript.js +7 -0
  728. package/build/dist/Types/Workflow/Components/JavaScript.js.map +1 -1
  729. package/build/dist/Types/Workflow/Components/MicrosoftTeams.js +3 -2
  730. package/build/dist/Types/Workflow/Components/MicrosoftTeams.js.map +1 -1
  731. package/build/dist/Types/Workflow/Components/Slack.js +1 -0
  732. package/build/dist/Types/Workflow/Components/Slack.js.map +1 -1
  733. package/build/dist/Types/Workflow/Components/Telegram.js +1 -0
  734. package/build/dist/Types/Workflow/Components/Telegram.js.map +1 -1
  735. package/build/dist/Types/Workflow/StepTrace.js +104 -0
  736. package/build/dist/Types/Workflow/StepTrace.js.map +1 -0
  737. package/build/dist/Types/Workflow/TemplateSyntax.js +338 -0
  738. package/build/dist/Types/Workflow/TemplateSyntax.js.map +1 -0
  739. package/build/dist/Types/Workflow/Templates.js +2111 -0
  740. package/build/dist/Types/Workflow/Templates.js.map +1 -0
  741. package/build/dist/UI/Components/Calendar/Calendar.js +1 -1
  742. package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
  743. package/build/dist/UI/Components/Checkbox/Checkbox.js +1 -1
  744. package/build/dist/UI/Components/Checkbox/Checkbox.js.map +1 -1
  745. package/build/dist/UI/Components/Date/CustomTimeRangeModal.js +126 -0
  746. package/build/dist/UI/Components/Date/CustomTimeRangeModal.js.map +1 -0
  747. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +123 -0
  748. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -0
  749. package/build/dist/UI/Components/Dictionary/Dictionary.js +47 -17
  750. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  751. package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
  752. package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +52 -13
  753. package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
  754. package/build/dist/UI/Components/Forms/Fields/IconPicker.js +28 -12
  755. package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
  756. package/build/dist/UI/Components/Forms/Validation.js +44 -0
  757. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  758. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +27 -4
  759. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  760. package/build/dist/UI/Components/Input/Input.js +13 -4
  761. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  762. package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js +163 -0
  763. package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js.map +1 -0
  764. package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js +47 -0
  765. package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js.map +1 -0
  766. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +4 -4
  767. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  768. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +11 -1
  769. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
  770. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +10 -8
  771. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -1
  772. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +227 -14
  773. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
  774. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +5 -108
  775. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  776. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +5 -0
  777. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  778. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +69 -5
  779. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
  780. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +8 -0
  781. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  782. package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
  783. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +9 -2
  784. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
  785. package/build/dist/UI/Components/Modal/Modal.js +31 -5
  786. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  787. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +8 -21
  788. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  789. package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js +251 -0
  790. package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js.map +1 -0
  791. package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js +9 -2
  792. package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js.map +1 -1
  793. package/build/dist/UI/Components/Table/Table.js +27 -15
  794. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  795. package/build/dist/UI/Components/Table/TableBody.js +24 -18
  796. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  797. package/build/dist/UI/Components/Table/TableHeader.js +9 -1
  798. package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
  799. package/build/dist/UI/Components/Table/TableRow.js +29 -21
  800. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  801. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +5 -105
  802. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  803. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +261 -14
  804. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  805. package/build/dist/UI/Components/Workflow/Component.js +20 -19
  806. package/build/dist/UI/Components/Workflow/Component.js.map +1 -1
  807. package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js +10 -1
  808. package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js.map +1 -1
  809. package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js +35 -7
  810. package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js.map +1 -1
  811. package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js +57 -7
  812. package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js.map +1 -1
  813. package/build/dist/UI/Components/Workflow/ComponentsModal.js +53 -18
  814. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  815. package/build/dist/UI/Components/Workflow/DocumentationViewer.js +19 -6
  816. package/build/dist/UI/Components/Workflow/DocumentationViewer.js.map +1 -1
  817. package/build/dist/UI/Components/Workflow/GraphLint.js +425 -0
  818. package/build/dist/UI/Components/Workflow/GraphLint.js.map +1 -0
  819. package/build/dist/UI/Components/Workflow/GraphLintSummary.js +231 -0
  820. package/build/dist/UI/Components/Workflow/GraphLintSummary.js.map +1 -0
  821. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +320 -0
  822. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -0
  823. package/build/dist/UI/Components/Workflow/ModelSchema.js +156 -0
  824. package/build/dist/UI/Components/Workflow/ModelSchema.js.map +1 -0
  825. package/build/dist/UI/Components/Workflow/RunForm.js +22 -6
  826. package/build/dist/UI/Components/Workflow/RunForm.js.map +1 -1
  827. package/build/dist/UI/Components/Workflow/RunStatusWatcher.js +76 -0
  828. package/build/dist/UI/Components/Workflow/RunStatusWatcher.js.map +1 -0
  829. package/build/dist/UI/Components/Workflow/StepTraceViewer.js +76 -0
  830. package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -0
  831. package/build/dist/UI/Components/Workflow/UseRunWatch.js +123 -0
  832. package/build/dist/UI/Components/Workflow/UseRunWatch.js.map +1 -0
  833. package/build/dist/UI/Components/Workflow/Utils.js +73 -1
  834. package/build/dist/UI/Components/Workflow/Utils.js.map +1 -1
  835. package/build/dist/UI/Components/Workflow/VariableModal.js +3 -2
  836. package/build/dist/UI/Components/Workflow/VariableModal.js.map +1 -1
  837. package/build/dist/UI/Components/Workflow/Workflow.js +92 -7
  838. package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
  839. package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js +99 -0
  840. package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js.map +1 -0
  841. package/build/dist/UI/Components/Workflow/WorkflowLogModal.js +56 -0
  842. package/build/dist/UI/Components/Workflow/WorkflowLogModal.js.map +1 -0
  843. package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js +92 -0
  844. package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js.map +1 -0
  845. package/build/dist/UI/Types/LayeredDismissal.js +21 -0
  846. package/build/dist/UI/Types/LayeredDismissal.js.map +1 -0
  847. package/build/dist/UI/Types/UseAnchoredFieldPopup.js +74 -1
  848. package/build/dist/UI/Types/UseAnchoredFieldPopup.js.map +1 -1
  849. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +9 -0
  850. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -1
  851. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +1 -1
  852. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  853. package/build/dist/UI/Utils/Platform.js +118 -0
  854. package/build/dist/UI/Utils/Platform.js.map +1 -0
  855. package/build/dist/UI/Utils/ProjectInvitationDisplay.js +106 -0
  856. package/build/dist/UI/Utils/ProjectInvitationDisplay.js.map +1 -0
  857. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +108 -0
  858. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -0
  859. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +386 -0
  860. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -0
  861. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +661 -126
  862. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  863. package/build/dist/Utils/Telemetry/CrossSignalScope.js +328 -0
  864. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -0
  865. package/build/dist/Utils/Telemetry/EntityKey.js +57 -5
  866. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  867. package/build/dist/Utils/Telemetry/EntityRelationship.js +1 -1
  868. package/jest.config.json +1 -0
  869. package/package.json +1 -1
  870. package/build/dist/Models/DatabaseModels/TelemetryEntity.js.map +0 -1
  871. package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +0 -1
  872. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +0 -1
  873. package/build/dist/Server/Services/TelemetryEntityService.js.map +0 -1
@@ -0,0 +1,1897 @@
1
+ import "@testing-library/jest-dom";
2
+ import { afterEach, describe, expect, jest, test } from "@jest/globals";
3
+ import {
4
+ cleanup,
5
+ fireEvent,
6
+ render,
7
+ screen,
8
+ waitFor,
9
+ within,
10
+ } from "@testing-library/react";
11
+ import React, { ReactElement } from "react";
12
+ import getJestMockFunction, { MockFunction } from "../../MockType";
13
+
14
+ /*
15
+ * THE DELETE GUARDS - the warnings that run at the moment something is TAKEN
16
+ * AWAY.
17
+ *
18
+ * An escalation level, a notification method or a notification rule can each be
19
+ * deleted out from under the pages that depend on it, and the properties worth
20
+ * testing are the ones that decide whether the confirmation gets read or clicked
21
+ * through:
22
+ *
23
+ * - it names REAL NUMBERS and real people, because "this may affect your
24
+ * on-call coverage" is true before every delete and therefore says nothing;
25
+ * - it stays SILENT when nothing is actually lost, because a confirmation that
26
+ * shouts before every delete stops registering before any of them;
27
+ * - it is HONEST when it does not know, rather than rendering the same nothing
28
+ * a harmless delete renders.
29
+ *
30
+ * The readiness warning that used to live in the add/edit escalation rule modal
31
+ * moved onto the escalation page itself and is covered by
32
+ * EscalationRuleReadiness.test.tsx.
33
+ */
34
+
35
+ const getMock: MockFunction = getJestMockFunction();
36
+ const postMock: MockFunction = getJestMockFunction();
37
+ const getListMock: MockFunction = getJestMockFunction();
38
+ const getItemMock: MockFunction = getJestMockFunction();
39
+ const getCommonHeadersMock: MockFunction = getJestMockFunction();
40
+ const deleteItemMock: MockFunction = getJestMockFunction();
41
+
42
+ /*
43
+ * The arrow wrappers are load bearing: jest.mock is hoisted above the compiled
44
+ * requires, so the consts above are still in their temporal dead zone when the
45
+ * factory body runs. Dereferencing them lazily, at call time, is what works.
46
+ */
47
+ jest.mock("../../../UI/Utils/API/API", () => {
48
+ return {
49
+ __esModule: true,
50
+ default: {
51
+ get: (...args: Array<any>) => {
52
+ return getMock(...args);
53
+ },
54
+ post: (...args: Array<any>) => {
55
+ return postMock(...args);
56
+ },
57
+ getFriendlyMessage: (error: unknown) => {
58
+ return error instanceof Error ? error.message : "Something went wrong";
59
+ },
60
+ },
61
+ };
62
+ });
63
+
64
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
65
+ return {
66
+ __esModule: true,
67
+ default: {
68
+ getCommonHeaders: (...args: Array<any>) => {
69
+ return getCommonHeadersMock(...args);
70
+ },
71
+ getList: (...args: Array<any>) => {
72
+ return getListMock(...args);
73
+ },
74
+ getItem: (...args: Array<any>) => {
75
+ return getItemMock(...args);
76
+ },
77
+ deleteItem: (...args: Array<any>) => {
78
+ return deleteItemMock(...args);
79
+ },
80
+ },
81
+ };
82
+ });
83
+
84
+ jest.mock("react-i18next", () => {
85
+ return {
86
+ useTranslation: () => {
87
+ return {
88
+ t: (key: string, options?: { defaultValue?: string }): string => {
89
+ return options?.defaultValue ?? key;
90
+ },
91
+ };
92
+ },
93
+ };
94
+ });
95
+
96
+ import EscalationRules, {
97
+ MembersByRuleId,
98
+ describeEscalationRuleDeletion,
99
+ getEscalationRuleDeletionImpact,
100
+ toSelectedOptions,
101
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/OnCallPolicy/EscalationRule/EscalationRules";
102
+ import {
103
+ CoverageCell,
104
+ DeletionImpact,
105
+ DeletionImpactModal,
106
+ NotificationRuleFacts,
107
+ OnCallExposure,
108
+ UNKNOWN_ON_CALL_EXPOSURE,
109
+ buildCoverageCells,
110
+ computeMethodDeletionImpact,
111
+ computeRuleDeletionImpact,
112
+ describeMethodDeletion,
113
+ describeOnCallExposure,
114
+ describeRuleDeletion,
115
+ getCellLabel,
116
+ parseOnCallExposure,
117
+ readRuleFacts,
118
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationMethods/NotificationMethod";
119
+ import EmailMethods from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationMethods/Email";
120
+ import SMSMethods from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationMethods/SMS";
121
+ import CallMethods from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationMethods/Call";
122
+ import PushMethods from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationMethods/Push";
123
+ import WhatsAppMethods from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationMethods/WhatsApp";
124
+ import TelegramMethods from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationMethods/Telegram";
125
+ import WebhookMethods from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationMethods/Webhook";
126
+ import BaseModel, {
127
+ DatabaseBaseModelType,
128
+ } from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
129
+ import UserCall from "../../../Models/DatabaseModels/UserCall";
130
+ import UserEmail from "../../../Models/DatabaseModels/UserEmail";
131
+ import UserNotificationRule from "../../../Models/DatabaseModels/UserNotificationRule";
132
+ import UserPush from "../../../Models/DatabaseModels/UserPush";
133
+ import UserSMS from "../../../Models/DatabaseModels/UserSMS";
134
+ import UserTelegram from "../../../Models/DatabaseModels/UserTelegram";
135
+ import UserWebhook from "../../../Models/DatabaseModels/UserWebhook";
136
+ import UserWhatsApp from "../../../Models/DatabaseModels/UserWhatsApp";
137
+ import HTTPResponse from "../../../Types/API/HTTPResponse";
138
+ import Email from "../../../Types/Email";
139
+ import { JSONObject } from "../../../Types/JSON";
140
+ import NotificationRuleType from "../../../Types/NotificationRule/NotificationRuleType";
141
+ import ObjectID from "../../../Types/ObjectID";
142
+ import Phone from "../../../Types/Phone";
143
+ import { ColumnValueReader } from "../../../UI/Utils/NotificationMethodUtil";
144
+
145
+ const USER_ALEX: string = "aaaaaaaa-1111-4111-8111-111111111111";
146
+ const USER_SAM: string = "bbbbbbbb-2222-4222-8222-222222222222";
147
+ const USER_JO: string = "cccccccc-3333-4333-8333-333333333333";
148
+
149
+ const PROJECT_ID: ObjectID = new ObjectID(
150
+ "dddddddd-4444-4444-8444-444444444444",
151
+ );
152
+
153
+ const SEV1_ID: string = "eeeeeeee-5555-4555-8555-555555555555";
154
+ const SEV2_ID: string = "ffffffff-6666-4666-8666-666666666666";
155
+
156
+ const EMAIL_METHOD_ID: string = "11111111-7777-4777-8777-777777777777";
157
+ const SMS_METHOD_ID: string = "22222222-8888-4888-8888-888888888888";
158
+
159
+ type OkResponseFunction = (data: JSONObject) => HTTPResponse<JSONObject>;
160
+
161
+ const okResponse: OkResponseFunction = (
162
+ data: JSONObject,
163
+ ): HTTPResponse<JSONObject> => {
164
+ return new HTTPResponse<JSONObject>(200, data, {});
165
+ };
166
+
167
+ afterEach(() => {
168
+ cleanup();
169
+ getMock.mockReset();
170
+ postMock.mockReset();
171
+ getListMock.mockReset();
172
+ getItemMock.mockReset();
173
+ getCommonHeadersMock.mockReset();
174
+ deleteItemMock.mockReset();
175
+ localStorage.clear();
176
+ sessionStorage.clear();
177
+ });
178
+
179
+ /*
180
+ * A readiness row as the per-user route serialises one. Built through the
181
+ * component's own parser rather than as a literal, so a change to the payload
182
+ * shape breaks these fixtures the same way it would break the browser.
183
+ */
184
+ type ReadinessJsonFunction = (overrides: JSONObject) => JSONObject;
185
+
186
+ const readinessJson: ReadinessJsonFunction = (
187
+ overrides: JSONObject,
188
+ ): JSONObject => {
189
+ return {
190
+ userId: USER_ALEX,
191
+ userName: "Alex Chen",
192
+ userEmail: "alex@example.com",
193
+ status: "NotReachable",
194
+ methods: [],
195
+ coverage: [],
196
+ reasons: [],
197
+ reachedVia: [],
198
+ ...overrides,
199
+ };
200
+ };
201
+
202
+ describe("Escalation rule form: reading the responder selection", () => {
203
+ /*
204
+ * The form hands a multi-select value over either as bare ids or as
205
+ * { value, label } envelopes depending on whether it was seeded as a default,
206
+ * and the save path reconciles join rows off the result. A shape this misreads
207
+ * is a responder silently dropped from - or never added to - a level.
208
+ */
209
+ test("flattens option envelopes and bare ids into id/label pairs", () => {
210
+ expect(
211
+ toSelectedOptions([
212
+ { value: USER_ALEX, label: "Alex Chen" },
213
+ USER_SAM,
214
+ null,
215
+ undefined,
216
+ ]),
217
+ ).toEqual([
218
+ { id: USER_ALEX, label: "Alex Chen" },
219
+ { id: USER_SAM, label: "" },
220
+ ]);
221
+ });
222
+
223
+ test("a non-array selection is no selection rather than a crash", () => {
224
+ expect(toSelectedOptions(undefined)).toEqual([]);
225
+ expect(toSelectedOptions("not-an-array")).toEqual([]);
226
+ });
227
+ });
228
+
229
+ /*
230
+ * The escalation-level delete confirmation. Every number below comes from rows
231
+ * already on the screen, which is why the sentence can be specific without a
232
+ * request that might fail.
233
+ */
234
+ type UserJoinFunction = (userId: string, name: string) => any;
235
+
236
+ const userJoin: UserJoinFunction = (userId: string, name: string): any => {
237
+ return { user: { id: new ObjectID(userId), name: name } };
238
+ };
239
+
240
+ const membersByRuleId: MembersByRuleId = {
241
+ "rule-1": {
242
+ userJoins: [
243
+ userJoin(USER_ALEX, "Alex Chen"),
244
+ userJoin(USER_SAM, "Sam Doe"),
245
+ ],
246
+ teamJoins: [
247
+ { team: { id: new ObjectID(USER_JO), name: "Platform" } } as any,
248
+ ],
249
+ scheduleJoins: [],
250
+ },
251
+ "rule-2": {
252
+ userJoins: [userJoin(USER_SAM, "Sam Doe")],
253
+ teamJoins: [],
254
+ scheduleJoins: [{ onCallDutyPolicySchedule: { name: "Weekends" } } as any],
255
+ },
256
+ };
257
+
258
+ describe("Escalation-level delete: the consequence is counted", () => {
259
+ test("it counts the users, teams and schedules the level notifies", () => {
260
+ const impact: ReturnType<typeof getEscalationRuleDeletionImpact> =
261
+ getEscalationRuleDeletionImpact({
262
+ ruleIdToDelete: "rule-1",
263
+ ruleIds: ["rule-1", "rule-2"],
264
+ membersByRuleId: membersByRuleId,
265
+ });
266
+
267
+ expect(impact.userCount).toBe(2);
268
+ expect(impact.teamCount).toBe(1);
269
+ expect(impact.scheduleCount).toBe(0);
270
+ expect(impact.isLastRule).toBe(false);
271
+ });
272
+
273
+ /*
274
+ * Sam is on level 2 as well, so deleting level 1 does not take him off the
275
+ * policy. Alex is only here.
276
+ */
277
+ test("it names only the users this policy stops naming altogether", () => {
278
+ const impact: ReturnType<typeof getEscalationRuleDeletionImpact> =
279
+ getEscalationRuleDeletionImpact({
280
+ ruleIdToDelete: "rule-1",
281
+ ruleIds: ["rule-1", "rule-2"],
282
+ membersByRuleId: membersByRuleId,
283
+ });
284
+
285
+ expect(impact.usersNamedNowhereElse).toEqual(["Alex Chen"]);
286
+ });
287
+
288
+ test("deleting the only level is called out as leaving nobody", () => {
289
+ const impact: ReturnType<typeof getEscalationRuleDeletionImpact> =
290
+ getEscalationRuleDeletionImpact({
291
+ ruleIdToDelete: "rule-1",
292
+ ruleIds: ["rule-1"],
293
+ membersByRuleId: membersByRuleId,
294
+ });
295
+
296
+ expect(impact.isLastRule).toBe(true);
297
+ expect(
298
+ describeEscalationRuleDeletion("First Responders", impact),
299
+ ).toContain("This is the only escalation level on this policy");
300
+ });
301
+
302
+ test("the sentence names the level and its responders", () => {
303
+ const sentence: string = describeEscalationRuleDeletion(
304
+ "First Responders",
305
+ getEscalationRuleDeletionImpact({
306
+ ruleIdToDelete: "rule-1",
307
+ ruleIds: ["rule-1", "rule-2"],
308
+ membersByRuleId: membersByRuleId,
309
+ }),
310
+ );
311
+
312
+ expect(sentence).toContain(
313
+ '"First Responders" notifies 2 users and 1 team.',
314
+ );
315
+ expect(sentence).toContain(
316
+ "Alex Chen is not named on any other level of this policy.",
317
+ );
318
+ expect(sentence).toContain("This action cannot be undone.");
319
+ });
320
+
321
+ test("a level that notifies nobody says exactly that", () => {
322
+ const sentence: string = describeEscalationRuleDeletion(
323
+ "Empty Level",
324
+ getEscalationRuleDeletionImpact({
325
+ ruleIdToDelete: "rule-3",
326
+ ruleIds: ["rule-1", "rule-2", "rule-3"],
327
+ membersByRuleId: membersByRuleId,
328
+ }),
329
+ );
330
+
331
+ expect(sentence).toContain('"Empty Level" currently notifies no one.');
332
+ });
333
+
334
+ test("a schedule-only level is described in schedules", () => {
335
+ const sentence: string = describeEscalationRuleDeletion(
336
+ "Weekend Cover",
337
+ getEscalationRuleDeletionImpact({
338
+ ruleIdToDelete: "rule-2",
339
+ ruleIds: ["rule-1", "rule-2"],
340
+ membersByRuleId: membersByRuleId,
341
+ }),
342
+ );
343
+
344
+ expect(sentence).toContain(
345
+ '"Weekend Cover" notifies 1 user and 1 on-call schedule.',
346
+ );
347
+ });
348
+ });
349
+
350
+ /*
351
+ * ---------------------------------------------------------------------------
352
+ * DELETE GUARDS on notification methods and rules.
353
+ * ---------------------------------------------------------------------------
354
+ */
355
+
356
+ interface RuleSpec {
357
+ ruleId: string;
358
+ ruleType: NotificationRuleType;
359
+ severityId?: string | undefined;
360
+ severityName?: string | undefined;
361
+ methodRelation?: string | undefined;
362
+ methodId?: string | undefined;
363
+ methodValue?: string | undefined;
364
+ isOptOut?: boolean | undefined;
365
+ }
366
+
367
+ /*
368
+ * A rule as the reader sees one. Deliberately a bare ColumnValueReader rather
369
+ * than a decorated entity: that is the only interface the impact maths uses, and
370
+ * building fixtures through it is what keeps these tests about the arithmetic
371
+ * instead of about TypeORM.
372
+ */
373
+ type RuleReaderFunction = (spec: RuleSpec) => ColumnValueReader;
374
+
375
+ const ruleReader: RuleReaderFunction = (spec: RuleSpec): ColumnValueReader => {
376
+ const columns: Record<string, unknown> = {
377
+ _id: spec.ruleId,
378
+ ruleType: spec.ruleType,
379
+ isOptOut: spec.isOptOut === true,
380
+ };
381
+
382
+ if (spec.severityId) {
383
+ const severity: Record<string, unknown> = {
384
+ _id: spec.severityId,
385
+ name: spec.severityName || "",
386
+ };
387
+
388
+ if (
389
+ spec.ruleType === NotificationRuleType.ON_CALL_EXECUTED_ALERT ||
390
+ spec.ruleType === NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE
391
+ ) {
392
+ columns["alertSeverity"] = severity;
393
+ } else {
394
+ columns["incidentSeverity"] = severity;
395
+ }
396
+ }
397
+
398
+ if (spec.methodRelation && spec.methodId) {
399
+ columns[spec.methodRelation] = {
400
+ _id: spec.methodId,
401
+ email: spec.methodRelation === "userEmail" ? spec.methodValue : undefined,
402
+ phone: spec.methodRelation === "userSms" ? spec.methodValue : undefined,
403
+ };
404
+ }
405
+
406
+ return {
407
+ getColumnValue: (columnName: string): unknown => {
408
+ return columns[columnName] ?? null;
409
+ },
410
+ } as ColumnValueReader;
411
+ };
412
+
413
+ /*
414
+ * A rule whose severity is written into a NAMED column, rather than into the
415
+ * one its rule type implies.
416
+ *
417
+ * `ruleReader` above always pairs the two correctly, which is exactly why it
418
+ * cannot express the row this feature has to survive: a rule carrying a
419
+ * severity id in the column its type does not use. That row is real - Gap G
420
+ * wrote episode rules with a NULL severity, and any retype or partial write
421
+ * leaves the other column populated - and reading it loosely is how a rule that
422
+ * matches no page at runtime gets counted as cover for one.
423
+ */
424
+ interface CrossedColumnRuleSpec {
425
+ ruleId: string;
426
+ ruleType: NotificationRuleType;
427
+ severityColumn: "incidentSeverity" | "alertSeverity";
428
+ severityId: string;
429
+ severityName: string;
430
+ methodRelation: string;
431
+ methodId: string;
432
+ }
433
+
434
+ type CrossedColumnRuleFunction = (
435
+ spec: CrossedColumnRuleSpec,
436
+ ) => ColumnValueReader;
437
+
438
+ const crossedColumnRule: CrossedColumnRuleFunction = (
439
+ spec: CrossedColumnRuleSpec,
440
+ ): ColumnValueReader => {
441
+ const columns: Record<string, unknown> = {
442
+ _id: spec.ruleId,
443
+ ruleType: spec.ruleType,
444
+ isOptOut: false,
445
+ [spec.severityColumn]: { _id: spec.severityId, name: spec.severityName },
446
+ [spec.methodRelation]: {
447
+ _id: spec.methodId,
448
+ email: "jane@example.com",
449
+ },
450
+ };
451
+
452
+ return {
453
+ getColumnValue: (columnName: string): unknown => {
454
+ return columns[columnName] ?? null;
455
+ },
456
+ } as ColumnValueReader;
457
+ };
458
+
459
+ const factsFor: (specs: Array<RuleSpec>) => Array<NotificationRuleFacts> = (
460
+ specs: Array<RuleSpec>,
461
+ ): Array<NotificationRuleFacts> => {
462
+ return specs.map((spec: RuleSpec): NotificationRuleFacts => {
463
+ return readRuleFacts(ruleReader(spec));
464
+ });
465
+ };
466
+
467
+ const EMAIL_ON_SEV1_INCIDENT: RuleSpec = {
468
+ ruleId: "rule-a",
469
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
470
+ severityId: SEV1_ID,
471
+ severityName: "Sev1",
472
+ methodRelation: "userEmail",
473
+ methodId: EMAIL_METHOD_ID,
474
+ methodValue: "jane@example.com",
475
+ };
476
+
477
+ const SMS_ON_SEV1_INCIDENT: RuleSpec = {
478
+ ruleId: "rule-b",
479
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
480
+ severityId: SEV1_ID,
481
+ severityName: "Sev1",
482
+ methodRelation: "userSms",
483
+ methodId: SMS_METHOD_ID,
484
+ methodValue: "+15550100",
485
+ };
486
+
487
+ const EMAIL_ON_SEV2_ALERT: RuleSpec = {
488
+ ruleId: "rule-c",
489
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
490
+ severityId: SEV2_ID,
491
+ severityName: "Sev2",
492
+ methodRelation: "userEmail",
493
+ methodId: EMAIL_METHOD_ID,
494
+ methodValue: "jane@example.com",
495
+ };
496
+
497
+ /* The same cell, deliberately silenced. Carries no method, by definition. */
498
+ const MUTED_SEV2_ALERT: RuleSpec = {
499
+ ruleId: "rule-mute-sev2",
500
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
501
+ severityId: SEV2_ID,
502
+ severityName: "Sev2",
503
+ isOptOut: true,
504
+ };
505
+
506
+ describe("Delete guards: reading a rule", () => {
507
+ test("it reads the severity, the rule type and the method off one rule", () => {
508
+ const facts: NotificationRuleFacts = readRuleFacts(
509
+ ruleReader(EMAIL_ON_SEV1_INCIDENT),
510
+ );
511
+
512
+ expect(facts).toEqual({
513
+ ruleId: "rule-a",
514
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
515
+ severityId: SEV1_ID,
516
+ severityName: "Sev1",
517
+ methodId: EMAIL_METHOD_ID,
518
+ methodType: "Email",
519
+ methodLabel: "Email: jane@example.com",
520
+ isOptOut: false,
521
+ });
522
+ });
523
+
524
+ test("an alert rule's severity comes off the alert relation", () => {
525
+ const facts: NotificationRuleFacts = readRuleFacts(
526
+ ruleReader(EMAIL_ON_SEV2_ALERT),
527
+ );
528
+
529
+ expect(facts.severityId).toBe(SEV2_ID);
530
+ expect(facts.severityName).toBe("Sev2");
531
+ });
532
+
533
+ /*
534
+ * An opt-out row carries no method at all, and that absence is the point
535
+ * rather than a misconfiguration.
536
+ */
537
+ test("an opt-out row is read as carrying no method", () => {
538
+ const facts: NotificationRuleFacts = readRuleFacts(
539
+ ruleReader({
540
+ ruleId: "rule-mute",
541
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
542
+ severityId: SEV2_ID,
543
+ severityName: "Sev2",
544
+ isOptOut: true,
545
+ }),
546
+ );
547
+
548
+ expect(facts.isOptOut).toBe(true);
549
+ expect(facts.methodId).toBe("");
550
+ expect(facts.methodLabel).toBe("");
551
+ });
552
+
553
+ test("a lifecycle rule type carries no severity", () => {
554
+ const facts: NotificationRuleFacts = readRuleFacts(
555
+ ruleReader({
556
+ ruleId: "rule-handoff",
557
+ ruleType: NotificationRuleType.WHEN_USER_GOES_ON_CALL,
558
+ methodRelation: "userEmail",
559
+ methodId: EMAIL_METHOD_ID,
560
+ methodValue: "jane@example.com",
561
+ }),
562
+ );
563
+
564
+ expect(facts.severityId).toBe("");
565
+ expect(getCellLabel(buildCoverageCells([facts])[0]!)).toBe("Goes on call");
566
+ });
567
+
568
+ /*
569
+ * The severity is taken from the column the RULE TYPE dictates, never from
570
+ * whichever of the two happens to be populated - the rule
571
+ * UserNotificationRuleService states for the server half of this maths and
572
+ * refuses to bend. At runtime an alert rule is only ever matched against an
573
+ * ALERT severity id, so a severity sitting in the incident column of an alert
574
+ * rule covers nothing at all, whatever it looks like.
575
+ */
576
+ test("a severity in the column the rule type does not use is not read", () => {
577
+ const facts: NotificationRuleFacts = readRuleFacts(
578
+ crossedColumnRule({
579
+ ruleId: "rule-crossed",
580
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
581
+ severityColumn: "incidentSeverity",
582
+ severityId: SEV2_ID,
583
+ severityName: "Sev2",
584
+ methodRelation: "userEmail",
585
+ methodId: EMAIL_METHOD_ID,
586
+ }),
587
+ );
588
+
589
+ expect(facts.severityId).toBe("");
590
+ expect(facts.severityName).toBe("");
591
+ });
592
+
593
+ test("an episode rule reads the severity its own kind is scoped by", () => {
594
+ expect(
595
+ readRuleFacts(
596
+ crossedColumnRule({
597
+ ruleId: "rule-alert-episode",
598
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE,
599
+ severityColumn: "alertSeverity",
600
+ severityId: SEV2_ID,
601
+ severityName: "Sev2",
602
+ methodRelation: "userEmail",
603
+ methodId: EMAIL_METHOD_ID,
604
+ }),
605
+ ).severityId,
606
+ ).toBe(SEV2_ID);
607
+
608
+ expect(
609
+ readRuleFacts(
610
+ crossedColumnRule({
611
+ ruleId: "rule-incident-episode",
612
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE,
613
+ severityColumn: "incidentSeverity",
614
+ severityId: SEV1_ID,
615
+ severityName: "Sev1",
616
+ methodRelation: "userEmail",
617
+ methodId: EMAIL_METHOD_ID,
618
+ }),
619
+ ).severityId,
620
+ ).toBe(SEV1_ID);
621
+ });
622
+
623
+ /*
624
+ * The consequence, and the reason the loose read is a bug rather than a
625
+ * tidiness complaint: a mis-columned rule that is read as covering Sev2 alerts
626
+ * VOUCHES for that cell. Delete the one rule that really does deliver there
627
+ * and the warning stays silent, because a rule that can never fire for the
628
+ * cell was counted as cover for it.
629
+ */
630
+ test("a mis-columned rule cannot vouch for a cell it can never fire for", () => {
631
+ const impact: DeletionImpact = computeMethodDeletionImpact({
632
+ rules: [
633
+ readRuleFacts(
634
+ ruleReader({
635
+ ruleId: "rule-real",
636
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
637
+ severityId: SEV2_ID,
638
+ severityName: "Sev2",
639
+ methodRelation: "userSms",
640
+ methodId: SMS_METHOD_ID,
641
+ methodValue: "+15550100",
642
+ }),
643
+ ),
644
+ readRuleFacts(
645
+ crossedColumnRule({
646
+ ruleId: "rule-crossed",
647
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
648
+ severityColumn: "incidentSeverity",
649
+ severityId: SEV2_ID,
650
+ severityName: "Sev2",
651
+ methodRelation: "userEmail",
652
+ methodId: EMAIL_METHOD_ID,
653
+ }),
654
+ ),
655
+ ],
656
+ methodId: SMS_METHOD_ID,
657
+ });
658
+
659
+ expect(
660
+ impact.orphanedCells.map((cell: CoverageCell): string => {
661
+ return getCellLabel(cell);
662
+ }),
663
+ ).toEqual(["Alert · Sev2"]);
664
+ });
665
+ });
666
+
667
+ describe("Delete guards: what a method deletion costs", () => {
668
+ test("it counts every rule that points at the method", () => {
669
+ const impact: DeletionImpact = computeMethodDeletionImpact({
670
+ rules: factsFor([
671
+ EMAIL_ON_SEV1_INCIDENT,
672
+ SMS_ON_SEV1_INCIDENT,
673
+ EMAIL_ON_SEV2_ALERT,
674
+ ]),
675
+ methodId: EMAIL_METHOD_ID,
676
+ });
677
+
678
+ expect(impact.ruleCount).toBe(2);
679
+ });
680
+
681
+ /*
682
+ * The central piece of arithmetic. Sev1 incidents keep the SMS rule, so that
683
+ * cell survives; Sev2 alerts had only the email rule, so that one goes dark.
684
+ * A warning that named both would be crying wolf, and one that named neither
685
+ * would be the silence this feature exists to end.
686
+ */
687
+ test("only the cells left with nothing are named", () => {
688
+ const impact: DeletionImpact = computeMethodDeletionImpact({
689
+ rules: factsFor([
690
+ EMAIL_ON_SEV1_INCIDENT,
691
+ SMS_ON_SEV1_INCIDENT,
692
+ EMAIL_ON_SEV2_ALERT,
693
+ ]),
694
+ methodId: EMAIL_METHOD_ID,
695
+ });
696
+
697
+ expect(
698
+ impact.orphanedCells.map((cell: CoverageCell): string => {
699
+ return getCellLabel(cell);
700
+ }),
701
+ ).toEqual(["Alert · Sev2"]);
702
+ });
703
+
704
+ /*
705
+ * A muted cell is a cell whose silence was chosen. Warning about it nags
706
+ * somebody for having configured the product correctly.
707
+ */
708
+ test("a cell the user muted is never reported as a loss", () => {
709
+ const impact: DeletionImpact = computeMethodDeletionImpact({
710
+ rules: factsFor([
711
+ EMAIL_ON_SEV2_ALERT,
712
+ {
713
+ ruleId: "rule-mute",
714
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
715
+ severityId: SEV2_ID,
716
+ severityName: "Sev2",
717
+ isOptOut: true,
718
+ },
719
+ ]),
720
+ methodId: EMAIL_METHOD_ID,
721
+ });
722
+
723
+ expect(impact.ruleCount).toBe(1);
724
+ expect(impact.orphanedCells).toHaveLength(0);
725
+ });
726
+
727
+ test("a method nothing points at costs nothing", () => {
728
+ const impact: DeletionImpact = computeMethodDeletionImpact({
729
+ rules: factsFor([EMAIL_ON_SEV1_INCIDENT]),
730
+ methodId: SMS_METHOD_ID,
731
+ });
732
+
733
+ expect(impact.ruleCount).toBe(0);
734
+ expect(impact.orphanedCells).toHaveLength(0);
735
+ });
736
+
737
+ /*
738
+ * An empty method id must not match the empty method id every opt-out row
739
+ * carries, or muting one severity would make every method look load bearing.
740
+ */
741
+ test("an empty method id matches nothing", () => {
742
+ const impact: DeletionImpact = computeMethodDeletionImpact({
743
+ rules: factsFor([
744
+ EMAIL_ON_SEV1_INCIDENT,
745
+ {
746
+ ruleId: "rule-mute",
747
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
748
+ severityId: SEV2_ID,
749
+ isOptOut: true,
750
+ },
751
+ ]),
752
+ methodId: "",
753
+ });
754
+
755
+ expect(impact.ruleCount).toBe(0);
756
+ });
757
+ });
758
+
759
+ describe("Delete guards: what a rule deletion costs", () => {
760
+ test("the last rule for a cell is called out", () => {
761
+ const impact: DeletionImpact = computeRuleDeletionImpact({
762
+ rules: factsFor([EMAIL_ON_SEV1_INCIDENT, EMAIL_ON_SEV2_ALERT]),
763
+ ruleId: "rule-c",
764
+ });
765
+
766
+ expect(impact.ruleCount).toBe(1);
767
+ expect(
768
+ impact.orphanedCells.map((cell: CoverageCell): string => {
769
+ return getCellLabel(cell);
770
+ }),
771
+ ).toEqual(["Alert · Sev2"]);
772
+ });
773
+
774
+ test("a rule with a sibling on the same cell leaves no gap", () => {
775
+ const impact: DeletionImpact = computeRuleDeletionImpact({
776
+ rules: factsFor([EMAIL_ON_SEV1_INCIDENT, SMS_ON_SEV1_INCIDENT]),
777
+ ruleId: "rule-a",
778
+ });
779
+
780
+ expect(impact.orphanedCells).toHaveLength(0);
781
+ });
782
+
783
+ /*
784
+ * An opt-out is a rule that exists in order to deliver NOTHING. It is not a
785
+ * warning when the last delivering rule for a muted cell goes - the silence
786
+ * was chosen - but it is not cover either, and the two must not be reported
787
+ * as the same thing.
788
+ */
789
+ test("a muted cell losing its last delivering rule is tracked, not silently dropped", () => {
790
+ const impact: DeletionImpact = computeRuleDeletionImpact({
791
+ rules: factsFor([EMAIL_ON_SEV2_ALERT, MUTED_SEV2_ALERT]),
792
+ ruleId: "rule-c",
793
+ });
794
+
795
+ expect(impact.orphanedCells).toHaveLength(0);
796
+ expect(
797
+ impact.mutedCells.map((cell: CoverageCell): string => {
798
+ return getCellLabel(cell);
799
+ }),
800
+ ).toEqual(["Alert · Sev2"]);
801
+ });
802
+
803
+ test("a cell that keeps a delivering rule is neither orphaned nor muted", () => {
804
+ const impact: DeletionImpact = computeRuleDeletionImpact({
805
+ rules: factsFor([EMAIL_ON_SEV1_INCIDENT, SMS_ON_SEV1_INCIDENT]),
806
+ ruleId: "rule-a",
807
+ });
808
+
809
+ expect(impact.orphanedCells).toHaveLength(0);
810
+ expect(impact.mutedCells).toHaveLength(0);
811
+ });
812
+
813
+ test("cells are keyed on rule type as well as severity", () => {
814
+ /*
815
+ * Same severity id, two rule types. Keying on severity alone would let an
816
+ * incident rule vouch for an alert cell it can never fire for.
817
+ */
818
+ const cells: Array<CoverageCell> = buildCoverageCells(
819
+ factsFor([
820
+ EMAIL_ON_SEV1_INCIDENT,
821
+ {
822
+ ruleId: "rule-d",
823
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
824
+ severityId: SEV1_ID,
825
+ severityName: "Sev1",
826
+ methodRelation: "userEmail",
827
+ methodId: EMAIL_METHOD_ID,
828
+ methodValue: "jane@example.com",
829
+ },
830
+ ]),
831
+ );
832
+
833
+ expect(cells).toHaveLength(2);
834
+ });
835
+ });
836
+
837
+ describe("Delete guards: the sentences", () => {
838
+ const exposure: OnCallExposure = {
839
+ isKnown: true,
840
+ isOnCallResponder: true,
841
+ sources: ["Team"],
842
+ };
843
+
844
+ test("the method sentence carries the real numbers and the real names", () => {
845
+ const sentence: string = describeMethodDeletion({
846
+ methodLabel: "Email: jane@example.com",
847
+ impact: computeMethodDeletionImpact({
848
+ rules: factsFor([
849
+ EMAIL_ON_SEV1_INCIDENT,
850
+ SMS_ON_SEV1_INCIDENT,
851
+ EMAIL_ON_SEV2_ALERT,
852
+ ]),
853
+ methodId: EMAIL_METHOD_ID,
854
+ }),
855
+ exposure: exposure,
856
+ });
857
+
858
+ expect(sentence).toBe(
859
+ "You are an on-call responder in this project, reached through a team. " +
860
+ "Deleting Email: jane@example.com removes 2 notification rules and leaves Alert · Sev2 with no rule.",
861
+ );
862
+ });
863
+
864
+ test("a harmless delete is described as harmless", () => {
865
+ const sentence: string = describeMethodDeletion({
866
+ methodLabel: "SMS: +15550100",
867
+ impact: computeMethodDeletionImpact({
868
+ rules: factsFor([EMAIL_ON_SEV1_INCIDENT]),
869
+ methodId: SMS_METHOD_ID,
870
+ }),
871
+ exposure: exposure,
872
+ });
873
+
874
+ expect(sentence).toContain(
875
+ "No notification rules use SMS: +15550100, so deleting it does not change how you are paged.",
876
+ );
877
+ });
878
+
879
+ test("one rule and one cell are singular", () => {
880
+ const sentence: string = describeMethodDeletion({
881
+ methodLabel: "Email: jane@example.com",
882
+ impact: computeMethodDeletionImpact({
883
+ rules: factsFor([EMAIL_ON_SEV2_ALERT]),
884
+ methodId: EMAIL_METHOD_ID,
885
+ }),
886
+ exposure: UNKNOWN_ON_CALL_EXPOSURE,
887
+ });
888
+
889
+ expect(sentence).toBe(
890
+ "Deleting Email: jane@example.com removes 1 notification rule and leaves Alert · Sev2 with no rule.",
891
+ );
892
+ });
893
+
894
+ test("the rule sentence says whether this was the last one standing", () => {
895
+ expect(
896
+ describeRuleDeletion({
897
+ impact: computeRuleDeletionImpact({
898
+ rules: factsFor([EMAIL_ON_SEV2_ALERT]),
899
+ ruleId: "rule-c",
900
+ }),
901
+ exposure: UNKNOWN_ON_CALL_EXPOSURE,
902
+ }),
903
+ ).toBe(
904
+ "This is the last rule covering Alert · Sev2. Deleting it leaves that with no rule.",
905
+ );
906
+
907
+ expect(
908
+ describeRuleDeletion({
909
+ impact: computeRuleDeletionImpact({
910
+ rules: factsFor([EMAIL_ON_SEV1_INCIDENT, SMS_ON_SEV1_INCIDENT]),
911
+ ruleId: "rule-a",
912
+ }),
913
+ exposure: UNKNOWN_ON_CALL_EXPOSURE,
914
+ }),
915
+ ).toBe(
916
+ "Other rules still cover this severity and rule type, so deleting this one does not leave a gap.",
917
+ );
918
+ });
919
+
920
+ /*
921
+ * "Other rules still cover this" is a CLAIM about somebody else picking the
922
+ * page up. An opt-out picks nothing up - it is the row that says do not send -
923
+ * so a cell whose only survivor is one has not been covered by anything, and
924
+ * the sentence that says it has is a false reassurance delivered at the exact
925
+ * moment a person is deciding whether to click Delete.
926
+ */
927
+ test("an opt-out is never described as other rules still covering the cell", () => {
928
+ const sentence: string = describeRuleDeletion({
929
+ impact: computeRuleDeletionImpact({
930
+ rules: factsFor([EMAIL_ON_SEV2_ALERT, MUTED_SEV2_ALERT]),
931
+ ruleId: "rule-c",
932
+ }),
933
+ exposure: UNKNOWN_ON_CALL_EXPOSURE,
934
+ });
935
+
936
+ expect(sentence).not.toContain("Other rules still cover");
937
+ expect(sentence).toBe(
938
+ "This is the last rule that delivers for Alert · Sev2. " +
939
+ "The only rule left for it is an opt-out, so nothing will be sent for it once this is gone.",
940
+ );
941
+ });
942
+
943
+ /*
944
+ * A muted cell is still not a WARNING. Nothing goes amber, nothing is listed
945
+ * as left with no rule - the user asked for that silence and gets to keep it.
946
+ */
947
+ test("a muted cell is stated, not raised as a coverage loss", () => {
948
+ const impact: DeletionImpact = computeRuleDeletionImpact({
949
+ rules: factsFor([EMAIL_ON_SEV2_ALERT, MUTED_SEV2_ALERT]),
950
+ ruleId: "rule-c",
951
+ });
952
+
953
+ expect(impact.orphanedCells).toHaveLength(0);
954
+ expect(
955
+ describeRuleDeletion({
956
+ impact: impact,
957
+ exposure: UNKNOWN_ON_CALL_EXPOSURE,
958
+ }),
959
+ ).not.toContain("with no rule");
960
+ });
961
+
962
+ /*
963
+ * The third way orphanedCells comes back empty: the rule was not among the
964
+ * ones we loaded. We do not know what it covered, and a confident "no gap"
965
+ * about a row we could not find is a guess with a sentence wrapped round it.
966
+ */
967
+ test("a rule we could not find is not reported as leaving no gap", () => {
968
+ const sentence: string = describeRuleDeletion({
969
+ impact: computeRuleDeletionImpact({
970
+ rules: factsFor([EMAIL_ON_SEV1_INCIDENT]),
971
+ ruleId: "rule-we-never-loaded",
972
+ }),
973
+ exposure: UNKNOWN_ON_CALL_EXPOSURE,
974
+ });
975
+
976
+ expect(sentence).not.toContain("does not leave a gap");
977
+ expect(sentence).toBe(
978
+ "We could not match this rule to your notification rules, so we cannot tell what deleting it leaves uncovered.",
979
+ );
980
+ });
981
+
982
+ /*
983
+ * The sentence is dropped rather than softened. "You are not a responder on
984
+ * any on-call policy" is a reassurance, and printing it because the server
985
+ * did not answer is the most dangerous shape this warning could take.
986
+ */
987
+ test("an unknown exposure produces no exposure sentence at all", () => {
988
+ expect(describeOnCallExposure(UNKNOWN_ON_CALL_EXPOSURE)).toBe("");
989
+ });
990
+
991
+ test("a known non-responder is told so", () => {
992
+ expect(
993
+ describeOnCallExposure({
994
+ isKnown: true,
995
+ isOnCallResponder: false,
996
+ sources: [],
997
+ }),
998
+ ).toBe(
999
+ "You are not currently a responder on any on-call policy in this project.",
1000
+ );
1001
+ });
1002
+
1003
+ test("the doors a responder is reached through are named", () => {
1004
+ expect(
1005
+ describeOnCallExposure({
1006
+ isKnown: true,
1007
+ isOnCallResponder: true,
1008
+ sources: ["Team", "Schedule"],
1009
+ }),
1010
+ ).toBe(
1011
+ "You are an on-call responder in this project, reached through a team and an on-call schedule.",
1012
+ );
1013
+ });
1014
+ });
1015
+
1016
+ describe("Delete guards: reading exposure off the readiness payload", () => {
1017
+ test("reachedVia becomes the exposure", () => {
1018
+ const exposure: OnCallExposure = parseOnCallExposure(
1019
+ readinessJson({ reachedVia: ["Direct", "Schedule"] }),
1020
+ );
1021
+
1022
+ expect(exposure).toEqual({
1023
+ isKnown: true,
1024
+ isOnCallResponder: true,
1025
+ sources: ["Direct", "Schedule"],
1026
+ });
1027
+ });
1028
+
1029
+ test("an empty reachedVia is a known non-responder", () => {
1030
+ expect(parseOnCallExposure(readinessJson({}))).toEqual({
1031
+ isKnown: true,
1032
+ isOnCallResponder: false,
1033
+ sources: [],
1034
+ });
1035
+ });
1036
+
1037
+ test("a payload with no user in it is unknown, not empty", () => {
1038
+ expect(parseOnCallExposure({})).toEqual(UNKNOWN_ON_CALL_EXPOSURE);
1039
+ });
1040
+
1041
+ test("a source this build has never heard of is dropped, not rendered", () => {
1042
+ const exposure: OnCallExposure = parseOnCallExposure(
1043
+ readinessJson({ reachedVia: ["Direct", "SomethingNew"] }),
1044
+ );
1045
+
1046
+ expect(exposure.sources).toEqual(["Direct"]);
1047
+ });
1048
+ });
1049
+
1050
+ /*
1051
+ * The two rendered surfaces. Both load the caller's own rules through ModelAPI
1052
+ * and their exposure through the readiness route, so both are driven here by the
1053
+ * same pair of mocks.
1054
+ */
1055
+ type RuleModelFunction = (spec: RuleSpec) => any;
1056
+
1057
+ const ruleModel: RuleModelFunction = (spec: RuleSpec): any => {
1058
+ const reader: ColumnValueReader = ruleReader(spec);
1059
+
1060
+ return {
1061
+ getColumnValue: (columnName: string): unknown => {
1062
+ return reader.getColumnValue(columnName);
1063
+ },
1064
+ };
1065
+ };
1066
+
1067
+ type MockRulesFunction = (specs: Array<RuleSpec>) => void;
1068
+
1069
+ const mockRules: MockRulesFunction = (specs: Array<RuleSpec>): void => {
1070
+ getCommonHeadersMock.mockReturnValue({});
1071
+ getListMock.mockResolvedValue({
1072
+ data: specs.map(ruleModel),
1073
+ count: specs.length,
1074
+ skip: 0,
1075
+ limit: 50,
1076
+ } as never);
1077
+ };
1078
+
1079
+ describe("Delete guards: the confirmation an admin actually reads", () => {
1080
+ test("it names the rules and the cells the method takes with it", async () => {
1081
+ mockRules([
1082
+ EMAIL_ON_SEV1_INCIDENT,
1083
+ SMS_ON_SEV1_INCIDENT,
1084
+ EMAIL_ON_SEV2_ALERT,
1085
+ ]);
1086
+ getMock.mockResolvedValue(
1087
+ okResponse(readinessJson({ reachedVia: ["Team"] })) as never,
1088
+ );
1089
+
1090
+ render(
1091
+ <DeletionImpactModal
1092
+ target={{
1093
+ type: "method",
1094
+ methodId: EMAIL_METHOD_ID,
1095
+ methodLabel: "Email: jane@example.com",
1096
+ }}
1097
+ userId={new ObjectID(USER_ALEX)}
1098
+ projectId={PROJECT_ID}
1099
+ title="Delete Email"
1100
+ submitButtonText="Delete"
1101
+ onClose={() => {}}
1102
+ onConfirm={() => {}}
1103
+ />,
1104
+ );
1105
+
1106
+ await waitFor(() => {
1107
+ expect(screen.getByTestId("confirm-modal-description")).toHaveTextContent(
1108
+ "Deleting Email: jane@example.com removes 2 notification rules and leaves Alert · Sev2 with no rule.",
1109
+ );
1110
+ });
1111
+
1112
+ expect(screen.getByTestId("deletion-impact-cells")).toHaveTextContent(
1113
+ "Alert · Sev2",
1114
+ );
1115
+ });
1116
+
1117
+ test("it says who is relying on this person when the server knows", async () => {
1118
+ mockRules([EMAIL_ON_SEV2_ALERT]);
1119
+ getMock.mockResolvedValue(
1120
+ okResponse(readinessJson({ reachedVia: ["Team"] })) as never,
1121
+ );
1122
+
1123
+ render(
1124
+ <DeletionImpactModal
1125
+ target={{
1126
+ type: "method",
1127
+ methodId: EMAIL_METHOD_ID,
1128
+ methodLabel: "Email: jane@example.com",
1129
+ }}
1130
+ userId={new ObjectID(USER_ALEX)}
1131
+ projectId={PROJECT_ID}
1132
+ title="Delete Email"
1133
+ submitButtonText="Delete"
1134
+ onClose={() => {}}
1135
+ onConfirm={() => {}}
1136
+ />,
1137
+ );
1138
+
1139
+ await waitFor(() => {
1140
+ expect(screen.getByTestId("confirm-modal-description")).toHaveTextContent(
1141
+ "You are an on-call responder in this project, reached through a team.",
1142
+ );
1143
+ });
1144
+ });
1145
+
1146
+ /*
1147
+ * The readiness call failing must not take the counts down with it. They are
1148
+ * computed from a different read and they are the half that has to be right.
1149
+ */
1150
+ test("a failed exposure lookup still leaves the counts standing", async () => {
1151
+ mockRules([EMAIL_ON_SEV2_ALERT]);
1152
+ getMock.mockRejectedValue(new Error("readiness is down") as never);
1153
+
1154
+ render(
1155
+ <DeletionImpactModal
1156
+ target={{
1157
+ type: "method",
1158
+ methodId: EMAIL_METHOD_ID,
1159
+ methodLabel: "Email: jane@example.com",
1160
+ }}
1161
+ userId={new ObjectID(USER_ALEX)}
1162
+ projectId={PROJECT_ID}
1163
+ title="Delete Email"
1164
+ submitButtonText="Delete"
1165
+ onClose={() => {}}
1166
+ onConfirm={() => {}}
1167
+ />,
1168
+ );
1169
+
1170
+ await waitFor(() => {
1171
+ expect(screen.getByTestId("confirm-modal-description")).toHaveTextContent(
1172
+ "Deleting Email: jane@example.com removes 1 notification rule",
1173
+ );
1174
+ });
1175
+
1176
+ expect(
1177
+ screen.getByTestId("confirm-modal-description"),
1178
+ ).not.toHaveTextContent("on-call responder");
1179
+ });
1180
+
1181
+ /*
1182
+ * The honest failure. We know a delete is imminent and we do not know what it
1183
+ * costs; saying nothing would let the generic confirmation imply we had
1184
+ * checked.
1185
+ */
1186
+ test("it admits when it could not work the cost out", async () => {
1187
+ getCommonHeadersMock.mockReturnValue({});
1188
+ getListMock.mockRejectedValue(new Error("rules are unavailable") as never);
1189
+ getMock.mockRejectedValue(new Error("readiness is down") as never);
1190
+
1191
+ render(
1192
+ <DeletionImpactModal
1193
+ target={{
1194
+ type: "method",
1195
+ methodId: EMAIL_METHOD_ID,
1196
+ methodLabel: "Email: jane@example.com",
1197
+ }}
1198
+ userId={new ObjectID(USER_ALEX)}
1199
+ projectId={PROJECT_ID}
1200
+ title="Delete Email"
1201
+ submitButtonText="Delete"
1202
+ onClose={() => {}}
1203
+ onConfirm={() => {}}
1204
+ />,
1205
+ );
1206
+
1207
+ await waitFor(() => {
1208
+ expect(screen.getByTestId("confirm-modal-description")).toHaveTextContent(
1209
+ "We could not check what this deletes: rules are unavailable",
1210
+ );
1211
+ });
1212
+
1213
+ expect(screen.getByTestId("confirm-modal-description")).toHaveTextContent(
1214
+ "Deleting a notification method also deletes every notification rule that uses it.",
1215
+ );
1216
+ });
1217
+
1218
+ test("confirming is what deletes, and it is never taken away", async () => {
1219
+ const onConfirm: MockFunction = getJestMockFunction();
1220
+
1221
+ mockRules([EMAIL_ON_SEV2_ALERT]);
1222
+ getMock.mockResolvedValue(okResponse(readinessJson({})) as never);
1223
+
1224
+ render(
1225
+ <DeletionImpactModal
1226
+ target={{
1227
+ type: "method",
1228
+ methodId: EMAIL_METHOD_ID,
1229
+ methodLabel: "Email: jane@example.com",
1230
+ }}
1231
+ userId={new ObjectID(USER_ALEX)}
1232
+ projectId={PROJECT_ID}
1233
+ title="Delete Email"
1234
+ submitButtonText="Delete"
1235
+ onClose={() => {}}
1236
+ onConfirm={onConfirm as unknown as () => void}
1237
+ />,
1238
+ );
1239
+
1240
+ await waitFor(() => {
1241
+ expect(screen.getByTestId("confirm-modal-description")).toHaveTextContent(
1242
+ "removes 1 notification rule",
1243
+ );
1244
+ });
1245
+
1246
+ fireEvent.click(screen.getByText("Delete"));
1247
+
1248
+ expect(onConfirm).toHaveBeenCalled();
1249
+ });
1250
+
1251
+ test("the rule variant answers the only question that matters", async () => {
1252
+ mockRules([EMAIL_ON_SEV1_INCIDENT, SMS_ON_SEV1_INCIDENT]);
1253
+ getMock.mockResolvedValue(okResponse(readinessJson({})) as never);
1254
+
1255
+ render(
1256
+ <DeletionImpactModal
1257
+ target={{ type: "rule", ruleId: "rule-a" }}
1258
+ userId={new ObjectID(USER_ALEX)}
1259
+ projectId={PROJECT_ID}
1260
+ title="Delete Notification Rule"
1261
+ submitButtonText="Delete"
1262
+ onClose={() => {}}
1263
+ onConfirm={() => {}}
1264
+ />,
1265
+ );
1266
+
1267
+ await waitFor(() => {
1268
+ expect(screen.getByTestId("confirm-modal-description")).toHaveTextContent(
1269
+ "Other rules still cover this severity and rule type",
1270
+ );
1271
+ });
1272
+ });
1273
+ });
1274
+
1275
+ /*
1276
+ * The escalation-rule surface rendered for real, wiring and all.
1277
+ *
1278
+ * The pure helpers above prove the arithmetic; this proves it is CONNECTED. A
1279
+ * guard whose copy is perfect and whose confirmation modal still shows the old
1280
+ * generic sentence has prevented nothing, and that failure is invisible to every
1281
+ * test that stops at the function boundary.
1282
+ */
1283
+ const POLICY_ID: ObjectID = new ObjectID(
1284
+ "77777777-9999-4999-8999-999999999999",
1285
+ );
1286
+
1287
+ const RULE_ONE_ID: ObjectID = new ObjectID(
1288
+ "88888888-1010-4010-8010-101010101010",
1289
+ );
1290
+ const RULE_TWO_ID: ObjectID = new ObjectID(
1291
+ "99999999-2020-4020-8020-202020202020",
1292
+ );
1293
+
1294
+ type EscalationDataFunction = () => void;
1295
+
1296
+ const mockEscalationPolicy: EscalationDataFunction = (): void => {
1297
+ getCommonHeadersMock.mockReturnValue({});
1298
+
1299
+ getItemMock.mockResolvedValue({
1300
+ repeatPolicyIfNoOneAcknowledges: false,
1301
+ repeatPolicyIfNoOneAcknowledgesNoOfTimes: 0,
1302
+ } as never);
1303
+
1304
+ /*
1305
+ * Keyed on the SELECT rather than the model class, because all three join
1306
+ * reads issue the same query against the same policy and only their selects
1307
+ * tell them apart - which is also exactly how a reader of the component tells
1308
+ * them apart.
1309
+ */
1310
+ getListMock.mockImplementation((params: any): Promise<any> => {
1311
+ const select: Record<string, unknown> = params.select || {};
1312
+
1313
+ if (select["escalateAfterInMinutes"]) {
1314
+ return Promise.resolve({
1315
+ data: [
1316
+ {
1317
+ id: RULE_ONE_ID,
1318
+ name: "First Responders",
1319
+ escalateAfterInMinutes: 5,
1320
+ order: 1,
1321
+ },
1322
+ {
1323
+ id: RULE_TWO_ID,
1324
+ name: "Backup",
1325
+ escalateAfterInMinutes: 10,
1326
+ order: 2,
1327
+ },
1328
+ ],
1329
+ count: 2,
1330
+ skip: 0,
1331
+ limit: 50,
1332
+ });
1333
+ }
1334
+
1335
+ if (select["user"]) {
1336
+ return Promise.resolve({
1337
+ data: [
1338
+ {
1339
+ id: new ObjectID("aaaaaaaa-0001-4001-8001-000000000001"),
1340
+ onCallDutyPolicyEscalationRuleId: RULE_ONE_ID,
1341
+ user: { id: new ObjectID(USER_ALEX), name: "Alex Chen" },
1342
+ },
1343
+ {
1344
+ id: new ObjectID("aaaaaaaa-0002-4002-8002-000000000002"),
1345
+ onCallDutyPolicyEscalationRuleId: RULE_ONE_ID,
1346
+ user: { id: new ObjectID(USER_SAM), name: "Sam Doe" },
1347
+ },
1348
+ {
1349
+ id: new ObjectID("aaaaaaaa-0003-4003-8003-000000000003"),
1350
+ onCallDutyPolicyEscalationRuleId: RULE_TWO_ID,
1351
+ user: { id: new ObjectID(USER_SAM), name: "Sam Doe" },
1352
+ },
1353
+ ],
1354
+ count: 3,
1355
+ skip: 0,
1356
+ limit: 50,
1357
+ });
1358
+ }
1359
+
1360
+ if (select["team"]) {
1361
+ return Promise.resolve({ data: [], count: 0, skip: 0, limit: 50 });
1362
+ }
1363
+
1364
+ return Promise.resolve({ data: [], count: 0, skip: 0, limit: 50 });
1365
+ });
1366
+
1367
+ // The policy readiness the escalation summary loads for its chip dots.
1368
+ getMock.mockResolvedValue(
1369
+ okResponse({
1370
+ projectId: PROJECT_ID.toString(),
1371
+ onCallDutyPolicyId: POLICY_ID.toString(),
1372
+ isFallbackEnabled: true,
1373
+ isTruncated: false,
1374
+ totalCount: 0,
1375
+ hasMore: false,
1376
+ users: [],
1377
+ }) as never,
1378
+ );
1379
+ };
1380
+
1381
+ describe("Escalation rules, wired: the delete confirmation says what it costs", () => {
1382
+ test("the confirmation counts the level's responders and names who else loses cover", async () => {
1383
+ mockEscalationPolicy();
1384
+
1385
+ render(
1386
+ <EscalationRules onCallDutyPolicyId={POLICY_ID} projectId={PROJECT_ID} />,
1387
+ );
1388
+
1389
+ await waitFor(() => {
1390
+ /*
1391
+ * The name appears twice on purpose - once in the escalation summary and
1392
+ * once on the rule card - so the wait is on the delete control, which
1393
+ * exists exactly once per level and is the thing the next line clicks.
1394
+ */
1395
+ expect(screen.getAllByLabelText("Delete rule").length).toBeGreaterThan(0);
1396
+ });
1397
+
1398
+ fireEvent.click(screen.getAllByLabelText("Delete rule")[0]!);
1399
+
1400
+ await waitFor(() => {
1401
+ expect(
1402
+ screen.getByTestId("confirm-modal-description"),
1403
+ ).toBeInTheDocument();
1404
+ });
1405
+
1406
+ const description: string =
1407
+ screen.getByTestId("confirm-modal-description").textContent || "";
1408
+
1409
+ expect(description).toContain('"First Responders" notifies 2 users.');
1410
+ /*
1411
+ * Sam is on the second level too, so only Alex is left with nothing. The
1412
+ * generic sentence this replaced ("its notification targets will be
1413
+ * removed") could not tell those two apart.
1414
+ */
1415
+ expect(description).toContain(
1416
+ "Alex Chen is not named on any other level of this policy.",
1417
+ );
1418
+ expect(description).not.toContain("Sam Doe");
1419
+ expect(description).not.toContain(
1420
+ "This is the only escalation level on this policy",
1421
+ );
1422
+ });
1423
+
1424
+ test("deleting the last remaining level says the policy will page no one", async () => {
1425
+ mockEscalationPolicy();
1426
+ getListMock.mockImplementation((params: any): Promise<any> => {
1427
+ const select: Record<string, unknown> = params.select || {};
1428
+
1429
+ if (select["escalateAfterInMinutes"]) {
1430
+ return Promise.resolve({
1431
+ data: [
1432
+ {
1433
+ id: RULE_ONE_ID,
1434
+ name: "First Responders",
1435
+ escalateAfterInMinutes: 5,
1436
+ order: 1,
1437
+ },
1438
+ ],
1439
+ count: 1,
1440
+ skip: 0,
1441
+ limit: 50,
1442
+ });
1443
+ }
1444
+
1445
+ return Promise.resolve({ data: [], count: 0, skip: 0, limit: 50 });
1446
+ });
1447
+
1448
+ render(
1449
+ <EscalationRules onCallDutyPolicyId={POLICY_ID} projectId={PROJECT_ID} />,
1450
+ );
1451
+
1452
+ await waitFor(() => {
1453
+ /*
1454
+ * The name appears twice on purpose - once in the escalation summary and
1455
+ * once on the rule card - so the wait is on the delete control, which
1456
+ * exists exactly once per level and is the thing the next line clicks.
1457
+ */
1458
+ expect(screen.getAllByLabelText("Delete rule").length).toBeGreaterThan(0);
1459
+ });
1460
+
1461
+ fireEvent.click(screen.getAllByLabelText("Delete rule")[0]!);
1462
+
1463
+ await waitFor(() => {
1464
+ expect(screen.getByTestId("confirm-modal-description")).toHaveTextContent(
1465
+ "This is the only escalation level on this policy. Deleting it leaves the policy with nobody to notify, so an incident routed here would page no one.",
1466
+ );
1467
+ });
1468
+ });
1469
+ });
1470
+
1471
+ /*
1472
+ * ---------------------------------------------------------------------------
1473
+ * THE SEVEN METHOD TABLES, RENDERED FOR REAL.
1474
+ * ---------------------------------------------------------------------------
1475
+ *
1476
+ * This block exists because the first cut of this feature shipped a correct,
1477
+ * fully tested DeletionImpactModal that NOTHING OPENED. Every table still
1478
+ * passed `isDeleteable={true}`, so a delete went through ModelTable's generic
1479
+ * "are you sure" and none of the arithmetic above ever ran. Every test passed.
1480
+ *
1481
+ * So these tests refuse to hand the modal its props. They render the real
1482
+ * component, wait for the real row, click the real Delete control, and assert on
1483
+ * what a person would then be looking at. The only thing that can make them pass
1484
+ * is a delete that actually goes through the guard.
1485
+ *
1486
+ * The second assertion in each case is the one that catches the half-measure:
1487
+ * exactly ONE Delete control per row. `isDeleteable={true}` left alongside a
1488
+ * custom Delete action gives two, and whichever one the user reaches for first
1489
+ * decides whether they are told anything.
1490
+ */
1491
+
1492
+ interface MethodSurface {
1493
+ /* What the tab is called, and the noun in the modal title. */
1494
+ name: string;
1495
+ Component: () => ReactElement;
1496
+ modelType: DatabaseBaseModelType;
1497
+ /* The relation on UserNotificationRule that points at this model. */
1498
+ relationName: string;
1499
+ methodId: string;
1500
+ /* One row, as the table's own list read would return it. */
1501
+ buildRow: () => BaseModel;
1502
+ /* The title the impact modal is expected to wear. */
1503
+ modalTitle: string;
1504
+ /* How the method is expected to be named in the sentence. */
1505
+ methodLabel: string;
1506
+ }
1507
+
1508
+ type BuildRowFunction = (
1509
+ modelType: DatabaseBaseModelType,
1510
+ methodId: string,
1511
+ columns: JSONObject,
1512
+ ) => BaseModel;
1513
+
1514
+ const buildMethodRow: BuildRowFunction = (
1515
+ modelType: DatabaseBaseModelType,
1516
+ methodId: string,
1517
+ columns: JSONObject,
1518
+ ): BaseModel => {
1519
+ const model: BaseModel = new modelType();
1520
+
1521
+ model.id = new ObjectID(methodId);
1522
+
1523
+ for (const columnName of Object.keys(columns)) {
1524
+ (model as unknown as Record<string, unknown>)[columnName] =
1525
+ columns[columnName];
1526
+ }
1527
+
1528
+ return model;
1529
+ };
1530
+
1531
+ const METHOD_ID_CALL: string = "33333333-9999-4999-8999-999999999999";
1532
+ const METHOD_ID_PUSH: string = "44444444-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
1533
+ const METHOD_ID_WHATSAPP: string = "55555555-bbbb-4bbb-8bbb-bbbbbbbbbbbb";
1534
+ const METHOD_ID_TELEGRAM: string = "66666666-cccc-4ccc-8ccc-cccccccccccc";
1535
+ const METHOD_ID_WEBHOOK: string = "77777777-dddd-4ddd-8ddd-dddddddddddd";
1536
+
1537
+ const METHOD_SURFACES: Array<MethodSurface> = [
1538
+ {
1539
+ name: "Email",
1540
+ Component: EmailMethods,
1541
+ modelType: UserEmail,
1542
+ relationName: "userEmail",
1543
+ methodId: EMAIL_METHOD_ID,
1544
+ buildRow: (): BaseModel => {
1545
+ return buildMethodRow(UserEmail, EMAIL_METHOD_ID, {
1546
+ email: new Email("jane@example.com") as never,
1547
+ isVerified: true,
1548
+ });
1549
+ },
1550
+ modalTitle: "Delete Email",
1551
+ methodLabel: "Email: jane@example.com",
1552
+ },
1553
+ {
1554
+ name: "SMS",
1555
+ Component: SMSMethods,
1556
+ modelType: UserSMS,
1557
+ relationName: "userSms",
1558
+ methodId: SMS_METHOD_ID,
1559
+ buildRow: (): BaseModel => {
1560
+ return buildMethodRow(UserSMS, SMS_METHOD_ID, {
1561
+ phone: new Phone("+15551230100") as never,
1562
+ isVerified: true,
1563
+ });
1564
+ },
1565
+ modalTitle: "Delete Phone Number",
1566
+ methodLabel: "SMS: +15551230100",
1567
+ },
1568
+ {
1569
+ name: "Call",
1570
+ Component: CallMethods,
1571
+ modelType: UserCall,
1572
+ relationName: "userCall",
1573
+ methodId: METHOD_ID_CALL,
1574
+ buildRow: (): BaseModel => {
1575
+ return buildMethodRow(UserCall, METHOD_ID_CALL, {
1576
+ phone: new Phone("+15551230199") as never,
1577
+ isVerified: true,
1578
+ });
1579
+ },
1580
+ modalTitle: "Delete Phone Number",
1581
+ methodLabel: "Call: +15551230199",
1582
+ },
1583
+ {
1584
+ name: "Push",
1585
+ Component: PushMethods,
1586
+ modelType: UserPush,
1587
+ relationName: "userPush",
1588
+ methodId: METHOD_ID_PUSH,
1589
+ buildRow: (): BaseModel => {
1590
+ return buildMethodRow(UserPush, METHOD_ID_PUSH, {
1591
+ deviceName: "Chrome on macOS",
1592
+ });
1593
+ },
1594
+ modalTitle: "Delete Device",
1595
+ methodLabel: "Push: Chrome on macOS",
1596
+ },
1597
+ {
1598
+ name: "WhatsApp",
1599
+ Component: WhatsAppMethods,
1600
+ modelType: UserWhatsApp,
1601
+ relationName: "userWhatsApp",
1602
+ methodId: METHOD_ID_WHATSAPP,
1603
+ buildRow: (): BaseModel => {
1604
+ return buildMethodRow(UserWhatsApp, METHOD_ID_WHATSAPP, {
1605
+ phone: new Phone("+15551230123") as never,
1606
+ isVerified: true,
1607
+ });
1608
+ },
1609
+ modalTitle: "Delete WhatsApp Number",
1610
+ methodLabel: "WhatsApp: +15551230123",
1611
+ },
1612
+ {
1613
+ name: "Telegram",
1614
+ Component: TelegramMethods,
1615
+ modelType: UserTelegram,
1616
+ relationName: "userTelegram",
1617
+ methodId: METHOD_ID_TELEGRAM,
1618
+ buildRow: (): BaseModel => {
1619
+ return buildMethodRow(UserTelegram, METHOD_ID_TELEGRAM, {
1620
+ telegramUserHandle: "@alexchen",
1621
+ isVerified: true,
1622
+ });
1623
+ },
1624
+ modalTitle: "Delete Telegram Account",
1625
+ methodLabel: "Telegram: @alexchen",
1626
+ },
1627
+ {
1628
+ name: "Webhook",
1629
+ Component: WebhookMethods,
1630
+ modelType: UserWebhook,
1631
+ relationName: "userWebhook",
1632
+ methodId: METHOD_ID_WEBHOOK,
1633
+ buildRow: (): BaseModel => {
1634
+ return buildMethodRow(UserWebhook, METHOD_ID_WEBHOOK, {
1635
+ name: "Internal alerts",
1636
+ });
1637
+ },
1638
+ modalTitle: "Delete Webhook",
1639
+ methodLabel: "Webhook: Internal alerts",
1640
+ },
1641
+ ];
1642
+
1643
+ /*
1644
+ * The session these pages read themselves out of. The components take the user
1645
+ * and the project from the real utils rather than from props, so the storage
1646
+ * those utils read is set instead of stubbing them - the guard passing the
1647
+ * WRONG user id to the impact lookup is a failure worth being able to catch,
1648
+ * and a stub would hide it.
1649
+ */
1650
+ type SignInFunction = () => void;
1651
+
1652
+ const signIn: SignInFunction = (): void => {
1653
+ localStorage.setItem("user_id", USER_ALEX);
1654
+ localStorage.setItem("is_master_admin", "true");
1655
+ sessionStorage.setItem("current_project_id", PROJECT_ID.toString());
1656
+ };
1657
+
1658
+ /*
1659
+ * One rule, pointing at the method about to be deleted, covering Sev2 alerts and
1660
+ * nothing else. Deleting the method therefore costs exactly one rule and leaves
1661
+ * exactly one cell with nothing - numbers small enough to state in the assertion
1662
+ * and specific enough that a generic confirmation could not have produced them.
1663
+ */
1664
+ type MockMethodTableFunction = (surface: MethodSurface) => void;
1665
+
1666
+ const mockMethodTable: MockMethodTableFunction = (
1667
+ surface: MethodSurface,
1668
+ ): void => {
1669
+ getCommonHeadersMock.mockReturnValue({});
1670
+
1671
+ getListMock.mockImplementation((params: any): Promise<any> => {
1672
+ if (params.modelType === UserNotificationRule) {
1673
+ return Promise.resolve({
1674
+ data: [
1675
+ ruleModel({
1676
+ ruleId: "rule-on-this-method",
1677
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
1678
+ severityId: SEV2_ID,
1679
+ severityName: "Sev2",
1680
+ methodRelation: surface.relationName,
1681
+ methodId: surface.methodId,
1682
+ methodValue: "does-not-matter",
1683
+ }),
1684
+ ],
1685
+ count: 1,
1686
+ skip: 0,
1687
+ limit: 50,
1688
+ });
1689
+ }
1690
+
1691
+ if (params.modelType === surface.modelType) {
1692
+ return Promise.resolve({
1693
+ data: [surface.buildRow()],
1694
+ count: 1,
1695
+ skip: 0,
1696
+ limit: 50,
1697
+ });
1698
+ }
1699
+
1700
+ return Promise.resolve({ data: [], count: 0, skip: 0, limit: 50 });
1701
+ });
1702
+
1703
+ getMock.mockResolvedValue(
1704
+ okResponse(readinessJson({ reachedVia: ["Team"] })) as never,
1705
+ );
1706
+ };
1707
+
1708
+ type OpenDeleteFunction = () => Promise<void>;
1709
+
1710
+ const openDeleteConfirmation: OpenDeleteFunction = async (): Promise<void> => {
1711
+ await waitFor(() => {
1712
+ expect(screen.getAllByText("Delete").length).toBeGreaterThan(0);
1713
+ });
1714
+
1715
+ /*
1716
+ * Exactly one Delete control on the row. Two would mean the built-in delete
1717
+ * was left switched on beside the guard, and a user reaching for the wrong one
1718
+ * gets the generic confirmation and no numbers at all.
1719
+ */
1720
+ expect(screen.getAllByText("Delete")).toHaveLength(1);
1721
+
1722
+ fireEvent.click(screen.getByText("Delete"));
1723
+
1724
+ await waitFor(() => {
1725
+ expect(screen.getByTestId("confirm-modal-description")).toBeInTheDocument();
1726
+ });
1727
+
1728
+ /*
1729
+ * And what opened is the impact modal, not ModelTable's fixed-description
1730
+ * confirmation. Without this line a table that still routes its delete
1731
+ * through the built-in "Are you sure you want to delete this user email?"
1732
+ * would satisfy every step above.
1733
+ */
1734
+ expect(
1735
+ screen.queryByText(/Are you sure you want to delete/),
1736
+ ).not.toBeInTheDocument();
1737
+ };
1738
+
1739
+ /*
1740
+ * The confirmation's own button, not the row's. Once the modal is open the page
1741
+ * carries two controls reading "Delete" and clicking the wrong one would prove
1742
+ * nothing.
1743
+ */
1744
+ type ClickInModalFunction = (name: string) => void;
1745
+
1746
+ const clickInModal: ClickInModalFunction = (name: string): void => {
1747
+ fireEvent.click(
1748
+ within(screen.getByTestId("modal")).getByRole("button", { name: name }),
1749
+ );
1750
+ };
1751
+
1752
+ describe.each(METHOD_SURFACES)(
1753
+ "$name methods, wired: deleting goes through the impact modal",
1754
+ (surface: MethodSurface) => {
1755
+ test("the Delete control opens the impact modal, with the real numbers", async () => {
1756
+ signIn();
1757
+ mockMethodTable(surface);
1758
+
1759
+ render(<surface.Component />);
1760
+
1761
+ await openDeleteConfirmation();
1762
+
1763
+ await waitFor(() => {
1764
+ expect(
1765
+ screen.getByTestId("confirm-modal-description"),
1766
+ ).toHaveTextContent(
1767
+ `Deleting ${surface.methodLabel} removes 1 notification rule and leaves Alert · Sev2 with no rule.`,
1768
+ );
1769
+ });
1770
+
1771
+ // The cells are listed as well as counted.
1772
+ expect(screen.getByTestId("deletion-impact-cells")).toHaveTextContent(
1773
+ "Alert · Sev2",
1774
+ );
1775
+
1776
+ // The stakes, read off the readiness route for this user.
1777
+ expect(screen.getByTestId("confirm-modal-description")).toHaveTextContent(
1778
+ "You are an on-call responder in this project, reached through a team.",
1779
+ );
1780
+
1781
+ // Titled for this method, so the reader knows which row they are on.
1782
+ expect(screen.getByTestId("modal-title")).toHaveTextContent(
1783
+ surface.modalTitle,
1784
+ );
1785
+ });
1786
+
1787
+ test("the impact is computed for the signed-in user's own rules", async () => {
1788
+ signIn();
1789
+ mockMethodTable(surface);
1790
+
1791
+ render(<surface.Component />);
1792
+
1793
+ await openDeleteConfirmation();
1794
+
1795
+ const ruleReadCalls: Array<any> = (getListMock as any).mock.calls.filter(
1796
+ (call: Array<any>): boolean => {
1797
+ return call[0].modelType === UserNotificationRule;
1798
+ },
1799
+ );
1800
+
1801
+ expect(ruleReadCalls.length).toBeGreaterThan(0);
1802
+ expect(ruleReadCalls[0][0].query.userId.toString()).toBe(USER_ALEX);
1803
+ expect(ruleReadCalls[0][0].query.projectId.toString()).toBe(
1804
+ PROJECT_ID.toString(),
1805
+ );
1806
+ });
1807
+
1808
+ test("confirming is what deletes, and it deletes this row", async () => {
1809
+ signIn();
1810
+ mockMethodTable(surface);
1811
+ deleteItemMock.mockResolvedValue(undefined as never);
1812
+
1813
+ render(<surface.Component />);
1814
+
1815
+ await openDeleteConfirmation();
1816
+
1817
+ await waitFor(() => {
1818
+ expect(
1819
+ screen.getByTestId("confirm-modal-description"),
1820
+ ).toHaveTextContent("removes 1 notification rule");
1821
+ });
1822
+
1823
+ // The modal's own submit, which only exists once the guard has opened it.
1824
+ clickInModal("Delete");
1825
+
1826
+ await waitFor(() => {
1827
+ expect(deleteItemMock).toHaveBeenCalled();
1828
+ });
1829
+
1830
+ const deleteCall: any = (deleteItemMock as any).mock.calls[0][0];
1831
+
1832
+ expect(deleteCall.modelType).toBe(surface.modelType);
1833
+ expect(deleteCall.id.toString()).toBe(surface.methodId);
1834
+ });
1835
+
1836
+ /*
1837
+ * Opening the confirmation must not delete anything. It reads as obvious and
1838
+ * it is the property that lets the modal be honest rather than a gate: a
1839
+ * person who reads what it costs and changes their mind has to end up with
1840
+ * the method still there.
1841
+ */
1842
+ test("opening the confirmation deletes nothing on its own", async () => {
1843
+ signIn();
1844
+ mockMethodTable(surface);
1845
+
1846
+ render(<surface.Component />);
1847
+
1848
+ await openDeleteConfirmation();
1849
+
1850
+ expect(deleteItemMock).not.toHaveBeenCalled();
1851
+
1852
+ clickInModal("Cancel");
1853
+
1854
+ await waitFor(() => {
1855
+ expect(
1856
+ screen.queryByTestId("confirm-modal-description"),
1857
+ ).not.toBeInTheDocument();
1858
+ });
1859
+
1860
+ expect(deleteItemMock).not.toHaveBeenCalled();
1861
+ });
1862
+
1863
+ /*
1864
+ * A delete that fails leaves the row on screen. Closing the modal on failure
1865
+ * would leave a person looking at a method they believe they just deleted.
1866
+ */
1867
+ test("a failed delete is reported in the modal rather than swallowed", async () => {
1868
+ signIn();
1869
+ mockMethodTable(surface);
1870
+ deleteItemMock.mockRejectedValue(
1871
+ new Error("You do not have permission to delete this.") as never,
1872
+ );
1873
+
1874
+ render(<surface.Component />);
1875
+
1876
+ await openDeleteConfirmation();
1877
+
1878
+ await waitFor(() => {
1879
+ expect(
1880
+ screen.getByTestId("confirm-modal-description"),
1881
+ ).toHaveTextContent("removes 1 notification rule");
1882
+ });
1883
+
1884
+ clickInModal("Delete");
1885
+
1886
+ await waitFor(() => {
1887
+ expect(
1888
+ screen.getByText("You do not have permission to delete this."),
1889
+ ).toBeInTheDocument();
1890
+ });
1891
+
1892
+ expect(
1893
+ screen.getByTestId("confirm-modal-description"),
1894
+ ).toBeInTheDocument();
1895
+ });
1896
+ },
1897
+ );