@oneuptime/common 12.0.18 → 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 (206) 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/EnterpriseLicenseService.ts +60 -0
  20. package/Server/Services/Index.ts +6 -2
  21. package/Server/Services/LlmLogService.ts +25 -9
  22. package/Server/Services/OnCallReadinessService.ts +86 -2
  23. package/Server/Services/ProjectService.ts +67 -0
  24. package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
  25. package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
  26. package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
  27. package/Server/Services/UserNotificationRuleService.ts +589 -11
  28. package/Server/Services/UserNotificationSettingService.ts +138 -0
  29. package/Server/Services/UserService.ts +28 -0
  30. package/Server/Services/UserSlackService.ts +218 -0
  31. package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
  32. package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
  33. package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
  34. package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
  35. package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
  36. package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
  37. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
  38. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
  39. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
  40. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
  41. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
  42. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
  43. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
  44. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
  45. package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
  46. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
  47. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
  48. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
  49. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
  50. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
  51. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
  52. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
  53. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
  54. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
  55. package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
  56. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
  57. package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
  58. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
  59. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
  60. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
  61. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
  62. package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
  63. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
  64. package/Tests/Server/Services/UserSlackService.test.ts +407 -0
  65. package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
  66. package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
  67. package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
  68. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
  69. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
  70. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
  71. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
  72. package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
  73. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
  74. package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
  75. package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
  76. package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
  77. package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
  78. package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
  79. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
  80. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
  81. package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
  82. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
  83. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
  84. package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
  85. package/Types/Marketing/MarketingEvent.ts +97 -0
  86. package/UI/Utils/NotificationMethodUtil.ts +41 -1
  87. package/UI/Utils/PermissionGate.ts +56 -0
  88. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
  89. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  90. package/build/dist/Models/DatabaseModels/Index.js +4 -2
  91. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  92. package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
  93. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  94. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
  95. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
  96. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
  97. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  98. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
  99. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
  101. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  102. package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
  103. package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
  104. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  105. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
  106. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
  107. package/build/dist/Server/API/UserSlackAPI.js +81 -0
  108. package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
  109. package/build/dist/Server/EnvironmentConfig.js +10 -70
  110. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  111. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
  112. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
  113. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
  114. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
  115. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
  116. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
  117. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
  118. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
  119. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  121. package/build/dist/Server/Infrastructure/Queue.js +7 -0
  122. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  123. package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
  124. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  125. package/build/dist/Server/Services/Index.js +6 -2
  126. package/build/dist/Server/Services/Index.js.map +1 -1
  127. package/build/dist/Server/Services/LlmLogService.js +25 -9
  128. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  129. package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
  130. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
  131. package/build/dist/Server/Services/ProjectService.js +56 -0
  132. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  133. package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
  134. package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
  135. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
  136. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  137. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
  138. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  139. package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
  140. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  141. package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
  142. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  143. package/build/dist/Server/Services/UserService.js +26 -1
  144. package/build/dist/Server/Services/UserService.js.map +1 -1
  145. package/build/dist/Server/Services/UserSlackService.js +195 -0
  146. package/build/dist/Server/Services/UserSlackService.js.map +1 -0
  147. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
  148. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  149. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
  150. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
  151. package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
  152. package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
  153. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
  154. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
  155. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
  156. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
  157. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
  158. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  159. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
  160. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
  161. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
  162. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  163. package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
  164. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
  165. package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
  166. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
  167. package/build/dist/UI/Utils/PermissionGate.js +32 -0
  168. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  169. package/package.json +1 -1
  170. package/Models/DatabaseModels/MarketingConversion.ts +0 -410
  171. package/Server/Services/MarketingConversionService.ts +0 -10
  172. package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
  173. package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
  174. package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
  175. package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
  176. package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
  177. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
  178. package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
  179. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
  180. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
  181. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
  182. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
  183. package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
  184. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
  185. package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
  186. package/Types/Marketing/MarketingConversion.ts +0 -53
  187. package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
  188. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
  189. package/build/dist/Server/Services/MarketingConversionService.js +0 -9
  190. package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
  191. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
  192. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
  193. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
  194. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
  195. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
  196. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
  197. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
  198. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
  199. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
  200. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
  201. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
  202. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
  203. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
  204. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
  205. package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
  206. package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
@@ -15,12 +15,14 @@ import ProjectService from "../../../Server/Services/ProjectService";
15
15
  import TeamMemberService from "../../../Server/Services/TeamMemberService";
