@oneuptime/common 7.0.3617 → 7.0.3652

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 (152) hide show
  1. package/Models/DatabaseModels/Alert.ts +29 -0
  2. package/Models/DatabaseModels/Index.ts +10 -0
  3. package/Models/DatabaseModels/ScheduledMaintenance.ts +29 -0
  4. package/Models/DatabaseModels/WorkspaceNotificationRule.ts +461 -0
  5. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +379 -0
  6. package/Models/DatabaseModels/WorkspaceSetting.ts +230 -0
  7. package/Models/DatabaseModels/WorkspaceUserAuthToken.ts +312 -0
  8. package/Server/API/SlackAPI.ts +368 -0
  9. package/Server/EnvironmentConfig.ts +23 -2
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/1739209832500-MigrationName.ts +147 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1739210586538-MigrationName.ts +19 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1739217257089-MigrationName.ts +23 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  14. package/Server/Middleware/SlackAuthorization.ts +60 -0
  15. package/Server/Services/AlertService.ts +38 -2
  16. package/Server/Services/Index.ts +9 -0
  17. package/Server/Services/ProjectService.ts +177 -16
  18. package/Server/Services/ScheduledMaintenanceService.ts +39 -2
  19. package/Server/Services/UserService.ts +4 -4
  20. package/Server/Services/WorkspaceCommunicationTools/Slack.ts +0 -0
  21. package/Server/Services/WorkspaceCommunicationTools/Teams.ts +0 -0
  22. package/Server/Services/WorkspaceCommunicationTools/WorkspaceCommunicationBaseService.ts +0 -0
  23. package/Server/Services/WorkspaceNotificationRuleService.ts +22 -0
  24. package/Server/Services/WorkspaceProjectAuthTokenService.ts +84 -0
  25. package/Server/Services/WorkspaceSettingService.ts +78 -0
  26. package/Server/Services/WorkspaceUserAuthTokenService.ts +89 -0
  27. package/Server/Types/Workflow/Components/Slack/SendMessageToChannel.ts +1 -1
  28. package/Server/Utils/Express.ts +2 -1
  29. package/Server/Utils/Monitor/MonitorResource.ts +2 -5
  30. package/Server/Utils/{Slack.ts → Slack/Slack.ts} +40 -0
  31. package/Server/Utils/Slack/app-manifest-temp.json +198 -0
  32. package/Server/Utils/Slack/app-manifest.json +67 -0
  33. package/Server/Utils/StartServer.ts +2 -1
  34. package/Types/Filter/FilterCondition.ts +2 -2
  35. package/Types/Monitor/CriteriaFilter.ts +31 -5
  36. package/Types/Monitor/MonitorCriteriaInstance.ts +40 -10
  37. package/Types/Monitor/MonitorStep.ts +1 -1
  38. package/Types/Permission.ts +34 -0
  39. package/Types/Workspace/NotificationRules/BaseNotificationRule.ts +3 -0
  40. package/Types/Workspace/NotificationRules/EventType.ts +8 -0
  41. package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +355 -0
  42. package/Types/Workspace/NotificationRules/SlackNotificationRule.ts +19 -0
  43. package/Types/Workspace/WorkspaceNotificationPayload.ts +3 -0
  44. package/Types/Workspace/WorkspaceType.ts +6 -0
  45. package/UI/Components/Forms/BasicForm.tsx +9 -0
  46. package/UI/Components/Forms/Fields/FormField.tsx +36 -1
  47. package/UI/Components/Forms/Types/Field.ts +2 -0
  48. package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -1
  49. package/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.ts +2 -2
  50. package/UI/Components/Icon/Icon.tsx +18 -5
  51. package/UI/Components/Radio/Radio.tsx +12 -10
  52. package/UI/Config.ts +3 -0
  53. package/build/dist/Models/DatabaseModels/Alert.js +31 -0
  54. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  55. package/build/dist/Models/DatabaseModels/Index.js +8 -0
  56. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  57. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +31 -0
  58. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  59. package/build/dist/Models/DatabaseModels/WorkspaceNotificationRule.js +481 -0
  60. package/build/dist/Models/DatabaseModels/WorkspaceNotificationRule.js.map +1 -0
  61. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js +390 -0
  62. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -0
  63. package/build/dist/Models/DatabaseModels/WorkspaceSetting.js +236 -0
  64. package/build/dist/Models/DatabaseModels/WorkspaceSetting.js.map +1 -0
  65. package/build/dist/Models/DatabaseModels/WorkspaceUserAuthToken.js +326 -0
  66. package/build/dist/Models/DatabaseModels/WorkspaceUserAuthToken.js.map +1 -0
  67. package/build/dist/Server/API/SlackAPI.js +237 -0
  68. package/build/dist/Server/API/SlackAPI.js.map +1 -0
  69. package/build/dist/Server/EnvironmentConfig.js +11 -2
  70. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  71. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1739209832500-MigrationName.js +58 -0
  72. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1739209832500-MigrationName.js.map +1 -0
  73. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1739210586538-MigrationName.js +14 -0
  74. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1739210586538-MigrationName.js.map +1 -0
  75. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1739217257089-MigrationName.js +14 -0
  76. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1739217257089-MigrationName.js.map +1 -0
  77. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  78. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  79. package/build/dist/Server/Middleware/SlackAuthorization.js +28 -0
  80. package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -0
  81. package/build/dist/Server/Services/AlertService.js +31 -5
  82. package/build/dist/Server/Services/AlertService.js.map +1 -1
  83. package/build/dist/Server/Services/Index.js +8 -0
  84. package/build/dist/Server/Services/Index.js.map +1 -1
  85. package/build/dist/Server/Services/ProjectService.js +144 -15
  86. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  87. package/build/dist/Server/Services/ScheduledMaintenanceService.js +30 -3
  88. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  89. package/build/dist/Server/Services/UserService.js +4 -4
  90. package/build/dist/Server/Services/UserService.js.map +1 -1
  91. package/build/dist/Server/Services/WorkspaceCommunicationTools/Slack.js +2 -0
  92. package/build/dist/Server/Services/WorkspaceCommunicationTools/Slack.js.map +1 -0
  93. package/build/dist/Server/Services/WorkspaceCommunicationTools/Teams.js +2 -0
  94. package/build/dist/Server/Services/WorkspaceCommunicationTools/Teams.js.map +1 -0
  95. package/build/dist/Server/Services/WorkspaceCommunicationTools/WorkspaceCommunicationBaseService.js +2 -0
  96. package/build/dist/Server/Services/WorkspaceCommunicationTools/WorkspaceCommunicationBaseService.js.map +1 -0
  97. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +14 -0
  98. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -0
  99. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +63 -0
  100. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -0
  101. package/build/dist/Server/Services/WorkspaceSettingService.js +59 -0
  102. package/build/dist/Server/Services/WorkspaceSettingService.js.map +1 -0
  103. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +66 -0
  104. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -0
  105. package/build/dist/Server/Types/Workflow/Components/Slack/SendMessageToChannel.js +1 -1
  106. package/build/dist/Server/Types/Workflow/Components/Slack/SendMessageToChannel.js.map +1 -1
  107. package/build/dist/Server/Utils/Express.js.map +1 -1
  108. package/build/dist/Server/Utils/Monitor/MonitorResource.js +2 -1
  109. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  110. package/build/dist/Server/Utils/Slack/Slack.js +49 -0
  111. package/build/dist/Server/Utils/Slack/Slack.js.map +1 -0
  112. package/build/dist/Server/Utils/Slack/app-manifest.json +67 -0
  113. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  114. package/build/dist/Types/Filter/FilterCondition.js +2 -2
  115. package/build/dist/Types/Filter/FilterCondition.js.map +1 -1
  116. package/build/dist/Types/Monitor/CriteriaFilter.js +19 -5
  117. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  118. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +33 -10
  119. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  120. package/build/dist/Types/Monitor/MonitorStep.js +1 -1
  121. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  122. package/build/dist/Types/Permission.js +32 -0
  123. package/build/dist/Types/Permission.js.map +1 -1
  124. package/build/dist/Types/Workspace/NotificationRules/BaseNotificationRule.js +2 -0
  125. package/build/dist/Types/Workspace/NotificationRules/BaseNotificationRule.js.map +1 -0
  126. package/build/dist/Types/Workspace/NotificationRules/EventType.js +9 -0
  127. package/build/dist/Types/Workspace/NotificationRules/EventType.js.map +1 -0
  128. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +275 -0
  129. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -0
  130. package/build/dist/Types/Workspace/NotificationRules/SlackNotificationRule.js +2 -0
  131. package/build/dist/Types/Workspace/NotificationRules/SlackNotificationRule.js.map +1 -0
  132. package/build/dist/Types/Workspace/WorkspaceNotificationPayload.js +2 -0
  133. package/build/dist/Types/Workspace/WorkspaceNotificationPayload.js.map +1 -0
  134. package/build/dist/Types/Workspace/WorkspaceType.js +7 -0
  135. package/build/dist/Types/Workspace/WorkspaceType.js.map +1 -0
  136. package/build/dist/UI/Components/Forms/BasicForm.js +7 -0
  137. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  138. package/build/dist/UI/Components/Forms/Fields/FormField.js +21 -4
  139. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  140. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -1
  141. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
  142. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js +2 -2
  143. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js.map +1 -1
  144. package/build/dist/UI/Components/Icon/Icon.js +5 -1
  145. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  146. package/build/dist/UI/Components/Radio/Radio.js +4 -4
  147. package/build/dist/UI/Components/Radio/Radio.js.map +1 -1
  148. package/build/dist/UI/Config.js +1 -0
  149. package/build/dist/UI/Config.js.map +1 -1
  150. package/package.json +2 -2
  151. package/build/dist/Server/Utils/Slack.js +0 -20
  152. package/build/dist/Server/Utils/Slack.js.map +0 -1
