@oneuptime/common 9.5.3 → 9.5.4

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 (149) hide show
  1. package/Models/DatabaseModels/Alert.ts +27 -0
  2. package/Models/DatabaseModels/AlertEpisode.ts +26 -0
  3. package/Models/DatabaseModels/Incident.ts +27 -0
  4. package/Models/DatabaseModels/IncidentEpisode.ts +26 -0
  5. package/Models/DatabaseModels/Project.ts +250 -0
  6. package/Models/DatabaseModels/ScheduledMaintenance.ts +27 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1770237245070-MigrationName.ts +57 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1770407024682-MigrationName.ts +83 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  10. package/Server/Services/AlertEpisodeMemberService.ts +6 -3
  11. package/Server/Services/AlertEpisodeService.ts +45 -97
  12. package/Server/Services/AlertEpisodeStateTimelineService.ts +4 -2
  13. package/Server/Services/AlertInternalNoteService.ts +5 -2
  14. package/Server/Services/AlertOwnerTeamService.ts +10 -4
  15. package/Server/Services/AlertOwnerUserService.ts +10 -4
  16. package/Server/Services/AlertService.ts +24 -38
  17. package/Server/Services/AlertStateTimelineService.ts +6 -3
  18. package/Server/Services/DatabaseService.ts +12 -0
  19. package/Server/Services/IncidentEpisodeMemberService.ts +8 -4
  20. package/Server/Services/IncidentEpisodePublicNoteService.ts +9 -6
  21. package/Server/Services/IncidentEpisodeService.ts +65 -117
  22. package/Server/Services/IncidentEpisodeStateTimelineService.ts +4 -2
  23. package/Server/Services/IncidentInternalNoteService.ts +10 -5
  24. package/Server/Services/IncidentMemberService.ts +20 -10
  25. package/Server/Services/IncidentOwnerTeamService.ts +20 -10
  26. package/Server/Services/IncidentOwnerUserService.ts +20 -10
  27. package/Server/Services/IncidentPublicNoteService.ts +10 -5
  28. package/Server/Services/IncidentService.ts +34 -110
  29. package/Server/Services/IncidentStateTimelineService.ts +11 -6
  30. package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +61 -39
  31. package/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +31 -19
  32. package/Server/Services/ProjectService.ts +227 -0
  33. package/Server/Services/ScheduledMaintenanceInternalNoteService.ts +9 -6
  34. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +9 -6
  35. package/Server/Services/ScheduledMaintenanceService.ts +27 -39
  36. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +8 -6
  37. package/Server/Services/UserNotificationRuleService.ts +32 -21
  38. package/Server/Utils/AI/IncidentEpisodeAIContextBuilder.ts +4 -2
  39. package/Server/Utils/Browser.ts +28 -20
  40. package/Server/Utils/Monitor/MonitorAlert.ts +5 -0
  41. package/Server/Utils/Monitor/MonitorIncident.ts +7 -0
  42. package/Server/Utils/PushNotificationUtil.ts +69 -26
  43. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +8 -4
  44. package/Server/Utils/Workspace/Slack/Actions/Alert.ts +20 -8
  45. package/Server/Utils/Workspace/Slack/Actions/Incident.ts +42 -22
  46. package/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.ts +23 -17
  47. package/Server/Utils/Workspace/WorkspaceMessages/Alert.ts +1 -0
  48. package/Server/Utils/Workspace/WorkspaceMessages/Incident.ts +1 -0
  49. package/Server/Utils/Workspace/WorkspaceMessages/ScheduledMaintenance.ts +1 -0
  50. package/Types/Monitor/MonitorEvaluationSummary.ts +2 -0
  51. package/Utils/Analytics.ts +11 -0
  52. package/build/dist/Models/DatabaseModels/Alert.js +29 -0
  53. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  54. package/build/dist/Models/DatabaseModels/AlertEpisode.js +28 -0
  55. package/build/dist/Models/DatabaseModels/AlertEpisode.js.map +1 -1
  56. package/build/dist/Models/DatabaseModels/Incident.js +29 -0
  57. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  58. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +28 -0
  59. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  60. package/build/dist/Models/DatabaseModels/Project.js +265 -0
  61. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  62. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +28 -0
  63. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  64. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770237245070-MigrationName.js +27 -0
  65. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770237245070-MigrationName.js.map +1 -0
  66. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770407024682-MigrationName.js +34 -0
  67. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770407024682-MigrationName.js.map +1 -0
  68. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  69. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  70. package/build/dist/Server/Services/AlertEpisodeMemberService.js +6 -3
  71. package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -1
  72. package/build/dist/Server/Services/AlertEpisodeService.js +33 -90
  73. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  74. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +3 -2
  75. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -1
  76. package/build/dist/Server/Services/AlertInternalNoteService.js +2 -2
  77. package/build/dist/Server/Services/AlertInternalNoteService.js.map +1 -1
  78. package/build/dist/Server/Services/AlertOwnerTeamService.js +4 -4
  79. package/build/dist/Server/Services/AlertOwnerTeamService.js.map +1 -1
  80. package/build/dist/Server/Services/AlertOwnerUserService.js +4 -4
  81. package/build/dist/Server/Services/AlertOwnerUserService.js.map +1 -1
  82. package/build/dist/Server/Services/AlertService.js +16 -34
  83. package/build/dist/Server/Services/AlertService.js.map +1 -1
  84. package/build/dist/Server/Services/AlertStateTimelineService.js +3 -3
  85. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  86. package/build/dist/Server/Services/DatabaseService.js +9 -0
  87. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  88. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +8 -4
  89. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  90. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +4 -3
  91. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  92. package/build/dist/Server/Services/IncidentEpisodeService.js +46 -103
  93. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  94. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +3 -2
  95. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  96. package/build/dist/Server/Services/IncidentInternalNoteService.js +4 -2
  97. package/build/dist/Server/Services/IncidentInternalNoteService.js.map +1 -1
  98. package/build/dist/Server/Services/IncidentMemberService.js +8 -4
  99. package/build/dist/Server/Services/IncidentMemberService.js.map +1 -1
  100. package/build/dist/Server/Services/IncidentOwnerTeamService.js +8 -4
  101. package/build/dist/Server/Services/IncidentOwnerTeamService.js.map +1 -1
  102. package/build/dist/Server/Services/IncidentOwnerUserService.js +8 -4
  103. package/build/dist/Server/Services/IncidentOwnerUserService.js.map +1 -1
  104. package/build/dist/Server/Services/IncidentPublicNoteService.js +4 -2
  105. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  106. package/build/dist/Server/Services/IncidentService.js +24 -94
  107. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  108. package/build/dist/Server/Services/IncidentStateTimelineService.js +5 -3
  109. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  110. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +20 -16
  111. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
  112. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js +10 -8
  113. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js.map +1 -1
  114. package/build/dist/Server/Services/ProjectService.js +207 -0
  115. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  116. package/build/dist/Server/Services/ScheduledMaintenanceInternalNoteService.js +4 -3
  117. package/build/dist/Server/Services/ScheduledMaintenanceInternalNoteService.js.map +1 -1
  118. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +4 -3
  119. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  120. package/build/dist/Server/Services/ScheduledMaintenanceService.js +16 -37
  121. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  122. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +3 -3
  123. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  124. package/build/dist/Server/Services/UserNotificationRuleService.js +33 -25
  125. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  126. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js +4 -2
  127. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js.map +1 -1
  128. package/build/dist/Server/Utils/Browser.js +19 -12
  129. package/build/dist/Server/Utils/Browser.js.map +1 -1
  130. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +4 -0
  131. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  132. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +4 -0
  133. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  134. package/build/dist/Server/Utils/PushNotificationUtil.js +36 -28
  135. package/build/dist/Server/Utils/PushNotificationUtil.js.map +1 -1
  136. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +8 -4
  137. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  138. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js +8 -8
  139. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js.map +1 -1
  140. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +18 -10
  141. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js.map +1 -1
  142. package/build/dist/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.js +8 -8
  143. package/build/dist/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.js.map +1 -1
  144. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Alert.js.map +1 -1
  145. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Incident.js.map +1 -1
  146. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/ScheduledMaintenance.js.map +1 -1
  147. package/build/dist/Utils/Analytics.js +5 -0
  148. package/build/dist/Utils/Analytics.js.map +1 -1
  149. package/package.json +1 -1
