@oneuptime/common 9.5.7 → 9.5.9

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 (178) hide show
  1. package/Models/DatabaseModels/Alert.ts +8 -9
  2. package/Models/DatabaseModels/Incident.ts +5 -5
  3. package/Models/DatabaseModels/IncidentTemplate.ts +4 -3
  4. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -1
  5. package/Models/DatabaseModels/UserOnCallLog.ts +1 -1
  6. package/Models/DatabaseModels/UserPush.ts +2 -1
  7. package/Server/API/UserPushAPI.ts +51 -4
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1770833704656-MigrationName.ts +156 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1770834237090-MigrationName.ts +119 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  11. package/Server/Middleware/UserAuthorization.ts +14 -9
  12. package/Server/Services/AlertEpisodeFeedService.ts +50 -0
  13. package/Server/Services/AlertEpisodeInternalNoteService.ts +162 -0
  14. package/Server/Services/AlertEpisodeMemberService.ts +7 -0
  15. package/Server/Services/AlertEpisodeOwnerTeamService.ts +186 -0
  16. package/Server/Services/AlertEpisodeOwnerUserService.ts +180 -0
  17. package/Server/Services/AlertEpisodeService.ts +68 -0
  18. package/Server/Services/AlertEpisodeStateTimelineService.ts +5 -0
  19. package/Server/Services/AlertService.ts +3 -0
  20. package/Server/Services/IncidentEpisodeFeedService.ts +50 -0
  21. package/Server/Services/IncidentEpisodeInternalNoteService.ts +163 -0
  22. package/Server/Services/IncidentEpisodeMemberService.ts +7 -0
  23. package/Server/Services/IncidentEpisodeOwnerTeamService.ts +189 -0
  24. package/Server/Services/IncidentEpisodeOwnerUserService.ts +183 -0
  25. package/Server/Services/IncidentEpisodePublicNoteService.ts +8 -0
  26. package/Server/Services/IncidentEpisodeService.ts +91 -12
  27. package/Server/Services/IncidentEpisodeStateTimelineService.ts +5 -0
  28. package/Server/Services/IncidentService.ts +5 -0
  29. package/Server/Services/PushNotificationService.ts +129 -27
  30. package/Server/Services/UserNotificationRuleService.ts +13 -3
  31. package/Server/Services/UserPushService.ts +2 -1
  32. package/Server/Services/WorkspaceNotificationRuleService.ts +20 -0
  33. package/Server/Utils/PushNotificationUtil.ts +56 -0
  34. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +1 -1
  35. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +7 -6
  36. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +1 -1
  37. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +7 -6
  38. package/Server/Utils/Workspace/Slack/Actions/Alert.ts +17 -0
  39. package/Server/Utils/Workspace/Slack/Actions/AlertEpisode.ts +27 -12
  40. package/Server/Utils/Workspace/Slack/Actions/Incident.ts +17 -0
  41. package/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.ts +86 -28
  42. package/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.ts +6 -6
  43. package/Server/Utils/Workspace/Slack/Slack.ts +49 -0
  44. package/Server/Utils/Workspace/WorkspaceMessages/Alert.ts +2 -1
  45. package/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.ts +3 -1
  46. package/Server/Utils/Workspace/WorkspaceMessages/Incident.ts +2 -1
  47. package/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.ts +3 -1
  48. package/Types/Permission.ts +641 -0
  49. package/Types/PushNotification/PushDeviceType.ts +7 -0
  50. package/Types/PushNotification/PushNotificationRequest.ts +3 -1
  51. package/UI/Components/Detail/Detail.tsx +13 -4
  52. package/UI/Components/Detail/Field.ts +2 -2
  53. package/UI/Components/Dropdown/Dropdown.tsx +38 -7
  54. package/UI/Components/Forms/BasicForm.tsx +35 -5
  55. package/UI/Components/Forms/Fields/PermissionPicker.tsx +261 -0
  56. package/UI/Components/Forms/Types/Field.ts +5 -3
  57. package/UI/Components/ModelDelete/ModelDelete.tsx +4 -1
  58. package/UI/Components/ModelDetail/CardModelDetail.tsx +4 -0
  59. package/UI/Components/ModelDetail/ModelDetail.tsx +4 -1
  60. package/UI/Components/Page/ModelPage.tsx +4 -1
  61. package/UI/Utils/Permission.ts +29 -6
  62. package/build/dist/Models/DatabaseModels/Alert.js +8 -8
  63. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  64. package/build/dist/Models/DatabaseModels/Incident.js +5 -5
  65. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  66. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +3 -3
  67. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  68. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -1
  69. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  70. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +1 -1
  71. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  72. package/build/dist/Models/DatabaseModels/UserPush.js +2 -1
  73. package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
  74. package/build/dist/Server/API/UserPushAPI.js +34 -3
  75. package/build/dist/Server/API/UserPushAPI.js.map +1 -1
  76. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770833704656-MigrationName.js +63 -0
  77. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770833704656-MigrationName.js.map +1 -0
  78. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770834237090-MigrationName.js +46 -0
  79. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770834237090-MigrationName.js.map +1 -0
  80. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  81. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  82. package/build/dist/Server/Middleware/UserAuthorization.js +10 -4
  83. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  84. package/build/dist/Server/Services/AlertEpisodeFeedService.js +33 -0
  85. package/build/dist/Server/Services/AlertEpisodeFeedService.js.map +1 -1
  86. package/build/dist/Server/Services/AlertEpisodeInternalNoteService.js +132 -0
  87. package/build/dist/Server/Services/AlertEpisodeInternalNoteService.js.map +1 -1
  88. package/build/dist/Server/Services/AlertEpisodeMemberService.js +7 -0
  89. package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -1
  90. package/build/dist/Server/Services/AlertEpisodeOwnerTeamService.js +163 -0
  91. package/build/dist/Server/Services/AlertEpisodeOwnerTeamService.js.map +1 -1
  92. package/build/dist/Server/Services/AlertEpisodeOwnerUserService.js +156 -0
  93. package/build/dist/Server/Services/AlertEpisodeOwnerUserService.js.map +1 -1
  94. package/build/dist/Server/Services/AlertEpisodeService.js +53 -0
  95. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  96. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +4 -0
  97. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -1
  98. package/build/dist/Server/Services/AlertService.js +3 -5
  99. package/build/dist/Server/Services/AlertService.js.map +1 -1
  100. package/build/dist/Server/Services/IncidentEpisodeFeedService.js +33 -0
  101. package/build/dist/Server/Services/IncidentEpisodeFeedService.js.map +1 -1
  102. package/build/dist/Server/Services/IncidentEpisodeInternalNoteService.js +132 -0
  103. package/build/dist/Server/Services/IncidentEpisodeInternalNoteService.js.map +1 -1
  104. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +7 -0
  105. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  106. package/build/dist/Server/Services/IncidentEpisodeOwnerTeamService.js +163 -0
  107. package/build/dist/Server/Services/IncidentEpisodeOwnerTeamService.js.map +1 -1
  108. package/build/dist/Server/Services/IncidentEpisodeOwnerUserService.js +156 -0
  109. package/build/dist/Server/Services/IncidentEpisodeOwnerUserService.js.map +1 -1
  110. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +8 -0
  111. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  112. package/build/dist/Server/Services/IncidentEpisodeService.js +72 -10
  113. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  114. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +4 -0
  115. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  116. package/build/dist/Server/Services/IncidentService.js +5 -5
  117. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  118. package/build/dist/Server/Services/PushNotificationService.js +77 -21
  119. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  120. package/build/dist/Server/Services/UserNotificationRuleService.js +12 -9
  121. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  122. package/build/dist/Server/Services/UserPushService.js +2 -1
  123. package/build/dist/Server/Services/UserPushService.js.map +1 -1
  124. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +16 -0
  125. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  126. package/build/dist/Server/Utils/PushNotificationUtil.js +32 -8
  127. package/build/dist/Server/Utils/PushNotificationUtil.js.map +1 -1
  128. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +1 -1
  129. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  130. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +7 -6
  131. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  132. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +1 -1
  133. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  134. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +7 -6
  135. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  136. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js +16 -0
  137. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js.map +1 -1
  138. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js +25 -9
  139. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js.map +1 -1
  140. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +16 -0
  141. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js.map +1 -1
  142. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js +71 -25
  143. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js.map +1 -1
  144. package/build/dist/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.js +6 -6
  145. package/build/dist/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.js.map +1 -1
  146. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +40 -0
  147. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  148. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Alert.js +1 -1
  149. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Alert.js.map +1 -1
  150. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.js +1 -1
  151. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.js.map +1 -1
  152. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Incident.js +1 -1
  153. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Incident.js.map +1 -1
  154. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.js +1 -1
  155. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.js.map +1 -1
  156. package/build/dist/Types/Permission.js +637 -0
  157. package/build/dist/Types/Permission.js.map +1 -1
  158. package/build/dist/Types/PushNotification/PushDeviceType.js +8 -0
  159. package/build/dist/Types/PushNotification/PushDeviceType.js.map +1 -0
  160. package/build/dist/UI/Components/Detail/Detail.js +7 -1
  161. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  162. package/build/dist/UI/Components/Dropdown/Dropdown.js +17 -2
  163. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  164. package/build/dist/UI/Components/Forms/BasicForm.js +17 -3
  165. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  166. package/build/dist/UI/Components/Forms/Fields/PermissionPicker.js +129 -0
  167. package/build/dist/UI/Components/Forms/Fields/PermissionPicker.js.map +1 -0
  168. package/build/dist/UI/Components/ModelDelete/ModelDelete.js +2 -1
  169. package/build/dist/UI/Components/ModelDelete/ModelDelete.js.map +1 -1
  170. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
  171. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  172. package/build/dist/UI/Components/ModelDetail/ModelDetail.js +2 -1
  173. package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
  174. package/build/dist/UI/Components/Page/ModelPage.js +2 -1
  175. package/build/dist/UI/Components/Page/ModelPage.js.map +1 -1
  176. package/build/dist/UI/Utils/Permission.js +17 -4
  177. package/build/dist/UI/Utils/Permission.js.map +1 -1
  178. package/package.json +2 -1
