@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,713 @@
1
+ import Entities from "../../../Models/DatabaseModels/Index";
2
+ import IncidentCustomField from "../../../Models/DatabaseModels/IncidentCustomField";
3
+ import { backfillIncidentCustomFieldVariableKeys } from "../../../Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings";
4
+ import PostgresAppInstance from "../../../Server/Infrastructure/PostgresDatabase";
5
+ import CustomFieldMappingService from "../../../Server/Services/CustomFieldMappingService";
6
+ import IncidentCustomFieldService from "../../../Server/Services/IncidentCustomFieldService";
7
+ import IncidentService from "../../../Server/Services/IncidentService";
8
+ import IncidentTemplateService from "../../../Server/Services/IncidentTemplateService";
9
+ import TableViewService from "../../../Server/Services/TableViewService";
10
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
11
+ import CustomFieldType from "../../../Types/CustomField/CustomFieldType";
12
+ import { JSONObject } from "../../../Types/JSON";
13
+ import ObjectID from "../../../Types/ObjectID";
14
+ import Permission, {
15
+ UserTenantAccessPermission,
16
+ } from "../../../Types/Permission";
17
+ import UserType from "../../../Types/UserType";
18
+ import { DataSource, QueryRunner } from "typeorm";
19
+
20
+ /*
21
+ * Incident custom field keys and renames against a migrated Postgres.
22
+ *
23
+ * Opt in with RUN_POSTGRES_INCIDENT_CUSTOM_FIELD_TESTS=true against a database
24
+ * the registered migrations have been applied to, e.g.
25
+ *
26
+ * RUN_POSTGRES_INCIDENT_CUSTOM_FIELD_TESTS=true \
27
+ * INCIDENT_CUSTOM_FIELD_TEST_DATABASE_HOST=127.0.0.1 \
28
+ * INCIDENT_CUSTOM_FIELD_TEST_DATABASE_PORT=5400 \
29
+ * DATABASE_PASSWORD=... npx jest Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts
30
+ *
31
+ * CI runs it in .github/workflows/postgres-schema-drift.yaml, right after that
32
+ * job has applied every registered migration to an empty database. It works
33
+ * on structure-only clones (LIKE ... INCLUDING ALL, so the unique
34
+ * (projectId, variableKey) index comes along) of IncidentCustomField,
35
+ * Incident, IncidentTemplate and TableView in a uniquely named schema that is
36
+ * dropped afterwards; every row is synthetic.
37
+ *
38
+ * What it pins:
39
+ * - a project admin renaming a field through IncidentCustomFieldService
40
+ * moves its values in Incident and IncidentTemplate, and rewrites the
41
+ * incidents list's saved views, for that project only - without the
42
+ * "On Update" workflow of any incident, template or view, and without
43
+ * touching their version or updatedAt;
44
+ * - bags that are not objects, and other projects, are left alone;
45
+ * - the renamed field never picks up values it did not hold: whatever a
46
+ * record held under the new name without holding the old one (a
47
+ * deleted field's answer) is cleared;
48
+ * - a rename onto another field's name is refused and moves nothing;
49
+ * - the tables move together or not at all, and a rename whose values
50
+ * could not move leaves the field under its old name;
51
+ * - a new field gets a unique template key, and the index refuses a
52
+ * duplicate however it is written;
53
+ * - the migration's backfill gives every existing field a key, oldest
54
+ * first, and does nothing the second time.
55
+ */
56
+ const describePostgres: typeof describe =
57
+ process.env["RUN_POSTGRES_INCIDENT_CUSTOM_FIELD_TESTS"] === "true"
58
+ ? describe
59
+ : describe.skip;
60
+
61
+ const TABLES: Array<string> = [
62
+ "IncidentCustomField",
63
+ "Incident",
64
+ "IncidentTemplate",
65
+ "TableView",
66
+ ];
67
+
68
+ describePostgres(
69
+ "incident custom field keys and renames against Postgres",
70
+ () => {
71
+ const schema: string = `incident_custom_field_${ObjectID.generate()
72
+ .toString()
73
+ .replace(/-/g, "")}`;
74
+
75
+ const projectId: ObjectID = ObjectID.generate();
76
+ const otherProjectId: ObjectID = ObjectID.generate();
77
+
78
+ let database: DataSource;
79
+
80
+ function adminProps(): DatabaseCommonInteractionProps {
81
+ const tenantPermission: UserTenantAccessPermission = {
82
+ projectId: projectId,
83
+ _type: "UserTenantAccessPermission",
84
+ permissions: [Permission.ProjectAdmin].map((permission: Permission) => {
85
+ return {
86
+ _type: "UserPermission",
87
+ permission: permission,
88
+ labelIds: [],
89
+ isBlockPermission: false,
90
+ };
91
+ }),
92
+ };
93
+
94
+ return {
95
+ tenantId: projectId,
96
+ userId: ObjectID.generate(),
97
+ userType: UserType.User,
98
+ userTenantAccessPermission: {
99
+ [projectId.toString()]: tenantPermission,
100
+ },
101
+ };
102
+ }
103
+
104
+ async function createField(name: string): Promise<IncidentCustomField> {
105
+ const field: IncidentCustomField = new IncidentCustomField();
106
+ field.name = name;
107
+ field.customFieldType = CustomFieldType.Text;
108
+
109
+ return await IncidentCustomFieldService.create({
110
+ data: field,
111
+ props: adminProps(),
112
+ });
113
+ }
114
+
115
+ async function seedIncident(
116
+ customFields: unknown,
117
+ project: ObjectID = projectId,
118
+ ): Promise<ObjectID> {
119
+ const id: ObjectID = ObjectID.generate();
120
+ await database.query(
121
+ `INSERT INTO "${schema}"."Incident"
122
+ ("_id", "projectId", "title", "slug", "currentIncidentStateId", "incidentSeverityId", "customFields", "version", "updatedAt")
123
+ VALUES ($1, $2, 'Shared uplink down', $3, $4, $5, $6::jsonb, 3, '2026-01-01T00:00:00Z')`,
124
+ [
125
+ id.toString(),
126
+ project.toString(),
127
+ `incident-${id.toString()}`,
128
+ ObjectID.generate().toString(),
129
+ ObjectID.generate().toString(),
130
+ customFields === null ? null : JSON.stringify(customFields),
131
+ ],
132
+ );
133
+ return id;
134
+ }
135
+
136
+ async function seedTemplate(
137
+ customFields: unknown,
138
+ project: ObjectID = projectId,
139
+ ): Promise<ObjectID> {
140
+ const id: ObjectID = ObjectID.generate();
141
+ await database.query(
142
+ `INSERT INTO "${schema}"."IncidentTemplate"
143
+ ("_id", "projectId", "title", "templateName", "templateDescription", "slug", "customFields", "version")
144
+ VALUES ($1, $2, 'Site outage', 'Site outage', 'A site is down', $3, $4::jsonb, 1)`,
145
+ [
146
+ id.toString(),
147
+ project.toString(),
148
+ `template-${id.toString()}`,
149
+ JSON.stringify(customFields),
150
+ ],
151
+ );
152
+ return id;
153
+ }
154
+
155
+ async function seedTableView(data: {
156
+ tableId: string;
157
+ columns?: JSONObject;
158
+ facets?: JSONObject;
159
+ project?: ObjectID;
160
+ }): Promise<ObjectID> {
161
+ const id: ObjectID = ObjectID.generate();
162
+ await database.query(
163
+ `INSERT INTO "${schema}"."TableView"
164
+ ("_id", "projectId", "name", "tableId", "query", "sort", "columns", "facets", "version", "updatedAt")
165
+ VALUES ($1, $2, 'My view', $3, '{}'::jsonb, '{}'::jsonb, $4::jsonb, $5::jsonb, 1, '2026-01-01T00:00:00Z')`,
166
+ [
167
+ id.toString(),
168
+ (data.project || projectId).toString(),
169
+ data.tableId,
170
+ data.columns ? JSON.stringify(data.columns) : null,
171
+ data.facets ? JSON.stringify(data.facets) : null,
172
+ ],
173
+ );
174
+ return id;
175
+ }
176
+
177
+ async function readRow(
178
+ table: string,
179
+ id: ObjectID,
180
+ ): Promise<{
181
+ customFields: unknown;
182
+ version: number;
183
+ updatedAt: Date;
184
+ columns?: unknown;
185
+ facets?: unknown;
186
+ }> {
187
+ const rows: Array<{
188
+ customFields: unknown;
189
+ version: number;
190
+ updatedAt: Date;
191
+ columns?: unknown;
192
+ facets?: unknown;
193
+ }> = await database.query(
194
+ `SELECT * FROM "${schema}"."${table}" WHERE "_id" = $1`,
195
+ [id.toString()],
196
+ );
197
+ return rows[0]!;
198
+ }
199
+
200
+ beforeAll(async () => {
201
+ database = new DataSource({
202
+ type: "postgres",
203
+ host:
204
+ process.env["INCIDENT_CUSTOM_FIELD_TEST_DATABASE_HOST"] ||
205
+ "localhost",
206
+ port: Number(
207
+ process.env["INCIDENT_CUSTOM_FIELD_TEST_DATABASE_PORT"] || "5400",
208
+ ),
209
+ username: process.env["DATABASE_USERNAME"] || "postgres",
210
+ password: process.env["DATABASE_PASSWORD"] || "password",
211
+ database:
212
+ process.env["INCIDENT_CUSTOM_FIELD_TEST_DATABASE_NAME"] ||
213
+ process.env["DATABASE_NAME"] ||
214
+ "oneuptimedb",
215
+ entities: Entities,
216
+ schema,
217
+ synchronize: false,
218
+ extra: { options: `-c search_path=${schema},public` },
219
+ });
220
+ await database.initialize();
221
+ await database.query(`CREATE SCHEMA "${schema}"`);
222
+
223
+ for (const table of TABLES) {
224
+ await database.query(
225
+ `CREATE TABLE "${schema}"."${table}" (LIKE public."${table}" INCLUDING ALL)`,
226
+ );
227
+ }
228
+
229
+ jest.spyOn(PostgresAppInstance, "isConnected").mockReturnValue(true);
230
+ jest
231
+ .spyOn(PostgresAppInstance, "getDataSource")
232
+ .mockReturnValue(database);
233
+
234
+ jest
235
+ .spyOn(IncidentCustomFieldService, "onTriggerRealtime")
236
+ .mockResolvedValue();
237
+ jest
238
+ .spyOn(IncidentCustomFieldService, "onTriggerWorkflow")
239
+ .mockResolvedValue();
240
+ // The mapping backfill after a save has no mapping to apply here.
241
+ jest
242
+ .spyOn(CustomFieldMappingService, "backfillProject")
243
+ .mockResolvedValue();
244
+ });
245
+
246
+ let incidentWorkflow: jest.SpiedFunction<
247
+ typeof IncidentService.onTriggerWorkflow
248
+ >;
249
+ let templateWorkflow: jest.SpiedFunction<
250
+ typeof IncidentTemplateService.onTriggerWorkflow
251
+ >;
252
+ let tableViewWorkflow: jest.SpiedFunction<
253
+ typeof TableViewService.onTriggerWorkflow
254
+ >;
255
+
256
+ beforeEach(async () => {
257
+ await database.query(
258
+ TABLES.map((table: string): string => {
259
+ return `DELETE FROM "${schema}"."${table}"`;
260
+ }).join("; "),
261
+ );
262
+
263
+ incidentWorkflow = jest
264
+ .spyOn(IncidentService, "onTriggerWorkflow")
265
+ .mockResolvedValue();
266
+ templateWorkflow = jest
267
+ .spyOn(IncidentTemplateService, "onTriggerWorkflow")
268
+ .mockResolvedValue();
269
+ tableViewWorkflow = jest
270
+ .spyOn(TableViewService, "onTriggerWorkflow")
271
+ .mockResolvedValue();
272
+ incidentWorkflow.mockClear();
273
+ templateWorkflow.mockClear();
274
+ tableViewWorkflow.mockClear();
275
+ });
276
+
277
+ afterAll(async () => {
278
+ jest.restoreAllMocks();
279
+ if (database?.isInitialized) {
280
+ await database.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
281
+ await database.destroy();
282
+ }
283
+ });
284
+
285
+ test("renaming a field moves its values, and its saved views, and starts no workflow", async () => {
286
+ const impact: IncidentCustomField = await createField("Impact");
287
+
288
+ const withValue: ObjectID = await seedIncident({
289
+ Impact: "High",
290
+ Region: "East",
291
+ });
292
+ const withStaleNewKey: ObjectID = await seedIncident({
293
+ Impact: "Low",
294
+ "Business Impact": "stale",
295
+ });
296
+ const withoutValue: ObjectID = await seedIncident({ Region: "West" });
297
+ const withOnlyStaleNewKey: ObjectID = await seedIncident({
298
+ "Business Impact": "stale",
299
+ Region: "North",
300
+ });
301
+ const withNoBag: ObjectID = await seedIncident(null);
302
+ const withArrayBag: ObjectID = await seedIncident(["Impact"]);
303
+ const otherProjects: ObjectID = await seedIncident(
304
+ { Impact: "High" },
305
+ otherProjectId,
306
+ );
307
+ const otherProjectsStaleNewKey: ObjectID = await seedIncident(
308
+ { "Business Impact": "theirs" },
309
+ otherProjectId,
310
+ );
311
+
312
+ const template: ObjectID = await seedTemplate({ Impact: "Medium" });
313
+ const staleTemplate: ObjectID = await seedTemplate({
314
+ "Business Impact": "stale",
315
+ });
316
+ const otherProjectsTemplate: ObjectID = await seedTemplate(
317
+ { Impact: "Medium" },
318
+ otherProjectId,
319
+ );
320
+
321
+ const incidentsView: ObjectID = await seedTableView({
322
+ tableId: "all-incidents-table",
323
+ columns: { order: ["title", "customFields.Impact"], hidden: [] },
324
+ facets: {
325
+ facetSelections: { "customField:Impact": ["High"] },
326
+ facetOperators: { "customField:Impact": "is" },
327
+ },
328
+ });
329
+ const monitorsView: ObjectID = await seedTableView({
330
+ tableId: "all-monitors-table",
331
+ columns: { order: ["customFields.Impact"], hidden: [] },
332
+ });
333
+ const otherProjectsView: ObjectID = await seedTableView({
334
+ tableId: "all-incidents-table",
335
+ columns: { order: ["customFields.Impact"], hidden: [] },
336
+ project: otherProjectId,
337
+ });
338
+
339
+ await IncidentCustomFieldService.updateOneById({
340
+ id: impact.id!,
341
+ data: { name: "Business Impact" },
342
+ props: adminProps(),
343
+ });
344
+
345
+ const moved: {
346
+ customFields: unknown;
347
+ version: number;
348
+ updatedAt: Date;
349
+ } = await readRow("Incident", withValue);
350
+
351
+ expect(moved.customFields).toEqual({
352
+ "Business Impact": "High",
353
+ Region: "East",
354
+ });
355
+ // Not an edit of the incident: no version bump, updatedAt as it was.
356
+ expect(moved.version).toBe(3);
357
+ expect(new Date(moved.updatedAt).toISOString()).toBe(
358
+ "2026-01-01T00:00:00.000Z",
359
+ );
360
+
361
+ // The renamed field's value wins over a stale one under the new name.
362
+ expect((await readRow("Incident", withStaleNewKey)).customFields).toEqual(
363
+ {
364
+ "Business Impact": "Low",
365
+ },
366
+ );
367
+ expect((await readRow("Incident", withoutValue)).customFields).toEqual({
368
+ Region: "West",
369
+ });
370
+ // A value under the new name that the field never held is cleared.
371
+ const cleared: { customFields: unknown; version: number } = await readRow(
372
+ "Incident",
373
+ withOnlyStaleNewKey,
374
+ );
375
+ expect(cleared.customFields).toEqual({ Region: "North" });
376
+ expect(cleared.version).toBe(3);
377
+ expect((await readRow("Incident", withNoBag)).customFields).toBeNull();
378
+ expect((await readRow("Incident", withArrayBag)).customFields).toEqual([
379
+ "Impact",
380
+ ]);
381
+ expect((await readRow("Incident", otherProjects)).customFields).toEqual({
382
+ Impact: "High",
383
+ });
384
+ expect(
385
+ (await readRow("Incident", otherProjectsStaleNewKey)).customFields,
386
+ ).toEqual({ "Business Impact": "theirs" });
387
+
388
+ expect(
389
+ (await readRow("IncidentTemplate", template)).customFields,
390
+ ).toEqual({ "Business Impact": "Medium" });
391
+ expect(
392
+ (await readRow("IncidentTemplate", staleTemplate)).customFields,
393
+ ).toEqual({});
394
+ expect(
395
+ (await readRow("IncidentTemplate", otherProjectsTemplate)).customFields,
396
+ ).toEqual({ Impact: "Medium" });
397
+
398
+ const view: { columns?: unknown; facets?: unknown; updatedAt: Date } =
399
+ await readRow("TableView", incidentsView);
400
+ expect(view.columns).toEqual({
401
+ order: ["title", "customFields.Business Impact"],
402
+ hidden: [],
403
+ });
404
+ expect(view.facets).toEqual({
405
+ facetSelections: { "customField:Business Impact": ["High"] },
406
+ facetOperators: { "customField:Business Impact": "is" },
407
+ });
408
+ expect(new Date(view.updatedAt).toISOString()).toBe(
409
+ "2026-01-01T00:00:00.000Z",
410
+ );
411
+
412
+ // A monitors table's field of the same name is a different field.
413
+ expect((await readRow("TableView", monitorsView)).columns).toEqual({
414
+ order: ["customFields.Impact"],
415
+ hidden: [],
416
+ });
417
+ expect((await readRow("TableView", otherProjectsView)).columns).toEqual({
418
+ order: ["customFields.Impact"],
419
+ hidden: [],
420
+ });
421
+
422
+ expect(incidentWorkflow).not.toHaveBeenCalled();
423
+ expect(templateWorkflow).not.toHaveBeenCalled();
424
+ expect(tableViewWorkflow).not.toHaveBeenCalled();
425
+
426
+ // The key stays what it was made from.
427
+ const stored: IncidentCustomField | null =
428
+ await IncidentCustomFieldService.findOneById({
429
+ id: impact.id!,
430
+ select: { name: true, variableKey: true },
431
+ props: { isRoot: true },
432
+ });
433
+ expect(stored?.name).toBe("Business Impact");
434
+ expect(stored?.variableKey).toBe("impact");
435
+ });
436
+
437
+ /*
438
+ * Deleting a field leaves its values in the incidents. A field flagged
439
+ * for subscriber notifications renamed onto that name must not start
440
+ * sending the deleted field's answers, which were never shared.
441
+ */
442
+ test("a field renamed onto a deleted field's name does not inherit its values", async () => {
443
+ // "Internal RCA" was a field once; its values outlived it.
444
+ const customerImpact: IncidentCustomField =
445
+ await createField("Customer Impact");
446
+ const answered: ObjectID = await seedIncident({
447
+ "Customer Impact": "Checkout is slow",
448
+ "Internal RCA": "Bad deploy by team X",
449
+ });
450
+ const unanswered: ObjectID = await seedIncident({
451
+ "Internal RCA": "Expired certificate",
452
+ });
453
+ const template: ObjectID = await seedTemplate({
454
+ "Internal RCA": "Fill in after the review",
455
+ });
456
+
457
+ await IncidentCustomFieldService.updateOneById({
458
+ id: customerImpact.id!,
459
+ data: { name: "Internal RCA" },
460
+ props: adminProps(),
461
+ });
462
+
463
+ expect((await readRow("Incident", answered)).customFields).toEqual({
464
+ "Internal RCA": "Checkout is slow",
465
+ });
466
+ expect((await readRow("Incident", unanswered)).customFields).toEqual({});
467
+ expect(
468
+ (await readRow("IncidentTemplate", template)).customFields,
469
+ ).toEqual({});
470
+ expect(incidentWorkflow).not.toHaveBeenCalled();
471
+ expect(templateWorkflow).not.toHaveBeenCalled();
472
+ });
473
+
474
+ /*
475
+ * The move also clears stale values under the new name, so a rename
476
+ * that moved one table and not the other could not be undone by
477
+ * renaming back: that would clear what the second table still held. So
478
+ * the tables move together or not at all, and when they do not, the
479
+ * field takes its old name back.
480
+ */
481
+ test("a move that fails in one table moves nothing, and the field keeps its old name", async () => {
482
+ const impact: IncidentCustomField = await createField("Impact");
483
+ const incident: ObjectID = await seedIncident({ Impact: "High" });
484
+ const template: ObjectID = await seedTemplate({ Impact: "Medium" });
485
+
486
+ // The second table's statement fails.
487
+ await database.query(
488
+ `ALTER TABLE "${schema}"."IncidentTemplate" RENAME COLUMN "customFields" TO "customFieldsBroken"`,
489
+ );
490
+
491
+ try {
492
+ await expect(
493
+ IncidentCustomFieldService.updateOneById({
494
+ id: impact.id!,
495
+ data: { name: "Business Impact" },
496
+ props: adminProps(),
497
+ }),
498
+ ).rejects.toThrow();
499
+ } finally {
500
+ await database.query(
501
+ `ALTER TABLE "${schema}"."IncidentTemplate" RENAME COLUMN "customFieldsBroken" TO "customFields"`,
502
+ );
503
+ }
504
+
505
+ // The first table's move was rolled back with the second's.
506
+ expect((await readRow("Incident", incident)).customFields).toEqual({
507
+ Impact: "High",
508
+ });
509
+ expect(
510
+ (await readRow("IncidentTemplate", template)).customFields,
511
+ ).toEqual({ Impact: "Medium" });
512
+
513
+ const stored: IncidentCustomField | null =
514
+ await IncidentCustomFieldService.findOneById({
515
+ id: impact.id!,
516
+ select: { name: true },
517
+ props: { isRoot: true },
518
+ });
519
+ expect(stored?.name).toBe("Impact");
520
+
521
+ // And the rename can simply be tried again.
522
+ await IncidentCustomFieldService.updateOneById({
523
+ id: impact.id!,
524
+ data: { name: "Business Impact" },
525
+ props: adminProps(),
526
+ });
527
+ expect((await readRow("Incident", incident)).customFields).toEqual({
528
+ "Business Impact": "High",
529
+ });
530
+ expect(
531
+ (await readRow("IncidentTemplate", template)).customFields,
532
+ ).toEqual({ "Business Impact": "Medium" });
533
+ });
534
+
535
+ test("renaming back moves the values back", async () => {
536
+ const impact: IncidentCustomField = await createField("Impact");
537
+ const incident: ObjectID = await seedIncident({ Impact: "High" });
538
+
539
+ await IncidentCustomFieldService.updateOneById({
540
+ id: impact.id!,
541
+ data: { name: "Severity" },
542
+ props: adminProps(),
543
+ });
544
+ await IncidentCustomFieldService.updateOneById({
545
+ id: impact.id!,
546
+ data: { name: "Impact" },
547
+ props: adminProps(),
548
+ });
549
+
550
+ expect((await readRow("Incident", incident)).customFields).toEqual({
551
+ Impact: "High",
552
+ });
553
+ });
554
+
555
+ test("a rename onto another field's name is refused and moves nothing", async () => {
556
+ await createField("Region");
557
+ const impact: IncidentCustomField = await createField("Impact");
558
+ const incident: ObjectID = await seedIncident({
559
+ Impact: "High",
560
+ Region: "East",
561
+ });
562
+
563
+ await expect(
564
+ IncidentCustomFieldService.updateOneById({
565
+ id: impact.id!,
566
+ data: { name: " region " },
567
+ props: adminProps(),
568
+ }),
569
+ ).rejects.toThrow("Another incident custom field already has this name.");
570
+
571
+ expect((await readRow("Incident", incident)).customFields).toEqual({
572
+ Impact: "High",
573
+ Region: "East",
574
+ });
575
+ });
576
+
577
+ test("new fields get unique template keys, and the index refuses a duplicate", async () => {
578
+ const first: IncidentCustomField = await createField(
579
+ "Expected Resolution",
580
+ );
581
+ const second: IncidentCustomField = await createField(
582
+ "expected-resolution",
583
+ );
584
+ const third: IncidentCustomField = await createField("影响");
585
+
586
+ const keys: Array<{ name: string; variableKey: string }> =
587
+ await database.query(
588
+ `SELECT "name", "variableKey" FROM "${schema}"."IncidentCustomField" WHERE "_id" = ANY($1::uuid[]) ORDER BY "createdAt"`,
589
+ [[first.id!.toString(), second.id!.toString(), third.id!.toString()]],
590
+ );
591
+
592
+ expect(keys).toEqual([
593
+ { name: "Expected Resolution", variableKey: "expected_resolution" },
594
+ { name: "expected-resolution", variableKey: "expected_resolution_2" },
595
+ { name: "影响", variableKey: "field" },
596
+ ]);
597
+
598
+ await expect(
599
+ database.query(
600
+ `INSERT INTO "${schema}"."IncidentCustomField" ("_id", "projectId", "name", "variableKey", "version")
601
+ VALUES ($1, $2, 'Sneaky', 'expected_resolution', 1)`,
602
+ [ObjectID.generate().toString(), projectId.toString()],
603
+ ),
604
+ ).rejects.toThrow(/duplicate key value/);
605
+
606
+ // The same key in another project is fine.
607
+ await database.query(
608
+ `INSERT INTO "${schema}"."IncidentCustomField" ("_id", "projectId", "name", "variableKey", "version")
609
+ VALUES ($1, $2, 'Expected Resolution', 'expected_resolution', 1)`,
610
+ [ObjectID.generate().toString(), otherProjectId.toString()],
611
+ );
612
+ });
613
+
614
+ test("an update cannot change a field's template key", async () => {
615
+ const impact: IncidentCustomField = await createField("Impact");
616
+
617
+ await IncidentCustomFieldService.updateOneById({
618
+ id: impact.id!,
619
+ data: { variableKey: "hijacked", description: "Business impact" },
620
+ props: adminProps(),
621
+ });
622
+
623
+ const rows: Array<{ variableKey: string; description: string }> =
624
+ await database.query(
625
+ `SELECT "variableKey", "description" FROM "${schema}"."IncidentCustomField" WHERE "_id" = $1`,
626
+ [impact.id!.toString()],
627
+ );
628
+
629
+ expect(rows[0]).toEqual({
630
+ variableKey: "impact",
631
+ description: "Business impact",
632
+ });
633
+ });
634
+
635
+ test("the migration's backfill keys every existing field, oldest first, once", async () => {
636
+ const seed: (
637
+ name: string,
638
+ project: ObjectID,
639
+ daysAgo: number,
640
+ variableKey?: string,
641
+ ) => Promise<string> = async (
642
+ name: string,
643
+ project: ObjectID,
644
+ daysAgo: number,
645
+ variableKey?: string,
646
+ ): Promise<string> => {
647
+ const id: string = ObjectID.generate().toString();
648
+ await database.query(
649
+ `INSERT INTO "${schema}"."IncidentCustomField" ("_id", "projectId", "name", "variableKey", "createdAt", "version")
650
+ VALUES ($1, $2, $3, $4, now() - ($5 || ' days')::interval, 1)`,
651
+ [id, project.toString(), name, variableKey ?? null, String(daysAgo)],
652
+ );
653
+ return id;
654
+ };
655
+
656
+ /*
657
+ * The migration hands the backfill its own QueryRunner; so does this,
658
+ * one from the test's data source, on the test's schema.
659
+ */
660
+ const backfill: () => Promise<number> = async (): Promise<number> => {
661
+ const queryRunner: QueryRunner = database.createQueryRunner();
662
+
663
+ try {
664
+ return await backfillIncidentCustomFieldVariableKeys(queryRunner);
665
+ } finally {
666
+ await queryRunner.release();
667
+ }
668
+ };
669
+
670
+ const newest: string = await seed("Impact", projectId, 1);
671
+ const oldest: string = await seed("impact!", projectId, 5);
672
+ const keyed: string = await seed("Region", projectId, 3, "impact_2");
673
+ const accented: string = await seed("Größe", projectId, 2);
674
+ const elsewhere: string = await seed("Impact", otherProjectId, 1);
675
+
676
+ // Enough rows to span several UPDATE batches.
677
+ const many: Array<string> = [];
678
+ for (let i: number = 0; i < 1200; i++) {
679
+ many.push(await seed(`Field ${i % 3}`, otherProjectId, 10));
680
+ }
681
+
682
+ const given: number = await backfill();
683
+
684
+ expect(given).toBe(4 + 1200);
685
+
686
+ const keyOf: (id: string) => Promise<string> = async (
687
+ id: string,
688
+ ): Promise<string> => {
689
+ const rows: Array<{ variableKey: string }> = await database.query(
690
+ `SELECT "variableKey" FROM "${schema}"."IncidentCustomField" WHERE "_id" = $1`,
691
+ [id],
692
+ );
693
+ return rows[0]!.variableKey;
694
+ };
695
+
696
+ expect(await keyOf(oldest)).toBe("impact");
697
+ expect(await keyOf(keyed)).toBe("impact_2");
698
+ expect(await keyOf(newest)).toBe("impact_3");
699
+ expect(await keyOf(accented)).toBe("grosse");
700
+ expect(await keyOf(elsewhere)).toBe("impact");
701
+
702
+ const manyKeys: Array<{ count: number }> = await database.query(
703
+ `SELECT COUNT(DISTINCT "variableKey")::int AS "count" FROM "${schema}"."IncidentCustomField" WHERE "_id" = ANY($1::uuid[])`,
704
+ [many],
705
+ );
706
+ expect(manyKeys[0]!.count).toBe(1200);
707
+
708
+ // A second run finds nothing to do and changes nothing.
709
+ expect(await backfill()).toBe(0);
710
+ expect(await keyOf(newest)).toBe("impact_3");
711
+ });
712
+ },
713
+ );