@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
@@ -368,6 +368,9 @@ export class Service extends DatabaseService<Model> {
368
368
  projectId: createdItem.projectId,
369
369
  alertId: createdItem.id,
370
370
  alertNumber: createdItem.alertNumber!,
371
+ ...(createdItem.alertNumberWithPrefix
372
+ ? { alertNumberWithPrefix: createdItem.alertNumberWithPrefix }
373
+ : {}),
371
374
  });
372
375
 
373
376
  logger.debug("Alert created. Workspace result:");
@@ -9,6 +9,9 @@ import DatabaseService from "./DatabaseService";
9
9
  import Model, {
10
10
  IncidentEpisodeFeedEventType,
11
11
  } from "../../Models/DatabaseModels/IncidentEpisodeFeed";
12
+ import WorkspaceNotificationRuleService, {
13
+ MessageBlocksByWorkspaceType,
14
+ } from "./WorkspaceNotificationRuleService";
12
15
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
13
16
 
14
17
  export class Service extends DatabaseService<Model> {
@@ -30,6 +33,13 @@ export class Service extends DatabaseService<Model> {
30
33
  displayColor?: Color | undefined;
31
34
  userId?: ObjectID | undefined;
32
35
  postedAt?: Date | undefined;
36
+ workspaceNotification?:
37
+ | {
38
+ notifyUserId?: ObjectID | undefined;
39
+ sendWorkspaceNotification: boolean;
40
+ appendMessageBlocks?: Array<MessageBlocksByWorkspaceType> | undefined;
41
+ }
42
+ | undefined;
33
43
  }): Promise<void> {
34
44
  try {
35
45
  if (!data.incidentEpisodeId) {
@@ -83,12 +93,52 @@ export class Service extends DatabaseService<Model> {
83
93
  isRoot: true,
84
94
  },
85
95
  });
96
+
97
+ try {
98
+ if (
99
+ data.workspaceNotification &&
100
+ data.workspaceNotification?.sendWorkspaceNotification
101
+ ) {
102
+ await this.sendWorkspaceNotification({
103
+ projectId: data.projectId,
104
+ incidentEpisodeId: data.incidentEpisodeId,
105
+ feedInfoInMarkdown: data.feedInfoInMarkdown,
106
+ workspaceNotification: data.workspaceNotification,
107
+ });
108
+ }
109
+ } catch (e) {
110
+ logger.error("Error in sending notification to slack and teams");
111
+ logger.error(e);
112
+ }
86
113
  } catch (error) {
87
114
  logger.error("IncidentEpisodeFeedService.createIncidentEpisodeFeedItem");
88
115
  logger.error(error);
89
116
  // we dont want to throw the error here, as this is a non-critical operation
90
117
  }
91
118
  }
119
+
120
+ @CaptureSpan()
121
+ public async sendWorkspaceNotification(data: {
122
+ projectId: ObjectID;
123
+ incidentEpisodeId: ObjectID;
124
+ feedInfoInMarkdown: string;
125
+ workspaceNotification: {
126
+ notifyUserId?: ObjectID | undefined;
127
+ sendWorkspaceNotification: boolean;
128
+ appendMessageBlocks?: Array<MessageBlocksByWorkspaceType> | undefined;
129
+ };
130
+ }): Promise<void> {
131
+ return await WorkspaceNotificationRuleService.sendWorkspaceMarkdownNotification(
132
+ {
133
+ projectId: data.projectId,
134
+ notificationFor: {
135
+ incidentEpisodeId: data.incidentEpisodeId,
136
+ },
137
+ feedInfoInMarkdown: data.feedInfoInMarkdown,
138
+ workspaceNotification: data.workspaceNotification,
139
+ },
140
+ );
141
+ }
92
142
  }
93
143
 
94
144
  export default new Service();
@@ -1,8 +1,16 @@
1
1
  import ObjectID from "../../Types/ObjectID";
2
2
  import DatabaseService from "./DatabaseService";
3
3
  import Model from "../../Models/DatabaseModels/IncidentEpisodeInternalNote";
