@oneuptime/common 14.0.7 → 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/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/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 +14 -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/DatabaseService.ts +93 -10
- 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 +1535 -65
- package/Server/Services/IncidentStateTimelineService.ts +13 -0
- package/Server/Services/IncidentTemplateService.ts +109 -0
- package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
- package/Server/Services/ScheduledMaintenanceService.ts +68 -18
- 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/Types/Markdown.ts +153 -6
- package/Server/Types/Workflow/Components/Email.ts +61 -2
- package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
- package/Server/Utils/DataSource/EgressGuard.ts +66 -21
- package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -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/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/CustomFieldsSettingsIncidentOptions.test.tsx +357 -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 +242 -5
- package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
- 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/PublicNotePagesNotifyDefault.test.tsx +113 -1
- 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 +14 -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/DatabaseService.js +56 -7
- package/build/dist/Server/Services/DatabaseService.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 +1052 -0
- 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/ScheduledMaintenancePublicNoteService.js +26 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +47 -17
- 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/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/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/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/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/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/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/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/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,805 @@
|
|
|
1
|
+
import Incident from "../../../Models/DatabaseModels/Incident";
|
|
2
|
+
import IncidentCustomField from "../../../Models/DatabaseModels/IncidentCustomField";
|
|
3
|
+
import Label from "../../../Models/DatabaseModels/Label";
|
|
4
|
+
import StatusPage from "../../../Models/DatabaseModels/StatusPage";
|
|
5
|
+
import StatusPageResource from "../../../Models/DatabaseModels/StatusPageResource";
|
|
6
|
+
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
7
|
+
import { sortCustomFieldDefinitions } from "../../../Types/CustomField/CustomFieldOrder";
|
|
8
|
+
import CustomFieldType from "../../../Types/CustomField/CustomFieldType";
|
|
9
|
+
import {
|
|
10
|
+
customFieldValueToDate,
|
|
11
|
+
customFieldValueToText,
|
|
12
|
+
formatCustomFieldBoolean,
|
|
13
|
+
formatCustomFieldCalendarDate,
|
|
14
|
+
} from "../../../Types/CustomField/CustomFieldValueFormat";
|
|
15
|
+
import { isCustomFieldValueEmpty } from "../../../Types/CustomField/CustomFieldValueMapping";
|
|
16
|
+
import {
|
|
17
|
+
getCustomFieldTemplateVariableName,
|
|
18
|
+
isValidCustomFieldVariableKey,
|
|
19
|
+
} from "../../../Types/CustomField/CustomFieldVariableKey";
|
|
20
|
+
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
|
|
21
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
22
|
+
import { JSONObject, JSONValue } from "../../../Types/JSON";
|
|
23
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
24
|
+
import SafeHtml from "../../../Types/SafeHtml";
|
|
25
|
+
import SubscriberNotificationTemplateCompiler, {
|
|
26
|
+
SubscriberNotificationEmailBodyTemplateVariables,
|
|
27
|
+
SubscriberNotificationTextTemplateVariables,
|
|
28
|
+
} from "../../../Types/StatusPage/SubscriberNotificationTemplateCompiler";
|
|
29
|
+
import Timezone from "../../../Types/Timezone";
|
|
30
|
+
import { escapeMarkdownInline } from "../../../Utils/Markdown/MarkdownEscape";
|
|
31
|
+
import IncidentCustomFieldService from "../../Services/IncidentCustomFieldService";
|
|
32
|
+
import Markdown, { MarkdownContentType } from "../../Types/Markdown";
|
|
33
|
+
import { syncIsPublicForMarkdownImages } from "../InlineImageAccessTokenSync";
|
|
34
|
+
import StatusPageResourceUtil from "../StatusPageResource";
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* The values an incident's status page subscriber messages are filled with -
|
|
38
|
+
* the incident created, state changed, public note (posted and updated) and
|
|
39
|
+
* postmortem sends - in the format each channel renders.
|
|
40
|
+
*
|
|
41
|
+
* The four incident jobs used to assemble these inline, each a little
|
|
42
|
+
* differently. Built here once, so a value every channel should carry is in
|
|
43
|
+
* every channel, and so the escaping rules are written down once:
|
|
44
|
+
*
|
|
45
|
+
* - emailBody, for a custom EMAIL template's body. Plain values stay plain
|
|
46
|
+
* strings, which compileEmailBodyTemplate escapes; only HTML this code
|
|
47
|
+
* produced itself - rendered Markdown, the escaped resource list, a long
|
|
48
|
+
* text value's escaped lines, a date and time in the page's time zones -
|
|
49
|
+
* is wrapped in SafeHtml and goes in as it is.
|
|
50
|
+
* - plainText, for a custom email subject and SMS: every value as the text
|
|
51
|
+
* it reads as, Markdown flattened to plain text, nothing escaped.
|
|
52
|
+
* - markdown, for custom Slack and Microsoft Teams messages: the same, but
|
|
53
|
+
* Markdown kept as it was written.
|
|
54
|
+
*
|
|
55
|
+
* On top of the values the jobs always had, every incident message offers
|
|
56
|
+
* {{incidentLabels}}, {{affectedStatusPages}} and one {{customFields.<key>}}
|
|
57
|
+
* per incident custom field of the project, by the field's Template Variable
|
|
58
|
+
* key (SubscriberNotificationTemplateVariables). A field the incident holds
|
|
59
|
+
* no value for is an empty string, never a placeholder left as written.
|
|
60
|
+
*
|
|
61
|
+
* The default templates cannot name a project's fields, so they get the
|
|
62
|
+
* fields marked "Include in Subscriber Notifications" as a list, in the
|
|
63
|
+
* fields' order: customFieldRows for the default email (DetailBoxField, with
|
|
64
|
+
* a plain value on plainText= and HTML on the raw slot) and
|
|
65
|
+
* customFieldsMarkdownLines for the default Slack and Teams messages. Fields
|
|
66
|
+
* with no value are left out of those lists. The default SMS carries none:
|
|
67
|
+
* an SMS is billed by the segment, and a rich or long value cannot fit.
|
|
68
|
+
*
|
|
69
|
+
* The object records which fields go out, as each message is about to be
|
|
70
|
+
* sent (recordIncludedFieldsSent, recordFieldsUsedBy), for the incident
|
|
71
|
+
* feed's "Subscriber Notification Sent" item (getSentCustomFieldsMarkdown),
|
|
72
|
+
* and gives webhooks their structured customFields (getWebhookCustomFields).
|
|
73
|
+
*
|
|
74
|
+
* A Rich text field's inline images are uploaded private. The ones in a
|
|
75
|
+
* value that goes out are made public, as a public note's are when it is
|
|
76
|
+
* posted, so the recipient's mail client can load them - and only then: by
|
|
77
|
+
* those same two calls, the first time a message carries the field, never
|
|
78
|
+
* when the values are built. A send that reaches no status page or no
|
|
79
|
+
* subscriber, or a custom template that is not used (an email template on a
|
|
80
|
+
* page with no SMTP of its own, an SMS template with no Twilio), makes no
|
|
81
|
+
* image public. Like a public note's, an image stays public once it went
|
|
82
|
+
* out: the messages that carry it have been delivered and must still load it.
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
// One incident custom field, as the subscriber messages read it.
|
|
86
|
+
export interface IncidentTemplateCustomFieldDefinition {
|
|
87
|
+
name?: string | null | undefined;
|
|
88
|
+
variableKey?: string | null | undefined;
|
|
89
|
+
customFieldType?: CustomFieldType | null | undefined;
|
|
90
|
+
includeInSubscriberNotifications?: boolean | null | undefined;
|
|
91
|
+
sortOrder?: number | null | undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
* A row of the default email's custom fields. The template escapes the title
|
|
96
|
+
* and plainText; renderedHtml goes into its raw slot, and only this module
|
|
97
|
+
* fills it, with HTML it built from escaped or rendered values.
|
|
98
|
+
*/
|
|
99
|
+
export interface IncidentCustomFieldEmailRow extends JSONObject {
|
|
100
|
+
title: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// What one status page's messages are filled with.
|
|
104
|
+
export interface IncidentStatusPageTemplateVariables {
|
|
105
|
+
// A custom email template's body.
|
|
106
|
+
emailBody: SubscriberNotificationEmailBodyTemplateVariables;
|
|
107
|
+
// A custom email subject and a custom SMS.
|
|
108
|
+
plainText: SubscriberNotificationTextTemplateVariables;
|
|
109
|
+
// A custom Slack or Microsoft Teams message.
|
|
110
|
+
markdown: SubscriberNotificationTextTemplateVariables;
|
|
111
|
+
// The resource list for a default email's raw slot: every name escaped.
|
|
112
|
+
resourcesAffectedHtml: string;
|
|
113
|
+
// The resource list as written, for everything else.
|
|
114
|
+
resourcesAffectedPlainText: string;
|
|
115
|
+
// The default email's rows for the included fields that hold a value.
|
|
116
|
+
customFieldRows: Array<IncidentCustomFieldEmailRow>;
|
|
117
|
+
/*
|
|
118
|
+
* The same fields as Markdown lines ("**Name:** value") for the default
|
|
119
|
+
* Slack and Teams messages; empty when there are none.
|
|
120
|
+
*/
|
|
121
|
+
customFieldsMarkdownLines: Array<string>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// A custom field and its value, read once per send.
|
|
125
|
+
interface PreparedCustomField {
|
|
126
|
+
name: string;
|
|
127
|
+
// Null for a field without a valid key: no placeholder reaches it.
|
|
128
|
+
variableKey: string | null;
|
|
129
|
+
customFieldType: CustomFieldType | undefined;
|
|
130
|
+
isIncludedInSubscriberNotifications: boolean;
|
|
131
|
+
value: unknown;
|
|
132
|
+
hasValue: boolean;
|
|
133
|
+
// A Rich text field's value, rendered once rather than once per page.
|
|
134
|
+
markdown?: {
|
|
135
|
+
source: string;
|
|
136
|
+
html: string;
|
|
137
|
+
plainText: string;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// One custom field value in each format.
|
|
142
|
+
interface FormattedCustomFieldValue {
|
|
143
|
+
plainText: string;
|
|
144
|
+
markdown: string;
|
|
145
|
+
// HTML this module built, or null when the plain text is escaped instead.
|
|
146
|
+
html: SafeHtml | null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const LINE_BREAK_PATTERN: RegExp = /\r\n|\r|\n/g;
|
|
150
|
+
|
|
151
|
+
export class IncidentTemplateVariables {
|
|
152
|
+
private readonly incident: Incident;
|
|
153
|
+
private readonly customFields: Array<PreparedCustomField>;
|
|
154
|
+
private readonly textVariables: Record<string, string>;
|
|
155
|
+
private readonly markdownVariables: Record<
|
|
156
|
+
string,
|
|
157
|
+
{ source: string; html: string; plainText: string }
|
|
158
|
+
>;
|
|
159
|
+
private readonly incidentLabels: string;
|
|
160
|
+
private readonly affectedStatusPages: string;
|
|
161
|
+
|
|
162
|
+
/*
|
|
163
|
+
* Fields whose images are being or have been made public in this send, by
|
|
164
|
+
* name, with the sync that does it. A send awaits several subscribers'
|
|
165
|
+
* messages at once (SubscriberNotificationFanOut), so a second message
|
|
166
|
+
* that carries a field waits for the sync the first one started rather
|
|
167
|
+
* than going out before it has finished.
|
|
168
|
+
*/
|
|
169
|
+
private readonly publishedImageFields: Map<string, Promise<void>> = new Map<
|
|
170
|
+
string,
|
|
171
|
+
Promise<void>
|
|
172
|
+
>();
|
|
173
|
+
|
|
174
|
+
// What went out, for the feed.
|
|
175
|
+
private includedFieldsSent: boolean = false;
|
|
176
|
+
private includedFieldsPublished: Promise<void> | null = null;
|
|
177
|
+
private readonly fieldKeysSent: Set<string> = new Set<string>();
|
|
178
|
+
|
|
179
|
+
public constructor(data: {
|
|
180
|
+
incident: Incident;
|
|
181
|
+
customFields: Array<PreparedCustomField>;
|
|
182
|
+
textVariables: Record<string, string>;
|
|
183
|
+
markdownVariables: Record<
|
|
184
|
+
string,
|
|
185
|
+
{ source: string; html: string; plainText: string }
|
|
186
|
+
>;
|
|
187
|
+
incidentLabels: string;
|
|
188
|
+
affectedStatusPages: string;
|
|
189
|
+
}) {
|
|
190
|
+
this.incident = data.incident;
|
|
191
|
+
this.customFields = data.customFields;
|
|
192
|
+
this.textVariables = data.textVariables;
|
|
193
|
+
this.markdownVariables = data.markdownVariables;
|
|
194
|
+
this.incidentLabels = data.incidentLabels;
|
|
195
|
+
this.affectedStatusPages = data.affectedStatusPages;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Every value one status page's messages are filled with, in each
|
|
200
|
+
* channel's format, except the subscriber's own unsubscribeUrl, which the
|
|
201
|
+
* job adds per subscriber.
|
|
202
|
+
*/
|
|
203
|
+
public forStatusPage(data: {
|
|
204
|
+
statusPage: StatusPage;
|
|
205
|
+
statusPageUrl: string;
|
|
206
|
+
detailsUrl: string;
|
|
207
|
+
resources: Array<StatusPageResource>;
|
|
208
|
+
/*
|
|
209
|
+
* What the resource list reads when the page lists none of the
|
|
210
|
+
* incident's resources. The state change notification has always said
|
|
211
|
+
* "None"; the others leave it empty.
|
|
212
|
+
*/
|
|
213
|
+
noResourcesText?: string | undefined;
|
|
214
|
+
}): IncidentStatusPageTemplateVariables {
|
|
215
|
+
const statusPage: StatusPage = data.statusPage;
|
|
216
|
+
const noResourcesText: string = data.noResourcesText || "";
|
|
217
|
+
|
|
218
|
+
// Empty when the page lists none: the default messages test for that.
|
|
219
|
+
const resourcesAffectedHtml: string =
|
|
220
|
+
StatusPageResourceUtil.getResourcesGroupedByGroupName(data.resources);
|
|
221
|
+
const resourcesAffectedPlainText: string =
|
|
222
|
+
StatusPageResourceUtil.getResourcesGroupedByGroupNameAsPlainText(
|
|
223
|
+
data.resources,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const timezones: Array<Timezone> = statusPage.subscriberTimezones || [];
|
|
227
|
+
|
|
228
|
+
// The values that read the same on every channel.
|
|
229
|
+
const shared: Record<string, string> = {
|
|
230
|
+
statusPageName:
|
|
231
|
+
IncidentTemplateVariableBuilder.getStatusPageName(statusPage),
|
|
232
|
+
statusPageUrl: data.statusPageUrl,
|
|
233
|
+
detailsUrl: data.detailsUrl,
|
|
234
|
+
incidentTitle: this.incident.title || "",
|
|
235
|
+
incidentSeverity: this.incident.incidentSeverity?.name || " - ",
|
|
236
|
+
incidentLabels: this.incidentLabels,
|
|
237
|
+
affectedStatusPages: this.affectedStatusPages,
|
|
238
|
+
...this.textVariables,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const emailBody: SubscriberNotificationEmailBodyTemplateVariables = {
|
|
242
|
+
...shared,
|
|
243
|
+
resourcesAffected: SafeHtml.fromTrustedHtml(
|
|
244
|
+
resourcesAffectedHtml || SafeHtml.escape(noResourcesText),
|
|
245
|
+
),
|
|
246
|
+
};
|
|
247
|
+
const plainText: Record<string, string> = {
|
|
248
|
+
...shared,
|
|
249
|
+
resourcesAffected: resourcesAffectedPlainText || noResourcesText,
|
|
250
|
+
};
|
|
251
|
+
const markdown: Record<string, string> = {
|
|
252
|
+
...shared,
|
|
253
|
+
resourcesAffected: resourcesAffectedPlainText || noResourcesText,
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
for (const [name, value] of Object.entries(this.markdownVariables)) {
|
|
257
|
+
emailBody[name] = SafeHtml.fromTrustedHtml(value.html);
|
|
258
|
+
plainText[name] = value.plainText;
|
|
259
|
+
markdown[name] = value.source;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const customFieldRows: Array<IncidentCustomFieldEmailRow> = [];
|
|
263
|
+
const customFieldsMarkdownLines: Array<string> = [];
|
|
264
|
+
|
|
265
|
+
for (const field of this.customFields) {
|
|
266
|
+
const formatted: FormattedCustomFieldValue = this.formatValue(
|
|
267
|
+
field,
|
|
268
|
+
timezones,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
if (field.variableKey) {
|
|
272
|
+
const variableName: string = getCustomFieldTemplateVariableName(
|
|
273
|
+
field.variableKey,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
emailBody[variableName] = formatted.html || formatted.plainText;
|
|
277
|
+
plainText[variableName] = formatted.plainText;
|
|
278
|
+
markdown[variableName] = formatted.markdown;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!field.isIncludedInSubscriberNotifications || !field.hasValue) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
customFieldRows.push(
|
|
286
|
+
formatted.html
|
|
287
|
+
? { title: field.name, renderedHtml: formatted.html.toHtml() }
|
|
288
|
+
: { title: field.name, plainText: formatted.plainText },
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
customFieldsMarkdownLines.push(
|
|
292
|
+
field.customFieldType === CustomFieldType.Markdown ||
|
|
293
|
+
field.customFieldType === CustomFieldType.LongText
|
|
294
|
+
? `**${field.name}:**\n${formatted.markdown}`
|
|
295
|
+
: `**${field.name}:** ${formatted.markdown}`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
emailBody: emailBody,
|
|
301
|
+
plainText: plainText,
|
|
302
|
+
markdown: markdown,
|
|
303
|
+
resourcesAffectedHtml: resourcesAffectedHtml,
|
|
304
|
+
resourcesAffectedPlainText: resourcesAffectedPlainText,
|
|
305
|
+
customFieldRows: customFieldRows,
|
|
306
|
+
customFieldsMarkdownLines: customFieldsMarkdownLines,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/*
|
|
311
|
+
* One of the event's Markdown values (markdownVariables), in its three
|
|
312
|
+
* forms, for a default template: "" in each when it has none.
|
|
313
|
+
*/
|
|
314
|
+
public getMarkdownVariable(name: string): {
|
|
315
|
+
source: string;
|
|
316
|
+
html: string;
|
|
317
|
+
plainText: string;
|
|
318
|
+
} {
|
|
319
|
+
return (
|
|
320
|
+
this.markdownVariables[name] || { source: "", html: "", plainText: "" }
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* A default message is about to go out on a channel that carries the
|
|
326
|
+
* included fields: the default email, Slack or Teams message, or a
|
|
327
|
+
* webhook. Awaited before the message is sent: the first time, it makes
|
|
328
|
+
* the included Rich text fields' images public, so the recipient can load
|
|
329
|
+
* them. Never throws.
|
|
330
|
+
*/
|
|
331
|
+
public async recordIncludedFieldsSent(): Promise<void> {
|
|
332
|
+
if (!this.includedFieldsPublished) {
|
|
333
|
+
this.includedFieldsSent = true;
|
|
334
|
+
|
|
335
|
+
this.includedFieldsPublished = (async (): Promise<void> => {
|
|
336
|
+
for (const field of this.customFields) {
|
|
337
|
+
if (field.isIncludedInSubscriberNotifications && field.hasValue) {
|
|
338
|
+
await this.publishImages(field);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
})();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
await this.includedFieldsPublished;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* A message is about to go out through these custom templates - the ones
|
|
349
|
+
* actually compiled into it. Awaited before the message is sent: it makes
|
|
350
|
+
* the images of the Rich text fields they place ({{customFields.<key>}})
|
|
351
|
+
* public, each field once per send. Never throws.
|
|
352
|
+
*/
|
|
353
|
+
public async recordFieldsUsedBy(
|
|
354
|
+
templates: Array<string | null | undefined>,
|
|
355
|
+
): Promise<void> {
|
|
356
|
+
const keys: Set<string> =
|
|
357
|
+
IncidentTemplateVariables.getKeysUsedBy(templates);
|
|
358
|
+
|
|
359
|
+
for (const key of keys) {
|
|
360
|
+
this.fieldKeysSent.add(key);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
for (const field of this.customFields) {
|
|
364
|
+
if (field.variableKey && keys.has(field.variableKey) && field.hasValue) {
|
|
365
|
+
await this.publishImages(field);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* The custom field values this send put into a message, as Markdown for
|
|
372
|
+
* the incident feed's "Subscriber Notification Sent" item, or "" when it
|
|
373
|
+
* sent none. Fields with no value are left out.
|
|
374
|
+
*/
|
|
375
|
+
public getSentCustomFieldsMarkdown(): string {
|
|
376
|
+
const sent: Array<PreparedCustomField> = this.customFields.filter(
|
|
377
|
+
(field: PreparedCustomField): boolean => {
|
|
378
|
+
if (!field.hasValue) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return (
|
|
383
|
+
(this.includedFieldsSent &&
|
|
384
|
+
field.isIncludedInSubscriberNotifications) ||
|
|
385
|
+
Boolean(
|
|
386
|
+
field.variableKey && this.fieldKeysSent.has(field.variableKey),
|
|
387
|
+
)
|
|
388
|
+
);
|
|
389
|
+
},
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
if (sent.length === 0) {
|
|
393
|
+
return "";
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const listLines: Array<string> = [];
|
|
397
|
+
const richTextBlocks: Array<string> = [];
|
|
398
|
+
|
|
399
|
+
for (const field of sent) {
|
|
400
|
+
const name: string = escapeMarkdownInline(field.name);
|
|
401
|
+
|
|
402
|
+
// Rich text is Markdown already, and goes in as the block it is.
|
|
403
|
+
if (field.customFieldType === CustomFieldType.Markdown) {
|
|
404
|
+
richTextBlocks.push(`**${name}:**\n\n${field.markdown?.source || ""}`);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const value: FormattedCustomFieldValue = this.formatValue(field, [
|
|
409
|
+
Timezone.UTC,
|
|
410
|
+
]);
|
|
411
|
+
|
|
412
|
+
listLines.push(`- **${name}:** ${escapeMarkdownInline(value.plainText)}`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return ["**Custom fields sent:**", listLines.join("\n"), ...richTextBlocks]
|
|
416
|
+
.filter(Boolean)
|
|
417
|
+
.join("\n\n");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* The included fields for a webhook's data, by Template Variable key:
|
|
422
|
+
* each field's name, type and value as stored (null when it holds none),
|
|
423
|
+
* so an integration reads typed values under keys that survive a rename.
|
|
424
|
+
*/
|
|
425
|
+
public getWebhookCustomFields(): JSONObject {
|
|
426
|
+
const customFields: JSONObject = {};
|
|
427
|
+
|
|
428
|
+
for (const field of this.customFields) {
|
|
429
|
+
if (!field.isIncludedInSubscriberNotifications || !field.variableKey) {
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
customFields[field.variableKey] = {
|
|
434
|
+
name: field.name,
|
|
435
|
+
type: field.customFieldType || null,
|
|
436
|
+
value: field.hasValue ? (field.value as JSONValue) : null,
|
|
437
|
+
} as JSONObject;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return customFields;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private async publishImages(field: PreparedCustomField): Promise<void> {
|
|
444
|
+
if (!field.markdown || !field.markdown.source) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
let published: Promise<void> | undefined = this.publishedImageFields.get(
|
|
449
|
+
field.name,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
if (!published) {
|
|
453
|
+
// Never rejects: syncIsPublicForMarkdownImages logs and swallows.
|
|
454
|
+
published = Promise.resolve(
|
|
455
|
+
syncIsPublicForMarkdownImages(
|
|
456
|
+
field.markdown.source,
|
|
457
|
+
true,
|
|
458
|
+
`incident ${this.incident.id?.toString() || ""} custom field "${field.name}"`,
|
|
459
|
+
),
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
this.publishedImageFields.set(field.name, published);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
await published;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
private formatValue(
|
|
469
|
+
field: PreparedCustomField,
|
|
470
|
+
timezones: Array<Timezone>,
|
|
471
|
+
): FormattedCustomFieldValue {
|
|
472
|
+
if (!field.hasValue) {
|
|
473
|
+
return { plainText: "", markdown: "", html: null };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const value: unknown = field.value;
|
|
477
|
+
|
|
478
|
+
switch (field.customFieldType) {
|
|
479
|
+
case CustomFieldType.Markdown: {
|
|
480
|
+
const rendered: { source: string; html: string; plainText: string } =
|
|
481
|
+
field.markdown || { source: "", html: "", plainText: "" };
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
plainText: rendered.plainText,
|
|
485
|
+
markdown: rendered.source,
|
|
486
|
+
html: SafeHtml.fromTrustedHtml(rendered.html),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Several lines of plain text: escaped, with its line breaks kept.
|
|
491
|
+
case CustomFieldType.LongText: {
|
|
492
|
+
const text: string = customFieldValueToText(value);
|
|
493
|
+
|
|
494
|
+
return {
|
|
495
|
+
plainText: text,
|
|
496
|
+
markdown: text,
|
|
497
|
+
html: SafeHtml.fromTrustedHtml(
|
|
498
|
+
SafeHtml.escape(text).replace(LINE_BREAK_PATTERN, "<br/>"),
|
|
499
|
+
),
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
case CustomFieldType.Boolean: {
|
|
504
|
+
const text: string = formatCustomFieldBoolean(value);
|
|
505
|
+
return { plainText: text, markdown: text, html: null };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/*
|
|
509
|
+
* The day that was picked. It is stored as the author's local
|
|
510
|
+
* midnight, not as the day (see formatCustomFieldCalendarDate), so it
|
|
511
|
+
* is not read in the page's time zones: those are the subscribers',
|
|
512
|
+
* not the author's.
|
|
513
|
+
*/
|
|
514
|
+
case CustomFieldType.Date: {
|
|
515
|
+
const text: string = formatCustomFieldCalendarDate(value);
|
|
516
|
+
return { plainText: text, markdown: text, html: null };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/*
|
|
520
|
+
* A date and time in the status page's subscriber time zones, as its
|
|
521
|
+
* scheduled maintenance emails show one: a line each in HTML, and
|
|
522
|
+
* separated by commas in text.
|
|
523
|
+
*/
|
|
524
|
+
case CustomFieldType.DateTime: {
|
|
525
|
+
const date: Date | null = customFieldValueToDate(value);
|
|
526
|
+
|
|
527
|
+
if (!date) {
|
|
528
|
+
const text: string = customFieldValueToText(value);
|
|
529
|
+
return { plainText: text, markdown: text, html: null };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const lines: Array<string> =
|
|
533
|
+
OneUptimeDate.getDateAsFormattedArrayInMultipleTimezones({
|
|
534
|
+
date: date,
|
|
535
|
+
timezones: timezones,
|
|
536
|
+
use12HourFormat: true,
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
plainText: lines.join(", "),
|
|
541
|
+
markdown: lines.join(", "),
|
|
542
|
+
html: SafeHtml.fromTrustedHtml(
|
|
543
|
+
lines
|
|
544
|
+
.map((line: string): string => {
|
|
545
|
+
return SafeHtml.escape(line);
|
|
546
|
+
})
|
|
547
|
+
.join("<br/>"),
|
|
548
|
+
),
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
default: {
|
|
553
|
+
const text: string = customFieldValueToText(value);
|
|
554
|
+
return { plainText: text, markdown: text, html: null };
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/*
|
|
560
|
+
* The custom field keys these templates place, {{customFields.<key>}},
|
|
561
|
+
* found the way the compiler finds the placeholders it fills.
|
|
562
|
+
*/
|
|
563
|
+
private static getKeysUsedBy(
|
|
564
|
+
templates: Array<string | null | undefined>,
|
|
565
|
+
): Set<string> {
|
|
566
|
+
const keys: Set<string> = new Set<string>();
|
|
567
|
+
const prefix: string = getCustomFieldTemplateVariableName("");
|
|
568
|
+
|
|
569
|
+
for (const name of SubscriberNotificationTemplateCompiler.getPlaceholderNames(
|
|
570
|
+
templates,
|
|
571
|
+
)) {
|
|
572
|
+
if (name.startsWith(prefix)) {
|
|
573
|
+
keys.add(name.slice(prefix.length));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return keys;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export default class IncidentTemplateVariableBuilder {
|
|
582
|
+
/**
|
|
583
|
+
* Reads what an incident's subscriber messages need, once per send.
|
|
584
|
+
*
|
|
585
|
+
* The incident needs projectId, title, incidentSeverity.name, labels.name
|
|
586
|
+
* and customFields selected. statusPages are the pages the send reaches
|
|
587
|
+
* (IncidentStatusPageScope), and name {{affectedStatusPages}}.
|
|
588
|
+
* markdownVariables are the event's Markdown values - the description, a
|
|
589
|
+
* note, the postmortem - rendered here once for every page;
|
|
590
|
+
* textVariables are its plain ones, such as incidentState and postedAt.
|
|
591
|
+
*
|
|
592
|
+
* customFieldDefinitions skips reading the project's incident custom
|
|
593
|
+
* fields, for a caller that has them already.
|
|
594
|
+
*/
|
|
595
|
+
public static async build(data: {
|
|
596
|
+
incident: Incident;
|
|
597
|
+
statusPages: Array<StatusPage>;
|
|
598
|
+
markdownVariables?: Record<string, string | null | undefined> | undefined;
|
|
599
|
+
textVariables?: Record<string, string> | undefined;
|
|
600
|
+
customFieldDefinitions?:
|
|
601
|
+
| Array<IncidentTemplateCustomFieldDefinition>
|
|
602
|
+
| undefined;
|
|
603
|
+
}): Promise<IncidentTemplateVariables> {
|
|
604
|
+
const incident: Incident = data.incident;
|
|
605
|
+
|
|
606
|
+
const definitions: Array<IncidentTemplateCustomFieldDefinition> =
|
|
607
|
+
data.customFieldDefinitions ||
|
|
608
|
+
(await this.getCustomFieldDefinitions(incident.projectId));
|
|
609
|
+
|
|
610
|
+
const customFields: Array<PreparedCustomField> =
|
|
611
|
+
await this.prepareCustomFields({
|
|
612
|
+
definitions: definitions,
|
|
613
|
+
values: incident.customFields,
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
const markdownVariables: Record<
|
|
617
|
+
string,
|
|
618
|
+
{ source: string; html: string; plainText: string }
|
|
619
|
+
> = {};
|
|
620
|
+
|
|
621
|
+
for (const [name, source] of Object.entries(data.markdownVariables || {})) {
|
|
622
|
+
markdownVariables[name] = await this.renderMarkdown(source || "");
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/*
|
|
626
|
+
* No image is made public here: nothing has gone out yet, and a send may
|
|
627
|
+
* reach no one (see recordIncludedFieldsSent).
|
|
628
|
+
*/
|
|
629
|
+
return new IncidentTemplateVariables({
|
|
630
|
+
incident: incident,
|
|
631
|
+
customFields: customFields,
|
|
632
|
+
textVariables: { ...(data.textVariables || {}) },
|
|
633
|
+
markdownVariables: markdownVariables,
|
|
634
|
+
incidentLabels: this.getLabelNames(incident.labels).join(", "),
|
|
635
|
+
affectedStatusPages: this.getAffectedStatusPageNames(
|
|
636
|
+
data.statusPages,
|
|
637
|
+
).join(", "),
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/*
|
|
642
|
+
* The name a status page goes by in its subscribers' messages: its page
|
|
643
|
+
* title, or its name.
|
|
644
|
+
*/
|
|
645
|
+
public static getStatusPageName(statusPage: StatusPage): string {
|
|
646
|
+
return statusPage.pageTitle || statusPage.name || "Status Page";
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/*
|
|
650
|
+
* The pages that show the incident, by the name their subscribers know
|
|
651
|
+
* them by, in the order given (name order, from IncidentStatusPageScope).
|
|
652
|
+
* A page that does not show incidents is left out: the incident is not on
|
|
653
|
+
* it.
|
|
654
|
+
*/
|
|
655
|
+
public static getAffectedStatusPageNames(
|
|
656
|
+
statusPages: Array<StatusPage>,
|
|
657
|
+
): Array<string> {
|
|
658
|
+
const names: Array<string> = [];
|
|
659
|
+
|
|
660
|
+
for (const statusPage of statusPages) {
|
|
661
|
+
if (statusPage.showIncidentsOnStatusPage === false) {
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const name: string = this.getStatusPageName(statusPage).trim();
|
|
666
|
+
|
|
667
|
+
if (name && !names.includes(name)) {
|
|
668
|
+
names.push(name);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return names;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Label names, alphabetically, so the order does not depend on a join.
|
|
676
|
+
public static getLabelNames(
|
|
677
|
+
labels: Array<Label> | null | undefined,
|
|
678
|
+
): Array<string> {
|
|
679
|
+
return (labels || [])
|
|
680
|
+
.map((label: Label): string => {
|
|
681
|
+
return (label.name || "").trim();
|
|
682
|
+
})
|
|
683
|
+
.filter((name: string): boolean => {
|
|
684
|
+
return name.length > 0;
|
|
685
|
+
})
|
|
686
|
+
.sort((a: string, b: string): number => {
|
|
687
|
+
return a.localeCompare(b, undefined, { sensitivity: "base" });
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// The project's incident custom fields, in the order they are shown.
|
|
692
|
+
public static async getCustomFieldDefinitions(
|
|
693
|
+
projectId: ObjectID | undefined,
|
|
694
|
+
): Promise<Array<IncidentTemplateCustomFieldDefinition>> {
|
|
695
|
+
if (!projectId) {
|
|
696
|
+
return [];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const fields: Array<IncidentCustomField> =
|
|
700
|
+
await IncidentCustomFieldService.findBy({
|
|
701
|
+
query: {
|
|
702
|
+
projectId: projectId,
|
|
703
|
+
},
|
|
704
|
+
select: {
|
|
705
|
+
name: true,
|
|
706
|
+
variableKey: true,
|
|
707
|
+
customFieldType: true,
|
|
708
|
+
includeInSubscriberNotifications: true,
|
|
709
|
+
sortOrder: true,
|
|
710
|
+
},
|
|
711
|
+
// Creation order breaks ties in the order below.
|
|
712
|
+
sort: {
|
|
713
|
+
createdAt: SortOrder.Ascending,
|
|
714
|
+
},
|
|
715
|
+
limit: LIMIT_PER_PROJECT,
|
|
716
|
+
skip: 0,
|
|
717
|
+
props: {
|
|
718
|
+
isRoot: true,
|
|
719
|
+
},
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
return fields;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
private static async prepareCustomFields(data: {
|
|
726
|
+
definitions: Array<IncidentTemplateCustomFieldDefinition>;
|
|
727
|
+
values: JSONObject | null | undefined;
|
|
728
|
+
}): Promise<Array<PreparedCustomField>> {
|
|
729
|
+
// Values are stored under each field's name.
|
|
730
|
+
const values: JSONObject =
|
|
731
|
+
data.values &&
|
|
732
|
+
typeof data.values === "object" &&
|
|
733
|
+
!Array.isArray(data.values)
|
|
734
|
+
? data.values
|
|
735
|
+
: {};
|
|
736
|
+
|
|
737
|
+
const prepared: Array<PreparedCustomField> = [];
|
|
738
|
+
const seenNames: Set<string> = new Set<string>();
|
|
739
|
+
|
|
740
|
+
for (const definition of sortCustomFieldDefinitions(data.definitions)) {
|
|
741
|
+
const name: string =
|
|
742
|
+
typeof definition.name === "string" ? definition.name : "";
|
|
743
|
+
|
|
744
|
+
if (!name || seenNames.has(name)) {
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
seenNames.add(name);
|
|
749
|
+
|
|
750
|
+
const variableKey: string | null = isValidCustomFieldVariableKey(
|
|
751
|
+
definition.variableKey,
|
|
752
|
+
)
|
|
753
|
+
? (definition.variableKey as string)
|
|
754
|
+
: null;
|
|
755
|
+
|
|
756
|
+
const value: unknown = Object.prototype.hasOwnProperty.call(values, name)
|
|
757
|
+
? values[name]
|
|
758
|
+
: undefined;
|
|
759
|
+
|
|
760
|
+
/*
|
|
761
|
+
* 0 and false are values (a Boolean left at No, a count of zero); an
|
|
762
|
+
* answer of only spaces is not, and would show a subscriber a row
|
|
763
|
+
* with nothing in it.
|
|
764
|
+
*/
|
|
765
|
+
const hasValue: boolean =
|
|
766
|
+
!isCustomFieldValueEmpty(value) &&
|
|
767
|
+
!(typeof value === "string" && value.trim().length === 0);
|
|
768
|
+
|
|
769
|
+
const field: PreparedCustomField = {
|
|
770
|
+
name: name,
|
|
771
|
+
variableKey: variableKey,
|
|
772
|
+
customFieldType: definition.customFieldType || undefined,
|
|
773
|
+
isIncludedInSubscriberNotifications:
|
|
774
|
+
definition.includeInSubscriberNotifications === true,
|
|
775
|
+
value: value,
|
|
776
|
+
hasValue: hasValue,
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
if (hasValue && field.customFieldType === CustomFieldType.Markdown) {
|
|
780
|
+
field.markdown = await this.renderMarkdown(
|
|
781
|
+
customFieldValueToText(value),
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
prepared.push(field);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
return prepared;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/*
|
|
792
|
+
* Markdown in the three forms the channels take: HTML through the email
|
|
793
|
+
* renderer (which escapes raw HTML and keeps only safe links), plain text,
|
|
794
|
+
* and the source as written.
|
|
795
|
+
*/
|
|
796
|
+
private static async renderMarkdown(
|
|
797
|
+
source: string,
|
|
798
|
+
): Promise<{ source: string; html: string; plainText: string }> {
|
|
799
|
+
return {
|
|
800
|
+
source: source,
|
|
801
|
+
html: await Markdown.convertToHTML(source, MarkdownContentType.Email),
|
|
802
|
+
plainText: Markdown.convertToPlainText(source),
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
}
|