@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
@@ -0,0 +1,395 @@
1
+ import { AddIncidentStatusPageScope1795400000000 } from "../../../../Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope";
2
+ import SchemaMigrations from "../../../../Server/Infrastructure/Postgres/SchemaMigrations/Index";
3
+ import Incident from "../../../../Models/DatabaseModels/Incident";
4
+ import IncidentTemplate from "../../../../Models/DatabaseModels/IncidentTemplate";
5
+ import StatusPage from "../../../../Models/DatabaseModels/StatusPage";
6
+ import ColumnType from "../../../../Types/Database/ColumnType";
7
+ import { describe, expect, test } from "@jest/globals";
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { QueryRunner, getMetadataArgsStorage } from "typeorm";
11
+ import type { ColumnMetadataArgs } from "typeorm/metadata-args/ColumnMetadataArgs";
12
+ import type { JoinTableMetadataArgs } from "typeorm/metadata-args/JoinTableMetadataArgs";
13
+
14
+ /*
15
+ * The schema half of per-incident status page scope: the incident and
16
+ * incident template join tables to StatusPage, the incident's derived scope
17
+ * flag and record of notified pages, and the status page's "only show scoped
18
+ * incidents" switch.
19
+ *
20
+ * It pins that the migration matches the models (a mismatch is a green deploy
21
+ * followed by a red Schema Drift job), that the join rows cascade with either
22
+ * side - which is what keeps a deleted status page from widening an
23
+ * incident's reach, together with the flag that nothing recomputes - that
24
+ * every existing incident and page keeps today's behaviour without a
25
+ * backfill, and that down() undoes up().
26
+ *
27
+ * Fake QueryRunner only. Generating the migration against a database with
28
+ * every registered migration applied, applying it, and generating again to
29
+ * "No changes in database schema were found" is how it was verified;
30
+ * IncidentStatusPageScopePostgres exercises it against a real database.
31
+ */
32
+
33
+ const OWN_CLASS_NAME: string = "AddIncidentStatusPageScope1795400000000";
34
+
35
+ const MIGRATION_PATH: string = path.join(
36
+ __dirname,
37
+ "..",
38
+ "..",
39
+ "..",
40
+ "..",
41
+ "Server",
42
+ "Infrastructure",
43
+ "Postgres",
44
+ "SchemaMigrations",
45
+ "1795400000000-AddIncidentStatusPageScope.ts",
46
+ );
47
+
48
+ function timestampOfClassName(className: string): number | null {
49
+ const match: RegExpMatchArray | null = className.match(/(\d{13})$/);
50
+ return match ? Number(match[1]) : null;
51
+ }
52
+
53
+ const registeredNames: Array<string> = (
54
+ SchemaMigrations as unknown as Array<{ name: string }>
55
+ ).map((migration: { name: string }): string => {
56
+ return migration.name;
57
+ });
58
+
59
+ async function recordQueries(direction: "up" | "down"): Promise<Array<string>> {
60
+ const statements: Array<string> = [];
61
+
62
+ const queryRunner: QueryRunner = {
63
+ query: async (statement: string): Promise<void> => {
64
+ statements.push(statement);
65
+ },
66
+ } as unknown as QueryRunner;
67
+
68
+ await new AddIncidentStatusPageScope1795400000000()[direction](queryRunner);
69
+
70
+ return statements;
71
+ }
72
+
73
+ function declaredColumn(
74
+ target: new () => unknown,
75
+ property: string,
76
+ ): ColumnMetadataArgs {
77
+ const declared: ColumnMetadataArgs | undefined =
78
+ getMetadataArgsStorage().columns.find(
79
+ (column: ColumnMetadataArgs): boolean => {
80
+ return column.target === target && column.propertyName === property;
81
+ },
82
+ );
83
+
84
+ if (!declared) {
85
+ throw new Error(`${String(target)} declares no column ${property}`);
86
+ }
87
+
88
+ return declared;
89
+ }
90
+
91
+ function declaredJoinTable(
92
+ target: new () => unknown,
93
+ property: string,
94
+ ): JoinTableMetadataArgs {
95
+ const declared: JoinTableMetadataArgs | undefined =
96
+ getMetadataArgsStorage().joinTables.find(
97
+ (joinTable: JoinTableMetadataArgs): boolean => {
98
+ return (
99
+ joinTable.target === target && joinTable.propertyName === property
100
+ );
101
+ },
102
+ );
103
+
104
+ if (!declared) {
105
+ throw new Error(`${String(target)} declares no join table ${property}`);
106
+ }
107
+
108
+ return declared;
109
+ }
110
+
111
+ function statementsMatching(
112
+ statements: Array<string>,
113
+ pattern: RegExp,
114
+ ): Array<string> {
115
+ return statements.filter((statement: string): boolean => {
116
+ return pattern.test(statement);
117
+ });
118
+ }
119
+
120
+ describe("AddIncidentStatusPageScope migration - identity and registration", () => {
121
+ test("lives at its round stamp, with a class and name that carry it", () => {
122
+ const source: string = fs.readFileSync(MIGRATION_PATH, "utf8");
123
+
124
+ expect(source).toContain(`export class ${OWN_CLASS_NAME}`);
125
+ expect(source).toContain(`public name: string = "${OWN_CLASS_NAME}";`);
126
+ expect(new AddIncidentStatusPageScope1795400000000().name).toBe(
127
+ OWN_CLASS_NAME,
128
+ );
129
+ });
130
+
131
+ test("is registered, so the schema actually reaches every database", () => {
132
+ expect(registeredNames).toContain(OWN_CLASS_NAME);
133
+ });
134
+
135
+ test("its timestamp keeps it behind every migration registered before it", () => {
136
+ const ownIndex: number = registeredNames.indexOf(OWN_CLASS_NAME);
137
+
138
+ expect(ownIndex).toBeGreaterThan(0);
139
+
140
+ const ownTimestamp: number = timestampOfClassName(OWN_CLASS_NAME)!;
141
+
142
+ const notBehind: Array<string> = registeredNames
143
+ .slice(0, ownIndex)
144
+ .filter((className: string): boolean => {
145
+ const timestamp: number | null = timestampOfClassName(className);
146
+ return timestamp !== null && timestamp >= ownTimestamp;
147
+ });
148
+
149
+ expect(notBehind).toEqual([]);
150
+ });
151
+ });
152
+
153
+ describe("AddIncidentStatusPageScope migration - up()", () => {
154
+ test.each([
155
+ {
156
+ model: Incident,
157
+ property: "statusPages",
158
+ table: "IncidentStatusPage",
159
+ ownerColumn: "incidentId",
160
+ ownerTable: "Incident",
161
+ },
162
+ {
163
+ model: IncidentTemplate,
164
+ property: "statusPages",
165
+ table: "IncidentTemplateStatusPage",
166
+ ownerColumn: "incidentTemplateId",
167
+ ownerTable: "IncidentTemplate",
168
+ },
169
+ ])(
170
+ "creates $table as the model declares it, cascading with either side",
171
+ async ({
172
+ model,
173
+ property,
174
+ table,
175
+ ownerColumn,
176
+ ownerTable,
177
+ }: {
178
+ model: new () => unknown;
179
+ property: string;
180
+ table: string;
181
+ ownerColumn: string;
182
+ ownerTable: string;
183
+ }) => {
184
+ const joinTable: JoinTableMetadataArgs = declaredJoinTable(
185
+ model,
186
+ property,
187
+ );
188
+
189
+ expect(joinTable.name).toBe(table);
190
+ expect(
191
+ (joinTable.joinColumns as Array<{ name: string }> | undefined)?.[0]
192
+ ?.name,
193
+ ).toBe(ownerColumn);
194
+ expect(
195
+ (
196
+ joinTable.inverseJoinColumns as Array<{ name: string }> | undefined
197
+ )?.[0]?.name,
198
+ ).toBe("statusPageId");
199
+
200
+ const statements: Array<string> = await recordQueries("up");
201
+
202
+ const created: Array<string> = statementsMatching(
203
+ statements,
204
+ new RegExp(`^CREATE TABLE "${table}" `),
205
+ );
206
+
207
+ expect(created).toHaveLength(1);
208
+ expect(created[0]).toContain(
209
+ `("${ownerColumn}" uuid NOT NULL, "statusPageId" uuid NOT NULL, CONSTRAINT`,
210
+ );
211
+ expect(created[0]).toContain(
212
+ `PRIMARY KEY ("${ownerColumn}", "statusPageId")`,
213
+ );
214
+
215
+ // Both sides are indexed for the lookups each direction makes.
216
+ for (const column of [ownerColumn, "statusPageId"]) {
217
+ expect(
218
+ statementsMatching(
219
+ statements,
220
+ new RegExp(
221
+ `^CREATE INDEX "IDX_\\w+" ON "${table}" \\("${column}"\\)`,
222
+ ),
223
+ ),
224
+ ).toHaveLength(1);
225
+ }
226
+
227
+ const foreignKeys: Array<string> = statementsMatching(
228
+ statements,
229
+ new RegExp(
230
+ `^ALTER TABLE "${table}" ADD CONSTRAINT "FK_\\w+" FOREIGN KEY`,
231
+ ),
232
+ );
233
+
234
+ expect(foreignKeys).toHaveLength(2);
235
+ expect(
236
+ foreignKeys.some((statement: string): boolean => {
237
+ return statement.includes(
238
+ `FOREIGN KEY ("${ownerColumn}") REFERENCES "${ownerTable}"("_id") ON DELETE CASCADE`,
239
+ );
240
+ }),
241
+ ).toBe(true);
242
+ /*
243
+ * Deleting a status page takes its scope rows with it. The incident
244
+ * keeps isScopedToStatusPages, so it is hidden rather than widened.
245
+ */
246
+ expect(
247
+ foreignKeys.some((statement: string): boolean => {
248
+ return statement.includes(
249
+ `FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE`,
250
+ );
251
+ }),
252
+ ).toBe(true);
253
+ },
254
+ );
255
+
256
+ test("adds the three columns as the models declare them, keeping today's behaviour without a backfill", async () => {
257
+ const statements: Array<string> = await recordQueries("up");
258
+
259
+ expect(statementsMatching(statements, /^ALTER TABLE "\w+" ADD "/)).toEqual([
260
+ `ALTER TABLE "StatusPage" ADD "onlyShowScopedIncidents" boolean NOT NULL DEFAULT false`,
261
+ `ALTER TABLE "Incident" ADD "isScopedToStatusPages" boolean NOT NULL DEFAULT false`,
262
+ `ALTER TABLE "Incident" ADD "statusPagesNotifiedOnCreation" jsonb`,
263
+ ]);
264
+
265
+ for (const [model, property] of [
266
+ [StatusPage, "onlyShowScopedIncidents"],
267
+ [Incident, "isScopedToStatusPages"],
268
+ ] as Array<[new () => unknown, string]>) {
269
+ const declared: ColumnMetadataArgs = declaredColumn(model, property);
270
+
271
+ expect(declared.options.type).toBe(ColumnType.Boolean);
272
+ expect(declared.options.nullable).toBe(false);
273
+ expect(declared.options.default).toBe(false);
274
+ }
275
+
276
+ const record: ColumnMetadataArgs = declaredColumn(
277
+ Incident,
278
+ "statusPagesNotifiedOnCreation",
279
+ );
280
+
281
+ expect(record.options.type).toBe(ColumnType.JSON);
282
+ expect(record.options.nullable).toBe(true);
283
+
284
+ // Every existing incident is unscoped and every page shows them: no UPDATE.
285
+ expect(statementsMatching(statements, /^(UPDATE|INSERT|DELETE)/)).toEqual(
286
+ [],
287
+ );
288
+ });
289
+
290
+ /*
291
+ * The migration alters Incident, so an index built on it in the same
292
+ * transaction holds that ALTER's lock - which blocks reads - for the whole
293
+ * build over the table. The flag is false on nearly every row, so an index
294
+ * could not help the queries that split on it anyway.
295
+ */
296
+ test("builds no index on Incident, which it alters in the same transaction", async () => {
297
+ expect(
298
+ statementsMatching(
299
+ await recordQueries("up"),
300
+ /^CREATE INDEX "IDX_\w+" ON "Incident"/,
301
+ ),
302
+ ).toEqual([]);
303
+ expect(
304
+ getMetadataArgsStorage().indices.filter(
305
+ (index: { target: unknown; columns?: unknown }): boolean => {
306
+ return (
307
+ index.target === Incident &&
308
+ JSON.stringify(index.columns || []).includes(
309
+ "isScopedToStatusPages",
310
+ )
311
+ );
312
+ },
313
+ ),
314
+ ).toEqual([]);
315
+ });
316
+
317
+ test("touches no other table", async () => {
318
+ const tables: Set<string> = new Set();
319
+
320
+ for (const statement of await recordQueries("up")) {
321
+ const match: RegExpMatchArray | null = statement.match(
322
+ /^(?:CREATE TABLE|ALTER TABLE|CREATE INDEX "\w+" ON) "(\w+)"/,
323
+ );
324
+
325
+ if (match) {
326
+ tables.add(match[1]!);
327
+ }
328
+ }
329
+
330
+ expect([...tables].sort()).toEqual(
331
+ [
332
+ "Incident",
333
+ "IncidentStatusPage",
334
+ "IncidentTemplateStatusPage",
335
+ "StatusPage",
336
+ ].sort(),
337
+ );
338
+ });
339
+ });
340
+
341
+ describe("AddIncidentStatusPageScope migration - down()", () => {
342
+ test("drops every table, column, index and constraint up() created", async () => {
343
+ const up: Array<string> = await recordQueries("up");
344
+ const down: Array<string> = await recordQueries("down");
345
+
346
+ const createdNames: Array<string> = up
347
+ .map((statement: string): string | null => {
348
+ const constraint: RegExpMatchArray | null = statement.match(
349
+ /ADD CONSTRAINT "(\w+)"/,
350
+ );
351
+ const index: RegExpMatchArray | null = statement.match(
352
+ /^CREATE INDEX "(\w+)"/,
353
+ );
354
+ const table: RegExpMatchArray | null = statement.match(
355
+ /^CREATE TABLE "(\w+)"/,
356
+ );
357
+ const column: RegExpMatchArray | null = statement.match(
358
+ /^ALTER TABLE "\w+" ADD "(\w+)"/,
359
+ );
360
+
361
+ return (
362
+ constraint?.[1] || index?.[1] || table?.[1] || column?.[1] || null
363
+ );
364
+ })
365
+ .filter((name: string | null): name is string => {
366
+ return name !== null;
367
+ });
368
+
369
+ for (const name of createdNames) {
370
+ expect(
371
+ down.some((statement: string): boolean => {
372
+ return statement.includes(`"${name}"`);
373
+ }),
374
+ ).toBe(true);
375
+ }
376
+
377
+ // Constraints go before the tables and columns they depend on.
378
+ const lastConstraintDrop: number = Math.max(
379
+ ...down
380
+ .map((statement: string, index: number): number => {
381
+ return statement.includes("DROP CONSTRAINT") ? index : -1;
382
+ })
383
+ .filter((index: number): boolean => {
384
+ return index >= 0;
385
+ }),
386
+ );
387
+ const firstTableDrop: number = down.findIndex(
388
+ (statement: string): boolean => {
389
+ return statement.startsWith("DROP TABLE");
390
+ },
391
+ );
392
+
393
+ expect(lastConstraintDrop).toBeLessThan(firstTableDrop);
394
+ });
395
+ });
@@ -0,0 +1,150 @@
1
+ import { AddIncidentTemplateStatusPageScopeFlag1795500000000 } from "../../../../Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag";
2
+ import SchemaMigrations from "../../../../Server/Infrastructure/Postgres/SchemaMigrations/Index";
3
+ import IncidentTemplate from "../../../../Models/DatabaseModels/IncidentTemplate";
4
+ import ColumnType from "../../../../Types/Database/ColumnType";
5
+ import { describe, expect, test } from "@jest/globals";
6
+ import fs from "fs";
7
+ import path from "path";
8
+ import { QueryRunner, getMetadataArgsStorage } from "typeorm";
9
+ import type { ColumnMetadataArgs } from "typeorm/metadata-args/ColumnMetadataArgs";
10
+
11
+ /*
12
+ * IncidentTemplate.isScopedToStatusPages: whether a template limits the
13
+ * incidents declared from it to status pages, kept apart from its list of
14
+ * pages so a template whose pages were all deleted does not start declaring
15
+ * incidents that reach every status page.
16
+ *
17
+ * It pins that the column matches the model (a mismatch is a red Schema Drift
18
+ * job), that templates already listing status pages come out scoped and no
19
+ * other template changes, that down() undoes up(), and that the migration is
20
+ * registered after the ones before it. Fake QueryRunner only; generating the
21
+ * migration against a fully migrated database, applying it and generating
22
+ * again to "No changes" is how the schema half was verified.
23
+ */
24
+
25
+ const OWN_CLASS_NAME: string =
26
+ "AddIncidentTemplateStatusPageScopeFlag1795500000000";
27
+
28
+ const MIGRATION_PATH: string = path.join(
29
+ __dirname,
30
+ "..",
31
+ "..",
32
+ "..",
33
+ "..",
34
+ "Server",
35
+ "Infrastructure",
36
+ "Postgres",
37
+ "SchemaMigrations",
38
+ "1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts",
39
+ );
40
+
41
+ const registeredNames: Array<string> = (
42
+ SchemaMigrations as unknown as Array<{ name: string }>
43
+ ).map((migration: { name: string }): string => {
44
+ return migration.name;
45
+ });
46
+
47
+ function timestampOfClassName(className: string): number | null {
48
+ const match: RegExpMatchArray | null = className.match(/(\d{13})$/);
49
+ return match ? Number(match[1]) : null;
50
+ }
51
+
52
+ async function recordQueries(direction: "up" | "down"): Promise<Array<string>> {
53
+ const statements: Array<string> = [];
54
+
55
+ const queryRunner: QueryRunner = {
56
+ query: async (statement: string): Promise<void> => {
57
+ statements.push(statement);
58
+ },
59
+ } as unknown as QueryRunner;
60
+
61
+ await new AddIncidentTemplateStatusPageScopeFlag1795500000000()[direction](
62
+ queryRunner,
63
+ );
64
+
65
+ return statements;
66
+ }
67
+
68
+ describe("AddIncidentTemplateStatusPageScopeFlag migration - identity and registration", () => {
69
+ test("lives at its round stamp, with a class and name that carry it", () => {
70
+ const source: string = fs.readFileSync(MIGRATION_PATH, "utf8");
71
+
72
+ expect(source).toContain(`export class ${OWN_CLASS_NAME}`);
73
+ expect(source).toContain(`public name: string = "${OWN_CLASS_NAME}";`);
74
+ expect(new AddIncidentTemplateStatusPageScopeFlag1795500000000().name).toBe(
75
+ OWN_CLASS_NAME,
76
+ );
77
+ });
78
+
79
+ test("is registered once, right after the incident scope migration", () => {
80
+ expect(
81
+ registeredNames.filter((name: string): boolean => {
82
+ return name === OWN_CLASS_NAME;
83
+ }),
84
+ ).toHaveLength(1);
85
+
86
+ expect(registeredNames.indexOf(OWN_CLASS_NAME)).toBe(
87
+ registeredNames.indexOf("AddIncidentStatusPageScope1795400000000") + 1,
88
+ );
89
+ });
90
+
91
+ test("its timestamp keeps it behind every migration registered before it", () => {
92
+ const ownIndex: number = registeredNames.indexOf(OWN_CLASS_NAME);
93
+ const ownTimestamp: number = timestampOfClassName(OWN_CLASS_NAME)!;
94
+
95
+ const notBehind: Array<string> = registeredNames
96
+ .slice(0, ownIndex)
97
+ .filter((className: string): boolean => {
98
+ const timestamp: number | null = timestampOfClassName(className);
99
+ return timestamp !== null && timestamp >= ownTimestamp;
100
+ });
101
+
102
+ expect(notBehind).toEqual([]);
103
+ });
104
+ });
105
+
106
+ describe("AddIncidentTemplateStatusPageScopeFlag migration - up() and down()", () => {
107
+ test("adds the column as the model declares it", async () => {
108
+ const declared: ColumnMetadataArgs | undefined =
109
+ getMetadataArgsStorage().columns.find(
110
+ (column: ColumnMetadataArgs): boolean => {
111
+ return (
112
+ column.target === IncidentTemplate &&
113
+ column.propertyName === "isScopedToStatusPages"
114
+ );
115
+ },
116
+ );
117
+
118
+ expect(declared?.options.type).toBe(ColumnType.Boolean);
119
+ expect(declared?.options.nullable).toBe(false);
120
+ expect(declared?.options.default).toBe(false);
121
+
122
+ const statements: Array<string> = await recordQueries("up");
123
+
124
+ expect(statements[0]).toBe(
125
+ `ALTER TABLE "IncidentTemplate" ADD "isScopedToStatusPages" boolean NOT NULL DEFAULT false`,
126
+ );
127
+ });
128
+
129
+ test("scopes the templates that already list status pages, and only those", async () => {
130
+ const statements: Array<string> = await recordQueries("up");
131
+
132
+ expect(statements).toHaveLength(2);
133
+ expect(statements[1]).toBe(
134
+ `UPDATE "IncidentTemplate" SET "isScopedToStatusPages" = true WHERE "_id" IN (SELECT "incidentTemplateId" FROM "IncidentTemplateStatusPage")`,
135
+ );
136
+ });
137
+
138
+ test("touches no other table", async () => {
139
+ for (const statement of await recordQueries("up")) {
140
+ expect(statement).not.toMatch(/"Incident"\s/);
141
+ expect(statement).not.toMatch(/ALTER TABLE "(?!IncidentTemplate")/);
142
+ }
143
+ });
144
+
145
+ test("down() drops the column", async () => {
146
+ expect(await recordQueries("down")).toEqual([
147
+ `ALTER TABLE "IncidentTemplate" DROP COLUMN "isScopedToStatusPages"`,
148
+ ]);
149
+ });
150
+ });
@@ -0,0 +1,156 @@
1
+ import { AddSubscriberNotificationClaimedAt1795900000000 } from "../../../../Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt";
2
+ import SchemaMigrations from "../../../../Server/Infrastructure/Postgres/SchemaMigrations/Index";
3
+ import Incident from "../../../../Models/DatabaseModels/Incident";
4
+ import IncidentEpisode from "../../../../Models/DatabaseModels/IncidentEpisode";
5
+ import ColumnType from "../../../../Types/Database/ColumnType";
6
+ import { describe, expect, test } from "@jest/globals";
7
+ import fs from "fs";
8
+ import path from "path";
9
+ import { QueryRunner, getMetadataArgsStorage } from "typeorm";
10
+ import type { ColumnMetadataArgs } from "typeorm/metadata-args/ColumnMetadataArgs";
11
+
12
+ /*
13
+ * When a subscriber job claimed the incident created, postmortem and episode
14
+ * created notifications, for the sweeper to time an interrupted send from
15
+ * instead of updatedAt, which other code keeps moving on an open incident or
16
+ * episode.
17
+ *
18
+ * It pins that the columns match the models (a mismatch is a red Schema
19
+ * Drift job), that they are nullable with no default - adding them rewrites
20
+ * nothing, and a row claimed before they existed is timed from updatedAt -
21
+ * that down() undoes up(), and that the migration is registered last. Fake
22
+ * QueryRunner only; generating the migration against a fully migrated
23
+ * database, applying it and generating again to "No changes" is how the
24
+ * schema was verified.
25
+ */
26
+
27
+ const OWN_CLASS_NAME: string =
28
+ "AddSubscriberNotificationClaimedAt1795900000000";
29
+
30
+ const MIGRATION_PATH: string = path.join(
31
+ __dirname,
32
+ "..",
33
+ "..",
34
+ "..",
35
+ "..",
36
+ "Server",
37
+ "Infrastructure",
38
+ "Postgres",
39
+ "SchemaMigrations",
40
+ "1795900000000-AddSubscriberNotificationClaimedAt.ts",
41
+ );
42
+
43
+ const COLUMNS: Array<{ table: string; model: unknown; column: string }> = [
44
+ {
45
+ table: "Incident",
46
+ model: Incident,
47
+ column: "subscriberNotificationClaimedAtOnIncidentCreated",
48
+ },
49
+ {
50
+ table: "Incident",
51
+ model: Incident,
52
+ column: "subscriberNotificationClaimedAtOnPostmortemPublished",
53
+ },
54
+ {
55
+ table: "IncidentEpisode",
56
+ model: IncidentEpisode,
57
+ column: "subscriberNotificationClaimedAtOnEpisodeCreated",
58
+ },
59
+ ];
60
+
61
+ const registeredNames: Array<string> = (
62
+ SchemaMigrations as unknown as Array<{ name: string }>
63
+ ).map((migration: { name: string }): string => {
64
+ return migration.name;
65
+ });
66
+
67
+ function timestampOfClassName(className: string): number | null {
68
+ const match: RegExpMatchArray | null = className.match(/(\d{13})$/);
69
+ return match ? Number(match[1]) : null;
70
+ }
71
+
72
+ async function recordQueries(direction: "up" | "down"): Promise<Array<string>> {
73
+ const statements: Array<string> = [];
74
+
75
+ const queryRunner: QueryRunner = {
76
+ query: async (statement: string): Promise<void> => {
77
+ statements.push(statement);
78
+ },
79
+ } as unknown as QueryRunner;
80
+
81
+ await new AddSubscriberNotificationClaimedAt1795900000000()[direction](
82
+ queryRunner,
83
+ );
84
+
85
+ return statements;
86
+ }
87
+
88
+ describe("AddSubscriberNotificationClaimedAt migration - identity and registration", () => {
89
+ test("lives at its round stamp, with a class and name that carry it", () => {
90
+ const source: string = fs.readFileSync(MIGRATION_PATH, "utf8");
91
+
92
+ expect(source).toContain(`export class ${OWN_CLASS_NAME}`);
93
+ expect(source).toContain(`public name: string = "${OWN_CLASS_NAME}";`);
94
+ expect(new AddSubscriberNotificationClaimedAt1795900000000().name).toBe(
95
+ OWN_CLASS_NAME,
96
+ );
97
+ });
98
+
99
+ test("is registered once, behind every migration registered before it", () => {
100
+ expect(
101
+ registeredNames.filter((name: string): boolean => {
102
+ return name === OWN_CLASS_NAME;
103
+ }),
104
+ ).toHaveLength(1);
105
+
106
+ const ownIndex: number = registeredNames.indexOf(OWN_CLASS_NAME);
107
+ const ownTimestamp: number = timestampOfClassName(OWN_CLASS_NAME)!;
108
+
109
+ expect(
110
+ registeredNames.slice(0, ownIndex).filter((className: string) => {
111
+ const timestamp: number | null = timestampOfClassName(className);
112
+ return timestamp !== null && timestamp >= ownTimestamp;
113
+ }),
114
+ ).toEqual([]);
115
+ });
116
+ });
117
+
118
+ describe("AddSubscriberNotificationClaimedAt migration - up() and down()", () => {
119
+ test.each(COLUMNS)(
120
+ "adds $table.$column as the model declares it: a nullable timestamp with no default",
121
+ async (entry: { table: string; model: unknown; column: string }) => {
122
+ const declared: ColumnMetadataArgs | undefined =
123
+ getMetadataArgsStorage().columns.find(
124
+ (column: ColumnMetadataArgs): boolean => {
125
+ return (
126
+ column.target === entry.model &&
127
+ column.propertyName === entry.column
128
+ );
129
+ },
130
+ );
131
+
132
+ expect(declared?.options.type).toBe(ColumnType.Date);
133
+ expect(declared?.options.nullable).toBe(true);
134
+ expect(declared?.options.default).toBeUndefined();
135
+
136
+ expect(await recordQueries("up")).toContain(
137
+ `ALTER TABLE "${entry.table}" ADD "${entry.column}" TIMESTAMP WITH TIME ZONE`,
138
+ );
139
+ expect(await recordQueries("down")).toContain(
140
+ `ALTER TABLE "${entry.table}" DROP COLUMN "${entry.column}"`,
141
+ );
142
+ },
143
+ );
144
+
145
+ test("adds nothing else: no default, no index, no backfill", async () => {
146
+ const up: Array<string> = await recordQueries("up");
147
+
148
+ expect(up).toHaveLength(COLUMNS.length);
149
+ for (const statement of up) {
150
+ expect(statement).toMatch(
151
+ /^ALTER TABLE "\w+" ADD "\w+" TIMESTAMP WITH TIME ZONE$/,
152
+ );
153
+ }
154
+ expect(await recordQueries("down")).toHaveLength(COLUMNS.length);
155
+ });
156
+ });