@oneuptime/common 12.0.17 → 12.0.19

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 (220) hide show
  1. package/Models/DatabaseModels/EnterpriseLicense.ts +23 -0
  2. package/Models/DatabaseModels/Index.ts +4 -2
  3. package/Models/DatabaseModels/Monitor.ts +39 -9
  4. package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
  5. package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
  6. package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
  8. package/Models/DatabaseModels/UserSlack.ts +341 -0
  9. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
  10. package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
  11. package/Server/API/UserSlackAPI.ts +125 -0
  12. package/Server/EnvironmentConfig.ts +12 -107
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  18. package/Server/Infrastructure/Queue.ts +7 -0
  19. package/Server/Services/BillingService.ts +8 -0
  20. package/Server/Services/EnterpriseLicenseService.ts +60 -0
  21. package/Server/Services/Index.ts +6 -2
  22. package/Server/Services/LlmLogService.ts +25 -9
  23. package/Server/Services/OnCallReadinessService.ts +86 -2
  24. package/Server/Services/ProjectService.ts +67 -0
  25. package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
  26. package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
  27. package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
  28. package/Server/Services/UserNotificationRuleService.ts +589 -11
  29. package/Server/Services/UserNotificationSettingService.ts +138 -0
  30. package/Server/Services/UserService.ts +28 -0
  31. package/Server/Services/UserSlackService.ts +218 -0
  32. package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
  33. package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
  34. package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
  35. package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
  36. package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
  37. package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
  38. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
  39. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
  40. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
  41. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
  42. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
  43. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
  44. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +17 -0
  45. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
  46. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
  47. package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
  48. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
  49. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
  50. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
  51. package/Tests/Server/Services/BillingService.test.ts +20 -0
  52. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
  53. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
  54. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
  55. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
  56. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
  57. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
  58. package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
  59. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
  60. package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
  61. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
  62. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
  63. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
  64. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
  65. package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
  66. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
  67. package/Tests/Server/Services/UserSlackService.test.ts +407 -0
  68. package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
  69. package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
  70. package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
  71. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
  72. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
  73. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
  74. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
  75. package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
  76. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
  77. package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
  78. package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
  79. package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
  80. package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
  81. package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
  82. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
  83. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
  84. package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
  85. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
  86. package/Tests/Types/Kubernetes/KubernetesObjectParsers.test.ts +794 -0
  87. package/Tests/Types/Kubernetes/KubernetesRightSizing.test.ts +53 -0
  88. package/Tests/Types/Measurement/MeasurementAggregationType.test.ts +100 -0
  89. package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +114 -0
  90. package/Tests/Types/Workspace/WorkspaceType.test.ts +53 -0
  91. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
  92. package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
  93. package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +257 -0
  94. package/Types/Billing/PayAsYouGoPricing.ts +1 -1
  95. package/Types/Marketing/MarketingEvent.ts +97 -0
  96. package/UI/Utils/NotificationMethodUtil.ts +41 -1
  97. package/UI/Utils/PermissionGate.ts +56 -0
  98. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
  99. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/Index.js +4 -2
  101. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  102. package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
  103. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  104. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
  105. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
  106. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
  107. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
  109. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
  110. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
  111. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
  113. package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
  114. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  115. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
  116. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
  117. package/build/dist/Server/API/UserSlackAPI.js +81 -0
  118. package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
  119. package/build/dist/Server/EnvironmentConfig.js +10 -70
  120. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
  123. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
  126. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  130. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  131. package/build/dist/Server/Infrastructure/Queue.js +7 -0
  132. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  133. package/build/dist/Server/Services/BillingService.js +6 -0
  134. package/build/dist/Server/Services/BillingService.js.map +1 -1
  135. package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
  136. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  137. package/build/dist/Server/Services/Index.js +6 -2
  138. package/build/dist/Server/Services/Index.js.map +1 -1
  139. package/build/dist/Server/Services/LlmLogService.js +25 -9
  140. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  141. package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
  142. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
  143. package/build/dist/Server/Services/ProjectService.js +56 -0
  144. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  145. package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
  146. package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
  147. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
  148. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  149. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
  150. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  151. package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
  152. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  153. package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
  154. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  155. package/build/dist/Server/Services/UserService.js +26 -1
  156. package/build/dist/Server/Services/UserService.js.map +1 -1
  157. package/build/dist/Server/Services/UserSlackService.js +195 -0
  158. package/build/dist/Server/Services/UserSlackService.js.map +1 -0
  159. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
  160. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  161. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
  162. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
  163. package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
  164. package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
  165. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
  166. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
  167. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
  168. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
  169. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
  170. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  171. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
  172. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
  173. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
  174. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  175. package/build/dist/Types/Billing/PayAsYouGoPricing.js +1 -1
  176. package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -1
  177. package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
  178. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
  179. package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
  180. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
  181. package/build/dist/UI/Utils/PermissionGate.js +32 -0
  182. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  183. package/package.json +1 -1
  184. package/Models/DatabaseModels/MarketingConversion.ts +0 -410
  185. package/Server/Services/MarketingConversionService.ts +0 -10
  186. package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
  187. package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
  188. package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
  189. package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
  190. package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
  191. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
  192. package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
  193. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
  194. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
  195. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
  196. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
  197. package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
  198. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
  199. package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
  200. package/Types/Marketing/MarketingConversion.ts +0 -53
  201. package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
  202. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
  203. package/build/dist/Server/Services/MarketingConversionService.js +0 -9
  204. package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
  205. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
  206. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
  207. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
  208. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
  209. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
  210. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
  211. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
  212. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
  213. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
  214. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
  215. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
  216. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
  217. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
  218. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
  219. package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
  220. package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