@@ -1,8 +1,16 @@
1
1
  import ObjectID from "../../Types/ObjectID";
2
2
  import DatabaseService from "./DatabaseService";
3
3
  import Model from "../../Models/DatabaseModels/AlertEpisodeInternalNote";
4
+ import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
5
+ import AlertEpisodeFeedService from "./AlertEpisodeFeedService";
6
+ import { AlertEpisodeFeedEventType } from "../../Models/DatabaseModels/AlertEpisodeFeed";
7
+ import { Blue500 } from "../../Types/BrandColors";
8
+ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
9
+ import AlertEpisodeService from "./AlertEpisodeService";
10
+ import AlertEpisode from "../../Models/DatabaseModels/AlertEpisode";
4
11
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
5
12
  import File from "../../Models/DatabaseModels/File";
13
+ import FileAttachmentMarkdownUtil from "../Utils/FileAttachmentMarkdownUtil";
6
14
 
7
15
  export class Service extends DatabaseService<Model> {
8
16
  public constructor() {
@@ -66,6 +74,160 @@ export class Service extends DatabaseService<Model> {
66
74
 
67
75
  return existingNote !== null;
68
76
  }
77
+
78
+ @CaptureSpan()
79
+ public override async onCreateSuccess(
80
+ _onCreate: OnCreate<Model>,
81
+ createdItem: Model,
82
+ ): Promise<Model> {
83
+ const userId: ObjectID | null | undefined =
84
+ createdItem.createdByUserId || createdItem.createdByUser?.id;
85
+
86
+ const alertEpisodeId: ObjectID = createdItem.alertEpisodeId!;
87
+
88
+ const episodeNumberResult: {
89
+ number: number | null;
90
+ numberWithPrefix: string | null;
91
+ } = await AlertEpisodeService.getEpisodeNumber({
92
+ episodeId: alertEpisodeId,
93
+ });
94
+ const episodeNumberDisplay: string =
95
+ episodeNumberResult.numberWithPrefix || "#" + episodeNumberResult.number;
96
+
97
+ const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
98
+ createdItem.id!,
99
+ "/alert-episode-internal-note/attachment",
100
+ );
101
+
102
+ await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
103
+ alertEpisodeId: createdItem.alertEpisodeId!,
104
+ projectId: createdItem.projectId!,
105
+ alertEpisodeFeedEventType: AlertEpisodeFeedEventType.PrivateNote,
106
+ displayColor: Blue500,
107
+ userId: userId || undefined,
108
+ feedInfoInMarkdown: `📄 posted **private note** for this [Episode ${episodeNumberDisplay}](${(await AlertEpisodeService.getEpisodeLinkInDashboard(createdItem.projectId!, alertEpisodeId)).toString()}):
109
+
110
+ ${(createdItem.note || "") + attachmentsMarkdown}
111
+ `,
112
+ workspaceNotification: {
113
+ sendWorkspaceNotification: true,
114
+ notifyUserId: userId || undefined,
115
+ },
116
+ });
117
+
118
+ return createdItem;
119
+ }
120
+
121
+ @CaptureSpan()
122
+ public override async onUpdateSuccess(
123
+ onUpdate: OnUpdate<Model>,
124
+ _updatedItemIds: Array<ObjectID>,
125
+ ): Promise<OnUpdate<Model>> {
126
+ if (onUpdate.updateBy.data.note) {
127
+ const updatedItems: Array<Model> = await this.findBy({
128
+ query: onUpdate.updateBy.query,
129
+ limit: LIMIT_PER_PROJECT,
130
+ skip: 0,
131
+ props: {
132
+ isRoot: true,
133
+ },
134
+ select: {
135
+ alertEpisodeId: true,
136
+ alertEpisode: {
137
+ projectId: true,
138
+ episodeNumber: true,
139
+ episodeNumberWithPrefix: true,
140
+ },
141
+ projectId: true,
142
+ note: true,
143
+ createdByUserId: true,
144
+ createdByUser: {
145
+ _id: true,
146
+ },
147
+ },
148
+ });
149
+
150
+ const userId: ObjectID | null | undefined =
151
+ onUpdate.updateBy.props.userId;
152
+
153
+ for (const updatedItem of updatedItems) {
154
+ const episode: AlertEpisode = updatedItem.alertEpisode!;
155
+
156
+ const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
157
+ updatedItem.id!,
158
+ "/alert-episode-internal-note/attachment",
159
+ );
160
+
161
+ await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
162
+ alertEpisodeId: updatedItem.alertEpisodeId!,
163
+ projectId: updatedItem.projectId!,
164
+ alertEpisodeFeedEventType: AlertEpisodeFeedEventType.PrivateNote,
165
+ displayColor: Blue500,
166
+ userId: userId || undefined,
167
+ feedInfoInMarkdown: `📄 updated **Private Note** for this [Episode ${episode.episodeNumberWithPrefix || "#" + episode.episodeNumber}](${(await AlertEpisodeService.getEpisodeLinkInDashboard(episode.projectId!, episode.id!)).toString()})
168
+
169
+ ${(updatedItem.note || "") + attachmentsMarkdown}
170
+ `,
171
+ workspaceNotification: {
172
+ sendWorkspaceNotification: true,
173
+ notifyUserId: userId || undefined,
174
+ },
175
+ });
176
+ }
177
+ }
178
+ return onUpdate;
179
+ }
180
+
181
+ private async getAttachmentsMarkdown(
182
+ modelId: ObjectID,
183
+ attachmentApiPath: string,
184
+ ): Promise<string> {
185
+ if (!modelId) {
186
+ return "";
187
+ }
188
+
189
+ const noteWithAttachments: Model | null = await this.findOneById({
190
+ id: modelId,
191
+ select: {
192
+ attachments: {
193
+ _id: true,
194
+ },
195
+ },
196
+ props: {
197
+ isRoot: true,
198
+ },
199
+ });
200
+
201
+ if (!noteWithAttachments || !noteWithAttachments.attachments) {
202
+ return "";
203
+ }
204
+
205
+ const attachmentIds: Array<ObjectID> = noteWithAttachments.attachments
206
+ .map((file: File) => {
207
+ if (file.id) {
208
+ return file.id;
209
+ }
210
+
211
+ if (file._id) {
212
+ return new ObjectID(file._id);
213
+ }
214
+
215
+ return null;
216
+ })
217
+ .filter((id: ObjectID | null): id is ObjectID => {
218
+ return Boolean(id);
219
+ });
220
+
221
+ if (!attachmentIds.length) {
222
+ return "";
223
+ }
224
+
225
+ return await FileAttachmentMarkdownUtil.buildAttachmentMarkdown({
226
+ modelId,
227
+ attachmentIds,
228
+ attachmentApiPath,
229
+ });
230
+ }
69
231
  }
