@oneuptime/common 9.5.8 → 9.5.10

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 (190) 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/API/OpenSourceDeploymentAPI.ts +8 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1770833704656-MigrationName.ts +156 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1770834237090-MigrationName.ts +119 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  10. package/Server/Middleware/UserAuthorization.ts +8 -3
  11. package/Server/Services/AlertEpisodeFeedService.ts +50 -0
  12. package/Server/Services/AlertEpisodeInternalNoteService.ts +162 -0
  13. package/Server/Services/AlertEpisodeMemberService.ts +7 -0
  14. package/Server/Services/AlertEpisodeOwnerTeamService.ts +186 -0
  15. package/Server/Services/AlertEpisodeOwnerUserService.ts +180 -0
  16. package/Server/Services/AlertEpisodeService.ts +68 -0
  17. package/Server/Services/AlertEpisodeStateTimelineService.ts +5 -0
  18. package/Server/Services/AlertService.ts +3 -0
  19. package/Server/Services/IncidentEpisodeFeedService.ts +50 -0
  20. package/Server/Services/IncidentEpisodeInternalNoteService.ts +163 -0
  21. package/Server/Services/IncidentEpisodeMemberService.ts +7 -0
  22. package/Server/Services/IncidentEpisodeOwnerTeamService.ts +189 -0
  23. package/Server/Services/IncidentEpisodeOwnerUserService.ts +183 -0
  24. package/Server/Services/IncidentEpisodePublicNoteService.ts +8 -0
  25. package/Server/Services/IncidentEpisodeService.ts +91 -12
  26. package/Server/Services/IncidentEpisodeStateTimelineService.ts +5 -0
  27. package/Server/Services/IncidentService.ts +5 -0
  28. package/Server/Services/MonitorService.ts +8 -0
  29. package/Server/Services/WorkspaceNotificationRuleService.ts +31 -2
  30. package/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.ts +183 -0
  31. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +13 -0
  32. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +37 -0
  33. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +1 -1
  34. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +7 -6
  35. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +1 -1
  36. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +7 -6
  37. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +11 -7
  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 +64 -12
  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/Tests/Server/Middleware/UserAuthorization.test.ts +10 -2
  49. package/Types/Monitor/CriteriaFilter.ts +20 -3
  50. package/Types/Monitor/DnsMonitor/DnsMonitorResponse.ts +16 -0
  51. package/Types/Monitor/DnsMonitor/DnsRecordType.ts +14 -0
  52. package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
  53. package/Types/Monitor/MonitorStep.ts +30 -0
  54. package/Types/Monitor/MonitorStepDnsMonitor.ts +46 -0
  55. package/Types/Monitor/MonitorType.ts +15 -2
  56. package/Types/Permission.ts +641 -0
  57. package/Types/Probe/ProbeMonitorResponse.ts +2 -0
  58. package/UI/Components/Detail/Detail.tsx +13 -4
  59. package/UI/Components/Detail/Field.ts +2 -2
  60. package/UI/Components/Dropdown/Dropdown.tsx +38 -7
  61. package/UI/Components/Forms/BasicForm.tsx +35 -5
  62. package/UI/Components/Forms/Fields/PermissionPicker.tsx +261 -0
  63. package/UI/Components/Forms/Types/Field.ts +5 -3
  64. package/UI/Utils/Permission.ts +29 -6
  65. package/Utils/Monitor/MonitorMetricType.ts +2 -1
  66. package/build/dist/Models/DatabaseModels/Alert.js +8 -8
  67. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  68. package/build/dist/Models/DatabaseModels/Incident.js +5 -5
  69. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  70. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +3 -3
  71. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  72. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -1
  73. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  74. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +1 -1
  75. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  76. package/build/dist/Server/API/OpenSourceDeploymentAPI.js +5 -0
  77. package/build/dist/Server/API/OpenSourceDeploymentAPI.js.map +1 -1
  78. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770833704656-MigrationName.js +63 -0
  79. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770833704656-MigrationName.js.map +1 -0
  80. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770834237090-MigrationName.js +46 -0
  81. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770834237090-MigrationName.js.map +1 -0
  82. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  83. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  84. package/build/dist/Server/Middleware/UserAuthorization.js +2 -3
  85. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  86. package/build/dist/Server/Services/AlertEpisodeFeedService.js +33 -0
  87. package/build/dist/Server/Services/AlertEpisodeFeedService.js.map +1 -1
  88. package/build/dist/Server/Services/AlertEpisodeInternalNoteService.js +132 -0
  89. package/build/dist/Server/Services/AlertEpisodeInternalNoteService.js.map +1 -1
  90. package/build/dist/Server/Services/AlertEpisodeMemberService.js +7 -0
  91. package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -1
  92. package/build/dist/Server/Services/AlertEpisodeOwnerTeamService.js +163 -0
  93. package/build/dist/Server/Services/AlertEpisodeOwnerTeamService.js.map +1 -1
  94. package/build/dist/Server/Services/AlertEpisodeOwnerUserService.js +156 -0
  95. package/build/dist/Server/Services/AlertEpisodeOwnerUserService.js.map +1 -1
  96. package/build/dist/Server/Services/AlertEpisodeService.js +53 -0
  97. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  98. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +4 -0
  99. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -1
  100. package/build/dist/Server/Services/AlertService.js +3 -5
  101. package/build/dist/Server/Services/AlertService.js.map +1 -1
  102. package/build/dist/Server/Services/IncidentEpisodeFeedService.js +33 -0
  103. package/build/dist/Server/Services/IncidentEpisodeFeedService.js.map +1 -1
  104. package/build/dist/Server/Services/IncidentEpisodeInternalNoteService.js +132 -0
  105. package/build/dist/Server/Services/IncidentEpisodeInternalNoteService.js.map +1 -1
  106. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +7 -0
  107. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  108. package/build/dist/Server/Services/IncidentEpisodeOwnerTeamService.js +163 -0
  109. package/build/dist/Server/Services/IncidentEpisodeOwnerTeamService.js.map +1 -1
  110. package/build/dist/Server/Services/IncidentEpisodeOwnerUserService.js +156 -0
  111. package/build/dist/Server/Services/IncidentEpisodeOwnerUserService.js.map +1 -1
  112. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +8 -0
  113. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  114. package/build/dist/Server/Services/IncidentEpisodeService.js +72 -10
  115. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  116. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +4 -0
  117. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  118. package/build/dist/Server/Services/IncidentService.js +5 -5
  119. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  120. package/build/dist/Server/Services/MonitorService.js +8 -1
  121. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  122. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +26 -1
  123. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  124. package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js +138 -0
  125. package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js.map +1 -0
  126. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +10 -0
  127. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  128. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +24 -0
  129. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  130. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +1 -1
  131. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  132. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +7 -6
  133. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  134. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +1 -1
  135. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  136. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +7 -6
  137. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  138. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +10 -7
  139. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  140. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js +16 -0
  141. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js.map +1 -1
  142. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js +25 -9
  143. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js.map +1 -1
  144. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +16 -0
  145. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js.map +1 -1
  146. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js +71 -25
  147. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js.map +1 -1
  148. package/build/dist/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.js +6 -6
  149. package/build/dist/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.js.map +1 -1
  150. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +55 -11
  151. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  152. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Alert.js +1 -1
  153. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Alert.js.map +1 -1
  154. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.js +1 -1
  155. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.js.map +1 -1
  156. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Incident.js +1 -1
  157. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Incident.js.map +1 -1
  158. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.js +1 -1
  159. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.js.map +1 -1
  160. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js +4 -2
  161. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js.map +1 -1
  162. package/build/dist/Types/Monitor/CriteriaFilter.js +15 -3
  163. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  164. package/build/dist/Types/Monitor/DnsMonitor/DnsMonitorResponse.js +2 -0
  165. package/build/dist/Types/Monitor/DnsMonitor/DnsMonitorResponse.js.map +1 -0
  166. package/build/dist/Types/Monitor/DnsMonitor/DnsRecordType.js +15 -0
  167. package/build/dist/Types/Monitor/DnsMonitor/DnsRecordType.js.map +1 -0
  168. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
  169. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  170. package/build/dist/Types/Monitor/MonitorStep.js +22 -0
  171. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  172. package/build/dist/Types/Monitor/MonitorStepDnsMonitor.js +34 -0
  173. package/build/dist/Types/Monitor/MonitorStepDnsMonitor.js.map +1 -0
  174. package/build/dist/Types/Monitor/MonitorType.js +13 -2
  175. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  176. package/build/dist/Types/Permission.js +637 -0
  177. package/build/dist/Types/Permission.js.map +1 -1
  178. package/build/dist/UI/Components/Detail/Detail.js +7 -1
  179. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  180. package/build/dist/UI/Components/Dropdown/Dropdown.js +17 -2
  181. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  182. package/build/dist/UI/Components/Forms/BasicForm.js +17 -3
  183. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  184. package/build/dist/UI/Components/Forms/Fields/PermissionPicker.js +129 -0
  185. package/build/dist/UI/Components/Forms/Fields/PermissionPicker.js.map +1 -0
  186. package/build/dist/UI/Utils/Permission.js +17 -4
  187. package/build/dist/UI/Utils/Permission.js.map +1 -1
  188. package/build/dist/Utils/Monitor/MonitorMetricType.js +2 -1
  189. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  190. package/package.json +1 -1