@@ -13,10 +13,13 @@ import UserEmailService from "./UserEmailService";
13
13
  import UserSmsService from "./UserSmsService";
14
14
  import PushNotificationService from "./PushNotificationService";
15
15
  import UserTelegramService from "./UserTelegramService";
16
+ import UserSlackService from "./UserSlackService";
17
+ import UserMicrosoftTeamsService from "./UserMicrosoftTeamsService";
16
18
  import UserWebhookService from "./UserWebhookService";
17
19
  import UserWhatsAppService from "./UserWhatsAppService";
18
20
  import WebhookService from "./WebhookService";
19
21
  import WhatsAppService from "./WhatsAppService";
22
+ import WorkspaceUserNotificationService from "./WorkspaceUserNotificationService";
20
23
  import { CallRequestMessage } from "../../Types/Call/CallRequest";
21
24
  import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
22
25
  import { EmailEnvelope } from "../../Types/Email/EmailMessage";
@@ -39,8 +42,15 @@ import UserEmail from "../../Models/DatabaseModels/UserEmail";
39
42
  import UserNotificationSetting from "../../Models/DatabaseModels/UserNotificationSetting";
40
43
  import UserSMS from "../../Models/DatabaseModels/UserSMS";
41
44
  import UserTelegram from "../../Models/DatabaseModels/UserTelegram";
45
+ import UserSlack from "../../Models/DatabaseModels/UserSlack";
46
+ import UserMicrosoftTeams from "../../Models/DatabaseModels/UserMicrosoftTeams";
42
47
  import UserWebhook from "../../Models/DatabaseModels/UserWebhook";
43
48
  import UserWhatsApp from "../../Models/DatabaseModels/UserWhatsApp";
49
+ import WorkspaceType from "../../Types/Workspace/WorkspaceType";
50
+ import {
51
+ WorkspaceMessageBlock,
52
+ WorkspacePayloadMarkdown,
53
+ } from "../../Types/Workspace/WorkspaceMessagePayload";
44
54
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
45
55
  import { appendRecipientToWhatsAppMessage } from "../Utils/WhatsAppTemplateUtil";
