@oneuptime/common 7.0.4751 → 7.0.4755
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/Server/Services/AlertService.ts +222 -109
- package/Server/Services/IncidentService.ts +290 -147
- package/Server/Services/MonitorService.ts +1 -1
- package/Server/Services/ScheduledMaintenanceService.ts +201 -111
- package/Server/Services/StatusPageService.ts +8 -6
- package/build/dist/Server/Services/AlertService.js +190 -88
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +246 -109
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +152 -78
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +6 -3
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/package.json +1 -1
|
@@ -54,6 +54,7 @@ import { MessageBlocksByWorkspaceType } from "./WorkspaceNotificationRuleService
|
|
|
54
54
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
55
55
|
import MetricType from "../../Models/DatabaseModels/MetricType";
|
|
56
56
|
import Dictionary from "../../Types/Dictionary";
|
|
57
|
+
import OnCallDutyPolicy from "../../Models/DatabaseModels/OnCallDutyPolicy";
|
|
57
58
|
|
|
58
59
|
export class Service extends DatabaseService<Model> {
|
|
59
60
|
public constructor() {
|
|
@@ -272,6 +273,7 @@ export class Service extends DatabaseService<Model> {
|
|
|
272
273
|
throw new BadDataException("currentAlertStateId is required");
|
|
273
274
|
}
|
|
274
275
|
|
|
276
|
+
// Get alert data for feed creation
|
|
275
277
|
const alert: Model | null = await this.findOneById({
|
|
276
278
|
id: createdItem.id,
|
|
277
279
|
select: {
|
|
@@ -304,145 +306,256 @@ export class Service extends DatabaseService<Model> {
|
|
|
304
306
|
throw new BadDataException("Alert not found");
|
|
305
307
|
}
|
|
306
308
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
+
// Execute core operations in parallel first
|
|
310
|
+
const coreOperations: Array<Promise<any>> = [];
|
|
309
311
|
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
channelsCreated: Array<NotificationRuleWorkspaceChannel>;
|
|
313
|
-
} | null =
|
|
314
|
-
await AlertWorkspaceMessages.createChannelsAndInviteUsersToChannels({
|
|
315
|
-
projectId: createdItem.projectId,
|
|
316
|
-
alertId: createdItem.id!,
|
|
317
|
-
alertNumber: createdItem.alertNumber!,
|
|
318
|
-
});
|
|
312
|
+
// Create feed item asynchronously
|
|
313
|
+
coreOperations.push(this.createAlertFeedAsync(alert, createdItem));
|
|
319
314
|
|
|
320
|
-
|
|
321
|
-
|
|
315
|
+
// Handle state change asynchronously
|
|
316
|
+
coreOperations.push(this.handleAlertStateChangeAsync(createdItem));
|
|
322
317
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
318
|
+
// Handle owner assignment asynchronously
|
|
319
|
+
if (
|
|
320
|
+
onCreate.createBy.miscDataProps &&
|
|
321
|
+
(onCreate.createBy.miscDataProps["ownerTeams"] ||
|
|
322
|
+
onCreate.createBy.miscDataProps["ownerUsers"])
|
|
323
|
+
) {
|
|
324
|
+
coreOperations.push(
|
|
325
|
+
this.addOwners(
|
|
326
|
+
createdItem.projectId,
|
|
327
|
+
createdItem.id,
|
|
328
|
+
(onCreate.createBy.miscDataProps["ownerUsers"] as Array<ObjectID>) ||
|
|
329
|
+
[],
|
|
330
|
+
(onCreate.createBy.miscDataProps["ownerTeams"] as Array<ObjectID>) ||
|
|
331
|
+
[],
|
|
332
|
+
false,
|
|
333
|
+
onCreate.createBy.props,
|
|
334
|
+
),
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Execute core operations in parallel with error handling
|
|
339
|
+
Promise.allSettled(coreOperations)
|
|
340
|
+
.then((coreResults: any[]) => {
|
|
341
|
+
// Log any errors from core operations
|
|
342
|
+
coreResults.forEach((result: any, index: number) => {
|
|
343
|
+
if (result.status === "rejected") {
|
|
344
|
+
logger.error(
|
|
345
|
+
`Core operation ${index} failed in AlertService.onCreateSuccess: ${result.reason}`,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Handle on-call duty policies asynchronously
|
|
351
|
+
if (
|
|
352
|
+
createdItem.onCallDutyPolicies?.length &&
|
|
353
|
+
createdItem.onCallDutyPolicies?.length > 0
|
|
354
|
+
) {
|
|
355
|
+
this.executeAlertOnCallDutyPoliciesAsync(createdItem).catch(
|
|
356
|
+
(error: Error) => {
|
|
357
|
+
logger.error(
|
|
358
|
+
`On-call duty policy execution failed in AlertService.onCreateSuccess: ${error}`,
|
|
359
|
+
);
|
|
360
|
+
},
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Handle workspace operations after core operations complete
|
|
365
|
+
if (createdItem.projectId && createdItem.id) {
|
|
366
|
+
// Run workspace operations in background without blocking response
|
|
367
|
+
this.handleAlertWorkspaceOperationsAsync(createdItem).catch(
|
|
368
|
+
(error: Error) => {
|
|
369
|
+
logger.error(
|
|
370
|
+
`Workspace operations failed in AlertService.onCreateSuccess: ${error}`,
|
|
371
|
+
);
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
.catch((error: Error) => {
|
|
377
|
+
logger.error(
|
|
378
|
+
`Critical error in AlertService core operations: ${error}`,
|
|
379
|
+
);
|
|
333
380
|
});
|
|
381
|
+
|
|
382
|
+
return createdItem;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
@CaptureSpan()
|
|
386
|
+
private async handleAlertWorkspaceOperationsAsync(
|
|
387
|
+
createdItem: Model,
|
|
388
|
+
): Promise<void> {
|
|
389
|
+
try {
|
|
390
|
+
if (!createdItem.projectId || !createdItem.id) {
|
|
391
|
+
throw new BadDataException(
|
|
392
|
+
"projectId and id are required for workspace operations",
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// send message to workspaces - slack, teams, etc.
|
|
397
|
+
const workspaceResult: {
|
|
398
|
+
channelsCreated: Array<NotificationRuleWorkspaceChannel>;
|
|
399
|
+
} | null =
|
|
400
|
+
await AlertWorkspaceMessages.createChannelsAndInviteUsersToChannels({
|
|
401
|
+
projectId: createdItem.projectId,
|
|
402
|
+
alertId: createdItem.id,
|
|
403
|
+
alertNumber: createdItem.alertNumber!,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
logger.debug("Alert created. Workspace result:");
|
|
407
|
+
logger.debug(workspaceResult);
|
|
408
|
+
|
|
409
|
+
if (workspaceResult && workspaceResult.channelsCreated?.length > 0) {
|
|
410
|
+
// update alert with these channels.
|
|
411
|
+
await this.updateOneById({
|
|
412
|
+
id: createdItem.id,
|
|
413
|
+
data: {
|
|
414
|
+
postUpdatesToWorkspaceChannels:
|
|
415
|
+
workspaceResult.channelsCreated || [],
|
|
416
|
+
},
|
|
417
|
+
props: {
|
|
418
|
+
isRoot: true,
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
} catch (error) {
|
|
423
|
+
logger.error(`Error in handleAlertWorkspaceOperationsAsync: ${error}`);
|
|
424
|
+
throw error;
|
|
334
425
|
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
@CaptureSpan()
|
|
429
|
+
private async createAlertFeedAsync(
|
|
430
|
+
alert: Model,
|
|
431
|
+
createdItem: Model,
|
|
432
|
+
): Promise<void> {
|
|
433
|
+
try {
|
|
434
|
+
const createdByUserId: ObjectID | undefined | null =
|
|
435
|
+
createdItem.createdByUserId || createdItem.createdByUser?.id;
|
|
335
436
|
|
|
336
|
-
|
|
337
|
-
|
|
437
|
+
let feedInfoInMarkdown: string = `#### 🚨 Alert ${createdItem.alertNumber?.toString()} Created:
|
|
438
|
+
|
|
338
439
|
**${createdItem.title || "No title provided."}**:
|
|
339
|
-
|
|
440
|
+
|
|
340
441
|
${createdItem.description || "No description provided."}
|
|
341
|
-
|
|
342
|
-
|
|
442
|
+
|
|
443
|
+
`;
|
|
343
444
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
445
|
+
if (alert.currentAlertState?.name) {
|
|
446
|
+
feedInfoInMarkdown += `🔴 **Alert State**: ${alert.currentAlertState.name} \n\n`;
|
|
447
|
+
}
|
|
347
448
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
449
|
+
if (alert.alertSeverity?.name) {
|
|
450
|
+
feedInfoInMarkdown += `⚠️ **Severity**: ${alert.alertSeverity.name} \n\n`;
|
|
451
|
+
}
|
|
351
452
|
|
|
352
|
-
|
|
353
|
-
|
|
453
|
+
if (alert.monitor) {
|
|
454
|
+
feedInfoInMarkdown += `🌎 **Resources Affected**:\n`;
|
|
354
455
|
|
|
355
|
-
|
|
356
|
-
|
|
456
|
+
const monitor: Monitor = alert.monitor;
|
|
457
|
+
feedInfoInMarkdown += `- [${monitor.name}](${(await MonitorService.getMonitorLinkInDashboard(createdItem.projectId!, monitor.id!)).toString()})\n`;
|
|
357
458
|
|
|
358
|
-
|
|
359
|
-
|
|
459
|
+
feedInfoInMarkdown += `\n\n`;
|
|
460
|
+
}
|
|
360
461
|
|
|
361
|
-
|
|
362
|
-
|
|
462
|
+
if (createdItem.rootCause) {
|
|
463
|
+
feedInfoInMarkdown += `\n
|
|
363
464
|
📄 **Root Cause**:
|
|
364
|
-
|
|
465
|
+
|
|
365
466
|
${createdItem.rootCause || "No root cause provided."}
|
|
366
|
-
|
|
467
|
+
|
|
367
468
|
`;
|
|
368
|
-
|
|
469
|
+
}
|
|
369
470
|
|
|
370
|
-
|
|
371
|
-
|
|
471
|
+
if (createdItem.remediationNotes) {
|
|
472
|
+
feedInfoInMarkdown += `\n
|
|
372
473
|
🎯 **Remediation Notes**:
|
|
373
|
-
|
|
474
|
+
|
|
374
475
|
${createdItem.remediationNotes || "No remediation notes provided."}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
`;
|
|
479
|
+
}
|
|
379
480
|
|
|
380
|
-
|
|
381
|
-
|
|
481
|
+
const alertCreateMessageBlocks: Array<MessageBlocksByWorkspaceType> =
|
|
482
|
+
await AlertWorkspaceMessages.getAlertCreateMessageBlocks({
|
|
483
|
+
alertId: createdItem.id!,
|
|
484
|
+
projectId: createdItem.projectId!,
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
await AlertFeedService.createAlertFeedItem({
|
|
382
488
|
alertId: createdItem.id!,
|
|
383
489
|
projectId: createdItem.projectId!,
|
|
490
|
+
alertFeedEventType: AlertFeedEventType.AlertCreated,
|
|
491
|
+
displayColor: Red500,
|
|
492
|
+
feedInfoInMarkdown: feedInfoInMarkdown,
|
|
493
|
+
userId: createdByUserId || undefined,
|
|
494
|
+
workspaceNotification: {
|
|
495
|
+
appendMessageBlocks: alertCreateMessageBlocks,
|
|
496
|
+
sendWorkspaceNotification: true,
|
|
497
|
+
},
|
|
384
498
|
});
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
projectId: createdItem.projectId!,
|
|
389
|
-
alertFeedEventType: AlertFeedEventType.AlertCreated,
|
|
390
|
-
displayColor: Red500,
|
|
391
|
-
feedInfoInMarkdown: feedInfoInMarkdown,
|
|
392
|
-
userId: createdByUserId || undefined,
|
|
393
|
-
workspaceNotification: {
|
|
394
|
-
appendMessageBlocks: alertCreateMessageBlocks,
|
|
395
|
-
sendWorkspaceNotification: true,
|
|
396
|
-
},
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
await this.changeAlertState({
|
|
400
|
-
projectId: createdItem.projectId,
|
|
401
|
-
alertId: createdItem.id,
|
|
402
|
-
alertStateId: createdItem.currentAlertStateId,
|
|
403
|
-
notifyOwners: false,
|
|
404
|
-
rootCause: createdItem.rootCause,
|
|
405
|
-
stateChangeLog: createdItem.createdStateLog,
|
|
406
|
-
props: {
|
|
407
|
-
isRoot: true,
|
|
408
|
-
},
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
// add owners.
|
|
412
|
-
|
|
413
|
-
if (
|
|
414
|
-
onCreate.createBy.miscDataProps &&
|
|
415
|
-
(onCreate.createBy.miscDataProps["ownerTeams"] ||
|
|
416
|
-
onCreate.createBy.miscDataProps["ownerUsers"])
|
|
417
|
-
) {
|
|
418
|
-
await this.addOwners(
|
|
419
|
-
createdItem.projectId,
|
|
420
|
-
createdItem.id,
|
|
421
|
-
(onCreate.createBy.miscDataProps["ownerUsers"] as Array<ObjectID>) ||
|
|
422
|
-
[],
|
|
423
|
-
(onCreate.createBy.miscDataProps["ownerTeams"] as Array<ObjectID>) ||
|
|
424
|
-
[],
|
|
425
|
-
false,
|
|
426
|
-
onCreate.createBy.props,
|
|
427
|
-
);
|
|
499
|
+
} catch (error) {
|
|
500
|
+
logger.error(`Error in createAlertFeedAsync: ${error}`);
|
|
501
|
+
throw error;
|
|
428
502
|
}
|
|
503
|
+
}
|
|
429
504
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
new ObjectID(policy._id as string),
|
|
437
|
-
{
|
|
438
|
-
triggeredByAlertId: createdItem.id!,
|
|
439
|
-
userNotificationEventType: UserNotificationEventType.AlertCreated,
|
|
440
|
-
},
|
|
505
|
+
@CaptureSpan()
|
|
506
|
+
private async handleAlertStateChangeAsync(createdItem: Model): Promise<void> {
|
|
507
|
+
try {
|
|
508
|
+
if (!createdItem.projectId || !createdItem.id) {
|
|
509
|
+
throw new BadDataException(
|
|
510
|
+
"projectId and id are required for state change",
|
|
441
511
|
);
|
|
442
512
|
}
|
|
513
|
+
|
|
514
|
+
await this.changeAlertState({
|
|
515
|
+
projectId: createdItem.projectId,
|
|
516
|
+
alertId: createdItem.id,
|
|
517
|
+
alertStateId: createdItem.currentAlertStateId!,
|
|
518
|
+
notifyOwners: false,
|
|
519
|
+
rootCause: createdItem.rootCause,
|
|
520
|
+
stateChangeLog: createdItem.createdStateLog,
|
|
521
|
+
props: {
|
|
522
|
+
isRoot: true,
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
} catch (error) {
|
|
526
|
+
logger.error(`Error in handleAlertStateChangeAsync: ${error}`);
|
|
527
|
+
throw error;
|
|
443
528
|
}
|
|
529
|
+
}
|
|
444
530
|
|
|
445
|
-
|
|
531
|
+
@CaptureSpan()
|
|
532
|
+
private async executeAlertOnCallDutyPoliciesAsync(
|
|
533
|
+
createdItem: Model,
|
|
534
|
+
): Promise<void> {
|
|
535
|
+
try {
|
|
536
|
+
if (
|
|
537
|
+
createdItem.onCallDutyPolicies?.length &&
|
|
538
|
+
createdItem.onCallDutyPolicies?.length > 0
|
|
539
|
+
) {
|
|
540
|
+
// Execute all on-call policies in parallel
|
|
541
|
+
const policyPromises: Promise<void>[] =
|
|
542
|
+
createdItem.onCallDutyPolicies.map((policy: OnCallDutyPolicy) => {
|
|
543
|
+
return OnCallDutyPolicyService.executePolicy(
|
|
544
|
+
new ObjectID(policy["_id"] as string),
|
|
545
|
+
{
|
|
546
|
+
triggeredByAlertId: createdItem.id!,
|
|
547
|
+
userNotificationEventType:
|
|
548
|
+
UserNotificationEventType.AlertCreated,
|
|
549
|
+
},
|
|
550
|
+
);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
await Promise.allSettled(policyPromises);
|
|
554
|
+
}
|
|
555
|
+
} catch (error) {
|
|
556
|
+
logger.error(`Error in executeAlertOnCallDutyPoliciesAsync: ${error}`);
|
|
557
|
+
throw error;
|
|
558
|
+
}
|
|
446
559
|
}
|
|
447
560
|
|
|
448
561
|
@CaptureSpan()
|