@@ -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
  };
@@ -155,6 +155,55 @@ export default class SlackUtil extends WorkspaceBase {
155
155
  logger.debug("Modal shown to user successfully.");
156
156
  }
157
157
 
158
+ @CaptureSpan()
159
+ public static async sendEphemeralMessageToChannel(data: {
160
+ authToken: string;
161
+ channelId: string;
162
+ userId: string;
163
+ messageBlocks: Array<WorkspaceMessageBlock>;
164
+ }): Promise<void> {
165
+ const blocks: Array<JSONObject> = this.getBlocksFromWorkspaceMessagePayload(
166
+ {
167
+ messageBlocks: data.messageBlocks,
168
+ },
169
+ );
170
+
171
+ const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
172
+ await API.post({
173
+ url: URL.fromString("https://slack.com/api/chat.postEphemeral"),
174
+ data: {
175
+ channel: data.channelId,
176
+ user: data.userId,
177
+ blocks: blocks,
178
+ },
179
+ headers: {
180
+ Authorization: `Bearer ${data.authToken}`,
181
+ ["Content-Type"]: "application/json",
182
+ },
183
+ options: {
184
+ retries: 3,
185
+ exponentialBackoff: true,
186
+ },
187
+ });
188
+
189
+ if (response instanceof HTTPErrorResponse) {
190
+ logger.error("Error response from Slack API for ephemeral message:");
191
+ logger.error(response);
192
+ throw response;
193
+ }
194
+
195
+ if ((response.jsonData as JSONObject)?.["ok"] !== true) {
196
+ logger.error("Invalid response from Slack API for ephemeral message:");
197
+ logger.error(response.jsonData);
198
+ const messageFromSlack: string = (response.jsonData as JSONObject)?.[
199
+ "error"
200
+ ] as string;
201
+ throw new BadRequestException("Error from Slack " + messageFromSlack);
202
+ }
203
+
204
+ logger.debug("Ephemeral message sent successfully.");
205
+ }
206
+
158
207
  @CaptureSpan()
