@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
@@ -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) {
@@ -127,6 +127,14 @@ export class Service extends DatabaseService<Model> {
127
127
  monitorDestination = `${monitorDestination}:${port}`;
128
128
  }
129
129
  }
130
+
131
+ // For DNS monitors, use the queryName from dnsMonitor config
132
+ if (monitorType === MonitorType.DNS && firstStep?.data?.dnsMonitor) {
133
+ monitorDestination = firstStep.data.dnsMonitor.queryName || "";
134
+ if (firstStep.data.dnsMonitor.hostname) {
135
+ monitorDestination = `${monitorDestination} @${firstStep.data.dnsMonitor.hostname}`;
136
+ }
137
+ }
130
138
  }
131
139
  }
132
140
 
@@ -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
 
@@ -1785,9 +1805,18 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
1785
1805
  logger.debug("New channel template name:");
1786
1806
  logger.debug(notificationChannels);
1787
1807
 
1808
+ /*
1809
+ * Sanitize the suffix for workspace channel names.
1810
+ * When no custom prefix is set, the default "#" prefix (e.g. "#42") produces
1811
+ * invalid Slack channel names (e.g. "oneuptime-alert-#42"). Strip characters
1812
+ * that are not valid in Slack/Teams channel names.
1813
+ */
1814
+ const sanitizedSuffix: string = data.channelNameSiffix
1815
+ .toLowerCase()
1816
+ .replace(/[^a-z0-9\-_]/g, "");
1817
+
1788
1818
  // add suffix and then check if it is already added or not.
1789
- const channelName: string =
1790
- notificationChannels + data.channelNameSiffix;
1819
+ const channelName: string = notificationChannels + sanitizedSuffix;
1791
1820
 
1792
1821
  logger.debug("Final channel name with suffix:");
1793
1822
  logger.debug(channelName);
