@oneuptime/common 9.5.8 → 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 (141) 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/Server/Infrastructure/Postgres/SchemaMigrations/1770833704656-MigrationName.ts +156 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1770834237090-MigrationName.ts +119 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  9. package/Server/Services/AlertEpisodeFeedService.ts +50 -0
  10. package/Server/Services/AlertEpisodeInternalNoteService.ts +162 -0
  11. package/Server/Services/AlertEpisodeMemberService.ts +7 -0
  12. package/Server/Services/AlertEpisodeOwnerTeamService.ts +186 -0
  13. package/Server/Services/AlertEpisodeOwnerUserService.ts +180 -0
  14. package/Server/Services/AlertEpisodeService.ts +68 -0
  15. package/Server/Services/AlertEpisodeStateTimelineService.ts +5 -0
  16. package/Server/Services/AlertService.ts +3 -0
  17. package/Server/Services/IncidentEpisodeFeedService.ts +50 -0
  18. package/Server/Services/IncidentEpisodeInternalNoteService.ts +163 -0
  19. package/Server/Services/IncidentEpisodeMemberService.ts +7 -0
  20. package/Server/Services/IncidentEpisodeOwnerTeamService.ts +189 -0
  21. package/Server/Services/IncidentEpisodeOwnerUserService.ts +183 -0
  22. package/Server/Services/IncidentEpisodePublicNoteService.ts +8 -0
  23. package/Server/Services/IncidentEpisodeService.ts +91 -12
  24. package/Server/Services/IncidentEpisodeStateTimelineService.ts +5 -0
  25. package/Server/Services/IncidentService.ts +5 -0
  26. package/Server/Services/WorkspaceNotificationRuleService.ts +20 -0
  27. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +1 -1
  28. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +7 -6
  29. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +1 -1
  30. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +7 -6
  31. package/Server/Utils/Workspace/Slack/Actions/Alert.ts +17 -0
  32. package/Server/Utils/Workspace/Slack/Actions/AlertEpisode.ts +27 -12
  33. package/Server/Utils/Workspace/Slack/Actions/Incident.ts +17 -0
  34. package/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.ts +86 -28
  35. package/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.ts +6 -6
  36. package/Server/Utils/Workspace/Slack/Slack.ts +49 -0
  37. package/Server/Utils/Workspace/WorkspaceMessages/Alert.ts +2 -1
  38. package/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.ts +3 -1
  39. package/Server/Utils/Workspace/WorkspaceMessages/Incident.ts +2 -1
  40. package/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.ts +3 -1
  41. package/Types/Permission.ts +641 -0
  42. package/UI/Components/Detail/Detail.tsx +13 -4
  43. package/UI/Components/Detail/Field.ts +2 -2
  44. package/UI/Components/Dropdown/Dropdown.tsx +38 -7
  45. package/UI/Components/Forms/BasicForm.tsx +35 -5
  46. package/UI/Components/Forms/Fields/PermissionPicker.tsx +261 -0
  47. package/UI/Components/Forms/Types/Field.ts +5 -3
  48. package/UI/Utils/Permission.ts +29 -6
  49. package/build/dist/Models/DatabaseModels/Alert.js +8 -8
  50. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  51. package/build/dist/Models/DatabaseModels/Incident.js +5 -5
  52. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  53. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +3 -3
  54. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  55. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -1
  56. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  57. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +1 -1
  58. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  59. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770833704656-MigrationName.js +63 -0
  60. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770833704656-MigrationName.js.map +1 -0
  61. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770834237090-MigrationName.js +46 -0
  62. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770834237090-MigrationName.js.map +1 -0
  63. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  64. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  65. package/build/dist/Server/Services/AlertEpisodeFeedService.js +33 -0
  66. package/build/dist/Server/Services/AlertEpisodeFeedService.js.map +1 -1
  67. package/build/dist/Server/Services/AlertEpisodeInternalNoteService.js +132 -0
  68. package/build/dist/Server/Services/AlertEpisodeInternalNoteService.js.map +1 -1
  69. package/build/dist/Server/Services/AlertEpisodeMemberService.js +7 -0
  70. package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -1
  71. package/build/dist/Server/Services/AlertEpisodeOwnerTeamService.js +163 -0
  72. package/build/dist/Server/Services/AlertEpisodeOwnerTeamService.js.map +1 -1
  73. package/build/dist/Server/Services/AlertEpisodeOwnerUserService.js +156 -0
  74. package/build/dist/Server/Services/AlertEpisodeOwnerUserService.js.map +1 -1
  75. package/build/dist/Server/Services/AlertEpisodeService.js +53 -0
  76. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  77. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +4 -0
  78. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -1
  79. package/build/dist/Server/Services/AlertService.js +3 -5
  80. package/build/dist/Server/Services/AlertService.js.map +1 -1
  81. package/build/dist/Server/Services/IncidentEpisodeFeedService.js +33 -0
  82. package/build/dist/Server/Services/IncidentEpisodeFeedService.js.map +1 -1
  83. package/build/dist/Server/Services/IncidentEpisodeInternalNoteService.js +132 -0
  84. package/build/dist/Server/Services/IncidentEpisodeInternalNoteService.js.map +1 -1
  85. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +7 -0
  86. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  87. package/build/dist/Server/Services/IncidentEpisodeOwnerTeamService.js +163 -0
  88. package/build/dist/Server/Services/IncidentEpisodeOwnerTeamService.js.map +1 -1
  89. package/build/dist/Server/Services/IncidentEpisodeOwnerUserService.js +156 -0
  90. package/build/dist/Server/Services/IncidentEpisodeOwnerUserService.js.map +1 -1
  91. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +8 -0
  92. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  93. package/build/dist/Server/Services/IncidentEpisodeService.js +72 -10
  94. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  95. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +4 -0
  96. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  97. package/build/dist/Server/Services/IncidentService.js +5 -5
  98. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  99. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +16 -0
  100. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  101. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +1 -1
  102. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  103. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +7 -6
  104. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  105. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +1 -1
  106. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  107. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +7 -6
  108. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  109. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js +16 -0
  110. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js.map +1 -1
  111. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js +25 -9
  112. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js.map +1 -1
  113. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +16 -0
  114. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js.map +1 -1
  115. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js +71 -25
  116. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js.map +1 -1
  117. package/build/dist/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.js +6 -6
  118. package/build/dist/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.js.map +1 -1
  119. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +40 -0
  120. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  121. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Alert.js +1 -1
  122. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Alert.js.map +1 -1
  123. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.js +1 -1
  124. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.js.map +1 -1
  125. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Incident.js +1 -1
  126. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Incident.js.map +1 -1
  127. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.js +1 -1
  128. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.js.map +1 -1
  129. package/build/dist/Types/Permission.js +637 -0
  130. package/build/dist/Types/Permission.js.map +1 -1
  131. package/build/dist/UI/Components/Detail/Detail.js +7 -1
  132. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  133. package/build/dist/UI/Components/Dropdown/Dropdown.js +17 -2
  134. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  135. package/build/dist/UI/Components/Forms/BasicForm.js +17 -3
  136. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  137. package/build/dist/UI/Components/Forms/Fields/PermissionPicker.js +129 -0
  138. package/build/dist/UI/Components/Forms/Fields/PermissionPicker.js.map +1 -0
  139. package/build/dist/UI/Utils/Permission.js +17 -4
  140. package/build/dist/UI/Utils/Permission.js.map +1 -1
  141. package/package.json +1 -1
@@ -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;
@@ -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();