@oneuptime/common 14.0.6 → 14.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/Incident.ts +267 -1
- package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
- package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
- package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
- package/Models/DatabaseModels/InventoryItem.ts +23 -12
- package/Models/DatabaseModels/StatusPage.ts +55 -0
- package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
- package/Server/API/IncidentAPI.ts +162 -0
- package/Server/API/StatusPageAPI.ts +487 -171
- package/Server/API/StatusPageSubscriberAPI.ts +63 -15
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.ts +217 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +16 -0
- package/Server/Infrastructure/Semaphore.ts +10 -0
- package/Server/Middleware/IdentityRateLimit.ts +108 -3
- package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
- package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
- package/Server/Services/AlertService.ts +32 -36
- package/Server/Services/DatabaseService.ts +93 -10
- package/Server/Services/IncidentAlertService.ts +609 -2
- package/Server/Services/IncidentCustomFieldService.ts +276 -1
- package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
- package/Server/Services/IncidentEpisodeService.ts +15 -0
- package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
- package/Server/Services/IncidentPublicNoteService.ts +30 -1
- package/Server/Services/IncidentService.ts +1663 -103
- package/Server/Services/IncidentStateTimelineService.ts +13 -0
- package/Server/Services/IncidentTemplateService.ts +109 -0
- package/Server/Services/InventoryItemService.ts +38 -0
- package/Server/Services/IoTDeviceCredentialService.ts +13 -4
- package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
- package/Server/Services/ScheduledMaintenanceService.ts +118 -64
- package/Server/Services/SmsService.ts +10 -0
- package/Server/Services/StatusPageService.ts +65 -8
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
- package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
- package/Server/Services/StatusPageSubscriberService.ts +1161 -64
- package/Server/Services/UserNotificationRuleService.ts +55 -53
- package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
- package/Server/Types/Markdown.ts +153 -6
- package/Server/Types/Workflow/Components/Email.ts +61 -2
- package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
- package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
- package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
- package/Server/Utils/DataSource/EgressGuard.ts +66 -21
- package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
- package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
- package/Server/Utils/Response.ts +31 -0
- package/Server/Utils/SSRFProtection.ts +76 -16
- package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
- package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
- package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
- package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
- package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
- package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
- package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
- package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
- package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
- package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
- package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
- package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
- package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
- package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
- package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
- package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
- package/Server/Utils/StatusPageResource.ts +51 -20
- package/Server/Utils/Topology/TopologyConcurrencyLimiter.ts +229 -0
- package/Server/Utils/Topology/TopologyQueries.ts +1350 -0
- package/Server/Utils/Topology/TopologyRequest.ts +463 -0
- package/Server/Utils/Topology/TopologyResponseCache.ts +183 -0
- package/Server/Utils/Topology/TopologySql.ts +1024 -0
- package/Server/Utils/VM/VMAPI.ts +1 -0
- package/Server/Utils/VM/VMRunner.ts +10 -0
- package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
- package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
- package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
- package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
- package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
- package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
- package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
- package/Tests/App/Dashboard/AcknowledgeAlertsOnDeclare.test.ts +369 -0
- package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +1 -1
- package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +510 -123
- package/Tests/App/Dashboard/AlertsTableAffectedResources.test.tsx +490 -0
- package/Tests/App/Dashboard/ChangeAlertStateDeclareIncident.test.tsx +886 -0
- package/Tests/App/Dashboard/CreateReviewStepRoleSummary.test.tsx +1250 -0
- package/Tests/App/Dashboard/CreateReviewStepSelectionSummary.test.tsx +738 -0
- package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
- package/Tests/App/Dashboard/DeclareIncidentFromAlert.test.ts +417 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +1948 -229
- package/Tests/App/Dashboard/EntityDetailPanelLifecycle.test.tsx +493 -0
- package/Tests/App/Dashboard/EntityDetailPanelNavigation.test.tsx +439 -0
- package/Tests/App/Dashboard/EventNotes.test.tsx +776 -0
- package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
- package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +534 -6
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +798 -2
- package/Tests/App/Dashboard/FetchStateAndSeverityElements.test.tsx +669 -0
- package/Tests/App/Dashboard/FetchUsers.test.tsx +230 -0
- package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
- package/Tests/App/Dashboard/IncidentCreateAcknowledgeAlerts.test.tsx +1611 -0
- package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
- package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
- package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
- package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
- package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
- package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
- package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
- package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
- package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
- package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
- package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +1365 -57
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +964 -23
- package/Tests/App/Dashboard/InventoryRelationships.test.tsx +885 -0
- package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
- package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
- package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
- package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
- package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
- package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
- package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
- package/Tests/App/Dashboard/TopologyDataLifecycle.test.tsx +583 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +982 -218
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +818 -114
- package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
- package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
- package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
- package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
- package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
- package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
- package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
- package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
- package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
- package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
- package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
- package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
- package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
- package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
- package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
- package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
- package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +90 -21
- package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
- package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
- package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
- package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
- package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
- package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
- package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
- package/Tests/Server/Services/DomainService.test.ts +525 -0
- package/Tests/Server/Services/IncidentAlertAcknowledgeOnDeclare.test.ts +2630 -0
- package/Tests/Server/Services/IncidentAlertPostgres.test.ts +1625 -1
- package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
- package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
- package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
- package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
- package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
- package/Tests/Server/Services/IncidentDeclareAcknowledgeAlerts.test.ts +2189 -0
- package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
- package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
- package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
- package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
- package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
- package/Tests/Server/Services/InventoryItemServiceOverviewCounts.test.ts +345 -0
- package/Tests/Server/Services/InventoryOverviewPostgres.test.ts +254 -0
- package/Tests/Server/Services/IoTDeviceCredentialService.test.ts +819 -0
- package/Tests/Server/Services/LinkedAffectedResourcesCreatedFeed.test.ts +420 -0
- package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
- package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
- package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
- package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
- package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
- package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
- package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
- package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
- package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
- package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
- package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
- package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
- package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
- package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
- package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
- package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
- package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
- package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
- package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
- package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
- package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
- package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
- package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
- package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
- package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
- package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
- package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
- package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
- package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
- package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigration.test.ts +386 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigrationPostgres.test.ts +430 -0
- package/Tests/Server/Utils/Topology/TopologyActivityParityPostgres.test.ts +279 -0
- package/Tests/Server/Utils/Topology/TopologyConcurrencyLimiter.test.ts +534 -0
- package/Tests/Server/Utils/Topology/TopologyEstateGenerator.ts +624 -0
- package/Tests/Server/Utils/Topology/TopologyJsonResponse.test.ts +90 -0
- package/Tests/Server/Utils/Topology/TopologyQueries.test.ts +1634 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesDifferentialPostgres.test.ts +683 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesPostgres.test.ts +2624 -0
- package/Tests/Server/Utils/Topology/TopologyReference.ts +1018 -0
- package/Tests/Server/Utils/Topology/TopologyRequest.test.ts +643 -0
- package/Tests/Server/Utils/Topology/TopologyResponseCache.test.ts +305 -0
- package/Tests/Server/Utils/Topology/TopologySql.test.ts +973 -0
- package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
- package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
- package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
- package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
- package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
- package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
- package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
- package/Tests/Types/SafeHtml.test.ts +102 -0
- package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
- package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
- package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
- package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
- package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
- package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
- package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
- package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
- package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
- package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
- package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
- package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
- package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
- package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
- package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
- package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
- package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
- package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
- package/Types/CustomField/CustomFieldOrder.ts +55 -0
- package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
- package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
- package/Types/CustomField/CustomFieldType.ts +14 -0
- package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
- package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
- package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
- package/Types/Incident/IncidentAlertLink.ts +19 -0
- package/Types/SafeHtml.ts +93 -0
- package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
- package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
- package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
- package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
- package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
- package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
- package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
- package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
- package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
- package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
- package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
- package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
- package/Types/Telemetry/InventoryLiveness.ts +26 -0
- package/Types/Topology/TopologyApi.ts +428 -0
- package/Types/Topology/TopologyTypeRules.ts +191 -0
- package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
- package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
- package/UI/Components/Forms/ModelForm.tsx +70 -8
- package/UI/Components/Forms/Validation.ts +1 -1
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
- package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
- package/UI/Components/ModelTable/BaseModelTable.tsx +9 -8
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
- package/UI/Components/ModelTable/ModelTable.tsx +6 -7
- package/UI/Utils/SensitiveUrlToken.ts +10 -5
- package/Utils/API.ts +247 -2
- package/Utils/Incident/IncidentFromAlerts.ts +78 -0
- package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
- package/Utils/Markdown/MarkdownEscape.ts +65 -0
- package/Utils/ProbeNetworkFailureUtil.ts +673 -0
- package/build/dist/Models/DatabaseModels/Incident.js +267 -1
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +20 -11
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Server/API/IncidentAPI.js +100 -1
- package/build/dist/Server/API/IncidentAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +417 -176
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
- package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js +165 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
- package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
- package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
- package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
- package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
- package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
- package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
- package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
- package/build/dist/Server/Services/AlertService.js +21 -33
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +56 -7
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentAlertService.js +457 -3
- package/build/dist/Server/Services/IncidentAlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
- package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
- package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +1148 -42
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
- package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +31 -0
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceCredentialService.js +12 -5
- package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +84 -57
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/SmsService.js +2 -22
- package/build/dist/Server/Services/SmsService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +47 -9
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +55 -51
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +42 -25
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +110 -6
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
- package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js +405 -0
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js.map +1 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js +112 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js +81 -0
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js.map +1 -0
- package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
- package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +30 -0
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +48 -6
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
- package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
- package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
- package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPageResource.js +39 -7
- package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js +178 -0
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js +884 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js +275 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js +146 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js +688 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js.map +1 -0
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
- package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
- package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
- package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
- package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
- package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
- package/build/dist/Types/SafeHtml.js +84 -0
- package/build/dist/Types/SafeHtml.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
- package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
- package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
- package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
- package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
- package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
- package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +23 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -1
- package/build/dist/Types/Topology/TopologyApi.js +55 -0
- package/build/dist/Types/Topology/TopologyApi.js.map +1 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js +165 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js.map +1 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +1 -1
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -2
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
- package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
- package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
- package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
- package/build/dist/Utils/API.js +177 -1
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Incident/IncidentFromAlerts.js +38 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -1
- package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
- package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
- package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
- package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
- package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
- package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
import Incident from "../../../Models/DatabaseModels/Incident";
|
|
2
|
+
import IncidentEpisodeMember from "../../../Models/DatabaseModels/IncidentEpisodeMember";
|
|
3
|
+
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
4
|
+
import StatusPage from "../../../Models/DatabaseModels/StatusPage";
|
|
5
|
+
import StatusPageResource from "../../../Models/DatabaseModels/StatusPageResource";
|
|
6
|
+
import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
7
|
+
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
8
|
+
import LIMIT_MAX, { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
|
|
9
|
+
import Dictionary from "../../../Types/Dictionary";
|
|
10
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
11
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
12
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
13
|
+
import PositiveNumber from "../../../Types/PositiveNumber";
|
|
14
|
+
import IncidentScopeAddedPagesNotification from "../../../Types/StatusPage/IncidentScopeAddedPagesNotification";
|
|
15
|
+
import IncidentEpisodeMemberService from "../../Services/IncidentEpisodeMemberService";
|
|
16
|
+
import IncidentService from "../../Services/IncidentService";
|
|
17
|
+
import StatusPageResourceService from "../../Services/StatusPageResourceService";
|
|
18
|
+
import StatusPageSubscriberService from "../../Services/StatusPageSubscriberService";
|
|
19
|
+
import Query from "../../Types/Database/Query";
|
|
20
|
+
import QueryHelper from "../../Types/Database/QueryHelper";
|
|
21
|
+
import Select from "../../Types/Database/Select";
|
|
22
|
+
import Sort from "../../Types/Database/Sort";
|
|
23
|
+
import logger from "../Logger";
|
|
24
|
+
import {
|
|
25
|
+
ExcludedStatusPage,
|
|
26
|
+
StatusPageExclusionReason,
|
|
27
|
+
} from "./StatusPageExclusion";
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* Which status pages an incident reaches - which pages show it, and whose
|
|
31
|
+
* subscribers hear about it. This is the only place that decides it; the
|
|
32
|
+
* subscriber jobs, the public status page and the reports all ask here, and
|
|
33
|
+
* a guard test (IncidentStatusPageScopeCallSites.test.ts) fails on code that
|
|
34
|
+
* works it out for itself from an incident's monitors.
|
|
35
|
+
*
|
|
36
|
+
* An incident reaches a status page through its monitors: a page reaches
|
|
37
|
+
* every incident on a monitor it lists (directly or through a monitor group).
|
|
38
|
+
* Two things narrow that:
|
|
39
|
+
*
|
|
40
|
+
* - Incident.statusPages. A scoped incident (isScopedToStatusPages) reaches
|
|
41
|
+
* only the pages it is limited to AMONG the pages its monitors reach. The
|
|
42
|
+
* two sets intersect: a scope never puts an incident on a page that does
|
|
43
|
+
* not list its monitors. A scope whose pages have all been deleted is a
|
|
44
|
+
* scope to nothing - the incident reaches no page at all, rather than every
|
|
45
|
+
* page its monitors reach.
|
|
46
|
+
* - StatusPage.onlyShowScopedIncidents. Such a page never reaches an unscoped
|
|
47
|
+
* incident, so an incident a monitor, Slack, Teams, the API or AI created
|
|
48
|
+
* on a monitor shared by many pages reaches none of them until someone
|
|
49
|
+
* scopes it.
|
|
50
|
+
*
|
|
51
|
+
* Both flags fail closed. isScopedToStatusPages is read here, never trusted
|
|
52
|
+
* from a caller. A status page whose onlyShowScopedIncidents was not loaded
|
|
53
|
+
* is treated as showing only scoped incidents, and an incident whose scope
|
|
54
|
+
* was not loaded is in no page's scope: a select that forgets a column then
|
|
55
|
+
* hides an incident, visibly, instead of broadcasting it to pages that opted
|
|
56
|
+
* out.
|
|
57
|
+
*
|
|
58
|
+
* Scope is applied in SQL for the status page queries (two queries split on
|
|
59
|
+
* isScopedToStatusPages), never as a post-filter: those queries are capped at
|
|
60
|
+
* LIMIT_PER_PROJECT, and a filter applied after the cap would silently drop
|
|
61
|
+
* incidents that are in scope.
|
|
62
|
+
*
|
|
63
|
+
* Nothing here selects statusPages, isScopedToStatusPages or
|
|
64
|
+
* statusPagesNotifiedOnCreation into the incidents it returns for display, so
|
|
65
|
+
* one audience's page names cannot reach another audience's public page.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
export { StatusPageExclusionReason };
|
|
69
|
+
export type { ExcludedStatusPage };
|
|
70
|
+
|
|
71
|
+
/*
|
|
72
|
+
* What decides where one incident reaches: the monitors it is on, and the
|
|
73
|
+
* pages it is limited to (null when it is not limited to any).
|
|
74
|
+
*/
|
|
75
|
+
export interface IncidentStatusPageReach {
|
|
76
|
+
// Absent for an incident that is not saved yet.
|
|
77
|
+
incidentId?: string | undefined;
|
|
78
|
+
monitorIds: Array<ObjectID>;
|
|
79
|
+
/*
|
|
80
|
+
* Lower-cased status page ids, or null for an unscoped incident. An empty
|
|
81
|
+
* list is a scope with no pages left: it reaches nothing.
|
|
82
|
+
*/
|
|
83
|
+
scopedStatusPageIds: Array<string> | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ResolvedIncidentStatusPages {
|
|
87
|
+
/*
|
|
88
|
+
* The status pages to tell, in name order (see compareStatusPagesByName).
|
|
89
|
+
* Loaded by StatusPageSubscriberService.getStatusPagesToSendNotification, so
|
|
90
|
+
* they carry everything a subscriber job needs.
|
|
91
|
+
*/
|
|
92
|
+
statusPages: Array<StatusPage>;
|
|
93
|
+
/*
|
|
94
|
+
* The resources each of those pages lists that the incidents affect, keyed
|
|
95
|
+
* by the page's _id. Only resources the scope lets through: a page reached
|
|
96
|
+
* through one incident of an episode lists only that incident's resources.
|
|
97
|
+
*/
|
|
98
|
+
statusPageToResources: Dictionary<Array<StatusPageResource>>;
|
|
99
|
+
/*
|
|
100
|
+
* Whether any of the incidents is limited to specific status pages. Email
|
|
101
|
+
* and SMS are sent once per address across pages only then; an unscoped
|
|
102
|
+
* incident notifies exactly as it always has.
|
|
103
|
+
*/
|
|
104
|
+
isScoped: boolean;
|
|
105
|
+
// Pages the monitors reach that the scope rules left out, in name order.
|
|
106
|
+
excludedStatusPages: Array<ExcludedStatusPage>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/*
|
|
110
|
+
* The status page a display query runs for. onlyShowScopedIncidents must be
|
|
111
|
+
* loaded: a page without it is treated as showing only scoped incidents.
|
|
112
|
+
*/
|
|
113
|
+
export interface StatusPageScopeTarget {
|
|
114
|
+
_id?: string | undefined;
|
|
115
|
+
id?: ObjectID | null | undefined;
|
|
116
|
+
onlyShowScopedIncidents?: boolean | undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// The incident columns that say where an incident reaches. Never public.
|
|
120
|
+
export const INCIDENT_SCOPE_COLUMNS: ReadonlyArray<string> = [
|
|
121
|
+
"statusPages",
|
|
122
|
+
"isScopedToStatusPages",
|
|
123
|
+
"statusPagesNotifiedOnCreation",
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
/*
|
|
127
|
+
* What isIncidentInScope reads of an incident, for a caller that reads
|
|
128
|
+
* incidents by id (an episode's members) and applies the scope in memory.
|
|
129
|
+
* Such a caller removes the columns again (removeScopeColumns) before
|
|
130
|
+
* anything it read is serialized.
|
|
131
|
+
*/
|
|
132
|
+
export const INCIDENT_SCOPE_SELECT: Select<Incident> = {
|
|
133
|
+
isScopedToStatusPages: true,
|
|
134
|
+
statusPages: {
|
|
135
|
+
_id: true,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// What every subscriber job reads of the resources an incident affects.
|
|
140
|
+
export const SUBSCRIBER_NOTIFICATION_RESOURCE_SELECT: Select<StatusPageResource> =
|
|
141
|
+
{
|
|
142
|
+
_id: true,
|
|
143
|
+
displayName: true,
|
|
144
|
+
statusPageId: true,
|
|
145
|
+
statusPageGroupId: true,
|
|
146
|
+
statusPageGroup: {
|
|
147
|
+
name: true,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export default class IncidentStatusPageScope {
|
|
152
|
+
/*
|
|
153
|
+
* The status pages these saved incidents reach, with the resources each
|
|
154
|
+
* page lists that they affect. Several incidents - an episode's members -
|
|
155
|
+
* reach the union of what each one reaches, each through its own scope.
|
|
156
|
+
*
|
|
157
|
+
* Each incident needs _id and its monitors. Its scope is read from the
|
|
158
|
+
* database here rather than taken from the objects passed in, so a caller
|
|
159
|
+
* that did not select it (or selected it a while ago) cannot widen the
|
|
160
|
+
* reach. An incident that is no longer found reaches nothing.
|
|
161
|
+
*/
|
|
162
|
+
public static async resolvePagesForIncidents(data: {
|
|
163
|
+
incidents: Array<Incident>;
|
|
164
|
+
resourceSelect?: Select<StatusPageResource> | undefined;
|
|
165
|
+
}): Promise<ResolvedIncidentStatusPages> {
|
|
166
|
+
const incidentIds: Array<string> = [];
|
|
167
|
+
|
|
168
|
+
for (const incident of data.incidents) {
|
|
169
|
+
const incidentId: string | undefined = this.getIncidentId(incident);
|
|
170
|
+
|
|
171
|
+
if (!incidentId) {
|
|
172
|
+
throw new BadDataException(
|
|
173
|
+
"Cannot work out which status pages an incident reaches without its id.",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!incidentIds.includes(incidentId)) {
|
|
178
|
+
incidentIds.push(incidentId);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const scopes: Dictionary<Array<string> | null> =
|
|
183
|
+
await this.getIncidentScopes(incidentIds);
|
|
184
|
+
|
|
185
|
+
const reaches: Array<IncidentStatusPageReach> = [];
|
|
186
|
+
|
|
187
|
+
for (const incident of data.incidents) {
|
|
188
|
+
const incidentId: string = this.getIncidentId(incident)!;
|
|
189
|
+
|
|
190
|
+
reaches.push({
|
|
191
|
+
incidentId: incidentId,
|
|
192
|
+
monitorIds: this.getMonitorIds(incident.monitors),
|
|
193
|
+
/*
|
|
194
|
+
* Not found: deleted since, or not readable. Scoped to nothing, so it
|
|
195
|
+
* reaches no page.
|
|
196
|
+
*/
|
|
197
|
+
scopedStatusPageIds: Object.prototype.hasOwnProperty.call(
|
|
198
|
+
scopes,
|
|
199
|
+
incidentId,
|
|
200
|
+
)
|
|
201
|
+
? (scopes[incidentId] as Array<string> | null)
|
|
202
|
+
: [],
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return this.resolvePagesForReaches({
|
|
207
|
+
reaches: reaches,
|
|
208
|
+
resourceSelect: data.resourceSelect,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/*
|
|
213
|
+
* The status pages an incident that is not saved yet would reach: the
|
|
214
|
+
* monitors and pages picked on a form. An empty page list is unscoped, the
|
|
215
|
+
* way IncidentService derives isScopedToStatusPages on create.
|
|
216
|
+
*/
|
|
217
|
+
public static async resolvePagesForDraftIncident(data: {
|
|
218
|
+
monitorIds: Array<ObjectID>;
|
|
219
|
+
statusPageIds?: Array<ObjectID | string> | undefined;
|
|
220
|
+
resourceSelect?: Select<StatusPageResource> | undefined;
|
|
221
|
+
}): Promise<ResolvedIncidentStatusPages> {
|
|
222
|
+
const statusPageIds: Array<string> =
|
|
223
|
+
IncidentScopeAddedPagesNotification.normalizeStatusPageIds(
|
|
224
|
+
data.statusPageIds || [],
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
return this.resolvePagesForReaches({
|
|
228
|
+
reaches: [
|
|
229
|
+
{
|
|
230
|
+
monitorIds: data.monitorIds,
|
|
231
|
+
scopedStatusPageIds: statusPageIds.length > 0 ? statusPageIds : null,
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
resourceSelect: data.resourceSelect,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/*
|
|
239
|
+
* The incidents of an episode, each with its _id and monitors, ready for
|
|
240
|
+
* resolvePagesForIncidents. An episode reaches the union of the pages its
|
|
241
|
+
* member incidents reach.
|
|
242
|
+
*/
|
|
243
|
+
public static async getEpisodeMemberIncidents(
|
|
244
|
+
incidentEpisodeId: ObjectID,
|
|
245
|
+
): Promise<Array<Incident>> {
|
|
246
|
+
const members: Array<IncidentEpisodeMember> =
|
|
247
|
+
await IncidentEpisodeMemberService.findBy({
|
|
248
|
+
query: {
|
|
249
|
+
incidentEpisodeId: incidentEpisodeId,
|
|
250
|
+
},
|
|
251
|
+
select: {
|
|
252
|
+
incidentId: true,
|
|
253
|
+
incident: {
|
|
254
|
+
_id: true,
|
|
255
|
+
monitors: {
|
|
256
|
+
_id: true,
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
props: {
|
|
261
|
+
isRoot: true,
|
|
262
|
+
},
|
|
263
|
+
limit: LIMIT_PER_PROJECT,
|
|
264
|
+
skip: 0,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const incidents: Array<Incident> = [];
|
|
268
|
+
const seenIncidentIds: Set<string> = new Set();
|
|
269
|
+
|
|
270
|
+
for (const member of members) {
|
|
271
|
+
const incident: Incident | undefined = member.incident;
|
|
272
|
+
|
|
273
|
+
if (!incident) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// The relation's own _id, or the member's foreign key when it is not.
|
|
278
|
+
if (!incident._id && member.incidentId) {
|
|
279
|
+
incident._id = member.incidentId.toString();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const incidentId: string | undefined = this.getIncidentId(incident);
|
|
283
|
+
|
|
284
|
+
if (!incidentId || seenIncidentIds.has(incidentId)) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
seenIncidentIds.add(incidentId);
|
|
289
|
+
incidents.push(incident);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return incidents;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/*
|
|
296
|
+
* The pages a set of reaches adds up to. Unscoped incidents are looked up
|
|
297
|
+
* together (their pages are filtered the same way); each scoped incident on
|
|
298
|
+
* its own, since its resources are filtered to its own pages.
|
|
299
|
+
*/
|
|
300
|
+
public static async resolvePagesForReaches(data: {
|
|
301
|
+
reaches: Array<IncidentStatusPageReach>;
|
|
302
|
+
resourceSelect?: Select<StatusPageResource> | undefined;
|
|
303
|
+
}): Promise<ResolvedIncidentStatusPages> {
|
|
304
|
+
const resourceSelect: Select<StatusPageResource> = {
|
|
305
|
+
...(data.resourceSelect || SUBSCRIBER_NOTIFICATION_RESOURCE_SELECT),
|
|
306
|
+
// Needed to group and to dedupe, whatever the caller asked for.
|
|
307
|
+
_id: true,
|
|
308
|
+
statusPageId: true,
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const isScoped: boolean = data.reaches.some(
|
|
312
|
+
(reach: IncidentStatusPageReach): boolean => {
|
|
313
|
+
return reach.scopedStatusPageIds !== null;
|
|
314
|
+
},
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
const batches: Array<{
|
|
318
|
+
resources: Array<StatusPageResource>;
|
|
319
|
+
scopedStatusPageIds: Array<string> | null;
|
|
320
|
+
}> = [];
|
|
321
|
+
|
|
322
|
+
const unscopedMonitorIds: Array<ObjectID> = this.distinctMonitorIds(
|
|
323
|
+
data.reaches
|
|
324
|
+
.filter((reach: IncidentStatusPageReach): boolean => {
|
|
325
|
+
return reach.scopedStatusPageIds === null;
|
|
326
|
+
})
|
|
327
|
+
.flatMap((reach: IncidentStatusPageReach): Array<ObjectID> => {
|
|
328
|
+
return reach.monitorIds;
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
if (unscopedMonitorIds.length > 0) {
|
|
333
|
+
batches.push({
|
|
334
|
+
resources: await StatusPageResourceService.findByMonitors({
|
|
335
|
+
monitorIds: unscopedMonitorIds,
|
|
336
|
+
select: resourceSelect,
|
|
337
|
+
}),
|
|
338
|
+
scopedStatusPageIds: null,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
for (const reach of data.reaches) {
|
|
343
|
+
if (reach.scopedStatusPageIds === null) {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const monitorIds: Array<ObjectID> = this.distinctMonitorIds(
|
|
348
|
+
reach.monitorIds,
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
// Scoped to nothing, or on no monitor: it reaches no page to look up.
|
|
352
|
+
if (reach.scopedStatusPageIds.length === 0 || monitorIds.length === 0) {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
batches.push({
|
|
357
|
+
resources: await StatusPageResourceService.findByMonitors({
|
|
358
|
+
monitorIds: monitorIds,
|
|
359
|
+
select: resourceSelect,
|
|
360
|
+
}),
|
|
361
|
+
scopedStatusPageIds: reach.scopedStatusPageIds,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const candidateStatusPageIds: Array<string> = [];
|
|
366
|
+
|
|
367
|
+
for (const batch of batches) {
|
|
368
|
+
for (const resource of batch.resources) {
|
|
369
|
+
const statusPageId: string | undefined = this.normalizeId(
|
|
370
|
+
resource.statusPageId,
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
if (statusPageId && !candidateStatusPageIds.includes(statusPageId)) {
|
|
374
|
+
candidateStatusPageIds.push(statusPageId);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (candidateStatusPageIds.length === 0) {
|
|
380
|
+
return {
|
|
381
|
+
statusPages: [],
|
|
382
|
+
statusPageToResources: {},
|
|
383
|
+
isScoped: isScoped,
|
|
384
|
+
excludedStatusPages: [],
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const loadedStatusPages: Array<StatusPage> =
|
|
389
|
+
await StatusPageSubscriberService.getStatusPagesToSendNotification(
|
|
390
|
+
candidateStatusPageIds.map((id: string): ObjectID => {
|
|
391
|
+
return new ObjectID(id);
|
|
392
|
+
}),
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
const statusPageById: Map<string, StatusPage> = new Map();
|
|
396
|
+
|
|
397
|
+
for (const statusPage of loadedStatusPages) {
|
|
398
|
+
const statusPageId: string | undefined = this.normalizeId(statusPage._id);
|
|
399
|
+
|
|
400
|
+
if (statusPageId) {
|
|
401
|
+
statusPageById.set(statusPageId, statusPage);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Keyed by the lower-cased page id while collecting.
|
|
406
|
+
const reachedResources: Map<string, Array<StatusPageResource>> = new Map();
|
|
407
|
+
/*
|
|
408
|
+
* The pages a scoped incident's monitors are on that its scope left out:
|
|
409
|
+
* it is limited to other pages. A page only an unscoped incident's
|
|
410
|
+
* monitors reached, and which only shows scoped incidents, is left out for
|
|
411
|
+
* that instead.
|
|
412
|
+
*/
|
|
413
|
+
const outsideScopedIncidentReach: Set<string> = new Set();
|
|
414
|
+
|
|
415
|
+
for (const batch of batches) {
|
|
416
|
+
for (const resource of batch.resources) {
|
|
417
|
+
const statusPageId: string | undefined = this.normalizeId(
|
|
418
|
+
resource.statusPageId,
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
if (!statusPageId) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const statusPage: StatusPage | undefined =
|
|
426
|
+
statusPageById.get(statusPageId);
|
|
427
|
+
|
|
428
|
+
// Deleted since the resource was read: nothing to tell.
|
|
429
|
+
if (!statusPage) {
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const isInScope: boolean =
|
|
434
|
+
batch.scopedStatusPageIds === null
|
|
435
|
+
? !this.showsOnlyScopedIncidents(statusPage)
|
|
436
|
+
: batch.scopedStatusPageIds.includes(statusPageId);
|
|
437
|
+
|
|
438
|
+
if (!isInScope) {
|
|
439
|
+
if (batch.scopedStatusPageIds !== null) {
|
|
440
|
+
outsideScopedIncidentReach.add(statusPageId);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const resources: Array<StatusPageResource> =
|
|
447
|
+
reachedResources.get(statusPageId) || [];
|
|
448
|
+
|
|
449
|
+
const resourceId: string | undefined = this.normalizeId(resource._id);
|
|
450
|
+
|
|
451
|
+
const isDuplicate: boolean = resources.some(
|
|
452
|
+
(existing: StatusPageResource): boolean => {
|
|
453
|
+
return (
|
|
454
|
+
resourceId !== undefined &&
|
|
455
|
+
this.normalizeId(existing._id) === resourceId
|
|
456
|
+
);
|
|
457
|
+
},
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
if (!isDuplicate) {
|
|
461
|
+
resources.push(resource);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
reachedResources.set(statusPageId, resources);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const statusPages: Array<StatusPage> = [];
|
|
469
|
+
const excludedStatusPages: Array<ExcludedStatusPage> = [];
|
|
470
|
+
const statusPageToResources: Dictionary<Array<StatusPageResource>> = {};
|
|
471
|
+
|
|
472
|
+
for (const [statusPageId, statusPage] of statusPageById.entries()) {
|
|
473
|
+
const resources: Array<StatusPageResource> | undefined =
|
|
474
|
+
reachedResources.get(statusPageId);
|
|
475
|
+
|
|
476
|
+
if (resources && resources.length > 0) {
|
|
477
|
+
statusPages.push(statusPage);
|
|
478
|
+
// Keyed as the subscriber jobs look pages up: by the page's own _id.
|
|
479
|
+
statusPageToResources[statusPage._id || statusPageId] = resources;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/*
|
|
484
|
+
* Why it was left out. A scoped incident that lists the page's monitors
|
|
485
|
+
* but not the page is limited to other pages, whatever the page's own
|
|
486
|
+
* setting; only a page reached by unscoped incidents alone was left
|
|
487
|
+
* out because it only shows incidents limited to it.
|
|
488
|
+
*/
|
|
489
|
+
excludedStatusPages.push({
|
|
490
|
+
statusPage: statusPage,
|
|
491
|
+
reason:
|
|
492
|
+
!outsideScopedIncidentReach.has(statusPageId) &&
|
|
493
|
+
this.showsOnlyScopedIncidents(statusPage)
|
|
494
|
+
? StatusPageExclusionReason.OnlyShowsScopedIncidents
|
|
495
|
+
: StatusPageExclusionReason.OutsideIncidentScope,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
statusPages.sort(this.compareStatusPagesByName);
|
|
500
|
+
excludedStatusPages.sort(
|
|
501
|
+
(a: ExcludedStatusPage, b: ExcludedStatusPage): number => {
|
|
502
|
+
return this.compareStatusPagesByName(a.statusPage, b.statusPage);
|
|
503
|
+
},
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
logger.debug(
|
|
507
|
+
`Incident status page scope: ${data.reaches.length} incident(s) reach ${statusPages.length} status page(s); ${excludedStatusPages.length} left out by scope.`,
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
statusPages: statusPages,
|
|
512
|
+
statusPageToResources: statusPageToResources,
|
|
513
|
+
isScoped: isScoped,
|
|
514
|
+
excludedStatusPages: excludedStatusPages,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/*
|
|
519
|
+
* The incidents a status page shows that match `query`. The caller's query
|
|
520
|
+
* keeps its monitors filter (the pages' monitors); this runs it twice,
|
|
521
|
+
* split on isScopedToStatusPages:
|
|
522
|
+
*
|
|
523
|
+
* - unscoped incidents, unless the page only shows scoped incidents;
|
|
524
|
+
* - incidents scoped to this page (the join-table filter QueryUtil builds
|
|
525
|
+
* from an id list, as the scheduled maintenance queries use).
|
|
526
|
+
*
|
|
527
|
+
* The two halves are disjoint. They are merged, sorted by `sort` as the
|
|
528
|
+
* database would have, and cut to `skip` and `limit` - each half is read up
|
|
529
|
+
* to skip + limit rows, so the cut sees every row that can make the page.
|
|
530
|
+
* Sort keys must be top-level columns; any the caller did not select are
|
|
531
|
+
* read for the merge and removed again.
|
|
532
|
+
*/
|
|
533
|
+
public static async findIncidentsForStatusPage(data: {
|
|
534
|
+
statusPage: StatusPageScopeTarget;
|
|
535
|
+
query: Query<Incident>;
|
|
536
|
+
select: Select<Incident>;
|
|
537
|
+
sort?: Sort<Incident> | undefined;
|
|
538
|
+
limit: number;
|
|
539
|
+
skip?: number | undefined;
|
|
540
|
+
props: DatabaseCommonInteractionProps;
|
|
541
|
+
}): Promise<Array<Incident>> {
|
|
542
|
+
const skip: number = Math.max(0, data.skip || 0);
|
|
543
|
+
const limit: number = Math.max(0, data.limit);
|
|
544
|
+
|
|
545
|
+
if (limit === 0) {
|
|
546
|
+
return [];
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const sort: Sort<Incident> | undefined = data.sort;
|
|
550
|
+
const sortKeys: Array<string> = Object.keys(sort || {});
|
|
551
|
+
|
|
552
|
+
const select: Select<Incident> = { ...data.select };
|
|
553
|
+
const addedKeys: Array<string> = [];
|
|
554
|
+
|
|
555
|
+
for (const key of ["_id", ...sortKeys]) {
|
|
556
|
+
if (!(select as Dictionary<unknown>)[key]) {
|
|
557
|
+
(select as Dictionary<unknown>)[key] = true;
|
|
558
|
+
addedKeys.push(key);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const results: Array<Array<Incident>> = await Promise.all(
|
|
563
|
+
this.getScopedQueries({
|
|
564
|
+
statusPage: data.statusPage,
|
|
565
|
+
query: data.query,
|
|
566
|
+
}).map((query: Query<Incident>): Promise<Array<Incident>> => {
|
|
567
|
+
return IncidentService.findBy({
|
|
568
|
+
query: query,
|
|
569
|
+
select: select,
|
|
570
|
+
sort: sort,
|
|
571
|
+
limit: skip + limit,
|
|
572
|
+
skip: 0,
|
|
573
|
+
props: data.props,
|
|
574
|
+
});
|
|
575
|
+
}),
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
const incidents: Array<Incident> = this.mergeIncidents({
|
|
579
|
+
results: results,
|
|
580
|
+
sort: sort,
|
|
581
|
+
}).slice(skip, skip + limit);
|
|
582
|
+
|
|
583
|
+
for (const incident of incidents) {
|
|
584
|
+
for (const key of addedKeys) {
|
|
585
|
+
// _id stays: it is what every caller keys on.
|
|
586
|
+
if (key !== "_id") {
|
|
587
|
+
delete (incident as unknown as Dictionary<unknown>)[key];
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return incidents;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// The one incident matching `query` that a status page shows, if any.
|
|
596
|
+
public static async findOneIncidentForStatusPage(data: {
|
|
597
|
+
statusPage: StatusPageScopeTarget;
|
|
598
|
+
query: Query<Incident>;
|
|
599
|
+
select: Select<Incident>;
|
|
600
|
+
sort?: Sort<Incident> | undefined;
|
|
601
|
+
props: DatabaseCommonInteractionProps;
|
|
602
|
+
}): Promise<Incident | null> {
|
|
603
|
+
const incidents: Array<Incident> = await this.findIncidentsForStatusPage({
|
|
604
|
+
statusPage: data.statusPage,
|
|
605
|
+
query: data.query,
|
|
606
|
+
select: data.select,
|
|
607
|
+
sort: data.sort,
|
|
608
|
+
limit: 1,
|
|
609
|
+
skip: 0,
|
|
610
|
+
props: data.props,
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
return incidents[0] || null;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/*
|
|
617
|
+
* How many incidents matching `query` a status page shows: the unscoped
|
|
618
|
+
* ones (unless the page only shows scoped incidents) plus the ones scoped
|
|
619
|
+
* to it. The two counts are disjoint, so they add up. Only the project's
|
|
620
|
+
* incidents that are visible on status pages count.
|
|
621
|
+
*/
|
|
622
|
+
public static async countIncidentsForStatusPage(data: {
|
|
623
|
+
statusPage: StatusPageScopeTarget;
|
|
624
|
+
projectId: ObjectID;
|
|
625
|
+
query: Query<Incident>;
|
|
626
|
+
props?: DatabaseCommonInteractionProps | undefined;
|
|
627
|
+
}): Promise<number> {
|
|
628
|
+
const counts: Array<PositiveNumber> = await Promise.all(
|
|
629
|
+
this.getScopedQueries({
|
|
630
|
+
statusPage: data.statusPage,
|
|
631
|
+
query: {
|
|
632
|
+
...data.query,
|
|
633
|
+
projectId: data.projectId,
|
|
634
|
+
isVisibleOnStatusPage: true,
|
|
635
|
+
},
|
|
636
|
+
}).map((query: Query<Incident>): Promise<PositiveNumber> => {
|
|
637
|
+
return IncidentService.countBy({
|
|
638
|
+
query: query,
|
|
639
|
+
props: data.props || {
|
|
640
|
+
isRoot: true,
|
|
641
|
+
},
|
|
642
|
+
});
|
|
643
|
+
}),
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
return counts.reduce((total: number, count: PositiveNumber): number => {
|
|
647
|
+
return total + count.toNumber();
|
|
648
|
+
}, 0);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/*
|
|
652
|
+
* Whether a status page shows this incident, as far as scope goes (whether
|
|
653
|
+
* the page lists its monitors is the caller's query). For incidents already
|
|
654
|
+
* read, like an episode's members: the incident needs isScopedToStatusPages
|
|
655
|
+
* and statusPages loaded, and the page onlyShowScopedIncidents. Missing
|
|
656
|
+
* either fails closed.
|
|
657
|
+
*/
|
|
658
|
+
public static isIncidentInScope(
|
|
659
|
+
incident: Pick<Incident, "isScopedToStatusPages" | "statusPages">,
|
|
660
|
+
statusPage: StatusPageScopeTarget,
|
|
661
|
+
): boolean {
|
|
662
|
+
if (incident.isScopedToStatusPages === true) {
|
|
663
|
+
const statusPageId: string | undefined = this.getStatusPageId(statusPage);
|
|
664
|
+
|
|
665
|
+
return Boolean(
|
|
666
|
+
statusPageId &&
|
|
667
|
+
IncidentScopeAddedPagesNotification.normalizeStatusPageIds(
|
|
668
|
+
incident.statusPages,
|
|
669
|
+
).includes(statusPageId),
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (incident.isScopedToStatusPages === false) {
|
|
674
|
+
return !this.showsOnlyScopedIncidents(statusPage);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Not loaded: in no page's scope.
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/*
|
|
682
|
+
* Removes the scope columns from an incident about to be serialized for a
|
|
683
|
+
* public status page - the model or its JSON - in case a select brought
|
|
684
|
+
* them in.
|
|
685
|
+
*/
|
|
686
|
+
public static removeScopeColumns<T extends Incident | JSONObject>(
|
|
687
|
+
incident: T,
|
|
688
|
+
): T {
|
|
689
|
+
for (const column of INCIDENT_SCOPE_COLUMNS) {
|
|
690
|
+
delete (incident as unknown as Dictionary<unknown>)[column];
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return incident;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/*
|
|
697
|
+
* Visiting order for a send: by name, then id. Email and SMS go once per
|
|
698
|
+
* address across the pages of a scoped incident, so the page visited first
|
|
699
|
+
* is the one whose template, branding and unsubscribe link a person on
|
|
700
|
+
* several of them gets - and name order makes that predictable.
|
|
701
|
+
*/
|
|
702
|
+
public static compareStatusPagesByName(a: StatusPage, b: StatusPage): number {
|
|
703
|
+
const nameA: string = (a.name || a.pageTitle || "").trim();
|
|
704
|
+
const nameB: string = (b.name || b.pageTitle || "").trim();
|
|
705
|
+
|
|
706
|
+
const byName: number = nameA.localeCompare(nameB, "en", {
|
|
707
|
+
sensitivity: "base",
|
|
708
|
+
numeric: true,
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
if (byName !== 0) {
|
|
712
|
+
return byName;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return (a._id || "").localeCompare(b._id || "");
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// The two halves of a display query for one status page.
|
|
719
|
+
private static getScopedQueries(data: {
|
|
720
|
+
statusPage: StatusPageScopeTarget;
|
|
721
|
+
query: Query<Incident>;
|
|
722
|
+
}): Array<Query<Incident>> {
|
|
723
|
+
const statusPageId: string | undefined = this.getStatusPageId(
|
|
724
|
+
data.statusPage,
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
if (!statusPageId) {
|
|
728
|
+
throw new BadDataException(
|
|
729
|
+
"Cannot find the incidents a status page shows without its id.",
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const queries: Array<Query<Incident>> = [];
|
|
734
|
+
|
|
735
|
+
if (!this.showsOnlyScopedIncidents(data.statusPage)) {
|
|
736
|
+
queries.push({
|
|
737
|
+
...data.query,
|
|
738
|
+
isScopedToStatusPages: false,
|
|
739
|
+
} as Query<Incident>);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
queries.push({
|
|
743
|
+
...data.query,
|
|
744
|
+
isScopedToStatusPages: true,
|
|
745
|
+
// An id list on an entity-array column becomes a join-table filter.
|
|
746
|
+
statusPages: [statusPageId],
|
|
747
|
+
} as unknown as Query<Incident>);
|
|
748
|
+
|
|
749
|
+
return queries;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/*
|
|
753
|
+
* One list out of the two halves: each incident once, sorted by `sort`
|
|
754
|
+
* the way Postgres sorts - NULLs last ascending and first descending. With
|
|
755
|
+
* no sort, unscoped incidents come first, as each half came back.
|
|
756
|
+
*/
|
|
757
|
+
private static mergeIncidents(data: {
|
|
758
|
+
results: Array<Array<Incident>>;
|
|
759
|
+
sort: Sort<Incident> | undefined;
|
|
760
|
+
}): Array<Incident> {
|
|
761
|
+
const merged: Array<Incident> = [];
|
|
762
|
+
const seen: Set<string> = new Set();
|
|
763
|
+
|
|
764
|
+
for (const incident of data.results.flat()) {
|
|
765
|
+
const incidentId: string | undefined = this.getIncidentId(incident);
|
|
766
|
+
|
|
767
|
+
if (incidentId) {
|
|
768
|
+
if (seen.has(incidentId)) {
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
seen.add(incidentId);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
merged.push(incident);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const sortEntries: Array<[string, SortOrder]> = Object.entries(
|
|
779
|
+
data.sort || {},
|
|
780
|
+
) as Array<[string, SortOrder]>;
|
|
781
|
+
|
|
782
|
+
if (sortEntries.length === 0) {
|
|
783
|
+
return merged;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return merged.sort((a: Incident, b: Incident): number => {
|
|
787
|
+
for (const [key, order] of sortEntries) {
|
|
788
|
+
const compared: number = this.compareSortValues(
|
|
789
|
+
(a as unknown as Dictionary<unknown>)[key],
|
|
790
|
+
(b as unknown as Dictionary<unknown>)[key],
|
|
791
|
+
);
|
|
792
|
+
|
|
793
|
+
if (compared !== 0) {
|
|
794
|
+
return order === SortOrder.Descending ? -compared : compared;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return 0;
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Ascending order of two column values; a missing value sorts last.
|
|
803
|
+
private static compareSortValues(a: unknown, b: unknown): number {
|
|
804
|
+
const aMissing: boolean = a === undefined || a === null;
|
|
805
|
+
const bMissing: boolean = b === undefined || b === null;
|
|
806
|
+
|
|
807
|
+
if (aMissing || bMissing) {
|
|
808
|
+
return aMissing === bMissing ? 0 : aMissing ? 1 : -1;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const aValue: unknown = a instanceof Date ? a.getTime() : a;
|
|
812
|
+
const bValue: unknown = b instanceof Date ? b.getTime() : b;
|
|
813
|
+
|
|
814
|
+
if (typeof aValue === "number" && typeof bValue === "number") {
|
|
815
|
+
return aValue - bValue;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (typeof aValue === "boolean" && typeof bValue === "boolean") {
|
|
819
|
+
return Number(aValue) - Number(bValue);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return String(aValue).localeCompare(String(bValue));
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/*
|
|
826
|
+
* Each incident's scope as stored: the lower-cased ids of the pages it is
|
|
827
|
+
* limited to, or null when unscoped. Incidents not found are left out.
|
|
828
|
+
*/
|
|
829
|
+
private static async getIncidentScopes(
|
|
830
|
+
incidentIds: Array<string>,
|
|
831
|
+
): Promise<Dictionary<Array<string> | null>> {
|
|
832
|
+
const scopes: Dictionary<Array<string> | null> = {};
|
|
833
|
+
|
|
834
|
+
if (incidentIds.length === 0) {
|
|
835
|
+
return scopes;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const incidents: Array<Incident> = await IncidentService.findBy({
|
|
839
|
+
query: {
|
|
840
|
+
_id: QueryHelper.any(incidentIds),
|
|
841
|
+
},
|
|
842
|
+
select: {
|
|
843
|
+
_id: true,
|
|
844
|
+
isScopedToStatusPages: true,
|
|
845
|
+
statusPages: {
|
|
846
|
+
_id: true,
|
|
847
|
+
},
|
|
848
|
+
},
|
|
849
|
+
limit: LIMIT_MAX,
|
|
850
|
+
skip: 0,
|
|
851
|
+
props: {
|
|
852
|
+
isRoot: true,
|
|
853
|
+
},
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
for (const incident of incidents) {
|
|
857
|
+
const incidentId: string | undefined = this.getIncidentId(incident);
|
|
858
|
+
|
|
859
|
+
if (!incidentId) {
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/*
|
|
864
|
+
* The flag decides, not the list: a scoped incident whose pages were
|
|
865
|
+
* all deleted has an empty list and still reaches nothing.
|
|
866
|
+
*/
|
|
867
|
+
scopes[incidentId] =
|
|
868
|
+
incident.isScopedToStatusPages === false
|
|
869
|
+
? null
|
|
870
|
+
: IncidentScopeAddedPagesNotification.normalizeStatusPageIds(
|
|
871
|
+
incident.statusPages,
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
return scopes;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/*
|
|
879
|
+
* Whether a page only shows scoped incidents. Not loaded counts as yes (see
|
|
880
|
+
* the top of this file): the column is NOT NULL, so a read that has it is
|
|
881
|
+
* always true or false.
|
|
882
|
+
*/
|
|
883
|
+
private static showsOnlyScopedIncidents(statusPage: {
|
|
884
|
+
onlyShowScopedIncidents?: boolean | undefined;
|
|
885
|
+
}): boolean {
|
|
886
|
+
return statusPage.onlyShowScopedIncidents !== false;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
private static getStatusPageId(
|
|
890
|
+
statusPage: StatusPageScopeTarget,
|
|
891
|
+
): string | undefined {
|
|
892
|
+
return this.normalizeId(statusPage._id || statusPage.id);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
private static getIncidentId(incident: Incident): string | undefined {
|
|
896
|
+
return this.normalizeId(incident._id || incident.id);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
private static getMonitorIds(
|
|
900
|
+
monitors: Array<Monitor> | undefined,
|
|
901
|
+
): Array<ObjectID> {
|
|
902
|
+
return this.distinctMonitorIds(
|
|
903
|
+
(monitors || [])
|
|
904
|
+
.map((monitor: Monitor): string | undefined => {
|
|
905
|
+
return this.normalizeId(monitor?._id || monitor?.id);
|
|
906
|
+
})
|
|
907
|
+
.filter((id: string | undefined): id is string => {
|
|
908
|
+
return Boolean(id);
|
|
909
|
+
})
|
|
910
|
+
.map((id: string): ObjectID => {
|
|
911
|
+
return new ObjectID(id);
|
|
912
|
+
}),
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
private static distinctMonitorIds(
|
|
917
|
+
monitorIds: Array<ObjectID>,
|
|
918
|
+
): Array<ObjectID> {
|
|
919
|
+
const seen: Set<string> = new Set();
|
|
920
|
+
const distinct: Array<ObjectID> = [];
|
|
921
|
+
|
|
922
|
+
for (const monitorId of monitorIds) {
|
|
923
|
+
const id: string | undefined = this.normalizeId(monitorId);
|
|
924
|
+
|
|
925
|
+
if (!id || seen.has(id)) {
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
seen.add(id);
|
|
930
|
+
distinct.push(new ObjectID(id));
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
return distinct;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// An id in any shape it arrives in, lower-cased as the database returns it.
|
|
937
|
+
private static normalizeId(
|
|
938
|
+
value: ObjectID | string | null | undefined,
|
|
939
|
+
): string | undefined {
|
|
940
|
+
if (value === undefined || value === null) {
|
|
941
|
+
return undefined;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const id: string = value.toString().trim().toLowerCase();
|
|
945
|
+
|
|
946
|
+
return id || undefined;
|
|
947
|
+
}
|
|
948
|
+
}
|