@oneuptime/common 9.4.12 → 9.4.13

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 (256) hide show
  1. package/Models/DatabaseModels/Incident.ts +77 -0
  2. package/Models/DatabaseModels/IncidentEpisode.ts +1223 -0
  3. package/Models/DatabaseModels/IncidentEpisodeFeed.ts +533 -0
  4. package/Models/DatabaseModels/IncidentEpisodeInternalNote.ts +456 -0
  5. package/Models/DatabaseModels/IncidentEpisodeMember.ts +587 -0
  6. package/Models/DatabaseModels/IncidentEpisodeOwnerTeam.ts +421 -0
  7. package/Models/DatabaseModels/IncidentEpisodeOwnerUser.ts +419 -0
  8. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +524 -0
  9. package/Models/DatabaseModels/IncidentGroupingRule.ts +1430 -0
  10. package/Models/DatabaseModels/Index.ts +18 -0
  11. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +70 -0
  12. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.ts +59 -0
  13. package/Models/DatabaseModels/UserOnCallLog.ts +48 -0
  14. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +49 -0
  15. package/Models/DatabaseModels/WorkspaceNotificationLog.ts +57 -0
  16. package/Server/API/IncidentEpisodeAPI.ts +150 -0
  17. package/Server/API/SlackAPI.ts +23 -0
  18. package/Server/API/UserOnCallLogTimelineAPI.ts +24 -4
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1769626069479-MigrationName.ts +729 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1769629928240-MigrationName.ts +261 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1769676117342-RenameEvaluateOverTimeInCriteriaFilter.ts +28 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  23. package/Server/Services/BillingService.ts +1 -3
  24. package/Server/Services/CallService.ts +1 -0
  25. package/Server/Services/IncidentEpisodeFeedService.ts +94 -0
  26. package/Server/Services/IncidentEpisodeInternalNoteService.ts +71 -0
  27. package/Server/Services/IncidentEpisodeMemberService.ts +321 -0
  28. package/Server/Services/IncidentEpisodeOwnerTeamService.ts +10 -0
  29. package/Server/Services/IncidentEpisodeOwnerUserService.ts +10 -0
  30. package/Server/Services/IncidentEpisodeService.ts +1045 -0
  31. package/Server/Services/IncidentEpisodeStateTimelineService.ts +566 -0
  32. package/Server/Services/IncidentGroupingEngineService.ts +1047 -0
  33. package/Server/Services/IncidentGroupingRuleService.ts +14 -0
  34. package/Server/Services/IncidentService.ts +11 -0
  35. package/Server/Services/Index.ts +18 -0
  36. package/Server/Services/MailService.ts +1 -0
  37. package/Server/Services/MonitorService.ts +9 -0
  38. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +18 -0
  39. package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +64 -2
  40. package/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +26 -1
  41. package/Server/Services/OnCallDutyPolicyService.ts +15 -0
  42. package/Server/Services/SmsService.ts +1 -0
  43. package/Server/Services/UserNotificationRuleService.ts +48 -2
  44. package/Server/Services/UserNotificationSettingService.ts +23 -0
  45. package/Server/Services/UserOnCallLogService.ts +41 -4
  46. package/Server/Services/WhatsAppService.ts +1 -0
  47. package/Server/Services/WorkspaceNotificationLogService.ts +16 -0
  48. package/Server/Services/WorkspaceNotificationRuleService.ts +116 -0
  49. package/Server/Utils/AI/IncidentEpisodeAIContextBuilder.ts +490 -0
  50. package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +1 -1
  51. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
  52. package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +1 -1
  53. package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +1 -1
  54. package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +2 -2
  55. package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +182 -0
  56. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +13 -0
  57. package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +1 -1
  58. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +37 -0
  59. package/Server/Utils/PushNotificationUtil.ts +31 -0
  60. package/Server/Utils/WhatsAppTemplateUtil.ts +14 -0
  61. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ActionTypes.ts +18 -0
  62. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +702 -0
  63. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +20 -0
  64. package/Server/Utils/Workspace/Slack/Actions/ActionTypes.ts +11 -0
  65. package/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.ts +918 -0
  66. package/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.ts +120 -0
  67. package/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.ts +74 -0
  68. package/Types/Email/EmailTemplateType.ts +6 -0
  69. package/Types/Monitor/CriteriaFilter.ts +24 -4
  70. package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
  71. package/Types/Monitor/MonitorStep.ts +37 -0
  72. package/Types/Monitor/MonitorStepSnmpMonitor.ts +102 -0
  73. package/Types/Monitor/MonitorType.ts +15 -2
  74. package/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.ts +8 -0
  75. package/Types/Monitor/SnmpMonitor/SnmpDataType.ts +21 -0
  76. package/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.ts +16 -0
  77. package/Types/Monitor/SnmpMonitor/SnmpOid.ts +60 -0
  78. package/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.ts +7 -0
  79. package/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.ts +7 -0
  80. package/Types/Monitor/SnmpMonitor/SnmpV3Auth.ts +12 -0
  81. package/Types/Monitor/SnmpMonitor/SnmpVersion.ts +7 -0
  82. package/Types/NotificationSetting/NotificationSettingEventType.ts +7 -0
  83. package/Types/Permission.ts +311 -0
  84. package/Types/Probe/ProbeMonitorResponse.ts +2 -0
  85. package/Types/UserNotification/UserNotificationEventType.ts +1 -0
  86. package/Types/WhatsApp/WhatsAppTemplates.ts +24 -0
  87. package/Types/Workspace/NotificationRules/EventType.ts +1 -0
  88. package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +38 -1
  89. package/Utils/Monitor/MonitorMetricType.ts +2 -1
  90. package/build/dist/Models/DatabaseModels/Incident.js +78 -0
  91. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  92. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1250 -0
  93. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -0
  94. package/build/dist/Models/DatabaseModels/IncidentEpisodeFeed.js +555 -0
  95. package/build/dist/Models/DatabaseModels/IncidentEpisodeFeed.js.map +1 -0
  96. package/build/dist/Models/DatabaseModels/IncidentEpisodeInternalNote.js +467 -0
  97. package/build/dist/Models/DatabaseModels/IncidentEpisodeInternalNote.js.map +1 -0
  98. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +607 -0
  99. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -0
  100. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerTeam.js +437 -0
  101. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerTeam.js.map +1 -0
  102. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerUser.js +436 -0
  103. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerUser.js.map +1 -0
  104. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +546 -0
  105. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -0
  106. package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js +1437 -0
  107. package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js.map +1 -0
  108. package/build/dist/Models/DatabaseModels/Index.js +16 -0
  109. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  110. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +69 -0
  111. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.js +58 -0
  113. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.js.map +1 -1
  114. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +47 -0
  115. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  116. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +48 -0
  117. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  118. package/build/dist/Models/DatabaseModels/WorkspaceNotificationLog.js +58 -0
  119. package/build/dist/Models/DatabaseModels/WorkspaceNotificationLog.js.map +1 -1
  120. package/build/dist/Server/API/IncidentEpisodeAPI.js +97 -0
  121. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -0
  122. package/build/dist/Server/API/SlackAPI.js +18 -0
  123. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  124. package/build/dist/Server/API/UserOnCallLogTimelineAPI.js +30 -10
  125. package/build/dist/Server/API/UserOnCallLogTimelineAPI.js.map +1 -1
  126. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769626069479-MigrationName.js +256 -0
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769626069479-MigrationName.js.map +1 -0
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769629928240-MigrationName.js +96 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769629928240-MigrationName.js.map +1 -0
  130. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769676117342-RenameEvaluateOverTimeInCriteriaFilter.js +25 -0
  131. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769676117342-RenameEvaluateOverTimeInCriteriaFilter.js.map +1 -0
  132. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  133. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  134. package/build/dist/Server/Services/BillingService.js +1 -2
  135. package/build/dist/Server/Services/BillingService.js.map +1 -1
  136. package/build/dist/Server/Services/CallService.js.map +1 -1
  137. package/build/dist/Server/Services/IncidentEpisodeFeedService.js +83 -0
  138. package/build/dist/Server/Services/IncidentEpisodeFeedService.js.map +1 -0
  139. package/build/dist/Server/Services/IncidentEpisodeInternalNoteService.js +70 -0
  140. package/build/dist/Server/Services/IncidentEpisodeInternalNoteService.js.map +1 -0
  141. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +298 -0
  142. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -0
  143. package/build/dist/Server/Services/IncidentEpisodeOwnerTeamService.js +9 -0
  144. package/build/dist/Server/Services/IncidentEpisodeOwnerTeamService.js.map +1 -0
  145. package/build/dist/Server/Services/IncidentEpisodeOwnerUserService.js +9 -0
  146. package/build/dist/Server/Services/IncidentEpisodeOwnerUserService.js.map +1 -0
  147. package/build/dist/Server/Services/IncidentEpisodeService.js +933 -0
  148. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -0
  149. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +498 -0
  150. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -0
  151. package/build/dist/Server/Services/IncidentGroupingEngineService.js +799 -0
  152. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -0
  153. package/build/dist/Server/Services/IncidentGroupingRuleService.js +13 -0
  154. package/build/dist/Server/Services/IncidentGroupingRuleService.js.map +1 -0
  155. package/build/dist/Server/Services/IncidentService.js +10 -0
  156. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  157. package/build/dist/Server/Services/Index.js +16 -0
  158. package/build/dist/Server/Services/Index.js.map +1 -1
  159. package/build/dist/Server/Services/MailService.js.map +1 -1
  160. package/build/dist/Server/Services/MonitorService.js +9 -1
  161. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  162. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +10 -0
  163. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  164. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +48 -2
  165. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
  166. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js +20 -1
  167. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js.map +1 -1
  168. package/build/dist/Server/Services/OnCallDutyPolicyService.js +8 -0
  169. package/build/dist/Server/Services/OnCallDutyPolicyService.js.map +1 -1
  170. package/build/dist/Server/Services/SmsService.js.map +1 -1
  171. package/build/dist/Server/Services/UserNotificationRuleService.js +39 -2
  172. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  173. package/build/dist/Server/Services/UserNotificationSettingService.js +9 -0
  174. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  175. package/build/dist/Server/Services/UserOnCallLogService.js +35 -3
  176. package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
  177. package/build/dist/Server/Services/WhatsAppService.js.map +1 -1
  178. package/build/dist/Server/Services/WorkspaceNotificationLogService.js +12 -0
  179. package/build/dist/Server/Services/WorkspaceNotificationLogService.js.map +1 -1
  180. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +95 -1
  181. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  182. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js +402 -0
  183. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js.map +1 -0
  184. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +1 -1
  185. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
  186. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +1 -1
  187. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +1 -1
  188. package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +2 -2
  189. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +135 -0
  190. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -0
  191. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +10 -0
  192. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  193. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +1 -1
  194. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +26 -0
  195. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  196. package/build/dist/Server/Utils/PushNotificationUtil.js +20 -0
  197. package/build/dist/Server/Utils/PushNotificationUtil.js.map +1 -1
  198. package/build/dist/Server/Utils/WhatsAppTemplateUtil.js +8 -0
  199. package/build/dist/Server/Utils/WhatsAppTemplateUtil.js.map +1 -1
  200. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ActionTypes.js +17 -0
  201. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ActionTypes.js.map +1 -1
  202. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +547 -0
  203. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -0
  204. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +15 -0
  205. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  206. package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js +10 -0
  207. package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js.map +1 -1
  208. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js +651 -0
  209. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js.map +1 -0
  210. package/build/dist/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.js +100 -0
  211. package/build/dist/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.js.map +1 -0
  212. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.js +70 -0
  213. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.js.map +1 -0
  214. package/build/dist/Types/Email/EmailTemplateType.js +5 -0
  215. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  216. package/build/dist/Types/Monitor/CriteriaFilter.js +16 -3
  217. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  218. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
  219. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  220. package/build/dist/Types/Monitor/MonitorStep.js +26 -0
  221. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  222. package/build/dist/Types/Monitor/MonitorStepSnmpMonitor.js +77 -0
  223. package/build/dist/Types/Monitor/MonitorStepSnmpMonitor.js.map +1 -0
  224. package/build/dist/Types/Monitor/MonitorType.js +13 -2
  225. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  226. package/build/dist/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.js +9 -0
  227. package/build/dist/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.js.map +1 -0
  228. package/build/dist/Types/Monitor/SnmpMonitor/SnmpDataType.js +22 -0
  229. package/build/dist/Types/Monitor/SnmpMonitor/SnmpDataType.js.map +1 -0
  230. package/build/dist/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.js +2 -0
  231. package/build/dist/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.js.map +1 -0
  232. package/build/dist/Types/Monitor/SnmpMonitor/SnmpOid.js +55 -0
  233. package/build/dist/Types/Monitor/SnmpMonitor/SnmpOid.js.map +1 -0
  234. package/build/dist/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.js +8 -0
  235. package/build/dist/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.js.map +1 -0
  236. package/build/dist/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.js +8 -0
  237. package/build/dist/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.js.map +1 -0
  238. package/build/dist/Types/Monitor/SnmpMonitor/SnmpV3Auth.js +2 -0
  239. package/build/dist/Types/Monitor/SnmpMonitor/SnmpV3Auth.js.map +1 -0
  240. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js +8 -0
  241. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js.map +1 -0
  242. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +5 -0
  243. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  244. package/build/dist/Types/Permission.js +264 -0
  245. package/build/dist/Types/Permission.js.map +1 -1
  246. package/build/dist/Types/UserNotification/UserNotificationEventType.js +1 -0
  247. package/build/dist/Types/UserNotification/UserNotificationEventType.js.map +1 -1
  248. package/build/dist/Types/WhatsApp/WhatsAppTemplates.js +15 -0
  249. package/build/dist/Types/WhatsApp/WhatsAppTemplates.js.map +1 -1
  250. package/build/dist/Types/Workspace/NotificationRules/EventType.js +1 -0
  251. package/build/dist/Types/Workspace/NotificationRules/EventType.js.map +1 -1
  252. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +33 -1
  253. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
  254. package/build/dist/Utils/Monitor/MonitorMetricType.js +2 -1
  255. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  256. package/package.json +1 -1