159
208
  public static override async sendDirectMessageToUser(data: {
160
209
  authToken: string;
@@ -393,12 +442,14 @@ export default class SlackUtil extends WorkspaceBase {
393
442
  const workspaceChannels: Array<WorkspaceChannel> = [];
394
443
 
395
444
  for (let channelName of data.channelNames) {
396
- // Normalize channel name
397
- if (channelName && channelName.startsWith("#")) {
398
- channelName = channelName.substring(1);
399
- }
400
- channelName = channelName.toLowerCase();
401
- channelName = channelName.replace(/\s+/g, "-");
445
+ /*
446
+ * Normalize channel name: replace spaces with hyphens, then strip
447
+ * any characters not valid in Slack channel names.
448
+ */
449
+ channelName = channelName
450
+ .toLowerCase()
451
+ .replace(/\s+/g, "-")
452
+ .replace(/[^a-z0-9\-_]/g, "");
402
453
 
403
454
  // Check if channel exists using optimized method
404
455
  const existingChannel: WorkspaceChannel | null =
@@ -1296,12 +1347,13 @@ export default class SlackUtil extends WorkspaceBase {
1296
1347
  channelName: string;
1297
1348
  projectId: ObjectID;
1298
1349
  }): Promise<WorkspaceChannel> {
1299
- if (data.channelName && data.channelName.startsWith("#")) {
1300
- data.channelName = data.channelName.substring(1);
1301
- }
1302
-
1303
- // lower case channel name
1304
- data.channelName = data.channelName.toLowerCase();
1350
+ /*
1351
+ * Sanitize channel name: Slack only allows lowercase letters, numbers,
1352
+ * hyphens, and underscores. Remove all other characters (including #).
1353
+ */
1354
+ data.channelName = data.channelName
1355
+ .toLowerCase()
1356
+ .replace(/[^a-z0-9\-_]/g, "");
1305
1357
 
1306
1358
  logger.debug("Creating channel with data:");
1307
1359
  logger.debug(data);
@@ -29,7 +29,8 @@ export default class AlertWorkspaceMessages {
29
29
  alertId: data.alertId,
30
30
  },
31
31
  notificationRuleEventType: NotificationRuleEventType.Alert,
32
- channelNameSiffix: data.alertNumber.toString(),
32
+ channelNameSiffix:
33
+ data.alertNumberWithPrefix || data.alertNumber.toString(),
33
34
  },
34
35
  );
35
36
  } catch (err) {
@@ -16,6 +16,7 @@ export default class AlertEpisodeWorkspaceMessages {
16
16
  projectId: ObjectID;
17
17
  alertEpisodeId: ObjectID;
18
18
  episodeNumber: number;
19
+ episodeNumberWithPrefix?: string;
19
20
  }): Promise<{
20
21
  channelsCreated: NotificationRuleWorkspaceChannel[];
21
22
  } | null> {
@@ -28,7 +29,8 @@ export default class AlertEpisodeWorkspaceMessages {
28
29
  alertEpisodeId: data.alertEpisodeId,
29
30
  },
30
31
  notificationRuleEventType: NotificationRuleEventType.AlertEpisode,
31
- channelNameSiffix: data.episodeNumber.toString(),
32
+ channelNameSiffix:
33
+ data.episodeNumberWithPrefix || data.episodeNumber.toString(),
32
34
  },
33
35
  );