70
232
 
71
233
  export default new Service();
@@ -139,6 +139,10 @@ export class Service extends DatabaseService<Model> {
139
139
  displayColor: Yellow500,
140
140
  feedInfoInMarkdown: `**Alert ${alert?.alertNumberWithPrefix || "#" + (alert?.alertNumber || "N/A")}** added to episode: ${alert?.title || "No title"}`,
141
141
  userId: createdItem.addedByUserId || undefined,
142
+ workspaceNotification: {
143
+ sendWorkspaceNotification: true,
144
+ notifyUserId: createdItem.addedByUserId || undefined,
145
+ },
142
146
  });
143
147
 
144
148
  // Create feed item on alert
@@ -235,6 +239,9 @@ export class Service extends DatabaseService<Model> {
235
239
  alertEpisodeFeedEventType: AlertEpisodeFeedEventType.AlertRemoved,
236
240
  displayColor: Green500,
237
241
  feedInfoInMarkdown: `**Alert #${alert?.alertNumber || "N/A"}** removed from episode: ${alert?.title || "No title"}`,
242
+ workspaceNotification: {
243
+ sendWorkspaceNotification: true,
244
+ },
238
245
  });
239
246
 
240
247
  // Create feed item on alert
@@ -1,10 +1,196 @@
1
+ import ObjectID from "../../Types/ObjectID";
2
+ import { OnCreate, OnDelete } from "../Types/Database/Hooks";
1
3
  import DatabaseService from "./DatabaseService";