@@ -0,0 +1,183 @@
1
+ import DataToProcess from "../DataToProcess";
2
+ import CompareCriteria from "./CompareCriteria";
3
+ import {
4
+ CheckOn,
5
+ CriteriaFilter,
6
+ FilterType,
7
+ } from "../../../../Types/Monitor/CriteriaFilter";
8
+ import DnsMonitorResponse from "../../../../Types/Monitor/DnsMonitor/DnsMonitorResponse";
9
+ import ProbeMonitorResponse from "../../../../Types/Probe/ProbeMonitorResponse";
10
+ import EvaluateOverTime from "./EvaluateOverTime";
11
+ import CaptureSpan from "../../Telemetry/CaptureSpan";
12
+ import logger from "../../Logger";
13
+
14
+ export default class DnsMonitorCriteria {
15
+ @CaptureSpan()
16
+ public static async isMonitorInstanceCriteriaFilterMet(input: {
17
+ dataToProcess: DataToProcess;
18
+ criteriaFilter: CriteriaFilter;
19
+ }): Promise<string | null> {
20
+ let threshold: number | string | undefined | null =
21
+ input.criteriaFilter.value;
22
+
23
+ const dataToProcess: ProbeMonitorResponse =
24
+ input.dataToProcess as ProbeMonitorResponse;
25
+
26
+ const dnsResponse: DnsMonitorResponse | undefined =
27
+ dataToProcess.dnsResponse;
28
+
29
+ let overTimeValue: Array<number | boolean> | number | boolean | undefined =
30
+ undefined;
31
+
32
+ if (
33
+ input.criteriaFilter.evaluateOverTime &&
34
+ input.criteriaFilter.evaluateOverTimeOptions
35
+ ) {
36
+ try {
37
+ overTimeValue = await EvaluateOverTime.getValueOverTime({
38
+ projectId: (input.dataToProcess as ProbeMonitorResponse).projectId,
39
+ monitorId: input.dataToProcess.monitorId!,
40
+ evaluateOverTimeOptions: input.criteriaFilter.evaluateOverTimeOptions,
41
+ metricType: input.criteriaFilter.checkOn,
42
+ });
43
+
44
+ if (Array.isArray(overTimeValue) && overTimeValue.length === 0) {
45
+ overTimeValue = undefined;
46
+ }
47
+ } catch (err) {
48
+ logger.error(
49
+ `Error in getting over time value for ${input.criteriaFilter.checkOn}`,
50
+ );
51
+ logger.error(err);
52
+ overTimeValue = undefined;
53
+ }
54
+ }
55
+
56
+ // Check if DNS is online
57
+ if (input.criteriaFilter.checkOn === CheckOn.DnsIsOnline) {
58
+ const currentIsOnline: boolean | Array<boolean> =
59
+ (overTimeValue as Array<boolean>) ||
60
+ (input.dataToProcess as ProbeMonitorResponse).isOnline;
61
+
62
+ return CompareCriteria.compareCriteriaBoolean({
63
+ value: currentIsOnline,
64
+ criteriaFilter: input.criteriaFilter,
65
+ });
66
+ }
67
+
68
+ // Check DNS response time
69
+ if (input.criteriaFilter.checkOn === CheckOn.DnsResponseTime) {
70
+ threshold = CompareCriteria.convertToNumber(threshold);
71
+
72
+ if (threshold === null || threshold === undefined) {
73
+ return null;
74
+ }
75
+
76
+ const currentResponseTime: number | Array<number> =
77
+ (overTimeValue as Array<number>) ||
78
+ dnsResponse?.responseTimeInMs ||
79
+ (input.dataToProcess as ProbeMonitorResponse).responseTimeInMs;
80
+
81
+ if (currentResponseTime === null || currentResponseTime === undefined) {
82
+ return null;
83
+ }
84
+
85
+ return CompareCriteria.compareCriteriaNumbers({
86
+ value: currentResponseTime,
87
+ threshold: threshold as number,
88
+ criteriaFilter: input.criteriaFilter,
89
+ });
90
+ }
91
+
92
+ // Check if DNS record exists
93
+ if (input.criteriaFilter.checkOn === CheckOn.DnsRecordExists) {
94
+ const exists: boolean = Boolean(
95
+ dnsResponse?.records && dnsResponse.records.length > 0,
96
+ );
97
+
98
+ const isTrue: boolean =
99
+ input.criteriaFilter.filterType === FilterType.True;
100
+ const isFalse: boolean =
101
+ input.criteriaFilter.filterType === FilterType.False;
102
+
103
+ if (exists && isTrue) {
104
+ return `DNS records exist for the query.`;
105
+ }
106
+
107
+ if (!exists && isFalse) {
108
+ return `No DNS records found for the query.`;
109
+ }
110
+
111
+ return null;
112
+ }
113
+
114
+ // Check DNSSEC validity
115
+ if (input.criteriaFilter.checkOn === CheckOn.DnssecIsValid) {
116
+ const isTrue: boolean =
117
+ input.criteriaFilter.filterType === FilterType.True;
118
+ const isFalse: boolean =
119
+ input.criteriaFilter.filterType === FilterType.False;
120
+
121
+ if (dnsResponse?.isDnssecValid === undefined) {
122
+ return null;
123
+ }
124
+
125
+ if (dnsResponse.isDnssecValid && isTrue) {
126
+ return `DNSSEC is valid.`;
127
+ }
128
+
129
+ if (!dnsResponse.isDnssecValid && isFalse) {
130
+ return `DNSSEC is not valid.`;
131
+ }
132
+
133
+ return null;
134
+ }
135
+
136
+ // Check DNS record value
137
+ if (input.criteriaFilter.checkOn === CheckOn.DnsRecordValue) {
138
+ if (!dnsResponse?.records || dnsResponse.records.length === 0) {
139
+ return null;
140
+ }
141
+
142
+ // Check if any record value matches the criteria
143
+ for (const record of dnsResponse.records) {
144
+ const recordValue: string = record.value;
145
+
146
+ // Try numeric comparison first
147
+ if (
148
+ typeof threshold === "number" ||
149
+ (typeof threshold === "string" && !isNaN(Number(threshold)))
150
+ ) {
151
+ const numericThreshold: number | null =
152
+ CompareCriteria.convertToNumber(threshold);
153
+
154
+ if (numericThreshold !== null && !isNaN(Number(recordValue))) {
155
+ const result: string | null =
156
+ CompareCriteria.compareCriteriaNumbers({
157
+ value: Number(recordValue),
158
+ threshold: numericThreshold,
159
+ criteriaFilter: input.criteriaFilter,
160
+ });
161
+
162
+ if (result) {
163
+ return `DNS record (${record.type}): ${result}`;
164
+ }
165
+ }
166
+ }
167
+
168
+ // String comparison
169
+ const result: string | null = CompareCriteria.compareCriteriaStrings({
170
+ value: recordValue,
171
+ threshold: String(threshold),
172
+ criteriaFilter: input.criteriaFilter,
173
+ });
174
+
175
+ if (result) {
176
+ return `DNS record (${record.type}): ${result}`;
177
+ }
178
+ }
179
+ }
180
+
181
+ return null;
182
+ }
183
+ }
@@ -12,6 +12,7 @@ import MetricMonitorCriteria from "./Criteria/MetricMonitorCriteria";
12
12
  import TraceMonitorCriteria from "./Criteria/TraceMonitorCriteria";
13
13
  import ExceptionMonitorCriteria from "./Criteria/ExceptionMonitorCriteria";
14
14
  import SnmpMonitorCriteria from "./Criteria/SnmpMonitorCriteria";
