@oneuptime/common 8.0.5142 → 8.0.5149

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 (33) hide show
  1. package/Models/DatabaseModels/StatusPageAnnouncement.ts +48 -0
  2. package/Models/DatabaseModels/StatusPageAnnouncementTemplate.ts +48 -0
  3. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +8 -1
  4. package/Server/API/BaseAPI.ts +18 -6
  5. package/Server/API/StatusPageAPI.ts +65 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1757423505855-MigrationName.ts +79 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  8. package/Server/Services/StatusPageSubscriberService.ts +4 -1
  9. package/Server/Services/WorkspaceNotificationRuleService.ts +7 -0
  10. package/Server/Utils/Workspace/Slack/Slack.ts +268 -38
  11. package/Server/Utils/Workspace/WorkspaceBase.ts +12 -0
  12. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +46 -0
  13. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  14. package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js +46 -0
  15. package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js.map +1 -1
  16. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  17. package/build/dist/Server/API/BaseAPI.js +18 -6
  18. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  19. package/build/dist/Server/API/StatusPageAPI.js +54 -0
  20. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  21. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1757423505855-MigrationName.js +34 -0
  22. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1757423505855-MigrationName.js.map +1 -0
  23. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  24. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  25. package/build/dist/Server/Services/StatusPageSubscriberService.js +2 -1
  26. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  27. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +7 -0
  28. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  29. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +213 -28
  30. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  31. package/build/dist/Server/Utils/Workspace/WorkspaceBase.js +2 -0
  32. package/build/dist/Server/Utils/Workspace/WorkspaceBase.js.map +1 -1
  33. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import Monitor from "./Monitor";
1
2
  import Project from "./Project";
2
3
  import StatusPage from "./StatusPage";
3
4
  import User from "./User";
@@ -198,6 +199,53 @@ export default class StatusPageAnnouncement extends BaseModel {
198
199
  })
199
200
  public statusPages?: Array<StatusPage> = undefined;
200
201
 