2
4
  import Model from "../../Models/DatabaseModels/AlertEpisodeOwnerTeam";
5
+ import AlertEpisodeFeedService from "./AlertEpisodeFeedService";
6
+ import { AlertEpisodeFeedEventType } from "../../Models/DatabaseModels/AlertEpisodeFeed";
7
+ import { Gray500, Red500 } from "../../Types/BrandColors";
8
+ import TeamService from "./TeamService";
9
+ import Team from "../../Models/DatabaseModels/Team";
10
+ import DeleteBy from "../Types/Database/DeleteBy";
11
+ import AlertEpisodeService from "./AlertEpisodeService";
12
+ import AlertEpisode from "../../Models/DatabaseModels/AlertEpisode";
13
+ import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
14
+ import NotificationRuleEventType from "../../Types/Workspace/NotificationRules/EventType";
15
+ import WorkspaceNotificationRule from "../../Models/DatabaseModels/WorkspaceNotificationRule";
16
+ import logger from "../Utils/Logger";
17
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
3
18
 
4
19
  export class Service extends DatabaseService<Model> {
5
20
  public constructor() {
6
21
  super(Model);
7
22
  }
23
+
24
+ @CaptureSpan()
25
+ protected override async onBeforeDelete(
26
+ deleteBy: DeleteBy<Model>,
27
+ ): Promise<OnDelete<Model>> {
28
+ const itemsToDelete: Model[] = await this.findBy({
29
+ query: deleteBy.query,
30
+ limit: deleteBy.limit,
31
+ skip: deleteBy.skip,
32
+ props: {
33
+ isRoot: true,
34
+ },
35
+ select: {
36
+ alertEpisodeId: true,
37
+ projectId: true,
38
+ teamId: true,
39
+ },
40
+ });
41
+
42
+ return {
43
+ carryForward: {
44
+ itemsToDelete: itemsToDelete,
45
+ },
46
+ deleteBy: deleteBy,
47
+ };
48
+ }
49
+
50
+ @CaptureSpan()
51
+ protected override async onDeleteSuccess(
52
+ onDelete: OnDelete<Model>,
53
+ _itemIdsBeforeDelete: Array<ObjectID>,
54
+ ): Promise<OnDelete<Model>> {
55
+ const deleteByUserId: ObjectID | undefined =
56
+ onDelete.deleteBy.deletedByUser?.id || onDelete.deleteBy.props.userId;
57
+
58
+ const itemsToDelete: Model[] = onDelete.carryForward.itemsToDelete;
59
+
60
+ for (const item of itemsToDelete) {
61
+ const alertEpisodeId: ObjectID | undefined = item.alertEpisodeId;
62
+ const projectId: ObjectID | undefined = item.projectId;
63
+ const teamId: ObjectID | undefined = item.teamId;
64
+
65
+ if (alertEpisodeId && teamId && projectId) {
66
+ const team: Team | null = await TeamService.findOneById({
67
+ id: teamId,
68
+ select: {
69
+ name: true,
70
+ },
71
+ props: {
72
+ isRoot: true,
73
+ },
74
+ });
75
+
76
+ const episodeNumberResult: {
77
+ number: number | null;
78
+ numberWithPrefix: string | null;
79
+ } = await AlertEpisodeService.getEpisodeNumber({
80
+ episodeId: alertEpisodeId,
81
+ });
82
+ const episodeNumberDisplay: string =
83
+ episodeNumberResult.numberWithPrefix ||
84
+ "#" + episodeNumberResult.number;
85
+
86
+ if (team && team.name) {
87
+ await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
88
+ alertEpisodeId: alertEpisodeId,
89
+ projectId: projectId,
90
+ alertEpisodeFeedEventType:
91
+ AlertEpisodeFeedEventType.OwnerTeamRemoved,
92
+ displayColor: Red500,
93
+ feedInfoInMarkdown: `👨🏻‍👩🏻‍👦🏻 Removed team **${team.name}** from the [Episode ${episodeNumberDisplay}](${(await AlertEpisodeService.getEpisodeLinkInDashboard(projectId!, alertEpisodeId!)).toString()}) as the owner.`,
94
+ userId: deleteByUserId || undefined,
95
+ workspaceNotification: {
96
+ sendWorkspaceNotification: true,
97
+ notifyUserId: deleteByUserId || undefined,
98
+ },
99
+ });
100
+ }
101
+ }
102
+ }
103
+
104
+ return onDelete;
105
+ }
106
+
107
+ @CaptureSpan()
108
+ public override async onCreateSuccess(
109
+ onCreate: OnCreate<Model>,
110
+ createdItem: Model,
111
+ ): Promise<Model> {
112
+ const alertEpisodeId: ObjectID | undefined = createdItem.alertEpisodeId;
113
+ const projectId: ObjectID | undefined = createdItem.projectId;
114
+ const teamId: ObjectID | undefined = createdItem.teamId;
115
+ const createdByUserId: ObjectID | undefined =
116
+ createdItem.createdByUserId || onCreate.createBy.props.userId;
117
+
118
+ if (alertEpisodeId && teamId && projectId) {
119
+ const team: Team | null = await TeamService.findOneById({
120
+ id: teamId,
121
+ select: {
122
+ name: true,
123
+ },
124
+ props: {
125
+ isRoot: true,
126
+ },
127
+ });
128
+
129
+ if (team && team.name) {
130
+ const episodeNumberResult: {
131
+ number: number | null;
132
+ numberWithPrefix: string | null;
133
+ } = await AlertEpisodeService.getEpisodeNumber({
134
+ episodeId: alertEpisodeId,
135
+ });
136
+ const episodeNumberDisplay: string =
137
+ episodeNumberResult.numberWithPrefix ||
138
+ "#" + episodeNumberResult.number;
139
+
140
+ await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
141
+ alertEpisodeId: alertEpisodeId,
142
+ projectId: projectId,
143
+ alertEpisodeFeedEventType: AlertEpisodeFeedEventType.OwnerTeamAdded,
144
+ displayColor: Gray500,
145
+ feedInfoInMarkdown: `👨🏻‍👩🏻‍👦🏻 Added team **${team.name}** to the [Episode ${episodeNumberDisplay}](${(await AlertEpisodeService.getEpisodeLinkInDashboard(projectId!, alertEpisodeId!)).toString()}) as the owner.`,
146
+ userId: createdByUserId || undefined,
147
+ workspaceNotification: {
148
+ sendWorkspaceNotification: true,
149
+ notifyUserId: createdByUserId || undefined,
150
+ },
151
+ });
152
+ }
153
+
154
+ // get notification rule where inviteOwners is true.
155
+ const notificationRules: Array<WorkspaceNotificationRule> =
156
+ await WorkspaceNotificationRuleService.getNotificationRulesWhereInviteOwnersIsTrue(
157
+ {
158
+ projectId: projectId,
159
+ notificationFor: {
160
+ alertEpisodeId: alertEpisodeId,
161
+ },
162
+ notificationRuleEventType: NotificationRuleEventType.AlertEpisode,
163
+ },
164
+ );
165
+
166
+ // Fetch episode to get workspace channels
167
+ const episode: AlertEpisode | null =
168
+ await AlertEpisodeService.findOneById({
169
+ id: alertEpisodeId,
170
+ select: {
171
+ postUpdatesToWorkspaceChannels: true,
172
+ },
173
+ props: {
174
+ isRoot: true,
175
+ },
176
+ });
177
+
178
+ if (episode) {
179
+ WorkspaceNotificationRuleService.inviteTeamsBasedOnRulesAndWorkspaceChannels(
180
+ {
181
+ notificationRules: notificationRules,
182
+ projectId: projectId,
183
+ workspaceChannels: episode.postUpdatesToWorkspaceChannels || [],
184
+ teamIds: [teamId],
185
+ },
186
+ ).catch((error: Error) => {
187
+ logger.error(error);
188
+ });
189
+ }
190
+ }
191
+
192
+ return createdItem;
193
+ }
8
194
  }
