@oneuptime/common 14.0.10 → 14.0.11
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/DatabaseModels/AIConversation.ts +127 -1
- package/Models/DatabaseModels/CephCluster.ts +12 -0
- package/Models/DatabaseModels/CloudResource.ts +12 -0
- package/Models/DatabaseModels/DatabaseServer.ts +12 -0
- package/Models/DatabaseModels/DockerHost.ts +12 -0
- package/Models/DatabaseModels/DockerSwarmCluster.ts +12 -0
- package/Models/DatabaseModels/GlobalOidc.ts +0 -4
- package/Models/DatabaseModels/GlobalOidcProject.ts +0 -4
- package/Models/DatabaseModels/GlobalSso.ts +0 -4
- package/Models/DatabaseModels/GlobalSsoProject.ts +0 -4
- package/Models/DatabaseModels/Host.ts +12 -0
- package/Models/DatabaseModels/IncidentForm.ts +594 -0
- package/Models/DatabaseModels/IncidentFormSubmission.ts +283 -0
- package/Models/DatabaseModels/IncidentTemplate.ts +59 -0
- package/Models/DatabaseModels/Index.ts +14 -0
- package/Models/DatabaseModels/IoTFleet.ts +12 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +12 -0
- package/Models/DatabaseModels/MessageQueue.ts +1102 -0
- package/Models/DatabaseModels/MessageQueueLabelRule.ts +553 -0
- package/Models/DatabaseModels/MessageQueueOwnerRule.ts +633 -0
- package/Models/DatabaseModels/MessageQueueOwnerTeam.ts +491 -0
- package/Models/DatabaseModels/MessageQueueOwnerUser.ts +490 -0
- package/Models/DatabaseModels/PodmanHost.ts +12 -0
- package/Models/DatabaseModels/Project.ts +59 -0
- package/Models/DatabaseModels/ProjectOidc.ts +0 -4
- package/Models/DatabaseModels/ProjectSso.ts +0 -4
- package/Models/DatabaseModels/ProxmoxCluster.ts +12 -0
- package/Models/DatabaseModels/RumApplication.ts +12 -0
- package/Models/DatabaseModels/ServerlessFunction.ts +12 -0
- package/Models/DatabaseModels/Service.ts +12 -0
- package/Models/DatabaseModels/StatusPageOidc.ts +0 -4
- package/Models/DatabaseModels/StatusPageSso.ts +0 -4
- package/Models/DatabaseModels/TeamComplianceSetting.ts +47 -2
- package/Models/DatabaseModels/VMwareVCenter.ts +12 -0
- package/Server/API/AIChatAPI.ts +13 -114
- package/Server/API/AIInvestigationAPI.ts +3 -3
- package/Server/API/AIInvestigationConversationAPI.ts +266 -0
- package/Server/API/IncidentFormAPI.ts +376 -0
- package/Server/API/ProjectOIDC.ts +16 -25
- package/Server/API/ProjectSSO.ts +16 -24
- package/Server/API/StatusPageAPI.ts +42 -77
- package/Server/Enterprise/EnterpriseEdition.ts +93 -28
- package/Server/Enterprise/EnterpriseFeature.ts +25 -3
- package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +1 -1
- package/Server/Enterprise/EnterpriseServerModule.ts +4 -1
- package/Server/EnvironmentConfig.ts +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.ts +129 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.ts +52 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.ts +75 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.ts +477 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Middleware/IncidentFormRateLimit.ts +847 -0
- package/Server/Middleware/UserAuthorization.ts +16 -38
- package/Server/Services/AIService.ts +10 -13
- package/Server/Services/IncidentCustomFieldService.ts +112 -2
- package/Server/Services/IncidentEpisodeMemberService.ts +24 -4
- package/Server/Services/IncidentEpisodeService.ts +10 -1
- package/Server/Services/IncidentFormService.ts +1566 -0
- package/Server/Services/IncidentFormSubmissionService.ts +97 -0
- package/Server/Services/IncidentGroupingEngineService.ts +45 -8
- package/Server/Services/IncidentService.ts +123 -5
- package/Server/Services/IncidentTemplateService.ts +22 -0
- package/Server/Services/Index.ts +14 -0
- package/Server/Services/MessageQueueLabelRuleEngineService.ts +321 -0
- package/Server/Services/MessageQueueLabelRuleService.ts +14 -0
- package/Server/Services/MessageQueueOwnerRuleEngineService.ts +412 -0
- package/Server/Services/MessageQueueOwnerRuleService.ts +14 -0
- package/Server/Services/MessageQueueOwnerTeamService.ts +62 -0
- package/Server/Services/MessageQueueOwnerUserService.ts +62 -0
- package/Server/Services/MessageQueueService.ts +1463 -0
- package/Server/Services/OpenTelemetryIngestService.ts +37 -4
- package/Server/Services/ProjectService.ts +0 -59
- package/Server/Services/StatusPagePrivateUserService.ts +1 -4
- package/Server/Services/StatusPageService.ts +1 -51
- package/Server/Services/TeamComplianceSettingService.ts +163 -42
- package/Server/Services/UserNotificationRuleService.ts +8 -1
- package/Server/Services/WorkspaceNotificationSummaryService.ts +12 -3
- package/Server/Types/Database/Permissions/CreatePermission.ts +17 -3
- package/Server/Types/Database/Permissions/EditionPermission.ts +140 -38
- package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +16 -0
- package/Server/Types/Database/Permissions/TablePermission.ts +3 -3
- package/Server/Types/Database/Permissions/UpdatePermission.ts +20 -6
- package/Server/Utils/AI/Chat/AgentContextCompactor.ts +200 -0
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +307 -48
- package/Server/Utils/AI/Chat/ChatRunCancellation.ts +156 -0
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +78 -18
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
- package/Server/Utils/AI/Chat/ToolOutputPager.ts +322 -0
- package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +88 -30
- package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +13 -1
- package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +0 -32
- package/Server/Utils/AI/CodeFix/FixRunBudget.ts +27 -25
- package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +7 -1
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +34 -14
- package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +69 -24
- package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +9 -0
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +53 -10
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +27 -10
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +27 -10
- package/Server/Utils/AI/SRE/InvestigationQueue.ts +16 -19
- package/Server/Utils/AI/SRE/InvestigationThread.ts +646 -0
- package/Server/Utils/AI/SRE/InvestigationThreadService.ts +715 -0
- package/Server/Utils/AI/Toolbox/Serializer.ts +10 -3
- package/Server/Utils/EditionEnforcement.ts +19 -202
- package/Server/Utils/LLM/LLMService.ts +259 -63
- package/Server/Utils/RelaxedScimTeamLocksReport.ts +245 -0
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
- package/Server/Utils/SameOriginRequest.ts +203 -0
- package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +4 -1
- package/Server/Utils/Telemetry/MessageQueueDiscovery.ts +1402 -0
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +4 -1
- package/Server/Utils/VendorAssets.ts +3 -3
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +3 -1
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +3 -1
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +10 -1
- package/Tests/App/Accounts/IncidentFormPage.test.tsx +2036 -0
- package/Tests/App/Accounts/IncidentFormServerMessages.test.ts +119 -0
- package/Tests/App/Accounts/IncidentFormUtils.test.ts +1157 -0
- package/Tests/App/Accounts/LoginPasskey.test.tsx +6 -4
- package/Tests/App/Accounts/LoginSso.test.tsx +724 -0
- package/Tests/App/AdminDashboard/AdminAuthenticationRequireSso.test.tsx +338 -0
- package/Tests/App/AdminDashboard/AdminAuthenticationSsoCardLocales.test.tsx +331 -0
- package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +47 -33
- package/Tests/App/AdminDashboard/GlobalSsoOidcPages.test.tsx +1021 -0
- package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +876 -78
- package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +20 -3
- package/Tests/App/Dashboard/CephProxmoxVMwareDocumentationCards.test.tsx +359 -0
- package/Tests/App/Dashboard/CephSetupGuide.test.ts +961 -0
- package/Tests/App/Dashboard/CloudDocumentationCard.test.tsx +381 -95
- package/Tests/App/Dashboard/CloudSetupGuide.test.ts +1463 -0
- package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +174 -12
- package/Tests/App/Dashboard/ContainerSetupGuideCards.test.tsx +345 -0
- package/Tests/App/Dashboard/CoreListEmptyStates.test.tsx +501 -0
- package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +0 -1
- package/Tests/App/Dashboard/DatabaseDocumentationCard.test.tsx +400 -0
- package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +5 -0
- package/Tests/App/Dashboard/DatabaseSetupGuide.test.ts +1869 -0
- package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +44 -0
- package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +50 -9
- package/Tests/App/Dashboard/DockerSetupGuide.test.ts +899 -0
- package/Tests/App/Dashboard/DockerSwarmSetupGuide.test.ts +964 -0
- package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +141 -7
- package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +52 -39
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +7 -1
- package/Tests/App/Dashboard/GettingStartedTaskRoutes.test.tsx +331 -0
- package/Tests/App/Dashboard/HomeOverviewStatsFirstRun.test.tsx +925 -0
- package/Tests/App/Dashboard/HostDocumentationCard.test.tsx +225 -0
- package/Tests/App/Dashboard/HostSetupGuide.test.ts +1183 -0
- package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +69 -45
- package/Tests/App/Dashboard/IncidentCreateTemplateCustomFieldSettings.test.tsx +745 -0
- package/Tests/App/Dashboard/IncidentCreateTemplateOwners.test.tsx +414 -0
- package/Tests/App/Dashboard/IncidentCustomFieldCreateSettingsForm.test.ts +1042 -0
- package/Tests/App/Dashboard/IncidentCustomFieldSettingsCard.test.tsx +1816 -0
- package/Tests/App/Dashboard/IncidentFormPlan.test.ts +89 -0
- package/Tests/App/Dashboard/IncidentFormShareLinkCard.test.tsx +846 -0
- package/Tests/App/Dashboard/IncidentFormsPages.test.tsx +1324 -0
- package/Tests/App/Dashboard/IncidentTemplateCustomFieldSettings.test.tsx +630 -0
- package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +13 -0
- package/Tests/App/Dashboard/IncomingEmailMonitorLinkVerifyHint.test.tsx +104 -0
- package/Tests/App/Dashboard/InvestigationConversation.test.tsx +1107 -0
- package/Tests/App/Dashboard/InvestigationConversationData.test.ts +546 -0
- package/Tests/App/Dashboard/InvestigationEvidenceList.test.tsx +170 -5
- package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +207 -2
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +729 -34
- package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +9 -21
- package/Tests/App/Dashboard/InvestigationReportView.test.tsx +274 -37
- package/Tests/App/Dashboard/InvestigationStatusBadge.test.tsx +130 -0
- package/Tests/App/Dashboard/IoTDocumentationCard.test.tsx +218 -0
- package/Tests/App/Dashboard/IoTSetupGuide.test.ts +1124 -0
- package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +20 -6
- package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +107 -83
- package/Tests/App/Dashboard/KubernetesSetupGuide.test.ts +787 -0
- package/Tests/App/Dashboard/MessageQueueBreadcrumbs.test.tsx +228 -0
- package/Tests/App/Dashboard/MessageQueueBrokerHealth.test.tsx +1538 -0
- package/Tests/App/Dashboard/MessageQueueBrokerHealthMetricChart.test.tsx +969 -0
- package/Tests/App/Dashboard/MessageQueueBrokerHealthReads.test.ts +889 -0
- package/Tests/App/Dashboard/MessageQueueBrokerMetricsReach.test.ts +236 -0
- package/Tests/App/Dashboard/MessageQueueDocumentationCard.test.tsx +479 -0
- package/Tests/App/Dashboard/MessageQueueListPages.test.tsx +1232 -0
- package/Tests/App/Dashboard/MessageQueueMetricDescriptions.test.ts +244 -0
- package/Tests/App/Dashboard/MessageQueueMonitorLink.test.ts +952 -0
- package/Tests/App/Dashboard/MessageQueueMonitorLinkMonitorCreate.test.tsx +859 -0
- package/Tests/App/Dashboard/MessageQueueOverview.test.tsx +1773 -0
- package/Tests/App/Dashboard/MessageQueueOverviewPresentation.test.ts +196 -0
- package/Tests/App/Dashboard/MessageQueuePresentation.test.ts +799 -0
- package/Tests/App/Dashboard/MessageQueueProducersConsumers.test.tsx +680 -0
- package/Tests/App/Dashboard/MessageQueueRoutes.test.tsx +383 -0
- package/Tests/App/Dashboard/MessageQueueRulePages.test.tsx +278 -0
- package/Tests/App/Dashboard/MessageQueueSetupGuide.test.ts +2778 -0
- package/Tests/App/Dashboard/MessageQueueSideMenus.test.tsx +273 -0
- package/Tests/App/Dashboard/MessageQueueSpanPopulations.test.ts +930 -0
- package/Tests/App/Dashboard/MessageQueueSpanStore.ts +351 -0
- package/Tests/App/Dashboard/MessageQueueTelemetryScope.test.ts +375 -0
- package/Tests/App/Dashboard/MessageQueueTelemetryScopeTabs.test.tsx +668 -0
- package/Tests/App/Dashboard/MessageQueueViewPages.test.tsx +685 -0
- package/Tests/App/Dashboard/MonitorAndHomeNotOperationalMenus.test.tsx +342 -0
- package/Tests/App/Dashboard/MonitorCreateFirstRunDefaults.test.tsx +623 -0
- package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +442 -0
- package/Tests/App/Dashboard/MonitorLogsPageViewSummary.test.tsx +814 -0
- package/Tests/App/Dashboard/MonitorSetupCardBodies.test.tsx +246 -0
- package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +8 -0
- package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +27 -12
- package/Tests/App/Dashboard/PlanGatedPage.test.tsx +292 -0
- package/Tests/App/Dashboard/PodmanSetupGuide.test.ts +948 -0
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +5 -4
- package/Tests/App/Dashboard/ProductSettingsSectionCollapsed.test.tsx +266 -0
- package/Tests/App/Dashboard/ProxmoxSetupGuide.test.ts +1318 -0
- package/Tests/App/Dashboard/ResourceAiAgentAttentionServerParity.test.ts +555 -0
- package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +298 -9
- package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +453 -0
- package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +16 -0
- package/Tests/App/Dashboard/ResourceConnectionGuideCard.test.tsx +444 -0
- package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsInfra.test.tsx +798 -0
- package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsPlatforms.test.tsx +717 -0
- package/Tests/App/Dashboard/ResourceConnectionGuides.test.ts +981 -0
- package/Tests/App/Dashboard/ResourceConnectionState.test.ts +132 -0
- package/Tests/App/Dashboard/RumDocumentationCard.test.tsx +293 -0
- package/Tests/App/Dashboard/RumSessionReplayRetentionSettings.test.tsx +467 -0
- package/Tests/App/Dashboard/RumSetupGuide.test.ts +758 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +25 -0
- package/Tests/App/Dashboard/ServerlessDocumentationCard.test.tsx +254 -0
- package/Tests/App/Dashboard/ServerlessSetupGuide.test.ts +1074 -0
- package/Tests/App/Dashboard/SetupGuide.test.ts +246 -0
- package/Tests/App/Dashboard/SetupGuideCard.test.tsx +756 -0
- package/Tests/App/Dashboard/SloListSideMenu.test.tsx +120 -0
- package/Tests/App/Dashboard/SsoPages.test.tsx +833 -0
- package/Tests/App/Dashboard/TelemetryDocumentation.test.tsx +253 -0
- package/Tests/App/Dashboard/TelemetryRetentionShells.test.tsx +730 -0
- package/Tests/App/Dashboard/VMwareSetupGuide.test.ts +1033 -0
- package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +87 -7
- package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +28 -9
- package/Tests/Models/DatabaseModels/IncidentFormModel.test.ts +1250 -0
- package/Tests/Models/DatabaseModels/MessageQueueModels.test.ts +1983 -0
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +190 -14
- package/Tests/Models/IncidentTemplateCustomFieldSettingsColumn.test.ts +291 -0
- package/Tests/Server/API/AIInvestigationConversationAPI.test.ts +412 -0
- package/Tests/Server/API/GlobalConfigLicense.test.ts +38 -9
- package/Tests/Server/API/IncidentFormAPI.test.ts +942 -0
- package/Tests/Server/API/IncidentFormPublicRoutes.test.ts +1653 -0
- package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -1
- package/Tests/Server/API/{SsoProviderListingsEdition.test.ts → SsoProviderListings.test.ts} +164 -110
- package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +76 -54
- package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +125 -42
- package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +36 -10
- package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +51 -9
- package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +13 -8
- package/Tests/Server/Enterprise/SsoInCommunityEdition.test.ts +540 -0
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentFormsMigration.test.ts +438 -0
- package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleNotificationChannelsMigration.test.ts +255 -0
- package/Tests/Server/Infrastructure/Postgres/MessageQueueTablesMigration.test.ts +637 -0
- package/Tests/Server/Middleware/IncidentFormRateLimit.test.ts +1630 -0
- package/Tests/Server/Middleware/UserAuthorization.test.ts +16 -17
- package/Tests/Server/Middleware/UserAuthorizationSsoEnforcement.test.ts +544 -0
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +37 -8
- package/Tests/Server/Services/IncidentCreatedFeedTitle.test.ts +212 -0
- package/Tests/Server/Services/IncidentCustomFieldDeleteFormQuestions.test.ts +297 -0
- package/Tests/Server/Services/IncidentFormOwnersCreatedNotification.test.ts +497 -0
- package/Tests/Server/Services/IncidentFormPostgres.test.ts +958 -0
- package/Tests/Server/Services/IncidentFormPublicForm.test.ts +1260 -0
- package/Tests/Server/Services/IncidentFormPublicSubmission.test.ts +2520 -0
- package/Tests/Server/Services/IncidentFormRegistration.test.ts +159 -0
- package/Tests/Server/Services/IncidentFormService.test.ts +663 -0
- package/Tests/Server/Services/IncidentFormSubmissionService.test.ts +430 -0
- package/Tests/Server/Services/IncidentFormSubmissionTemplateBranch.test.ts +473 -0
- package/Tests/Server/Services/IncidentGroupingTemplateSubstitution.test.ts +81 -0
- package/Tests/Server/Services/IncidentTemplateCustomFieldSettings.test.ts +262 -0
- package/Tests/Server/Services/IncidentTemplateOwnersOnDeclare.test.ts +566 -0
- package/Tests/Server/Services/IncidentTitleMarkdownSinks.test.ts +640 -0
- package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +3 -3
- package/Tests/Server/Services/MessageQueueOwnerServices.test.ts +440 -0
- package/Tests/Server/Services/MessageQueueRegistries.test.ts +577 -0
- package/Tests/Server/Services/MessageQueueRuleEngines.test.ts +900 -0
- package/Tests/Server/Services/MessageQueueService.test.ts +2210 -0
- package/Tests/Server/Services/MessageQueueSqlPostgres.test.ts +1271 -0
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +28 -1
- package/Tests/Server/Services/OpenTelemetryRetentionOverrideEdition.test.ts +474 -0
- package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +21 -0
- package/Tests/Server/Services/OwnerRowUniqueness.test.ts +3 -0
- package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +0 -1
- package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +56 -6
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -1
- package/Tests/Server/Services/SsoRequirementReads.test.ts +454 -0
- package/Tests/Server/Services/SsoRequirementWrites.test.ts +529 -0
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
- package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +107 -89
- package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +1068 -176
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +1962 -200
- package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +654 -297
- package/Tests/Server/Types/Database/Permissions/RetentionOverrideColumns.test.ts +784 -0
- package/Tests/Server/Types/Database/Permissions/SsoScalePlanGate.test.ts +418 -0
- package/Tests/Server/Utils/AI/AIAlertGating.test.ts +0 -1
- package/Tests/Server/Utils/AI/AIInvestigationEngineRunLimits.test.ts +136 -0
- package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +15 -14
- package/Tests/Server/Utils/AI/AgentContextCompactor.test.ts +351 -0
- package/Tests/Server/Utils/AI/ChatAgentRunnerSharedThread.test.ts +842 -0
- package/Tests/Server/Utils/AI/ChatRunCancellation.test.ts +195 -0
- package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +10 -57
- package/Tests/Server/Utils/AI/FixRunBudget.test.ts +20 -24
- package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +7 -30
- package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +225 -29
- package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +266 -25
- package/Tests/Server/Utils/AI/InvestigationThread.test.ts +580 -0
- package/Tests/Server/Utils/AI/InvestigationThreadService.test.ts +801 -0
- package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +525 -43
- package/Tests/Server/Utils/AI/KubectlJobRunner.test.ts +4 -2
- package/Tests/Server/Utils/AI/LLMServiceUnreachableProvider.test.ts +435 -0
- package/Tests/Server/Utils/AI/ObservabilityAssistantLimits.test.ts +462 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +14 -25
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +2 -0
- package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +297 -15
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +10 -6
- package/Tests/Server/Utils/AI/ToolOutputPager.test.ts +382 -0
- package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +23 -4
- package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +25 -6
- package/Tests/Server/Utils/AI/Toolbox/Serializer.test.ts +14 -3
- package/Tests/Server/Utils/EditionEnforcement.test.ts +107 -147
- package/Tests/Server/Utils/{CommunityEditionSsoReport.test.ts → RelaxedScimTeamLocksReport.test.ts} +212 -208
- package/Tests/Server/Utils/SameOriginRequest.test.ts +372 -0
- package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +20 -0
- package/Tests/Server/Utils/Telemetry/MessageQueueDiscovery.test.ts +2590 -0
- package/Tests/Server/Utils/Telemetry/MessageQueueSpanQueryTwin.ts +246 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +23 -0
- package/Tests/Types/AI/AIAgentRunLimits.test.ts +255 -0
- package/Tests/Types/Code/JsonSyntax.test.ts +1300 -0
- package/Tests/Types/CustomField/CustomFieldCreateSettings.test.ts +1113 -0
- package/Tests/Types/CustomField/CustomFieldMappingCatalog.test.ts +91 -0
- package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +30 -0
- package/Tests/Types/Incident/IncidentFormIpAllowlist.test.ts +227 -0
- package/Tests/Types/Incident/IncidentFormPublic.test.ts +1916 -0
- package/Tests/Types/MessageQueue/MessageQueueEntityKey.test.ts +916 -0
- package/Tests/Types/MessageQueue/MessageQueueIdentity.test.ts +567 -0
- package/Tests/Types/MessageQueue/MessageQueueManualIdentity.test.ts +686 -0
- package/Tests/Types/MessageQueue/MessageQueueMetricCatalog.test.ts +1726 -0
- package/Tests/Types/MessageQueue/MessageQueueMetricResolution.test.ts +1758 -0
- package/Tests/Types/MessageQueue/MessagingInstrumentationFixtures.test.ts +4686 -0
- package/Tests/Types/MessageQueue/MessagingSystem.test.ts +840 -0
- package/Tests/Types/MessageQueue/MessagingTelemetryFixtures.ts +3287 -0
- package/Tests/Types/MessageQueue/MessagingTelemetryResolver.test.ts +2966 -0
- package/Tests/Types/Monitor/MessageQueueAlertTemplates.test.ts +5401 -0
- package/Tests/Types/Monitor/MetricEngineFold.ts +154 -0
- package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +5 -3
- package/Tests/Types/Team/ComplianceRule.test.ts +588 -37
- package/Tests/Types/Team/TeamComplianceStatus.test.ts +64 -11
- package/Tests/Types/XML.test.ts +51 -0
- package/Tests/UI/Components/CodeEditor/CodeEditor.test.tsx +2362 -0
- package/Tests/UI/Components/CodeEditor/CodeEditorCommands.test.ts +810 -0
- package/Tests/UI/Components/CodeEditor/CodeEditorHighlight.test.ts +338 -0
- package/Tests/UI/Components/CodeEditor/CodeEditorLanguages.test.ts +222 -0
- package/Tests/UI/Components/CodeEditor/CodeEditorStyles.test.ts +807 -0
- package/Tests/UI/Components/CodeEditor/NoMonaco.test.ts +245 -0
- package/Tests/UI/Components/CodeEditor/YamlEditor.test.tsx +729 -0
- package/Tests/UI/Components/CopyTextButton.test.tsx +76 -0
- package/Tests/UI/Components/EditionLabelLapseCopy.ts +51 -14
- package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +131 -23
- package/Tests/UI/Components/ErrorMessageAction.test.tsx +142 -0
- package/Tests/UI/Components/FacetSidebarInScope.test.tsx +459 -0
- package/Tests/UI/Components/FacetValuesInScope.test.ts +270 -0
- package/Tests/UI/Components/Forms/FormFieldCodeEditors.test.tsx +294 -0
- package/Tests/UI/Components/Forms/FormFieldMarkdown.test.tsx +126 -0
- package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +22 -75
- package/Tests/UI/Components/Forms/ModelFormOnBeforeCreateMiscData.test.tsx +254 -0
- package/Tests/UI/Components/Icon.test.tsx +32 -0
- package/Tests/UI/Components/Input.test.tsx +31 -0
- package/Tests/UI/Components/LockedFilterChipScope.test.tsx +501 -0
- package/Tests/UI/Components/MarkdownConverters.test.ts +253 -1
- package/Tests/UI/Components/MarkdownEditor.test.tsx +2785 -2
- package/Tests/UI/Components/MarkdownEditorHistory.test.ts +243 -0
- package/Tests/UI/Components/MarkdownListEditing.test.ts +1118 -0
- package/Tests/UI/Components/MarkdownPaste.test.ts +1180 -0
- package/Tests/UI/Components/MarkdownViewerCopy.test.tsx +466 -0
- package/Tests/UI/Components/MarkdownVisualEditing.test.ts +1089 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableEmptyStateAction.test.tsx +623 -0
- package/Tests/UI/Components/ResetObjectID.test.tsx +282 -0
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/Table/EmptyTableMessage.test.ts +439 -0
- package/Tests/UI/Components/Table/TableEmptyState.test.tsx +352 -0
- package/Tests/UI/Components/TableRowMobileTitles.test.tsx +78 -0
- package/Tests/UI/Components/TextArea.test.tsx +19 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +12 -29
- package/Tests/UI/Components/fixtures/MarkdownPasteFixtures.ts +460 -0
- package/Tests/UI/EsbuildConfig.test.ts +115 -39
- package/Tests/UI/EsbuildEnterpriseAlias.test.ts +4 -4
- package/Tests/UI/Monitor/MonitorLogSummaryRender.test.tsx +377 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +81 -1
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +17 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
- package/Tests/Utils/Markdown/DashboardMarkdownRenderer.ts +81 -0
- package/Tests/Utils/Markdown/UntrustedMarkdown.test.ts +1339 -0
- package/Tests/Utils/Monitor/MonitorLogSummaryUtil.test.ts +862 -0
- package/Types/AI/AIAgentRunLimits.ts +146 -0
- package/Types/Code/CodeType.ts +2 -0
- package/Types/Code/JsonSyntax.ts +923 -0
- package/Types/CustomField/CustomFieldCreateSettings.ts +549 -0
- package/Types/CustomField/CustomFieldMappingCatalog.ts +77 -0
- package/Types/CustomField/CustomFieldValueValidator.ts +10 -7
- package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +1 -1
- package/Types/Icon/IconProp.ts +2 -0
- package/Types/Incident/IncidentFormIpAllowlist.ts +206 -0
- package/Types/Incident/IncidentFormPublic.ts +1145 -0
- package/Types/Kubernetes/KubernetesClusterAiAccess.ts +10 -3
- package/Types/MessageQueue/MessageQueueIdentity.ts +256 -0
- package/Types/MessageQueue/MessageQueueManualIdentity.ts +325 -0
- package/Types/MessageQueue/MessageQueueMetricCatalog.ts +1357 -0
- package/Types/MessageQueue/MessagingSystem.ts +518 -0
- package/Types/MessageQueue/MessagingTelemetryResolver.ts +2311 -0
- package/Types/Monitor/MessageQueueAlertTemplates.ts +2128 -0
- package/Types/Permission.ts +310 -0
- package/Types/ResourceAiAgent/ResourceAiAccess.ts +10 -4
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +12 -0
- package/Types/Rules/RuleRun.ts +4 -0
- package/Types/Team/ComplianceNotificationChannel.ts +4 -3
- package/Types/Team/ComplianceRule.ts +71 -18
- package/Types/Team/ComplianceRuleType.ts +2 -2
- package/Types/Team/TeamComplianceStatus.ts +6 -2
- package/Types/Telemetry/EntityType.ts +16 -0
- package/Types/Telemetry/LockedFilterDetail.ts +22 -0
- package/Types/XML.ts +26 -0
- package/UI/Components/CodeEditor/CodeEditor.css +379 -0
- package/UI/Components/CodeEditor/CodeEditor.tsx +1128 -294
- package/UI/Components/CodeEditor/CodeEditorCommands.ts +774 -0
- package/UI/Components/CodeEditor/CodeEditorHighlight.ts +106 -0
- package/UI/Components/CodeEditor/CodeEditorLanguages.ts +164 -0
- package/UI/Components/CodeEditor/YamlEditor.tsx +28 -326
- package/UI/Components/CopyTextButton/CopyTextButton.tsx +5 -1
- package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +6 -25
- package/UI/Components/EditionLabel/EditionLabel.tsx +14 -13
- package/UI/Components/ErrorMessage/ErrorMessage.tsx +11 -1
- package/UI/Components/Forms/Fields/FormField.tsx +8 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Icon/Icon.tsx +18 -0
- package/UI/Components/Input/Input.tsx +11 -4
- package/UI/Components/LogsViewer/LogsViewer.tsx +4 -0
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +21 -3
- package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +877 -79
- package/UI/Components/Markdown.tsx/MarkdownEditorHistory.ts +284 -0
- package/UI/Components/Markdown.tsx/MarkdownListEditing.ts +1362 -0
- package/UI/Components/Markdown.tsx/MarkdownPaste.ts +1326 -0
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +106 -10
- package/UI/Components/Markdown.tsx/MarkdownVisualEditing.ts +1032 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +85 -7
- package/UI/Components/ResetObjectID/ResetObjectID.tsx +35 -9
- package/UI/Components/Table/EmptyTableMessage.ts +81 -0
- package/UI/Components/Table/Table.tsx +50 -21
- package/UI/Components/Table/TableRow.tsx +9 -1
- package/UI/Components/TelemetryViewer/FacetVisibility.ts +97 -2
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +13 -0
- package/UI/Components/TelemetryViewer/components/LockedFilterChip.tsx +214 -7
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +21 -3
- package/UI/Components/TextArea/TextArea.tsx +9 -2
- package/UI/Components/Workflow/ModelFieldPicker.tsx +10 -12
- package/UI/Styles/Theme.css +8 -0
- package/UI/esbuild-config.js +2 -32
- package/Utils/LabelRuleImportExport.ts +2 -0
- package/Utils/Markdown/UntrustedMarkdown.ts +1513 -0
- package/Utils/Monitor/IncomingEmailMonitorRequestUtil.ts +41 -0
- package/Utils/Monitor/MonitorLogSummaryUtil.ts +212 -0
- package/Utils/Telemetry/EntityKey.ts +48 -0
- package/build/dist/Models/DatabaseModels/AIConversation.js +125 -1
- package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js +12 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CloudResource.js +12 -0
- package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseServer.js +12 -0
- package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +12 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +12 -0
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidc.js +0 -4
- package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +0 -4
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSso.js +0 -4
- package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +0 -4
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Host.js +12 -0
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentForm.js +605 -0
- package/build/dist/Models/DatabaseModels/IncidentForm.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js +299 -0
- package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +60 -0
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +14 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js +12 -0
- package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +12 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MessageQueue.js +1106 -0
- package/build/dist/Models/DatabaseModels/MessageQueue.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js +562 -0
- package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js +643 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js +506 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js +505 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/PodmanHost.js +12 -0
- package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +61 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectOidc.js +0 -4
- package/build/dist/Models/DatabaseModels/ProjectOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectSso.js +0 -4
- package/build/dist/Models/DatabaseModels/ProjectSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +12 -0
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +12 -0
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServerlessFunction.js +12 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunction.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +12 -0
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js +0 -4
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSso.js +0 -4
- package/build/dist/Models/DatabaseModels/StatusPageSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +49 -3
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/VMwareVCenter.js +12 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +9 -103
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +3 -3
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationConversationAPI.js +162 -0
- package/build/dist/Server/API/AIInvestigationConversationAPI.js.map +1 -0
- package/build/dist/Server/API/IncidentFormAPI.js +245 -0
- package/build/dist/Server/API/IncidentFormAPI.js.map +1 -0
- package/build/dist/Server/API/ProjectOIDC.js +16 -24
- package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
- package/build/dist/Server/API/ProjectSSO.js +16 -24
- package/build/dist/Server/API/ProjectSSO.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +42 -75
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Enterprise/EnterpriseEdition.js +79 -25
- package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -1
- package/build/dist/Server/Enterprise/EnterpriseFeature.js +24 -3
- package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -1
- package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +1 -1
- package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js +63 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js +41 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js +183 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/IncidentFormRateLimit.js +559 -0
- package/build/dist/Server/Middleware/IncidentFormRateLimit.js.map +1 -0
- package/build/dist/Server/Middleware/UserAuthorization.js +11 -30
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +10 -13
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/IncidentCustomFieldService.js +83 -0
- package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeMemberService.js +8 -4
- package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js +10 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/IncidentFormService.js +1202 -0
- package/build/dist/Server/Services/IncidentFormService.js.map +1 -0
- package/build/dist/Server/Services/IncidentFormSubmissionService.js +91 -0
- package/build/dist/Server/Services/IncidentFormSubmissionService.js.map +1 -0
- package/build/dist/Server/Services/IncidentGroupingEngineService.js +20 -8
- package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +106 -4
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentTemplateService.js +18 -0
- package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +14 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js +262 -0
- package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueLabelRuleService.js +13 -0
- package/build/dist/Server/Services/MessageQueueLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js +334 -0
- package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/MessageQueueOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueOwnerTeamService.js +62 -0
- package/build/dist/Server/Services/MessageQueueOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueOwnerUserService.js +62 -0
- package/build/dist/Server/Services/MessageQueueOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueService.js +1140 -0
- package/build/dist/Server/Services/MessageQueueService.js.map +1 -0
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +25 -4
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +0 -54
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -4
- package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +0 -46
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TeamComplianceSettingService.js +120 -31
- package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +8 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +12 -3
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +6 -3
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +109 -37
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +16 -0
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js +3 -3
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +9 -6
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js +128 -0
- package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +192 -50
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js +133 -0
- package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +53 -13
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
- package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js +206 -0
- package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js.map +1 -0
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +53 -26
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +4 -2
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +1 -29
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +24 -24
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +7 -2
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +29 -14
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +38 -14
- package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -1
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +2 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +40 -10
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +19 -7
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +19 -7
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +15 -18
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js +412 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js +495 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +10 -3
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
- package/build/dist/Server/Utils/EditionEnforcement.js +20 -158
- package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +193 -54
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js +160 -0
- package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js.map +1 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/SameOriginRequest.js +151 -0
- package/build/dist/Server/Utils/SameOriginRequest.js.map +1 -0
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +4 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js +1015 -0
- package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +4 -1
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
- package/build/dist/Server/Utils/VendorAssets.js +3 -3
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +3 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +3 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +10 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/AI/AIAgentRunLimits.js +91 -0
- package/build/dist/Types/AI/AIAgentRunLimits.js.map +1 -0
- package/build/dist/Types/Code/CodeType.js +2 -0
- package/build/dist/Types/Code/CodeType.js.map +1 -1
- package/build/dist/Types/Code/JsonSyntax.js +604 -0
- package/build/dist/Types/Code/JsonSyntax.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldCreateSettings.js +310 -0
- package/build/dist/Types/CustomField/CustomFieldCreateSettings.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js +38 -0
- package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js +10 -7
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -1
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +1 -1
- package/build/dist/Types/Icon/IconProp.js +2 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/Incident/IncidentFormIpAllowlist.js +119 -0
- package/build/dist/Types/Incident/IncidentFormIpAllowlist.js.map +1 -0
- package/build/dist/Types/Incident/IncidentFormPublic.js +678 -0
- package/build/dist/Types/Incident/IncidentFormPublic.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +10 -3
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
- package/build/dist/Types/MessageQueue/MessageQueueIdentity.js +184 -0
- package/build/dist/Types/MessageQueue/MessageQueueIdentity.js.map +1 -0
- package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js +190 -0
- package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js.map +1 -0
- package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js +1057 -0
- package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js.map +1 -0
- package/build/dist/Types/MessageQueue/MessagingSystem.js +394 -0
- package/build/dist/Types/MessageQueue/MessagingSystem.js.map +1 -0
- package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js +1768 -0
- package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js.map +1 -0
- package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js +1207 -0
- package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js.map +1 -0
- package/build/dist/Types/Permission.js +274 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +10 -4
- package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +12 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/Types/Team/ComplianceNotificationChannel.js +4 -3
- package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -1
- package/build/dist/Types/Team/ComplianceRule.js +48 -14
- package/build/dist/Types/Team/ComplianceRule.js.map +1 -1
- package/build/dist/Types/Team/ComplianceRuleType.js +2 -2
- package/build/dist/Types/Telemetry/EntityType.js +16 -0
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/XML.js +23 -0
- package/build/dist/Types/XML.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +641 -266
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js +464 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js.map +1 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js +69 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js.map +1 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js +108 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js.map +1 -0
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js +14 -186
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -1
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js +5 -1
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +5 -14
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +14 -13
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js +1 -1
- package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +8 -3
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +8 -0
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +11 -3
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +2 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +14 -4
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +710 -65
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js +238 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js +1028 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js +1088 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +70 -10
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js +887 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +57 -6
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js +7 -5
- package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js.map +1 -1
- package/build/dist/UI/Components/Table/EmptyTableMessage.js +51 -0
- package/build/dist/UI/Components/Table/EmptyTableMessage.js.map +1 -0
- package/build/dist/UI/Components/Table/Table.js +30 -11
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +9 -2
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js +63 -1
- package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +4 -2
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js +91 -10
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +14 -4
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js +9 -3
- package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ModelFieldPicker.js +1 -2
- package/build/dist/UI/Components/Workflow/ModelFieldPicker.js.map +1 -1
- package/build/dist/Utils/LabelRuleImportExport.js +2 -0
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/Markdown/UntrustedMarkdown.js +988 -0
- package/build/dist/Utils/Markdown/UntrustedMarkdown.js.map +1 -0
- package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js +34 -0
- package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js +148 -0
- package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js.map +1 -0
- package/build/dist/Utils/Telemetry/EntityKey.js +36 -0
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- package/package.json +1 -3
- package/Server/Utils/CommunityEditionSsoReport.ts +0 -361
- package/Tests/App/Accounts/LoginSsoEdition.test.tsx +0 -616
- package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +0 -217
- package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +0 -271
- package/Tests/App/Dashboard/CloudDocumentationMarkdown.test.ts +0 -215
- package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +0 -784
- package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +0 -518
- package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +0 -419
- package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +0 -769
- package/Tests/UI/Components/CodeEditor.test.tsx +0 -638
- package/Tests/UI/Components/MonacoLoader.test.ts +0 -67
- package/Tests/UI/Components/MonacoRuntime.test.ts +0 -121
- package/Tests/UI/Components/Telemetry/TelemetryRetentionConfigForm.test.tsx +0 -329
- package/Tests/UI/Components/YamlEditor.test.tsx +0 -847
- package/UI/Components/CodeEditor/MonacoLoader.ts +0 -36
- package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +0 -319
- package/UI/Components/Telemetry/TelemetryRetentionConfigSummary.tsx +0 -223
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js +0 -223
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +0 -1
- package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +0 -34
- package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +0 -1
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js +0 -171
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +0 -1
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js +0 -119
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js.map +0 -1
|
@@ -5,6 +5,7 @@ import TeamComplianceSettingService, {
|
|
|
5
5
|
OPTIONS_DIFFER_MESSAGE,
|
|
6
6
|
SEVERITIES_DELETED_ENABLE_MESSAGE,
|
|
7
7
|
SEVERITIES_DELETED_OPTION,
|
|
8
|
+
StoredChannelColumns,
|
|
8
9
|
TeamComplianceSettingService as TeamComplianceSettingServiceClass,
|
|
9
10
|
} from "../../../Server/Services/TeamComplianceSettingService";
|
|
10
11
|
import EnterpriseEdition from "../../../Server/Enterprise/EnterpriseEdition";
|
|
@@ -42,24 +43,35 @@ import { FindOperator } from "typeorm";
|
|
|
42
43
|
/*
|
|
43
44
|
* TeamComplianceSettingService guards what a team compliance rule may say.
|
|
44
45
|
*
|
|
45
|
-
* A rule is a type (ComplianceRuleType), optionally
|
|
46
|
-
*
|
|
47
|
-
* "
|
|
46
|
+
* A rule is a type (ComplianceRuleType), optionally channels ("Call", or
|
|
47
|
+
* "Call and Push" - a member needs a rule on each) and a severity scope
|
|
48
|
+
* ("Critical"). One team may hold several rules of one type - "Call for
|
|
49
|
+
* Critical incidents" and "Push for Critical incidents" are both
|
|
48
50
|
* HasIncidentOnCallRules - so there is no unique index any more; the service
|
|
49
|
-
* refuses EXACT duplicates instead (same type, channel and severity set).
|
|
51
|
+
* refuses EXACT duplicates instead (same type, channel set and severity set).
|
|
52
|
+
*
|
|
53
|
+
* The channels are stored twice: as the list in notificationChannels, and -
|
|
54
|
+
* for API clients and builds from before the list - as the list's first
|
|
55
|
+
* channel in notificationChannel. A payload may send either; the hooks write
|
|
56
|
+
* both, and a stored row is read by getStoredChannels, which trusts the list
|
|
57
|
+
* only while the single column still agrees with it.
|
|
50
58
|
*
|
|
51
59
|
* These tests pin, against an in-memory team of stored rules and project
|
|
52
60
|
* severities:
|
|
53
61
|
*
|
|
54
|
-
* - create refuses unknown rule types and channels
|
|
55
|
-
* project or team, before reading anything;
|
|
62
|
+
* - create refuses unknown rule types and channels (in a list or on their
|
|
63
|
+
* own), and anything without a project or team, before reading anything;
|
|
64
|
+
* - a channel list is stored canonical - catalog order, each channel once -
|
|
65
|
+
* with its first channel beside it, and a single-channel payload from an
|
|
66
|
+
* older client is stored as a one-item list;
|
|
56
67
|
* - options a rule type does not use are cleared, per kind, so nothing is
|
|
57
68
|
* stored that looks like part of a rule without being checked;
|
|
58
69
|
* - selected severities must exist in the rule's own project, of the rule's
|
|
59
70
|
* own kind - checked in one count, as root, and never for a stray option;
|
|
60
71
|
* - duplicates are judged on content (order, repeats and id case do not
|
|
61
|
-
* matter), legacy "no channel, every
|
|
62
|
-
*
|
|
72
|
+
* matter, for channels as for severities), legacy "no channel, every
|
|
73
|
+
* severity" rows and rows an older build wrote included, while a different
|
|
74
|
+
* channel set, severity set, rule type or team is a different rule;
|
|
63
75
|
* - an update that only switches a rule off reads nothing at all; one that
|
|
64
76
|
* switches it on reads only whether a severity delete left it with
|
|
65
77
|
* nothing to check, and refuses that;
|
|
@@ -69,8 +81,9 @@ import { FindOperator } from "typeorm";
|
|
|
69
81
|
* siblings excluding the row itself, reads only inside the caller's
|
|
70
82
|
* project, and a rule type change clears the options the new type does not
|
|
71
83
|
* use, including ones already stored on the row;
|
|
72
|
-
* - the static
|
|
73
|
-
* isSameScope)
|
|
84
|
+
* - the static helpers the ee server can reuse (getIds, getScope,
|
|
85
|
+
* isSameScope, getStoredChannels) and the channel ones behind the hooks
|
|
86
|
+
* (resolveSentChannels, normaliseChannelFields).
|
|
74
87
|
*
|
|
75
88
|
* The protected hooks are called directly, as the other service tests do; a
|
|
76
89
|
* last block drives updateOneById - the path the API takes - to show that what
|
|
@@ -113,6 +126,9 @@ const UNKNOWN_CHANNEL_MESSAGE: (value: string) => string = (
|
|
|
113
126
|
return `"${value}" is not a notification channel a compliance rule can require.`;
|
|
114
127
|
};
|
|
115
128
|
|
|
129
|
+
const NOT_A_CHANNEL_LIST_MESSAGE: string =
|
|
130
|
+
"The notification channels of a compliance rule must be a list of channels.";
|
|
131
|
+
|
|
116
132
|
const UNKNOWN_RULE_TYPE_MESSAGE: (value: string) => string = (
|
|
117
133
|
value: string,
|
|
118
134
|
): string => {
|
|
@@ -314,6 +330,14 @@ interface StoredRuleInput {
|
|
|
314
330
|
ruleType: string;
|
|
315
331
|
teamId?: ObjectID | undefined;
|
|
316
332
|
projectId?: ObjectID | undefined;
|
|
333
|
+
/*
|
|
334
|
+
* The channel columns as the row holds them. A row a current build wrote
|
|
335
|
+
* has the list, and its first channel in notificationChannel - give only
|
|
336
|
+
* `notificationChannels` for that. A row an older build wrote (or one from
|
|
337
|
+
* before the list) has notificationChannel alone and the list NULL - give
|
|
338
|
+
* only `notificationChannel`. Give both to store a pair that disagrees.
|
|
339
|
+
*/
|
|
340
|
+
notificationChannels?: Array<string> | null | undefined;
|
|
317
341
|
notificationChannel?: string | null | undefined;
|
|
318
342
|
incidentSeverityIds?: Array<string> | undefined;
|
|
319
343
|
alertSeverityIds?: Array<string> | undefined;
|
|
@@ -341,8 +365,14 @@ const storedRule: (input: StoredRuleInput) => TeamComplianceSetting = (
|
|
|
341
365
|
}
|
|
342
366
|
|
|
343
367
|
(setting as unknown as Record<string, unknown>)["ruleType"] = input.ruleType;
|
|
368
|
+
(setting as unknown as Record<string, unknown>)["notificationChannels"] =
|
|
369
|
+
input.notificationChannels === undefined
|
|
370
|
+
? null
|
|
371
|
+
: input.notificationChannels;
|
|
344
372
|
(setting as unknown as Record<string, unknown>)["notificationChannel"] =
|
|
345
|
-
input.notificationChannel
|
|
373
|
+
input.notificationChannel !== undefined
|
|
374
|
+
? input.notificationChannel
|
|
375
|
+
: input.notificationChannels?.[0] || null;
|
|
346
376
|
setting.enabled = input.enabled === undefined ? true : input.enabled;
|
|
347
377
|
|
|
348
378
|
if (input.options !== undefined) {
|
|
@@ -378,6 +408,8 @@ const store: (...rows: Array<StoredRuleInput>) => void = (
|
|
|
378
408
|
|
|
379
409
|
interface NewRuleInput {
|
|
380
410
|
ruleType?: unknown;
|
|
411
|
+
notificationChannels?: unknown;
|
|
412
|
+
// The single channel of a client from before the list.
|
|
381
413
|
notificationChannel?: unknown;
|
|
382
414
|
incidentSeverities?: unknown;
|
|
383
415
|
alertSeverities?: unknown;
|
|
@@ -405,6 +437,7 @@ const newRule: (input: NewRuleInput) => TeamComplianceSetting = (
|
|
|
405
437
|
|
|
406
438
|
for (const key of [
|
|
407
439
|
"ruleType",
|
|
440
|
+
"notificationChannels",
|
|
408
441
|
"notificationChannel",
|
|
409
442
|
"incidentSeverities",
|
|
410
443
|
"alertSeverities",
|
|
@@ -613,6 +646,28 @@ const modelValue: (setting: TeamComplianceSetting, key: string) => unknown = (
|
|
|
613
646
|
return (setting as unknown as Record<string, unknown>)[key];
|
|
614
647
|
};
|
|
615
648
|
|
|
649
|
+
// The two channel columns a create or an update is about to write.
|
|
650
|
+
interface ChannelPair {
|
|
651
|
+
notificationChannels: unknown;
|
|
652
|
+
notificationChannel: unknown;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const channelsOf: (
|
|
656
|
+
data: TeamComplianceSetting | Record<string, unknown>,
|
|
657
|
+
) => ChannelPair = (
|
|
658
|
+
data: TeamComplianceSetting | Record<string, unknown>,
|
|
659
|
+
): ChannelPair => {
|
|
660
|
+
const values: Record<string, unknown> = data as unknown as Record<
|
|
661
|
+
string,
|
|
662
|
+
unknown
|
|
663
|
+
>;
|
|
664
|
+
|
|
665
|
+
return {
|
|
666
|
+
notificationChannels: values["notificationChannels"],
|
|
667
|
+
notificationChannel: values["notificationChannel"],
|
|
668
|
+
};
|
|
669
|
+
};
|
|
670
|
+
|
|
616
671
|
/*
|
|
617
672
|
* A severity list as the hooks leave it for the relation save: models of the
|
|
618
673
|
* list's own kind, each carrying nothing but its id. Returns those ids.
|
|
@@ -706,47 +761,69 @@ describe("TeamComplianceSettingService onBeforeCreate - what may be created", ()
|
|
|
706
761
|
});
|
|
707
762
|
|
|
708
763
|
test.each(ALL_CHANNELS)(
|
|
709
|
-
"accepts the %s channel on every on-call rule type and keeps it",
|
|
764
|
+
"accepts the %s channel on every on-call rule type and keeps it, with the channel beside the list",
|
|
710
765
|
async (channel: ComplianceNotificationChannel) => {
|
|
711
766
|
for (const ruleType of ON_CALL_RULE_TYPES) {
|
|
712
767
|
const result: OnCreate<TeamComplianceSetting> = await create({
|
|
713
768
|
ruleType: ruleType,
|
|
714
|
-
|
|
769
|
+
notificationChannels: [channel],
|
|
715
770
|
});
|
|
716
771
|
|
|
717
|
-
expect({
|
|
772
|
+
expect({ ruleType, ...channelsOf(result.createBy.data) }).toEqual({
|
|
718
773
|
ruleType,
|
|
719
|
-
|
|
720
|
-
|
|
774
|
+
notificationChannels: [channel],
|
|
775
|
+
notificationChannel: channel,
|
|
776
|
+
});
|
|
721
777
|
}
|
|
722
778
|
},
|
|
723
779
|
);
|
|
724
780
|
|
|
725
781
|
test.each([
|
|
726
|
-
["
|
|
782
|
+
["an empty list", []],
|
|
727
783
|
["null", null],
|
|
728
784
|
])(
|
|
729
|
-
"
|
|
730
|
-
async (_label: string,
|
|
785
|
+
"channels sent as %s are 'any channel': valid, and written as an empty list with no channel beside it",
|
|
786
|
+
async (_label: string, channels: unknown) => {
|
|
731
787
|
const result: OnCreate<TeamComplianceSetting> = await create({
|
|
732
788
|
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
733
|
-
|
|
789
|
+
notificationChannels: channels,
|
|
734
790
|
});
|
|
735
791
|
|
|
736
|
-
expect(
|
|
737
|
-
|
|
738
|
-
|
|
792
|
+
expect(channelsOf(result.createBy.data)).toEqual({
|
|
793
|
+
notificationChannels: [],
|
|
794
|
+
notificationChannel: null,
|
|
795
|
+
});
|
|
739
796
|
},
|
|
740
797
|
);
|
|
741
798
|
|
|
799
|
+
test("channels not sent are 'any channel' too, and stay unsent", async () => {
|
|
800
|
+
const result: OnCreate<TeamComplianceSetting> = await create({
|
|
801
|
+
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
802
|
+
notificationChannels: undefined,
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
expect(channelsOf(result.createBy.data)).toEqual({
|
|
806
|
+
notificationChannels: undefined,
|
|
807
|
+
notificationChannel: undefined,
|
|
808
|
+
});
|
|
809
|
+
});
|
|
810
|
+
|
|
742
811
|
test.each([
|
|
743
812
|
["a channel that does not exist", "Pager"],
|
|
744
813
|
["a channel in the wrong case", "call"],
|
|
745
814
|
["an empty string", ""],
|
|
746
815
|
["a method rule type", "HasNotificationCallMethod"],
|
|
816
|
+
["a label rather than a value", "Push notification"],
|
|
747
817
|
])(
|
|
748
|
-
"refuses %s as
|
|
818
|
+
"refuses %s as a channel, before reading anything - in a list, and on its own",
|
|
749
819
|
async (_label: string, channel: string) => {
|
|
820
|
+
await expect(
|
|
821
|
+
create({
|
|
822
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
823
|
+
notificationChannels: [ComplianceNotificationChannel.Call, channel],
|
|
824
|
+
}),
|
|
825
|
+
).rejects.toThrow(new BadDataException(UNKNOWN_CHANNEL_MESSAGE(channel)));
|
|
826
|
+
|
|
750
827
|
await expect(
|
|
751
828
|
create({
|
|
752
829
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
@@ -758,7 +835,32 @@ describe("TeamComplianceSettingService onBeforeCreate - what may be created", ()
|
|
|
758
835
|
},
|
|
759
836
|
);
|
|
760
837
|
|
|
838
|
+
test.each([
|
|
839
|
+
["one channel as a bare string", ComplianceNotificationChannel.Call],
|
|
840
|
+
["an object", { channel: ComplianceNotificationChannel.Call }],
|
|
841
|
+
["a number", 3],
|
|
842
|
+
])(
|
|
843
|
+
"refuses channels sent as %s rather than a list, before reading anything",
|
|
844
|
+
async (_label: string, channels: unknown) => {
|
|
845
|
+
await expect(
|
|
846
|
+
create({
|
|
847
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
848
|
+
notificationChannels: channels,
|
|
849
|
+
}),
|
|
850
|
+
).rejects.toThrow(new BadDataException(NOT_A_CHANNEL_LIST_MESSAGE));
|
|
851
|
+
|
|
852
|
+
expectNoReads();
|
|
853
|
+
},
|
|
854
|
+
);
|
|
855
|
+
|
|
761
856
|
test("refuses a channel that is not a channel even on a method rule - garbage is refused, not quietly dropped", async () => {
|
|
857
|
+
await expect(
|
|
858
|
+
create({
|
|
859
|
+
ruleType: ComplianceRuleType.HasNotificationEmailMethod,
|
|
860
|
+
notificationChannels: ["Pager"],
|
|
861
|
+
}),
|
|
862
|
+
).rejects.toThrow(UNKNOWN_CHANNEL_MESSAGE("Pager"));
|
|
863
|
+
|
|
762
864
|
await expect(
|
|
763
865
|
create({
|
|
764
866
|
ruleType: ComplianceRuleType.HasNotificationEmailMethod,
|
|
@@ -845,32 +947,52 @@ describe("TeamComplianceSettingService onBeforeCreate - options a rule type does
|
|
|
845
947
|
async (ruleType: ComplianceRuleType) => {
|
|
846
948
|
const result: OnCreate<TeamComplianceSetting> = await create({
|
|
847
949
|
ruleType: ruleType,
|
|
848
|
-
|
|
950
|
+
notificationChannels: [
|
|
951
|
+
ComplianceNotificationChannel.Call,
|
|
952
|
+
ComplianceNotificationChannel.Push,
|
|
953
|
+
],
|
|
849
954
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
850
955
|
alertSeverities: [CRITICAL_ALERT],
|
|
851
956
|
});
|
|
852
957
|
|
|
853
|
-
expect(
|
|
854
|
-
|
|
855
|
-
|
|
958
|
+
expect(channelsOf(result.createBy.data)).toEqual({
|
|
959
|
+
notificationChannels: [],
|
|
960
|
+
notificationChannel: null,
|
|
961
|
+
});
|
|
856
962
|
expect(result.createBy.data.incidentSeverities).toEqual([]);
|
|
857
963
|
expect(result.createBy.data.alertSeverities).toEqual([]);
|
|
858
964
|
},
|
|
859
965
|
);
|
|
860
966
|
|
|
967
|
+
test.each(METHOD_RULE_TYPES)(
|
|
968
|
+
"%s keeps no channel sent the old way either",
|
|
969
|
+
async (ruleType: ComplianceRuleType) => {
|
|
970
|
+
const result: OnCreate<TeamComplianceSetting> = await create({
|
|
971
|
+
ruleType: ruleType,
|
|
972
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
expect(channelsOf(result.createBy.data)).toEqual({
|
|
976
|
+
notificationChannels: [],
|
|
977
|
+
notificationChannel: null,
|
|
978
|
+
});
|
|
979
|
+
},
|
|
980
|
+
);
|
|
981
|
+
|
|
861
982
|
test.each(INCIDENT_RULE_TYPES)(
|
|
862
983
|
"%s keeps its channel and incident severities, and drops alert severities",
|
|
863
984
|
async (ruleType: ComplianceRuleType) => {
|
|
864
985
|
const result: OnCreate<TeamComplianceSetting> = await create({
|
|
865
986
|
ruleType: ruleType,
|
|
866
|
-
|
|
987
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
867
988
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
868
989
|
alertSeverities: [CRITICAL_ALERT],
|
|
869
990
|
});
|
|
870
991
|
|
|
871
|
-
expect(result.createBy.data
|
|
872
|
-
ComplianceNotificationChannel.Push,
|
|
873
|
-
|
|
992
|
+
expect(channelsOf(result.createBy.data)).toEqual({
|
|
993
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
994
|
+
notificationChannel: ComplianceNotificationChannel.Push,
|
|
995
|
+
});
|
|
874
996
|
expect(
|
|
875
997
|
writtenIds(
|
|
876
998
|
modelValue(result.createBy.data, "incidentSeverities"),
|
|
@@ -886,14 +1008,15 @@ describe("TeamComplianceSettingService onBeforeCreate - options a rule type does
|
|
|
886
1008
|
async (ruleType: ComplianceRuleType) => {
|
|
887
1009
|
const result: OnCreate<TeamComplianceSetting> = await create({
|
|
888
1010
|
ruleType: ruleType,
|
|
889
|
-
|
|
1011
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
890
1012
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
891
1013
|
alertSeverities: [CRITICAL_ALERT],
|
|
892
1014
|
});
|
|
893
1015
|
|
|
894
|
-
expect(result.createBy.data
|
|
895
|
-
ComplianceNotificationChannel.SMS,
|
|
896
|
-
|
|
1016
|
+
expect(channelsOf(result.createBy.data)).toEqual({
|
|
1017
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
1018
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
1019
|
+
});
|
|
897
1020
|
expect(
|
|
898
1021
|
writtenIds(
|
|
899
1022
|
modelValue(result.createBy.data, "alertSeverities"),
|
|
@@ -933,6 +1056,7 @@ describe("TeamComplianceSettingService onBeforeCreate - options a rule type does
|
|
|
933
1056
|
ruleType: ComplianceRuleType.HasNotificationEmailMethod,
|
|
934
1057
|
});
|
|
935
1058
|
|
|
1059
|
+
expect(result.createBy.data.notificationChannels).toBeUndefined();
|
|
936
1060
|
expect(result.createBy.data.notificationChannel).toBeUndefined();
|
|
937
1061
|
expect(result.createBy.data.incidentSeverities).toBeUndefined();
|
|
938
1062
|
expect(result.createBy.data.alertSeverities).toBeUndefined();
|
|
@@ -1099,7 +1223,7 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1099
1223
|
test("the duplicate lookup reads the team's rules of that type in the project, as root", async () => {
|
|
1100
1224
|
await create({
|
|
1101
1225
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1102
|
-
|
|
1226
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1103
1227
|
});
|
|
1104
1228
|
|
|
1105
1229
|
expect(settingsFindBy).toHaveBeenCalledTimes(1);
|
|
@@ -1113,6 +1237,7 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1113
1237
|
expect(findBy["select"]).toEqual({
|
|
1114
1238
|
_id: true,
|
|
1115
1239
|
ruleType: true,
|
|
1240
|
+
notificationChannels: true,
|
|
1116
1241
|
notificationChannel: true,
|
|
1117
1242
|
// A rule a severity delete emptied is no duplicate: see below.
|
|
1118
1243
|
options: true,
|
|
@@ -1137,12 +1262,12 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1137
1262
|
{
|
|
1138
1263
|
id: SIBLING_SETTING_ID,
|
|
1139
1264
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1140
|
-
|
|
1265
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1141
1266
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
1142
1267
|
},
|
|
1143
1268
|
{
|
|
1144
1269
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1145
|
-
|
|
1270
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1146
1271
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
1147
1272
|
},
|
|
1148
1273
|
],
|
|
@@ -1151,12 +1276,12 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1151
1276
|
{
|
|
1152
1277
|
id: SIBLING_SETTING_ID,
|
|
1153
1278
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1154
|
-
|
|
1279
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1155
1280
|
incidentSeverityIds: [MAJOR_INCIDENT, CRITICAL_INCIDENT],
|
|
1156
1281
|
},
|
|
1157
1282
|
{
|
|
1158
1283
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1159
|
-
|
|
1284
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1160
1285
|
incidentSeverities: [
|
|
1161
1286
|
{ _id: CRITICAL_INCIDENT },
|
|
1162
1287
|
{ _id: MAJOR_INCIDENT },
|
|
@@ -1169,12 +1294,12 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1169
1294
|
{
|
|
1170
1295
|
id: SIBLING_SETTING_ID,
|
|
1171
1296
|
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
1172
|
-
|
|
1297
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
1173
1298
|
alertSeverityIds: [CRITICAL_ALERT],
|
|
1174
1299
|
},
|
|
1175
1300
|
{
|
|
1176
1301
|
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
1177
|
-
|
|
1302
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
1178
1303
|
alertSeverities: [CRITICAL_ALERT.toUpperCase()],
|
|
1179
1304
|
},
|
|
1180
1305
|
],
|
|
@@ -1196,7 +1321,7 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1196
1321
|
},
|
|
1197
1322
|
{
|
|
1198
1323
|
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
1199
|
-
|
|
1324
|
+
notificationChannels: [],
|
|
1200
1325
|
alertSeverities: [],
|
|
1201
1326
|
},
|
|
1202
1327
|
],
|
|
@@ -1218,7 +1343,7 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1218
1343
|
},
|
|
1219
1344
|
{
|
|
1220
1345
|
ruleType: ComplianceRuleType.HasNotificationCallMethod,
|
|
1221
|
-
|
|
1346
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1222
1347
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
1223
1348
|
alertSeverities: [CRITICAL_ALERT],
|
|
1224
1349
|
},
|
|
@@ -1250,12 +1375,12 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1250
1375
|
{
|
|
1251
1376
|
id: SIBLING_SETTING_ID,
|
|
1252
1377
|
ruleType: ComplianceRuleType.HasAlertEpisodeOnCallRules,
|
|
1253
|
-
|
|
1378
|
+
notificationChannels: [ComplianceNotificationChannel.Webhook],
|
|
1254
1379
|
alertSeverityIds: [MAJOR_ALERT, CRITICAL_ALERT],
|
|
1255
1380
|
},
|
|
1256
1381
|
{
|
|
1257
1382
|
ruleType: ComplianceRuleType.HasAlertEpisodeOnCallRules,
|
|
1258
|
-
|
|
1383
|
+
notificationChannels: [ComplianceNotificationChannel.Webhook],
|
|
1259
1384
|
alertSeverities: [CRITICAL_ALERT, MAJOR_ALERT],
|
|
1260
1385
|
},
|
|
1261
1386
|
],
|
|
@@ -1280,12 +1405,12 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1280
1405
|
{
|
|
1281
1406
|
id: SIBLING_SETTING_ID,
|
|
1282
1407
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1283
|
-
|
|
1408
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1284
1409
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
1285
1410
|
},
|
|
1286
1411
|
{
|
|
1287
1412
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1288
|
-
|
|
1413
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
1289
1414
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
1290
1415
|
},
|
|
1291
1416
|
],
|
|
@@ -1294,12 +1419,12 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1294
1419
|
{
|
|
1295
1420
|
id: SIBLING_SETTING_ID,
|
|
1296
1421
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1297
|
-
|
|
1422
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1298
1423
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
1299
1424
|
},
|
|
1300
1425
|
{
|
|
1301
1426
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1302
|
-
|
|
1427
|
+
notificationChannels: [],
|
|
1303
1428
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
1304
1429
|
},
|
|
1305
1430
|
],
|
|
@@ -1311,7 +1436,7 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1311
1436
|
},
|
|
1312
1437
|
{
|
|
1313
1438
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1314
|
-
|
|
1439
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1315
1440
|
},
|
|
1316
1441
|
],
|
|
1317
1442
|
[
|
|
@@ -1319,12 +1444,12 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1319
1444
|
{
|
|
1320
1445
|
id: SIBLING_SETTING_ID,
|
|
1321
1446
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1322
|
-
|
|
1447
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1323
1448
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
1324
1449
|
},
|
|
1325
1450
|
{
|
|
1326
1451
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1327
|
-
|
|
1452
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1328
1453
|
incidentSeverities: [CRITICAL_INCIDENT, MAJOR_INCIDENT],
|
|
1329
1454
|
},
|
|
1330
1455
|
],
|
|
@@ -1333,12 +1458,12 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1333
1458
|
{
|
|
1334
1459
|
id: SIBLING_SETTING_ID,
|
|
1335
1460
|
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
1336
|
-
|
|
1461
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
1337
1462
|
alertSeverityIds: [CRITICAL_ALERT],
|
|
1338
1463
|
},
|
|
1339
1464
|
{
|
|
1340
1465
|
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
1341
|
-
|
|
1466
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
1342
1467
|
},
|
|
1343
1468
|
],
|
|
1344
1469
|
[
|
|
@@ -1346,12 +1471,12 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1346
1471
|
{
|
|
1347
1472
|
id: SIBLING_SETTING_ID,
|
|
1348
1473
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1349
|
-
|
|
1474
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1350
1475
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
1351
1476
|
},
|
|
1352
1477
|
{
|
|
1353
1478
|
ruleType: ComplianceRuleType.HasIncidentEpisodeOnCallRules,
|
|
1354
|
-
|
|
1479
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1355
1480
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
1356
1481
|
},
|
|
1357
1482
|
],
|
|
@@ -1406,13 +1531,13 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1406
1531
|
{
|
|
1407
1532
|
id: "66666666-6666-4666-8666-00000000000a",
|
|
1408
1533
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1409
|
-
|
|
1534
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1410
1535
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
1411
1536
|
},
|
|
1412
1537
|
{
|
|
1413
1538
|
id: "66666666-6666-4666-8666-00000000000b",
|
|
1414
1539
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1415
|
-
|
|
1540
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
1416
1541
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
1417
1542
|
},
|
|
1418
1543
|
{
|
|
@@ -1424,7 +1549,7 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1424
1549
|
await expect(
|
|
1425
1550
|
create({
|
|
1426
1551
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1427
|
-
|
|
1552
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
1428
1553
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
1429
1554
|
}),
|
|
1430
1555
|
).resolves.toBeDefined();
|
|
@@ -1432,7 +1557,7 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1432
1557
|
await expect(
|
|
1433
1558
|
create({
|
|
1434
1559
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1435
|
-
|
|
1560
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
1436
1561
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
1437
1562
|
}),
|
|
1438
1563
|
).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
|
|
@@ -1448,7 +1573,7 @@ describe("TeamComplianceSettingService onBeforeCreate - exact duplicates are ref
|
|
|
1448
1573
|
const INCIDENT_CALL_CRITICAL: StoredRuleInput = {
|
|
1449
1574
|
id: SETTING_ID,
|
|
1450
1575
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1451
|
-
|
|
1576
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1452
1577
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
1453
1578
|
};
|
|
1454
1579
|
|
|
@@ -1459,7 +1584,7 @@ const INCIDENT_CALL_CRITICAL: StoredRuleInput = {
|
|
|
1459
1584
|
const EMPTIED_BY_DELETE: StoredRuleInput = {
|
|
1460
1585
|
id: SETTING_ID,
|
|
1461
1586
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1462
|
-
|
|
1587
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1463
1588
|
enabled: false,
|
|
1464
1589
|
options: { [SEVERITIES_DELETED_OPTION]: true },
|
|
1465
1590
|
};
|
|
@@ -1531,7 +1656,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - turning a rule on", () =
|
|
|
1531
1656
|
store(INCIDENT_CALL_CRITICAL, {
|
|
1532
1657
|
id: SIBLING_SETTING_ID,
|
|
1533
1658
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1534
|
-
|
|
1659
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1535
1660
|
enabled: false,
|
|
1536
1661
|
});
|
|
1537
1662
|
|
|
@@ -1643,7 +1768,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - turning a rule on", () =
|
|
|
1643
1768
|
store({
|
|
1644
1769
|
id: SETTING_ID,
|
|
1645
1770
|
ruleType: ComplianceRuleType.HasAlertEpisodeOnCallRules,
|
|
1646
|
-
|
|
1771
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
1647
1772
|
enabled: false,
|
|
1648
1773
|
options: { [SEVERITIES_DELETED_OPTION]: true },
|
|
1649
1774
|
// A stray severity of the other kind is not a scope.
|
|
@@ -1662,7 +1787,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - re-scoping a rule a seve
|
|
|
1662
1787
|
|
|
1663
1788
|
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor({
|
|
1664
1789
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1665
|
-
|
|
1790
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1666
1791
|
incidentSeverities: [{ _id: MAJOR_INCIDENT }],
|
|
1667
1792
|
alertSeverities: [],
|
|
1668
1793
|
enabled: true,
|
|
@@ -1685,7 +1810,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - re-scoping a rule a seve
|
|
|
1685
1810
|
],
|
|
1686
1811
|
[
|
|
1687
1812
|
"a new channel",
|
|
1688
|
-
{
|
|
1813
|
+
{ notificationChannels: [ComplianceNotificationChannel.Push] },
|
|
1689
1814
|
],
|
|
1690
1815
|
[
|
|
1691
1816
|
"a new rule type",
|
|
@@ -1752,7 +1877,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - re-scoping a rule a seve
|
|
|
1752
1877
|
{
|
|
1753
1878
|
...INCIDENT_CALL_CRITICAL,
|
|
1754
1879
|
id: SIBLING_SETTING_ID,
|
|
1755
|
-
|
|
1880
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
1756
1881
|
options: { note: "the sibling's own" },
|
|
1757
1882
|
},
|
|
1758
1883
|
);
|
|
@@ -1791,14 +1916,14 @@ describe("TeamComplianceSettingService - a rule a severity delete emptied duplic
|
|
|
1791
1916
|
{
|
|
1792
1917
|
id: SETTING_ID,
|
|
1793
1918
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1794
|
-
|
|
1919
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1795
1920
|
},
|
|
1796
1921
|
);
|
|
1797
1922
|
|
|
1798
1923
|
await expect(
|
|
1799
1924
|
update({
|
|
1800
1925
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1801
|
-
|
|
1926
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1802
1927
|
incidentSeverities: [],
|
|
1803
1928
|
alertSeverities: [],
|
|
1804
1929
|
enabled: false,
|
|
@@ -1812,7 +1937,7 @@ describe("TeamComplianceSettingService - a rule a severity delete emptied duplic
|
|
|
1812
1937
|
await expect(
|
|
1813
1938
|
create({
|
|
1814
1939
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1815
|
-
|
|
1940
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1816
1941
|
}),
|
|
1817
1942
|
).resolves.toBeDefined();
|
|
1818
1943
|
});
|
|
@@ -1827,7 +1952,7 @@ describe("TeamComplianceSettingService - a rule a severity delete emptied duplic
|
|
|
1827
1952
|
await expect(
|
|
1828
1953
|
create({
|
|
1829
1954
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1830
|
-
|
|
1955
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1831
1956
|
}),
|
|
1832
1957
|
).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
|
|
1833
1958
|
});
|
|
@@ -1842,7 +1967,7 @@ describe("TeamComplianceSettingService - a rule a severity delete emptied duplic
|
|
|
1842
1967
|
await expect(
|
|
1843
1968
|
create({
|
|
1844
1969
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1845
|
-
|
|
1970
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1846
1971
|
incidentSeverities: [MAJOR_INCIDENT],
|
|
1847
1972
|
}),
|
|
1848
1973
|
).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
|
|
@@ -1867,7 +1992,9 @@ describe("TeamComplianceSettingService onBeforeUpdate - scope changes", () => {
|
|
|
1867
1992
|
test("reads the rows being changed inside the caller's project, as root, with what their scope needs", async () => {
|
|
1868
1993
|
store(INCIDENT_CALL_CRITICAL);
|
|
1869
1994
|
|
|
1870
|
-
await update({
|
|
1995
|
+
await update({
|
|
1996
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
1997
|
+
});
|
|
1871
1998
|
|
|
1872
1999
|
const existingRead: JSONObject = findByCalls()[0]!;
|
|
1873
2000
|
expect(existingRead["query"]).toEqual({
|
|
@@ -1879,6 +2006,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - scope changes", () => {
|
|
|
1879
2006
|
teamId: true,
|
|
1880
2007
|
projectId: true,
|
|
1881
2008
|
ruleType: true,
|
|
2009
|
+
notificationChannels: true,
|
|
1882
2010
|
notificationChannel: true,
|
|
1883
2011
|
options: true,
|
|
1884
2012
|
incidentSeverities: { _id: true },
|
|
@@ -1893,7 +2021,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - scope changes", () => {
|
|
|
1893
2021
|
store(INCIDENT_CALL_CRITICAL);
|
|
1894
2022
|
|
|
1895
2023
|
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor({
|
|
1896
|
-
|
|
2024
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
1897
2025
|
});
|
|
1898
2026
|
|
|
1899
2027
|
await onBeforeUpdate(updateBy);
|
|
@@ -1911,13 +2039,13 @@ describe("TeamComplianceSettingService onBeforeUpdate - scope changes", () => {
|
|
|
1911
2039
|
store({
|
|
1912
2040
|
...INCIDENT_CALL_CRITICAL,
|
|
1913
2041
|
id: SIBLING_SETTING_ID,
|
|
1914
|
-
|
|
2042
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
1915
2043
|
});
|
|
1916
2044
|
|
|
1917
2045
|
await expect(
|
|
1918
2046
|
onBeforeUpdate(
|
|
1919
2047
|
updateByFor(
|
|
1920
|
-
{
|
|
2048
|
+
{ notificationChannels: [ComplianceNotificationChannel.Push] },
|
|
1921
2049
|
{ query: { _id: OTHER_PROJECT_SETTING_ID } },
|
|
1922
2050
|
),
|
|
1923
2051
|
),
|
|
@@ -1936,7 +2064,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - scope changes", () => {
|
|
|
1936
2064
|
|
|
1937
2065
|
await onBeforeUpdate(
|
|
1938
2066
|
updateByFor(
|
|
1939
|
-
{
|
|
2067
|
+
{ notificationChannels: [ComplianceNotificationChannel.Push] },
|
|
1940
2068
|
{ props: { isRoot: true } },
|
|
1941
2069
|
),
|
|
1942
2070
|
);
|
|
@@ -1952,7 +2080,9 @@ describe("TeamComplianceSettingService onBeforeUpdate - scope changes", () => {
|
|
|
1952
2080
|
test("compares against the team's other rules, never against the row itself", async () => {
|
|
1953
2081
|
store(INCIDENT_CALL_CRITICAL);
|
|
1954
2082
|
|
|
1955
|
-
await update({
|
|
2083
|
+
await update({
|
|
2084
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
2085
|
+
});
|
|
1956
2086
|
|
|
1957
2087
|
const siblings: Array<JSONObject> = siblingReads();
|
|
1958
2088
|
expect(siblings).toHaveLength(1);
|
|
@@ -1978,7 +2108,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - scope changes", () => {
|
|
|
1978
2108
|
await expect(
|
|
1979
2109
|
update({
|
|
1980
2110
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
1981
|
-
|
|
2111
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
1982
2112
|
incidentSeverities: [{ _id: CRITICAL_INCIDENT }],
|
|
1983
2113
|
alertSeverities: [],
|
|
1984
2114
|
enabled: true,
|
|
@@ -1990,11 +2120,11 @@ describe("TeamComplianceSettingService onBeforeUpdate - scope changes", () => {
|
|
|
1990
2120
|
store(INCIDENT_CALL_CRITICAL, {
|
|
1991
2121
|
...INCIDENT_CALL_CRITICAL,
|
|
1992
2122
|
id: SIBLING_SETTING_ID,
|
|
1993
|
-
|
|
2123
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
1994
2124
|
});
|
|
1995
2125
|
|
|
1996
2126
|
await expect(
|
|
1997
|
-
update({
|
|
2127
|
+
update({ notificationChannels: [ComplianceNotificationChannel.Push] }),
|
|
1998
2128
|
).rejects.toThrow(new BadDataException(DUPLICATE_COMPLIANCE_RULE_MESSAGE));
|
|
1999
2129
|
});
|
|
2000
2130
|
|
|
@@ -2020,12 +2150,12 @@ describe("TeamComplianceSettingService onBeforeUpdate - scope changes", () => {
|
|
|
2020
2150
|
store(INCIDENT_CALL_CRITICAL, {
|
|
2021
2151
|
id: SIBLING_SETTING_ID,
|
|
2022
2152
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
2023
|
-
|
|
2153
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
2024
2154
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
2025
2155
|
});
|
|
2026
2156
|
|
|
2027
2157
|
await expect(
|
|
2028
|
-
update({
|
|
2158
|
+
update({ notificationChannels: [ComplianceNotificationChannel.SMS] }),
|
|
2029
2159
|
).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
|
|
2030
2160
|
});
|
|
2031
2161
|
|
|
@@ -2036,7 +2166,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - scope changes", () => {
|
|
|
2036
2166
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
2037
2167
|
});
|
|
2038
2168
|
|
|
2039
|
-
await expect(update({
|
|
2169
|
+
await expect(update({ notificationChannels: [] })).rejects.toThrow(
|
|
2040
2170
|
DUPLICATE_COMPLIANCE_RULE_MESSAGE,
|
|
2041
2171
|
);
|
|
2042
2172
|
});
|
|
@@ -2066,11 +2196,11 @@ describe("TeamComplianceSettingService onBeforeUpdate - scope changes", () => {
|
|
|
2066
2196
|
store({
|
|
2067
2197
|
id: SETTING_ID,
|
|
2068
2198
|
ruleType: "RequireTwoFactorAuth",
|
|
2069
|
-
|
|
2199
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
2070
2200
|
});
|
|
2071
2201
|
|
|
2072
2202
|
await expect(
|
|
2073
|
-
update({
|
|
2203
|
+
update({ notificationChannels: [ComplianceNotificationChannel.Push] }),
|
|
2074
2204
|
).resolves.toBeDefined();
|
|
2075
2205
|
|
|
2076
2206
|
expect(siblingReads()).toEqual([]);
|
|
@@ -2177,7 +2307,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - scope changes", () => {
|
|
|
2177
2307
|
await expect(
|
|
2178
2308
|
onBeforeUpdate(
|
|
2179
2309
|
updateByFor(
|
|
2180
|
-
{
|
|
2310
|
+
{ notificationChannels: [ComplianceNotificationChannel.Push] },
|
|
2181
2311
|
{ props: { isRoot: true } },
|
|
2182
2312
|
),
|
|
2183
2313
|
),
|
|
@@ -2196,7 +2326,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - scope changes", () => {
|
|
|
2196
2326
|
|
|
2197
2327
|
await onBeforeUpdate(
|
|
2198
2328
|
updateByFor(
|
|
2199
|
-
{
|
|
2329
|
+
{ notificationChannels: [ComplianceNotificationChannel.Push] },
|
|
2200
2330
|
{
|
|
2201
2331
|
query: {
|
|
2202
2332
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
@@ -2225,6 +2355,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - a rule type change", ()
|
|
|
2225
2355
|
|
|
2226
2356
|
expect(dataOf(updateBy)).toEqual({
|
|
2227
2357
|
ruleType: ComplianceRuleType.HasNotificationEmailMethod,
|
|
2358
|
+
notificationChannels: [],
|
|
2228
2359
|
notificationChannel: null,
|
|
2229
2360
|
incidentSeverities: [],
|
|
2230
2361
|
alertSeverities: [],
|
|
@@ -2268,7 +2399,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - a rule type change", ()
|
|
|
2268
2399
|
store(INCIDENT_CALL_CRITICAL, {
|
|
2269
2400
|
id: SIBLING_SETTING_ID,
|
|
2270
2401
|
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
2271
|
-
|
|
2402
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
2272
2403
|
});
|
|
2273
2404
|
|
|
2274
2405
|
await expect(
|
|
@@ -2350,13 +2481,14 @@ describe("TeamComplianceSettingService onBeforeUpdate - options the stored type
|
|
|
2350
2481
|
});
|
|
2351
2482
|
|
|
2352
2483
|
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor({
|
|
2353
|
-
|
|
2484
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
2354
2485
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
2355
2486
|
});
|
|
2356
2487
|
|
|
2357
2488
|
await onBeforeUpdate(updateBy);
|
|
2358
2489
|
|
|
2359
2490
|
expect(dataOf(updateBy)).toEqual({
|
|
2491
|
+
notificationChannels: [],
|
|
2360
2492
|
notificationChannel: null,
|
|
2361
2493
|
incidentSeverities: [],
|
|
2362
2494
|
});
|
|
@@ -2366,7 +2498,7 @@ describe("TeamComplianceSettingService onBeforeUpdate - options the stored type
|
|
|
2366
2498
|
store(INCIDENT_CALL_CRITICAL);
|
|
2367
2499
|
|
|
2368
2500
|
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor({
|
|
2369
|
-
|
|
2501
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
2370
2502
|
incidentSeverities: [{ _id: MAJOR_INCIDENT }],
|
|
2371
2503
|
});
|
|
2372
2504
|
|
|
@@ -2375,10 +2507,12 @@ describe("TeamComplianceSettingService onBeforeUpdate - options the stored type
|
|
|
2375
2507
|
expect(Object.keys(dataOf(updateBy)).sort()).toEqual([
|
|
2376
2508
|
"incidentSeverities",
|
|
2377
2509
|
"notificationChannel",
|
|
2510
|
+
"notificationChannels",
|
|
2378
2511
|
]);
|
|
2379
|
-
expect(dataOf(updateBy)
|
|
2380
|
-
ComplianceNotificationChannel.Push,
|
|
2381
|
-
|
|
2512
|
+
expect(channelsOf(dataOf(updateBy))).toEqual({
|
|
2513
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
2514
|
+
notificationChannel: ComplianceNotificationChannel.Push,
|
|
2515
|
+
});
|
|
2382
2516
|
expect(
|
|
2383
2517
|
writtenIds(dataOf(updateBy)["incidentSeverities"], IncidentSeverity),
|
|
2384
2518
|
).toEqual([MAJOR_INCIDENT]);
|
|
@@ -2403,7 +2537,7 @@ describe("TeamComplianceSettingService - the severities checked are the severiti
|
|
|
2403
2537
|
test("create: repeats and case collapse to one lower-case id each, handed to the save as models", async () => {
|
|
2404
2538
|
const result: OnCreate<TeamComplianceSetting> = await create({
|
|
2405
2539
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
2406
|
-
|
|
2540
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
2407
2541
|
incidentSeverities: [
|
|
2408
2542
|
CRITICAL_INCIDENT.toUpperCase(),
|
|
2409
2543
|
{ _id: CRITICAL_INCIDENT },
|
|
@@ -2459,7 +2593,7 @@ describe("TeamComplianceSettingService - the severities checked are the severiti
|
|
|
2459
2593
|
store(INCIDENT_CALL_CRITICAL, {
|
|
2460
2594
|
id: SIBLING_SETTING_ID,
|
|
2461
2595
|
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
2462
|
-
|
|
2596
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
2463
2597
|
alertSeverityIds: [CRITICAL_ALERT],
|
|
2464
2598
|
});
|
|
2465
2599
|
|
|
@@ -2529,7 +2663,7 @@ describe("TeamComplianceSettingService - the severities checked are the severiti
|
|
|
2529
2663
|
await expect(
|
|
2530
2664
|
create({
|
|
2531
2665
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
2532
|
-
|
|
2666
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
2533
2667
|
incidentSeverities: [{ _id: 1, id: CRITICAL_INCIDENT }],
|
|
2534
2668
|
}),
|
|
2535
2669
|
).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
|
|
@@ -2885,7 +3019,7 @@ describe("TeamComplianceSettingService - the caller's permission is checked befo
|
|
|
2885
3019
|
createByFor(
|
|
2886
3020
|
newRule({
|
|
2887
3021
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
2888
|
-
|
|
3022
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
2889
3023
|
incidentSeverities: [FOREIGN_INCIDENT],
|
|
2890
3024
|
}),
|
|
2891
3025
|
props,
|
|
@@ -2898,7 +3032,7 @@ describe("TeamComplianceSettingService - the caller's permission is checked befo
|
|
|
2898
3032
|
createByFor(
|
|
2899
3033
|
newRule({
|
|
2900
3034
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
2901
|
-
|
|
3035
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
2902
3036
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
2903
3037
|
}),
|
|
2904
3038
|
props,
|
|
@@ -2916,12 +3050,12 @@ describe("TeamComplianceSettingService - the caller's permission is checked befo
|
|
|
2916
3050
|
store(INCIDENT_CALL_CRITICAL, {
|
|
2917
3051
|
...INCIDENT_CALL_CRITICAL,
|
|
2918
3052
|
id: SIBLING_SETTING_ID,
|
|
2919
|
-
|
|
3053
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
2920
3054
|
});
|
|
2921
3055
|
|
|
2922
3056
|
for (const data of [
|
|
2923
3057
|
{ incidentSeverities: [FOREIGN_INCIDENT] },
|
|
2924
|
-
{
|
|
3058
|
+
{ notificationChannels: [ComplianceNotificationChannel.Push] },
|
|
2925
3059
|
]) {
|
|
2926
3060
|
await expect(
|
|
2927
3061
|
onBeforeUpdate(updateByFor({ ...data }, { props: props })),
|
|
@@ -2995,13 +3129,13 @@ describe("TeamComplianceSettingService - the caller's permission is checked befo
|
|
|
2995
3129
|
for (const input of [
|
|
2996
3130
|
{
|
|
2997
3131
|
ruleType: ComplianceRuleType.HasNotificationCallMethod,
|
|
2998
|
-
|
|
3132
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
2999
3133
|
incidentSeverities: [FOREIGN_INCIDENT],
|
|
3000
3134
|
alertSeverities: [FOREIGN_ALERT],
|
|
3001
3135
|
},
|
|
3002
3136
|
{
|
|
3003
3137
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
3004
|
-
|
|
3138
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
3005
3139
|
incidentSeverities: [CRITICAL_INCIDENT, CRITICAL_INCIDENT],
|
|
3006
3140
|
alertSeverities: [FOREIGN_ALERT],
|
|
3007
3141
|
},
|
|
@@ -3030,7 +3164,7 @@ describe("TeamComplianceSettingService - the caller's permission is checked befo
|
|
|
3030
3164
|
createByFor(
|
|
3031
3165
|
newRule({
|
|
3032
3166
|
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
3033
|
-
|
|
3167
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
3034
3168
|
alertSeverities: [CRITICAL_ALERT],
|
|
3035
3169
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
3036
3170
|
}),
|
|
@@ -3058,7 +3192,7 @@ describe("TeamComplianceSettingService - the caller's permission is checked befo
|
|
|
3058
3192
|
incidentSeverities: [{ _id: MAJOR_INCIDENT }, MAJOR_INCIDENT],
|
|
3059
3193
|
alertSeverities: [FOREIGN_ALERT],
|
|
3060
3194
|
},
|
|
3061
|
-
{
|
|
3195
|
+
{ notificationChannels: [] },
|
|
3062
3196
|
]) {
|
|
3063
3197
|
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor(
|
|
3064
3198
|
{ ...data },
|
|
@@ -3105,7 +3239,7 @@ describe("TeamComplianceSettingService - the caller's permission is checked befo
|
|
|
3105
3239
|
);
|
|
3106
3240
|
await onBeforeUpdate(
|
|
3107
3241
|
updateByFor(
|
|
3108
|
-
{
|
|
3242
|
+
{ notificationChannels: [ComplianceNotificationChannel.Push] },
|
|
3109
3243
|
{ props: { isRoot: true } },
|
|
3110
3244
|
),
|
|
3111
3245
|
);
|
|
@@ -3164,37 +3298,37 @@ describe("TeamComplianceSettingService - a severity delete pauses and marks the
|
|
|
3164
3298
|
{
|
|
3165
3299
|
id: RULE_CRITICAL_ONLY,
|
|
3166
3300
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
3167
|
-
|
|
3301
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
3168
3302
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
3169
3303
|
},
|
|
3170
3304
|
{
|
|
3171
3305
|
id: RULE_EPISODE_CRITICAL_ONLY,
|
|
3172
3306
|
ruleType: ComplianceRuleType.HasIncidentEpisodeOnCallRules,
|
|
3173
|
-
|
|
3307
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
3174
3308
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
3175
3309
|
},
|
|
3176
3310
|
{
|
|
3177
3311
|
id: RULE_CRITICAL_AND_MAJOR,
|
|
3178
3312
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
3179
|
-
|
|
3313
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
3180
3314
|
incidentSeverityIds: [CRITICAL_INCIDENT, MAJOR_INCIDENT],
|
|
3181
3315
|
},
|
|
3182
3316
|
{
|
|
3183
3317
|
id: RULE_EVERY_SEVERITY,
|
|
3184
3318
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
3185
|
-
|
|
3319
|
+
notificationChannels: [ComplianceNotificationChannel.Email],
|
|
3186
3320
|
},
|
|
3187
3321
|
{
|
|
3188
3322
|
id: RULE_PAUSED,
|
|
3189
3323
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
3190
|
-
|
|
3324
|
+
notificationChannels: [ComplianceNotificationChannel.Slack],
|
|
3191
3325
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
3192
3326
|
enabled: false,
|
|
3193
3327
|
},
|
|
3194
3328
|
{
|
|
3195
3329
|
id: RULE_ALERT,
|
|
3196
3330
|
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
3197
|
-
|
|
3331
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
3198
3332
|
alertSeverityIds: [CRITICAL_ALERT],
|
|
3199
3333
|
},
|
|
3200
3334
|
{
|
|
@@ -3213,7 +3347,7 @@ describe("TeamComplianceSettingService - a severity delete pauses and marks the
|
|
|
3213
3347
|
{
|
|
3214
3348
|
id: RULE_MAJOR_ONLY,
|
|
3215
3349
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
3216
|
-
|
|
3350
|
+
notificationChannels: [ComplianceNotificationChannel.WhatsApp],
|
|
3217
3351
|
incidentSeverityIds: [MAJOR_INCIDENT],
|
|
3218
3352
|
},
|
|
3219
3353
|
);
|
|
@@ -3319,7 +3453,7 @@ describe("TeamComplianceSettingService - a severity delete pauses and marks the
|
|
|
3319
3453
|
{
|
|
3320
3454
|
id: RULE_CRITICAL_ONLY,
|
|
3321
3455
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
3322
|
-
|
|
3456
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
3323
3457
|
},
|
|
3324
3458
|
{
|
|
3325
3459
|
id: RULE_EPISODE_CRITICAL_ONLY,
|
|
@@ -3492,7 +3626,7 @@ describe("TeamComplianceSettingService - a severity delete pauses and marks the
|
|
|
3492
3626
|
store({
|
|
3493
3627
|
id: RULE_CRITICAL_AND_MAJOR,
|
|
3494
3628
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
3495
|
-
|
|
3629
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
3496
3630
|
incidentSeverityIds: [MAJOR_INCIDENT],
|
|
3497
3631
|
});
|
|
3498
3632
|
|
|
@@ -3508,7 +3642,7 @@ describe("TeamComplianceSettingService - a severity delete pauses and marks the
|
|
|
3508
3642
|
store({
|
|
3509
3643
|
id: RULE_CRITICAL_AND_MAJOR,
|
|
3510
3644
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
3511
|
-
|
|
3645
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
3512
3646
|
});
|
|
3513
3647
|
|
|
3514
3648
|
expect(
|
|
@@ -3699,13 +3833,13 @@ describe("TeamComplianceSettingService.getScope", () => {
|
|
|
3699
3833
|
expect(
|
|
3700
3834
|
TeamComplianceSettingServiceClass.getScope({
|
|
3701
3835
|
ruleType: ruleType,
|
|
3702
|
-
|
|
3836
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
3703
3837
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
3704
3838
|
alertSeverities: [CRITICAL_ALERT],
|
|
3705
3839
|
}),
|
|
3706
3840
|
).toEqual({
|
|
3707
3841
|
ruleType: ruleType,
|
|
3708
|
-
|
|
3842
|
+
notificationChannels: [],
|
|
3709
3843
|
severityKind: null,
|
|
3710
3844
|
severityIds: [],
|
|
3711
3845
|
});
|
|
@@ -3718,13 +3852,13 @@ describe("TeamComplianceSettingService.getScope", () => {
|
|
|
3718
3852
|
expect(
|
|
3719
3853
|
TeamComplianceSettingServiceClass.getScope({
|
|
3720
3854
|
ruleType: ruleType,
|
|
3721
|
-
|
|
3855
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
3722
3856
|
incidentSeverities: [MAJOR_INCIDENT, CRITICAL_INCIDENT],
|
|
3723
3857
|
alertSeverities: [CRITICAL_ALERT],
|
|
3724
3858
|
}),
|
|
3725
3859
|
).toEqual({
|
|
3726
3860
|
ruleType: ruleType,
|
|
3727
|
-
|
|
3861
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
3728
3862
|
severityKind: ComplianceSeverityKind.Incident,
|
|
3729
3863
|
severityIds: [CRITICAL_INCIDENT, MAJOR_INCIDENT].sort(),
|
|
3730
3864
|
});
|
|
@@ -3737,36 +3871,72 @@ describe("TeamComplianceSettingService.getScope", () => {
|
|
|
3737
3871
|
expect(
|
|
3738
3872
|
TeamComplianceSettingServiceClass.getScope({
|
|
3739
3873
|
ruleType: ruleType,
|
|
3740
|
-
|
|
3874
|
+
notificationChannels: [],
|
|
3741
3875
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
3742
3876
|
alertSeverities: [MAJOR_ALERT],
|
|
3743
3877
|
}),
|
|
3744
3878
|
).toEqual({
|
|
3745
3879
|
ruleType: ruleType,
|
|
3746
|
-
|
|
3880
|
+
notificationChannels: [],
|
|
3747
3881
|
severityKind: ComplianceSeverityKind.Alert,
|
|
3748
3882
|
severityIds: [MAJOR_ALERT],
|
|
3749
3883
|
});
|
|
3750
3884
|
},
|
|
3751
3885
|
);
|
|
3752
3886
|
|
|
3753
|
-
test.each([
|
|
3887
|
+
test.each<[string, unknown]>([
|
|
3754
3888
|
["undefined", undefined],
|
|
3755
3889
|
["null", null],
|
|
3756
|
-
["an
|
|
3757
|
-
["
|
|
3890
|
+
["an empty list", []],
|
|
3891
|
+
["one channel as a bare string, not a list", "Call"],
|
|
3892
|
+
["a list of an unknown channel", ["Pager"]],
|
|
3893
|
+
["a list of a channel in the wrong case", ["call"]],
|
|
3894
|
+
["a list of a number", [3]],
|
|
3758
3895
|
["a number", 3],
|
|
3759
|
-
])("reads %s as 'any channel'", (_label: string,
|
|
3896
|
+
])("reads %s as 'any channel'", (_label: string, channels: unknown) => {
|
|
3760
3897
|
expect(
|
|
3761
3898
|
TeamComplianceSettingServiceClass.getScope({
|
|
3762
3899
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
3763
|
-
|
|
3900
|
+
notificationChannels: channels,
|
|
3901
|
+
incidentSeverities: undefined,
|
|
3902
|
+
alertSeverities: undefined,
|
|
3903
|
+
}).notificationChannels,
|
|
3904
|
+
).toEqual([]);
|
|
3905
|
+
});
|
|
3906
|
+
|
|
3907
|
+
test("an on-call rule's channels are read canonical: known ones only, each once, in catalog order", () => {
|
|
3908
|
+
expect(
|
|
3909
|
+
TeamComplianceSettingServiceClass.getScope({
|
|
3910
|
+
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
3911
|
+
notificationChannels: [
|
|
3912
|
+
ComplianceNotificationChannel.Webhook,
|
|
3913
|
+
"Pager",
|
|
3914
|
+
ComplianceNotificationChannel.Call,
|
|
3915
|
+
ComplianceNotificationChannel.Webhook,
|
|
3916
|
+
],
|
|
3764
3917
|
incidentSeverities: undefined,
|
|
3765
3918
|
alertSeverities: undefined,
|
|
3766
|
-
}).
|
|
3767
|
-
).
|
|
3919
|
+
}).notificationChannels,
|
|
3920
|
+
).toEqual([
|
|
3921
|
+
ComplianceNotificationChannel.Call,
|
|
3922
|
+
ComplianceNotificationChannel.Webhook,
|
|
3923
|
+
]);
|
|
3768
3924
|
});
|
|
3769
3925
|
|
|
3926
|
+
test.each(METHOD_RULE_TYPES)(
|
|
3927
|
+
"%s has no channels, however many are passed",
|
|
3928
|
+
(ruleType: ComplianceRuleType) => {
|
|
3929
|
+
expect(
|
|
3930
|
+
TeamComplianceSettingServiceClass.getScope({
|
|
3931
|
+
ruleType: ruleType,
|
|
3932
|
+
notificationChannels: ALL_CHANNELS,
|
|
3933
|
+
incidentSeverities: undefined,
|
|
3934
|
+
alertSeverities: undefined,
|
|
3935
|
+
}).notificationChannels,
|
|
3936
|
+
).toEqual([]);
|
|
3937
|
+
},
|
|
3938
|
+
);
|
|
3939
|
+
|
|
3770
3940
|
test.each(
|
|
3771
3941
|
COMPLIANCE_RULE_DEFINITIONS.map(
|
|
3772
3942
|
(definition: ComplianceRuleDefinition): [ComplianceRuleType] => {
|
|
@@ -3779,7 +3949,7 @@ describe("TeamComplianceSettingService.getScope", () => {
|
|
|
3779
3949
|
const scope: ComplianceRuleScope =
|
|
3780
3950
|
TeamComplianceSettingServiceClass.getScope({
|
|
3781
3951
|
ruleType: ruleType,
|
|
3782
|
-
|
|
3952
|
+
notificationChannels: [ComplianceNotificationChannel.Slack],
|
|
3783
3953
|
incidentSeverities: [CRITICAL_INCIDENT],
|
|
3784
3954
|
alertSeverities: [CRITICAL_ALERT],
|
|
3785
3955
|
});
|
|
@@ -3787,10 +3957,10 @@ describe("TeamComplianceSettingService.getScope", () => {
|
|
|
3787
3957
|
expect(scope.severityKind).toBe(
|
|
3788
3958
|
ComplianceRule.getSeverityKind(ruleType) || null,
|
|
3789
3959
|
);
|
|
3790
|
-
expect(scope.
|
|
3960
|
+
expect(scope.notificationChannels).toEqual(
|
|
3791
3961
|
ComplianceRule.supportsChannel(ruleType)
|
|
3792
|
-
? ComplianceNotificationChannel.Slack
|
|
3793
|
-
:
|
|
3962
|
+
? [ComplianceNotificationChannel.Slack]
|
|
3963
|
+
: [],
|
|
3794
3964
|
);
|
|
3795
3965
|
expect(scope.severityIds).toHaveLength(
|
|
3796
3966
|
ComplianceRule.supportsSeverityScope(ruleType) ? 1 : 0,
|
|
@@ -3802,7 +3972,7 @@ describe("TeamComplianceSettingService.getScope", () => {
|
|
|
3802
3972
|
describe("TeamComplianceSettingService.isSameScope", () => {
|
|
3803
3973
|
const base: ComplianceRuleScope = TeamComplianceSettingServiceClass.getScope({
|
|
3804
3974
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
3805
|
-
|
|
3975
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
3806
3976
|
incidentSeverities: [CRITICAL_INCIDENT, MAJOR_INCIDENT],
|
|
3807
3977
|
alertSeverities: undefined,
|
|
3808
3978
|
});
|
|
@@ -3823,7 +3993,7 @@ describe("TeamComplianceSettingService.isSameScope", () => {
|
|
|
3823
3993
|
const reordered: ComplianceRuleScope =
|
|
3824
3994
|
TeamComplianceSettingServiceClass.getScope({
|
|
3825
3995
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
3826
|
-
|
|
3996
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
3827
3997
|
incidentSeverities: [
|
|
3828
3998
|
MAJOR_INCIDENT.toUpperCase(),
|
|
3829
3999
|
{ _id: CRITICAL_INCIDENT },
|
|
@@ -3847,9 +4017,9 @@ describe("TeamComplianceSettingService.isSameScope", () => {
|
|
|
3847
4017
|
],
|
|
3848
4018
|
[
|
|
3849
4019
|
"the channel",
|
|
3850
|
-
{
|
|
4020
|
+
{ notificationChannels: [ComplianceNotificationChannel.Push] },
|
|
3851
4021
|
],
|
|
3852
|
-
["'any channel' against one channel", {
|
|
4022
|
+
["'any channel' against one channel", { notificationChannels: [] }],
|
|
3853
4023
|
["a narrower severity set", { severityIds: [CRITICAL_INCIDENT] }],
|
|
3854
4024
|
["every severity against some", { severityIds: [] }],
|
|
3855
4025
|
[
|
|
@@ -3875,78 +4045,1573 @@ describe("TeamComplianceSettingService.isSameScope", () => {
|
|
|
3875
4045
|
|
|
3876
4046
|
/*
|
|
3877
4047
|
* ---------------------------------------------------------------------------
|
|
3878
|
-
*
|
|
4048
|
+
* Channels: a list, with its first channel beside it.
|
|
3879
4049
|
* ---------------------------------------------------------------------------
|
|
4050
|
+
*
|
|
4051
|
+
* A rule insists on the channels in notificationChannels - a member needs a
|
|
4052
|
+
* rule on every one of them. notificationChannel, the column the list
|
|
4053
|
+
* replaced, is kept equal to the list's first channel for whatever only knows
|
|
4054
|
+
* that one column: an API client or a Dashboard bundle from before the list,
|
|
4055
|
+
* or a replica of an older build still serving during an upgrade. The hooks
|
|
4056
|
+
* write the pair from whichever field a payload sends, and a stored row is
|
|
4057
|
+
* read back by getStoredChannels.
|
|
3880
4058
|
*/
|
|
3881
4059
|
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
4060
|
+
// Every subset of the nine channels, each in catalog order.
|
|
4061
|
+
const everyChannelSubset: () => Array<
|
|
4062
|
+
Array<ComplianceNotificationChannel>
|
|
4063
|
+
> = (): Array<Array<ComplianceNotificationChannel>> => {
|
|
4064
|
+
const subsets: Array<Array<ComplianceNotificationChannel>> = [];
|
|
4065
|
+
|
|
4066
|
+
for (let mask: number = 0; mask < 1 << ALL_CHANNELS.length; mask++) {
|
|
4067
|
+
subsets.push(
|
|
4068
|
+
ALL_CHANNELS.filter(
|
|
4069
|
+
(_channel: ComplianceNotificationChannel, index: number): boolean => {
|
|
4070
|
+
return (mask & (1 << index)) !== 0;
|
|
4071
|
+
},
|
|
4072
|
+
),
|
|
4073
|
+
);
|
|
4074
|
+
}
|
|
3885
4075
|
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
}
|
|
4076
|
+
return subsets;
|
|
4077
|
+
};
|
|
3889
4078
|
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
4079
|
+
// "Call and Push for Critical incidents", as a current build stores it.
|
|
4080
|
+
const CALL_AND_PUSH_FOR_CRITICAL: StoredRuleInput = {
|
|
4081
|
+
id: SIBLING_SETTING_ID,
|
|
4082
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
4083
|
+
notificationChannels: [
|
|
4084
|
+
ComplianceNotificationChannel.Call,
|
|
4085
|
+
ComplianceNotificationChannel.Push,
|
|
4086
|
+
],
|
|
4087
|
+
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
4088
|
+
};
|
|
3894
4089
|
|
|
3895
|
-
|
|
3896
|
-
|
|
4090
|
+
describe("TeamComplianceSettingService.resolveSentChannels", () => {
|
|
4091
|
+
test("null is no channels - 'any channel'", () => {
|
|
4092
|
+
expect(TeamComplianceSettingServiceClass.resolveSentChannels(null)).toEqual(
|
|
4093
|
+
[],
|
|
4094
|
+
);
|
|
4095
|
+
});
|
|
3897
4096
|
|
|
3898
|
-
|
|
4097
|
+
test("an empty list is no channels", () => {
|
|
4098
|
+
expect(TeamComplianceSettingServiceClass.resolveSentChannels([])).toEqual(
|
|
4099
|
+
[],
|
|
4100
|
+
);
|
|
4101
|
+
});
|
|
3899
4102
|
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
4103
|
+
test("keeps each channel once, in catalog order, whatever order they were picked in", () => {
|
|
4104
|
+
expect(
|
|
4105
|
+
TeamComplianceSettingServiceClass.resolveSentChannels([
|
|
4106
|
+
ComplianceNotificationChannel.Push,
|
|
4107
|
+
ComplianceNotificationChannel.Call,
|
|
4108
|
+
ComplianceNotificationChannel.Push,
|
|
4109
|
+
]),
|
|
4110
|
+
).toEqual([
|
|
4111
|
+
ComplianceNotificationChannel.Call,
|
|
4112
|
+
ComplianceNotificationChannel.Push,
|
|
4113
|
+
]);
|
|
4114
|
+
expect(
|
|
4115
|
+
TeamComplianceSettingServiceClass.resolveSentChannels(
|
|
4116
|
+
[...ALL_CHANNELS].reverse(),
|
|
4117
|
+
),
|
|
4118
|
+
).toEqual(ALL_CHANNELS);
|
|
4119
|
+
});
|
|
3910
4120
|
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
.
|
|
3914
|
-
.
|
|
3915
|
-
|
|
3916
|
-
row._id = SETTING_ID;
|
|
3917
|
-
row.projectId = PROJECT_ID;
|
|
3918
|
-
return Promise.resolve([row]);
|
|
3919
|
-
});
|
|
4121
|
+
test("leaves the list it was sent alone", () => {
|
|
4122
|
+
const sent: Array<ComplianceNotificationChannel> = [
|
|
4123
|
+
ComplianceNotificationChannel.Webhook,
|
|
4124
|
+
ComplianceNotificationChannel.Call,
|
|
4125
|
+
];
|
|
3920
4126
|
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
});
|
|
3924
|
-
repositoryUpdate = jest.fn(() => {
|
|
3925
|
-
return Promise.resolve({ affected: 1 });
|
|
3926
|
-
});
|
|
3927
|
-
jest.spyOn(service, "getRepository").mockReturnValue({
|
|
3928
|
-
save: save,
|
|
3929
|
-
update: repositoryUpdate,
|
|
3930
|
-
} as never);
|
|
4127
|
+
const resolved: Array<ComplianceNotificationChannel> =
|
|
4128
|
+
TeamComplianceSettingServiceClass.resolveSentChannels(sent);
|
|
3931
4129
|
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
.
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
.mockResolvedValue(undefined as never);
|
|
4130
|
+
expect(resolved).toEqual([
|
|
4131
|
+
ComplianceNotificationChannel.Call,
|
|
4132
|
+
ComplianceNotificationChannel.Webhook,
|
|
4133
|
+
]);
|
|
4134
|
+
expect(sent).toEqual([
|
|
4135
|
+
ComplianceNotificationChannel.Webhook,
|
|
4136
|
+
ComplianceNotificationChannel.Call,
|
|
4137
|
+
]);
|
|
3941
4138
|
});
|
|
3942
4139
|
|
|
3943
|
-
test
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
4140
|
+
test.each<[string, unknown]>([
|
|
4141
|
+
["undefined", undefined],
|
|
4142
|
+
["one channel as a bare string", ComplianceNotificationChannel.Call],
|
|
4143
|
+
["a comma-separated string", "Call,Push"],
|
|
4144
|
+
["an object", { 0: ComplianceNotificationChannel.Call }],
|
|
4145
|
+
["a number", 3],
|
|
4146
|
+
["true", true],
|
|
4147
|
+
])(
|
|
4148
|
+
"refuses %s, which is not a list, and says what it must be",
|
|
4149
|
+
(_label: string, value: unknown) => {
|
|
4150
|
+
expect(() => {
|
|
4151
|
+
TeamComplianceSettingServiceClass.resolveSentChannels(value);
|
|
4152
|
+
}).toThrow(new BadDataException(NOT_A_CHANNEL_LIST_MESSAGE));
|
|
4153
|
+
},
|
|
4154
|
+
);
|
|
4155
|
+
|
|
4156
|
+
/*
|
|
4157
|
+
* Refused, never dropped: a list that silently lost a channel checks less
|
|
4158
|
+
* than the admin asked for, and one that lost its only channel checks
|
|
4159
|
+
* every channel.
|
|
4160
|
+
*/
|
|
4161
|
+
test.each<[string, unknown, string]>([
|
|
4162
|
+
["an unknown channel", "Pager", UNKNOWN_CHANNEL_MESSAGE("Pager")],
|
|
4163
|
+
["a channel in the wrong case", "push", UNKNOWN_CHANNEL_MESSAGE("push")],
|
|
4164
|
+
[
|
|
4165
|
+
"a label rather than a value",
|
|
4166
|
+
"Microsoft Teams",
|
|
4167
|
+
UNKNOWN_CHANNEL_MESSAGE("Microsoft Teams"),
|
|
4168
|
+
],
|
|
4169
|
+
["an empty string", "", UNKNOWN_CHANNEL_MESSAGE("")],
|
|
4170
|
+
["null", null, UNKNOWN_CHANNEL_MESSAGE("null")],
|
|
4171
|
+
["a number", 7, UNKNOWN_CHANNEL_MESSAGE("7")],
|
|
4172
|
+
])(
|
|
4173
|
+
"refuses a list holding %s, by name",
|
|
4174
|
+
(_label: string, item: unknown, message: string) => {
|
|
4175
|
+
expect(() => {
|
|
4176
|
+
TeamComplianceSettingServiceClass.resolveSentChannels([
|
|
4177
|
+
ComplianceNotificationChannel.Call,
|
|
4178
|
+
item,
|
|
4179
|
+
]);
|
|
4180
|
+
}).toThrow(new BadDataException(message));
|
|
4181
|
+
},
|
|
4182
|
+
);
|
|
4183
|
+
|
|
4184
|
+
test("refuses a list of nothing but garbage rather than read it as 'any channel'", () => {
|
|
4185
|
+
expect(() => {
|
|
4186
|
+
TeamComplianceSettingServiceClass.resolveSentChannels(["Pager"]);
|
|
4187
|
+
}).toThrow(UNKNOWN_CHANNEL_MESSAGE("Pager"));
|
|
4188
|
+
});
|
|
4189
|
+
});
|
|
4190
|
+
|
|
4191
|
+
describe("TeamComplianceSettingService.normaliseChannelFields", () => {
|
|
4192
|
+
const normalised: (
|
|
4193
|
+
data: Record<string, unknown>,
|
|
4194
|
+
) => Record<string, unknown> = (
|
|
4195
|
+
data: Record<string, unknown>,
|
|
4196
|
+
): Record<string, unknown> => {
|
|
4197
|
+
const copy: Record<string, unknown> = { ...data };
|
|
4198
|
+
|
|
4199
|
+
TeamComplianceSettingServiceClass.normaliseChannelFields(
|
|
4200
|
+
copy as JSONObject,
|
|
4201
|
+
);
|
|
4202
|
+
|
|
4203
|
+
return copy;
|
|
4204
|
+
};
|
|
4205
|
+
|
|
4206
|
+
test("a payload that sends neither channel field is left exactly as it was", () => {
|
|
4207
|
+
const written: Record<string, unknown> = normalised({
|
|
4208
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
4209
|
+
enabled: true,
|
|
4210
|
+
});
|
|
4211
|
+
|
|
4212
|
+
expect(written).toEqual({
|
|
4213
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
4214
|
+
enabled: true,
|
|
4215
|
+
});
|
|
4216
|
+
expect("notificationChannels" in written).toBe(false);
|
|
4217
|
+
expect("notificationChannel" in written).toBe(false);
|
|
4218
|
+
});
|
|
4219
|
+
|
|
4220
|
+
test.each<[string, Record<string, unknown>, ChannelPair]>([
|
|
4221
|
+
[
|
|
4222
|
+
"a list",
|
|
4223
|
+
{
|
|
4224
|
+
notificationChannels: [
|
|
4225
|
+
ComplianceNotificationChannel.Push,
|
|
4226
|
+
ComplianceNotificationChannel.Call,
|
|
4227
|
+
],
|
|
4228
|
+
},
|
|
4229
|
+
{
|
|
4230
|
+
notificationChannels: [
|
|
4231
|
+
ComplianceNotificationChannel.Call,
|
|
4232
|
+
ComplianceNotificationChannel.Push,
|
|
4233
|
+
],
|
|
4234
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4235
|
+
},
|
|
4236
|
+
],
|
|
4237
|
+
[
|
|
4238
|
+
"a list of one",
|
|
4239
|
+
{ notificationChannels: [ComplianceNotificationChannel.Webhook] },
|
|
4240
|
+
{
|
|
4241
|
+
notificationChannels: [ComplianceNotificationChannel.Webhook],
|
|
4242
|
+
notificationChannel: ComplianceNotificationChannel.Webhook,
|
|
4243
|
+
},
|
|
4244
|
+
],
|
|
4245
|
+
[
|
|
4246
|
+
"a list of every channel, backwards",
|
|
4247
|
+
{ notificationChannels: [...ALL_CHANNELS].reverse() },
|
|
4248
|
+
{
|
|
4249
|
+
notificationChannels: ALL_CHANNELS,
|
|
4250
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4251
|
+
},
|
|
4252
|
+
],
|
|
4253
|
+
[
|
|
4254
|
+
"a list with a repeat",
|
|
4255
|
+
{
|
|
4256
|
+
notificationChannels: [
|
|
4257
|
+
ComplianceNotificationChannel.SMS,
|
|
4258
|
+
ComplianceNotificationChannel.SMS,
|
|
4259
|
+
],
|
|
4260
|
+
},
|
|
4261
|
+
{
|
|
4262
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
4263
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
4264
|
+
},
|
|
4265
|
+
],
|
|
4266
|
+
[
|
|
4267
|
+
"an empty list",
|
|
4268
|
+
{ notificationChannels: [] },
|
|
4269
|
+
{ notificationChannels: [], notificationChannel: null },
|
|
4270
|
+
],
|
|
4271
|
+
[
|
|
4272
|
+
"a null list",
|
|
4273
|
+
{ notificationChannels: null },
|
|
4274
|
+
{ notificationChannels: [], notificationChannel: null },
|
|
4275
|
+
],
|
|
4276
|
+
[
|
|
4277
|
+
"one channel, the old way",
|
|
4278
|
+
{ notificationChannel: ComplianceNotificationChannel.SMS },
|
|
4279
|
+
{
|
|
4280
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
4281
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
4282
|
+
},
|
|
4283
|
+
],
|
|
4284
|
+
[
|
|
4285
|
+
"no channel, the old way",
|
|
4286
|
+
{ notificationChannel: null },
|
|
4287
|
+
{ notificationChannels: [], notificationChannel: null },
|
|
4288
|
+
],
|
|
4289
|
+
[
|
|
4290
|
+
"a list, and the stale single channel a client read it with",
|
|
4291
|
+
{
|
|
4292
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
4293
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4294
|
+
},
|
|
4295
|
+
{
|
|
4296
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
4297
|
+
notificationChannel: ComplianceNotificationChannel.Push,
|
|
4298
|
+
},
|
|
4299
|
+
],
|
|
4300
|
+
[
|
|
4301
|
+
"an empty list, and a stale single channel",
|
|
4302
|
+
{
|
|
4303
|
+
notificationChannels: [],
|
|
4304
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4305
|
+
},
|
|
4306
|
+
{ notificationChannels: [], notificationChannel: null },
|
|
4307
|
+
],
|
|
4308
|
+
[
|
|
4309
|
+
"a list, and no single channel",
|
|
4310
|
+
{
|
|
4311
|
+
notificationChannels: [
|
|
4312
|
+
ComplianceNotificationChannel.Call,
|
|
4313
|
+
ComplianceNotificationChannel.Push,
|
|
4314
|
+
],
|
|
4315
|
+
notificationChannel: null,
|
|
4316
|
+
},
|
|
4317
|
+
{
|
|
4318
|
+
notificationChannels: [
|
|
4319
|
+
ComplianceNotificationChannel.Call,
|
|
4320
|
+
ComplianceNotificationChannel.Push,
|
|
4321
|
+
],
|
|
4322
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4323
|
+
},
|
|
4324
|
+
],
|
|
4325
|
+
[
|
|
4326
|
+
"a list, and the single channel that agrees with it - a rule sent back as it was read",
|
|
4327
|
+
{
|
|
4328
|
+
notificationChannels: [
|
|
4329
|
+
ComplianceNotificationChannel.Call,
|
|
4330
|
+
ComplianceNotificationChannel.Push,
|
|
4331
|
+
],
|
|
4332
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4333
|
+
},
|
|
4334
|
+
{
|
|
4335
|
+
notificationChannels: [
|
|
4336
|
+
ComplianceNotificationChannel.Call,
|
|
4337
|
+
ComplianceNotificationChannel.Push,
|
|
4338
|
+
],
|
|
4339
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4340
|
+
},
|
|
4341
|
+
],
|
|
4342
|
+
])(
|
|
4343
|
+
"%s is written as the list and its first channel",
|
|
4344
|
+
(_label: string, sent: Record<string, unknown>, written: ChannelPair) => {
|
|
4345
|
+
expect(channelsOf(normalised(sent))).toEqual(written);
|
|
4346
|
+
},
|
|
4347
|
+
);
|
|
4348
|
+
|
|
4349
|
+
test("a single channel that is garbage is refused, even beside a good list", () => {
|
|
4350
|
+
expect(() => {
|
|
4351
|
+
normalised({
|
|
4352
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
4353
|
+
notificationChannel: "Pager",
|
|
4354
|
+
});
|
|
4355
|
+
}).toThrow(new BadDataException(UNKNOWN_CHANNEL_MESSAGE("Pager")));
|
|
4356
|
+
});
|
|
4357
|
+
|
|
4358
|
+
test("a refused list leaves the payload as it was sent", () => {
|
|
4359
|
+
const data: Record<string, unknown> = {
|
|
4360
|
+
notificationChannels: [ComplianceNotificationChannel.Call, "Pager"],
|
|
4361
|
+
};
|
|
4362
|
+
|
|
4363
|
+
expect(() => {
|
|
4364
|
+
TeamComplianceSettingServiceClass.normaliseChannelFields(
|
|
4365
|
+
data as JSONObject,
|
|
4366
|
+
);
|
|
4367
|
+
}).toThrow(UNKNOWN_CHANNEL_MESSAGE("Pager"));
|
|
4368
|
+
|
|
4369
|
+
expect(data).toEqual({
|
|
4370
|
+
notificationChannels: [ComplianceNotificationChannel.Call, "Pager"],
|
|
4371
|
+
});
|
|
4372
|
+
});
|
|
4373
|
+
|
|
4374
|
+
test("channels that are not a list are refused", () => {
|
|
4375
|
+
expect(() => {
|
|
4376
|
+
normalised({ notificationChannels: ComplianceNotificationChannel.Call });
|
|
4377
|
+
}).toThrow(new BadDataException(NOT_A_CHANNEL_LIST_MESSAGE));
|
|
4378
|
+
});
|
|
4379
|
+
|
|
4380
|
+
test("touches nothing but the two channel fields", () => {
|
|
4381
|
+
const severities: Array<string> = [CRITICAL_INCIDENT];
|
|
4382
|
+
const options: JSONObject = { note: "kept" };
|
|
4383
|
+
|
|
4384
|
+
const written: Record<string, unknown> = normalised({
|
|
4385
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
4386
|
+
incidentSeverities: severities,
|
|
4387
|
+
options: options,
|
|
4388
|
+
enabled: false,
|
|
4389
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
4390
|
+
});
|
|
4391
|
+
|
|
4392
|
+
expect(Object.keys(written).sort()).toEqual(
|
|
4393
|
+
[
|
|
4394
|
+
"enabled",
|
|
4395
|
+
"incidentSeverities",
|
|
4396
|
+
"notificationChannel",
|
|
4397
|
+
"notificationChannels",
|
|
4398
|
+
"options",
|
|
4399
|
+
"ruleType",
|
|
4400
|
+
].sort(),
|
|
4401
|
+
);
|
|
4402
|
+
expect(written["incidentSeverities"]).toBe(severities);
|
|
4403
|
+
expect(written["options"]).toBe(options);
|
|
4404
|
+
expect(written["enabled"]).toBe(false);
|
|
4405
|
+
});
|
|
4406
|
+
|
|
4407
|
+
test("writes a new list: the one the caller sent is not changed or shared", () => {
|
|
4408
|
+
const sent: Array<ComplianceNotificationChannel> = [
|
|
4409
|
+
ComplianceNotificationChannel.Push,
|
|
4410
|
+
ComplianceNotificationChannel.Call,
|
|
4411
|
+
ComplianceNotificationChannel.Push,
|
|
4412
|
+
];
|
|
4413
|
+
const data: Record<string, unknown> = { notificationChannels: sent };
|
|
4414
|
+
|
|
4415
|
+
TeamComplianceSettingServiceClass.normaliseChannelFields(
|
|
4416
|
+
data as JSONObject,
|
|
4417
|
+
);
|
|
4418
|
+
|
|
4419
|
+
expect(sent).toEqual([
|
|
4420
|
+
ComplianceNotificationChannel.Push,
|
|
4421
|
+
ComplianceNotificationChannel.Call,
|
|
4422
|
+
ComplianceNotificationChannel.Push,
|
|
4423
|
+
]);
|
|
4424
|
+
expect(data["notificationChannels"]).not.toBe(sent);
|
|
4425
|
+
});
|
|
4426
|
+
|
|
4427
|
+
test("works on the model a create hands over as well as on an update's JSON", () => {
|
|
4428
|
+
const setting: TeamComplianceSetting = new TeamComplianceSetting();
|
|
4429
|
+
setting.notificationChannels = [
|
|
4430
|
+
ComplianceNotificationChannel.Push,
|
|
4431
|
+
ComplianceNotificationChannel.Call,
|
|
4432
|
+
];
|
|
4433
|
+
|
|
4434
|
+
TeamComplianceSettingServiceClass.normaliseChannelFields(setting);
|
|
4435
|
+
|
|
4436
|
+
expect(setting.notificationChannels).toEqual([
|
|
4437
|
+
ComplianceNotificationChannel.Call,
|
|
4438
|
+
ComplianceNotificationChannel.Push,
|
|
4439
|
+
]);
|
|
4440
|
+
expect(setting.notificationChannel).toBe(
|
|
4441
|
+
ComplianceNotificationChannel.Call,
|
|
4442
|
+
);
|
|
4443
|
+
});
|
|
4444
|
+
|
|
4445
|
+
test("every set of channels, sent as a list in any order, is read back as itself", () => {
|
|
4446
|
+
for (const subset of everyChannelSubset()) {
|
|
4447
|
+
const data: Record<string, unknown> = {
|
|
4448
|
+
notificationChannels: [...subset].reverse(),
|
|
4449
|
+
};
|
|
4450
|
+
|
|
4451
|
+
TeamComplianceSettingServiceClass.normaliseChannelFields(
|
|
4452
|
+
data as JSONObject,
|
|
4453
|
+
);
|
|
4454
|
+
|
|
4455
|
+
expect(channelsOf(data)).toEqual({
|
|
4456
|
+
notificationChannels: subset,
|
|
4457
|
+
notificationChannel: subset[0] || null,
|
|
4458
|
+
});
|
|
4459
|
+
expect(TeamComplianceSettingServiceClass.getStoredChannels(data)).toEqual(
|
|
4460
|
+
subset,
|
|
4461
|
+
);
|
|
4462
|
+
}
|
|
4463
|
+
});
|
|
4464
|
+
});
|
|
4465
|
+
|
|
4466
|
+
describe("TeamComplianceSettingService.getStoredChannels", () => {
|
|
4467
|
+
/*
|
|
4468
|
+
* A current build writes the pair together; an older build writes only the
|
|
4469
|
+
* single column and leaves the list as it was. So the list is what the rule
|
|
4470
|
+
* says while the single column still agrees with its first channel, and the
|
|
4471
|
+
* single column is what the rule says once it does not - or when there is
|
|
4472
|
+
* no list at all.
|
|
4473
|
+
*/
|
|
4474
|
+
test.each<
|
|
4475
|
+
[string, StoredChannelColumns, Array<ComplianceNotificationChannel>]
|
|
4476
|
+
>([
|
|
4477
|
+
[
|
|
4478
|
+
"a pair a current build wrote",
|
|
4479
|
+
{
|
|
4480
|
+
notificationChannels: [
|
|
4481
|
+
ComplianceNotificationChannel.Call,
|
|
4482
|
+
ComplianceNotificationChannel.Push,
|
|
4483
|
+
],
|
|
4484
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4485
|
+
},
|
|
4486
|
+
[ComplianceNotificationChannel.Call, ComplianceNotificationChannel.Push],
|
|
4487
|
+
],
|
|
4488
|
+
[
|
|
4489
|
+
"one channel a current build wrote",
|
|
4490
|
+
{
|
|
4491
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
4492
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
4493
|
+
},
|
|
4494
|
+
[ComplianceNotificationChannel.SMS],
|
|
4495
|
+
],
|
|
4496
|
+
[
|
|
4497
|
+
"'any channel' a current build wrote",
|
|
4498
|
+
{ notificationChannels: [], notificationChannel: null },
|
|
4499
|
+
[],
|
|
4500
|
+
],
|
|
4501
|
+
[
|
|
4502
|
+
"every channel",
|
|
4503
|
+
{
|
|
4504
|
+
notificationChannels: ALL_CHANNELS,
|
|
4505
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4506
|
+
},
|
|
4507
|
+
ALL_CHANNELS,
|
|
4508
|
+
],
|
|
4509
|
+
[
|
|
4510
|
+
"a row from before the list, with a channel",
|
|
4511
|
+
{
|
|
4512
|
+
notificationChannels: null,
|
|
4513
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4514
|
+
},
|
|
4515
|
+
[ComplianceNotificationChannel.Call],
|
|
4516
|
+
],
|
|
4517
|
+
[
|
|
4518
|
+
"a row from before the list, with no channel",
|
|
4519
|
+
{ notificationChannels: null, notificationChannel: null },
|
|
4520
|
+
[],
|
|
4521
|
+
],
|
|
4522
|
+
[
|
|
4523
|
+
"a row an older build created, its list never written",
|
|
4524
|
+
{
|
|
4525
|
+
notificationChannels: undefined,
|
|
4526
|
+
notificationChannel: ComplianceNotificationChannel.Push,
|
|
4527
|
+
},
|
|
4528
|
+
[ComplianceNotificationChannel.Push],
|
|
4529
|
+
],
|
|
4530
|
+
[
|
|
4531
|
+
"a list an older build re-pointed to another channel",
|
|
4532
|
+
{
|
|
4533
|
+
notificationChannels: [
|
|
4534
|
+
ComplianceNotificationChannel.Call,
|
|
4535
|
+
ComplianceNotificationChannel.Push,
|
|
4536
|
+
],
|
|
4537
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
4538
|
+
},
|
|
4539
|
+
[ComplianceNotificationChannel.SMS],
|
|
4540
|
+
],
|
|
4541
|
+
[
|
|
4542
|
+
"a list an older build cleared to 'any channel'",
|
|
4543
|
+
{
|
|
4544
|
+
notificationChannels: [
|
|
4545
|
+
ComplianceNotificationChannel.Call,
|
|
4546
|
+
ComplianceNotificationChannel.Push,
|
|
4547
|
+
],
|
|
4548
|
+
notificationChannel: null,
|
|
4549
|
+
},
|
|
4550
|
+
[],
|
|
4551
|
+
],
|
|
4552
|
+
[
|
|
4553
|
+
"'any channel' an older build put a channel on",
|
|
4554
|
+
{
|
|
4555
|
+
notificationChannels: [],
|
|
4556
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4557
|
+
},
|
|
4558
|
+
[ComplianceNotificationChannel.Call],
|
|
4559
|
+
],
|
|
4560
|
+
[
|
|
4561
|
+
"a list whose rule an older build re-saved without touching its channel",
|
|
4562
|
+
{
|
|
4563
|
+
notificationChannels: [
|
|
4564
|
+
ComplianceNotificationChannel.Call,
|
|
4565
|
+
ComplianceNotificationChannel.Push,
|
|
4566
|
+
],
|
|
4567
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4568
|
+
},
|
|
4569
|
+
[ComplianceNotificationChannel.Call, ComplianceNotificationChannel.Push],
|
|
4570
|
+
],
|
|
4571
|
+
[
|
|
4572
|
+
"a list a newer build wrote, led by a channel this one does not know",
|
|
4573
|
+
{
|
|
4574
|
+
notificationChannels: ["Pager", ComplianceNotificationChannel.Call],
|
|
4575
|
+
notificationChannel: "Pager",
|
|
4576
|
+
},
|
|
4577
|
+
[ComplianceNotificationChannel.Call],
|
|
4578
|
+
],
|
|
4579
|
+
[
|
|
4580
|
+
"a list stored out of catalog order that agrees with its own first channel",
|
|
4581
|
+
{
|
|
4582
|
+
notificationChannels: [
|
|
4583
|
+
ComplianceNotificationChannel.Push,
|
|
4584
|
+
ComplianceNotificationChannel.Call,
|
|
4585
|
+
],
|
|
4586
|
+
notificationChannel: ComplianceNotificationChannel.Push,
|
|
4587
|
+
},
|
|
4588
|
+
[ComplianceNotificationChannel.Call, ComplianceNotificationChannel.Push],
|
|
4589
|
+
],
|
|
4590
|
+
[
|
|
4591
|
+
"a list holding a channel twice",
|
|
4592
|
+
{
|
|
4593
|
+
notificationChannels: [
|
|
4594
|
+
ComplianceNotificationChannel.Call,
|
|
4595
|
+
ComplianceNotificationChannel.Call,
|
|
4596
|
+
ComplianceNotificationChannel.Push,
|
|
4597
|
+
],
|
|
4598
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4599
|
+
},
|
|
4600
|
+
[ComplianceNotificationChannel.Call, ComplianceNotificationChannel.Push],
|
|
4601
|
+
],
|
|
4602
|
+
[
|
|
4603
|
+
"no list, and a single channel this build does not know",
|
|
4604
|
+
{ notificationChannels: null, notificationChannel: "Pager" },
|
|
4605
|
+
[],
|
|
4606
|
+
],
|
|
4607
|
+
[
|
|
4608
|
+
"a list column holding a bare string",
|
|
4609
|
+
{
|
|
4610
|
+
notificationChannels: ComplianceNotificationChannel.Push,
|
|
4611
|
+
notificationChannel: ComplianceNotificationChannel.Push,
|
|
4612
|
+
},
|
|
4613
|
+
[ComplianceNotificationChannel.Push],
|
|
4614
|
+
],
|
|
4615
|
+
["neither column", {}, []],
|
|
4616
|
+
])(
|
|
4617
|
+
"reads %s",
|
|
4618
|
+
(
|
|
4619
|
+
_label: string,
|
|
4620
|
+
row: StoredChannelColumns,
|
|
4621
|
+
channels: Array<ComplianceNotificationChannel>,
|
|
4622
|
+
) => {
|
|
4623
|
+
expect(TeamComplianceSettingServiceClass.getStoredChannels(row)).toEqual(
|
|
4624
|
+
channels,
|
|
4625
|
+
);
|
|
4626
|
+
},
|
|
4627
|
+
);
|
|
4628
|
+
|
|
4629
|
+
test("an older build's single channel wins over every list it disagrees with", () => {
|
|
4630
|
+
const lists: Array<Array<ComplianceNotificationChannel>> = [
|
|
4631
|
+
[],
|
|
4632
|
+
[ComplianceNotificationChannel.Call, ComplianceNotificationChannel.Push],
|
|
4633
|
+
ALL_CHANNELS,
|
|
4634
|
+
];
|
|
4635
|
+
|
|
4636
|
+
for (const channel of ALL_CHANNELS) {
|
|
4637
|
+
for (const list of lists) {
|
|
4638
|
+
if ((list[0] || null) === channel) {
|
|
4639
|
+
continue;
|
|
4640
|
+
}
|
|
4641
|
+
|
|
4642
|
+
expect({
|
|
4643
|
+
list,
|
|
4644
|
+
channel,
|
|
4645
|
+
read: TeamComplianceSettingServiceClass.getStoredChannels({
|
|
4646
|
+
notificationChannels: list,
|
|
4647
|
+
notificationChannel: channel,
|
|
4648
|
+
}),
|
|
4649
|
+
}).toEqual({ list, channel, read: [channel] });
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
});
|
|
4653
|
+
|
|
4654
|
+
test("reads a row as findBy hands it over", () => {
|
|
4655
|
+
expect(
|
|
4656
|
+
TeamComplianceSettingServiceClass.getStoredChannels(
|
|
4657
|
+
storedRule({ ...CALL_AND_PUSH_FOR_CRITICAL }),
|
|
4658
|
+
),
|
|
4659
|
+
).toEqual([
|
|
4660
|
+
ComplianceNotificationChannel.Call,
|
|
4661
|
+
ComplianceNotificationChannel.Push,
|
|
4662
|
+
]);
|
|
4663
|
+
expect(
|
|
4664
|
+
TeamComplianceSettingServiceClass.getStoredChannels(
|
|
4665
|
+
storedRule({
|
|
4666
|
+
id: SETTING_ID,
|
|
4667
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
4668
|
+
notificationChannel: ComplianceNotificationChannel.Push,
|
|
4669
|
+
}),
|
|
4670
|
+
),
|
|
4671
|
+
).toEqual([ComplianceNotificationChannel.Push]);
|
|
4672
|
+
});
|
|
4673
|
+
|
|
4674
|
+
test("changes nothing on the row it reads", () => {
|
|
4675
|
+
const row: StoredChannelColumns = {
|
|
4676
|
+
notificationChannels: [
|
|
4677
|
+
ComplianceNotificationChannel.Push,
|
|
4678
|
+
ComplianceNotificationChannel.Call,
|
|
4679
|
+
],
|
|
4680
|
+
notificationChannel: ComplianceNotificationChannel.Push,
|
|
4681
|
+
};
|
|
4682
|
+
|
|
4683
|
+
TeamComplianceSettingServiceClass.getStoredChannels(row);
|
|
4684
|
+
|
|
4685
|
+
expect(row).toEqual({
|
|
4686
|
+
notificationChannels: [
|
|
4687
|
+
ComplianceNotificationChannel.Push,
|
|
4688
|
+
ComplianceNotificationChannel.Call,
|
|
4689
|
+
],
|
|
4690
|
+
notificationChannel: ComplianceNotificationChannel.Push,
|
|
4691
|
+
});
|
|
4692
|
+
});
|
|
4693
|
+
});
|
|
4694
|
+
|
|
4695
|
+
describe("TeamComplianceSettingService.getScope / isSameScope - channel lists", () => {
|
|
4696
|
+
const scopeOf: (channels: unknown) => ComplianceRuleScope = (
|
|
4697
|
+
channels: unknown,
|
|
4698
|
+
): ComplianceRuleScope => {
|
|
4699
|
+
return TeamComplianceSettingServiceClass.getScope({
|
|
4700
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
4701
|
+
notificationChannels: channels,
|
|
4702
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
4703
|
+
alertSeverities: undefined,
|
|
4704
|
+
});
|
|
4705
|
+
};
|
|
4706
|
+
|
|
4707
|
+
test("the same channels in another order, or with a repeat, are the same scope", () => {
|
|
4708
|
+
const callAndPush: ComplianceRuleScope = scopeOf([
|
|
4709
|
+
ComplianceNotificationChannel.Call,
|
|
4710
|
+
ComplianceNotificationChannel.Push,
|
|
4711
|
+
]);
|
|
4712
|
+
|
|
4713
|
+
for (const channels of [
|
|
4714
|
+
[ComplianceNotificationChannel.Push, ComplianceNotificationChannel.Call],
|
|
4715
|
+
[
|
|
4716
|
+
ComplianceNotificationChannel.Push,
|
|
4717
|
+
ComplianceNotificationChannel.Call,
|
|
4718
|
+
ComplianceNotificationChannel.Push,
|
|
4719
|
+
],
|
|
4720
|
+
]) {
|
|
4721
|
+
expect(
|
|
4722
|
+
TeamComplianceSettingServiceClass.isSameScope(
|
|
4723
|
+
callAndPush,
|
|
4724
|
+
scopeOf(channels),
|
|
4725
|
+
),
|
|
4726
|
+
).toBe(true);
|
|
4727
|
+
}
|
|
4728
|
+
});
|
|
4729
|
+
|
|
4730
|
+
test.each<[string, Array<ComplianceNotificationChannel>]>([
|
|
4731
|
+
["one of its channels", [ComplianceNotificationChannel.Call]],
|
|
4732
|
+
[
|
|
4733
|
+
"a channel more",
|
|
4734
|
+
[
|
|
4735
|
+
ComplianceNotificationChannel.Call,
|
|
4736
|
+
ComplianceNotificationChannel.SMS,
|
|
4737
|
+
ComplianceNotificationChannel.Push,
|
|
4738
|
+
],
|
|
4739
|
+
],
|
|
4740
|
+
[
|
|
4741
|
+
"another pair",
|
|
4742
|
+
[ComplianceNotificationChannel.Call, ComplianceNotificationChannel.SMS],
|
|
4743
|
+
],
|
|
4744
|
+
["any channel", []],
|
|
4745
|
+
])(
|
|
4746
|
+
"'Call and Push' differs from %s",
|
|
4747
|
+
(_label: string, channels: Array<ComplianceNotificationChannel>) => {
|
|
4748
|
+
const callAndPush: ComplianceRuleScope = scopeOf([
|
|
4749
|
+
ComplianceNotificationChannel.Call,
|
|
4750
|
+
ComplianceNotificationChannel.Push,
|
|
4751
|
+
]);
|
|
4752
|
+
|
|
4753
|
+
expect(
|
|
4754
|
+
TeamComplianceSettingServiceClass.isSameScope(
|
|
4755
|
+
callAndPush,
|
|
4756
|
+
scopeOf(channels),
|
|
4757
|
+
),
|
|
4758
|
+
).toBe(false);
|
|
4759
|
+
expect(
|
|
4760
|
+
TeamComplianceSettingServiceClass.isSameScope(
|
|
4761
|
+
scopeOf(channels),
|
|
4762
|
+
callAndPush,
|
|
4763
|
+
),
|
|
4764
|
+
).toBe(false);
|
|
4765
|
+
},
|
|
4766
|
+
);
|
|
4767
|
+
|
|
4768
|
+
test("two different sets of channels never compare the same", () => {
|
|
4769
|
+
const subsets: Array<Array<ComplianceNotificationChannel>> =
|
|
4770
|
+
everyChannelSubset().filter(
|
|
4771
|
+
(subset: Array<ComplianceNotificationChannel>): boolean => {
|
|
4772
|
+
return subset.length <= 2;
|
|
4773
|
+
},
|
|
4774
|
+
);
|
|
4775
|
+
|
|
4776
|
+
for (const a of subsets) {
|
|
4777
|
+
for (const b of subsets) {
|
|
4778
|
+
expect({
|
|
4779
|
+
a,
|
|
4780
|
+
b,
|
|
4781
|
+
same: TeamComplianceSettingServiceClass.isSameScope(
|
|
4782
|
+
scopeOf(a),
|
|
4783
|
+
scopeOf([...b].reverse()),
|
|
4784
|
+
),
|
|
4785
|
+
}).toEqual({ a, b, same: a.join(",") === b.join(",") });
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
});
|
|
4789
|
+
});
|
|
4790
|
+
|
|
4791
|
+
describe("TeamComplianceSettingService onBeforeCreate - a rule on several channels", () => {
|
|
4792
|
+
test("is stored as the canonical list - catalog order, each channel once - with its first channel beside it", async () => {
|
|
4793
|
+
const result: OnCreate<TeamComplianceSetting> = await create({
|
|
4794
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
4795
|
+
notificationChannels: [
|
|
4796
|
+
ComplianceNotificationChannel.Push,
|
|
4797
|
+
ComplianceNotificationChannel.SMS,
|
|
4798
|
+
ComplianceNotificationChannel.Call,
|
|
4799
|
+
ComplianceNotificationChannel.Push,
|
|
4800
|
+
],
|
|
4801
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
4802
|
+
});
|
|
4803
|
+
|
|
4804
|
+
expect(channelsOf(result.createBy.data)).toEqual({
|
|
4805
|
+
notificationChannels: [
|
|
4806
|
+
ComplianceNotificationChannel.Call,
|
|
4807
|
+
ComplianceNotificationChannel.SMS,
|
|
4808
|
+
ComplianceNotificationChannel.Push,
|
|
4809
|
+
],
|
|
4810
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4811
|
+
});
|
|
4812
|
+
});
|
|
4813
|
+
|
|
4814
|
+
test.each(ON_CALL_RULE_TYPES)(
|
|
4815
|
+
"%s may insist on every channel there is",
|
|
4816
|
+
async (ruleType: ComplianceRuleType) => {
|
|
4817
|
+
const result: OnCreate<TeamComplianceSetting> = await create({
|
|
4818
|
+
ruleType: ruleType,
|
|
4819
|
+
notificationChannels: [...ALL_CHANNELS].reverse(),
|
|
4820
|
+
});
|
|
4821
|
+
|
|
4822
|
+
expect(channelsOf(result.createBy.data)).toEqual({
|
|
4823
|
+
notificationChannels: ALL_CHANNELS,
|
|
4824
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4825
|
+
});
|
|
4826
|
+
},
|
|
4827
|
+
);
|
|
4828
|
+
|
|
4829
|
+
test("is refused as a duplicate of a rule on the same channels, picked in any order", async () => {
|
|
4830
|
+
store(CALL_AND_PUSH_FOR_CRITICAL);
|
|
4831
|
+
|
|
4832
|
+
for (const channels of [
|
|
4833
|
+
[ComplianceNotificationChannel.Call, ComplianceNotificationChannel.Push],
|
|
4834
|
+
[ComplianceNotificationChannel.Push, ComplianceNotificationChannel.Call],
|
|
4835
|
+
[
|
|
4836
|
+
ComplianceNotificationChannel.Push,
|
|
4837
|
+
ComplianceNotificationChannel.Call,
|
|
4838
|
+
ComplianceNotificationChannel.Push,
|
|
4839
|
+
],
|
|
4840
|
+
]) {
|
|
4841
|
+
await expect(
|
|
4842
|
+
create({
|
|
4843
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
4844
|
+
notificationChannels: channels,
|
|
4845
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
4846
|
+
}),
|
|
4847
|
+
).rejects.toThrow(
|
|
4848
|
+
new BadDataException(DUPLICATE_COMPLIANCE_RULE_MESSAGE),
|
|
4849
|
+
);
|
|
4850
|
+
}
|
|
4851
|
+
});
|
|
4852
|
+
|
|
4853
|
+
test.each<[string, Array<ComplianceNotificationChannel>, Array<string>]>([
|
|
4854
|
+
[
|
|
4855
|
+
"one of its channels",
|
|
4856
|
+
[ComplianceNotificationChannel.Call],
|
|
4857
|
+
[CRITICAL_INCIDENT],
|
|
4858
|
+
],
|
|
4859
|
+
[
|
|
4860
|
+
"the other of its channels",
|
|
4861
|
+
[ComplianceNotificationChannel.Push],
|
|
4862
|
+
[CRITICAL_INCIDENT],
|
|
4863
|
+
],
|
|
4864
|
+
[
|
|
4865
|
+
"a channel more",
|
|
4866
|
+
[
|
|
4867
|
+
ComplianceNotificationChannel.Call,
|
|
4868
|
+
ComplianceNotificationChannel.SMS,
|
|
4869
|
+
ComplianceNotificationChannel.Push,
|
|
4870
|
+
],
|
|
4871
|
+
[CRITICAL_INCIDENT],
|
|
4872
|
+
],
|
|
4873
|
+
[
|
|
4874
|
+
"another pair",
|
|
4875
|
+
[ComplianceNotificationChannel.Call, ComplianceNotificationChannel.SMS],
|
|
4876
|
+
[CRITICAL_INCIDENT],
|
|
4877
|
+
],
|
|
4878
|
+
["any channel", [], [CRITICAL_INCIDENT]],
|
|
4879
|
+
[
|
|
4880
|
+
"the same channels for another severity",
|
|
4881
|
+
[ComplianceNotificationChannel.Call, ComplianceNotificationChannel.Push],
|
|
4882
|
+
[MAJOR_INCIDENT],
|
|
4883
|
+
],
|
|
4884
|
+
[
|
|
4885
|
+
"the same channels for every severity",
|
|
4886
|
+
[ComplianceNotificationChannel.Push, ComplianceNotificationChannel.Call],
|
|
4887
|
+
[],
|
|
4888
|
+
],
|
|
4889
|
+
])(
|
|
4890
|
+
"is a different rule from 'Call and Push for Critical' when it asks for %s",
|
|
4891
|
+
async (
|
|
4892
|
+
_label: string,
|
|
4893
|
+
channels: Array<ComplianceNotificationChannel>,
|
|
4894
|
+
severities: Array<string>,
|
|
4895
|
+
) => {
|
|
4896
|
+
store(CALL_AND_PUSH_FOR_CRITICAL);
|
|
4897
|
+
|
|
4898
|
+
await expect(
|
|
4899
|
+
create({
|
|
4900
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
4901
|
+
notificationChannels: channels,
|
|
4902
|
+
incidentSeverities: severities,
|
|
4903
|
+
}),
|
|
4904
|
+
).resolves.toBeDefined();
|
|
4905
|
+
},
|
|
4906
|
+
);
|
|
4907
|
+
|
|
4908
|
+
test("the same channels on the episode rule type are another rule", async () => {
|
|
4909
|
+
store(CALL_AND_PUSH_FOR_CRITICAL);
|
|
4910
|
+
|
|
4911
|
+
await expect(
|
|
4912
|
+
create({
|
|
4913
|
+
ruleType: ComplianceRuleType.HasIncidentEpisodeOnCallRules,
|
|
4914
|
+
notificationChannels: [
|
|
4915
|
+
ComplianceNotificationChannel.Call,
|
|
4916
|
+
ComplianceNotificationChannel.Push,
|
|
4917
|
+
],
|
|
4918
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
4919
|
+
}),
|
|
4920
|
+
).resolves.toBeDefined();
|
|
4921
|
+
});
|
|
4922
|
+
|
|
4923
|
+
test.each<[string, DatabaseCommonInteractionProps]>([
|
|
4924
|
+
["a team editor", EDITOR_PROPS],
|
|
4925
|
+
["Edit Teams on its own", memberProps([Permission.EditProjectTeam])],
|
|
4926
|
+
])(
|
|
4927
|
+
"what the hook leaves - the list and the channel it adds beside it - passes the create check DatabaseService repeats, for %s",
|
|
4928
|
+
async (_label: string, props: DatabaseCommonInteractionProps) => {
|
|
4929
|
+
const result: OnCreate<TeamComplianceSetting> = await onBeforeCreate(
|
|
4930
|
+
createByFor(
|
|
4931
|
+
newRule({
|
|
4932
|
+
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
4933
|
+
notificationChannels: [
|
|
4934
|
+
ComplianceNotificationChannel.Push,
|
|
4935
|
+
ComplianceNotificationChannel.SMS,
|
|
4936
|
+
],
|
|
4937
|
+
alertSeverities: [CRITICAL_ALERT],
|
|
4938
|
+
}),
|
|
4939
|
+
props,
|
|
4940
|
+
),
|
|
4941
|
+
);
|
|
4942
|
+
|
|
4943
|
+
expect(channelsOf(result.createBy.data)).toEqual({
|
|
4944
|
+
notificationChannels: [
|
|
4945
|
+
ComplianceNotificationChannel.SMS,
|
|
4946
|
+
ComplianceNotificationChannel.Push,
|
|
4947
|
+
],
|
|
4948
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
4949
|
+
});
|
|
4950
|
+
expect((): void => {
|
|
4951
|
+
ModelPermission.checkCreatePermissions(
|
|
4952
|
+
TeamComplianceSetting,
|
|
4953
|
+
result.createBy.data,
|
|
4954
|
+
props,
|
|
4955
|
+
);
|
|
4956
|
+
}).not.toThrow();
|
|
4957
|
+
},
|
|
4958
|
+
);
|
|
4959
|
+
|
|
4960
|
+
test("a caller with no say in the project is refused before a list is even looked at", async () => {
|
|
4961
|
+
await expect(
|
|
4962
|
+
onBeforeCreate(
|
|
4963
|
+
createByFor(
|
|
4964
|
+
newRule({
|
|
4965
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
4966
|
+
notificationChannels: [ComplianceNotificationChannel.Call, "Pager"],
|
|
4967
|
+
}),
|
|
4968
|
+
OUTSIDER_PROPS,
|
|
4969
|
+
),
|
|
4970
|
+
),
|
|
4971
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
4972
|
+
|
|
4973
|
+
expectNoReads();
|
|
4974
|
+
});
|
|
4975
|
+
});
|
|
4976
|
+
|
|
4977
|
+
describe("TeamComplianceSettingService onBeforeUpdate - channel lists", () => {
|
|
4978
|
+
test("a list is written canonical, with its first channel beside it", async () => {
|
|
4979
|
+
store(INCIDENT_CALL_CRITICAL);
|
|
4980
|
+
|
|
4981
|
+
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor({
|
|
4982
|
+
notificationChannels: [
|
|
4983
|
+
ComplianceNotificationChannel.Push,
|
|
4984
|
+
ComplianceNotificationChannel.Call,
|
|
4985
|
+
],
|
|
4986
|
+
});
|
|
4987
|
+
|
|
4988
|
+
await onBeforeUpdate(updateBy);
|
|
4989
|
+
|
|
4990
|
+
expect(dataOf(updateBy)).toEqual({
|
|
4991
|
+
notificationChannels: [
|
|
4992
|
+
ComplianceNotificationChannel.Call,
|
|
4993
|
+
ComplianceNotificationChannel.Push,
|
|
4994
|
+
],
|
|
4995
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
4996
|
+
});
|
|
4997
|
+
});
|
|
4998
|
+
|
|
4999
|
+
test("an update that sends only the list is a scope change: it is checked for duplicates, as a set", async () => {
|
|
5000
|
+
store(INCIDENT_CALL_CRITICAL, CALL_AND_PUSH_FOR_CRITICAL);
|
|
5001
|
+
|
|
5002
|
+
await expect(
|
|
5003
|
+
update({
|
|
5004
|
+
notificationChannels: [
|
|
5005
|
+
ComplianceNotificationChannel.Push,
|
|
5006
|
+
ComplianceNotificationChannel.Call,
|
|
5007
|
+
],
|
|
5008
|
+
}),
|
|
5009
|
+
).rejects.toThrow(new BadDataException(DUPLICATE_COMPLIANCE_RULE_MESSAGE));
|
|
5010
|
+
|
|
5011
|
+
expect(siblingReads()).toHaveLength(1);
|
|
5012
|
+
});
|
|
5013
|
+
|
|
5014
|
+
test("an update that sends only the list is a scope change: the caller's permission is checked before anything is read", async () => {
|
|
5015
|
+
store(INCIDENT_CALL_CRITICAL);
|
|
5016
|
+
|
|
5017
|
+
await expect(
|
|
5018
|
+
onBeforeUpdate(
|
|
5019
|
+
updateByFor(
|
|
5020
|
+
{ notificationChannels: [ComplianceNotificationChannel.Push] },
|
|
5021
|
+
{ props: OUTSIDER_PROPS },
|
|
5022
|
+
),
|
|
5023
|
+
),
|
|
5024
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
5025
|
+
|
|
5026
|
+
expectNoReads();
|
|
5027
|
+
});
|
|
5028
|
+
|
|
5029
|
+
test("adding a channel to a rule makes a rule neither it nor its one-channel sibling is", async () => {
|
|
5030
|
+
store(INCIDENT_CALL_CRITICAL, {
|
|
5031
|
+
...INCIDENT_CALL_CRITICAL,
|
|
5032
|
+
id: SIBLING_SETTING_ID,
|
|
5033
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
5034
|
+
});
|
|
5035
|
+
|
|
5036
|
+
await expect(
|
|
5037
|
+
update({
|
|
5038
|
+
notificationChannels: [
|
|
5039
|
+
ComplianceNotificationChannel.Call,
|
|
5040
|
+
ComplianceNotificationChannel.Push,
|
|
5041
|
+
],
|
|
5042
|
+
}),
|
|
5043
|
+
).resolves.toBeDefined();
|
|
5044
|
+
});
|
|
5045
|
+
|
|
5046
|
+
test("a channel change keeps the stored severities when it is judged", async () => {
|
|
5047
|
+
store(INCIDENT_CALL_CRITICAL, {
|
|
5048
|
+
...CALL_AND_PUSH_FOR_CRITICAL,
|
|
5049
|
+
incidentSeverityIds: [MAJOR_INCIDENT],
|
|
5050
|
+
});
|
|
5051
|
+
|
|
5052
|
+
// "Call and Push for Critical" - the sibling is for Major.
|
|
5053
|
+
await expect(
|
|
5054
|
+
update({
|
|
5055
|
+
notificationChannels: [
|
|
5056
|
+
ComplianceNotificationChannel.Push,
|
|
5057
|
+
ComplianceNotificationChannel.Call,
|
|
5058
|
+
],
|
|
5059
|
+
}),
|
|
5060
|
+
).resolves.toBeDefined();
|
|
5061
|
+
|
|
5062
|
+
// A severity change keeps the stored channels: "Call for Major" is new too.
|
|
5063
|
+
await expect(
|
|
5064
|
+
update({ incidentSeverities: [MAJOR_INCIDENT] }),
|
|
5065
|
+
).resolves.toBeDefined();
|
|
5066
|
+
});
|
|
5067
|
+
|
|
5068
|
+
test.each<[string, Record<string, unknown>, string]>([
|
|
5069
|
+
[
|
|
5070
|
+
"a list with an unknown channel",
|
|
5071
|
+
{ notificationChannels: [ComplianceNotificationChannel.Call, "Pager"] },
|
|
5072
|
+
UNKNOWN_CHANNEL_MESSAGE("Pager"),
|
|
5073
|
+
],
|
|
5074
|
+
[
|
|
5075
|
+
"a list with a channel in the wrong case",
|
|
5076
|
+
{ notificationChannels: ["push"] },
|
|
5077
|
+
UNKNOWN_CHANNEL_MESSAGE("push"),
|
|
5078
|
+
],
|
|
5079
|
+
[
|
|
5080
|
+
"channels that are not a list",
|
|
5081
|
+
{ notificationChannels: ComplianceNotificationChannel.Call },
|
|
5082
|
+
NOT_A_CHANNEL_LIST_MESSAGE,
|
|
5083
|
+
],
|
|
5084
|
+
[
|
|
5085
|
+
"a garbage single channel beside a good list",
|
|
5086
|
+
{
|
|
5087
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
5088
|
+
notificationChannel: "Pager",
|
|
5089
|
+
},
|
|
5090
|
+
UNKNOWN_CHANNEL_MESSAGE("Pager"),
|
|
5091
|
+
],
|
|
5092
|
+
])(
|
|
5093
|
+
"refuses %s before anything is read",
|
|
5094
|
+
async (_label: string, data: Record<string, unknown>, message: string) => {
|
|
5095
|
+
store(INCIDENT_CALL_CRITICAL);
|
|
5096
|
+
|
|
5097
|
+
await expect(update(data)).rejects.toThrow(new BadDataException(message));
|
|
5098
|
+
expectNoReads();
|
|
5099
|
+
},
|
|
5100
|
+
);
|
|
5101
|
+
|
|
5102
|
+
test("a type change to a method rule clears a stored list of several channels", async () => {
|
|
5103
|
+
store({
|
|
5104
|
+
...INCIDENT_CALL_CRITICAL,
|
|
5105
|
+
notificationChannels: [
|
|
5106
|
+
ComplianceNotificationChannel.Call,
|
|
5107
|
+
ComplianceNotificationChannel.SMS,
|
|
5108
|
+
ComplianceNotificationChannel.Push,
|
|
5109
|
+
],
|
|
5110
|
+
});
|
|
5111
|
+
|
|
5112
|
+
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor({
|
|
5113
|
+
ruleType: ComplianceRuleType.HasNotificationEmailMethod,
|
|
5114
|
+
});
|
|
5115
|
+
|
|
5116
|
+
await onBeforeUpdate(updateBy);
|
|
5117
|
+
|
|
5118
|
+
expect(channelsOf(dataOf(updateBy))).toEqual({
|
|
5119
|
+
notificationChannels: [],
|
|
5120
|
+
notificationChannel: null,
|
|
5121
|
+
});
|
|
5122
|
+
});
|
|
5123
|
+
|
|
5124
|
+
test("a type change to another on-call rule keeps the stored channels, and judges the new rule by them", async () => {
|
|
5125
|
+
store(
|
|
5126
|
+
{
|
|
5127
|
+
...INCIDENT_CALL_CRITICAL,
|
|
5128
|
+
notificationChannels: [
|
|
5129
|
+
ComplianceNotificationChannel.Call,
|
|
5130
|
+
ComplianceNotificationChannel.Push,
|
|
5131
|
+
],
|
|
5132
|
+
},
|
|
5133
|
+
{
|
|
5134
|
+
...CALL_AND_PUSH_FOR_CRITICAL,
|
|
5135
|
+
ruleType: ComplianceRuleType.HasIncidentEpisodeOnCallRules,
|
|
5136
|
+
},
|
|
5137
|
+
);
|
|
5138
|
+
|
|
5139
|
+
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor({
|
|
5140
|
+
ruleType: ComplianceRuleType.HasIncidentEpisodeOnCallRules,
|
|
5141
|
+
});
|
|
5142
|
+
|
|
5143
|
+
await expect(onBeforeUpdate(updateBy)).rejects.toThrow(
|
|
5144
|
+
DUPLICATE_COMPLIANCE_RULE_MESSAGE,
|
|
5145
|
+
);
|
|
5146
|
+
// Not sent, so not rewritten: the row keeps the list it has.
|
|
5147
|
+
expect("notificationChannels" in dataOf(updateBy)).toBe(false);
|
|
5148
|
+
expect("notificationChannel" in dataOf(updateBy)).toBe(false);
|
|
5149
|
+
});
|
|
5150
|
+
|
|
5151
|
+
test("what the hook leaves for any channel payload passes the update check _updateBy repeats", async () => {
|
|
5152
|
+
store(INCIDENT_CALL_CRITICAL);
|
|
5153
|
+
|
|
5154
|
+
for (const data of [
|
|
5155
|
+
{
|
|
5156
|
+
notificationChannels: [
|
|
5157
|
+
ComplianceNotificationChannel.Push,
|
|
5158
|
+
ComplianceNotificationChannel.Call,
|
|
5159
|
+
],
|
|
5160
|
+
},
|
|
5161
|
+
{ notificationChannels: null },
|
|
5162
|
+
{ notificationChannel: ComplianceNotificationChannel.SMS },
|
|
5163
|
+
{ notificationChannel: null },
|
|
5164
|
+
]) {
|
|
5165
|
+
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor({
|
|
5166
|
+
...data,
|
|
5167
|
+
});
|
|
5168
|
+
|
|
5169
|
+
await onBeforeUpdate(updateBy);
|
|
5170
|
+
|
|
5171
|
+
expect(Object.keys(dataOf(updateBy)).sort()).toEqual([
|
|
5172
|
+
"notificationChannel",
|
|
5173
|
+
"notificationChannels",
|
|
5174
|
+
]);
|
|
5175
|
+
await expect(
|
|
5176
|
+
ModelPermission.checkUpdateQueryPermissions(
|
|
5177
|
+
TeamComplianceSetting,
|
|
5178
|
+
{ _id: SETTING_ID },
|
|
5179
|
+
updateBy.data,
|
|
5180
|
+
EDITOR_PROPS,
|
|
5181
|
+
),
|
|
5182
|
+
).resolves.toBeDefined();
|
|
5183
|
+
}
|
|
5184
|
+
});
|
|
5185
|
+
});
|
|
5186
|
+
|
|
5187
|
+
describe("TeamComplianceSettingService - a payload from before the channel list", () => {
|
|
5188
|
+
/*
|
|
5189
|
+
* API clients written before a rule could require several channels, and a
|
|
5190
|
+
* Dashboard bundle from before the list, send notificationChannel alone. It
|
|
5191
|
+
* still means what it always meant: exactly that channel, or any channel.
|
|
5192
|
+
* Before the list existed the model had no other field, so dropping it -
|
|
5193
|
+
* reading such a payload as "any channel" - would silently widen the rule.
|
|
5194
|
+
*/
|
|
5195
|
+
test("create: one channel is stored as a one-item list, with itself beside it", async () => {
|
|
5196
|
+
const result: OnCreate<TeamComplianceSetting> = await create({
|
|
5197
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5198
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
5199
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
5200
|
+
});
|
|
5201
|
+
|
|
5202
|
+
expect(channelsOf(result.createBy.data)).toEqual({
|
|
5203
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
5204
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
5205
|
+
});
|
|
5206
|
+
});
|
|
5207
|
+
|
|
5208
|
+
test("create: null is 'any channel'", async () => {
|
|
5209
|
+
const result: OnCreate<TeamComplianceSetting> = await create({
|
|
5210
|
+
ruleType: ComplianceRuleType.HasAlertOnCallRules,
|
|
5211
|
+
notificationChannel: null,
|
|
5212
|
+
});
|
|
5213
|
+
|
|
5214
|
+
expect(channelsOf(result.createBy.data)).toEqual({
|
|
5215
|
+
notificationChannels: [],
|
|
5216
|
+
notificationChannel: null,
|
|
5217
|
+
});
|
|
5218
|
+
});
|
|
5219
|
+
|
|
5220
|
+
test("create: a channel sent the old way duplicates a rule stored with that channel alone in its list", async () => {
|
|
5221
|
+
store({
|
|
5222
|
+
id: SIBLING_SETTING_ID,
|
|
5223
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5224
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
5225
|
+
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
5226
|
+
});
|
|
5227
|
+
|
|
5228
|
+
await expect(
|
|
5229
|
+
create({
|
|
5230
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5231
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
5232
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
5233
|
+
}),
|
|
5234
|
+
).rejects.toThrow(new BadDataException(DUPLICATE_COMPLIANCE_RULE_MESSAGE));
|
|
5235
|
+
});
|
|
5236
|
+
|
|
5237
|
+
test("create: ...but not a rule on that channel and another", async () => {
|
|
5238
|
+
store(CALL_AND_PUSH_FOR_CRITICAL);
|
|
5239
|
+
|
|
5240
|
+
await expect(
|
|
5241
|
+
create({
|
|
5242
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5243
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
5244
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
5245
|
+
}),
|
|
5246
|
+
).resolves.toBeDefined();
|
|
5247
|
+
});
|
|
5248
|
+
|
|
5249
|
+
test("update: one channel replaces the rule's whole list", async () => {
|
|
5250
|
+
store({ ...CALL_AND_PUSH_FOR_CRITICAL, id: SETTING_ID });
|
|
5251
|
+
|
|
5252
|
+
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor({
|
|
5253
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
5254
|
+
});
|
|
5255
|
+
|
|
5256
|
+
await onBeforeUpdate(updateBy);
|
|
5257
|
+
|
|
5258
|
+
expect(dataOf(updateBy)).toEqual({
|
|
5259
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
5260
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
5261
|
+
});
|
|
5262
|
+
});
|
|
5263
|
+
|
|
5264
|
+
test("update: null clears the list to 'any channel' - a scope change, checked for duplicates", async () => {
|
|
5265
|
+
store(INCIDENT_CALL_CRITICAL, {
|
|
5266
|
+
id: SIBLING_SETTING_ID,
|
|
5267
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5268
|
+
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
5269
|
+
});
|
|
5270
|
+
|
|
5271
|
+
await expect(update({ notificationChannel: null })).rejects.toThrow(
|
|
5272
|
+
DUPLICATE_COMPLIANCE_RULE_MESSAGE,
|
|
5273
|
+
);
|
|
5274
|
+
});
|
|
5275
|
+
|
|
5276
|
+
test("update: a channel sent the old way is a scope change, so the caller's permission is checked before anything is read", async () => {
|
|
5277
|
+
store(INCIDENT_CALL_CRITICAL);
|
|
5278
|
+
|
|
5279
|
+
await expect(
|
|
5280
|
+
onBeforeUpdate(
|
|
5281
|
+
updateByFor(
|
|
5282
|
+
{ notificationChannel: ComplianceNotificationChannel.Push },
|
|
5283
|
+
{ props: OUTSIDER_PROPS },
|
|
5284
|
+
),
|
|
5285
|
+
),
|
|
5286
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
5287
|
+
|
|
5288
|
+
expectNoReads();
|
|
5289
|
+
});
|
|
5290
|
+
|
|
5291
|
+
test("update: a rule sent back as it was read - the list and its first channel - keeps its list", async () => {
|
|
5292
|
+
store({ ...CALL_AND_PUSH_FOR_CRITICAL, id: SETTING_ID });
|
|
5293
|
+
|
|
5294
|
+
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor({
|
|
5295
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5296
|
+
notificationChannels: [
|
|
5297
|
+
ComplianceNotificationChannel.Call,
|
|
5298
|
+
ComplianceNotificationChannel.Push,
|
|
5299
|
+
],
|
|
5300
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
5301
|
+
incidentSeverities: [{ _id: CRITICAL_INCIDENT }],
|
|
5302
|
+
alertSeverities: [],
|
|
5303
|
+
enabled: true,
|
|
5304
|
+
});
|
|
5305
|
+
|
|
5306
|
+
await expect(onBeforeUpdate(updateBy)).resolves.toBeDefined();
|
|
5307
|
+
|
|
5308
|
+
expect(channelsOf(dataOf(updateBy))).toEqual({
|
|
5309
|
+
notificationChannels: [
|
|
5310
|
+
ComplianceNotificationChannel.Call,
|
|
5311
|
+
ComplianceNotificationChannel.Push,
|
|
5312
|
+
],
|
|
5313
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
5314
|
+
});
|
|
5315
|
+
});
|
|
5316
|
+
|
|
5317
|
+
test("update: a list changed beside the single channel it was read with is written as the new list", async () => {
|
|
5318
|
+
store(INCIDENT_CALL_CRITICAL);
|
|
5319
|
+
|
|
5320
|
+
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor({
|
|
5321
|
+
notificationChannels: [
|
|
5322
|
+
ComplianceNotificationChannel.Push,
|
|
5323
|
+
ComplianceNotificationChannel.SMS,
|
|
5324
|
+
],
|
|
5325
|
+
// Stale: what the rule was read with.
|
|
5326
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
5327
|
+
});
|
|
5328
|
+
|
|
5329
|
+
await onBeforeUpdate(updateBy);
|
|
5330
|
+
|
|
5331
|
+
expect(dataOf(updateBy)).toEqual({
|
|
5332
|
+
notificationChannels: [
|
|
5333
|
+
ComplianceNotificationChannel.SMS,
|
|
5334
|
+
ComplianceNotificationChannel.Push,
|
|
5335
|
+
],
|
|
5336
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
5337
|
+
});
|
|
5338
|
+
});
|
|
5339
|
+
|
|
5340
|
+
test("update: a channel sent the old way to a method rule is dropped, list and all", async () => {
|
|
5341
|
+
store({
|
|
5342
|
+
id: SETTING_ID,
|
|
5343
|
+
ruleType: ComplianceRuleType.HasNotificationSMSMethod,
|
|
5344
|
+
});
|
|
5345
|
+
|
|
5346
|
+
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor({
|
|
5347
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
5348
|
+
});
|
|
5349
|
+
|
|
5350
|
+
await onBeforeUpdate(updateBy);
|
|
5351
|
+
|
|
5352
|
+
expect(dataOf(updateBy)).toEqual({
|
|
5353
|
+
notificationChannels: [],
|
|
5354
|
+
notificationChannel: null,
|
|
5355
|
+
});
|
|
5356
|
+
});
|
|
5357
|
+
});
|
|
5358
|
+
|
|
5359
|
+
describe("TeamComplianceSettingService - rules an older build wrote", () => {
|
|
5360
|
+
/*
|
|
5361
|
+
* An older replica still serving during an upgrade - or the build a site
|
|
5362
|
+
* rolled back to - writes only notificationChannel and leaves the list as
|
|
5363
|
+
* it was. Its rows are judged by what that column says, so a rule such a
|
|
5364
|
+
* build created or changed is neither missed by the duplicate check nor
|
|
5365
|
+
* read as a rule it no longer is.
|
|
5366
|
+
*/
|
|
5367
|
+
test("a rule it created - no list, one channel - duplicates a new rule on that one channel", async () => {
|
|
5368
|
+
store({
|
|
5369
|
+
id: SIBLING_SETTING_ID,
|
|
5370
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5371
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
5372
|
+
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
5373
|
+
});
|
|
5374
|
+
|
|
5375
|
+
await expect(
|
|
5376
|
+
create({
|
|
5377
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5378
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
5379
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
5380
|
+
}),
|
|
5381
|
+
).rejects.toThrow(new BadDataException(DUPLICATE_COMPLIANCE_RULE_MESSAGE));
|
|
5382
|
+
|
|
5383
|
+
await expect(
|
|
5384
|
+
create({
|
|
5385
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5386
|
+
notificationChannels: [
|
|
5387
|
+
ComplianceNotificationChannel.Call,
|
|
5388
|
+
ComplianceNotificationChannel.Push,
|
|
5389
|
+
],
|
|
5390
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
5391
|
+
}),
|
|
5392
|
+
).resolves.toBeDefined();
|
|
5393
|
+
});
|
|
5394
|
+
|
|
5395
|
+
test("a rule it re-pointed to another channel is that channel only, whatever its list still says", async () => {
|
|
5396
|
+
store({
|
|
5397
|
+
...CALL_AND_PUSH_FOR_CRITICAL,
|
|
5398
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
5399
|
+
});
|
|
5400
|
+
|
|
5401
|
+
await expect(
|
|
5402
|
+
create({
|
|
5403
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5404
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
5405
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
5406
|
+
}),
|
|
5407
|
+
).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
|
|
5408
|
+
|
|
5409
|
+
await expect(
|
|
5410
|
+
create({
|
|
5411
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5412
|
+
notificationChannels: [
|
|
5413
|
+
ComplianceNotificationChannel.Call,
|
|
5414
|
+
ComplianceNotificationChannel.Push,
|
|
5415
|
+
],
|
|
5416
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
5417
|
+
}),
|
|
5418
|
+
).resolves.toBeDefined();
|
|
5419
|
+
});
|
|
5420
|
+
|
|
5421
|
+
test("a rule it cleared to 'any channel' is 'any channel', whatever its list still says", async () => {
|
|
5422
|
+
store({ ...CALL_AND_PUSH_FOR_CRITICAL, notificationChannel: null });
|
|
5423
|
+
|
|
5424
|
+
await expect(
|
|
5425
|
+
create({
|
|
5426
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5427
|
+
notificationChannels: [],
|
|
5428
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
5429
|
+
}),
|
|
5430
|
+
).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
|
|
5431
|
+
|
|
5432
|
+
await expect(
|
|
5433
|
+
create({
|
|
5434
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5435
|
+
notificationChannels: [
|
|
5436
|
+
ComplianceNotificationChannel.Call,
|
|
5437
|
+
ComplianceNotificationChannel.Push,
|
|
5438
|
+
],
|
|
5439
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
5440
|
+
}),
|
|
5441
|
+
).resolves.toBeDefined();
|
|
5442
|
+
});
|
|
5443
|
+
|
|
5444
|
+
test("a rule it re-saved without touching the channel keeps its whole list", async () => {
|
|
5445
|
+
store({
|
|
5446
|
+
...CALL_AND_PUSH_FOR_CRITICAL,
|
|
5447
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
5448
|
+
});
|
|
5449
|
+
|
|
5450
|
+
await expect(
|
|
5451
|
+
create({
|
|
5452
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5453
|
+
notificationChannels: [
|
|
5454
|
+
ComplianceNotificationChannel.Push,
|
|
5455
|
+
ComplianceNotificationChannel.Call,
|
|
5456
|
+
],
|
|
5457
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
5458
|
+
}),
|
|
5459
|
+
).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
|
|
5460
|
+
|
|
5461
|
+
await expect(
|
|
5462
|
+
create({
|
|
5463
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5464
|
+
notificationChannels: [ComplianceNotificationChannel.Call],
|
|
5465
|
+
incidentSeverities: [CRITICAL_INCIDENT],
|
|
5466
|
+
}),
|
|
5467
|
+
).resolves.toBeDefined();
|
|
5468
|
+
});
|
|
5469
|
+
|
|
5470
|
+
test("the rule being updated is judged by its single channel when its list disagrees", async () => {
|
|
5471
|
+
store(
|
|
5472
|
+
{
|
|
5473
|
+
// SMS for Critical: an older build re-pointed "Call and Push".
|
|
5474
|
+
...CALL_AND_PUSH_FOR_CRITICAL,
|
|
5475
|
+
id: SETTING_ID,
|
|
5476
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
5477
|
+
},
|
|
5478
|
+
{
|
|
5479
|
+
id: SIBLING_SETTING_ID,
|
|
5480
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5481
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
5482
|
+
incidentSeverityIds: [MAJOR_INCIDENT],
|
|
5483
|
+
},
|
|
5484
|
+
);
|
|
5485
|
+
|
|
5486
|
+
// Re-scoped to Major it is "SMS for Major" - the sibling.
|
|
5487
|
+
await expect(
|
|
5488
|
+
update({ incidentSeverities: [MAJOR_INCIDENT] }),
|
|
5489
|
+
).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
|
|
5490
|
+
});
|
|
5491
|
+
|
|
5492
|
+
test("...and by its list when the two agree", async () => {
|
|
5493
|
+
store(
|
|
5494
|
+
{ ...CALL_AND_PUSH_FOR_CRITICAL, id: SETTING_ID },
|
|
5495
|
+
{
|
|
5496
|
+
id: SIBLING_SETTING_ID,
|
|
5497
|
+
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
5498
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
5499
|
+
incidentSeverityIds: [MAJOR_INCIDENT],
|
|
5500
|
+
},
|
|
5501
|
+
);
|
|
5502
|
+
|
|
5503
|
+
// "Call and Push for Major" is not "SMS for Major".
|
|
5504
|
+
await expect(
|
|
5505
|
+
update({ incidentSeverities: [MAJOR_INCIDENT] }),
|
|
5506
|
+
).resolves.toBeDefined();
|
|
5507
|
+
});
|
|
5508
|
+
|
|
5509
|
+
test("an update that sends the list writes the pair, so the row reads as its list again", async () => {
|
|
5510
|
+
store({
|
|
5511
|
+
...CALL_AND_PUSH_FOR_CRITICAL,
|
|
5512
|
+
id: SETTING_ID,
|
|
5513
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
5514
|
+
});
|
|
5515
|
+
|
|
5516
|
+
const updateBy: UpdateBy<TeamComplianceSetting> = updateByFor({
|
|
5517
|
+
notificationChannels: [
|
|
5518
|
+
ComplianceNotificationChannel.Push,
|
|
5519
|
+
ComplianceNotificationChannel.SMS,
|
|
5520
|
+
],
|
|
5521
|
+
});
|
|
5522
|
+
|
|
5523
|
+
await onBeforeUpdate(updateBy);
|
|
5524
|
+
|
|
5525
|
+
expect(channelsOf(dataOf(updateBy))).toEqual({
|
|
5526
|
+
notificationChannels: [
|
|
5527
|
+
ComplianceNotificationChannel.SMS,
|
|
5528
|
+
ComplianceNotificationChannel.Push,
|
|
5529
|
+
],
|
|
5530
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
5531
|
+
});
|
|
5532
|
+
expect(
|
|
5533
|
+
TeamComplianceSettingServiceClass.getStoredChannels(dataOf(updateBy)),
|
|
5534
|
+
).toEqual([
|
|
5535
|
+
ComplianceNotificationChannel.SMS,
|
|
5536
|
+
ComplianceNotificationChannel.Push,
|
|
5537
|
+
]);
|
|
5538
|
+
});
|
|
5539
|
+
});
|
|
5540
|
+
|
|
5541
|
+
/*
|
|
5542
|
+
* ---------------------------------------------------------------------------
|
|
5543
|
+
* Through updateOneById, the path BaseAPI takes.
|
|
5544
|
+
* ---------------------------------------------------------------------------
|
|
5545
|
+
*/
|
|
5546
|
+
|
|
5547
|
+
type InternalFindFunction = (
|
|
5548
|
+
findBy: JSONObject,
|
|
5549
|
+
) => Promise<Array<TeamComplianceSetting>>;
|
|
5550
|
+
|
|
5551
|
+
interface ServiceInternals {
|
|
5552
|
+
_findBy: InternalFindFunction;
|
|
5553
|
+
}
|
|
5554
|
+
|
|
5555
|
+
describe("TeamComplianceSettingService updates reach the write normalised", () => {
|
|
5556
|
+
let service: TeamComplianceSettingServiceClass;
|
|
5557
|
+
let save: jest.Mock;
|
|
5558
|
+
let repositoryUpdate: jest.Mock;
|
|
5559
|
+
|
|
5560
|
+
beforeEach(() => {
|
|
5561
|
+
service = new TeamComplianceSettingServiceClass();
|
|
5562
|
+
|
|
5563
|
+
store(INCIDENT_CALL_CRITICAL);
|
|
5564
|
+
|
|
5565
|
+
// The hook's reads, from the in-memory team.
|
|
5566
|
+
jest.spyOn(service, "findBy").mockImplementation(((findBy: {
|
|
5567
|
+
query: JSONObject;
|
|
5568
|
+
}) => {
|
|
5569
|
+
return Promise.resolve(
|
|
5570
|
+
storedSettings.filter((row: TeamComplianceSetting): boolean => {
|
|
5571
|
+
return matchesQuery(row, findBy.query);
|
|
5572
|
+
}),
|
|
5573
|
+
);
|
|
5574
|
+
}) as never);
|
|
5575
|
+
|
|
5576
|
+
// _updateBy's own internal find of the row it is about to write.
|
|
5577
|
+
jest
|
|
5578
|
+
.spyOn(service as unknown as ServiceInternals, "_findBy")
|
|
5579
|
+
.mockImplementation((): Promise<Array<TeamComplianceSetting>> => {
|
|
5580
|
+
const row: TeamComplianceSetting = new TeamComplianceSetting();
|
|
5581
|
+
row._id = SETTING_ID;
|
|
5582
|
+
row.projectId = PROJECT_ID;
|
|
5583
|
+
return Promise.resolve([row]);
|
|
5584
|
+
});
|
|
5585
|
+
|
|
5586
|
+
save = jest.fn((item: unknown) => {
|
|
5587
|
+
return Promise.resolve(item);
|
|
5588
|
+
});
|
|
5589
|
+
repositoryUpdate = jest.fn(() => {
|
|
5590
|
+
return Promise.resolve({ affected: 1 });
|
|
5591
|
+
});
|
|
5592
|
+
jest.spyOn(service, "getRepository").mockReturnValue({
|
|
5593
|
+
save: save,
|
|
5594
|
+
update: repositoryUpdate,
|
|
5595
|
+
} as never);
|
|
5596
|
+
|
|
5597
|
+
// The permission layer needs a database and is not what this is about.
|
|
5598
|
+
jest
|
|
5599
|
+
.spyOn(ModelPermission, "checkUpdateQueryPermissions")
|
|
5600
|
+
.mockImplementation(((_modelType: unknown, query: unknown) => {
|
|
5601
|
+
return Promise.resolve(query);
|
|
5602
|
+
}) as never);
|
|
5603
|
+
jest
|
|
5604
|
+
.spyOn(ModelPermission, "checkUpdatePermissionByModel")
|
|
5605
|
+
.mockResolvedValue(undefined as never);
|
|
5606
|
+
});
|
|
5607
|
+
|
|
5608
|
+
test("a toggle is a plain column update: no hook reads, no relation write", async () => {
|
|
5609
|
+
await service.updateOneById({
|
|
5610
|
+
id: new ObjectID(SETTING_ID),
|
|
5611
|
+
data: { enabled: false },
|
|
5612
|
+
props: { isRoot: true },
|
|
5613
|
+
});
|
|
5614
|
+
|
|
3950
5615
|
expect(service.findBy).not.toHaveBeenCalled();
|
|
3951
5616
|
expect(save).not.toHaveBeenCalled();
|
|
3952
5617
|
expect(repositoryUpdate).toHaveBeenCalledTimes(1);
|
|
@@ -3973,6 +5638,7 @@ describe("TeamComplianceSettingService updates reach the write normalised", () =
|
|
|
3973
5638
|
expect(written["ruleType"]).toBe(
|
|
3974
5639
|
ComplianceRuleType.HasNotificationEmailMethod,
|
|
3975
5640
|
);
|
|
5641
|
+
expect(written["notificationChannels"]).toEqual([]);
|
|
3976
5642
|
expect(written["notificationChannel"]).toBeNull();
|
|
3977
5643
|
expect(written["incidentSeverities"]).toEqual([]);
|
|
3978
5644
|
expect(written["alertSeverities"]).toEqual([]);
|
|
@@ -3982,14 +5648,14 @@ describe("TeamComplianceSettingService updates reach the write normalised", () =
|
|
|
3982
5648
|
store({
|
|
3983
5649
|
id: SIBLING_SETTING_ID,
|
|
3984
5650
|
ruleType: ComplianceRuleType.HasIncidentOnCallRules,
|
|
3985
|
-
|
|
5651
|
+
notificationChannels: [ComplianceNotificationChannel.Push],
|
|
3986
5652
|
incidentSeverityIds: [CRITICAL_INCIDENT],
|
|
3987
5653
|
});
|
|
3988
5654
|
|
|
3989
5655
|
await expect(
|
|
3990
5656
|
service.updateOneById({
|
|
3991
5657
|
id: new ObjectID(SETTING_ID),
|
|
3992
|
-
data: {
|
|
5658
|
+
data: { notificationChannels: [ComplianceNotificationChannel.Push] },
|
|
3993
5659
|
props: { isRoot: true },
|
|
3994
5660
|
}),
|
|
3995
5661
|
).rejects.toThrow(DUPLICATE_COMPLIANCE_RULE_MESSAGE);
|
|
@@ -4038,4 +5704,100 @@ describe("TeamComplianceSettingService updates reach the write normalised", () =
|
|
|
4038
5704
|
expect(save).not.toHaveBeenCalled();
|
|
4039
5705
|
expect(repositoryUpdate).not.toHaveBeenCalled();
|
|
4040
5706
|
});
|
|
5707
|
+
|
|
5708
|
+
/*
|
|
5709
|
+
* The channel columns are plain columns - the list is JSON, not a relation
|
|
5710
|
+
* - so an update that changes only them is a plain column update, and
|
|
5711
|
+
* reaches it as the pair the hook wrote.
|
|
5712
|
+
*/
|
|
5713
|
+
test("a channel list reaches the write canonical, with its first channel beside it", async () => {
|
|
5714
|
+
await service.updateOneById({
|
|
5715
|
+
id: new ObjectID(SETTING_ID),
|
|
5716
|
+
data: {
|
|
5717
|
+
notificationChannels: [
|
|
5718
|
+
ComplianceNotificationChannel.Push,
|
|
5719
|
+
ComplianceNotificationChannel.Call,
|
|
5720
|
+
ComplianceNotificationChannel.Push,
|
|
5721
|
+
],
|
|
5722
|
+
} as never,
|
|
5723
|
+
props: { isRoot: true },
|
|
5724
|
+
});
|
|
5725
|
+
|
|
5726
|
+
expect(save).not.toHaveBeenCalled();
|
|
5727
|
+
expect(repositoryUpdate).toHaveBeenCalledTimes(1);
|
|
5728
|
+
expect(repositoryUpdate.mock.calls[0]?.[1]).toMatchObject({
|
|
5729
|
+
notificationChannels: [
|
|
5730
|
+
ComplianceNotificationChannel.Call,
|
|
5731
|
+
ComplianceNotificationChannel.Push,
|
|
5732
|
+
],
|
|
5733
|
+
notificationChannel: ComplianceNotificationChannel.Call,
|
|
5734
|
+
});
|
|
5735
|
+
});
|
|
5736
|
+
|
|
5737
|
+
test("a channel sent the old way reaches the write as a one-item list", async () => {
|
|
5738
|
+
await service.updateOneById({
|
|
5739
|
+
id: new ObjectID(SETTING_ID),
|
|
5740
|
+
data: { notificationChannel: ComplianceNotificationChannel.SMS },
|
|
5741
|
+
props: { isRoot: true },
|
|
5742
|
+
});
|
|
5743
|
+
|
|
5744
|
+
expect(repositoryUpdate).toHaveBeenCalledTimes(1);
|
|
5745
|
+
expect(repositoryUpdate.mock.calls[0]?.[1]).toMatchObject({
|
|
5746
|
+
notificationChannels: [ComplianceNotificationChannel.SMS],
|
|
5747
|
+
notificationChannel: ComplianceNotificationChannel.SMS,
|
|
5748
|
+
});
|
|
5749
|
+
});
|
|
5750
|
+
|
|
5751
|
+
test("clearing the channels reaches the write as an empty list and no channel", async () => {
|
|
5752
|
+
await service.updateOneById({
|
|
5753
|
+
id: new ObjectID(SETTING_ID),
|
|
5754
|
+
data: { notificationChannels: [] } as never,
|
|
5755
|
+
props: { isRoot: true },
|
|
5756
|
+
});
|
|
5757
|
+
|
|
5758
|
+
expect(repositoryUpdate).toHaveBeenCalledTimes(1);
|
|
5759
|
+
expect(repositoryUpdate.mock.calls[0]?.[1]).toMatchObject({
|
|
5760
|
+
notificationChannels: [],
|
|
5761
|
+
notificationChannel: null,
|
|
5762
|
+
});
|
|
5763
|
+
});
|
|
5764
|
+
|
|
5765
|
+
test("a list with a channel this build does not know is refused before anything is written", async () => {
|
|
5766
|
+
await expect(
|
|
5767
|
+
service.updateOneById({
|
|
5768
|
+
id: new ObjectID(SETTING_ID),
|
|
5769
|
+
data: {
|
|
5770
|
+
notificationChannels: [ComplianceNotificationChannel.Call, "Pager"],
|
|
5771
|
+
} as never,
|
|
5772
|
+
props: { isRoot: true },
|
|
5773
|
+
}),
|
|
5774
|
+
).rejects.toThrow(UNKNOWN_CHANNEL_MESSAGE("Pager"));
|
|
5775
|
+
|
|
5776
|
+
expect(save).not.toHaveBeenCalled();
|
|
5777
|
+
expect(repositoryUpdate).not.toHaveBeenCalled();
|
|
5778
|
+
});
|
|
5779
|
+
|
|
5780
|
+
test("a rule type change clears a list of several channels in the same write", async () => {
|
|
5781
|
+
storedSettings = [];
|
|
5782
|
+
store({
|
|
5783
|
+
...INCIDENT_CALL_CRITICAL,
|
|
5784
|
+
notificationChannels: [
|
|
5785
|
+
ComplianceNotificationChannel.Call,
|
|
5786
|
+
ComplianceNotificationChannel.Push,
|
|
5787
|
+
],
|
|
5788
|
+
});
|
|
5789
|
+
|
|
5790
|
+
await service.updateOneById({
|
|
5791
|
+
id: new ObjectID(SETTING_ID),
|
|
5792
|
+
data: { ruleType: ComplianceRuleType.HasNotificationWebhookMethod },
|
|
5793
|
+
props: { isRoot: true },
|
|
5794
|
+
});
|
|
5795
|
+
|
|
5796
|
+
const written: Record<string, unknown> = save.mock.calls[0]?.[0] as Record<
|
|
5797
|
+
string,
|
|
5798
|
+
unknown
|
|
5799
|
+
>;
|
|
5800
|
+
expect(written["notificationChannels"]).toEqual([]);
|
|
5801
|
+
expect(written["notificationChannel"]).toBeNull();
|
|
5802
|
+
});
|
|
4041
5803
|
});
|