16
16
  import UserCallService from "../../../Server/Services/UserCallService";
17
17
  import UserEmailService from "../../../Server/Services/UserEmailService";
18
+ import UserMicrosoftTeamsService from "../../../Server/Services/UserMicrosoftTeamsService";
18
19
  import UserNotificationRuleService, {
19
20
  CoverageLossCell,
20
21
  NotificationDeletionImpact,
21
22
  PostDeletionReachability,
22
23
  } from "../../../Server/Services/UserNotificationRuleService";
23
24
  import UserPushService from "../../../Server/Services/UserPushService";
25
+ import UserSlackService from "../../../Server/Services/UserSlackService";
24
26
  import UserSmsService from "../../../Server/Services/UserSmsService";
25
27
  import UserTelegramService from "../../../Server/Services/UserTelegramService";
26
28
  import UserWebhookService from "../../../Server/Services/UserWebhookService";
@@ -31,8 +33,10 @@ import IncidentSeverity from "../../../Models/DatabaseModels/IncidentSeverity";
31
33
  import Project from "../../../Models/DatabaseModels/Project";
32
34
  import UserCall from "../../../Models/DatabaseModels/UserCall";
33
35
  import UserEmail from "../../../Models/DatabaseModels/UserEmail";
36
+ import UserMicrosoftTeams from "../../../Models/DatabaseModels/UserMicrosoftTeams";
34
37
  import UserNotificationRule from "../../../Models/DatabaseModels/UserNotificationRule";
35
38
  import UserPush from "../../../Models/DatabaseModels/UserPush";
39
+ import UserSlack from "../../../Models/DatabaseModels/UserSlack";
36
40
  import UserSMS from "../../../Models/DatabaseModels/UserSMS";
37
41
  import UserTelegram from "../../../Models/DatabaseModels/UserTelegram";
38
42
  import UserWebhook from "../../../Models/DatabaseModels/UserWebhook";
@@ -57,7 +61,7 @@ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
57
61
  * is the only thing left for Sev1 incidents" is written nowhere.
58
62
  *
59
63
  * (b) Deleting a METHOD, which CASCADES. Every method foreign key on
60
- * UserNotificationRule is onDelete: "CASCADE" - and six of the seven
64
+ * UserNotificationRule is onDelete: "CASCADE" - and eight of the nine
61
65
  * method services delete the rows themselves in onBeforeDelete besides -
62
66
  * so removing one phone number destroys every rule that pointed at it.
63
67
  * The delete dialog for a phone number mentions notification rules
@@ -90,8 +94,9 @@ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
90
94
  * A rule read through the wrong column matches no page at runtime, so
91
95
  * counting it as coverage would certify a gap as covered.
92
96
  *
93
- * CERTAINTY ABOUT REACHABILITY. Push, Email and Webhook have no project
94
- * switch, which is what makes "a verified one of those survives" a certain
97
+ * CERTAINTY ABOUT REACHABILITY. Push, Email, Slack, Microsoft Teams and
98
+ * Webhook have no project switch, which is what makes "a verified one of
99
+ * those survives" a certain
95
100
  * answer and "only paid channels survive" an honest "depends". Inventing a
96
101
  * green in the second case is the exact false reassurance this whole feature
97
102
  * exists to prevent.
@@ -191,6 +196,8 @@ let callFindOneBy: jest.SpyInstance;
191
196
  let pushFindOneBy: jest.SpyInstance;
192
197
  let whatsAppFindOneBy: jest.SpyInstance;
193
198
  let telegramFindOneBy: jest.SpyInstance;
199
+ let slackFindOneBy: jest.SpyInstance;
200
+ let microsoftTeamsFindOneBy: jest.SpyInstance;
194
201
  let webhookFindOneBy: jest.SpyInstance;
195
202
  let escalationUserFindBy: jest.SpyInstance;
196
203
  let escalationTeamFindBy: jest.SpyInstance;