34
36
  } catch (err) {
@@ -29,7 +29,8 @@ export default class IncidentWorkspaceMessages {
29
29
  incidentId: data.incidentId,
30
30
  },
31
31
  notificationRuleEventType: NotificationRuleEventType.Incident,
32
- channelNameSiffix: data.incidentNumber.toString(),
32
+ channelNameSiffix:
33
+ data.incidentNumberWithPrefix || data.incidentNumber.toString(),
33
34
  },
34
35
  );
35
36
  } catch (err) {
@@ -16,6 +16,7 @@ export default class IncidentEpisodeWorkspaceMessages {
16
16
  projectId: ObjectID;
17
17
  incidentEpisodeId: ObjectID;
18
18
  episodeNumber: number;
19
+ episodeNumberWithPrefix?: string;
19
20
  }): Promise<{
20
21
  channelsCreated: NotificationRuleWorkspaceChannel[];
21
22
  } | null> {
@@ -28,7 +29,8 @@ export default class IncidentEpisodeWorkspaceMessages {
28
29
  incidentEpisodeId: data.incidentEpisodeId,
29
30
  },
30
31
  notificationRuleEventType: NotificationRuleEventType.IncidentEpisode,
31
- channelNameSiffix: data.episodeNumber.toString(),
32
+ channelNameSiffix:
33
+ data.episodeNumberWithPrefix || data.episodeNumber.toString(),
32
34
  },
33
35
  );
