@oneuptime/common 9.4.11 → 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 (286) 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/Project.ts +29 -0
  14. package/Models/DatabaseModels/UserOnCallLog.ts +48 -0
  15. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +49 -0
  16. package/Models/DatabaseModels/WorkspaceNotificationLog.ts +57 -0
  17. package/Server/API/BillingAPI.ts +78 -1
  18. package/Server/API/IncidentEpisodeAPI.ts +150 -0
  19. package/Server/API/SlackAPI.ts +23 -0
  20. package/Server/API/UserOnCallLogTimelineAPI.ts +24 -4
  21. package/Server/BillingConfig.ts +3 -0
  22. package/Server/EnvironmentConfig.ts +1 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1769599843642-MigrationName.ts +29 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1769626069479-MigrationName.ts +729 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1769629928240-MigrationName.ts +261 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1769676117342-RenameEvaluateOverTimeInCriteriaFilter.ts +28 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  28. package/Server/Services/AIBillingService.ts +10 -0
  29. package/Server/Services/BillingService.ts +349 -1
  30. package/Server/Services/CallService.ts +1 -0
  31. package/Server/Services/IncidentEpisodeFeedService.ts +94 -0
  32. package/Server/Services/IncidentEpisodeInternalNoteService.ts +71 -0
  33. package/Server/Services/IncidentEpisodeMemberService.ts +321 -0
  34. package/Server/Services/IncidentEpisodeOwnerTeamService.ts +10 -0
  35. package/Server/Services/IncidentEpisodeOwnerUserService.ts +10 -0
  36. package/Server/Services/IncidentEpisodeService.ts +1045 -0
  37. package/Server/Services/IncidentEpisodeStateTimelineService.ts +566 -0
  38. package/Server/Services/IncidentGroupingEngineService.ts +1047 -0
  39. package/Server/Services/IncidentGroupingRuleService.ts +14 -0
  40. package/Server/Services/IncidentService.ts +11 -0
  41. package/Server/Services/Index.ts +18 -0
  42. package/Server/Services/MailService.ts +1 -0
  43. package/Server/Services/MonitorService.ts +9 -0
  44. package/Server/Services/NotificationService.ts +10 -0
  45. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +18 -0
  46. package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +64 -2
  47. package/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +26 -1
  48. package/Server/Services/OnCallDutyPolicyService.ts +15 -0
  49. package/Server/Services/ProjectService.ts +33 -2
  50. package/Server/Services/SmsService.ts +1 -0
  51. package/Server/Services/UserNotificationRuleService.ts +48 -2
  52. package/Server/Services/UserNotificationSettingService.ts +23 -0
  53. package/Server/Services/UserOnCallLogService.ts +41 -4
  54. package/Server/Services/UserService.ts +45 -1
  55. package/Server/Services/WhatsAppService.ts +1 -0
  56. package/Server/Services/WorkspaceNotificationLogService.ts +16 -0
  57. package/Server/Services/WorkspaceNotificationRuleService.ts +116 -0
  58. package/Server/Types/Database/Permissions/TenantPermission.ts +20 -0
  59. package/Server/Utils/AI/IncidentEpisodeAIContextBuilder.ts +490 -0
  60. package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +1 -1
  61. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
  62. package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +1 -1
  63. package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +1 -1
  64. package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +2 -2
  65. package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +182 -0
  66. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +13 -0
  67. package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +1 -1
  68. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +37 -0
  69. package/Server/Utils/PushNotificationUtil.ts +31 -0
  70. package/Server/Utils/WhatsAppTemplateUtil.ts +14 -0
  71. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ActionTypes.ts +18 -0
  72. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +702 -0
  73. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +20 -0
  74. package/Server/Utils/Workspace/Slack/Actions/ActionTypes.ts +11 -0
  75. package/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.ts +918 -0
  76. package/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.ts +120 -0
  77. package/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.ts +74 -0
  78. package/Types/Email/EmailTemplateType.ts +7 -0
  79. package/Types/Monitor/CriteriaFilter.ts +24 -4
  80. package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
  81. package/Types/Monitor/MonitorStep.ts +37 -0
  82. package/Types/Monitor/MonitorStepSnmpMonitor.ts +102 -0
  83. package/Types/Monitor/MonitorType.ts +15 -2
  84. package/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.ts +8 -0
  85. package/Types/Monitor/SnmpMonitor/SnmpDataType.ts +21 -0
  86. package/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.ts +16 -0
  87. package/Types/Monitor/SnmpMonitor/SnmpOid.ts +60 -0
  88. package/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.ts +7 -0
  89. package/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.ts +7 -0
  90. package/Types/Monitor/SnmpMonitor/SnmpV3Auth.ts +12 -0
  91. package/Types/Monitor/SnmpMonitor/SnmpVersion.ts +7 -0
  92. package/Types/NotificationSetting/NotificationSettingEventType.ts +7 -0
  93. package/Types/Permission.ts +311 -0
  94. package/Types/Probe/ProbeMonitorResponse.ts +2 -0
  95. package/Types/UserNotification/UserNotificationEventType.ts +1 -0
  96. package/Types/WhatsApp/WhatsAppTemplates.ts +24 -0
  97. package/Types/Workspace/NotificationRules/EventType.ts +1 -0
  98. package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +38 -1
  99. package/Utils/Monitor/MonitorMetricType.ts +2 -1
  100. package/build/dist/Models/DatabaseModels/Incident.js +78 -0
  101. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  102. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1250 -0
  103. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -0
  104. package/build/dist/Models/DatabaseModels/IncidentEpisodeFeed.js +555 -0
  105. package/build/dist/Models/DatabaseModels/IncidentEpisodeFeed.js.map +1 -0
  106. package/build/dist/Models/DatabaseModels/IncidentEpisodeInternalNote.js +467 -0
  107. package/build/dist/Models/DatabaseModels/IncidentEpisodeInternalNote.js.map +1 -0
  108. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +607 -0
  109. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -0
  110. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerTeam.js +437 -0
  111. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerTeam.js.map +1 -0
  112. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerUser.js +436 -0
  113. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerUser.js.map +1 -0
  114. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +546 -0
  115. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -0
  116. package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js +1437 -0
  117. package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js.map +1 -0
  118. package/build/dist/Models/DatabaseModels/Index.js +16 -0
  119. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  120. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +69 -0
  121. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  122. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.js +58 -0
  123. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.js.map +1 -1
  124. package/build/dist/Models/DatabaseModels/Project.js +30 -0
  125. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  126. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +47 -0
  127. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  128. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +48 -0
  129. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  130. package/build/dist/Models/DatabaseModels/WorkspaceNotificationLog.js +58 -0
  131. package/build/dist/Models/DatabaseModels/WorkspaceNotificationLog.js.map +1 -1
  132. package/build/dist/Server/API/BillingAPI.js +44 -1
  133. package/build/dist/Server/API/BillingAPI.js.map +1 -1
  134. package/build/dist/Server/API/IncidentEpisodeAPI.js +97 -0
  135. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -0
  136. package/build/dist/Server/API/SlackAPI.js +18 -0
  137. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  138. package/build/dist/Server/API/UserOnCallLogTimelineAPI.js +30 -10
  139. package/build/dist/Server/API/UserOnCallLogTimelineAPI.js.map +1 -1
  140. package/build/dist/Server/BillingConfig.js +2 -0
  141. package/build/dist/Server/BillingConfig.js.map +1 -1
  142. package/build/dist/Server/EnvironmentConfig.js +1 -0
  143. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  144. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769599843642-MigrationName.js +16 -0
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769599843642-MigrationName.js.map +1 -0
  146. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769626069479-MigrationName.js +256 -0
  147. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769626069479-MigrationName.js.map +1 -0
  148. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769629928240-MigrationName.js +96 -0
  149. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769629928240-MigrationName.js.map +1 -0
  150. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769676117342-RenameEvaluateOverTimeInCriteriaFilter.js +25 -0
  151. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769676117342-RenameEvaluateOverTimeInCriteriaFilter.js.map +1 -0
  152. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  153. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  154. package/build/dist/Server/Services/AIBillingService.js +10 -1
  155. package/build/dist/Server/Services/AIBillingService.js.map +1 -1
  156. package/build/dist/Server/Services/BillingService.js +224 -5
  157. package/build/dist/Server/Services/BillingService.js.map +1 -1
  158. package/build/dist/Server/Services/CallService.js.map +1 -1
  159. package/build/dist/Server/Services/IncidentEpisodeFeedService.js +83 -0
  160. package/build/dist/Server/Services/IncidentEpisodeFeedService.js.map +1 -0
  161. package/build/dist/Server/Services/IncidentEpisodeInternalNoteService.js +70 -0
  162. package/build/dist/Server/Services/IncidentEpisodeInternalNoteService.js.map +1 -0
  163. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +298 -0
  164. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -0
  165. package/build/dist/Server/Services/IncidentEpisodeOwnerTeamService.js +9 -0
  166. package/build/dist/Server/Services/IncidentEpisodeOwnerTeamService.js.map +1 -0
  167. package/build/dist/Server/Services/IncidentEpisodeOwnerUserService.js +9 -0
  168. package/build/dist/Server/Services/IncidentEpisodeOwnerUserService.js.map +1 -0
  169. package/build/dist/Server/Services/IncidentEpisodeService.js +933 -0
  170. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -0
  171. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +498 -0
  172. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -0
  173. package/build/dist/Server/Services/IncidentGroupingEngineService.js +799 -0
  174. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -0
  175. package/build/dist/Server/Services/IncidentGroupingRuleService.js +13 -0
  176. package/build/dist/Server/Services/IncidentGroupingRuleService.js.map +1 -0
  177. package/build/dist/Server/Services/IncidentService.js +10 -0
  178. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  179. package/build/dist/Server/Services/Index.js +16 -0
  180. package/build/dist/Server/Services/Index.js.map +1 -1
  181. package/build/dist/Server/Services/MailService.js.map +1 -1
  182. package/build/dist/Server/Services/MonitorService.js +9 -1
  183. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  184. package/build/dist/Server/Services/NotificationService.js +10 -1
  185. package/build/dist/Server/Services/NotificationService.js.map +1 -1
  186. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +10 -0
  187. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  188. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +48 -2
  189. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
  190. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js +20 -1
  191. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js.map +1 -1
  192. package/build/dist/Server/Services/OnCallDutyPolicyService.js +8 -0
  193. package/build/dist/Server/Services/OnCallDutyPolicyService.js.map +1 -1
  194. package/build/dist/Server/Services/ProjectService.js +16 -3
  195. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  196. package/build/dist/Server/Services/SmsService.js.map +1 -1
  197. package/build/dist/Server/Services/UserNotificationRuleService.js +39 -2
  198. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  199. package/build/dist/Server/Services/UserNotificationSettingService.js +9 -0
  200. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  201. package/build/dist/Server/Services/UserOnCallLogService.js +35 -3
  202. package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
  203. package/build/dist/Server/Services/UserService.js +40 -0
  204. package/build/dist/Server/Services/UserService.js.map +1 -1
  205. package/build/dist/Server/Services/WhatsAppService.js.map +1 -1
  206. package/build/dist/Server/Services/WorkspaceNotificationLogService.js +12 -0
  207. package/build/dist/Server/Services/WorkspaceNotificationLogService.js.map +1 -1
  208. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +95 -1
  209. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  210. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +17 -0
  211. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
  212. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js +402 -0
  213. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js.map +1 -0
  214. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +1 -1
  215. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
  216. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +1 -1
  217. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +1 -1
  218. package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +2 -2
  219. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +135 -0
  220. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -0
  221. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +10 -0
  222. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  223. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +1 -1
  224. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +26 -0
  225. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  226. package/build/dist/Server/Utils/PushNotificationUtil.js +20 -0
  227. package/build/dist/Server/Utils/PushNotificationUtil.js.map +1 -1
  228. package/build/dist/Server/Utils/WhatsAppTemplateUtil.js +8 -0
  229. package/build/dist/Server/Utils/WhatsAppTemplateUtil.js.map +1 -1
  230. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ActionTypes.js +17 -0
  231. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ActionTypes.js.map +1 -1
  232. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +547 -0
  233. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -0
  234. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +15 -0
  235. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  236. package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js +10 -0
  237. package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js.map +1 -1
  238. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js +651 -0
  239. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js.map +1 -0
  240. package/build/dist/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.js +100 -0
  241. package/build/dist/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.js.map +1 -0
  242. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.js +70 -0
  243. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.js.map +1 -0
  244. package/build/dist/Types/Email/EmailTemplateType.js +6 -0
  245. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  246. package/build/dist/Types/Monitor/CriteriaFilter.js +16 -3
  247. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  248. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
  249. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  250. package/build/dist/Types/Monitor/MonitorStep.js +26 -0
  251. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  252. package/build/dist/Types/Monitor/MonitorStepSnmpMonitor.js +77 -0
  253. package/build/dist/Types/Monitor/MonitorStepSnmpMonitor.js.map +1 -0
  254. package/build/dist/Types/Monitor/MonitorType.js +13 -2
  255. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  256. package/build/dist/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.js +9 -0
  257. package/build/dist/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.js.map +1 -0
  258. package/build/dist/Types/Monitor/SnmpMonitor/SnmpDataType.js +22 -0
  259. package/build/dist/Types/Monitor/SnmpMonitor/SnmpDataType.js.map +1 -0
  260. package/build/dist/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.js +2 -0
  261. package/build/dist/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.js.map +1 -0
  262. package/build/dist/Types/Monitor/SnmpMonitor/SnmpOid.js +55 -0
  263. package/build/dist/Types/Monitor/SnmpMonitor/SnmpOid.js.map +1 -0
  264. package/build/dist/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.js +8 -0
  265. package/build/dist/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.js.map +1 -0
  266. package/build/dist/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.js +8 -0
  267. package/build/dist/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.js.map +1 -0
  268. package/build/dist/Types/Monitor/SnmpMonitor/SnmpV3Auth.js +2 -0
  269. package/build/dist/Types/Monitor/SnmpMonitor/SnmpV3Auth.js.map +1 -0
  270. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js +8 -0
  271. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js.map +1 -0
  272. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +5 -0
  273. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  274. package/build/dist/Types/Permission.js +264 -0
  275. package/build/dist/Types/Permission.js.map +1 -1
  276. package/build/dist/Types/UserNotification/UserNotificationEventType.js +1 -0
  277. package/build/dist/Types/UserNotification/UserNotificationEventType.js.map +1 -1
  278. package/build/dist/Types/WhatsApp/WhatsAppTemplates.js +15 -0
  279. package/build/dist/Types/WhatsApp/WhatsAppTemplates.js.map +1 -1
  280. package/build/dist/Types/Workspace/NotificationRules/EventType.js +1 -0
  281. package/build/dist/Types/Workspace/NotificationRules/EventType.js.map +1 -1
  282. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +33 -1
  283. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
  284. package/build/dist/Utils/Monitor/MonitorMetricType.js +2 -1
  285. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  286. package/package.json +1 -1
