@oneuptime/common 7.0.4349 → 7.0.4372

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 (64) hide show
  1. package/Models/AnalyticsModels/ExceptionInstance.ts +2 -2
  2. package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.ts +2 -2
  3. package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.ts +2 -2
  4. package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.ts +2 -2
  5. package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +2 -2
  6. package/Models/DatabaseModels/Probe.ts +7 -1
  7. package/Models/DatabaseModels/ServiceCatalog.ts +2 -2
  8. package/Models/DatabaseModels/ServiceCopilotCodeRepository.ts +2 -2
  9. package/Models/DatabaseModels/StatusPage.ts +37 -0
  10. package/Models/DatabaseModels/StatusPageSubscriber.ts +60 -0
  11. package/Server/API/StatusPageAPI.ts +104 -10
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1749065784320-MigrationName.ts +23 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1749133333893-MigrationName.ts +17 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  15. package/Server/Services/ScheduledMaintenanceService.ts +21 -0
  16. package/Server/Services/StatusPageSubscriberService.ts +116 -1
  17. package/Server/Utils/OpenAPI.ts +738 -11
  18. package/Server/Utils/Workspace/Slack/Slack.ts +14 -0
  19. package/Utils/Schema/AnalyticsModelSchema.ts +764 -0
  20. package/Utils/Schema/BaseSchema.ts +450 -0
  21. package/Utils/Schema/ModelSchema.ts +1099 -38
  22. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +2 -2
  23. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
  24. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.js +2 -2
  25. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.js.map +1 -1
  26. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.js +2 -2
  27. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.js.map +1 -1
  28. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.js +2 -2
  29. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.js.map +1 -1
  30. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +2 -2
  31. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
  32. package/build/dist/Models/DatabaseModels/Probe.js +7 -1
  33. package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
  34. package/build/dist/Models/DatabaseModels/ServiceCatalog.js +2 -2
  35. package/build/dist/Models/DatabaseModels/ServiceCatalog.js.map +1 -1
  36. package/build/dist/Models/DatabaseModels/ServiceCopilotCodeRepository.js +2 -2
  37. package/build/dist/Models/DatabaseModels/ServiceCopilotCodeRepository.js.map +1 -1
  38. package/build/dist/Models/DatabaseModels/StatusPage.js +39 -0
  39. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  40. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +62 -0
  41. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  42. package/build/dist/Server/API/StatusPageAPI.js +73 -10
  43. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  44. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1749065784320-MigrationName.js +14 -0
  45. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1749065784320-MigrationName.js.map +1 -0
  46. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1749133333893-MigrationName.js +12 -0
  47. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1749133333893-MigrationName.js.map +1 -0
  48. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  49. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  50. package/build/dist/Server/Services/ScheduledMaintenanceService.js +19 -0
  51. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  52. package/build/dist/Server/Services/StatusPageSubscriberService.js +98 -1
  53. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  54. package/build/dist/Server/Utils/OpenAPI.js +578 -11
  55. package/build/dist/Server/Utils/OpenAPI.js.map +1 -1
  56. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +15 -0
  57. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  58. package/build/dist/Utils/Schema/AnalyticsModelSchema.js +636 -0
  59. package/build/dist/Utils/Schema/AnalyticsModelSchema.js.map +1 -0
  60. package/build/dist/Utils/Schema/BaseSchema.js +295 -0
  61. package/build/dist/Utils/Schema/BaseSchema.js.map +1 -0
  62. package/build/dist/Utils/Schema/ModelSchema.js +940 -27
  63. package/build/dist/Utils/Schema/ModelSchema.js.map +1 -1
  64. package/package.json +1 -1