15
+ import DnsMonitorCriteria from "./Criteria/DnsMonitorCriteria";
15
16
  import MonitorCriteriaMessageBuilder from "./MonitorCriteriaMessageBuilder";
16
17
  import MonitorCriteriaDataExtractor from "./MonitorCriteriaDataExtractor";
17
18
  import MonitorCriteriaMessageFormatter from "./MonitorCriteriaMessageFormatter";
@@ -493,6 +494,18 @@ ${contextBlock}
493
494
  }
494
495
  }
495
496
 
497
+ if (input.monitor.monitorType === MonitorType.DNS) {
498
+ const dnsMonitorResult: string | null =
499
+ await DnsMonitorCriteria.isMonitorInstanceCriteriaFilterMet({
500
+ dataToProcess: input.dataToProcess,
501
+ criteriaFilter: input.criteriaFilter,
502
+ });
503
+
504
+ if (dnsMonitorResult) {
505
+ return dnsMonitorResult;
506
+ }
507
+ }
508
+
496
509
  return null;
497
510
  }
498
511
 
@@ -14,6 +14,9 @@ import SyntheticMonitorResponse from "../../../Types/Monitor/SyntheticMonitors/S
14
14
  import SnmpMonitorResponse, {
15
15
  SnmpOidResponse,
16
16
  } from "../../../Types/Monitor/SnmpMonitor/SnmpMonitorResponse";
17
+ import DnsMonitorResponse, {
18
+ DnsRecordResponse,
19
+ } from "../../../Types/Monitor/DnsMonitor/DnsMonitorResponse";
17
20
  import Typeof from "../../../Types/Typeof";
18
21
  import VMUtil from "../VM/VMAPI";
19
22
  import DataToProcess from "./DataToProcess";
@@ -240,6 +243,40 @@ export default class MonitorTemplateUtil {
240
243
  }
241
244
  }
242
245
  }
246
+
247
+ if (data.monitorType === MonitorType.DNS) {
248
+ const dnsResponse: DnsMonitorResponse | undefined = (
249
+ data.dataToProcess as ProbeMonitorResponse
250
+ ).dnsResponse;
251
+
252
+ storageMap = {
253
+ isOnline: (data.dataToProcess as ProbeMonitorResponse).isOnline,
254
+ responseTimeInMs: dnsResponse?.responseTimeInMs,
255
+ failureCause: dnsResponse?.failureCause,
256
+ isTimeout: dnsResponse?.isTimeout,
257
+ isDnssecValid: dnsResponse?.isDnssecValid,
258
+ } as JSONObject;
259
+
260
+ // Add DNS records
261
+ if (dnsResponse?.records) {
262
+ storageMap["records"] = dnsResponse.records.map(
263
+ (record: DnsRecordResponse) => {
264
+ return {
265
+ type: record.type,
266
+ value: record.value,
267
+ ttl: record.ttl,
268
+ };
269
+ },
270
+ );
271
+
272
+ // Add record values as a flat array for easier templating
273
+ storageMap["recordValues"] = dnsResponse.records.map(
274
+ (record: DnsRecordResponse) => {
275
+ return record.value;
276
+ },
277
+ );
278
+ }
279
+ }
243
280
  } catch (err) {
244
281
  logger.error(err);
245
282
  }
@@ -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
  },
@@ -780,13 +780,14 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
780
780
 
781
781
  const workspaceChannels: Array<WorkspaceChannel> = [];
782
782
 
783
- for (let channelName of data.channelNames) {
784
- // Normalize channel name - Teams has different naming requirements
785
- if (channelName && channelName.startsWith("#")) {
786
- channelName = channelName.substring(1);
787
- }
788
- // Teams channels cannot have spaces in the name for some operations
789
- const normalizedChannelName: string = channelName.replace(/\s+/g, "-");
783
+ for (const channelName of data.channelNames) {
784
+ /*
785
+ * Normalize channel name: replace spaces with hyphens, then strip
786
+ * characters not valid in Teams channel names (e.g. #, %, &, *, etc.).
787
+ */
788
+ const normalizedChannelName: string = channelName
789
+ .replace(/\s+/g, "-")
790
+ .replace(/[^a-zA-Z0-9\-_]/g, "");
790
791
 
791
792
  // Check if channel exists
792
793
  const existingChannel: WorkspaceChannel | null =
@@ -839,6 +840,9 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
839
840
  }): Promise<WorkspaceChannel> {
840
841
  const teamId: string = data.teamId;
841
842
 
843
+ // Sanitize channel name: strip characters not valid in Teams channel names.
844
+ data.channelName = data.channelName.replace(/[^a-zA-Z0-9\-_\s]/g, "");
845
+
842
846
  // Get valid access token
843
847
  const accessToken: string = await this.getValidAccessToken({
844
848
  authToken: data.authToken,
@@ -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",