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