34
36
  } catch (err) {
@@ -14,6 +14,7 @@ import Response from "../../../Server/Utils/Response";
14
14
  import Dictionary from "../../../Types/Dictionary";
15
15
  import Email from "../../../Types/Email";
16
16
  import BadDataException from "../../../Types/Exception/BadDataException";
17
+ import NotAuthenticatedException from "../../../Types/Exception/NotAuthenticatedException";
17
18
  import SsoAuthorizationException from "../../../Types/Exception/SsoAuthorizationException";
18
19
  import HashedString from "../../../Types/HashedString";
19
20
  import JSONFunctions from "../../../Types/JSONFunctions";
@@ -330,7 +331,7 @@ describe("UserMiddleware", () => {
330
331
  expect(JSONWebToken.decode).not.toHaveBeenCalled();
331
332
  });
332
333
 
333
- test("should call function 'next' and return, when accessToken can not be decoded", async () => {
334
+ test("should call Response.sendErrorResponse with NotAuthenticatedException, when accessToken can not be decoded", async () => {
334
335
  const error: Error = new Error("Invalid access token");
335
336
 
336
337
  const spyJWTDecode: jest.SpyInstance = getJestSpyOn(
@@ -342,7 +343,14 @@ describe("UserMiddleware", () => {
342
343
 
343
344
  await UserMiddleware.getUserMiddleware(req, res, next);
344
345
 
345
- expect(next).toHaveBeenCalled();
346
+ expect(Response.sendErrorResponse).toHaveBeenCalledWith(
347
+ req,
348
+ res,
349
+ new NotAuthenticatedException(
350
+ "AccessToken is invalid or expired. Please refresh your token.",
351
+ ),
352
+ );
353
+ expect(next).not.toHaveBeenCalled();
346
354
  expect(spyJWTDecode).toHaveBeenCalledWith(mockedAccessToken);
347
355
  expect(UserService.updateOneBy).not.toHaveBeenCalled();
348
356
  });
@@ -60,6 +60,13 @@ export enum CheckOn {
60
60
  SnmpOidExists = "SNMP OID Exists",
61
61
  SnmpResponseTime = "SNMP Response Time (in ms)",
62
62
  SnmpIsOnline = "SNMP Device Is Online",
63
+
64
+ // DNS monitors.
65
+ DnsResponseTime = "DNS Response Time (in ms)",
66
+ DnsIsOnline = "DNS Is Online",
67
+ DnsRecordValue = "DNS Record Value",
68
+ DnssecIsValid = "DNSSEC Is Valid",
69
+ DnsRecordExists = "DNS Record Exists",
63
70
  }
64
71
 
65
72
  export interface ServerMonitorOptions {
@@ -141,7 +148,11 @@ export class CriteriaFilterUtil {
141
148
  }): boolean {
142
149
  const { checkOn } = data;
143
150
 
144
- if (checkOn === CheckOn.IsOnline || checkOn === CheckOn.SnmpIsOnline) {
151
+ if (
152
+ checkOn === CheckOn.IsOnline ||
153
+ checkOn === CheckOn.SnmpIsOnline ||
154
+ checkOn === CheckOn.DnsIsOnline
155
+ ) {
145
156
  return false;
146
157
  }
147
158
 
@@ -149,7 +160,11 @@ export class CriteriaFilterUtil {
149
160
  return false;
150
161
  }
151
162
 
152
- if (checkOn === CheckOn.SnmpOidExists) {
163
+ if (
164
+ checkOn === CheckOn.SnmpOidExists ||
165
+ checkOn === CheckOn.DnssecIsValid ||
166
+ checkOn === CheckOn.DnsRecordExists
167
+ ) {
153
168
  return false;
154
169
  }
155
170
 
@@ -204,7 +219,9 @@ export class CriteriaFilterUtil {
204
219
  checkOn === CheckOn.MemoryUsagePercent ||
205
220
  checkOn === CheckOn.IsOnline ||
206
221
  checkOn === CheckOn.SnmpResponseTime ||
207
- checkOn === CheckOn.SnmpIsOnline
222
+ checkOn === CheckOn.SnmpIsOnline ||
223
+ checkOn === CheckOn.DnsResponseTime ||
224
+ checkOn === CheckOn.DnsIsOnline
208
225
  );
209
226
  }
210
227
  }
@@ -0,0 +1,16 @@
1
+ import DnsRecordType from "./DnsRecordType";
2
+
3
+ export interface DnsRecordResponse {
4
+ type: DnsRecordType;
5
+ value: string;
6
+ ttl?: number | undefined;
7
+ }
8
+
9
+ export default interface DnsMonitorResponse {
10
+ isOnline: boolean;
11
+ responseTimeInMs: number;
12
+ failureCause: string;
13
+ records: Array<DnsRecordResponse>;
14
+ isDnssecValid?: boolean | undefined;
15
+ isTimeout?: boolean | undefined;
16
+ }
@@ -0,0 +1,14 @@
1
+ enum DnsRecordType {
2
+ A = "A",
3
+ AAAA = "AAAA",
4
+ CNAME = "CNAME",
5
+ MX = "MX",
6
+ NS = "NS",
7
+ TXT = "TXT",
8
+ SOA = "SOA",
9
+ PTR = "PTR",
10
+ SRV = "SRV",
11
+ CAA = "CAA",
12
+ }
13
+
14
+ export default DnsRecordType;
@@ -394,6 +394,33 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
394
394
  return monitorCriteriaInstance;
395
395
  }
396
396
 
397
+ if (arg.monitorType === MonitorType.DNS) {
398
+ const monitorCriteriaInstance: MonitorCriteriaInstance =
399
+ new MonitorCriteriaInstance();
400
+
401
+ monitorCriteriaInstance.data = {
402
+ id: ObjectID.generate().toString(),
403
+ monitorStatusId: arg.monitorStatusId,
404
+ filterCondition: FilterCondition.All,
405
+ filters: [
406
+ {
407
+ checkOn: CheckOn.DnsIsOnline,
408
+ filterType: FilterType.True,
409
+ value: undefined,
410
+ },
411
+ ],
412
+ incidents: [],
413
+ alerts: [],
414
+ createAlerts: false,
415
+ changeMonitorStatus: true,
416
+ createIncidents: false,
417
+ name: `Check if ${arg.monitorName} is online`,
418
+ description: `This criteria checks if the ${arg.monitorName} DNS resolution is online`,
419
+ };
420
+
421
+ return monitorCriteriaInstance;
422
+ }
423
+
397
424
  return null;
398
425
  }
399
426
 
@@ -495,6 +522,46 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
495
522
  };
496
523
  }
497
524
 
525
+ if (arg.monitorType === MonitorType.DNS) {
526
+ monitorCriteriaInstance.data = {
527
+ id: ObjectID.generate().toString(),
528
+ monitorStatusId: arg.monitorStatusId,
529
+ filterCondition: FilterCondition.Any,
530
+ filters: [
531
+ {
532
+ checkOn: CheckOn.DnsIsOnline,
533
+ filterType: FilterType.False,
534
+ value: undefined,
535
+ },
536
+ ],
537
+ incidents: [
538
+ {
539
+ title: `${arg.monitorName} is offline`,
540
+ description: `${arg.monitorName} DNS resolution is currently failing.`,
541
+ incidentSeverityId: arg.incidentSeverityId,
542
+ autoResolveIncident: true,
543
+ id: ObjectID.generate().toString(),
544
+ onCallPolicyIds: [],
545
+ },
546
+ ],
547
+ changeMonitorStatus: true,
548
+ createIncidents: true,
549
+ createAlerts: false,
550
+ alerts: [
551
+ {
552
+ title: `${arg.monitorName} is offline`,
553
+ description: `${arg.monitorName} DNS resolution is currently failing.`,
554
+ alertSeverityId: arg.alertSeverityId,
555
+ autoResolveAlert: true,
556
+ id: ObjectID.generate().toString(),
557
+ onCallPolicyIds: [],
558
+ },
559
+ ],
560
+ name: `Check if ${arg.monitorName} is offline`,
561
+ description: `This criteria checks if the ${arg.monitorName} DNS resolution is failing`,
562
+ };
563
+ }
564
+
498
565
  if (
499
566
  arg.monitorType === MonitorType.API ||
500
567
  arg.monitorType === MonitorType.Website