@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.
@@ -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
- const createdByUserId: ObjectID | undefined | null =
308
- createdItem.createdByUserId || createdItem.createdByUser?.id;
309
+ // Execute core operations in parallel first
310
+ const coreOperations: Array<Promise<any>> = [];
309
311
 
310
- // send message to workspaces - slack, teams, etc.
311
- const workspaceResult: {
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
- logger.debug("Alert created. Workspace result:");
321
- logger.debug(workspaceResult);
315
+ // Handle state change asynchronously
316
+ coreOperations.push(this.handleAlertStateChangeAsync(createdItem));
322
317
 
323
- if (workspaceResult && workspaceResult.channelsCreated?.length > 0) {
324
- // update alert with these channels.
325
- await this.updateOneById({
326
- id: createdItem.id!,
327
- data: {
328
- postUpdatesToWorkspaceChannels: workspaceResult.channelsCreated || [],
329
- },
330
- props: {
331
- isRoot: true,
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
- let feedInfoInMarkdown: string = `#### 🚨 Alert ${createdItem.alertNumber?.toString()} Created:
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
- if (alert.currentAlertState?.name) {
345
- feedInfoInMarkdown += `🔴 **Alert State**: ${alert.currentAlertState.name} \n\n`;
346
- }
445
+ if (alert.currentAlertState?.name) {
446
+ feedInfoInMarkdown += `🔴 **Alert State**: ${alert.currentAlertState.name} \n\n`;
447
+ }
347
448
 
348
- if (alert.alertSeverity?.name) {
349
- feedInfoInMarkdown += `⚠️ **Severity**: ${alert.alertSeverity.name} \n\n`;
350
- }
449
+ if (alert.alertSeverity?.name) {
450
+ feedInfoInMarkdown += `⚠️ **Severity**: ${alert.alertSeverity.name} \n\n`;
451
+ }
351
452
 
352
- if (alert.monitor) {
353
- feedInfoInMarkdown += `🌎 **Resources Affected**:\n`;
453
+ if (alert.monitor) {
454
+ feedInfoInMarkdown += `🌎 **Resources Affected**:\n`;
354
455
 
355
- const monitor: Monitor = alert.monitor;
356
- feedInfoInMarkdown += `- [${monitor.name}](${(await MonitorService.getMonitorLinkInDashboard(createdItem.projectId!, monitor.id!)).toString()})\n`;
456
+ const monitor: Monitor = alert.monitor;
457
+ feedInfoInMarkdown += `- [${monitor.name}](${(await MonitorService.getMonitorLinkInDashboard(createdItem.projectId!, monitor.id!)).toString()})\n`;
357
458
 
358
- feedInfoInMarkdown += `\n\n`;
359
- }
459
+ feedInfoInMarkdown += `\n\n`;
460
+ }
360
461
 
361
- if (createdItem.rootCause) {
362
- feedInfoInMarkdown += `\n
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
- if (createdItem.remediationNotes) {
371
- feedInfoInMarkdown += `\n
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
- const alertCreateMessageBlocks: Array<MessageBlocksByWorkspaceType> =
381
- await AlertWorkspaceMessages.getAlertCreateMessageBlocks({
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
- await AlertFeedService.createAlertFeedItem({
387
- alertId: createdItem.id!,
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
- if (
431
- createdItem.onCallDutyPolicies?.length &&
432
- createdItem.onCallDutyPolicies?.length > 0
433
- ) {
434
- for (const policy of createdItem.onCallDutyPolicies) {
435
- await OnCallDutyPolicyService.executePolicy(
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
- return createdItem;
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()