4
+ import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
5
+ import IncidentEpisodeFeedService from "./IncidentEpisodeFeedService";
6
+ import { IncidentEpisodeFeedEventType } from "../../Models/DatabaseModels/IncidentEpisodeFeed";
7
+ import { Blue500 } from "../../Types/BrandColors";
8
+ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
9
+ import IncidentEpisodeService from "./IncidentEpisodeService";
10
+ import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
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,161 @@ 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 incidentEpisodeId: ObjectID = createdItem.incidentEpisodeId!;
87
+
88
+ const episodeNumberResult: {
89
+ number: number | null;
90
+ numberWithPrefix: string | null;
91
+ } = await IncidentEpisodeService.getEpisodeNumber({
92
+ episodeId: incidentEpisodeId,
93
+ });
94
+ const episodeNumberDisplay: string =
95
+ episodeNumberResult.numberWithPrefix || "#" + episodeNumberResult.number;
96
+
97
+ const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
98
+ createdItem.id!,
99
+ "/incident-episode-internal-note/attachment",
100
+ );
101
+
102
+ await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
103
+ incidentEpisodeId: createdItem.incidentEpisodeId!,
104
+ projectId: createdItem.projectId!,
105
+ incidentEpisodeFeedEventType: IncidentEpisodeFeedEventType.PrivateNote,
106
+ displayColor: Blue500,
107
+ userId: userId || undefined,
108
+ feedInfoInMarkdown: `📄 posted **private note** for this [Episode ${episodeNumberDisplay}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(createdItem.projectId!, incidentEpisodeId)).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
+ incidentEpisodeId: true,
136
+ incidentEpisode: {
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: IncidentEpisode = updatedItem.incidentEpisode!;
155
+
156
+ const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
157
+ updatedItem.id!,
158
+ "/incident-episode-internal-note/attachment",
159
+ );
160
+
161
+ await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
162
+ incidentEpisodeId: updatedItem.incidentEpisodeId!,
163
+ projectId: updatedItem.projectId!,
164
+ incidentEpisodeFeedEventType:
165
+ IncidentEpisodeFeedEventType.PrivateNote,
166
+ displayColor: Blue500,
167
+ userId: userId || undefined,
168
+ feedInfoInMarkdown: `📄 updated **Private Note** for this [Episode ${episode.episodeNumberWithPrefix || "#" + episode.episodeNumber}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(episode.projectId!, episode.id!)).toString()})
169
+
170
+ ${(updatedItem.note || "") + attachmentsMarkdown}
171
+ `,
172
+ workspaceNotification: {
173
+ sendWorkspaceNotification: true,
174
+ notifyUserId: userId || undefined,
175
+ },
176
+ });
177
+ }
178
+ }
179
+ return onUpdate;
180
+ }
181
+
182
+ private async getAttachmentsMarkdown(
183
+ modelId: ObjectID,
184
+ attachmentApiPath: string,
185
+ ): Promise<string> {
186
+ if (!modelId) {
187
+ return "";
188
+ }
189
+
190
+ const noteWithAttachments: Model | null = await this.findOneById({
191
+ id: modelId,
192
+ select: {
193
+ attachments: {
194
+ _id: true,
195
+ },
196
+ },
197
+ props: {
198
+ isRoot: true,
199
+ },
200
+ });
201
+
202
+ if (!noteWithAttachments || !noteWithAttachments.attachments) {
203
+ return "";
204
+ }
205
+
206
+ const attachmentIds: Array<ObjectID> = noteWithAttachments.attachments
207
+ .map((file: File) => {
208
+ if (file.id) {
209
+ return file.id;
210
+ }
211
+
212
+ if (file._id) {
213
+ return new ObjectID(file._id);
214
+ }
215
+
216
+ return null;
217
+ })
218
+ .filter((id: ObjectID | null): id is ObjectID => {
219
+ return Boolean(id);
220
+ });
221
+
222
+ if (!attachmentIds.length) {
223
+ return "";
224
+ }
225
+
226
+ return await FileAttachmentMarkdownUtil.buildAttachmentMarkdown({
227
+ modelId,
228
+ attachmentIds,
229
+ attachmentApiPath,
230
+ });
231
+ }
69
232
  }
70
233
 
71
234
  export default new Service();
@@ -144,6 +144,10 @@ export class Service extends DatabaseService<Model> {
144
144
  displayColor: Yellow500,
145
145
  feedInfoInMarkdown: `**Incident ${incident?.incidentNumberWithPrefix || "#" + (incident?.incidentNumber || "N/A")}** added to episode: ${incident?.title || "No title"}`,
146
146
  userId: createdItem.addedByUserId || undefined,
147
+ workspaceNotification: {
148
+ sendWorkspaceNotification: true,
149
+ notifyUserId: createdItem.addedByUserId || undefined,
150
+ },
147
151
  });
148
152
 
149
153
  // Create feed item on incident
@@ -242,6 +246,9 @@ export class Service extends DatabaseService<Model> {
242
246
  IncidentEpisodeFeedEventType.IncidentRemoved,
243
247
  displayColor: Green500,
244
248
  feedInfoInMarkdown: `**Incident ${incident?.incidentNumberWithPrefix || "#" + (incident?.incidentNumber || "N/A")}** removed from episode: ${incident?.title || "No title"}`,
249
+ workspaceNotification: {
250
+ sendWorkspaceNotification: true,
251
+ },
245
252
  });
246
253
 
247
254
  // Create feed item on incident
@@ -1,10 +1,199 @@
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/IncidentEpisodeOwnerTeam";
5
+ import IncidentEpisodeFeedService from "./IncidentEpisodeFeedService";
6
+ import { IncidentEpisodeFeedEventType } from "../../Models/DatabaseModels/IncidentEpisodeFeed";
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 IncidentEpisodeService from "./IncidentEpisodeService";
12
+ import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
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
+ incidentEpisodeId: 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 incidentEpisodeId: ObjectID | undefined = item.incidentEpisodeId;
62
+ const projectId: ObjectID | undefined = item.projectId;
63
+ const teamId: ObjectID | undefined = item.teamId;
64
+
65
+ if (incidentEpisodeId && 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 IncidentEpisodeService.getEpisodeNumber({
80
+ episodeId: incidentEpisodeId,
81
+ });
82
+ const episodeNumberDisplay: string =
83
+ episodeNumberResult.numberWithPrefix ||
84
+ "#" + episodeNumberResult.number;
85
+
86
+ if (team && team.name) {
87
+ await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
88
+ incidentEpisodeId: incidentEpisodeId,
89
+ projectId: projectId,
90
+ incidentEpisodeFeedEventType:
91
+ IncidentEpisodeFeedEventType.OwnerTeamRemoved,
92
+ displayColor: Red500,
93
+ feedInfoInMarkdown: `👨🏻‍👩🏻‍👦🏻 Removed team **${team.name}** from the [Episode ${episodeNumberDisplay}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(projectId!, incidentEpisodeId!)).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 incidentEpisodeId: ObjectID | undefined =
113
+ createdItem.incidentEpisodeId;
114
+ const projectId: ObjectID | undefined = createdItem.projectId;
115
+ const teamId: ObjectID | undefined = createdItem.teamId;
116
+ const createdByUserId: ObjectID | undefined =
117
+ createdItem.createdByUserId || onCreate.createBy.props.userId;
118
+
119
+ if (incidentEpisodeId && teamId && projectId) {
120
+ const team: Team | null = await TeamService.findOneById({
121
+ id: teamId,
122
+ select: {
123
+ name: true,
124
+ },
125
+ props: {
126
+ isRoot: true,
127
+ },
128
+ });
129
+
130
+ if (team && team.name) {
131
+ const episodeNumberResult: {
132
+ number: number | null;
133
+ numberWithPrefix: string | null;
134
+ } = await IncidentEpisodeService.getEpisodeNumber({
135
+ episodeId: incidentEpisodeId,
136
+ });
137
+ const episodeNumberDisplay: string =
138
+ episodeNumberResult.numberWithPrefix ||
139
+ "#" + episodeNumberResult.number;
140
+
141
+ await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
142
+ incidentEpisodeId: incidentEpisodeId,
143
+ projectId: projectId,
144
+ incidentEpisodeFeedEventType:
145
+ IncidentEpisodeFeedEventType.OwnerTeamAdded,
146
+ displayColor: Gray500,
147
+ feedInfoInMarkdown: `👨🏻‍👩🏻‍👦🏻 Added team **${team.name}** to the [Episode ${episodeNumberDisplay}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(projectId!, incidentEpisodeId!)).toString()}) as the owner.`,
148
+ userId: createdByUserId || undefined,
149
+ workspaceNotification: {
150
+ sendWorkspaceNotification: true,
151
+ notifyUserId: createdByUserId || undefined,
152
+ },
153
+ });
154
+ }
155
+
156
+ // get notification rule where inviteOwners is true.
157
+ const notificationRules: Array<WorkspaceNotificationRule> =
158
+ await WorkspaceNotificationRuleService.getNotificationRulesWhereInviteOwnersIsTrue(
159
+ {
160
+ projectId: projectId,
161
+ notificationFor: {
162
+ incidentEpisodeId: incidentEpisodeId,
163
+ },
164
+ notificationRuleEventType:
165
+ NotificationRuleEventType.IncidentEpisode,
166
+ },
167
+ );
168
+
169
+ // Fetch episode to get workspace channels
170
+ const episode: IncidentEpisode | null =
171
+ await IncidentEpisodeService.findOneById({
172
+ id: incidentEpisodeId,
173
+ select: {
174
+ postUpdatesToWorkspaceChannels: true,
175
+ },
176
+ props: {
177
+ isRoot: true,
178
+ },
179
+ });
180
+
181
+ if (episode) {
182
+ WorkspaceNotificationRuleService.inviteTeamsBasedOnRulesAndWorkspaceChannels(
183
+ {
184
+ notificationRules: notificationRules,
185
+ projectId: projectId,
186
+ workspaceChannels: episode.postUpdatesToWorkspaceChannels || [],
187
+ teamIds: [teamId],
188
+ },
189
+ ).catch((error: Error) => {
190
+ logger.error(error);
191
+ });
192
+ }
193
+ }
194
+
195
+ return createdItem;
196
+ }
8
197
  }
