@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.
Files changed (440) hide show
  1. package/Models/DatabaseModels/Incident.ts +267 -1
  2. package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
  3. package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
  4. package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
  5. package/Models/DatabaseModels/StatusPage.ts +55 -0
  6. package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
  7. package/Server/API/IncidentAPI.ts +162 -0
  8. package/Server/API/StatusPageAPI.ts +487 -171
  9. package/Server/API/StatusPageSubscriberAPI.ts +63 -15
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
  18. package/Server/Infrastructure/Semaphore.ts +10 -0
  19. package/Server/Middleware/IdentityRateLimit.ts +108 -3
  20. package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
  21. package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
  22. package/Server/Services/DatabaseService.ts +93 -10
  23. package/Server/Services/IncidentCustomFieldService.ts +276 -1
  24. package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
  25. package/Server/Services/IncidentEpisodeService.ts +15 -0
  26. package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
  27. package/Server/Services/IncidentPublicNoteService.ts +30 -1
  28. package/Server/Services/IncidentService.ts +1535 -65
  29. package/Server/Services/IncidentStateTimelineService.ts +13 -0
  30. package/Server/Services/IncidentTemplateService.ts +109 -0
  31. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
  32. package/Server/Services/ScheduledMaintenanceService.ts +68 -18
  33. package/Server/Services/SmsService.ts +10 -0
  34. package/Server/Services/StatusPageService.ts +65 -8
  35. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
  36. package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
  37. package/Server/Services/StatusPageSubscriberService.ts +1161 -64
  38. package/Server/Types/Markdown.ts +153 -6
  39. package/Server/Types/Workflow/Components/Email.ts +61 -2
  40. package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
  41. package/Server/Utils/DataSource/EgressGuard.ts +66 -21
  42. package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
  43. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
  44. package/Server/Utils/SSRFProtection.ts +76 -16
  45. package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
  46. package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
  47. package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
  48. package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
  49. package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
  50. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
  51. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
  52. package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
  53. package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
  54. package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
  55. package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
  56. package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
  57. package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
  58. package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
  59. package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
  60. package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
  61. package/Server/Utils/StatusPageResource.ts +51 -20
  62. package/Server/Utils/VM/VMAPI.ts +1 -0
  63. package/Server/Utils/VM/VMRunner.ts +10 -0
  64. package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
  65. package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
  66. package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
  67. package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
  68. package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
  69. package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
  70. package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
  71. package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
  72. package/Tests/App/Dashboard/EventNotes.test.tsx +776 -0
  73. package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
  74. package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
  75. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +242 -5
  76. package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
  77. package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
  78. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
  79. package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
  80. package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
  81. package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
  82. package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
  83. package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
  84. package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
  85. package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
  86. package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
  87. package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
  88. package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
  89. package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
  90. package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
  91. package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
  92. package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
  93. package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
  94. package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
  95. package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
  96. package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
  97. package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
  98. package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
  99. package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
  100. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
  101. package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
  102. package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
  103. package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
  104. package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
  105. package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
  106. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
  107. package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
  108. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
  109. package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
  110. package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
  111. package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
  112. package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
  113. package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
  114. package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
  115. package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
  116. package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
  117. package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
  118. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
  119. package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
  120. package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
  121. package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
  122. package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
  123. package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
  124. package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
  125. package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
  126. package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
  127. package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
  128. package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
  129. package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
  130. package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
  131. package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
  132. package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
  133. package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
  134. package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
  135. package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
  136. package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
  137. package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
  138. package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
  139. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
  140. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
  141. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
  142. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
  143. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
  144. package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
  145. package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
  146. package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
  147. package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
  148. package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
  149. package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
  150. package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
  151. package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
  152. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
  153. package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
  154. package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
  155. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
  156. package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
  157. package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
  158. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
  159. package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
  160. package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
  161. package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
  162. package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
  163. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
  164. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
  165. package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
  166. package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
  167. package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
  168. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
  169. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
  170. package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
  171. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
  172. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
  173. package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
  174. package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
  175. package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
  176. package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
  177. package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
  178. package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
  179. package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
  180. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
  181. package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
  182. package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
  183. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
  184. package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
  185. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
  186. package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
  187. package/Tests/Types/SafeHtml.test.ts +102 -0
  188. package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
  189. package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
  190. package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
  191. package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
  192. package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
  193. package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
  194. package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
  195. package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
  196. package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
  197. package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
  198. package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
  199. package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
  200. package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
  201. package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
  202. package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
  203. package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
  204. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
  205. package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
  206. package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
  207. package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
  208. package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
  209. package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
  210. package/Types/CustomField/CustomFieldOrder.ts +55 -0
  211. package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
  212. package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
  213. package/Types/CustomField/CustomFieldType.ts +14 -0
  214. package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
  215. package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
  216. package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
  217. package/Types/SafeHtml.ts +93 -0
  218. package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
  219. package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
  220. package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
  221. package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
  222. package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
  223. package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
  224. package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
  225. package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
  226. package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
  227. package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
  228. package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
  229. package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
  230. package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
  231. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
  232. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
  233. package/UI/Components/Forms/ModelForm.tsx +70 -8
  234. package/UI/Components/Forms/Validation.ts +1 -1
  235. package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
  236. package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
  237. package/UI/Components/ModelTable/BaseModelTable.tsx +9 -8
  238. package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
  239. package/UI/Components/ModelTable/ModelTable.tsx +6 -7
  240. package/UI/Utils/SensitiveUrlToken.ts +10 -5
  241. package/Utils/API.ts +247 -2
  242. package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
  243. package/Utils/Markdown/MarkdownEscape.ts +65 -0
  244. package/Utils/ProbeNetworkFailureUtil.ts +673 -0
  245. package/build/dist/Models/DatabaseModels/Incident.js +267 -1
  246. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  247. package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
  248. package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
  249. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
  250. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  251. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
  252. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  253. package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
  254. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  255. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
  256. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  257. package/build/dist/Server/API/IncidentAPI.js +100 -1
  258. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  259. package/build/dist/Server/API/StatusPageAPI.js +417 -176
  260. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  261. package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
  262. package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
  263. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
  264. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
  265. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
  266. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
  267. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
  268. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
  269. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
  270. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
  271. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
  272. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
  273. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
  274. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
  275. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
  276. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
  277. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
  278. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  279. package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
  280. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  281. package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
  282. package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
  283. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
  284. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
  285. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
  286. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
  287. package/build/dist/Server/Services/DatabaseService.js +56 -7
  288. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  289. package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
  290. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  291. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
  292. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  293. package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
  294. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  295. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
  296. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  297. package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
  298. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  299. package/build/dist/Server/Services/IncidentService.js +1052 -0
  300. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  301. package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
  302. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  303. package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
  304. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  305. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
  306. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  307. package/build/dist/Server/Services/ScheduledMaintenanceService.js +47 -17
  308. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  309. package/build/dist/Server/Services/SmsService.js +2 -22
  310. package/build/dist/Server/Services/SmsService.js.map +1 -1
  311. package/build/dist/Server/Services/StatusPageService.js +47 -9
  312. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  313. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
  314. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  315. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
  316. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
  317. package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
  318. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  319. package/build/dist/Server/Types/Markdown.js +110 -6
  320. package/build/dist/Server/Types/Markdown.js.map +1 -1
  321. package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
  322. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  323. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
  324. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
  325. package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
  326. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  327. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
  328. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
  329. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
  330. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  331. package/build/dist/Server/Utils/SSRFProtection.js +48 -6
  332. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  333. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
  334. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
  335. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
  336. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
  337. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
  338. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
  339. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
  340. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
  341. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
  342. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
  343. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
  344. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
  345. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
  346. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
  347. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
  348. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
  349. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
  350. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
  351. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
  352. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
  353. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
  354. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
  355. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
  356. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
  357. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
  358. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
  359. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
  360. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
  361. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
  362. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
  363. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
  364. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
  365. package/build/dist/Server/Utils/StatusPageResource.js +39 -7
  366. package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
  367. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  368. package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
  369. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  370. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
  371. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
  372. package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
  373. package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
  374. package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
  375. package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
  376. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
  377. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
  378. package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
  379. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  380. package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
  381. package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
  382. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
  383. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
  384. package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
  385. package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
  386. package/build/dist/Types/SafeHtml.js +84 -0
  387. package/build/dist/Types/SafeHtml.js.map +1 -0
  388. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
  389. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
  390. package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
  391. package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
  392. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
  393. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
  394. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
  395. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
  396. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
  397. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
  398. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
  399. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
  400. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
  401. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
  402. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
  403. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
  404. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
  405. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
  406. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
  407. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
  408. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
  409. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
  410. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
  411. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
  412. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
  413. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
  414. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
  415. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
  416. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
  417. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  418. package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
  419. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  420. package/build/dist/UI/Components/Forms/Validation.js +1 -1
  421. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  422. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  423. package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
  424. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -2
  425. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  426. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
  427. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  428. package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
  429. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  430. package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
  431. package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
  432. package/build/dist/Utils/API.js +177 -1
  433. package/build/dist/Utils/API.js.map +1 -1
  434. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
  435. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
  436. package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
  437. package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
  438. package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
  439. package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
  440. package/package.json +1 -1