@@ -0,0 +1,1047 @@
1
+ import ObjectID from "../../Types/ObjectID";
2
+ import IncidentGroupingRule from "../../Models/DatabaseModels/IncidentGroupingRule";
3
+ import Incident from "../../Models/DatabaseModels/Incident";
4
+ import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
5
+ import IncidentEpisodeMember, {
6
+ IncidentEpisodeMemberAddedBy,
7
+ } from "../../Models/DatabaseModels/IncidentEpisodeMember";
8
+ import Label from "../../Models/DatabaseModels/Label";
9
+ import Monitor from "../../Models/DatabaseModels/Monitor";
10
+ import IncidentSeverity from "../../Models/DatabaseModels/IncidentSeverity";
11
+ import ServiceMonitor from "../../Models/DatabaseModels/ServiceMonitor";
12
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
13
+ import logger from "../Utils/Logger";
14
+ import SortOrder from "../../Types/BaseDatabase/SortOrder";
15
+ import OneUptimeDate from "../../Types/Date";
16
+ import QueryHelper from "../Types/Database/QueryHelper";
17
+ import IncidentGroupingRuleService from "./IncidentGroupingRuleService";
18
+ import IncidentEpisodeService from "./IncidentEpisodeService";
19
+ import IncidentEpisodeMemberService from "./IncidentEpisodeMemberService";
20
+ import MonitorService from "./MonitorService";
21
+ import ServiceMonitorService from "./ServiceMonitorService";
22
+ import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
23
+ import IncidentEpisodeFeedService from "./IncidentEpisodeFeedService";
24
+ import { IncidentEpisodeFeedEventType } from "../../Models/DatabaseModels/IncidentEpisodeFeed";
25
+ import { Green500 } from "../../Types/BrandColors";
26
+
27
+ export interface GroupingResult {
28
+ grouped: boolean;
29
+ episodeId?: ObjectID;
30
+ isNewEpisode?: boolean;
31
+ wasReopened?: boolean;
32
+ }
33
+
34
+ class IncidentGroupingEngineServiceClass {
35
+ @CaptureSpan()
36
+ public async processIncident(incident: Incident): Promise<GroupingResult> {
37
+ logger.debug(`Processing incident ${incident.id} for grouping`);
38
+
39
+ try {
40
+ if (!incident.id || !incident.projectId) {
41
+ logger.warn("Incident missing id or projectId, skipping grouping");
42
+ return { grouped: false };
43
+ }
44
+
45
+ // If incident already has an episode, don't reprocess
46
+ if (incident.incidentEpisodeId) {
47
+ return { grouped: true, episodeId: incident.incidentEpisodeId };
48
+ }
49
+
50
+ // Get enabled rules sorted by priority
51
+ const rules: Array<IncidentGroupingRule> =
52
+ await IncidentGroupingRuleService.findBy({
53
+ query: {
54
+ projectId: incident.projectId,
55
+ isEnabled: true,
56
+ },
57
+ sort: {
58
+ priority: SortOrder.Ascending,
59
+ },
60
+ props: {
61
+ isRoot: true,
62
+ },
63
+ select: {
64
+ _id: true,
65
+ name: true,
66
+ priority: true,
67
+ // Match criteria fields
68
+ monitors: {
69
+ _id: true,
70
+ },
71
+ incidentSeverities: {
72
+ _id: true,
73
+ },
74
+ incidentLabels: {
75
+ _id: true,
76
+ },
77
+ monitorLabels: {
78
+ _id: true,
79
+ },
80
+ incidentTitlePattern: true,
81
+ incidentDescriptionPattern: true,
82
+ monitorNamePattern: true,
83
+ monitorDescriptionPattern: true,
84
+ // Group by fields
85
+ groupByMonitor: true,
86
+ groupBySeverity: true,
87
+ groupByIncidentTitle: true,
88
+ groupByService: true,
89
+ // Time settings
90
+ enableTimeWindow: true,
91
+ timeWindowMinutes: true,
92
+ episodeTitleTemplate: true,
93
+ episodeDescriptionTemplate: true,
94
+ enableResolveDelay: true,
95
+ resolveDelayMinutes: true,
96
+ enableReopenWindow: true,
97
+ reopenWindowMinutes: true,
98
+ enableInactivityTimeout: true,
99
+ inactivityTimeoutMinutes: true,
100
+ defaultAssignToUserId: true,
101
+ defaultAssignToTeamId: true,
102
+ onCallDutyPolicies: {
103
+ _id: true,
104
+ },
105
+ },
106
+ limit: 100,
107
+ skip: 0,
108
+ });
109
+
110
+ if (rules.length === 0) {
111
+ logger.debug(
112
+ `No enabled grouping rules found for project ${incident.projectId}`,
113
+ );
114
+ return { grouped: false };
115
+ }
116
+
117
+ logger.debug(
118
+ `Found ${rules.length} enabled grouping rules for project ${incident.projectId}`,
119
+ );
120
+
121
+ // Find first matching rule
122
+ for (const rule of rules) {
123
+ const matches: boolean = await this.doesIncidentMatchRule(
124
+ incident,
125
+ rule,
126
+ );
127
+
128
+ if (matches) {
129
+ logger.debug(
130
+ `Incident ${incident.id} matches rule ${rule.name || rule.id}`,
131
+ );
132
+
133
+ // Try to find existing episode or create new one
134
+ const result: GroupingResult = await this.groupIncidentWithRule(
135
+ incident,
136
+ rule,
137
+ );
138
+ return result;
139
+ }
140
+ }
141
+
142
+ logger.debug(`Incident ${incident.id} did not match any grouping rules`);
143
+ return { grouped: false };
144
+ } catch (error) {
145
+ logger.error(`Error processing incident for grouping: ${error}`);
146
+ return { grouped: false };
147
+ }
148
+ }
149
+
150
+ @CaptureSpan()
151
+ private async doesIncidentMatchRule(
152
+ incident: Incident,
153
+ rule: IncidentGroupingRule,
154
+ ): Promise<boolean> {
155
+ logger.debug(
156
+ `Checking if incident ${incident.id} matches rule ${rule.name || rule.id}`,
157
+ );
158
+
159
+ // Check monitor IDs - if monitors are specified, incident must be from one of them
160
+ if (rule.monitors && rule.monitors.length > 0) {
161
+ if (!incident.monitors || incident.monitors.length === 0) {
162
+ return false;
163
+ }
164
+ const ruleMonitorIds: Array<string> = rule.monitors.map((m: Monitor) => {
165
+ return m.id?.toString() || "";
166
+ });
167
+ const incidentMonitorIds: Array<string> = incident.monitors.map(
168
+ (m: Monitor) => {
169
+ return m.id?.toString() || "";
170
+ },
171
+ );
172
+ const hasMatchingMonitor: boolean = ruleMonitorIds.some(
173
+ (monitorId: string) => {
174
+ return incidentMonitorIds.includes(monitorId);
175
+ },
176
+ );
177
+ if (!hasMatchingMonitor) {
178
+ return false;
179
+ }
180
+ }
181
+
182
+ // Check incident severity IDs - if severities are specified, incident must have one of them
183
+ if (rule.incidentSeverities && rule.incidentSeverities.length > 0) {
184
+ if (!incident.incidentSeverityId) {
185
+ return false;
186
+ }
187
+ const severityIds: Array<string> = rule.incidentSeverities.map(
188
+ (s: IncidentSeverity) => {
189
+ return s.id?.toString() || "";
190
+ },
191
+ );
192
+ const incidentSeverityIdStr: string =
193
+ incident.incidentSeverityId.toString();
194
+ if (!severityIds.includes(incidentSeverityIdStr)) {
195
+ return false;
196
+ }
197
+ }
198
+
199
+ // Check incident label IDs - if incident labels are specified, incident must have at least one of them
200
+ if (rule.incidentLabels && rule.incidentLabels.length > 0) {
201
+ if (!incident.labels || incident.labels.length === 0) {
202
+ return false;
203
+ }
204
+ const ruleLabelIds: Array<string> = rule.incidentLabels.map(
205
+ (l: Label) => {
206
+ return l.id?.toString() || "";
207
+ },
208
+ );
209
+ const incidentLabelIds: Array<string> = incident.labels.map(
210
+ (l: Label) => {
211
+ return l.id?.toString() || "";
212
+ },
213
+ );
214
+ const hasMatchingLabel: boolean = ruleLabelIds.some((labelId: string) => {
215
+ return incidentLabelIds.includes(labelId);
216
+ });
217
+ if (!hasMatchingLabel) {
218
+ return false;
219
+ }
220
+ }
221
+
222
+ // Check monitor-related criteria (labels, name pattern, description pattern)
223
+ const hasMonitorCriteria: boolean = Boolean(
224
+ (rule.monitorLabels && rule.monitorLabels.length > 0) ||
225
+ rule.monitorNamePattern ||
226
+ rule.monitorDescriptionPattern,
227
+ );
228
+
229
+ if (hasMonitorCriteria) {
230
+ if (!incident.monitors || incident.monitors.length === 0) {
231
+ return false;
232
+ }
233
+
234
+ // Check at least one monitor matches all criteria
235
+ let anyMonitorMatches: boolean = false;
236
+
237
+ for (const incidentMonitor of incident.monitors) {
238
+ if (!incidentMonitor.id) {
239
+ continue;
240
+ }
241
+
242
+ // Load monitor with all needed fields
243
+ const monitor: Monitor | null = await MonitorService.findOneById({
244
+ id: incidentMonitor.id,
245
+ select: {
246
+ name: true,
247
+ description: true,
248
+ labels: {
249
+ _id: true,
250
+ },
251
+ },
252
+ props: {
253
+ isRoot: true,
254
+ },
255
+ });
256
+
257
+ if (!monitor) {
258
+ continue;
259
+ }
260
+
261
+ let monitorMatches: boolean = true;
262
+
263
+ // Check monitor labels
264
+ if (rule.monitorLabels && rule.monitorLabels.length > 0) {
265
+ if (!monitor.labels || monitor.labels.length === 0) {
266
+ monitorMatches = false;
267
+ } else {
268
+ const ruleMonitorLabelIds: Array<string> = rule.monitorLabels.map(
269
+ (l: Label) => {
270
+ return l.id?.toString() || "";
271
+ },
272
+ );
273
+ const monitorLabelIds: Array<string> = monitor.labels.map(
274
+ (l: Label) => {
275
+ return l.id?.toString() || "";
276
+ },
277
+ );
278
+ const hasMatchingMonitorLabel: boolean = ruleMonitorLabelIds.some(
279
+ (labelId: string) => {
280
+ return monitorLabelIds.includes(labelId);
281
+ },
282
+ );
283
+ if (!hasMatchingMonitorLabel) {
284
+ monitorMatches = false;
285
+ }
286
+ }
287
+ }
288
+
289
+ // Check monitor name pattern (regex)
290
+ if (monitorMatches && rule.monitorNamePattern) {
291
+ if (!monitor.name) {
292
+ monitorMatches = false;
293
+ } else {
294
+ try {
295
+ const regex: RegExp = new RegExp(rule.monitorNamePattern, "i");
296
+ if (!regex.test(monitor.name)) {
297
+ monitorMatches = false;
298
+ }
299
+ } catch {
300
+ logger.warn(
301
+ `Invalid regex pattern in rule ${rule.id}: ${rule.monitorNamePattern}`,
302
+ );
303
+ monitorMatches = false;
304
+ }
305
+ }
306
+ }
307
+
308
+ // Check monitor description pattern (regex)
309
+ if (monitorMatches && rule.monitorDescriptionPattern) {
310
+ if (!monitor.description) {
311
+ monitorMatches = false;
312
+ } else {
313
+ try {
314
+ const regex: RegExp = new RegExp(
315
+ rule.monitorDescriptionPattern,
316
+ "i",
317
+ );
318
+ if (!regex.test(monitor.description)) {
319
+ monitorMatches = false;
320
+ }
321
+ } catch {
322
+ logger.warn(
323
+ `Invalid regex pattern in rule ${rule.id}: ${rule.monitorDescriptionPattern}`,
324
+ );
325
+ monitorMatches = false;
326
+ }
327
+ }
328
+ }
329
+
330
+ if (monitorMatches) {
331
+ anyMonitorMatches = true;
332
+ break;
333
+ }
334
+ }
335
+
336
+ if (!anyMonitorMatches) {
337
+ return false;
338
+ }
339
+ }
340
+
341
+ // Check incident title pattern (regex)
342
+ if (rule.incidentTitlePattern) {
343
+ if (!incident.title) {
344
+ return false;
345
+ }
346
+ try {
347
+ const regex: RegExp = new RegExp(rule.incidentTitlePattern, "i");
348
+ if (!regex.test(incident.title)) {
349
+ return false;
350
+ }
351
+ } catch {
352
+ logger.warn(
353
+ `Invalid regex pattern in rule ${rule.id}: ${rule.incidentTitlePattern}`,
354
+ );
355
+ return false;
356
+ }
357
+ }
358
+
359
+ // Check incident description pattern (regex)
360
+ if (rule.incidentDescriptionPattern) {
361
+ if (!incident.description) {
362
+ return false;
363
+ }
364
+ try {
365
+ const regex: RegExp = new RegExp(rule.incidentDescriptionPattern, "i");
366
+ if (!regex.test(incident.description)) {
367
+ return false;
368
+ }
369
+ } catch {
370
+ logger.warn(
371
+ `Invalid regex pattern in rule ${rule.id}: ${rule.incidentDescriptionPattern}`,
372
+ );
373
+ return false;
374
+ }
375
+ }
376
+
377
+ // If no criteria specified (all fields empty), rule matches all incidents
378
+ logger.debug(
379
+ `Rule ${rule.name || rule.id} matched incident ${incident.id} (all criteria passed)`,
380
+ );
381
+ return true;
382
+ }
383
+
384
+ @CaptureSpan()
385
+ private async groupIncidentWithRule(
386
+ incident: Incident,
387
+ rule: IncidentGroupingRule,
388
+ ): Promise<GroupingResult> {
389
+ // Build the grouping key based on groupBy fields
390
+ const groupingKey: string = await this.buildGroupingKey(incident, rule);
391
+
392
+ // Create mutex key to prevent race conditions when creating episodes
393
+ const mutexKey: string = `${incident.projectId?.toString()}-${rule.id?.toString()}-${groupingKey}`;
394
+
395
+ let mutex: SemaphoreMutex | null = null;
396
+
397
+ try {
398
+ /*
399
+ * Acquire mutex to prevent concurrent episode creation for the same grouping key
400
+ * This is critical - we must have the lock before proceeding to prevent race conditions
401
+ */
402
+ logger.debug(
403
+ `Acquiring mutex for grouping key: ${mutexKey} for incident ${incident.id}`,
404
+ );
405
+ mutex = await Semaphore.lock({
406
+ key: mutexKey,
407
+ namespace: "IncidentGroupingEngine.groupIncidentWithRule",
408
+ lockTimeout: 30000, // 30 seconds - enough time to complete episode creation
409
+ acquireTimeout: 60000, // Wait up to 60 seconds to acquire the lock
410
+ });
411
+ logger.debug(
412
+ `Acquired mutex for grouping key: ${mutexKey} for incident ${incident.id}`,
413
+ );
414
+
415
+ // Calculate time window cutoff (only if time window is enabled)
416
+ let timeWindowCutoff: Date | null = null;
417
+ if (rule.enableTimeWindow) {
418
+ const timeWindowMinutes: number = rule.timeWindowMinutes || 60;
419
+ timeWindowCutoff = OneUptimeDate.getSomeMinutesAgo(timeWindowMinutes);
420
+ }
421
+
422
+ // Find existing active episode that matches
423
+ const existingEpisode: IncidentEpisode | null =
424
+ await this.findMatchingActiveEpisode(
425
+ incident.projectId!,
426
+ rule.id!,
427
+ groupingKey,
428
+ timeWindowCutoff,
429
+ );
430
+
431
+ if (existingEpisode && existingEpisode.id) {
432
+ // Add incident to existing episode
433
+ await this.addIncidentToEpisode(
434
+ incident,
435
+ existingEpisode.id,
436
+ IncidentEpisodeMemberAddedBy.Rule,
437
+ rule.id!,
438
+ );
439
+
440
+ // Update episode severity if incident has higher severity
441
+ if (incident.incidentSeverityId) {
442
+ await IncidentEpisodeService.updateEpisodeSeverity(
443
+ existingEpisode.id,
444
+ incident.incidentSeverityId,
445
+ true, // onlyIfHigher
446
+ );
447
+ }
448
+
449
+ return {
450
+ grouped: true,
451
+ episodeId: existingEpisode.id,
452
+ isNewEpisode: false,
453
+ };
454
+ }
455
+
456
+ // Check if we can reopen a recently resolved episode (only if enabled)
457
+ if (rule.enableReopenWindow) {
458
+ const reopenWindowMinutes: number = rule.reopenWindowMinutes || 0;
459
+ if (reopenWindowMinutes > 0) {
460
+ const reopenCutoff: Date =
461
+ OneUptimeDate.getSomeMinutesAgo(reopenWindowMinutes);
462
+ const recentlyResolvedEpisode: IncidentEpisode | null =
463
+ await this.findRecentlyResolvedEpisode(
464
+ incident.projectId!,
465
+ rule.id!,
466
+ groupingKey,
467
+ reopenCutoff,
468
+ );
469
+
470
+ if (recentlyResolvedEpisode && recentlyResolvedEpisode.id) {
471
+ // Reopen the episode
472
+ await IncidentEpisodeService.reopenEpisode(
473
+ recentlyResolvedEpisode.id,
474
+ );
475
+
476
+ // Add incident to reopened episode
477
+ await this.addIncidentToEpisode(
478
+ incident,
479
+ recentlyResolvedEpisode.id,
480
+ IncidentEpisodeMemberAddedBy.Rule,
481
+ rule.id!,
482
+ );
483
+
484
+ // Update episode severity if incident has higher severity
485
+ if (incident.incidentSeverityId) {
486
+ await IncidentEpisodeService.updateEpisodeSeverity(
487
+ recentlyResolvedEpisode.id,
488
+ incident.incidentSeverityId,
489
+ true, // onlyIfHigher
490
+ );
491
+ }
492
+
493
+ return {
494
+ grouped: true,
495
+ episodeId: recentlyResolvedEpisode.id,
496
+ isNewEpisode: false,
497
+ wasReopened: true,
498
+ };
499
+ }
500
+ }
501
+ }
502
+
503
+ // Create new episode
504
+ const newEpisode: IncidentEpisode | null = await this.createNewEpisode(
505
+ incident,
506
+ rule,
507
+ groupingKey,
508
+ );
509
+
510
+ if (newEpisode && newEpisode.id) {
511
+ // Add incident to new episode
512
+ await this.addIncidentToEpisode(
513
+ incident,
514
+ newEpisode.id,
515
+ IncidentEpisodeMemberAddedBy.Rule,
516
+ rule.id!,
517
+ );
518
+
519
+ return { grouped: true, episodeId: newEpisode.id, isNewEpisode: true };
520
+ }
521
+
522
+ return { grouped: false };
523
+ } finally {
524
+ // Release mutex
525
+ if (mutex) {
526
+ try {
527
+ logger.debug(
528
+ `Releasing mutex for grouping key: ${mutexKey} for incident ${incident.id}`,
529
+ );
530
+ await Semaphore.release(mutex);
531
+ logger.debug(
532
+ `Released mutex for grouping key: ${mutexKey} for incident ${incident.id}`,
533
+ );
534
+ } catch (err) {
535
+ logger.error(
536
+ `Error releasing mutex for grouping key: ${mutexKey}: ${err}`,
537
+ );
538
+ }
539
+ }
540
+ }
541
+ }
542
+
543
+ @CaptureSpan()
544
+ private async buildGroupingKey(
545
+ incident: Incident,
546
+ rule: IncidentGroupingRule,
547
+ ): Promise<string> {
548
+ const parts: Array<string> = [];
549
+
550
+ /*
551
+ * Group by service - only if explicitly enabled
552
+ * Must be checked before monitor since service contains multiple monitors
553
+ */
554
+ if (
555
+ rule.groupByService &&
556
+ incident.monitors &&
557
+ incident.monitors.length > 0
558
+ ) {
559
+ // Use the first monitor's service for grouping
560
+ const firstMonitor: Monitor | undefined = incident.monitors[0];
561
+ if (firstMonitor && firstMonitor.id) {
562
+ const serviceMonitor: ServiceMonitor | null =
563
+ await ServiceMonitorService.findOneBy({
564
+ query: {
565
+ monitorId: firstMonitor.id,
566
+ },
567
+ select: {
568
+ serviceId: true,
569
+ },
570
+ props: {
571
+ isRoot: true,
572
+ },
573
+ });
574
+
575
+ if (serviceMonitor?.serviceId) {
576
+ parts.push(`service:${serviceMonitor.serviceId.toString()}`);
577
+ }
578
+ }
579
+ }
580
+
581
+ // Group by monitor - only if explicitly enabled
582
+ if (
583
+ rule.groupByMonitor &&
584
+ incident.monitors &&
585
+ incident.monitors.length > 0
586
+ ) {
587
+ // Use the first monitor for grouping key
588
+ const firstMonitor: Monitor | undefined = incident.monitors[0];
589
+ if (firstMonitor && firstMonitor.id) {
590
+ parts.push(`monitor:${firstMonitor.id.toString()}`);
591
+ }
592
+ }
593
+
594
+ // Group by severity - only if explicitly enabled
595
+ if (rule.groupBySeverity && incident.incidentSeverityId) {
596
+ parts.push(`severity:${incident.incidentSeverityId.toString()}`);
597
+ }
598
+
599
+ // Group by incident title - only if explicitly enabled
600
+ if (rule.groupByIncidentTitle && incident.title) {
601
+ // Normalize title for grouping (remove numbers, etc.)
602
+ const normalizedTitle: string = incident.title
603
+ .toLowerCase()
604
+ .replace(/\d+/g, "X");
605
+ parts.push(`title:${normalizedTitle}`);
606
+ }
607
+
608
+ // If no group by options are enabled, all matching incidents go into a single episode
609
+ return parts.join("|") || "default";
610
+ }
611
+
612
+ @CaptureSpan()
613
+ private async findMatchingActiveEpisode(
614
+ projectId: ObjectID,
615
+ ruleId: ObjectID,
616
+ groupingKey: string,
617
+ timeWindowCutoff: Date | null,
618
+ ): Promise<IncidentEpisode | null> {
619
+ /*
620
+ * Find active episode with matching rule and grouping key
621
+ * Active episodes have resolvedAt = null (not yet resolved)
622
+ * If time window is enabled, also filter by lastIncidentAddedAt
623
+ * If time window is disabled (timeWindowCutoff is null), find any matching active episode
624
+ */
625
+ interface EpisodeQueryType {
626
+ projectId: ObjectID;
627
+ incidentGroupingRuleId: ObjectID;
628
+ groupingKey: string;
629
+ resolvedAt: null;
630
+ lastIncidentAddedAt?: ReturnType<typeof QueryHelper.greaterThanEqualTo>;
631
+ }
632
+
633
+ const query: EpisodeQueryType = {
634
+ projectId: projectId,
635
+ incidentGroupingRuleId: ruleId,
636
+ groupingKey: groupingKey,
637
+ resolvedAt: null, // Only find active (non-resolved) episodes
638
+ };
639
+
640
+ // Only add time window filter if enabled
641
+ if (timeWindowCutoff) {
642
+ query.lastIncidentAddedAt =
643
+ QueryHelper.greaterThanEqualTo(timeWindowCutoff);
644
+ }
645
+
646
+ const episode: IncidentEpisode | null =
647
+ await IncidentEpisodeService.findOneBy({
648
+ query: query as any,
649
+ sort: {
650
+ lastIncidentAddedAt: SortOrder.Descending,
651
+ },
652
+ select: {
653
+ _id: true,
654
+ lastIncidentAddedAt: true,
655
+ },
656
+ props: {
657
+ isRoot: true,
658
+ },
659
+ });
660
+
661
+ return episode;
662
+ }
663
+
664
+ @CaptureSpan()
665
+ private async findRecentlyResolvedEpisode(
666
+ projectId: ObjectID,
667
+ ruleId: ObjectID,
668
+ groupingKey: string,
669
+ reopenCutoff: Date,
670
+ ): Promise<IncidentEpisode | null> {
671
+ // Find recently resolved episode with matching rule and grouping key
672
+ const episode: IncidentEpisode | null =
673
+ await IncidentEpisodeService.findOneBy({
674
+ query: {
675
+ projectId: projectId,
676
+ incidentGroupingRuleId: ruleId,
677
+ groupingKey: groupingKey,
678
+ resolvedAt: QueryHelper.greaterThanEqualTo(reopenCutoff),
679
+ },
680
+ sort: {
681
+ resolvedAt: SortOrder.Descending,
682
+ },
683
+ select: {
684
+ _id: true,
685
+ resolvedAt: true,
686
+ },
687
+ props: {
688
+ isRoot: true,
689
+ },
690
+ });
691
+
692
+ return episode;
693
+ }
694
+
695
+ @CaptureSpan()
696
+ private async createNewEpisode(
697
+ incident: Incident,
698
+ rule: IncidentGroupingRule,
699
+ groupingKey: string,
700
+ ): Promise<IncidentEpisode | null> {
701
+ // Generate episode title from template (with initial incidentCount of 1)
702
+ const title: string = this.generateEpisodeTitle(
703
+ incident,
704
+ rule.episodeTitleTemplate,
705
+ 1, // Initial incident count
706
+ );
707
+
708
+ // Generate episode description from template (with initial incidentCount of 1)
709
+ const description: string | undefined = this.generateEpisodeDescription(
710
+ incident,
711
+ rule.episodeDescriptionTemplate,
712
+ 1, // Initial incident count
713
+ );
714
+
715
+ const newEpisode: IncidentEpisode = new IncidentEpisode();
716
+ newEpisode.projectId = incident.projectId!;
717
+ newEpisode.title = title;
718
+ if (description) {
719
+ newEpisode.description = description;
720
+ }
721
+ /*
722
+ * Store preprocessed templates for dynamic variable updates
723
+ * Static variables are replaced, dynamic ones (like {{incidentCount}}) remain as placeholders
724
+ */
725
+ if (rule.episodeTitleTemplate) {
726
+ newEpisode.titleTemplate = this.preprocessTemplate(
727
+ incident,
728
+ rule.episodeTitleTemplate,
729
+ );
730
+ }
731
+ if (rule.episodeDescriptionTemplate) {
732
+ newEpisode.descriptionTemplate = this.preprocessTemplate(
733
+ incident,
734
+ rule.episodeDescriptionTemplate,
735
+ );
736
+ }
737
+ newEpisode.incidentGroupingRuleId = rule.id!;
738
+ newEpisode.groupingKey = groupingKey;
739
+ newEpisode.isManuallyCreated = false;
740
+
741
+ // Set severity from incident
742
+ if (incident.incidentSeverityId) {
743
+ newEpisode.incidentSeverityId = incident.incidentSeverityId;
744
+ }
745
+
746
+ // Set default ownership from rule
747
+ if (rule.defaultAssignToUserId) {
748
+ newEpisode.assignedToUserId = rule.defaultAssignToUserId;
749
+ }
750
+
751
+ if (rule.defaultAssignToTeamId) {
752
+ newEpisode.assignedToTeamId = rule.defaultAssignToTeamId;
753
+ }
754
+
755
+ // Copy on-call policies from rule
756
+ if (rule.onCallDutyPolicies && rule.onCallDutyPolicies.length > 0) {
757
+ newEpisode.onCallDutyPolicies = rule.onCallDutyPolicies;
758
+ }
759
+
760
+ try {
761
+ const createdEpisode: IncidentEpisode =
762
+ await IncidentEpisodeService.create({
763
+ data: newEpisode,
764
+ props: {
765
+ isRoot: true,
766
+ },
767
+ });
768
+
769
+ // Add episode feed entry for episode creation
770
+ if (createdEpisode.id) {
771
+ const groupByParts: Array<string> = [];
772
+
773
+ if (rule.groupByMonitor) {
774
+ groupByParts.push("Monitor");
775
+ }
776
+ if (rule.groupBySeverity) {
777
+ groupByParts.push("Severity");
778
+ }
779
+ if (rule.groupByIncidentTitle) {
780
+ groupByParts.push("Incident Title");
781
+ }
782
+ if (rule.groupByService) {
783
+ groupByParts.push("Service");
784
+ }
785
+
786
+ const groupByDescription: string =
787
+ groupByParts.length > 0
788
+ ? `Grouping by: ${groupByParts.join(", ")}`
789
+ : "Grouping all matching incidents together";
790
+
791
+ let moreInfo: string = `**Rule:** ${rule.name || "Unnamed Rule"}\n\n`;
792
+ moreInfo += `**Grouping Key:** \`${groupingKey}\`\n\n`;
793
+ moreInfo += `**${groupByDescription}**`;
794
+
795
+ if (rule.enableTimeWindow && rule.timeWindowMinutes) {
796
+ moreInfo += `\n\n**Time Window:** ${rule.timeWindowMinutes} minutes`;
797
+ }
798
+
799
+ try {
800
+ await IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
801
+ incidentEpisodeId: createdEpisode.id,
802
+ projectId: incident.projectId!,
803
+ incidentEpisodeFeedEventType:
804
+ IncidentEpisodeFeedEventType.EpisodeCreated,
805
+ displayColor: Green500,
806
+ feedInfoInMarkdown: `**Episode Created** by grouping rule **${rule.name || "Unnamed Rule"}**`,
807
+ moreInformationInMarkdown: moreInfo,
808
+ });
809
+ } catch (feedError) {
810
+ logger.error(
811
+ `Error creating episode feed for episode creation: ${feedError}`,
812
+ );
813
+ }
814
+ }
815
+
816
+ return createdEpisode;
817
+ } catch (error) {
818
+ logger.error(`Error creating new episode: ${error}`);
819
+ return null;
820
+ }
821
+ }
822
+
823
+ private generateEpisodeTitle(
824
+ incident: Incident,
825
+ template: string | undefined,
826
+ incidentCount: number = 1,
827
+ ): string {
828
+ if (!template) {
829
+ // Default title based on incident
830
+ if (
831
+ incident.monitors &&
832
+ incident.monitors.length > 0 &&
833
+ incident.monitors[0]?.name
834
+ ) {
835
+ return `Incident Episode: ${incident.monitors[0].name}`;
836
+ }
837
+ if (incident.title) {
838
+ return `Incident Episode: ${incident.title.substring(0, 50)}`;
839
+ }
840
+ return "Incident Episode";
841
+ }
842
+
843
+ return (
844
+ this.replaceTemplatePlaceholders(incident, template, incidentCount) ||
845
+ "Incident Episode"
846
+ );
847
+ }
848
+
849
+ private generateEpisodeDescription(
850
+ incident: Incident,
851
+ template: string | undefined,
852
+ incidentCount: number = 1,
853
+ ): string | undefined {
854
+ if (!template) {
855
+ return undefined;
856
+ }
857
+
858
+ return (
859
+ this.replaceTemplatePlaceholders(incident, template, incidentCount) ||
860
+ undefined
861
+ );
862
+ }
863
+
864
+ private replaceTemplatePlaceholders(
865
+ incident: Incident,
866
+ template: string,
867
+ incidentCount: number = 1,
868
+ ): string {
869
+ let result: string = template;
870
+
871
+ /*
872
+ * Static variables (from first incident)
873
+ * {{incidentTitle}}
874
+ */
875
+ if (incident.title) {
876
+ result = result.replace(/\{\{incidentTitle\}\}/g, incident.title);
877
+ }
878
+
879
+ // {{incidentDescription}}
880
+ if (incident.description) {
881
+ result = result.replace(
882
+ /\{\{incidentDescription\}\}/g,
883
+ incident.description,
884
+ );
885
+ }
886
+
887
+ // {{monitorName}} - use first monitor's name
888
+ if (
889
+ incident.monitors &&
890
+ incident.monitors.length > 0 &&
891
+ incident.monitors[0]?.name
892
+ ) {
893
+ result = result.replace(
894
+ /\{\{monitorName\}\}/g,
895
+ incident.monitors[0].name,
896
+ );
897
+ }
898
+
899
+ // {{incidentSeverity}}
900
+ if (incident.incidentSeverity?.name) {
901
+ result = result.replace(
902
+ /\{\{incidentSeverity\}\}/g,
903
+ incident.incidentSeverity.name,
904
+ );
905
+ }
906
+
907
+ /*
908
+ * Dynamic variables (updated when incidents are added/removed)
909
+ * {{incidentCount}}
910
+ */
911
+ result = result.replace(/\{\{incidentCount\}\}/g, incidentCount.toString());
912
+
913
+ // Clean up any remaining unknown placeholders
914
+ result = result.replace(/\{\{[^}]+\}\}/g, "");
915
+
916
+ return result;
917
+ }
918
+
919
+ /*
920
+ * Preprocess template: replace static variables but keep dynamic ones as placeholders
921
+ * This is stored on the episode so we can re-render with updated dynamic values later
922
+ */
923
+ private preprocessTemplate(incident: Incident, template: string): string {
924
+ let result: string = template;
925
+
926
+ /*
927
+ * Replace static variables (from first incident)
928
+ * {{incidentTitle}}
929
+ */
930
+ if (incident.title) {
931
+ result = result.replace(/\{\{incidentTitle\}\}/g, incident.title);
932
+ }
933
+
934
+ // {{incidentDescription}}
935
+ if (incident.description) {
936
+ result = result.replace(
937
+ /\{\{incidentDescription\}\}/g,
938
+ incident.description,
939
+ );
940
+ }
941
+
942
+ // {{monitorName}} - use first monitor's name
943
+ if (
944
+ incident.monitors &&
945
+ incident.monitors.length > 0 &&
946
+ incident.monitors[0]?.name
947
+ ) {
948
+ result = result.replace(
949
+ /\{\{monitorName\}\}/g,
950
+ incident.monitors[0].name,
951
+ );
952
+ }
953
+
954
+ // {{incidentSeverity}}
955
+ if (incident.incidentSeverity?.name) {
956
+ result = result.replace(
957
+ /\{\{incidentSeverity\}\}/g,
958
+ incident.incidentSeverity.name,
959
+ );
960
+ }
961
+
962
+ /*
963
+ * Keep dynamic variables as placeholders (e.g., {{incidentCount}})
964
+ * They will be replaced when title/description is re-rendered
965
+ */
966
+
967
+ return result;
968
+ }
969
+
970
+ @CaptureSpan()
971
+ private async addIncidentToEpisode(
972
+ incident: Incident,
973
+ episodeId: ObjectID,
974
+ addedBy: IncidentEpisodeMemberAddedBy,
975
+ ruleId?: ObjectID,
976
+ ): Promise<void> {
977
+ const member: IncidentEpisodeMember = new IncidentEpisodeMember();
978
+ member.projectId = incident.projectId!;
979
+ member.incidentEpisodeId = episodeId;
980
+ member.incidentId = incident.id!;
981
+ member.addedBy = addedBy;
982
+
983
+ if (ruleId) {
984
+ member.matchedRuleId = ruleId;
985
+ }
986
+
987
+ try {
988
+ await IncidentEpisodeMemberService.create({
989
+ data: member,
990
+ props: {
991
+ isRoot: true,
992
+ },
993
+ });
994
+
995
+ // Feed entries are created by IncidentEpisodeMemberService.onCreateSuccess
996
+ } catch (error) {
997
+ // Check if it's a duplicate error (incident already in episode)
998
+ if (
999
+ error instanceof Error &&
1000
+ error.message.includes("already a member")
1001
+ ) {
1002
+ logger.debug(
1003
+ `Incident ${incident.id} is already in episode ${episodeId}`,
1004
+ );
1005
+ return;
1006
+ }
1007
+ throw error;
1008
+ }
1009
+ }
1010
+
1011
+ @CaptureSpan()
1012
+ public async addIncidentToEpisodeManually(
1013
+ incident: Incident,
1014
+ episodeId: ObjectID,
1015
+ addedByUserId?: ObjectID,
1016
+ ): Promise<void> {
1017
+ const member: IncidentEpisodeMember = new IncidentEpisodeMember();
1018
+ member.projectId = incident.projectId!;
1019
+ member.incidentEpisodeId = episodeId;
1020
+ member.incidentId = incident.id!;
1021
+ member.addedBy = IncidentEpisodeMemberAddedBy.Manual;
1022
+
1023
+ if (addedByUserId) {
1024
+ member.addedByUserId = addedByUserId;
1025
+ }
1026
+
1027
+ await IncidentEpisodeMemberService.create({
1028
+ data: member,
1029
+ props: {
1030
+ isRoot: true,
1031
+ },
1032
+ });
1033
+
1034
+ // Feed entries are created by IncidentEpisodeMemberService.onCreateSuccess
1035
+
1036
+ // Update episode severity if needed
1037
+ if (incident.incidentSeverityId) {
1038
+ await IncidentEpisodeService.updateEpisodeSeverity(
1039
+ episodeId,
1040
+ incident.incidentSeverityId,
1041
+ true, // onlyIfHigher
1042
+ );
1043
+ }
1044
+ }
1045
+ }
1046
+
1047
+ export default new IncidentGroupingEngineServiceClass();