9
198
 
10
199
  export default new Service();
@@ -1,10 +1,193 @@
1
+ import ObjectID from "../../Types/ObjectID";
1
2
  import DatabaseService from "./DatabaseService";
2
3
  import Model from "../../Models/DatabaseModels/IncidentEpisodeOwnerUser";
4
+ import IncidentEpisodeFeedService from "./IncidentEpisodeFeedService";
5
+ import { IncidentEpisodeFeedEventType } from "../../Models/DatabaseModels/IncidentEpisodeFeed";
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 IncidentEpisodeService from "./IncidentEpisodeService";
12
+ import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
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
+ incidentEpisodeId: 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 incidentEpisodeId: ObjectID | undefined = item.incidentEpisodeId;
62
+ const projectId: ObjectID | undefined = item.projectId;
63
+ const userId: ObjectID | undefined = item.userId;
64
+
65
+ if (incidentEpisodeId && 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 IncidentEpisodeService.getEpisodeNumber({
81
+ episodeId: incidentEpisodeId,
82
+ });
83
+ const episodeNumberDisplay: string =
84
+ episodeNumberResult.numberWithPrefix ||
85
+ "#" + episodeNumberResult.number;
86
+
87
+ if (user && user.name) {
88
+ await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
89
+ incidentEpisodeId: incidentEpisodeId,
90
+ projectId: projectId,
91
+ incidentEpisodeFeedEventType:
92
+ IncidentEpisodeFeedEventType.OwnerUserRemoved,
93
+ displayColor: Red500,
94
+ feedInfoInMarkdown: `👨🏻‍💻 Removed **${user.name.toString()}** (${user.email?.toString()}) from the [Episode ${episodeNumberDisplay}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(projectId!, incidentEpisodeId!)).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 incidentEpisodeId: ObjectID | undefined =
114
+ createdItem.incidentEpisodeId;
115
+ const projectId: ObjectID | undefined = createdItem.projectId;
116
+ const userId: ObjectID | undefined = createdItem.userId;
117
+ const createdByUserId: ObjectID | undefined =
118
+ createdItem.createdByUserId || onCreate.createBy.props.userId;
119
+
120
+ if (incidentEpisodeId && userId && projectId) {
121
+ const episodeNumberResult: {
122
+ number: number | null;
123
+ numberWithPrefix: string | null;
124
+ } = await IncidentEpisodeService.getEpisodeNumber({
125
+ episodeId: incidentEpisodeId,
126
+ });
127
+ const episodeNumberDisplay: string =
128
+ episodeNumberResult.numberWithPrefix ||
129
+ "#" + episodeNumberResult.number;
130
+
131
+ await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
132
+ incidentEpisodeId: incidentEpisodeId,
133
+ projectId: projectId,
134
+ incidentEpisodeFeedEventType:
135
+ IncidentEpisodeFeedEventType.OwnerUserAdded,
136
+ displayColor: Gray500,
137
+ feedInfoInMarkdown: `👨🏻‍💻 Added **${await UserService.getUserMarkdownString(
138
+ {
139
+ userId: userId,
140
+ projectId: projectId,
141
+ },
142
+ )}** to the [Episode ${episodeNumberDisplay}](${(await IncidentEpisodeService.getEpisodeLinkInDashboard(projectId!, incidentEpisodeId!)).toString()}) as the owner.`,
143
+ userId: createdByUserId || undefined,
144
+ workspaceNotification: {
145
+ sendWorkspaceNotification: true,
146
+ notifyUserId: userId || undefined,
147
+ },
148
+ });
149
+
150
+ // get notification rule where inviteOwners is true.
151
+ const notificationRules: Array<WorkspaceNotificationRule> =
152
+ await WorkspaceNotificationRuleService.getNotificationRulesWhereInviteOwnersIsTrue(
153
+ {
154
+ projectId: projectId,
155
+ notificationFor: {
156
+ incidentEpisodeId: incidentEpisodeId,
157
+ },
158
+ notificationRuleEventType:
159
+ NotificationRuleEventType.IncidentEpisode,
160
+ },
161
+ );
162
+
163
+ // Fetch episode to get workspace channels
164
+ const episode: IncidentEpisode | null =
165
+ await IncidentEpisodeService.findOneById({
166
+ id: incidentEpisodeId,
167
+ select: {
168
+ postUpdatesToWorkspaceChannels: true,
169
+ },
170
+ props: {
171
+ isRoot: true,
172
+ },
173
+ });
174
+
175
+ if (episode) {
176
+ WorkspaceNotificationRuleService.inviteUsersBasedOnRulesAndWorkspaceChannels(
177
+ {
178
+ notificationRules: notificationRules,
179
+ projectId: projectId,
180
+ workspaceChannels: episode.postUpdatesToWorkspaceChannels || [],
181
+ userIds: [userId],
182
+ },
183
+ ).catch((error: Error) => {
184
+ logger.error(error);
185
+ });
186
+ }
187
+ }
188
+
189
+ return createdItem;
190
+ }
8
191
  }
9
192
 
10
193
  export default new Service();
@@ -140,6 +140,10 @@ export class Service extends DatabaseService<Model> {
140
140
 
141
141
  ${(createdItem.note || "") + attachmentsMarkdown}
142
142
  `,
143
+ workspaceNotification: {
144
+ sendWorkspaceNotification: true,
145
+ notifyUserId: userId || undefined,
146
+ },
143
147
  });
144
148
 
145
149
  return createdItem;
@@ -196,6 +200,10 @@ ${(createdItem.note || "") + attachmentsMarkdown}
196
200
 
197
201
  ${(updatedItem.note || "") + attachmentsMarkdown}
198
202
  `,
203
+ workspaceNotification: {
204
+ sendWorkspaceNotification: true,
205
+ notifyUserId: userId || undefined,
206
+ },
199
207
  });
200
208
  }
201
209
  }