@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,586 @@
1
+ import {
2
+ MoveCustomFieldValueKeyResult,
3
+ moveCustomFieldValueKey,
4
+ renameCustomFieldInTableViews,
5
+ } from "../../../../Server/Utils/CustomField/CustomFieldRename";
6
+ import DatabaseService from "../../../../Server/Services/DatabaseService";
7
+ import TableViewService from "../../../../Server/Services/TableViewService";
8
+ import TableView from "../../../../Models/DatabaseModels/TableView";
9
+ import ObjectID from "../../../../Types/ObjectID";
10
+ import { JSONObject } from "../../../../Types/JSON";
11
+ import getJestMockFunction, { MockFunction } from "../../../MockType";
12
+ import {
13
+ afterEach,
14
+ beforeEach,
15
+ describe,
16
+ expect,
17
+ jest,
18
+ test,
19
+ } from "@jest/globals";
20
+
21
+ /*
22
+ * Renaming a custom field.
23
+ *
24
+ * A custom field's values are stored in each record's `customFields` bag under
25
+ * the field's display name, so a rename has to move the key or every stored
26
+ * answer is orphaned. This does it with one raw UPDATE per table rather than
27
+ * through DatabaseService, deliberately: updateBy fires the model's "on
28
+ * update" workflow trigger per row, and customers run workflows on incident
29
+ * custom fields. A rename is a change of label, not of anyone's answers.
30
+ *
31
+ * NOT the only cover this has, and deliberately not the same cover.
32
+ * Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts drives a real
33
+ * rename through IncidentCustomFieldService against a migrated Postgres and
34
+ * pins the outcome: values moved in Incident and IncidentTemplate, saved views
35
+ * rewritten, other projects untouched, no workflow fired. That is the stronger
36
+ * test of the two and it is where the behaviour lives. But it is opt-in
37
+ * (RUN_POSTGRES_INCIDENT_CUSTOM_FIELD_TESTS) and runs only in the
38
+ * postgres-schema-drift workflow, after that job has a migrated database to
39
+ * work with; it does not run in Common Test, so on an ordinary change to this
40
+ * file nothing here goes red. These tests do, and they cover the statement
41
+ * itself rather than its effect.
42
+ *
43
+ * Two things make that worth doing without a database. The statement is
44
+ * assembled from entity metadata, and the claim that it is not an injection
45
+ * surface rests on the names coming from metadata and every value being bound
46
+ * -- assertable exactly here. And its second job is destructive: it *removes*
47
+ * what a record holds under the new name, because a deleted field leaves its
48
+ * values behind and the renamed field must not adopt them. A record picking up
49
+ * a stranger's value is the bug this guards, and on an incident field marked
50
+ * for subscriber notifications it would be sent out.
51
+ *
52
+ * So the query is captured rather than run. What Postgres does with the
53
+ * statement is not in scope here; what is in scope is that the right statement,
54
+ * with the right bound values, is what gets sent -- and that the early returns
55
+ * send nothing at all.
56
+ */
57
+
58
+ const PROJECT_ID: ObjectID = new ObjectID(
59
+ "a0000000-0000-4000-8000-000000000001",
60
+ );
61
+
62
+ interface CapturedQuery {
63
+ sql: string;
64
+ parameters: Array<unknown>;
65
+ }
66
+
67
+ let queries: Array<CapturedQuery> = [];
68
+ let queryResult: unknown = [{ moved: 0, cleared: 0 }];
69
+
70
+ // Stands in for the service's repository: the metadata the SQL is built from.
71
+ function serviceFor(data: {
72
+ tableName: string;
73
+ columns: Record<string, string>;
74
+ }): DatabaseService<any> {
75
+ const query: MockFunction = getJestMockFunction();
76
+ query.mockImplementation(
77
+ (sql: string, parameters: Array<unknown>): Promise<unknown> => {
78
+ queries.push({ sql: sql, parameters: parameters });
79
+ return Promise.resolve(queryResult);
80
+ },
81
+ );
82
+
83
+ return {
84
+ getRepository: (): unknown => {
85
+ return {
86
+ metadata: {
87
+ tableName: data.tableName,
88
+ findColumnWithPropertyName: (
89
+ propertyName: string,
90
+ ): { databaseName: string } | undefined => {
91
+ const databaseName: string | undefined = data.columns[propertyName];
92
+ return databaseName ? { databaseName: databaseName } : undefined;
93
+ },
94
+ },
95
+ manager: { query: query },
96
+ };
97
+ },
98
+ } as unknown as DatabaseService<any>;
99
+ }
100
+
101
+ function incidentService(): DatabaseService<any> {
102
+ return serviceFor({
103
+ tableName: "Incident",
104
+ columns: { customFields: "customFields", projectId: "projectId" },
105
+ });
106
+ }
107
+
108
+ beforeEach(() => {
109
+ queries = [];
110
+ queryResult = [{ moved: 0, cleared: 0 }];
111
+ });
112
+
113
+ afterEach(() => {
114
+ jest.restoreAllMocks();
115
+ });
116
+
117
+ describe("moveCustomFieldValueKey", () => {
118
+ describe("what it refuses to run at all", () => {
119
+ /*
120
+ * Each of these would be a destructive no-op if it reached the database:
121
+ * with the two names equal the statement's ELSE branch deletes the very
122
+ * key it was asked to keep, and a blank name matches no field.
123
+ */
124
+ test.each([
125
+ ["the names are the same", "Impact", "Impact"],
126
+ ["the old name is blank", "", "Business Impact"],
127
+ ["the new name is blank", "Impact", ""],
128
+ ])(
129
+ "sends no statement when %s",
130
+ async (_case: string, oldName: string, newName: string) => {
131
+ const result: MoveCustomFieldValueKeyResult =
132
+ await moveCustomFieldValueKey({
133
+ service: incidentService(),
134
+ projectId: PROJECT_ID,
135
+ oldName: oldName,
136
+ newName: newName,
137
+ });
138
+
139
+ expect(queries).toHaveLength(0);
140
+ expect(result).toEqual({ moved: 0, cleared: 0 });
141
+ },
142
+ );
143
+ });
144
+
145
+ describe("the statement it sends", () => {
146
+ beforeEach(async () => {
147
+ await moveCustomFieldValueKey({
148
+ service: incidentService(),
149
+ projectId: PROJECT_ID,
150
+ oldName: "Impact",
151
+ newName: "Business Impact",
152
+ });
153
+ });
154
+
155
+ test("runs exactly one statement", () => {
156
+ expect(queries).toHaveLength(1);
157
+ });
158
+
159
+ /*
160
+ * The injection claim, stated as a test: the only things interpolated are
161
+ * the table and column names read from the entity metadata. Neither name
162
+ * the caller passed appears in the SQL text.
163
+ */
164
+ test("interpolates only names taken from the entity metadata", () => {
165
+ expect(queries[0]!.sql).toContain('UPDATE "Incident"');
166
+ expect(queries[0]!.sql).toContain('"customFields"');
167
+ expect(queries[0]!.sql).toContain('"projectId" = $1');
168
+ });
169
+
170
+ test("carries both field names as bound parameters, never as SQL text", () => {
171
+ expect(queries[0]!.parameters).toEqual([
172
+ PROJECT_ID.toString(),
173
+ "Impact",
174
+ "Business Impact",
175
+ ]);
176
+ expect(queries[0]!.sql).not.toContain("Impact");
177
+ });
178
+
179
+ // Without this the rename would run across every project on the instance.
180
+ test("is scoped to the project", () => {
181
+ expect(queries[0]!.sql).toContain('WHERE "projectId" = $1');
182
+ expect(queries[0]!.parameters[0]).toBe(PROJECT_ID.toString());
183
+ });
184
+
185
+ /*
186
+ * The clean-start rule: a record with nothing under the old name has the
187
+ * new name removed, so a deleted field's leftover answer is not adopted by
188
+ * the renamed field.
189
+ */
190
+ test("clears the new name from records that hold nothing under the old one", () => {
191
+ expect(queries[0]!.sql).toContain('ELSE "customFields" - $3::text');
192
+ });
193
+
194
+ test("moves the value out from under the old name and back under the new one", () => {
195
+ expect(queries[0]!.sql).toContain(
196
+ '("customFields" - $2::text) || jsonb_build_object($3::text, "customFields" -> $2::text)',
197
+ );
198
+ });
199
+
200
+ /*
201
+ * `-` and `?` act on a jsonb array's elements rather than on object keys,
202
+ * so a bag that is not an object is not a bag of named values.
203
+ */
204
+ test("only touches rows whose bag is a JSON object", () => {
205
+ expect(queries[0]!.sql).toContain(
206
+ `jsonb_typeof("customFields") = 'object'`,
207
+ );
208
+ });
209
+
210
+ test("only touches rows that hold one of the two names", () => {
211
+ expect(queries[0]!.sql).toContain(
212
+ 'jsonb_exists("customFields", $2::text)',
213
+ );
214
+ expect(queries[0]!.sql).toContain(
215
+ 'jsonb_exists("customFields", $3::text)',
216
+ );
217
+ });
218
+ });
219
+
220
+ describe("the counts it reports", () => {
221
+ async function moveWithResult(
222
+ result: unknown,
223
+ ): Promise<MoveCustomFieldValueKeyResult> {
224
+ queryResult = result;
225
+
226
+ return moveCustomFieldValueKey({
227
+ service: incidentService(),
228
+ projectId: PROJECT_ID,
229
+ oldName: "Impact",
230
+ newName: "Business Impact",
231
+ });
232
+ }
233
+
234
+ test("reports what the statement returned", async () => {
235
+ await expect(moveWithResult([{ moved: 3, cleared: 2 }])).resolves.toEqual(
236
+ { moved: 3, cleared: 2 },
237
+ );
238
+ });
239
+
240
+ /*
241
+ * Postgres hands back bigint-ish counts as strings through some drivers,
242
+ * and a count that arrived as "3" must not be reported as 0.
243
+ */
244
+ test("reads counts that arrived as strings", async () => {
245
+ await expect(
246
+ moveWithResult([{ moved: "3", cleared: "2" }]),
247
+ ).resolves.toEqual({ moved: 3, cleared: 2 });
248
+ });
249
+
250
+ test.each([
251
+ ["no rows came back", []],
252
+ ["the answer was not a list", { moved: 1 }],
253
+ ["the row is missing the counts", [{}]],
254
+ ["a count is null", [{ moved: null, cleared: null }]],
255
+ ["a count is not a number", [{ moved: "many", cleared: "some" }]],
256
+ ])("reports zero when %s", async (_case: string, result: unknown) => {
257
+ await expect(moveWithResult(result)).resolves.toEqual({
258
+ moved: 0,
259
+ cleared: 0,
260
+ });
261
+ });
262
+ });
263
+
264
+ describe("where it runs", () => {
265
+ test("uses the transaction it was given, so the rename is one unit of work", async () => {
266
+ const transactionQuery: MockFunction = getJestMockFunction();
267
+ transactionQuery.mockImplementation((): Promise<unknown> => {
268
+ return Promise.resolve([{ moved: 1, cleared: 0 }]);
269
+ });
270
+
271
+ const service: DatabaseService<any> = incidentService();
272
+
273
+ await moveCustomFieldValueKey({
274
+ service: service,
275
+ projectId: PROJECT_ID,
276
+ oldName: "Impact",
277
+ newName: "Business Impact",
278
+ manager: { query: transactionQuery } as never,
279
+ });
280
+
281
+ expect(transactionQuery).toHaveBeenCalledTimes(1);
282
+ // Nothing went to the repository's own manager, outside the transaction.
283
+ expect(queries).toHaveLength(0);
284
+ });
285
+
286
+ test("falls back to the repository's manager when there is no transaction", async () => {
287
+ await moveCustomFieldValueKey({
288
+ service: incidentService(),
289
+ projectId: PROJECT_ID,
290
+ oldName: "Impact",
291
+ newName: "Business Impact",
292
+ });
293
+
294
+ expect(queries).toHaveLength(1);
295
+ });
296
+ });
297
+
298
+ describe("a table it cannot rename on", () => {
299
+ /*
300
+ * Better to fail than to build a statement against a guessed column name:
301
+ * the model would be one that has no custom fields at all.
302
+ */
303
+ test("refuses a table with no customFields column, and names it", async () => {
304
+ const service: DatabaseService<any> = serviceFor({
305
+ tableName: "Label",
306
+ columns: { projectId: "projectId" },
307
+ });
308
+
309
+ await expect(
310
+ moveCustomFieldValueKey({
311
+ service: service,
312
+ projectId: PROJECT_ID,
313
+ oldName: "Impact",
314
+ newName: "Business Impact",
315
+ }),
316
+ ).rejects.toThrow(
317
+ 'Cannot rename a custom field on Label: it has no "customFields" column.',
318
+ );
319
+
320
+ expect(queries).toHaveLength(0);
321
+ });
322
+
323
+ test("refuses a table with no projectId column", async () => {
324
+ const service: DatabaseService<any> = serviceFor({
325
+ tableName: "GlobalConfig",
326
+ columns: { customFields: "customFields" },
327
+ });
328
+
329
+ await expect(
330
+ moveCustomFieldValueKey({
331
+ service: service,
332
+ projectId: PROJECT_ID,
333
+ oldName: "Impact",
334
+ newName: "Business Impact",
335
+ }),
336
+ ).rejects.toThrow('it has no "projectId" column');
337
+
338
+ expect(queries).toHaveLength(0);
339
+ });
340
+
341
+ /*
342
+ * The column the statement writes is the database name, which is not
343
+ * always the property name.
344
+ */
345
+ test("writes the database column name, not the property name", async () => {
346
+ const service: DatabaseService<any> = serviceFor({
347
+ tableName: "Incident",
348
+ columns: { customFields: "custom_fields", projectId: "project_id" },
349
+ });
350
+
351
+ await moveCustomFieldValueKey({
352
+ service: service,
353
+ projectId: PROJECT_ID,
354
+ oldName: "Impact",
355
+ newName: "Business Impact",
356
+ });
357
+
358
+ expect(queries[0]!.sql).toContain('"custom_fields"');
359
+ expect(queries[0]!.sql).toContain('"project_id" = $1');
360
+ });
361
+ });
362
+ });
363
+
364
+ describe("renameCustomFieldInTableViews", () => {
365
+ let findBy: MockFunction;
366
+ let updateColumns: MockFunction;
367
+
368
+ function viewNaming(data: {
369
+ id: string;
370
+ columns?: JSONObject | undefined;
371
+ facets?: JSONObject | undefined;
372
+ query?: JSONObject | undefined;
373
+ }): TableView {
374
+ const view: TableView = new TableView();
375
+ view._id = data.id;
376
+ view.columns = data.columns as never;
377
+ view.facets = data.facets as never;
378
+ view.query = data.query as never;
379
+ return view;
380
+ }
381
+
382
+ beforeEach(() => {
383
+ findBy = getJestMockFunction();
384
+ findBy.mockImplementation((): Promise<Array<TableView>> => {
385
+ return Promise.resolve([]);
386
+ });
387
+ jest.spyOn(TableViewService, "findBy").mockImplementation(findBy as never);
388
+
389
+ updateColumns = getJestMockFunction();
390
+ updateColumns.mockImplementation((): Promise<void> => {
391
+ return Promise.resolve();
392
+ });
393
+ jest
394
+ .spyOn(TableViewService, "updateColumnsByIdWithoutHooks")
395
+ .mockImplementation(updateColumns as never);
396
+ });
397
+
398
+ test.each([
399
+ ["the names are the same", "Impact", "Impact", ["all-incidents-table"]],
400
+ ["the old name is blank", "", "Business Impact", ["all-incidents-table"]],
401
+ ["the new name is blank", "Impact", "", ["all-incidents-table"]],
402
+ ["no table has saved views", "Impact", "Business Impact", []],
403
+ ])(
404
+ "reads nothing when %s",
405
+ async (
406
+ _case: string,
407
+ oldName: string,
408
+ newName: string,
409
+ tableIds: Array<string>,
410
+ ) => {
411
+ await expect(
412
+ renameCustomFieldInTableViews({
413
+ projectId: PROJECT_ID,
414
+ tableIds: tableIds,
415
+ oldName: oldName,
416
+ newName: newName,
417
+ }),
418
+ ).resolves.toBe(0);
419
+
420
+ expect(findBy).not.toHaveBeenCalled();
421
+ },
422
+ );
423
+
424
+ test("rewrites the column id a view remembers the field by", async () => {
425
+ findBy.mockImplementation((): Promise<Array<TableView>> => {
426
+ return Promise.resolve([
427
+ viewNaming({
428
+ id: "b0000000-0000-4000-8000-00000000000a",
429
+ columns: {
430
+ order: ["name", "customFields.Impact"],
431
+ hidden: ["customFields.Impact"],
432
+ },
433
+ }),
434
+ ]);
435
+ });
436
+
437
+ await expect(
438
+ renameCustomFieldInTableViews({
439
+ projectId: PROJECT_ID,
440
+ tableIds: ["all-incidents-table"],
441
+ oldName: "Impact",
442
+ newName: "Business Impact",
443
+ }),
444
+ ).resolves.toBe(1);
445
+
446
+ const call: JSONObject = updateColumns.mock.calls[0]![0] as JSONObject;
447
+
448
+ expect(call["data"]).toEqual({
449
+ columns: {
450
+ order: ["name", "customFields.Business Impact"],
451
+ hidden: ["customFields.Business Impact"],
452
+ },
453
+ });
454
+ });
455
+
456
+ /*
457
+ * The write is a compare-and-set against what was read, so a view someone
458
+ * saves between the read and the write is left as they saved it rather than
459
+ * half overwritten with a stale copy.
460
+ */
461
+ test("writes each view against the value it was read with", async () => {
462
+ const columns: JSONObject = { order: ["customFields.Impact"] };
463
+
464
+ findBy.mockImplementation((): Promise<Array<TableView>> => {
465
+ return Promise.resolve([
466
+ viewNaming({
467
+ id: "b0000000-0000-4000-8000-00000000000a",
468
+ columns: columns,
469
+ }),
470
+ ]);
471
+ });
472
+
473
+ await renameCustomFieldInTableViews({
474
+ projectId: PROJECT_ID,
475
+ tableIds: ["all-incidents-table"],
476
+ oldName: "Impact",
477
+ newName: "Business Impact",
478
+ });
479
+
480
+ const call: JSONObject = updateColumns.mock.calls[0]![0] as JSONObject;
481
+
482
+ expect(call["expectedData"]).toEqual({ columns: columns });
483
+ // A relabelled column is not a change anyone made to the view.
484
+ expect(call["skipUpdateDateColumn"]).toBe(true);
485
+ });
486
+
487
+ test("leaves a view that never mentioned the field alone", async () => {
488
+ findBy.mockImplementation((): Promise<Array<TableView>> => {
489
+ return Promise.resolve([
490
+ viewNaming({
491
+ id: "b0000000-0000-4000-8000-00000000000a",
492
+ columns: { order: ["name", "customFields.Severity"] },
493
+ }),
494
+ ]);
495
+ });
496
+
497
+ await expect(
498
+ renameCustomFieldInTableViews({
499
+ projectId: PROJECT_ID,
500
+ tableIds: ["all-incidents-table"],
501
+ oldName: "Impact",
502
+ newName: "Business Impact",
503
+ }),
504
+ ).resolves.toBe(0);
505
+
506
+ expect(updateColumns).not.toHaveBeenCalled();
507
+ });
508
+
509
+ test("counts only the views it rewrote", async () => {
510
+ findBy.mockImplementation((): Promise<Array<TableView>> => {
511
+ return Promise.resolve([
512
+ viewNaming({
513
+ id: "b0000000-0000-4000-8000-00000000000a",
514
+ columns: { order: ["customFields.Impact"] },
515
+ }),
516
+ viewNaming({
517
+ id: "b0000000-0000-4000-8000-00000000000b",
518
+ columns: { order: ["name"] },
519
+ }),
520
+ viewNaming({
521
+ id: "b0000000-0000-4000-8000-00000000000c",
522
+ facets: {
523
+ facetSelections: { "customField:Impact": ["High"] },
524
+ },
525
+ }),
526
+ ]);
527
+ });
528
+
529
+ await expect(
530
+ renameCustomFieldInTableViews({
531
+ projectId: PROJECT_ID,
532
+ tableIds: ["all-incidents-table"],
533
+ oldName: "Impact",
534
+ newName: "Business Impact",
535
+ }),
536
+ ).resolves.toBe(2);
537
+
538
+ expect(updateColumns).toHaveBeenCalledTimes(2);
539
+ });
540
+
541
+ test("rewrites the facet chip a view filters by", async () => {
542
+ findBy.mockImplementation((): Promise<Array<TableView>> => {
543
+ return Promise.resolve([
544
+ viewNaming({
545
+ id: "b0000000-0000-4000-8000-00000000000a",
546
+ facets: {
547
+ facetSelections: { "customField:Impact": ["High"] },
548
+ facetOperators: { "customField:Impact": "EqualTo" },
549
+ },
550
+ }),
551
+ ]);
552
+ });
553
+
554
+ await renameCustomFieldInTableViews({
555
+ projectId: PROJECT_ID,
556
+ tableIds: ["all-incidents-table"],
557
+ oldName: "Impact",
558
+ newName: "Business Impact",
559
+ });
560
+
561
+ const call: JSONObject = updateColumns.mock.calls[0]![0] as JSONObject;
562
+
563
+ expect(call["data"]).toEqual({
564
+ facets: {
565
+ facetSelections: { "customField:Business Impact": ["High"] },
566
+ facetOperators: { "customField:Business Impact": "EqualTo" },
567
+ },
568
+ });
569
+ });
570
+
571
+ // Only the project's own views, and only the tables that have any.
572
+ test("reads only this project's views, for the tables asked about", async () => {
573
+ await renameCustomFieldInTableViews({
574
+ projectId: PROJECT_ID,
575
+ tableIds: ["all-incidents-table"],
576
+ oldName: "Impact",
577
+ newName: "Business Impact",
578
+ });
579
+
580
+ const query: JSONObject = (findBy.mock.calls[0]![0] as JSONObject)[
581
+ "query"
582
+ ] as JSONObject;
583
+
584
+ expect(query["projectId"]).toBe(PROJECT_ID);
585
+ });
586
+ });