@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,528 @@
1
+ import Entities from "../../../../Models/DatabaseModels/Index";
2
+ import Incident from "../../../../Models/DatabaseModels/Incident";
3
+ import IncidentPublicNote from "../../../../Models/DatabaseModels/IncidentPublicNote";
4
+ import PostgresAppInstance from "../../../../Server/Infrastructure/PostgresDatabase";
5
+ import DatabaseService from "../../../../Server/Services/DatabaseService";
6
+ import QueryHelper from "../../../../Server/Types/Database/QueryHelper";
7
+ import logger from "../../../../Server/Utils/Logger";
8
+ import SubscriberNotificationClaim from "../../../../Server/Utils/StatusPage/SubscriberNotificationClaim";
9
+ import ObjectID from "../../../../Types/ObjectID";
10
+ import StatusPageSubscriberNotificationStatus from "../../../../Types/StatusPage/StatusPageSubscriberNotificationStatus";
11
+ import SubscriberNotificationInterruption from "../../../../Types/StatusPage/SubscriberNotificationInterruption";
12
+ import { DataSource } from "typeorm";
13
+
14
+ /*
15
+ * The subscriber notification claim and the interrupted-send write against a
16
+ * real Postgres. Both are one compare-and-set UPDATE that must also SAY
17
+ * whether it wrote: a claim reported won when it was lost is a notification
18
+ * sent twice, and one reported lost when it was won is a notification never
19
+ * sent and stuck In progress. TypeORM's postgres runner answers a bare
20
+ * UPDATE with [rows, rowCount] whether or not anything matched, and the unit
21
+ * suites fake `manager.query`, so only a real driver shows the answer is
22
+ * right. It also writes, raw, the incident created notification's record of
23
+ * the status pages told so far: a JSON column, which must round-trip.
24
+ *
25
+ * Opt in with RUN_POSTGRES_SUBSCRIBER_NOTIFICATION_CLAIM_TESTS=true against
26
+ * a Postgres migrated to the current head - the Postgres Schema Drift
27
+ * workflow's database right after its drift check. The IncidentPublicNote
28
+ * and Incident tables' STRUCTURE is cloned into a unique schema
29
+ * (search_path holds only that schema) that is dropped afterwards.
30
+ * Credentials from DATABASE_USERNAME / DATABASE_PASSWORD, database from
31
+ * SUBSCRIBER_NOTIFICATION_CLAIM_TEST_DATABASE_NAME or DATABASE_NAME,
32
+ * endpoint from SUBSCRIBER_NOTIFICATION_CLAIM_TEST_DATABASE_HOST / _PORT
33
+ * (default localhost:5400, Scripts/Dev/docker-compose.dev.yml).
34
+ */
35
+ const describePostgres: typeof describe =
36
+ process.env["RUN_POSTGRES_SUBSCRIBER_NOTIFICATION_CLAIM_TESTS"] === "true"
37
+ ? describe
38
+ : describe.skip;
39
+
40
+ describePostgres("subscriber notification claims against Postgres", () => {
41
+ const schema: string = `subscriber_claim_${ObjectID.generate()
42
+ .toString()
43
+ .replace(/-/g, "")}`;
44
+ let database: DataSource;
45
+ let noteService: DatabaseService<IncidentPublicNote>;
46
+ let incidentService: DatabaseService<Incident>;
47
+
48
+ async function cloneTable(table: string): Promise<void> {
49
+ await database.query(
50
+ `CREATE TABLE "${schema}"."${table}" (LIKE public."${table}" INCLUDING ALL)`,
51
+ );
52
+ // A fixture names only what the statements touch; the key stays NOT NULL.
53
+ const columns: Array<{ column_name: string }> = await database.query(
54
+ `SELECT column_name FROM information_schema.columns
55
+ WHERE table_schema = $1 AND table_name = $2 AND is_nullable = 'NO' AND column_name <> '_id'`,
56
+ [schema, table],
57
+ );
58
+ for (const column of columns) {
59
+ await database.query(
60
+ `ALTER TABLE "${schema}"."${table}" ALTER COLUMN "${column.column_name}" DROP NOT NULL`,
61
+ );
62
+ }
63
+ }
64
+
65
+ beforeAll(async () => {
66
+ database = new DataSource({
67
+ type: "postgres",
68
+ host:
69
+ process.env["SUBSCRIBER_NOTIFICATION_CLAIM_TEST_DATABASE_HOST"] ||
70
+ "localhost",
71
+ port: Number(
72
+ process.env["SUBSCRIBER_NOTIFICATION_CLAIM_TEST_DATABASE_PORT"] ||
73
+ "5400",
74
+ ),
75
+ username: process.env["DATABASE_USERNAME"] || "postgres",
76
+ password: process.env["DATABASE_PASSWORD"] || "password",
77
+ database:
78
+ process.env["SUBSCRIBER_NOTIFICATION_CLAIM_TEST_DATABASE_NAME"] ||
79
+ process.env["DATABASE_NAME"] ||
80
+ "oneuptimedb",
81
+ entities: Entities,
82
+ schema,
83
+ synchronize: false,
84
+ extra: {
85
+ options: `-c search_path=${schema}`,
86
+ },
87
+ });
88
+ await database.initialize();
89
+ await database.query(`CREATE SCHEMA "${schema}"`);
90
+ await cloneTable("IncidentPublicNote");
91
+ await cloneTable("Incident");
92
+ expect(
93
+ (await database.query("SELECT current_schema()"))[0].current_schema,
94
+ ).toBe(schema);
95
+ });
96
+
97
+ afterAll(async () => {
98
+ if (database?.isInitialized) {
99
+ await database.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
100
+ await database.destroy();
101
+ }
102
+ });
103
+
104
+ beforeEach(async () => {
105
+ for (const level of ["debug", "info", "warn"] as const) {
106
+ jest.spyOn(logger, level).mockImplementation(() => {
107
+ return undefined as never;
108
+ });
109
+ }
110
+ jest.spyOn(PostgresAppInstance, "isConnected").mockReturnValue(true);
111
+ jest.spyOn(PostgresAppInstance, "getDataSource").mockReturnValue(database);
112
+
113
+ noteService = new DatabaseService<IncidentPublicNote>(IncidentPublicNote);
114
+ incidentService = new DatabaseService<Incident>(Incident);
115
+ await database.query(`DELETE FROM "${schema}"."IncidentPublicNote"`);
116
+ await database.query(`DELETE FROM "${schema}"."Incident"`);
117
+ });
118
+
119
+ afterEach(() => {
120
+ jest.restoreAllMocks();
121
+ });
122
+
123
+ const LONG_AGO: string = "2020-01-01T00:00:00.000Z";
124
+
125
+ async function insertNote(data: {
126
+ status: StatusPageSubscriberNotificationStatus;
127
+ version: number;
128
+ }): Promise<ObjectID> {
129
+ const id: ObjectID = ObjectID.generate();
130
+ await database.query(
131
+ `INSERT INTO "${schema}"."IncidentPublicNote" ("_id", "subscriberNotificationStatusOnNoteCreated", "version", "updatedAt") VALUES ($1, $2, $3, $4)`,
132
+ [id.toString(), data.status, data.version, new Date(LONG_AGO)],
133
+ );
134
+ return id;
135
+ }
136
+
137
+ async function readNote(id: ObjectID): Promise<{
138
+ status: string;
139
+ message: string | null;
140
+ version: number;
141
+ updatedAt: Date;
142
+ }> {
143
+ const rows: Array<{
144
+ status: string;
145
+ message: string | null;
146
+ version: number;
147
+ updatedAt: Date;
148
+ }> = await database.query(
149
+ `SELECT "subscriberNotificationStatusOnNoteCreated" AS "status", "subscriberNotificationStatusMessage" AS "message", "version", "updatedAt" FROM "${schema}"."IncidentPublicNote" WHERE "_id" = $1`,
150
+ [id.toString()],
151
+ );
152
+ return rows[0]!;
153
+ }
154
+
155
+ function claim(id: ObjectID, version: number): Promise<boolean> {
156
+ return SubscriberNotificationClaim.claim({
157
+ service: noteService,
158
+ id: id,
159
+ statusColumn: "subscriberNotificationStatusOnNoteCreated",
160
+ version: version,
161
+ });
162
+ }
163
+
164
+ test("a Pending notification at the version read is claimed, reported claimed, and timed from now", async () => {
165
+ const id: ObjectID = await insertNote({
166
+ status: StatusPageSubscriberNotificationStatus.Pending,
167
+ version: 3,
168
+ });
169
+
170
+ await expect(claim(id, 3)).resolves.toBe(true);
171
+
172
+ const row: {
173
+ status: string;
174
+ version: number;
175
+ updatedAt: Date;
176
+ } = await readNote(id);
177
+ expect(row.status).toBe(StatusPageSubscriberNotificationStatus.InProgress);
178
+ // The claim does not bump the version it guards on.
179
+ expect(row.version).toBe(3);
180
+ // The sweeper times the send from here.
181
+ expect(row.updatedAt.getTime()).toBeGreaterThan(
182
+ new Date(LONG_AGO).getTime(),
183
+ );
184
+ });
185
+
186
+ test("the version a job reads with its select is the one its claim wins with", async () => {
187
+ const id: ObjectID = await insertNote({
188
+ status: StatusPageSubscriberNotificationStatus.Pending,
189
+ version: 12,
190
+ });
191
+
192
+ const pending: Array<IncidentPublicNote> = await noteService.findBy({
193
+ query: {
194
+ subscriberNotificationStatusOnNoteCreated:
195
+ StatusPageSubscriberNotificationStatus.Pending,
196
+ },
197
+ select: {
198
+ _id: true,
199
+ version: true,
200
+ },
201
+ limit: 10,
202
+ skip: 0,
203
+ props: {
204
+ isRoot: true,
205
+ },
206
+ });
207
+
208
+ expect(pending).toHaveLength(1);
209
+ expect(pending[0]!.version).toBe(12);
210
+ await expect(claim(id, pending[0]!.version!)).resolves.toBe(true);
211
+ });
212
+
213
+ test("a notification already claimed is reported lost, and left as it is", async () => {
214
+ const id: ObjectID = await insertNote({
215
+ status: StatusPageSubscriberNotificationStatus.Pending,
216
+ version: 3,
217
+ });
218
+
219
+ await expect(claim(id, 3)).resolves.toBe(true);
220
+ await expect(claim(id, 3)).resolves.toBe(false);
221
+ });
222
+
223
+ test("a notification changed since it was read is reported lost, and left Pending for a fresh read", async () => {
224
+ const id: ObjectID = await insertNote({
225
+ status: StatusPageSubscriberNotificationStatus.Pending,
226
+ version: 4,
227
+ });
228
+
229
+ await expect(claim(id, 3)).resolves.toBe(false);
230
+
231
+ const row: { status: string; updatedAt: Date } = await readNote(id);
232
+ expect(row.status).toBe(StatusPageSubscriberNotificationStatus.Pending);
233
+ expect(row.updatedAt.toISOString()).toBe(LONG_AGO);
234
+ });
235
+
236
+ test("of runs racing for one notification, exactly one wins", async () => {
237
+ const id: ObjectID = await insertNote({
238
+ status: StatusPageSubscriberNotificationStatus.Pending,
239
+ version: 3,
240
+ });
241
+
242
+ const results: Array<boolean> = await Promise.all(
243
+ Array.from({ length: 8 }, () => {
244
+ return claim(id, 3);
245
+ }),
246
+ );
247
+
248
+ expect(
249
+ results.filter((won: boolean): boolean => {
250
+ return won;
251
+ }),
252
+ ).toHaveLength(1);
253
+ });
254
+
255
+ test("a notification that does not exist is reported lost", async () => {
256
+ await expect(claim(ObjectID.generate(), 3)).resolves.toBe(false);
257
+ });
258
+
259
+ describe("an interrupted send", () => {
260
+ function failInterrupted(id: ObjectID, version: number): Promise<boolean> {
261
+ return SubscriberNotificationClaim.failInterrupted({
262
+ service: noteService,
263
+ id: id,
264
+ statusColumn: "subscriberNotificationStatusOnNoteCreated",
265
+ messageColumn: "subscriberNotificationStatusMessage",
266
+ message: SubscriberNotificationInterruption.resendsMessage,
267
+ version: version,
268
+ });
269
+ }
270
+
271
+ test("still In progress at the version read is marked Failed, with the reason", async () => {
272
+ const id: ObjectID = await insertNote({
273
+ status: StatusPageSubscriberNotificationStatus.InProgress,
274
+ version: 5,
275
+ });
276
+
277
+ await expect(failInterrupted(id, 5)).resolves.toBe(true);
278
+
279
+ const row: { status: string; message: string | null } =
280
+ await readNote(id);
281
+ expect(row.status).toBe(StatusPageSubscriberNotificationStatus.Failed);
282
+ expect(row.message).toBe(
283
+ SubscriberNotificationInterruption.resendsMessage,
284
+ );
285
+ });
286
+
287
+ test.each([
288
+ StatusPageSubscriberNotificationStatus.Success,
289
+ StatusPageSubscriberNotificationStatus.Pending,
290
+ StatusPageSubscriberNotificationStatus.Failed,
291
+ ])(
292
+ "one that settled (%s) in the meantime keeps its outcome",
293
+ async (status: StatusPageSubscriberNotificationStatus) => {
294
+ const id: ObjectID = await insertNote({ status, version: 5 });
295
+
296
+ await expect(failInterrupted(id, 5)).resolves.toBe(false);
297
+ expect((await readNote(id)).status).toBe(status);
298
+ },
299
+ );
300
+
301
+ test("one sent again since it was read keeps its new send", async () => {
302
+ const id: ObjectID = await insertNote({
303
+ status: StatusPageSubscriberNotificationStatus.InProgress,
304
+ version: 6,
305
+ });
306
+
307
+ await expect(failInterrupted(id, 5)).resolves.toBe(false);
308
+ expect((await readNote(id)).status).toBe(
309
+ StatusPageSubscriberNotificationStatus.InProgress,
310
+ );
311
+ });
312
+ });
313
+
314
+ /*
315
+ * The incident created notification is timed from its claim, not from
316
+ * updatedAt, which the owners' reminders and state changes keep moving
317
+ * while the incident is open. The claim stamps the column; the sweeper
318
+ * finds a row claimed before the cutoff even though the row was written
319
+ * since, and finds a row claimed before the column existed by updatedAt.
320
+ */
321
+ describe("the incident created notification's claimed-at time", () => {
322
+ async function insertIncident(data: {
323
+ status: StatusPageSubscriberNotificationStatus;
324
+ updatedAt: Date;
325
+ claimedAt?: Date | null;
326
+ }): Promise<ObjectID> {
327
+ const id: ObjectID = ObjectID.generate();
328
+ await database.query(
329
+ `INSERT INTO "${schema}"."Incident" ("_id", "version", "updatedAt", "subscriberNotificationStatusOnIncidentCreated", "subscriberNotificationClaimedAtOnIncidentCreated") VALUES ($1, 1, $2, $3, $4)`,
330
+ [id.toString(), data.updatedAt, data.status, data.claimedAt ?? null],
331
+ );
332
+ return id;
333
+ }
334
+
335
+ test("the claim stamps it with the time of the claim", async () => {
336
+ const id: ObjectID = await insertIncident({
337
+ status: StatusPageSubscriberNotificationStatus.Pending,
338
+ updatedAt: new Date(LONG_AGO),
339
+ });
340
+ const before: number = Date.now();
341
+
342
+ await expect(
343
+ SubscriberNotificationClaim.claim({
344
+ service: incidentService,
345
+ id: id,
346
+ statusColumn: "subscriberNotificationStatusOnIncidentCreated",
347
+ claimedAtColumn: "subscriberNotificationClaimedAtOnIncidentCreated",
348
+ version: 1,
349
+ }),
350
+ ).resolves.toBe(true);
351
+
352
+ const rows: Array<{ claimedAt: Date; status: string }> =
353
+ await database.query(
354
+ `SELECT "subscriberNotificationClaimedAtOnIncidentCreated" AS "claimedAt", "subscriberNotificationStatusOnIncidentCreated" AS "status" FROM "${schema}"."Incident" WHERE "_id" = $1`,
355
+ [id.toString()],
356
+ );
357
+
358
+ expect(rows[0]!.status).toBe(
359
+ StatusPageSubscriberNotificationStatus.InProgress,
360
+ );
361
+ expect(rows[0]!.claimedAt.getTime()).toBeGreaterThanOrEqual(
362
+ before - 1000,
363
+ );
364
+ });
365
+
366
+ test("the sweeper's queries find a send claimed long ago on a row written since, and a legacy row by updatedAt", async () => {
367
+ const cutoff: Date = new Date(Date.now() - 40 * 60 * 1000);
368
+ const claimedLongAgoWrittenNow: ObjectID = await insertIncident({
369
+ status: StatusPageSubscriberNotificationStatus.InProgress,
370
+ updatedAt: new Date(),
371
+ claimedAt: new Date(LONG_AGO),
372
+ });
373
+ const claimedJustNowWrittenLongAgo: ObjectID = await insertIncident({
374
+ status: StatusPageSubscriberNotificationStatus.InProgress,
375
+ updatedAt: new Date(LONG_AGO),
376
+ claimedAt: new Date(),
377
+ });
378
+ const legacy: ObjectID = await insertIncident({
379
+ status: StatusPageSubscriberNotificationStatus.InProgress,
380
+ updatedAt: new Date(LONG_AGO),
381
+ });
382
+
383
+ const byClaim: Array<Incident> = await incidentService.findBy({
384
+ query: {
385
+ subscriberNotificationStatusOnIncidentCreated:
386
+ StatusPageSubscriberNotificationStatus.InProgress,
387
+ subscriberNotificationClaimedAtOnIncidentCreated:
388
+ QueryHelper.lessThan(cutoff),
389
+ },
390
+ select: { _id: true },
391
+ limit: 10,
392
+ skip: 0,
393
+ props: { isRoot: true },
394
+ });
395
+ const byUpdatedAt: Array<Incident> = await incidentService.findBy({
396
+ query: {
397
+ subscriberNotificationStatusOnIncidentCreated:
398
+ StatusPageSubscriberNotificationStatus.InProgress,
399
+ subscriberNotificationClaimedAtOnIncidentCreated:
400
+ QueryHelper.isNull(),
401
+ updatedAt: QueryHelper.lessThan(cutoff),
402
+ },
403
+ select: { _id: true },
404
+ limit: 10,
405
+ skip: 0,
406
+ props: { isRoot: true },
407
+ });
408
+
409
+ const ids: (rows: Array<Incident>) => Array<string> = (
410
+ rows: Array<Incident>,
411
+ ): Array<string> => {
412
+ return rows.map((row: Incident): string => {
413
+ return row._id!.toString();
414
+ });
415
+ };
416
+
417
+ expect(ids(byClaim)).toEqual([claimedLongAgoWrittenNow.toString()]);
418
+ expect(ids(byUpdatedAt)).toEqual([legacy.toString()]);
419
+ expect([...ids(byClaim), ...ids(byUpdatedAt)]).not.toContain(
420
+ claimedJustNowWrittenLongAgo.toString(),
421
+ );
422
+ });
423
+ });
424
+
425
+ /*
426
+ * Claiming a note's posted notification also skips an update notification
427
+ * it read as Pending, in the same write - only while it is still Pending.
428
+ */
429
+ describe("claiming a posted note that covers a pending update", () => {
430
+ async function insertNoteWithUpdate(data: {
431
+ updateStatus: StatusPageSubscriberNotificationStatus;
432
+ }): Promise<ObjectID> {
433
+ const id: ObjectID = ObjectID.generate();
434
+ await database.query(
435
+ `INSERT INTO "${schema}"."IncidentPublicNote" ("_id", "subscriberNotificationStatusOnNoteCreated", "subscriberNotificationStatusOnNoteUpdated", "version", "updatedAt") VALUES ($1, $2, $3, 2, $4)`,
436
+ [
437
+ id.toString(),
438
+ StatusPageSubscriberNotificationStatus.Pending,
439
+ data.updateStatus,
440
+ new Date(LONG_AGO),
441
+ ],
442
+ );
443
+ return id;
444
+ }
445
+
446
+ function claimCovering(id: ObjectID): Promise<boolean> {
447
+ return SubscriberNotificationClaim.claim({
448
+ service: noteService,
449
+ id: id,
450
+ statusColumn: "subscriberNotificationStatusOnNoteCreated",
451
+ version: 2,
452
+ alsoSet: {
453
+ data: {
454
+ subscriberNotificationStatusOnNoteUpdated:
455
+ StatusPageSubscriberNotificationStatus.Skipped,
456
+ } as never,
457
+ expected: {
458
+ subscriberNotificationStatusOnNoteUpdated:
459
+ StatusPageSubscriberNotificationStatus.Pending,
460
+ } as never,
461
+ },
462
+ });
463
+ }
464
+
465
+ async function statuses(id: ObjectID): Promise<{
466
+ created: string;
467
+ updated: string;
468
+ }> {
469
+ const rows: Array<{ created: string; updated: string }> =
470
+ await database.query(
471
+ `SELECT "subscriberNotificationStatusOnNoteCreated" AS "created", "subscriberNotificationStatusOnNoteUpdated" AS "updated" FROM "${schema}"."IncidentPublicNote" WHERE "_id" = $1`,
472
+ [id.toString()],
473
+ );
474
+ return rows[0]!;
475
+ }
476
+
477
+ test("a pending update is skipped in the same write as the claim", async () => {
478
+ const id: ObjectID = await insertNoteWithUpdate({
479
+ updateStatus: StatusPageSubscriberNotificationStatus.Pending,
480
+ });
481
+
482
+ await expect(claimCovering(id)).resolves.toBe(true);
483
+ expect(await statuses(id)).toEqual({
484
+ created: StatusPageSubscriberNotificationStatus.InProgress,
485
+ updated: StatusPageSubscriberNotificationStatus.Skipped,
486
+ });
487
+ });
488
+
489
+ test("an update another run is sending is left alone, and so is the post, for a fresh read", async () => {
490
+ const id: ObjectID = await insertNoteWithUpdate({
491
+ updateStatus: StatusPageSubscriberNotificationStatus.InProgress,
492
+ });
493
+
494
+ await expect(claimCovering(id)).resolves.toBe(false);
495
+ expect(await statuses(id)).toEqual({
496
+ created: StatusPageSubscriberNotificationStatus.Pending,
497
+ updated: StatusPageSubscriberNotificationStatus.InProgress,
498
+ });
499
+ });
500
+ });
501
+
502
+ test("the status pages told so far round-trip through the raw write, version untouched", async () => {
503
+ const id: ObjectID = ObjectID.generate();
504
+ await database.query(
505
+ `INSERT INTO "${schema}"."Incident" ("_id", "version", "updatedAt") VALUES ($1, 2, $2)`,
506
+ [id.toString(), new Date(LONG_AGO)],
507
+ );
508
+ const told: Array<string> = [
509
+ "b0000000-0000-4000-8000-000000000003",
510
+ "b0000000-0000-4000-8000-000000000007",
511
+ ];
512
+
513
+ await incidentService.updateColumnsByIdWithoutHooks({
514
+ id: id,
515
+ data: { statusPagesNotifiedOnCreation: told } as never,
516
+ });
517
+
518
+ const rows: Array<{
519
+ statusPagesNotifiedOnCreation: Array<string>;
520
+ version: number;
521
+ }> = await database.query(
522
+ `SELECT "statusPagesNotifiedOnCreation", "version" FROM "${schema}"."Incident" WHERE "_id" = $1`,
523
+ [id.toString()],
524
+ );
525
+ expect(rows[0]!.statusPagesNotifiedOnCreation).toEqual(told);
526
+ expect(rows[0]!.version).toBe(2);
527
+ });
528
+ });