@@ -12,8 +12,8 @@ export default class ExceptionInstance extends AnalyticsBaseModel {
12
12
  super({
13
13
  tableName: "ExceptionItem",
14
14
  tableEngine: AnalyticsTableEngine.MergeTree,
15
- singularName: "Exception",
16
- pluralName: "Exceptions",
15
+ singularName: "Exception Instance",
16
+ pluralName: "Exception Instances",
17
17
  enableRealtimeEventsOn: {
18
18
  create: true,
19
19
  },
@@ -53,8 +53,8 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
53
53
  })
54
54
  @TableMetadata({
55
55
  tableName: "OnCallDutyPolicyEscalationRuleSchedule",
56
- singularName: "On-Call Duty Escalation Rule Schedule",
57
- pluralName: "On-Call Duty Escalation Rule Schedules",
56
+ singularName: "Schedule's On-Call Duty Escalation Rule",
57
+ pluralName: "Schedule's On-Call Duty Escalation Rules",
58
58
  icon: IconProp.Calendar,
59
59
  tableDescription:
60
60
  "Manage schedules for on-call duty policy escalation rules.",
@@ -53,8 +53,8 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
53
53
  })
54
54
  @TableMetadata({
55
55
  tableName: "OnCallDutyPolicyEscalationRuleTeam",
56
- singularName: "On-Call Duty Escalation Rule",
57
- pluralName: "On-Call Duty Escalation Rules",
56
+ singularName: "Team On-Call Duty Escalation Rule",
57
+ pluralName: "Team On-Call Duty Escalation Rules",
58
58
  icon: IconProp.Call,
59
59
  tableDescription:
60
60
  "Manage on-call duty escalation rule for the on-call policy.",
@@ -52,8 +52,8 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
52
52
  })
53
53
  @TableMetadata({
54
54
  tableName: "OnCallDutyPolicyEscalationRuleUser",
55
- singularName: "On-Call Duty Escalation Rule",
56
- pluralName: "On-Call Duty Esdcalation Rules",
55
+ singularName: "User's On-Call Duty Escalation Rule",
56
+ pluralName: "User's On-Call Duty Esdcalation Rules",
57
57
  icon: IconProp.Call,
58
58
  tableDescription:
59
59
  "Manage on-call duty escalation rule for the on-call policy.",
@@ -55,8 +55,8 @@ import { PlanType } from "../../Types/Billing/SubscriptionPlan";
55
55
  })
56
56
  @TableMetadata({
57
57
  tableName: "OnCallDutyPolicyTimeLog",
58
- singularName: "User Override",
59
- pluralName: "User Overrides",
58
+ singularName: "On Call Time Log",
59
+ pluralName: "On Call Time Logs",
60
60
  icon: IconProp.Call,
61
61
  tableDescription:
62
62
  "Manage on-call duty user overrides, for example if the user is on leave you can override the on-call duty policy for that user so all the alerts will be routed to the other user.",
@@ -71,7 +71,13 @@ export enum ProbeConnectionStatus {
71
71
  Permission.ProjectMember,
72
72
  Permission.CreateProjectProbe,
73
73
  ],
74
- read: [Permission.Public],
74
+ read: [
75
+ Permission.Public,
76
+ Permission.ProjectOwner,
77
+ Permission.ProjectAdmin,
78
+ Permission.ProjectMember,
79
+ Permission.ReadProjectProbe,
80
+ ],
75
81
  delete: [
76
82
  Permission.ProjectOwner,
77
83
  Permission.ProjectAdmin,
@@ -80,8 +80,8 @@ import {
80
80
  @SlugifyColumn("name", "slug")
81
81
  @TableMetadata({
82
82
  tableName: "ServiceCatalog",
83
- singularName: "Service",
84
- pluralName: "Services",
83
+ singularName: "Service in Service Catalog",
84
+ pluralName: "Services in Service Catalog",
85
85
  icon: IconProp.SquareStack,
86
86
  tableDescription:
87
87
  "Service Catalog is a collection of services that you have in your organization. It can be a collection of services that you are monitoring or services that you are providing to your customers. It can be anything that you want to keep track of.",
@@ -68,8 +68,8 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
68
68
  @CrudApiEndpoint(new Route("/service-copilot-code-repository"))
69
69
  @TableMetadata({
70
70
  tableName: "ServiceCopilotCodeRepository",
71
- singularName: "Service",
72
- pluralName: "Services",
71
+ singularName: "Service Code Repository for Copilot",
72
+ pluralName: "Service Code Repositories for Copilot",
73
73
  icon: IconProp.SquareStack,
74
74
  tableDescription:
75
75
  "Add services to your code repository to categorize and manage them easily. This will help copilot understand and generate code.",
@@ -1136,6 +1136,43 @@ export default class StatusPage extends BaseModel {
1136
1136
  })
1137
1137
  public enableSmsSubscribers?: boolean = undefined;
1138
1138
 
1139
+ @ColumnAccessControl({
1140
+ create: [
1141
+ Permission.ProjectOwner,
1142
+ Permission.ProjectAdmin,
1143
+ Permission.ProjectMember,
1144
+ Permission.CreateProjectStatusPage,
1145
+ ],
1146
+ read: [
1147
+ Permission.ProjectOwner,
1148
+ Permission.ProjectAdmin,
1149
+ Permission.ProjectMember,
1150
+ Permission.ReadProjectStatusPage,
1151
+ ],
1152
+ update: [
1153
+ Permission.ProjectOwner,
1154
+ Permission.ProjectAdmin,
1155
+ Permission.ProjectMember,
1156
+ Permission.EditProjectStatusPage,
1157
+ ],
1158
+ })
1159
+ @TableColumn({
1160
+ isDefaultValueColumn: true,
1161
+ type: TableColumnType.Boolean,
1162
+ title: "Enable Slack Subscribers",
1163
+ description: "Can Slack subscribers subscribe to this Status Page?",
1164
+ })
1165
+ @Column({
1166
+ type: ColumnType.Boolean,
1167
+ default: false,
1168
+ })
1169
+ @ColumnBillingAccessControl({
1170
+ read: PlanType.Free,
1171
+ update: PlanType.Scale,
1172
+ create: PlanType.Free,
1173
+ })
1174
+ public enableSlackSubscribers?: boolean = undefined;
1175
+
1139
1176
  @ColumnAccessControl({
1140
1177
  create: [
1141
1178
  Permission.ProjectOwner,
@@ -319,6 +319,66 @@ export default class StatusPageSubscriber extends BaseModel {
319
319
  })
320
320
  public subscriberWebhook?: URL = undefined;
321
321
 
322
+ @ColumnAccessControl({
323
+ create: [
324
+ Permission.ProjectOwner,
325
+ Permission.ProjectAdmin,
326
+ Permission.ProjectMember,
327
+ Permission.CreateStatusPageSubscriber,
328
+ Permission.Public,
329
+ ],
330
+ read: [],
331
+ update: [],
332
+ })
333
+ @TableColumn({
334
+ required: false,
335
+ type: TableColumnType.ShortURL,
336
+ title: "Slack Incoming Webhook URL",
337
+ description:
338
+ "Slack incoming webhook URL to send notifications to Slack channel",
339
+ })
340
+ @Column({
341
+ nullable: true,
342
+ type: ColumnType.ShortURL,
343
+ transformer: URL.getDatabaseTransformer(),
344
+ })
345
+ public slackIncomingWebhookUrl?: URL = undefined;
346
+
347
+ @ColumnAccessControl({
348
+ create: [
349
+ Permission.ProjectOwner,
350
+ Permission.ProjectAdmin,
351
+ Permission.ProjectMember,
352
+ Permission.CreateStatusPageSubscriber,
353
+ Permission.Public,
354
+ ],
355
+ read: [
356
+ Permission.ProjectOwner,
357
+ Permission.ProjectAdmin,
358
+ Permission.ProjectMember,
359
+ Permission.ReadStatusPageSubscriber,
360
+ ],
361
+ update: [
362
+ Permission.ProjectOwner,
363
+ Permission.ProjectAdmin,
364
+ Permission.ProjectMember,
365
+ Permission.EditStatusPageSubscriber,
366
+ ],
367
+ })
368
+ @TableColumn({
369
+ required: false,
370
+ type: TableColumnType.ShortText,
371
+ title: "Slack Workspace Name",
372
+ description:
373
+ "Name of the Slack workspace for validation and identification",
374
+ })
375
+ @Column({
376
+ nullable: true,
377
+ type: ColumnType.ShortText,
378
+ length: ColumnLength.ShortText,
379
+ })
380
+ public slackWorkspaceName?: string = undefined;
381
+
322
382
  @ColumnAccessControl({
323
383
  create: [
324
384
  Permission.ProjectOwner,
@@ -89,6 +89,7 @@ import DatabaseConfig from "../DatabaseConfig";
89
89
  import { FileRoute } from "../../ServiceRoute";
90
90
  import ProjectSmtpConfigService from "../Services/ProjectSmtpConfigService";
91
91
  import ForbiddenException from "../../Types/Exception/ForbiddenException";
92
+ import SlackUtil from "../Utils/Workspace/Slack/Slack";
92
93
 
93
94
  export default class StatusPageAPI extends BaseAPI<
94
95
  StatusPage,
@@ -495,6 +496,7 @@ export default class StatusPageAPI extends BaseAPI<
495
496
  headerHTML: true,
496
497
  footerHTML: true,
497
498
  enableEmailSubscribers: true,
499
+ enableSlackSubscribers: true,
498
500
  enableSmsSubscribers: true,
499
501
  isPublicStatusPage: true,
500
502
  allowSubscribersToChooseResources: true,
@@ -2135,6 +2137,7 @@ export default class StatusPageAPI extends BaseAPI<
2135
2137
  _id: true,
2136
2138
  projectId: true,
2137
2139
  enableEmailSubscribers: true,
2140
+ enableSlackSubscribers: true,
2138
2141
  enableSmsSubscribers: true,
2139
2142
  allowSubscribersToChooseResources: true,
2140
2143
  allowSubscribersToChooseEventTypes: true,
@@ -2173,6 +2176,18 @@ export default class StatusPageAPI extends BaseAPI<
2173
2176
  );
2174
2177
  }
2175
2178
 
2179
+ if (
2180
+ req.body.data["slackIncomingWebhookUrl"] &&
2181
+ !statusPage.enableSlackSubscribers
2182
+ ) {
2183
+ logger.debug(
2184
+ `Slack subscribers not enabled for status page with ID: ${statusPageId}`,
2185
+ );
2186
+ throw new BadDataException(
2187
+ "Slack subscribers not enabled for this status page.",
2188
+ );
2189
+ }
2190
+
2176
2191
  if (req.body.data["subscriberPhone"] && !statusPage.enableSmsSubscribers) {
2177
2192
  logger.debug(
2178
2193
  `SMS subscribers not enabled for status page with ID: ${statusPageId}`,
@@ -2186,13 +2201,14 @@ export default class StatusPageAPI extends BaseAPI<
2186
2201
 
2187
2202
  if (
2188
2203
  !req.body.data["subscriberEmail"] &&
2189
- !req.body.data["subscriberPhone"]
2204
+ !req.body.data["subscriberPhone"] &&
2205
+ !req.body.data["slackWorkspaceName"]
2190
2206
  ) {
2191
2207
  logger.debug(
2192
- `No email or phone provided for subscription to status page with ID: ${statusPageId}`,
2208
+ `No email, slack workspace name or phone provided for subscription to status page with ID: ${statusPageId}`,
2193
2209
  );
2194
2210
  throw new BadDataException(
2195
- "Email or phone is required to subscribe to this status page.",
2211
+ "Email, phone or slack workspace name is required to subscribe to this status page.",
2196
2212
  );
2197
2213
  }
2198
2214
 
@@ -2204,6 +2220,12 @@ export default class StatusPageAPI extends BaseAPI<
2204
2220
  ? new Phone(req.body.data["subscriberPhone"] as string)
2205
2221
  : undefined;
2206
2222
 
2223
+ const slackWorkspaceName: string | undefined = req.body.data[
2224
+ "slackWorkspaceName"
2225
+ ]
2226
+ ? (req.body.data["slackWorkspaceName"] as string)
2227
+ : undefined;
2228
+
2207
2229
  let statusPageSubscriber: StatusPageSubscriber | null = null;
2208
2230
 
2209
2231
  if (email) {
@@ -2240,19 +2262,39 @@ export default class StatusPageAPI extends BaseAPI<
2240
2262
  });
2241
2263
  }
2242
2264
 
2265
+ if (slackWorkspaceName) {
2266
+ logger.debug(`Setting subscriber slack workspace: ${slackWorkspaceName}`);
2267
+ statusPageSubscriber = await StatusPageSubscriberService.findOneBy({
2268
+ query: {
2269
+ slackWorkspaceName: slackWorkspaceName,
2270
+ statusPageId: statusPageId,
2271
+ },
2272
+ select: {
2273
+ _id: true,
2274
+ slackWorkspaceName: true,
2275
+ slackIncomingWebhookUrl: true,
2276
+ },
2277
+ props: {
2278
+ isRoot: true,
2279
+ },
2280
+ });
2281
+ }
2282
+
2243
2283
  if (!statusPageSubscriber) {
2244
2284
  // not found, return bad data
2245
2285
  logger.debug(
2246
- `Subscriber not found for email: ${email} or phone: ${phone}`,
2286
+ `Subscriber not found for email: ${email}, phone: ${phone}, or slack workspace: ${slackWorkspaceName}`,
2247
2287
  );
2248
2288
 
2249
- let emailOrPhone: string = "email";
2289
+ let identifierType: string = "email";
2250
2290
  if (phone) {
2251
- emailOrPhone = "phone";
2291
+ identifierType = "phone";
2292
+ } else if (slackWorkspaceName) {
2293
+ identifierType = "slack workspace name";
2252
2294
  }
2253
2295
 
2254
2296
  throw new BadDataException(
2255
- `Subscription not found for this status page. Please make sure your ${emailOrPhone} is correct.`,
2297
+ `Subscription not found for this status page. Please make sure your ${identifierType} is correct.`,
2256
2298
  );
2257
2299
  }
2258
2300
 
@@ -2327,6 +2369,17 @@ export default class StatusPageAPI extends BaseAPI<
2327
2369
  });
2328
2370
  }
2329
2371
 
2372
+ if (statusPageSubscriber.slackIncomingWebhookUrl) {
2373
+ const slackMessage: string = `You have selected to manage your subscription for the status page: ${statusPage.name}. You can manage your subscription here: ${manageUrlink}`;
2374
+
2375
+ SlackUtil.sendMessageToChannelViaIncomingWebhook({
2376
+ url: statusPageSubscriber.slackIncomingWebhookUrl,
2377
+ text: slackMessage,
2378
+ }).catch((err: Error) => {
2379
+ logger.error(err);
2380
+ });
2381
+ }
2382
+
2330
2383
  logger.debug(
2331
2384
  `Subscription management link sent to subscriber with ID: ${statusPageSubscriber.id}`,
2332
2385
  );
@@ -2355,6 +2408,7 @@ export default class StatusPageAPI extends BaseAPI<
2355
2408
  projectId: true,
2356
2409
  enableEmailSubscribers: true,
2357
2410
  enableSmsSubscribers: true,
2411
+ enableSlackSubscribers: true,
2358
2412
  allowSubscribersToChooseResources: true,
2359
2413
  allowSubscribersToChooseEventTypes: true,
2360
2414
  showSubscriberPageOnStatusPage: true,
@@ -2403,15 +2457,28 @@ export default class StatusPageAPI extends BaseAPI<
2403
2457
 
2404
2458
  // if no email or phone, throw error.
2405
2459
 
2460
+ if (
2461
+ req.body.data["slackWorkspaceName"] &&
2462
+ !statusPage.enableSlackSubscribers
2463
+ ) {
2464
+ logger.debug(
2465
+ `Slack subscribers not enabled for status page with ID: ${objectId}`,
2466
+ );
2467
+ throw new BadDataException(
2468
+ "Slack subscribers not enabled for this status page.",
2469
+ );
2470
+ }
2471
+
2406
2472
  if (
2407
2473
  !req.body.data["subscriberEmail"] &&
2408
- !req.body.data["subscriberPhone"]
2474
+ !req.body.data["subscriberPhone"] &&
2475
+ !req.body.data["slackWorkspaceName"]
2409
2476
  ) {
2410
2477
  logger.debug(
2411
- `No email or phone provided for subscription to status page with ID: ${objectId}`,
2478
+ `No email, phone, or slack workspace name provided for subscription to status page with ID: ${objectId}`,
2412
2479
  );
2413
2480
  throw new BadDataException(
2414
- "Email or phone is required to subscribe to this status page.",
2481
+ "Email, phone or slack workspace name is required to subscribe to this status page.",
2415
2482
  );
2416
2483
  }
2417
2484
 
@@ -2423,6 +2490,18 @@ export default class StatusPageAPI extends BaseAPI<
2423
2490
  ? new Phone(req.body.data["subscriberPhone"] as string)
2424
2491
  : undefined;
2425
2492
 
2493
+ const slackIncomingWebhookUrl: string | undefined = req.body.data[
2494
+ "slackIncomingWebhookUrl"
2495
+ ]
2496
+ ? (req.body.data["slackIncomingWebhookUrl"] as string)
2497
+ : undefined;
2498
+
2499
+ const slackWorkspaceName: string | undefined = req.body.data[
2500
+ "slackWorkspaceName"
2501
+ ]
2502
+ ? (req.body.data["slackWorkspaceName"] as string)
2503
+ : undefined;
2504
+
2426
2505
  let statusPageSubscriber: StatusPageSubscriber | null = null;
2427
2506
 
2428
2507
  let isUpdate: boolean = false;
@@ -2467,6 +2546,20 @@ export default class StatusPageAPI extends BaseAPI<
2467
2546
  statusPageSubscriber.subscriberPhone = phone;
2468
2547
  }
2469
2548
 
2549
+ if (slackIncomingWebhookUrl) {
2550
+ logger.debug(`Setting subscriber slack: ${slackIncomingWebhookUrl}`);
2551
+ statusPageSubscriber.slackIncomingWebhookUrl = URL.fromString(
2552
+ slackIncomingWebhookUrl,
2553
+ );
2554
+ }
2555
+
2556
+ if (slackWorkspaceName) {
2557
+ logger.debug(
2558
+ `Setting subscriber slack workspace name: ${slackWorkspaceName}`,
2559
+ );
2560
+ statusPageSubscriber.slackWorkspaceName = slackWorkspaceName;
2561
+ }
2562
+
2470
2563
  if (
2471
2564
  req.body.data["statusPageResources"] &&
2472
2565
  !statusPage.allowSubscribersToChooseResources
@@ -2606,6 +2699,7 @@ export default class StatusPageAPI extends BaseAPI<
2606
2699
  isUnsubscribed: true,
2607
2700
  subscriberEmail: true,
2608
2701
  subscriberPhone: true,
2702
+ slackWorkspaceName: true,
2609
2703
  statusPageId: true,
2610
2704
  statusPageResources: true,
2611
2705
  isSubscribedToAllResources: true,
@@ -0,0 +1,23 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1749065784320 implements MigrationInterface {
4
+ public name = "MigrationName1749065784320";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "StatusPage" ADD "enableSlackSubscribers" boolean NOT NULL DEFAULT false`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "StatusPageSubscriber" ADD "slackIncomingWebhookUrl" character varying`,
12
+ );
13
+ }
14
+
15
+ public async down(queryRunner: QueryRunner): Promise<void> {
16
+ await queryRunner.query(
17
+ `ALTER TABLE "StatusPageSubscriber" DROP COLUMN "slackIncomingWebhookUrl"`,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "StatusPage" DROP COLUMN "enableSlackSubscribers"`,
21
+ );
22
+ }
23
+ }
@@ -0,0 +1,17 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1749133333893 implements MigrationInterface {
4
+ public name = "MigrationName1749133333893";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "StatusPageSubscriber" ADD "slackWorkspaceName" character varying(100)`,
9
+ );
10
+ }
11
+
12
+ public async down(queryRunner: QueryRunner): Promise<void> {
13
+ await queryRunner.query(
14
+ `ALTER TABLE "StatusPageSubscriber" DROP COLUMN "slackWorkspaceName"`,
15
+ );
16
+ }
17
+ }
@@ -134,6 +134,8 @@ import { MigrationName1744809770336 } from "./1744809770336-MigrationName";
134
134
  import { MigrationName1747305098533 } from "./1747305098533-MigrationName";
135
135
  import { MigrationName1747674762672 } from "./1747674762672-MigrationName";
136
136
  import { MigrationName1748456937826 } from "./1748456937826-MigrationName";
137
+ import { MigrationName1749065784320 } from "./1749065784320-MigrationName";
138
+ import { MigrationName1749133333893 } from "./1749133333893-MigrationName";
137
139
 
138
140
  export default [
139
141
  InitialMigration,
@@ -272,4 +274,6 @@ export default [
272
274
  MigrationName1747305098533,
273
275
  MigrationName1747674762672,
274
276
  MigrationName1748456937826,
277
+ MigrationName1749065784320,
278
+ MigrationName1749133333893,
275
279
  ];
@@ -49,6 +49,7 @@ import { IsBillingEnabled } from "../EnvironmentConfig";
49
49
  import StatusPageEventType from "../../Types/StatusPage/StatusPageEventType";
50
50
  import ScheduledMaintenanceFeedService from "./ScheduledMaintenanceFeedService";
51
51
  import { ScheduledMaintenanceFeedEventType } from "../../Models/DatabaseModels/ScheduledMaintenanceFeed";
52
+ import SlackUtil from "../Utils/Workspace/Slack/Slack";
52
53
  import { Gray500, Red500 } from "../../Types/BrandColors";
53
54
  import Label from "../../Models/DatabaseModels/Label";
54
55
  import LabelService from "./LabelService";
@@ -254,6 +255,26 @@ export class Service extends DatabaseService<Model> {
254
255
  });
255
256
  }
256
257
 
258
+ if (subscriber.slackIncomingWebhookUrl) {
259
+ const slackMessage: string = `## 🔧 Scheduled Maintenance - ${event.title || ""}
260
+
261
+ **Scheduled Date:** ${OneUptimeDate.getDateAsFormattedString(event.startsAt!)}
262
+
263
+ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
264
+
265
+ **Description:** ${event.description || ""}
266
+
267
+ [View Status Page](${statusPageURL}) | [Unsubscribe](${unsubscribeUrl})`;
268
+
269
+ // send Slack notification here.
270
+ SlackUtil.sendMessageToChannelViaIncomingWebhook({
271
+ url: subscriber.slackIncomingWebhookUrl,
272
+ text: SlackUtil.convertMarkdownToSlackRichText(slackMessage),
273
+ }).catch((err: Error) => {
274
+ logger.error(err);
275
+ });
276
+ }
277
+
257
278
  if (subscriber.subscriberEmail) {
258
279
  // send email here.
259
280