@@ -131,6 +131,14 @@ import StatusPageSubscriberNotificationTemplateService, {
131
131
  Service as StatusPageSubscriberNotificationTemplateServiceClass,
132
132
  } from "../Services/StatusPageSubscriberNotificationTemplateService";
133
133
  import { canServeStatusPageCustomizations } from "../Utils/StatusPageCustomizationAccess";
134
+ import IncidentStatusPageScope, {
135
+ INCIDENT_SCOPE_SELECT,
136
+ } from "../Utils/StatusPage/IncidentStatusPageScope";
137
+ import { StatusPageSubscriberUnsubscribeSource } from "../Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice";
138
+ import StatusPageSubscriberUnsubscribe, {
139
+ StatusPageSubscriberUnsubscribeDetails,
140
+ StatusPageSubscriberUnsubscribeState,
141
+ } from "../../Types/StatusPage/StatusPageSubscriberUnsubscribe";
134
142
 
135
143
  /*
136
144
  * A manage-subscription request is unauthenticated, and one Slack or Microsoft
@@ -765,6 +773,80 @@ export default class StatusPageAPI extends BaseAPI<
765
773
  },
766
774
  );
767
775
 
776
+ /*
777
+ * The unsubscribe link in every subscriber notification opens the status
778
+ * page's unsubscribe page ({statusPageUrl}/unsubscribe/{id}-{token}, see
779
+ * Common/Types/StatusPage/StatusPageSubscriberUnsubscribe), which reads
780
+ * what the link belongs to with GET and cancels it with POST.
781
+ *
782
+ * - GET changes nothing. Mail scanners and link previewers fetch every
783
+ * link in a message; they must not unsubscribe anybody.
784
+ * - POST unsubscribes, and is idempotent. It answers whatever its body is
785
+ * - the page sends none, and an RFC 8058 one-click request sends
786
+ * "List-Unsubscribe=One-Click" - because holding the token is the whole
787
+ * authorisation.
788
+ *
789
+ * Deliberately no read-access check: a private status page's subscribers
790
+ * are exactly the people who may not be able to sign in to it, and the
791
+ * token proves the request came from the subscription's own inbox (or
792
+ * phone, or channel). Nothing but the subscription's own contact is ever
793
+ * returned - no page content - so a private page stays private.
794
+ *
795
+ * Every bad link answers the same Invalid, so the endpoint cannot be used
796
+ * to learn which subscriptions exist; see
797
+ * StatusPageSubscriberService.getUnsubscribeLinkDetails.
798
+ */
799
+ const unsubscribeApiPath: string = `${new this.entityType()
800
+ .getCrudApiPath()
801
+ ?.toString()}/unsubscribe/:statusPageId/:subscriberId/:token`;
802
+
803
+ this.router.get(
804
+ unsubscribeApiPath,
805
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
806
+ try {
807
+ const details: StatusPageSubscriberUnsubscribeDetails =
808
+ await StatusPageSubscriberService.getUnsubscribeLinkDetails({
809
+ statusPageId: req.params["statusPageId"] as string,
810
+ subscriberId: req.params["subscriberId"] as string,
811
+ token: req.params["token"] as string,
812
+ });
813
+
814
+ // A token-bearing answer: never let a shared cache keep it.
815
+ Response.setNoCacheHeaders(res);
816
+
817
+ return Response.sendJsonObjectResponse(
818
+ req,
819
+ res,
820
+ details as unknown as JSONObject,
821
+ );
822
+ } catch (err) {
823
+ next(err);
824
+ }
825
+ },
826
+ );
827
+
828
+ this.router.post(
829
+ unsubscribeApiPath,
830
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
831
+ try {
832
+ const state: StatusPageSubscriberUnsubscribeState =
833
+ await StatusPageSubscriberService.unsubscribeWithLink({
834
+ statusPageId: req.params["statusPageId"] as string,
835
+ subscriberId: req.params["subscriberId"] as string,
836
+ token: req.params["token"] as string,
837
+ });
838
+
839
+ Response.setNoCacheHeaders(res);
840
+
841
+ return Response.sendJsonObjectResponse(req, res, {
842
+ state: state,
843
+ });
844
+ } catch (err) {
845
+ next(err);
846
+ }
847
+ },
848
+ );
849
+
768
850
  // CNAME verification api