9
195
 
10
196
  export default new Service();
@@ -1,10 +1,190 @@
1
+ import ObjectID from "../../Types/ObjectID";
1
2
  import DatabaseService from "./DatabaseService";
2
3
  import Model from "../../Models/DatabaseModels/AlertEpisodeOwnerUser";
4
+ import AlertEpisodeFeedService from "./AlertEpisodeFeedService";
5
+ import { AlertEpisodeFeedEventType } from "../../Models/DatabaseModels/AlertEpisodeFeed";
6
+ import { Gray500, Red500 } from "../../Types/BrandColors";
7
+ import User from "../../Models/DatabaseModels/User";
8
+ import UserService from "./UserService";
9
+ import { OnCreate, OnDelete } from "../Types/Database/Hooks";
10
+ import DeleteBy from "../Types/Database/DeleteBy";
11
+ import AlertEpisodeService from "./AlertEpisodeService";
12
+ import AlertEpisode from "../../Models/DatabaseModels/AlertEpisode";
13
+ import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
14
+ import NotificationRuleEventType from "../../Types/Workspace/NotificationRules/EventType";
15
+ import WorkspaceNotificationRule from "../../Models/DatabaseModels/WorkspaceNotificationRule";
16
+ import logger from "../Utils/Logger";
17
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
3
18
 