@@ -40,10 +40,10 @@ import NotificationRuleWorkspaceChannel from "../../Types/Workspace/Notification
40
40
  import WorkspaceType from "../../Types/Workspace/WorkspaceType";
41
41
  import Typeof from "../../Types/Typeof";
42
42
  import AlertService from "./AlertService";
43
- import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
44
43
  import OnCallDutyPolicyService from "./OnCallDutyPolicyService";
45
44
  import OnCallDutyPolicy from "../../Models/DatabaseModels/OnCallDutyPolicy";
46
45
  import UserNotificationEventType from "../../Types/UserNotification/UserNotificationEventType";
46
+ import ProjectService from "./ProjectService";
47
47
 
48
48
  export class Service extends DatabaseService<Model> {
49
49
  public constructor() {
@@ -53,32 +53,6 @@ export class Service extends DatabaseService<Model> {
53
53
  }
54
54
  }
55
55
 
56
- @CaptureSpan()
57
- public async getExistingEpisodeNumberForProject(data: {
58
- projectId: ObjectID;
59
- }): Promise<number> {
60
- const lastEpisode: Model | null = await this.findOneBy({
61
- query: {
62
- projectId: data.projectId,
63
- },
64
- select: {
65
- episodeNumber: true,
66
- },
67
- sort: {
68
- episodeNumber: SortOrder.Descending,
69
- },
70
- props: {
71
- isRoot: true,
72
- },
73
- });
74
-
75
- if (!lastEpisode) {
76
- return 0;
77
- }
78
-
79
- return lastEpisode.episodeNumber ? Number(lastEpisode.episodeNumber) : 0;
80
- }
81
-
82
56
  @CaptureSpan()
83
57
  protected override async onBeforeCreate(
84
58
  createBy: CreateBy<Model>,
@@ -90,83 +64,52 @@ export class Service extends DatabaseService<Model> {
90
64
  const projectId: ObjectID =
91
65
  createBy.props.tenantId || createBy.data.projectId!;
92
66
 
93
- let mutex: SemaphoreMutex | null = null;
94
-
95
- try {
96
- // Acquire mutex to prevent race conditions when generating episode numbers
97
- try {
98
- mutex = await Semaphore.lock({
99
- key: projectId.toString(),
100
- namespace: "AlertEpisode.create",
101
- });
102
- } catch (err) {
103
- logger.error(err);
104
- }
105
-
106
- // Get the created state for episodes
107
- const alertState: AlertState | null = await AlertStateService.findOneBy({
108
- query: {
109
- projectId: projectId,
110
- isCreatedState: true,
111
- },
112
- select: {
113
- _id: true,
114
- },
115
- props: {
116
- isRoot: true,
117
- },
118
- });
119
-
120
- if (!alertState || !alertState.id) {
121
- throw new BadDataException(
122
- "Created alert state not found for this project. Please add created alert state from settings.",
123
- );
124
- }
67
+ // Get the created state for episodes
68
+ const alertState: AlertState | null = await AlertStateService.findOneBy({
69
+ query: {
70
+ projectId: projectId,
71
+ isCreatedState: true,
72
+ },
73
+ select: {
74
+ _id: true,
75
+ },
76
+ props: {
77
+ isRoot: true,
78
+ },
79
+ });
125
80
 
126
- createBy.data.currentAlertStateId = alertState.id;
81
+ if (!alertState || !alertState.id) {
82
+ throw new BadDataException(
83
+ "Created alert state not found for this project. Please add created alert state from settings.",
84
+ );
85
+ }
127
86
 
128
- // Auto-generate episode number
129
- const episodeNumberForThisEpisode: number =
130
- (await this.getExistingEpisodeNumberForProject({
131
- projectId: projectId,
132
- })) + 1;
87
+ createBy.data.currentAlertStateId = alertState.id;
133
88
 
134
- createBy.data.episodeNumber = episodeNumberForThisEpisode;
89
+ // Auto-generate episode number
90
+ const episodeCounterResult: {
91
+ counter: number;
92
+ prefix: string | undefined;
93
+ } = await ProjectService.incrementAndGetAlertEpisodeCounter(projectId);
135
94
 
136
- // Set initial lastAlertAddedAt
137
- if (!createBy.data.lastAlertAddedAt) {
138
- createBy.data.lastAlertAddedAt = OneUptimeDate.getCurrentDate();
139
- }
95
+ createBy.data.episodeNumber = episodeCounterResult.counter;
96
+ createBy.data.episodeNumberWithPrefix = episodeCounterResult.prefix
97
+ ? `${episodeCounterResult.prefix}${episodeCounterResult.counter}`
98
+ : `#${episodeCounterResult.counter}`;
140
99
 
141
- return { createBy, carryForward: { mutex } };
142
- } catch (error) {
143
- // Release the mutex if it was acquired and an error occurred
144
- if (mutex) {
145
- try {
146
- await Semaphore.release(mutex);
147
- } catch (err) {
148
- logger.error(err);
149
- }
150
- }
151
- throw error;
100
+ // Set initial lastAlertAddedAt
101
+ if (!createBy.data.lastAlertAddedAt) {
102
+ createBy.data.lastAlertAddedAt = OneUptimeDate.getCurrentDate();
152
103
  }
104
+
105
+ return { createBy, carryForward: null };
153
106
  }
154
107
 
155
108
  @CaptureSpan()
156
109
  protected override async onCreateSuccess(
157
- onCreate: OnCreate<Model>,
110
+ _onCreate: OnCreate<Model>,
158
111
  createdItem: Model,
159
112
  ): Promise<Model> {
160
- // Release the mutex acquired in onBeforeCreate
161
- const mutex: SemaphoreMutex | null = onCreate.carryForward?.mutex || null;
162
- if (mutex) {
163
- try {
164
- await Semaphore.release(mutex);
165
- } catch (err) {
166
- logger.error(err);
167
- }
168
- }
169
-
170
113
  if (!createdItem.projectId) {
171
114
  throw new BadDataException("projectId is required");
172
115
  }
@@ -233,7 +176,7 @@ export class Service extends DatabaseService<Model> {
233
176
  return;
234
177
  }
235
178
 
236
- let feedInfoInMarkdown: string = `#### Episode ${episode.episodeNumber?.toString()} Created
179
+ let feedInfoInMarkdown: string = `#### Episode ${episode.episodeNumberWithPrefix || "#" + episode.episodeNumber?.toString()} Created
237
180
 
238
181
  **${episode.title || "No title provided."}**
239
182
 
@@ -897,13 +840,15 @@ export class Service extends DatabaseService<Model> {
897
840
  }
898
841
 
899
842
  @CaptureSpan()
900
- public async getEpisodeNumber(data: {
901
- episodeId: ObjectID;
902
- }): Promise<number | null> {
843
+ public async getEpisodeNumber(data: { episodeId: ObjectID }): Promise<{
844
+ number: number | null;
845
+ numberWithPrefix: string | null;
846
+ }> {
903
847
  const episode: Model | null = await this.findOneById({
904
848
  id: data.episodeId,
905
849
  select: {
906
850
  episodeNumber: true,
851
+ episodeNumberWithPrefix: true,
907
852
  },
908
853
  props: {
909
854
  isRoot: true,
@@ -914,7 +859,10 @@ export class Service extends DatabaseService<Model> {
914
859
  throw new BadDataException("Episode not found.");
915
860
  }
916
861
 
917
- return episode.episodeNumber ? Number(episode.episodeNumber) : null;
862
+ return {
863
+ number: episode.episodeNumber ? Number(episode.episodeNumber) : null,
864
+ numberWithPrefix: episode.episodeNumberWithPrefix || null,
865
+ };
918
866
  }
919
867
 
920
868
  @CaptureSpan()
@@ -350,13 +350,15 @@ export class Service extends DatabaseService<AlertEpisodeStateTimeline> {
350
350
  id: createdItem.alertEpisodeId,
351
351
  select: {
352
352
  episodeNumber: true,
353
+ episodeNumberWithPrefix: true,
353
354
  },
354
355
  props: {
355
356
  isRoot: true,
356
357
  },
357
358
  });
358
359
 
359
- const episodeNumber: number = episode?.episodeNumber || 0;
360
+ const episodeDisplayNumber: string =
361
+ episode?.episodeNumberWithPrefix || "#" + (episode?.episodeNumber || 0);
360
362
 
361
363
  await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
362
364
  alertEpisodeId: createdItem.alertEpisodeId!,
@@ -365,7 +367,7 @@ export class Service extends DatabaseService<AlertEpisodeStateTimeline> {
365
367
  displayColor: alertState?.color,
366
368
  feedInfoInMarkdown:
367
369
  stateEmoji +
368
- ` Changed **Episode ${episodeNumber} State** to **` +
370
+ ` Changed **Episode ${episodeDisplayNumber} State** to **` +
369
371
  stateName +
370
372
  "**",
371
373
  moreInformationInMarkdown: createdItem.rootCause
@@ -85,7 +85,10 @@ export class Service extends DatabaseService<Model> {
85
85
 
86
86
  const alertId: ObjectID = createdItem.alertId!;
87
87
 
88
- const alertNumber: number | null = await AlertService.getAlertNumber({
88
+ const alertNumberResult: {
89
+ number: number | null;
90
+ numberWithPrefix: string | null;
91
+ } = await AlertService.getAlertNumber({
89
92
  alertId: alertId,
90
93
  });
91
94
 
@@ -101,7 +104,7 @@ export class Service extends DatabaseService<Model> {
101
104
  displayColor: Blue500,
102
105
  userId: userId || undefined,
103
106
 
104
- feedInfoInMarkdown: `📄 posted **private note** for this [Alert ${alertNumber}](${(await AlertService.getAlertLinkInDashboard(createdItem.projectId!, alertId)).toString()}):
107
+ feedInfoInMarkdown: `📄 posted **private note** for this [Alert ${alertNumberResult.numberWithPrefix || "#" + alertNumberResult.number}](${(await AlertService.getAlertLinkInDashboard(createdItem.projectId!, alertId)).toString()}):
105
108
 
106
109
  ${(createdItem.note || "") + attachmentsMarkdown}
107
110
  `,
@@ -73,7 +73,10 @@ export class Service extends DatabaseService<Model> {
73
73
  });
74
74
 
75
75
  if (team && team.name) {
76
- const alertNumber: number | null = await AlertService.getAlertNumber({
76
+ const alertNumberResult: {
77
+ number: number | null;
78
+ numberWithPrefix: string | null;
79
+ } = await AlertService.getAlertNumber({
77
80
  alertId: alertId,
78
81
  });
79
82
  await AlertFeedService.createAlertFeedItem({
@@ -81,7 +84,7 @@ export class Service extends DatabaseService<Model> {
81
84
  projectId: projectId,
82
85
  alertFeedEventType: AlertFeedEventType.OwnerTeamRemoved,
83
86
  displayColor: Red500,
84
- feedInfoInMarkdown: `👨🏻‍👩🏻‍👦🏻 Removed team **${team.name}** from the [Alert ${alertNumber}](${(await AlertService.getAlertLinkInDashboard(projectId!, alertId!)).toString()}) as the owner.`,
87
+ feedInfoInMarkdown: `👨🏻‍👩🏻‍👦🏻 Removed team **${team.name}** from the [Alert ${alertNumberResult.numberWithPrefix || "#" + alertNumberResult.number}](${(await AlertService.getAlertLinkInDashboard(projectId!, alertId!)).toString()}) as the owner.`,
85
88
  userId: deleteByUserId || undefined,
86
89
  workspaceNotification: {
87
90
  sendWorkspaceNotification: true,
@@ -120,7 +123,10 @@ export class Service extends DatabaseService<Model> {
120
123
  });
121
124
 
122
125
  if (team && team.name) {
123
- const alertNumber: number | null = await AlertService.getAlertNumber({
126
+ const alertNumberResult: {
127
+ number: number | null;
128
+ numberWithPrefix: string | null;
129
+ } = await AlertService.getAlertNumber({
124
130
  alertId: alertId,
125
131
  });
126
132
 
@@ -129,7 +135,7 @@ export class Service extends DatabaseService<Model> {
129
135
  projectId: projectId,
130
136
  alertFeedEventType: AlertFeedEventType.OwnerTeamAdded,
131
137
  displayColor: Gray500,
132
- feedInfoInMarkdown: `👨🏻‍👩🏻‍👦🏻 Added team **${team.name}** to the [Alert ${alertNumber}](${(await AlertService.getAlertLinkInDashboard(projectId!, alertId!)).toString()}) as the owner.`,
138
+ feedInfoInMarkdown: `👨🏻‍👩🏻‍👦🏻 Added team **${team.name}** to the [Alert ${alertNumberResult.numberWithPrefix || "#" + alertNumberResult.number}](${(await AlertService.getAlertLinkInDashboard(projectId!, alertId!)).toString()}) as the owner.`,
133
139
  userId: createdByUserId || undefined,
134
140
  workspaceNotification: {
135
141
  sendWorkspaceNotification: true,
@@ -74,7 +74,10 @@ export class Service extends DatabaseService<Model> {
74
74
  });
75
75
 
76
76
  if (user && user.name) {
77
- const alertNumber: number | null = await AlertService.getAlertNumber({
77
+ const alertNumberResult: {
78
+ number: number | null;
79
+ numberWithPrefix: string | null;
80
+ } = await AlertService.getAlertNumber({
78
81
  alertId: alertId,
79
82
  });
80
83
 
@@ -83,7 +86,7 @@ export class Service extends DatabaseService<Model> {
83
86
  projectId: projectId,
84
87
  alertFeedEventType: AlertFeedEventType.OwnerUserRemoved,
85
88
  displayColor: Red500,
86
- feedInfoInMarkdown: `👨🏻‍💻 Removed **${user.name.toString()}** (${user.email?.toString()}) from the [Alert ${alertNumber}](${(await AlertService.getAlertLinkInDashboard(projectId!, alertId!)).toString()}) as the owner.`,
89
+ feedInfoInMarkdown: `👨🏻‍💻 Removed **${user.name.toString()}** (${user.email?.toString()}) from the [Alert ${alertNumberResult.numberWithPrefix || "#" + alertNumberResult.number}](${(await AlertService.getAlertLinkInDashboard(projectId!, alertId!)).toString()}) as the owner.`,
87
90
  userId: deleteByUserId || undefined,
88
91
  workspaceNotification: {
89
92
  sendWorkspaceNotification: true,
@@ -123,7 +126,10 @@ export class Service extends DatabaseService<Model> {
123
126
  });
124
127
 
125
128
  if (user && user.name) {
126
- const alertNumber: number | null = await AlertService.getAlertNumber({
129
+ const alertNumberResult: {
130
+ number: number | null;
131
+ numberWithPrefix: string | null;
132
+ } = await AlertService.getAlertNumber({
127
133
  alertId: alertId,
128
134
  });
129
135
  await AlertFeedService.createAlertFeedItem({
@@ -136,7 +142,7 @@ export class Service extends DatabaseService<Model> {
136
142
  userId: userId,
137
143
  projectId: projectId,
138
144
  },
139
- )}** to the [Alert ${alertNumber}](${(await AlertService.getAlertLinkInDashboard(projectId!, alertId!)).toString()}) as the owner.`,
145
+ )}** to the [Alert ${alertNumberResult.numberWithPrefix || "#" + alertNumberResult.number}](${(await AlertService.getAlertLinkInDashboard(projectId!, alertId!)).toString()}) as the owner.`,
140
146
  userId: createdByUserId || undefined,
141
147
  workspaceNotification: {
142
148
  sendWorkspaceNotification: true,
@@ -55,6 +55,7 @@ import MetricType from "../../Models/DatabaseModels/MetricType";
55
55
  import Dictionary from "../../Types/Dictionary";
56
56
  import OnCallDutyPolicy from "../../Models/DatabaseModels/OnCallDutyPolicy";
57
57
  import AlertGroupingEngineService from "./AlertGroupingEngineService";
58
+ import ProjectService from "./ProjectService";
58
59
 
59
60
  export class Service extends DatabaseService<Model> {
60
61
  public constructor() {
@@ -109,33 +110,6 @@ export class Service extends DatabaseService<Model> {
109
110
  return false;
110
111
  }
111
112
 
112
- @CaptureSpan()
113
- public async getExistingAlertNumberForProject(data: {
114
- projectId: ObjectID;
115
- }): Promise<number> {
116
- // get last alert number.
117
- const lastAlert: Model | null = await this.findOneBy({
118
- query: {
119
- projectId: data.projectId,
120
- },
121
- select: {
122
- alertNumber: true,
123
- },
124
- sort: {
125
- createdAt: SortOrder.Descending,
126
- },
127
- props: {
128
- isRoot: true,
129
- },
130
- });
131
-
132
- if (!lastAlert) {
133
- return 0;
134
- }
135
-
136
- return lastAlert.alertNumber ? Number(lastAlert.alertNumber) : 0;
137
- }
138
-
139
113
  @CaptureSpan()
140
114
  public async acknowledgeAlert(
141
115
  alertId: ObjectID,
@@ -220,12 +194,15 @@ export class Service extends DatabaseService<Model> {
220
194
 
221
195
  createBy.data.currentAlertStateId = alertState.id;
222
196
 
223
- const alertNumberForThisAlert: number =
224
- (await this.getExistingAlertNumberForProject({
225
- projectId: projectId,
226
- })) + 1;
197
+ const alertCounterResult: {
198
+ counter: number;
199
+ prefix: string | undefined;
200
+ } = await ProjectService.incrementAndGetAlertCounter(projectId);
227
201
 
228
- createBy.data.alertNumber = alertNumberForThisAlert;
202
+ createBy.data.alertNumber = alertCounterResult.counter;
203
+ createBy.data.alertNumberWithPrefix = alertCounterResult.prefix
204
+ ? `${alertCounterResult.prefix}${alertCounterResult.counter}`
205
+ : `#${alertCounterResult.counter}`;
229
206
 
230
207
  if (
231
208
  (createBy.data.createdByUserId ||
@@ -424,6 +401,7 @@ export class Service extends DatabaseService<Model> {
424
401
  select: {
425
402
  projectId: true,
426
403
  alertNumber: true,
404
+ alertNumberWithPrefix: true,
427
405
  title: true,
428
406
  description: true,
429
407
  alertSeverity: {
@@ -460,7 +438,7 @@ export class Service extends DatabaseService<Model> {
460
438
  const createdByUserId: ObjectID | undefined | null =
461
439
  alert.createdByUserId || alert.createdByUser?.id;
462
440
 
463
- let feedInfoInMarkdown: string = `#### 🚨 Alert ${alert.alertNumber?.toString()} Created:
441
+ let feedInfoInMarkdown: string = `#### 🚨 Alert ${alert.alertNumberWithPrefix || "#" + alert.alertNumber?.toString()} Created:
464
442
 
465
443
  **${alert.title || "No title provided."}**:
466
444
 
@@ -800,6 +778,7 @@ ${alert.remediationNotes || "No remediation notes provided."}
800
778
  select: {
801
779
  projectId: true,
802
780
  alertNumber: true,
781
+ alertNumberWithPrefix: true,
803
782
  },
804
783
  props: {
805
784
  isRoot: true,
@@ -808,8 +787,10 @@ ${alert.remediationNotes || "No remediation notes provided."}
808
787
 
809
788
  const projectId: ObjectID = alert!.projectId!;
810
789
  const alertNumber: number = alert!.alertNumber!;
790
+ const alertNumberWithPrefix: string | undefined =
791
+ alert!.alertNumberWithPrefix || undefined;
811
792
 
812
- let feedInfoInMarkdown: string = `**[Alert ${alertNumber}](${(await this.getAlertLinkInDashboard(projectId!, alertId!)).toString()}) was updated.**`;
793
+ let feedInfoInMarkdown: string = `**[Alert ${alertNumberWithPrefix || "#" + alertNumber}](${(await this.getAlertLinkInDashboard(projectId!, alertId!)).toString()}) was updated.**`;
813
794
 
814
795
  const createdByUserId: ObjectID | undefined | null =
815
796
  onUpdate.updateBy.props.userId;
@@ -1404,13 +1385,15 @@ ${alertSeverity.name}
1404
1385
  }
1405
1386
 
1406
1387
  @CaptureSpan()
1407
- public async getAlertNumber(data: {
1408
- alertId: ObjectID;
1409
- }): Promise<number | null> {
1388
+ public async getAlertNumber(data: { alertId: ObjectID }): Promise<{
1389
+ number: number | null;
1390
+ numberWithPrefix: string | null;
1391
+ }> {
1410
1392
  const alert: Model | null = await this.findOneById({
1411
1393
  id: data.alertId,
1412
1394
  select: {
1413
1395
  alertNumber: true,
1396
+ alertNumberWithPrefix: true,
1414
1397
  },
1415
1398
  props: {
1416
1399
  isRoot: true,
@@ -1421,7 +1404,10 @@ ${alertSeverity.name}
1421
1404
  throw new BadDataException("Alert not found.");
1422
1405
  }
1423
1406
 
1424
- return alert.alertNumber ? Number(alert.alertNumber) : null;
1407
+ return {
1408
+ number: alert.alertNumber ? Number(alert.alertNumber) : null,
1409
+ numberWithPrefix: alert.alertNumberWithPrefix || null,
1410
+ };
1425
1411
  }
1426
1412
 
1427
1413
  @CaptureSpan()
@@ -400,7 +400,10 @@ export class Service extends DatabaseService<AlertStateTimeline> {
400
400
  stateEmoji = "🔴";
401
401
  }
402
402
 
403
- const alertNumber: number | null = await AlertService.getAlertNumber({
403
+ const alertNumberResult: {
404
+ number: number | null;
405
+ numberWithPrefix: string | null;
406
+ } = await AlertService.getAlertNumber({
404
407
  alertId: createdItem.alertId,
405
408
  });
406
409
 
@@ -414,7 +417,7 @@ export class Service extends DatabaseService<AlertStateTimeline> {
414
417
  displayColor: alertState?.color,
415
418
  feedInfoInMarkdown:
416
419
  stateEmoji +
417
- ` Changed **[Alert ${alertNumber}](${(await AlertService.getAlertLinkInDashboard(projectId!, alertId!)).toString()}) State** to **` +
420
+ ` Changed **[Alert ${alertNumberResult.numberWithPrefix || "#" + alertNumberResult.number}](${(await AlertService.getAlertLinkInDashboard(projectId!, alertId!)).toString()}) State** to **` +
418
421
  stateName +
419
422
  "**",
420
423
  moreInformationInMarkdown: `**Cause:**
@@ -464,7 +467,7 @@ ${createdItem.rootCause}`,
464
467
  },
465
468
  sendMessageBeforeArchiving: {
466
469
  _type: "WorkspacePayloadMarkdown",
467
- text: `**[Alert ${alertNumber}](${(
470
+ text: `**[Alert ${alertNumberResult.numberWithPrefix || "#" + alertNumberResult.number}](${(
468
471
  await AlertService.getAlertLinkInDashboard(
469
472
  createdItem.projectId!,
470
473
  createdItem.alertId!,
@@ -1679,6 +1679,18 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
1679
1679
  });
1680
1680
  }
1681
1681
 
1682
+ @CaptureSpan()
1683
+ protected async atomicIncrementColumnValueByOne(data: {
1684
+ id: ObjectID;
1685
+ columnName: keyof TBaseModel;
1686
+ }): Promise<void> {
1687
+ await this.getRepository().increment(
1688
+ { _id: data.id.toString() } as any,
1689
+ data.columnName as string,
1690
+ 1,
1691
+ );
1692
+ }
1693
+
1682
1694
  @CaptureSpan()
1683
1695
  public async searchBy({
1684
1696
  skip,
@@ -122,6 +122,7 @@ export class Service extends DatabaseService<Model> {
122
122
  id: createdItem.incidentId,
123
123
  select: {
124
124
  incidentNumber: true,
125
+ incidentNumberWithPrefix: true,
125
126
  title: true,
126
127
  },
127
128
  props: {
@@ -135,6 +136,7 @@ export class Service extends DatabaseService<Model> {
135
136
  id: createdItem.incidentEpisodeId,
136
137
  select: {
137
138
  episodeNumber: true,
139
+ episodeNumberWithPrefix: true,
138
140
  title: true,
139
141
  },
140
142
  props: {
@@ -148,7 +150,7 @@ export class Service extends DatabaseService<Model> {
148
150
  projectId: createdItem.projectId,
149
151
  incidentEpisodeFeedEventType: IncidentEpisodeFeedEventType.IncidentAdded,
150
152
  displayColor: Yellow500,
151
- feedInfoInMarkdown: `**Incident #${incident?.incidentNumber || "N/A"}** added to episode: ${incident?.title || "No title"}`,
153
+ feedInfoInMarkdown: `**Incident ${incident?.incidentNumberWithPrefix || "#" + (incident?.incidentNumber || "N/A")}** added to episode: ${incident?.title || "No title"}`,
152
154
  userId: createdItem.addedByUserId || undefined,
153
155
  });
154
156
 
@@ -158,7 +160,7 @@ export class Service extends DatabaseService<Model> {
158
160
  projectId: createdItem.projectId,
159
161
  incidentFeedEventType: IncidentFeedEventType.IncidentUpdated,
160
162
  displayColor: Yellow500,
161
- feedInfoInMarkdown: `Added to **Episode #${episode?.episodeNumber || "N/A"}**: ${episode?.title || "No title"}`,
163
+ feedInfoInMarkdown: `Added to **Episode ${episode?.episodeNumberWithPrefix || "#" + (episode?.episodeNumber || "N/A")}**: ${episode?.title || "No title"}`,
162
164
  userId: createdItem.addedByUserId || undefined,
163
165
  });
164
166
 
@@ -216,6 +218,7 @@ export class Service extends DatabaseService<Model> {
216
218
  id: member.incidentId,
217
219
  select: {
218
220
  incidentNumber: true,
221
+ incidentNumberWithPrefix: true,
219
222
  title: true,
220
223
  },
221
224
  props: {
@@ -231,6 +234,7 @@ export class Service extends DatabaseService<Model> {
231
234
  id: member.incidentEpisodeId,
232
235
  select: {
233
236
  episodeNumber: true,
237
+ episodeNumberWithPrefix: true,
234
238
  title: true,
235
239
  },
236
240
  props: {
@@ -245,7 +249,7 @@ export class Service extends DatabaseService<Model> {
245
249
  incidentEpisodeFeedEventType:
246
250
  IncidentEpisodeFeedEventType.IncidentRemoved,
247
251
  displayColor: Green500,
248
- feedInfoInMarkdown: `**Incident #${incident?.incidentNumber || "N/A"}** removed from episode: ${incident?.title || "No title"}`,
252
+ feedInfoInMarkdown: `**Incident ${incident?.incidentNumberWithPrefix || "#" + (incident?.incidentNumber || "N/A")}** removed from episode: ${incident?.title || "No title"}`,
249
253
  });
250
254
 
251
255
  // Create feed item on incident
@@ -254,7 +258,7 @@ export class Service extends DatabaseService<Model> {
254
258
  projectId: member.projectId,
255
259
  incidentFeedEventType: IncidentFeedEventType.IncidentUpdated,
256
260
  displayColor: Green500,
257
- feedInfoInMarkdown: `Removed from **Episode #${episode?.episodeNumber || "N/A"}**: ${episode?.title || "No title"}`,
261
+ feedInfoInMarkdown: `Removed from **Episode ${episode?.episodeNumberWithPrefix || "#" + (episode?.episodeNumber || "N/A")}**: ${episode?.title || "No title"}`,
258
262
  });
259
263
  }
260
264
  }
@@ -118,10 +118,12 @@ export class Service extends DatabaseService<Model> {
118
118
 
119
119
  const incidentEpisodeId: ObjectID = createdItem.incidentEpisodeId!;
120
120
  const projectId: ObjectID = createdItem.projectId!;
121
- const episodeNumber: number | null =
122
- await IncidentEpisodeService.getEpisodeNumber({
123
- episodeId: incidentEpisodeId,
124
- });
121
+ const episodeNumberResult: {
122
+ number: number | null;
123
+ numberWithPrefix: string | null;
124
+ } = await IncidentEpisodeService.getEpisodeNumber({
125
+ episodeId: incidentEpisodeId,
126
+ });
125
127
 
126
128
  const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
127
129
  createdItem.id!,
@@ -134,7 +136,7 @@ export class Service extends DatabaseService<Model> {
134
136
  incidentEpisodeFeedEventType: IncidentEpisodeFeedEventType.PublicNote,
135
137
  displayColor: Indigo500,
136
138
  userId: userId || undefined,
137
- feedInfoInMarkdown: `📄 posted **public note** for this [Episode ${episodeNumber}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(projectId!, incidentEpisodeId!)).toString()}) on status page:
139
+ feedInfoInMarkdown: `📄 posted **public note** for this [Episode ${episodeNumberResult.numberWithPrefix || "#" + episodeNumberResult.number}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(projectId!, incidentEpisodeId!)).toString()}) on status page:
138
140
 
139
141
  ${(createdItem.note || "") + attachmentsMarkdown}
140
142
  `,
@@ -162,6 +164,7 @@ ${(createdItem.note || "") + attachmentsMarkdown}
162
164
  incidentEpisode: {
163
165
  _id: true,
164
166
  episodeNumber: true,
167
+ episodeNumberWithPrefix: true,
165
168
  projectId: true,
166
169
  },
167
170
  note: true,
@@ -189,7 +192,7 @@ ${(createdItem.note || "") + attachmentsMarkdown}
189
192
  incidentEpisodeFeedEventType: IncidentEpisodeFeedEventType.PublicNote,
190
193
  displayColor: Blue500,
191
194
  userId: userId || undefined,
192
- feedInfoInMarkdown: `📄 updated **Public Note** for this [Episode ${episode.episodeNumber}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(episode.projectId!, episode.id!)).toString()})
195
+ feedInfoInMarkdown: `📄 updated **Public Note** for this [Episode ${episode.episodeNumberWithPrefix || "#" + episode.episodeNumber}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(episode.projectId!, episode.id!)).toString()})
193
196
 
194
197
  ${(updatedItem.note || "") + attachmentsMarkdown}
195
198
  `,