@oneuptime/common 12.0.32 → 12.0.33
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/Span.ts +14 -0
- package/Models/DatabaseModels/EnterpriseLicense.ts +56 -0
- package/Models/DatabaseModels/Index.ts +10 -0
- package/Models/DatabaseModels/Monitor.ts +76 -0
- package/Models/DatabaseModels/NetworkAlertPolicy.ts +690 -0
- package/Models/DatabaseModels/NetworkDevice.ts +176 -1
- package/Models/DatabaseModels/NetworkSite.ts +201 -2
- package/Models/DatabaseModels/NetworkSiteType.ts +102 -3
- package/Models/DatabaseModels/NetworkSnmpCredentialProfile.ts +841 -0
- package/Models/DatabaseModels/TelemetryException.ts +119 -0
- package/Models/DatabaseModels/UserNotificationEmailRollupBatch.ts +334 -0
- package/Models/DatabaseModels/UserNotificationEmailRollupItem.ts +370 -0
- package/Models/DatabaseModels/UserNotificationEmailRollupSetting.ts +282 -0
- package/Server/API/EnterpriseLicenseAPI.ts +391 -64
- package/Server/API/LlmProviderAPI.ts +137 -22
- package/Server/EnvironmentConfig.ts +14 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790700000000-AddNetworkSiteTypeParentHierarchy.ts +55 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddEnterpriseLicenseUsageProvenance.ts +31 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddTelemetryExceptionErrorClass.ts +124 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791000000000-AddUserNotificationEmailRollup.ts +132 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791100000000-AddUserNotificationEmailRollupSetting.ts +79 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791200000000-AddNetworkSnmpCredentialProfilesAndAlertPolicies.ts +191 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
- package/Server/Infrastructure/QueueWorker.ts +38 -8
- package/Server/Middleware/BearerTokenAuthorization.ts +12 -0
- package/Server/Middleware/ProjectAuthorization.ts +11 -0
- package/Server/Middleware/SCIMAuthorization.ts +12 -0
- package/Server/Middleware/TelemetryIngest.ts +11 -0
- package/Server/Services/AIBillingService.ts +101 -27
- package/Server/Services/EnterpriseLicenseInstanceService.ts +197 -0
- package/Server/Services/EnterpriseLicenseService.ts +55 -0
- package/Server/Services/Index.ts +10 -0
- package/Server/Services/MonitorService.ts +143 -21
- package/Server/Services/MonitorStatusTimelineService.ts +150 -8
- package/Server/Services/MonitorTemplateService.ts +217 -1
- package/Server/Services/NetworkAlertPolicyEngineService.ts +2159 -0
- package/Server/Services/NetworkAlertPolicyService.ts +944 -0
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +9 -10
- package/Server/Services/NetworkDeviceAutoImportRuleService.ts +60 -15
- package/Server/Services/NetworkDeviceService.ts +1032 -73
- package/Server/Services/NetworkSiteService.ts +1502 -129
- package/Server/Services/NetworkSiteTypeService.ts +1216 -0
- package/Server/Services/NetworkSnmpCredentialProfileService.ts +238 -0
- package/Server/Services/NotificationService.ts +118 -28
- package/Server/Services/ProjectService.ts +105 -48
- package/Server/Services/StatusPagePrivateUserService.ts +103 -3
- package/Server/Services/UserNotificationEmailRollupBatchService.ts +78 -0
- package/Server/Services/UserNotificationEmailRollupItemService.ts +79 -0
- package/Server/Services/UserNotificationEmailRollupSettingService.ts +113 -0
- package/Server/Services/UserNotificationSettingService.ts +62 -25
- package/Server/Services/UserService.ts +95 -0
- package/Server/Types/Database/QueryHelper.ts +2 -2
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +14 -3
- package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +10 -0
- package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +10 -0
- package/Server/Utils/AI/SRE/Insights/InsightTriageRunner.ts +62 -1
- package/Server/Utils/AI/Toolbox/AlertTools.ts +15 -1
- package/Server/Utils/Billing/BillingFailureNoticeThrottle.ts +80 -0
- package/Server/Utils/DataSource/HttpFetch.ts +9 -2
- package/Server/Utils/EmailRollup/EmailRollupConstants.ts +98 -0
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +872 -0
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +781 -0
- package/Server/Utils/EmailRollup/EmailRollupWriter.ts +368 -0
- package/Server/Utils/LLM/LLMService.ts +78 -0
- package/Server/Utils/LogRedaction.ts +8 -0
- package/Server/Utils/Logger.ts +79 -1
- package/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.ts +190 -0
- package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +9 -2
- package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +61 -1
- package/Server/Utils/Monitor/MonitorCriteriaDataExtractor.ts +14 -0
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +49 -0
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +255 -4
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +41 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +43 -0
- package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +499 -19
- package/Server/Utils/Monitor/NetworkDeviceMetricUtil.ts +66 -10
- package/Server/Utils/Monitor/NetworkDeviceWalkUtil.ts +238 -44
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +146 -39
- package/Server/Utils/NetworkDevice/DeviceHealthAggregation.ts +31 -18
- package/Server/Utils/NetworkSite/NetworkSiteHierarchyLock.ts +213 -0
- package/Server/Utils/OutboundUserAgent.ts +152 -0
- package/Server/Utils/StartServer.ts +51 -19
- package/Server/Utils/Telemetry/CaptureSpan.ts +32 -8
- package/Server/Utils/Telemetry/ErrorClassResolver.ts +120 -0
- package/Server/Utils/Telemetry/SpanUtil.ts +33 -0
- package/Server/Utils/Telemetry.ts +198 -51
- package/Server/Views/Partials/SensitiveUrlToken.ejs +140 -0
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +134 -33
- package/Tests/App/Dashboard/AskAiSuggestedPrompts.test.ts +279 -0
- package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +517 -0
- package/Tests/App/Dashboard/DashboardVariablesDiscard.test.tsx +26 -0
- package/Tests/App/Dashboard/DeviceStatusHero.test.tsx +588 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +18 -9
- package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +632 -0
- package/Tests/App/Dashboard/MonitorRecommendationCreateProgress.test.tsx +682 -0
- package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +4 -2
- package/Tests/App/Dashboard/NetworkDeviceCreateFormMonitorBinding.test.tsx +676 -0
- package/Tests/App/Dashboard/NetworkDeviceCreateFormPingMonitor.test.tsx +791 -0
- package/Tests/App/Dashboard/NetworkDeviceCreateFormProbeAndSnmp.test.tsx +833 -0
- package/Tests/App/Dashboard/NetworkDeviceCriteriaFilter.test.ts +161 -0
- package/Tests/App/Dashboard/NetworkDeviceSettingsMonitoringSection.test.tsx +458 -0
- package/Tests/App/Dashboard/NetworkSideMenu.test.tsx +41 -5
- package/Tests/App/Dashboard/PingMonitorProvisioning.test.ts +368 -0
- package/Tests/App/Dashboard/RecommendationCard.test.tsx +796 -0
- package/Tests/App/Dashboard/RecommendationToolbar.test.tsx +519 -0
- package/Tests/App/Dashboard/RecommendationsList.test.tsx +661 -0
- package/Tests/App/Dashboard/UseBulkCreatePingMonitors.test.tsx +899 -0
- package/Tests/App/Dashboard/UseBulkSnmpCredentialProfileActions.test.tsx +732 -0
- package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +256 -0
- package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +11 -1
- package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +9 -4
- package/Tests/Models/NetworkSiteHierarchy.test.ts +41 -4
- package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +951 -7
- package/Tests/Server/API/LlmProviderConnectionTest.test.ts +522 -0
- package/Tests/Server/Infrastructure/Postgres/NetworkSnmpCredentialProfilesAndAlertPoliciesMigration.test.ts +854 -0
- package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +18 -0
- package/Tests/Server/Infrastructure/Postgres/UserNotificationEmailRollupTableMigration.test.ts +535 -0
- package/Tests/Server/Services/AIChatPrivacyPin.test.ts +560 -0
- package/Tests/Server/Services/BillingRechargeOwnerEmailGuards.test.ts +783 -0
- package/Tests/Server/Services/EnterpriseLicenseInstanceServiceDeletionUsage.test.ts +707 -0
- package/Tests/Server/Services/EnterpriseLicenseServiceUsageAggregationLock.test.ts +329 -0
- package/Tests/Server/Services/MonitorServiceDeleteNetworkDeviceCleanup.test.ts +294 -0
- package/Tests/Server/Services/MonitorStatusBridgeProbePath.test.ts +802 -0
- package/Tests/Server/Services/MonitorStatusTimelineService.test.ts +72 -2
- package/Tests/Server/Services/NetworkAlertPolicyEngineHooks.test.ts +752 -0
- package/Tests/Server/Services/NetworkAlertPolicyEngineService.test.ts +1849 -0
- package/Tests/Server/Services/NetworkAlertPolicyModel.test.ts +1793 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +47 -4
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleService.test.ts +97 -12
- package/Tests/Server/Services/NetworkDeviceAutoMonitorLifecycle.test.ts +148 -3
- package/Tests/Server/Services/NetworkDeviceMonitorBindingUpdateGuard.test.ts +514 -0
- package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +447 -58
- package/Tests/Server/Services/NetworkDeviceMonitoringMethodTransition.test.ts +739 -0
- package/Tests/Server/Services/NetworkDevicePollingTenancy.test.ts +501 -0
- package/Tests/Server/Services/NetworkDeviceSiteDefaultProbe.test.ts +432 -0
- package/Tests/Server/Services/NetworkSiteMonitoringDefaults.test.ts +498 -0
- package/Tests/Server/Services/NetworkSiteRollupPolicyAndMaintenance.test.ts +337 -55
- package/Tests/Server/Services/NetworkSiteService.test.ts +1868 -131
- package/Tests/Server/Services/NetworkSiteTypeService.test.ts +1109 -0
- package/Tests/Server/Services/NetworkSnmpCredentialProfileModel.test.ts +926 -0
- package/Tests/Server/Services/NetworkSnmpCredentialProfileService.test.ts +796 -0
- package/Tests/Server/Services/ProjectServiceNetworkSiteTypeDefaults.test.ts +214 -0
- package/Tests/Server/Services/StatusPagePrivateUserEmailChangePasswordResetExpiry.test.ts +269 -0
- package/Tests/Server/Services/UserEmailChangePasswordResetExpiry.test.ts +472 -0
- package/Tests/Server/Services/UserNotificationEmailRollupModels.test.ts +569 -0
- package/Tests/Server/Services/UserNotificationEmailRollupSettingModel.test.ts +314 -0
- package/Tests/Server/Services/UserNotificationEmailRollupSettingService.test.ts +409 -0
- package/Tests/Server/Services/UserNotificationSettingRollupRouting.test.ts +856 -0
- package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +102 -1
- package/Tests/Server/Types/Database/Permissions/DashboardAccessPermission.test.ts +321 -0
- package/Tests/Server/Types/Database/QueryHelperOperators.test.ts +17 -0
- package/Tests/Server/Utils/AI/AlertMonitorFilters.test.ts +124 -0
- package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +208 -0
- package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +235 -0
- package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +253 -0
- package/Tests/Server/Utils/Captcha.test.ts +422 -0
- package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +96 -2
- package/Tests/Server/Utils/Database/MigrationFailureLog.test.ts +466 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1096 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerClaim.test.ts +466 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +1428 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupTestHarness.ts +752 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupWriter.test.ts +864 -0
- package/Tests/Server/Utils/LoggerFaultDemotion.test.ts +346 -0
- package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +633 -0
- package/Tests/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.test.ts +196 -0
- package/Tests/Server/Utils/Monitor/DatabaseMetricWrite.test.ts +367 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +100 -0
- package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +5 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceHydrationUtil.test.ts +512 -31
- package/Tests/Server/Utils/Monitor/NetworkDeviceMetricUtil.test.ts +252 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +479 -42
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +351 -5
- package/Tests/Server/Utils/NetworkDevice/DeviceHealthAggregation.test.ts +136 -12
- package/Tests/Server/Utils/NetworkSite/NetworkSiteHierarchyLock.test.ts +306 -0
- package/Tests/Server/Utils/OutboundUserAgent.test.ts +266 -0
- package/Tests/Server/Utils/SecurityEvent/ThreatIntel/TaxiiClientUserAgent.test.ts +141 -0
- package/Tests/Server/Utils/Telemetry/ErrorClassResolver.test.ts +298 -0
- package/Tests/Server/Utils/Telemetry.test.ts +456 -66
- package/Tests/Types/Monitor/DatabaseMetricCatalog.test.ts +388 -0
- package/Tests/Types/Monitor/MonitorStepDatabaseMonitor.test.ts +243 -0
- package/Tests/Types/Monitor/MonitorStepDefaultTelemetryConfig.test.ts +1 -0
- package/Tests/Types/Monitor/MonitorType.test.ts +1 -0
- package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +16 -7
- package/Tests/Types/Monitor/NetworkDeviceAlertPack.test.ts +60 -0
- package/Tests/Types/NetworkDevice/NetworkAlertPolicyScope.test.ts +655 -0
- package/Tests/Types/NetworkDevice/NetworkDeviceMonitoringMethod.test.ts +115 -26
- package/Tests/Types/NetworkSite/DefaultNetworkSiteType.test.ts +39 -0
- package/Tests/Types/NotificationSetting/NotificationEmailRollupPolicy.test.ts +299 -0
- package/Tests/Types/Telemetry/ErrorClass.test.ts +483 -0
- package/Tests/UI/Components/BasicRadioButtons.test.tsx +336 -0
- package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +15 -0
- package/Tests/UI/Components/ProgressBar.test.tsx +38 -2
- package/Tests/UI/Components/SideMenuItem.test.tsx +226 -4
- package/Tests/UI/Components/SideMenuSection.test.tsx +310 -0
- package/Tests/UI/Components/SideOverSubmitState.test.tsx +335 -0
- package/Tests/UI/Utils/PageScrollLock.test.tsx +377 -0
- package/Tests/UI/Utils/PermissionGate.test.ts +78 -0
- package/Tests/UI/Utils/SensitiveUrlToken.test.ts +140 -0
- package/Tests/Utils/EnterpriseLicenseSeats.test.ts +69 -0
- package/Tests/Utils/EnterpriseLicenseSync.test.ts +7 -1
- package/Tests/Utils/EnterpriseLicenseUsage.test.ts +433 -2
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +280 -1
- package/Tests/Utils/Monitor/MonitorSummarySnapshotUtil.test.ts +1 -0
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +38 -0
- package/Tests/Utils/NetworkDevice/DeviceHealthStateUtil.test.ts +284 -23
- package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +177 -0
- package/Tests/Utils/NetworkDevice/MonitoringMethodThreadedClassifiers.test.ts +992 -0
- package/Tests/Utils/NetworkDevice/NetworkAlertPolicyBootstrapUtil.test.ts +427 -0
- package/Tests/Utils/NetworkDevice/ReachabilityStampConsistency.test.ts +185 -0
- package/Tests/Utils/NetworkDevice/SnmpCredentialUtil.test.ts +268 -0
- package/Tests/Utils/NetworkDiscovery/AutoImportRuleMatcher.test.ts +55 -5
- package/Tests/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.test.ts +98 -21
- package/Tests/Utils/NetworkDiscovery/DiscoveryImportEligibility.test.ts +98 -20
- package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +8 -7
- package/Tests/Utils/NetworkDiscovery/PingMonitorBuilderForAddress.test.ts +274 -0
- package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +230 -51
- package/Tests/Utils/NetworkSite/TypeHierarchyUtil.test.ts +209 -0
- package/Tests/Utils/Telemetry/CaptureSpanExportGating.test.ts +145 -0
- package/Types/AI/ExceptionAIClassification.ts +10 -24
- package/Types/Email/EmailTemplateType.ts +2 -0
- package/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.ts +5 -0
- package/Types/EnterpriseLicense/EnterpriseLicenseUsageSnapshot.ts +13 -0
- package/Types/EnterpriseLicense/EnterpriseLicenseUserCountSource.ts +6 -0
- package/Types/Exception/ApiException.ts +10 -0
- package/Types/Exception/BadDataException.ts +11 -0
- package/Types/Exception/BadRequestException.ts +11 -0
- package/Types/Exception/Exception.ts +65 -0
- package/Types/Exception/ForbiddenException.ts +10 -0
- package/Types/Exception/NotAuthenticatedException.ts +10 -0
- package/Types/Exception/NotAuthorizedException.ts +10 -0
- package/Types/Exception/NotFoundException.ts +11 -0
- package/Types/Exception/PayloadTooLargeException.ts +11 -0
- package/Types/Exception/PaymentRequiredException.ts +10 -0
- package/Types/Exception/ServiceUnavailableException.ts +10 -0
- package/Types/Exception/SsoAuthorizationException.ts +10 -0
- package/Types/Exception/TenantNotFoundException.ts +11 -0
- package/Types/Exception/TimeoutException.ts +10 -0
- package/Types/Exception/TooManyRequestsException.ts +10 -0
- package/Types/Exception/UnableToReachServer.ts +10 -0
- package/Types/Exception/WebsiteRequestException.ts +10 -0
- package/Types/Monitor/CriteriaFilter.ts +60 -2
- package/Types/Monitor/DatabaseMetricCatalog.ts +693 -0
- package/Types/Monitor/DatabaseMonitor/DatabaseMonitorResponse.ts +83 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +76 -0
- package/Types/Monitor/MonitorMetricType.ts +71 -0
- package/Types/Monitor/MonitorStep.ts +76 -0
- package/Types/Monitor/MonitorStepDatabaseMonitor.ts +171 -0
- package/Types/Monitor/MonitorStepSqlMonitor.ts +2 -20
- package/Types/Monitor/MonitorType.ts +47 -1
- package/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.ts +22 -1
- package/Types/Monitor/SqlConnectionConfig.ts +31 -0
- package/Types/NetworkDevice/NetworkAlertPolicyScope.ts +343 -0
- package/Types/NetworkDevice/NetworkDeviceMonitoringMethod.ts +42 -22
- package/Types/NetworkSite/DefaultNetworkSiteTypeHierarchy.ts +30 -0
- package/Types/NotificationSetting/NotificationEmailRollupCategory.ts +255 -0
- package/Types/NotificationSetting/NotificationEmailRollupPolicy.ts +104 -0
- package/Types/Permission.ts +90 -0
- package/Types/Probe/ProbeMonitorResponse.ts +16 -0
- package/Types/Telemetry/ErrorClass.ts +274 -0
- package/Types/Telemetry/UnitOfWork.ts +59 -0
- package/UI/Components/CommandPalette/CommandPalette.tsx +2 -8
- package/UI/Components/EditionLabel/EditionLabel.tsx +4 -0
- package/UI/Components/Modal/Modal.tsx +2 -8
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +44 -0
- package/UI/Components/ProgressBar/ProgressBar.tsx +12 -1
- package/UI/Components/RadioButtons/BasicRadioButtons.tsx +37 -7
- package/UI/Components/SideMenu/SideMenu.tsx +123 -5
- package/UI/Components/SideMenu/SideMenuItem.tsx +76 -84
- package/UI/Components/SideMenu/SideMenuSection.tsx +55 -23
- package/UI/Components/SideOver/SideOver.tsx +21 -20
- package/UI/Utils/PageScrollLock.ts +79 -3
- package/UI/Utils/PermissionGate.ts +61 -0
- package/UI/Utils/SensitiveUrlToken.ts +112 -0
- package/UI/Utils/TestLLMProvider.ts +11 -0
- package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +16 -0
- package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +191 -11
- package/Utils/Monitor/MonitorMetricType.ts +153 -1
- package/Utils/Monitor/NetworkTopologyUtil.ts +23 -3
- package/Utils/NetworkDevice/DeviceHealthStateUtil.ts +65 -14
- package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +43 -14
- package/Utils/NetworkDevice/NetworkAlertPolicyBootstrapUtil.ts +247 -0
- package/Utils/NetworkDevice/SnmpCredentialUtil.ts +68 -0
- package/Utils/NetworkDiscovery/AutoImportRuleMatcher.ts +15 -7
- package/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.ts +52 -17
- package/Utils/NetworkDiscovery/DiscoveryImportEligibility.ts +55 -23
- package/Utils/NetworkDiscovery/PingMonitorBuilder.ts +115 -13
- package/Utils/NetworkSite/SiteStatusRollupUtil.ts +66 -20
- package/Utils/NetworkSite/TypeHierarchyUtil.ts +313 -0
- package/build/dist/Models/AnalyticsModels/Span.js +14 -0
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +59 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +10 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +75 -0
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkAlertPolicy.js +713 -0
- package/build/dist/Models/DatabaseModels/NetworkAlertPolicy.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +180 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSite.js +200 -2
- package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSiteType.js +102 -3
- package/build/dist/Models/DatabaseModels/NetworkSiteType.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSnmpCredentialProfile.js +878 -0
- package/build/dist/Models/DatabaseModels/NetworkSnmpCredentialProfile.js.map +1 -0
- package/build/dist/Models/DatabaseModels/TelemetryException.js +123 -0
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupBatch.js +363 -0
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupBatch.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupItem.js +402 -0
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupItem.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupSetting.js +296 -0
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupSetting.js.map +1 -0
- package/build/dist/Server/API/EnterpriseLicenseAPI.js +273 -60
- package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +108 -13
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +12 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790700000000-AddNetworkSiteTypeParentHierarchy.js +24 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790700000000-AddNetworkSiteTypeParentHierarchy.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddEnterpriseLicenseUsageProvenance.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddEnterpriseLicenseUsageProvenance.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddTelemetryExceptionErrorClass.js +111 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddTelemetryExceptionErrorClass.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791000000000-AddUserNotificationEmailRollup.js +77 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791000000000-AddUserNotificationEmailRollup.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791100000000-AddUserNotificationEmailRollupSetting.js +50 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791100000000-AddUserNotificationEmailRollupSetting.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791200000000-AddNetworkSnmpCredentialProfilesAndAlertPolicies.js +83 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791200000000-AddNetworkSnmpCredentialProfilesAndAlertPolicies.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/QueueWorker.js +33 -5
- package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
- package/build/dist/Server/Middleware/BearerTokenAuthorization.js +12 -0
- package/build/dist/Server/Middleware/BearerTokenAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +11 -0
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/SCIMAuthorization.js +12 -0
- package/build/dist/Server/Middleware/SCIMAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +11 -0
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AIBillingService.js +70 -16
- package/build/dist/Server/Services/AIBillingService.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseInstanceService.js +167 -0
- package/build/dist/Server/Services/EnterpriseLicenseInstanceService.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseService.js +39 -0
- package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +10 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +119 -21
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +127 -8
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +162 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkAlertPolicyEngineService.js +1683 -0
- package/build/dist/Server/Services/NetworkAlertPolicyEngineService.js.map +1 -0
- package/build/dist/Server/Services/NetworkAlertPolicyService.js +732 -0
- package/build/dist/Server/Services/NetworkAlertPolicyService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +10 -9
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js +54 -11
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +841 -70
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +1145 -107
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteTypeService.js +863 -0
- package/build/dist/Server/Services/NetworkSiteTypeService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSnmpCredentialProfileService.js +217 -0
- package/build/dist/Server/Services/NetworkSnmpCredentialProfileService.js.map +1 -0
- package/build/dist/Server/Services/NotificationService.js +80 -17
- package/build/dist/Server/Services/NotificationService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +66 -30
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserService.js +92 -2
- package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationEmailRollupBatchService.js +76 -0
- package/build/dist/Server/Services/UserNotificationEmailRollupBatchService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationEmailRollupItemService.js +77 -0
- package/build/dist/Server/Services/UserNotificationEmailRollupItemService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationEmailRollupSettingService.js +111 -0
- package/build/dist/Server/Services/UserNotificationEmailRollupSettingService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationSettingService.js +46 -20
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +90 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +14 -3
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +10 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +10 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js +54 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +18 -4
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -1
- package/build/dist/Server/Utils/Billing/BillingFailureNoticeThrottle.js +59 -0
- package/build/dist/Server/Utils/Billing/BillingFailureNoticeThrottle.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/HttpFetch.js +2 -1
- package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js +91 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js.map +1 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +691 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +497 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupWriter.js +257 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupWriter.js.map +1 -0
- package/build/dist/Server/Utils/LLM/LLMService.js +64 -1
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/LogRedaction.js +8 -0
- package/build/dist/Server/Utils/LogRedaction.js.map +1 -1
- package/build/dist/Server/Utils/Logger.js +63 -1
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.js +141 -0
- package/build/dist/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +51 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js +7 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +37 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +158 -6
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +33 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +32 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +330 -21
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js +55 -10
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js +178 -49
- package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +119 -29
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/NetworkDevice/DeviceHealthAggregation.js +30 -17
- package/build/dist/Server/Utils/NetworkDevice/DeviceHealthAggregation.js.map +1 -1
- package/build/dist/Server/Utils/NetworkSite/NetworkSiteHierarchyLock.js +158 -0
- package/build/dist/Server/Utils/NetworkSite/NetworkSiteHierarchyLock.js.map +1 -0
- package/build/dist/Server/Utils/OutboundUserAgent.js +123 -0
- package/build/dist/Server/Utils/OutboundUserAgent.js.map +1 -0
- package/build/dist/Server/Utils/StartServer.js +38 -14
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/CaptureSpan.js +28 -8
- package/build/dist/Server/Utils/Telemetry/CaptureSpan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ErrorClassResolver.js +95 -0
- package/build/dist/Server/Utils/Telemetry/ErrorClassResolver.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/SpanUtil.js +29 -0
- package/build/dist/Server/Utils/Telemetry/SpanUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry.js +162 -46
- package/build/dist/Server/Utils/Telemetry.js.map +1 -1
- package/build/dist/Types/AI/ExceptionAIClassification.js +10 -24
- package/build/dist/Types/AI/ExceptionAIClassification.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseUsageSnapshot.js +2 -0
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseUsageSnapshot.js.map +1 -0
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseUserCountSource.js +7 -0
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseUserCountSource.js.map +1 -0
- package/build/dist/Types/Exception/ApiException.js +9 -0
- package/build/dist/Types/Exception/ApiException.js.map +1 -1
- package/build/dist/Types/Exception/BadDataException.js +10 -0
- package/build/dist/Types/Exception/BadDataException.js.map +1 -1
- package/build/dist/Types/Exception/BadRequestException.js +10 -0
- package/build/dist/Types/Exception/BadRequestException.js.map +1 -1
- package/build/dist/Types/Exception/Exception.js +56 -0
- package/build/dist/Types/Exception/Exception.js.map +1 -1
- package/build/dist/Types/Exception/ForbiddenException.js +9 -0
- package/build/dist/Types/Exception/ForbiddenException.js.map +1 -1
- package/build/dist/Types/Exception/NotAuthenticatedException.js +9 -0
- package/build/dist/Types/Exception/NotAuthenticatedException.js.map +1 -1
- package/build/dist/Types/Exception/NotAuthorizedException.js +9 -0
- package/build/dist/Types/Exception/NotAuthorizedException.js.map +1 -1
- package/build/dist/Types/Exception/NotFoundException.js +10 -0
- package/build/dist/Types/Exception/NotFoundException.js.map +1 -1
- package/build/dist/Types/Exception/PayloadTooLargeException.js +10 -0
- package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -1
- package/build/dist/Types/Exception/PaymentRequiredException.js +9 -0
- package/build/dist/Types/Exception/PaymentRequiredException.js.map +1 -1
- package/build/dist/Types/Exception/ServiceUnavailableException.js +9 -0
- package/build/dist/Types/Exception/ServiceUnavailableException.js.map +1 -1
- package/build/dist/Types/Exception/SsoAuthorizationException.js +9 -0
- package/build/dist/Types/Exception/SsoAuthorizationException.js.map +1 -1
- package/build/dist/Types/Exception/TenantNotFoundException.js +10 -0
- package/build/dist/Types/Exception/TenantNotFoundException.js.map +1 -1
- package/build/dist/Types/Exception/TimeoutException.js +9 -0
- package/build/dist/Types/Exception/TimeoutException.js.map +1 -1
- package/build/dist/Types/Exception/TooManyRequestsException.js +9 -0
- package/build/dist/Types/Exception/TooManyRequestsException.js.map +1 -1
- package/build/dist/Types/Exception/UnableToReachServer.js +9 -0
- package/build/dist/Types/Exception/UnableToReachServer.js.map +1 -1
- package/build/dist/Types/Exception/WebsiteRequestException.js +9 -0
- package/build/dist/Types/Exception/WebsiteRequestException.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +45 -2
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/DatabaseMetricCatalog.js +594 -0
- package/build/dist/Types/Monitor/DatabaseMetricCatalog.js.map +1 -0
- package/build/dist/Types/Monitor/DatabaseMonitor/DatabaseMonitorResponse.js +21 -0
- package/build/dist/Types/Monitor/DatabaseMonitor/DatabaseMonitorResponse.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +70 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorMetricType.js +61 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +54 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepDatabaseMonitor.js +122 -0
- package/build/dist/Types/Monitor/MonitorStepDatabaseMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorStepSqlMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +45 -1
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.js +21 -1
- package/build/dist/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.js.map +1 -1
- package/build/dist/Types/Monitor/SqlConnectionConfig.js +2 -0
- package/build/dist/Types/Monitor/SqlConnectionConfig.js.map +1 -0
- package/build/dist/Types/NetworkDevice/NetworkAlertPolicyScope.js +172 -0
- package/build/dist/Types/NetworkDevice/NetworkAlertPolicyScope.js.map +1 -0
- package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js +40 -22
- package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js.map +1 -1
- package/build/dist/Types/NetworkSite/DefaultNetworkSiteTypeHierarchy.js +26 -0
- package/build/dist/Types/NetworkSite/DefaultNetworkSiteTypeHierarchy.js.map +1 -0
- package/build/dist/Types/NotificationSetting/NotificationEmailRollupCategory.js +186 -0
- package/build/dist/Types/NotificationSetting/NotificationEmailRollupCategory.js.map +1 -0
- package/build/dist/Types/NotificationSetting/NotificationEmailRollupPolicy.js +87 -0
- package/build/dist/Types/NotificationSetting/NotificationEmailRollupPolicy.js.map +1 -0
- package/build/dist/Types/Permission.js +80 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Telemetry/ErrorClass.js +216 -0
- package/build/dist/Types/Telemetry/ErrorClass.js.map +1 -0
- package/build/dist/Types/Telemetry/UnitOfWork.js +59 -0
- package/build/dist/Types/Telemetry/UnitOfWork.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js +2 -7
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +3 -0
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +2 -7
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +39 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/UI/Components/ProgressBar/ProgressBar.js +12 -1
- package/build/dist/UI/Components/ProgressBar/ProgressBar.js.map +1 -1
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js +6 -6
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js +67 -5
- package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenuItem.js +49 -53
- package/build/dist/UI/Components/SideMenu/SideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenuSection.js +25 -7
- package/build/dist/UI/Components/SideMenu/SideMenuSection.js.map +1 -1
- package/build/dist/UI/Components/SideOver/SideOver.js +5 -20
- package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
- package/build/dist/UI/Utils/PageScrollLock.js +31 -3
- package/build/dist/UI/Utils/PageScrollLock.js.map +1 -1
- package/build/dist/UI/Utils/PermissionGate.js +42 -1
- package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
- package/build/dist/UI/Utils/SensitiveUrlToken.js +97 -0
- package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -0
- package/build/dist/UI/Utils/TestLLMProvider.js +4 -5
- package/build/dist/UI/Utils/TestLLMProvider.js.map +1 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +15 -0
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +1 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +111 -9
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +109 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +8 -3
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js +27 -7
- package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +7 -5
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/NetworkAlertPolicyBootstrapUtil.js +180 -0
- package/build/dist/Utils/NetworkDevice/NetworkAlertPolicyBootstrapUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDevice/SnmpCredentialUtil.js +12 -0
- package/build/dist/Utils/NetworkDevice/SnmpCredentialUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js +15 -5
- package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js +44 -15
- package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/DiscoveryImportEligibility.js +52 -23
- package/build/dist/Utils/NetworkDiscovery/DiscoveryImportEligibility.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/PingMonitorBuilder.js +85 -13
- package/build/dist/Utils/NetworkDiscovery/PingMonitorBuilder.js.map +1 -1
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +30 -15
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
- package/build/dist/Utils/NetworkSite/TypeHierarchyUtil.js +191 -0
- package/build/dist/Utils/NetworkSite/TypeHierarchyUtil.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,864 @@
|
|
|
1
|
+
import MailService from "../../../../Server/Services/MailService";
|
|
2
|
+
import UserNotificationEmailRollupItemService from "../../../../Server/Services/UserNotificationEmailRollupItemService";
|
|
3
|
+
import UserNotificationEmailRollupSettingService from "../../../../Server/Services/UserNotificationEmailRollupSettingService";
|
|
4
|
+
import EmailRollupWriter, {
|
|
5
|
+
RollupMailOptions,
|
|
6
|
+
SendOrRollupData,
|
|
7
|
+
} from "../../../../Server/Utils/EmailRollup/EmailRollupWriter";
|
|
8
|
+
import {
|
|
9
|
+
BURST_THRESHOLD,
|
|
10
|
+
BURST_WINDOW_MINUTES,
|
|
11
|
+
ROLLUP_SUBJECT_MAX_LENGTH,
|
|
12
|
+
} from "../../../../Server/Utils/EmailRollup/EmailRollupConstants";
|
|
13
|
+
import logger from "../../../../Server/Utils/Logger";
|
|
14
|
+
import CreateBy from "../../../../Server/Types/Database/CreateBy";
|
|
15
|
+
import UserNotificationEmailRollupItem from "../../../../Models/DatabaseModels/UserNotificationEmailRollupItem";
|
|
16
|
+
import Dictionary from "../../../../Types/Dictionary";
|
|
17
|
+
import Email from "../../../../Types/Email";
|
|
18
|
+
import { EmailEnvelope } from "../../../../Types/Email/EmailMessage";
|
|
19
|
+
import EmailTemplateType from "../../../../Types/Email/EmailTemplateType";
|
|
20
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
21
|
+
import * as NotificationEmailRollupCategory from "../../../../Types/NotificationSetting/NotificationEmailRollupCategory";
|
|
22
|
+
import { NEVER_ROLLED_UP_EVENT_TYPES } from "../../../../Types/NotificationSetting/NotificationEmailRollupPolicy";
|
|
23
|
+
import NotificationSettingEventType from "../../../../Types/NotificationSetting/NotificationSettingEventType";
|
|
24
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
25
|
+
import PositiveNumber from "../../../../Types/PositiveNumber";
|
|
26
|
+
import { describe, expect, test, beforeEach, afterEach } from "@jest/globals";
|
|
27
|
+
import { FindOperator } from "typeorm";
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* EmailRollupWriter is the single place an owner notification email can be
|
|
31
|
+
* held back on its way to MailService. What breaks in production if any of
|
|
32
|
+
* the behaviour below regresses:
|
|
33
|
+
*
|
|
34
|
+
* 1. FAIL OPEN. Every database line in the writer sits inside one try/catch
|
|
35
|
+
* whose catch resets `deferred`. If that ever stops being true, a missing
|
|
36
|
+
* table, a statement timeout or a degraded Postgres stops being "the
|
|
37
|
+
* product behaves exactly as it did last month" and starts being silently
|
|
38
|
+
* dropped incident mail. This is the property the whole feature's safety
|
|
39
|
+
* argument rests on, so four separate failure shapes are pinned here.
|
|
40
|
+
*
|
|
41
|
+
* 2. THE ON-CALL BYPASS. NEVER_ROLLED_UP event types must reach MailService
|
|
42
|
+
* with no database work at all - not "usually", not "unless the counter
|
|
43
|
+
* says otherwise". A regression here delays a page.
|
|
44
|
+
*
|
|
45
|
+
* 3. THE PER-CATEGORY COUNTER. The burst counter is scoped to one
|
|
46
|
+
* RollupCategory. If it ever becomes global, a monitor that flaps every
|
|
47
|
+
* thirty seconds eats the free immediate sends the first "production is
|
|
48
|
+
* down" incident email needs, and the feature makes the exact problem it
|
|
49
|
+
* exists to fix worse.
|
|
50
|
+
*
|
|
51
|
+
* 4. BYTE-IDENTICAL BELOW THRESHOLD. Under the threshold MailService must
|
|
52
|
+
* receive the same envelope and the same correlation-id bag it received
|
|
53
|
+
* before this feature existed. Those ids are what join an email to its
|
|
54
|
+
* incident in every downstream log; losing one breaks attribution with
|
|
55
|
+
* nothing failing loudly.
|
|
56
|
+
*
|
|
57
|
+
* 5. THE SUBJECT IS SANITISED BEFORE STORAGE. Subjects are Handlebars-
|
|
58
|
+
* compiled downstream, and the stored subject is replayed into the rollup
|
|
59
|
+
* email, so an incident title containing `{{` would be evaluated as a
|
|
60
|
+
* template. Over-length subjects must truncate rather than throw, because
|
|
61
|
+
* a throw inside the writer degrades every notification for that project
|
|
62
|
+
* to the fail-open path.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
66
|
+
"11111111-1111-4111-8111-111111111111",
|
|
67
|
+
);
|
|
68
|
+
const USER_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
|
|
69
|
+
const INCIDENT_ID: ObjectID = new ObjectID(
|
|
70
|
+
"33333333-3333-4333-8333-333333333333",
|
|
71
|
+
);
|
|
72
|
+
const TO_EMAIL: Email = new Email("owner@example.com");
|
|
73
|
+
|
|
74
|
+
const INCIDENT_EVENT: NotificationSettingEventType =
|
|
75
|
+
NotificationSettingEventType.SEND_INCIDENT_CREATED_OWNER_NOTIFICATION;
|
|
76
|
+
const MONITOR_EVENT: NotificationSettingEventType =
|
|
77
|
+
NotificationSettingEventType.SEND_MONITOR_STATUS_CHANGED_OWNER_NOTIFICATION;
|
|
78
|
+
|
|
79
|
+
const INCIDENT_LINK: string = "https://oneuptime.com/dashboard/incidents/1";
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
* What the writer hands countBy. Declared here rather than reaching through
|
|
83
|
+
* Query<Model>, because the two things worth asserting - the category scope
|
|
84
|
+
* and the window bound - are exactly the two the generic type erases.
|
|
85
|
+
*/
|
|
86
|
+
interface CapturedCountBy {
|
|
87
|
+
query: {
|
|
88
|
+
projectId: ObjectID;
|
|
89
|
+
userId: ObjectID;
|
|
90
|
+
toEmail: Email;
|
|
91
|
+
rollupCategory: NotificationEmailRollupCategory.RollupCategory;
|
|
92
|
+
createdAt: FindOperator<Date>;
|
|
93
|
+
};
|
|
94
|
+
props: {
|
|
95
|
+
isRoot: boolean;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
* What the writer hands the preference read. Two ids and nothing else: the
|
|
101
|
+
* preference is per (user, project), and asserting the shape here is what
|
|
102
|
+
* proves the writer asks about the recipient of THIS email rather than about
|
|
103
|
+
* whoever it happened to have in scope.
|
|
104
|
+
*/
|
|
105
|
+
interface CapturedPreferenceRead {
|
|
106
|
+
userId: ObjectID;
|
|
107
|
+
projectId: ObjectID;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildEnvelope(overrides: Partial<EmailEnvelope> = {}): EmailEnvelope {
|
|
111
|
+
return {
|
|
112
|
+
subject: "Incident created: Checkout is down",
|
|
113
|
+
templateType: EmailTemplateType.BlankTemplate,
|
|
114
|
+
vars: {
|
|
115
|
+
incidentViewLink: INCIDENT_LINK,
|
|
116
|
+
},
|
|
117
|
+
...overrides,
|
|
118
|
+
} as EmailEnvelope;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function buildMailOptions(): RollupMailOptions {
|
|
122
|
+
return {
|
|
123
|
+
projectId: PROJECT_ID,
|
|
124
|
+
incidentId: INCIDENT_ID,
|
|
125
|
+
userId: USER_ID,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function sendData(overrides: Partial<SendOrRollupData> = {}): SendOrRollupData {
|
|
130
|
+
return {
|
|
131
|
+
projectId: PROJECT_ID,
|
|
132
|
+
userId: USER_ID,
|
|
133
|
+
toEmail: TO_EMAIL,
|
|
134
|
+
eventType: INCIDENT_EVENT,
|
|
135
|
+
emailEnvelope: buildEnvelope(),
|
|
136
|
+
mailOptions: buildMailOptions(),
|
|
137
|
+
...overrides,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
describe("EmailRollupWriter.sendOrRollup", () => {
|
|
142
|
+
let sendMail: jest.SpyInstance;
|
|
143
|
+
let countRecent: jest.SpyInstance;
|
|
144
|
+
let createItem: jest.SpyInstance;
|
|
145
|
+
let loggerError: jest.SpyInstance;
|
|
146
|
+
let rollupEnabled: jest.SpyInstance;
|
|
147
|
+
|
|
148
|
+
beforeEach(() => {
|
|
149
|
+
loggerError = jest.spyOn(logger, "error").mockImplementation((): void => {
|
|
150
|
+
return undefined;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
sendMail = jest
|
|
154
|
+
.spyOn(MailService, "sendMail")
|
|
155
|
+
.mockResolvedValue(undefined as never);
|
|
156
|
+
|
|
157
|
+
countRecent = jest
|
|
158
|
+
.spyOn(UserNotificationEmailRollupItemService, "countBy")
|
|
159
|
+
.mockResolvedValue(new PositiveNumber(0) as never);
|
|
160
|
+
|
|
161
|
+
createItem = jest
|
|
162
|
+
.spyOn(UserNotificationEmailRollupItemService, "create")
|
|
163
|
+
.mockResolvedValue(new UserNotificationEmailRollupItem() as never);
|
|
164
|
+
|
|
165
|
+
/*
|
|
166
|
+
* The per-user escape hatch, stubbed to its default for every test in the
|
|
167
|
+
* file. Absent a row the real service answers `true`, so this is the
|
|
168
|
+
* behaviour every existing test above was written against; stubbing it
|
|
169
|
+
* keeps them asserting the burst decision rather than accidentally
|
|
170
|
+
* asserting what an unreachable database does to the preference read.
|
|
171
|
+
*/
|
|
172
|
+
rollupEnabled = jest
|
|
173
|
+
.spyOn(
|
|
174
|
+
UserNotificationEmailRollupSettingService,
|
|
175
|
+
"isRollupEnabledForUser",
|
|
176
|
+
)
|
|
177
|
+
.mockResolvedValue(true as never);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
afterEach(() => {
|
|
181
|
+
jest.restoreAllMocks();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
function recentCount(count: number): void {
|
|
185
|
+
countRecent.mockResolvedValue(new PositiveNumber(count) as never);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function writtenItem(index: number = 0): UserNotificationEmailRollupItem {
|
|
189
|
+
const arg: CreateBy<UserNotificationEmailRollupItem> = createItem.mock
|
|
190
|
+
.calls[index]?.[0] as CreateBy<UserNotificationEmailRollupItem>;
|
|
191
|
+
return arg.data;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function capturedCountBy(index: number = 0): CapturedCountBy {
|
|
195
|
+
return countRecent.mock.calls[index]?.[0] as CapturedCountBy;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/*
|
|
199
|
+
* ----------------------------------------------------------------------- *
|
|
200
|
+
* (A) The decision table.
|
|
201
|
+
* -----------------------------------------------------------------------
|
|
202
|
+
*/
|
|
203
|
+
|
|
204
|
+
describe("the burst decision", () => {
|
|
205
|
+
test("below the threshold it sends immediately with the envelope and options unchanged", async () => {
|
|
206
|
+
recentCount(BURST_THRESHOLD - 1);
|
|
207
|
+
const data: SendOrRollupData = sendData();
|
|
208
|
+
|
|
209
|
+
await EmailRollupWriter.sendOrRollup(data);
|
|
210
|
+
|
|
211
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
212
|
+
expect(sendMail.mock.calls[0]?.[0]).toEqual({
|
|
213
|
+
subject: "Incident created: Checkout is down",
|
|
214
|
+
templateType: EmailTemplateType.BlankTemplate,
|
|
215
|
+
vars: {
|
|
216
|
+
incidentViewLink: INCIDENT_LINK,
|
|
217
|
+
},
|
|
218
|
+
toEmail: TO_EMAIL,
|
|
219
|
+
});
|
|
220
|
+
/*
|
|
221
|
+
* Reference identity, not deep equality: the correlation-id bag is
|
|
222
|
+
* passed through verbatim, and a writer that rebuilt it would be free to
|
|
223
|
+
* quietly drop a field.
|
|
224
|
+
*/
|
|
225
|
+
expect(sendMail.mock.calls[0]?.[1]).toBe(data.mailOptions);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("below the threshold exactly one row is written, with sentAt set", async () => {
|
|
229
|
+
recentCount(BURST_THRESHOLD - 1);
|
|
230
|
+
|
|
231
|
+
await EmailRollupWriter.sendOrRollup(sendData());
|
|
232
|
+
|
|
233
|
+
expect(createItem).toHaveBeenCalledTimes(1);
|
|
234
|
+
|
|
235
|
+
const item: UserNotificationEmailRollupItem = writtenItem();
|
|
236
|
+
expect(item.sentAt).toBeInstanceOf(Date);
|
|
237
|
+
expect(item.projectId?.toString()).toBe(PROJECT_ID.toString());
|
|
238
|
+
expect(item.userId?.toString()).toBe(USER_ID.toString());
|
|
239
|
+
expect(item.toEmail?.toString()).toBe(TO_EMAIL.toString());
|
|
240
|
+
expect(item.eventType).toBe(INCIDENT_EVENT);
|
|
241
|
+
expect(item.rollupCategory).toBe(
|
|
242
|
+
NotificationEmailRollupCategory.RollupCategory.Incidents,
|
|
243
|
+
);
|
|
244
|
+
expect(item.subject).toBe("Incident created: Checkout is down");
|
|
245
|
+
expect(item.viewLink).toBe(INCIDENT_LINK);
|
|
246
|
+
expect(item.rollupBatchId).toBeUndefined();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("at the threshold it defers: no mail, one pending row with sentAt undefined", async () => {
|
|
250
|
+
recentCount(BURST_THRESHOLD);
|
|
251
|
+
|
|
252
|
+
await EmailRollupWriter.sendOrRollup(sendData());
|
|
253
|
+
|
|
254
|
+
expect(sendMail).not.toHaveBeenCalled();
|
|
255
|
+
expect(createItem).toHaveBeenCalledTimes(1);
|
|
256
|
+
expect(writtenItem().sentAt).toBeUndefined();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("above the threshold it defers too", async () => {
|
|
260
|
+
recentCount(BURST_THRESHOLD + 40);
|
|
261
|
+
|
|
262
|
+
await EmailRollupWriter.sendOrRollup(sendData());
|
|
263
|
+
|
|
264
|
+
expect(sendMail).not.toHaveBeenCalled();
|
|
265
|
+
expect(createItem).toHaveBeenCalledTimes(1);
|
|
266
|
+
expect(writtenItem().sentAt).toBeUndefined();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("the ledger row is always written as root, since the service denies everything else", async () => {
|
|
270
|
+
recentCount(0);
|
|
271
|
+
|
|
272
|
+
await EmailRollupWriter.sendOrRollup(sendData());
|
|
273
|
+
|
|
274
|
+
const arg: CreateBy<UserNotificationEmailRollupItem> = createItem.mock
|
|
275
|
+
.calls[0]?.[0] as CreateBy<UserNotificationEmailRollupItem>;
|
|
276
|
+
expect(arg.props.isRoot).toBe(true);
|
|
277
|
+
expect(capturedCountBy().props.isRoot).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
/*
|
|
282
|
+
* ----------------------------------------------------------------------- *
|
|
283
|
+
* (B) The counter is scoped per category.
|
|
284
|
+
* -----------------------------------------------------------------------
|
|
285
|
+
*/
|
|
286
|
+
|
|
287
|
+
describe("the per-category counter scope", () => {
|
|
288
|
+
test("ten recent Monitors rows do not defer an incident-created notification", async () => {
|
|
289
|
+
/*
|
|
290
|
+
* The regression guard for the whole feature: the noisiest resource must
|
|
291
|
+
* never be able to delay the most important one. A flapping monitor
|
|
292
|
+
* produces a Monitors-category flood; the first incident email that
|
|
293
|
+
* follows it must still go out immediately.
|
|
294
|
+
*/
|
|
295
|
+
countRecent.mockImplementation(
|
|
296
|
+
(params: CapturedCountBy): Promise<PositiveNumber> => {
|
|
297
|
+
return Promise.resolve(
|
|
298
|
+
new PositiveNumber(
|
|
299
|
+
params.query.rollupCategory ===
|
|
300
|
+
NotificationEmailRollupCategory.RollupCategory.Monitors
|
|
301
|
+
? 10
|
|
302
|
+
: 0,
|
|
303
|
+
),
|
|
304
|
+
);
|
|
305
|
+
},
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
await EmailRollupWriter.sendOrRollup(sendData());
|
|
309
|
+
|
|
310
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
311
|
+
expect(writtenItem().sentAt).toBeInstanceOf(Date);
|
|
312
|
+
|
|
313
|
+
/*
|
|
314
|
+
* And the same flood does defer the next monitor notification, which is
|
|
315
|
+
* what proves the mock above was actually reached rather than the
|
|
316
|
+
* incident having been immediate for some unrelated reason.
|
|
317
|
+
*/
|
|
318
|
+
sendMail.mockClear();
|
|
319
|
+
createItem.mockClear();
|
|
320
|
+
|
|
321
|
+
await EmailRollupWriter.sendOrRollup(
|
|
322
|
+
sendData({ eventType: MONITOR_EVENT }),
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
expect(sendMail).not.toHaveBeenCalled();
|
|
326
|
+
expect(writtenItem().sentAt).toBeUndefined();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("the count is keyed on project, user, address and category", async () => {
|
|
330
|
+
await EmailRollupWriter.sendOrRollup(sendData());
|
|
331
|
+
|
|
332
|
+
const captured: CapturedCountBy = capturedCountBy();
|
|
333
|
+
expect(captured.query.projectId.toString()).toBe(PROJECT_ID.toString());
|
|
334
|
+
expect(captured.query.userId.toString()).toBe(USER_ID.toString());
|
|
335
|
+
expect(captured.query.toEmail.toString()).toBe(TO_EMAIL.toString());
|
|
336
|
+
expect(captured.query.rollupCategory).toBe(
|
|
337
|
+
NotificationEmailRollupCategory.RollupCategory.Incidents,
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("the counter counts rows NEWER than now minus BURST_WINDOW_MINUTES, not older", async () => {
|
|
342
|
+
/*
|
|
343
|
+
* Both halves of this matter, and only one of them is a number.
|
|
344
|
+
*
|
|
345
|
+
* The BOUND is now minus ten minutes. The DIRECTION is what makes the
|
|
346
|
+
* bound mean anything: QueryHelper.greaterThan and QueryHelper.lessThan
|
|
347
|
+
* are the same call shape, take the same Date and produce the same
|
|
348
|
+
* objectLiteralParameters, so a test that reads only the bound stays
|
|
349
|
+
* green through the one-token swap that inverts the entire feature -
|
|
350
|
+
* `lessThan` would count everything OLDER than the window, so a genuine
|
|
351
|
+
* burst would never be detected and a recipient who had been quiet for
|
|
352
|
+
* an hour would be deferred forever.
|
|
353
|
+
*
|
|
354
|
+
* The only part of the returned FindOperator that distinguishes them is
|
|
355
|
+
* the SQL fragment its generator builds, so that is what is asserted.
|
|
356
|
+
*/
|
|
357
|
+
const before: number = Date.now();
|
|
358
|
+
|
|
359
|
+
await EmailRollupWriter.sendOrRollup(sendData());
|
|
360
|
+
|
|
361
|
+
const after: number = Date.now();
|
|
362
|
+
|
|
363
|
+
const createdAt: FindOperator<Date> = capturedCountBy().query.createdAt;
|
|
364
|
+
|
|
365
|
+
const rawParameters: Record<string, unknown> =
|
|
366
|
+
(createdAt.objectLiteralParameters ?? {}) as Record<string, unknown>;
|
|
367
|
+
const parameterNames: Array<string> = Object.keys(rawParameters);
|
|
368
|
+
expect(parameterNames).toHaveLength(1);
|
|
369
|
+
|
|
370
|
+
const parameterName: string = parameterNames[0] ?? "";
|
|
371
|
+
const windowStart: Date = rawParameters[parameterName] as Date;
|
|
372
|
+
expect(windowStart).toBeInstanceOf(Date);
|
|
373
|
+
|
|
374
|
+
const offsetMs: number = BURST_WINDOW_MINUTES * 60 * 1000;
|
|
375
|
+
expect(windowStart.getTime()).toBeGreaterThanOrEqual(before - offsetMs);
|
|
376
|
+
expect(windowStart.getTime()).toBeLessThanOrEqual(after - offsetMs);
|
|
377
|
+
|
|
378
|
+
const buildSql: ((alias: string) => string) | undefined =
|
|
379
|
+
createdAt.getSql;
|
|
380
|
+
expect(typeof buildSql).toBe("function");
|
|
381
|
+
|
|
382
|
+
const sql: string = (buildSql as (alias: string) => string)(
|
|
383
|
+
"createdAtColumn",
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
/*
|
|
387
|
+
* Anchored, and the parameter name is the one the bound above was read
|
|
388
|
+
* from, so this cannot pass on a fragment that compares some other
|
|
389
|
+
* column or some other value. `>=` is allowed because a half-open window
|
|
390
|
+
* is the same window; `<` in either position is not, and neither is the
|
|
391
|
+
* `or IS NULL` variant, which would make every never-created row count.
|
|
392
|
+
*/
|
|
393
|
+
expect(sql).toMatch(
|
|
394
|
+
new RegExp(`^\\(createdAtColumn >=? :${parameterName}\\)$`),
|
|
395
|
+
);
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
/*
|
|
400
|
+
* ----------------------------------------------------------------------- *
|
|
401
|
+
* (C) The structural bypasses.
|
|
402
|
+
* -----------------------------------------------------------------------
|
|
403
|
+
*/
|
|
404
|
+
|
|
405
|
+
describe("the structural bypasses", () => {
|
|
406
|
+
test("every never-rolled-up event type sends immediately, writes no row and never counts", async () => {
|
|
407
|
+
/*
|
|
408
|
+
* Armed with a flood that would defer anything eligible, precisely so a
|
|
409
|
+
* regression that moved the eligibility check below the counter would
|
|
410
|
+
* fail here rather than pass by luck.
|
|
411
|
+
*/
|
|
412
|
+
recentCount(100);
|
|
413
|
+
|
|
414
|
+
for (const eventType of Array.from(NEVER_ROLLED_UP_EVENT_TYPES)) {
|
|
415
|
+
sendMail.mockClear();
|
|
416
|
+
countRecent.mockClear();
|
|
417
|
+
createItem.mockClear();
|
|
418
|
+
|
|
419
|
+
await EmailRollupWriter.sendOrRollup(sendData({ eventType }));
|
|
420
|
+
|
|
421
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
422
|
+
expect(countRecent).not.toHaveBeenCalled();
|
|
423
|
+
expect(createItem).not.toHaveBeenCalled();
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test("forceImmediate sends immediately, writes no row and counts nothing", async () => {
|
|
428
|
+
recentCount(100);
|
|
429
|
+
|
|
430
|
+
await EmailRollupWriter.sendOrRollup(sendData({ forceImmediate: true }));
|
|
431
|
+
|
|
432
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
433
|
+
expect(countRecent).not.toHaveBeenCalled();
|
|
434
|
+
expect(createItem).not.toHaveBeenCalled();
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test("forceImmediate false is not a bypass and still goes through the counter", async () => {
|
|
438
|
+
recentCount(BURST_THRESHOLD);
|
|
439
|
+
|
|
440
|
+
await EmailRollupWriter.sendOrRollup(sendData({ forceImmediate: false }));
|
|
441
|
+
|
|
442
|
+
expect(countRecent).toHaveBeenCalledTimes(1);
|
|
443
|
+
expect(sendMail).not.toHaveBeenCalled();
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
/*
|
|
448
|
+
* ----------------------------------------------------------------------- *
|
|
449
|
+
* (D) Fail open.
|
|
450
|
+
* -----------------------------------------------------------------------
|
|
451
|
+
*/
|
|
452
|
+
|
|
453
|
+
describe("fail open", () => {
|
|
454
|
+
test("a countBy that throws still sends the email, logging once", async () => {
|
|
455
|
+
countRecent.mockRejectedValue(
|
|
456
|
+
new Error('relation "UserNotificationEmailRollupItem" does not exist'),
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
await expect(
|
|
460
|
+
EmailRollupWriter.sendOrRollup(sendData()),
|
|
461
|
+
).resolves.toBeUndefined();
|
|
462
|
+
|
|
463
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
464
|
+
expect(createItem).not.toHaveBeenCalled();
|
|
465
|
+
expect(loggerError).toHaveBeenCalledTimes(1);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test("a create that throws still sends the email, logging once", async () => {
|
|
469
|
+
recentCount(0);
|
|
470
|
+
createItem.mockRejectedValue(new Error("insert timed out"));
|
|
471
|
+
|
|
472
|
+
await expect(
|
|
473
|
+
EmailRollupWriter.sendOrRollup(sendData()),
|
|
474
|
+
).resolves.toBeUndefined();
|
|
475
|
+
|
|
476
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
477
|
+
expect(loggerError).toHaveBeenCalledTimes(1);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test("both throwing still sends the email, logging once", async () => {
|
|
481
|
+
countRecent.mockRejectedValue(new Error("postgres is unreachable"));
|
|
482
|
+
createItem.mockRejectedValue(new Error("postgres is unreachable"));
|
|
483
|
+
|
|
484
|
+
await expect(
|
|
485
|
+
EmailRollupWriter.sendOrRollup(sendData()),
|
|
486
|
+
).resolves.toBeUndefined();
|
|
487
|
+
|
|
488
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
489
|
+
expect(loggerError).toHaveBeenCalledTimes(1);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("a category lookup that throws still sends the email, logging once", async () => {
|
|
493
|
+
/*
|
|
494
|
+
* Stands in for every way the classification layer can go wrong at
|
|
495
|
+
* runtime - a value the Record has no entry for, an import cycle that
|
|
496
|
+
* leaves the map undefined at first call. The answer has to be the same
|
|
497
|
+
* as for a dead database: send it the old way.
|
|
498
|
+
*/
|
|
499
|
+
jest
|
|
500
|
+
.spyOn(NotificationEmailRollupCategory, "getRollupCategory")
|
|
501
|
+
.mockImplementation((): never => {
|
|
502
|
+
throw new Error("unclassifiable event type");
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
await expect(
|
|
506
|
+
EmailRollupWriter.sendOrRollup(sendData()),
|
|
507
|
+
).resolves.toBeUndefined();
|
|
508
|
+
|
|
509
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
510
|
+
expect(countRecent).not.toHaveBeenCalled();
|
|
511
|
+
expect(createItem).not.toHaveBeenCalled();
|
|
512
|
+
expect(loggerError).toHaveBeenCalledTimes(1);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test("a deferred notification is NOT sent twice when the write succeeds", async () => {
|
|
516
|
+
/*
|
|
517
|
+
* The mirror image of fail-open, and the reason the catch resets a flag
|
|
518
|
+
* rather than returning early: if the catch were ever widened to cover
|
|
519
|
+
* the send decision itself, a successful deferral could still fall
|
|
520
|
+
* through to sendNow and the recipient would get both the immediate mail
|
|
521
|
+
* and the rollup line.
|
|
522
|
+
*/
|
|
523
|
+
recentCount(BURST_THRESHOLD);
|
|
524
|
+
|
|
525
|
+
await EmailRollupWriter.sendOrRollup(sendData());
|
|
526
|
+
|
|
527
|
+
expect(sendMail).not.toHaveBeenCalled();
|
|
528
|
+
expect(loggerError).not.toHaveBeenCalled();
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
/*
|
|
533
|
+
* ----------------------------------------------------------------------- *
|
|
534
|
+
* (E) The stored subject.
|
|
535
|
+
* -----------------------------------------------------------------------
|
|
536
|
+
*/
|
|
537
|
+
|
|
538
|
+
describe("the stored subject", () => {
|
|
539
|
+
test("a 5,000 character subject is truncated to the column length rather than throwing", async () => {
|
|
540
|
+
const longSubject: string = "x".repeat(5000);
|
|
541
|
+
|
|
542
|
+
await expect(
|
|
543
|
+
EmailRollupWriter.sendOrRollup(
|
|
544
|
+
sendData({ emailEnvelope: buildEnvelope({ subject: longSubject }) }),
|
|
545
|
+
),
|
|
546
|
+
).resolves.toBeUndefined();
|
|
547
|
+
|
|
548
|
+
expect(loggerError).not.toHaveBeenCalled();
|
|
549
|
+
expect(writtenItem().subject).toHaveLength(ROLLUP_SUBJECT_MAX_LENGTH);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test("Handlebars braces are stripped before storage", async () => {
|
|
553
|
+
await EmailRollupWriter.sendOrRollup(
|
|
554
|
+
sendData({
|
|
555
|
+
emailEnvelope: buildEnvelope({
|
|
556
|
+
subject: "{{projectName}} is down }}",
|
|
557
|
+
}),
|
|
558
|
+
}),
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
expect(writtenItem().subject).toBe("projectName is down ");
|
|
562
|
+
expect(writtenItem().subject).not.toContain("{{");
|
|
563
|
+
expect(writtenItem().subject).not.toContain("}}");
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
test("the outgoing email keeps the producer's original subject", async () => {
|
|
567
|
+
/*
|
|
568
|
+
* Stripping and truncation are storage concerns only. The immediate send
|
|
569
|
+
* below the threshold must be byte-identical to what shipped before.
|
|
570
|
+
*/
|
|
571
|
+
const subject: string = "{{projectName}} is down";
|
|
572
|
+
|
|
573
|
+
await EmailRollupWriter.sendOrRollup(
|
|
574
|
+
sendData({ emailEnvelope: buildEnvelope({ subject }) }),
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
expect((sendMail.mock.calls[0]?.[0] as EmailEnvelope).subject).toBe(
|
|
578
|
+
subject,
|
|
579
|
+
);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test("an empty subject is stored as an empty string rather than failing the insert", async () => {
|
|
583
|
+
await EmailRollupWriter.sendOrRollup(
|
|
584
|
+
sendData({ emailEnvelope: buildEnvelope({ subject: "" }) }),
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
expect(loggerError).not.toHaveBeenCalled();
|
|
588
|
+
expect(writtenItem().subject).toBe("");
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
/*
|
|
593
|
+
* ----------------------------------------------------------------------- *
|
|
594
|
+
* (F) extractViewLink - pure, no mocks needed.
|
|
595
|
+
* -----------------------------------------------------------------------
|
|
596
|
+
*/
|
|
597
|
+
|
|
598
|
+
describe("extractViewLink", () => {
|
|
599
|
+
function vars(
|
|
600
|
+
value: Dictionary<string | JSONObject>,
|
|
601
|
+
): Dictionary<string | JSONObject> {
|
|
602
|
+
return value;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
test("the first known variable in list order wins", () => {
|
|
606
|
+
/*
|
|
607
|
+
* Insertion order here is deliberately the opposite of preference order,
|
|
608
|
+
* so a fallback that merely walked Object.keys would pick the monitor
|
|
609
|
+
* link and fail.
|
|
610
|
+
*/
|
|
611
|
+
expect(
|
|
612
|
+
EmailRollupWriter.extractViewLink(
|
|
613
|
+
vars({
|
|
614
|
+
monitorViewLink: "https://oneuptime.com/dashboard/monitors/1",
|
|
615
|
+
incidentViewLink: INCIDENT_LINK,
|
|
616
|
+
}),
|
|
617
|
+
),
|
|
618
|
+
).toBe(INCIDENT_LINK);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
test("the plural probe and AI agent variables resolve, as well as their singular twins", () => {
|
|
622
|
+
expect(
|
|
623
|
+
EmailRollupWriter.extractViewLink(
|
|
624
|
+
vars({ viewProbesLink: "https://oneuptime.com/dashboard/probes" }),
|
|
625
|
+
),
|
|
626
|
+
).toBe("https://oneuptime.com/dashboard/probes");
|
|
627
|
+
|
|
628
|
+
expect(
|
|
629
|
+
EmailRollupWriter.extractViewLink(
|
|
630
|
+
vars({ viewProbeLink: "https://oneuptime.com/dashboard/probes/1" }),
|
|
631
|
+
),
|
|
632
|
+
).toBe("https://oneuptime.com/dashboard/probes/1");
|
|
633
|
+
|
|
634
|
+
expect(
|
|
635
|
+
EmailRollupWriter.extractViewLink(
|
|
636
|
+
vars({
|
|
637
|
+
viewAIAgentsLink: "https://oneuptime.com/dashboard/ai-agents",
|
|
638
|
+
}),
|
|
639
|
+
),
|
|
640
|
+
).toBe("https://oneuptime.com/dashboard/ai-agents");
|
|
641
|
+
|
|
642
|
+
expect(
|
|
643
|
+
EmailRollupWriter.extractViewLink(
|
|
644
|
+
vars({
|
|
645
|
+
viewAIAgentLink: "https://oneuptime.com/dashboard/ai-agents/1",
|
|
646
|
+
}),
|
|
647
|
+
),
|
|
648
|
+
).toBe("https://oneuptime.com/dashboard/ai-agents/1");
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
test("deviceViewLink resolves", () => {
|
|
652
|
+
expect(
|
|
653
|
+
EmailRollupWriter.extractViewLink(
|
|
654
|
+
vars({ deviceViewLink: "https://oneuptime.com/dashboard/devices/1" }),
|
|
655
|
+
),
|
|
656
|
+
).toBe("https://oneuptime.com/dashboard/devices/1");
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
test("an unknown *Link variable with an http value resolves through the fallback", () => {
|
|
660
|
+
/*
|
|
661
|
+
* This is what buys the feature a clickable line for a producer nobody
|
|
662
|
+
* has written yet, with no list here to keep in sync.
|
|
663
|
+
*/
|
|
664
|
+
expect(
|
|
665
|
+
EmailRollupWriter.extractViewLink(
|
|
666
|
+
vars({
|
|
667
|
+
somethingBrandNewViewLink: "https://oneuptime.com/dashboard/new/1",
|
|
668
|
+
}),
|
|
669
|
+
),
|
|
670
|
+
).toBe("https://oneuptime.com/dashboard/new/1");
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
test("a non-string or relative value is ignored", () => {
|
|
674
|
+
expect(
|
|
675
|
+
EmailRollupWriter.extractViewLink(
|
|
676
|
+
vars({
|
|
677
|
+
incidentViewLink: 42 as unknown as string,
|
|
678
|
+
alertViewLink: { url: "https://oneuptime.com" } as JSONObject,
|
|
679
|
+
monitorViewLink: "/dashboard/monitors/1",
|
|
680
|
+
}),
|
|
681
|
+
),
|
|
682
|
+
).toBeUndefined();
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
test("no link variables at all returns undefined", () => {
|
|
686
|
+
expect(
|
|
687
|
+
EmailRollupWriter.extractViewLink(vars({ projectName: "Acme" })),
|
|
688
|
+
).toBeUndefined();
|
|
689
|
+
expect(EmailRollupWriter.extractViewLink(undefined)).toBeUndefined();
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
test("an envelope with no usable link still writes the row", async () => {
|
|
693
|
+
await EmailRollupWriter.sendOrRollup(
|
|
694
|
+
sendData({
|
|
695
|
+
emailEnvelope: buildEnvelope({ vars: { projectName: "Acme" } }),
|
|
696
|
+
}),
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
expect(createItem).toHaveBeenCalledTimes(1);
|
|
700
|
+
expect(writtenItem().viewLink).toBeUndefined();
|
|
701
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
/*
|
|
706
|
+
* ----------------------------------------------------------------------- *
|
|
707
|
+
* (G) The per-user escape hatch.
|
|
708
|
+
* -----------------------------------------------------------------------
|
|
709
|
+
*/
|
|
710
|
+
|
|
711
|
+
describe("the per-user escape hatch", () => {
|
|
712
|
+
function capturedPreferenceRead(index: number = 0): CapturedPreferenceRead {
|
|
713
|
+
return rollupEnabled.mock.calls[index]?.[0] as CapturedPreferenceRead;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
test("below the threshold the preference is never read", async () => {
|
|
717
|
+
/*
|
|
718
|
+
* The reason the read sits inside the `if (deferred)` and not above it.
|
|
719
|
+
* Under the threshold the outcome is an immediate send whatever the
|
|
720
|
+
* preference says, so a read here would be one extra indexed query per
|
|
721
|
+
* owner notification per address, on the path that every project takes
|
|
722
|
+
* every day, that could never change the answer. The feature's
|
|
723
|
+
* no-op-below-the-threshold promise has to hold in queries, not only in
|
|
724
|
+
* emails.
|
|
725
|
+
*/
|
|
726
|
+
recentCount(BURST_THRESHOLD - 1);
|
|
727
|
+
|
|
728
|
+
await EmailRollupWriter.sendOrRollup(sendData());
|
|
729
|
+
|
|
730
|
+
expect(rollupEnabled).not.toHaveBeenCalled();
|
|
731
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
test("at the threshold with rollup on, the email defers exactly as it did before the toggle existed", async () => {
|
|
735
|
+
recentCount(BURST_THRESHOLD);
|
|
736
|
+
rollupEnabled.mockResolvedValue(true as never);
|
|
737
|
+
|
|
738
|
+
await EmailRollupWriter.sendOrRollup(sendData());
|
|
739
|
+
|
|
740
|
+
expect(rollupEnabled).toHaveBeenCalledTimes(1);
|
|
741
|
+
expect(sendMail).not.toHaveBeenCalled();
|
|
742
|
+
expect(createItem).toHaveBeenCalledTimes(1);
|
|
743
|
+
expect(writtenItem().sentAt).toBeUndefined();
|
|
744
|
+
expect(loggerError).not.toHaveBeenCalled();
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
test("at the threshold with rollup off, the email is sent immediately with the envelope and options unchanged", async () => {
|
|
748
|
+
/*
|
|
749
|
+
* Opting out has to reproduce the pre-rollup product exactly, envelope
|
|
750
|
+
* and correlation-id bag included - not "an email that says roughly the
|
|
751
|
+
* same thing". The assertions here are deliberately the same two the
|
|
752
|
+
* below-threshold test makes.
|
|
753
|
+
*/
|
|
754
|
+
recentCount(BURST_THRESHOLD + 10);
|
|
755
|
+
rollupEnabled.mockResolvedValue(false as never);
|
|
756
|
+
|
|
757
|
+
const data: SendOrRollupData = sendData();
|
|
758
|
+
|
|
759
|
+
await EmailRollupWriter.sendOrRollup(data);
|
|
760
|
+
|
|
761
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
762
|
+
expect(sendMail.mock.calls[0]?.[0]).toEqual({
|
|
763
|
+
subject: "Incident created: Checkout is down",
|
|
764
|
+
templateType: EmailTemplateType.BlankTemplate,
|
|
765
|
+
vars: {
|
|
766
|
+
incidentViewLink: INCIDENT_LINK,
|
|
767
|
+
},
|
|
768
|
+
toEmail: TO_EMAIL,
|
|
769
|
+
});
|
|
770
|
+
expect(sendMail.mock.calls[0]?.[1]).toBe(data.mailOptions);
|
|
771
|
+
expect(loggerError).not.toHaveBeenCalled();
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
test("an opted-out send STILL writes its ledger row, with sentAt set", async () => {
|
|
775
|
+
/*
|
|
776
|
+
* The one most likely to be lost in a later refactor, because returning
|
|
777
|
+
* early on the opt-out reads like the tidy version of the same thing.
|
|
778
|
+
*
|
|
779
|
+
* It is not. The row is what the burst counter counts, and the counter is
|
|
780
|
+
* scoped to (project, user, address, category) - so an opted-out person
|
|
781
|
+
* whose sends wrote no row would make the window under-count, and their
|
|
782
|
+
* traffic would become invisible to the volume ledger this table exists
|
|
783
|
+
* to be. sentAt must be SET, because the row describes an email that has
|
|
784
|
+
* already gone out; a NULL sentAt is what "pending" means to the flush
|
|
785
|
+
* sweep, and the sweep would pick this row up and send the notification a
|
|
786
|
+
* second time inside a rollup.
|
|
787
|
+
*/
|
|
788
|
+
recentCount(BURST_THRESHOLD);
|
|
789
|
+
rollupEnabled.mockResolvedValue(false as never);
|
|
790
|
+
|
|
791
|
+
await EmailRollupWriter.sendOrRollup(sendData());
|
|
792
|
+
|
|
793
|
+
expect(createItem).toHaveBeenCalledTimes(1);
|
|
794
|
+
|
|
795
|
+
const item: UserNotificationEmailRollupItem = writtenItem();
|
|
796
|
+
expect(item.sentAt).toBeInstanceOf(Date);
|
|
797
|
+
expect(item.projectId?.toString()).toBe(PROJECT_ID.toString());
|
|
798
|
+
expect(item.userId?.toString()).toBe(USER_ID.toString());
|
|
799
|
+
expect(item.toEmail?.toString()).toBe(TO_EMAIL.toString());
|
|
800
|
+
expect(item.rollupCategory).toBe(
|
|
801
|
+
NotificationEmailRollupCategory.RollupCategory.Incidents,
|
|
802
|
+
);
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
test("the preference is read for the recipient of this email", async () => {
|
|
806
|
+
recentCount(BURST_THRESHOLD);
|
|
807
|
+
|
|
808
|
+
await EmailRollupWriter.sendOrRollup(sendData());
|
|
809
|
+
|
|
810
|
+
const captured: CapturedPreferenceRead = capturedPreferenceRead();
|
|
811
|
+
expect(captured.userId.toString()).toBe(USER_ID.toString());
|
|
812
|
+
expect(captured.projectId.toString()).toBe(PROJECT_ID.toString());
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
test("a preference read that throws sends the email immediately and logs, rather than deferring", async () => {
|
|
816
|
+
/*
|
|
817
|
+
* Fail open, again: the failure mode of the escape hatch is the escape
|
|
818
|
+
* hatch. A read that threw and left `deferred` true would hold back mail
|
|
819
|
+
* on the strength of a preference nobody managed to look up.
|
|
820
|
+
*/
|
|
821
|
+
recentCount(BURST_THRESHOLD);
|
|
822
|
+
rollupEnabled.mockRejectedValue(
|
|
823
|
+
new Error(
|
|
824
|
+
'relation "UserNotificationEmailRollupSetting" does not exist',
|
|
825
|
+
),
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
await expect(
|
|
829
|
+
EmailRollupWriter.sendOrRollup(sendData()),
|
|
830
|
+
).resolves.toBeUndefined();
|
|
831
|
+
|
|
832
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
833
|
+
expect(loggerError).toHaveBeenCalledTimes(1);
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
test("forceImmediate short-circuits before the preference is read", async () => {
|
|
837
|
+
/*
|
|
838
|
+
* The structural bypasses have to cost zero extra queries. A preference
|
|
839
|
+
* read reached from either of them would put a database round trip on
|
|
840
|
+
* the paging-adjacent path the bypasses exist to keep clear.
|
|
841
|
+
*/
|
|
842
|
+
recentCount(100);
|
|
843
|
+
|
|
844
|
+
await EmailRollupWriter.sendOrRollup(sendData({ forceImmediate: true }));
|
|
845
|
+
|
|
846
|
+
expect(rollupEnabled).not.toHaveBeenCalled();
|
|
847
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
test("a never-rolled-up event type short-circuits before the preference is read", async () => {
|
|
851
|
+
recentCount(100);
|
|
852
|
+
|
|
853
|
+
for (const eventType of Array.from(NEVER_ROLLED_UP_EVENT_TYPES)) {
|
|
854
|
+
rollupEnabled.mockClear();
|
|
855
|
+
sendMail.mockClear();
|
|
856
|
+
|
|
857
|
+
await EmailRollupWriter.sendOrRollup(sendData({ eventType }));
|
|
858
|
+
|
|
859
|
+
expect(rollupEnabled).not.toHaveBeenCalled();
|
|
860
|
+
expect(sendMail).toHaveBeenCalledTimes(1);
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
});
|
|
864
|
+
});
|