4
19
  export class Service extends DatabaseService<Model> {
5
20
  public constructor() {
6
21
  super(Model);
7
22
  }
23
+
24
+ @CaptureSpan()
25
+ protected override async onBeforeDelete(
26
+ deleteBy: DeleteBy<Model>,
27
+ ): Promise<OnDelete<Model>> {
28
+ const itemsToDelete: Model[] = await this.findBy({
29
+ query: deleteBy.query,
30
+ limit: deleteBy.limit,
31
+ skip: deleteBy.skip,
32
+ props: {
33
+ isRoot: true,
34
+ },
35
+ select: {
36
+ alertEpisodeId: true,
37
+ projectId: true,
38
+ userId: true,
39
+ },
40
+ });
41
+
42
+ return {
43
+ carryForward: {
44
+ itemsToDelete: itemsToDelete,
45
+ },
46
+ deleteBy: deleteBy,
47
+ };
48
+ }
49
+
50
+ @CaptureSpan()
51
+ protected override async onDeleteSuccess(
52
+ onDelete: OnDelete<Model>,
53
+ _itemIdsBeforeDelete: Array<ObjectID>,
54
+ ): Promise<OnDelete<Model>> {
55
+ const deleteByUserId: ObjectID | undefined =
56
+ onDelete.deleteBy.deletedByUser?.id || onDelete.deleteBy.props.userId;
57
+
58
+ const itemsToDelete: Model[] = onDelete.carryForward.itemsToDelete;
59
+
60
+ for (const item of itemsToDelete) {
61
+ const alertEpisodeId: ObjectID | undefined = item.alertEpisodeId;
62
+ const projectId: ObjectID | undefined = item.projectId;
63
+ const userId: ObjectID | undefined = item.userId;
64
+
65
+ if (alertEpisodeId && userId && projectId) {
66
+ const user: User | null = await UserService.findOneById({
67
+ id: userId,
68
+ select: {
69
+ name: true,
70
+ email: true,
71
+ },
72
+ props: {
73
+ isRoot: true,
74
+ },
75
+ });
76
+
77
+ const episodeNumberResult: {
78
+ number: number | null;
79
+ numberWithPrefix: string | null;
80
+ } = await AlertEpisodeService.getEpisodeNumber({
81
+ episodeId: alertEpisodeId,
82
+ });
83
+ const episodeNumberDisplay: string =
84
+ episodeNumberResult.numberWithPrefix ||
85
+ "#" + episodeNumberResult.number;
86
+
87
+ if (user && user.name) {
88
+ await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
89
+ alertEpisodeId: alertEpisodeId,
90
+ projectId: projectId,
91
+ alertEpisodeFeedEventType:
92
+ AlertEpisodeFeedEventType.OwnerUserRemoved,
93
+ displayColor: Red500,
94
+ feedInfoInMarkdown: `👨🏻‍💻 Removed **${user.name.toString()}** (${user.email?.toString()}) from the [Episode ${episodeNumberDisplay}](${(await AlertEpisodeService.getEpisodeLinkInDashboard(projectId!, alertEpisodeId!)).toString()}) as the owner.`,
95
+ userId: deleteByUserId || undefined,
96
+ workspaceNotification: {
97
+ sendWorkspaceNotification: true,
98
+ notifyUserId: userId || undefined,
99
+ },
100
+ });
101
+ }
102
+ }
103
+ }
104
+
105
+ return onDelete;
106
+ }
107
+
108
+ @CaptureSpan()
109
+ public override async onCreateSuccess(
110
+ onCreate: OnCreate<Model>,
111
+ createdItem: Model,
112
+ ): Promise<Model> {
113
+ const alertEpisodeId: ObjectID | undefined = createdItem.alertEpisodeId;
114
+ const projectId: ObjectID | undefined = createdItem.projectId;
115
+ const userId: ObjectID | undefined = createdItem.userId;
116
+ const createdByUserId: ObjectID | undefined =
117
+ createdItem.createdByUserId || onCreate.createBy.props.userId;
118
+
119
+ if (alertEpisodeId && userId && projectId) {
120
+ const episodeNumberResult: {
121
+ number: number | null;
122
+ numberWithPrefix: string | null;
123
+ } = await AlertEpisodeService.getEpisodeNumber({
124
+ episodeId: alertEpisodeId,
125
+ });
126
+ const episodeNumberDisplay: string =
127
+ episodeNumberResult.numberWithPrefix ||
128
+ "#" + episodeNumberResult.number;
129
+
130
+ await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
131
+ alertEpisodeId: alertEpisodeId,
132
+ projectId: projectId,
133
+ alertEpisodeFeedEventType: AlertEpisodeFeedEventType.OwnerUserAdded,
134
+ displayColor: Gray500,
135
+ feedInfoInMarkdown: `👨🏻‍💻 Added **${await UserService.getUserMarkdownString(
136
+ {
137
+ userId: userId,
138
+ projectId: projectId,
139
+ },
140
+ )}** to the [Episode ${episodeNumberDisplay}](${(await AlertEpisodeService.getEpisodeLinkInDashboard(projectId!, alertEpisodeId!)).toString()}) as the owner.`,
141
+ userId: createdByUserId || undefined,
142
+ workspaceNotification: {
143
+ sendWorkspaceNotification: true,
144
+ notifyUserId: userId || undefined,
145
+ },
146
+ });
147
+
148
+ // get notification rule where inviteOwners is true.
149
+ const notificationRules: Array<WorkspaceNotificationRule> =
150
+ await WorkspaceNotificationRuleService.getNotificationRulesWhereInviteOwnersIsTrue(
151
+ {
152
+ projectId: projectId,
153
+ notificationFor: {
154
+ alertEpisodeId: alertEpisodeId,
155
+ },
156
+ notificationRuleEventType: NotificationRuleEventType.AlertEpisode,
157
+ },
158
+ );
159
+
160
+ // Fetch episode to get workspace channels
161
+ const episode: AlertEpisode | null =
162
+ await AlertEpisodeService.findOneById({
163
+ id: alertEpisodeId,
164
+ select: {
165
+ postUpdatesToWorkspaceChannels: true,
166
+ },
167
+ props: {
168
+ isRoot: true,
169
+ },
170
+ });
171
+
172
+ if (episode) {
173
+ WorkspaceNotificationRuleService.inviteUsersBasedOnRulesAndWorkspaceChannels(
174
+ {
175
+ notificationRules: notificationRules,
176
+ projectId: projectId,
177
+ workspaceChannels: episode.postUpdatesToWorkspaceChannels || [],
178
+ userIds: [userId],
179
+ },
180
+ ).catch((error: Error) => {
181
+ logger.error(error);
182
+ });
183
+ }
184
+ }
185
+
186
+ return createdItem;
187
+ }
8
188
  }
