@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
@@ -33,6 +33,8 @@ import User from "../../Models/DatabaseModels/User";
33
33
  import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
34
34
  import NotificationRuleWorkspaceChannel from "../../Types/Workspace/NotificationRules/NotificationRuleWorkspaceChannel";
35
35
  import WorkspaceType from "../../Types/Workspace/WorkspaceType";
36
+ import IncidentEpisodeWorkspaceMessages from "../Utils/Workspace/WorkspaceMessages/IncidentEpisode";
37
+ import { MessageBlocksByWorkspaceType } from "./WorkspaceNotificationRuleService";
36
38
  import IncidentService from "./IncidentService";
37
39
  import OnCallDutyPolicyService from "./OnCallDutyPolicyService";
38
40
  import OnCallDutyPolicy from "../../Models/DatabaseModels/OnCallDutyPolicy";
@@ -147,6 +149,17 @@ export class Service extends DatabaseService<Model> {
147
149
 
148
150
  // Create initial state timeline entry
149
151
  Promise.resolve()
152
+ .then(async () => {
153
+ try {
154
+ if (createdItem.projectId && createdItem.id) {
155
+ await this.handleEpisodeWorkspaceOperationsAsync(createdItem);
156
+ }
157
+ } catch (error) {
158
+ logger.error(
159
+ `Workspace operations failed in IncidentEpisodeService.onCreateSuccess: ${error}`,
160
+ );
161
+ }
162
+ })
150
163
  .then(async () => {
151
164
  try {
152
165
  await this.changeEpisodeState({
@@ -193,6 +206,51 @@ export class Service extends DatabaseService<Model> {
193
206
  return createdItem;
194
207
  }
195
208
 
209
+ @CaptureSpan()
210
+ private async handleEpisodeWorkspaceOperationsAsync(
211
+ createdItem: Model,
212
+ ): Promise<void> {
213
+ try {
214
+ if (!createdItem.projectId || !createdItem.id) {
215
+ throw new BadDataException(
216
+ "projectId and id are required for workspace operations",
217
+ );
218
+ }
219
+
220
+ const workspaceResult: {
221
+ channelsCreated: Array<NotificationRuleWorkspaceChannel>;
222
+ } | null =
223
+ await IncidentEpisodeWorkspaceMessages.createChannelsAndInviteUsersToChannels(
224
+ {
225
+ projectId: createdItem.projectId,
226
+ incidentEpisodeId: createdItem.id,
227
+ episodeNumber: createdItem.episodeNumber || 0,
228
+ ...(createdItem.episodeNumberWithPrefix
229
+ ? {
230
+ episodeNumberWithPrefix: createdItem.episodeNumberWithPrefix,
231
+ }
232
+ : {}),
233
+ },
234
+ );
235
+
236
+ if (workspaceResult && workspaceResult.channelsCreated?.length > 0) {
237
+ await this.updateOneById({
238
+ id: createdItem.id,
239
+ data: {
240
+ postUpdatesToWorkspaceChannels:
241
+ workspaceResult.channelsCreated || [],
242
+ },
243
+ props: {
244
+ isRoot: true,
245
+ },
246
+ });
247
+ }
248
+ } catch (error) {
249
+ logger.error(`Error in handleEpisodeWorkspaceOperationsAsync: ${error}`);
250
+ throw error;
251
+ }
252
+ }
253
+
196
254
  @CaptureSpan()
197
255
  private async createEpisodeCreatedFeed(episode: Model): Promise<void> {
198
256
  if (!episode.id || !episode.projectId) {
@@ -213,6 +271,14 @@ export class Service extends DatabaseService<Model> {
213
271
  feedInfoInMarkdown += `This episode was manually created.\n\n`;
214
272
  }
215
273
 
274
+ const episodeCreateMessageBlocks: Array<MessageBlocksByWorkspaceType> =
275
+ await IncidentEpisodeWorkspaceMessages.getIncidentEpisodeCreateMessageBlocks(
276
+ {
277
+ incidentEpisodeId: episode.id,
278
+ projectId: episode.projectId,
279
+ },
280
+ );
281
+
216
282
  await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
217
283
  incidentEpisodeId: episode.id,
218
284
  projectId: episode.projectId,
@@ -220,6 +286,10 @@ export class Service extends DatabaseService<Model> {
220
286
  displayColor: Red500,
221
287
  feedInfoInMarkdown: feedInfoInMarkdown,
222
288
  userId: episode.createdByUserId || undefined,
289
+ workspaceNotification: {
290
+ appendMessageBlocks: episodeCreateMessageBlocks,
291
+ sendWorkspaceNotification: true,
292
+ },
223
293
  });
224
294
  }
225
295
 
@@ -952,21 +1022,30 @@ export class Service extends DatabaseService<Model> {
952
1022
  }
953
1023
 
954
1024
  @CaptureSpan()
955
- public getWorkspaceChannelForEpisode(
956
- episode: Model,
957
- workspaceType: WorkspaceType,
958
- ): Array<NotificationRuleWorkspaceChannel> {
959
- if (
960
- !episode.postUpdatesToWorkspaceChannels ||
961
- !Array.isArray(episode.postUpdatesToWorkspaceChannels) ||
962
- episode.postUpdatesToWorkspaceChannels.length === 0
963
- ) {
964
- return [];
1025
+ public async getWorkspaceChannelForEpisode(data: {
1026
+ episodeId: ObjectID;
1027
+ workspaceType?: WorkspaceType | null;
1028
+ }): Promise<Array<NotificationRuleWorkspaceChannel>> {
1029
+ const episode: Model | null = await this.findOneById({
1030
+ id: data.episodeId,
1031
+ select: {
1032
+ postUpdatesToWorkspaceChannels: true,
1033
+ },
1034
+ props: {
1035
+ isRoot: true,
1036
+ },
1037
+ });
1038
+
1039
+ if (!episode) {
1040
+ throw new BadDataException("Incident Episode not found.");
965
1041
  }
966
1042
 
967
- return episode.postUpdatesToWorkspaceChannels.filter(
1043
+ return (episode.postUpdatesToWorkspaceChannels || []).filter(
968
1044
  (channel: NotificationRuleWorkspaceChannel) => {
969
- return channel.workspaceType === workspaceType;
1045
+ if (!data.workspaceType) {
1046
+ return true;
1047
+ }
1048
+ return channel.workspaceType === data.workspaceType;
970
1049
  },
971
1050
  );
972
1051
  }
@@ -383,6 +383,11 @@ export class Service extends DatabaseService<IncidentEpisodeStateTimeline> {
383
383
  ? `**Cause:** \n${createdItem.rootCause}`
384
384
  : undefined,
385
385
  userId: createdItem.createdByUserId || onCreate.createBy.props.userId,
386
+ workspaceNotification: {
387
+ sendWorkspaceNotification: true,
388
+ notifyUserId:
389
+ createdItem.createdByUserId || onCreate.createBy.props.userId,
390
+ },
386
391
  });
387
392
 
388
393
  return createdItem;
@@ -843,6 +843,11 @@ export class Service extends DatabaseService<Model> {
843
843
  projectId: createdItem.projectId,
844
844
  incidentId: createdItem.id,
845
845
  incidentNumber: createdItem.incidentNumber!,
846
+ ...(createdItem.incidentNumberWithPrefix
847
+ ? {
848
+ incidentNumberWithPrefix: createdItem.incidentNumberWithPrefix,
849
+ }
850
+ : {}),
846
851
  });
847
852
 
848
853
  if (workspaceResult && workspaceResult.channelsCreated?.length > 0) {
@@ -52,6 +52,7 @@ import WorkspaceNotificationActionType from "../../Types/Workspace/WorkspaceNoti
52
52
  import ExceptionMessages from "../../Types/Exception/ExceptionMessages";
53
53
  import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
54
54
  import IncidentEpisodeService from "./IncidentEpisodeService";
55
+ import AlertEpisodeService from "./AlertEpisodeService";
55
56
 
56
57
  export interface MessageBlocksByWorkspaceType {
57
58
  workspaceType: WorkspaceType;
@@ -735,6 +736,25 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
735
736
  );
736
737
  }
737
738
 
739
+ // incident episodes
740
+ if (data.notificationFor.incidentEpisodeId) {
741
+ monitorChannels =
742
+ await IncidentEpisodeService.getWorkspaceChannelForEpisode({
743
+ episodeId: data.notificationFor.incidentEpisodeId,
744
+ workspaceType: data.workspaceType,
745
+ });
746
+ }
747
+
748
+ // alert episodes
749
+ if (data.notificationFor.alertEpisodeId) {
750
+ monitorChannels = await AlertEpisodeService.getWorkspaceChannelForEpisode(
751
+ {
752
+ episodeId: data.notificationFor.alertEpisodeId,
753
+ workspaceType: data.workspaceType,
754
+ },
755
+ );
756
+ }
757
+
738
758
  logger.debug("Workspace channels found:");
739
759
  logger.debug(monitorChannels);
740
760
 
@@ -361,7 +361,7 @@ export default class MicrosoftTeamsAlertActions {
361
361
  await this.buildExecuteAlertOnCallPolicyCard(actionValue, projectId);
362
362
  if (!card) {
363
363
  await turnContext.sendActivity(
364
- "No on-call policies found in the project",
364
+ "No on-call policies have been configured for this project yet. Please add an on-call policy in the OneUptime Dashboard under On-Call Duty > Policies to use this feature.",
365
365
  );
366
366
  return;
367
367
  }
@@ -391,7 +391,7 @@ export default class MicrosoftTeamsAlertEpisodeActions {
391
391
  );
392
392
  if (!card) {
393
393
  await turnContext.sendActivity(
394
- "No on-call policies found in the project",
394
+ "No on-call policies have been configured for this project yet. Please add an on-call policy in the OneUptime Dashboard under On-Call Duty > Policies to use this feature.",
395
395
  );
396
396
  return;
397
397
  }
@@ -495,11 +495,12 @@ export default class MicrosoftTeamsAlertEpisodeActions {
495
495
  // Update the state
496
496
  const episodeId: ObjectID = new ObjectID(actionValue);
497
497
 
498
- await AlertEpisodeService.updateOneById({
499
- id: episodeId,
500
- data: {
501
- currentAlertStateId: new ObjectID(alertStateId.toString()),
502
- },
498
+ await AlertEpisodeService.changeEpisodeState({
499
+ projectId: projectId,
500
+ episodeId: episodeId,
501
+ alertStateId: new ObjectID(alertStateId.toString()),
502
+ notifyOwners: true,
503
+ rootCause: "State changed via Microsoft Teams.",
503
504
  props: {
504
505
  isRoot: true,
505
506
  },
@@ -424,7 +424,7 @@ export default class MicrosoftTeamsIncidentActions {
424
424
  );
425
425
  if (!card) {
426
426
  await turnContext.sendActivity(
427
- "No on-call policies found in the project",
427
+ "No on-call policies have been configured for this project yet. Please add an on-call policy in the OneUptime Dashboard under On-Call Duty > Policies to use this feature.",
428
428
  );
429
429
  return;
430
430
  }
@@ -403,7 +403,7 @@ export default class MicrosoftTeamsIncidentEpisodeActions {
403
403
  );
404
404
  if (!card) {
405
405
  await turnContext.sendActivity(
406
- "No on-call policies found in the project",
406
+ "No on-call policies have been configured for this project yet. Please add an on-call policy in the OneUptime Dashboard under On-Call Duty > Policies to use this feature.",
407
407
  );
408
408
  return;
409
409
  }
@@ -505,11 +505,12 @@ export default class MicrosoftTeamsIncidentEpisodeActions {
505
505
  // Update the state
506
506
  const episodeId: ObjectID = new ObjectID(actionValue);
507
507
 
508
- await IncidentEpisodeService.updateOneById({
509
- id: episodeId,
510
- data: {
511
- currentIncidentStateId: new ObjectID(incidentStateId.toString()),
512
- },
508
+ await IncidentEpisodeService.changeEpisodeState({
509
+ projectId: projectId,
510
+ episodeId: episodeId,
511
+ incidentStateId: new ObjectID(incidentStateId.toString()),
512
+ notifyOwners: true,
513
+ rootCause: "State changed via Microsoft Teams.",
513
514
  props: {
514
515
  isRoot: true,
515
516
  },
@@ -322,6 +322,23 @@ export default class SlackAlertActions {
322
322
  return option.label !== "" || option.value !== "";
323
323
  });
324
324
 
325
+ if (dropdownOption.length === 0) {
326
+ if (data.slackRequest.slackChannelId) {
327
+ await SlackUtil.sendEphemeralMessageToChannel({
328
+ messageBlocks: [
329
+ {
330
+ _type: "WorkspacePayloadMarkdown",
331
+ text: "No on-call policies have been configured for this project yet. Please add an on-call policy in the OneUptime Dashboard under On-Call Duty > Policies to use this feature.",
332
+ } as WorkspacePayloadMarkdown,
333
+ ],
334
+ authToken: data.slackRequest.projectAuthToken!,
335
+ channelId: data.slackRequest.slackChannelId,
336
+ userId: data.slackRequest.slackUserId!,
337
+ });
338
+ }
339
+ return;
340
+ }
341
+
325
342
  const onCallPolicyDropdown: WorkspaceDropdownBlock = {
326
343
  _type: "WorkspaceDropdownBlock",
327
344
  label: "On Call Policy",
@@ -21,7 +21,7 @@ import UserNotificationEventType from "../../../../../Types/UserNotification/Use
21
21
  import AlertState from "../../../../../Models/DatabaseModels/AlertState";
22
22
  import AlertStateService from "../../../../Services/AlertStateService";
23
23
  import logger from "../../../Logger";
24
- import AccessTokenService from "../../../../Services/AccessTokenService";
24
+
25
25
  import CaptureSpan from "../../../Telemetry/CaptureSpan";
26
26
  import WorkspaceNotificationLogService from "../../../../Services/WorkspaceNotificationLogService";
27
27
  import WorkspaceUserAuthTokenService from "../../../../Services/WorkspaceUserAuthTokenService";
@@ -305,6 +305,23 @@ export default class SlackAlertEpisodeActions {
305
305
  return option.label !== "" || option.value !== "";
306
306
  });
307
307
 
308
+ if (dropdownOption.length === 0) {
309
+ if (data.slackRequest.slackChannelId) {
310
+ await SlackUtil.sendEphemeralMessageToChannel({
311
+ messageBlocks: [
312
+ {
313
+ _type: "WorkspacePayloadMarkdown",
314
+ text: "No on-call policies have been configured for this project yet. Please add an on-call policy in the OneUptime Dashboard under On-Call Duty > Policies to use this feature.",
315
+ } as WorkspacePayloadMarkdown,
316
+ ],
317
+ authToken: data.slackRequest.projectAuthToken!,
318
+ channelId: data.slackRequest.slackChannelId,
319
+ userId: data.slackRequest.slackUserId!,
320
+ });
321
+ }
322
+ return;
323
+ }
324
+
308
325
  const onCallPolicyDropdown: WorkspaceDropdownBlock = {
309
326
  _type: "WorkspaceDropdownBlock",
310
327
  label: "On Call Policy",
@@ -438,18 +455,16 @@ export default class SlackAlertEpisodeActions {
438
455
 
439
456
  const stateId: ObjectID = new ObjectID(stateString);
440
457
 
441
- await AlertEpisodeService.updateOneById({
442
- id: episodeId,
443
- data: {
444
- currentAlertStateId: stateId,
458
+ await AlertEpisodeService.changeEpisodeState({
459
+ projectId: data.slackRequest.projectId!,
460
+ episodeId: episodeId,
461
+ alertStateId: stateId,
462
+ notifyOwners: true,
463
+ rootCause: "State changed via Slack.",
464
+ props: {
465
+ isRoot: true,
466
+ userId: data.slackRequest.userId!,
445
467
  },
446
- props:
447
- await AccessTokenService.getDatabaseCommonInteractionPropsByUserAndProject(
448
- {
449
- userId: data.slackRequest.userId!,
450
- projectId: data.slackRequest.projectId!,
451
- },
452
- ),
453
468
  });
454
469
 
455
470
  // Log the button interaction
@@ -782,6 +782,23 @@ export default class SlackIncidentActions {
782
782
  return option.label !== "" || option.value !== "";
783
783
  });
784
784
 
785
+ if (dropdownOption.length === 0) {
786
+ if (data.slackRequest.slackChannelId) {
787
+ await SlackUtil.sendEphemeralMessageToChannel({
788
+ messageBlocks: [
789
+ {
790
+ _type: "WorkspacePayloadMarkdown",
791
+ text: "No on-call policies have been configured for this project yet. Please add an on-call policy in the OneUptime Dashboard under On-Call Duty > Policies to use this feature.",
792
+ } as WorkspacePayloadMarkdown,
793
+ ],
794
+ authToken: data.slackRequest.projectAuthToken!,
795
+ channelId: data.slackRequest.slackChannelId,
796
+ userId: data.slackRequest.slackUserId!,
797
+ });
798
+ }
799
+ return;
800
+ }
801
+
785
802
  const onCallPolicyDropdown: WorkspaceDropdownBlock = {
786
803
  _type: "WorkspaceDropdownBlock",
787
804
  label: "On Call Policy",
@@ -13,6 +13,7 @@ import {
13
13
  WorkspaceTextAreaBlock,
14
14
  } from "../../../../../Types/Workspace/WorkspaceMessagePayload";
15
15
  import IncidentEpisodeInternalNoteService from "../../../../Services/IncidentEpisodeInternalNoteService";
16
+ import IncidentEpisodePublicNoteService from "../../../../Services/IncidentEpisodePublicNoteService";
16
17
  import OnCallDutyPolicy from "../../../../../Models/DatabaseModels/OnCallDutyPolicy";
17
18
  import OnCallDutyPolicyService from "../../../../Services/OnCallDutyPolicyService";
18
19
  import { LIMIT_PER_PROJECT } from "../../../../../Types/Database/LimitMax";
@@ -21,7 +22,7 @@ import UserNotificationEventType from "../../../../../Types/UserNotification/Use
21
22
  import IncidentState from "../../../../../Models/DatabaseModels/IncidentState";
22
23
  import IncidentStateService from "../../../../Services/IncidentStateService";
23
24
  import logger from "../../../Logger";
24
- import AccessTokenService from "../../../../Services/AccessTokenService";
25
+
25
26
  import CaptureSpan from "../../../Telemetry/CaptureSpan";
26
27
  import WorkspaceNotificationLogService from "../../../../Services/WorkspaceNotificationLogService";
27
28
  import WorkspaceUserAuthTokenService from "../../../../Services/WorkspaceUserAuthTokenService";
@@ -275,9 +276,7 @@ export default class SlackIncidentEpisodeActions {
275
276
  }
276
277
 
277
278
  // We send this early let slack know we're ok. We'll do the rest in the background.
278
- Response.sendJsonObjectResponse(req, res, {
279
- response_action: "clear",
280
- });
279
+ Response.sendTextResponse(req, res, "");
281
280
 
282
281
  const onCallPolicies: Array<OnCallDutyPolicy> =
283
282
  await OnCallDutyPolicyService.findBy({
@@ -305,6 +304,23 @@ export default class SlackIncidentEpisodeActions {
305
304
  return option.label !== "" || option.value !== "";
306
305
  });
307
306
 
307
+ if (dropdownOption.length === 0) {
308
+ if (data.slackRequest.slackChannelId) {
309
+ await SlackUtil.sendEphemeralMessageToChannel({
310
+ messageBlocks: [
311
+ {
312
+ _type: "WorkspacePayloadMarkdown",
313
+ text: "No on-call policies have been configured for this project yet. Please add an on-call policy in the OneUptime Dashboard under On-Call Duty > Policies to use this feature.",
314
+ } as WorkspacePayloadMarkdown,
315
+ ],
316
+ authToken: data.slackRequest.projectAuthToken!,
317
+ channelId: data.slackRequest.slackChannelId,
318
+ userId: data.slackRequest.slackUserId!,
319
+ });
320
+ }
321
+ return;
322
+ }
323
+
308
324
  const onCallPolicyDropdown: WorkspaceDropdownBlock = {
309
325
  _type: "WorkspaceDropdownBlock",
310
326
  label: "On Call Policy",
@@ -349,9 +365,7 @@ export default class SlackIncidentEpisodeActions {
349
365
  }
350
366
 
351
367
  // We send this early let slack know we're ok. We'll do the rest in the background.
352
- Response.sendJsonObjectResponse(req, res, {
353
- response_action: "clear",
354
- });
368
+ Response.sendTextResponse(req, res, "");
355
369
 
356
370
  // Incident Episodes use incident states
357
371
  const incidentStates: Array<IncidentState> =
@@ -438,18 +452,16 @@ export default class SlackIncidentEpisodeActions {
438
452
 
439
453
  const stateId: ObjectID = new ObjectID(stateString);
440
454
 
441
- await IncidentEpisodeService.updateOneById({
442
- id: episodeId,
443
- data: {
444
- currentIncidentStateId: stateId,
455
+ await IncidentEpisodeService.changeEpisodeState({
456
+ projectId: data.slackRequest.projectId!,
457
+ episodeId: episodeId,
458
+ incidentStateId: stateId,
459
+ notifyOwners: true,
460
+ rootCause: "State changed via Slack.",
461
+ props: {
462
+ isRoot: true,
463
+ userId: data.slackRequest.userId!,
445
464
  },
446
- props:
447
- await AccessTokenService.getDatabaseCommonInteractionPropsByUserAndProject(
448
- {
449
- userId: data.slackRequest.userId!,
450
- projectId: data.slackRequest.projectId!,
451
- },
452
- ),
453
465
  });
454
466
 
455
467
  // Log the button interaction
@@ -610,6 +622,14 @@ export default class SlackIncidentEpisodeActions {
610
622
  );
611
623
  }
612
624
 
625
+ if (!data.slackRequest.viewValues["noteType"]) {
626
+ return Response.sendErrorResponse(
627
+ req,
628
+ res,
629
+ new BadDataException("Invalid Note Type"),
630
+ );
631
+ }
632
+
613
633
  if (!data.slackRequest.viewValues["note"]) {
614
634
  return Response.sendErrorResponse(
615
635
  req,
@@ -620,18 +640,41 @@ export default class SlackIncidentEpisodeActions {
620
640
 
621
641
  const episodeId: ObjectID = new ObjectID(actionValue);
622
642
  const note: string = data.slackRequest.viewValues["note"].toString();
643
+ const noteType: string =
644
+ data.slackRequest.viewValues["noteType"].toString();
645
+
646
+ if (noteType !== "public" && noteType !== "private") {
647
+ return Response.sendErrorResponse(
648
+ req,
649
+ res,
650
+ new BadDataException("Invalid Note Type"),
651
+ );
652
+ }
623
653
 
624
654
  // send empty response.
625
655
  Response.sendJsonObjectResponse(req, res, {
626
656
  response_action: "clear",
627
657
  });
628
658
 
629
- await IncidentEpisodeInternalNoteService.addNote({
630
- incidentEpisodeId: episodeId!,
631
- note: note || "",
632
- projectId: data.slackRequest.projectId!,
633
- userId: data.slackRequest.userId!,
634
- });
659
+ // if public note then, add a note.
660
+ if (noteType === "public") {
661
+ await IncidentEpisodePublicNoteService.addNote({
662
+ incidentEpisodeId: episodeId!,
663
+ note: note || "",
664
+ projectId: data.slackRequest.projectId!,
665
+ userId: data.slackRequest.userId!,
666
+ });
667
+ }
668
+
669
+ // if private note then, add a note.
670
+ if (noteType === "private") {
671
+ await IncidentEpisodeInternalNoteService.addNote({
672
+ incidentEpisodeId: episodeId!,
673
+ note: note || "",
674
+ projectId: data.slackRequest.projectId!,
675
+ userId: data.slackRequest.userId!,
676
+ });
677
+ }
635
678
  }
636
679
 
637
680
  @CaptureSpan()
@@ -653,9 +696,24 @@ export default class SlackIncidentEpisodeActions {
653
696
  }
654
697
 
655
698
  // We send this early let slack know we're ok. We'll do the rest in the background.
656
- Response.sendJsonObjectResponse(req, res, {
657
- response_action: "clear",
658
- });
699
+ Response.sendTextResponse(req, res, "");
700
+
701
+ const notePickerDropdown: WorkspaceDropdownBlock = {
702
+ _type: "WorkspaceDropdownBlock",
703
+ label: "Note Type",
704
+ blockId: "noteType",
705
+ placeholder: "Select Note Type",
706
+ options: [
707
+ {
708
+ label: "Public Note (Will be posted on Status Page)",
709
+ value: "public",
710
+ },
711
+ {
712
+ label: "Private Note (Only visible to team members)",
713
+ value: "private",
714
+ },
715
+ ],
716
+ };
659
717
 
660
718
  const noteTextArea: WorkspaceTextAreaBlock = {
661
719
  _type: "WorkspaceTextAreaBlock",
@@ -672,7 +730,7 @@ export default class SlackIncidentEpisodeActions {
672
730
  cancelButtonTitle: "Cancel",
673
731
  actionId: SlackActionType.SubmitIncidentEpisodeNote,
674
732
  actionValue: actionValue,
675
- blocks: [noteTextArea],
733
+ blocks: [notePickerDropdown, noteTextArea],
676
734
  };
677
735
 
678
736
  await SlackUtil.showModalToUser({
@@ -47,7 +47,7 @@ export default class SlackIncidentEpisodeMessages {
47
47
  // view data.
48
48
  const viewIncidentEpisodeButton: WorkspaceMessagePayloadButton = {
49
49
  _type: "WorkspaceMessagePayloadButton",
50
- title: "View Episode",
50
+ title: "🔗 View Episode",
51
51
  url: await IncidentEpisodeService.getEpisodeLinkInDashboard(
52
52
  data.projectId!,
53
53
  data.incidentEpisodeId!,
@@ -61,7 +61,7 @@ export default class SlackIncidentEpisodeMessages {
61
61
  // execute on call.
62
62
  const executeOnCallButton: WorkspaceMessagePayloadButton = {
63
63
  _type: "WorkspaceMessagePayloadButton",
64
- title: "Execute On Call",
64
+ title: "📞 Execute On Call",
65
65
  value: data.incidentEpisodeId?.toString() || "",
66
66
  actionId: SlackActionType.ViewExecuteIncidentEpisodeOnCallPolicy,
67
67
  };
@@ -71,7 +71,7 @@ export default class SlackIncidentEpisodeMessages {
71
71
  // acknowledge data.
72
72
  const acknowledgeIncidentEpisodeButton: WorkspaceMessagePayloadButton = {
73
73
  _type: "WorkspaceMessagePayloadButton",
74
- title: "Acknowledge Episode",
74
+ title: "👀 Acknowledge Episode",
75
75
  value: data.incidentEpisodeId?.toString() || "",
76
76
  actionId: SlackActionType.AcknowledgeIncidentEpisode,
77
77
  };
@@ -81,7 +81,7 @@ export default class SlackIncidentEpisodeMessages {
81
81
  // resolve data.
82
82
  const resolveIncidentEpisodeButton: WorkspaceMessagePayloadButton = {
83
83
  _type: "WorkspaceMessagePayloadButton",
84
- title: "Resolve Episode",
84
+ title: "Resolve Episode",
85
85
  value: data.incidentEpisodeId?.toString() || "",
86
86
  actionId: SlackActionType.ResolveIncidentEpisode,
87
87
  };
@@ -91,7 +91,7 @@ export default class SlackIncidentEpisodeMessages {
91
91
  // change incident episode state.
92
92
  const changeIncidentEpisodeStateButton: WorkspaceMessagePayloadButton = {
93
93
  _type: "WorkspaceMessagePayloadButton",
94
- title: "Change Episode State",
94
+ title: "➡️ Change Episode State",
95
95
  value: data.incidentEpisodeId?.toString() || "",
96
96
  actionId: SlackActionType.ViewChangeIncidentEpisodeState,
97
97
  };
@@ -101,7 +101,7 @@ export default class SlackIncidentEpisodeMessages {
101
101
  // add note.
102
102
  const addNoteButton: WorkspaceMessagePayloadButton = {
103
103
  _type: "WorkspaceMessagePayloadButton",
104
- title: "Add Note",
104
+ title: "📄 Add Note",
105
105
  value: data.incidentEpisodeId?.toString() || "",
106
106
  actionId: SlackActionType.ViewAddIncidentEpisodeNote,
107
107
  };