769
851
  this.router.get(
770
852
  `${new this.entityType()
@@ -2912,20 +2994,40 @@ export default class StatusPageAPI extends BaseAPI<
2912
2994
  let lookupQuery: Query<StatusPageSubscriber>;
2913
2995
  let lookupSelect: Select<StatusPageSubscriber>;
2914
2996
 
2997
+ /*
2998
+ * The unsubscribe token is read too: the message carries the
2999
+ * subscription's unsubscribe link as well as its manage link.
3000
+ */
2915
3001
  if (email) {
2916
3002
  lookupQuery = { subscriberEmail: email };
2917
- lookupSelect = { _id: true, subscriberEmail: true };
3003
+ lookupSelect = {
3004
+ _id: true,
3005
+ subscriberEmail: true,
3006
+ unsubscribeToken: true,
3007
+ };
2918
3008
  } else if (phone) {
2919
3009
  lookupQuery = { subscriberPhone: phone };
2920
- lookupSelect = { _id: true, subscriberPhone: true };
3010
+ lookupSelect = {
3011
+ _id: true,
3012
+ subscriberPhone: true,
3013
+ unsubscribeToken: true,
3014
+ };
2921
3015
  } else if (slackWorkspaceName) {
2922
3016
  lookupQuery = { slackWorkspaceName: slackWorkspaceName };
2923
- lookupSelect = { _id: true, slackIncomingWebhookUrl: true };
3017
+ lookupSelect = {
3018
+ _id: true,
3019
+ slackIncomingWebhookUrl: true,
3020
+ unsubscribeToken: true,
3021
+ };
2924
3022
  } else {
2925
3023
  lookupQuery = {
2926
3024
  microsoftTeamsWorkspaceName: microsoftTeamsWorkspaceName!,
2927
3025
  };
2928
- lookupSelect = { _id: true, microsoftTeamsIncomingWebhookUrl: true };
3026
+ lookupSelect = {
3027
+ _id: true,
3028
+ microsoftTeamsIncomingWebhookUrl: true,
3029
+ unsubscribeToken: true,
3030
+ };
2929
3031
  }
2930
3032
 
2931
3033
  logger.debug(
@@ -2990,6 +3092,13 @@ export default class StatusPageAPI extends BaseAPI<
2990
3092
  }): Promise<void> {
2991
3093
  const { statusPageId, subscribers, req } = data;
2992
3094
 
3095
+ /*
3096
+ * Their unsubscribe links need their tokens, as every other sender's do:
3097
+ * a subscriber the backfill has not reached yet is given one first,
3098
+ * rather than sent a link to the "out of date" page.
3099
+ */
3100
+ await StatusPageSubscriberService.ensureUnsubscribeTokens(subscribers);
3101
+
2993
3102
  const statusPageURL: string =
2994
3103
  await StatusPageService.getStatusPageURL(statusPageId);
2995
3104
 
@@ -3060,20 +3169,28 @@ export default class StatusPageAPI extends BaseAPI<
3060
3169
  statusPageSubscriber.microsoftTeamsIncomingWebhookUrl;
3061
3170
 
3062
3171
  const manageUrlink: string =
3063
- StatusPageSubscriberService.getUnsubscribeLink(
3172
+ StatusPageSubscriberService.getManageSubscriptionLink(
3064
3173
  URL.fromString(statusPageURL),
3065
3174
  statusPageSubscriber.id!,
3066
3175
  ).toString();
3067
3176
 
3068
3177
  /*
3069
- * The manage link is the subscriber's update-subscription page, which
3070
- * is the same URL every other sender passes as unsubscribeUrl, so a
3071
- * template may use either name for it.
3178
+ * The visitor asked to manage their subscription, so the message
3179
+ * links to its Update Subscription page. It also hands templates the
3180
+ * unsubscribe link every other sender passes as unsubscribeUrl: that
3181
+ * one works without signing in, which the manage page on a private
3182
+ * status page does not.
3072
3183
  */
3184
+ const unsubscribeUrl: string =
3185
+ StatusPageSubscriberService.getUnsubscribeLink(
3186
+ URL.fromString(statusPageURL),
3187
+ statusPageSubscriber,
3188
+ ).toString();
3189
+
3073
3190
  const manageTemplateVariables: Record<string, string> = {
3074
3191
  statusPageName: statusPageNameStr,
3075
3192
  statusPageUrl: statusPageURL,
3076
- unsubscribeUrl: manageUrlink,
3193
+ unsubscribeUrl: unsubscribeUrl,
3077
3194
  manageSubscriptionUrl: manageUrlink,
3078
3195
  };
3079
3196
 
@@ -3086,8 +3203,9 @@ export default class StatusPageAPI extends BaseAPI<
3086
3203
  statusPage.id?.toString() || statusPage._id?.toString() || null;
3087
3204
 
3088
3205
  if (manageEmailTemplate?.templateBody && statusPage.smtpConfig) {
3206
+ // The body is HTML, so the (plain-text) values are escaped into it.
3089
3207
  const compiledBody: string =
3090
- StatusPageSubscriberNotificationTemplateServiceClass.compileTemplate(
3208
+ StatusPageSubscriberNotificationTemplateServiceClass.compileEmailBodyTemplate(
3091
3209
  manageEmailTemplate.templateBody,
3092
3210
  manageTemplateVariables,
3093
3211
  );
@@ -3159,10 +3277,24 @@ export default class StatusPageAPI extends BaseAPI<
3159
3277
  if (subscriberPhone) {
3160
3278
  let smsMessage: string;
3161
3279
  if (manageSmsTemplate?.templateBody && statusPage.callSmsConfig) {
3280
+ /*
3281
+ * On a public status page an SMS carries the shorter manage link
3282
+ * where other messages carry the unsubscribe link: an SMS is
3283
+ * billed by the segment (see
3284
+ * StatusPageSubscriberUnsubscribe.buildSmsLink).
3285
+ */
3162
3286
  smsMessage =
3163
3287
  StatusPageSubscriberNotificationTemplateServiceClass.compileTemplate(
3164
3288
  manageSmsTemplate.templateBody,
3165
- manageTemplateVariables,
3289
+ {
3290
+ ...manageTemplateVariables,
3291
+ unsubscribeUrl: StatusPageSubscriberUnsubscribe.buildSmsLink({
3292
+ isPublicStatusPage: statusPage.isPublicStatusPage,
3293
+ statusPageUrl: statusPageURL,
3294
+ subscriberId: statusPageSubscriber.id!,
3295
+ unsubscribeUrl: unsubscribeUrl,
3296
+ }),
3297
+ },
3166
3298
  );
3167
3299
  } else {
3168
3300
  smsMessage = defaultChatMessage;
@@ -3588,12 +3720,12 @@ export default class StatusPageAPI extends BaseAPI<
3588
3720
  }
3589
3721
 
3590
3722
  if (isUpdate) {
3591
- // check isUnsubscribed is set to false.
3592
3723
  logger.debug(
3593
3724
  `Updating subscriber with ID: ${statusPageSubscriber.id}`,
3594
3725
  getLogAttributesFromRequest(req as any),
3595
3726
  );
3596
- statusPageSubscriber.isUnsubscribed = Boolean(
3727
+
3728
+ const wantsToUnsubscribe: boolean = Boolean(
3597
3729
  req.body.data["isUnsubscribed"],
3598
3730
  );
3599
3731
 
@@ -3606,23 +3738,34 @@ export default class StatusPageAPI extends BaseAPI<
3606
3738
  statusPageEventTypes: statusPageSubscriber.statusPageEventTypes!,
3607
3739
  isSubscribedToAllEventTypes:
3608
3740
  statusPageSubscriber.isSubscribedToAllEventTypes!,
3609
- isUnsubscribed: statusPageSubscriber.isUnsubscribed,
3741
+ /*
3742
+ * Cancelling goes through unsubscribe() below, like the unsubscribe
3743
+ * link: it records when, once, and tells the team about a
3744
+ * subscriber it added. Only turning the subscription back on is
3745
+ * written here (which clears Unsubscribed At).
3746
+ */
3747
+ ...(wantsToUnsubscribe ? {} : { isUnsubscribed: false }),
3610
3748
  } as any,
3611
3749
  props: {
3612
3750
  isRoot: true,
3613
3751
  },
3614
3752
  });
3753
+
3754
+ if (wantsToUnsubscribe) {
3755
+ await StatusPageSubscriberService.unsubscribe({
3756
+ subscriberId: statusPageSubscriber.id!,
3757
+ source: StatusPageSubscriberUnsubscribeSource.ManageSubscriptionPage,
3758
+ });
3759
+ }
3615
3760
  } else {
3616
3761
  logger.debug(
3617
3762
  `Creating new subscriber: ${JSON.stringify(statusPageSubscriber)}`,
3618
3763
  getLogAttributesFromRequest(req as any),
3619
3764
  );
3620
- await StatusPageSubscriberService.create({
3621
- data: statusPageSubscriber,
3622
- props: {
3623
- isRoot: true,
3624
- },
3625
- });
3765
+ // A sign-up, not a subscriber the team added (Is Added By Team stays off).
3766
+ await StatusPageSubscriberService.createFromStatusPageSignUp(
3767
+ statusPageSubscriber,
3768
+ );
3626
3769
  }
3627
3770
 
3628
3771
  logger.debug(
@@ -3694,6 +3837,12 @@ export default class StatusPageAPI extends BaseAPI<
3694
3837
  return statusPageSubscriber;
3695
3838
  }
3696
3839
 
3840
+ /*
3841
+ * Every incident a public status page is sent goes through here - the
3842
+ * overview's active and timeline incidents, and the incident list and
3843
+ * detail - so it is where the page's JSON, and the overview cache, are made
3844
+ * safe to serve.
3845
+ */
3697
3846
  private serializeIncidentsForStatusPage(
3698
3847
  incidents: Array<Incident>,
3699
3848
  ): JSONArray {
@@ -3707,10 +3856,41 @@ export default class StatusPageAPI extends BaseAPI<
3707
3856
  delete incidentJson["postmortemAttachments"];
3708
3857
  }
3709
3858
 
3859
+ /*
3860
+ * Which status pages an incident is limited to names the other
3861
+ * audiences it is shown to. The display queries never select these
3862
+ * columns (IncidentStatusPageScope), so this is a second line of
3863
+ * defence against a select that one day does.
3864
+ */
3865
+ IncidentStatusPageScope.removeScopeColumns(incidentJson);
3866
+
3710
3867
  return incidentJson;
3711
3868
  });
3712
3869
  }
3713
3870
 
3871
+ /*
3872
+ * An episode's member incidents, read by id to work out which of the
3873
+ * page's monitors the episode affects, keeping only those this page shows
3874
+ * as far as scope goes. The read selects the scope columns for that
3875
+ * (INCIDENT_SCOPE_SELECT); they are removed again before the incidents are
3876
+ * used, so they cannot reach the page's JSON.
3877
+ */
3878
+ private keepMemberIncidentsInScope(data: {
3879
+ incidents: Array<Incident>;
3880
+ statusPage: StatusPage;
3881
+ }): Array<Incident> {
3882
+ return data.incidents
3883
+ .filter((incident: Incident): boolean => {
3884
+ return IncidentStatusPageScope.isIncidentInScope(
3885
+ incident,
3886
+ data.statusPage,
3887
+ );
3888
+ })
3889
+ .map((incident: Incident): Incident => {
3890
+ return IncidentStatusPageScope.removeScopeColumns(incident);
3891
+ });
3892
+ }
3893
+
3714
3894
  @CaptureSpan()
3715
3895
  public async getIncidents(
3716
3896
  statusPageId: ObjectID,
@@ -3732,6 +3912,8 @@ export default class StatusPageAPI extends BaseAPI<
3732
3912
  showIncidentHistoryInDays: true,
3733
3913
  showIncidentLabelsOnStatusPage: true,
3734
3914
  showIncidentsOnStatusPage: true,
3915
+ // Read by IncidentStatusPageScope to decide which incidents it shows.
3916
+ onlyShowScopedIncidents: true,
3735
3917
  },
3736
3918
  props: {
3737
3919
  isRoot: true,
@@ -3826,7 +4008,15 @@ export default class StatusPageAPI extends BaseAPI<
3826
4008
  }
3827
4009
 
3828
4010
  if (monitorsOnStatusPage.length > 0) {
3829
- incidents = await IncidentService.findBy({
4011
+ /*
4012
+ * Through IncidentStatusPageScope, like every incident read here that
4013
+ * decides what the page shows: an incident limited to other status
4014
+ * pages is left out, and so is an unlimited one when this page only
4015
+ * shows incidents limited to it. The scope is applied in SQL, before
4016
+ * LIMIT_PER_PROJECT cuts the list, not filtered out afterwards.
4017
+ */
4018
+ incidents = await IncidentStatusPageScope.findIncidentsForStatusPage({
4019
+ statusPage: statusPage,
3830
4020
  query: incidentQuery,
3831
4021
  select: selectIncidents,
3832
4022
  sort: {
@@ -3857,27 +4047,29 @@ export default class StatusPageAPI extends BaseAPI<
3857
4047
 
3858
4048
  // If there is no particular incident id to fetch then fetch active incidents.
3859
4049
  if (!incidentId) {
3860
- activeIncidents = await IncidentService.findBy({
3861
- query: {
3862
- monitors: monitorsOnStatusPage as any,
3863
- isVisibleOnStatusPage: true,
3864
- currentIncidentStateId: QueryHelper.any(
3865
- unresolvbedIncidentStateIds,
3866
- ),
3867
- projectId: statusPage.projectId!,
3868
- },
3869
- select: selectIncidents,
3870
- sort: {
3871
- declaredAt: SortOrder.Descending,
3872
- createdAt: SortOrder.Descending,
3873
- },
4050
+ activeIncidents =
4051
+ await IncidentStatusPageScope.findIncidentsForStatusPage({
4052
+ statusPage: statusPage,
4053
+ query: {
4054
+ monitors: monitorsOnStatusPage as any,
4055
+ isVisibleOnStatusPage: true,
4056
+ currentIncidentStateId: QueryHelper.any(
4057
+ unresolvbedIncidentStateIds,
4058
+ ),
4059
+ projectId: statusPage.projectId!,
4060
+ },
4061
+ select: selectIncidents,
4062
+ sort: {
4063
+ declaredAt: SortOrder.Descending,
4064
+ createdAt: SortOrder.Descending,
4065
+ },
3874
4066
 
3875
- skip: 0,
3876
- limit: LIMIT_PER_PROJECT,
3877
- props: {
3878
- isRoot: true,
3879
- },
3880
- });
4067
+ skip: 0,
4068
+ limit: LIMIT_PER_PROJECT,
4069
+ props: {
4070
+ isRoot: true,
4071
+ },
4072
+ });
3881
4073
  }
3882
4074
 
3883
4075
  incidents = [...activeIncidents, ...incidents];
@@ -4010,6 +4202,8 @@ export default class StatusPageAPI extends BaseAPI<
4010
4202
  showEpisodeHistoryInDays: true,
4011
4203
  showEpisodesOnStatusPage: true,
4012
4204
  showEpisodeLabelsOnStatusPage: true,
4205
+ // Read by IncidentStatusPageScope to decide which incidents it shows.
4206
+ onlyShowScopedIncidents: true,
4013
4207
  },
4014
4208
  props: {
4015
4209
  isRoot: true,
@@ -4061,11 +4255,22 @@ export default class StatusPageAPI extends BaseAPI<
4061
4255
  let incidents: Array<Incident> = [];
4062
4256
 
4063
4257
  if (monitorsOnStatusPage.length > 0) {
4064
- incidents = await IncidentService.findBy({
4258
+ /*
4259
+ * An episode is listed through the incidents in it that this page
4260
+ * shows, so an incident limited to other status pages (or an unlimited
4261
+ * one, on a page that only shows incidents limited to it) does not
4262
+ * bring its episode here. Newest first, so that when LIMIT_PER_PROJECT
4263
+ * cuts the list it keeps the recent episodes.
4264
+ */
4265
+ incidents = await IncidentStatusPageScope.findIncidentsForStatusPage({
4266
+ statusPage: statusPage,
4065
4267
  query: incidentQuery,
4066
4268
  select: {
4067
4269
  _id: true,
4068
4270
  },
4271
+ sort: {
4272
+ createdAt: SortOrder.Descending,
4273
+ },
4069
4274
  skip: 0,
4070
4275
  limit: LIMIT_PER_PROJECT,
4071
4276
  props: {
@@ -4142,6 +4347,47 @@ export default class StatusPageAPI extends BaseAPI<
4142
4347
  },
4143
4348
  });
4144
4349
 
4350
+ /*
4351
+ * An episode is this page's to show only through an incident in it
4352
+ * that the page shows: one on a monitor the page lists, and in scope
4353
+ * for the page. Without this check any visible episode of the project
4354
+ * could be read from any of its status pages by id - including an
4355
+ * episode whose incidents are all limited to other status pages. Like
4356
+ * the list, it does not ask whether the incidents themselves are
4357
+ * visible on status pages, and unlike the list, it has no history
4358
+ * window: a link to an older episode keeps working.
4359
+ */
4360
+ const episodeMemberIncidentIds: Array<ObjectID> =
4361
+ episodeMembersForSpecificEpisode
4362
+ .map((member: IncidentEpisodeMember) => {
4363
+ return member.incidentId;
4364
+ })
4365
+ .filter((id: ObjectID | undefined): id is ObjectID => {
4366
+ return Boolean(id);
4367
+ });
4368
+
4369
+ const incidentShownOnStatusPage: Incident | null =
4370
+ monitorsOnStatusPage.length > 0 && episodeMemberIncidentIds.length > 0
4371
+ ? await IncidentStatusPageScope.findOneIncidentForStatusPage({
4372
+ statusPage: statusPage,
4373
+ query: {
4374
+ _id: QueryHelper.any(episodeMemberIncidentIds),
4375
+ monitors: monitorsOnStatusPage as any,
4376
+ projectId: statusPage.projectId!,
4377
+ },
4378
+ select: {
4379
+ _id: true,
4380
+ },
4381
+ props: {
4382
+ isRoot: true,
4383
+ },
4384
+ })
4385
+ : null;
4386
+
4387
+ if (!incidentShownOnStatusPage) {
4388
+ throw new NotFoundException("Episode not found");
4389
+ }
4390
+
4145
4391
  // Merge with existing episode members
4146
4392
  for (const member of episodeMembersForSpecificEpisode) {
4147
4393
  if (
@@ -4276,25 +4522,34 @@ export default class StatusPageAPI extends BaseAPI<
4276
4522
  }
4277
4523
  }
4278
4524
 
4279
- // Fetch incidents with their monitors
4525
+ /*
4526
+ * Fetch incidents with their monitors. Read by id, so the scope is
4527
+ * applied here, in memory: a member incident limited to other status
4528
+ * pages does not tell this page which of its monitors the episode
4529
+ * affects. (An episode viewed by id brings all of its members.)
4530
+ */
4280
4531
  let memberIncidents: Array<Incident> = [];
4281
4532
  if (memberIncidentIds.length > 0) {
4282
- memberIncidents = await IncidentService.findBy({
4283
- query: {
4284
- _id: QueryHelper.any(memberIncidentIds),
4285
- projectId: statusPage.projectId!,
4286
- },
4287
- select: {
4288
- _id: true,
4289
- monitors: {
4533
+ memberIncidents = this.keepMemberIncidentsInScope({
4534
+ incidents: await IncidentService.findBy({
4535
+ query: {
4536
+ _id: QueryHelper.any(memberIncidentIds),
4537
+ projectId: statusPage.projectId!,
4538
+ },
4539
+ select: {
4290
4540
  _id: true,
4541
+ monitors: {
4542
+ _id: true,
4543
+ },
4544
+ ...INCIDENT_SCOPE_SELECT,
4291
4545
  },
4292
- },
4293
- skip: 0,
4294
- limit: LIMIT_PER_PROJECT,
4295
- props: {
4296
- isRoot: true,
4297
- },
4546
+ skip: 0,
4547
+ limit: LIMIT_PER_PROJECT,
4548
+ props: {
4549
+ isRoot: true,
4550
+ },
4551
+ }),
4552
+ statusPage: statusPage,
4298
4553
  });
4299
4554
  }
4300
4555
 
@@ -4507,6 +4762,11 @@ export default class StatusPageAPI extends BaseAPI<
4507
4762
  showEpisodesOnStatusPage: true,
4508
4763
  showScheduledMaintenanceEventsOnStatusPage: true,
4509
4764
  showUptimeHistoryInDays: true,
4765
+ /*
4766
+ * Read by IncidentStatusPageScope to decide which incidents the
4767
+ * overview shows. Not public: the overview removes it again.
4768
+ */
4769
+ onlyShowScopedIncidents: true,
4510
4770
  },
4511
4771
  props: {
4512
4772
  isRoot: true,
@@ -4956,25 +5216,30 @@ export default class StatusPageAPI extends BaseAPI<
4956
5216
  });
4957
5217
 
4958
5218
  if (statusPage.showIncidentsOnStatusPage) {
4959
- activeIncidents = await IncidentService.findBy({
4960
- query: {
4961
- monitors: monitorsOnStatusPage as any,
4962
- currentIncidentStateId: QueryHelper.any(unresolvedIncidentStateIds),
4963
- isVisibleOnStatusPage: true,
4964
- projectId: statusPage.projectId!,
4965
- },
4966
- select: select,
4967
- sort: {
4968
- declaredAt: SortOrder.Descending,
4969
- createdAt: SortOrder.Descending,
4970
- },
5219
+ // Only the incidents in scope for this page; see getIncidents.
5220
+ activeIncidents =
5221
+ await IncidentStatusPageScope.findIncidentsForStatusPage({
5222
+ statusPage: statusPage,
5223
+ query: {
5224
+ monitors: monitorsOnStatusPage as any,
5225
+ currentIncidentStateId: QueryHelper.any(
5226
+ unresolvedIncidentStateIds,
5227
+ ),
5228
+ isVisibleOnStatusPage: true,
5229
+ projectId: statusPage.projectId!,
5230
+ },
5231
+ select: select,
5232
+ sort: {
5233
+ declaredAt: SortOrder.Descending,
5234
+ createdAt: SortOrder.Descending,
5235
+ },
4971
5236
 
4972
- skip: 0,
4973
- limit: LIMIT_PER_PROJECT,
4974
- props: {
4975
- isRoot: true,
4976
- },
4977
- });
5237
+ skip: 0,
5238
+ limit: LIMIT_PER_PROJECT,
5239
+ props: {
5240
+ isRoot: true,
5241
+ },
5242
+ });
4978
5243
  }
4979
5244
  }
4980
5245
 
@@ -5098,9 +5363,16 @@ export default class StatusPageAPI extends BaseAPI<
5098
5363
  }
5099
5364
 
5100
5365
  if (hasActiveEpisodes) {
5101
- // First, get incidents that have monitors on status page
5366
+ /*
5367
+ * First, get incidents that have monitors on status page, and are in
5368
+ * scope for it: an episode is shown through the incidents in it that
5369
+ * the page shows (see getEpisodes). Newest first, so that when
5370
+ * LIMIT_PER_PROJECT cuts the list it keeps the incidents most likely
5371
+ * to be in an active episode.
5372
+ */
5102
5373
  const incidentsForEpisodes: Array<Incident> =
5103
- await IncidentService.findBy({
5374
+ await IncidentStatusPageScope.findIncidentsForStatusPage({
5375
+ statusPage: statusPage,
5104
5376
  query: {
5105
5377
  monitors: monitorsOnStatusPage as any,
5106
5378
  isVisibleOnStatusPage: true,
@@ -5109,6 +5381,9 @@ export default class StatusPageAPI extends BaseAPI<
5109
5381
  select: {
5110
5382
  _id: true,
5111
5383
  },
5384
+ sort: {
5385
+ createdAt: SortOrder.Descending,
5386
+ },
5112
5387
  skip: 0,
5113
5388
  limit: LIMIT_PER_PROJECT,
5114
5389
  props: {
@@ -5233,26 +5508,35 @@ export default class StatusPageAPI extends BaseAPI<
5233
5508
  }
5234
5509
  }
5235
5510
 
5236
- // Fetch incidents with monitors
5511
+ /*
5512
+ * Fetch incidents with monitors. Read by id, so the scope is
5513
+ * applied in memory, as in getEpisodes - these members came from
5514
+ * incidents in scope, and this keeps it that way should that
5515
+ * change.
5516
+ */
5237
5517
  let memberIncidents: Array<Incident> = [];
5238
5518
  if (memberIncidentIds.length > 0) {
5239
- memberIncidents = await IncidentService.findBy({
5240
- query: {
5241
- _id: QueryHelper.any(memberIncidentIds),
5242
- isVisibleOnStatusPage: true,
5243
- projectId: statusPage.projectId!,
5244
- },
5245
- select: {
5246
- _id: true,
5247
- monitors: {
5519
+ memberIncidents = this.keepMemberIncidentsInScope({
5520
+ incidents: await IncidentService.findBy({
5521
+ query: {
5522
+ _id: QueryHelper.any(memberIncidentIds),
5523
+ isVisibleOnStatusPage: true,
5524
+ projectId: statusPage.projectId!,
5525
+ },
5526
+ select: {
5248
5527
  _id: true,
5528
+ monitors: {
5529
+ _id: true,
5530
+ },
5531
+ ...INCIDENT_SCOPE_SELECT,
5249
5532
  },
5250
- },
5251
- skip: 0,
5252
- limit: LIMIT_PER_PROJECT,
5253
- props: {
5254
- isRoot: true,
5255
- },
5533
+ skip: 0,
5534
+ limit: LIMIT_PER_PROJECT,
5535
+ props: {
5536
+ isRoot: true,
5537
+ },
5538
+ }),
5539
+ statusPage: statusPage,
5256
5540
  });
5257
5541
  }
5258
5542
 
@@ -5608,39 +5892,47 @@ export default class StatusPageAPI extends BaseAPI<
5608
5892
  monitorsOnStatusPage.length > 0 &&
5609
5893
  statusPage.showIncidentsOnStatusPage
5610
5894
  ) {
5611
- timelineIncidents = await IncidentService.findBy({
5612
- query: {
5613
- monitors: monitorsOnStatusPage as any,
5614
- declaredAt: QueryHelper.inBetween(startDate, endDate),
5615
- isVisibleOnStatusPage: true,
5616
- projectId: statusPage.projectId!,
5617
- },
5618
- select: {
5619
- _id: true,
5620
- title: true,
5621
- declaredAt: true,
5622
- incidentSeverity: {
5623
- name: true,
5624
- color: true,
5895
+ /*
5896
+ * Only the incidents in scope for this page; see getIncidents. The
5897
+ * uptime bars stay as they are - a monitor's status is shared by every
5898
+ * page that lists it - but an incident limited to other pages is not
5899
+ * named in their tooltips.
5900
+ */
5901
+ timelineIncidents =
5902
+ await IncidentStatusPageScope.findIncidentsForStatusPage({
5903
+ statusPage: statusPage,
5904
+ query: {
5905
+ monitors: monitorsOnStatusPage as any,
5906
+ declaredAt: QueryHelper.inBetween(startDate, endDate),
5907
+ isVisibleOnStatusPage: true,
5908
+ projectId: statusPage.projectId!,
5625
5909
  },
5626
- currentIncidentState: {
5910
+ select: {
5627
5911
  _id: true,
5628
- name: true,
5629
- color: true,
5912
+ title: true,
5913
+ declaredAt: true,
5914
+ incidentSeverity: {
5915
+ name: true,
5916
+ color: true,
5917
+ },
5918
+ currentIncidentState: {
5919
+ _id: true,
5920
+ name: true,
5921
+ color: true,
5922
+ },
5923
+ monitors: {
5924
+ _id: true,
5925
+ },
5630
5926
  },
5631
- monitors: {
5632
- _id: true,
5927
+ sort: {
5928
+ declaredAt: SortOrder.Descending,
5633
5929
  },
5634
- },
5635
- sort: {
5636
- declaredAt: SortOrder.Descending,
5637
- },
5638
- skip: 0,
5639
- limit: LIMIT_PER_PROJECT,
5640
- props: {
5641
- isRoot: true,
5642
- },
5643
- });
5930
+ skip: 0,
5931
+ limit: LIMIT_PER_PROJECT,
5932
+ props: {
5933
+ isRoot: true,
5934
+ },
5935
+ });
5644
5936
  }
5645
5937
 
5646
5938
  const overallStatus: MonitorStatus | null =
@@ -5713,6 +6005,11 @@ export default class StatusPageAPI extends BaseAPI<
5713
6005
  StatusPage,
5714
6006
  );
5715
6007
  delete statusPageJson["projectId"];
6008
+ /*
6009
+ * Read only to decide which incidents the overview shows; the page
6010
+ * itself has no use for it.
6011
+ */
6012
+ delete statusPageJson["onlyShowScopedIncidents"];
5716
6013
  return statusPageJson;
5717
6014
  })(),
5718
6015
  scheduledMaintenanceStateTimelines: BaseModel.toJSONArray(
@@ -5724,7 +6021,8 @@ export default class StatusPageAPI extends BaseAPI<
5724
6021
  monitorGroupCurrentStatuses,
5725
6022
  ),
5726
6023
  monitorsInGroup: JSONFunctions.serialize(monitorsInGroup),
5727
- timelineIncidents: BaseModel.toJSONArray(timelineIncidents, Incident),
6024
+ timelineIncidents:
6025
+ this.serializeIncidentsForStatusPage(timelineIncidents),
5728
6026
  };
5729
6027
 
5730
6028
  return response;
@@ -5984,6 +6282,8 @@ export default class StatusPageAPI extends BaseAPI<
5984
6282
  _id: true,
5985
6283
  projectId: true,
5986
6284
  showIncidentsOnStatusPage: true,
6285
+ // Read by IncidentStatusPageScope to decide which incidents it shows.
6286
+ onlyShowScopedIncidents: true,
5987
6287
  },
5988
6288
  props: {
5989
6289
  isRoot: true,
@@ -6007,26 +6307,29 @@ export default class StatusPageAPI extends BaseAPI<
6007
6307
  throw new NotFoundException("Attachment not found");
6008
6308
  }
6009
6309
 
6010
- const incident: Incident | null = await IncidentService.findOneBy({
6011
- query: {
6012
- _id: incidentId.toString(),
6013
- projectId: statusPage.projectId!,
6014
- isVisibleOnStatusPage: true,
6015
- showPostmortemOnStatusPage: true,
6016
- monitors: monitorsOnStatusPage as any,
6017
- },
6018
- select: {
6019
- postmortemAttachments: {
6020
- _id: true,
6021
- file: true,
6022
- fileType: true,
6023
- name: true,
6310
+ // Only an incident this page shows: on its monitors and in its scope.
6311
+ const incident: Incident | null =
6312
+ await IncidentStatusPageScope.findOneIncidentForStatusPage({
6313
+ statusPage: statusPage,
6314
+ query: {
6315
+ _id: incidentId.toString(),
6316
+ projectId: statusPage.projectId!,
6317
+ isVisibleOnStatusPage: true,
6318
+ showPostmortemOnStatusPage: true,
6319
+ monitors: monitorsOnStatusPage as any,
6024
6320
  },
6025
- },
6026
- props: {
6027
- isRoot: true,
6028
- },
6029
- });
6321
+ select: {
6322
+ postmortemAttachments: {
6323
+ _id: true,
6324
+ file: true,
6325
+ fileType: true,
6326
+ name: true,
6327
+ },
6328
+ },
6329
+ props: {
6330
+ isRoot: true,
6331
+ },
6332
+ });
6030
6333
 
6031
6334
  if (!incident) {
6032
6335
  throw new NotFoundException("Attachment not found");
@@ -6096,6 +6399,8 @@ export default class StatusPageAPI extends BaseAPI<
6096
6399
  _id: true,
6097
6400
  projectId: true,
6098
6401
  showIncidentsOnStatusPage: true,
6402
+ // Read by IncidentStatusPageScope to decide which incidents it shows.
6403
+ onlyShowScopedIncidents: true,
6099
6404
  },
6100
6405
  props: {
6101
6406
  isRoot: true,
@@ -6119,20 +6424,23 @@ export default class StatusPageAPI extends BaseAPI<
6119
6424
  throw new NotFoundException("Attachment not found");
6120
6425
  }
6121
6426
 
6122
- const incident: Incident | null = await IncidentService.findOneBy({
6123
- query: {
6124
- _id: incidentId.toString(),
6125
- projectId: statusPage.projectId!,
6126
- isVisibleOnStatusPage: true,
6127
- monitors: monitorsOnStatusPage as any,
6128
- },
6129
- select: {
6130
- _id: true,
6131
- },
6132
- props: {
6133
- isRoot: true,
6134
- },
6135
- });
6427
+ // Only an incident this page shows: on its monitors and in its scope.
6428
+ const incident: Incident | null =
6429
+ await IncidentStatusPageScope.findOneIncidentForStatusPage({
6430
+ statusPage: statusPage,
6431
+ query: {
6432
+ _id: incidentId.toString(),
6433
+ projectId: statusPage.projectId!,
6434
+ isVisibleOnStatusPage: true,
6435
+ monitors: monitorsOnStatusPage as any,
6436
+ },
6437
+ select: {
6438
+ _id: true,
6439
+ },
6440
+ props: {
6441
+ isRoot: true,
6442
+ },
6443
+ });
6136
6444
 
6137
6445
  if (!incident) {
6138
6446
  throw new NotFoundException("Attachment not found");
@@ -6221,6 +6529,8 @@ export default class StatusPageAPI extends BaseAPI<
6221
6529
  _id: true,
6222
6530
  projectId: true,
6223
6531
  showEpisodesOnStatusPage: true,
6532
+ // Read by IncidentStatusPageScope to decide which incidents it shows.
6533
+ onlyShowScopedIncidents: true,
6224
6534
  },
6225
6535
  props: {
6226
6536
  isRoot: true,
@@ -6273,21 +6583,27 @@ export default class StatusPageAPI extends BaseAPI<
6273
6583
  return Boolean(id);
6274
6584
  });
6275
6585
 
6276
- // Check if any of the incidents are linked to monitors on the status page
6277
- const incident: Incident | null = await IncidentService.findOneBy({
6278
- query: {
6279
- _id: QueryHelper.any(incidentIds),
6280
- projectId: statusPage.projectId!,
6281
- isVisibleOnStatusPage: true,
6282
- monitors: monitorsOnStatusPage as any,
6283
- },
6284
- select: {
6285
- _id: true,
6286
- },
6287
- props: {
6288
- isRoot: true,
6289
- },
6290
- });
6586
+ /*
6587
+ * Check if any of the incidents are linked to monitors on the status
6588
+ * page, and in scope for it: an episode whose incidents are all limited
6589
+ * to other status pages is not this page's to serve.
6590
+ */
6591
+ const incident: Incident | null =
6592
+ await IncidentStatusPageScope.findOneIncidentForStatusPage({
6593
+ statusPage: statusPage,
6594
+ query: {
6595
+ _id: QueryHelper.any(incidentIds),
6596
+ projectId: statusPage.projectId!,
6597
+ isVisibleOnStatusPage: true,
6598
+ monitors: monitorsOnStatusPage as any,
6599
+ },
6600
+ select: {
6601
+ _id: true,
6602
+ },
6603
+ props: {
6604
+ isRoot: true,
6605
+ },
6606
+ });
6291
6607
 
6292
6608
  if (!incident) {
6293
6609
  throw new NotFoundException("Attachment not found");