@@ -211,6 +218,8 @@ function rule(data: {
211
218
  userPushId?: ObjectID | undefined;
212
219
  userWhatsAppId?: ObjectID | undefined;
213
220
  userTelegramId?: ObjectID | undefined;
221
+ userSlackId?: ObjectID | undefined;
222
+ userMicrosoftTeamsId?: ObjectID | undefined;
214
223
  userWebhookId?: ObjectID | undefined;
215
224
  }): UserNotificationRule {
216
225
  const model: UserNotificationRule = new UserNotificationRule();
@@ -258,6 +267,14 @@ function rule(data: {
258
267
  model.userTelegramId = data.userTelegramId;
259
268
  }
260
269
 
270
+ if (data.userSlackId) {
271
+ model.userSlackId = data.userSlackId;
272
+ }
273
+
274
+ if (data.userMicrosoftTeamsId) {
275
+ model.userMicrosoftTeamsId = data.userMicrosoftTeamsId;
276
+ }
277
+
261
278
  if (data.userWebhookId) {
262
279
  model.userWebhookId = data.userWebhookId;
263
280
  }
@@ -500,6 +517,20 @@ beforeEach(() => {
500
517
  telegramRow.userId = USER_ID;
501
518
  telegramRow.isVerified = true;
502
519
 
520
+ /*
521
+ * Born verified: a UserSlack / UserMicrosoftTeams row is a pointer at the
522
+ * owner's own OAuth workspace link, so creation is verification.
523
+ */
524
+ const slackRow: UserSlack = new UserSlack();
525
+ slackRow.id = SMS_METHOD_ID;
526
+ slackRow.userId = USER_ID;
527
+ slackRow.isVerified = true;
528
+
529
+ const microsoftTeamsRow: UserMicrosoftTeams = new UserMicrosoftTeams();
530
+ microsoftTeamsRow.id = SMS_METHOD_ID;
531
+ microsoftTeamsRow.userId = USER_ID;
532
+ microsoftTeamsRow.isVerified = true;
533
+
503
534
  /*
504
535
  * Deliberately carries NO isVerified. UserWebhook has no such column at all,
505
536
  * and the service has to treat presence as the whole test - a fixture that
@@ -527,6 +558,12 @@ beforeEach(() => {
527
558
  telegramFindOneBy = jest
528
559
  .spyOn(UserTelegramService, "findOneBy")
529
560
  .mockResolvedValue(telegramRow as never);
561
+ slackFindOneBy = jest
562
+ .spyOn(UserSlackService, "findOneBy")
563
+ .mockResolvedValue(slackRow as never);
564
+ microsoftTeamsFindOneBy = jest
565
+ .spyOn(UserMicrosoftTeamsService, "findOneBy")
566
+ .mockResolvedValue(microsoftTeamsRow as never);
530
567
  webhookFindOneBy = jest
531
568
  .spyOn(UserWebhookService, "findOneBy")
532
569
  .mockResolvedValue(webhookRow as never);
@@ -1117,7 +1154,7 @@ describe("deleting a notification method: the cascade", () => {
1117
1154
  expect(impact.coverageLost).toHaveLength(0);
1118
1155
  });
1119
1156
 
1120
- test("each of the seven channels reads its own foreign key", async () => {
1157
+ test("each of the nine channels reads its own foreign key", async () => {
1121
1158
  const cases: Array<{
1122
1159
  methodType: ReadinessMethodType;
1123
1160
  makeRule: () => UserNotificationRule;
@@ -1188,6 +1225,28 @@ describe("deleting a notification method: the cascade", () => {
1188
1225
  });
1189
1226
  },
1190
1227
  },
1228
+ {
1229
+ methodType: ReadinessMethodType.Slack,
1230
+ makeRule: (): UserNotificationRule => {
1231
+ return rule({
1232
+ id: RULE_1_ID,
1233
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
1234
+ incidentSeverityId: SEV1_ID,
1235
+ userSlackId: SMS_METHOD_ID,
1236
+ });
1237
+ },
1238
+ },
1239
+ {
1240
+ methodType: ReadinessMethodType.MicrosoftTeams,
1241
+ makeRule: (): UserNotificationRule => {
1242
+ return rule({
1243
+ id: RULE_1_ID,
1244
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
1245
+ incidentSeverityId: SEV1_ID,
1246
+ userMicrosoftTeamsId: SMS_METHOD_ID,
1247
+ });
1248
+ },
1249
+ },
1191
1250
  {
1192
1251
  methodType: ReadinessMethodType.Webhook,
1193
1252
  makeRule: (): UserNotificationRule => {
@@ -1315,8 +1374,9 @@ describe("deleting a notification method: the cascade", () => {
1315
1374
  * (F) Reachability after the deletion.
1316
1375
  *
1317
1376
  * Two of the four answers are certain and two are not, and the split is
1318
- * structural: a method must be VERIFIED to be used at all, and Push, Email and
1319
- * Webhook have no project switch that can turn them off. Anything that turns
1377
+ * structural: a method must be VERIFIED to be used at all, and Push, Email,
1378
+ * Slack, Microsoft Teams and Webhook have no project switch that can turn
1379
+ * them off. Anything that turns
1320
1380
  * the honest "depends" into a green is a false reassurance in the one dialog
1321
1381
  * where it costs a page.
1322
1382
  * ---------------------------------------------------------------------------
@@ -1380,6 +1440,42 @@ describe("reachability after the deletion", () => {
1380
1440
  expect(impact.reachability).toBe(PostDeletionReachability.Reachable);
1381
1441
  });
1382
1442
 
1443
+ test("a surviving verified Slack account is equally certain - no project switch exists for it", async () => {
1444
+ readinessRows = [
1445
+ readiness({
1446
+ methods: [
1447
+ method(ReadinessMethodType.SMS),
1448
+ method(ReadinessMethodType.Slack),
1449
+ ],
1450
+ }),
1451
+ ];
1452
+
1453
+ const impact: NotificationDeletionImpact = await methodDeletionImpact(
1454
+ ReadinessMethodType.SMS,
1455
+ SMS_METHOD_ID,
1456
+ );
1457
+
1458
+ expect(impact.reachability).toBe(PostDeletionReachability.Reachable);
1459
+ });
1460
+
1461
+ test("a surviving verified Microsoft Teams account is equally certain", async () => {
1462
+ readinessRows = [
1463
+ readiness({
1464
+ methods: [
1465
+ method(ReadinessMethodType.SMS),
1466
+ method(ReadinessMethodType.MicrosoftTeams),
1467
+ ],
1468
+ }),
1469
+ ];
1470
+
1471
+ const impact: NotificationDeletionImpact = await methodDeletionImpact(
1472
+ ReadinessMethodType.SMS,
1473
+ SMS_METHOD_ID,
1474
+ );
1475
+
1476
+ expect(impact.reachability).toBe(PostDeletionReachability.Reachable);
1477
+ });
1478
+
1383
1479
  test("a surviving Webhook counts, because a webhook has no verification concept", async () => {
1384
1480
  readinessRows = [
1385
1481
  readiness({
@@ -2055,7 +2151,7 @@ describe("paging the rule read", () => {
2055
2151
  * ---------------------------------------------------------------------------
2056
2152
  * (L) The per-method-service entry points.
2057
2153
  *
2058
- * Each of the seven notification-method services can answer for its own row,
2154
+ * Each of the nine notification-method services can answer for its own row,
2059
2155
  * which is where the delete is actually initiated from. Each must name its own
2060
2156
  * channel: a service that passed the wrong one would preview the cascade of a
2061
2157
  * method the user is not deleting.
@@ -2159,6 +2255,36 @@ describe("the per-method-service entry points", () => {
2159
2255
  userTelegramId: SMS_METHOD_ID,
2160
2256
  }),
2161
2257
  },
2258
+ {
2259
+ name: "UserSlackService",
2260
+ call: (): Promise<NotificationDeletionImpact> => {
2261
+ return UserSlackService.getDeletionImpact({
2262
+ itemId: SMS_METHOD_ID,
2263
+ projectId: PROJECT_ID,
2264
+ });
2265
+ },
2266
+ ruleWithThisMethod: rule({
2267
+ id: RULE_1_ID,
2268
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
2269
+ incidentSeverityId: SEV1_ID,
2270
+ userSlackId: SMS_METHOD_ID,
2271
+ }),
2272
+ },
2273
+ {
2274
+ name: "UserMicrosoftTeamsService",
2275
+ call: (): Promise<NotificationDeletionImpact> => {
2276
+ return UserMicrosoftTeamsService.getDeletionImpact({
2277
+ itemId: SMS_METHOD_ID,
2278
+ projectId: PROJECT_ID,
2279
+ });
2280
+ },
2281
+ ruleWithThisMethod: rule({
2282
+ id: RULE_1_ID,
2283
+ ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
2284
+ incidentSeverityId: SEV1_ID,
2285
+ userMicrosoftTeamsId: SMS_METHOD_ID,
2286
+ }),
2287
+ },
2162
2288
  {
2163
2289
  name: "UserWebhookService",
2164
2290
  call: (): Promise<NotificationDeletionImpact> => {
@@ -2229,7 +2355,7 @@ describe("the per-method-service entry points", () => {
2229
2355
  expect(impact.reachability).toBe(PostDeletionReachability.NotReachable);
2230
2356
  });
2231
2357
 
2232
- test("the seven services do not read each other's tables", async () => {
2358
+ test("the nine services do not read each other's tables", async () => {
2233
2359
  ruleRows = [];
2234
2360
 
2235
2361
  await UserTelegramService.getDeletionImpact({
@@ -2243,6 +2369,8 @@ describe("the per-method-service entry points", () => {
2243
2369
  expect(callFindOneBy).not.toHaveBeenCalled();
2244
2370
  expect(pushFindOneBy).not.toHaveBeenCalled();
2245
2371
  expect(whatsAppFindOneBy).not.toHaveBeenCalled();
2372
+ expect(slackFindOneBy).not.toHaveBeenCalled();
2373
+ expect(microsoftTeamsFindOneBy).not.toHaveBeenCalled();
2246
2374
  expect(webhookFindOneBy).not.toHaveBeenCalled();
2247
2375
  });
2248
2376
  });
@@ -3,6 +3,7 @@ import OnCallNotificationAlertingService from "../../../Server/Services/OnCallNo
3
3
  import ProjectService from "../../../Server/Services/ProjectService";
4
4
  import UserCallService from "../../../Server/Services/UserCallService";
5
5
  import UserEmailService from "../../../Server/Services/UserEmailService";
6
+ import UserMicrosoftTeamsService from "../../../Server/Services/UserMicrosoftTeamsService";
6
7
  import UserNotificationRuleService, {
7
8
  ExecuteFallbackNotificationOptions,
8
9
  ExecuteNotificationRuleOptions,
@@ -15,6 +16,7 @@ import UserOnCallLogService, {
15
16
  } from "../../../Server/Services/UserOnCallLogService";
16
17
  import UserPushService from "../../../Server/Services/UserPushService";
17
18
  import UserService from "../../../Server/Services/UserService";
19
+ import UserSlackService from "../../../Server/Services/UserSlackService";
18
20
  import UserSmsService from "../../../Server/Services/UserSmsService";
19
21
  import UserTelegramService from "../../../Server/Services/UserTelegramService";
20
22
  import UserWebhookService from "../../../Server/Services/UserWebhookService";
@@ -43,10 +45,12 @@ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
43
45
  * verified. This file pins the four things about it that are easy to get subtly,
44
46
  * silently wrong:
45
47
  *
46
- * 1. WHICH CHANNELS. Zero-cost first: a responder with a verified push device
47
- * and a verified email gets both, because there is no reason to choose and
48
- * neither costs the project anything. Only a responder with neither is
49
- * worth spending money on, and then exactly once, down the ladder
48
+ * 1. WHICH CHANNELS. Zero-cost first: a responder with any of a verified push
49
+ * device, a verified email, a linked Slack account or a linked Microsoft
50
+ * Teams account gets ALL of those they have, because there is no reason to
51
+ * choose and none of them costs the project anything. Only a responder
52
+ * with none of the four is worth spending money on, and then exactly once,
53
+ * down the ladder
50
54
  * SMS -> Call -> WhatsApp -> Telegram -> Webhook - and only through
51
55
  * channels the PROJECT still has switched on. That last clause is the one
52
56
  * with a bill attached: SmsService and CallService check their project flag
@@ -83,7 +87,7 @@ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
83
87
  *
84
88
  * Nothing here touches a database or a sender: deliverNotificationForRule is
85
89
  * stubbed at the service boundary (it fans out into template generation, short
86
- * links, seven providers and the timeline writer), and every lookup is a
90
+ * links, nine providers and the timeline writer), and every lookup is a
87
91
  * jest.spyOn. What is under test is the fallback's own decision-making.
88
92
  */
89
93
 
@@ -126,6 +130,12 @@ const TELEGRAM_METHOD_ID: ObjectID = new ObjectID(
126
130
  const WEBHOOK_METHOD_ID: ObjectID = new ObjectID(
127
131
  "a7a7a7a7-a7a7-4a7a-8a7a-a7a7a7a7a7a7",
128
132
  );
133
+ const SLACK_METHOD_ID: ObjectID = new ObjectID(
134
+ "a8a8a8a8-a8a8-4a8a-8a8a-a8a8a8a8a8a8",
135
+ );
136
+ const MICROSOFT_TEAMS_METHOD_ID: ObjectID = new ObjectID(
137
+ "a9a9a9a9-a9a9-4a9a-8a9a-a9a9a9a9a9a9",
138
+ );
129
139
 
130
140
  const RESPONDER_EMAIL: string = "responder@company.com";
131
141
  const SEVERITY_NAME: string = "Sev4";
@@ -179,6 +189,31 @@ function makeVerifiedEmail(): Record<string, unknown> {
179
189
  };
180
190
  }
181
191
 
192
+ /*
193
+ * Slack and Microsoft Teams rows are born verified - they come from the user's
194
+ * own OAuth workspace link, not a verification-code flow - but the fallback
195
+ * still queries on isVerified, so the fixtures carry it.
196
+ */
197
+ function makeVerifiedSlack(): Record<string, unknown> {
198
+ return {
199
+ id: SLACK_METHOD_ID,
200
+ _id: SLACK_METHOD_ID.toString(),
201
+ slackUserId: "U0123456789",
202
+ slackUserName: "responder",
203
+ isVerified: true,
204
+ };
205
+ }
206
+
207
+ function makeVerifiedMicrosoftTeams(): Record<string, unknown> {
208
+ return {
209
+ id: MICROSOFT_TEAMS_METHOD_ID,
210
+ _id: MICROSOFT_TEAMS_METHOD_ID.toString(),
211
+ microsoftTeamsUserId: "teams-user-1",
212
+ microsoftTeamsUserName: RESPONDER_NAME,
213
+ isVerified: true,
214
+ };
215
+ }
216
+
182
217
  function makeVerifiedSms(): Record<string, unknown> {
183
218
  return {
184
219
  id: SMS_METHOD_ID,
@@ -308,6 +343,8 @@ describe("UserNotificationRuleService.executeFallbackNotification", () => {
308
343
  let ruleCreateSpy: jest.SpyInstance;
309
344
  let pushFindSpy: jest.SpyInstance;
310
345
  let emailFindSpy: jest.SpyInstance;
346
+ let slackFindSpy: jest.SpyInstance;
347
+ let microsoftTeamsFindSpy: jest.SpyInstance;
311
348
  let smsFindSpy: jest.SpyInstance;
312
349
  let callFindSpy: jest.SpyInstance;
313
350
  let whatsAppFindSpy: jest.SpyInstance;
@@ -329,7 +366,7 @@ describe("UserNotificationRuleService.executeFallbackNotification", () => {
329
366
 
330
367
  /*
331
368
  * Stubbed at the service boundary. The real method fans out into template
332
- * generation, short-link creation, seven providers and the timeline writer;
369
+ * generation, short-link creation, nine providers and the timeline writer;
333
370
  * its return value ("was a page actually handed to a sender") is the only
334
371
  * part of it the fallback reasons about.
335
372
  */
@@ -348,6 +385,12 @@ describe("UserNotificationRuleService.executeFallbackNotification", () => {
348
385
  emailFindSpy = jest
349
386
  .spyOn(UserEmailService, "findOneBy")
350
387
  .mockResolvedValue(null as never);
388
+ slackFindSpy = jest
389
+ .spyOn(UserSlackService, "findOneBy")
390
+ .mockResolvedValue(null as never);
391
+ microsoftTeamsFindSpy = jest
392
+ .spyOn(UserMicrosoftTeamsService, "findOneBy")
393
+ .mockResolvedValue(null as never);
351
394
  smsFindSpy = jest
352
395
  .spyOn(UserSmsService, "findOneBy")
353
396
  .mockResolvedValue(null as never);
@@ -426,7 +469,7 @@ describe("UserNotificationRuleService.executeFallbackNotification", () => {
426
469
  * -----------------------------------------------------------------------
427
470
  */
428
471
 
429
- describe("channel selection: the zero-cost pair", () => {
472
+ describe("channel selection: the zero-cost tier", () => {
430
473
  test("a responder with a verified push device AND a verified email gets BOTH", async () => {
431
474
  pushFindSpy.mockResolvedValue(makeVerifiedPush() as never);
432
475
  emailFindSpy.mockResolvedValue(makeVerifiedEmail() as never);
@@ -466,6 +509,86 @@ describe("UserNotificationRuleService.executeFallbackNotification", () => {
466
509
  }
467
510
  });
468
511
 
512
+ test('a verified Slack account alone is chosen, as "Slack", with the relation AND the FK', async () => {
513
+ const slack: Record<string, unknown> = makeVerifiedSlack();
514
+ slackFindSpy.mockResolvedValue(slack as never);
515
+
516
+ const result: FallbackNotificationResult = await runFallback();
517
+
518
+ expect(result.channelsUsed).toEqual(["Slack"]);
519
+ expect(result.notified).toBe(true);
520
+ expect(result.outcome).toBe(FallbackNotificationOutcome.Delivered);
521
+
522
+ /*
523
+ * The relation, not merely the FK: the Slack channel block reads
524
+ * `userSlack?.slackUserId && userSlack?.isVerified` off the loaded
525
+ * relation and never dereferences the id, so a rule carrying only
526
+ * userSlackId would sail through delivery sending nothing at all.
527
+ */
528
+ const rule: UserNotificationRule = deliveredRules()[0]!;
529
+ expect(rule.userSlack).toBe(slack);
530
+ expect(rule.userSlackId!.toString()).toBe(SLACK_METHOD_ID.toString());
531
+
532
+ for (const spy of everyPaidChannelFindSpy()) {
533
+ expect(spy).not.toHaveBeenCalled();
534
+ }
535
+ expect(webhookFindSpy).not.toHaveBeenCalled();
536
+ });
537
+
538
+ test('a verified Microsoft Teams account alone is chosen, as "Microsoft Teams", with the relation AND the FK', async () => {
539
+ const microsoftTeams: Record<string, unknown> =
540
+ makeVerifiedMicrosoftTeams();
541
+ microsoftTeamsFindSpy.mockResolvedValue(microsoftTeams as never);
542
+
543
+ const result: FallbackNotificationResult = await runFallback();
544
+
545
+ expect(result.channelsUsed).toEqual(["Microsoft Teams"]);
546
+ expect(result.notified).toBe(true);
547
+ expect(result.outcome).toBe(FallbackNotificationOutcome.Delivered);
548
+
549
+ const rule: UserNotificationRule = deliveredRules()[0]!;
550
+ expect(rule.userMicrosoftTeams).toBe(microsoftTeams);
551
+ expect(rule.userMicrosoftTeamsId!.toString()).toBe(
552
+ MICROSOFT_TEAMS_METHOD_ID.toString(),
553
+ );
554
+
555
+ for (const spy of everyPaidChannelFindSpy()) {
556
+ expect(spy).not.toHaveBeenCalled();
557
+ }
558
+ expect(webhookFindSpy).not.toHaveBeenCalled();
559
+ });
560
+
561
+ test("a responder with push, email, Slack AND Microsoft Teams gets ALL FOUR, in this order", async () => {
562
+ pushFindSpy.mockResolvedValue(makeVerifiedPush() as never);
563
+ emailFindSpy.mockResolvedValue(makeVerifiedEmail() as never);
564
+ slackFindSpy.mockResolvedValue(makeVerifiedSlack() as never);
565
+ microsoftTeamsFindSpy.mockResolvedValue(
566
+ makeVerifiedMicrosoftTeams() as never,
567
+ );
568
+
569
+ const result: FallbackNotificationResult = await runFallback();
570
+
571
+ /*
572
+ * The zero-cost tier takes everything present, never a subset: none of
573
+ * the four costs the project anything, so there is no reason to pick
574
+ * between them and every reason to maximise the chance of reaching a
575
+ * human wherever they happen to be looking.
576
+ */
577
+ expect(result.channelsUsed).toEqual([
578
+ "Push",
579
+ "Email",
580
+ "Slack",
581
+ "Microsoft Teams",
582
+ ]);
583
+ expect(result.notified).toBe(true);
584
+ expect(result.outcome).toBe(FallbackNotificationOutcome.Delivered);
585
+ // Four channels, four delivery calls, each with its own rule.
586
+ expect(deliverSpy).toHaveBeenCalledTimes(4);
587
+ for (const spy of everyPaidChannelFindSpy()) {
588
+ expect(spy).not.toHaveBeenCalled();
589
+ }
590
+ });
591
+
469
592
  test("with a zero-cost channel available the project settings are never even read", async () => {
470
593
  emailFindSpy.mockResolvedValue(makeVerifiedEmail() as never);
471
594
 
@@ -509,6 +632,34 @@ describe("UserNotificationRuleService.executeFallbackNotification", () => {
509
632
  expect((query["userId"] as ObjectID).toString()).toBe(USER_ID.toString());
510
633
  expect(query["isVerified"]).toBe(true);
511
634
  });
635
+
636
+ test("the Slack lookup is scoped to this project, this user, and verified accounts only", async () => {
637
+ slackFindSpy.mockResolvedValue(makeVerifiedSlack() as never);
638
+
639
+ await runFallback();
640
+
641
+ const query: Record<string, unknown> = queryOf(slackFindSpy);
642
+ expect((query["projectId"] as ObjectID).toString()).toBe(
643
+ PROJECT_ID.toString(),
644
+ );
645
+ expect((query["userId"] as ObjectID).toString()).toBe(USER_ID.toString());
646
+ expect(query["isVerified"]).toBe(true);
647
+ });
648
+
649
+ test("the Microsoft Teams lookup is scoped to this project, this user, and verified accounts only", async () => {
650
+ microsoftTeamsFindSpy.mockResolvedValue(
651
+ makeVerifiedMicrosoftTeams() as never,
652
+ );
653
+
654
+ await runFallback();
655
+
656
+ const query: Record<string, unknown> = queryOf(microsoftTeamsFindSpy);
657
+ expect((query["projectId"] as ObjectID).toString()).toBe(
658
+ PROJECT_ID.toString(),
659
+ );
660
+ expect((query["userId"] as ObjectID).toString()).toBe(USER_ID.toString());
661
+ expect(query["isVerified"]).toBe(true);
662
+ });
512
663
  });
513
664
 
514
665
  /*
@@ -659,7 +810,7 @@ describe("UserNotificationRuleService.executeFallbackNotification", () => {
659
810
  },
660
811
  );
661
812
 
662
- test("the paid ladder is only reached when BOTH zero-cost channels are missing", async () => {
813
+ test("the paid ladder is only reached when ALL FOUR zero-cost channels are missing", async () => {
663
814
  pushFindSpy.mockResolvedValue(makeVerifiedPush() as never);
664
815
  giveResponderEveryPaidMethod();
665
816
  projectFindSpy.mockResolvedValue(
@@ -671,6 +822,36 @@ describe("UserNotificationRuleService.executeFallbackNotification", () => {
671
822
  expect(result.channelsUsed).toEqual(["Push"]);
672
823
  expect(smsFindSpy).not.toHaveBeenCalled();
673
824
  });
825
+
826
+ test("a Slack account alone keeps the paid ladder shut - the project row is never even read", async () => {
827
+ slackFindSpy.mockResolvedValue(makeVerifiedSlack() as never);
828
+ giveResponderEveryPaidMethod();
829
+ projectFindSpy.mockResolvedValue(
830
+ makeProject({ [SMS_FLAG]: true }) as never,
831
+ );
832
+
833
+ const result: FallbackNotificationResult = await runFallback();
834
+
835
+ expect(result.channelsUsed).toEqual(["Slack"]);
836
+ expect(projectFindSpy).not.toHaveBeenCalled();
837
+ expect(smsFindSpy).not.toHaveBeenCalled();
838
+ });
839
+
840
+ test("a Microsoft Teams account alone keeps the paid ladder shut too", async () => {
841
+ microsoftTeamsFindSpy.mockResolvedValue(
842
+ makeVerifiedMicrosoftTeams() as never,
843
+ );
844
+ giveResponderEveryPaidMethod();
845
+ projectFindSpy.mockResolvedValue(
846
+ makeProject({ [SMS_FLAG]: true }) as never,
847
+ );
848
+
849
+ const result: FallbackNotificationResult = await runFallback();
850
+
851
+ expect(result.channelsUsed).toEqual(["Microsoft Teams"]);
852
+ expect(projectFindSpy).not.toHaveBeenCalled();
853
+ expect(smsFindSpy).not.toHaveBeenCalled();
854
+ });
674
855
  });
675
856
 
676
857
  /*
@@ -795,6 +976,8 @@ describe("UserNotificationRuleService.executeFallbackNotification", () => {
795
976
  rule.userCall,
796
977
  rule.userWhatsApp,
797
978
  rule.userTelegram,
979
+ rule.userSlack,
980
+ rule.userMicrosoftTeams,
798
981
  rule.userWebhook,
799
982
  ].filter((method: unknown): boolean => {
800
983
  return Boolean(method);