@oneuptime/common 12.0.7 → 12.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.ts +1 -1
- package/Models/AnalyticsModels/MetricItemAggMV1mByService.ts +1 -1
- package/Models/DatabaseModels/AIConversation.ts +32 -0
- package/Models/DatabaseModels/AIConversationMessage.ts +41 -0
- package/Models/DatabaseModels/AlertEpisodeMember.ts +27 -0
- package/Models/DatabaseModels/IncidentEpisodeMember.ts +28 -0
- package/Models/DatabaseModels/Index.ts +12 -4
- package/Models/DatabaseModels/{TelemetryEntity.ts → InventoryItem.ts} +184 -9
- package/Models/DatabaseModels/InventoryItemCustomField.ts +434 -0
- package/Models/DatabaseModels/{TelemetryEntityRelationship.ts → InventoryItemRelationship.ts} +33 -7
- package/Models/DatabaseModels/NetworkDevice.ts +141 -0
- package/Models/DatabaseModels/NetworkDeviceLink.ts +699 -0
- package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +467 -0
- package/Models/DatabaseModels/NetworkTopologySuppression.ts +429 -0
- package/Models/DatabaseModels/OnCallDutyPolicyFeed.ts +9 -0
- package/Models/DatabaseModels/Project.ts +78 -0
- package/Models/DatabaseModels/UserCall.ts +42 -0
- package/Models/DatabaseModels/UserEmail.ts +44 -0
- package/Models/DatabaseModels/UserNotificationRule.ts +425 -55
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +24 -2
- package/Models/DatabaseModels/UserPush.ts +49 -0
- package/Models/DatabaseModels/UserSMS.ts +43 -0
- package/Models/DatabaseModels/UserTelegram.ts +59 -0
- package/Models/DatabaseModels/UserWebhook.ts +52 -0
- package/Models/DatabaseModels/UserWhatsApp.ts +41 -0
- package/Models/DatabaseModels/WorkflowLog.ts +38 -0
- package/Models/DatabaseModels/WorkflowVariable.ts +12 -0
- package/Server/API/AIChatAPI.ts +315 -1
- package/Server/API/DashboardAPI.ts +217 -1
- package/Server/API/OnCallReadinessAPI.ts +841 -0
- package/Server/API/TeamComplianceAPI.ts +69 -17
- package/Server/API/TelemetryAPI.ts +220 -12
- package/Server/API/UserAPI.ts +16 -1
- package/Server/EnvironmentConfig.ts +52 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +22 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +4 -4
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +5 -5
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.ts +82 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.ts +91 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.ts +255 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.ts +107 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.ts +90 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.ts +39 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.ts +59 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +24 -0
- package/Server/Infrastructure/Queue.ts +78 -13
- package/Server/Middleware/MasterAdminAuthorization.ts +11 -6
- package/Server/Middleware/PublicDashboardRateLimit.ts +593 -0
- package/Server/Services/AIService.ts +7 -0
- package/Server/Services/AlertEpisodeStateTimelineService.ts +29 -0
- package/Server/Services/AlertSeverityService.ts +63 -0
- package/Server/Services/DashboardService.ts +9 -10
- package/Server/Services/DatabaseService.ts +32 -2
- package/Server/Services/IncidentEpisodeStateTimelineService.ts +29 -0
- package/Server/Services/IncidentSeverityService.ts +76 -0
- package/Server/Services/Index.ts +12 -4
- package/Server/Services/InventoryItemCustomFieldService.ts +9 -0
- package/Server/Services/{TelemetryEntityRelationshipService.ts → InventoryItemRelationshipService.ts} +7 -4
- package/Server/Services/{TelemetryEntityService.ts → InventoryItemService.ts} +203 -21
- package/Server/Services/LogAggregationService.ts +45 -8
- package/Server/Services/MetricAggregationService.ts +121 -0
- package/Server/Services/MetricService.ts +7 -7
- package/Server/Services/NetworkDeviceLinkRuleService.ts +10 -0
- package/Server/Services/NetworkDeviceLinkService.ts +84 -0
- package/Server/Services/NetworkDeviceService.ts +140 -0
- package/Server/Services/NetworkSiteService.ts +77 -25
- package/Server/Services/NetworkTopologySuppressionService.ts +84 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +41 -29
- package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +8 -0
- package/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +62 -13
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +61 -1
- package/Server/Services/OnCallNotificationAlertingService.ts +742 -0
- package/Server/Services/OnCallReadinessService.ts +2803 -0
- package/Server/Services/OnCallSetupReminderService.ts +955 -0
- package/Server/Services/ProfileAggregationService.ts +123 -0
- package/Server/Services/StatusPageService.ts +9 -10
- package/Server/Services/TeamComplianceService.ts +429 -252
- package/Server/Services/UserCallService.ts +26 -1
- package/Server/Services/UserEmailService.ts +26 -1
- package/Server/Services/UserNotificationRuleAdminService.ts +1183 -0
- package/Server/Services/UserNotificationRuleService.ts +3812 -333
- package/Server/Services/UserOnCallLogService.ts +561 -48
- package/Server/Services/UserPushService.ts +29 -0
- package/Server/Services/UserService.ts +11 -0
- package/Server/Services/UserSmsService.ts +26 -1
- package/Server/Services/UserTelegramService.ts +24 -1
- package/Server/Services/UserWebhookService.ts +28 -1
- package/Server/Services/UserWhatsAppService.ts +24 -1
- package/Server/Types/Database/Permissions/BasePermission.ts +19 -0
- package/Server/Types/Database/Permissions/CreatePermission.ts +164 -0
- package/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.ts +340 -0
- package/Server/Types/Database/Permissions/QueryPermission.ts +48 -0
- package/Server/Types/Database/Permissions/TenantPermission.ts +8 -1
- package/Server/Types/Workflow/Components/API/Delete.ts +1 -1
- package/Server/Types/Workflow/Components/API/Get.ts +1 -1
- package/Server/Types/Workflow/Components/API/Patch.ts +1 -1
- package/Server/Types/Workflow/Components/API/Post.ts +1 -1
- package/Server/Types/Workflow/Components/API/Put.ts +1 -1
- package/Server/Types/Workflow/Components/API/Utils.ts +44 -1
- package/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +29 -5
- package/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +18 -10
- package/Server/Types/Workflow/Components/BaseModel/ModelArguments.ts +55 -0
- package/Server/Types/Workflow/Components/Conditions/IfElse.ts +3 -17
- package/Server/Types/Workflow/Components/Email.ts +25 -7
- package/Server/Types/Workflow/Components/JavaScript.ts +10 -3
- package/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +1 -1
- package/Server/Types/Workflow/TriggerCode.ts +12 -0
- package/Server/Types/Workflow/Workflow.ts +5 -0
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +643 -48
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +32 -3
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +20 -6
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +7 -0
- package/Server/Utils/AI/Toolbox/AIActionTools.ts +2 -2
- package/Server/Utils/AI/Toolbox/AIMetaTools.ts +863 -0
- package/Server/Utils/AI/Toolbox/AlertTools.ts +177 -15
- package/Server/Utils/AI/Toolbox/IncidentTools.ts +191 -10
- package/Server/Utils/AI/Toolbox/Index.ts +48 -0
- package/Server/Utils/AI/Toolbox/MonitorTools.ts +298 -11
- package/Server/Utils/AI/Toolbox/NoteWriteTools.ts +295 -0
- package/Server/Utils/AI/Toolbox/OnCallTools.ts +1246 -0
- package/Server/Utils/AI/Toolbox/RunbookTools.ts +424 -0
- package/Server/Utils/AI/Toolbox/SloTools.ts +456 -0
- package/Server/Utils/AI/Toolbox/StatusPageTools.ts +559 -0
- package/Server/Utils/AI/Toolbox/TeamTools.ts +327 -0
- package/Server/Utils/AI/Toolbox/TimelineTools.ts +615 -0
- package/Server/Utils/AI/Toolbox/WorkflowProbeTools.ts +664 -0
- package/Server/Utils/ClientIp.ts +221 -0
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +47 -0
- package/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.ts +163 -0
- package/Server/Utils/Dashboard/PublicDashboardSloWidget.ts +147 -0
- package/Server/Utils/Express.ts +12 -17
- package/Server/Utils/LLM/LLMService.ts +85 -8
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +204 -10
- package/Server/Utils/SSRFProtection.ts +98 -23
- package/Server/Utils/StartServer.ts +12 -3
- package/Server/Utils/Telemetry/EntityRegistry.ts +205 -18
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +689 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +160 -52
- package/Server/Utils/VM/VMAPI.ts +56 -8
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +7 -3
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +2146 -0
- package/Tests/App/Dashboard/CreateWorkflowModal.test.tsx +561 -0
- package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +2470 -0
- package/Tests/App/Dashboard/MonitorCriteriaAttributeFilter.test.tsx +574 -0
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +1897 -0
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +3606 -0
- package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +784 -0
- package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +1119 -0
- package/Tests/App/Dashboard/SloWidgetFetching.test.tsx +531 -0
- package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +1312 -0
- package/Tests/App/Dashboard/UserSettingsSetupChecklistPage.test.tsx +1390 -0
- package/Tests/Models/InventoryItemModel.test.ts +174 -0
- package/Tests/Models/InventoryItemNaming.test.ts +302 -0
- package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +437 -0
- package/Tests/Server/API/DashboardPublicRateLimit.test.ts +659 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +18 -0
- package/Tests/Server/API/DashboardPublicSloAPI.test.ts +880 -0
- package/Tests/Server/API/Helpers.ts +24 -15
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +2680 -0
- package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +915 -0
- package/Tests/Server/API/UserProjectsAPI.test.ts +478 -4
- package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +533 -0
- package/Tests/Server/Infrastructure/Postgres/InventoryItemArchiveMigration.test.ts +213 -0
- package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +432 -0
- package/Tests/Server/Infrastructure/Queue.test.ts +293 -0
- package/Tests/Server/Middleware/PublicDashboardRateLimit.test.ts +1645 -0
- package/Tests/Server/Services/AdminRuleEditGuards.test.ts +2848 -0
- package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1393 -0
- package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +1802 -0
- package/Tests/Server/Services/EpisodeStateTimelineNote.test.ts +304 -0
- package/Tests/Server/Services/InventoryItemDisplayName.test.ts +339 -0
- package/Tests/Server/Services/InventoryItemManualCreate.test.ts +248 -0
- package/Tests/Server/Services/IpAllowlistSpoofing.test.ts +450 -0
- package/Tests/Server/Services/LogAggregationService.test.ts +235 -1
- package/Tests/Server/Services/MetricAggregationService.test.ts +231 -0
- package/Tests/Server/Services/MetricEntityMVKeyParity.test.ts +80 -29
- package/Tests/Server/Services/MetricServiceAggregate.test.ts +30 -30
- package/Tests/Server/Services/NetworkSiteService.test.ts +18 -3
- package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +1728 -0
- package/Tests/Server/Services/NotificationDeletionImpact.test.ts +2402 -0
- package/Tests/Server/Services/OnCallDutyPolicyExecutionLogTimelineGapFeed.test.ts +394 -0
- package/Tests/Server/Services/OnCallNotificationFallback.test.ts +1744 -0
- package/Tests/Server/Services/OnCallReadinessService.test.ts +4295 -0
- package/Tests/Server/Services/OnCallSetupReminder.test.ts +1272 -0
- package/Tests/Server/Services/OnCallWeeklyReadinessDigest.test.ts +1021 -0
- package/Tests/Server/Services/ProfileAggregationService.test.ts +296 -0
- package/Tests/Server/Services/SeverityCreationRuleBackfill.test.ts +1536 -0
- package/Tests/Server/Services/SeverityRuleBackfill.test.ts +1818 -0
- package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +1845 -0
- package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +1394 -0
- package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +1166 -0
- package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1468 -0
- package/Tests/Server/Services/UserOnCallLogNoNotificationRules.test.ts +1457 -0
- package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +1546 -0
- package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +529 -0
- package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +1219 -0
- package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +1089 -0
- package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +2 -1
- package/Tests/Server/Types/Workflow/Components/ApiComponentHeaders.test.ts +192 -0
- package/Tests/Server/Types/Workflow/Components/BaseModelDatabaseComponents.test.ts +190 -0
- package/Tests/Server/Types/Workflow/Components/ChatWebhookComponents.test.ts +44 -14
- package/Tests/Server/Types/Workflow/Components/Email.test.ts +151 -0
- package/Tests/Server/Types/Workflow/Components/IfElse.test.ts +98 -0
- package/Tests/Server/Types/Workflow/Components/JavaScript.test.ts +51 -0
- package/Tests/Server/Utils/AI/AIMetaTools.test.ts +586 -0
- package/Tests/Server/Utils/AI/AlertMonitorFilters.test.ts +582 -0
- package/Tests/Server/Utils/AI/ChatAgentRunner.test.ts +726 -0
- package/Tests/Server/Utils/AI/IncidentToolsFilters.test.ts +315 -0
- package/Tests/Server/Utils/AI/LLMServiceStopReason.test.ts +314 -0
- package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +26 -3
- package/Tests/Server/Utils/AI/NoteWriteTools.test.ts +268 -0
- package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +169 -0
- package/Tests/Server/Utils/AI/OnCallTools.test.ts +664 -0
- package/Tests/Server/Utils/AI/RunbookTools.test.ts +325 -0
- package/Tests/Server/Utils/AI/SloTools.test.ts +306 -0
- package/Tests/Server/Utils/AI/StatusPageTools.test.ts +391 -0
- package/Tests/Server/Utils/AI/TeamTools.test.ts +257 -0
- package/Tests/Server/Utils/AI/TimelineTools.test.ts +472 -0
- package/Tests/Server/Utils/AI/WorkflowProbeTools.test.ts +428 -0
- package/Tests/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.test.ts +152 -0
- package/Tests/Server/Utils/ClientIp.test.ts +438 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +171 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.test.ts +383 -0
- package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +23 -25
- package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +6 -0
- package/Tests/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.test.ts +307 -0
- package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +468 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorTelemetryDeepLinks.test.ts +460 -0
- package/Tests/Server/Utils/ResponseRateLimitStatusCodes.test.ts +137 -0
- package/Tests/Server/Utils/SSRFProtectionBypasses.test.ts +40 -8
- package/Tests/Server/Utils/SSRFProtectionUserInfo.test.ts +351 -0
- package/Tests/Server/Utils/Telemetry/EntityRegistryRetirement.test.ts +531 -0
- package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +322 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +356 -39
- package/Tests/Server/Utils/VM/VMAPISubstitution.test.ts +243 -0
- package/Tests/Types/IP/IP.test.ts +263 -0
- package/Tests/Types/IP/IPWhitelist.test.ts +197 -0
- package/Tests/Types/IP/IPv6.test.ts +12 -1
- package/Tests/Types/Monitor/SnmpOid.test.ts +64 -0
- package/Tests/Types/NetworkDevice/NetworkDeviceMonitoringMethod.test.ts +110 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +106 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeDifferential.test.ts +511 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageEndToEnd.test.ts +489 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageGapTolerance.test.ts +479 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageState.test.ts +822 -0
- package/Tests/Types/SerializableObjectDictionaryImportCycle.test.ts +197 -0
- package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +140 -0
- package/Tests/Types/Workflow/BaseModelComponents.test.ts +429 -0
- package/Tests/Types/Workflow/Components/BaseModel.test.ts +225 -0
- package/Tests/Types/Workflow/IntegrationCredentialMetadata.test.ts +100 -0
- package/Tests/Types/Workflow/StepTrace.test.ts +235 -0
- package/Tests/Types/Workflow/TemplateSyntax.test.ts +491 -0
- package/Tests/Types/Workflow/Templates.test.ts +1218 -0
- package/Tests/UI/Components/ActiveFilterChipsOpenRoute.test.tsx +82 -0
- package/Tests/UI/Components/ComponentsModal.test.tsx +564 -7
- package/Tests/UI/Components/CustomTimeRangeModal.test.tsx +459 -0
- package/Tests/UI/Components/DictionaryAttributeFilterRow.test.tsx +477 -0
- package/Tests/UI/Components/Forms/AnchoredFieldPopupKeyboard.test.tsx +382 -0
- package/Tests/UI/Components/Forms/ColorPickerPicking.test.tsx +569 -0
- package/Tests/UI/Components/Forms/ValidationJSON.test.ts +241 -0
- package/Tests/UI/Components/KeyboardShortcut.test.tsx +95 -0
- package/Tests/UI/Components/LogDetailsPanelCrossSignal.test.tsx +448 -0
- package/Tests/UI/Components/LogTimeRangePicker.test.tsx +42 -0
- package/Tests/UI/Components/LogsTableCrossLinks.test.tsx +263 -0
- package/Tests/UI/Components/ModalPortalDismissal.test.tsx +90 -0
- package/Tests/UI/Components/PendingProjectInvitations.test.tsx +913 -0
- package/Tests/UI/Components/SimpleLogViewer.test.tsx +228 -0
- package/Tests/UI/Components/TableRowSelectability.test.tsx +312 -0
- package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +49 -7
- package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +325 -0
- package/Tests/UI/Components/Workflow/GraphLint.test.ts +893 -0
- package/Tests/UI/Components/Workflow/GraphLintSummary.test.ts +755 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.test.ts +522 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditorServerContract.test.ts +193 -0
- package/Tests/UI/Components/Workflow/ModelSchema.test.ts +388 -0
- package/Tests/UI/Components/Workflow/RunStatusWatcher.test.ts +210 -0
- package/Tests/UI/Components/Workflow/StepTraceViewer.test.tsx +256 -0
- package/Tests/UI/Components/Workflow/UseRunWatch.test.tsx +665 -0
- package/Tests/UI/Components/Workflow/Utils.test.ts +192 -0
- package/Tests/UI/Components/Workflow/WorkflowIssuesModal.test.tsx +485 -0
- package/Tests/UI/Components/Workflow/WorkflowLogModal.test.tsx +478 -0
- package/Tests/UI/Components/Workflow/WorkflowStatusBar.test.tsx +379 -0
- package/Tests/UI/EsbuildConfig.test.ts +607 -0
- package/Tests/UI/Monitor/MonitorStepCriteriaView.test.tsx +432 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +11 -4
- package/Tests/UI/Utils/ModelAPICreateMiscData.test.ts +94 -0
- package/Tests/UI/Utils/Platform.test.ts +147 -0
- package/Tests/UI/Utils/ProjectInvitationDisplay.test.ts +357 -0
- package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +198 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +514 -0
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +727 -0
- package/Tests/Utils/Schema/AnalyticsModelSchema.test.ts +469 -0
- package/Tests/Utils/Schema/ModelSchema.test.ts +268 -0
- package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +698 -0
- package/Tests/Utils/Telemetry/EntityKeyNonTelemetry.test.ts +193 -0
- package/Tests/__mocks__/bullmq.js +55 -0
- package/Types/AI/AIChatMessageStatus.ts +8 -1
- package/Types/AI/AIChatTypes.ts +12 -0
- package/Types/Database/AccessControl/OwnerOnlyColumn.ts +88 -0
- package/Types/Exception/ExceptionCode.ts +2 -0
- package/Types/Exception/ServiceUnavailableException.ts +8 -0
- package/Types/Exception/TooManyRequestsException.ts +8 -0
- package/Types/IP/IP.ts +93 -47
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +80 -3
- package/Types/NetworkDevice/NetworkDeviceMonitoringMethod.ts +55 -0
- package/Types/OnCallDutyPolicy/Layer.ts +203 -149
- package/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.ts +13 -0
- package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +155 -10
- package/Types/Permission.ts +193 -0
- package/Types/SerializableObjectDictionary.ts +133 -39
- package/Types/Telemetry/EntityRelationshipType.ts +1 -1
- package/Types/Telemetry/EntitySource.ts +40 -0
- package/Types/Telemetry/EntityType.ts +28 -1
- package/Types/Telemetry/EntityTypeGroups.ts +65 -0
- package/Types/Workflow/Component.ts +29 -0
- package/Types/Workflow/Components/API.ts +35 -0
- package/Types/Workflow/Components/BaseModel.ts +77 -27
- package/Types/Workflow/Components/Discord.ts +1 -0
- package/Types/Workflow/Components/Email.ts +12 -3
- package/Types/Workflow/Components/JavaScript.ts +7 -0
- package/Types/Workflow/Components/MicrosoftTeams.ts +3 -2
- package/Types/Workflow/Components/Slack.ts +1 -0
- package/Types/Workflow/Components/Telegram.ts +1 -0
- package/Types/Workflow/StepTrace.ts +181 -0
- package/Types/Workflow/TemplateSyntax.ts +543 -0
- package/Types/Workflow/Templates.ts +2368 -0
- package/UI/Components/Calendar/Calendar.css +43 -0
- package/UI/Components/Calendar/Calendar.tsx +8 -0
- package/UI/Components/Card/Card.tsx +2 -2
- package/UI/Components/Checkbox/Checkbox.tsx +16 -0
- package/UI/Components/Date/CustomTimeRangeModal.tsx +278 -0
- package/UI/Components/Date/TimeRangePickerDropdown.tsx +250 -0
- package/UI/Components/Dictionary/Dictionary.tsx +71 -15
- package/UI/Components/FormModal/BasicFormModal.tsx +2 -1
- package/UI/Components/Forms/Fields/ColorPicker.tsx +66 -10
- package/UI/Components/Forms/Fields/IconPicker.tsx +48 -10
- package/UI/Components/Forms/Types/Field.ts +7 -0
- package/UI/Components/Forms/Validation.ts +63 -1
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +53 -3
- package/UI/Components/Input/Input.tsx +32 -3
- package/UI/Components/KeyboardShortcut/KeyboardKey.ts +185 -0
- package/UI/Components/KeyboardShortcut/KeyboardShortcut.tsx +87 -0
- package/UI/Components/LogsViewer/LogsViewer.tsx +28 -0
- package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +31 -0
- package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +18 -18
- package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +363 -14
- package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +11 -216
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +11 -0
- package/UI/Components/LogsViewer/components/LogsTable.tsx +155 -12
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +29 -0
- package/UI/Components/LogsViewer/types.ts +23 -0
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +9 -2
- package/UI/Components/Modal/Modal.tsx +37 -4
- package/UI/Components/Navbar/NavBarMenuModal.tsx +15 -30
- package/UI/Components/ProjectInvitations/PendingProjectInvitations.tsx +442 -0
- package/UI/Components/SimpleLogViewer/SimpleLogViewer.tsx +23 -1
- package/UI/Components/Table/Table.tsx +49 -16
- package/UI/Components/Table/TableBody.tsx +53 -28
- package/UI/Components/Table/TableHeader.tsx +13 -0
- package/UI/Components/Table/TableRow.tsx +58 -26
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +11 -210
- package/UI/Components/Workflow/ArgumentsForm.tsx +341 -8
- package/UI/Components/Workflow/Component.tsx +28 -26
- package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +26 -0
- package/UI/Components/Workflow/ComponentSettingsModal.tsx +79 -9
- package/UI/Components/Workflow/ComponentValuePickerModal.tsx +135 -7
- package/UI/Components/Workflow/ComponentsModal.tsx +116 -22
- package/UI/Components/Workflow/DocumentationViewer.tsx +59 -9
- package/UI/Components/Workflow/GraphLint.ts +628 -0
- package/UI/Components/Workflow/GraphLintSummary.ts +390 -0
- package/UI/Components/Workflow/ModelColumnEditor.tsx +528 -0
- package/UI/Components/Workflow/ModelSchema.ts +278 -0
- package/UI/Components/Workflow/RunForm.tsx +41 -7
- package/UI/Components/Workflow/RunStatusWatcher.ts +122 -0
- package/UI/Components/Workflow/StepTraceViewer.tsx +186 -0
- package/UI/Components/Workflow/UseRunWatch.ts +212 -0
- package/UI/Components/Workflow/Utils.ts +93 -1
- package/UI/Components/Workflow/VariableModal.tsx +6 -2
- package/UI/Components/Workflow/Workflow.tsx +126 -7
- package/UI/Components/Workflow/WorkflowIssuesModal.tsx +255 -0
- package/UI/Components/Workflow/WorkflowLogModal.tsx +128 -0
- package/UI/Components/Workflow/WorkflowStatusBar.tsx +224 -0
- package/UI/Types/LayeredDismissal.ts +31 -0
- package/UI/Types/UseAnchoredFieldPopup.ts +99 -0
- package/UI/Utils/AIChatExport/ConversationMarkdown.ts +10 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +7 -1
- package/UI/Utils/Platform.ts +149 -0
- package/UI/Utils/ProjectInvitationDisplay.ts +118 -0
- package/UI/esbuild-config.js +22 -1
- package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +187 -0
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +533 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +917 -155
- package/Utils/Telemetry/CrossSignalScope.ts +502 -0
- package/Utils/Telemetry/EntityKey.ts +71 -5
- package/Utils/Telemetry/EntityRelationship.ts +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js +1 -1
- package/build/dist/Models/DatabaseModels/AIConversation.js +32 -0
- package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIConversationMessage.js +42 -0
- package/build/dist/Models/DatabaseModels/AIConversationMessage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +28 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +29 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +12 -4
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/{TelemetryEntity.js → InventoryItem.js} +212 -29
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -0
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js +454 -0
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js.map +1 -0
- package/build/dist/Models/DatabaseModels/{TelemetryEntityRelationship.js → InventoryItemRelationship.js} +52 -25
- package/build/dist/Models/DatabaseModels/InventoryItemRelationship.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +141 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +719 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +475 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js +446 -0
- package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js +9 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +80 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserCall.js +46 -2
- package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserEmail.js +48 -2
- package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js +424 -55
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +24 -2
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserPush.js +51 -1
- package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSMS.js +47 -2
- package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTelegram.js +65 -3
- package/build/dist/Models/DatabaseModels/UserTelegram.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWebhook.js +56 -2
- package/build/dist/Models/DatabaseModels/UserWebhook.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js +45 -2
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowLog.js +39 -0
- package/build/dist/Models/DatabaseModels/WorkflowLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js +12 -0
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +237 -1
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +165 -13
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/OnCallReadinessAPI.js +599 -0
- package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -0
- package/build/dist/Server/API/TeamComplianceAPI.js +68 -9
- package/build/dist/Server/API/TeamComplianceAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +129 -18
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +16 -1
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +45 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +4 -4
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js +39 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js +150 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js +57 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js +69 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js +32 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js +48 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +24 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +72 -13
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -6
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/PublicDashboardRateLimit.js +399 -0
- package/build/dist/Server/Middleware/PublicDashboardRateLimit.js.map +1 -0
- package/build/dist/Server/Services/AIService.js +1 -0
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +24 -3
- package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/AlertSeverityService.js +54 -0
- package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
- package/build/dist/Server/Services/DashboardService.js +10 -9
- package/build/dist/Server/Services/DashboardService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +24 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +24 -3
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentSeverityService.js +67 -0
- package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +12 -4
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemCustomFieldService.js +9 -0
- package/build/dist/Server/Services/InventoryItemCustomFieldService.js.map +1 -0
- package/build/dist/Server/Services/{TelemetryEntityRelationshipService.js → InventoryItemRelationshipService.js} +9 -6
- package/build/dist/Server/Services/InventoryItemRelationshipService.js.map +1 -0
- package/build/dist/Server/Services/{TelemetryEntityService.js → InventoryItemService.js} +158 -23
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -0
- package/build/dist/Server/Services/LogAggregationService.js +27 -8
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricAggregationService.js +80 -0
- package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +6 -6
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js +9 -0
- package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceLinkService.js +71 -0
- package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceService.js +113 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +53 -13
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/NetworkTopologySuppressionService.js +85 -0
- package/build/dist/Server/Services/NetworkTopologySuppressionService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +48 -32
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +8 -0
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js +51 -12
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +57 -13
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallNotificationAlertingService.js +548 -0
- package/build/dist/Server/Services/OnCallNotificationAlertingService.js.map +1 -0
- package/build/dist/Server/Services/OnCallReadinessService.js +1961 -0
- package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -0
- package/build/dist/Server/Services/OnCallSetupReminderService.js +738 -0
- package/build/dist/Server/Services/OnCallSetupReminderService.js.map +1 -0
- package/build/dist/Server/Services/ProfileAggregationService.js +68 -4
- package/build/dist/Server/Services/ProfileAggregationService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +11 -10
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TeamComplianceService.js +312 -160
- package/build/dist/Server/Services/TeamComplianceService.js.map +1 -1
- package/build/dist/Server/Services/UserCallService.js +24 -1
- package/build/dist/Server/Services/UserCallService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +24 -1
- package/build/dist/Server/Services/UserEmailService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js +858 -0
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js +2830 -175
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogService.js +488 -43
- package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
- package/build/dist/Server/Services/UserPushService.js +26 -0
- package/build/dist/Server/Services/UserPushService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +10 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserSmsService.js +24 -1
- package/build/dist/Server/Services/UserSmsService.js.map +1 -1
- package/build/dist/Server/Services/UserTelegramService.js +22 -1
- package/build/dist/Server/Services/UserTelegramService.js.map +1 -1
- package/build/dist/Server/Services/UserWebhookService.js +25 -1
- package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
- package/build/dist/Server/Services/UserWhatsAppService.js +22 -1
- package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js +12 -1
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +126 -0
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js +254 -0
- package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js.map +1 -0
- package/build/dist/Server/Types/Database/Permissions/QueryPermission.js +47 -2
- package/build/dist/Server/Types/Database/Permissions/QueryPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +7 -0
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Get.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Post.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Put.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Utils.js +28 -0
- package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js +21 -5
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js +14 -10
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js +31 -0
- package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +3 -9
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Email.js +15 -4
- package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js +7 -2
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/TriggerCode.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +514 -56
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +20 -3
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +19 -6
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +7 -0
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js +2 -2
- package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js +692 -0
- package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +148 -12
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +157 -10
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +37 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js +259 -14
- package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js +235 -0
- package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +1000 -0
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js +356 -0
- package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +394 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js +465 -0
- package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js +280 -0
- package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js +527 -0
- package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js +548 -0
- package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js.map +1 -0
- package/build/dist/Server/Utils/ClientIp.js +137 -0
- package/build/dist/Server/Utils/ClientIp.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +38 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js +89 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js +77 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js.map +1 -0
- package/build/dist/Server/Utils/Express.js +12 -12
- package/build/dist/Server/Utils/Express.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +70 -7
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +121 -11
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +82 -21
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +12 -4
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +165 -18
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +507 -0
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +122 -47
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMAPI.js +51 -4
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +7 -3
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/AI/AIChatMessageStatus.js +8 -1
- package/build/dist/Types/AI/AIChatMessageStatus.js.map +1 -1
- package/build/dist/Types/AI/AIChatTypes.js +12 -0
- package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +60 -0
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js +2 -0
- package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
- package/build/dist/Types/Exception/ServiceUnavailableException.js +8 -0
- package/build/dist/Types/Exception/ServiceUnavailableException.js.map +1 -0
- package/build/dist/Types/Exception/TooManyRequestsException.js +8 -0
- package/build/dist/Types/Exception/TooManyRequestsException.js.map +1 -0
- package/build/dist/Types/IP/IP.js +87 -43
- package/build/dist/Types/IP/IP.js.map +1 -1
- package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js +50 -0
- package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +186 -123
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js +13 -0
- package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +105 -11
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
- package/build/dist/Types/Permission.js +174 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +133 -39
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityRelationshipType.js +1 -1
- package/build/dist/Types/Telemetry/EntitySource.js +39 -0
- package/build/dist/Types/Telemetry/EntitySource.js.map +1 -0
- package/build/dist/Types/Telemetry/EntityType.js +28 -1
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityTypeGroups.js +57 -0
- package/build/dist/Types/Telemetry/EntityTypeGroups.js.map +1 -0
- package/build/dist/Types/Workflow/Component.js +21 -0
- package/build/dist/Types/Workflow/Component.js.map +1 -1
- package/build/dist/Types/Workflow/Components/API.js +35 -0
- package/build/dist/Types/Workflow/Components/API.js.map +1 -1
- package/build/dist/Types/Workflow/Components/BaseModel.js +68 -26
- package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Discord.js +1 -0
- package/build/dist/Types/Workflow/Components/Discord.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Email.js +12 -3
- package/build/dist/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Types/Workflow/Components/JavaScript.js +7 -0
- package/build/dist/Types/Workflow/Components/JavaScript.js.map +1 -1
- package/build/dist/Types/Workflow/Components/MicrosoftTeams.js +3 -2
- package/build/dist/Types/Workflow/Components/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Slack.js +1 -0
- package/build/dist/Types/Workflow/Components/Slack.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Telegram.js +1 -0
- package/build/dist/Types/Workflow/Components/Telegram.js.map +1 -1
- package/build/dist/Types/Workflow/StepTrace.js +104 -0
- package/build/dist/Types/Workflow/StepTrace.js.map +1 -0
- package/build/dist/Types/Workflow/TemplateSyntax.js +338 -0
- package/build/dist/Types/Workflow/TemplateSyntax.js.map +1 -0
- package/build/dist/Types/Workflow/Templates.js +2111 -0
- package/build/dist/Types/Workflow/Templates.js.map +1 -0
- package/build/dist/UI/Components/Calendar/Calendar.js +1 -1
- package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
- package/build/dist/UI/Components/Checkbox/Checkbox.js +1 -1
- package/build/dist/UI/Components/Checkbox/Checkbox.js.map +1 -1
- package/build/dist/UI/Components/Date/CustomTimeRangeModal.js +126 -0
- package/build/dist/UI/Components/Date/CustomTimeRangeModal.js.map +1 -0
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +123 -0
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -0
- package/build/dist/UI/Components/Dictionary/Dictionary.js +47 -17
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +52 -13
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js +28 -12
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +44 -0
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +27 -4
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +13 -4
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js +163 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js.map +1 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js +47 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +4 -4
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +11 -1
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +10 -8
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +227 -14
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +5 -108
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +5 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +69 -5
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +8 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +9 -2
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +31 -5
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +8 -21
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js +251 -0
- package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js.map +1 -0
- package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js +9 -2
- package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +27 -15
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableBody.js +24 -18
- package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
- package/build/dist/UI/Components/Table/TableHeader.js +9 -1
- package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +29 -21
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +5 -105
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +261 -14
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Component.js +20 -19
- package/build/dist/UI/Components/Workflow/Component.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js +10 -1
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js +35 -7
- package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js +57 -7
- package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +53 -18
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/DocumentationViewer.js +19 -6
- package/build/dist/UI/Components/Workflow/DocumentationViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/GraphLint.js +425 -0
- package/build/dist/UI/Components/Workflow/GraphLint.js.map +1 -0
- package/build/dist/UI/Components/Workflow/GraphLintSummary.js +231 -0
- package/build/dist/UI/Components/Workflow/GraphLintSummary.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +320 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelSchema.js +156 -0
- package/build/dist/UI/Components/Workflow/ModelSchema.js.map +1 -0
- package/build/dist/UI/Components/Workflow/RunForm.js +22 -6
- package/build/dist/UI/Components/Workflow/RunForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js +76 -0
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js.map +1 -0
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js +76 -0
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -0
- package/build/dist/UI/Components/Workflow/UseRunWatch.js +123 -0
- package/build/dist/UI/Components/Workflow/UseRunWatch.js.map +1 -0
- package/build/dist/UI/Components/Workflow/Utils.js +73 -1
- package/build/dist/UI/Components/Workflow/Utils.js.map +1 -1
- package/build/dist/UI/Components/Workflow/VariableModal.js +3 -2
- package/build/dist/UI/Components/Workflow/VariableModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Workflow.js +92 -7
- package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
- package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js +99 -0
- package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js.map +1 -0
- package/build/dist/UI/Components/Workflow/WorkflowLogModal.js +56 -0
- package/build/dist/UI/Components/Workflow/WorkflowLogModal.js.map +1 -0
- package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js +92 -0
- package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js.map +1 -0
- package/build/dist/UI/Types/LayeredDismissal.js +21 -0
- package/build/dist/UI/Types/LayeredDismissal.js.map +1 -0
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js +74 -1
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js.map +1 -1
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +9 -0
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/Platform.js +118 -0
- package/build/dist/UI/Utils/Platform.js.map +1 -0
- package/build/dist/UI/Utils/ProjectInvitationDisplay.js +106 -0
- package/build/dist/UI/Utils/ProjectInvitationDisplay.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +108 -0
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +386 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +661 -126
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +328 -0
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -0
- package/build/dist/Utils/Telemetry/EntityKey.js +57 -5
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- package/build/dist/Utils/Telemetry/EntityRelationship.js +1 -1
- package/jest.config.json +1 -0
- package/package.json +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryEntity.js.map +0 -1
- package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +0 -1
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +0 -1
- package/build/dist/Server/Services/TelemetryEntityService.js.map +0 -1
|
@@ -0,0 +1,1536 @@
|
|
|
1
|
+
import AlertService from "../../../Server/Services/AlertService";
|
|
2
|
+
import AlertSeverityService, {
|
|
3
|
+
Service as AlertSeverityServiceClass,
|
|
4
|
+
} from "../../../Server/Services/AlertSeverityService";
|
|
5
|
+
import DatabaseService from "../../../Server/Services/DatabaseService";
|
|
6
|
+
import IncidentService from "../../../Server/Services/IncidentService";
|
|
7
|
+
import IncidentSeverityService, {
|
|
8
|
+
Service as IncidentSeverityServiceClass,
|
|
9
|
+
} from "../../../Server/Services/IncidentSeverityService";
|
|
10
|
+
import OnCallDutyPolicyExecutionLogTimelineService from "../../../Server/Services/OnCallDutyPolicyExecutionLogTimelineService";
|
|
11
|
+
import ProjectService from "../../../Server/Services/ProjectService";
|
|
12
|
+
import UserNotificationRuleService, {
|
|
13
|
+
FallbackNotificationOutcome,
|
|
14
|
+
} from "../../../Server/Services/UserNotificationRuleService";
|
|
15
|
+
import UserOnCallLogService from "../../../Server/Services/UserOnCallLogService";
|
|
16
|
+
import UserService from "../../../Server/Services/UserService";
|
|
17
|
+
import Queue, { QueueName } from "../../../Server/Infrastructure/Queue";
|
|
18
|
+
import CreateBy from "../../../Server/Types/Database/CreateBy";
|
|
19
|
+
import { OnCreate } from "../../../Server/Types/Database/Hooks";
|
|
20
|
+
import Alert from "../../../Models/DatabaseModels/Alert";
|
|
21
|
+
import AlertSeverity from "../../../Models/DatabaseModels/AlertSeverity";
|
|
22
|
+
import Incident from "../../../Models/DatabaseModels/Incident";
|
|
23
|
+
import IncidentSeverity from "../../../Models/DatabaseModels/IncidentSeverity";
|
|
24
|
+
import UserNotificationRule from "../../../Models/DatabaseModels/UserNotificationRule";
|
|
25
|
+
import UserOnCallLog from "../../../Models/DatabaseModels/UserOnCallLog";
|
|
26
|
+
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
|
|
27
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
28
|
+
import NotificationRuleType from "../../../Types/NotificationRule/NotificationRuleType";
|
|
29
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
30
|
+
import OnCallDutyExecutionLogTimelineStatus from "../../../Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus";
|
|
31
|
+
import PositiveNumber from "../../../Types/PositiveNumber";
|
|
32
|
+
import UserNotificationEventType from "../../../Types/UserNotification/UserNotificationEventType";
|
|
33
|
+
import UserNotificationExecutionStatus from "../../../Types/UserNotification/UserNotificationExecutionStatus";
|
|
34
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* GAP A — a severity created after a user joined was a severity that user had
|
|
38
|
+
* no notification rule for, and nothing anywhere backfilled one.
|
|
39
|
+
*
|
|
40
|
+
* The default rules a responder gets are written by
|
|
41
|
+
* UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod(),
|
|
42
|
+
* whose two severity-shaped halves — createIncidentOnCallRules() and
|
|
43
|
+
* createAlertOnCallRules() — iterate the severities that exist AT THE MOMENT
|
|
44
|
+
* THEY RUN. They run when a member joins a project, when a notification method
|
|
45
|
+
* is verified, and in the MigrateDefaultUserNotificationRule migration. They did
|
|
46
|
+
* NOT run when a severity was created, because neither IncidentSeverityService
|
|
47
|
+
* nor AlertSeverityService defined any create-success hook at all: both stopped
|
|
48
|
+
* at onBeforeCreate, whose entire job is order rearrangement.
|
|
49
|
+
*
|
|
50
|
+
* So "add a Sev4 a year into the project" was a silent paging outage for every
|
|
51
|
+
* existing user: UserOnCallLogService.onCreateSuccess counts rules with
|
|
52
|
+
* { userId, projectId, ruleType, incidentSeverityId }, got zero, wrote
|
|
53
|
+
* "No notification rules found for this user." into an execution log nobody
|
|
54
|
+
* reads, and returned without paging anyone.
|
|
55
|
+
*
|
|
56
|
+
* PHASE 1 CLOSED THIS, on two independent fronts, and this file now pins both:
|
|
57
|
+
*
|
|
58
|
+
* (A) Creating a severity enqueues
|
|
59
|
+
* "OnCallDutyPolicy:BackfillNotificationRulesForNewSeverities" onto the
|
|
60
|
+
* Worker queue. Proven structurally (onCreateSuccess is now an own
|
|
61
|
+
* property on both services, no longer DatabaseService's placeholder) and
|
|
62
|
+
* behaviourally (drive the hooks; assert the enqueue, its queue, its name
|
|
63
|
+
* and its payload). The fan-out itself stays OUT of the request — a
|
|
64
|
+
* project can hold thousands of responders — so "no rules written inline"
|
|
65
|
+
* is still asserted, but it no longer means "no backfill".
|
|
66
|
+
* (B) Rule creation is still severity-snapshot-driven, and that is fine now
|
|
67
|
+
* that something else revisits the snapshot. This section is unchanged: it
|
|
68
|
+
* documents why the backfill has to exist.
|
|
69
|
+
* (C) The runtime hole. A user with rules for [A, B] and a project with
|
|
70
|
+
* severities [A, B, C] still has zero rules matching a C-severity
|
|
71
|
+
* incident — but the page is no longer dropped. UserOnCallLogService now
|
|
72
|
+
* reaches for the verified-method fallback instead of the dead-end, so the
|
|
73
|
+
* responder is notified and the log says so.
|
|
74
|
+
*
|
|
75
|
+
* Assertions that used to carry the "GAP A" banner now carry "GAP A CLOSED" and
|
|
76
|
+
* state the post-Phase-1 behaviour.
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
* Mirrors the private constant in IncidentSeverityService / AlertSeverityService,
|
|
81
|
+
* which in turn mirrors the RunCron job name in
|
|
82
|
+
* App/FeatureSet/Workers/Jobs/OnCallDutyPolicy/BackfillNotificationRulesForNewSeverities.ts.
|
|
83
|
+
* If a rename ever misses one of the three, this is where it surfaces.
|
|
84
|
+
*/
|
|
85
|
+
const BACKFILL_JOB_NAME: string =
|
|
86
|
+
"OnCallDutyPolicy:BackfillNotificationRulesForNewSeverities";
|
|
87
|
+
|
|
88
|
+
const PROJECT_ID: ObjectID = new ObjectID("project-1");
|
|
89
|
+
const USER_ID: ObjectID = new ObjectID("user-1");
|
|
90
|
+
const USER_EMAIL_ID: ObjectID = new ObjectID("user-email-1");
|
|
91
|
+
const LOG_ID: ObjectID = new ObjectID("oncall-log-1");
|
|
92
|
+
const TIMELINE_ID: ObjectID = new ObjectID("timeline-1");
|
|
93
|
+
const INCIDENT_ID: ObjectID = new ObjectID("incident-1");
|
|
94
|
+
const ALERT_ID: ObjectID = new ObjectID("alert-1");
|
|
95
|
+
|
|
96
|
+
// The two severities that existed when the user joined, and the late arrival.
|
|
97
|
+
const SEV_A: ObjectID = new ObjectID("incident-severity-A");
|
|
98
|
+
const SEV_B: ObjectID = new ObjectID("incident-severity-B");
|
|
99
|
+
const SEV_C: ObjectID = new ObjectID("incident-severity-C-added-later");
|
|
100
|
+
|
|
101
|
+
const ALERT_SEV_A: ObjectID = new ObjectID("alert-severity-A");
|
|
102
|
+
const ALERT_SEV_B: ObjectID = new ObjectID("alert-severity-B");
|
|
103
|
+
const ALERT_SEV_C: ObjectID = new ObjectID("alert-severity-C-added-later");
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
* ------------------------------------------------------------------------- *
|
|
107
|
+
* A tiny in-memory stand-in for the UserNotificationRule table.
|
|
108
|
+
*
|
|
109
|
+
* Both halves of the gap are read through it: section B writes into it via the
|
|
110
|
+
* real createIncidentOnCallRules()/createAlertOnCallRules() code paths, and
|
|
111
|
+
* section C then queries it with exactly the shape UserOnCallLogService uses.
|
|
112
|
+
* That is deliberate — the coverage hole is only meaningful if the rows the
|
|
113
|
+
* writer produced are the rows the reader misses.
|
|
114
|
+
* -------------------------------------------------------------------------
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
interface StoredRule {
|
|
118
|
+
id: ObjectID;
|
|
119
|
+
projectId: string | undefined;
|
|
120
|
+
userId: string | undefined;
|
|
121
|
+
ruleType: NotificationRuleType | undefined;
|
|
122
|
+
incidentSeverityId: string | undefined;
|
|
123
|
+
alertSeverityId: string | undefined;
|
|
124
|
+
notifyAfterMinutes: number | undefined;
|
|
125
|
+
userEmailId: string | undefined;
|
|
126
|
+
isOptOut: boolean | undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let ruleStore: Array<StoredRule> = [];
|
|
130
|
+
|
|
131
|
+
function toIdString(value: unknown): string | undefined {
|
|
132
|
+
if (value === undefined || value === null) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return String(value);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/*
|
|
140
|
+
* Match a stored row against a service query. A key the query does not carry is
|
|
141
|
+
* not a constraint — that mirrors how the real queries are built, where
|
|
142
|
+
* createSingleRule simply omits the severity columns and UserOnCallLogService
|
|
143
|
+
* passes `alertSeverityId: undefined` on the incident path.
|
|
144
|
+
*/
|
|
145
|
+
function ruleMatchesQuery(
|
|
146
|
+
rule: StoredRule,
|
|
147
|
+
query: Record<string, unknown>,
|
|
148
|
+
): boolean {
|
|
149
|
+
const idFields: Array<keyof StoredRule> = [
|
|
150
|
+
"projectId",
|
|
151
|
+
"userId",
|
|
152
|
+
"incidentSeverityId",
|
|
153
|
+
"alertSeverityId",
|
|
154
|
+
"userEmailId",
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
for (const field of idFields) {
|
|
158
|
+
const expected: string | undefined = toIdString(query[field as string]);
|
|
159
|
+
|
|
160
|
+
if (expected !== undefined && toIdString(rule[field]) !== expected) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const expectedRuleType: unknown = query["ruleType"];
|
|
166
|
+
|
|
167
|
+
if (expectedRuleType !== undefined && rule.ruleType !== expectedRuleType) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const expectedNotifyAfterMinutes: unknown = query["notifyAfterMinutes"];
|
|
172
|
+
|
|
173
|
+
if (
|
|
174
|
+
expectedNotifyAfterMinutes !== undefined &&
|
|
175
|
+
rule.notifyAfterMinutes !== expectedNotifyAfterMinutes
|
|
176
|
+
) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/*
|
|
181
|
+
* isOptOut appears in queries in TWO different shapes, and conflating them
|
|
182
|
+
* makes this matcher reject everything.
|
|
183
|
+
*
|
|
184
|
+
* - As a literal `true`: the no-rule path's opt-out lookup, asking whether
|
|
185
|
+
* this exact (ruleType x severity) cell was deliberately muted.
|
|
186
|
+
* - As a PREDICATE OBJECT: every counting and execution query excludes
|
|
187
|
+
* opt-out rows with notOptOutRuleQuery(), which is
|
|
188
|
+
* QueryHelper.notInOrNull(["true"]) - a TypeORM operator, not a boolean.
|
|
189
|
+
* It is deliberately null-tolerant: isOptOut is nullable and every rule
|
|
190
|
+
* written before the column existed has it NULL, so a plain
|
|
191
|
+
* `isOptOut: false` would match none of them and quietly exclude every
|
|
192
|
+
* pre-existing rule from paging.
|
|
193
|
+
*
|
|
194
|
+
* A boolean is compared directly; anything else is the exclusion predicate,
|
|
195
|
+
* which a row satisfies precisely when it is not an opt-out.
|
|
196
|
+
*/
|
|
197
|
+
const expectedIsOptOut: unknown = query["isOptOut"];
|
|
198
|
+
|
|
199
|
+
if (expectedIsOptOut !== undefined) {
|
|
200
|
+
if (typeof expectedIsOptOut === "boolean") {
|
|
201
|
+
if (Boolean(rule.isOptOut) !== expectedIsOptOut) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
} else if (rule.isOptOut === true) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function matchingRules(query: Record<string, unknown>): Array<StoredRule> {
|
|
213
|
+
return ruleStore.filter((rule: StoredRule): boolean => {
|
|
214
|
+
return ruleMatchesQuery(rule, query);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/*
|
|
219
|
+
* Remove a column the way a request that simply omitted it would.
|
|
220
|
+
*
|
|
221
|
+
* Takes `unknown` and narrows inside, the way callHook below does. Callers hand
|
|
222
|
+
* it a model INSTANCE, and a class with declared properties is not assignable to
|
|
223
|
+
* Record<string, unknown> under this repo's strict config - while `object` is
|
|
224
|
+
* banned by the lint rules. Widening at the boundary satisfies both.
|
|
225
|
+
*/
|
|
226
|
+
function unsetColumn(model: unknown, column: string): void {
|
|
227
|
+
Reflect.deleteProperty(model as Record<string, unknown>, column);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Calls a protected hook without widening the service's public surface.
|
|
231
|
+
function callHook(
|
|
232
|
+
service: unknown,
|
|
233
|
+
name: string,
|
|
234
|
+
...args: Array<unknown>
|
|
235
|
+
): Promise<unknown> {
|
|
236
|
+
const hooks: Record<
|
|
237
|
+
string,
|
|
238
|
+
(...hookArgs: Array<unknown>) => Promise<unknown>
|
|
239
|
+
> = service as Record<
|
|
240
|
+
string,
|
|
241
|
+
(...hookArgs: Array<unknown>) => Promise<unknown>
|
|
242
|
+
>;
|
|
243
|
+
|
|
244
|
+
return hooks[name]!.apply(service, args);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Same reason as unsetColumn: callers pass a class prototype, not a map.
|
|
248
|
+
function ownHookNames(prototype: unknown): Array<string> {
|
|
249
|
+
return Object.getOwnPropertyNames(prototype as Record<string, unknown>)
|
|
250
|
+
.filter((name: string): boolean => {
|
|
251
|
+
return name.startsWith("on");
|
|
252
|
+
})
|
|
253
|
+
.sort();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function incidentSeverityRows(ids: Array<ObjectID>): Array<IncidentSeverity> {
|
|
257
|
+
return ids.map((id: ObjectID): IncidentSeverity => {
|
|
258
|
+
const severity: IncidentSeverity = new IncidentSeverity();
|
|
259
|
+
severity._id = id.toString();
|
|
260
|
+
severity.projectId = PROJECT_ID;
|
|
261
|
+
|
|
262
|
+
return severity;
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function alertSeverityRows(ids: Array<ObjectID>): Array<AlertSeverity> {
|
|
267
|
+
return ids.map((id: ObjectID): AlertSeverity => {
|
|
268
|
+
const severity: AlertSeverity = new AlertSeverity();
|
|
269
|
+
severity._id = id.toString();
|
|
270
|
+
severity.projectId = PROJECT_ID;
|
|
271
|
+
|
|
272
|
+
return severity;
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Wire the store into the service's own read/write helpers. */
|
|
277
|
+
function stubUserNotificationRuleStore(): void {
|
|
278
|
+
jest
|
|
279
|
+
.spyOn(UserNotificationRuleService, "findOneBy")
|
|
280
|
+
.mockImplementation(((data: {
|
|
281
|
+
query: Record<string, unknown>;
|
|
282
|
+
}): Promise<UserNotificationRule | null> => {
|
|
283
|
+
const match: StoredRule | undefined = matchingRules(data.query)[0];
|
|
284
|
+
|
|
285
|
+
if (!match) {
|
|
286
|
+
return Promise.resolve(null);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const model: UserNotificationRule = new UserNotificationRule();
|
|
290
|
+
model._id = match.id.toString();
|
|
291
|
+
|
|
292
|
+
return Promise.resolve(model);
|
|
293
|
+
}) as never);
|
|
294
|
+
|
|
295
|
+
jest.spyOn(UserNotificationRuleService, "findBy").mockImplementation(((data: {
|
|
296
|
+
query: Record<string, unknown>;
|
|
297
|
+
}): Promise<Array<UserNotificationRule>> => {
|
|
298
|
+
return Promise.resolve(
|
|
299
|
+
matchingRules(data.query).map(
|
|
300
|
+
(stored: StoredRule): UserNotificationRule => {
|
|
301
|
+
const model: UserNotificationRule = new UserNotificationRule();
|
|
302
|
+
model._id = stored.id.toString();
|
|
303
|
+
|
|
304
|
+
return model;
|
|
305
|
+
},
|
|
306
|
+
),
|
|
307
|
+
);
|
|
308
|
+
}) as never);
|
|
309
|
+
|
|
310
|
+
jest
|
|
311
|
+
.spyOn(UserNotificationRuleService, "countBy")
|
|
312
|
+
.mockImplementation(((data: {
|
|
313
|
+
query: Record<string, unknown>;
|
|
314
|
+
}): Promise<PositiveNumber> => {
|
|
315
|
+
return Promise.resolve(
|
|
316
|
+
new PositiveNumber(matchingRules(data.query).length),
|
|
317
|
+
);
|
|
318
|
+
}) as never);
|
|
319
|
+
|
|
320
|
+
jest.spyOn(UserNotificationRuleService, "create").mockImplementation(((data: {
|
|
321
|
+
data: UserNotificationRule;
|
|
322
|
+
}): Promise<UserNotificationRule> => {
|
|
323
|
+
const model: UserNotificationRule = data.data;
|
|
324
|
+
const id: ObjectID = new ObjectID(`rule-${ruleStore.length + 1}`);
|
|
325
|
+
model._id = id.toString();
|
|
326
|
+
|
|
327
|
+
ruleStore.push({
|
|
328
|
+
id: id,
|
|
329
|
+
projectId: toIdString(model.projectId),
|
|
330
|
+
userId: toIdString(model.userId),
|
|
331
|
+
ruleType: model.ruleType,
|
|
332
|
+
incidentSeverityId: toIdString(model.incidentSeverityId),
|
|
333
|
+
alertSeverityId: toIdString(model.alertSeverityId),
|
|
334
|
+
notifyAfterMinutes: model.notifyAfterMinutes,
|
|
335
|
+
userEmailId: toIdString(model.userEmailId),
|
|
336
|
+
isOptOut: model.isOptOut,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
return Promise.resolve(model);
|
|
340
|
+
}) as never);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* The severities the project has AT THIS MOMENT. */
|
|
344
|
+
function stubSeverities(
|
|
345
|
+
incidentSeverityIds: Array<ObjectID>,
|
|
346
|
+
alertSeverityIds: Array<ObjectID>,
|
|
347
|
+
): void {
|
|
348
|
+
jest
|
|
349
|
+
.spyOn(IncidentSeverityService, "findBy")
|
|
350
|
+
.mockResolvedValue(incidentSeverityRows(incidentSeverityIds) as never);
|
|
351
|
+
jest
|
|
352
|
+
.spyOn(AlertSeverityService, "findBy")
|
|
353
|
+
.mockResolvedValue(alertSeverityRows(alertSeverityIds) as never);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function runDefaults(): Promise<void> {
|
|
357
|
+
return UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod(
|
|
358
|
+
{
|
|
359
|
+
projectId: PROJECT_ID,
|
|
360
|
+
userId: USER_ID,
|
|
361
|
+
notificationMethod: {
|
|
362
|
+
userEmailId: USER_EMAIL_ID,
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function rulesOfType(ruleType: NotificationRuleType): Array<StoredRule> {
|
|
369
|
+
return ruleStore.filter((rule: StoredRule): boolean => {
|
|
370
|
+
return rule.ruleType === ruleType;
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function incidentSeverityIdsCovered(): Array<string | undefined> {
|
|
375
|
+
return rulesOfType(NotificationRuleType.ON_CALL_EXECUTED_INCIDENT).map(
|
|
376
|
+
(rule: StoredRule): string | undefined => {
|
|
377
|
+
return rule.incidentSeverityId;
|
|
378
|
+
},
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function alertSeverityIdsCovered(): Array<string | undefined> {
|
|
383
|
+
return rulesOfType(NotificationRuleType.ON_CALL_EXECUTED_ALERT).map(
|
|
384
|
+
(rule: StoredRule): string | undefined => {
|
|
385
|
+
return rule.alertSeverityId;
|
|
386
|
+
},
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
beforeEach(() => {
|
|
391
|
+
ruleStore = [];
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
afterEach(() => {
|
|
395
|
+
jest.restoreAllMocks();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
/*
|
|
399
|
+
* ========================================================================= *
|
|
400
|
+
* (A) Creating a severity now queues the backfill.
|
|
401
|
+
*
|
|
402
|
+
* GAP A CLOSED IN PHASE 1. This section used to prove the opposite: that
|
|
403
|
+
* neither severity service had any create-success hook at all, and that
|
|
404
|
+
* committing a severity row was followed by exactly nothing. Both services now
|
|
405
|
+
* override onCreateSuccess and enqueue
|
|
406
|
+
* "OnCallDutyPolicy:BackfillNotificationRulesForNewSeverities" onto the Worker
|
|
407
|
+
* queue.
|
|
408
|
+
*
|
|
409
|
+
* Note what did NOT change, and why it is still asserted: no rule is written
|
|
410
|
+
* inline. That is deliberate rather than residual. A project can hold thousands
|
|
411
|
+
* of responders and this hook runs inside the request that created the
|
|
412
|
+
* severity, so the fan-out belongs to the worker. "No inline rule creation" and
|
|
413
|
+
* "no backfill" used to be the same observation; they are now opposites, and
|
|
414
|
+
* the enqueue assertion is what tells them apart.
|
|
415
|
+
* =========================================================================
|
|
416
|
+
*/
|
|
417
|
+
|
|
418
|
+
describe("GAP A CLOSED - IncidentSeverityService queues the rule backfill on create", () => {
|
|
419
|
+
let createSpy: jest.SpyInstance;
|
|
420
|
+
let addDefaultsForMethodSpy: jest.SpyInstance;
|
|
421
|
+
let addDefaultsForUserSpy: jest.SpyInstance;
|
|
422
|
+
let addJobSpy: jest.SpyInstance;
|
|
423
|
+
|
|
424
|
+
beforeEach(() => {
|
|
425
|
+
/*
|
|
426
|
+
* onBeforeCreate's only real work is order rearrangement, which reads and
|
|
427
|
+
* rewrites sibling rows. Stub both so the hook is drivable without a
|
|
428
|
+
* database.
|
|
429
|
+
*/
|
|
430
|
+
jest
|
|
431
|
+
.spyOn(IncidentSeverityService, "findBy")
|
|
432
|
+
.mockResolvedValue([] as never);
|
|
433
|
+
jest
|
|
434
|
+
.spyOn(IncidentSeverityService, "updateOneBy")
|
|
435
|
+
.mockResolvedValue(0 as never);
|
|
436
|
+
|
|
437
|
+
// The queue is the hook's only side effect; never reach Redis for it.
|
|
438
|
+
addJobSpy = jest.spyOn(Queue, "addJob").mockResolvedValue({} as never);
|
|
439
|
+
|
|
440
|
+
createSpy = jest
|
|
441
|
+
.spyOn(UserNotificationRuleService, "create")
|
|
442
|
+
.mockResolvedValue(new UserNotificationRule() as never);
|
|
443
|
+
addDefaultsForMethodSpy = jest
|
|
444
|
+
.spyOn(
|
|
445
|
+
UserNotificationRuleService,
|
|
446
|
+
"addDefaultNotificationRulesForVerifiedMethod",
|
|
447
|
+
)
|
|
448
|
+
.mockResolvedValue(undefined as never);
|
|
449
|
+
addDefaultsForUserSpy = jest
|
|
450
|
+
.spyOn(UserNotificationRuleService, "addDefaultNotificationRuleForUser")
|
|
451
|
+
.mockResolvedValue(undefined as never);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
function createBySeverity(): CreateBy<IncidentSeverity> {
|
|
455
|
+
const severity: IncidentSeverity = new IncidentSeverity();
|
|
456
|
+
severity.name = "Sev4";
|
|
457
|
+
severity.order = 4;
|
|
458
|
+
severity.projectId = PROJECT_ID;
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
data: severity,
|
|
462
|
+
props: { isRoot: true },
|
|
463
|
+
} as CreateBy<IncidentSeverity>;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function createdSeverity(): IncidentSeverity {
|
|
467
|
+
const created: IncidentSeverity = new IncidentSeverity();
|
|
468
|
+
created._id = SEV_C.toString();
|
|
469
|
+
created.projectId = PROJECT_ID;
|
|
470
|
+
|
|
471
|
+
return created;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function expectNoInlineRuleCreation(): void {
|
|
475
|
+
expect(createSpy).not.toHaveBeenCalled();
|
|
476
|
+
expect(addDefaultsForMethodSpy).not.toHaveBeenCalled();
|
|
477
|
+
expect(addDefaultsForUserSpy).not.toHaveBeenCalled();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/*
|
|
481
|
+
* GAP A CLOSED - onCreateSuccess is the hook Phase 1 added, so it now appears
|
|
482
|
+
* in this list. The rest of the list is unchanged: the backfill hangs off the
|
|
483
|
+
* success hook, not off any of the before-hooks.
|
|
484
|
+
*/
|
|
485
|
+
test("it overrides onCreateSuccess alongside its ordering hooks", () => {
|
|
486
|
+
expect(ownHookNames(IncidentSeverityServiceClass.prototype)).toEqual([
|
|
487
|
+
"onBeforeCreate",
|
|
488
|
+
"onBeforeDelete",
|
|
489
|
+
"onBeforeUpdate",
|
|
490
|
+
"onCreateSuccess",
|
|
491
|
+
"onDeleteSuccess",
|
|
492
|
+
]);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
/*
|
|
496
|
+
* GAP A CLOSED - the inverse of the original assertion. Sharing
|
|
497
|
+
* DatabaseService's identity was the precise statement of "does nothing"; a
|
|
498
|
+
* distinct own property is the precise statement that it now does something.
|
|
499
|
+
*/
|
|
500
|
+
test("onCreateSuccess is no longer DatabaseService's do-nothing placeholder", () => {
|
|
501
|
+
const proto: Record<string, unknown> =
|
|
502
|
+
IncidentSeverityServiceClass.prototype as unknown as Record<
|
|
503
|
+
string,
|
|
504
|
+
unknown
|
|
505
|
+
>;
|
|
506
|
+
const base: Record<string, unknown> =
|
|
507
|
+
DatabaseService.prototype as unknown as Record<string, unknown>;
|
|
508
|
+
|
|
509
|
+
expect(proto["onCreateSuccess"]).not.toBe(base["onCreateSuccess"]);
|
|
510
|
+
expect(
|
|
511
|
+
Object.prototype.hasOwnProperty.call(
|
|
512
|
+
IncidentSeverityServiceClass.prototype,
|
|
513
|
+
"onCreateSuccess",
|
|
514
|
+
),
|
|
515
|
+
).toBe(true);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test("it does not override onCreateError either, so there is no rule work on the failure path", () => {
|
|
519
|
+
expect(
|
|
520
|
+
Object.prototype.hasOwnProperty.call(
|
|
521
|
+
IncidentSeverityServiceClass.prototype,
|
|
522
|
+
"onCreateError",
|
|
523
|
+
),
|
|
524
|
+
).toBe(false);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
/*
|
|
528
|
+
* GAP A CLOSED - the backfill hangs off onCreateSuccess, so the before-hook
|
|
529
|
+
* on its own still queues nothing and writes nothing. A severity that fails
|
|
530
|
+
* validation must not leave a backfill job behind for a row that was never
|
|
531
|
+
* committed.
|
|
532
|
+
*/
|
|
533
|
+
test("driving onBeforeCreate alone neither writes rules nor queues the backfill", async () => {
|
|
534
|
+
await callHook(
|
|
535
|
+
IncidentSeverityService,
|
|
536
|
+
"onBeforeCreate",
|
|
537
|
+
createBySeverity(),
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
expectNoInlineRuleCreation();
|
|
541
|
+
expect(addJobSpy).not.toHaveBeenCalled();
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test("onBeforeCreate only rearranges sibling order - that is its whole job", async () => {
|
|
545
|
+
const findBySpy: jest.SpyInstance = jest.spyOn(
|
|
546
|
+
IncidentSeverityService,
|
|
547
|
+
"findBy",
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
await callHook(
|
|
551
|
+
IncidentSeverityService,
|
|
552
|
+
"onBeforeCreate",
|
|
553
|
+
createBySeverity(),
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
expect(findBySpy).toHaveBeenCalledTimes(1);
|
|
557
|
+
expectNoInlineRuleCreation();
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
/*
|
|
561
|
+
* GAP A CLOSED - driving the full create sequence is the behavioural proof.
|
|
562
|
+
* The severity row is committed and a backfill job follows it, on the Worker
|
|
563
|
+
* queue, named for the cron that owns it.
|
|
564
|
+
*/
|
|
565
|
+
test("the full create sequence queues the backfill job", async () => {
|
|
566
|
+
const createBy: CreateBy<IncidentSeverity> = createBySeverity();
|
|
567
|
+
|
|
568
|
+
const onCreate: OnCreate<IncidentSeverity> = (await callHook(
|
|
569
|
+
IncidentSeverityService,
|
|
570
|
+
"onBeforeCreate",
|
|
571
|
+
createBy,
|
|
572
|
+
)) as OnCreate<IncidentSeverity>;
|
|
573
|
+
|
|
574
|
+
const created: IncidentSeverity = createdSeverity();
|
|
575
|
+
|
|
576
|
+
const returned: unknown = await callHook(
|
|
577
|
+
IncidentSeverityService,
|
|
578
|
+
"onCreateSuccess",
|
|
579
|
+
onCreate,
|
|
580
|
+
created,
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
// The hook still hands the created item straight back.
|
|
584
|
+
expect(returned).toBe(created);
|
|
585
|
+
|
|
586
|
+
expect(addJobSpy).toHaveBeenCalledTimes(1);
|
|
587
|
+
const call: Array<unknown> = addJobSpy.mock.calls[0]!;
|
|
588
|
+
expect(call[0]).toBe(QueueName.Worker);
|
|
589
|
+
expect(call[2]).toBe(BACKFILL_JOB_NAME);
|
|
590
|
+
|
|
591
|
+
// Still nothing inline - the fan-out is the worker's job, not the request's.
|
|
592
|
+
expectNoInlineRuleCreation();
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
/*
|
|
596
|
+
* GAP A CLOSED - a queue that is unreachable must not take the severity down
|
|
597
|
+
* with it. The scheduled sweep inside the backfill job re-scans recently
|
|
598
|
+
* created severities, so a dropped enqueue costs latency, not coverage.
|
|
599
|
+
*/
|
|
600
|
+
test("an enqueue failure is swallowed and the created item is still returned", async () => {
|
|
601
|
+
addJobSpy.mockRejectedValue(new Error("redis is down") as never);
|
|
602
|
+
|
|
603
|
+
const created: IncidentSeverity = createdSeverity();
|
|
604
|
+
|
|
605
|
+
const returned: unknown = await callHook(
|
|
606
|
+
IncidentSeverityService,
|
|
607
|
+
"onCreateSuccess",
|
|
608
|
+
{ createBy: createBySeverity(), carryForward: null },
|
|
609
|
+
created,
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
expect(returned).toBe(created);
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
test("a severity with no order is rejected, and still nothing touches rules", async () => {
|
|
616
|
+
const createBy: CreateBy<IncidentSeverity> = createBySeverity();
|
|
617
|
+
unsetColumn(createBy.data, "order");
|
|
618
|
+
|
|
619
|
+
await expect(
|
|
620
|
+
callHook(IncidentSeverityService, "onBeforeCreate", createBy),
|
|
621
|
+
).rejects.toBeInstanceOf(BadDataException);
|
|
622
|
+
|
|
623
|
+
expectNoInlineRuleCreation();
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
test("a severity with no projectId is rejected, and still nothing touches rules", async () => {
|
|
627
|
+
const createBy: CreateBy<IncidentSeverity> = createBySeverity();
|
|
628
|
+
unsetColumn(createBy.data, "projectId");
|
|
629
|
+
|
|
630
|
+
await expect(
|
|
631
|
+
callHook(IncidentSeverityService, "onBeforeCreate", createBy),
|
|
632
|
+
).rejects.toBeInstanceOf(BadDataException);
|
|
633
|
+
|
|
634
|
+
expectNoInlineRuleCreation();
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
/*
|
|
638
|
+
* GAP A CLOSED - the user-visible consequence, inverted. However many people
|
|
639
|
+
* are already in the project, creating a severity used to consider none of
|
|
640
|
+
* them. It still enumerates none of them HERE, because it hands the whole
|
|
641
|
+
* question to a job that carries the project and severity that changed.
|
|
642
|
+
*/
|
|
643
|
+
test("the queued job names the project and severity whose responders need covering", async () => {
|
|
644
|
+
const createBy: CreateBy<IncidentSeverity> = createBySeverity();
|
|
645
|
+
|
|
646
|
+
const onCreate: OnCreate<IncidentSeverity> = (await callHook(
|
|
647
|
+
IncidentSeverityService,
|
|
648
|
+
"onBeforeCreate",
|
|
649
|
+
createBy,
|
|
650
|
+
)) as OnCreate<IncidentSeverity>;
|
|
651
|
+
|
|
652
|
+
await callHook(
|
|
653
|
+
IncidentSeverityService,
|
|
654
|
+
"onCreateSuccess",
|
|
655
|
+
onCreate,
|
|
656
|
+
createdSeverity(),
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
expect(addJobSpy).toHaveBeenCalledTimes(1);
|
|
660
|
+
const payload: Record<string, unknown> = addJobSpy.mock
|
|
661
|
+
.calls[0]![3] as Record<string, unknown>;
|
|
662
|
+
|
|
663
|
+
expect(payload["projectId"]).toBe(PROJECT_ID.toString());
|
|
664
|
+
expect(payload["incidentSeverityId"]).toBe(SEV_C.toString());
|
|
665
|
+
|
|
666
|
+
// The request itself still writes no rules and reads no users.
|
|
667
|
+
expect(addDefaultsForMethodSpy).toHaveBeenCalledTimes(0);
|
|
668
|
+
expect(createSpy).toHaveBeenCalledTimes(0);
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
describe("GAP A CLOSED - AlertSeverityService queues the rule backfill on create", () => {
|
|
673
|
+
let createSpy: jest.SpyInstance;
|
|
674
|
+
let addDefaultsForMethodSpy: jest.SpyInstance;
|
|
675
|
+
let addDefaultsForUserSpy: jest.SpyInstance;
|
|
676
|
+
let addJobSpy: jest.SpyInstance;
|
|
677
|
+
|
|
678
|
+
beforeEach(() => {
|
|
679
|
+
jest.spyOn(AlertSeverityService, "findBy").mockResolvedValue([] as never);
|
|
680
|
+
jest
|
|
681
|
+
.spyOn(AlertSeverityService, "updateOneBy")
|
|
682
|
+
.mockResolvedValue(0 as never);
|
|
683
|
+
|
|
684
|
+
addJobSpy = jest.spyOn(Queue, "addJob").mockResolvedValue({} as never);
|
|
685
|
+
|
|
686
|
+
createSpy = jest
|
|
687
|
+
.spyOn(UserNotificationRuleService, "create")
|
|
688
|
+
.mockResolvedValue(new UserNotificationRule() as never);
|
|
689
|
+
addDefaultsForMethodSpy = jest
|
|
690
|
+
.spyOn(
|
|
691
|
+
UserNotificationRuleService,
|
|
692
|
+
"addDefaultNotificationRulesForVerifiedMethod",
|
|
693
|
+
)
|
|
694
|
+
.mockResolvedValue(undefined as never);
|
|
695
|
+
addDefaultsForUserSpy = jest
|
|
696
|
+
.spyOn(UserNotificationRuleService, "addDefaultNotificationRuleForUser")
|
|
697
|
+
.mockResolvedValue(undefined as never);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
function createBySeverity(): CreateBy<AlertSeverity> {
|
|
701
|
+
const severity: AlertSeverity = new AlertSeverity();
|
|
702
|
+
severity.name = "Sev4";
|
|
703
|
+
severity.order = 4;
|
|
704
|
+
severity.projectId = PROJECT_ID;
|
|
705
|
+
|
|
706
|
+
return {
|
|
707
|
+
data: severity,
|
|
708
|
+
props: { isRoot: true },
|
|
709
|
+
} as CreateBy<AlertSeverity>;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function expectNoInlineRuleCreation(): void {
|
|
713
|
+
expect(createSpy).not.toHaveBeenCalled();
|
|
714
|
+
expect(addDefaultsForMethodSpy).not.toHaveBeenCalled();
|
|
715
|
+
expect(addDefaultsForUserSpy).not.toHaveBeenCalled();
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/*
|
|
719
|
+
* GAP A CLOSED - the alert half moves in lockstep with the incident half.
|
|
720
|
+
*/
|
|
721
|
+
test("it overrides onCreateSuccess alongside its ordering hooks", () => {
|
|
722
|
+
expect(ownHookNames(AlertSeverityServiceClass.prototype)).toEqual([
|
|
723
|
+
"onBeforeCreate",
|
|
724
|
+
"onBeforeDelete",
|
|
725
|
+
"onBeforeUpdate",
|
|
726
|
+
"onCreateSuccess",
|
|
727
|
+
"onDeleteSuccess",
|
|
728
|
+
]);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
/*
|
|
732
|
+
* GAP A CLOSED - the inverse of the original assertion.
|
|
733
|
+
*/
|
|
734
|
+
test("onCreateSuccess is no longer DatabaseService's do-nothing placeholder", () => {
|
|
735
|
+
const proto: Record<string, unknown> =
|
|
736
|
+
AlertSeverityServiceClass.prototype as unknown as Record<string, unknown>;
|
|
737
|
+
const base: Record<string, unknown> =
|
|
738
|
+
DatabaseService.prototype as unknown as Record<string, unknown>;
|
|
739
|
+
|
|
740
|
+
expect(proto["onCreateSuccess"]).not.toBe(base["onCreateSuccess"]);
|
|
741
|
+
expect(
|
|
742
|
+
Object.prototype.hasOwnProperty.call(
|
|
743
|
+
AlertSeverityServiceClass.prototype,
|
|
744
|
+
"onCreateSuccess",
|
|
745
|
+
),
|
|
746
|
+
).toBe(true);
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
/*
|
|
750
|
+
* GAP A CLOSED - the alert payload carries alertSeverityId, so the job can
|
|
751
|
+
* tell which of the two severity tables changed without guessing.
|
|
752
|
+
*/
|
|
753
|
+
test("the full create sequence queues the backfill job for the alert severity", async () => {
|
|
754
|
+
const createBy: CreateBy<AlertSeverity> = createBySeverity();
|
|
755
|
+
|
|
756
|
+
const onCreate: OnCreate<AlertSeverity> = (await callHook(
|
|
757
|
+
AlertSeverityService,
|
|
758
|
+
"onBeforeCreate",
|
|
759
|
+
createBy,
|
|
760
|
+
)) as OnCreate<AlertSeverity>;
|
|
761
|
+
|
|
762
|
+
const created: AlertSeverity = new AlertSeverity();
|
|
763
|
+
created._id = ALERT_SEV_C.toString();
|
|
764
|
+
created.projectId = PROJECT_ID;
|
|
765
|
+
|
|
766
|
+
const returned: unknown = await callHook(
|
|
767
|
+
AlertSeverityService,
|
|
768
|
+
"onCreateSuccess",
|
|
769
|
+
onCreate,
|
|
770
|
+
created,
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
expect(returned).toBe(created);
|
|
774
|
+
|
|
775
|
+
expect(addJobSpy).toHaveBeenCalledTimes(1);
|
|
776
|
+
const call: Array<unknown> = addJobSpy.mock.calls[0]!;
|
|
777
|
+
expect(call[0]).toBe(QueueName.Worker);
|
|
778
|
+
expect(call[2]).toBe(BACKFILL_JOB_NAME);
|
|
779
|
+
|
|
780
|
+
const payload: Record<string, unknown> = call[3] as Record<string, unknown>;
|
|
781
|
+
expect(payload["projectId"]).toBe(PROJECT_ID.toString());
|
|
782
|
+
expect(payload["alertSeverityId"]).toBe(ALERT_SEV_C.toString());
|
|
783
|
+
|
|
784
|
+
expectNoInlineRuleCreation();
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
test("a severity with no order is rejected, and still nothing touches rules", async () => {
|
|
788
|
+
const createBy: CreateBy<AlertSeverity> = createBySeverity();
|
|
789
|
+
unsetColumn(createBy.data, "order");
|
|
790
|
+
|
|
791
|
+
await expect(
|
|
792
|
+
callHook(AlertSeverityService, "onBeforeCreate", createBy),
|
|
793
|
+
).rejects.toBeInstanceOf(BadDataException);
|
|
794
|
+
|
|
795
|
+
expectNoInlineRuleCreation();
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
test("a severity with no projectId is rejected, and still nothing touches rules", async () => {
|
|
799
|
+
const createBy: CreateBy<AlertSeverity> = createBySeverity();
|
|
800
|
+
unsetColumn(createBy.data, "projectId");
|
|
801
|
+
|
|
802
|
+
await expect(
|
|
803
|
+
callHook(AlertSeverityService, "onBeforeCreate", createBy),
|
|
804
|
+
).rejects.toBeInstanceOf(BadDataException);
|
|
805
|
+
|
|
806
|
+
expectNoInlineRuleCreation();
|
|
807
|
+
});
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
/*
|
|
811
|
+
* ========================================================================= *
|
|
812
|
+
* (B) Rule creation is a snapshot of the severities that exist at call time.
|
|
813
|
+
*
|
|
814
|
+
* Unchanged by Phase 1, and deliberately so: the fix was not to make this
|
|
815
|
+
* method clairvoyant, it was to arrange for something to call it again. Every
|
|
816
|
+
* assertion here still describes today's behaviour exactly.
|
|
817
|
+
* =========================================================================
|
|
818
|
+
*/
|
|
819
|
+
|
|
820
|
+
describe("GAP A CLOSED - default rules are still a snapshot of the severities that exist at call time", () => {
|
|
821
|
+
beforeEach(() => {
|
|
822
|
+
stubUserNotificationRuleStore();
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
test("a two-severity project produces exactly one incident rule per existing severity", async () => {
|
|
826
|
+
stubSeverities([SEV_A, SEV_B], []);
|
|
827
|
+
|
|
828
|
+
await runDefaults();
|
|
829
|
+
|
|
830
|
+
expect(incidentSeverityIdsCovered()).toEqual([
|
|
831
|
+
SEV_A.toString(),
|
|
832
|
+
SEV_B.toString(),
|
|
833
|
+
]);
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
test("a severity that does not exist yet gets no rule - there is nothing to iterate", async () => {
|
|
837
|
+
stubSeverities([SEV_A, SEV_B], []);
|
|
838
|
+
|
|
839
|
+
await runDefaults();
|
|
840
|
+
|
|
841
|
+
expect(incidentSeverityIdsCovered()).not.toContain(SEV_C.toString());
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
test("alert severities behave identically", async () => {
|
|
845
|
+
stubSeverities([], [ALERT_SEV_A, ALERT_SEV_B]);
|
|
846
|
+
|
|
847
|
+
await runDefaults();
|
|
848
|
+
|
|
849
|
+
expect(alertSeverityIdsCovered()).toEqual([
|
|
850
|
+
ALERT_SEV_A.toString(),
|
|
851
|
+
ALERT_SEV_B.toString(),
|
|
852
|
+
]);
|
|
853
|
+
expect(alertSeverityIdsCovered()).not.toContain(ALERT_SEV_C.toString());
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
test("each severity rule is an immediate rule of the right type bound to the verified method", async () => {
|
|
857
|
+
stubSeverities([SEV_A], [ALERT_SEV_A]);
|
|
858
|
+
|
|
859
|
+
await runDefaults();
|
|
860
|
+
|
|
861
|
+
const incidentRule: StoredRule = rulesOfType(
|
|
862
|
+
NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
863
|
+
)[0]!;
|
|
864
|
+
|
|
865
|
+
expect(incidentRule.projectId).toBe(PROJECT_ID.toString());
|
|
866
|
+
expect(incidentRule.userId).toBe(USER_ID.toString());
|
|
867
|
+
expect(incidentRule.userEmailId).toBe(USER_EMAIL_ID.toString());
|
|
868
|
+
expect(incidentRule.notifyAfterMinutes).toBe(0);
|
|
869
|
+
expect(incidentRule.incidentSeverityId).toBe(SEV_A.toString());
|
|
870
|
+
expect(incidentRule.alertSeverityId).toBeUndefined();
|
|
871
|
+
|
|
872
|
+
const alertRule: StoredRule = rulesOfType(
|
|
873
|
+
NotificationRuleType.ON_CALL_EXECUTED_ALERT,
|
|
874
|
+
)[0]!;
|
|
875
|
+
|
|
876
|
+
expect(alertRule.alertSeverityId).toBe(ALERT_SEV_A.toString());
|
|
877
|
+
expect(alertRule.incidentSeverityId).toBeUndefined();
|
|
878
|
+
expect(alertRule.notifyAfterMinutes).toBe(0);
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
test("only the two go-on/off-call rule types survive a project with no severities", async () => {
|
|
882
|
+
/*
|
|
883
|
+
* GAP G CLOSED - the two EPISODE rule types are severity-scoped now.
|
|
884
|
+
*
|
|
885
|
+
* They used to be created by createSingleRule, which sets ruleType and
|
|
886
|
+
* notifyAfterMinutes and no severity at all. That produced exactly one row
|
|
887
|
+
* per rule type carrying a NULL severity - and UserOnCallLogService counts
|
|
888
|
+
* episode rules filtered by a concrete severity id, so NULL never matched
|
|
889
|
+
* and every one of those "defaults" was dead. Worse, both episode pages
|
|
890
|
+
* scope their table by severity id, so the rows were invisible to the user
|
|
891
|
+
* who owned them: unmatchable AND unfixable.
|
|
892
|
+
*
|
|
893
|
+
* So with zero severities in the project there is now nothing to scope an
|
|
894
|
+
* episode rule TO, and none are written. Only WHEN_USER_GOES_ON_CALL and
|
|
895
|
+
* WHEN_USER_GOES_OFF_CALL remain genuinely severity-less, because "I went
|
|
896
|
+
* on call" is not an event that has a severity.
|
|
897
|
+
*/
|
|
898
|
+
stubSeverities([], []);
|
|
899
|
+
|
|
900
|
+
await runDefaults();
|
|
901
|
+
|
|
902
|
+
expect(
|
|
903
|
+
rulesOfType(NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE),
|
|
904
|
+
).toHaveLength(0);
|
|
905
|
+
expect(
|
|
906
|
+
rulesOfType(NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE),
|
|
907
|
+
).toHaveLength(0);
|
|
908
|
+
expect(
|
|
909
|
+
rulesOfType(NotificationRuleType.WHEN_USER_GOES_ON_CALL),
|
|
910
|
+
).toHaveLength(1);
|
|
911
|
+
expect(
|
|
912
|
+
rulesOfType(NotificationRuleType.WHEN_USER_GOES_OFF_CALL),
|
|
913
|
+
).toHaveLength(1);
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
test("a project with no severities yields only the two go-on/off-call rules", async () => {
|
|
917
|
+
stubSeverities([], []);
|
|
918
|
+
|
|
919
|
+
await runDefaults();
|
|
920
|
+
|
|
921
|
+
expect(ruleStore).toHaveLength(2);
|
|
922
|
+
expect(incidentSeverityIdsCovered()).toEqual([]);
|
|
923
|
+
expect(alertSeverityIdsCovered()).toEqual([]);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
test("two incident and two alert severities yield 2 + 2 + 2 + 2 + 2 rows", async () => {
|
|
927
|
+
/*
|
|
928
|
+
* Per severity: one incident rule, one alert rule, one incident-episode
|
|
929
|
+
* rule and one alert-episode rule - eight in total across two of each
|
|
930
|
+
* severity - plus the two severity-less go-on/off-call rules.
|
|
931
|
+
*/
|
|
932
|
+
stubSeverities([SEV_A, SEV_B], [ALERT_SEV_A, ALERT_SEV_B]);
|
|
933
|
+
|
|
934
|
+
await runDefaults();
|
|
935
|
+
|
|
936
|
+
expect(ruleStore).toHaveLength(10);
|
|
937
|
+
expect(
|
|
938
|
+
rulesOfType(NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE),
|
|
939
|
+
).toHaveLength(2);
|
|
940
|
+
expect(
|
|
941
|
+
rulesOfType(NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE),
|
|
942
|
+
).toHaveLength(2);
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
test("the severity lookup is project-scoped, root-privileged and capped at LIMIT_PER_PROJECT", async () => {
|
|
946
|
+
stubSeverities([SEV_A], [ALERT_SEV_A]);
|
|
947
|
+
|
|
948
|
+
await runDefaults();
|
|
949
|
+
|
|
950
|
+
const incidentFindBy: jest.SpyInstance = jest.spyOn(
|
|
951
|
+
IncidentSeverityService,
|
|
952
|
+
"findBy",
|
|
953
|
+
);
|
|
954
|
+
const findArg: {
|
|
955
|
+
query: { projectId: ObjectID };
|
|
956
|
+
props: { isRoot: boolean };
|
|
957
|
+
limit: number;
|
|
958
|
+
skip: number;
|
|
959
|
+
} = incidentFindBy.mock.calls[0]![0] as {
|
|
960
|
+
query: { projectId: ObjectID };
|
|
961
|
+
props: { isRoot: boolean };
|
|
962
|
+
limit: number;
|
|
963
|
+
skip: number;
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
expect(findArg.query.projectId.toString()).toBe(PROJECT_ID.toString());
|
|
967
|
+
expect(findArg.props.isRoot).toBe(true);
|
|
968
|
+
expect(findArg.limit).toBe(LIMIT_PER_PROJECT);
|
|
969
|
+
expect(findArg.skip).toBe(0);
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
test("running twice against the same severities is idempotent - no duplicate rows", async () => {
|
|
973
|
+
stubSeverities([SEV_A, SEV_B], [ALERT_SEV_A, ALERT_SEV_B]);
|
|
974
|
+
|
|
975
|
+
await runDefaults();
|
|
976
|
+
const afterFirstRun: number = ruleStore.length;
|
|
977
|
+
|
|
978
|
+
await runDefaults();
|
|
979
|
+
|
|
980
|
+
expect(ruleStore).toHaveLength(afterFirstRun);
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
/*
|
|
984
|
+
* GAP A CLOSED - this behaviour is unchanged, and it is exactly WHY the
|
|
985
|
+
* backfill has to exist.
|
|
986
|
+
*
|
|
987
|
+
* Call one: severities [A, B]. A third severity C is then created in the
|
|
988
|
+
* project. Call two: severities [A, B, C] - and only NOW does the C rule
|
|
989
|
+
* appear. This method is still a snapshot of the moment it runs; what Phase 1
|
|
990
|
+
* added is something that makes call two happen, namely the backfill job that
|
|
991
|
+
* IncidentSeverityService.onCreateSuccess now enqueues.
|
|
992
|
+
*/
|
|
993
|
+
test("a third severity only gets a rule because the method was called a SECOND time", async () => {
|
|
994
|
+
stubSeverities([SEV_A, SEV_B], []);
|
|
995
|
+
await runDefaults();
|
|
996
|
+
|
|
997
|
+
// Snapshot taken by call one: the user is covered for A and B only.
|
|
998
|
+
expect(incidentSeverityIdsCovered()).toEqual([
|
|
999
|
+
SEV_A.toString(),
|
|
1000
|
+
SEV_B.toString(),
|
|
1001
|
+
]);
|
|
1002
|
+
|
|
1003
|
+
/*
|
|
1004
|
+
* "Sev4" is created here. In production creating it enqueues the backfill
|
|
1005
|
+
* job (see section (A)); this test stands in for that second pass by
|
|
1006
|
+
* re-running the defaults directly.
|
|
1007
|
+
*/
|
|
1008
|
+
stubSeverities([SEV_A, SEV_B, SEV_C], []);
|
|
1009
|
+
await runDefaults();
|
|
1010
|
+
|
|
1011
|
+
expect(incidentSeverityIdsCovered()).toEqual([
|
|
1012
|
+
SEV_A.toString(),
|
|
1013
|
+
SEV_B.toString(),
|
|
1014
|
+
SEV_C.toString(),
|
|
1015
|
+
]);
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
test("the re-run adds exactly one row - the pre-existing severities are skipped", async () => {
|
|
1019
|
+
stubSeverities([SEV_A, SEV_B], []);
|
|
1020
|
+
await runDefaults();
|
|
1021
|
+
const afterFirstRun: number = ruleStore.length;
|
|
1022
|
+
|
|
1023
|
+
stubSeverities([SEV_A, SEV_B, SEV_C], []);
|
|
1024
|
+
await runDefaults();
|
|
1025
|
+
|
|
1026
|
+
/*
|
|
1027
|
+
* GAP G CLOSED - two rows, not one. A new incident severity now earns the
|
|
1028
|
+
* responder an ON_CALL_EXECUTED_INCIDENT rule AND an
|
|
1029
|
+
* ON_CALL_EXECUTED_INCIDENT_EPISODE rule, because episode rules are
|
|
1030
|
+
* severity-scoped now instead of a single severity-less row that could
|
|
1031
|
+
* never match.
|
|
1032
|
+
*/
|
|
1033
|
+
expect(ruleStore.length - afterFirstRun).toBe(2);
|
|
1034
|
+
expect(
|
|
1035
|
+
rulesOfType(NotificationRuleType.ON_CALL_EXECUTED_INCIDENT),
|
|
1036
|
+
).toHaveLength(3);
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
/*
|
|
1040
|
+
* GAP A CLOSED - the alert half of the same snapshot property. Unchanged, and
|
|
1041
|
+
* likewise the reason AlertSeverityService now enqueues the backfill too.
|
|
1042
|
+
*/
|
|
1043
|
+
test("a late alert severity is missed by this method the same way", async () => {
|
|
1044
|
+
stubSeverities([], [ALERT_SEV_A, ALERT_SEV_B]);
|
|
1045
|
+
await runDefaults();
|
|
1046
|
+
|
|
1047
|
+
expect(alertSeverityIdsCovered()).toEqual([
|
|
1048
|
+
ALERT_SEV_A.toString(),
|
|
1049
|
+
ALERT_SEV_B.toString(),
|
|
1050
|
+
]);
|
|
1051
|
+
|
|
1052
|
+
// Severity C is created in the project. This method alone does no rule work.
|
|
1053
|
+
stubSeverities([], [ALERT_SEV_A, ALERT_SEV_B]);
|
|
1054
|
+
|
|
1055
|
+
expect(alertSeverityIdsCovered()).not.toContain(ALERT_SEV_C.toString());
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
test("the existing-rule check is severity-specific, so a rule for A never satisfies C", async () => {
|
|
1059
|
+
stubSeverities([SEV_A], []);
|
|
1060
|
+
await runDefaults();
|
|
1061
|
+
|
|
1062
|
+
const findOneBySpy: jest.SpyInstance = jest.spyOn(
|
|
1063
|
+
UserNotificationRuleService,
|
|
1064
|
+
"findOneBy",
|
|
1065
|
+
);
|
|
1066
|
+
|
|
1067
|
+
const severityQueries: Array<Record<string, unknown>> =
|
|
1068
|
+
findOneBySpy.mock.calls
|
|
1069
|
+
.map((call: Array<unknown>): Record<string, unknown> => {
|
|
1070
|
+
return (call[0] as { query: Record<string, unknown> }).query;
|
|
1071
|
+
})
|
|
1072
|
+
.filter((query: Record<string, unknown>): boolean => {
|
|
1073
|
+
return (
|
|
1074
|
+
query["ruleType"] === NotificationRuleType.ON_CALL_EXECUTED_INCIDENT
|
|
1075
|
+
);
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
expect(severityQueries).toHaveLength(1);
|
|
1079
|
+
expect(toIdString(severityQueries[0]!["incidentSeverityId"])).toBe(
|
|
1080
|
+
SEV_A.toString(),
|
|
1081
|
+
);
|
|
1082
|
+
});
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
/*
|
|
1086
|
+
* ========================================================================= *
|
|
1087
|
+
* (C) The coverage hole, read through the query UserOnCallLogService really
|
|
1088
|
+
* issues: { userId, projectId, ruleType, incidentSeverityId }.
|
|
1089
|
+
*
|
|
1090
|
+
* The hole itself is unchanged - a user with rules for [A, B] still has no
|
|
1091
|
+
* row for C until the backfill job runs - but the consequence is not. Phase
|
|
1092
|
+
* 1 replaced the dead-end with the verified-method fallback, so the very
|
|
1093
|
+
* same C-severity incident now reaches the responder and the log records
|
|
1094
|
+
* which channel carried it.
|
|
1095
|
+
* =========================================================================
|
|
1096
|
+
*/
|
|
1097
|
+
|
|
1098
|
+
describe("GAP A CLOSED - a severity added after the fact still pages, via the fallback", () => {
|
|
1099
|
+
let logUpdateSpy: jest.SpyInstance;
|
|
1100
|
+
let timelineUpdateSpy: jest.SpyInstance;
|
|
1101
|
+
let executeRuleSpy: jest.SpyInstance;
|
|
1102
|
+
let fallbackSpy: jest.SpyInstance;
|
|
1103
|
+
|
|
1104
|
+
beforeEach(async () => {
|
|
1105
|
+
stubUserNotificationRuleStore();
|
|
1106
|
+
|
|
1107
|
+
// The user joined when the project had severities A and B only.
|
|
1108
|
+
stubSeverities([SEV_A, SEV_B], [ALERT_SEV_A, ALERT_SEV_B]);
|
|
1109
|
+
await runDefaults();
|
|
1110
|
+
|
|
1111
|
+
logUpdateSpy = jest
|
|
1112
|
+
.spyOn(UserOnCallLogService, "updateOneById")
|
|
1113
|
+
.mockResolvedValue(1 as never);
|
|
1114
|
+
timelineUpdateSpy = jest
|
|
1115
|
+
.spyOn(OnCallDutyPolicyExecutionLogTimelineService, "updateOneById")
|
|
1116
|
+
.mockResolvedValue(1 as never);
|
|
1117
|
+
executeRuleSpy = jest
|
|
1118
|
+
.spyOn(UserNotificationRuleService, "executeNotificationRuleItem")
|
|
1119
|
+
.mockResolvedValue(undefined as never);
|
|
1120
|
+
|
|
1121
|
+
/*
|
|
1122
|
+
* The three collaborators the no-rule path reaches for now. The project read
|
|
1123
|
+
* decides whether the fallback is allowed at all (null => allowed, the
|
|
1124
|
+
* default), the fallback itself reports which channels it dispatched on, and
|
|
1125
|
+
* the user read only ever feeds a name into a failure message.
|
|
1126
|
+
*/
|
|
1127
|
+
jest.spyOn(ProjectService, "findOneById").mockResolvedValue(null as never);
|
|
1128
|
+
jest.spyOn(UserService, "findOneById").mockResolvedValue(null as never);
|
|
1129
|
+
fallbackSpy = jest
|
|
1130
|
+
.spyOn(UserNotificationRuleService, "executeFallbackNotification")
|
|
1131
|
+
.mockResolvedValue({
|
|
1132
|
+
notified: true,
|
|
1133
|
+
channelsUsed: ["Email"],
|
|
1134
|
+
} as never);
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
function stubIncident(incidentSeverityId: ObjectID): void {
|
|
1138
|
+
jest
|
|
1139
|
+
.spyOn(IncidentService, "findOneById")
|
|
1140
|
+
.mockImplementation(((): Promise<Incident> => {
|
|
1141
|
+
const incident: Incident = new Incident();
|
|
1142
|
+
incident._id = INCIDENT_ID.toString();
|
|
1143
|
+
incident.incidentSeverityId = incidentSeverityId;
|
|
1144
|
+
|
|
1145
|
+
return Promise.resolve(incident);
|
|
1146
|
+
}) as never);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function stubAlert(alertSeverityId: ObjectID): void {
|
|
1150
|
+
jest
|
|
1151
|
+
.spyOn(AlertService, "findOneById")
|
|
1152
|
+
.mockImplementation(((): Promise<Alert> => {
|
|
1153
|
+
const alert: Alert = new Alert();
|
|
1154
|
+
alert._id = ALERT_ID.toString();
|
|
1155
|
+
alert.alertSeverityId = alertSeverityId;
|
|
1156
|
+
|
|
1157
|
+
return Promise.resolve(alert);
|
|
1158
|
+
}) as never);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
function incidentOnCallLog(): UserOnCallLog {
|
|
1162
|
+
const log: UserOnCallLog = new UserOnCallLog();
|
|
1163
|
+
log._id = LOG_ID.toString();
|
|
1164
|
+
log.projectId = PROJECT_ID;
|
|
1165
|
+
log.userId = USER_ID;
|
|
1166
|
+
log.triggeredByIncidentId = INCIDENT_ID;
|
|
1167
|
+
log.userNotificationEventType = UserNotificationEventType.IncidentCreated;
|
|
1168
|
+
log.onCallDutyPolicyExecutionLogTimelineId = TIMELINE_ID;
|
|
1169
|
+
|
|
1170
|
+
return log;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
function alertOnCallLog(): UserOnCallLog {
|
|
1174
|
+
const log: UserOnCallLog = new UserOnCallLog();
|
|
1175
|
+
log._id = LOG_ID.toString();
|
|
1176
|
+
log.projectId = PROJECT_ID;
|
|
1177
|
+
log.userId = USER_ID;
|
|
1178
|
+
log.triggeredByAlertId = ALERT_ID;
|
|
1179
|
+
log.userNotificationEventType = UserNotificationEventType.AlertCreated;
|
|
1180
|
+
log.onCallDutyPolicyExecutionLogTimelineId = TIMELINE_ID;
|
|
1181
|
+
|
|
1182
|
+
return log;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function driveOnCreateSuccess(log: UserOnCallLog): Promise<unknown> {
|
|
1186
|
+
const onCreate: OnCreate<UserOnCallLog> = {
|
|
1187
|
+
createBy: {
|
|
1188
|
+
data: log,
|
|
1189
|
+
props: { isRoot: true },
|
|
1190
|
+
} as CreateBy<UserOnCallLog>,
|
|
1191
|
+
carryForward: null,
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
return callHook(UserOnCallLogService, "onCreateSuccess", onCreate, log);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function statusUpdates(): Array<{
|
|
1198
|
+
status: UserNotificationExecutionStatus;
|
|
1199
|
+
statusMessage?: string | undefined;
|
|
1200
|
+
}> {
|
|
1201
|
+
return logUpdateSpy.mock.calls.map(
|
|
1202
|
+
(
|
|
1203
|
+
call: Array<unknown>,
|
|
1204
|
+
): {
|
|
1205
|
+
status: UserNotificationExecutionStatus;
|
|
1206
|
+
statusMessage?: string | undefined;
|
|
1207
|
+
} => {
|
|
1208
|
+
return (
|
|
1209
|
+
call[0] as {
|
|
1210
|
+
data: {
|
|
1211
|
+
status: UserNotificationExecutionStatus;
|
|
1212
|
+
statusMessage?: string | undefined;
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
).data;
|
|
1216
|
+
},
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
test("the runtime lookup keys on ruleType AND the incident's severity", async () => {
|
|
1221
|
+
stubIncident(SEV_C);
|
|
1222
|
+
const countBySpy: jest.SpyInstance = jest.spyOn(
|
|
1223
|
+
UserNotificationRuleService,
|
|
1224
|
+
"countBy",
|
|
1225
|
+
);
|
|
1226
|
+
|
|
1227
|
+
await driveOnCreateSuccess(incidentOnCallLog());
|
|
1228
|
+
|
|
1229
|
+
const query: Record<string, unknown> = (
|
|
1230
|
+
countBySpy.mock.calls[0]![0] as { query: Record<string, unknown> }
|
|
1231
|
+
).query;
|
|
1232
|
+
|
|
1233
|
+
expect(toIdString(query["userId"])).toBe(USER_ID.toString());
|
|
1234
|
+
expect(toIdString(query["projectId"])).toBe(PROJECT_ID.toString());
|
|
1235
|
+
expect(query["ruleType"]).toBe(
|
|
1236
|
+
NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
1237
|
+
);
|
|
1238
|
+
expect(toIdString(query["incidentSeverityId"])).toBe(SEV_C.toString());
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
/*
|
|
1242
|
+
* GAP A CLOSED - the count is still zero, and that is correct: the backfill
|
|
1243
|
+
* job has not run in this fixture. What changed is what a zero count now
|
|
1244
|
+
* causes, which the tests below assert. This one stays as the premise.
|
|
1245
|
+
*/
|
|
1246
|
+
test("a user with rules for [A, B] has ZERO rules matching a C-severity incident", async () => {
|
|
1247
|
+
const count: PositiveNumber = await UserNotificationRuleService.countBy({
|
|
1248
|
+
query: {
|
|
1249
|
+
userId: USER_ID,
|
|
1250
|
+
projectId: PROJECT_ID,
|
|
1251
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
1252
|
+
incidentSeverityId: SEV_C,
|
|
1253
|
+
},
|
|
1254
|
+
skip: 0,
|
|
1255
|
+
limit: LIMIT_PER_PROJECT,
|
|
1256
|
+
props: { isRoot: true },
|
|
1257
|
+
} as never);
|
|
1258
|
+
|
|
1259
|
+
expect(count.toNumber()).toBe(0);
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
test("the same user does have a rule for the severities that existed when they joined", async () => {
|
|
1263
|
+
const countForA: PositiveNumber = await UserNotificationRuleService.countBy(
|
|
1264
|
+
{
|
|
1265
|
+
query: {
|
|
1266
|
+
userId: USER_ID,
|
|
1267
|
+
projectId: PROJECT_ID,
|
|
1268
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
1269
|
+
incidentSeverityId: SEV_A,
|
|
1270
|
+
},
|
|
1271
|
+
skip: 0,
|
|
1272
|
+
limit: LIMIT_PER_PROJECT,
|
|
1273
|
+
props: { isRoot: true },
|
|
1274
|
+
} as never,
|
|
1275
|
+
);
|
|
1276
|
+
|
|
1277
|
+
expect(countForA.toNumber()).toBe(1);
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
/*
|
|
1281
|
+
* GAP A CLOSED - the C-severity page is no longer dropped. No RULE is
|
|
1282
|
+
* executed, because there is still no rule to execute; the fallback is what
|
|
1283
|
+
* carries it, and it is handed the same severity name and rule type the count
|
|
1284
|
+
* came up empty for.
|
|
1285
|
+
*/
|
|
1286
|
+
test("the C-severity page reaches the responder through the fallback", async () => {
|
|
1287
|
+
stubIncident(SEV_C);
|
|
1288
|
+
|
|
1289
|
+
await driveOnCreateSuccess(incidentOnCallLog());
|
|
1290
|
+
|
|
1291
|
+
expect(executeRuleSpy).not.toHaveBeenCalled();
|
|
1292
|
+
expect(fallbackSpy).toHaveBeenCalledTimes(1);
|
|
1293
|
+
|
|
1294
|
+
const options: Record<string, unknown> = fallbackSpy.mock
|
|
1295
|
+
.calls[0]![0] as Record<string, unknown>;
|
|
1296
|
+
|
|
1297
|
+
expect(toIdString(options["userId"])).toBe(USER_ID.toString());
|
|
1298
|
+
expect(toIdString(options["projectId"])).toBe(PROJECT_ID.toString());
|
|
1299
|
+
expect(options["ruleType"]).toBe(
|
|
1300
|
+
NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
1301
|
+
);
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
/*
|
|
1305
|
+
* GAP A CLOSED - the inversion that matters most. The log used to end at
|
|
1306
|
+
* Error with a message nobody reads; it now ends at Executing with a message
|
|
1307
|
+
* that says a human was reached and on what.
|
|
1308
|
+
*/
|
|
1309
|
+
test("the outcome is a fallback delivery, not a terminal Error", async () => {
|
|
1310
|
+
stubIncident(SEV_C);
|
|
1311
|
+
|
|
1312
|
+
await driveOnCreateSuccess(incidentOnCallLog());
|
|
1313
|
+
|
|
1314
|
+
const updates: Array<{
|
|
1315
|
+
status: UserNotificationExecutionStatus;
|
|
1316
|
+
statusMessage?: string | undefined;
|
|
1317
|
+
}> = statusUpdates();
|
|
1318
|
+
|
|
1319
|
+
// First update marks it Started, second records the fallback delivery.
|
|
1320
|
+
expect(updates[0]!.status).toBe(UserNotificationExecutionStatus.Started);
|
|
1321
|
+
expect(updates[1]!.status).toBe(UserNotificationExecutionStatus.Executing);
|
|
1322
|
+
expect(updates[1]!.statusMessage).toContain("notified via fallback");
|
|
1323
|
+
expect(updates[1]!.statusMessage).toContain("Email");
|
|
1324
|
+
|
|
1325
|
+
// The dead-end's sentence is gone from this path entirely.
|
|
1326
|
+
expect(
|
|
1327
|
+
updates.some(
|
|
1328
|
+
(update: { statusMessage?: string | undefined }): boolean => {
|
|
1329
|
+
return (
|
|
1330
|
+
update.statusMessage ===
|
|
1331
|
+
"No notification rules found for this user. User should add the rules in User Settings > On-Call Rules."
|
|
1332
|
+
);
|
|
1333
|
+
},
|
|
1334
|
+
),
|
|
1335
|
+
).toBe(false);
|
|
1336
|
+
expect(
|
|
1337
|
+
updates.some(
|
|
1338
|
+
(update: { status: UserNotificationExecutionStatus }): boolean => {
|
|
1339
|
+
return update.status === UserNotificationExecutionStatus.Error;
|
|
1340
|
+
},
|
|
1341
|
+
),
|
|
1342
|
+
).toBe(false);
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
/*
|
|
1346
|
+
* GAP A CLOSED - the timeline now reads as a delivery rather than a failure,
|
|
1347
|
+
* and it names the channel. An operator reading the escalation can tell that
|
|
1348
|
+
* somebody was reached and how.
|
|
1349
|
+
*/
|
|
1350
|
+
test("the on-call timeline shows a notification sent through the fallback", async () => {
|
|
1351
|
+
stubIncident(SEV_C);
|
|
1352
|
+
|
|
1353
|
+
await driveOnCreateSuccess(incidentOnCallLog());
|
|
1354
|
+
|
|
1355
|
+
expect(timelineUpdateSpy).toHaveBeenCalledTimes(1);
|
|
1356
|
+
const timelineArg: {
|
|
1357
|
+
id: ObjectID;
|
|
1358
|
+
data: {
|
|
1359
|
+
status: OnCallDutyExecutionLogTimelineStatus;
|
|
1360
|
+
statusMessage: string;
|
|
1361
|
+
};
|
|
1362
|
+
} = timelineUpdateSpy.mock.calls[0]![0] as {
|
|
1363
|
+
id: ObjectID;
|
|
1364
|
+
data: {
|
|
1365
|
+
status: OnCallDutyExecutionLogTimelineStatus;
|
|
1366
|
+
statusMessage: string;
|
|
1367
|
+
};
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
expect(timelineArg.id.toString()).toBe(TIMELINE_ID.toString());
|
|
1371
|
+
expect(timelineArg.data.status).toBe(
|
|
1372
|
+
OnCallDutyExecutionLogTimelineStatus.NotificationSent,
|
|
1373
|
+
);
|
|
1374
|
+
expect(timelineArg.data.statusMessage).toContain("notified via fallback");
|
|
1375
|
+
expect(timelineArg.data.statusMessage).not.toContain(
|
|
1376
|
+
"No notification rules found for this user",
|
|
1377
|
+
);
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1380
|
+
test("an incident on a severity that existed at join time pages the responder normally", async () => {
|
|
1381
|
+
stubIncident(SEV_A);
|
|
1382
|
+
|
|
1383
|
+
await driveOnCreateSuccess(incidentOnCallLog());
|
|
1384
|
+
|
|
1385
|
+
expect(executeRuleSpy).toHaveBeenCalledTimes(1);
|
|
1386
|
+
|
|
1387
|
+
const updates: Array<{ status: UserNotificationExecutionStatus }> =
|
|
1388
|
+
statusUpdates();
|
|
1389
|
+
expect(updates[updates.length - 1]!.status).toBe(
|
|
1390
|
+
UserNotificationExecutionStatus.Executing,
|
|
1391
|
+
);
|
|
1392
|
+
expect(timelineUpdateSpy).toHaveBeenCalledWith(
|
|
1393
|
+
expect.objectContaining({
|
|
1394
|
+
data: expect.objectContaining({
|
|
1395
|
+
status: OnCallDutyExecutionLogTimelineStatus.NotificationSent,
|
|
1396
|
+
}),
|
|
1397
|
+
}),
|
|
1398
|
+
);
|
|
1399
|
+
});
|
|
1400
|
+
|
|
1401
|
+
test("the immediate-rule lookup is severity-filtered too, so there is no accidental fallback", async () => {
|
|
1402
|
+
stubIncident(SEV_A);
|
|
1403
|
+
const findBySpy: jest.SpyInstance = jest.spyOn(
|
|
1404
|
+
UserNotificationRuleService,
|
|
1405
|
+
"findBy",
|
|
1406
|
+
);
|
|
1407
|
+
|
|
1408
|
+
await driveOnCreateSuccess(incidentOnCallLog());
|
|
1409
|
+
|
|
1410
|
+
const query: Record<string, unknown> = (
|
|
1411
|
+
findBySpy.mock.calls[0]![0] as { query: Record<string, unknown> }
|
|
1412
|
+
).query;
|
|
1413
|
+
|
|
1414
|
+
expect(query["notifyAfterMinutes"]).toBe(0);
|
|
1415
|
+
expect(query["ruleType"]).toBe(
|
|
1416
|
+
NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
1417
|
+
);
|
|
1418
|
+
expect(toIdString(query["incidentSeverityId"])).toBe(SEV_A.toString());
|
|
1419
|
+
expect(query["alertSeverityId"]).toBeUndefined();
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
/*
|
|
1423
|
+
* GAP A CLOSED - alerts take the same route as incidents. The rule type
|
|
1424
|
+
* handed to the fallback is the ALERT one, so whatever the fallback decides is
|
|
1425
|
+
* decided against the right cell.
|
|
1426
|
+
*/
|
|
1427
|
+
test("an ALERT on a severity added after the fact is rescued the same way", async () => {
|
|
1428
|
+
stubAlert(ALERT_SEV_C);
|
|
1429
|
+
|
|
1430
|
+
await driveOnCreateSuccess(alertOnCallLog());
|
|
1431
|
+
|
|
1432
|
+
expect(executeRuleSpy).not.toHaveBeenCalled();
|
|
1433
|
+
expect(fallbackSpy).toHaveBeenCalledTimes(1);
|
|
1434
|
+
expect(
|
|
1435
|
+
(fallbackSpy.mock.calls[0]![0] as Record<string, unknown>)["ruleType"],
|
|
1436
|
+
).toBe(NotificationRuleType.ON_CALL_EXECUTED_ALERT);
|
|
1437
|
+
|
|
1438
|
+
const updates: Array<{
|
|
1439
|
+
status: UserNotificationExecutionStatus;
|
|
1440
|
+
statusMessage?: string | undefined;
|
|
1441
|
+
}> = statusUpdates();
|
|
1442
|
+
expect(updates[1]!.status).toBe(UserNotificationExecutionStatus.Executing);
|
|
1443
|
+
expect(updates[1]!.statusMessage).toContain("notified via fallback");
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
/*
|
|
1447
|
+
* GAP A CLOSED - and the other side of it. A responder with no verified
|
|
1448
|
+
* method at all cannot be rescued by anything, and that is the one case that
|
|
1449
|
+
* must still end as a loud, terminal Error naming what to do about it.
|
|
1450
|
+
*/
|
|
1451
|
+
test("a responder with no verified method still ends as an explicit Error", async () => {
|
|
1452
|
+
stubIncident(SEV_C);
|
|
1453
|
+
fallbackSpy.mockResolvedValue({
|
|
1454
|
+
notified: false,
|
|
1455
|
+
outcome: FallbackNotificationOutcome.NoUsableNotificationMethod,
|
|
1456
|
+
channelsUsed: [],
|
|
1457
|
+
} as never);
|
|
1458
|
+
|
|
1459
|
+
await driveOnCreateSuccess(incidentOnCallLog());
|
|
1460
|
+
|
|
1461
|
+
const updates: Array<{
|
|
1462
|
+
status: UserNotificationExecutionStatus;
|
|
1463
|
+
statusMessage?: string | undefined;
|
|
1464
|
+
}> = statusUpdates();
|
|
1465
|
+
|
|
1466
|
+
expect(updates[1]!.status).toBe(UserNotificationExecutionStatus.Error);
|
|
1467
|
+
expect(updates[1]!.statusMessage).toContain(
|
|
1468
|
+
"has no verified notification method",
|
|
1469
|
+
);
|
|
1470
|
+
expect(updates[1]!.statusMessage).toContain(
|
|
1471
|
+
"User Settings > Notification Methods",
|
|
1472
|
+
);
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
test("an alert on a severity that existed at join time still pages", async () => {
|
|
1476
|
+
stubAlert(ALERT_SEV_A);
|
|
1477
|
+
|
|
1478
|
+
await driveOnCreateSuccess(alertOnCallLog());
|
|
1479
|
+
|
|
1480
|
+
expect(executeRuleSpy).toHaveBeenCalledTimes(1);
|
|
1481
|
+
});
|
|
1482
|
+
|
|
1483
|
+
/*
|
|
1484
|
+
* The two Phase 1 halves are independent, and this is the proof: with the row
|
|
1485
|
+
* present the responder is paged through their OWN configured rule and the
|
|
1486
|
+
* fallback is never consulted at all. The backfill is what restores the
|
|
1487
|
+
* responder's intent; the fallback is only the safety net under it.
|
|
1488
|
+
*/
|
|
1489
|
+
test("backfilling the missing row is sufficient: the very same C-severity incident pages through the real rule", async () => {
|
|
1490
|
+
stubIncident(SEV_C);
|
|
1491
|
+
|
|
1492
|
+
// Re-running the defaults against a project that now has C is the backfill.
|
|
1493
|
+
stubSeverities([SEV_A, SEV_B, SEV_C], [ALERT_SEV_A, ALERT_SEV_B]);
|
|
1494
|
+
await runDefaults();
|
|
1495
|
+
|
|
1496
|
+
await driveOnCreateSuccess(incidentOnCallLog());
|
|
1497
|
+
|
|
1498
|
+
expect(executeRuleSpy).toHaveBeenCalledTimes(1);
|
|
1499
|
+
expect(fallbackSpy).not.toHaveBeenCalled();
|
|
1500
|
+
|
|
1501
|
+
const updates: Array<{ status: UserNotificationExecutionStatus }> =
|
|
1502
|
+
statusUpdates();
|
|
1503
|
+
expect(updates[updates.length - 1]!.status).toBe(
|
|
1504
|
+
UserNotificationExecutionStatus.Executing,
|
|
1505
|
+
);
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
test("without the backfill the store simply has no row for the new severity", async () => {
|
|
1509
|
+
const rowsForC: Array<StoredRule> = matchingRules({
|
|
1510
|
+
userId: USER_ID,
|
|
1511
|
+
projectId: PROJECT_ID,
|
|
1512
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
1513
|
+
incidentSeverityId: SEV_C,
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
expect(rowsForC).toHaveLength(0);
|
|
1517
|
+
|
|
1518
|
+
// ... while the severities present at join time each have exactly one.
|
|
1519
|
+
expect(
|
|
1520
|
+
matchingRules({
|
|
1521
|
+
userId: USER_ID,
|
|
1522
|
+
projectId: PROJECT_ID,
|
|
1523
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
1524
|
+
incidentSeverityId: SEV_A,
|
|
1525
|
+
}),
|
|
1526
|
+
).toHaveLength(1);
|
|
1527
|
+
expect(
|
|
1528
|
+
matchingRules({
|
|
1529
|
+
userId: USER_ID,
|
|
1530
|
+
projectId: PROJECT_ID,
|
|
1531
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
1532
|
+
incidentSeverityId: SEV_B,
|
|
1533
|
+
}),
|
|
1534
|
+
).toHaveLength(1);
|
|
1535
|
+
});
|
|
1536
|
+
});
|