@@ -2,6 +2,7 @@ import {
2
2
  AccountsRoute,
3
3
  AdminDashboardRoute,
4
4
  DashboardRoute,
5
+ AppApiRoute,
5
6
  } from "Common/ServiceRoute";
6
7
  import BillingConfig from "./BillingConfig";
7
8
  import Hostname from "Common/Types/API/Hostname";
@@ -279,8 +280,19 @@ export const AllowedSubscribersCountInFreePlan: number = process.env[
279
280
  ? parseInt(process.env["ALLOWED_SUBSCRIBERS_COUNT_IN_FREE_PLAN"].toString())
280
281
  : 100;
281
282
 
282
- export const NotificationWebhookOnCreateUser: string =
283
- process.env["NOTIFICATION_WEBHOOK_ON_CREATED_USER"] || "";
283
+ export const NotificationSlackWebhookOnCreateUser: string =
284
+ process.env["NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_USER"] || "";
285
+
286
+ export const NotificationSlackWebhookOnCreateProject: string =
287
+ process.env["NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_PROJECT"] || "";
288
+
289
+ // notification delete project
290
+ export const NotificationSlackWebhookOnDeleteProject: string =
291
+ process.env["NOTIFICATION_SLACK_WEBHOOK_ON_DELETED_PROJECT"] || "";
292
+
293
+ // notification subscripton update.
294
+ export const NotificationSlackWebhookOnSubscriptionUpdate: string =
295
+ process.env["NOTIFICATION_SLACK_WEBHOOK_ON_SUBSCRIPTION_UPDATE"] || "";
284
296
 
285
297
  export const AdminDashboardClientURL: URL = new URL(
286
298
  HttpProtocol,
@@ -288,6 +300,8 @@ export const AdminDashboardClientURL: URL = new URL(
288
300
  AdminDashboardRoute,
289
301
  );
290
302
 
303
+ export const AppApiClientUrl: URL = new URL(HttpProtocol, Host, AppApiRoute);
304
+
291
305
  export const DashboardClientUrl: URL = new URL(
292
306
  HttpProtocol,
293
307
  Host,
@@ -302,3 +316,10 @@ export const AccountsClientUrl: URL = new URL(
302
316
 
303
317
  export const DisableTelemetry: boolean =
304
318
  process.env["DISABLE_TELEMETRY"] === "true";
319
+
320
+ export const SlackAppClientId: string | null =
321
+ process.env["SLACK_APP_CLIENT_ID"] || null;
322
+ export const SlackAppClientSecret: string | null =
323
+ process.env["SLACK_APP_CLIENT_SECRET"] || null;
324
+ export const SlackAppSigningSecret: string | null =
325
+ process.env["SLACK_APP_SIGNING_SECRET"] || null;
@@ -0,0 +1,147 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1739209832500 implements MigrationInterface {
4
+ public name = "MigrationName1739209832500";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `CREATE TABLE "WorkspaceUserAuthToken" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "authToken" text NOT NULL, "workspaceUserId" character varying(500) NOT NULL, "workspaceType" character varying(500) NOT NULL, "miscData" jsonb NOT NULL, "userId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_ae2f1b46b7e26f58a1f4a56b6ea" PRIMARY KEY ("_id"))`,
9
+ );
10
+ await queryRunner.query(
11
+ `CREATE INDEX "IDX_bee888f5782b9585e01f13455f" ON "WorkspaceUserAuthToken" ("projectId") `,
12
+ );
13
+ await queryRunner.query(
14
+ `CREATE INDEX "IDX_4b7c7d1a8b2259df8c790db094" ON "WorkspaceUserAuthToken" ("userId") `,
15
+ );
16
+ await queryRunner.query(
17
+ `CREATE TABLE "WorkspaceProjectAuthToken" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "authToken" text NOT NULL, "workspaceType" character varying(500) NOT NULL, "workspaceProjectId" character varying(500) NOT NULL, "miscData" jsonb NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_c0caa6a69da614ee74d8c1291da" PRIMARY KEY ("_id"))`,
18
+ );
19
+ await queryRunner.query(
20
+ `CREATE INDEX "IDX_73f5887268b09c0abccf04ef02" ON "WorkspaceProjectAuthToken" ("projectId") `,
21
+ );
22
+ await queryRunner.query(
23
+ `CREATE TABLE "WorkspaceSetting" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "settings" jsonb NOT NULL, "workspaceType" character varying(500) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_eb98d42edd6489fbe1cf3f34515" PRIMARY KEY ("_id"))`,
24
+ );
25
+ await queryRunner.query(
26
+ `CREATE INDEX "IDX_c68f38e2b2b061c40209e85bf2" ON "WorkspaceSetting" ("projectId") `,
27
+ );
28
+ await queryRunner.query(
29
+ `CREATE TABLE "WorkspaceNotificationRule" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(500) NOT NULL, "description" character varying(500), "notificationRule" jsonb NOT NULL, "eventType" character varying NOT NULL, "workspaceType" character varying(500) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_d1485681c7695ac9841dc52a451" PRIMARY KEY ("_id"))`,
30
+ );
31
+ await queryRunner.query(
32
+ `CREATE INDEX "IDX_349b022afa9a50a597d6c91ec9" ON "WorkspaceNotificationRule" ("projectId") `,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
36
+ );
37
+ await queryRunner.query(
38
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
39
+ );
40
+ await queryRunner.query(
41
+ `ALTER TABLE "WorkspaceUserAuthToken" ADD CONSTRAINT "FK_bee888f5782b9585e01f13455fb" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
42
+ );
43
+ await queryRunner.query(
44
+ `ALTER TABLE "WorkspaceUserAuthToken" ADD CONSTRAINT "FK_4b7c7d1a8b2259df8c790db0940" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
45
+ );
46
+ await queryRunner.query(
47
+ `ALTER TABLE "WorkspaceUserAuthToken" ADD CONSTRAINT "FK_ec5cbf4536681fe4bea883c98ea" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
48
+ );
49
+ await queryRunner.query(
50
+ `ALTER TABLE "WorkspaceUserAuthToken" ADD CONSTRAINT "FK_1b2cb71eaf9e665e4556d1b1263" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
51
+ );
52
+ await queryRunner.query(
53
+ `ALTER TABLE "WorkspaceProjectAuthToken" ADD CONSTRAINT "FK_73f5887268b09c0abccf04ef02e" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
54
+ );
55
+ await queryRunner.query(
56
+ `ALTER TABLE "WorkspaceProjectAuthToken" ADD CONSTRAINT "FK_8aa5804c7a728039564bf5d967d" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
57
+ );
58
+ await queryRunner.query(
59
+ `ALTER TABLE "WorkspaceProjectAuthToken" ADD CONSTRAINT "FK_6287095997a16f1cbdd4fb24b61" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
60
+ );
61
+ await queryRunner.query(
62
+ `ALTER TABLE "WorkspaceSetting" ADD CONSTRAINT "FK_c68f38e2b2b061c40209e85bf22" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
63
+ );
64
+ await queryRunner.query(
65
+ `ALTER TABLE "WorkspaceSetting" ADD CONSTRAINT "FK_c8fdd61b95bfd0a2ca268b8c602" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
66
+ );
67
+ await queryRunner.query(
68
+ `ALTER TABLE "WorkspaceSetting" ADD CONSTRAINT "FK_cb3b7931417a4b4ee05d487b614" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
69
+ );
70
+ await queryRunner.query(
71
+ `ALTER TABLE "WorkspaceNotificationRule" ADD CONSTRAINT "FK_349b022afa9a50a597d6c91ec95" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
72
+ );
73
+ await queryRunner.query(
74
+ `ALTER TABLE "WorkspaceNotificationRule" ADD CONSTRAINT "FK_55f4e43427fc217ed32cf640a28" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
75
+ );
76
+ await queryRunner.query(
77
+ `ALTER TABLE "WorkspaceNotificationRule" ADD CONSTRAINT "FK_65ac673d16286be2dcd5229fe24" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
78
+ );
79
+ }
80
+
81
+ public async down(queryRunner: QueryRunner): Promise<void> {
82
+ await queryRunner.query(
83
+ `ALTER TABLE "WorkspaceNotificationRule" DROP CONSTRAINT "FK_65ac673d16286be2dcd5229fe24"`,
84
+ );
85
+ await queryRunner.query(
86
+ `ALTER TABLE "WorkspaceNotificationRule" DROP CONSTRAINT "FK_55f4e43427fc217ed32cf640a28"`,
87
+ );
88
+ await queryRunner.query(
89
+ `ALTER TABLE "WorkspaceNotificationRule" DROP CONSTRAINT "FK_349b022afa9a50a597d6c91ec95"`,
90
+ );
91
+ await queryRunner.query(
92
+ `ALTER TABLE "WorkspaceSetting" DROP CONSTRAINT "FK_cb3b7931417a4b4ee05d487b614"`,
93
+ );
94
+ await queryRunner.query(
95
+ `ALTER TABLE "WorkspaceSetting" DROP CONSTRAINT "FK_c8fdd61b95bfd0a2ca268b8c602"`,
96
+ );
97
+ await queryRunner.query(
98
+ `ALTER TABLE "WorkspaceSetting" DROP CONSTRAINT "FK_c68f38e2b2b061c40209e85bf22"`,
99
+ );
100
+ await queryRunner.query(
101
+ `ALTER TABLE "WorkspaceProjectAuthToken" DROP CONSTRAINT "FK_6287095997a16f1cbdd4fb24b61"`,
102
+ );
103
+ await queryRunner.query(
104
+ `ALTER TABLE "WorkspaceProjectAuthToken" DROP CONSTRAINT "FK_8aa5804c7a728039564bf5d967d"`,
105
+ );
106
+ await queryRunner.query(
107
+ `ALTER TABLE "WorkspaceProjectAuthToken" DROP CONSTRAINT "FK_73f5887268b09c0abccf04ef02e"`,
108
+ );
109
+ await queryRunner.query(
110
+ `ALTER TABLE "WorkspaceUserAuthToken" DROP CONSTRAINT "FK_1b2cb71eaf9e665e4556d1b1263"`,
111
+ );
112
+ await queryRunner.query(
113
+ `ALTER TABLE "WorkspaceUserAuthToken" DROP CONSTRAINT "FK_ec5cbf4536681fe4bea883c98ea"`,
114
+ );
115
+ await queryRunner.query(
116
+ `ALTER TABLE "WorkspaceUserAuthToken" DROP CONSTRAINT "FK_4b7c7d1a8b2259df8c790db0940"`,
117
+ );
118
+ await queryRunner.query(
119
+ `ALTER TABLE "WorkspaceUserAuthToken" DROP CONSTRAINT "FK_bee888f5782b9585e01f13455fb"`,
120
+ );
121
+ await queryRunner.query(
122
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
123
+ );
124
+ await queryRunner.query(
125
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
126
+ );
127
+ await queryRunner.query(
128
+ `DROP INDEX "public"."IDX_349b022afa9a50a597d6c91ec9"`,
129
+ );
130
+ await queryRunner.query(`DROP TABLE "WorkspaceNotificationRule"`);
131
+ await queryRunner.query(
132
+ `DROP INDEX "public"."IDX_c68f38e2b2b061c40209e85bf2"`,
133
+ );
134
+ await queryRunner.query(`DROP TABLE "WorkspaceSetting"`);
135
+ await queryRunner.query(
136
+ `DROP INDEX "public"."IDX_73f5887268b09c0abccf04ef02"`,
137
+ );
138
+ await queryRunner.query(`DROP TABLE "WorkspaceProjectAuthToken"`);
139
+ await queryRunner.query(
140
+ `DROP INDEX "public"."IDX_4b7c7d1a8b2259df8c790db094"`,
141
+ );
142
+ await queryRunner.query(
143
+ `DROP INDEX "public"."IDX_bee888f5782b9585e01f13455f"`,
144
+ );
145
+ await queryRunner.query(`DROP TABLE "WorkspaceUserAuthToken"`);
146
+ }
147
+ }
@@ -0,0 +1,19 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1739210586538 implements MigrationInterface {
4
+ public name = "MigrationName1739210586538";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`ALTER TABLE "Alert" ADD "alertNumber" integer`);
8
+ await queryRunner.query(
9
+ `CREATE INDEX "IDX_aa91b2228a2b35424a3ae93fdc" ON "Alert" ("alertNumber") `,
10
+ );
11
+ }
12
+
13
+ public async down(queryRunner: QueryRunner): Promise<void> {
14
+ await queryRunner.query(
15
+ `DROP INDEX "public"."IDX_aa91b2228a2b35424a3ae93fdc"`,
16
+ );
17
+ await queryRunner.query(`ALTER TABLE "Alert" DROP COLUMN "alertNumber"`);
18
+ }
19
+ }
@@ -0,0 +1,23 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1739217257089 implements MigrationInterface {
4
+ public name = "MigrationName1739217257089";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "ScheduledMaintenance" ADD "scheduledMaintenanceNumber" integer`,
9
+ );
10
+ await queryRunner.query(
11
+ `CREATE INDEX "IDX_207fe82fd8bdc67bbe1aa0ebf8" ON "ScheduledMaintenance" ("scheduledMaintenanceNumber") `,
12
+ );
13
+ }
14
+
15
+ public async down(queryRunner: QueryRunner): Promise<void> {
16
+ await queryRunner.query(
17
+ `DROP INDEX "public"."IDX_207fe82fd8bdc67bbe1aa0ebf8"`,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "ScheduledMaintenance" DROP COLUMN "scheduledMaintenanceNumber"`,
21
+ );
22
+ }
23
+ }
@@ -99,6 +99,9 @@ import { MigrationName1737141420441 } from "./1737141420441-MigrationName";
99
99
  import { MigrationName1737713529424 } from "./1737713529424-MigrationName";
100
100
  import { MigrationName1737715240684 } from "./1737715240684-MigrationName";
101
101
  import { MigrationName1737997557974 } from "./1737997557974-MigrationName";
102
+ import { MigrationName1739209832500 } from "./1739209832500-MigrationName";
103
+ import { MigrationName1739210586538 } from "./1739210586538-MigrationName";
104
+ import { MigrationName1739217257089 } from "./1739217257089-MigrationName";
102
105
 
103
106
  export default [
104
107
  InitialMigration,
@@ -202,4 +205,7 @@ export default [
202
205
  MigrationName1737713529424,
203
206
  MigrationName1737715240684,
204
207
  MigrationName1737997557974,
208
+ MigrationName1739209832500,
209
+ MigrationName1739210586538,
210
+ MigrationName1739217257089,
205
211
  ];
@@ -0,0 +1,60 @@
1
+ import {
2
+ ExpressResponse,
3
+ NextFunction,
4
+ OneUptimeRequest,
5
+ } from "../Utils/Express";
6
+ import Response from "../Utils/Response";
7
+ import BadDataException from "Common/Types/Exception/BadDataException";
8
+ import { SlackAppSigningSecret } from "../EnvironmentConfig";
9
+ import crypto from "crypto";
10
+ import logger from "../Utils/Logger";
11
+
12
+ export default class SlackAuthorization {
13
+ public static async isAuthorizedSlackRequest(
14
+ req: OneUptimeRequest,
15
+ res: ExpressResponse,
16
+ next: NextFunction,
17
+ ): Promise<void> {
18
+ if (!SlackAppSigningSecret) {
19
+ return Response.sendErrorResponse(
20
+ req,
21
+ res,
22
+ new BadDataException(
23
+ "SLACK_APP_SIGNING_SECRET env variable not found.",
24
+ ),
25
+ );
26
+ }
27
+
28
+ // validate slack signing secret
29
+ const slackSigningSecret: string = SlackAppSigningSecret.toString();
30
+
31
+ const slackSignature: string = req.headers["x-slack-signature"] as string;
32
+ const timestamp: string = req.headers[
33
+ "x-slack-request-timestamp"
34
+ ] as string;
35
+ const requestBody: string = req.body;
36
+
37
+ logger.debug(`slackSignature: ${slackSignature}`);
38
+ logger.debug(`timestamp: ${timestamp}`);
39
+ logger.debug(`requestBody: ${requestBody}`);
40
+
41
+ const baseString: string = `v0:${timestamp}:${requestBody}`;
42
+ const signature: string = `v0=${crypto.createHmac("sha256", slackSigningSecret).update(baseString).digest("hex")}`;
43
+
44
+ // check if the signature is valid
45
+ if (
46
+ !crypto.timingSafeEqual(
47
+ Buffer.from(signature),
48
+ Buffer.from(slackSignature),
49
+ )
50
+ ) {
51
+ return Response.sendErrorResponse(
52
+ req,
53
+ res,
54
+ new BadDataException("Slack Signature Verification Failed."),
55
+ );
56
+ }
57
+
58
+ next();
59
+ }
60
+ }
@@ -98,6 +98,32 @@ export class Service extends DatabaseService<Model> {
98
98
  return false;
99
99
  }
100
100
 
101
+ public async getExistingAlertNumberForProject(data: {
102
+ projectId: ObjectID;
103
+ }): Promise<number> {
104
+ // get last alert number.
105
+ const lastAlert: Model | null = await this.findOneBy({
106
+ query: {
107
+ projectId: data.projectId,
108
+ },
109
+ select: {
110
+ alertNumber: true,
111
+ },
112
+ sort: {
113
+ createdAt: SortOrder.Descending,
114
+ },
115
+ props: {
116
+ isRoot: true,
117
+ },
118
+ });
119
+
120
+ if (!lastAlert) {
121
+ return 0;
122
+ }
123
+
124
+ return lastAlert.alertNumber || 0;
125
+ }
126
+
101
127
  public async acknowledgeAlert(
102
128
  alertId: ObjectID,
103
129
  acknowledgedByUserId: ObjectID,
@@ -156,9 +182,12 @@ export class Service extends DatabaseService<Model> {
156
182
  throw new BadDataException("ProjectId required to create alert.");
157
183
  }
158
184
 
185
+ const projectId: ObjectID =
186
+ createBy.props.tenantId || createBy.data.projectId!;
187
+
159
188
  const alertState: AlertState | null = await AlertStateService.findOneBy({
160
189
  query: {
161
- projectId: createBy.props.tenantId || createBy.data.projectId!,
190
+ projectId: projectId,
162
191
  isCreatedState: true,
163
192
  },
164
193
  select: {
@@ -177,6 +206,13 @@ export class Service extends DatabaseService<Model> {
177
206
 
178
207
  createBy.data.currentAlertStateId = alertState.id;
179
208
 
209
+ const alertNumberForThisAlert: number =
210
+ (await this.getExistingAlertNumberForProject({
211
+ projectId: projectId,
212
+ })) + 1;
213
+
214
+ createBy.data.alertNumber = alertNumberForThisAlert;
215
+
180
216
  if (
181
217
  (createBy.data.createdByUserId ||
182
218
  createBy.data.createdByUser ||
@@ -239,7 +275,7 @@ export class Service extends DatabaseService<Model> {
239
275
  projectId: createdItem.projectId!,
240
276
  alertFeedEventType: AlertFeedEventType.AlertCreated,
241
277
  displayColor: Red500,
242
- feedInfoInMarkdown: `**Alert Created**:
278
+ feedInfoInMarkdown: `**Alert #${createdItem.alertNumber?.toString()} Created**:
243
279
 
244
280
  **Alert Title**:
245
281
 
@@ -152,6 +152,10 @@ import AlertFeedService from "./AlertFeedService";
152
152
  import IncidentFeedService from "./IncidentFeedService";
153
153
 
154
154
  import MonitorTestService from "./MonitorTestService";
155
+ import WorkspaceProjectAuthTokenService from "./WorkspaceProjectAuthTokenService";
156
+ import WorkspaceUserAuthTokenService from "./WorkspaceUserAuthTokenService";
157
+ import WorkspaceSettingService from "./WorkspaceSettingService";
158
+ import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
155
159
 
156
160
  const services: Array<BaseService> = [
157
161
  AcmeCertificateService,
@@ -317,6 +321,11 @@ const services: Array<BaseService> = [
317
321
 
318
322
  TableViewService,
319
323
  MonitorTestService,
324
+
325
+ WorkspaceProjectAuthTokenService,
326
+ WorkspaceUserAuthTokenService,
327
+ WorkspaceSettingService,
328
+ WorkspaceNotificationRuleService,
320
329
  ];
321
330
 
322
331
  export const AnalyticsServices: Array<
@@ -1,5 +1,11 @@
1
1
  import ResellerPlan from "Common/Models/DatabaseModels/ResellerPlan";
2
- import { IsBillingEnabled, getAllEnvVars } from "../EnvironmentConfig";
2
+ import {
3
+ IsBillingEnabled,
4
+ NotificationSlackWebhookOnCreateProject,
5
+ NotificationSlackWebhookOnDeleteProject,
6
+ NotificationSlackWebhookOnSubscriptionUpdate,
7
+ getAllEnvVars,
8
+ } from "../EnvironmentConfig";
3
9
  import AllMeteredPlans from "../Types/Billing/MeteredPlan/AllMeteredPlans";
4
10
  import CreateBy from "../Types/Database/CreateBy";
5
11
  import DeleteBy from "../Types/Database/DeleteBy";
@@ -61,6 +67,9 @@ import AlertSeverity from "../../Models/DatabaseModels/AlertSeverity";
61
67
  import AlertSeverityService from "./AlertSeverityService";
62
68
  import AlertState from "../../Models/DatabaseModels/AlertState";
63
69
  import AlertStateService from "./AlertStateService";
70
+ import SlackUtil from "../Utils/Slack/Slack";
71
+ import URL from "../../Types/API/URL";
72
+ import Exception from "../../Types/Exception/Exception";
64
73
 
65
74
  export interface CurrentPlan {
66
75
  plan: PlanType | null;
@@ -95,6 +104,8 @@ export class ProjectService extends DatabaseService<Model> {
95
104
  );
96
105
  }
97
106
 
107
+ logger.debug("Creating project for user " + data.props.userId);
108
+
98
109
  const user: User | null = await UserService.findOneById({
99
110
  id: data.props.userId,
100
111
  select: {
@@ -398,6 +409,13 @@ export class ProjectService extends DatabaseService<Model> {
398
409
  project.paymentProviderSubscriptionSeats +
399
410
  " completed and project updated.",
400
411
  );
412
+
413
+ if (project.id) {
414
+ // send slack message on plan change.
415
+ await this.sendSubscriptionChangeWebhookSlackNotification(
416
+ project.id,
417
+ );
418
+ }
401
419
  }
402
420
  }
403
421
  }
@@ -405,6 +423,57 @@ export class ProjectService extends DatabaseService<Model> {
405
423
  return { updateBy, carryForward: [] };
406
424
  }
407
425
 
426
+ private async sendSubscriptionChangeWebhookSlackNotification(
427
+ projectId: ObjectID,
428
+ ): Promise<void> {
429
+ if (NotificationSlackWebhookOnSubscriptionUpdate) {
430
+ // fetch project again.
431
+ const project: Model | null = await this.findOneById({
432
+ id: new ObjectID(projectId.toString()),
433
+ select: {
434
+ name: true,
435
+ _id: true,
436
+ createdOwnerName: true,
437
+ createdOwnerEmail: true,
438
+ planName: true,
439
+ createdByUserId: true,
440
+ paymentProviderSubscriptionStatus: true,
441
+ },
442
+ props: {
443
+ isRoot: true,
444
+ },
445
+ });
446
+
447
+ if (!project) {
448
+ throw new BadDataException("Project not found");
449
+ }
450
+
451
+ let slackMessage: string = `*Project Plan Changed:*
452
+ *Project Name:* ${project.name?.toString() || "N/A"}
453
+ *Project ID:* ${project.id?.toString() || "N/A"}
454
+ `;
455
+
456
+ if (project.createdOwnerName && project.createdOwnerEmail) {
457
+ slackMessage += `*Project Created By:* ${project?.createdOwnerName?.toString() + " (" + project.createdOwnerEmail.toString() + ")" || "N/A"}
458
+ `;
459
+ }
460
+
461
+ if (IsBillingEnabled) {
462
+ // which plan?
463
+ slackMessage += `*Plan:* ${project.planName?.toString() || "N/A"}
464
+ *Subscription Status:* ${project.paymentProviderSubscriptionStatus?.toString() || "N/A"}
465
+ `;
466
+ }
467
+
468
+ SlackUtil.sendMessageToChannel({
469
+ url: URL.fromString(NotificationSlackWebhookOnSubscriptionUpdate),
470
+ text: slackMessage,
471
+ }).catch((error: Exception) => {
472
+ logger.error("Error sending slack message: " + error);
473
+ });
474
+ }
475
+ }
476
+
408
477
  private async addDefaultScheduledMaintenanceState(
409
478
  createdItem: Model,
410
479
  ): Promise<Model> {
@@ -561,6 +630,53 @@ export class ProjectService extends DatabaseService<Model> {
561
630
  createdItem = await this.addDefaultScheduledMaintenanceState(createdItem);
562
631
  createdItem = await this.addDefaultAlertState(createdItem);
563
632
 
633
+ if (NotificationSlackWebhookOnCreateProject) {
634
+ // fetch project again.
635
+ const project: Model | null = await this.findOneById({
636
+ id: createdItem.id!,
637
+ select: {
638
+ name: true,
639
+ _id: true,
640
+ createdOwnerName: true,
641
+ createdOwnerEmail: true,
642
+ planName: true,
643
+ createdByUserId: true,
644
+ paymentProviderSubscriptionStatus: true,
645
+ },
646
+ props: {
647
+ isRoot: true,
648
+ },
649
+ });
650
+
651
+ if (!project) {
652
+ throw new BadDataException("Project not found");
653
+ }
654
+
655
+ let slackMessage: string = `*Project Created:*
656
+ *Project Name:* ${project.name?.toString() || "N/A"}
657
+ *Project ID:* ${project.id?.toString() || "N/A"}
658
+ `;
659
+
660
+ if (project.createdOwnerName && project.createdOwnerEmail) {
661
+ slackMessage += `*Created By:* ${project?.createdOwnerName?.toString() + " (" + project.createdOwnerEmail.toString() + ")" || "N/A"}
662
+ `;
663
+
664
+ if (IsBillingEnabled) {
665
+ // which plan?
666
+ slackMessage += `*Plan:* ${project.planName?.toString() || "N/A"}
667
+ *Subscription Status:* ${project.paymentProviderSubscriptionStatus?.toString() || "N/A"}
668
+ `;
669
+ }
670
+
671
+ SlackUtil.sendMessageToChannel({
672
+ url: URL.fromString(NotificationSlackWebhookOnCreateProject),
673
+ text: slackMessage,
674
+ }).catch((error: Exception) => {
675
+ logger.error("Error sending slack message: " + error);
676
+ });
677
+ }
678
+ }
679
+
564
680
  return createdItem;
565
681
  }
566
682
 
@@ -1007,29 +1123,71 @@ export class ProjectService extends DatabaseService<Model> {
1007
1123
  protected override async onBeforeDelete(
1008
1124
  deleteBy: DeleteBy<Model>,
1009
1125
  ): Promise<OnDelete<Model>> {
1010
- if (IsBillingEnabled) {
1011
- const projects: Array<Model> = await this.findBy({
1012
- query: deleteBy.query,
1013
- props: deleteBy.props,
1014
- limit: LIMIT_MAX,
1015
- skip: 0,
1016
- select: {
1017
- _id: true,
1018
- paymentProviderSubscriptionId: true,
1019
- paymentProviderMeteredSubscriptionId: true,
1126
+ const projects: Array<Model> = await this.findBy({
1127
+ query: deleteBy.query,
1128
+ props: {
1129
+ isRoot: true,
1130
+ },
1131
+ limit: LIMIT_MAX,
1132
+ skip: 0,
1133
+ select: {
1134
+ _id: true,
1135
+ paymentProviderSubscriptionId: true,
1136
+ paymentProviderMeteredSubscriptionId: true,
1137
+ name: true,
1138
+ createdByUser: {
1139
+ name: true,
1140
+ email: true,
1020
1141
  },
1021
- });
1022
-
1023
- return { deleteBy, carryForward: projects };
1024
- }
1142
+ },
1143
+ });
1025
1144
 
1026
- return { deleteBy, carryForward: [] };
1145
+ return { deleteBy, carryForward: projects };
1027
1146
  }
1028
1147
 
1029
1148
  protected override async onDeleteSuccess(
1030
1149
  onDelete: OnDelete<Model>,
1031
1150
  _itemIdsBeforeDelete: ObjectID[],
1032
1151
  ): Promise<OnDelete<Model>> {
1152
+ if (NotificationSlackWebhookOnDeleteProject) {
1153
+ for (const project of onDelete.carryForward) {
1154
+ let subscriptionStatus: SubscriptionStatus | null = null;
1155
+
1156
+ if (IsBillingEnabled) {
1157
+ subscriptionStatus = await BillingService.getSubscriptionStatus(
1158
+ project.paymentProviderSubscriptionId!,
1159
+ );
1160
+ }
1161
+
1162
+ let slackMessage: string = `*Project Deleted:*
1163
+ *Project Name:* ${project.name?.toString() || "N/A"}
1164
+ *Project ID:* ${project._id?.toString() || "N/A"}
1165
+ `;
1166
+
1167
+ if (subscriptionStatus) {
1168
+ slackMessage += `*Project Subscription Status:* ${subscriptionStatus?.toString() || "N/A"}
1169
+ `;
1170
+ }
1171
+
1172
+ if (
1173
+ project.createdByUser &&
1174
+ project.createdByUser.name &&
1175
+ project.createdByUser.email
1176
+ ) {
1177
+ slackMessage += `*Created By:* ${project?.createdByUser.name?.toString() + " (" + project.createdByUser.email.toString() + ")" || "N/A"}
1178
+ `;
1179
+ }
1180
+
1181
+ SlackUtil.sendMessageToChannel({
1182
+ url: URL.fromString(NotificationSlackWebhookOnDeleteProject),
1183
+ text: slackMessage,
1184
+ }).catch((err: Error) => {
1185
+ // log this error but do not throw it. Not important enough to stop the process.
1186
+ logger.error(err);
1187
+ });
1188
+ }
1189
+ }
1190
+
1033
1191
  // get project id
1034
1192
  if (IsBillingEnabled) {
1035
1193
  for (const project of onDelete.carryForward) {
@@ -1224,6 +1382,9 @@ export class ProjectService extends DatabaseService<Model> {
1224
1382
  isRoot: true,
1225
1383
  },
1226
1384
  });
1385
+
1386
+ // send slack message on plan change.
1387
+ await this.sendSubscriptionChangeWebhookSlackNotification(projectId);
1227
1388
  }
1228
1389
 
1229
1390
  public getActiveProjectStatusQuery(): Query<Model> {