@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,343 @@
|
|
|
1
|
+
import { CustomFieldDefinition } from "../../../Types/CustomField/CustomFieldDefinition";
|
|
2
|
+
import CustomFieldType from "../../../Types/CustomField/CustomFieldType";
|
|
3
|
+
import {
|
|
4
|
+
CustomFieldValueValidationError,
|
|
5
|
+
formatCustomFieldValueValidationErrors,
|
|
6
|
+
validateCustomFieldValues,
|
|
7
|
+
} from "../../../Types/CustomField/CustomFieldValueValidator";
|
|
8
|
+
import { describe, expect, test } from "@jest/globals";
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* The server now checks custom field values against their definitions, in
|
|
12
|
+
* front of writes that have worked for years. What must hold:
|
|
13
|
+
*
|
|
14
|
+
* - a value this write CHANGES must fit its field;
|
|
15
|
+
* - a value it does not change passes, however it looks - the Custom
|
|
16
|
+
* Fields card sends the whole bag back, so a legacy value (an option an
|
|
17
|
+
* admin has since removed, text stored before the field became a
|
|
18
|
+
* Number) must not block saving the other fields;
|
|
19
|
+
* - unknown keys, empty values and untyped fields pass.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const DEFINITIONS: Array<CustomFieldDefinition> = [
|
|
23
|
+
{ name: "Summary", customFieldType: CustomFieldType.Text },
|
|
24
|
+
{ name: "Details", customFieldType: CustomFieldType.LongText },
|
|
25
|
+
{ name: "Notes", customFieldType: CustomFieldType.Markdown },
|
|
26
|
+
{ name: "Estimated Duration", customFieldType: CustomFieldType.Number },
|
|
27
|
+
{ name: "Acknowledgement", customFieldType: CustomFieldType.Boolean },
|
|
28
|
+
{ name: "Started On", customFieldType: CustomFieldType.Date },
|
|
29
|
+
{ name: "Expected Resolution", customFieldType: CustomFieldType.DateTime },
|
|
30
|
+
{
|
|
31
|
+
name: "Impact",
|
|
32
|
+
customFieldType: CustomFieldType.Dropdown,
|
|
33
|
+
dropdownOptions: "Low\nMedium\nHigh",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "Regions",
|
|
37
|
+
customFieldType: CustomFieldType.MultiSelectDropdown,
|
|
38
|
+
dropdownOptions: JSON.stringify([
|
|
39
|
+
{ value: "East", color: "#ff0000" },
|
|
40
|
+
{ value: "West", color: "#00ff00" },
|
|
41
|
+
{ value: "North" },
|
|
42
|
+
]),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "Unfinished Dropdown",
|
|
46
|
+
customFieldType: CustomFieldType.Dropdown,
|
|
47
|
+
},
|
|
48
|
+
{ name: "Untyped" },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
type ValidateFunction = (
|
|
52
|
+
customFields: unknown,
|
|
53
|
+
storedCustomFields?: unknown,
|
|
54
|
+
) => Array<CustomFieldValueValidationError>;
|
|
55
|
+
|
|
56
|
+
const validate: ValidateFunction = (
|
|
57
|
+
customFields: unknown,
|
|
58
|
+
storedCustomFields?: unknown,
|
|
59
|
+
): Array<CustomFieldValueValidationError> => {
|
|
60
|
+
return validateCustomFieldValues({
|
|
61
|
+
definitions: DEFINITIONS,
|
|
62
|
+
customFields,
|
|
63
|
+
storedCustomFields,
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type FieldNamesFunction = (
|
|
68
|
+
errors: Array<CustomFieldValueValidationError>,
|
|
69
|
+
) => Array<string>;
|
|
70
|
+
|
|
71
|
+
const fieldNames: FieldNamesFunction = (
|
|
72
|
+
errors: Array<CustomFieldValueValidationError>,
|
|
73
|
+
): Array<string> => {
|
|
74
|
+
return errors.map((error: CustomFieldValueValidationError) => {
|
|
75
|
+
return error.fieldName;
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
describe("validateCustomFieldValues - values that fit", () => {
|
|
80
|
+
test.each([
|
|
81
|
+
["Summary", "Payments are slow"],
|
|
82
|
+
["Summary", 42],
|
|
83
|
+
["Summary", true],
|
|
84
|
+
["Details", "Line one\nLine two"],
|
|
85
|
+
["Notes", "**Bold** and a [link](https://example.com)"],
|
|
86
|
+
["Estimated Duration", 90],
|
|
87
|
+
["Estimated Duration", 0],
|
|
88
|
+
["Estimated Duration", -1.5],
|
|
89
|
+
["Estimated Duration", "0"],
|
|
90
|
+
["Estimated Duration", " 42 "],
|
|
91
|
+
["Estimated Duration", "3.5"],
|
|
92
|
+
["Acknowledgement", true],
|
|
93
|
+
["Acknowledgement", false],
|
|
94
|
+
["Acknowledgement", "true"],
|
|
95
|
+
["Acknowledgement", "false"],
|
|
96
|
+
["Started On", "2026-09-27"],
|
|
97
|
+
["Started On", "2026-09-27T00:00:00.000Z"],
|
|
98
|
+
["Expected Resolution", "2026-09-27T14:30:00.000Z"],
|
|
99
|
+
["Expected Resolution", new Date("2026-09-27T14:30:00.000Z")],
|
|
100
|
+
["Impact", "High"],
|
|
101
|
+
["Regions", ["East", "West"]],
|
|
102
|
+
["Regions", "North"],
|
|
103
|
+
["Unfinished Dropdown", "Anything"],
|
|
104
|
+
["Untyped", { any: "thing" }],
|
|
105
|
+
])("%s accepts %j", (name: string, value: unknown) => {
|
|
106
|
+
expect(validate({ [name]: value })).toEqual([]);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("validateCustomFieldValues - values that do not fit", () => {
|
|
111
|
+
test.each([
|
|
112
|
+
["Summary", { nested: true }],
|
|
113
|
+
["Summary", ["a", "b"]],
|
|
114
|
+
["Details", { nested: true }],
|
|
115
|
+
["Notes", ["**a**"]],
|
|
116
|
+
["Estimated Duration", "banana"],
|
|
117
|
+
["Estimated Duration", " "],
|
|
118
|
+
["Estimated Duration", true],
|
|
119
|
+
["Estimated Duration", ["1"]],
|
|
120
|
+
["Acknowledgement", "yes"],
|
|
121
|
+
["Acknowledgement", 1],
|
|
122
|
+
["Started On", "not a date"],
|
|
123
|
+
["Started On", 20260927],
|
|
124
|
+
["Expected Resolution", "tomorrow-ish"],
|
|
125
|
+
["Expected Resolution", new Date("invalid")],
|
|
126
|
+
["Impact", "Critical"],
|
|
127
|
+
["Impact", ["High"]],
|
|
128
|
+
["Impact", { value: "High" }],
|
|
129
|
+
["Regions", ["East", "South"]],
|
|
130
|
+
["Regions", "South"],
|
|
131
|
+
["Regions", [{ value: "East" }]],
|
|
132
|
+
])("%s refuses %j", (name: string, value: unknown) => {
|
|
133
|
+
const errors: Array<CustomFieldValueValidationError> = validate({
|
|
134
|
+
[name]: value,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(fieldNames(errors)).toEqual([name]);
|
|
138
|
+
expect(errors[0]!.message).toContain(`"${name}"`);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("names the options a dropdown value has to be one of", () => {
|
|
142
|
+
const errors: Array<CustomFieldValueValidationError> = validate({
|
|
143
|
+
Impact: "Critical",
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(errors[0]!.message).toBe(
|
|
147
|
+
'"Critical" is not one of the options for "Impact". Choose one of: "Low", "Medium", "High".',
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("names every new multi-select entry that is not an option", () => {
|
|
152
|
+
const errors: Array<CustomFieldValueValidationError> = validate({
|
|
153
|
+
Regions: ["East", "South", "Central"],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(errors[0]!.message).toBe(
|
|
157
|
+
'"South", "Central" are not among the options for "Regions". Choose from: "East", "West", "North".',
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("reports every field that does not fit, in the order sent", () => {
|
|
162
|
+
const errors: Array<CustomFieldValueValidationError> = validate({
|
|
163
|
+
Impact: "Critical",
|
|
164
|
+
Summary: "fine",
|
|
165
|
+
"Estimated Duration": "soon",
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(fieldNames(errors)).toEqual(["Impact", "Estimated Duration"]);
|
|
169
|
+
expect(formatCustomFieldValueValidationErrors(errors)).toBe(
|
|
170
|
+
`${errors[0]!.message} ${errors[1]!.message}`,
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("keeps a long value and a long option list to one line", () => {
|
|
175
|
+
const options: Array<string> = Array.from(
|
|
176
|
+
{ length: 25 },
|
|
177
|
+
(_value: unknown, index: number) => {
|
|
178
|
+
return `Option ${index + 1}`;
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const errors: Array<CustomFieldValueValidationError> =
|
|
183
|
+
validateCustomFieldValues({
|
|
184
|
+
definitions: [
|
|
185
|
+
{
|
|
186
|
+
name: "Big",
|
|
187
|
+
customFieldType: CustomFieldType.Dropdown,
|
|
188
|
+
dropdownOptions: options.join("\n"),
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
customFields: { Big: "x".repeat(500) },
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
expect(errors[0]!.message).toContain("...");
|
|
195
|
+
expect(errors[0]!.message).toContain("and 15 more");
|
|
196
|
+
expect(errors[0]!.message.length).toBeLessThan(400);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("validateCustomFieldValues - only what this write changes", () => {
|
|
201
|
+
test("an unchanged legacy dropdown value passes", () => {
|
|
202
|
+
// "Severe" was an option once; the admin has since removed it.
|
|
203
|
+
expect(
|
|
204
|
+
validate(
|
|
205
|
+
{ Impact: "Severe", Summary: "edited" },
|
|
206
|
+
{ Impact: "Severe", Summary: "before" },
|
|
207
|
+
),
|
|
208
|
+
).toEqual([]);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("an unchanged value of the wrong type passes", () => {
|
|
212
|
+
// Stored as free text before the field became a Number.
|
|
213
|
+
expect(
|
|
214
|
+
validate(
|
|
215
|
+
{ "Estimated Duration": "about an hour", Impact: "High" },
|
|
216
|
+
{ "Estimated Duration": "about an hour" },
|
|
217
|
+
),
|
|
218
|
+
).toEqual([]);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("changing a legacy value to another invalid one fails", () => {
|
|
222
|
+
expect(
|
|
223
|
+
fieldNames(validate({ Impact: "Critical" }, { Impact: "Severe" })),
|
|
224
|
+
).toEqual(["Impact"]);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("changing a legacy value to a valid one passes", () => {
|
|
228
|
+
expect(validate({ Impact: "High" }, { Impact: "Severe" })).toEqual([]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("a multi-select keeps its removed options, and only new ones are checked", () => {
|
|
232
|
+
expect(
|
|
233
|
+
validate({ Regions: ["Old", "East"] }, { Regions: ["Old"] }),
|
|
234
|
+
).toEqual([]);
|
|
235
|
+
|
|
236
|
+
expect(
|
|
237
|
+
fieldNames(validate({ Regions: ["Old", "South"] }, { Regions: ["Old"] })),
|
|
238
|
+
).toEqual(["Regions"]);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("a multi-select in a different order is unchanged", () => {
|
|
242
|
+
expect(
|
|
243
|
+
validate({ Regions: ["Gone", "Old"] }, { Regions: ["Old", "Gone"] }),
|
|
244
|
+
).toEqual([]);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("a multi-select that was a single value keeps it", () => {
|
|
248
|
+
expect(validate({ Regions: ["Old", "West"] }, { Regions: "Old" })).toEqual(
|
|
249
|
+
[],
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("a date sent as a Date matches the same stored string", () => {
|
|
254
|
+
expect(
|
|
255
|
+
validate(
|
|
256
|
+
{ "Expected Resolution": new Date("2026-09-27T14:30:00.000Z") },
|
|
257
|
+
{ "Expected Resolution": "2026-09-27T14:30:00.000Z" },
|
|
258
|
+
),
|
|
259
|
+
).toEqual([]);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("every value counts as changed on create", () => {
|
|
263
|
+
expect(fieldNames(validate({ Impact: "Severe" }, {}))).toEqual(["Impact"]);
|
|
264
|
+
expect(fieldNames(validate({ Impact: "Severe" }))).toEqual(["Impact"]);
|
|
265
|
+
expect(fieldNames(validate({ Impact: "Severe" }, null))).toEqual([
|
|
266
|
+
"Impact",
|
|
267
|
+
]);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe("validateCustomFieldValues - what always passes", () => {
|
|
272
|
+
test("keys with no definition pass", () => {
|
|
273
|
+
expect(
|
|
274
|
+
validate({ "Deleted Field": { anything: [1, 2] }, Renamed: "banana" }),
|
|
275
|
+
).toEqual([]);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("keys match definitions by exact name", () => {
|
|
279
|
+
// A differently cased key is some other (unknown) key, not "Impact".
|
|
280
|
+
expect(validate({ impact: "Critical" })).toEqual([]);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test.each([[null], [undefined], [""], [[]]])(
|
|
284
|
+
"clearing a field with %j passes",
|
|
285
|
+
(value: unknown) => {
|
|
286
|
+
expect(
|
|
287
|
+
validate(
|
|
288
|
+
{
|
|
289
|
+
Impact: value,
|
|
290
|
+
Regions: value,
|
|
291
|
+
"Estimated Duration": value,
|
|
292
|
+
Acknowledgement: value,
|
|
293
|
+
},
|
|
294
|
+
{ Impact: "High", Regions: ["East"] },
|
|
295
|
+
),
|
|
296
|
+
).toEqual([]);
|
|
297
|
+
},
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
test("false and 0 are values, and are checked as such", () => {
|
|
301
|
+
expect(
|
|
302
|
+
validate({ Acknowledgement: false, "Estimated Duration": 0 }),
|
|
303
|
+
).toEqual([]);
|
|
304
|
+
expect(fieldNames(validate({ Impact: 0 }))).toEqual(["Impact"]);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test.each([[null], [undefined], ["a string"], [["an", "array"]], [42]])(
|
|
308
|
+
"a bag that is not an object (%j) is left to the rest of the write",
|
|
309
|
+
(customFields: unknown) => {
|
|
310
|
+
expect(validate(customFields)).toEqual([]);
|
|
311
|
+
},
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
test("a stored bag that is not an object counts as empty", () => {
|
|
315
|
+
expect(fieldNames(validate({ Impact: "Nope" }, "garbage"))).toEqual([
|
|
316
|
+
"Impact",
|
|
317
|
+
]);
|
|
318
|
+
expect(validate({ Impact: "High" }, ["garbage"])).toEqual([]);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("no definitions means nothing to check", () => {
|
|
322
|
+
expect(
|
|
323
|
+
validateCustomFieldValues({
|
|
324
|
+
definitions: [],
|
|
325
|
+
customFields: { Impact: "anything" },
|
|
326
|
+
}),
|
|
327
|
+
).toEqual([]);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("a field type this version does not know passes", () => {
|
|
331
|
+
expect(
|
|
332
|
+
validateCustomFieldValues({
|
|
333
|
+
definitions: [
|
|
334
|
+
{
|
|
335
|
+
name: "Future",
|
|
336
|
+
customFieldType: "Geolocation" as CustomFieldType,
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
customFields: { Future: { lat: 1, lng: 2 } },
|
|
340
|
+
}),
|
|
341
|
+
).toEqual([]);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CUSTOM_FIELD_TEMPLATE_VARIABLE_PREFIX,
|
|
3
|
+
CUSTOM_FIELD_VARIABLE_KEY_FALLBACK,
|
|
4
|
+
CUSTOM_FIELD_VARIABLE_KEY_MAX_LENGTH,
|
|
5
|
+
generateCustomFieldVariableKey,
|
|
6
|
+
getCustomFieldTemplateVariableName,
|
|
7
|
+
getCustomFieldVariableKeyBase,
|
|
8
|
+
isValidCustomFieldVariableKey,
|
|
9
|
+
} from "../../../Types/CustomField/CustomFieldVariableKey";
|
|
10
|
+
import Slug from "../../../Utils/Slug";
|
|
11
|
+
import SubscriberNotificationTemplateCompiler from "../../../Types/StatusPage/SubscriberNotificationTemplateCompiler";
|
|
12
|
+
import { describe, expect, test } from "@jest/globals";
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* An incident custom field's template key is how a custom subscriber email
|
|
16
|
+
* reaches its value: {{customFields.<key>}}. The template compiler fills only
|
|
17
|
+
* placeholders of ASCII word characters and dots, so what matters most here
|
|
18
|
+
* is that every key - whatever the name, whatever else the project holds -
|
|
19
|
+
* is one the compiler will actually fill.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
type FillFunction = (key: string, value: string) => string;
|
|
23
|
+
|
|
24
|
+
// What a template using the key comes out as.
|
|
25
|
+
const fill: FillFunction = (key: string, value: string): string => {
|
|
26
|
+
return SubscriberNotificationTemplateCompiler.compileTemplate(
|
|
27
|
+
`Impact: {{${getCustomFieldTemplateVariableName(key)}}}`,
|
|
28
|
+
{
|
|
29
|
+
[getCustomFieldTemplateVariableName(key)]: value,
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
describe("getCustomFieldVariableKeyBase", () => {
|
|
35
|
+
test.each([
|
|
36
|
+
["Impact", "impact"],
|
|
37
|
+
["Expected Resolution", "expected_resolution"],
|
|
38
|
+
["Estimated Duration (minutes)", "estimated_duration_minutes"],
|
|
39
|
+
["Affected Users/Systems", "affected_users_systems"],
|
|
40
|
+
[" Leading and trailing ", "leading_and_trailing"],
|
|
41
|
+
["multiple---separators___here", "multiple_separators_here"],
|
|
42
|
+
["ALL CAPS", "all_caps"],
|
|
43
|
+
["2FA Status", "2fa_status"],
|
|
44
|
+
["already_snake_case", "already_snake_case"],
|
|
45
|
+
["tabs\tand\nnewlines", "tabs_and_newlines"],
|
|
46
|
+
["customFields.dotted", "customfields_dotted"],
|
|
47
|
+
])("%j becomes %j", (name: string, key: string) => {
|
|
48
|
+
expect(getCustomFieldVariableKeyBase(name)).toBe(key);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test.each([
|
|
52
|
+
["Café", "cafe"],
|
|
53
|
+
["Résumé des impacts", "resume_des_impacts"],
|
|
54
|
+
["Größe", "grosse"],
|
|
55
|
+
["Straße", "strasse"],
|
|
56
|
+
["Ærø Ø", "aero_o"],
|
|
57
|
+
["Łódź", "lodz"],
|
|
58
|
+
["Þórður", "thordur"],
|
|
59
|
+
["Ñandú", "nandu"],
|
|
60
|
+
["Full width", "full_width"],
|
|
61
|
+
["field", "field"],
|
|
62
|
+
["İstanbul", "istanbul"],
|
|
63
|
+
["São Tomé", "sao_tome"],
|
|
64
|
+
])(
|
|
65
|
+
"transliterates the Latin letters of %j into %j",
|
|
66
|
+
(name: string, key: string) => {
|
|
67
|
+
expect(getCustomFieldVariableKeyBase(name)).toBe(key);
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
test.each([
|
|
72
|
+
["影响"],
|
|
73
|
+
["Влияние"],
|
|
74
|
+
["التأثير"],
|
|
75
|
+
["🔥🔥"],
|
|
76
|
+
["!!!"],
|
|
77
|
+
[""],
|
|
78
|
+
[" "],
|
|
79
|
+
["___"],
|
|
80
|
+
])(
|
|
81
|
+
"falls back to %j's fallback key when nothing Latin is left",
|
|
82
|
+
(name: string) => {
|
|
83
|
+
expect(getCustomFieldVariableKeyBase(name)).toBe(
|
|
84
|
+
CUSTOM_FIELD_VARIABLE_KEY_FALLBACK,
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
test("keeps the Latin part of a mixed name", () => {
|
|
90
|
+
expect(getCustomFieldVariableKeyBase("Impact 影响")).toBe("impact");
|
|
91
|
+
expect(getCustomFieldVariableKeyBase("SLA — 🔥 Tier")).toBe("sla_tier");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("tolerates a missing name", () => {
|
|
95
|
+
expect(getCustomFieldVariableKeyBase(undefined as unknown as string)).toBe(
|
|
96
|
+
CUSTOM_FIELD_VARIABLE_KEY_FALLBACK,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("cuts a long name to the maximum without a trailing underscore", () => {
|
|
101
|
+
const name: string = `${"word ".repeat(40)}end`;
|
|
102
|
+
const key: string = getCustomFieldVariableKeyBase(name);
|
|
103
|
+
|
|
104
|
+
expect(key.length).toBeLessThanOrEqual(
|
|
105
|
+
CUSTOM_FIELD_VARIABLE_KEY_MAX_LENGTH,
|
|
106
|
+
);
|
|
107
|
+
expect(key.endsWith("_")).toBe(false);
|
|
108
|
+
expect(isValidCustomFieldVariableKey(key)).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("generateCustomFieldVariableKey", () => {
|
|
113
|
+
test("uses the name's key when nothing else has it", () => {
|
|
114
|
+
expect(
|
|
115
|
+
generateCustomFieldVariableKey({
|
|
116
|
+
name: "Impact",
|
|
117
|
+
existingKeys: ["expected_resolution"],
|
|
118
|
+
}),
|
|
119
|
+
).toBe("impact");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("numbers a collision from _2", () => {
|
|
123
|
+
expect(
|
|
124
|
+
generateCustomFieldVariableKey({
|
|
125
|
+
name: "Impact",
|
|
126
|
+
existingKeys: ["impact"],
|
|
127
|
+
}),
|
|
128
|
+
).toBe("impact_2");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("takes the first free number", () => {
|
|
132
|
+
expect(
|
|
133
|
+
generateCustomFieldVariableKey({
|
|
134
|
+
name: "impact!",
|
|
135
|
+
existingKeys: ["impact", "impact_2", "impact_3", "impact_5"],
|
|
136
|
+
}),
|
|
137
|
+
).toBe("impact_4");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("names differing only in punctuation or case get different keys", () => {
|
|
141
|
+
const taken: Array<string> = [];
|
|
142
|
+
|
|
143
|
+
for (const name of [
|
|
144
|
+
"Expected Resolution",
|
|
145
|
+
"expected-resolution",
|
|
146
|
+
"EXPECTED RESOLUTION",
|
|
147
|
+
"Expected Resolution!",
|
|
148
|
+
]) {
|
|
149
|
+
taken.push(generateCustomFieldVariableKey({ name, existingKeys: taken }));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
expect(taken).toEqual([
|
|
153
|
+
"expected_resolution",
|
|
154
|
+
"expected_resolution_2",
|
|
155
|
+
"expected_resolution_3",
|
|
156
|
+
"expected_resolution_4",
|
|
157
|
+
]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("a field literally named like a numbered key is not given it twice", () => {
|
|
161
|
+
const first: string = generateCustomFieldVariableKey({
|
|
162
|
+
name: "Impact 2",
|
|
163
|
+
existingKeys: [],
|
|
164
|
+
});
|
|
165
|
+
const second: string = generateCustomFieldVariableKey({
|
|
166
|
+
name: "Impact",
|
|
167
|
+
existingKeys: [first],
|
|
168
|
+
});
|
|
169
|
+
const third: string = generateCustomFieldVariableKey({
|
|
170
|
+
name: "Impact",
|
|
171
|
+
existingKeys: [first, second],
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(first).toBe("impact_2");
|
|
175
|
+
expect(second).toBe("impact");
|
|
176
|
+
expect(third).toBe("impact_3");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("names with nothing Latin share the fallback, numbered", () => {
|
|
180
|
+
const taken: Array<string> = [];
|
|
181
|
+
|
|
182
|
+
for (const name of ["影响", "Влияние", "🔥"]) {
|
|
183
|
+
taken.push(generateCustomFieldVariableKey({ name, existingKeys: taken }));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
expect(taken).toEqual(["field", "field_2", "field_3"]);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("ignores empty and missing existing keys", () => {
|
|
190
|
+
expect(
|
|
191
|
+
generateCustomFieldVariableKey({
|
|
192
|
+
name: "Impact",
|
|
193
|
+
existingKeys: [null, undefined, ""],
|
|
194
|
+
}),
|
|
195
|
+
).toBe("impact");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("accepts any iterable of existing keys", () => {
|
|
199
|
+
expect(
|
|
200
|
+
generateCustomFieldVariableKey({
|
|
201
|
+
name: "Impact",
|
|
202
|
+
existingKeys: new Set<string>(["impact"]),
|
|
203
|
+
}),
|
|
204
|
+
).toBe("impact_2");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("a numbered key of a maximum-length name still fits", () => {
|
|
208
|
+
const name: string = "a".repeat(200);
|
|
209
|
+
const base: string = getCustomFieldVariableKeyBase(name);
|
|
210
|
+
const numbered: string = generateCustomFieldVariableKey({
|
|
211
|
+
name,
|
|
212
|
+
existingKeys: [base],
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(base.length).toBe(CUSTOM_FIELD_VARIABLE_KEY_MAX_LENGTH);
|
|
216
|
+
expect(numbered.length).toBeLessThanOrEqual(
|
|
217
|
+
CUSTOM_FIELD_VARIABLE_KEY_MAX_LENGTH,
|
|
218
|
+
);
|
|
219
|
+
expect(numbered.endsWith("_2")).toBe(true);
|
|
220
|
+
expect(isValidCustomFieldVariableKey(numbered)).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("never ends the cut before a suffix on an underscore", () => {
|
|
224
|
+
// 62 characters, where the cut for "_2" lands right after an underscore.
|
|
225
|
+
const name: string = `${"a".repeat(61)} b`;
|
|
226
|
+
const base: string = getCustomFieldVariableKeyBase(name);
|
|
227
|
+
const numbered: string = generateCustomFieldVariableKey({
|
|
228
|
+
name,
|
|
229
|
+
existingKeys: [base],
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
expect(numbered).not.toContain("__");
|
|
233
|
+
expect(isValidCustomFieldVariableKey(numbered)).toBe(true);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("hundreds of colliding fields all get distinct, valid keys", () => {
|
|
237
|
+
const taken: Array<string> = [];
|
|
238
|
+
|
|
239
|
+
for (let i: number = 0; i < 300; i++) {
|
|
240
|
+
taken.push(
|
|
241
|
+
generateCustomFieldVariableKey({
|
|
242
|
+
name: i % 2 === 0 ? "Impact" : "impact?",
|
|
243
|
+
existingKeys: taken,
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
expect(new Set<string>(taken).size).toBe(300);
|
|
249
|
+
for (const key of taken) {
|
|
250
|
+
expect(isValidCustomFieldVariableKey(key)).toBe(true);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe("keys always fit the template placeholder pattern", () => {
|
|
256
|
+
const names: Array<string> = [
|
|
257
|
+
"Impact",
|
|
258
|
+
"Expected Resolution",
|
|
259
|
+
"Größe",
|
|
260
|
+
"影响",
|
|
261
|
+
"Влияние",
|
|
262
|
+
"🔥 hot",
|
|
263
|
+
"a.b.c",
|
|
264
|
+
"{{customFields.x}}",
|
|
265
|
+
"$&",
|
|
266
|
+
"<script>alert(1)</script>",
|
|
267
|
+
"x".repeat(500),
|
|
268
|
+
" ",
|
|
269
|
+
"2FA",
|
|
270
|
+
"Full width",
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
test.each(names)(
|
|
274
|
+
"the key for %j is filled in by the compiler",
|
|
275
|
+
(name: string) => {
|
|
276
|
+
const key: string = generateCustomFieldVariableKey({
|
|
277
|
+
name,
|
|
278
|
+
existingKeys: ["impact", "field"],
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
expect(key).toMatch(/^[a-z0-9]+(?:_[a-z0-9]+)*$/);
|
|
282
|
+
expect(isValidCustomFieldVariableKey(key)).toBe(true);
|
|
283
|
+
expect(
|
|
284
|
+
SubscriberNotificationTemplateCompiler.isPlaceholderName(
|
|
285
|
+
getCustomFieldTemplateVariableName(key),
|
|
286
|
+
),
|
|
287
|
+
).toBe(true);
|
|
288
|
+
expect(fill(key, "High")).toBe("Impact: High");
|
|
289
|
+
},
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
test("the prefix is the one custom templates use", () => {
|
|
293
|
+
expect(CUSTOM_FIELD_TEMPLATE_VARIABLE_PREFIX).toBe("customFields.");
|
|
294
|
+
expect(getCustomFieldTemplateVariableName("impact")).toBe(
|
|
295
|
+
"customFields.impact",
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("a slug would not work, which is why keys are not slugs", () => {
|
|
300
|
+
// Slug.getSlug joins with hyphens, which the compiler leaves as written.
|
|
301
|
+
const slug: string = Slug.getSlug("Expected Resolution");
|
|
302
|
+
|
|
303
|
+
expect(isValidCustomFieldVariableKey(slug)).toBe(false);
|
|
304
|
+
expect(
|
|
305
|
+
SubscriberNotificationTemplateCompiler.compileTemplate(
|
|
306
|
+
`{{customFields.${slug}}}`,
|
|
307
|
+
{ [`customFields.${slug}`]: "x" },
|
|
308
|
+
),
|
|
309
|
+
).toBe(`{{customFields.${slug}}}`);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe("isValidCustomFieldVariableKey", () => {
|
|
314
|
+
test.each(["impact", "expected_resolution", "a1_b2", "2fa", "x"])(
|
|
315
|
+
"accepts %j",
|
|
316
|
+
(key: string) => {
|
|
317
|
+
expect(isValidCustomFieldVariableKey(key)).toBe(true);
|
|
318
|
+
},
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
test.each([
|
|
322
|
+
"",
|
|
323
|
+
"Impact",
|
|
324
|
+
"expected-resolution",
|
|
325
|
+
"expected resolution",
|
|
326
|
+
"_impact",
|
|
327
|
+
"impact_",
|
|
328
|
+
"im__pact",
|
|
329
|
+
"im.pact",
|
|
330
|
+
"größe",
|
|
331
|
+
"a".repeat(CUSTOM_FIELD_VARIABLE_KEY_MAX_LENGTH + 1),
|
|
332
|
+
null,
|
|
333
|
+
undefined,
|
|
334
|
+
42,
|
|
335
|
+
])("rejects %j", (key: unknown) => {
|
|
336
|
+
expect(isValidCustomFieldVariableKey(key)).toBe(false);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe("SubscriberNotificationTemplateCompiler.isPlaceholderName", () => {
|
|
341
|
+
test("agrees with what the compiler fills", () => {
|
|
342
|
+
for (const name of ["a", "a.b", "customFields.impact_2", "A_1.b"]) {
|
|
343
|
+
expect(
|
|
344
|
+
SubscriberNotificationTemplateCompiler.isPlaceholderName(name),
|
|
345
|
+
).toBe(true);
|
|
346
|
+
expect(
|
|
347
|
+
SubscriberNotificationTemplateCompiler.compileTemplate(`{{${name}}}`, {
|
|
348
|
+
[name]: "filled",
|
|
349
|
+
}),
|
|
350
|
+
).toBe("filled");
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
for (const name of ["a-b", "a b", "é", "", "a{b"]) {
|
|
354
|
+
expect(
|
|
355
|
+
SubscriberNotificationTemplateCompiler.isPlaceholderName(name),
|
|
356
|
+
).toBe(false);
|
|
357
|
+
expect(
|
|
358
|
+
SubscriberNotificationTemplateCompiler.compileTemplate(`{{${name}}}`, {
|
|
359
|
+
[name]: "filled",
|
|
360
|
+
}),
|
|
361
|
+
).toBe(`{{${name}}}`);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
});
|