@@ -0,0 +1,1045 @@
1
+ import CreateBy from "../Types/Database/CreateBy";
2
+ import { OnCreate } from "../Types/Database/Hooks";
3
+ import DatabaseService from "./DatabaseService";
4
+ import IncidentStateService from "./IncidentStateService";
5
+ import BadDataException from "../../Types/Exception/BadDataException";
6
+ import ObjectID from "../../Types/ObjectID";
7
+ import PositiveNumber from "../../Types/PositiveNumber";
8
+ import Model from "../../Models/DatabaseModels/IncidentEpisode";
9
+ import IncidentState from "../../Models/DatabaseModels/IncidentState";
10
+ import IncidentSeverity from "../../Models/DatabaseModels/IncidentSeverity";
11
+ import SortOrder from "../../Types/BaseDatabase/SortOrder";
12
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
13
+ import logger from "../Utils/Logger";
14
+ import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
15
+ import IncidentEpisodeStateTimeline from "../../Models/DatabaseModels/IncidentEpisodeStateTimeline";
16
+ import IncidentEpisodeStateTimelineService from "./IncidentEpisodeStateTimelineService";
17
+ import { IsBillingEnabled } from "../EnvironmentConfig";
18
+ import OneUptimeDate from "../../Types/Date";
19
+ import IncidentEpisodeFeedService from "./IncidentEpisodeFeedService";
20
+ import { IncidentEpisodeFeedEventType } from "../../Models/DatabaseModels/IncidentEpisodeFeed";
21
+ import { Red500, Yellow500, Purple500 } from "../../Types/BrandColors";
22
+ import URL from "../../Types/API/URL";
23
+ import DatabaseConfig from "../DatabaseConfig";
24
+ import IncidentSeverityService from "./IncidentSeverityService";
25
+ import IncidentEpisodeMemberService from "./IncidentEpisodeMemberService";
26
+ import IncidentEpisodeOwnerUserService from "./IncidentEpisodeOwnerUserService";
27
+ import IncidentEpisodeOwnerTeamService from "./IncidentEpisodeOwnerTeamService";
28
+ import TeamMemberService from "./TeamMemberService";
29
+ import IncidentEpisodeOwnerUser from "../../Models/DatabaseModels/IncidentEpisodeOwnerUser";
30
+ import IncidentEpisodeOwnerTeam from "../../Models/DatabaseModels/IncidentEpisodeOwnerTeam";
31
+ import IncidentEpisodeMember from "../../Models/DatabaseModels/IncidentEpisodeMember";
32
+ import User from "../../Models/DatabaseModels/User";
33
+ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
34
+ import NotificationRuleWorkspaceChannel from "../../Types/Workspace/NotificationRules/NotificationRuleWorkspaceChannel";
35
+ import WorkspaceType from "../../Types/Workspace/WorkspaceType";
36
+ import IncidentService from "./IncidentService";
37
+ import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
38
+ import OnCallDutyPolicyService from "./OnCallDutyPolicyService";
39
+ import OnCallDutyPolicy from "../../Models/DatabaseModels/OnCallDutyPolicy";
40
+ import UserNotificationEventType from "../../Types/UserNotification/UserNotificationEventType";
41
+
42
+ export class Service extends DatabaseService<Model> {
43
+ public constructor() {
44
+ super(Model);
45
+ if (IsBillingEnabled) {
46
+ this.hardDeleteItemsOlderThanInDays("createdAt", 3 * 365); // 3 years
47
+ }
48
+ }
49
+
50
+ @CaptureSpan()
51
+ public async getExistingEpisodeNumberForProject(data: {
52
+ projectId: ObjectID;
53
+ }): Promise<number> {
54
+ const lastEpisode: Model | null = await this.findOneBy({
55
+ query: {
56
+ projectId: data.projectId,
57
+ },
58
+ select: {
59
+ episodeNumber: true,
60
+ },
61
+ sort: {
62
+ episodeNumber: SortOrder.Descending,
63
+ },
64
+ props: {
65
+ isRoot: true,
66
+ },
67
+ });
68
+
69
+ if (!lastEpisode) {
70
+ return 0;
71
+ }
72
+
73
+ return lastEpisode.episodeNumber ? Number(lastEpisode.episodeNumber) : 0;
74
+ }
75
+
76
+ @CaptureSpan()
77
+ protected override async onBeforeCreate(
78
+ createBy: CreateBy<Model>,
79
+ ): Promise<OnCreate<Model>> {
80
+ if (!createBy.props.tenantId && !createBy.props.isRoot) {
81
+ throw new BadDataException(
82
+ "ProjectId required to create incident episode.",
83
+ );
84
+ }
85
+
86
+ const projectId: ObjectID =
87
+ createBy.props.tenantId || createBy.data.projectId!;
88
+
89
+ let mutex: SemaphoreMutex | null = null;
90
+
91
+ try {
92
+ // Acquire mutex to prevent race conditions when generating episode numbers
93
+ try {
94
+ mutex = await Semaphore.lock({
95
+ key: projectId.toString(),
96
+ namespace: "IncidentEpisode.create",
97
+ });
98
+ } catch (err) {
99
+ logger.error(err);
100
+ }
101
+
102
+ // Get the created state for episodes
103
+ const incidentState: IncidentState | null =
104
+ await IncidentStateService.findOneBy({
105
+ query: {
106
+ projectId: projectId,
107
+ isCreatedState: true,
108
+ },
109
+ select: {
110
+ _id: true,
111
+ },
112
+ props: {
113
+ isRoot: true,
114
+ },
115
+ });
116
+
117
+ if (!incidentState || !incidentState.id) {
118
+ throw new BadDataException(
119
+ "Created incident state not found for this project. Please add created incident state from settings.",
120
+ );
121
+ }
122
+
123
+ createBy.data.currentIncidentStateId = incidentState.id;
124
+
125
+ // Auto-generate episode number
126
+ const episodeNumberForThisEpisode: number =
127
+ (await this.getExistingEpisodeNumberForProject({
128
+ projectId: projectId,
129
+ })) + 1;
130
+
131
+ createBy.data.episodeNumber = episodeNumberForThisEpisode;
132
+
133
+ // Set initial lastIncidentAddedAt
134
+ if (!createBy.data.lastIncidentAddedAt) {
135
+ createBy.data.lastIncidentAddedAt = OneUptimeDate.getCurrentDate();
136
+ }
137
+
138
+ return { createBy, carryForward: { mutex } };
139
+ } catch (error) {
140
+ // Release the mutex if it was acquired and an error occurred
141
+ if (mutex) {
142
+ try {
143
+ await Semaphore.release(mutex);
144
+ } catch (err) {
145
+ logger.error(err);
146
+ }
147
+ }
148
+ throw error;
149
+ }
150
+ }
151
+
152
+ @CaptureSpan()
153
+ protected override async onCreateSuccess(
154
+ onCreate: OnCreate<Model>,
155
+ createdItem: Model,
156
+ ): Promise<Model> {
157
+ // Release the mutex acquired in onBeforeCreate
158
+ const mutex: SemaphoreMutex | null = onCreate.carryForward?.mutex || null;
159
+ if (mutex) {
160
+ try {
161
+ await Semaphore.release(mutex);
162
+ } catch (err) {
163
+ logger.error(err);
164
+ }
165
+ }
166
+
167
+ if (!createdItem.projectId) {
168
+ throw new BadDataException("projectId is required");
169
+ }
170
+
171
+ if (!createdItem.id) {
172
+ throw new BadDataException("id is required");
173
+ }
174
+
175
+ if (!createdItem.currentIncidentStateId) {
176
+ throw new BadDataException("currentIncidentStateId is required");
177
+ }
178
+
179
+ // Create initial state timeline entry
180
+ Promise.resolve()
181
+ .then(async () => {
182
+ try {
183
+ await this.changeEpisodeState({
184
+ projectId: createdItem.projectId!,
185
+ episodeId: createdItem.id!,
186
+ incidentStateId: createdItem.currentIncidentStateId!,
187
+ notifyOwners: false,
188
+ rootCause: undefined,
189
+ props: {
190
+ isRoot: true,
191
+ },
192
+ });
193
+ } catch (error) {
194
+ logger.error(
195
+ `Handle episode state change failed in IncidentEpisodeService.onCreateSuccess: ${error}`,
196
+ );
197
+ }
198
+ })
199
+ .then(async () => {
200
+ try {
201
+ await this.createEpisodeCreatedFeed(createdItem);
202
+ } catch (error) {
203
+ logger.error(
204
+ `Create episode feed failed in IncidentEpisodeService.onCreateSuccess: ${error}`,
205
+ );
206
+ }
207
+ })
208
+ .then(async () => {
209
+ // Execute on-call duty policies
210
+ try {
211
+ await this.executeEpisodeOnCallDutyPoliciesAsync(createdItem);
212
+ } catch (error) {
213
+ logger.error(
214
+ `On-call duty policy execution failed in IncidentEpisodeService.onCreateSuccess: ${error}`,
215
+ );
216
+ }
217
+ })
218
+ .catch((error: Error) => {
219
+ logger.error(
220
+ `Critical error in IncidentEpisodeService.onCreateSuccess: ${error}`,
221
+ );
222
+ });
223
+
224
+ return createdItem;
225
+ }
226
+
227
+ @CaptureSpan()
228
+ private async createEpisodeCreatedFeed(episode: Model): Promise<void> {
229
+ if (!episode.id || !episode.projectId) {
230
+ return;
231
+ }
232
+
233
+ let feedInfoInMarkdown: string = `#### Episode ${episode.episodeNumber?.toString()} Created
234
+
235
+ **${episode.title || "No title provided."}**
236
+
237
+ `;
238
+
239
+ if (episode.description) {
240
+ feedInfoInMarkdown += `${episode.description}\n\n`;
241
+ }
242
+
243
+ if (episode.isManuallyCreated) {
244
+ feedInfoInMarkdown += `This episode was manually created.\n\n`;
245
+ }
246
+
247
+ await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
248
+ incidentEpisodeId: episode.id,
249
+ projectId: episode.projectId,
250
+ incidentEpisodeFeedEventType: IncidentEpisodeFeedEventType.EpisodeCreated,
251
+ displayColor: Red500,
252
+ feedInfoInMarkdown: feedInfoInMarkdown,
253
+ userId: episode.createdByUserId || undefined,
254
+ });
255
+ }
256
+
257
+ @CaptureSpan()
258
+ private async executeEpisodeOnCallDutyPoliciesAsync(
259
+ createdItem: Model,
260
+ ): Promise<void> {
261
+ if (!createdItem.id || !createdItem.projectId) {
262
+ return;
263
+ }
264
+
265
+ try {
266
+ // Fetch the episode with on-call duty policies since they may not be loaded
267
+ const episodeWithPolicies: Model | null = await this.findOneById({
268
+ id: createdItem.id,
269
+ select: {
270
+ onCallDutyPolicies: {
271
+ _id: true,
272
+ name: true,
273
+ },
274
+ },
275
+ props: {
276
+ isRoot: true,
277
+ },
278
+ });
279
+
280
+ if (
281
+ !episodeWithPolicies?.onCallDutyPolicies?.length ||
282
+ episodeWithPolicies.onCallDutyPolicies.length === 0
283
+ ) {
284
+ return;
285
+ }
286
+
287
+ // Execute all on-call policies in parallel
288
+ const policyPromises: Promise<void>[] =
289
+ episodeWithPolicies.onCallDutyPolicies.map(
290
+ (policy: OnCallDutyPolicy) => {
291
+ return OnCallDutyPolicyService.executePolicy(
292
+ new ObjectID(policy._id as string),
293
+ {
294
+ triggeredByIncidentEpisodeId: createdItem.id!,
295
+ userNotificationEventType:
296
+ UserNotificationEventType.IncidentCreated,
297
+ },
298
+ );
299
+ },
300
+ );
301
+
302
+ await Promise.allSettled(policyPromises);
303
+
304
+ // Update the flag to indicate on-call policy has been executed
305
+ await this.updateOneById({
306
+ id: createdItem.id,
307
+ data: {
308
+ isOnCallPolicyExecuted: true,
309
+ },
310
+ props: {
311
+ isRoot: true,
312
+ },
313
+ });
314
+
315
+ // Create feed entry for on-call policy execution
316
+ const policyNames: string[] = episodeWithPolicies.onCallDutyPolicies
317
+ .map((policy: OnCallDutyPolicy) => {
318
+ return policy.name || "Unnamed Policy";
319
+ })
320
+ .filter((name: string) => {
321
+ return Boolean(name);
322
+ });
323
+
324
+ let feedInfoInMarkdown: string = `#### On-Call Policy Executed\n\n`;
325
+ feedInfoInMarkdown += `The following on-call ${policyNames.length === 1 ? "policy has" : "policies have"} been executed for this episode:\n\n`;
326
+
327
+ for (const policyName of policyNames) {
328
+ feedInfoInMarkdown += `- ${policyName}\n`;
329
+ }
330
+
331
+ await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
332
+ incidentEpisodeId: createdItem.id,
333
+ projectId: createdItem.projectId,
334
+ incidentEpisodeFeedEventType: IncidentEpisodeFeedEventType.OnCallPolicy,
335
+ displayColor: Purple500,
336
+ feedInfoInMarkdown: feedInfoInMarkdown,
337
+ });
338
+ } catch (error) {
339
+ logger.error(`Error in executeEpisodeOnCallDutyPoliciesAsync: ${error}`);
340
+ throw error;
341
+ }
342
+ }
343
+
344
+ @CaptureSpan()
345
+ public async changeEpisodeState(data: {
346
+ projectId: ObjectID;
347
+ episodeId: ObjectID;
348
+ incidentStateId: ObjectID;
349
+ notifyOwners: boolean;
350
+ rootCause: string | undefined;
351
+ props: DatabaseCommonInteractionProps;
352
+ cascadeToIncidents?: boolean;
353
+ }): Promise<void> {
354
+ const {
355
+ projectId,
356
+ episodeId,
357
+ incidentStateId,
358
+ notifyOwners,
359
+ rootCause,
360
+ props,
361
+ cascadeToIncidents,
362
+ } = data;
363
+
364
+ // Get last episode state timeline
365
+ const lastEpisodeStateTimeline: IncidentEpisodeStateTimeline | null =
366
+ await IncidentEpisodeStateTimelineService.findOneBy({
367
+ query: {
368
+ incidentEpisodeId: episodeId,
369
+ projectId: projectId,
370
+ },
371
+ select: {
372
+ _id: true,
373
+ incidentStateId: true,
374
+ },
375
+ sort: {
376
+ createdAt: SortOrder.Descending,
377
+ },
378
+ props: {
379
+ isRoot: true,
380
+ },
381
+ });
382
+
383
+ if (
384
+ lastEpisodeStateTimeline &&
385
+ lastEpisodeStateTimeline.incidentStateId &&
386
+ lastEpisodeStateTimeline.incidentStateId.toString() ===
387
+ incidentStateId.toString()
388
+ ) {
389
+ return;
390
+ }
391
+
392
+ const stateTimeline: IncidentEpisodeStateTimeline =
393
+ new IncidentEpisodeStateTimeline();
394
+
395
+ stateTimeline.incidentEpisodeId = episodeId;
396
+ stateTimeline.incidentStateId = incidentStateId;
397
+ stateTimeline.projectId = projectId;
398
+ stateTimeline.isOwnerNotified = !notifyOwners;
399
+
400
+ if (rootCause) {
401
+ stateTimeline.rootCause = rootCause;
402
+ }
403
+
404
+ await IncidentEpisodeStateTimelineService.create({
405
+ data: stateTimeline,
406
+ props: props || {},
407
+ });
408
+
409
+ /*
410
+ * Note: resolvedAt is updated by IncidentEpisodeStateTimelineService.onCreateSuccess()
411
+ * to avoid duplicate updates.
412
+ */
413
+
414
+ // Cascade state change to all member incidents if requested
415
+ if (cascadeToIncidents) {
416
+ await this.cascadeStateToMemberIncidents({
417
+ projectId,
418
+ episodeId,
419
+ incidentStateId,
420
+ props,
421
+ });
422
+ }
423
+ }
424
+
425
+ @CaptureSpan()
426
+ public async cascadeStateToMemberIncidents(data: {
427
+ projectId: ObjectID;
428
+ episodeId: ObjectID;
429
+ incidentStateId: ObjectID;
430
+ props: DatabaseCommonInteractionProps;
431
+ }): Promise<void> {
432
+ const { projectId, episodeId, incidentStateId, props } = data;
433
+
434
+ // Get all member incidents for this episode
435
+ const members: Array<IncidentEpisodeMember> =
436
+ await IncidentEpisodeMemberService.findBy({
437
+ query: {
438
+ incidentEpisodeId: episodeId,
439
+ projectId: projectId,
440
+ },
441
+ select: {
442
+ incidentId: true,
443
+ },
444
+ props: {
445
+ isRoot: true,
446
+ },
447
+ limit: LIMIT_PER_PROJECT,
448
+ skip: 0,
449
+ });
450
+
451
+ if (members.length === 0) {
452
+ return;
453
+ }
454
+
455
+ // Update state for each member incident
456
+ for (const member of members) {
457
+ if (!member.incidentId) {
458
+ continue;
459
+ }
460
+
461
+ try {
462
+ await IncidentService.changeIncidentState({
463
+ projectId: projectId,
464
+ incidentId: member.incidentId,
465
+ incidentStateId: incidentStateId,
466
+ shouldNotifyStatusPageSubscribers: false,
467
+ isSubscribersNotified: false,
468
+ notifyOwners: false, // Don't send notifications for cascaded state changes
469
+ rootCause: "State changed by episode state cascade.",
470
+ stateChangeLog: undefined,
471
+ props: props,
472
+ });
473
+ } catch (error) {
474
+ logger.error(
475
+ `Failed to cascade state change to incident ${member.incidentId.toString()}: ${error}`,
476
+ );
477
+ }
478
+ }
479
+ }
480
+
481
+ @CaptureSpan()
482
+ public async acknowledgeEpisode(
483
+ episodeId: ObjectID,
484
+ acknowledgedByUserId?: ObjectID,
485
+ cascadeToIncidents: boolean = true,
486
+ ): Promise<void> {
487
+ const episode: Model | null = await this.findOneById({
488
+ id: episodeId,
489
+ select: {
490
+ projectId: true,
491
+ },
492
+ props: {
493
+ isRoot: true,
494
+ },
495
+ });
496
+
497
+ if (!episode || !episode.projectId) {
498
+ throw new BadDataException("Episode not found.");
499
+ }
500
+
501
+ const incidentState: IncidentState | null =
502
+ await IncidentStateService.findOneBy({
503
+ query: {
504
+ projectId: episode.projectId,
505
+ isAcknowledgedState: true,
506
+ },
507
+ select: {
508
+ _id: true,
509
+ },
510
+ props: {
511
+ isRoot: true,
512
+ },
513
+ });
514
+
515
+ if (!incidentState || !incidentState.id) {
516
+ throw new BadDataException(
517
+ "Acknowledged incident state not found for this project.",
518
+ );
519
+ }
520
+
521
+ await this.changeEpisodeState({
522
+ projectId: episode.projectId,
523
+ episodeId: episodeId,
524
+ incidentStateId: incidentState.id,
525
+ notifyOwners: false,
526
+ rootCause: acknowledgedByUserId
527
+ ? `Acknowledged by user.`
528
+ : "Acknowledged via API.",
529
+ props: {
530
+ isRoot: true,
531
+ userId: acknowledgedByUserId,
532
+ },
533
+ cascadeToIncidents: cascadeToIncidents,
534
+ });
535
+ }
536
+
537
+ @CaptureSpan()
538
+ public async resolveEpisode(
539
+ episodeId: ObjectID,
540
+ resolvedByUserId?: ObjectID,
541
+ cascadeToIncidents: boolean = true,
542
+ ): Promise<void> {
543
+ const episode: Model | null = await this.findOneById({
544
+ id: episodeId,
545
+ select: {
546
+ projectId: true,
547
+ },
548
+ props: {
549
+ isRoot: true,
550
+ },
551
+ });
552
+
553
+ if (!episode || !episode.projectId) {
554
+ throw new BadDataException("Episode not found.");
555
+ }
556
+
557
+ const incidentState: IncidentState | null =
558
+ await IncidentStateService.findOneBy({
559
+ query: {
560
+ projectId: episode.projectId,
561
+ isResolvedState: true,
562
+ },
563
+ select: {
564
+ _id: true,
565
+ },
566
+ props: {
567
+ isRoot: true,
568
+ },
569
+ });
570
+
571
+ if (!incidentState || !incidentState.id) {
572
+ throw new BadDataException(
573
+ "Resolved incident state not found for this project.",
574
+ );
575
+ }
576
+
577
+ await this.changeEpisodeState({
578
+ projectId: episode.projectId,
579
+ episodeId: episodeId,
580
+ incidentStateId: incidentState.id,
581
+ notifyOwners: false,
582
+ rootCause: resolvedByUserId ? `Resolved by user.` : "Resolved via API.",
583
+ props: {
584
+ isRoot: true,
585
+ userId: resolvedByUserId,
586
+ },
587
+ cascadeToIncidents: cascadeToIncidents,
588
+ });
589
+ }
590
+
591
+ @CaptureSpan()
592
+ public async reopenEpisode(
593
+ episodeId: ObjectID,
594
+ reopenedByUserId?: ObjectID,
595
+ cascadeToIncidents: boolean = true,
596
+ ): Promise<void> {
597
+ const episode: Model | null = await this.findOneById({
598
+ id: episodeId,
599
+ select: {
600
+ projectId: true,
601
+ },
602
+ props: {
603
+ isRoot: true,
604
+ },
605
+ });
606
+
607
+ if (!episode || !episode.projectId) {
608
+ throw new BadDataException("Episode not found.");
609
+ }
610
+
611
+ const incidentState: IncidentState | null =
612
+ await IncidentStateService.findOneBy({
613
+ query: {
614
+ projectId: episode.projectId,
615
+ isCreatedState: true,
616
+ },
617
+ select: {
618
+ _id: true,
619
+ },
620
+ props: {
621
+ isRoot: true,
622
+ },
623
+ });
624
+
625
+ if (!incidentState || !incidentState.id) {
626
+ throw new BadDataException(
627
+ "Created incident state not found for this project.",
628
+ );
629
+ }
630
+
631
+ await this.changeEpisodeState({
632
+ projectId: episode.projectId,
633
+ episodeId: episodeId,
634
+ incidentStateId: incidentState.id,
635
+ notifyOwners: false,
636
+ rootCause: reopenedByUserId ? `Reopened by user.` : "Reopened via API.",
637
+ props: {
638
+ isRoot: true,
639
+ userId: reopenedByUserId,
640
+ },
641
+ cascadeToIncidents: cascadeToIncidents,
642
+ });
643
+ }
644
+
645
+ @CaptureSpan()
646
+ public async updateEpisodeSeverity(
647
+ episodeId: ObjectID,
648
+ severityId: ObjectID,
649
+ onlyIfHigher: boolean = false,
650
+ ): Promise<void> {
651
+ const episode: Model | null = await this.findOneById({
652
+ id: episodeId,
653
+ select: {
654
+ projectId: true,
655
+ incidentSeverityId: true,
656
+ },
657
+ props: {
658
+ isRoot: true,
659
+ },
660
+ });
661
+
662
+ if (!episode || !episode.projectId) {
663
+ throw new BadDataException("Episode not found.");
664
+ }
665
+
666
+ // If onlyIfHigher is true, check if the new severity is higher than the current
667
+ if (onlyIfHigher && episode.incidentSeverityId) {
668
+ const currentSeverity: IncidentSeverity | null =
669
+ await IncidentSeverityService.findOneById({
670
+ id: episode.incidentSeverityId,
671
+ select: {
672
+ order: true,
673
+ },
674
+ props: {
675
+ isRoot: true,
676
+ },
677
+ });
678
+
679
+ const newSeverity: IncidentSeverity | null =
680
+ await IncidentSeverityService.findOneById({
681
+ id: severityId,
682
+ select: {
683
+ order: true,
684
+ },
685
+ props: {
686
+ isRoot: true,
687
+ },
688
+ });
689
+
690
+ // Lower order number means higher severity
691
+ if (
692
+ currentSeverity?.order !== undefined &&
693
+ newSeverity?.order !== undefined &&
694
+ newSeverity.order >= currentSeverity.order
695
+ ) {
696
+ return; // New severity is not higher, don't update
697
+ }
698
+ }
699
+
700
+ await this.updateOneById({
701
+ id: episodeId,
702
+ data: {
703
+ incidentSeverityId: severityId,
704
+ },
705
+ props: {
706
+ isRoot: true,
707
+ },
708
+ });
709
+
710
+ // Create feed entry for severity change
711
+ const newSeverity: IncidentSeverity | null =
712
+ await IncidentSeverityService.findOneById({
713
+ id: severityId,
714
+ select: {
715
+ name: true,
716
+ color: true,
717
+ },
718
+ props: {
719
+ isRoot: true,
720
+ },
721
+ });
722
+
723
+ if (newSeverity && episode.projectId) {
724
+ await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
725
+ incidentEpisodeId: episodeId,
726
+ projectId: episode.projectId,
727
+ incidentEpisodeFeedEventType:
728
+ IncidentEpisodeFeedEventType.SeverityChanged,
729
+ displayColor: newSeverity.color || Yellow500,
730
+ feedInfoInMarkdown: `Episode severity changed to **${newSeverity.name || "Unknown"}**`,
731
+ });
732
+ }
733
+ }
734
+
735
+ @CaptureSpan()
736
+ public async updateIncidentCount(episodeId: ObjectID): Promise<void> {
737
+ const count: PositiveNumber = await IncidentEpisodeMemberService.countBy({
738
+ query: {
739
+ incidentEpisodeId: episodeId,
740
+ },
741
+ props: {
742
+ isRoot: true,
743
+ },
744
+ });
745
+
746
+ await this.updateOneById({
747
+ id: episodeId,
748
+ data: {
749
+ incidentCount: count.toNumber(),
750
+ },
751
+ props: {
752
+ isRoot: true,
753
+ },
754
+ });
755
+ }
756
+
757
+ @CaptureSpan()
758
+ public async updateLastIncidentAddedAt(episodeId: ObjectID): Promise<void> {
759
+ await this.updateOneById({
760
+ id: episodeId,
761
+ data: {
762
+ lastIncidentAddedAt: OneUptimeDate.getCurrentDate(),
763
+ },
764
+ props: {
765
+ isRoot: true,
766
+ },
767
+ });
768
+ }
769
+
770
+ @CaptureSpan()
771
+ public async findOwners(episodeId: ObjectID): Promise<Array<User>> {
772
+ // Get direct user owners
773
+ const userOwners: Array<IncidentEpisodeOwnerUser> =
774
+ await IncidentEpisodeOwnerUserService.findBy({
775
+ query: {
776
+ incidentEpisodeId: episodeId,
777
+ },
778
+ select: {
779
+ userId: true,
780
+ user: {
781
+ _id: true,
782
+ email: true,
783
+ name: true,
784
+ },
785
+ },
786
+ props: {
787
+ isRoot: true,
788
+ },
789
+ limit: LIMIT_PER_PROJECT,
790
+ skip: 0,
791
+ });
792
+
793
+ // Get team owners
794
+ const teamOwners: Array<IncidentEpisodeOwnerTeam> =
795
+ await IncidentEpisodeOwnerTeamService.findBy({
796
+ query: {
797
+ incidentEpisodeId: episodeId,
798
+ },
799
+ select: {
800
+ teamId: true,
801
+ },
802
+ props: {
803
+ isRoot: true,
804
+ },
805
+ limit: LIMIT_PER_PROJECT,
806
+ skip: 0,
807
+ });
808
+
809
+ // Collect all unique users
810
+ const usersMap: Map<string, User> = new Map();
811
+
812
+ // Add direct user owners
813
+ for (const owner of userOwners) {
814
+ if (owner.user && owner.userId) {
815
+ usersMap.set(owner.userId.toString(), owner.user);
816
+ }
817
+ }
818
+
819
+ // Add users from teams
820
+ for (const teamOwner of teamOwners) {
821
+ if (teamOwner.teamId) {
822
+ const teamMembers: Array<User> = await TeamMemberService.getUsersInTeam(
823
+ teamOwner.teamId,
824
+ );
825
+ for (const user of teamMembers) {
826
+ if (user.id) {
827
+ usersMap.set(user.id.toString(), user);
828
+ }
829
+ }
830
+ }
831
+ }
832
+
833
+ return Array.from(usersMap.values());
834
+ }
835
+
836
+ @CaptureSpan()
837
+ public async addOwners(data: {
838
+ episodeId: ObjectID;
839
+ projectId: ObjectID;
840
+ userIds?: Array<ObjectID>;
841
+ teamIds?: Array<ObjectID>;
842
+ createdByUserId?: ObjectID;
843
+ }): Promise<void> {
844
+ const { episodeId, projectId, userIds, teamIds, createdByUserId } = data;
845
+
846
+ // Add user owners
847
+ if (userIds && userIds.length > 0) {
848
+ for (const userId of userIds) {
849
+ // Check if already exists
850
+ const existing: IncidentEpisodeOwnerUser | null =
851
+ await IncidentEpisodeOwnerUserService.findOneBy({
852
+ query: {
853
+ incidentEpisodeId: episodeId,
854
+ userId: userId,
855
+ },
856
+ props: {
857
+ isRoot: true,
858
+ },
859
+ select: {
860
+ _id: true,
861
+ },
862
+ });
863
+
864
+ if (!existing) {
865
+ const ownerUser: IncidentEpisodeOwnerUser =
866
+ new IncidentEpisodeOwnerUser();
867
+ ownerUser.incidentEpisodeId = episodeId;
868
+ ownerUser.userId = userId;
869
+ ownerUser.projectId = projectId;
870
+ if (createdByUserId) {
871
+ ownerUser.createdByUserId = createdByUserId;
872
+ }
873
+
874
+ await IncidentEpisodeOwnerUserService.create({
875
+ data: ownerUser,
876
+ props: {
877
+ isRoot: true,
878
+ },
879
+ });
880
+ }
881
+ }
882
+ }
883
+
884
+ // Add team owners
885
+ if (teamIds && teamIds.length > 0) {
886
+ for (const teamId of teamIds) {
887
+ // Check if already exists
888
+ const existing: IncidentEpisodeOwnerTeam | null =
889
+ await IncidentEpisodeOwnerTeamService.findOneBy({
890
+ query: {
891
+ incidentEpisodeId: episodeId,
892
+ teamId: teamId,
893
+ },
894
+ props: {
895
+ isRoot: true,
896
+ },
897
+ select: {
898
+ _id: true,
899
+ },
900
+ });
901
+
902
+ if (!existing) {
903
+ const ownerTeam: IncidentEpisodeOwnerTeam =
904
+ new IncidentEpisodeOwnerTeam();
905
+ ownerTeam.incidentEpisodeId = episodeId;
906
+ ownerTeam.teamId = teamId;
907
+ ownerTeam.projectId = projectId;
908
+ if (createdByUserId) {
909
+ ownerTeam.createdByUserId = createdByUserId;
910
+ }
911
+
912
+ await IncidentEpisodeOwnerTeamService.create({
913
+ data: ownerTeam,
914
+ props: {
915
+ isRoot: true,
916
+ },
917
+ });
918
+ }
919
+ }
920
+ }
921
+ }
922
+
923
+ @CaptureSpan()
924
+ public getWorkspaceChannelForEpisode(
925
+ episode: Model,
926
+ workspaceType: WorkspaceType,
927
+ ): Array<NotificationRuleWorkspaceChannel> {
928
+ if (
929
+ !episode.postUpdatesToWorkspaceChannels ||
930
+ !Array.isArray(episode.postUpdatesToWorkspaceChannels) ||
931
+ episode.postUpdatesToWorkspaceChannels.length === 0
932
+ ) {
933
+ return [];
934
+ }
935
+
936
+ return episode.postUpdatesToWorkspaceChannels.filter(
937
+ (channel: NotificationRuleWorkspaceChannel) => {
938
+ return channel.workspaceType === workspaceType;
939
+ },
940
+ );
941
+ }
942
+
943
+ @CaptureSpan()
944
+ public async isEpisodeResolved(episodeId: ObjectID): Promise<boolean> {
945
+ const episode: Model | null = await this.findOneById({
946
+ id: episodeId,
947
+ select: {
948
+ projectId: true,
949
+ currentIncidentState: {
950
+ order: true,
951
+ },
952
+ },
953
+ props: {
954
+ isRoot: true,
955
+ },
956
+ });
957
+
958
+ if (!episode || !episode.projectId) {
959
+ throw new BadDataException("Episode not found.");
960
+ }
961
+
962
+ const resolvedState: IncidentState =
963
+ await IncidentStateService.getResolvedIncidentState({
964
+ projectId: episode.projectId,
965
+ props: {
966
+ isRoot: true,
967
+ },
968
+ });
969
+
970
+ const currentOrder: number = episode.currentIncidentState?.order || 0;
971
+ const resolvedOrder: number = resolvedState.order || 0;
972
+
973
+ return currentOrder >= resolvedOrder;
974
+ }
975
+
976
+ @CaptureSpan()
977
+ public async isEpisodeAcknowledged(data: {
978
+ episodeId: ObjectID;
979
+ }): Promise<boolean> {
980
+ const episode: Model | null = await this.findOneById({
981
+ id: data.episodeId,
982
+ select: {
983
+ projectId: true,
984
+ currentIncidentState: {
985
+ order: true,
986
+ },
987
+ },
988
+ props: {
989
+ isRoot: true,
990
+ },
991
+ });
992
+
993
+ if (!episode || !episode.projectId) {
994
+ throw new BadDataException("Episode not found.");
995
+ }
996
+
997
+ const acknowledgedState: IncidentState =
998
+ await IncidentStateService.getAcknowledgedIncidentState({
999
+ projectId: episode.projectId,
1000
+ props: {
1001
+ isRoot: true,
1002
+ },
1003
+ });
1004
+
1005
+ const currentOrder: number = episode.currentIncidentState?.order || 0;
1006
+ const acknowledgedOrder: number = acknowledgedState.order || 0;
1007
+
1008
+ return currentOrder >= acknowledgedOrder;
1009
+ }
1010
+
1011
+ @CaptureSpan()
1012
+ public async getEpisodeLinkInDashboard(
1013
+ projectId: ObjectID,
1014
+ episodeId: ObjectID,
1015
+ ): Promise<URL> {
1016
+ const dashboardUrl: URL = await DatabaseConfig.getDashboardUrl();
1017
+
1018
+ return URL.fromString(dashboardUrl.toString()).addRoute(
1019
+ `/${projectId.toString()}/incidents/episodes/${episodeId.toString()}`,
1020
+ );
1021
+ }
1022
+
1023
+ @CaptureSpan()
1024
+ public async getEpisodeNumber(data: {
1025
+ episodeId: ObjectID;
1026
+ }): Promise<number | null> {
1027
+ const episode: Model | null = await this.findOneById({
1028
+ id: data.episodeId,
1029
+ select: {
1030
+ episodeNumber: true,
1031
+ },
1032
+ props: {
1033
+ isRoot: true,
1034
+ },
1035
+ });
1036
+
1037
+ if (!episode) {
1038
+ throw new BadDataException("Episode not found.");
1039
+ }
1040
+
1041
+ return episode.episodeNumber ? Number(episode.episodeNumber) : null;
1042
+ }
1043
+ }
1044
+
1045
+ export default new Service();