202
+ @ColumnAccessControl({
203
+ create: [
204
+ Permission.ProjectOwner,
205
+ Permission.ProjectAdmin,
206
+ Permission.ProjectMember,
207
+ Permission.CreateStatusPageAnnouncement,
208
+ ],
209
+ read: [
210
+ Permission.ProjectOwner,
211
+ Permission.ProjectAdmin,
212
+ Permission.ProjectMember,
213
+ Permission.ReadStatusPageAnnouncement,
214
+ ],
215
+ update: [
216
+ Permission.ProjectOwner,
217
+ Permission.ProjectAdmin,
218
+ Permission.ProjectMember,
219
+ Permission.EditStatusPageAnnouncement,
220
+ ],
221
+ })
222
+ @TableColumn({
223
+ required: false,
224
+ type: TableColumnType.EntityArray,
225
+ modelType: Monitor,
226
+ title: "Monitors",
227
+ description:
228
+ "List of monitors affected by this announcement. If none are selected, all subscribers will be notified.",
229
+ })
230
+ @ManyToMany(
231
+ () => {
232
+ return Monitor;
233
+ },
234
+ { eager: false },
235
+ )
236
+ @JoinTable({
237
+ name: "AnnouncementMonitor",
238
+ inverseJoinColumn: {
239
+ name: "monitorId",
240
+ referencedColumnName: "_id",
241
+ },
242
+ joinColumn: {
243
+ name: "announcementId",
244
+ referencedColumnName: "_id",
245
+ },
246
+ })
247
+ public monitors?: Array<Monitor> = undefined;
248
+
201
249
  @ColumnAccessControl({
202
250
  create: [
203
251
  Permission.ProjectOwner,
@@ -1,3 +1,4 @@
1
+ import Monitor from "./Monitor";
1
2
  import Project from "./Project";
2
3
  import StatusPage from "./StatusPage";
3
4
  import User from "./User";
@@ -328,6 +329,53 @@ export default class StatusPageAnnouncementTemplate extends BaseModel {
328
329
  })
329
330
  public statusPages?: Array<StatusPage> = undefined;
330
331
 
332
+ @ColumnAccessControl({
333
+ create: [
334
+ Permission.ProjectOwner,
335
+ Permission.ProjectAdmin,
336
+ Permission.ProjectMember,
337
+ Permission.CreateStatusPageAnnouncementTemplate,
338
+ ],
339
+ read: [
340
+ Permission.ProjectOwner,
341
+ Permission.ProjectAdmin,
342
+ Permission.ProjectMember,
343
+ Permission.ReadStatusPageAnnouncementTemplate,
344
+ ],
345
+ update: [
346
+ Permission.ProjectOwner,
347
+ Permission.ProjectAdmin,
348
+ Permission.ProjectMember,
349
+ Permission.EditStatusPageAnnouncementTemplate,
350
+ ],
351
+ })
352
+ @TableColumn({
353
+ required: false,
354
+ type: TableColumnType.EntityArray,
355
+ modelType: Monitor,
356
+ title: "Monitors",
357
+ description:
358
+ "List of monitors affected by this announcement template. If none are selected, all subscribers will be notified.",
359
+ })
360
+ @ManyToMany(
361
+ () => {
362
+ return Monitor;
363
+ },
364
+ { eager: false },
365
+ )
366
+ @JoinTable({
367
+ name: "AnnouncementTemplateMonitor",
368
+ inverseJoinColumn: {
369
+ name: "monitorId",
370
+ referencedColumnName: "_id",
371
+ },
372
+ joinColumn: {
373
+ name: "announcementTemplateId",
374
+ referencedColumnName: "_id",
375
+ },
376
+ })
377
+ public monitors?: Array<Monitor> = undefined;
378
+
331
379
  @ColumnAccessControl({
332
380
  create: [
333
381
  Permission.ProjectOwner,
@@ -18,13 +18,20 @@ import WorkspaceType from "../../Types/Workspace/WorkspaceType";
18
18
  import Permission from "../../Types/Permission";
19
19
 
20
20
  export interface MiscData {
21
- [key: string]: string;
21
+ [key: string]: any;
22
22
  }
23
23
 
24
24
  export interface SlackMiscData extends MiscData {
25
25
  teamId: string;
26
26
  teamName: string;
27
27
  botUserId: string;
28
+ channelCache?: {
29
+ [channelName: string]: {
30
+ id: string;
31
+ name: string;
32
+ lastUpdated: string;
33
+ };
34
+ };
28
35
  }
29
36
 
30
37
  @TenantColumn("projectId")
@@ -235,13 +235,25 @@ export default class BaseAPI<
235
235
  ): Promise<void> {
236
236
  await this.onBeforeList(req, res);
237
237
 
238
- const skip: PositiveNumber = req.query["skip"]
239
- ? new PositiveNumber(req.query["skip"] as string)
240
- : new PositiveNumber(0);
238
+ // Extract pagination parameters from query or body (for POST requests)
239
+ // Support both 'skip' and 'offset' parameters (offset is alias for skip)
240
+ let skipValue: number = 0;
241
+ let limitValue: number = DEFAULT_LIMIT;
242
+
243
+ if (req.query["skip"]) {
244
+ skipValue = parseInt(req.query["skip"] as string, 10) || 0;
245
+ } else if (req.body && req.body["skip"] !== undefined) {
246
+ skipValue = parseInt(req.body["skip"] as string, 10) || 0;
247
+ }
248
+
249
+ if (req.query["limit"]) {
250
+ limitValue = parseInt(req.query["limit"] as string, 10) || DEFAULT_LIMIT;
251
+ } else if (req.body && req.body["limit"] !== undefined) {
252
+ limitValue = parseInt(req.body["limit"] as string, 10) || DEFAULT_LIMIT;
253
+ }
241
254
 
242
- const limit: PositiveNumber = req.query["limit"]
243
- ? new PositiveNumber(req.query["limit"] as string)
244
- : new PositiveNumber(DEFAULT_LIMIT);
255
+ const skip: PositiveNumber = new PositiveNumber(skipValue);
256
+ const limit: PositiveNumber = new PositiveNumber(limitValue);
245
257
 
246
258
  if (limit.toNumber() > LIMIT_PER_PROJECT) {
247
259
  throw new BadRequestException(
@@ -2076,6 +2076,10 @@ export default class StatusPageAPI extends BaseAPI<
2076
2076
  _id: true,
2077
2077
  showAnnouncementAt: true,
2078
2078
  endAnnouncementAt: true,
2079
+ monitors: {
2080
+ _id: true,
2081
+ name: true,
2082
+ },
2079
2083
  },
2080
2084
  skip: 0,
2081
2085
  limit: LIMIT_PER_PROJECT,
@@ -2096,6 +2100,7 @@ export default class StatusPageAPI extends BaseAPI<
2096
2100
  displayTooltip: true,
2097
2101
  displayDescription: true,
2098
2102
  displayName: true,
2103
+ monitorGroupId: true,
2099
2104
  monitor: {
2100
2105
  _id: true,
2101
2106
  currentMonitorStatusId: true,
@@ -2109,6 +2114,65 @@ export default class StatusPageAPI extends BaseAPI<
2109
2114
  },
2110
2115
  });
2111
2116
 
2117
+ const monitorGroupIds: Array<ObjectID> = statusPageResources
2118
+ .map((resource: StatusPageResource) => {
2119
+ return resource.monitorGroupId!;
2120
+ })
2121
+ .filter((id: ObjectID) => {
2122
+ return Boolean(id); // remove nulls
2123
+ });
2124
+
2125
+ // get monitors in the group.
2126
+ const monitorsInGroup: Dictionary<Array<ObjectID>> = {};
2127
+
2128
+ // get monitor status charts.
2129
+ const monitorsOnStatusPage: Array<ObjectID> = statusPageResources
2130
+ .map((monitor: StatusPageResource) => {
2131
+ return monitor.monitorId!;
2132
+ })
2133
+ .filter((id: ObjectID) => {
2134
+ return Boolean(id); // remove nulls
2135
+ });
2136
+
2137
+ for (const monitorGroupId of monitorGroupIds) {
2138
+ // get monitors in the group.
2139
+
2140
+ const groupResources: Array<MonitorGroupResource> =
2141
+ await MonitorGroupResourceService.findBy({
2142
+ query: {
2143
+ monitorGroupId: monitorGroupId,
2144
+ },
2145
+ select: {
2146
+ monitorId: true,
2147
+ },
2148
+ props: {
2149
+ isRoot: true,
2150
+ },
2151
+ limit: LIMIT_PER_PROJECT,
2152
+ skip: 0,
2153
+ });
2154
+
2155
+ const monitorsInGroupIds: Array<ObjectID> = groupResources
2156
+ .map((resource: MonitorGroupResource) => {
2157
+ return resource.monitorId!;
2158
+ })
2159
+ .filter((id: ObjectID) => {
2160
+ return Boolean(id); // remove nulls
2161
+ });
2162
+
2163
+ for (const monitorId of monitorsInGroupIds) {
2164
+ if (
2165
+ !monitorsOnStatusPage.find((item: ObjectID) => {
2166
+ return item.toString() === monitorId.toString();
2167
+ })
2168
+ ) {
2169
+ monitorsOnStatusPage.push(monitorId);
2170
+ }
2171
+ }
2172
+
2173
+ monitorsInGroup[monitorGroupId.toString()] = monitorsInGroupIds;
2174
+ }
2175
+
2112
2176
  const response: JSONObject = {
2113
2177
  announcements: BaseModel.toJSONArray(
2114
2178
  announcements,
@@ -2118,6 +2182,7 @@ export default class StatusPageAPI extends BaseAPI<
2118
2182
  statusPageResources,
2119
2183
  StatusPageResource,
2120
2184
  ),
2185
+ monitorsInGroup: JSONFunctions.serialize(monitorsInGroup),
2121
2186
  };
2122
2187
 
2123
2188
  return response;
@@ -0,0 +1,79 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1757423505855 implements MigrationInterface {
4
+ public name = "MigrationName1757423505855";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `CREATE TABLE "AnnouncementMonitor" ("announcementId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_7acb54ddede76e67b5e2eb84519" PRIMARY KEY ("announcementId", "monitorId"))`,
9
+ );
10
+ await queryRunner.query(
11
+ `CREATE INDEX "IDX_b43baa07f7be40b5cfb61153fd" ON "AnnouncementMonitor" ("announcementId") `,
12
+ );
13
+ await queryRunner.query(
14
+ `CREATE INDEX "IDX_751be8c61cfeb7e1a0af9fcc3a" ON "AnnouncementMonitor" ("monitorId") `,
15
+ );
16
+ await queryRunner.query(
17
+ `CREATE TABLE "AnnouncementTemplateMonitor" ("announcementTemplateId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_ad19f2b65c1b6b77e7a8d80c028" PRIMARY KEY ("announcementTemplateId", "monitorId"))`,
18
+ );
19
+ await queryRunner.query(
20
+ `CREATE INDEX "IDX_46bee9106e631ebe9f6c95ff15" ON "AnnouncementTemplateMonitor" ("announcementTemplateId") `,
21
+ );
22
+ await queryRunner.query(
23
+ `CREATE INDEX "IDX_0d979cb538fde87c7441d7bc93" ON "AnnouncementTemplateMonitor" ("monitorId") `,
24
+ );
25
+ await queryRunner.query(
26
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
27
+ );
28
+ await queryRunner.query(
29
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "AnnouncementMonitor" ADD CONSTRAINT "FK_b43baa07f7be40b5cfb61153fd3" FOREIGN KEY ("announcementId") REFERENCES "StatusPageAnnouncement"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "AnnouncementMonitor" ADD CONSTRAINT "FK_751be8c61cfeb7e1a0af9fcc3a0" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
36
+ );
37
+ await queryRunner.query(
38
+ `ALTER TABLE "AnnouncementTemplateMonitor" ADD CONSTRAINT "FK_46bee9106e631ebe9f6c95ff153" FOREIGN KEY ("announcementTemplateId") REFERENCES "StatusPageAnnouncementTemplate"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
39
+ );
40
+ await queryRunner.query(
41
+ `ALTER TABLE "AnnouncementTemplateMonitor" ADD CONSTRAINT "FK_0d979cb538fde87c7441d7bc936" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
42
+ );
43
+ }
44
+
45
+ public async down(queryRunner: QueryRunner): Promise<void> {
46
+ await queryRunner.query(
47
+ `ALTER TABLE "AnnouncementTemplateMonitor" DROP CONSTRAINT "FK_0d979cb538fde87c7441d7bc936"`,
48
+ );
49
+ await queryRunner.query(
50
+ `ALTER TABLE "AnnouncementTemplateMonitor" DROP CONSTRAINT "FK_46bee9106e631ebe9f6c95ff153"`,
51
+ );
52
+ await queryRunner.query(
53
+ `ALTER TABLE "AnnouncementMonitor" DROP CONSTRAINT "FK_751be8c61cfeb7e1a0af9fcc3a0"`,
54
+ );
55
+ await queryRunner.query(
56
+ `ALTER TABLE "AnnouncementMonitor" DROP CONSTRAINT "FK_b43baa07f7be40b5cfb61153fd3"`,
57
+ );
58
+ await queryRunner.query(
59
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
60
+ );
61
+ await queryRunner.query(
62
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
63
+ );
64
+ await queryRunner.query(
65
+ `DROP INDEX "public"."IDX_0d979cb538fde87c7441d7bc93"`,
66
+ );
67
+ await queryRunner.query(
68
+ `DROP INDEX "public"."IDX_46bee9106e631ebe9f6c95ff15"`,
69
+ );
70
+ await queryRunner.query(`DROP TABLE "AnnouncementTemplateMonitor"`);
71
+ await queryRunner.query(
72
+ `DROP INDEX "public"."IDX_751be8c61cfeb7e1a0af9fcc3a"`,
73
+ );
74
+ await queryRunner.query(
75
+ `DROP INDEX "public"."IDX_b43baa07f7be40b5cfb61153fd"`,
76
+ );
77
+ await queryRunner.query(`DROP TABLE "AnnouncementMonitor"`);
78
+ }
79
+ }
@@ -166,6 +166,7 @@ import { MigrationName1756296282627 } from "./1756296282627-MigrationName";
166
166
  import { MigrationName1756300358095 } from "./1756300358095-MigrationName";
167
167
  import { MigrationName1756821449686 } from "./1756821449686-MigrationName";
168
168
  import { MigrationName1757416939595 } from "./1757416939595-MigrationName";
169
+ import { MigrationName1757423505855 } from "./1757423505855-MigrationName";
169
170
 
170
171
  export default [
171
172
  InitialMigration,
@@ -336,4 +337,5 @@ export default [
336
337
  MigrationName1756300358095,
337
338
  MigrationName1756821449686,
338
339
  MigrationName1757416939595,
340
+ MigrationName1757423505855,
339
341
  ];
@@ -804,7 +804,10 @@ Stay informed about service availability! 🚀`;
804
804
  if (
805
805
  data.statusPage.allowSubscribersToChooseResources &&
806
806
  !data.subscriber.isSubscribedToAllResources &&
807
- data.eventType !== StatusPageEventType.Announcement // announcements dont have resources
807
+ !(
808
+ data.eventType === StatusPageEventType.Announcement &&
809
+ data.statusPageResources.length === 0
810
+ ) // announcements with no monitors don't use resource filtering
808
811
  ) {
809
812
  logger.debug(
810
813
  "Subscriber can choose resources and is not subscribed to all resources.",
@@ -214,6 +214,7 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
214
214
  ).doesChannelExist({
215
215
  authToken: projectAuthToken,
216
216
  channelName: channelName,
217
+ projectId: data.projectId,
217
218
  });
218
219
 
219
220
  if (!channelExists) {
@@ -459,6 +460,7 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
459
460
  workspaceType: workspaceType,
460
461
  }),
461
462
  sendMessageBeforeArchiving: data.sendMessageBeforeArchiving,
463
+ projectId: data.projectId,
462
464
  });
463
465
  }
464
466
  }
@@ -1032,6 +1034,7 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
1032
1034
  } as WorkspacePayloadMarkdown,
1033
1035
  ],
1034
1036
  },
1037
+ projectId: data.projectId,
1035
1038
  });
1036
1039
  } catch (e) {
1037
1040
  logger.error("Error in sending message to channel");
@@ -1059,6 +1062,7 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
1059
1062
  }),
1060
1063
  workspaceUserIds: workspaceUserIds,
1061
1064
  },
1065
+ projectId: data.projectId,
1062
1066
  });
1063
1067
  }
1064
1068
  }
@@ -1187,6 +1191,7 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
1187
1191
  } as WorkspacePayloadMarkdown,
1188
1192
  ],
1189
1193
  },
1194
+ projectId: data.projectId,
1190
1195
  },
1191
1196
  );
1192
1197
  } catch (e) {
@@ -1216,6 +1221,7 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
1216
1221
  channelNames: channelNames,
1217
1222
  workspaceUserIds: workspaceUserIds,
1218
1223
  },
1224
+ projectId: data.projectId,
1219
1225
  });
1220
1226
 
1221
1227
  // Log user invitations
@@ -1373,6 +1379,7 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
1373
1379
  ).createChannel({
1374
1380
  authToken: data.projectOrUserAuthTokenForWorkspace,
1375
1381
  channelName: notificationChannel.channelName,
1382
+ projectId: data.projectId,
1376
1383
  });
1377
1384
 
1378
1385
  const notificationWorkspaceChannel: NotificationRuleWorkspaceChannel = {