@oneuptime/common 12.0.8 → 12.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.ts +1 -1
- package/Models/AnalyticsModels/MetricItemAggMV1mByService.ts +1 -1
- package/Models/DatabaseModels/AIConversation.ts +32 -0
- package/Models/DatabaseModels/AIConversationMessage.ts +41 -0
- package/Models/DatabaseModels/AlertEpisodeMember.ts +27 -0
- package/Models/DatabaseModels/IncidentEpisodeMember.ts +28 -0
- package/Models/DatabaseModels/Index.ts +12 -4
- package/Models/DatabaseModels/{TelemetryEntity.ts → InventoryItem.ts} +139 -9
- package/Models/DatabaseModels/InventoryItemCustomField.ts +434 -0
- package/Models/DatabaseModels/{TelemetryEntityRelationship.ts → InventoryItemRelationship.ts} +8 -8
- package/Models/DatabaseModels/NetworkDevice.ts +141 -0
- package/Models/DatabaseModels/NetworkDeviceLink.ts +699 -0
- package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +467 -0
- package/Models/DatabaseModels/NetworkTopologySuppression.ts +429 -0
- package/Models/DatabaseModels/OnCallDutyPolicyFeed.ts +9 -0
- package/Models/DatabaseModels/Project.ts +78 -0
- package/Models/DatabaseModels/UserCall.ts +42 -0
- package/Models/DatabaseModels/UserEmail.ts +44 -0
- package/Models/DatabaseModels/UserNotificationRule.ts +425 -55
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +24 -2
- package/Models/DatabaseModels/UserPush.ts +49 -0
- package/Models/DatabaseModels/UserSMS.ts +43 -0
- package/Models/DatabaseModels/UserTelegram.ts +59 -0
- package/Models/DatabaseModels/UserWebhook.ts +52 -0
- package/Models/DatabaseModels/UserWhatsApp.ts +41 -0
- package/Models/DatabaseModels/WorkflowVariable.ts +12 -0
- package/Server/API/AIChatAPI.ts +315 -1
- package/Server/API/DashboardAPI.ts +217 -1
- package/Server/API/OnCallReadinessAPI.ts +841 -0
- package/Server/API/TeamComplianceAPI.ts +69 -17
- package/Server/API/TelemetryAPI.ts +220 -12
- package/Server/EnvironmentConfig.ts +52 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +22 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +4 -4
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +5 -5
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.ts +82 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.ts +91 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.ts +255 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.ts +107 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.ts +90 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.ts +39 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.ts +59 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
- package/Server/Infrastructure/Queue.ts +78 -13
- package/Server/Middleware/PublicDashboardRateLimit.ts +593 -0
- package/Server/Services/AIService.ts +7 -0
- package/Server/Services/AlertEpisodeStateTimelineService.ts +29 -0
- package/Server/Services/AlertSeverityService.ts +63 -0
- package/Server/Services/DashboardService.ts +9 -10
- package/Server/Services/DatabaseService.ts +32 -2
- package/Server/Services/IncidentEpisodeStateTimelineService.ts +29 -0
- package/Server/Services/IncidentSeverityService.ts +76 -0
- package/Server/Services/Index.ts +12 -4
- package/Server/Services/InventoryItemCustomFieldService.ts +9 -0
- package/Server/Services/{TelemetryEntityRelationshipService.ts → InventoryItemRelationshipService.ts} +4 -4
- package/Server/Services/{TelemetryEntityService.ts → InventoryItemService.ts} +89 -20
- package/Server/Services/LogAggregationService.ts +45 -8
- package/Server/Services/MetricAggregationService.ts +121 -0
- package/Server/Services/MetricService.ts +7 -7
- package/Server/Services/NetworkDeviceLinkRuleService.ts +10 -0
- package/Server/Services/NetworkDeviceLinkService.ts +84 -0
- package/Server/Services/NetworkDeviceService.ts +140 -0
- package/Server/Services/NetworkSiteService.ts +77 -25
- package/Server/Services/NetworkTopologySuppressionService.ts +84 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +41 -29
- package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +8 -0
- package/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +62 -13
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +61 -1
- package/Server/Services/OnCallNotificationAlertingService.ts +742 -0
- package/Server/Services/OnCallReadinessService.ts +2803 -0
- package/Server/Services/OnCallSetupReminderService.ts +955 -0
- package/Server/Services/ProfileAggregationService.ts +123 -0
- package/Server/Services/StatusPageService.ts +9 -10
- package/Server/Services/TeamComplianceService.ts +429 -252
- package/Server/Services/UserCallService.ts +26 -1
- package/Server/Services/UserEmailService.ts +26 -1
- package/Server/Services/UserNotificationRuleAdminService.ts +1183 -0
- package/Server/Services/UserNotificationRuleService.ts +3812 -333
- package/Server/Services/UserOnCallLogService.ts +561 -48
- package/Server/Services/UserPushService.ts +29 -0
- package/Server/Services/UserService.ts +11 -0
- package/Server/Services/UserSmsService.ts +26 -1
- package/Server/Services/UserTelegramService.ts +24 -1
- package/Server/Services/UserWebhookService.ts +28 -1
- package/Server/Services/UserWhatsAppService.ts +24 -1
- package/Server/Types/Database/Permissions/BasePermission.ts +19 -0
- package/Server/Types/Database/Permissions/CreatePermission.ts +164 -0
- package/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.ts +340 -0
- package/Server/Types/Database/Permissions/QueryPermission.ts +48 -0
- package/Server/Types/Database/Permissions/TenantPermission.ts +8 -1
- package/Server/Types/Workflow/Components/API/Delete.ts +1 -1
- package/Server/Types/Workflow/Components/API/Get.ts +1 -1
- package/Server/Types/Workflow/Components/API/Patch.ts +1 -1
- package/Server/Types/Workflow/Components/API/Post.ts +1 -1
- package/Server/Types/Workflow/Components/API/Put.ts +1 -1
- package/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +29 -5
- package/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +18 -10
- package/Server/Types/Workflow/Components/BaseModel/ModelArguments.ts +55 -0
- package/Server/Types/Workflow/Components/Conditions/IfElse.ts +3 -17
- package/Server/Types/Workflow/Components/Email.ts +25 -7
- package/Server/Types/Workflow/Components/JavaScript.ts +10 -3
- package/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +1 -1
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +643 -48
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +32 -3
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +20 -6
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +7 -0
- package/Server/Utils/AI/Toolbox/AIActionTools.ts +2 -2
- package/Server/Utils/AI/Toolbox/AIMetaTools.ts +863 -0
- package/Server/Utils/AI/Toolbox/AlertTools.ts +177 -15
- package/Server/Utils/AI/Toolbox/IncidentTools.ts +191 -10
- package/Server/Utils/AI/Toolbox/Index.ts +48 -0
- package/Server/Utils/AI/Toolbox/MonitorTools.ts +298 -11
- package/Server/Utils/AI/Toolbox/NoteWriteTools.ts +295 -0
- package/Server/Utils/AI/Toolbox/OnCallTools.ts +1246 -0
- package/Server/Utils/AI/Toolbox/RunbookTools.ts +424 -0
- package/Server/Utils/AI/Toolbox/SloTools.ts +456 -0
- package/Server/Utils/AI/Toolbox/StatusPageTools.ts +559 -0
- package/Server/Utils/AI/Toolbox/TeamTools.ts +327 -0
- package/Server/Utils/AI/Toolbox/TimelineTools.ts +615 -0
- package/Server/Utils/AI/Toolbox/WorkflowProbeTools.ts +664 -0
- package/Server/Utils/ClientIp.ts +221 -0
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +47 -0
- package/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.ts +163 -0
- package/Server/Utils/Dashboard/PublicDashboardSloWidget.ts +147 -0
- package/Server/Utils/Express.ts +12 -17
- package/Server/Utils/LLM/LLMService.ts +85 -8
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +204 -10
- package/Server/Utils/SSRFProtection.ts +98 -23
- package/Server/Utils/StartServer.ts +12 -3
- package/Server/Utils/Telemetry/EntityRegistry.ts +205 -18
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +25 -25
- package/Server/Utils/Telemetry/TelemetryEntity.ts +160 -52
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +7 -3
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +2146 -0
- package/Tests/App/Dashboard/CreateWorkflowModal.test.tsx +561 -0
- package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +2470 -0
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +1897 -0
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +3606 -0
- package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +784 -0
- package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +1119 -0
- package/Tests/App/Dashboard/SloWidgetFetching.test.tsx +531 -0
- package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +1312 -0
- package/Tests/App/Dashboard/UserSettingsSetupChecklistPage.test.tsx +1390 -0
- package/Tests/Models/InventoryItemModel.test.ts +174 -0
- package/Tests/Models/InventoryItemNaming.test.ts +302 -0
- package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +437 -0
- package/Tests/Server/API/DashboardPublicRateLimit.test.ts +659 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +18 -0
- package/Tests/Server/API/DashboardPublicSloAPI.test.ts +880 -0
- package/Tests/Server/API/Helpers.ts +24 -15
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +2680 -0
- package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +915 -0
- package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +533 -0
- package/Tests/Server/Infrastructure/Postgres/InventoryItemArchiveMigration.test.ts +213 -0
- package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +432 -0
- package/Tests/Server/Infrastructure/Queue.test.ts +293 -0
- package/Tests/Server/Middleware/PublicDashboardRateLimit.test.ts +1645 -0
- package/Tests/Server/Services/AdminRuleEditGuards.test.ts +2848 -0
- package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1393 -0
- package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +1802 -0
- package/Tests/Server/Services/EpisodeStateTimelineNote.test.ts +304 -0
- package/Tests/Server/Services/InventoryItemDisplayName.test.ts +339 -0
- package/Tests/Server/Services/{TelemetryEntityManualCreate.test.ts → InventoryItemManualCreate.test.ts} +27 -27
- package/Tests/Server/Services/IpAllowlistSpoofing.test.ts +450 -0
- package/Tests/Server/Services/LogAggregationService.test.ts +235 -1
- package/Tests/Server/Services/MetricAggregationService.test.ts +231 -0
- package/Tests/Server/Services/MetricEntityMVKeyParity.test.ts +80 -29
- package/Tests/Server/Services/MetricServiceAggregate.test.ts +30 -30
- package/Tests/Server/Services/NetworkSiteService.test.ts +18 -3
- package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +1728 -0
- package/Tests/Server/Services/NotificationDeletionImpact.test.ts +2402 -0
- package/Tests/Server/Services/OnCallDutyPolicyExecutionLogTimelineGapFeed.test.ts +394 -0
- package/Tests/Server/Services/OnCallNotificationFallback.test.ts +1744 -0
- package/Tests/Server/Services/OnCallReadinessService.test.ts +4295 -0
- package/Tests/Server/Services/OnCallSetupReminder.test.ts +1272 -0
- package/Tests/Server/Services/OnCallWeeklyReadinessDigest.test.ts +1021 -0
- package/Tests/Server/Services/ProfileAggregationService.test.ts +296 -0
- package/Tests/Server/Services/SeverityCreationRuleBackfill.test.ts +1536 -0
- package/Tests/Server/Services/SeverityRuleBackfill.test.ts +1818 -0
- package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +1845 -0
- package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +1394 -0
- package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +1166 -0
- package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1468 -0
- package/Tests/Server/Services/UserOnCallLogNoNotificationRules.test.ts +1457 -0
- package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +1546 -0
- package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +529 -0
- package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +1219 -0
- package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +1089 -0
- package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +2 -1
- package/Tests/Server/Types/Workflow/Components/BaseModelDatabaseComponents.test.ts +190 -0
- package/Tests/Server/Types/Workflow/Components/ChatWebhookComponents.test.ts +44 -14
- package/Tests/Server/Types/Workflow/Components/Email.test.ts +151 -0
- package/Tests/Server/Types/Workflow/Components/IfElse.test.ts +98 -0
- package/Tests/Server/Types/Workflow/Components/JavaScript.test.ts +51 -0
- package/Tests/Server/Utils/AI/AIMetaTools.test.ts +586 -0
- package/Tests/Server/Utils/AI/AlertMonitorFilters.test.ts +582 -0
- package/Tests/Server/Utils/AI/ChatAgentRunner.test.ts +726 -0
- package/Tests/Server/Utils/AI/IncidentToolsFilters.test.ts +315 -0
- package/Tests/Server/Utils/AI/LLMServiceStopReason.test.ts +314 -0
- package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +26 -3
- package/Tests/Server/Utils/AI/NoteWriteTools.test.ts +268 -0
- package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +169 -0
- package/Tests/Server/Utils/AI/OnCallTools.test.ts +664 -0
- package/Tests/Server/Utils/AI/RunbookTools.test.ts +325 -0
- package/Tests/Server/Utils/AI/SloTools.test.ts +306 -0
- package/Tests/Server/Utils/AI/StatusPageTools.test.ts +391 -0
- package/Tests/Server/Utils/AI/TeamTools.test.ts +257 -0
- package/Tests/Server/Utils/AI/TimelineTools.test.ts +472 -0
- package/Tests/Server/Utils/AI/WorkflowProbeTools.test.ts +428 -0
- package/Tests/Server/Utils/ClientIp.test.ts +438 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +171 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.test.ts +383 -0
- package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +23 -25
- package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +6 -0
- package/Tests/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.test.ts +307 -0
- package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +468 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorTelemetryDeepLinks.test.ts +460 -0
- package/Tests/Server/Utils/ResponseRateLimitStatusCodes.test.ts +137 -0
- package/Tests/Server/Utils/SSRFProtectionBypasses.test.ts +40 -8
- package/Tests/Server/Utils/SSRFProtectionUserInfo.test.ts +351 -0
- package/Tests/Server/Utils/Telemetry/EntityRegistryRetirement.test.ts +531 -0
- package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +15 -15
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +356 -39
- package/Tests/Types/IP/IP.test.ts +263 -0
- package/Tests/Types/IP/IPWhitelist.test.ts +197 -0
- package/Tests/Types/IP/IPv6.test.ts +12 -1
- package/Tests/Types/Monitor/SnmpOid.test.ts +64 -0
- package/Tests/Types/NetworkDevice/NetworkDeviceMonitoringMethod.test.ts +110 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +106 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeDifferential.test.ts +511 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageEndToEnd.test.ts +489 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageGapTolerance.test.ts +479 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageState.test.ts +822 -0
- package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +6 -6
- package/Tests/Types/Workflow/BaseModelComponents.test.ts +429 -0
- package/Tests/Types/Workflow/Components/BaseModel.test.ts +225 -0
- package/Tests/Types/Workflow/IntegrationCredentialMetadata.test.ts +100 -0
- package/Tests/Types/Workflow/TemplateSyntax.test.ts +112 -0
- package/Tests/Types/Workflow/Templates.test.ts +916 -101
- package/Tests/UI/Components/ActiveFilterChipsOpenRoute.test.tsx +82 -0
- package/Tests/UI/Components/ComponentsModal.test.tsx +564 -7
- package/Tests/UI/Components/KeyboardShortcut.test.tsx +95 -0
- package/Tests/UI/Components/LogDetailsPanelCrossSignal.test.tsx +448 -0
- package/Tests/UI/Components/LogsTableCrossLinks.test.tsx +263 -0
- package/Tests/UI/Components/PendingProjectInvitations.test.tsx +913 -0
- package/Tests/UI/Components/SimpleLogViewer.test.tsx +228 -0
- package/Tests/UI/Components/TableRowSelectability.test.tsx +312 -0
- package/Tests/UI/Components/Workflow/GraphLint.test.ts +72 -0
- package/Tests/UI/Components/Workflow/GraphLintSummary.test.ts +755 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.test.ts +242 -7
- package/Tests/UI/Components/Workflow/ModelColumnEditorServerContract.test.ts +15 -7
- package/Tests/UI/Components/Workflow/ModelSchema.test.ts +242 -28
- package/Tests/UI/Components/Workflow/RunStatusWatcher.test.ts +59 -1
- package/Tests/UI/Components/Workflow/StepTraceViewer.test.tsx +256 -0
- package/Tests/UI/Components/Workflow/UseRunWatch.test.tsx +665 -0
- package/Tests/UI/Components/Workflow/WorkflowIssuesModal.test.tsx +485 -0
- package/Tests/UI/Components/Workflow/WorkflowLogModal.test.tsx +478 -0
- package/Tests/UI/Components/Workflow/WorkflowStatusBar.test.tsx +379 -0
- package/Tests/UI/EsbuildConfig.test.ts +607 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +11 -4
- package/Tests/UI/Utils/ModelAPICreateMiscData.test.ts +94 -0
- package/Tests/UI/Utils/Platform.test.ts +147 -0
- package/Tests/UI/Utils/ProjectInvitationDisplay.test.ts +357 -0
- package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +198 -0
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +487 -0
- package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +698 -0
- package/Tests/__mocks__/bullmq.js +55 -0
- package/Types/AI/AIChatMessageStatus.ts +8 -1
- package/Types/AI/AIChatTypes.ts +12 -0
- package/Types/Database/AccessControl/OwnerOnlyColumn.ts +88 -0
- package/Types/Exception/ExceptionCode.ts +2 -0
- package/Types/Exception/ServiceUnavailableException.ts +8 -0
- package/Types/Exception/TooManyRequestsException.ts +8 -0
- package/Types/IP/IP.ts +93 -47
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +49 -3
- package/Types/NetworkDevice/NetworkDeviceMonitoringMethod.ts +55 -0
- package/Types/OnCallDutyPolicy/Layer.ts +203 -149
- package/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.ts +13 -0
- package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +155 -10
- package/Types/Permission.ts +193 -0
- package/Types/Telemetry/EntityRelationshipType.ts +1 -1
- package/Types/Telemetry/EntitySource.ts +1 -1
- package/Types/Telemetry/EntityType.ts +1 -1
- package/Types/Telemetry/EntityTypeGroups.ts +1 -1
- package/Types/Workflow/Components/BaseModel.ts +75 -29
- package/Types/Workflow/Components/Discord.ts +1 -0
- package/Types/Workflow/Components/Email.ts +12 -3
- package/Types/Workflow/Components/JavaScript.ts +7 -0
- package/Types/Workflow/Components/MicrosoftTeams.ts +3 -2
- package/Types/Workflow/Components/Slack.ts +1 -0
- package/Types/Workflow/Components/Telegram.ts +1 -0
- package/Types/Workflow/TemplateSyntax.ts +44 -0
- package/Types/Workflow/Templates.ts +2097 -45
- package/UI/Components/Calendar/Calendar.css +43 -0
- package/UI/Components/Calendar/Calendar.tsx +8 -0
- package/UI/Components/Card/Card.tsx +2 -2
- package/UI/Components/Checkbox/Checkbox.tsx +16 -0
- package/UI/Components/Dictionary/Dictionary.tsx +48 -9
- package/UI/Components/FormModal/BasicFormModal.tsx +2 -1
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +53 -3
- package/UI/Components/Input/Input.tsx +1 -0
- package/UI/Components/KeyboardShortcut/KeyboardKey.ts +185 -0
- package/UI/Components/KeyboardShortcut/KeyboardShortcut.tsx +87 -0
- package/UI/Components/LogsViewer/LogsViewer.tsx +28 -0
- package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +31 -0
- package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +18 -18
- package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +363 -14
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +11 -0
- package/UI/Components/LogsViewer/components/LogsTable.tsx +155 -12
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +29 -0
- package/UI/Components/LogsViewer/types.ts +23 -0
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +9 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +15 -30
- package/UI/Components/ProjectInvitations/PendingProjectInvitations.tsx +442 -0
- package/UI/Components/SimpleLogViewer/SimpleLogViewer.tsx +23 -1
- package/UI/Components/Table/Table.tsx +49 -16
- package/UI/Components/Table/TableBody.tsx +53 -28
- package/UI/Components/Table/TableHeader.tsx +13 -0
- package/UI/Components/Table/TableRow.tsx +58 -26
- package/UI/Components/Workflow/ArgumentsForm.tsx +155 -9
- package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +26 -0
- package/UI/Components/Workflow/ComponentSettingsModal.tsx +10 -1
- package/UI/Components/Workflow/ComponentValuePickerModal.tsx +135 -7
- package/UI/Components/Workflow/ComponentsModal.tsx +116 -22
- package/UI/Components/Workflow/DocumentationViewer.tsx +59 -9
- package/UI/Components/Workflow/GraphLint.ts +42 -5
- package/UI/Components/Workflow/GraphLintSummary.ts +390 -0
- package/UI/Components/Workflow/ModelColumnEditor.tsx +154 -22
- package/UI/Components/Workflow/ModelSchema.ts +115 -33
- package/UI/Components/Workflow/RunStatusWatcher.ts +1 -1
- package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
- package/UI/Components/Workflow/UseRunWatch.ts +212 -0
- package/UI/Components/Workflow/VariableModal.tsx +6 -2
- package/UI/Components/Workflow/Workflow.tsx +49 -3
- package/UI/Components/Workflow/WorkflowIssuesModal.tsx +255 -0
- package/UI/Components/Workflow/WorkflowLogModal.tsx +128 -0
- package/UI/Components/Workflow/WorkflowStatusBar.tsx +224 -0
- package/UI/Utils/AIChatExport/ConversationMarkdown.ts +10 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +7 -1
- package/UI/Utils/Platform.ts +149 -0
- package/UI/Utils/ProjectInvitationDisplay.ts +118 -0
- package/UI/esbuild-config.js +22 -1
- package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +187 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +888 -164
- package/Utils/Telemetry/CrossSignalScope.ts +502 -0
- package/Utils/Telemetry/EntityKey.ts +5 -5
- package/Utils/Telemetry/EntityRelationship.ts +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js +1 -1
- package/build/dist/Models/DatabaseModels/AIConversation.js +32 -0
- package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIConversationMessage.js +42 -0
- package/build/dist/Models/DatabaseModels/AIConversationMessage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +28 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +29 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +12 -4
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/{TelemetryEntity.js → InventoryItem.js} +167 -31
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -0
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js +454 -0
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js.map +1 -0
- package/build/dist/Models/DatabaseModels/{TelemetryEntityRelationship.js → InventoryItemRelationship.js} +27 -27
- package/build/dist/Models/DatabaseModels/InventoryItemRelationship.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +141 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +719 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +475 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js +446 -0
- package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js +9 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +80 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserCall.js +46 -2
- package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserEmail.js +48 -2
- package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js +424 -55
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +24 -2
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserPush.js +51 -1
- package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSMS.js +47 -2
- package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTelegram.js +65 -3
- package/build/dist/Models/DatabaseModels/UserTelegram.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWebhook.js +56 -2
- package/build/dist/Models/DatabaseModels/UserWebhook.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js +45 -2
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js +12 -0
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +237 -1
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +165 -13
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/OnCallReadinessAPI.js +599 -0
- package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -0
- package/build/dist/Server/API/TeamComplianceAPI.js +68 -9
- package/build/dist/Server/API/TeamComplianceAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +129 -18
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +45 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +4 -4
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js +39 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js +150 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js +57 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js +69 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js +32 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js +48 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +72 -13
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Middleware/PublicDashboardRateLimit.js +399 -0
- package/build/dist/Server/Middleware/PublicDashboardRateLimit.js.map +1 -0
- package/build/dist/Server/Services/AIService.js +1 -0
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +24 -3
- package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/AlertSeverityService.js +54 -0
- package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
- package/build/dist/Server/Services/DashboardService.js +10 -9
- package/build/dist/Server/Services/DashboardService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +24 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +24 -3
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentSeverityService.js +67 -0
- package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +12 -4
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemCustomFieldService.js +9 -0
- package/build/dist/Server/Services/InventoryItemCustomFieldService.js.map +1 -0
- package/build/dist/Server/Services/{TelemetryEntityRelationshipService.js → InventoryItemRelationshipService.js} +6 -6
- package/build/dist/Server/Services/InventoryItemRelationshipService.js.map +1 -0
- package/build/dist/Server/Services/{TelemetryEntityService.js → InventoryItemService.js} +68 -23
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -0
- package/build/dist/Server/Services/LogAggregationService.js +27 -8
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricAggregationService.js +80 -0
- package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +6 -6
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js +9 -0
- package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceLinkService.js +71 -0
- package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceService.js +113 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +53 -13
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/NetworkTopologySuppressionService.js +85 -0
- package/build/dist/Server/Services/NetworkTopologySuppressionService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +48 -32
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +8 -0
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js +51 -12
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +57 -13
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallNotificationAlertingService.js +548 -0
- package/build/dist/Server/Services/OnCallNotificationAlertingService.js.map +1 -0
- package/build/dist/Server/Services/OnCallReadinessService.js +1961 -0
- package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -0
- package/build/dist/Server/Services/OnCallSetupReminderService.js +738 -0
- package/build/dist/Server/Services/OnCallSetupReminderService.js.map +1 -0
- package/build/dist/Server/Services/ProfileAggregationService.js +68 -4
- package/build/dist/Server/Services/ProfileAggregationService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +11 -10
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TeamComplianceService.js +312 -160
- package/build/dist/Server/Services/TeamComplianceService.js.map +1 -1
- package/build/dist/Server/Services/UserCallService.js +24 -1
- package/build/dist/Server/Services/UserCallService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +24 -1
- package/build/dist/Server/Services/UserEmailService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js +858 -0
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js +2830 -175
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogService.js +488 -43
- package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
- package/build/dist/Server/Services/UserPushService.js +26 -0
- package/build/dist/Server/Services/UserPushService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +10 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserSmsService.js +24 -1
- package/build/dist/Server/Services/UserSmsService.js.map +1 -1
- package/build/dist/Server/Services/UserTelegramService.js +22 -1
- package/build/dist/Server/Services/UserTelegramService.js.map +1 -1
- package/build/dist/Server/Services/UserWebhookService.js +25 -1
- package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
- package/build/dist/Server/Services/UserWhatsAppService.js +22 -1
- package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js +12 -1
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +126 -0
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js +254 -0
- package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js.map +1 -0
- package/build/dist/Server/Types/Database/Permissions/QueryPermission.js +47 -2
- package/build/dist/Server/Types/Database/Permissions/QueryPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +7 -0
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Get.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Post.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Put.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js +21 -5
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js +14 -10
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js +31 -0
- package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +3 -9
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Email.js +15 -4
- package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js +7 -2
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +514 -56
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +20 -3
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +19 -6
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +7 -0
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js +2 -2
- package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js +692 -0
- package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +148 -12
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +157 -10
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +37 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js +259 -14
- package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js +235 -0
- package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +1000 -0
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js +356 -0
- package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +394 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js +465 -0
- package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js +280 -0
- package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js +527 -0
- package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js +548 -0
- package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js.map +1 -0
- package/build/dist/Server/Utils/ClientIp.js +137 -0
- package/build/dist/Server/Utils/ClientIp.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +38 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js +89 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js +77 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js.map +1 -0
- package/build/dist/Server/Utils/Express.js +12 -12
- package/build/dist/Server/Utils/Express.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +70 -7
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +121 -11
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +82 -21
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +12 -4
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +165 -18
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +11 -11
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +122 -47
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +7 -3
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/AI/AIChatMessageStatus.js +8 -1
- package/build/dist/Types/AI/AIChatMessageStatus.js.map +1 -1
- package/build/dist/Types/AI/AIChatTypes.js +12 -0
- package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +60 -0
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js +2 -0
- package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
- package/build/dist/Types/Exception/ServiceUnavailableException.js +8 -0
- package/build/dist/Types/Exception/ServiceUnavailableException.js.map +1 -0
- package/build/dist/Types/Exception/TooManyRequestsException.js +8 -0
- package/build/dist/Types/Exception/TooManyRequestsException.js.map +1 -0
- package/build/dist/Types/IP/IP.js +87 -43
- package/build/dist/Types/IP/IP.js.map +1 -1
- package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js +50 -0
- package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +186 -123
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js +13 -0
- package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +105 -11
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
- package/build/dist/Types/Permission.js +174 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityRelationshipType.js +1 -1
- package/build/dist/Types/Telemetry/EntitySource.js +1 -1
- package/build/dist/Types/Telemetry/EntityType.js +1 -1
- package/build/dist/Types/Telemetry/EntityTypeGroups.js +1 -1
- package/build/dist/Types/Telemetry/EntityTypeGroups.js.map +1 -1
- package/build/dist/Types/Workflow/Components/BaseModel.js +66 -28
- package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Discord.js +1 -0
- package/build/dist/Types/Workflow/Components/Discord.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Email.js +12 -3
- package/build/dist/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Types/Workflow/Components/JavaScript.js +7 -0
- package/build/dist/Types/Workflow/Components/JavaScript.js.map +1 -1
- package/build/dist/Types/Workflow/Components/MicrosoftTeams.js +3 -2
- package/build/dist/Types/Workflow/Components/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Slack.js +1 -0
- package/build/dist/Types/Workflow/Components/Slack.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Telegram.js +1 -0
- package/build/dist/Types/Workflow/Components/Telegram.js.map +1 -1
- package/build/dist/Types/Workflow/TemplateSyntax.js +12 -0
- package/build/dist/Types/Workflow/TemplateSyntax.js.map +1 -1
- package/build/dist/Types/Workflow/Templates.js +1931 -38
- package/build/dist/Types/Workflow/Templates.js.map +1 -1
- package/build/dist/UI/Components/Calendar/Calendar.js +1 -1
- package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
- package/build/dist/UI/Components/Checkbox/Checkbox.js +1 -1
- package/build/dist/UI/Components/Checkbox/Checkbox.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/Dictionary.js +25 -11
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +27 -4
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +1 -0
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js +163 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js.map +1 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js +47 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +4 -4
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +11 -1
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +10 -8
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +227 -14
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +5 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +69 -5
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +8 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +9 -2
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +8 -21
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js +251 -0
- package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js.map +1 -0
- package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js +9 -2
- package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +27 -15
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableBody.js +24 -18
- package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
- package/build/dist/UI/Components/Table/TableHeader.js +9 -1
- package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +29 -21
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +130 -12
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js +10 -1
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js +4 -4
- package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js +57 -7
- package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +53 -18
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/DocumentationViewer.js +19 -6
- package/build/dist/UI/Components/Workflow/DocumentationViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/GraphLint.js +33 -4
- package/build/dist/UI/Components/Workflow/GraphLint.js.map +1 -1
- package/build/dist/UI/Components/Workflow/GraphLintSummary.js +231 -0
- package/build/dist/UI/Components/Workflow/GraphLintSummary.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +116 -21
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ModelSchema.js +72 -34
- package/build/dist/UI/Components/Workflow/ModelSchema.js.map +1 -1
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js +1 -1
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js.map +1 -1
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/UseRunWatch.js +123 -0
- package/build/dist/UI/Components/Workflow/UseRunWatch.js.map +1 -0
- package/build/dist/UI/Components/Workflow/VariableModal.js +3 -2
- package/build/dist/UI/Components/Workflow/VariableModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Workflow.js +41 -1
- package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
- package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js +99 -0
- package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js.map +1 -0
- package/build/dist/UI/Components/Workflow/WorkflowLogModal.js +56 -0
- package/build/dist/UI/Components/Workflow/WorkflowLogModal.js.map +1 -0
- package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js +92 -0
- package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +9 -0
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/Platform.js +118 -0
- package/build/dist/UI/Utils/Platform.js.map +1 -0
- package/build/dist/UI/Utils/ProjectInvitationDisplay.js +106 -0
- package/build/dist/UI/Utils/ProjectInvitationDisplay.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +108 -0
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +642 -136
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +328 -0
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -0
- package/build/dist/Utils/Telemetry/EntityKey.js +5 -5
- package/build/dist/Utils/Telemetry/EntityRelationship.js +1 -1
- package/jest.config.json +1 -0
- package/package.json +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryEntity.js.map +0 -1
- package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +0 -1
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +0 -1
- package/build/dist/Server/Services/TelemetryEntityService.js.map +0 -1
|
@@ -0,0 +1,1272 @@
|
|
|
1
|
+
import DatabaseConfig from "../../../Server/DatabaseConfig";
|
|
2
|
+
import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
|
|
3
|
+
import MailService from "../../../Server/Services/MailService";
|
|
4
|
+
import OnCallReadinessService, {
|
|
5
|
+
ReadinessCoverageCell,
|
|
6
|
+
ReadinessStatus,
|
|
7
|
+
ResponderSource,
|
|
8
|
+
UserReadiness,
|
|
9
|
+
} from "../../../Server/Services/OnCallReadinessService";
|
|
10
|
+
import OnCallSetupReminderService, {
|
|
11
|
+
OnCallSetupReminderService as OnCallSetupReminderServiceClass,
|
|
12
|
+
SetupReminderOutcome,
|
|
13
|
+
SetupReminderResult,
|
|
14
|
+
SetupReminderUserResult,
|
|
15
|
+
SETUP_REMINDER_MAX_RECIPIENTS,
|
|
16
|
+
} from "../../../Server/Services/OnCallSetupReminderService";
|
|
17
|
+
import ProjectService from "../../../Server/Services/ProjectService";
|
|
18
|
+
import TeamMemberService from "../../../Server/Services/TeamMemberService";
|
|
19
|
+
import logger from "../../../Server/Utils/Logger";
|
|
20
|
+
import HTTPResponse from "../../../Types/API/HTTPResponse";
|
|
21
|
+
import Protocol from "../../../Types/API/Protocol";
|
|
22
|
+
import URL from "../../../Types/API/URL";
|
|
23
|
+
import Hostname from "../../../Types/API/Hostname";
|
|
24
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
25
|
+
import NotificationRuleType from "../../../Types/NotificationRule/NotificationRuleType";
|
|
26
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
27
|
+
import { beforeEach, describe, expect, test } from "@jest/globals";
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* The setup reminder: the one action in the on-call readiness feature that
|
|
31
|
+
* leaves the product and lands in somebody's inbox.
|
|
32
|
+
*
|
|
33
|
+
* Its first cut was a button wired to a function that threw, which is why the
|
|
34
|
+
* bar for this file is not "does it call MailService". The failure modes that
|
|
35
|
+
* matter are all failures of HONESTY or of RESTRAINT, and each section below
|
|
36
|
+
* pins one of them:
|
|
37
|
+
*
|
|
38
|
+
* (A) NEVER TRUST THE BODY. User is a global model, so a user id posted by a
|
|
39
|
+
* member of project A is a perfectly valid id in project B. Without a
|
|
40
|
+
* membership re-check inside the sending service, this endpoint mails
|
|
41
|
+
* strangers about a project they have never heard of - with that
|
|
42
|
+
* project's name in the message.
|
|
43
|
+
*
|
|
44
|
+
* (B) NEVER CLAIM MORE THAN IS TRUE. A Ready responder gets no mail, because
|
|
45
|
+
* a mail telling somebody they are missing something they are not missing
|
|
46
|
+
* is a claim they can disprove in ten seconds - and the next mail from
|
|
47
|
+
* this sender might be a page. Likewise the "you are on an on-call
|
|
48
|
+
* rotation" sentence appears only for a responder some policy actually
|
|
49
|
+
* reaches.
|
|
50
|
+
*
|
|
51
|
+
* (C) NEVER REPORT SENT FOR A MAIL THAT DID NOT GO. MailService goes through
|
|
52
|
+
* API.post, which RESOLVES with an error response for a non-200 rather
|
|
53
|
+
* than throwing. A try/catch on its own therefore reports success for
|
|
54
|
+
* every mail the notification service refuses, which is the exact green
|
|
55
|
+
* tick over nothing this whole phase exists to remove.
|
|
56
|
+
*
|
|
57
|
+
* (D) THE THROTTLE IS A LOCK, NOT A NOTE. It is claimed atomically before the
|
|
58
|
+
* send (a check-then-send lets two concurrent clicks both send), and it
|
|
59
|
+
* is RELEASED when the send fails (or an SMTP blip silences a responder
|
|
60
|
+
* for a day).
|
|
61
|
+
*
|
|
62
|
+
* (E) THE ESCAPING. SimpleMessage.hbs renders the message body through a
|
|
63
|
+
* triple stache, so the escaping in this service is the only escaping
|
|
64
|
+
* there is between an admin-typed project name and a recipient's inbox.
|
|
65
|
+
*
|
|
66
|
+
* Nothing here touches a database, a cache or a mail server: every collaborator
|
|
67
|
+
* is a jest.spyOn. What is under test is this service's own decision-making.
|
|
68
|
+
*
|
|
69
|
+
* Each test builds its own service instance rather than using the module
|
|
70
|
+
* singleton, because the 24h throttle is in-process state and a singleton
|
|
71
|
+
* carries one test's claims into the next. The singleton is asserted to be an
|
|
72
|
+
* instance of the same class at the end, so nothing here is testing a different
|
|
73
|
+
* object from the one the API mounts.
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
77
|
+
"11111111-1111-4111-8111-111111111111",
|
|
78
|
+
);
|
|
79
|
+
const OTHER_PROJECT_ID: ObjectID = new ObjectID(
|
|
80
|
+
"1e1e1e1e-1e1e-4e1e-8e1e-1e1e1e1e1e1e",
|
|
81
|
+
);
|
|
82
|
+
const USER_A: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
|
|
83
|
+
const USER_B: ObjectID = new ObjectID("33333333-3333-4333-8333-333333333333");
|
|
84
|
+
const USER_C: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
|
|
85
|
+
const SEVERITY_ID: ObjectID = new ObjectID(
|
|
86
|
+
"55555555-5555-4555-8555-555555555555",
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const DASHBOARD_URL: URL = new URL(
|
|
90
|
+
Protocol.HTTPS,
|
|
91
|
+
new Hostname("oneuptime.example.com"),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
interface SentMail {
|
|
95
|
+
toEmail: string;
|
|
96
|
+
subject: string;
|
|
97
|
+
message: string;
|
|
98
|
+
templateType: string;
|
|
99
|
+
options: {
|
|
100
|
+
projectId?: ObjectID | undefined;
|
|
101
|
+
userId?: ObjectID | undefined;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let readinessSpy: jest.SpyInstance;
|
|
106
|
+
let projectSpy: jest.SpyInstance;
|
|
107
|
+
let teamMemberSpy: jest.SpyInstance;
|
|
108
|
+
let dashboardUrlSpy: jest.SpyInstance;
|
|
109
|
+
let mailSpy: jest.SpyInstance;
|
|
110
|
+
let cacheClaimSpy: jest.SpyInstance;
|
|
111
|
+
let cacheReleaseSpy: jest.SpyInstance;
|
|
112
|
+
|
|
113
|
+
function buildCoverage(data: {
|
|
114
|
+
ruleType: NotificationRuleType;
|
|
115
|
+
hasRule: boolean;
|
|
116
|
+
}): ReadinessCoverageCell {
|
|
117
|
+
return {
|
|
118
|
+
ruleType: data.ruleType,
|
|
119
|
+
severityId: SEVERITY_ID,
|
|
120
|
+
severityName: "Sev1",
|
|
121
|
+
hasRule: data.hasRule,
|
|
122
|
+
isOptOut: false,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function buildReadiness(data: {
|
|
127
|
+
userId: ObjectID;
|
|
128
|
+
status?: ReadinessStatus | undefined;
|
|
129
|
+
email?: string | undefined;
|
|
130
|
+
reasons?: Array<string> | undefined;
|
|
131
|
+
reachedVia?: Array<ResponderSource> | undefined;
|
|
132
|
+
coverage?: Array<ReadinessCoverageCell> | undefined;
|
|
133
|
+
}): UserReadiness {
|
|
134
|
+
return {
|
|
135
|
+
userId: data.userId,
|
|
136
|
+
userName: "Jane Responder",
|
|
137
|
+
userEmail: data.email === undefined ? "jane@example.com" : data.email,
|
|
138
|
+
status: data.status || ReadinessStatus.NotReachable,
|
|
139
|
+
methods: [],
|
|
140
|
+
coverage: data.coverage || [],
|
|
141
|
+
reasons:
|
|
142
|
+
data.reasons === undefined
|
|
143
|
+
? ["No verified notification method - cannot be paged"]
|
|
144
|
+
: data.reasons,
|
|
145
|
+
reachedVia:
|
|
146
|
+
data.reachedVia === undefined
|
|
147
|
+
? [ResponderSource.Direct]
|
|
148
|
+
: data.reachedVia,
|
|
149
|
+
teams: [],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** A fresh service, with an empty in-process throttle. */
|
|
154
|
+
function newService(): OnCallSetupReminderServiceClass {
|
|
155
|
+
return new OnCallSetupReminderServiceClass();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function sentMails(): Array<SentMail> {
|
|
159
|
+
return mailSpy.mock.calls.map((args: Array<unknown>): SentMail => {
|
|
160
|
+
const mail: {
|
|
161
|
+
toEmail: { toString: () => string };
|
|
162
|
+
subject: string;
|
|
163
|
+
templateType: string;
|
|
164
|
+
vars: Record<string, string>;
|
|
165
|
+
} = args[0] as never;
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
toEmail: mail.toEmail.toString(),
|
|
169
|
+
subject: mail.subject,
|
|
170
|
+
message: mail.vars["message"] || "",
|
|
171
|
+
templateType: mail.templateType,
|
|
172
|
+
options: (args[1] || {}) as SentMail["options"],
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function onlyMail(): SentMail {
|
|
178
|
+
const mails: Array<SentMail> = sentMails();
|
|
179
|
+
|
|
180
|
+
if (mails.length !== 1) {
|
|
181
|
+
throw new Error(`Expected exactly one mail, saw ${mails.length}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return mails[0]!;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/*
|
|
188
|
+
* One case per severity-scoped rule type: the settings tab a gap of that type
|
|
189
|
+
* should link to. All four are covered because sending somebody to the wrong tab
|
|
190
|
+
* produces a screen with nothing wrong on it, which reads as a false alarm - and
|
|
191
|
+
* the mapping is four near-identical lines of the kind that get copy-pasted with
|
|
192
|
+
* one value left unchanged.
|
|
193
|
+
*/
|
|
194
|
+
interface GapLinkCase {
|
|
195
|
+
ruleType: NotificationRuleType;
|
|
196
|
+
path: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const GAP_LINK_CASES: Array<GapLinkCase> = [
|
|
200
|
+
{
|
|
201
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
202
|
+
path: "incident-on-call-rules",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE,
|
|
206
|
+
path: "incident-episode-on-call-rules",
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
|
|
210
|
+
path: "alert-on-call-rules",
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE,
|
|
214
|
+
path: "alert-episode-on-call-rules",
|
|
215
|
+
},
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
/*
|
|
219
|
+
* Declared out here rather than inline in the loop that calls it: a test body
|
|
220
|
+
* closing over a loop variable AND over the mutable `readinessSpy` is the shape
|
|
221
|
+
* `no-loop-func` exists to catch, and the lint rule is right that it is easy to
|
|
222
|
+
* get subtly wrong.
|
|
223
|
+
*/
|
|
224
|
+
function registerGapLinkTest(gapCase: GapLinkCase): void {
|
|
225
|
+
test(`a ${gapCase.ruleType} gap links to ${gapCase.path}`, async () => {
|
|
226
|
+
readinessSpy.mockResolvedValue([
|
|
227
|
+
buildReadiness({
|
|
228
|
+
userId: USER_A,
|
|
229
|
+
status: ReadinessStatus.PartiallyReady,
|
|
230
|
+
coverage: [
|
|
231
|
+
buildCoverage({ ruleType: gapCase.ruleType, hasRule: false }),
|
|
232
|
+
],
|
|
233
|
+
}),
|
|
234
|
+
] as never);
|
|
235
|
+
|
|
236
|
+
await newService().sendSetupReminders({
|
|
237
|
+
projectId: PROJECT_ID,
|
|
238
|
+
userIds: [USER_A],
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
expect(onlyMail().message).toContain(
|
|
242
|
+
`/${PROJECT_ID.toString()}/user-settings/${gapCase.path}`,
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function outcomeFor(
|
|
248
|
+
result: SetupReminderResult,
|
|
249
|
+
userId: ObjectID,
|
|
250
|
+
): SetupReminderOutcome {
|
|
251
|
+
const found: SetupReminderUserResult | undefined = result.results.find(
|
|
252
|
+
(one: SetupReminderUserResult): boolean => {
|
|
253
|
+
return one.userId.toString() === userId.toString();
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
if (!found) {
|
|
258
|
+
throw new Error(`No result was returned for user ${userId.toString()}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return found.outcome;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
beforeEach(() => {
|
|
265
|
+
jest.restoreAllMocks();
|
|
266
|
+
jest.clearAllMocks();
|
|
267
|
+
|
|
268
|
+
jest.spyOn(logger, "error").mockImplementation((): void => {});
|
|
269
|
+
jest.spyOn(logger, "warn").mockImplementation((): void => {});
|
|
270
|
+
jest.spyOn(logger, "debug").mockImplementation((): void => {});
|
|
271
|
+
|
|
272
|
+
projectSpy = jest
|
|
273
|
+
.spyOn(ProjectService, "findOneById")
|
|
274
|
+
.mockResolvedValue({ id: PROJECT_ID, name: "Acme Payments" } as never);
|
|
275
|
+
|
|
276
|
+
readinessSpy = jest
|
|
277
|
+
.spyOn(OnCallReadinessService, "getReadinessForUsers")
|
|
278
|
+
.mockResolvedValue([buildReadiness({ userId: USER_A })] as never);
|
|
279
|
+
|
|
280
|
+
teamMemberSpy = jest
|
|
281
|
+
.spyOn(TeamMemberService, "findBy")
|
|
282
|
+
.mockResolvedValue([] as never);
|
|
283
|
+
|
|
284
|
+
dashboardUrlSpy = jest
|
|
285
|
+
.spyOn(DatabaseConfig, "getDashboardUrl")
|
|
286
|
+
.mockResolvedValue(DASHBOARD_URL as never);
|
|
287
|
+
|
|
288
|
+
mailSpy = jest
|
|
289
|
+
.spyOn(MailService, "sendMail")
|
|
290
|
+
.mockResolvedValue(new HTTPResponse(200, {}, {}) as never);
|
|
291
|
+
|
|
292
|
+
cacheClaimSpy = jest
|
|
293
|
+
.spyOn(GlobalCache, "setStringIfNotExists")
|
|
294
|
+
.mockResolvedValue(true as never);
|
|
295
|
+
|
|
296
|
+
cacheReleaseSpy = jest
|
|
297
|
+
.spyOn(GlobalCache, "deleteKeyIfValue")
|
|
298
|
+
.mockResolvedValue(true as never);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe("OnCallSetupReminderService - (A) never trust the body", () => {
|
|
302
|
+
test("a user id that is not a member of the project is skipped and never mailed", async () => {
|
|
303
|
+
/*
|
|
304
|
+
* The readiness read omits non-members, and the follow-up membership read
|
|
305
|
+
* confirms there are no rows for them at all.
|
|
306
|
+
*/
|
|
307
|
+
readinessSpy.mockResolvedValue([] as never);
|
|
308
|
+
teamMemberSpy.mockResolvedValue([] as never);
|
|
309
|
+
|
|
310
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
311
|
+
projectId: PROJECT_ID,
|
|
312
|
+
userIds: [USER_A],
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
expect(outcomeFor(result, USER_A)).toBe(
|
|
316
|
+
SetupReminderOutcome.SkippedNotAMember,
|
|
317
|
+
);
|
|
318
|
+
expect(mailSpy).not.toHaveBeenCalled();
|
|
319
|
+
expect(result.sentCount).toBe(0);
|
|
320
|
+
expect(result.skippedCount).toBe(1);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("the membership re-check is scoped to the caller's project and reads as root", async () => {
|
|
324
|
+
readinessSpy.mockResolvedValue([] as never);
|
|
325
|
+
|
|
326
|
+
await newService().sendSetupReminders({
|
|
327
|
+
projectId: PROJECT_ID,
|
|
328
|
+
userIds: [USER_A],
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const call: {
|
|
332
|
+
query: Record<string, unknown>;
|
|
333
|
+
props: { isRoot?: boolean | undefined };
|
|
334
|
+
} = teamMemberSpy.mock.calls[0]![0] as never;
|
|
335
|
+
|
|
336
|
+
expect(call.query["projectId"]).toBe(PROJECT_ID);
|
|
337
|
+
expect(call.props.isRoot).toBe(true);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("the readiness read is scoped to the caller's project, not to anything in the request body", async () => {
|
|
341
|
+
await newService().sendSetupReminders({
|
|
342
|
+
projectId: PROJECT_ID,
|
|
343
|
+
userIds: [USER_A],
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
expect(readinessSpy).toHaveBeenCalledWith([USER_A], PROJECT_ID);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("a member the readiness read could not answer for is reported as failed, not as a stranger", async () => {
|
|
350
|
+
/*
|
|
351
|
+
* "Not in this project" and "we could not work out what they are missing"
|
|
352
|
+
* need two different sentences: the first is a list an admin should fix,
|
|
353
|
+
* the second is a bug they should report.
|
|
354
|
+
*/
|
|
355
|
+
readinessSpy.mockResolvedValue([] as never);
|
|
356
|
+
teamMemberSpy.mockResolvedValue([{ _id: "tm-1", userId: USER_A }] as never);
|
|
357
|
+
|
|
358
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
359
|
+
projectId: PROJECT_ID,
|
|
360
|
+
userIds: [USER_A],
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
expect(outcomeFor(result, USER_A)).toBe(SetupReminderOutcome.Failed);
|
|
364
|
+
expect(result.failedCount).toBe(1);
|
|
365
|
+
expect(mailSpy).not.toHaveBeenCalled();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test("no membership query is issued when readiness answered for everybody", async () => {
|
|
369
|
+
// The common case must not pay for the disambiguation of a rare one.
|
|
370
|
+
await newService().sendSetupReminders({
|
|
371
|
+
projectId: PROJECT_ID,
|
|
372
|
+
userIds: [USER_A],
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
expect(teamMemberSpy).not.toHaveBeenCalled();
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe("OnCallSetupReminderService - (B) never claim more than is true", () => {
|
|
380
|
+
test("a Ready responder is skipped rather than nagged", async () => {
|
|
381
|
+
readinessSpy.mockResolvedValue([
|
|
382
|
+
buildReadiness({
|
|
383
|
+
userId: USER_A,
|
|
384
|
+
status: ReadinessStatus.Ready,
|
|
385
|
+
reasons: [],
|
|
386
|
+
}),
|
|
387
|
+
] as never);
|
|
388
|
+
|
|
389
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
390
|
+
projectId: PROJECT_ID,
|
|
391
|
+
userIds: [USER_A],
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
expect(outcomeFor(result, USER_A)).toBe(
|
|
395
|
+
SetupReminderOutcome.SkippedNothingMissing,
|
|
396
|
+
);
|
|
397
|
+
expect(mailSpy).not.toHaveBeenCalled();
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test("a Ready responder does not consume their 24h window either", async () => {
|
|
401
|
+
/*
|
|
402
|
+
* The claim must come after the "is there anything to say" decision.
|
|
403
|
+
* Burning the window on a mail that was never going to be sent would
|
|
404
|
+
* silence a responder who becomes genuinely unreachable an hour later.
|
|
405
|
+
*/
|
|
406
|
+
readinessSpy.mockResolvedValue([
|
|
407
|
+
buildReadiness({ userId: USER_A, status: ReadinessStatus.Ready }),
|
|
408
|
+
] as never);
|
|
409
|
+
|
|
410
|
+
await newService().sendSetupReminders({
|
|
411
|
+
projectId: PROJECT_ID,
|
|
412
|
+
userIds: [USER_A],
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
expect(cacheClaimSpy).not.toHaveBeenCalled();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test("the mail says they are on a rotation only when a policy actually reaches them", async () => {
|
|
419
|
+
readinessSpy.mockResolvedValue([
|
|
420
|
+
buildReadiness({ userId: USER_A, reachedVia: [ResponderSource.Team] }),
|
|
421
|
+
] as never);
|
|
422
|
+
|
|
423
|
+
await newService().sendSetupReminders({
|
|
424
|
+
projectId: PROJECT_ID,
|
|
425
|
+
userIds: [USER_A],
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
expect(onlyMail().message).toContain("You are on an on-call rotation");
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("a responder on no policy is not told they are on one", async () => {
|
|
432
|
+
readinessSpy.mockResolvedValue([
|
|
433
|
+
buildReadiness({ userId: USER_A, reachedVia: [] }),
|
|
434
|
+
] as never);
|
|
435
|
+
|
|
436
|
+
await newService().sendSetupReminders({
|
|
437
|
+
projectId: PROJECT_ID,
|
|
438
|
+
userIds: [USER_A],
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const message: string = onlyMail().message;
|
|
442
|
+
|
|
443
|
+
expect(message).not.toContain("You are on an on-call rotation");
|
|
444
|
+
expect(message).toContain("can be added to an on-call rotation");
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("the mail names the real gap, using the readiness service's own words", async () => {
|
|
448
|
+
readinessSpy.mockResolvedValue([
|
|
449
|
+
buildReadiness({
|
|
450
|
+
userId: USER_A,
|
|
451
|
+
status: ReadinessStatus.PartiallyReady,
|
|
452
|
+
reasons: [
|
|
453
|
+
"No rules for Sev1, Sev2 incidents - pages fall back to Email, Push",
|
|
454
|
+
],
|
|
455
|
+
coverage: [
|
|
456
|
+
buildCoverage({
|
|
457
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
458
|
+
hasRule: false,
|
|
459
|
+
}),
|
|
460
|
+
],
|
|
461
|
+
}),
|
|
462
|
+
] as never);
|
|
463
|
+
|
|
464
|
+
await newService().sendSetupReminders({
|
|
465
|
+
projectId: PROJECT_ID,
|
|
466
|
+
userIds: [USER_A],
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
expect(onlyMail().message).toContain(
|
|
470
|
+
"No rules for Sev1, Sev2 incidents - pages fall back to Email, Push",
|
|
471
|
+
);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test("an unreachable responder gets the urgent subject; a partially ready one does not", async () => {
|
|
475
|
+
readinessSpy.mockResolvedValue([
|
|
476
|
+
buildReadiness({ userId: USER_A, status: ReadinessStatus.NotReachable }),
|
|
477
|
+
] as never);
|
|
478
|
+
|
|
479
|
+
await newService().sendSetupReminders({
|
|
480
|
+
projectId: PROJECT_ID,
|
|
481
|
+
userIds: [USER_A],
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
expect(onlyMail().subject).toBe(
|
|
485
|
+
"Action needed: nothing can page you in Acme Payments",
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
mailSpy.mockClear();
|
|
489
|
+
|
|
490
|
+
readinessSpy.mockResolvedValue([
|
|
491
|
+
buildReadiness({
|
|
492
|
+
userId: USER_A,
|
|
493
|
+
status: ReadinessStatus.PartiallyReady,
|
|
494
|
+
}),
|
|
495
|
+
] as never);
|
|
496
|
+
|
|
497
|
+
await newService().sendSetupReminders({
|
|
498
|
+
projectId: PROJECT_ID,
|
|
499
|
+
userIds: [USER_A],
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
expect(onlyMail().subject).toBe(
|
|
503
|
+
"Finish your on-call notification setup in Acme Payments",
|
|
504
|
+
);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test("the mail goes to the responder's own account email, tagged with project and user", async () => {
|
|
508
|
+
readinessSpy.mockResolvedValue([
|
|
509
|
+
buildReadiness({ userId: USER_A, email: "jane@example.com" }),
|
|
510
|
+
] as never);
|
|
511
|
+
|
|
512
|
+
await newService().sendSetupReminders({
|
|
513
|
+
projectId: PROJECT_ID,
|
|
514
|
+
userIds: [USER_A],
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
const mail: SentMail = onlyMail();
|
|
518
|
+
|
|
519
|
+
expect(mail.toEmail).toBe("jane@example.com");
|
|
520
|
+
expect(mail.options.projectId).toBe(PROJECT_ID);
|
|
521
|
+
expect(mail.options.userId).toBe(USER_A);
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
describe("OnCallSetupReminderService - the deep link points at the broken thing", () => {
|
|
526
|
+
test("an unreachable responder is sent to notification methods", async () => {
|
|
527
|
+
readinessSpy.mockResolvedValue([
|
|
528
|
+
buildReadiness({
|
|
529
|
+
userId: USER_A,
|
|
530
|
+
status: ReadinessStatus.NotReachable,
|
|
531
|
+
/*
|
|
532
|
+
* A coverage gap is present and must NOT win: with no verified method,
|
|
533
|
+
* adding a rule changes nothing.
|
|
534
|
+
*/
|
|
535
|
+
coverage: [
|
|
536
|
+
buildCoverage({
|
|
537
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
|
|
538
|
+
hasRule: false,
|
|
539
|
+
}),
|
|
540
|
+
],
|
|
541
|
+
}),
|
|
542
|
+
] as never);
|
|
543
|
+
|
|
544
|
+
await newService().sendSetupReminders({
|
|
545
|
+
projectId: PROJECT_ID,
|
|
546
|
+
userIds: [USER_A],
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
expect(onlyMail().message).toContain(
|
|
550
|
+
`/${PROJECT_ID.toString()}/user-settings/notification-methods`,
|
|
551
|
+
);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
for (const gapCase of GAP_LINK_CASES) {
|
|
555
|
+
registerGapLinkTest(gapCase);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
test("a covered cell is not mistaken for a gap", async () => {
|
|
559
|
+
readinessSpy.mockResolvedValue([
|
|
560
|
+
buildReadiness({
|
|
561
|
+
userId: USER_A,
|
|
562
|
+
status: ReadinessStatus.PartiallyReady,
|
|
563
|
+
coverage: [
|
|
564
|
+
buildCoverage({
|
|
565
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
566
|
+
hasRule: true,
|
|
567
|
+
}),
|
|
568
|
+
buildCoverage({
|
|
569
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
|
|
570
|
+
hasRule: false,
|
|
571
|
+
}),
|
|
572
|
+
],
|
|
573
|
+
}),
|
|
574
|
+
] as never);
|
|
575
|
+
|
|
576
|
+
await newService().sendSetupReminders({
|
|
577
|
+
projectId: PROJECT_ID,
|
|
578
|
+
userIds: [USER_A],
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
expect(onlyMail().message).toContain(
|
|
582
|
+
`/${PROJECT_ID.toString()}/user-settings/alert-on-call-rules`,
|
|
583
|
+
);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
test("a muted cell is not a gap either", async () => {
|
|
587
|
+
readinessSpy.mockResolvedValue([
|
|
588
|
+
buildReadiness({
|
|
589
|
+
userId: USER_A,
|
|
590
|
+
status: ReadinessStatus.PartiallyReady,
|
|
591
|
+
coverage: [
|
|
592
|
+
{
|
|
593
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
594
|
+
severityId: SEVERITY_ID,
|
|
595
|
+
severityName: "Sev4",
|
|
596
|
+
hasRule: false,
|
|
597
|
+
isOptOut: true,
|
|
598
|
+
},
|
|
599
|
+
buildCoverage({
|
|
600
|
+
ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE,
|
|
601
|
+
hasRule: false,
|
|
602
|
+
}),
|
|
603
|
+
],
|
|
604
|
+
}),
|
|
605
|
+
] as never);
|
|
606
|
+
|
|
607
|
+
await newService().sendSetupReminders({
|
|
608
|
+
projectId: PROJECT_ID,
|
|
609
|
+
userIds: [USER_A],
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
expect(onlyMail().message).toContain(
|
|
613
|
+
`/${PROJECT_ID.toString()}/user-settings/alert-episode-on-call-rules`,
|
|
614
|
+
);
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
describe("OnCallSetupReminderService - (E) escaping", () => {
|
|
619
|
+
test("an admin-typed project name cannot inject markup into the message body", async () => {
|
|
620
|
+
projectSpy.mockResolvedValue({
|
|
621
|
+
id: PROJECT_ID,
|
|
622
|
+
name: '<script>alert("x")</script> & Co',
|
|
623
|
+
} as never);
|
|
624
|
+
|
|
625
|
+
await newService().sendSetupReminders({
|
|
626
|
+
projectId: PROJECT_ID,
|
|
627
|
+
userIds: [USER_A],
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
const message: string = onlyMail().message;
|
|
631
|
+
|
|
632
|
+
expect(message).not.toContain("<script>");
|
|
633
|
+
expect(message).toContain("<script>");
|
|
634
|
+
expect(message).toContain("& Co");
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test("a reason sentence carrying a crafted severity name is escaped too", async () => {
|
|
638
|
+
readinessSpy.mockResolvedValue([
|
|
639
|
+
buildReadiness({
|
|
640
|
+
userId: USER_A,
|
|
641
|
+
reasons: ['No rules for <img src=x onerror="steal()"> incidents'],
|
|
642
|
+
}),
|
|
643
|
+
] as never);
|
|
644
|
+
|
|
645
|
+
await newService().sendSetupReminders({
|
|
646
|
+
projectId: PROJECT_ID,
|
|
647
|
+
userIds: [USER_A],
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
const message: string = onlyMail().message;
|
|
651
|
+
|
|
652
|
+
expect(message).not.toContain("<img src=x");
|
|
653
|
+
expect(message).toContain("<img src=x");
|
|
654
|
+
expect(message).toContain(""steal()"");
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("the subject is NOT escaped - Handlebars escapes it, and the SMTP header must stay raw", async () => {
|
|
658
|
+
projectSpy.mockResolvedValue({
|
|
659
|
+
id: PROJECT_ID,
|
|
660
|
+
name: "Bob's & Co",
|
|
661
|
+
} as never);
|
|
662
|
+
|
|
663
|
+
await newService().sendSetupReminders({
|
|
664
|
+
projectId: PROJECT_ID,
|
|
665
|
+
userIds: [USER_A],
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
expect(onlyMail().subject).toContain("Bob's & Co");
|
|
669
|
+
expect(onlyMail().subject).not.toContain("&");
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
test("the structural markup this service writes itself survives", async () => {
|
|
673
|
+
readinessSpy.mockResolvedValue([
|
|
674
|
+
buildReadiness({
|
|
675
|
+
userId: USER_A,
|
|
676
|
+
reasons: ["No verified notification method - cannot be paged"],
|
|
677
|
+
}),
|
|
678
|
+
] as never);
|
|
679
|
+
|
|
680
|
+
await newService().sendSetupReminders({
|
|
681
|
+
projectId: PROJECT_ID,
|
|
682
|
+
userIds: [USER_A],
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
const message: string = onlyMail().message;
|
|
686
|
+
|
|
687
|
+
expect(message).toContain("<ul>");
|
|
688
|
+
expect(message).toContain(
|
|
689
|
+
"<li>No verified notification method - cannot be paged</li>",
|
|
690
|
+
);
|
|
691
|
+
expect(message).toContain("<a href=");
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
test("a long reason list is summarised rather than printed in full", async () => {
|
|
695
|
+
const manyReasons: Array<string> = [];
|
|
696
|
+
|
|
697
|
+
for (let index: number = 0; index < 10; index++) {
|
|
698
|
+
manyReasons.push(`Reason number ${index}`);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
readinessSpy.mockResolvedValue([
|
|
702
|
+
buildReadiness({ userId: USER_A, reasons: manyReasons }),
|
|
703
|
+
] as never);
|
|
704
|
+
|
|
705
|
+
await newService().sendSetupReminders({
|
|
706
|
+
projectId: PROJECT_ID,
|
|
707
|
+
userIds: [USER_A],
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
const message: string = onlyMail().message;
|
|
711
|
+
|
|
712
|
+
expect(message).toContain("Reason number 5");
|
|
713
|
+
expect(message).not.toContain("Reason number 6");
|
|
714
|
+
expect(message).toContain("and 4 more, listed in your settings");
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
test("a status with no reasons still produces a message with a link", async () => {
|
|
718
|
+
readinessSpy.mockResolvedValue([
|
|
719
|
+
buildReadiness({ userId: USER_A, reasons: [] }),
|
|
720
|
+
] as never);
|
|
721
|
+
|
|
722
|
+
await newService().sendSetupReminders({
|
|
723
|
+
projectId: PROJECT_ID,
|
|
724
|
+
userIds: [USER_A],
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
const message: string = onlyMail().message;
|
|
728
|
+
|
|
729
|
+
expect(message).not.toContain("<ul>");
|
|
730
|
+
expect(message).toContain("Open your settings to see what is missing");
|
|
731
|
+
expect(message).toContain("<a href=");
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
describe("OnCallSetupReminderService - (D) the throttle is a lock", () => {
|
|
736
|
+
test("the window is claimed BEFORE the send, atomically, for 24 hours", async () => {
|
|
737
|
+
await newService().sendSetupReminders({
|
|
738
|
+
projectId: PROJECT_ID,
|
|
739
|
+
userIds: [USER_A],
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
expect(cacheClaimSpy).toHaveBeenCalledTimes(1);
|
|
743
|
+
|
|
744
|
+
const args: Array<unknown> = cacheClaimSpy.mock.calls[0]!;
|
|
745
|
+
|
|
746
|
+
expect(args[1]).toBe(`${PROJECT_ID.toString()}:${USER_A.toString()}`);
|
|
747
|
+
expect(args[3]).toEqual({ expiresInSeconds: 24 * 60 * 60 });
|
|
748
|
+
|
|
749
|
+
// Claim first, then send - not the other way round.
|
|
750
|
+
expect(cacheClaimSpy.mock.invocationCallOrder[0]!).toBeLessThan(
|
|
751
|
+
mailSpy.mock.invocationCallOrder[0]!,
|
|
752
|
+
);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
test("a second reminder for the same responder in the same process is skipped without a round trip", async () => {
|
|
756
|
+
const service: OnCallSetupReminderServiceClass = newService();
|
|
757
|
+
|
|
758
|
+
await service.sendSetupReminders({
|
|
759
|
+
projectId: PROJECT_ID,
|
|
760
|
+
userIds: [USER_A],
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
cacheClaimSpy.mockClear();
|
|
764
|
+
|
|
765
|
+
const second: SetupReminderResult = await service.sendSetupReminders({
|
|
766
|
+
projectId: PROJECT_ID,
|
|
767
|
+
userIds: [USER_A],
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
expect(outcomeFor(second, USER_A)).toBe(
|
|
771
|
+
SetupReminderOutcome.SkippedThrottled,
|
|
772
|
+
);
|
|
773
|
+
expect(mailSpy).toHaveBeenCalledTimes(1);
|
|
774
|
+
expect(cacheClaimSpy).not.toHaveBeenCalled();
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
test("a window already held by another pod skips the send", async () => {
|
|
778
|
+
cacheClaimSpy.mockResolvedValue(false as never);
|
|
779
|
+
|
|
780
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
781
|
+
projectId: PROJECT_ID,
|
|
782
|
+
userIds: [USER_A],
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
expect(outcomeFor(result, USER_A)).toBe(
|
|
786
|
+
SetupReminderOutcome.SkippedThrottled,
|
|
787
|
+
);
|
|
788
|
+
expect(mailSpy).not.toHaveBeenCalled();
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
test("a window held elsewhere is remembered locally, so the next click costs no round trip", async () => {
|
|
792
|
+
const service: OnCallSetupReminderServiceClass = newService();
|
|
793
|
+
|
|
794
|
+
cacheClaimSpy.mockResolvedValue(false as never);
|
|
795
|
+
|
|
796
|
+
await service.sendSetupReminders({
|
|
797
|
+
projectId: PROJECT_ID,
|
|
798
|
+
userIds: [USER_A],
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
cacheClaimSpy.mockClear();
|
|
802
|
+
|
|
803
|
+
await service.sendSetupReminders({
|
|
804
|
+
projectId: PROJECT_ID,
|
|
805
|
+
userIds: [USER_A],
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
expect(cacheClaimSpy).not.toHaveBeenCalled();
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
test("two different responders do not share a window", async () => {
|
|
812
|
+
readinessSpy.mockResolvedValue([
|
|
813
|
+
buildReadiness({ userId: USER_A }),
|
|
814
|
+
buildReadiness({ userId: USER_B }),
|
|
815
|
+
] as never);
|
|
816
|
+
|
|
817
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
818
|
+
projectId: PROJECT_ID,
|
|
819
|
+
userIds: [USER_A, USER_B],
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
expect(result.sentCount).toBe(2);
|
|
823
|
+
expect(mailSpy).toHaveBeenCalledTimes(2);
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
test("the same responder in two different projects does not share a window", async () => {
|
|
827
|
+
const service: OnCallSetupReminderServiceClass = newService();
|
|
828
|
+
|
|
829
|
+
await service.sendSetupReminders({
|
|
830
|
+
projectId: PROJECT_ID,
|
|
831
|
+
userIds: [USER_A],
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
const second: SetupReminderResult = await service.sendSetupReminders({
|
|
835
|
+
projectId: OTHER_PROJECT_ID,
|
|
836
|
+
userIds: [USER_A],
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
expect(outcomeFor(second, USER_A)).toBe(SetupReminderOutcome.Sent);
|
|
840
|
+
expect(mailSpy).toHaveBeenCalledTimes(2);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
test("a failed send gives the window back, using its own token", async () => {
|
|
844
|
+
mailSpy.mockRejectedValue(new Error("smtp is down") as never);
|
|
845
|
+
|
|
846
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
847
|
+
projectId: PROJECT_ID,
|
|
848
|
+
userIds: [USER_A],
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
expect(outcomeFor(result, USER_A)).toBe(SetupReminderOutcome.Failed);
|
|
852
|
+
expect(cacheReleaseSpy).toHaveBeenCalledTimes(1);
|
|
853
|
+
|
|
854
|
+
const claimArgs: Array<unknown> = cacheClaimSpy.mock.calls[0]!;
|
|
855
|
+
const releaseArgs: Array<unknown> = cacheReleaseSpy.mock.calls[0]!;
|
|
856
|
+
|
|
857
|
+
/*
|
|
858
|
+
* Same namespace, same key, and crucially the SAME token: a release must
|
|
859
|
+
* never be able to delete a claim somebody else is holding.
|
|
860
|
+
*/
|
|
861
|
+
expect(releaseArgs[0]).toBe(claimArgs[0]);
|
|
862
|
+
expect(releaseArgs[1]).toBe(claimArgs[1]);
|
|
863
|
+
expect(releaseArgs[2]).toBe(claimArgs[2]);
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
test("a released window can be claimed again by the very next request", async () => {
|
|
867
|
+
const service: OnCallSetupReminderServiceClass = newService();
|
|
868
|
+
|
|
869
|
+
mailSpy.mockRejectedValueOnce(new Error("smtp blip") as never);
|
|
870
|
+
|
|
871
|
+
await service.sendSetupReminders({
|
|
872
|
+
projectId: PROJECT_ID,
|
|
873
|
+
userIds: [USER_A],
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
const second: SetupReminderResult = await service.sendSetupReminders({
|
|
877
|
+
projectId: PROJECT_ID,
|
|
878
|
+
userIds: [USER_A],
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
expect(outcomeFor(second, USER_A)).toBe(SetupReminderOutcome.Sent);
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
test("a successful send never releases the window", async () => {
|
|
885
|
+
await newService().sendSetupReminders({
|
|
886
|
+
projectId: PROJECT_ID,
|
|
887
|
+
userIds: [USER_A],
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
expect(cacheReleaseSpy).not.toHaveBeenCalled();
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
test("an unreachable cache fails OPEN - the reminder still goes, with a warning", async () => {
|
|
894
|
+
cacheClaimSpy.mockRejectedValue(
|
|
895
|
+
new Error("cache is not connected") as never,
|
|
896
|
+
);
|
|
897
|
+
|
|
898
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
899
|
+
projectId: PROJECT_ID,
|
|
900
|
+
userIds: [USER_A],
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
expect(outcomeFor(result, USER_A)).toBe(SetupReminderOutcome.Sent);
|
|
904
|
+
expect(mailSpy).toHaveBeenCalledTimes(1);
|
|
905
|
+
expect(logger.warn).toHaveBeenCalled();
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
test("with the cache down, the in-process throttle still stops a repeat click", async () => {
|
|
909
|
+
const service: OnCallSetupReminderServiceClass = newService();
|
|
910
|
+
|
|
911
|
+
cacheClaimSpy.mockRejectedValue(
|
|
912
|
+
new Error("cache is not connected") as never,
|
|
913
|
+
);
|
|
914
|
+
|
|
915
|
+
await service.sendSetupReminders({
|
|
916
|
+
projectId: PROJECT_ID,
|
|
917
|
+
userIds: [USER_A],
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
const second: SetupReminderResult = await service.sendSetupReminders({
|
|
921
|
+
projectId: PROJECT_ID,
|
|
922
|
+
userIds: [USER_A],
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
expect(outcomeFor(second, USER_A)).toBe(
|
|
926
|
+
SetupReminderOutcome.SkippedThrottled,
|
|
927
|
+
);
|
|
928
|
+
expect(mailSpy).toHaveBeenCalledTimes(1);
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
test("a release that cannot reach the cache does not turn a failed send into a thrown request", async () => {
|
|
932
|
+
mailSpy.mockRejectedValue(new Error("smtp is down") as never);
|
|
933
|
+
cacheReleaseSpy.mockRejectedValue(
|
|
934
|
+
new Error("cache is not connected") as never,
|
|
935
|
+
);
|
|
936
|
+
|
|
937
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
938
|
+
projectId: PROJECT_ID,
|
|
939
|
+
userIds: [USER_A],
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
expect(outcomeFor(result, USER_A)).toBe(SetupReminderOutcome.Failed);
|
|
943
|
+
});
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
describe("OnCallSetupReminderService - (C) never report sent for a mail that did not go", () => {
|
|
947
|
+
test("a non-200 answer from the notification service is reported as failed", async () => {
|
|
948
|
+
/*
|
|
949
|
+
* The highest-value assertion in this file. MailService goes through
|
|
950
|
+
* API.post, which RESOLVES with an error response rather than throwing, so
|
|
951
|
+
* a bare try/catch would call this a success.
|
|
952
|
+
*/
|
|
953
|
+
mailSpy.mockResolvedValue(
|
|
954
|
+
new HTTPResponse(500, { message: "smtp refused" }, {}) as never,
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
958
|
+
projectId: PROJECT_ID,
|
|
959
|
+
userIds: [USER_A],
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
expect(outcomeFor(result, USER_A)).toBe(SetupReminderOutcome.Failed);
|
|
963
|
+
expect(result.sentCount).toBe(0);
|
|
964
|
+
expect(result.failedCount).toBe(1);
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
test("a non-200 answer also gives the throttle window back", async () => {
|
|
968
|
+
mailSpy.mockResolvedValue(new HTTPResponse(502, {}, {}) as never);
|
|
969
|
+
|
|
970
|
+
await newService().sendSetupReminders({
|
|
971
|
+
projectId: PROJECT_ID,
|
|
972
|
+
userIds: [USER_A],
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
expect(cacheReleaseSpy).toHaveBeenCalledTimes(1);
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
test("a thrown send is reported as failed", async () => {
|
|
979
|
+
mailSpy.mockRejectedValue(new Error("connect ECONNREFUSED") as never);
|
|
980
|
+
|
|
981
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
982
|
+
projectId: PROJECT_ID,
|
|
983
|
+
userIds: [USER_A],
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
expect(outcomeFor(result, USER_A)).toBe(SetupReminderOutcome.Failed);
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
test("the failure message given to the admin is prose, not a stack trace", async () => {
|
|
990
|
+
mailSpy.mockRejectedValue(
|
|
991
|
+
new Error("connect ECONNREFUSED 10.0.0.4:587") as never,
|
|
992
|
+
);
|
|
993
|
+
|
|
994
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
995
|
+
projectId: PROJECT_ID,
|
|
996
|
+
userIds: [USER_A],
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
const message: string = result.results[0]!.message;
|
|
1000
|
+
|
|
1001
|
+
expect(message).not.toContain("ECONNREFUSED");
|
|
1002
|
+
expect(message).toContain("Nothing was delivered");
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
test("a responder with no usable account email is failed, not silently dropped", async () => {
|
|
1006
|
+
readinessSpy.mockResolvedValue([
|
|
1007
|
+
buildReadiness({ userId: USER_A, email: "" }),
|
|
1008
|
+
] as never);
|
|
1009
|
+
|
|
1010
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
1011
|
+
projectId: PROJECT_ID,
|
|
1012
|
+
userIds: [USER_A],
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
expect(outcomeFor(result, USER_A)).toBe(SetupReminderOutcome.Failed);
|
|
1016
|
+
expect(mailSpy).not.toHaveBeenCalled();
|
|
1017
|
+
// And it does not burn a window it was never going to use.
|
|
1018
|
+
expect(cacheClaimSpy).not.toHaveBeenCalled();
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
test("a malformed account email is failed rather than thrown for", async () => {
|
|
1022
|
+
readinessSpy.mockResolvedValue([
|
|
1023
|
+
buildReadiness({ userId: USER_A, email: "not-an-email" }),
|
|
1024
|
+
] as never);
|
|
1025
|
+
|
|
1026
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
1027
|
+
projectId: PROJECT_ID,
|
|
1028
|
+
userIds: [USER_A],
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
expect(outcomeFor(result, USER_A)).toBe(SetupReminderOutcome.Failed);
|
|
1032
|
+
expect(mailSpy).not.toHaveBeenCalled();
|
|
1033
|
+
});
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
describe("OnCallSetupReminderService - the batch answer is per user", () => {
|
|
1037
|
+
test("one bad recipient does not cancel the rest of the batch", async () => {
|
|
1038
|
+
readinessSpy.mockResolvedValue([
|
|
1039
|
+
buildReadiness({ userId: USER_A }),
|
|
1040
|
+
buildReadiness({ userId: USER_B, email: "" }),
|
|
1041
|
+
buildReadiness({
|
|
1042
|
+
userId: USER_C,
|
|
1043
|
+
status: ReadinessStatus.PartiallyReady,
|
|
1044
|
+
}),
|
|
1045
|
+
] as never);
|
|
1046
|
+
|
|
1047
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
1048
|
+
projectId: PROJECT_ID,
|
|
1049
|
+
userIds: [USER_A, USER_B, USER_C],
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
expect(outcomeFor(result, USER_A)).toBe(SetupReminderOutcome.Sent);
|
|
1053
|
+
expect(outcomeFor(result, USER_B)).toBe(SetupReminderOutcome.Failed);
|
|
1054
|
+
expect(outcomeFor(result, USER_C)).toBe(SetupReminderOutcome.Sent);
|
|
1055
|
+
expect(mailSpy).toHaveBeenCalledTimes(2);
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
test("the counts add up and describe the whole request", async () => {
|
|
1059
|
+
readinessSpy.mockResolvedValue([
|
|
1060
|
+
buildReadiness({ userId: USER_A }),
|
|
1061
|
+
buildReadiness({
|
|
1062
|
+
userId: USER_B,
|
|
1063
|
+
status: ReadinessStatus.Ready,
|
|
1064
|
+
}),
|
|
1065
|
+
] as never);
|
|
1066
|
+
teamMemberSpy.mockResolvedValue([] as never);
|
|
1067
|
+
|
|
1068
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
1069
|
+
projectId: PROJECT_ID,
|
|
1070
|
+
userIds: [USER_A, USER_B, USER_C],
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
expect(result.requestedCount).toBe(3);
|
|
1074
|
+
expect(result.sentCount).toBe(1);
|
|
1075
|
+
// USER_B is Ready, USER_C is not a member.
|
|
1076
|
+
expect(result.skippedCount).toBe(2);
|
|
1077
|
+
expect(result.failedCount).toBe(0);
|
|
1078
|
+
expect(result.results).toHaveLength(3);
|
|
1079
|
+
expect(result.sentCount + result.skippedCount + result.failedCount).toBe(
|
|
1080
|
+
result.requestedCount,
|
|
1081
|
+
);
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
test("results come back in the order the caller asked, so a UI can line them up with its rows", async () => {
|
|
1085
|
+
readinessSpy.mockResolvedValue([
|
|
1086
|
+
// Deliberately returned in the opposite order to the request.
|
|
1087
|
+
buildReadiness({ userId: USER_C }),
|
|
1088
|
+
buildReadiness({ userId: USER_B }),
|
|
1089
|
+
buildReadiness({ userId: USER_A }),
|
|
1090
|
+
] as never);
|
|
1091
|
+
|
|
1092
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
1093
|
+
projectId: PROJECT_ID,
|
|
1094
|
+
userIds: [USER_A, USER_B, USER_C],
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
expect(
|
|
1098
|
+
result.results.map((one: SetupReminderUserResult): string => {
|
|
1099
|
+
return one.userId.toString();
|
|
1100
|
+
}),
|
|
1101
|
+
).toEqual([USER_A.toString(), USER_B.toString(), USER_C.toString()]);
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
test("a repeated id is one person, one mail and one result", async () => {
|
|
1105
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
1106
|
+
projectId: PROJECT_ID,
|
|
1107
|
+
userIds: [USER_A, USER_A, USER_A],
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
expect(result.requestedCount).toBe(1);
|
|
1111
|
+
expect(result.results).toHaveLength(1);
|
|
1112
|
+
expect(mailSpy).toHaveBeenCalledTimes(1);
|
|
1113
|
+
expect(outcomeFor(result, USER_A)).toBe(SetupReminderOutcome.Sent);
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
test("every result carries a sentence a human can act on", async () => {
|
|
1117
|
+
readinessSpy.mockResolvedValue([
|
|
1118
|
+
buildReadiness({ userId: USER_A }),
|
|
1119
|
+
buildReadiness({ userId: USER_B, status: ReadinessStatus.Ready }),
|
|
1120
|
+
] as never);
|
|
1121
|
+
teamMemberSpy.mockResolvedValue([] as never);
|
|
1122
|
+
|
|
1123
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
1124
|
+
projectId: PROJECT_ID,
|
|
1125
|
+
userIds: [USER_A, USER_B, USER_C],
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
for (const one of result.results) {
|
|
1129
|
+
expect(one.message.length).toBeGreaterThan(10);
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
test("no result echoes an email address back to the caller", async () => {
|
|
1134
|
+
readinessSpy.mockResolvedValue([
|
|
1135
|
+
buildReadiness({ userId: USER_A, email: "jane@example.com" }),
|
|
1136
|
+
] as never);
|
|
1137
|
+
|
|
1138
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
1139
|
+
projectId: PROJECT_ID,
|
|
1140
|
+
userIds: [USER_A],
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
expect(JSON.stringify(result.results)).not.toContain("jane@example.com");
|
|
1144
|
+
});
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
describe("OnCallSetupReminderService - requests that are wrong as a whole", () => {
|
|
1148
|
+
test("an empty selection is refused", async () => {
|
|
1149
|
+
await expect(
|
|
1150
|
+
newService().sendSetupReminders({
|
|
1151
|
+
projectId: PROJECT_ID,
|
|
1152
|
+
userIds: [],
|
|
1153
|
+
}),
|
|
1154
|
+
).rejects.toThrow(BadDataException);
|
|
1155
|
+
|
|
1156
|
+
expect(readinessSpy).not.toHaveBeenCalled();
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
test("more than the ceiling is REFUSED rather than quietly truncated", async () => {
|
|
1160
|
+
const tooMany: Array<ObjectID> = [];
|
|
1161
|
+
|
|
1162
|
+
for (
|
|
1163
|
+
let index: number = 0;
|
|
1164
|
+
index <= SETUP_REMINDER_MAX_RECIPIENTS;
|
|
1165
|
+
index++
|
|
1166
|
+
) {
|
|
1167
|
+
tooMany.push(ObjectID.generate());
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
await expect(
|
|
1171
|
+
newService().sendSetupReminders({
|
|
1172
|
+
projectId: PROJECT_ID,
|
|
1173
|
+
userIds: tooMany,
|
|
1174
|
+
}),
|
|
1175
|
+
).rejects.toThrow(
|
|
1176
|
+
new RegExp(`at most ${SETUP_REMINDER_MAX_RECIPIENTS} responders`),
|
|
1177
|
+
);
|
|
1178
|
+
|
|
1179
|
+
expect(mailSpy).not.toHaveBeenCalled();
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
test("exactly the ceiling is allowed", async () => {
|
|
1183
|
+
const exactly: Array<ObjectID> = [];
|
|
1184
|
+
|
|
1185
|
+
for (
|
|
1186
|
+
let index: number = 0;
|
|
1187
|
+
index < SETUP_REMINDER_MAX_RECIPIENTS;
|
|
1188
|
+
index++
|
|
1189
|
+
) {
|
|
1190
|
+
exactly.push(ObjectID.generate());
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
readinessSpy.mockResolvedValue([] as never);
|
|
1194
|
+
teamMemberSpy.mockResolvedValue([] as never);
|
|
1195
|
+
|
|
1196
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
1197
|
+
projectId: PROJECT_ID,
|
|
1198
|
+
userIds: exactly,
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
expect(result.requestedCount).toBe(SETUP_REMINDER_MAX_RECIPIENTS);
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
test("duplicates are collapsed before the ceiling is applied", async () => {
|
|
1205
|
+
const withDuplicates: Array<ObjectID> = [];
|
|
1206
|
+
|
|
1207
|
+
for (
|
|
1208
|
+
let index: number = 0;
|
|
1209
|
+
index <= SETUP_REMINDER_MAX_RECIPIENTS;
|
|
1210
|
+
index++
|
|
1211
|
+
) {
|
|
1212
|
+
withDuplicates.push(USER_A);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
const result: SetupReminderResult = await newService().sendSetupReminders({
|
|
1216
|
+
projectId: PROJECT_ID,
|
|
1217
|
+
userIds: withDuplicates,
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
expect(result.requestedCount).toBe(1);
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
test("a project that does not exist fails the whole request rather than mailing 'your project'", async () => {
|
|
1224
|
+
projectSpy.mockResolvedValue(null as never);
|
|
1225
|
+
|
|
1226
|
+
await expect(
|
|
1227
|
+
newService().sendSetupReminders({
|
|
1228
|
+
projectId: PROJECT_ID,
|
|
1229
|
+
userIds: [USER_A],
|
|
1230
|
+
}),
|
|
1231
|
+
).rejects.toThrow(BadDataException);
|
|
1232
|
+
|
|
1233
|
+
expect(mailSpy).not.toHaveBeenCalled();
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
test("the dashboard URL is resolved once for the whole batch", async () => {
|
|
1237
|
+
readinessSpy.mockResolvedValue([
|
|
1238
|
+
buildReadiness({ userId: USER_A }),
|
|
1239
|
+
buildReadiness({ userId: USER_B }),
|
|
1240
|
+
buildReadiness({ userId: USER_C }),
|
|
1241
|
+
] as never);
|
|
1242
|
+
|
|
1243
|
+
await newService().sendSetupReminders({
|
|
1244
|
+
projectId: PROJECT_ID,
|
|
1245
|
+
userIds: [USER_A, USER_B, USER_C],
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
expect(dashboardUrlSpy).toHaveBeenCalledTimes(1);
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
test("the project is read once, as root, for its name", async () => {
|
|
1252
|
+
await newService().sendSetupReminders({
|
|
1253
|
+
projectId: PROJECT_ID,
|
|
1254
|
+
userIds: [USER_A],
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
expect(projectSpy).toHaveBeenCalledTimes(1);
|
|
1258
|
+
|
|
1259
|
+
const call: { props: { isRoot?: boolean | undefined } } = projectSpy.mock
|
|
1260
|
+
.calls[0]![0] as never;
|
|
1261
|
+
|
|
1262
|
+
expect(call.props.isRoot).toBe(true);
|
|
1263
|
+
});
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
describe("OnCallSetupReminderService - the exported singleton", () => {
|
|
1267
|
+
test("the default export is an instance of the class the tests exercise", () => {
|
|
1268
|
+
expect(OnCallSetupReminderService).toBeInstanceOf(
|
|
1269
|
+
OnCallSetupReminderServiceClass,
|
|
1270
|
+
);
|
|
1271
|
+
});
|
|
1272
|
+
});
|