46
56
 
@@ -93,6 +103,8 @@ export class Service extends DatabaseService<UserNotificationSetting> {
93
103
  alertBySMS: true,
94
104
  alertByWhatsApp: true,
95
105
  alertByTelegram: true,
106
+ alertBySlack: true,
107
+ alertByMicrosoftTeams: true,
96
108
  alertByCall: true,
97
109
  alertByPush: true,
98
110
  alertByWebhook: true,
@@ -370,6 +382,132 @@ export class Service extends DatabaseService<UserNotificationSetting> {
370
382
  }
371
383
  }
372
384
 
385
+ if (
386
+ notificationSettings.alertBySlack ||
387
+ notificationSettings.alertByMicrosoftTeams
388
+ ) {
389
+ /*
390
+ * Slack and Microsoft Teams share one derived message: callers hand
391
+ * this method channel payloads for the older channels only, so the
392
+ * workspace body is synthesised from the email subject + SMS body the
393
+ * same way the Telegram fallback body is. Standard markdown — Slack
394
+ * slackifies it and Teams renders it into an adaptive card.
395
+ */
396
+ const subject: string = data.emailEnvelope.subject || "";
397
+ const smsBody: string = data.smsMessage.message || "";
398
+
399
+ let workspaceMarkdown: string = "";
400
+
401
+ if (subject || smsBody) {
402
+ const lines: Array<string> = [];
403
+ lines.push(`🔔 **${subject || "OneUptime notification"}**`);
404
+ if (smsBody) {
405
+ lines.push("");
406
+ lines.push(smsBody);
407
+ }
408
+ workspaceMarkdown = lines.join("\n");
409
+ }
410
+
411
+ if (!workspaceMarkdown) {
412
+ logger.warn(
413
+ "Skipping workspace notification because message body is empty.",
414
+ );
415
+ } else {
416
+ const markdownBlock: WorkspacePayloadMarkdown = {
417
+ _type: "WorkspacePayloadMarkdown",
418
+ text: workspaceMarkdown,
419
+ };
420
+ const messageBlocks: Array<WorkspaceMessageBlock> = [markdownBlock];
421
+
422
+ if (notificationSettings.alertBySlack) {
423
+ const userSlacks: Array<UserSlack> = await UserSlackService.findBy({
424
+ query: {
425
+ userId: data.userId,
426
+ projectId: data.projectId,
427
+ isVerified: true,
428
+ },
429
+ select: {
430
+ slackUserId: true,
431
+ },
432
+ limit: LIMIT_PER_PROJECT,
433
+ skip: 0,
434
+ props: {
435
+ isRoot: true,
436
+ },
437
+ });
438
+
439
+ for (const userSlack of userSlacks) {
440
+ if (!userSlack.slackUserId) {
441
+ continue;
442
+ }
443
+
444
+ WorkspaceUserNotificationService.sendDirectMessageToUser({
445
+ projectId: data.projectId,
446
+ workspaceType: WorkspaceType.Slack,
447
+ workspaceUserId: userSlack.slackUserId,
448
+ messageBlocks: messageBlocks,
449
+ messageSummary: subject || smsBody,
450
+ userId: data.userId,
451
+ incidentId: data.incidentId,
452
+ alertId: data.alertId,
453
+ alertEpisodeId: data.alertEpisodeId,
454
+ incidentEpisodeId: data.incidentEpisodeId,
455
+ teamId: data.teamId,
456
+ onCallPolicyId: data.onCallPolicyId,
457
+ onCallPolicyEscalationRuleId: data.onCallPolicyEscalationRuleId,
458
+ onCallScheduleId: data.onCallScheduleId,
459
+ }).catch((err: Error) => {
460
+ logger.error(err);
461
+ });
462
+ }
463
+ }
464
+
465
+ if (notificationSettings.alertByMicrosoftTeams) {
466
+ const userMicrosoftTeamsAccounts: Array<UserMicrosoftTeams> =
467
+ await UserMicrosoftTeamsService.findBy({
468
+ query: {
469
+ userId: data.userId,
470
+ projectId: data.projectId,
471
+ isVerified: true,
472
+ },
473
+ select: {
474
+ microsoftTeamsUserId: true,
475
+ },
476
+ limit: LIMIT_PER_PROJECT,
477
+ skip: 0,
478
+ props: {
479
+ isRoot: true,
480
+ },
481
+ });
482
+
483
+ for (const userMicrosoftTeams of userMicrosoftTeamsAccounts) {
484
+ if (!userMicrosoftTeams.microsoftTeamsUserId) {
485
+ continue;
486
+ }
487
+
488
+ WorkspaceUserNotificationService.sendDirectMessageToUser({
489
+ projectId: data.projectId,
490
+ workspaceType: WorkspaceType.MicrosoftTeams,
491
+ workspaceUserId: userMicrosoftTeams.microsoftTeamsUserId,
492
+ messageBlocks: messageBlocks,
493
+ messageSummary: subject || smsBody,
494
+ userId: data.userId,
495
+ incidentId: data.incidentId,
496
+ alertId: data.alertId,
497
+ alertEpisodeId: data.alertEpisodeId,
498
+ incidentEpisodeId: data.incidentEpisodeId,
499
+ teamId: data.teamId,
500
+ onCallPolicyId: data.onCallPolicyId,
501
+ onCallPolicyEscalationRuleId: data.onCallPolicyEscalationRuleId,
502
+ onCallScheduleId: data.onCallScheduleId,
503
+ }).catch((err: Error) => {
504
+ logger.error(err);
505
+ });
506
+ }
507
+ }
508
+ }
509
+ }
510
+
373
511
  if (notificationSettings.alertByCall) {
374
512
  const userCalls: Array<UserCall> = await UserCallService.findBy({
375
513
  query: {
@@ -42,6 +42,8 @@ import TeamMember from "../../Models/DatabaseModels/TeamMember";
42
42
  import Model from "../../Models/DatabaseModels/User";
43
43
  import SlackUtil from "../Utils/Workspace/Slack/Slack";
44
44
  import ProductAnalytics from "../Utils/ProductAnalytics";
45
+ import MarketingEventUtil from "../Utils/Marketing/MarketingEventUtil";
46
+ import { MarketingEventType } from "../../Types/Marketing/MarketingEvent";
45
47
  import UserTotpAuthService from "./UserTotpAuthService";
46
48
  import UserWebAuthnService from "./UserWebAuthnService";
47
49
  import BadDataException from "../../Types/Exception/BadDataException";
@@ -249,6 +251,32 @@ export class Service extends DatabaseService<Model> {
249
251
  first_touch: createdItem.firstTouchAttribution || {},
250
252
  },
251
253
  });
254
+
255
+ /*
256
+ * The outbound sign_up conversion.
257
+ *
258
+ * Emitted here rather than from the register endpoint so a signup
259
+ * reached any other way still counts, and keyed on the user id so a
260
+ * retried delivery cannot become a second conversion.
261
+ *
262
+ * hasPassword separates a direct signup from a user created by a team
263
+ * invite, exactly as the analytics event above does — both are real
264
+ * users, only one is an acquisition, and the receiver decides which it
265
+ * cares about rather than us deciding for it here.
266
+ */
267
+ MarketingEventUtil.emitInBackground(
268
+ MarketingEventUtil.buildEvent({
269
+ eventType: MarketingEventType.SignUp,
270
+ eventId: `${MarketingEventType.SignUp}:${createdItem.id?.toString()}`,
271
+ occurredAt: createdItem.createdAt || new Date(),
272
+ email: createdItem.email.toString(),
273
+ attributionSource: createdItem,
274
+ data: {
275
+ userId: createdItem.id?.toString() || "",
276
+ hasPassword: Boolean(createdItem.password),
277
+ },
278
+ }),
279
+ );
252
280
  }
253
281
 
254
282
  // A place holder method used for overriding.
@@ -0,0 +1,218 @@
1
+ import CreateBy from "../Types/Database/CreateBy";
2
+ import DeleteBy from "../Types/Database/DeleteBy";
3
+ import { OnCreate, OnDelete } from "../Types/Database/Hooks";
4
+ import DatabaseService from "./DatabaseService";
5
+ import UserNotificationRuleService, {
6
+ NotificationDeletionImpact,
7
+ NotificationMethodChannel,
8
+ } from "./UserNotificationRuleService";
9
+ import WorkspaceProjectAuthTokenService from "./WorkspaceProjectAuthTokenService";
10
+ import WorkspaceUserAuthTokenService from "./WorkspaceUserAuthTokenService";
11
+ import SlackUtil from "../Utils/Workspace/Slack/Slack";
12
+ import logger from "../Utils/Logger";
13
+ import LIMIT_MAX from "../../Types/Database/LimitMax";
14
+ import BadDataException from "../../Types/Exception/BadDataException";
15
+ import ObjectID from "../../Types/ObjectID";
16
+ import WorkspaceType from "../../Types/Workspace/WorkspaceType";
17
+ import WorkspaceProjectAuthToken from "../../Models/DatabaseModels/WorkspaceProjectAuthToken";
18
+ import WorkspaceUserAuthToken from "../../Models/DatabaseModels/WorkspaceUserAuthToken";
19
+ import Model from "../../Models/DatabaseModels/UserSlack";
20
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
21
+
22
+ export class Service extends DatabaseService<Model> {
23
+ public constructor() {
24
+ super(Model);
25
+ }
26
+
27
+ @CaptureSpan()
28
+ protected override async onBeforeDelete(
29
+ deleteBy: DeleteBy<Model>,
30
+ ): Promise<OnDelete<Model>> {
31
+ const itemsToDelete: Array<Model> = await this.findBy({
32
+ query: deleteBy.query,
33
+ select: {
34
+ _id: true,
35
+ projectId: true,
36
+ },
37
+ skip: 0,
38
+ limit: LIMIT_MAX,
39
+ props: {
40
+ isRoot: true,
41
+ },
42
+ });
43
+
44
+ for (const item of itemsToDelete) {
45
+ await UserNotificationRuleService.deleteBy({
46
+ query: {
47
+ userSlackId: item.id!,
48
+ projectId: item.projectId!,
49
+ },
50
+ limit: LIMIT_MAX,
51
+ skip: 0,
52
+ props: {
53
+ isRoot: true,
54
+ },
55
+ });
56
+ }
57
+
58
+ return {
59
+ deleteBy,
60
+ carryForward: null,
61
+ };
62
+ }
63
+
64
+ /**
65
+ * What this user would lose if this Slack account were deleted. Ask BEFORE
66
+ * calling delete; nothing here refuses anything.
67
+ *
68
+ * The hook directly above deletes every UserNotificationRule that points at
69
+ * this account, and the foreign key is onDelete: "CASCADE" so the rows would
70
+ * go even if it did not.
71
+ */
72
+ @CaptureSpan()
73
+ public async getDeletionImpact(data: {
74
+ itemId: ObjectID;
75
+ projectId: ObjectID;
76
+ }): Promise<NotificationDeletionImpact> {
77
+ return UserNotificationRuleService.getNotificationMethodDeletionImpact({
78
+ projectId: data.projectId,
79
+ methodType: NotificationMethodChannel.Slack,
80
+ methodId: data.itemId,
81
+ });
82
+ }
83
+
84
+ /*
85
+ * A Slack notification method is a POINTER at the user's existing workspace
86
+ * link, not a hand-typed address: the Slack member id is resolved from the
87
+ * user's own WorkspaceUserAuthToken and never accepted from the request.
88
+ * That is why creation IS verification — the OAuth dance that wrote the
89
+ * auth-token row already proved the person controls that Slack account.
90
+ */
91
+ @CaptureSpan()
92
+ protected override async onBeforeCreate(
93
+ createBy: CreateBy<Model>,
94
+ ): Promise<OnCreate<Model>> {
95
+ if (!createBy.props.isRoot && createBy.data.isVerified) {
96
+ throw new BadDataException("isVerified cannot be set to true");
97
+ }
98
+
99
+ if (!createBy.props.isRoot && createBy.data.slackUserId) {
100
+ throw new BadDataException("slackUserId cannot be set directly");
101
+ }
102
+
103
+ if (!createBy.data.projectId || !createBy.data.userId) {
104
+ throw new BadDataException("projectId and userId are required");
105
+ }
106
+
107
+ const projectId: ObjectID = new ObjectID(
108
+ createBy.data.projectId.toString(),
109
+ );
110
+ const userId: ObjectID = new ObjectID(createBy.data.userId.toString());
111
+
112
+ /*
113
+ * One workspace link per (user, project) means a second row would be an
114
+ * exact duplicate of the first.
115
+ */
116
+ const existingCount: number = (
117
+ await this.countBy({
118
+ query: {
119
+ projectId: projectId,
120
+ userId: userId,
121
+ },
122
+ props: {
123
+ isRoot: true,
124
+ },
125
+ })
126
+ ).toNumber();
127
+
128
+ if (existingCount > 0) {
129
+ throw new BadDataException(
130
+ "Slack is already added as a notification method for this project.",
131
+ );
132
+ }
133
+
134
+ const projectAuth: WorkspaceProjectAuthToken | null =
135
+ await WorkspaceProjectAuthTokenService.getProjectAuth({
136
+ projectId: projectId,
137
+ workspaceType: WorkspaceType.Slack,
138
+ });
139
+
140
+ if (!projectAuth || !projectAuth.authToken) {
141
+ throw new BadDataException(
142
+ "This project is not connected to Slack. Please ask a project admin to connect Slack in Project Settings > Slack Integration.",
143
+ );
144
+ }
145
+
146
+ const userAuth: WorkspaceUserAuthToken | null =
147
+ await WorkspaceUserAuthTokenService.getUserAuth({
148
+ projectId: projectId,
149
+ userId: userId,
150
+ workspaceType: WorkspaceType.Slack,
151
+ });
152
+
153
+ if (!userAuth || !userAuth.workspaceUserId) {
154
+ throw new BadDataException(
155
+ "Your Slack account is not connected to OneUptime for this project. Please go to User Settings > Slack Integration and connect your Slack account first.",
156
+ );
157
+ }
158
+
159
+ createBy.data.slackUserId = userAuth.workspaceUserId;
160
+ createBy.data.isVerified = true;
161
+
162
+ /*
163
+ * Display label only — best effort. A Slack API hiccup must not stop the
164
+ * method from being added.
165
+ */
166
+ try {
167
+ const slackUserName: string | null =
168
+ await SlackUtil.getUsernameFromUserId({
169
+ authToken: projectAuth.authToken,
170
+ userId: userAuth.workspaceUserId,
171
+ projectId: projectId,
172
+ });
173
+
174
+ if (slackUserName) {
175
+ createBy.data.slackUserName = slackUserName;
176
+ }
177
+ } catch (err) {
178
+ logger.error("Could not resolve Slack username for new UserSlack row.");
179
+ logger.error(err);
180
+ }
181
+
182
+ return {
183
+ createBy,
184
+ carryForward: null,
185
+ };
186
+ }
187
+
188
+ @CaptureSpan()
189
+ protected override async onCreateSuccess(
190
+ _onCreate: OnCreate<Model>,
191
+ createdItem: Model,
192
+ ): Promise<Model> {
193
+ /*
194
+ * The row is born verified (creation is gated on the live workspace
195
+ * link), so default on-call rules are seeded at create time — same as
196
+ * webhooks.
197
+ */
198
+ if (createdItem.projectId && createdItem.userId && createdItem.id) {
199
+ try {
200
+ await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod(
201
+ {
202
+ projectId: createdItem.projectId,
203
+ userId: createdItem.userId,
204
+ notificationMethod: {
205
+ userSlackId: createdItem.id,
206
+ },
207
+ },
208
+ );
209
+ } catch (err) {
210
+ logger.error(err);
211
+ }
212
+ }
213
+
214
+ return createdItem;
215
+ }
216
+ }
217
+
218
+ export default new Service();
@@ -3,10 +3,13 @@ import WorkspaceType, {
3
3
  getWorkspaceTypeDisplayName,
4
4
  } from "../../Types/Workspace/WorkspaceType";
5
5
  import DatabaseService from "./DatabaseService";
6
+ import WorkspaceUserAuthTokenService from "./WorkspaceUserAuthTokenService";
7
+ import DeleteBy from "../Types/Database/DeleteBy";
8
+ import { OnDelete } from "../Types/Database/Hooks";
6
9
  import Model, {
7
10
  WorkspaceMiscData,
8
11
  } from "../../Models/DatabaseModels/WorkspaceProjectAuthToken";
9
- import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
12
+ import LIMIT_MAX, { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
10
13
  import BadDataException from "../../Types/Exception/BadDataException";
11
14
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
12
15
  import logger from "../Utils/Logger";
@@ -16,6 +19,69 @@ export class Service extends DatabaseService<Model> {
16
19
  super(Model);
17
20
  }
18
21
 
22
+ /*
23
+ * Disconnecting a WORKSPACE disconnects every user link inside it.
24
+ *
25
+ * The dashboard's "Uninstall OneUptime from Slack / Microsoft Teams" button
26
+ * deletes this row through the generic CRUD path, and nothing else about
27
+ * the workspace survives that in a usable state: the user auth tokens were
28
+ * minted against this workspace's OAuth app, and the UserSlack /
29
+ * UserMicrosoftTeams notification methods are pointers at those links that
30
+ * WorkspaceUserNotificationService will deterministically refuse to send
31
+ * through once this row is gone ("This project is not connected to ...").
32
+ *
33
+ * Left behind, those verified-looking methods are worse than dead: the
34
+ * on-call fallback selects them into its zero-cost tier and stops looking,
35
+ * and the readiness surface keeps reporting the responder reachable. So the
36
+ * user tokens are deleted THROUGH THEIR SERVICE here, whose own
37
+ * onBeforeDelete cascades to the notification methods and their rules -
38
+ * returning every affected responder to the "no rule" state the
39
+ * verified-method fallback is built to rescue. This mirrors what the
40
+ * Slack-side app_uninstall webhook already did; the webhook remains for
41
+ * uninstalls initiated inside Slack, where this hook never fires.
42
+ */
43
+ @CaptureSpan()
44
+ protected override async onBeforeDelete(
45
+ deleteBy: DeleteBy<Model>,
46
+ ): Promise<OnDelete<Model>> {
47
+ const itemsToDelete: Array<Model> = await this.findBy({
48
+ query: deleteBy.query,
49
+ select: {
50
+ _id: true,
51
+ projectId: true,
52
+ workspaceType: true,
53
+ },
54
+ skip: 0,
55
+ limit: LIMIT_MAX,
56
+ props: {
57
+ isRoot: true,
58
+ },
59
+ });
60
+
61
+ for (const item of itemsToDelete) {
62
+ if (!item.projectId || !item.workspaceType) {
63
+ continue;
64
+ }
65
+
66
+ await WorkspaceUserAuthTokenService.deleteBy({
67
+ query: {
68
+ projectId: item.projectId,
69
+ workspaceType: item.workspaceType,
70
+ },
71
+ limit: LIMIT_MAX,
72
+ skip: 0,
73
+ props: {
74
+ isRoot: true,
75
+ },
76
+ });
77
+ }
78
+
79
+ return {
80
+ deleteBy,
81
+ carryForward: null,
82
+ };
83
+ }
84
+
19
85
  @CaptureSpan()
20
86
  public async getProjectAuth(data: {
21
87
  projectId: ObjectID;
@@ -1,6 +1,11 @@
1
1
  import ObjectID from "../../Types/ObjectID";
2
2
  import WorkspaceType from "../../Types/Workspace/WorkspaceType";
3
+ import LIMIT_MAX from "../../Types/Database/LimitMax";
3
4
  import DatabaseService from "./DatabaseService";
5
+ import UserSlackService from "./UserSlackService";
6
+ import UserMicrosoftTeamsService from "./UserMicrosoftTeamsService";
7
+ import DeleteBy from "../Types/Database/DeleteBy";
8
+ import { OnDelete } from "../Types/Database/Hooks";
4
9
  import Model, {
5
10
  SlackMiscData,
6
11
  } from "../../Models/DatabaseModels/WorkspaceUserAuthToken";
@@ -11,6 +16,74 @@ export class Service extends DatabaseService<Model> {
11
16
  super(Model);
12
17
  }
13
18
 
19
+ /*
20
+ * A UserSlack / UserMicrosoftTeams notification method is a pointer at the
21
+ * workspace link being deleted here, so it goes down with it (which also
22
+ * deletes the notification rules routing to it, via that service's own
23
+ * delete hook). Leaving the method row behind would be worse than deleting
24
+ * it: a rule pointing at a dead link fails with an error row and no re-page,
25
+ * while a user with NO matching rule is rescued by the verified-method
26
+ * fallback.
27
+ */
28
+ @CaptureSpan()
29
+ protected override async onBeforeDelete(
30
+ deleteBy: DeleteBy<Model>,
31
+ ): Promise<OnDelete<Model>> {
32
+ const itemsToDelete: Array<Model> = await this.findBy({
33
+ query: deleteBy.query,
34
+ select: {
35
+ _id: true,
36
+ projectId: true,
37
+ userId: true,
38
+ workspaceType: true,
39
+ },
40
+ skip: 0,
41
+ limit: LIMIT_MAX,
42
+ props: {
43
+ isRoot: true,
44
+ },
45
+ });
46
+
47
+ for (const item of itemsToDelete) {
48
+ if (!item.projectId || !item.userId) {
49
+ continue;
50
+ }
51
+
52
+ if (item.workspaceType === WorkspaceType.Slack) {
53
+ await UserSlackService.deleteBy({
54
+ query: {
55
+ projectId: item.projectId,
56
+ userId: item.userId,
57
+ },
58
+ limit: LIMIT_MAX,
59
+ skip: 0,
60
+ props: {
61
+ isRoot: true,
62
+ },
63
+ });
64
+ }
65
+
66
+ if (item.workspaceType === WorkspaceType.MicrosoftTeams) {
67
+ await UserMicrosoftTeamsService.deleteBy({
68
+ query: {
69
+ projectId: item.projectId,
70
+ userId: item.userId,
71
+ },
72
+ limit: LIMIT_MAX,
73
+ skip: 0,
74
+ props: {
75
+ isRoot: true,
76
+ },
77
+ });
78
+ }
79
+ }
80
+
81
+ return {
82
+ deleteBy,
83
+ carryForward: null,
84
+ };
85
+ }
86
+
14
87
  @CaptureSpan()
15
88
  public async getUserAuth(data: {
16
89
  projectId: ObjectID;