@oneuptime/common 12.0.7 → 12.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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} +184 -9
- package/Models/DatabaseModels/InventoryItemCustomField.ts +434 -0
- package/Models/DatabaseModels/{TelemetryEntityRelationship.ts → InventoryItemRelationship.ts} +33 -7
- 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/WorkflowLog.ts +38 -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/API/UserAPI.ts +16 -1
- 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/1786551733814-MigrationName.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.ts +17 -0
- 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 +24 -0
- package/Server/Infrastructure/Queue.ts +78 -13
- package/Server/Middleware/MasterAdminAuthorization.ts +11 -6
- 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} +7 -4
- package/Server/Services/{TelemetryEntityService.ts → InventoryItemService.ts} +203 -21
- 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/API/Utils.ts +44 -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/Types/Workflow/TriggerCode.ts +12 -0
- package/Server/Types/Workflow/Workflow.ts +5 -0
- 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 +689 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +160 -52
- package/Server/Utils/VM/VMAPI.ts +56 -8
- 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/MonitorCriteriaAttributeFilter.test.tsx +574 -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/API/UserProjectsAPI.test.ts +478 -4
- 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/InventoryItemManualCreate.test.ts +248 -0
- 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/ApiComponentHeaders.test.ts +192 -0
- 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/AnalyticsDatabase/QuerySettingsHelper.test.ts +152 -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 +322 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +356 -39
- package/Tests/Server/Utils/VM/VMAPISubstitution.test.ts +243 -0
- 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/SerializableObjectDictionaryImportCycle.test.ts +197 -0
- package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +140 -0
- 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/StepTrace.test.ts +235 -0
- package/Tests/Types/Workflow/TemplateSyntax.test.ts +491 -0
- package/Tests/Types/Workflow/Templates.test.ts +1218 -0
- package/Tests/UI/Components/ActiveFilterChipsOpenRoute.test.tsx +82 -0
- package/Tests/UI/Components/ComponentsModal.test.tsx +564 -7
- package/Tests/UI/Components/CustomTimeRangeModal.test.tsx +459 -0
- package/Tests/UI/Components/DictionaryAttributeFilterRow.test.tsx +477 -0
- package/Tests/UI/Components/Forms/AnchoredFieldPopupKeyboard.test.tsx +382 -0
- package/Tests/UI/Components/Forms/ColorPickerPicking.test.tsx +569 -0
- package/Tests/UI/Components/Forms/ValidationJSON.test.ts +241 -0
- package/Tests/UI/Components/KeyboardShortcut.test.tsx +95 -0
- package/Tests/UI/Components/LogDetailsPanelCrossSignal.test.tsx +448 -0
- package/Tests/UI/Components/LogTimeRangePicker.test.tsx +42 -0
- package/Tests/UI/Components/LogsTableCrossLinks.test.tsx +263 -0
- package/Tests/UI/Components/ModalPortalDismissal.test.tsx +90 -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/TelemetryTimeRangePicker.test.tsx +49 -7
- package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +325 -0
- package/Tests/UI/Components/Workflow/GraphLint.test.ts +893 -0
- package/Tests/UI/Components/Workflow/GraphLintSummary.test.ts +755 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.test.ts +522 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditorServerContract.test.ts +193 -0
- package/Tests/UI/Components/Workflow/ModelSchema.test.ts +388 -0
- package/Tests/UI/Components/Workflow/RunStatusWatcher.test.ts +210 -0
- 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/Utils.test.ts +192 -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/Monitor/MonitorStepCriteriaView.test.tsx +432 -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/NetworkDeviceRoleUtil.test.ts +514 -0
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +727 -0
- package/Tests/Utils/Schema/AnalyticsModelSchema.test.ts +469 -0
- package/Tests/Utils/Schema/ModelSchema.test.ts +268 -0
- package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +698 -0
- package/Tests/Utils/Telemetry/EntityKeyNonTelemetry.test.ts +193 -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 +80 -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/SerializableObjectDictionary.ts +133 -39
- package/Types/Telemetry/EntityRelationshipType.ts +1 -1
- package/Types/Telemetry/EntitySource.ts +40 -0
- package/Types/Telemetry/EntityType.ts +28 -1
- package/Types/Telemetry/EntityTypeGroups.ts +65 -0
- package/Types/Workflow/Component.ts +29 -0
- package/Types/Workflow/Components/API.ts +35 -0
- package/Types/Workflow/Components/BaseModel.ts +77 -27
- 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/StepTrace.ts +181 -0
- package/Types/Workflow/TemplateSyntax.ts +543 -0
- package/Types/Workflow/Templates.ts +2368 -0
- 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/Date/CustomTimeRangeModal.tsx +278 -0
- package/UI/Components/Date/TimeRangePickerDropdown.tsx +250 -0
- package/UI/Components/Dictionary/Dictionary.tsx +71 -15
- package/UI/Components/FormModal/BasicFormModal.tsx +2 -1
- package/UI/Components/Forms/Fields/ColorPicker.tsx +66 -10
- package/UI/Components/Forms/Fields/IconPicker.tsx +48 -10
- package/UI/Components/Forms/Types/Field.ts +7 -0
- package/UI/Components/Forms/Validation.ts +63 -1
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +53 -3
- package/UI/Components/Input/Input.tsx +32 -3
- 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/LogTimeRangePicker.tsx +11 -216
- 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/Modal/Modal.tsx +37 -4
- 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/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +11 -210
- package/UI/Components/Workflow/ArgumentsForm.tsx +341 -8
- package/UI/Components/Workflow/Component.tsx +28 -26
- package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +26 -0
- package/UI/Components/Workflow/ComponentSettingsModal.tsx +79 -9
- 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 +628 -0
- package/UI/Components/Workflow/GraphLintSummary.ts +390 -0
- package/UI/Components/Workflow/ModelColumnEditor.tsx +528 -0
- package/UI/Components/Workflow/ModelSchema.ts +278 -0
- package/UI/Components/Workflow/RunForm.tsx +41 -7
- package/UI/Components/Workflow/RunStatusWatcher.ts +122 -0
- package/UI/Components/Workflow/StepTraceViewer.tsx +186 -0
- package/UI/Components/Workflow/UseRunWatch.ts +212 -0
- package/UI/Components/Workflow/Utils.ts +93 -1
- package/UI/Components/Workflow/VariableModal.tsx +6 -2
- package/UI/Components/Workflow/Workflow.tsx +126 -7
- 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/Types/LayeredDismissal.ts +31 -0
- package/UI/Types/UseAnchoredFieldPopup.ts +99 -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/NetworkDeviceRoleUtil.ts +533 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +917 -155
- package/Utils/Telemetry/CrossSignalScope.ts +502 -0
- package/Utils/Telemetry/EntityKey.ts +71 -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} +212 -29
- 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} +52 -25
- 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/WorkflowLog.js +39 -0
- package/build/dist/Models/DatabaseModels/WorkflowLog.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/API/UserAPI.js +16 -1
- package/build/dist/Server/API/UserAPI.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/1786551733814-MigrationName.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js.map +1 -0
- 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 +24 -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/MasterAdminAuthorization.js +11 -6
- package/build/dist/Server/Middleware/MasterAdminAuthorization.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} +9 -6
- package/build/dist/Server/Services/InventoryItemRelationshipService.js.map +1 -0
- package/build/dist/Server/Services/{TelemetryEntityService.js → InventoryItemService.js} +158 -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/API/Utils.js +28 -0
- package/build/dist/Server/Types/Workflow/Components/API/Utils.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/Types/Workflow/TriggerCode.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 +507 -0
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -0
- 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/VM/VMAPI.js +51 -4
- package/build/dist/Server/Utils/VM/VMAPI.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/SerializableObjectDictionary.js +133 -39
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityRelationshipType.js +1 -1
- package/build/dist/Types/Telemetry/EntitySource.js +39 -0
- package/build/dist/Types/Telemetry/EntitySource.js.map +1 -0
- package/build/dist/Types/Telemetry/EntityType.js +28 -1
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityTypeGroups.js +57 -0
- package/build/dist/Types/Telemetry/EntityTypeGroups.js.map +1 -0
- package/build/dist/Types/Workflow/Component.js +21 -0
- package/build/dist/Types/Workflow/Component.js.map +1 -1
- package/build/dist/Types/Workflow/Components/API.js +35 -0
- package/build/dist/Types/Workflow/Components/API.js.map +1 -1
- package/build/dist/Types/Workflow/Components/BaseModel.js +68 -26
- 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/StepTrace.js +104 -0
- package/build/dist/Types/Workflow/StepTrace.js.map +1 -0
- package/build/dist/Types/Workflow/TemplateSyntax.js +338 -0
- package/build/dist/Types/Workflow/TemplateSyntax.js.map +1 -0
- package/build/dist/Types/Workflow/Templates.js +2111 -0
- package/build/dist/Types/Workflow/Templates.js.map +1 -0
- 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/Date/CustomTimeRangeModal.js +126 -0
- package/build/dist/UI/Components/Date/CustomTimeRangeModal.js.map +1 -0
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +123 -0
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -0
- package/build/dist/UI/Components/Dictionary/Dictionary.js +47 -17
- 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/Forms/Fields/ColorPicker.js +52 -13
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js +28 -12
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +44 -0
- package/build/dist/UI/Components/Forms/Validation.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 +13 -4
- 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/LogTimeRangePicker.js +5 -108
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.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/Modal/Modal.js +31 -5
- package/build/dist/UI/Components/Modal/Modal.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/TelemetryViewer/components/TelemetryTimeRangePicker.js +5 -105
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +261 -14
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Component.js +20 -19
- package/build/dist/UI/Components/Workflow/Component.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 +35 -7
- 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 +425 -0
- package/build/dist/UI/Components/Workflow/GraphLint.js.map +1 -0
- 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 +320 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelSchema.js +156 -0
- package/build/dist/UI/Components/Workflow/ModelSchema.js.map +1 -0
- package/build/dist/UI/Components/Workflow/RunForm.js +22 -6
- package/build/dist/UI/Components/Workflow/RunForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js +76 -0
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js.map +1 -0
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js +76 -0
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -0
- 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/Utils.js +73 -1
- package/build/dist/UI/Components/Workflow/Utils.js.map +1 -1
- 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 +92 -7
- 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/Types/LayeredDismissal.js +21 -0
- package/build/dist/UI/Types/LayeredDismissal.js.map +1 -0
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js +74 -1
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js.map +1 -1
- 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/NetworkDeviceRoleUtil.js +386 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +661 -126
- 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 +57 -5
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- 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,1728 @@
|
|
|
1
|
+
import UserNotificationRuleService from "../../../Server/Services/UserNotificationRuleService";
|
|
2
|
+
import UserOnCallLogService from "../../../Server/Services/UserOnCallLogService";
|
|
3
|
+
import UserOnCallLogTimelineService from "../../../Server/Services/UserOnCallLogTimelineService";
|
|
4
|
+
import ProjectCallSMSConfigService from "../../../Server/Services/ProjectCallSMSConfigService";
|
|
5
|
+
import IncidentService from "../../../Server/Services/IncidentService";
|
|
6
|
+
import AlertService from "../../../Server/Services/AlertService";
|
|
7
|
+
import AlertEpisodeService from "../../../Server/Services/AlertEpisodeService";
|
|
8
|
+
import IncidentEpisodeService from "../../../Server/Services/IncidentEpisodeService";
|
|
9
|
+
import MailService from "../../../Server/Services/MailService";
|
|
10
|
+
import SmsService from "../../../Server/Services/SmsService";
|
|
11
|
+
import CallService from "../../../Server/Services/CallService";
|
|
12
|
+
import WhatsAppService from "../../../Server/Services/WhatsAppService";
|
|
13
|
+
import TelegramService from "../../../Server/Services/TelegramService";
|
|
14
|
+
import WebhookService from "../../../Server/Services/WebhookService";
|
|
15
|
+
import PushNotificationService from "../../../Server/Services/PushNotificationService";
|
|
16
|
+
import logger from "../../../Server/Utils/Logger";
|
|
17
|
+
import Alert from "../../../Models/DatabaseModels/Alert";
|
|
18
|
+
import AlertEpisode from "../../../Models/DatabaseModels/AlertEpisode";
|
|
19
|
+
import Incident from "../../../Models/DatabaseModels/Incident";
|
|
20
|
+
import IncidentEpisode from "../../../Models/DatabaseModels/IncidentEpisode";
|
|
21
|
+
import UserNotificationRule from "../../../Models/DatabaseModels/UserNotificationRule";
|
|
22
|
+
import UserOnCallLogTimeline from "../../../Models/DatabaseModels/UserOnCallLogTimeline";
|
|
23
|
+
import URL from "../../../Types/API/URL";
|
|
24
|
+
import Email from "../../../Types/Email";
|
|
25
|
+
import Phone from "../../../Types/Phone";
|
|
26
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
27
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
28
|
+
import PushDeviceType from "../../../Types/PushNotification/PushDeviceType";
|
|
29
|
+
import UserNotificationEventType from "../../../Types/UserNotification/UserNotificationEventType";
|
|
30
|
+
import UserNotificationStatus from "../../../Types/UserNotification/UserNotificationStatus";
|
|
31
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* THE SHAPE OF THE BUG THIS FILE EXISTS TO CATCH.
|
|
35
|
+
*
|
|
36
|
+
* `deliverNotificationForRule` is seven independent channel blocks stacked one
|
|
37
|
+
* after another - email, SMS, WhatsApp, Telegram, webhook, call, push - and each
|
|
38
|
+
* block is itself a stack of `if (eventType === X && entity)` branches, one per
|
|
39
|
+
* UserNotificationEventType. That is a 7 x 4 grid written out by hand, twenty
|
|
40
|
+
* eight times, with no compiler anywhere insisting the grid is full.
|
|
41
|
+
*
|
|
42
|
+
* It was not full. Gap F: `IncidentEpisodeCreated` was wired into the webhook
|
|
43
|
+
* and push blocks and nowhere else, so a responder whose rule pointed at their
|
|
44
|
+
* email, phone, WhatsApp, Telegram or a voice call was told NOTHING when an
|
|
45
|
+
* incident episode paged them. Not a failed send - no send at all, and no error
|
|
46
|
+
* row either. Every gate the delivery path has (is the method verified? did the
|
|
47
|
+
* entity load? did the send throw?) passed. The page simply evaporated between
|
|
48
|
+
* the last matching `if` and the end of the function, and the on-call log went
|
|
49
|
+
* on believing the human had been contacted and had chosen not to acknowledge.
|
|
50
|
+
*
|
|
51
|
+
* A test that checked "email works" and "SMS works" would not have caught it,
|
|
52
|
+
* because email and SMS did work - for three of the four event types. Only the
|
|
53
|
+
* CARTESIAN PRODUCT catches a hole in a grid. So this file is table-driven on
|
|
54
|
+
* purpose: it builds {seven channels} x {every member of UserNotificationEventType}
|
|
55
|
+
* and asserts every single cell hands a page to its provider and writes a
|
|
56
|
+
* timeline row. Adding an eighth channel or a fifth event type without filling
|
|
57
|
+
* in the new row or column fails here, loudly, in the cell that is missing.
|
|
58
|
+
*
|
|
59
|
+
* Two canaries keep the table honest, because a table that quietly stops
|
|
60
|
+
* covering the thing it is a table OF is worse than no table:
|
|
61
|
+
* - the event axis is checked against UserNotificationEventType itself, so a
|
|
62
|
+
* new enum member fails until the matrix is extended;
|
|
63
|
+
* - the channel axis is checked against getContactableChannelNames, which is
|
|
64
|
+
* the production code's own answer to "what can we reach this user on".
|
|
65
|
+
*
|
|
66
|
+
* Beyond the grid, three behaviours the grid depends on are pinned:
|
|
67
|
+
*
|
|
68
|
+
* 1. THE FELL-THROUGH GUARD. Phase 1 added a backstop after the seven blocks:
|
|
69
|
+
* if the rule could contact somebody and no branch claimed the event, write
|
|
70
|
+
* an Error row naming the event type and the channels. That converts the
|
|
71
|
+
* next Gap F from a silent hole into a visible failure - but only if it
|
|
72
|
+
* actually fires, and only if it builds a FRESH timeline row (the one the
|
|
73
|
+
* blocks share carries an _id after its first create, so reusing it would
|
|
74
|
+
* UPDATE the earlier row instead of recording the miss).
|
|
75
|
+
*
|
|
76
|
+
* 2. THE UNVERIFIED PATH. An unverified method must produce exactly one Error
|
|
77
|
+
* row saying so and must not send. Both halves matter: no row is a silent
|
|
78
|
+
* drop, and a send would page a number nobody proved they own.
|
|
79
|
+
*
|
|
80
|
+
* 3. THE ROW COMES BEFORE THE BODY. The acknowledge deep-link is derived from
|
|
81
|
+
* the timeline row's id, so the row has to be created BEFORE the message is
|
|
82
|
+
* generated (PHASE1_SPEC constraint 6). Hoisting template generation out of
|
|
83
|
+
* the per-attempt path - an obvious-looking optimisation, since the body is
|
|
84
|
+
* identical for every recipient - would ship a page whose "acknowledge"
|
|
85
|
+
* button points at nothing. Every generator call is therefore asserted to
|
|
86
|
+
* happen after the create AND to receive that create's id.
|
|
87
|
+
*
|
|
88
|
+
* Nothing here touches a database or renders a template: the senders, the
|
|
89
|
+
* generators, the entity lookups and the timeline writes are all stubbed, so
|
|
90
|
+
* what is under test is purely which branch runs and in what order.
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
94
|
+
"11111111-1111-4111-8111-111111111111",
|
|
95
|
+
);
|
|
96
|
+
const RULE_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
|
|
97
|
+
const LOG_ID: ObjectID = new ObjectID("33333333-3333-4333-8333-333333333333");
|
|
98
|
+
const USER_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
|
|
99
|
+
const METHOD_ID: ObjectID = new ObjectID(
|
|
100
|
+
"55555555-5555-4555-8555-555555555555",
|
|
101
|
+
);
|
|
102
|
+
const TIMELINE_ID: ObjectID = new ObjectID(
|
|
103
|
+
"66666666-6666-4666-8666-666666666666",
|
|
104
|
+
);
|
|
105
|
+
const POLICY_ID: ObjectID = new ObjectID(
|
|
106
|
+
"77777777-7777-4777-8777-777777777777",
|
|
107
|
+
);
|
|
108
|
+
const INCIDENT_ID: ObjectID = new ObjectID(
|
|
109
|
+
"88888888-8888-4888-8888-888888888888",
|
|
110
|
+
);
|
|
111
|
+
const ALERT_ID: ObjectID = new ObjectID("99999999-9999-4999-8999-999999999999");
|
|
112
|
+
const ALERT_EPISODE_ID: ObjectID = new ObjectID(
|
|
113
|
+
"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
114
|
+
);
|
|
115
|
+
const INCIDENT_EPISODE_ID: ObjectID = new ObjectID(
|
|
116
|
+
"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const RESPONDER_EMAIL: string = "responder@company.com";
|
|
120
|
+
const RESPONDER_PHONE: string = "+11234567890";
|
|
121
|
+
const TELEGRAM_CHAT_ID: string = "987654321";
|
|
122
|
+
const WEBHOOK_URL: string = "https://hooks.example.com/on-call";
|
|
123
|
+
const DEVICE_TOKEN: string = "device-token";
|
|
124
|
+
const TELEGRAM_BODY: string = "<b>telegram body</b>";
|
|
125
|
+
|
|
126
|
+
/* The options bag `executeNotificationRuleItem` takes, without re-declaring it. */
|
|
127
|
+
type ExecuteOptions = Parameters<
|
|
128
|
+
typeof UserNotificationRuleService.executeNotificationRuleItem
|
|
129
|
+
>[1];
|
|
130
|
+
|
|
131
|
+
/*
|
|
132
|
+
* The service mutates ONE UserOnCallLogTimeline instance and hands that same
|
|
133
|
+
* instance to create() over and over, so mock.calls all alias its final state.
|
|
134
|
+
* Snapshot the fields at call time instead. The instance itself is kept
|
|
135
|
+
* alongside, because "did the guard build a fresh row?" is a question about
|
|
136
|
+
* object identity and cannot be answered from a snapshot.
|
|
137
|
+
*/
|
|
138
|
+
interface TimelineRow {
|
|
139
|
+
status: UserNotificationStatus | undefined;
|
|
140
|
+
statusMessage: string | undefined;
|
|
141
|
+
userId: ObjectID | undefined;
|
|
142
|
+
userNotificationRuleId: ObjectID | undefined;
|
|
143
|
+
userNotificationLogId: ObjectID | undefined;
|
|
144
|
+
projectId: ObjectID | undefined;
|
|
145
|
+
userNotificationEventType: UserNotificationEventType | undefined;
|
|
146
|
+
methodIds: Record<string, ObjectID | undefined>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Everything the second argument of every sender has in common. */
|
|
150
|
+
interface SenderOptions {
|
|
151
|
+
projectId?: ObjectID | undefined;
|
|
152
|
+
userId?: ObjectID | undefined;
|
|
153
|
+
userOnCallLogTimelineId?: ObjectID | undefined;
|
|
154
|
+
alertEpisodeId?: ObjectID | undefined;
|
|
155
|
+
incidentEpisodeId?: ObjectID | undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface DeliverySpies {
|
|
159
|
+
claim: jest.SpyInstance;
|
|
160
|
+
findRule: jest.SpyInstance;
|
|
161
|
+
twilio: jest.SpyInstance;
|
|
162
|
+
timelineCreate: jest.SpyInstance;
|
|
163
|
+
timelineUpdate: jest.SpyInstance;
|
|
164
|
+
incidentFind: jest.SpyInstance;
|
|
165
|
+
alertFind: jest.SpyInstance;
|
|
166
|
+
alertEpisodeFind: jest.SpyInstance;
|
|
167
|
+
incidentEpisodeFind: jest.SpyInstance;
|
|
168
|
+
incidentLink: jest.SpyInstance;
|
|
169
|
+
alertLink: jest.SpyInstance;
|
|
170
|
+
alertEpisodeLink: jest.SpyInstance;
|
|
171
|
+
incidentEpisodeLink: jest.SpyInstance;
|
|
172
|
+
mail: jest.SpyInstance;
|
|
173
|
+
sms: jest.SpyInstance;
|
|
174
|
+
call: jest.SpyInstance;
|
|
175
|
+
whatsApp: jest.SpyInstance;
|
|
176
|
+
telegram: jest.SpyInstance;
|
|
177
|
+
webhook: jest.SpyInstance;
|
|
178
|
+
push: jest.SpyInstance;
|
|
179
|
+
loggerError: jest.SpyInstance;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let spies: DeliverySpies;
|
|
183
|
+
let generators: Record<string, jest.SpyInstance>;
|
|
184
|
+
let timelineRows: Array<TimelineRow>;
|
|
185
|
+
let timelineInstances: Array<UserOnCallLogTimeline>;
|
|
186
|
+
|
|
187
|
+
function flushMicrotasks(): Promise<void> {
|
|
188
|
+
return Promise.resolve()
|
|
189
|
+
.then((): Promise<void> => {
|
|
190
|
+
return Promise.resolve();
|
|
191
|
+
})
|
|
192
|
+
.then((): Promise<void> => {
|
|
193
|
+
return Promise.resolve();
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function ruleItem(
|
|
198
|
+
channels: Record<string, unknown> = {},
|
|
199
|
+
): UserNotificationRule {
|
|
200
|
+
return {
|
|
201
|
+
id: RULE_ID,
|
|
202
|
+
_id: RULE_ID.toString(),
|
|
203
|
+
userId: USER_ID,
|
|
204
|
+
...channels,
|
|
205
|
+
} as unknown as UserNotificationRule;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function fakeIncident(): Incident {
|
|
209
|
+
return {
|
|
210
|
+
id: INCIDENT_ID,
|
|
211
|
+
projectId: PROJECT_ID,
|
|
212
|
+
title: "Checkout is down",
|
|
213
|
+
description: "Checkout returns 500 for every request.",
|
|
214
|
+
incidentNumber: 42,
|
|
215
|
+
incidentNumberWithPrefix: "INC-42",
|
|
216
|
+
project: { name: "Acme" },
|
|
217
|
+
incidentSeverity: { name: "Sev1" },
|
|
218
|
+
currentIncidentState: { name: "Created" },
|
|
219
|
+
} as unknown as Incident;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function fakeAlert(): Alert {
|
|
223
|
+
return {
|
|
224
|
+
id: ALERT_ID,
|
|
225
|
+
projectId: PROJECT_ID,
|
|
226
|
+
title: "Disk almost full",
|
|
227
|
+
description: "94% used on node-3.",
|
|
228
|
+
alertNumber: 7,
|
|
229
|
+
alertNumberWithPrefix: "ALR-7",
|
|
230
|
+
project: { name: "Acme" },
|
|
231
|
+
alertSeverity: { name: "Sev2" },
|
|
232
|
+
currentAlertState: { name: "Created" },
|
|
233
|
+
} as unknown as Alert;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function fakeAlertEpisode(): AlertEpisode {
|
|
237
|
+
return {
|
|
238
|
+
id: ALERT_EPISODE_ID,
|
|
239
|
+
projectId: PROJECT_ID,
|
|
240
|
+
title: "Node-3 is flapping",
|
|
241
|
+
description: "Six alerts in ten minutes.",
|
|
242
|
+
episodeNumber: 3,
|
|
243
|
+
episodeNumberWithPrefix: "AEP-3",
|
|
244
|
+
project: { name: "Acme" },
|
|
245
|
+
alertSeverity: { name: "Sev2" },
|
|
246
|
+
currentAlertState: { name: "Created" },
|
|
247
|
+
} as unknown as AlertEpisode;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function fakeIncidentEpisode(): IncidentEpisode {
|
|
251
|
+
return {
|
|
252
|
+
id: INCIDENT_EPISODE_ID,
|
|
253
|
+
projectId: PROJECT_ID,
|
|
254
|
+
title: "Checkout degradation",
|
|
255
|
+
description: "Three incidents share a root cause.",
|
|
256
|
+
episodeNumber: 9,
|
|
257
|
+
episodeNumberWithPrefix: "IEP-9",
|
|
258
|
+
project: { name: "Acme" },
|
|
259
|
+
incidentSeverity: { name: "Sev1" },
|
|
260
|
+
currentIncidentState: { name: "Created" },
|
|
261
|
+
} as unknown as IncidentEpisode;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/*
|
|
265
|
+
* One channel of the matrix: how to make a rule that can only reach the user
|
|
266
|
+
* that way, which provider must end up holding the page, and how the timeline
|
|
267
|
+
* row and the generator are supposed to be stamped.
|
|
268
|
+
*/
|
|
269
|
+
interface ChannelSpec {
|
|
270
|
+
/* The display name getContactableChannelNames gives this channel. */
|
|
271
|
+
channel: string;
|
|
272
|
+
/* A rule relation, verified, so the channel's send gate opens. */
|
|
273
|
+
verifiedMethod: () => Record<string, unknown>;
|
|
274
|
+
/* The same relation unverified. Null for Webhook, which has no such concept. */
|
|
275
|
+
unverifiedMethod: (() => Record<string, unknown>) | null;
|
|
276
|
+
/* Fragment of the Sending row this channel writes before handing over. */
|
|
277
|
+
sendingMessage: string;
|
|
278
|
+
/* Fragment of the Error row an unverified method must produce. */
|
|
279
|
+
unverifiedMessage: string;
|
|
280
|
+
/* The provider function the block must call. Read lazily: spies are per-test. */
|
|
281
|
+
sender: () => jest.SpyInstance;
|
|
282
|
+
/* The timeline column that attributes the row to this method. */
|
|
283
|
+
timelineMethodIdField: string;
|
|
284
|
+
/*
|
|
285
|
+
* The generator family that renders the body, plus where the timeline row id
|
|
286
|
+
* sits in its arguments. Null for Webhook and Push, which build their payload
|
|
287
|
+
* inline rather than through a generator.
|
|
288
|
+
*/
|
|
289
|
+
generatorPrefix: string | null;
|
|
290
|
+
generatorTimelineIdArgIndex: number;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const CHANNEL_SPECS: Record<string, ChannelSpec> = {
|
|
294
|
+
Email: {
|
|
295
|
+
channel: "Email",
|
|
296
|
+
verifiedMethod: (): Record<string, unknown> => {
|
|
297
|
+
return {
|
|
298
|
+
userEmail: {
|
|
299
|
+
id: METHOD_ID,
|
|
300
|
+
email: new Email(RESPONDER_EMAIL),
|
|
301
|
+
isVerified: true,
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
},
|
|
305
|
+
unverifiedMethod: (): Record<string, unknown> => {
|
|
306
|
+
return {
|
|
307
|
+
userEmail: {
|
|
308
|
+
id: METHOD_ID,
|
|
309
|
+
email: new Email(RESPONDER_EMAIL),
|
|
310
|
+
isVerified: false,
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
},
|
|
314
|
+
sendingMessage: "Sending email to",
|
|
315
|
+
unverifiedMessage: "Email notification not sent because email",
|
|
316
|
+
sender: (): jest.SpyInstance => {
|
|
317
|
+
return spies.mail;
|
|
318
|
+
},
|
|
319
|
+
timelineMethodIdField: "userEmailId",
|
|
320
|
+
generatorPrefix: "generateEmailTemplateFor",
|
|
321
|
+
generatorTimelineIdArgIndex: 2,
|
|
322
|
+
},
|
|
323
|
+
SMS: {
|
|
324
|
+
channel: "SMS",
|
|
325
|
+
verifiedMethod: (): Record<string, unknown> => {
|
|
326
|
+
return {
|
|
327
|
+
userSms: {
|
|
328
|
+
id: METHOD_ID,
|
|
329
|
+
phone: new Phone(RESPONDER_PHONE),
|
|
330
|
+
isVerified: true,
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
unverifiedMethod: (): Record<string, unknown> => {
|
|
335
|
+
return {
|
|
336
|
+
userSms: {
|
|
337
|
+
id: METHOD_ID,
|
|
338
|
+
phone: new Phone(RESPONDER_PHONE),
|
|
339
|
+
isVerified: false,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
},
|
|
343
|
+
sendingMessage: "Sending SMS to",
|
|
344
|
+
unverifiedMessage: "SMS not sent because phone",
|
|
345
|
+
sender: (): jest.SpyInstance => {
|
|
346
|
+
return spies.sms;
|
|
347
|
+
},
|
|
348
|
+
timelineMethodIdField: "userSmsId",
|
|
349
|
+
generatorPrefix: "generateSmsTemplateFor",
|
|
350
|
+
generatorTimelineIdArgIndex: 2,
|
|
351
|
+
},
|
|
352
|
+
WhatsApp: {
|
|
353
|
+
channel: "WhatsApp",
|
|
354
|
+
verifiedMethod: (): Record<string, unknown> => {
|
|
355
|
+
return {
|
|
356
|
+
userWhatsApp: {
|
|
357
|
+
id: METHOD_ID,
|
|
358
|
+
phone: new Phone(RESPONDER_PHONE),
|
|
359
|
+
isVerified: true,
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
},
|
|
363
|
+
unverifiedMethod: (): Record<string, unknown> => {
|
|
364
|
+
return {
|
|
365
|
+
userWhatsApp: {
|
|
366
|
+
id: METHOD_ID,
|
|
367
|
+
phone: new Phone(RESPONDER_PHONE),
|
|
368
|
+
isVerified: false,
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
},
|
|
372
|
+
sendingMessage: "Sending WhatsApp message to",
|
|
373
|
+
unverifiedMessage: "WhatsApp message not sent because phone",
|
|
374
|
+
sender: (): jest.SpyInstance => {
|
|
375
|
+
return spies.whatsApp;
|
|
376
|
+
},
|
|
377
|
+
timelineMethodIdField: "userWhatsAppId",
|
|
378
|
+
generatorPrefix: "generateWhatsAppTemplateFor",
|
|
379
|
+
generatorTimelineIdArgIndex: 2,
|
|
380
|
+
},
|
|
381
|
+
Telegram: {
|
|
382
|
+
channel: "Telegram",
|
|
383
|
+
verifiedMethod: (): Record<string, unknown> => {
|
|
384
|
+
return {
|
|
385
|
+
userTelegram: {
|
|
386
|
+
id: METHOD_ID,
|
|
387
|
+
telegramChatId: TELEGRAM_CHAT_ID,
|
|
388
|
+
isVerified: true,
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
},
|
|
392
|
+
unverifiedMethod: (): Record<string, unknown> => {
|
|
393
|
+
return {
|
|
394
|
+
userTelegram: {
|
|
395
|
+
id: METHOD_ID,
|
|
396
|
+
telegramChatId: TELEGRAM_CHAT_ID,
|
|
397
|
+
isVerified: false,
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
},
|
|
401
|
+
sendingMessage: "Sending Telegram message",
|
|
402
|
+
unverifiedMessage: "Telegram message not sent because",
|
|
403
|
+
sender: (): jest.SpyInstance => {
|
|
404
|
+
return spies.telegram;
|
|
405
|
+
},
|
|
406
|
+
timelineMethodIdField: "userTelegramId",
|
|
407
|
+
/* Telegram's generator takes (entity, timelineId) - no `to` argument. */
|
|
408
|
+
generatorPrefix: "generateTelegramBodyFor",
|
|
409
|
+
generatorTimelineIdArgIndex: 1,
|
|
410
|
+
},
|
|
411
|
+
Webhook: {
|
|
412
|
+
channel: "Webhook",
|
|
413
|
+
verifiedMethod: (): Record<string, unknown> => {
|
|
414
|
+
return {
|
|
415
|
+
userWebhook: {
|
|
416
|
+
id: METHOD_ID,
|
|
417
|
+
webhookUrl: WEBHOOK_URL,
|
|
418
|
+
name: "Pager bridge",
|
|
419
|
+
secret: "s3cr3t",
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
},
|
|
423
|
+
/*
|
|
424
|
+
* UserWebhook has no isVerified column at all, so there is no unverified
|
|
425
|
+
* shape to build and no "not verified" row to expect.
|
|
426
|
+
*/
|
|
427
|
+
unverifiedMethod: null,
|
|
428
|
+
sendingMessage: "Sending webhook to",
|
|
429
|
+
unverifiedMessage: "",
|
|
430
|
+
sender: (): jest.SpyInstance => {
|
|
431
|
+
return spies.webhook;
|
|
432
|
+
},
|
|
433
|
+
timelineMethodIdField: "userWebhookId",
|
|
434
|
+
generatorPrefix: null,
|
|
435
|
+
generatorTimelineIdArgIndex: -1,
|
|
436
|
+
},
|
|
437
|
+
Call: {
|
|
438
|
+
channel: "Call",
|
|
439
|
+
verifiedMethod: (): Record<string, unknown> => {
|
|
440
|
+
return {
|
|
441
|
+
userCall: {
|
|
442
|
+
id: METHOD_ID,
|
|
443
|
+
phone: new Phone(RESPONDER_PHONE),
|
|
444
|
+
isVerified: true,
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
},
|
|
448
|
+
unverifiedMethod: (): Record<string, unknown> => {
|
|
449
|
+
return {
|
|
450
|
+
userCall: {
|
|
451
|
+
id: METHOD_ID,
|
|
452
|
+
phone: new Phone(RESPONDER_PHONE),
|
|
453
|
+
isVerified: false,
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
},
|
|
457
|
+
sendingMessage: "Making a call to",
|
|
458
|
+
unverifiedMessage: "Call not sent because phone",
|
|
459
|
+
sender: (): jest.SpyInstance => {
|
|
460
|
+
return spies.call;
|
|
461
|
+
},
|
|
462
|
+
timelineMethodIdField: "userCallId",
|
|
463
|
+
generatorPrefix: "generateCallTemplateFor",
|
|
464
|
+
generatorTimelineIdArgIndex: 2,
|
|
465
|
+
},
|
|
466
|
+
Push: {
|
|
467
|
+
channel: "Push",
|
|
468
|
+
verifiedMethod: (): Record<string, unknown> => {
|
|
469
|
+
return {
|
|
470
|
+
userPush: {
|
|
471
|
+
id: METHOD_ID,
|
|
472
|
+
deviceToken: DEVICE_TOKEN,
|
|
473
|
+
deviceType: PushDeviceType.iOS,
|
|
474
|
+
isVerified: true,
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
},
|
|
478
|
+
unverifiedMethod: (): Record<string, unknown> => {
|
|
479
|
+
return {
|
|
480
|
+
userPush: {
|
|
481
|
+
id: METHOD_ID,
|
|
482
|
+
deviceToken: DEVICE_TOKEN,
|
|
483
|
+
deviceType: PushDeviceType.iOS,
|
|
484
|
+
isVerified: false,
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
},
|
|
488
|
+
sendingMessage: "Sending push notification",
|
|
489
|
+
unverifiedMessage: "Push notification not sent because device is not",
|
|
490
|
+
sender: (): jest.SpyInstance => {
|
|
491
|
+
return spies.push;
|
|
492
|
+
},
|
|
493
|
+
timelineMethodIdField: "userPushId",
|
|
494
|
+
/* Push builds its payload through PushNotificationUtil, not a generator. */
|
|
495
|
+
generatorPrefix: null,
|
|
496
|
+
generatorTimelineIdArgIndex: -1,
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
/*
|
|
501
|
+
* One event type of the matrix: the options that make the delivery path resolve
|
|
502
|
+
* its triggering entity, and the names the channel blocks derive from it.
|
|
503
|
+
*/
|
|
504
|
+
interface EventSpec {
|
|
505
|
+
eventType: UserNotificationEventType;
|
|
506
|
+
makeOptions: () => ExecuteOptions;
|
|
507
|
+
/* Point this event's finder at a real entity; the other three stay null. */
|
|
508
|
+
arm: () => void;
|
|
509
|
+
/* Appended to a channel's generatorPrefix to name the generator for this cell. */
|
|
510
|
+
generatorSuffix: string;
|
|
511
|
+
/* The eventType string the webhook block puts on the wire. */
|
|
512
|
+
webhookEventType: string;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const EVENT_SPECS: Record<string, EventSpec> = {
|
|
516
|
+
IncidentCreated: {
|
|
517
|
+
eventType: UserNotificationEventType.IncidentCreated,
|
|
518
|
+
makeOptions: (): ExecuteOptions => {
|
|
519
|
+
return {
|
|
520
|
+
projectId: PROJECT_ID,
|
|
521
|
+
userNotificationEventType: UserNotificationEventType.IncidentCreated,
|
|
522
|
+
triggeredByIncidentId: INCIDENT_ID,
|
|
523
|
+
onCallPolicyId: POLICY_ID,
|
|
524
|
+
userNotificationLogId: LOG_ID,
|
|
525
|
+
};
|
|
526
|
+
},
|
|
527
|
+
arm: (): void => {
|
|
528
|
+
spies.incidentFind.mockResolvedValue(fakeIncident() as never);
|
|
529
|
+
},
|
|
530
|
+
generatorSuffix: "IncidentCreated",
|
|
531
|
+
webhookEventType: "on-call.incident.created",
|
|
532
|
+
},
|
|
533
|
+
AlertCreated: {
|
|
534
|
+
eventType: UserNotificationEventType.AlertCreated,
|
|
535
|
+
makeOptions: (): ExecuteOptions => {
|
|
536
|
+
return {
|
|
537
|
+
projectId: PROJECT_ID,
|
|
538
|
+
userNotificationEventType: UserNotificationEventType.AlertCreated,
|
|
539
|
+
triggeredByAlertId: ALERT_ID,
|
|
540
|
+
onCallPolicyId: POLICY_ID,
|
|
541
|
+
userNotificationLogId: LOG_ID,
|
|
542
|
+
};
|
|
543
|
+
},
|
|
544
|
+
arm: (): void => {
|
|
545
|
+
spies.alertFind.mockResolvedValue(fakeAlert() as never);
|
|
546
|
+
},
|
|
547
|
+
generatorSuffix: "AlertCreated",
|
|
548
|
+
webhookEventType: "on-call.alert.created",
|
|
549
|
+
},
|
|
550
|
+
AlertEpisodeCreated: {
|
|
551
|
+
eventType: UserNotificationEventType.AlertEpisodeCreated,
|
|
552
|
+
makeOptions: (): ExecuteOptions => {
|
|
553
|
+
return {
|
|
554
|
+
projectId: PROJECT_ID,
|
|
555
|
+
userNotificationEventType:
|
|
556
|
+
UserNotificationEventType.AlertEpisodeCreated,
|
|
557
|
+
triggeredByAlertEpisodeId: ALERT_EPISODE_ID,
|
|
558
|
+
onCallPolicyId: POLICY_ID,
|
|
559
|
+
userNotificationLogId: LOG_ID,
|
|
560
|
+
};
|
|
561
|
+
},
|
|
562
|
+
arm: (): void => {
|
|
563
|
+
spies.alertEpisodeFind.mockResolvedValue(fakeAlertEpisode() as never);
|
|
564
|
+
},
|
|
565
|
+
generatorSuffix: "AlertEpisodeCreated",
|
|
566
|
+
webhookEventType: "on-call.alertEpisode.created",
|
|
567
|
+
},
|
|
568
|
+
IncidentEpisodeCreated: {
|
|
569
|
+
eventType: UserNotificationEventType.IncidentEpisodeCreated,
|
|
570
|
+
makeOptions: (): ExecuteOptions => {
|
|
571
|
+
return {
|
|
572
|
+
projectId: PROJECT_ID,
|
|
573
|
+
userNotificationEventType:
|
|
574
|
+
UserNotificationEventType.IncidentEpisodeCreated,
|
|
575
|
+
triggeredByIncidentEpisodeId: INCIDENT_EPISODE_ID,
|
|
576
|
+
onCallPolicyId: POLICY_ID,
|
|
577
|
+
userNotificationLogId: LOG_ID,
|
|
578
|
+
};
|
|
579
|
+
},
|
|
580
|
+
arm: (): void => {
|
|
581
|
+
spies.incidentEpisodeFind.mockResolvedValue(
|
|
582
|
+
fakeIncidentEpisode() as never,
|
|
583
|
+
);
|
|
584
|
+
},
|
|
585
|
+
generatorSuffix: "IncidentEpisodeCreated",
|
|
586
|
+
webhookEventType: "on-call.incidentEpisode.created",
|
|
587
|
+
},
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
/*
|
|
591
|
+
* The two axes. Both are asserted against the production code below, so they
|
|
592
|
+
* cannot silently fall behind a newly added channel or event type.
|
|
593
|
+
*/
|
|
594
|
+
const CHANNEL_NAMES: Array<string> = [
|
|
595
|
+
"Email",
|
|
596
|
+
"SMS",
|
|
597
|
+
"WhatsApp",
|
|
598
|
+
"Telegram",
|
|
599
|
+
"Webhook",
|
|
600
|
+
"Call",
|
|
601
|
+
"Push",
|
|
602
|
+
];
|
|
603
|
+
|
|
604
|
+
const EVENT_NAMES: Array<string> = [
|
|
605
|
+
"IncidentCreated",
|
|
606
|
+
"AlertCreated",
|
|
607
|
+
"AlertEpisodeCreated",
|
|
608
|
+
"IncidentEpisodeCreated",
|
|
609
|
+
];
|
|
610
|
+
|
|
611
|
+
/* Channels whose body comes from a generator that takes the timeline row id. */
|
|
612
|
+
const GENERATOR_CHANNEL_NAMES: Array<string> = [
|
|
613
|
+
"Email",
|
|
614
|
+
"SMS",
|
|
615
|
+
"WhatsApp",
|
|
616
|
+
"Telegram",
|
|
617
|
+
"Call",
|
|
618
|
+
];
|
|
619
|
+
|
|
620
|
+
/* Channels that have a verification concept at all (everything but Webhook). */
|
|
621
|
+
const VERIFIABLE_CHANNEL_NAMES: Array<string> = [
|
|
622
|
+
"Email",
|
|
623
|
+
"SMS",
|
|
624
|
+
"WhatsApp",
|
|
625
|
+
"Telegram",
|
|
626
|
+
"Call",
|
|
627
|
+
"Push",
|
|
628
|
+
];
|
|
629
|
+
|
|
630
|
+
function buildMatrix(channelNames: Array<string>): Array<[string, string]> {
|
|
631
|
+
const rows: Array<[string, string]> = [];
|
|
632
|
+
|
|
633
|
+
for (const channelName of channelNames) {
|
|
634
|
+
for (const eventName of EVENT_NAMES) {
|
|
635
|
+
rows.push([channelName, eventName]);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return rows;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const FULL_MATRIX: Array<[string, string]> = buildMatrix(CHANNEL_NAMES);
|
|
643
|
+
const GENERATOR_MATRIX: Array<[string, string]> = buildMatrix(
|
|
644
|
+
GENERATOR_CHANNEL_NAMES,
|
|
645
|
+
);
|
|
646
|
+
const UNVERIFIED_MATRIX: Array<[string, string]> = buildMatrix(
|
|
647
|
+
VERIFIABLE_CHANNEL_NAMES,
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
function channelSpec(channelName: string): ChannelSpec {
|
|
651
|
+
return CHANNEL_SPECS[channelName]!;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function eventSpec(eventName: string): EventSpec {
|
|
655
|
+
return EVENT_SPECS[eventName]!;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/* Every verified method at once - used to interrogate the channel census. */
|
|
659
|
+
function everyVerifiedMethod(): Record<string, unknown> {
|
|
660
|
+
const methods: Record<string, unknown> = {};
|
|
661
|
+
|
|
662
|
+
for (const channelName of CHANNEL_NAMES) {
|
|
663
|
+
Object.assign(methods, channelSpec(channelName).verifiedMethod());
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return methods;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/*
|
|
670
|
+
* getContactableChannelNames is private. Call it through a cast rather than
|
|
671
|
+
* widening the service's public surface, as the neighbouring characterisation
|
|
672
|
+
* tests do for protected hooks.
|
|
673
|
+
*/
|
|
674
|
+
function contactableChannelNames(rule: UserNotificationRule): Array<string> {
|
|
675
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
676
|
+
return (UserNotificationRuleService as any).getContactableChannelNames(rule);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function everySender(): Array<jest.SpyInstance> {
|
|
680
|
+
return [
|
|
681
|
+
spies.mail,
|
|
682
|
+
spies.sms,
|
|
683
|
+
spies.call,
|
|
684
|
+
spies.whatsApp,
|
|
685
|
+
spies.telegram,
|
|
686
|
+
spies.webhook,
|
|
687
|
+
spies.push,
|
|
688
|
+
];
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function otherSenders(channel: ChannelSpec): Array<jest.SpyInstance> {
|
|
692
|
+
const own: jest.SpyInstance = channel.sender();
|
|
693
|
+
|
|
694
|
+
return everySender().filter((sender: jest.SpyInstance): boolean => {
|
|
695
|
+
return sender !== own;
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/* The options bag - always the second argument - that a sender was handed. */
|
|
700
|
+
function senderOptions(sender: jest.SpyInstance): SenderOptions {
|
|
701
|
+
return sender.mock.calls[0][1] as SenderOptions;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
describe("UserNotificationRuleService channel x event coverage", () => {
|
|
705
|
+
beforeEach(() => {
|
|
706
|
+
timelineRows = [];
|
|
707
|
+
timelineInstances = [];
|
|
708
|
+
|
|
709
|
+
const timelineCreate: jest.SpyInstance = jest.spyOn(
|
|
710
|
+
UserOnCallLogTimelineService,
|
|
711
|
+
"create",
|
|
712
|
+
);
|
|
713
|
+
timelineCreate.mockImplementation(
|
|
714
|
+
(createBy: unknown): Promise<UserOnCallLogTimeline> => {
|
|
715
|
+
const data: UserOnCallLogTimeline = (
|
|
716
|
+
createBy as { data: UserOnCallLogTimeline }
|
|
717
|
+
).data;
|
|
718
|
+
|
|
719
|
+
timelineInstances.push(data);
|
|
720
|
+
timelineRows.push({
|
|
721
|
+
status: data.status,
|
|
722
|
+
statusMessage: data.statusMessage,
|
|
723
|
+
userId: data.userId,
|
|
724
|
+
userNotificationRuleId: data.userNotificationRuleId,
|
|
725
|
+
userNotificationLogId: data.userNotificationLogId,
|
|
726
|
+
projectId: data.projectId,
|
|
727
|
+
userNotificationEventType: data.userNotificationEventType,
|
|
728
|
+
methodIds: {
|
|
729
|
+
userEmailId: data.userEmailId,
|
|
730
|
+
userSmsId: data.userSmsId,
|
|
731
|
+
userCallId: data.userCallId,
|
|
732
|
+
userWhatsAppId: data.userWhatsAppId,
|
|
733
|
+
userTelegramId: data.userTelegramId,
|
|
734
|
+
userPushId: data.userPushId,
|
|
735
|
+
userWebhookId: data.userWebhookId,
|
|
736
|
+
},
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
return Promise.resolve({
|
|
740
|
+
id: TIMELINE_ID,
|
|
741
|
+
} as unknown as UserOnCallLogTimeline);
|
|
742
|
+
},
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
spies = {
|
|
746
|
+
/*
|
|
747
|
+
* claimNotificationRuleExecution issues raw SQL through the repository
|
|
748
|
+
* manager and swallows failures into a non-atomic fallback, so it is
|
|
749
|
+
* stubbed whole rather than at the database boundary.
|
|
750
|
+
*/
|
|
751
|
+
claim: jest
|
|
752
|
+
.spyOn(UserOnCallLogService, "claimNotificationRuleExecution")
|
|
753
|
+
.mockResolvedValue(true as never),
|
|
754
|
+
findRule: jest
|
|
755
|
+
.spyOn(UserNotificationRuleService, "findOneById")
|
|
756
|
+
.mockResolvedValue(ruleItem() as never),
|
|
757
|
+
twilio: jest
|
|
758
|
+
.spyOn(ProjectCallSMSConfigService, "getProjectDefaultTwilioConfig")
|
|
759
|
+
.mockResolvedValue(undefined as never),
|
|
760
|
+
timelineCreate: timelineCreate,
|
|
761
|
+
timelineUpdate: jest
|
|
762
|
+
.spyOn(UserOnCallLogTimelineService, "updateOneById")
|
|
763
|
+
.mockResolvedValue(undefined as never),
|
|
764
|
+
/*
|
|
765
|
+
* All four finders start at null. Each test arms exactly the one its
|
|
766
|
+
* event type resolves through, which is also what proves the other three
|
|
767
|
+
* are never consulted.
|
|
768
|
+
*/
|
|
769
|
+
incidentFind: jest
|
|
770
|
+
.spyOn(IncidentService, "findOneById")
|
|
771
|
+
.mockResolvedValue(null as never),
|
|
772
|
+
alertFind: jest
|
|
773
|
+
.spyOn(AlertService, "findOneById")
|
|
774
|
+
.mockResolvedValue(null as never),
|
|
775
|
+
alertEpisodeFind: jest
|
|
776
|
+
.spyOn(AlertEpisodeService, "findOneById")
|
|
777
|
+
.mockResolvedValue(null as never),
|
|
778
|
+
incidentEpisodeFind: jest
|
|
779
|
+
.spyOn(IncidentEpisodeService, "findOneById")
|
|
780
|
+
.mockResolvedValue(null as never),
|
|
781
|
+
/* The push blocks build a dashboard deep-link before sending. */
|
|
782
|
+
incidentLink: jest
|
|
783
|
+
.spyOn(IncidentService, "getIncidentLinkInDashboard")
|
|
784
|
+
.mockResolvedValue(
|
|
785
|
+
URL.fromString("https://oneuptime.test/incident") as never,
|
|
786
|
+
),
|
|
787
|
+
alertLink: jest
|
|
788
|
+
.spyOn(AlertService, "getAlertLinkInDashboard")
|
|
789
|
+
.mockResolvedValue(
|
|
790
|
+
URL.fromString("https://oneuptime.test/alert") as never,
|
|
791
|
+
),
|
|
792
|
+
alertEpisodeLink: jest
|
|
793
|
+
.spyOn(AlertEpisodeService, "getEpisodeLinkInDashboard")
|
|
794
|
+
.mockResolvedValue(
|
|
795
|
+
URL.fromString("https://oneuptime.test/alert-episode") as never,
|
|
796
|
+
),
|
|
797
|
+
incidentEpisodeLink: jest
|
|
798
|
+
.spyOn(IncidentEpisodeService, "getEpisodeLinkInDashboard")
|
|
799
|
+
.mockResolvedValue(
|
|
800
|
+
URL.fromString("https://oneuptime.test/incident-episode") as never,
|
|
801
|
+
),
|
|
802
|
+
mail: jest
|
|
803
|
+
.spyOn(MailService, "sendMail")
|
|
804
|
+
.mockResolvedValue(undefined as never),
|
|
805
|
+
sms: jest
|
|
806
|
+
.spyOn(SmsService, "sendSms")
|
|
807
|
+
.mockResolvedValue(undefined as never),
|
|
808
|
+
call: jest
|
|
809
|
+
.spyOn(CallService, "makeCall")
|
|
810
|
+
.mockResolvedValue(undefined as never),
|
|
811
|
+
whatsApp: jest
|
|
812
|
+
.spyOn(WhatsAppService, "sendWhatsAppMessage")
|
|
813
|
+
.mockResolvedValue(undefined as never),
|
|
814
|
+
telegram: jest
|
|
815
|
+
.spyOn(TelegramService, "sendTelegramMessage")
|
|
816
|
+
.mockResolvedValue(undefined as never),
|
|
817
|
+
webhook: jest
|
|
818
|
+
.spyOn(WebhookService, "sendWebhook")
|
|
819
|
+
.mockResolvedValue(undefined as never),
|
|
820
|
+
push: jest
|
|
821
|
+
.spyOn(PushNotificationService, "sendPushNotification")
|
|
822
|
+
.mockResolvedValue(undefined as never),
|
|
823
|
+
loggerError: jest.spyOn(logger, "error").mockImplementation((): void => {
|
|
824
|
+
return undefined;
|
|
825
|
+
}),
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
/*
|
|
829
|
+
* Every template generator is stubbed. They render Handlebars, shorten
|
|
830
|
+
* links and read DatabaseConfig; none of that is what this file is about,
|
|
831
|
+
* and a real one would drag a database into the test. Stubbing them also
|
|
832
|
+
* makes them observable, which is how the "row before body" ordering is
|
|
833
|
+
* asserted.
|
|
834
|
+
*/
|
|
835
|
+
generators = {
|
|
836
|
+
generateEmailTemplateForIncidentCreated: jest
|
|
837
|
+
.spyOn(
|
|
838
|
+
UserNotificationRuleService,
|
|
839
|
+
"generateEmailTemplateForIncidentCreated",
|
|
840
|
+
)
|
|
841
|
+
.mockResolvedValue({} as never),
|
|
842
|
+
generateEmailTemplateForAlertCreated: jest
|
|
843
|
+
.spyOn(
|
|
844
|
+
UserNotificationRuleService,
|
|
845
|
+
"generateEmailTemplateForAlertCreated",
|
|
846
|
+
)
|
|
847
|
+
.mockResolvedValue({} as never),
|
|
848
|
+
generateEmailTemplateForAlertEpisodeCreated: jest
|
|
849
|
+
.spyOn(
|
|
850
|
+
UserNotificationRuleService,
|
|
851
|
+
"generateEmailTemplateForAlertEpisodeCreated",
|
|
852
|
+
)
|
|
853
|
+
.mockResolvedValue({} as never),
|
|
854
|
+
generateEmailTemplateForIncidentEpisodeCreated: jest
|
|
855
|
+
.spyOn(
|
|
856
|
+
UserNotificationRuleService,
|
|
857
|
+
"generateEmailTemplateForIncidentEpisodeCreated",
|
|
858
|
+
)
|
|
859
|
+
.mockResolvedValue({} as never),
|
|
860
|
+
generateSmsTemplateForIncidentCreated: jest
|
|
861
|
+
.spyOn(
|
|
862
|
+
UserNotificationRuleService,
|
|
863
|
+
"generateSmsTemplateForIncidentCreated",
|
|
864
|
+
)
|
|
865
|
+
.mockResolvedValue({} as never),
|
|
866
|
+
generateSmsTemplateForAlertCreated: jest
|
|
867
|
+
.spyOn(
|
|
868
|
+
UserNotificationRuleService,
|
|
869
|
+
"generateSmsTemplateForAlertCreated",
|
|
870
|
+
)
|
|
871
|
+
.mockResolvedValue({} as never),
|
|
872
|
+
generateSmsTemplateForAlertEpisodeCreated: jest
|
|
873
|
+
.spyOn(
|
|
874
|
+
UserNotificationRuleService,
|
|
875
|
+
"generateSmsTemplateForAlertEpisodeCreated",
|
|
876
|
+
)
|
|
877
|
+
.mockResolvedValue({} as never),
|
|
878
|
+
generateSmsTemplateForIncidentEpisodeCreated: jest
|
|
879
|
+
.spyOn(
|
|
880
|
+
UserNotificationRuleService,
|
|
881
|
+
"generateSmsTemplateForIncidentEpisodeCreated",
|
|
882
|
+
)
|
|
883
|
+
.mockResolvedValue({} as never),
|
|
884
|
+
generateWhatsAppTemplateForIncidentCreated: jest
|
|
885
|
+
.spyOn(
|
|
886
|
+
UserNotificationRuleService,
|
|
887
|
+
"generateWhatsAppTemplateForIncidentCreated",
|
|
888
|
+
)
|
|
889
|
+
.mockResolvedValue({} as never),
|
|
890
|
+
generateWhatsAppTemplateForAlertCreated: jest
|
|
891
|
+
.spyOn(
|
|
892
|
+
UserNotificationRuleService,
|
|
893
|
+
"generateWhatsAppTemplateForAlertCreated",
|
|
894
|
+
)
|
|
895
|
+
.mockResolvedValue({} as never),
|
|
896
|
+
generateWhatsAppTemplateForAlertEpisodeCreated: jest
|
|
897
|
+
.spyOn(
|
|
898
|
+
UserNotificationRuleService,
|
|
899
|
+
"generateWhatsAppTemplateForAlertEpisodeCreated",
|
|
900
|
+
)
|
|
901
|
+
.mockResolvedValue({} as never),
|
|
902
|
+
generateWhatsAppTemplateForIncidentEpisodeCreated: jest
|
|
903
|
+
.spyOn(
|
|
904
|
+
UserNotificationRuleService,
|
|
905
|
+
"generateWhatsAppTemplateForIncidentEpisodeCreated",
|
|
906
|
+
)
|
|
907
|
+
.mockResolvedValue({} as never),
|
|
908
|
+
generateTelegramBodyForIncidentCreated: jest
|
|
909
|
+
.spyOn(
|
|
910
|
+
UserNotificationRuleService,
|
|
911
|
+
"generateTelegramBodyForIncidentCreated",
|
|
912
|
+
)
|
|
913
|
+
.mockResolvedValue(TELEGRAM_BODY as never),
|
|
914
|
+
generateTelegramBodyForAlertCreated: jest
|
|
915
|
+
.spyOn(
|
|
916
|
+
UserNotificationRuleService,
|
|
917
|
+
"generateTelegramBodyForAlertCreated",
|
|
918
|
+
)
|
|
919
|
+
.mockResolvedValue(TELEGRAM_BODY as never),
|
|
920
|
+
generateTelegramBodyForAlertEpisodeCreated: jest
|
|
921
|
+
.spyOn(
|
|
922
|
+
UserNotificationRuleService,
|
|
923
|
+
"generateTelegramBodyForAlertEpisodeCreated",
|
|
924
|
+
)
|
|
925
|
+
.mockResolvedValue(TELEGRAM_BODY as never),
|
|
926
|
+
generateTelegramBodyForIncidentEpisodeCreated: jest
|
|
927
|
+
.spyOn(
|
|
928
|
+
UserNotificationRuleService,
|
|
929
|
+
"generateTelegramBodyForIncidentEpisodeCreated",
|
|
930
|
+
)
|
|
931
|
+
.mockResolvedValue(TELEGRAM_BODY as never),
|
|
932
|
+
generateCallTemplateForIncidentCreated: jest
|
|
933
|
+
.spyOn(
|
|
934
|
+
UserNotificationRuleService,
|
|
935
|
+
"generateCallTemplateForIncidentCreated",
|
|
936
|
+
)
|
|
937
|
+
.mockResolvedValue({} as never),
|
|
938
|
+
generateCallTemplateForAlertCreated: jest
|
|
939
|
+
.spyOn(
|
|
940
|
+
UserNotificationRuleService,
|
|
941
|
+
"generateCallTemplateForAlertCreated",
|
|
942
|
+
)
|
|
943
|
+
.mockResolvedValue({} as never),
|
|
944
|
+
generateCallTemplateForAlertEpisodeCreated: jest
|
|
945
|
+
.spyOn(
|
|
946
|
+
UserNotificationRuleService,
|
|
947
|
+
"generateCallTemplateForAlertEpisodeCreated",
|
|
948
|
+
)
|
|
949
|
+
.mockResolvedValue({} as never),
|
|
950
|
+
generateCallTemplateForIncidentEpisodeCreated: jest
|
|
951
|
+
.spyOn(
|
|
952
|
+
UserNotificationRuleService,
|
|
953
|
+
"generateCallTemplateForIncidentEpisodeCreated",
|
|
954
|
+
)
|
|
955
|
+
.mockResolvedValue({} as never),
|
|
956
|
+
};
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
afterEach(() => {
|
|
960
|
+
jest.restoreAllMocks();
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
/*
|
|
964
|
+
* Drive one cell of the matrix: a rule carrying exactly one verified method,
|
|
965
|
+
* an event type, and nothing else.
|
|
966
|
+
*/
|
|
967
|
+
async function deliverCell(
|
|
968
|
+
channel: ChannelSpec,
|
|
969
|
+
event: EventSpec,
|
|
970
|
+
): Promise<void> {
|
|
971
|
+
event.arm();
|
|
972
|
+
spies.findRule.mockResolvedValue(
|
|
973
|
+
ruleItem(channel.verifiedMethod()) as never,
|
|
974
|
+
);
|
|
975
|
+
|
|
976
|
+
await UserNotificationRuleService.executeNotificationRuleItem(
|
|
977
|
+
RULE_ID,
|
|
978
|
+
event.makeOptions(),
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
/*
|
|
982
|
+
* Sends are never awaited: executeNotificationRuleItem resolves while the
|
|
983
|
+
* provider promises are still in flight, and the .catch that flips a row to
|
|
984
|
+
* Error runs a microtask later. Flush before asserting on anything the
|
|
985
|
+
* post-send path touches.
|
|
986
|
+
*/
|
|
987
|
+
await flushMicrotasks();
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/*
|
|
991
|
+
* ----------------------------------------------------------------------- *
|
|
992
|
+
* (A) The axes themselves. If either of these fails, the matrix below has
|
|
993
|
+
* stopped covering the thing it claims to cover.
|
|
994
|
+
* -----------------------------------------------------------------------
|
|
995
|
+
*/
|
|
996
|
+
|
|
997
|
+
describe("the matrix axes track the production code", () => {
|
|
998
|
+
test("the event axis covers EVERY UserNotificationEventType member", () => {
|
|
999
|
+
/*
|
|
1000
|
+
* The canary for the next Gap F. Adding a fifth event type to the enum
|
|
1001
|
+
* fails here until it is added to EVENT_SPECS, at which point the matrix
|
|
1002
|
+
* grows by seven cells and any channel that forgot the new branch fails.
|
|
1003
|
+
*/
|
|
1004
|
+
const members: Array<string> = Object.values(UserNotificationEventType);
|
|
1005
|
+
|
|
1006
|
+
expect(EVENT_NAMES).toHaveLength(members.length);
|
|
1007
|
+
|
|
1008
|
+
const covered: Array<string> = EVENT_NAMES.map(
|
|
1009
|
+
(eventName: string): string => {
|
|
1010
|
+
return eventSpec(eventName).eventType;
|
|
1011
|
+
},
|
|
1012
|
+
);
|
|
1013
|
+
|
|
1014
|
+
for (const member of members) {
|
|
1015
|
+
expect(covered).toContain(member);
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
test("the channel axis covers every channel getContactableChannelNames knows about", () => {
|
|
1020
|
+
/*
|
|
1021
|
+
* getContactableChannelNames is the delivery path's own answer to "what
|
|
1022
|
+
* can this rule reach the user on", and the fell-through guard names
|
|
1023
|
+
* those channels. Anything it can return must have a row in the matrix.
|
|
1024
|
+
*/
|
|
1025
|
+
const allChannels: Array<string> = contactableChannelNames(
|
|
1026
|
+
ruleItem(everyVerifiedMethod()),
|
|
1027
|
+
);
|
|
1028
|
+
|
|
1029
|
+
expect([...allChannels].sort()).toEqual([...CHANNEL_NAMES].sort());
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
test("the matrix is the full cartesian product", () => {
|
|
1033
|
+
expect(FULL_MATRIX).toHaveLength(
|
|
1034
|
+
CHANNEL_NAMES.length * EVENT_NAMES.length,
|
|
1035
|
+
);
|
|
1036
|
+
expect(FULL_MATRIX).toHaveLength(28);
|
|
1037
|
+
});
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
/*
|
|
1041
|
+
* ----------------------------------------------------------------------- *
|
|
1042
|
+
* (B) The grid. One test per cell.
|
|
1043
|
+
* -----------------------------------------------------------------------
|
|
1044
|
+
*/
|
|
1045
|
+
|
|
1046
|
+
describe("every channel dispatches for every event type", () => {
|
|
1047
|
+
test.each<[string, string]>(FULL_MATRIX)(
|
|
1048
|
+
"%s hands the page to its provider for %s",
|
|
1049
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1050
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1051
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1052
|
+
|
|
1053
|
+
await deliverCell(channel, event);
|
|
1054
|
+
|
|
1055
|
+
/*
|
|
1056
|
+
* The Gap F assertion. Before Phase 1 five of these cells reached this
|
|
1057
|
+
* line with the provider untouched and the timeline empty.
|
|
1058
|
+
*/
|
|
1059
|
+
expect(channel.sender()).toHaveBeenCalledTimes(1);
|
|
1060
|
+
},
|
|
1061
|
+
);
|
|
1062
|
+
|
|
1063
|
+
test.each<[string, string]>(FULL_MATRIX)(
|
|
1064
|
+
"%s contacts NO other channel for %s",
|
|
1065
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1066
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1067
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1068
|
+
|
|
1069
|
+
await deliverCell(channel, event);
|
|
1070
|
+
|
|
1071
|
+
for (const sender of otherSenders(channel)) {
|
|
1072
|
+
expect(sender).not.toHaveBeenCalled();
|
|
1073
|
+
}
|
|
1074
|
+
},
|
|
1075
|
+
);
|
|
1076
|
+
|
|
1077
|
+
test.each<[string, string]>(FULL_MATRIX)(
|
|
1078
|
+
"%s writes exactly one Sending timeline row for %s",
|
|
1079
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1080
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1081
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1082
|
+
|
|
1083
|
+
await deliverCell(channel, event);
|
|
1084
|
+
|
|
1085
|
+
expect(timelineRows).toHaveLength(1);
|
|
1086
|
+
expect(timelineRows[0]?.status).toBe(UserNotificationStatus.Sending);
|
|
1087
|
+
/*
|
|
1088
|
+
* statusMessage is nullable: false on the model, and it is the only
|
|
1089
|
+
* thing an operator reading the timeline sees. A block that copied its
|
|
1090
|
+
* neighbour's message would name the wrong channel here.
|
|
1091
|
+
*/
|
|
1092
|
+
expect(timelineRows[0]?.statusMessage).toContain(
|
|
1093
|
+
channel.sendingMessage,
|
|
1094
|
+
);
|
|
1095
|
+
/* The guard row would be an Error. Delivery happened, so it stays quiet. */
|
|
1096
|
+
expect(timelineRows[0]?.status).not.toBe(UserNotificationStatus.Error);
|
|
1097
|
+
},
|
|
1098
|
+
);
|
|
1099
|
+
|
|
1100
|
+
test.each<[string, string]>(FULL_MATRIX)(
|
|
1101
|
+
"%s stamps the timeline row with the page's identifiers for %s",
|
|
1102
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1103
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1104
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1105
|
+
|
|
1106
|
+
await deliverCell(channel, event);
|
|
1107
|
+
|
|
1108
|
+
const row: TimelineRow | undefined = timelineRows[0];
|
|
1109
|
+
|
|
1110
|
+
expect(row?.userId?.toString()).toBe(USER_ID.toString());
|
|
1111
|
+
expect(row?.userNotificationRuleId?.toString()).toBe(
|
|
1112
|
+
RULE_ID.toString(),
|
|
1113
|
+
);
|
|
1114
|
+
expect(row?.userNotificationLogId?.toString()).toBe(LOG_ID.toString());
|
|
1115
|
+
expect(row?.projectId?.toString()).toBe(PROJECT_ID.toString());
|
|
1116
|
+
expect(row?.userNotificationEventType).toBe(event.eventType);
|
|
1117
|
+
/* ...and with the method it was delivered on, so the row is attributable. */
|
|
1118
|
+
expect(row?.methodIds[channel.timelineMethodIdField]?.toString()).toBe(
|
|
1119
|
+
METHOD_ID.toString(),
|
|
1120
|
+
);
|
|
1121
|
+
},
|
|
1122
|
+
);
|
|
1123
|
+
|
|
1124
|
+
test.each<[string, string]>(FULL_MATRIX)(
|
|
1125
|
+
"%s hands the sender the created row id, the project and the user for %s",
|
|
1126
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1127
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1128
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1129
|
+
|
|
1130
|
+
await deliverCell(channel, event);
|
|
1131
|
+
|
|
1132
|
+
const options: SenderOptions = senderOptions(channel.sender());
|
|
1133
|
+
|
|
1134
|
+
/*
|
|
1135
|
+
* userOnCallLogTimelineId is how the provider's delivery callback finds
|
|
1136
|
+
* the row to update, and how an acknowledgement is matched back to this
|
|
1137
|
+
* attempt. A cell that passed the wrong id would look delivered and
|
|
1138
|
+
* never reconcile.
|
|
1139
|
+
*/
|
|
1140
|
+
expect(options.userOnCallLogTimelineId?.toString()).toBe(
|
|
1141
|
+
TIMELINE_ID.toString(),
|
|
1142
|
+
);
|
|
1143
|
+
expect(options.projectId?.toString()).toBe(PROJECT_ID.toString());
|
|
1144
|
+
expect(options.userId?.toString()).toBe(USER_ID.toString());
|
|
1145
|
+
},
|
|
1146
|
+
);
|
|
1147
|
+
|
|
1148
|
+
test.each<[string, string]>(FULL_MATRIX)(
|
|
1149
|
+
"%s consults only the finder for %s",
|
|
1150
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1151
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1152
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1153
|
+
|
|
1154
|
+
await deliverCell(channel, event);
|
|
1155
|
+
|
|
1156
|
+
const finders: Array<jest.SpyInstance> = [
|
|
1157
|
+
spies.incidentFind,
|
|
1158
|
+
spies.alertFind,
|
|
1159
|
+
spies.alertEpisodeFind,
|
|
1160
|
+
spies.incidentEpisodeFind,
|
|
1161
|
+
];
|
|
1162
|
+
|
|
1163
|
+
const called: Array<jest.SpyInstance> = finders.filter(
|
|
1164
|
+
(finder: jest.SpyInstance): boolean => {
|
|
1165
|
+
return finder.mock.calls.length > 0;
|
|
1166
|
+
},
|
|
1167
|
+
);
|
|
1168
|
+
|
|
1169
|
+
// The event type, not the ids on the options bag, picks the entity.
|
|
1170
|
+
expect(called).toHaveLength(1);
|
|
1171
|
+
},
|
|
1172
|
+
);
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
/*
|
|
1176
|
+
* ----------------------------------------------------------------------- *
|
|
1177
|
+
* (C) The row must exist before the body is written (constraint 6).
|
|
1178
|
+
* -----------------------------------------------------------------------
|
|
1179
|
+
*/
|
|
1180
|
+
|
|
1181
|
+
describe("the timeline row is created BEFORE the message body", () => {
|
|
1182
|
+
test.each<[string, string]>(GENERATOR_MATRIX)(
|
|
1183
|
+
"%s generates its %s body only after the row exists",
|
|
1184
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1185
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1186
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1187
|
+
|
|
1188
|
+
await deliverCell(channel, event);
|
|
1189
|
+
|
|
1190
|
+
const generator: jest.SpyInstance =
|
|
1191
|
+
generators[channel.generatorPrefix! + event.generatorSuffix]!;
|
|
1192
|
+
|
|
1193
|
+
expect(generator).toHaveBeenCalledTimes(1);
|
|
1194
|
+
expect(spies.timelineCreate.mock.invocationCallOrder[0]).toBeLessThan(
|
|
1195
|
+
generator.mock.invocationCallOrder[0] as number,
|
|
1196
|
+
);
|
|
1197
|
+
},
|
|
1198
|
+
);
|
|
1199
|
+
|
|
1200
|
+
test.each<[string, string]>(GENERATOR_MATRIX)(
|
|
1201
|
+
"%s derives its %s acknowledge link from the created row id",
|
|
1202
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1203
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1204
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1205
|
+
|
|
1206
|
+
await deliverCell(channel, event);
|
|
1207
|
+
|
|
1208
|
+
const generator: jest.SpyInstance =
|
|
1209
|
+
generators[channel.generatorPrefix! + event.generatorSuffix]!;
|
|
1210
|
+
|
|
1211
|
+
/*
|
|
1212
|
+
* This is why the ordering is load-bearing rather than incidental: the
|
|
1213
|
+
* generator turns this id into the acknowledge deep-link that is baked
|
|
1214
|
+
* into the message. Hoisting generation above the create - the obvious
|
|
1215
|
+
* "render once, send to many" optimisation - would produce a page whose
|
|
1216
|
+
* acknowledge button points at a row that does not exist.
|
|
1217
|
+
*/
|
|
1218
|
+
const timelineIdArgument: ObjectID = generator.mock.calls[0][
|
|
1219
|
+
channel.generatorTimelineIdArgIndex
|
|
1220
|
+
] as ObjectID;
|
|
1221
|
+
|
|
1222
|
+
expect(timelineIdArgument.toString()).toBe(TIMELINE_ID.toString());
|
|
1223
|
+
},
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
test.each<[string, string]>(GENERATOR_MATRIX)(
|
|
1227
|
+
"%s generates the %s body before handing the page to the provider",
|
|
1228
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1229
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1230
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1231
|
+
|
|
1232
|
+
await deliverCell(channel, event);
|
|
1233
|
+
|
|
1234
|
+
const generator: jest.SpyInstance =
|
|
1235
|
+
generators[channel.generatorPrefix! + event.generatorSuffix]!;
|
|
1236
|
+
|
|
1237
|
+
expect(generator.mock.invocationCallOrder[0]).toBeLessThan(
|
|
1238
|
+
channel.sender().mock.invocationCallOrder[0] as number,
|
|
1239
|
+
);
|
|
1240
|
+
},
|
|
1241
|
+
);
|
|
1242
|
+
|
|
1243
|
+
test.each<[string, string]>(GENERATOR_MATRIX)(
|
|
1244
|
+
"%s uses no OTHER generator for %s",
|
|
1245
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1246
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1247
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1248
|
+
|
|
1249
|
+
await deliverCell(channel, event);
|
|
1250
|
+
|
|
1251
|
+
const expectedName: string =
|
|
1252
|
+
channel.generatorPrefix! + event.generatorSuffix;
|
|
1253
|
+
|
|
1254
|
+
for (const generatorName of Object.keys(generators)) {
|
|
1255
|
+
if (generatorName === expectedName) {
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/*
|
|
1260
|
+
* A cell that fell through to a neighbour's template would still send
|
|
1261
|
+
* something - the wrong thing, describing the wrong entity. Pin that
|
|
1262
|
+
* exactly one generator runs, and that it is this cell's.
|
|
1263
|
+
*/
|
|
1264
|
+
expect(generators[generatorName]).not.toHaveBeenCalled();
|
|
1265
|
+
}
|
|
1266
|
+
},
|
|
1267
|
+
);
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
/*
|
|
1271
|
+
* ----------------------------------------------------------------------- *
|
|
1272
|
+
* (D) Channels with no generator still carry the row id.
|
|
1273
|
+
* -----------------------------------------------------------------------
|
|
1274
|
+
*/
|
|
1275
|
+
|
|
1276
|
+
describe("the webhook block", () => {
|
|
1277
|
+
test.each<[string, string]>(buildMatrix(["Webhook"]))(
|
|
1278
|
+
"%s puts the right eventType on the wire for %s",
|
|
1279
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1280
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1281
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1282
|
+
|
|
1283
|
+
await deliverCell(channel, event);
|
|
1284
|
+
|
|
1285
|
+
const request: { url: string; eventType: string; payload: unknown } =
|
|
1286
|
+
spies.webhook.mock.calls[0][0] as {
|
|
1287
|
+
url: string;
|
|
1288
|
+
eventType: string;
|
|
1289
|
+
payload: unknown;
|
|
1290
|
+
};
|
|
1291
|
+
|
|
1292
|
+
expect(request.url).toBe(WEBHOOK_URL);
|
|
1293
|
+
expect(request.eventType).toBe(event.webhookEventType);
|
|
1294
|
+
},
|
|
1295
|
+
);
|
|
1296
|
+
|
|
1297
|
+
test.each<[string, string]>(buildMatrix(["Webhook"]))(
|
|
1298
|
+
"%s repeats the eventType inside the payload for %s",
|
|
1299
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1300
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1301
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1302
|
+
|
|
1303
|
+
await deliverCell(channel, event);
|
|
1304
|
+
|
|
1305
|
+
const request: { payload: Record<string, unknown> } = spies.webhook.mock
|
|
1306
|
+
.calls[0][0] as { payload: Record<string, unknown> };
|
|
1307
|
+
|
|
1308
|
+
expect(request.payload["eventType"]).toBe(event.webhookEventType);
|
|
1309
|
+
expect(request.payload["userId"]).toBe(USER_ID.toString());
|
|
1310
|
+
},
|
|
1311
|
+
);
|
|
1312
|
+
|
|
1313
|
+
test("a webhook is dispatched on webhookUrl alone - there is no verification gate", async () => {
|
|
1314
|
+
/*
|
|
1315
|
+
* UserWebhook has no isVerified column, so the presence of a URL is the
|
|
1316
|
+
* whole gate. That is why Webhook is absent from the unverified matrix
|
|
1317
|
+
* below rather than missing from it by oversight.
|
|
1318
|
+
*/
|
|
1319
|
+
await deliverCell(
|
|
1320
|
+
channelSpec("Webhook"),
|
|
1321
|
+
eventSpec("IncidentEpisodeCreated"),
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1324
|
+
expect(spies.webhook).toHaveBeenCalledTimes(1);
|
|
1325
|
+
expect(timelineRows).toHaveLength(1);
|
|
1326
|
+
expect(timelineRows[0]?.status).toBe(UserNotificationStatus.Sending);
|
|
1327
|
+
});
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
/*
|
|
1331
|
+
* ----------------------------------------------------------------------- *
|
|
1332
|
+
* (E) Episode linkage - what the sender is told the page is about.
|
|
1333
|
+
* -----------------------------------------------------------------------
|
|
1334
|
+
*/
|
|
1335
|
+
|
|
1336
|
+
describe("episode ids on the sender options", () => {
|
|
1337
|
+
test.each<[string, string]>(
|
|
1338
|
+
buildMatrix(["Email", "SMS", "WhatsApp", "Telegram", "Call", "Push"]),
|
|
1339
|
+
)(
|
|
1340
|
+
"%s links the alert episode it is paging about (%s is ignored unless it is the alert episode)",
|
|
1341
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1342
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1343
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1344
|
+
|
|
1345
|
+
await deliverCell(channel, event);
|
|
1346
|
+
|
|
1347
|
+
const options: SenderOptions = senderOptions(channel.sender());
|
|
1348
|
+
|
|
1349
|
+
if (event.eventType === UserNotificationEventType.AlertEpisodeCreated) {
|
|
1350
|
+
expect(options.alertEpisodeId?.toString()).toBe(
|
|
1351
|
+
ALERT_EPISODE_ID.toString(),
|
|
1352
|
+
);
|
|
1353
|
+
} else {
|
|
1354
|
+
expect(options.alertEpisodeId).toBeUndefined();
|
|
1355
|
+
}
|
|
1356
|
+
},
|
|
1357
|
+
);
|
|
1358
|
+
|
|
1359
|
+
test.each<[string, string]>(
|
|
1360
|
+
buildMatrix(["Email", "SMS", "WhatsApp", "Telegram", "Call", "Push"]),
|
|
1361
|
+
)(
|
|
1362
|
+
"%s never passes an incidentEpisodeId for %s",
|
|
1363
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1364
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1365
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1366
|
+
|
|
1367
|
+
await deliverCell(channel, event);
|
|
1368
|
+
|
|
1369
|
+
/*
|
|
1370
|
+
* Deliberate, and pinned so it does not get "fixed" by adding the key
|
|
1371
|
+
* without fixing the transport: every one of these services accepts
|
|
1372
|
+
* incidentEpisodeId in its options type and then never serialises it
|
|
1373
|
+
* onto the request body. Passing it would read as a link that exists.
|
|
1374
|
+
* Contrast with alertEpisodeId above, which does travel.
|
|
1375
|
+
*/
|
|
1376
|
+
expect(
|
|
1377
|
+
senderOptions(channel.sender()).incidentEpisodeId,
|
|
1378
|
+
).toBeUndefined();
|
|
1379
|
+
},
|
|
1380
|
+
);
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
/*
|
|
1384
|
+
* ----------------------------------------------------------------------- *
|
|
1385
|
+
* (F) The unverified path: an Error row, and nothing sent.
|
|
1386
|
+
* -----------------------------------------------------------------------
|
|
1387
|
+
*/
|
|
1388
|
+
|
|
1389
|
+
describe("an unverified method is never contacted", () => {
|
|
1390
|
+
async function deliverUnverifiedCell(
|
|
1391
|
+
channel: ChannelSpec,
|
|
1392
|
+
event: EventSpec,
|
|
1393
|
+
): Promise<void> {
|
|
1394
|
+
event.arm();
|
|
1395
|
+
spies.findRule.mockResolvedValue(
|
|
1396
|
+
ruleItem(channel.unverifiedMethod!()) as never,
|
|
1397
|
+
);
|
|
1398
|
+
|
|
1399
|
+
await UserNotificationRuleService.executeNotificationRuleItem(
|
|
1400
|
+
RULE_ID,
|
|
1401
|
+
event.makeOptions(),
|
|
1402
|
+
);
|
|
1403
|
+
await flushMicrotasks();
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
test.each<[string, string]>(UNVERIFIED_MATRIX)(
|
|
1407
|
+
"an unverified %s sends nothing for %s",
|
|
1408
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1409
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1410
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1411
|
+
|
|
1412
|
+
await deliverUnverifiedCell(channel, event);
|
|
1413
|
+
|
|
1414
|
+
for (const sender of everySender()) {
|
|
1415
|
+
expect(sender).not.toHaveBeenCalled();
|
|
1416
|
+
}
|
|
1417
|
+
},
|
|
1418
|
+
);
|
|
1419
|
+
|
|
1420
|
+
test.each<[string, string]>(UNVERIFIED_MATRIX)(
|
|
1421
|
+
"an unverified %s writes exactly one Error row saying so for %s",
|
|
1422
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1423
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1424
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1425
|
+
|
|
1426
|
+
await deliverUnverifiedCell(channel, event);
|
|
1427
|
+
|
|
1428
|
+
/*
|
|
1429
|
+
* Exactly one: the not-verified row. The fell-through guard must NOT
|
|
1430
|
+
* also fire here - an unverified method is not a contactable channel,
|
|
1431
|
+
* so there is no missing template to report and a second row would tell
|
|
1432
|
+
* the operator a second, wrong story.
|
|
1433
|
+
*/
|
|
1434
|
+
expect(timelineRows).toHaveLength(1);
|
|
1435
|
+
expect(timelineRows[0]?.status).toBe(UserNotificationStatus.Error);
|
|
1436
|
+
expect(timelineRows[0]?.statusMessage).toContain(
|
|
1437
|
+
channel.unverifiedMessage,
|
|
1438
|
+
);
|
|
1439
|
+
expect(timelineRows[0]?.statusMessage).toContain("not verified");
|
|
1440
|
+
expect(timelineRows[0]?.statusMessage).not.toContain(
|
|
1441
|
+
"No notification template",
|
|
1442
|
+
);
|
|
1443
|
+
},
|
|
1444
|
+
);
|
|
1445
|
+
|
|
1446
|
+
test.each<[string, string]>(UNVERIFIED_MATRIX)(
|
|
1447
|
+
"an unverified %s never reaches a generator for %s",
|
|
1448
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1449
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1450
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1451
|
+
|
|
1452
|
+
await deliverUnverifiedCell(channel, event);
|
|
1453
|
+
|
|
1454
|
+
for (const generatorName of Object.keys(generators)) {
|
|
1455
|
+
expect(generators[generatorName]).not.toHaveBeenCalled();
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
);
|
|
1459
|
+
|
|
1460
|
+
test("an unverified method is not a contactable channel", () => {
|
|
1461
|
+
for (const channelName of VERIFIABLE_CHANNEL_NAMES) {
|
|
1462
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1463
|
+
|
|
1464
|
+
expect(
|
|
1465
|
+
contactableChannelNames(ruleItem(channel.unverifiedMethod!())),
|
|
1466
|
+
).toEqual([]);
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
/*
|
|
1472
|
+
* ----------------------------------------------------------------------- *
|
|
1473
|
+
* (G) The fell-through guard.
|
|
1474
|
+
*
|
|
1475
|
+
* With all twenty eight cells wired, no rule reaches this guard today - which
|
|
1476
|
+
* is exactly the state it is meant to police, and exactly why it needs a test
|
|
1477
|
+
* of its own rather than incidental coverage. The precondition it fires on is
|
|
1478
|
+
* "the channel census found somebody reachable, and then no block claimed the
|
|
1479
|
+
* event". The census is taken before the triggering entity is loaded, so
|
|
1480
|
+
* dropping the relation inside the entity finder's stub reproduces that
|
|
1481
|
+
* precondition exactly, standing in for a future event type that is wired
|
|
1482
|
+
* into entity resolution but not into the channel blocks - which is precisely
|
|
1483
|
+
* the shape Gap F had.
|
|
1484
|
+
* -----------------------------------------------------------------------
|
|
1485
|
+
*/
|
|
1486
|
+
|
|
1487
|
+
describe("the fell-through guard", () => {
|
|
1488
|
+
function ruleThatLosesItsChannelsAfterTheCensus(
|
|
1489
|
+
methods: Record<string, unknown>,
|
|
1490
|
+
relationsToDrop: Array<string>,
|
|
1491
|
+
): UserNotificationRule {
|
|
1492
|
+
const rule: UserNotificationRule = ruleItem(methods);
|
|
1493
|
+
|
|
1494
|
+
spies.alertEpisodeFind.mockImplementation((): Promise<AlertEpisode> => {
|
|
1495
|
+
for (const relation of relationsToDrop) {
|
|
1496
|
+
(rule as unknown as Record<string, unknown>)[relation] = undefined;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
return Promise.resolve(fakeAlertEpisode());
|
|
1500
|
+
});
|
|
1501
|
+
|
|
1502
|
+
spies.findRule.mockResolvedValue(rule as never);
|
|
1503
|
+
|
|
1504
|
+
return rule;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
async function runGuardScenario(
|
|
1508
|
+
methods: Record<string, unknown>,
|
|
1509
|
+
relationsToDrop: Array<string>,
|
|
1510
|
+
): Promise<void> {
|
|
1511
|
+
ruleThatLosesItsChannelsAfterTheCensus(methods, relationsToDrop);
|
|
1512
|
+
|
|
1513
|
+
await UserNotificationRuleService.executeNotificationRuleItem(
|
|
1514
|
+
RULE_ID,
|
|
1515
|
+
eventSpec("AlertEpisodeCreated").makeOptions(),
|
|
1516
|
+
);
|
|
1517
|
+
await flushMicrotasks();
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
test("a contactable channel that no block claimed writes an Error row", async () => {
|
|
1521
|
+
await runGuardScenario(channelSpec("SMS").verifiedMethod(), ["userSms"]);
|
|
1522
|
+
|
|
1523
|
+
for (const sender of everySender()) {
|
|
1524
|
+
expect(sender).not.toHaveBeenCalled();
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
/*
|
|
1528
|
+
* The whole point. Before the guard existed this execution produced no
|
|
1529
|
+
* message AND no row: the page was simply gone, and the on-call log went
|
|
1530
|
+
* on waiting for an acknowledgement that could never come.
|
|
1531
|
+
*/
|
|
1532
|
+
expect(timelineRows).toHaveLength(1);
|
|
1533
|
+
expect(timelineRows[0]?.status).toBe(UserNotificationStatus.Error);
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
test("the guard's row names the event type and the channel that had no template", async () => {
|
|
1537
|
+
await runGuardScenario(channelSpec("SMS").verifiedMethod(), ["userSms"]);
|
|
1538
|
+
|
|
1539
|
+
expect(timelineRows[0]?.statusMessage).toBe(
|
|
1540
|
+
"No notification template for Alert Episode Created on SMS.",
|
|
1541
|
+
);
|
|
1542
|
+
});
|
|
1543
|
+
|
|
1544
|
+
test("the guard names EVERY channel the rule could have reached", async () => {
|
|
1545
|
+
const methods: Record<string, unknown> = {
|
|
1546
|
+
...channelSpec("Email").verifiedMethod(),
|
|
1547
|
+
...channelSpec("SMS").verifiedMethod(),
|
|
1548
|
+
};
|
|
1549
|
+
|
|
1550
|
+
await runGuardScenario(methods, ["userEmail", "userSms"]);
|
|
1551
|
+
|
|
1552
|
+
// In getContactableChannelNames order: Email before SMS.
|
|
1553
|
+
expect(timelineRows[0]?.statusMessage).toBe(
|
|
1554
|
+
"No notification template for Alert Episode Created on Email, SMS.",
|
|
1555
|
+
);
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
test("the guard's row carries the same identifiers a delivery row would", async () => {
|
|
1559
|
+
await runGuardScenario(channelSpec("SMS").verifiedMethod(), ["userSms"]);
|
|
1560
|
+
|
|
1561
|
+
const row: TimelineRow | undefined = timelineRows[0];
|
|
1562
|
+
|
|
1563
|
+
expect(row?.userId?.toString()).toBe(USER_ID.toString());
|
|
1564
|
+
expect(row?.userNotificationRuleId?.toString()).toBe(RULE_ID.toString());
|
|
1565
|
+
expect(row?.userNotificationLogId?.toString()).toBe(LOG_ID.toString());
|
|
1566
|
+
expect(row?.projectId?.toString()).toBe(PROJECT_ID.toString());
|
|
1567
|
+
expect(row?.userNotificationEventType).toBe(
|
|
1568
|
+
UserNotificationEventType.AlertEpisodeCreated,
|
|
1569
|
+
);
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
test("the guard also logs, so the miss is visible outside the timeline", async () => {
|
|
1573
|
+
await runGuardScenario(channelSpec("SMS").verifiedMethod(), ["userSms"]);
|
|
1574
|
+
|
|
1575
|
+
expect(spies.loggerError).toHaveBeenCalled();
|
|
1576
|
+
expect(String(spies.loggerError.mock.calls[0][0])).toContain(
|
|
1577
|
+
"No notification template for Alert Episode Created on SMS.",
|
|
1578
|
+
);
|
|
1579
|
+
// The on-call log id is what lets an operator find the dropped page.
|
|
1580
|
+
expect(String(spies.loggerError.mock.calls[0][0])).toContain(
|
|
1581
|
+
LOG_ID.toString(),
|
|
1582
|
+
);
|
|
1583
|
+
});
|
|
1584
|
+
|
|
1585
|
+
test("the guard builds a FRESH row rather than reusing the one the blocks write", async () => {
|
|
1586
|
+
/*
|
|
1587
|
+
* Constraint 1: the channel blocks share one UserOnCallLogTimeline
|
|
1588
|
+
* instance, and after its first create() it carries an _id, so a second
|
|
1589
|
+
* create() with it UPDATEs that row instead of inserting. Here an
|
|
1590
|
+
* unverified email writes the first row and a vanishing SMS trips the
|
|
1591
|
+
* guard; if the guard reused the shared instance it would overwrite the
|
|
1592
|
+
* "email not verified" row and the operator would lose one of the two
|
|
1593
|
+
* reasons this page failed.
|
|
1594
|
+
*/
|
|
1595
|
+
const methods: Record<string, unknown> = {
|
|
1596
|
+
...channelSpec("Email").unverifiedMethod!(),
|
|
1597
|
+
...channelSpec("SMS").verifiedMethod(),
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
await runGuardScenario(methods, ["userSms"]);
|
|
1601
|
+
|
|
1602
|
+
expect(timelineRows).toHaveLength(2);
|
|
1603
|
+
expect(timelineRows[0]?.statusMessage).toContain("not verified");
|
|
1604
|
+
expect(timelineRows[1]?.statusMessage).toContain(
|
|
1605
|
+
"No notification template",
|
|
1606
|
+
);
|
|
1607
|
+
// Two rows, two distinct instances - not one instance created twice.
|
|
1608
|
+
expect(timelineInstances[0]).not.toBe(timelineInstances[1]);
|
|
1609
|
+
});
|
|
1610
|
+
|
|
1611
|
+
test("a rule with no contactable channel at all stays silent", async () => {
|
|
1612
|
+
/*
|
|
1613
|
+
* The guard is gated on the census being non-empty. A rule whose method
|
|
1614
|
+
* was cascade-deleted can contact nobody, so there is no missing template
|
|
1615
|
+
* to report - it writes nothing, which is pinned here as the deliberate
|
|
1616
|
+
* boundary of the guard rather than a second silent hole.
|
|
1617
|
+
*/
|
|
1618
|
+
eventSpec("AlertEpisodeCreated").arm();
|
|
1619
|
+
spies.findRule.mockResolvedValue(ruleItem() as never);
|
|
1620
|
+
|
|
1621
|
+
await UserNotificationRuleService.executeNotificationRuleItem(
|
|
1622
|
+
RULE_ID,
|
|
1623
|
+
eventSpec("AlertEpisodeCreated").makeOptions(),
|
|
1624
|
+
);
|
|
1625
|
+
await flushMicrotasks();
|
|
1626
|
+
|
|
1627
|
+
for (const sender of everySender()) {
|
|
1628
|
+
expect(sender).not.toHaveBeenCalled();
|
|
1629
|
+
}
|
|
1630
|
+
expect(timelineRows).toHaveLength(0);
|
|
1631
|
+
});
|
|
1632
|
+
|
|
1633
|
+
test.each<[string, string]>(FULL_MATRIX)(
|
|
1634
|
+
"the guard stays quiet when %s delivered for %s",
|
|
1635
|
+
async (channelName: string, eventName: string): Promise<void> => {
|
|
1636
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1637
|
+
const event: EventSpec = eventSpec(eventName);
|
|
1638
|
+
|
|
1639
|
+
await deliverCell(channel, event);
|
|
1640
|
+
|
|
1641
|
+
for (const row of timelineRows) {
|
|
1642
|
+
expect(row.statusMessage).not.toContain("No notification template");
|
|
1643
|
+
}
|
|
1644
|
+
expect(spies.loggerError).not.toHaveBeenCalled();
|
|
1645
|
+
},
|
|
1646
|
+
);
|
|
1647
|
+
|
|
1648
|
+
test("an event type nothing is wired for throws before the guard can speak", async () => {
|
|
1649
|
+
/*
|
|
1650
|
+
* The guard's blind spot, recorded rather than asserted as desirable: it
|
|
1651
|
+
* sits AFTER the entity resolution that throws when no event branch
|
|
1652
|
+
* matched, so it can only ever report a channel gap for an event type
|
|
1653
|
+
* that already resolves an entity. A brand new event type with no
|
|
1654
|
+
* resolution block at all fails as a BadDataException instead - still
|
|
1655
|
+
* loud (the worker marks the log Error), but it never produces the
|
|
1656
|
+
* per-channel row the guard was written to produce.
|
|
1657
|
+
*/
|
|
1658
|
+
spies.findRule.mockResolvedValue(
|
|
1659
|
+
ruleItem(channelSpec("SMS").verifiedMethod()) as never,
|
|
1660
|
+
);
|
|
1661
|
+
spies.incidentFind.mockResolvedValue(fakeIncident() as never);
|
|
1662
|
+
|
|
1663
|
+
await expect(
|
|
1664
|
+
UserNotificationRuleService.executeNotificationRuleItem(RULE_ID, {
|
|
1665
|
+
projectId: PROJECT_ID,
|
|
1666
|
+
userNotificationEventType:
|
|
1667
|
+
"Maintenance Started" as unknown as UserNotificationEventType,
|
|
1668
|
+
triggeredByIncidentId: INCIDENT_ID,
|
|
1669
|
+
onCallPolicyId: POLICY_ID,
|
|
1670
|
+
userNotificationLogId: LOG_ID,
|
|
1671
|
+
}),
|
|
1672
|
+
).rejects.toThrow(BadDataException);
|
|
1673
|
+
|
|
1674
|
+
expect(spies.incidentFind).not.toHaveBeenCalled();
|
|
1675
|
+
expect(timelineRows).toHaveLength(0);
|
|
1676
|
+
expect(spies.sms).not.toHaveBeenCalled();
|
|
1677
|
+
});
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
/*
|
|
1681
|
+
* ----------------------------------------------------------------------- *
|
|
1682
|
+
* (H) A send that fails still flips its row - the matrix cells above all
|
|
1683
|
+
* resolve, so the failure half is pinned once per channel here.
|
|
1684
|
+
* -----------------------------------------------------------------------
|
|
1685
|
+
*/
|
|
1686
|
+
|
|
1687
|
+
describe("a provider rejection flips the row it created", () => {
|
|
1688
|
+
test.each<[string]>([
|
|
1689
|
+
["Email"],
|
|
1690
|
+
["SMS"],
|
|
1691
|
+
["WhatsApp"],
|
|
1692
|
+
["Telegram"],
|
|
1693
|
+
["Webhook"],
|
|
1694
|
+
["Call"],
|
|
1695
|
+
["Push"],
|
|
1696
|
+
])(
|
|
1697
|
+
"%s marks its incident-episode row Error when the provider rejects",
|
|
1698
|
+
async (channelName: string): Promise<void> => {
|
|
1699
|
+
const channel: ChannelSpec = channelSpec(channelName);
|
|
1700
|
+
const event: EventSpec = eventSpec("IncidentEpisodeCreated");
|
|
1701
|
+
|
|
1702
|
+
channel.sender().mockRejectedValue(new Error("provider down") as never);
|
|
1703
|
+
|
|
1704
|
+
await deliverCell(channel, event);
|
|
1705
|
+
|
|
1706
|
+
/*
|
|
1707
|
+
* Nothing is awaited, so this update lands a microtask after
|
|
1708
|
+
* executeNotificationRuleItem resolved - which is why deliverCell
|
|
1709
|
+
* flushes. Without the flush this assertion races and passes or fails
|
|
1710
|
+
* depending on how many awaits the block happened to contain.
|
|
1711
|
+
*/
|
|
1712
|
+
expect(spies.timelineUpdate).toHaveBeenCalledTimes(1);
|
|
1713
|
+
|
|
1714
|
+
const update: {
|
|
1715
|
+
id: ObjectID;
|
|
1716
|
+
data: { status: UserNotificationStatus; statusMessage: string };
|
|
1717
|
+
} = spies.timelineUpdate.mock.calls[0][0] as {
|
|
1718
|
+
id: ObjectID;
|
|
1719
|
+
data: { status: UserNotificationStatus; statusMessage: string };
|
|
1720
|
+
};
|
|
1721
|
+
|
|
1722
|
+
expect(update.id.toString()).toBe(TIMELINE_ID.toString());
|
|
1723
|
+
expect(update.data.status).toBe(UserNotificationStatus.Error);
|
|
1724
|
+
expect(update.data.statusMessage).toBe("provider down");
|
|
1725
|
+
},
|
|
1726
|
+
);
|
|
1727
|
+
});
|
|
1728
|
+
});
|