@oneuptime/common 7.0.4346 → 7.0.4358
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.
- package/Models/DatabaseModels/StatusPage.ts +37 -0
- package/Models/DatabaseModels/StatusPageSubscriber.ts +60 -0
- package/Server/API/StatusPageAPI.ts +104 -10
- package/Server/Infrastructure/Postgres/SchemaMigrations/1749065784320-MigrationName.ts +23 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1749133333893-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/AlertService.ts +51 -0
- package/Server/Services/IncidentService.ts +53 -0
- package/Server/Services/ScheduledMaintenanceService.ts +75 -0
- package/Server/Services/StatusPageSubscriberService.ts +116 -1
- package/Server/Utils/OpenAPI.ts +224 -27
- package/Server/Utils/Workspace/Slack/Slack.ts +14 -0
- package/Utils/Schema/ModelSchema.ts +1303 -11
- package/build/dist/Models/DatabaseModels/StatusPage.js +39 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +62 -0
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +73 -10
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1749065784320-MigrationName.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1749065784320-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1749133333893-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1749133333893-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +46 -0
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +46 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +65 -0
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +98 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Utils/OpenAPI.js +178 -50
- package/build/dist/Server/Utils/OpenAPI.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +15 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Utils/Schema/ModelSchema.js +1101 -6
- package/build/dist/Utils/Schema/ModelSchema.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
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}
|
|
2286
|
+
`Subscriber not found for email: ${email}, phone: ${phone}, or slack workspace: ${slackWorkspaceName}`,
|
|
2247
2287
|
);
|
|
2248
2288
|
|
|
2249
|
-
let
|
|
2289
|
+
let identifierType: string = "email";
|
|
2250
2290
|
if (phone) {
|
|
2251
|
-
|
|
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 ${
|
|
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
|
|
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
|
|
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
|
];
|
|
@@ -1338,5 +1338,56 @@ ${alertSeverity.name}
|
|
|
1338
1338
|
|
|
1339
1339
|
return alert;
|
|
1340
1340
|
}
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* Ensures the currentAlertStateId of the alert matches the latest timeline entry.
|
|
1344
|
+
*/
|
|
1345
|
+
public async refreshAlertCurrentStatus(alertId: ObjectID): Promise<void> {
|
|
1346
|
+
const alert: Model | null = await this.findOneById({
|
|
1347
|
+
id: alertId,
|
|
1348
|
+
select: {
|
|
1349
|
+
_id: true,
|
|
1350
|
+
projectId: true,
|
|
1351
|
+
currentAlertStateId: true,
|
|
1352
|
+
},
|
|
1353
|
+
props: { isRoot: true },
|
|
1354
|
+
});
|
|
1355
|
+
if (!alert || !alert.projectId) {
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
const latestTimeline: AlertStateTimeline | null =
|
|
1359
|
+
await AlertStateTimelineService.findOneBy({
|
|
1360
|
+
query: {
|
|
1361
|
+
alertId: alert.id!,
|
|
1362
|
+
projectId: alert.projectId,
|
|
1363
|
+
},
|
|
1364
|
+
sort: {
|
|
1365
|
+
startsAt: SortOrder.Descending,
|
|
1366
|
+
},
|
|
1367
|
+
select: {
|
|
1368
|
+
alertStateId: true,
|
|
1369
|
+
},
|
|
1370
|
+
props: {
|
|
1371
|
+
isRoot: true,
|
|
1372
|
+
},
|
|
1373
|
+
});
|
|
1374
|
+
if (
|
|
1375
|
+
latestTimeline &&
|
|
1376
|
+
latestTimeline.alertStateId &&
|
|
1377
|
+
alert.currentAlertStateId?.toString() !==
|
|
1378
|
+
latestTimeline.alertStateId.toString()
|
|
1379
|
+
) {
|
|
1380
|
+
await this.updateOneBy({
|
|
1381
|
+
query: { _id: alert.id!.toString() },
|
|
1382
|
+
data: {
|
|
1383
|
+
currentAlertStateId: latestTimeline.alertStateId,
|
|
1384
|
+
},
|
|
1385
|
+
props: { isRoot: true },
|
|
1386
|
+
});
|
|
1387
|
+
logger.info(
|
|
1388
|
+
`Updated Alert ${alert.id} current state to ${latestTimeline.alertStateId}`,
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1341
1392
|
}
|
|
1342
1393
|
export default new Service();
|
|
@@ -1932,6 +1932,59 @@ ${incidentSeverity.name}
|
|
|
1932
1932
|
|
|
1933
1933
|
return incident.incidentNumber ? Number(incident.incidentNumber) : null;
|
|
1934
1934
|
}
|
|
1935
|
+
|
|
1936
|
+
/**
|
|
1937
|
+
* Ensures the currentIncidentStateId of the incident matches the latest timeline entry.
|
|
1938
|
+
*/
|
|
1939
|
+
public async refreshIncidentCurrentStatus(
|
|
1940
|
+
incidentId: ObjectID,
|
|
1941
|
+
): Promise<void> {
|
|
1942
|
+
const incident: Model | null = await this.findOneById({
|
|
1943
|
+
id: incidentId,
|
|
1944
|
+
select: {
|
|
1945
|
+
_id: true,
|
|
1946
|
+
projectId: true,
|
|
1947
|
+
currentIncidentStateId: true,
|
|
1948
|
+
},
|
|
1949
|
+
props: { isRoot: true },
|
|
1950
|
+
});
|
|
1951
|
+
if (!incident || !incident.projectId) {
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
const latestTimeline: IncidentStateTimeline | null =
|
|
1955
|
+
await IncidentStateTimelineService.findOneBy({
|
|
1956
|
+
query: {
|
|
1957
|
+
incidentId: incident.id!,
|
|
1958
|
+
projectId: incident.projectId,
|
|
1959
|
+
},
|
|
1960
|
+
sort: {
|
|
1961
|
+
startsAt: SortOrder.Descending,
|
|
1962
|
+
},
|
|
1963
|
+
select: {
|
|
1964
|
+
incidentStateId: true,
|
|
1965
|
+
},
|
|
1966
|
+
props: {
|
|
1967
|
+
isRoot: true,
|
|
1968
|
+
},
|
|
1969
|
+
});
|
|
1970
|
+
if (
|
|
1971
|
+
latestTimeline &&
|
|
1972
|
+
latestTimeline.incidentStateId &&
|
|
1973
|
+
incident.currentIncidentStateId?.toString() !==
|
|
1974
|
+
latestTimeline.incidentStateId.toString()
|
|
1975
|
+
) {
|
|
1976
|
+
await this.updateOneBy({
|
|
1977
|
+
query: { _id: incident.id!.toString() },
|
|
1978
|
+
data: {
|
|
1979
|
+
currentIncidentStateId: latestTimeline.incidentStateId,
|
|
1980
|
+
},
|
|
1981
|
+
props: { isRoot: true },
|
|
1982
|
+
});
|
|
1983
|
+
logger.info(
|
|
1984
|
+
`Updated Incident ${incident.id} current state to ${latestTimeline.incidentStateId}`,
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1935
1988
|
}
|
|
1936
1989
|
|
|
1937
1990
|
export default new Service();
|
|
@@ -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
|
|
|
@@ -1467,5 +1488,59 @@ ${labels
|
|
|
1467
1488
|
},
|
|
1468
1489
|
);
|
|
1469
1490
|
}
|
|
1491
|
+
|
|
1492
|
+
/**
|
|
1493
|
+
* Ensures the currentScheduledMaintenanceStateId of the scheduled maintenance matches the latest timeline entry.
|
|
1494
|
+
*/
|
|
1495
|
+
public async refreshScheduledMaintenanceCurrentStatus(
|
|
1496
|
+
scheduledMaintenanceId: ObjectID,
|
|
1497
|
+
): Promise<void> {
|
|
1498
|
+
const scheduledMaintenance: Model | null = await this.findOneById({
|
|
1499
|
+
id: scheduledMaintenanceId,
|
|
1500
|
+
select: {
|
|
1501
|
+
_id: true,
|
|
1502
|
+
projectId: true,
|
|
1503
|
+
currentScheduledMaintenanceStateId: true,
|
|
1504
|
+
},
|
|
1505
|
+
props: { isRoot: true },
|
|
1506
|
+
});
|
|
1507
|
+
if (!scheduledMaintenance || !scheduledMaintenance.projectId) {
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
const latestTimeline: ScheduledMaintenanceStateTimeline | null =
|
|
1511
|
+
await ScheduledMaintenanceStateTimelineService.findOneBy({
|
|
1512
|
+
query: {
|
|
1513
|
+
scheduledMaintenanceId: scheduledMaintenance.id!,
|
|
1514
|
+
projectId: scheduledMaintenance.projectId,
|
|
1515
|
+
},
|
|
1516
|
+
sort: {
|
|
1517
|
+
startsAt: SortOrder.Descending,
|
|
1518
|
+
},
|
|
1519
|
+
select: {
|
|
1520
|
+
scheduledMaintenanceStateId: true,
|
|
1521
|
+
},
|
|
1522
|
+
props: {
|
|
1523
|
+
isRoot: true,
|
|
1524
|
+
},
|
|
1525
|
+
});
|
|
1526
|
+
if (
|
|
1527
|
+
latestTimeline &&
|
|
1528
|
+
latestTimeline.scheduledMaintenanceStateId &&
|
|
1529
|
+
scheduledMaintenance.currentScheduledMaintenanceStateId?.toString() !==
|
|
1530
|
+
latestTimeline.scheduledMaintenanceStateId.toString()
|
|
1531
|
+
) {
|
|
1532
|
+
await this.updateOneBy({
|
|
1533
|
+
query: { _id: scheduledMaintenance.id!.toString() },
|
|
1534
|
+
data: {
|
|
1535
|
+
currentScheduledMaintenanceStateId:
|
|
1536
|
+
latestTimeline.scheduledMaintenanceStateId,
|
|
1537
|
+
},
|
|
1538
|
+
props: { isRoot: true },
|
|
1539
|
+
});
|
|
1540
|
+
logger.info(
|
|
1541
|
+
`Updated ScheduledMaintenance ${scheduledMaintenance.id} current state to ${latestTimeline.scheduledMaintenanceStateId}`,
|
|
1542
|
+
);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1470
1545
|
}
|
|
1471
1546
|
export default new Service();
|