@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
@@ -21,6 +21,7 @@ import {
21
21
  } from "@testing-library/react";
22
22
  import React, { ReactElement } from "react";
23
23
  import getJestMockFunction, { MockFunction } from "../../MockType";
24
+ import { JSONObject } from "../../../Types/JSON";
24
25
 
25
26
  /*
26
27
  * The notes feed every incident, alert, scheduled maintenance and episode
@@ -334,6 +335,8 @@ import FileModel from "../../../Models/DatabaseModels/File";
334
335
  import IncidentInternalNote from "../../../Models/DatabaseModels/IncidentInternalNote";
335
336
  import IncidentNoteTemplate from "../../../Models/DatabaseModels/IncidentNoteTemplate";
336
337
  import IncidentPublicNote from "../../../Models/DatabaseModels/IncidentPublicNote";
338
+ import ScheduledMaintenancePublicNote from "../../../Models/DatabaseModels/ScheduledMaintenancePublicNote";
339
+ import SubscriberNotificationResendCopy from "../../../../App/FeatureSet/Dashboard/src/Components/StatusPageSubscribers/SubscriberNotificationResendCopy";
337
340
  import Project from "../../../Models/DatabaseModels/Project";
338
341
  import User from "../../../Models/DatabaseModels/User";
339
342
  import Route from "../../../Types/API/Route";
@@ -1253,6 +1256,104 @@ describe("event notes: the composer", () => {
1253
1256
  ).toBe(true);
1254
1257
  });
1255
1258
 
1259
+ /*
1260
+ * Who the note will reach ("Will notify: Site 03 (up to 41 email)"), for
1261
+ * the incident pages that pass one (SubscriberAudienceSummary): under the
1262
+ * notify box, and only while it is ticked - an unticked note reaches nobody.
1263
+ */
1264
+ test("shows who the note will reach while notifying is ticked", async () => {
1265
+ await renderPublic({
1266
+ subscriberNotifications: {
1267
+ isNotifyingByDefault: true,
1268
+ quietDescription: QUIET_DESCRIPTION,
1269
+ audienceSummary: (
1270
+ <div data-testid="stub-audience">Will notify: Site 03</div>
1271
+ ),
1272
+ },
1273
+ });
1274
+ await openComposer();
1275
+
1276
+ expect(
1277
+ within(screen.getByTestId("note-notify-audience")).getByTestId(
1278
+ "stub-audience",
1279
+ ),
1280
+ ).toHaveTextContent("Will notify: Site 03");
1281
+
1282
+ fireEvent.click(screen.getByTestId("note-notify-checkbox"));
1283
+
1284
+ expect(screen.queryByTestId("note-notify-audience")).toBeNull();
1285
+ expect(screen.queryByTestId("stub-audience")).toBeNull();
1286
+
1287
+ fireEvent.click(screen.getByTestId("note-notify-checkbox"));
1288
+
1289
+ expect(screen.getByTestId("stub-audience")).toBeInTheDocument();
1290
+ });
1291
+
1292
+ test("a quiet incident shows the audience only once notifying is ticked", async () => {
1293
+ await renderPublic({
1294
+ subscriberNotifications: {
1295
+ isNotifyingByDefault: false,
1296
+ quietDescription: QUIET_DESCRIPTION,
1297
+ audienceSummary: <div data-testid="stub-audience">Will notify</div>,
1298
+ },
1299
+ });
1300
+ await openComposer();
1301
+
1302
+ expect(screen.queryByTestId("stub-audience")).toBeNull();
1303
+
1304
+ fireEvent.click(screen.getByTestId("note-notify-checkbox"));
1305
+
1306
+ expect(screen.getByTestId("stub-audience")).toBeInTheDocument();
1307
+ });
1308
+
1309
+ /*
1310
+ * 'Preview notification' for the incident pages that pass one: rendered
1311
+ * from the note as it is being written, under the notify box, and only
1312
+ * while it is ticked.
1313
+ */
1314
+ test("offers a preview of the notification, from the note being written, while notifying is ticked", async () => {
1315
+ const drafts: Array<{ note: string; postedAt: Date | null }> = [];
1316
+
1317
+ await renderPublic({
1318
+ subscriberNotifications: {
1319
+ isNotifyingByDefault: true,
1320
+ quietDescription: QUIET_DESCRIPTION,
1321
+ renderPreview: (draft: {
1322
+ note: string;
1323
+ postedAt: Date | null;
1324
+ }): ReactElement => {
1325
+ drafts.push(draft);
1326
+ return <div data-testid="stub-preview">{draft.note}</div>;
1327
+ },
1328
+ },
1329
+ });
1330
+ await openComposer();
1331
+
1332
+ type(editor(), "Rolling back the release.");
1333
+
1334
+ expect(
1335
+ within(screen.getByTestId("note-notify-preview")).getByTestId(
1336
+ "stub-preview",
1337
+ ),
1338
+ ).toHaveTextContent("Rolling back the release.");
1339
+ expect(drafts[drafts.length - 1]).toEqual({
1340
+ note: "Rolling back the release.",
1341
+ postedAt: null,
1342
+ });
1343
+
1344
+ fireEvent.click(screen.getByTestId("note-notify-checkbox"));
1345
+
1346
+ expect(screen.queryByTestId("note-notify-preview")).toBeNull();
1347
+ });
1348
+
1349
+ test("without an audience to show, the notify box stands alone", async () => {
1350
+ await renderPublic();
1351
+ await openComposer();
1352
+
1353
+ expect(screen.getByTestId("note-notify-checkbox")).toBeChecked();
1354
+ expect(screen.queryByTestId("note-notify-audience")).toBeNull();
1355
+ });
1356
+
1256
1357
  test("unticking on a notifying incident explains the note still goes on the status page", async () => {
1257
1358
  await renderPublic();
1258
1359
  await openComposer();
@@ -1636,6 +1737,140 @@ describe("event notes: templates", () => {
1636
1737
  });
1637
1738
  });