9
189
 
10
190
  export default new Service();
@@ -38,6 +38,8 @@ import User from "../../Models/DatabaseModels/User";
38
38
  import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
39
39
  import NotificationRuleWorkspaceChannel from "../../Types/Workspace/NotificationRules/NotificationRuleWorkspaceChannel";
40
40
  import WorkspaceType from "../../Types/Workspace/WorkspaceType";
41
+ import AlertEpisodeWorkspaceMessages from "../Utils/Workspace/WorkspaceMessages/AlertEpisode";
42
+ import { MessageBlocksByWorkspaceType } from "./WorkspaceNotificationRuleService";
41
43
  import Typeof from "../../Types/Typeof";
42
44
  import AlertService from "./AlertService";
43
45
  import OnCallDutyPolicyService from "./OnCallDutyPolicyService";
@@ -124,6 +126,17 @@ export class Service extends DatabaseService<Model> {
124
126
 
125
127
  // Create initial state timeline entry
126
128
  Promise.resolve()
129
+ .then(async () => {
130
+ try {
131
+ if (createdItem.projectId && createdItem.id) {
132
+ await this.handleEpisodeWorkspaceOperationsAsync(createdItem);
133
+ }
134
+ } catch (error) {
135
+ logger.error(
136
+ `Workspace operations failed in AlertEpisodeService.onCreateSuccess: ${error}`,
137
+ );
138
+ }
139
+ })
127
140
  .then(async () => {
128
141
  try {
129
142
  await this.changeEpisodeState({
@@ -170,6 +183,51 @@ export class Service extends DatabaseService<Model> {
170
183
  return createdItem;
171
184
  }
172
185
 
186
+ @CaptureSpan()
187
+ private async handleEpisodeWorkspaceOperationsAsync(
188
+ createdItem: Model,
189
+ ): Promise<void> {
190
+ try {
191
+ if (!createdItem.projectId || !createdItem.id) {
192
+ throw new BadDataException(
193
+ "projectId and id are required for workspace operations",
194
+ );
195
+ }
196
+
197
+ const workspaceResult: {
198
+ channelsCreated: Array<NotificationRuleWorkspaceChannel>;
199
+ } | null =
200
+ await AlertEpisodeWorkspaceMessages.createChannelsAndInviteUsersToChannels(
201
+ {
202
+ projectId: createdItem.projectId,
203
+ alertEpisodeId: createdItem.id,
204
+ episodeNumber: createdItem.episodeNumber || 0,
205
+ ...(createdItem.episodeNumberWithPrefix
206
+ ? {
207
+ episodeNumberWithPrefix: createdItem.episodeNumberWithPrefix,
208
+ }
209
+ : {}),
210
+ },
211
+ );
212
+
213
+ if (workspaceResult && workspaceResult.channelsCreated?.length > 0) {
214
+ await this.updateOneById({
215
+ id: createdItem.id,
216
+ data: {
217
+ postUpdatesToWorkspaceChannels:
218
+ workspaceResult.channelsCreated || [],
219
+ },
220
+ props: {
221
+ isRoot: true,
222
+ },
223
+ });
224
+ }
225
+ } catch (error) {
226
+ logger.error(`Error in handleEpisodeWorkspaceOperationsAsync: ${error}`);
227
+ throw error;
228
+ }
229
+ }
230
+
173
231
  @CaptureSpan()
174
232
  private async createEpisodeCreatedFeed(episode: Model): Promise<void> {
175
233
  if (!episode.id || !episode.projectId) {
@@ -190,6 +248,12 @@ export class Service extends DatabaseService<Model> {
190
248
  feedInfoInMarkdown += `This episode was manually created.\n\n`;
191
249
  }
192
250
 
251
+ const episodeCreateMessageBlocks: Array<MessageBlocksByWorkspaceType> =
252
+ await AlertEpisodeWorkspaceMessages.getAlertEpisodeCreateMessageBlocks({
253
+ alertEpisodeId: episode.id,
254
+ projectId: episode.projectId,
255
+ });
256
+
193
257
  await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
194
258
  alertEpisodeId: episode.id,
195
259
  projectId: episode.projectId,
@@ -197,6 +261,10 @@ export class Service extends DatabaseService<Model> {
197
261
  displayColor: Red500,
198
262
  feedInfoInMarkdown: feedInfoInMarkdown,
199
263
  userId: episode.createdByUserId || undefined,
264
+ workspaceNotification: {
265
+ appendMessageBlocks: episodeCreateMessageBlocks,
266
+ sendWorkspaceNotification: true,
267
+ },
200
268
  });
201
269
  }
202
270
 
@@ -374,6 +374,11 @@ export class Service extends DatabaseService<AlertEpisodeStateTimeline> {
374
374
  ? `**Cause:** \n${createdItem.rootCause}`
375
375
  : undefined,
376
376
  userId: createdItem.createdByUserId || onCreate.createBy.props.userId,
377
+ workspaceNotification: {
378
+ sendWorkspaceNotification: true,
379
+ notifyUserId:
380
+ createdItem.createdByUserId || onCreate.createBy.props.userId,
381
+ },
377
382
  });
378
383
 
379
384
  return createdItem;