1638
1739
 
1740
+ describe("event notes: template placeholders", () => {
1741
+ const PLACEHOLDER_TEMPLATE: string =
1742
+ "**Incident**: {{incident.title}}\n**Impact**: {{customFields.impact}}\n**Owner**: {{incident.owner}}";
1743
+
1744
+ function seedPlaceholderTemplate(): void {
1745
+ tableOf(IncidentNoteTemplate).push({
1746
+ _id: "71000000-0000-4000-8000-000000000099",
1747
+ templateName: "Status update",
1748
+ note: PLACEHOLDER_TEMPLATE,
1749
+ });
1750
+ }
1751
+
1752
+ async function pickTemplate(): Promise<void> {
1753
+ fireEvent.click(screen.getByTestId("note-template-menu-button"));
1754
+ fireEvent.click(
1755
+ (await screen.findAllByText("Status update"))[0]!.closest("button")!,
1756
+ );
1757
+ }
1758
+
1759
+ function variablesLoader(): MockFunction {
1760
+ const loader: MockFunction = getJestMockFunction();
1761
+ loader.mockResolvedValue({
1762
+ "incident.title": "Payments are failing",
1763
+ "customFields.impact": "High",
1764
+ } as never);
1765
+ return loader;
1766
+ }
1767
+
1768
+ test("a picked template's placeholders are filled with the event's values; unknown ones stay", async () => {
1769
+ seedPlaceholderTemplate();
1770
+ const loader: MockFunction = variablesLoader();
1771
+ await renderPublic({ templateVariables: loader as never });
1772
+
1773
+ await pickTemplate();
1774
+
1775
+ await waitFor(() => {
1776
+ expect(editor()).toHaveValue(
1777
+ "**Incident**: Payments are failing\n**Impact**: High\n**Owner**: {{incident.owner}}",
1778
+ );
1779
+ });
1780
+ expect(loader).toHaveBeenCalledTimes(1);
1781
+ });
1782
+
1783
+ test("the values are asked for each time a template is picked, not once per page", async () => {
1784
+ seedPlaceholderTemplate();
1785
+ const loader: MockFunction = variablesLoader();
1786
+ await renderPublic({ templateVariables: loader as never });
1787
+
1788
+ // Nothing is read until a template is picked.
1789
+ expect(loader).not.toHaveBeenCalled();
1790
+
1791
+ await pickTemplate();
1792
+ await waitFor(() => {
1793
+ expect(editor().value).toContain("**Incident**: Payments are failing");
1794
+ });
1795
+ expect(loader).toHaveBeenCalledTimes(1);
1796
+
1797
+ loader.mockResolvedValue({
1798
+ "incident.title": "Payments are recovering",
1799
+ } as never);
1800
+
1801
+ await pickTemplate();
1802
+
1803
+ await waitFor(() => {
1804
+ expect(editor().value).toContain("**Incident**: Payments are recovering");
1805
+ });
1806
+ expect(loader).toHaveBeenCalledTimes(2);
1807
+ });
1808
+
1809
+ test("when the values cannot be read, the template still goes in, as written", async () => {
1810
+ seedPlaceholderTemplate();
1811
+ const loader: MockFunction = getJestMockFunction();
1812
+ loader.mockRejectedValue(new Error("No access to the incident.") as never);
1813
+ await renderPublic({ templateVariables: loader as never });
1814
+
1815
+ await pickTemplate();
1816
+
1817
+ await waitFor(() => {
1818
+ expect(editor()).toHaveValue(PLACEHOLDER_TEMPLATE);
1819
+ });
1820
+ });
1821
+
1822
+ test("only the template is filled: braces already typed are left alone", async () => {
1823
+ seedPlaceholderTemplate();
1824
+ const loader: MockFunction = variablesLoader();
1825
+ await renderPublic({ templateVariables: loader as never });
1826
+ await openComposer();
1827
+ type(editor(), "We saw {{incident.title}} in the logs.");
1828
+
1829
+ await pickTemplate();
1830
+
1831
+ await waitFor(() => {
1832
+ expect(editor()).toHaveValue(
1833
+ "We saw {{incident.title}} in the logs.\n\n**Incident**: Payments are failing\n**Impact**: High\n**Owner**: {{incident.owner}}",
1834
+ );
1835
+ });
1836
+ });
1837
+
1838
+ test("the private notes feed fills them too", async () => {
1839
+ seedPlaceholderTemplate();
1840
+ const loader: MockFunction = variablesLoader();
1841
+ await renderPrivate({ templateVariables: loader as never });
1842
+
1843
+ await pickTemplate();
1844
+
1845
+ await waitFor(() => {
1846
+ expect(editor().value).toContain("**Incident**: Payments are failing");
1847
+ });
1848
+ });
1849
+
1850
+ test("a feed given no values puts the template in as written", async () => {
1851
+ seedPlaceholderTemplate();
1852
+ await renderPublic();
1853
+
1854
+ await pickTemplate();
1855
+
1856
+ await waitFor(() => {
1857
+ expect(editor()).toHaveValue(PLACEHOLDER_TEMPLATE);
1858
+ });
1859
+ });
1860
+
1861
+ test("an AI draft is not a template: nothing is read for it", async () => {
1862
+ const loader: MockFunction = variablesLoader();
1863
+ await renderPublic({ templateVariables: loader as never });
1864
+
1865
+ fireEvent.click(screen.getByTestId("note-ai-button"));
1866
+ fireEvent.click(screen.getByText("Use AI draft"));
1867
+
1868
+ expect(await screen.findByTestId("note-composer")).toBeInTheDocument();
1869
+ expect(editor()).toHaveValue("Drafted by AI.");
1870
+ expect(loader).not.toHaveBeenCalled();
1871
+ });
1872
+ });
1873
+
1639
1874
  describe("event notes: drafting with AI", () => {
1640
1875
  test("hands the dialog this page's generator and puts the draft in the composer", async () => {
1641
1876
  await renderPublic();
@@ -2078,3 +2313,544 @@ describe("event notes: the page header", () => {
2078
2313
  ).toBeVisible();
2079
2314
  });
2080
2315
  });
2316
+
2317
+ /*
2318
+ * Sending a note's 'posted' notification again. The incident's public notes
2319
+ * pass a `resend` setting: a note whose notification went out offers Resend,
2320
+ * a failed one Retry, and both ask first, naming who the note would reach
2321
+ * now. The episode and scheduled maintenance notes pass none and keep Retry
2322
+ * only, straight from the details dialog. A note posted without notifying
2323
+ * subscribers offers neither: it would sit in Pending forever.
2324
+ */
2325
+ describe("event notes: sending a note's notification again", () => {
2326
+ const RESEND_AUDIENCE: ReactElement = (
2327
+ <div data-testid="resend-audience">
2328
+ Will notify: Site 03 (up to 41 email), Site 07 (up to 18 email)
2329
+ </div>
2330
+ );
2331
+
2332
+ function withResend(): Partial<EventNotesProps<IncidentPublicNote>> {
2333
+ return {
2334
+ subscriberNotifications: {
2335
+ isNotifyingByDefault: true,
2336
+ quietDescription: QUIET_DESCRIPTION,
2337
+ resend: { audience: RESEND_AUDIENCE },
2338
+ },
2339
+ };
2340
+ }
2341
+
2342
+ function openPostedStatus(text: string): HTMLElement {
2343
+ fireEvent.click(within(card(text)).getByTestId("note-notification-status"));
2344
+
2345
+ return screen.getByRole("dialog", { name: "Subscriber notification" });
2346
+ }
2347
+
2348
+ function seedQuietNote(): void {
2349
+ tableOf(IncidentPublicNote).push({
2350
+ _id: noteId(40),
2351
+ incidentId: INCIDENT_ID,
2352
+ note: "Posted quietly.",
2353
+ createdAt: ago(2 * MINUTE),
2354
+ postedAt: ago(2 * MINUTE),
2355
+ createdByUser: MAYA,
2356
+ shouldStatusPageSubscribersBeNotifiedOnNoteCreated: false,
2357
+ subscriberNotificationStatusOnNoteCreated:
2358
+ StatusPageSubscriberNotificationStatus.Skipped,
2359
+ subscriberNotificationStatusMessage:
2360
+ "Notifications skipped as subscribers are not to be notified for this incident note.",
2361
+ });
2362
+ }
2363
+
2364
+ test("a note whose notification went out offers Resend, which asks first and names who it reaches", async () => {
2365
+ seedPublicNotes();
2366
+ await renderPublic(withResend());
2367
+
2368
+ const details: HTMLElement = openPostedStatus("Monitoring.");
2369
+
2370
+ fireEvent.click(
2371
+ within(details).getByText(
2372
+ SubscriberNotificationResendCopy.resendNoteNotificationButton,
2373
+ ),
2374
+ );
2375
+
2376
+ // Nothing is sent from the details: the confirmation asks first.
2377
+ expect(updateByIdMock).not.toHaveBeenCalled();
2378
+
2379
+ const confirm: HTMLElement = screen.getByRole("dialog", {
2380
+ name: SubscriberNotificationResendCopy.resendConfirmTitle,
2381
+ });
2382
+
2383
+ expect(
2384
+ within(confirm).getByTestId("note-notification-resend-description"),
2385
+ ).toHaveTextContent(SubscriberNotificationResendCopy.noteResendDescription);
2386
+ expect(within(confirm).getByTestId("resend-audience")).toHaveTextContent(
2387
+ "Will notify: Site 03 (up to 41 email), Site 07 (up to 18 email)",
2388
+ );
2389
+
2390
+ await act(async () => {
2391
+ fireEvent.click(
2392
+ within(confirm).getByText(
2393
+ SubscriberNotificationResendCopy.resendButton,
2394
+ ),
2395
+ );
2396
+ });
2397
+
2398
+ expect(updateByIdMock).toHaveBeenCalledTimes(1);
2399
+ expect(updateByIdMock).toHaveBeenCalledWith(
2400
+ expect.objectContaining({
2401
+ modelType: IncidentPublicNote,
2402
+ data: {
2403
+ subscriberNotificationStatusOnNoteCreated:
2404
+ StatusPageSubscriberNotificationStatus.Pending,
2405
+ subscriberNotificationStatusMessage: null,
2406
+ },
2407
+ }),
2408
+ );
2409
+ expect(
2410
+ (updateByIdMock.mock.calls[0]![0] as { id: ObjectID }).id.toString(),
2411
+ ).toBe(noteId(1));
2412
+
2413
+ await waitFor(() => {
2414
+ expect(
2415
+ within(card("Monitoring.")).getByTestId("note-notification-status"),
2416
+ ).toHaveTextContent("Notifying subscribers soon");
2417
+ });
2418
+ expect(screen.queryByRole("dialog")).toBeNull();
2419
+ });
2420
+
2421
+ test("a failed note offers Retry, which says a note goes to every page again", async () => {
2422
+ seedPublicNotes();
2423
+ await renderPublic(withResend());
2424
+
2425
+ const details: HTMLElement = openPostedStatus("Identified.");
2426
+
2427
+ expect(within(details).getByText("The SMTP relay refused.")).toBeVisible();
2428
+ expect(
2429
+ within(details).queryByText(
2430
+ SubscriberNotificationResendCopy.resendNoteNotificationButton,
2431
+ ),
2432
+ ).toBeNull();
2433
+
2434
+ fireEvent.click(
2435
+ within(details).getByText(
2436
+ SubscriberNotificationResendCopy.retryNoteNotificationButton,
2437
+ ),
2438
+ );
2439
+
2440
+ const confirm: HTMLElement = screen.getByRole("dialog", {
2441
+ name: SubscriberNotificationResendCopy.retryConfirmTitle,
2442
+ });
2443
+
2444
+ expect(
2445
+ within(confirm).getByTestId("note-notification-resend-description"),
2446
+ ).toHaveTextContent(SubscriberNotificationResendCopy.noteRetryDescription);
2447
+ expect(within(confirm).getByTestId("resend-audience")).toBeVisible();
2448
+
2449
+ await act(async () => {
2450
+ fireEvent.click(
2451
+ within(confirm).getByText(SubscriberNotificationResendCopy.retryButton),
2452
+ );
2453
+ });
2454
+
2455
+ expect(
2456
+ (updateByIdMock.mock.calls[0]![0] as { id: ObjectID }).id.toString(),
2457
+ ).toBe(noteId(2));
2458
+ });
2459
+
2460
+ test("Cancel sends nothing", async () => {
2461
+ seedPublicNotes();
2462
+ await renderPublic(withResend());
2463
+
2464
+ fireEvent.click(
2465
+ within(openPostedStatus("Monitoring.")).getByText(
2466
+ SubscriberNotificationResendCopy.resendNoteNotificationButton,
2467
+ ),
2468
+ );
2469
+ fireEvent.click(
2470
+ within(
2471
+ screen.getByRole("dialog", {
2472
+ name: SubscriberNotificationResendCopy.resendConfirmTitle,
2473
+ }),
2474
+ ).getByText(SubscriberNotificationResendCopy.cancelButton),
2475
+ );
2476
+
2477
+ expect(updateByIdMock).not.toHaveBeenCalled();
2478
+ expect(screen.queryByRole("dialog")).toBeNull();
2479
+ });
2480
+
2481
+ test("a refused resend keeps the confirmation open with the server's reason", async () => {
2482
+ seedPublicNotes();
2483
+ await renderPublic(withResend());
2484
+ updateByIdMock.mockRejectedValueOnce(
2485
+ new Error(
2486
+ "This notification is being sent right now. Wait until it has finished, then send it again.",
2487
+ ),
2488
+ );
2489
+
2490
+ fireEvent.click(
2491
+ within(openPostedStatus("Monitoring.")).getByText(
2492
+ SubscriberNotificationResendCopy.resendNoteNotificationButton,
2493
+ ),
2494
+ );
2495
+
2496
+ const confirm: HTMLElement = screen.getByRole("dialog", {
2497
+ name: SubscriberNotificationResendCopy.resendConfirmTitle,
2498
+ });
2499
+
2500
+ await act(async () => {
2501
+ fireEvent.click(
2502
+ within(confirm).getByText(
2503
+ SubscriberNotificationResendCopy.resendButton,
2504
+ ),
2505
+ );
2506
+ });
2507
+
2508
+ expect(within(confirm).getByRole("alert")).toHaveTextContent(
2509
+ "This notification is being sent right now.",
2510
+ );
2511
+ });
2512
+
2513
+ test("a note posted without notifying subscribers offers neither", async () => {
2514
+ seedQuietNote();
2515
+ await renderPublic(withResend());
2516
+
2517
+ const details: HTMLElement = openPostedStatus("Posted quietly.");
2518
+
2519
+ expect(
2520
+ within(details).getByText(/not to be notified for this incident note/),
2521
+ ).toBeVisible();
2522
+ expect(
2523
+ within(details).queryByText(
2524
+ SubscriberNotificationResendCopy.resendNoteNotificationButton,
2525
+ ),
2526
+ ).toBeNull();
2527
+ expect(
2528
+ within(details).queryByText(
2529
+ SubscriberNotificationResendCopy.retryNoteNotificationButton,
2530
+ ),
2531
+ ).toBeNull();
2532
+ });
2533
+
2534
+ test.each([
2535
+ StatusPageSubscriberNotificationStatus.Pending,
2536
+ StatusPageSubscriberNotificationStatus.InProgress,
2537
+ ])(
2538
+ "a note whose notification is %s offers neither",
2539
+ async (status: StatusPageSubscriberNotificationStatus) => {
2540
+ tableOf(IncidentPublicNote).push({
2541
+ _id: noteId(41),
2542
+ incidentId: INCIDENT_ID,
2543
+ note: "On its way.",
2544
+ createdAt: ago(MINUTE),
2545
+ postedAt: ago(MINUTE),
2546
+ createdByUser: MAYA,
2547
+ subscriberNotificationStatusOnNoteCreated: status,
2548
+ });
2549
+ await renderPublic(withResend());
2550
+
2551
+ // No detail and nothing to offer: a plain pill, not a button.
2552
+ expect(
2553
+ within(card("On its way.")).getByTestId("note-notification-status")
2554
+ .tagName,
2555
+ ).toBe("SPAN");
2556
+ },
2557
+ );
2558
+
2559
+ test("an update notification that went out is not offered again", async () => {
2560
+ tableOf(IncidentPublicNote).push({
2561
+ _id: noteId(42),
2562
+ incidentId: INCIDENT_ID,
2563
+ note: "Edited and announced.",
2564
+ createdAt: ago(MINUTE),
2565
+ postedAt: ago(MINUTE),
2566
+ createdByUser: MAYA,
2567
+ subscriberNotificationStatusOnNoteCreated:
2568
+ StatusPageSubscriberNotificationStatus.Success,
2569
+ subscriberNotificationStatusOnNoteUpdated:
2570
+ StatusPageSubscriberNotificationStatus.Success,
2571
+ subscriberNotificationStatusMessageOnNoteUpdated:
2572
+ "Update notifications sent successfully to all subscribers.",
2573
+ });
2574
+ await renderPublic(withResend());
2575
+
2576
+ fireEvent.click(
2577
+ within(card("Edited and announced.")).getByTestId(
2578
+ "note-update-notification-status",
2579
+ ),
2580
+ );
2581
+
2582
+ const dialog: HTMLElement = screen.getByRole("dialog", {
2583
+ name: "Update notification",
2584
+ });
2585
+
2586
+ expect(
2587
+ within(dialog).queryByText(
2588
+ SubscriberNotificationResendCopy.resendNoteNotificationButton,
2589
+ ),
2590
+ ).toBeNull();
2591
+ });
2592
+
2593
+ describe("without the resend setting (the episode and scheduled maintenance notes)", () => {
2594
+ test("a note whose notification went out offers nothing", async () => {
2595
+ seedPublicNotes();
2596
+ await renderPublic();
2597
+
2598
+ expect(
2599
+ within(card("Monitoring.")).getByTestId("note-notification-status")
2600
+ .tagName,
2601
+ ).toBe("SPAN");
2602
+ });
2603
+
2604
+ test("a failed one retries straight from the details, with no confirmation", async () => {
2605
+ seedPublicNotes();
2606
+ await renderPublic();
2607
+
2608
+ const details: HTMLElement = openPostedStatus("Identified.");
2609
+
2610
+ await act(async () => {
2611
+ fireEvent.click(
2612
+ within(details).getByText(
2613
+ SubscriberNotificationResendCopy.retryNoteNotificationButton,
2614
+ ),
2615
+ );
2616
+ });
2617
+
2618
+ expect(updateByIdMock).toHaveBeenCalledTimes(1);
2619
+ expect(
2620
+ screen.queryByRole("dialog", {
2621
+ name: SubscriberNotificationResendCopy.retryConfirmTitle,
2622
+ }),
2623
+ ).toBeNull();
2624
+ });
2625
+
2626
+ test("a scheduled maintenance note keeps Retry only, for its own roles", async () => {
2627
+ isMasterAdmin = false;
2628
+ currentPermissions = [Permission.ScheduledMaintenanceMember];
2629
+
2630
+ const EVENT_ID: ObjectID = new ObjectID(
2631
+ "30000000-0000-4000-8000-000000000001",
2632
+ );
2633
+
2634
+ tableOf(ScheduledMaintenancePublicNote).push(
2635
+ {
2636
+ _id: noteId(50),
2637
+ scheduledMaintenanceId: EVENT_ID,
2638
+ note: "Maintenance started.",
2639
+ createdAt: ago(10 * MINUTE),
2640
+ postedAt: ago(10 * MINUTE),
2641
+ createdByUser: MAYA,
2642
+ subscriberNotificationStatusOnNoteCreated:
2643
+ StatusPageSubscriberNotificationStatus.Success,
2644
+ },
2645
+ {
2646
+ _id: noteId(51),
2647
+ scheduledMaintenanceId: EVENT_ID,
2648
+ note: "Maintenance extended.",
2649
+ createdAt: ago(5 * MINUTE),
2650
+ postedAt: ago(5 * MINUTE),
2651
+ createdByUser: MAYA,
2652
+ subscriberNotificationStatusOnNoteCreated:
2653
+ StatusPageSubscriberNotificationStatus.Failed,
2654
+ subscriberNotificationStatusMessage: "Relay refused.",
2655
+ },
2656
+ );
2657
+
2658
+ render(
2659
+ <EventNotes<ScheduledMaintenancePublicNote>
2660
+ modelType={ScheduledMaintenancePublicNote}
2661
+ visibility="public"
2662
+ eventNoun="scheduled maintenance event"
2663
+ parentIdField="scheduledMaintenanceId"
2664
+ parentId={EVENT_ID}
2665
+ currentProject={buildProject()}
2666
+ subscriberNotifications={{
2667
+ isNotifyingByDefault: true,
2668
+ quietDescription: QUIET_DESCRIPTION,
2669
+ }}
2670
+ />,
2671
+ );
2672
+ await waitFor(() => {
2673
+ expect(screen.queryByTestId("notes-loading")).toBeNull();
2674
+ });
2675
+
2676
+ expect(
2677
+ within(card("Maintenance started.")).getByTestId(
2678
+ "note-notification-status",
2679
+ ).tagName,
2680
+ ).toBe("SPAN");
2681
+
2682
+ const details: HTMLElement = openPostedStatus("Maintenance extended.");
2683
+
2684
+ await act(async () => {
2685
+ fireEvent.click(
2686
+ within(details).getByText(
2687
+ SubscriberNotificationResendCopy.retryNoteNotificationButton,
2688
+ ),
2689
+ );
2690
+ });
2691
+
2692
+ expect(updateByIdMock).toHaveBeenCalledWith(
2693
+ expect.objectContaining({
2694
+ modelType: ScheduledMaintenancePublicNote,
2695
+ data: {
2696
+ subscriberNotificationStatusOnNoteCreated:
2697
+ StatusPageSubscriberNotificationStatus.Pending,
2698
+ subscriberNotificationStatusMessage: null,
2699
+ },
2700
+ }),
2701
+ );
2702
+ });
2703
+ });
2704
+
2705
+ describe("who is offered it", () => {
2706
+ test("a project member is offered Resend", async () => {
2707
+ isMasterAdmin = false;
2708
+ currentPermissions = [Permission.ProjectMember];
2709
+ seedPublicNotes();
2710
+ await renderPublic(withResend());
2711
+
2712
+ expect(
2713
+ within(openPostedStatus("Monitoring.")).getByText(
2714
+ SubscriberNotificationResendCopy.resendNoteNotificationButton,
2715
+ ),
2716
+ ).toBeInTheDocument();
2717
+ });
2718
+
2719
+ test("a viewer is offered neither Resend nor Retry", async () => {
2720
+ isMasterAdmin = false;
2721
+ currentPermissions = [Permission.Viewer];
2722
+ seedPublicNotes();
2723
+ await renderPublic(withResend());
2724
+
2725
+ expect(
2726
+ within(card("Monitoring.")).getByTestId("note-notification-status")
2727
+ .tagName,
2728
+ ).toBe("SPAN");
2729
+ expect(
2730
+ within(openPostedStatus("Identified.")).queryByText(
2731
+ SubscriberNotificationResendCopy.retryNoteNotificationButton,
2732
+ ),
2733
+ ).toBeNull();
2734
+ });
2735
+
2736
+ /*
2737
+ * Telling subscribers about an edit tells them what the note says now,
2738
+ * and an editor can change the text first: it takes the permission to
2739
+ * post a notifying note, like sending the posted notification again.
2740
+ */
2741
+ test("an editor who could not post a note is offered neither the posted notification nor an edit's", async () => {
2742
+ isMasterAdmin = false;
2743
+ currentPermissions = [
2744
+ Permission.EditIncidentPublicNote,
2745
+ Permission.ReadIncidentPublicNote,
2746
+ ];
2747
+ seedPublicNotes();
2748
+ await renderPublic(withResend());
2749
+
2750
+ expect(
2751
+ within(card("Monitoring.")).getByTestId("note-notification-status")
2752
+ .tagName,
2753
+ ).toBe("SPAN");
2754
+ expect(
2755
+ within(openPostedStatus("Identified.")).queryByText(
2756
+ SubscriberNotificationResendCopy.retryNoteNotificationButton,
2757
+ ),
2758
+ ).toBeNull();
2759
+
2760
+ fireEvent.click(within(screen.getByRole("dialog")).getByText("Close"));
2761
+
2762
+ fireEvent.click(
2763
+ within(card("Investigating reports")).getByTestId(
2764
+ "note-update-notification-status",
2765
+ ),
2766
+ );
2767
+
2768
+ expect(
2769
+ within(
2770
+ screen.getByRole("dialog", { name: "Update notification" }),
2771
+ ).queryByText(
2772
+ SubscriberNotificationResendCopy.retryNoteNotificationButton,
2773
+ ),
2774
+ ).toBeNull();
2775
+ });
2776
+
2777
+ test("that editor may still edit a note, without the checkbox to notify subscribers", async () => {
2778
+ isMasterAdmin = false;
2779
+ currentPermissions = [
2780
+ Permission.EditIncidentPublicNote,
2781
+ Permission.ReadIncidentPublicNote,
2782
+ ];
2783
+ seedPublicNotes();
2784
+ await renderPublic(withResend());
2785
+
2786
+ const noteCard: HTMLElement = card("Monitoring.");
2787
+ await openActions(noteCard);
2788
+ fireEvent.click(
2789
+ within(noteCard).getByRole("menuitem", { name: "Edit note" }),
2790
+ );
2791
+ const composer: HTMLElement =
2792
+ await within(noteCard).findByTestId("note-edit-composer");
2793
+
2794
+ expect(within(composer).queryByTestId("note-notify-checkbox")).toBeNull();
2795
+ expect(
2796
+ within(composer).queryByText("Notify subscribers about this update"),
2797
+ ).toBeNull();
2798
+
2799
+ type(
2800
+ within(composer).getByLabelText("Note text") as HTMLTextAreaElement,
2801
+ "Monitoring. A fix is live everywhere.",
2802
+ );
2803
+ await act(async () => {
2804
+ fireEvent.click(within(composer).getByTestId("note-submit"));
2805
+ });
2806
+
2807
+ expect(updateMock).toHaveBeenCalledTimes(1);
2808
+ expect(
2809
+ (updateMock.mock.calls[0]![0] as { miscDataProps?: JSONObject })
2810
+ .miscDataProps,
2811
+ ).toEqual({});
2812
+ });
2813
+
2814
+ test("a role that may post and edit notes may retry an edit's notification", async () => {
2815
+ isMasterAdmin = false;
2816
+ currentPermissions = [
2817
+ Permission.CreateIncidentPublicNote,
2818
+ Permission.EditIncidentPublicNote,
2819
+ Permission.ReadIncidentPublicNote,
2820
+ ];
2821
+ seedPublicNotes();
2822
+ await renderPublic(withResend());
2823
+
2824
+ fireEvent.click(
2825
+ within(card("Investigating reports")).getByTestId(
2826
+ "note-update-notification-status",
2827
+ ),
2828
+ );
2829
+
2830
+ expect(
2831
+ within(
2832
+ screen.getByRole("dialog", { name: "Update notification" }),
2833
+ ).getByText(
2834
+ SubscriberNotificationResendCopy.retryNoteNotificationButton,
2835
+ ),
2836
+ ).toBeInTheDocument();
2837
+ });
2838
+
2839
+ test("a role that may post and edit notes is offered it", async () => {
2840
+ isMasterAdmin = false;
2841
+ currentPermissions = [
2842
+ Permission.CreateIncidentPublicNote,
2843
+ Permission.EditIncidentPublicNote,
2844
+ Permission.ReadIncidentPublicNote,
2845
+ ];
2846
+ seedPublicNotes();
2847
+ await renderPublic(withResend());
2848
+
2849
+ expect(
2850
+ within(openPostedStatus("Monitoring.")).getByText(
2851
+ SubscriberNotificationResendCopy.resendNoteNotificationButton,
2852
+ ),
2853
+ ).toBeInTheDocument();
2854
+ });
2855
+ });
2856
+ });