@oneuptime/common 9.4.7 → 9.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/Models/DatabaseModels/Alert.ts +76 -0
  2. package/Models/DatabaseModels/AlertEpisode.ts +1201 -0
  3. package/Models/DatabaseModels/AlertEpisodeFeed.ts +529 -0
  4. package/Models/DatabaseModels/AlertEpisodeInternalNote.ts +455 -0
  5. package/Models/DatabaseModels/AlertEpisodeMember.ts +586 -0
  6. package/Models/DatabaseModels/AlertEpisodeOwnerTeam.ts +421 -0
  7. package/Models/DatabaseModels/AlertEpisodeOwnerUser.ts +419 -0
  8. package/Models/DatabaseModels/AlertEpisodeStateTimeline.ts +523 -0
  9. package/Models/DatabaseModels/AlertFeed.ts +1 -0
  10. package/Models/DatabaseModels/AlertGroupingRule.ts +1432 -0
  11. package/Models/DatabaseModels/Index.ts +18 -0
  12. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +70 -0
  13. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.ts +59 -0
  14. package/Models/DatabaseModels/StatusPageDomain.ts +2 -0
  15. package/Models/DatabaseModels/UserOnCallLog.ts +48 -0
  16. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +49 -0
  17. package/Models/DatabaseModels/WorkspaceNotificationLog.ts +57 -0
  18. package/Server/API/SlackAPI.ts +21 -0
  19. package/Server/API/UserOnCallLogTimelineAPI.ts +65 -25
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1768938069147-MigrationName.ts +751 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1769125561322-MigrationName.ts +41 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1769170578688-MigrationName.ts +29 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1769172358833-MigrationName.ts +177 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1769176450526-MigrationName.ts +71 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1769190495840-MigrationName.ts +35 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1769199303656-MigrationName.ts +29 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1769202898645-MigrationName.ts +29 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1769428619414-MigrationName.ts +35 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1769428821686-MigrationName.ts +47 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1769469813786-MigrationName.ts +71 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1769517677937-RenameNotificationRuleTypes.ts +67 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +24 -0
  33. package/Server/Services/AlertEpisodeFeedService.ts +94 -0
  34. package/Server/Services/AlertEpisodeInternalNoteService.ts +71 -0
  35. package/Server/Services/AlertEpisodeMemberService.ts +267 -0
  36. package/Server/Services/AlertEpisodeOwnerTeamService.ts +10 -0
  37. package/Server/Services/AlertEpisodeOwnerUserService.ts +10 -0
  38. package/Server/Services/AlertEpisodeService.ts +1096 -0
  39. package/Server/Services/AlertEpisodeStateTimelineService.ts +557 -0
  40. package/Server/Services/AlertGroupingEngineService.ts +1120 -0
  41. package/Server/Services/AlertGroupingRuleService.ts +14 -0
  42. package/Server/Services/AlertService.ts +12 -0
  43. package/Server/Services/CallService.ts +2 -0
  44. package/Server/Services/Index.ts +21 -0
  45. package/Server/Services/MailService.ts +5 -0
  46. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +18 -1
  47. package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +64 -2
  48. package/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +27 -1
  49. package/Server/Services/OnCallDutyPolicyService.ts +16 -1
  50. package/Server/Services/PushNotificationService.ts +1 -0
  51. package/Server/Services/SmsService.ts +2 -0
  52. package/Server/Services/UserNotificationRuleService.ts +641 -10
  53. package/Server/Services/UserNotificationSettingService.ts +23 -0
  54. package/Server/Services/UserOnCallLogService.ts +58 -14
  55. package/Server/Services/WhatsAppService.ts +5 -0
  56. package/Server/Services/WorkspaceNotificationRuleService.ts +26 -0
  57. package/Server/Utils/AnalyticsDatabase/Statement.ts +6 -2
  58. package/Server/Utils/PushNotificationUtil.ts +75 -16
  59. package/Server/Utils/WhatsAppTemplateUtil.ts +13 -0
  60. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ActionTypes.ts +18 -0
  61. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +689 -0
  62. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +16 -0
  63. package/Server/Utils/Workspace/Slack/Actions/ActionTypes.ts +11 -0
  64. package/Server/Utils/Workspace/Slack/Actions/AlertEpisode.ts +915 -0
  65. package/Server/Utils/Workspace/Slack/Messages/AlertEpisode.ts +120 -0
  66. package/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.ts +74 -0
  67. package/Tests/Server/Services/AlertEpisodeMemberService.test.ts +200 -0
  68. package/Tests/Server/Services/AlertEpisodeService.test.ts +240 -0
  69. package/Tests/Server/Services/AlertGroupingEngineService.test.ts +542 -0
  70. package/Tests/Server/Services/AlertGroupingRuleService.test.ts +383 -0
  71. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +1 -1
  72. package/Tests/UI/Components/Input.test.tsx +1 -1
  73. package/Tests/UI/Components/TextArea.test.tsx +2 -2
  74. package/Types/BaseDatabase/SortOrder.ts +9 -0
  75. package/Types/Email/EmailTemplateType.ts +6 -0
  76. package/Types/NotificationRule/NotificationRuleType.ts +3 -1
  77. package/Types/NotificationSetting/NotificationSettingEventType.ts +7 -0
  78. package/Types/Permission.ts +309 -0
  79. package/Types/UserNotification/UserNotificationEventType.ts +1 -0
  80. package/Types/WhatsApp/WhatsAppTemplates.ts +24 -0
  81. package/Types/Workspace/NotificationRules/EventType.ts +1 -0
  82. package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +32 -3
  83. package/UI/Components/Accordion/Accordion.tsx +20 -2
  84. package/UI/Components/Alerts/Alert.tsx +1 -0
  85. package/UI/Components/Button/Button.tsx +29 -0
  86. package/UI/Components/CardSelect/CardSelect.tsx +5 -1
  87. package/UI/Components/Checkbox/Checkbox.tsx +7 -3
  88. package/UI/Components/ColorCircle/ColorCircle.tsx +2 -0
  89. package/UI/Components/ColorViewer/ColorViewer.tsx +19 -3
  90. package/UI/Components/CopyableButton/CopyableButton.tsx +22 -5
  91. package/UI/Components/Detail/Detail.tsx +1 -1
  92. package/UI/Components/Dropdown/Dropdown.tsx +14 -1
  93. package/UI/Components/Forms/Fields/FormField.tsx +28 -0
  94. package/UI/Components/FullPageModal/FullPageModal.tsx +35 -4
  95. package/UI/Components/Input/Input.tsx +14 -2
  96. package/UI/Components/Link/Link.tsx +1 -0
  97. package/UI/Components/Loader/Loader.tsx +8 -2
  98. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +76 -1
  99. package/UI/Components/Modal/Modal.tsx +47 -3
  100. package/UI/Components/ModelTable/BaseModelTable.tsx +42 -1
  101. package/UI/Components/MoreMenu/MoreMenu.tsx +84 -2
  102. package/UI/Components/OrderedStatesList/OrderedStatesList.tsx +30 -8
  103. package/UI/Components/Pagination/Pagination.tsx +113 -8
  104. package/UI/Components/ProgressBar/ProgressBar.tsx +12 -2
  105. package/UI/Components/Radio/Radio.tsx +21 -3
  106. package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +54 -27
  107. package/UI/Components/StatusBubble/StatusBubble.tsx +7 -2
  108. package/UI/Components/Table/TableHeader.tsx +20 -3
  109. package/UI/Components/Tabs/Tab.tsx +16 -1
  110. package/UI/Components/Tabs/Tabs.tsx +12 -1
  111. package/UI/Components/TextArea/TextArea.tsx +12 -2
  112. package/UI/Components/Toggle/Toggle.tsx +14 -3
  113. package/UI/Components/Tooltip/Tooltip.tsx +11 -1
  114. package/UI/Components/TopAlert/TopAlert.tsx +2 -0
  115. package/build/dist/Models/DatabaseModels/Alert.js +77 -0
  116. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  117. package/build/dist/Models/DatabaseModels/AlertEpisode.js +1225 -0
  118. package/build/dist/Models/DatabaseModels/AlertEpisode.js.map +1 -0
  119. package/build/dist/Models/DatabaseModels/AlertEpisodeFeed.js +553 -0
  120. package/build/dist/Models/DatabaseModels/AlertEpisodeFeed.js.map +1 -0
  121. package/build/dist/Models/DatabaseModels/AlertEpisodeInternalNote.js +467 -0
  122. package/build/dist/Models/DatabaseModels/AlertEpisodeInternalNote.js.map +1 -0
  123. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +607 -0
  124. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -0
  125. package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerTeam.js +437 -0
  126. package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerTeam.js.map +1 -0
  127. package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerUser.js +436 -0
  128. package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerUser.js.map +1 -0
  129. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js +546 -0
  130. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js.map +1 -0
  131. package/build/dist/Models/DatabaseModels/AlertFeed.js +1 -0
  132. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  133. package/build/dist/Models/DatabaseModels/AlertGroupingRule.js +1437 -0
  134. package/build/dist/Models/DatabaseModels/AlertGroupingRule.js.map +1 -0
  135. package/build/dist/Models/DatabaseModels/Index.js +16 -0
  136. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  137. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +69 -0
  138. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  139. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.js +58 -0
  140. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLogTimeline.js.map +1 -1
  141. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +2 -0
  142. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  143. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +47 -0
  144. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  145. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +48 -0
  146. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  147. package/build/dist/Models/DatabaseModels/WorkspaceNotificationLog.js +58 -0
  148. package/build/dist/Models/DatabaseModels/WorkspaceNotificationLog.js.map +1 -1
  149. package/build/dist/Server/API/SlackAPI.js +18 -0
  150. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  151. package/build/dist/Server/API/UserOnCallLogTimelineAPI.js +55 -15
  152. package/build/dist/Server/API/UserOnCallLogTimelineAPI.js.map +1 -1
  153. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1768938069147-MigrationName.js +266 -0
  154. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1768938069147-MigrationName.js.map +1 -0
  155. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769125561322-MigrationName.js +20 -0
  156. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769125561322-MigrationName.js.map +1 -0
  157. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769170578688-MigrationName.js +16 -0
  158. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769170578688-MigrationName.js.map +1 -0
  159. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769172358833-MigrationName.js +68 -0
  160. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769172358833-MigrationName.js.map +1 -0
  161. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769176450526-MigrationName.js +30 -0
  162. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769176450526-MigrationName.js.map +1 -0
  163. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769190495840-MigrationName.js +18 -0
  164. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769190495840-MigrationName.js.map +1 -0
  165. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769199303656-MigrationName.js +16 -0
  166. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769199303656-MigrationName.js.map +1 -0
  167. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769202898645-MigrationName.js +16 -0
  168. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769202898645-MigrationName.js.map +1 -0
  169. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769428619414-MigrationName.js +18 -0
  170. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769428619414-MigrationName.js.map +1 -0
  171. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769428821686-MigrationName.js +22 -0
  172. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769428821686-MigrationName.js.map +1 -0
  173. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769469813786-MigrationName.js +30 -0
  174. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769469813786-MigrationName.js.map +1 -0
  175. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769517677937-RenameNotificationRuleTypes.js +67 -0
  176. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769517677937-RenameNotificationRuleTypes.js.map +1 -0
  177. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +24 -0
  178. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  179. package/build/dist/Server/Services/AlertEpisodeFeedService.js +83 -0
  180. package/build/dist/Server/Services/AlertEpisodeFeedService.js.map +1 -0
  181. package/build/dist/Server/Services/AlertEpisodeInternalNoteService.js +70 -0
  182. package/build/dist/Server/Services/AlertEpisodeInternalNoteService.js.map +1 -0
  183. package/build/dist/Server/Services/AlertEpisodeMemberService.js +256 -0
  184. package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -0
  185. package/build/dist/Server/Services/AlertEpisodeOwnerTeamService.js +9 -0
  186. package/build/dist/Server/Services/AlertEpisodeOwnerTeamService.js.map +1 -0
  187. package/build/dist/Server/Services/AlertEpisodeOwnerUserService.js +9 -0
  188. package/build/dist/Server/Services/AlertEpisodeOwnerUserService.js.map +1 -0
  189. package/build/dist/Server/Services/AlertEpisodeService.js +973 -0
  190. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -0
  191. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +494 -0
  192. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -0
  193. package/build/dist/Server/Services/AlertGroupingEngineService.js +893 -0
  194. package/build/dist/Server/Services/AlertGroupingEngineService.js.map +1 -0
  195. package/build/dist/Server/Services/AlertGroupingRuleService.js +13 -0
  196. package/build/dist/Server/Services/AlertGroupingRuleService.js.map +1 -0
  197. package/build/dist/Server/Services/AlertService.js +11 -0
  198. package/build/dist/Server/Services/AlertService.js.map +1 -1
  199. package/build/dist/Server/Services/CallService.js +11 -10
  200. package/build/dist/Server/Services/CallService.js.map +1 -1
  201. package/build/dist/Server/Services/Index.js +18 -0
  202. package/build/dist/Server/Services/Index.js.map +1 -1
  203. package/build/dist/Server/Services/MailService.js +3 -0
  204. package/build/dist/Server/Services/MailService.js.map +1 -1
  205. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +10 -1
  206. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  207. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +49 -2
  208. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
  209. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js +21 -1
  210. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js.map +1 -1
  211. package/build/dist/Server/Services/OnCallDutyPolicyService.js +9 -1
  212. package/build/dist/Server/Services/OnCallDutyPolicyService.js.map +1 -1
  213. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  214. package/build/dist/Server/Services/SmsService.js +11 -10
  215. package/build/dist/Server/Services/SmsService.js.map +1 -1
  216. package/build/dist/Server/Services/UserNotificationRuleService.js +521 -43
  217. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  218. package/build/dist/Server/Services/UserNotificationSettingService.js +9 -0
  219. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  220. package/build/dist/Server/Services/UserOnCallLogService.js +48 -12
  221. package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
  222. package/build/dist/Server/Services/WhatsAppService.js +3 -0
  223. package/build/dist/Server/Services/WhatsAppService.js.map +1 -1
  224. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +25 -0
  225. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  226. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +4 -2
  227. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
  228. package/build/dist/Server/Utils/PushNotificationUtil.js +51 -16
  229. package/build/dist/Server/Utils/PushNotificationUtil.js.map +1 -1
  230. package/build/dist/Server/Utils/WhatsAppTemplateUtil.js +8 -0
  231. package/build/dist/Server/Utils/WhatsAppTemplateUtil.js.map +1 -1
  232. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ActionTypes.js +17 -0
  233. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ActionTypes.js.map +1 -1
  234. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +545 -0
  235. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -0
  236. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +13 -0
  237. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  238. package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js +10 -0
  239. package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js.map +1 -1
  240. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js +651 -0
  241. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js.map +1 -0
  242. package/build/dist/Server/Utils/Workspace/Slack/Messages/AlertEpisode.js +100 -0
  243. package/build/dist/Server/Utils/Workspace/Slack/Messages/AlertEpisode.js.map +1 -0
  244. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.js +70 -0
  245. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.js.map +1 -0
  246. package/build/dist/Tests/Server/Services/AlertEpisodeMemberService.test.js +165 -0
  247. package/build/dist/Tests/Server/Services/AlertEpisodeMemberService.test.js.map +1 -0
  248. package/build/dist/Tests/Server/Services/AlertEpisodeService.test.js +193 -0
  249. package/build/dist/Tests/Server/Services/AlertEpisodeService.test.js.map +1 -0
  250. package/build/dist/Tests/Server/Services/AlertGroupingEngineService.test.js +412 -0
  251. package/build/dist/Tests/Server/Services/AlertGroupingEngineService.test.js.map +1 -0
  252. package/build/dist/Tests/Server/Services/AlertGroupingRuleService.test.js +308 -0
  253. package/build/dist/Tests/Server/Services/AlertGroupingRuleService.test.js.map +1 -0
  254. package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js +1 -1
  255. package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js.map +1 -1
  256. package/build/dist/Tests/UI/Components/Input.test.js +1 -1
  257. package/build/dist/Tests/UI/Components/Input.test.js.map +1 -1
  258. package/build/dist/Tests/UI/Components/TextArea.test.js +2 -2
  259. package/build/dist/Tests/UI/Components/TextArea.test.js.map +1 -1
  260. package/build/dist/Types/BaseDatabase/SortOrder.js +5 -0
  261. package/build/dist/Types/BaseDatabase/SortOrder.js.map +1 -1
  262. package/build/dist/Types/Email/EmailTemplateType.js +5 -0
  263. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  264. package/build/dist/Types/NotificationRule/NotificationRuleType.js +3 -1
  265. package/build/dist/Types/NotificationRule/NotificationRuleType.js.map +1 -1
  266. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +5 -0
  267. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  268. package/build/dist/Types/Permission.js +264 -0
  269. package/build/dist/Types/Permission.js.map +1 -1
  270. package/build/dist/Types/UserNotification/UserNotificationEventType.js +1 -0
  271. package/build/dist/Types/UserNotification/UserNotificationEventType.js.map +1 -1
  272. package/build/dist/Types/WhatsApp/WhatsAppTemplates.js +15 -0
  273. package/build/dist/Types/WhatsApp/WhatsAppTemplates.js.map +1 -1
  274. package/build/dist/Types/Workspace/NotificationRules/EventType.js +1 -0
  275. package/build/dist/Types/Workspace/NotificationRules/EventType.js.map +1 -1
  276. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +28 -3
  277. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
  278. package/build/dist/UI/Components/Accordion/Accordion.js +10 -3
  279. package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
  280. package/build/dist/UI/Components/Alerts/Alert.js +1 -1
  281. package/build/dist/UI/Components/Alerts/Alert.js.map +1 -1
  282. package/build/dist/UI/Components/Button/Button.js +8 -2
  283. package/build/dist/UI/Components/Button/Button.js.map +1 -1
  284. package/build/dist/UI/Components/CardSelect/CardSelect.js +1 -1
  285. package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
  286. package/build/dist/UI/Components/Checkbox/Checkbox.js +2 -2
  287. package/build/dist/UI/Components/Checkbox/Checkbox.js.map +1 -1
  288. package/build/dist/UI/Components/ColorCircle/ColorCircle.js +1 -1
  289. package/build/dist/UI/Components/ColorCircle/ColorCircle.js.map +1 -1
  290. package/build/dist/UI/Components/ColorViewer/ColorViewer.js +12 -3
  291. package/build/dist/UI/Components/ColorViewer/ColorViewer.js.map +1 -1
  292. package/build/dist/UI/Components/CopyableButton/CopyableButton.js +12 -5
  293. package/build/dist/UI/Components/CopyableButton/CopyableButton.js.map +1 -1
  294. package/build/dist/UI/Components/Detail/Detail.js +1 -1
  295. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  296. package/build/dist/UI/Components/Dropdown/Dropdown.js +5 -3
  297. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  298. package/build/dist/UI/Components/Forms/Fields/FormField.js +19 -1
  299. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  300. package/build/dist/UI/Components/FullPageModal/FullPageModal.js +24 -5
  301. package/build/dist/UI/Components/FullPageModal/FullPageModal.js.map +1 -1
  302. package/build/dist/UI/Components/Input/Input.js +3 -3
  303. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  304. package/build/dist/UI/Components/Link/Link.js +1 -1
  305. package/build/dist/UI/Components/Link/Link.js.map +1 -1
  306. package/build/dist/UI/Components/Loader/Loader.js +6 -4
  307. package/build/dist/UI/Components/Loader/Loader.js.map +1 -1
  308. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +56 -3
  309. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  310. package/build/dist/UI/Components/Modal/Modal.js +28 -3
  311. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  312. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +23 -1
  313. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  314. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +67 -6
  315. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  316. package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js +14 -3
  317. package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js.map +1 -1
  318. package/build/dist/UI/Components/Pagination/Pagination.js +69 -13
  319. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  320. package/build/dist/UI/Components/ProgressBar/ProgressBar.js +2 -2
  321. package/build/dist/UI/Components/ProgressBar/ProgressBar.js.map +1 -1
  322. package/build/dist/UI/Components/Radio/Radio.js +8 -5
  323. package/build/dist/UI/Components/Radio/Radio.js.map +1 -1
  324. package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +23 -4
  325. package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
  326. package/build/dist/UI/Components/StatusBubble/StatusBubble.js +2 -2
  327. package/build/dist/UI/Components/StatusBubble/StatusBubble.js.map +1 -1
  328. package/build/dist/UI/Components/Table/TableHeader.js +12 -4
  329. package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
  330. package/build/dist/UI/Components/Tabs/Tab.js +8 -1
  331. package/build/dist/UI/Components/Tabs/Tab.js.map +1 -1
  332. package/build/dist/UI/Components/Tabs/Tabs.js +4 -3
  333. package/build/dist/UI/Components/Tabs/Tabs.js.map +1 -1
  334. package/build/dist/UI/Components/TextArea/TextArea.js +3 -3
  335. package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
  336. package/build/dist/UI/Components/Toggle/Toggle.js +7 -4
  337. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  338. package/build/dist/UI/Components/Tooltip/Tooltip.js +4 -1
  339. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  340. package/build/dist/UI/Components/TopAlert/TopAlert.js +1 -1
  341. package/build/dist/UI/Components/TopAlert/TopAlert.js.map +1 -1
  342. package/package.json +2 -1
@@ -0,0 +1,1096 @@
1
+ import CreateBy from "../Types/Database/CreateBy";
2
+ import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
3
+ import DatabaseService from "./DatabaseService";
4
+ import AlertStateService from "./AlertStateService";
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/AlertEpisode";
9
+ import AlertState from "../../Models/DatabaseModels/AlertState";
10
+ import AlertSeverity from "../../Models/DatabaseModels/AlertSeverity";
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 AlertEpisodeStateTimeline from "../../Models/DatabaseModels/AlertEpisodeStateTimeline";
16
+ import AlertEpisodeStateTimelineService from "./AlertEpisodeStateTimelineService";
17
+ import { IsBillingEnabled } from "../EnvironmentConfig";
18
+ import OneUptimeDate from "../../Types/Date";
19
+ import AlertEpisodeFeedService from "./AlertEpisodeFeedService";
20
+ import { AlertEpisodeFeedEventType } from "../../Models/DatabaseModels/AlertEpisodeFeed";
21
+ import {
22
+ Red500,
23
+ Green500,
24
+ Yellow500,
25
+ Purple500,
26
+ } from "../../Types/BrandColors";
27
+ import URL from "../../Types/API/URL";
28
+ import DatabaseConfig from "../DatabaseConfig";
29
+ import AlertSeverityService from "./AlertSeverityService";
30
+ import AlertEpisodeMemberService from "./AlertEpisodeMemberService";
31
+ import AlertEpisodeOwnerUserService from "./AlertEpisodeOwnerUserService";
32
+ import AlertEpisodeOwnerTeamService from "./AlertEpisodeOwnerTeamService";
33
+ import TeamMemberService from "./TeamMemberService";
34
+ import AlertEpisodeOwnerUser from "../../Models/DatabaseModels/AlertEpisodeOwnerUser";
35
+ import AlertEpisodeOwnerTeam from "../../Models/DatabaseModels/AlertEpisodeOwnerTeam";
36
+ import AlertEpisodeMember from "../../Models/DatabaseModels/AlertEpisodeMember";
37
+ import User from "../../Models/DatabaseModels/User";
38
+ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
39
+ import NotificationRuleWorkspaceChannel from "../../Types/Workspace/NotificationRules/NotificationRuleWorkspaceChannel";
40
+ import WorkspaceType from "../../Types/Workspace/WorkspaceType";
41
+ import Typeof from "../../Types/Typeof";
42
+ import AlertService from "./AlertService";
43
+ import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
44
+ import OnCallDutyPolicyService from "./OnCallDutyPolicyService";
45
+ import OnCallDutyPolicy from "../../Models/DatabaseModels/OnCallDutyPolicy";
46
+ import UserNotificationEventType from "../../Types/UserNotification/UserNotificationEventType";
47
+
48
+ export class Service extends DatabaseService<Model> {
49
+ public constructor() {
50
+ super(Model);
51
+ if (IsBillingEnabled) {
52
+ this.hardDeleteItemsOlderThanInDays("createdAt", 3 * 365); // 3 years
53
+ }
54
+ }
55
+
56
+ @CaptureSpan()
57
+ public async getExistingEpisodeNumberForProject(data: {
58
+ projectId: ObjectID;
59
+ }): Promise<number> {
60
+ const lastEpisode: Model | null = await this.findOneBy({
61
+ query: {
62
+ projectId: data.projectId,
63
+ },
64
+ select: {
65
+ episodeNumber: true,
66
+ },
67
+ sort: {
68
+ episodeNumber: SortOrder.Descending,
69
+ },
70
+ props: {
71
+ isRoot: true,
72
+ },
73
+ });
74
+
75
+ if (!lastEpisode) {
76
+ return 0;
77
+ }
78
+
79
+ return lastEpisode.episodeNumber ? Number(lastEpisode.episodeNumber) : 0;
80
+ }
81
+
82
+ @CaptureSpan()
83
+ protected override async onBeforeCreate(
84
+ createBy: CreateBy<Model>,
85
+ ): Promise<OnCreate<Model>> {
86
+ if (!createBy.props.tenantId && !createBy.props.isRoot) {
87
+ throw new BadDataException("ProjectId required to create alert episode.");
88
+ }
89
+
90
+ const projectId: ObjectID =
91
+ createBy.props.tenantId || createBy.data.projectId!;
92
+
93
+ let mutex: SemaphoreMutex | null = null;
94
+
95
+ try {
96
+ // Acquire mutex to prevent race conditions when generating episode numbers
97
+ try {
98
+ mutex = await Semaphore.lock({
99
+ key: projectId.toString(),
100
+ namespace: "AlertEpisode.create",
101
+ });
102
+ } catch (err) {
103
+ logger.error(err);
104
+ }
105
+
106
+ // Get the created state for episodes
107
+ const alertState: AlertState | null = await AlertStateService.findOneBy({
108
+ query: {
109
+ projectId: projectId,
110
+ isCreatedState: true,
111
+ },
112
+ select: {
113
+ _id: true,
114
+ },
115
+ props: {
116
+ isRoot: true,
117
+ },
118
+ });
119
+
120
+ if (!alertState || !alertState.id) {
121
+ throw new BadDataException(
122
+ "Created alert state not found for this project. Please add created alert state from settings.",
123
+ );
124
+ }
125
+
126
+ createBy.data.currentAlertStateId = alertState.id;
127
+
128
+ // Auto-generate episode number
129
+ const episodeNumberForThisEpisode: number =
130
+ (await this.getExistingEpisodeNumberForProject({
131
+ projectId: projectId,
132
+ })) + 1;
133
+
134
+ createBy.data.episodeNumber = episodeNumberForThisEpisode;
135
+
136
+ // Set initial lastAlertAddedAt
137
+ if (!createBy.data.lastAlertAddedAt) {
138
+ createBy.data.lastAlertAddedAt = OneUptimeDate.getCurrentDate();
139
+ }
140
+
141
+ return { createBy, carryForward: { mutex } };
142
+ } catch (error) {
143
+ // Release the mutex if it was acquired and an error occurred
144
+ if (mutex) {
145
+ try {
146
+ await Semaphore.release(mutex);
147
+ } catch (err) {
148
+ logger.error(err);
149
+ }
150
+ }
151
+ throw error;
152
+ }
153
+ }
154
+
155
+ @CaptureSpan()
156
+ protected override async onCreateSuccess(
157
+ onCreate: OnCreate<Model>,
158
+ createdItem: Model,
159
+ ): Promise<Model> {
160
+ // Release the mutex acquired in onBeforeCreate
161
+ const mutex: SemaphoreMutex | null = onCreate.carryForward?.mutex || null;
162
+ if (mutex) {
163
+ try {
164
+ await Semaphore.release(mutex);
165
+ } catch (err) {
166
+ logger.error(err);
167
+ }
168
+ }
169
+
170
+ if (!createdItem.projectId) {
171
+ throw new BadDataException("projectId is required");
172
+ }
173
+
174
+ if (!createdItem.id) {
175
+ throw new BadDataException("id is required");
176
+ }
177
+
178
+ if (!createdItem.currentAlertStateId) {
179
+ throw new BadDataException("currentAlertStateId is required");
180
+ }
181
+
182
+ // Create initial state timeline entry
183
+ Promise.resolve()
184
+ .then(async () => {
185
+ try {
186
+ await this.changeEpisodeState({
187
+ projectId: createdItem.projectId!,
188
+ episodeId: createdItem.id!,
189
+ alertStateId: createdItem.currentAlertStateId!,
190
+ notifyOwners: false,
191
+ rootCause: undefined,
192
+ props: {
193
+ isRoot: true,
194
+ },
195
+ });
196
+ } catch (error) {
197
+ logger.error(
198
+ `Handle episode state change failed in AlertEpisodeService.onCreateSuccess: ${error}`,
199
+ );
200
+ }
201
+ })
202
+ .then(async () => {
203
+ try {
204
+ await this.createEpisodeCreatedFeed(createdItem);
205
+ } catch (error) {
206
+ logger.error(
207
+ `Create episode feed failed in AlertEpisodeService.onCreateSuccess: ${error}`,
208
+ );
209
+ }
210
+ })
211
+ .then(async () => {
212
+ // Execute on-call duty policies
213
+ try {
214
+ await this.executeEpisodeOnCallDutyPoliciesAsync(createdItem);
215
+ } catch (error) {
216
+ logger.error(
217
+ `On-call duty policy execution failed in AlertEpisodeService.onCreateSuccess: ${error}`,
218
+ );
219
+ }
220
+ })
221
+ .catch((error: Error) => {
222
+ logger.error(
223
+ `Critical error in AlertEpisodeService.onCreateSuccess: ${error}`,
224
+ );
225
+ });
226
+
227
+ return createdItem;
228
+ }
229
+
230
+ @CaptureSpan()
231
+ private async createEpisodeCreatedFeed(episode: Model): Promise<void> {
232
+ if (!episode.id || !episode.projectId) {
233
+ return;
234
+ }
235
+
236
+ let feedInfoInMarkdown: string = `#### Episode ${episode.episodeNumber?.toString()} Created
237
+
238
+ **${episode.title || "No title provided."}**
239
+
240
+ `;
241
+
242
+ if (episode.description) {
243
+ feedInfoInMarkdown += `${episode.description}\n\n`;
244
+ }
245
+
246
+ if (episode.isManuallyCreated) {
247
+ feedInfoInMarkdown += `This episode was manually created.\n\n`;
248
+ }
249
+
250
+ await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
251
+ alertEpisodeId: episode.id,
252
+ projectId: episode.projectId,
253
+ alertEpisodeFeedEventType: AlertEpisodeFeedEventType.EpisodeCreated,
254
+ displayColor: Red500,
255
+ feedInfoInMarkdown: feedInfoInMarkdown,
256
+ userId: episode.createdByUserId || undefined,
257
+ });
258
+ }
259
+
260
+ @CaptureSpan()
261
+ private async executeEpisodeOnCallDutyPoliciesAsync(
262
+ createdItem: Model,
263
+ ): Promise<void> {
264
+ if (!createdItem.id || !createdItem.projectId) {
265
+ return;
266
+ }
267
+
268
+ try {
269
+ // Fetch the episode with on-call duty policies since they may not be loaded
270
+ const episodeWithPolicies: Model | null = await this.findOneById({
271
+ id: createdItem.id,
272
+ select: {
273
+ onCallDutyPolicies: {
274
+ _id: true,
275
+ name: true,
276
+ },
277
+ },
278
+ props: {
279
+ isRoot: true,
280
+ },
281
+ });
282
+
283
+ if (
284
+ !episodeWithPolicies?.onCallDutyPolicies?.length ||
285
+ episodeWithPolicies.onCallDutyPolicies.length === 0
286
+ ) {
287
+ return;
288
+ }
289
+
290
+ // Execute all on-call policies in parallel
291
+ const policyPromises: Promise<void>[] =
292
+ episodeWithPolicies.onCallDutyPolicies.map(
293
+ (policy: OnCallDutyPolicy) => {
294
+ return OnCallDutyPolicyService.executePolicy(
295
+ new ObjectID(policy._id as string),
296
+ {
297
+ triggeredByAlertEpisodeId: createdItem.id!,
298
+ userNotificationEventType:
299
+ UserNotificationEventType.AlertEpisodeCreated,
300
+ },
301
+ );
302
+ },
303
+ );
304
+
305
+ await Promise.allSettled(policyPromises);
306
+
307
+ // Update the flag to indicate on-call policy has been executed
308
+ await this.updateOneById({
309
+ id: createdItem.id,
310
+ data: {
311
+ isOnCallPolicyExecuted: true,
312
+ },
313
+ props: {
314
+ isRoot: true,
315
+ },
316
+ });
317
+
318
+ // Create feed entry for on-call policy execution
319
+ const policyNames: string[] = episodeWithPolicies.onCallDutyPolicies
320
+ .map((policy: OnCallDutyPolicy) => {
321
+ return policy.name || "Unnamed Policy";
322
+ })
323
+ .filter((name: string) => {
324
+ return Boolean(name);
325
+ });
326
+
327
+ let feedInfoInMarkdown: string = `#### On-Call Policy Executed\n\n`;
328
+ feedInfoInMarkdown += `The following on-call ${policyNames.length === 1 ? "policy has" : "policies have"} been executed for this episode:\n\n`;
329
+
330
+ for (const policyName of policyNames) {
331
+ feedInfoInMarkdown += `- ${policyName}\n`;
332
+ }
333
+
334
+ await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
335
+ alertEpisodeId: createdItem.id,
336
+ projectId: createdItem.projectId,
337
+ alertEpisodeFeedEventType: AlertEpisodeFeedEventType.OnCallPolicy,
338
+ displayColor: Purple500,
339
+ feedInfoInMarkdown: feedInfoInMarkdown,
340
+ });
341
+ } catch (error) {
342
+ logger.error(`Error in executeEpisodeOnCallDutyPoliciesAsync: ${error}`);
343
+ throw error;
344
+ }
345
+ }
346
+
347
+ @CaptureSpan()
348
+ public async changeEpisodeState(data: {
349
+ projectId: ObjectID;
350
+ episodeId: ObjectID;
351
+ alertStateId: ObjectID;
352
+ notifyOwners: boolean;
353
+ rootCause: string | undefined;
354
+ props: DatabaseCommonInteractionProps;
355
+ cascadeToAlerts?: boolean;
356
+ }): Promise<void> {
357
+ const {
358
+ projectId,
359
+ episodeId,
360
+ alertStateId,
361
+ notifyOwners,
362
+ rootCause,
363
+ props,
364
+ cascadeToAlerts,
365
+ } = data;
366
+
367
+ // Get last episode state timeline
368
+ const lastEpisodeStateTimeline: AlertEpisodeStateTimeline | null =
369
+ await AlertEpisodeStateTimelineService.findOneBy({
370
+ query: {
371
+ alertEpisodeId: episodeId,
372
+ projectId: projectId,
373
+ },
374
+ select: {
375
+ _id: true,
376
+ alertStateId: true,
377
+ },
378
+ sort: {
379
+ createdAt: SortOrder.Descending,
380
+ },
381
+ props: {
382
+ isRoot: true,
383
+ },
384
+ });
385
+
386
+ if (
387
+ lastEpisodeStateTimeline &&
388
+ lastEpisodeStateTimeline.alertStateId &&
389
+ lastEpisodeStateTimeline.alertStateId.toString() ===
390
+ alertStateId.toString()
391
+ ) {
392
+ return;
393
+ }
394
+
395
+ const stateTimeline: AlertEpisodeStateTimeline =
396
+ new AlertEpisodeStateTimeline();
397
+
398
+ stateTimeline.alertEpisodeId = episodeId;
399
+ stateTimeline.alertStateId = alertStateId;
400
+ stateTimeline.projectId = projectId;
401
+ stateTimeline.isOwnerNotified = !notifyOwners;
402
+
403
+ if (rootCause) {
404
+ stateTimeline.rootCause = rootCause;
405
+ }
406
+
407
+ await AlertEpisodeStateTimelineService.create({
408
+ data: stateTimeline,
409
+ props: props || {},
410
+ });
411
+
412
+ /*
413
+ * Note: resolvedAt is updated by AlertEpisodeStateTimelineService.onCreateSuccess()
414
+ * to avoid duplicate updates.
415
+ */
416
+
417
+ // Cascade state change to all member alerts if requested
418
+ if (cascadeToAlerts) {
419
+ await this.cascadeStateToMemberAlerts({
420
+ projectId,
421
+ episodeId,
422
+ alertStateId,
423
+ props,
424
+ });
425
+ }
426
+ }
427
+
428
+ @CaptureSpan()
429
+ public async cascadeStateToMemberAlerts(data: {
430
+ projectId: ObjectID;
431
+ episodeId: ObjectID;
432
+ alertStateId: ObjectID;
433
+ props: DatabaseCommonInteractionProps;
434
+ }): Promise<void> {
435
+ const { projectId, episodeId, alertStateId, props } = data;
436
+
437
+ // Get all member alerts for this episode
438
+ const members: Array<AlertEpisodeMember> =
439
+ await AlertEpisodeMemberService.findBy({
440
+ query: {
441
+ alertEpisodeId: episodeId,
442
+ projectId: projectId,
443
+ },
444
+ select: {
445
+ alertId: true,
446
+ },
447
+ props: {
448
+ isRoot: true,
449
+ },
450
+ limit: LIMIT_PER_PROJECT,
451
+ skip: 0,
452
+ });
453
+
454
+ if (members.length === 0) {
455
+ return;
456
+ }
457
+
458
+ // Update state for each member alert
459
+ for (const member of members) {
460
+ if (!member.alertId) {
461
+ continue;
462
+ }
463
+
464
+ try {
465
+ await AlertService.changeAlertState({
466
+ projectId: projectId,
467
+ alertId: member.alertId,
468
+ alertStateId: alertStateId,
469
+ notifyOwners: false, // Don't send notifications for cascaded state changes
470
+ rootCause: "State changed by episode state cascade.",
471
+ stateChangeLog: undefined,
472
+ props: props,
473
+ });
474
+ } catch (error) {
475
+ logger.error(
476
+ `Failed to cascade state change to alert ${member.alertId.toString()}: ${error}`,
477
+ );
478
+ }
479
+ }
480
+ }
481
+
482
+ @CaptureSpan()
483
+ public async acknowledgeEpisode(
484
+ episodeId: ObjectID,
485
+ acknowledgedByUserId?: ObjectID,
486
+ cascadeToAlerts: boolean = true,
487
+ ): Promise<void> {
488
+ const episode: Model | null = await this.findOneById({
489
+ id: episodeId,
490
+ select: {
491
+ projectId: true,
492
+ },
493
+ props: {
494
+ isRoot: true,
495
+ },
496
+ });
497
+
498
+ if (!episode || !episode.projectId) {
499
+ throw new BadDataException("Episode not found.");
500
+ }
501
+
502
+ const alertState: AlertState | null = await AlertStateService.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 (!alertState || !alertState.id) {
516
+ throw new BadDataException(
517
+ "Acknowledged state not found for this project.",
518
+ );
519
+ }
520
+
521
+ await this.changeEpisodeState({
522
+ projectId: episode.projectId,
523
+ episodeId: episodeId,
524
+ alertStateId: alertState.id,
525
+ notifyOwners: true,
526
+ rootCause: undefined,
527
+ props: {
528
+ isRoot: true,
529
+ userId: acknowledgedByUserId,
530
+ },
531
+ cascadeToAlerts: cascadeToAlerts,
532
+ });
533
+
534
+ // Create feed for episode acknowledged
535
+ let feedMessage: string = "Episode has been acknowledged.";
536
+ if (cascadeToAlerts) {
537
+ feedMessage += " All member alerts have also been acknowledged.";
538
+ }
539
+
540
+ await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
541
+ alertEpisodeId: episodeId,
542
+ projectId: episode.projectId,
543
+ alertEpisodeFeedEventType: AlertEpisodeFeedEventType.EpisodeStateChanged,
544
+ displayColor: Yellow500,
545
+ feedInfoInMarkdown: feedMessage,
546
+ userId: acknowledgedByUserId || undefined,
547
+ });
548
+ }
549
+
550
+ @CaptureSpan()
551
+ public async resolveEpisode(
552
+ episodeId: ObjectID,
553
+ resolvedByUserId?: ObjectID,
554
+ cascadeToAlerts: boolean = true,
555
+ ): Promise<void> {
556
+ const episode: Model | null = await this.findOneById({
557
+ id: episodeId,
558
+ select: {
559
+ projectId: true,
560
+ },
561
+ props: {
562
+ isRoot: true,
563
+ },
564
+ });
565
+
566
+ if (!episode || !episode.projectId) {
567
+ throw new BadDataException("Episode not found.");
568
+ }
569
+
570
+ const alertState: AlertState | null = await AlertStateService.findOneBy({
571
+ query: {
572
+ projectId: episode.projectId,
573
+ isResolvedState: true,
574
+ },
575
+ select: {
576
+ _id: true,
577
+ },
578
+ props: {
579
+ isRoot: true,
580
+ },
581
+ });
582
+
583
+ if (!alertState || !alertState.id) {
584
+ throw new BadDataException("Resolved state not found for this project.");
585
+ }
586
+
587
+ await this.changeEpisodeState({
588
+ projectId: episode.projectId,
589
+ episodeId: episodeId,
590
+ alertStateId: alertState.id,
591
+ notifyOwners: true,
592
+ rootCause: undefined,
593
+ props: {
594
+ isRoot: true,
595
+ userId: resolvedByUserId,
596
+ },
597
+ cascadeToAlerts: cascadeToAlerts,
598
+ });
599
+
600
+ // Create feed for episode resolved
601
+ let feedMessage: string = "Episode has been resolved.";
602
+ if (cascadeToAlerts) {
603
+ feedMessage += " All member alerts have also been resolved.";
604
+ }
605
+
606
+ await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
607
+ alertEpisodeId: episodeId,
608
+ projectId: episode.projectId,
609
+ alertEpisodeFeedEventType: AlertEpisodeFeedEventType.EpisodeStateChanged,
610
+ displayColor: Green500,
611
+ feedInfoInMarkdown: feedMessage,
612
+ userId: resolvedByUserId || undefined,
613
+ });
614
+ }
615
+
616
+ @CaptureSpan()
617
+ public async updateEpisodeSeverity(data: {
618
+ episodeId: ObjectID;
619
+ severityId: ObjectID;
620
+ onlyIfHigher: boolean;
621
+ }): Promise<void> {
622
+ const { episodeId, severityId, onlyIfHigher } = data;
623
+
624
+ const episode: Model | null = await this.findOneById({
625
+ id: episodeId,
626
+ select: {
627
+ alertSeverityId: true,
628
+ alertSeverity: {
629
+ order: true,
630
+ },
631
+ },
632
+ props: {
633
+ isRoot: true,
634
+ },
635
+ });
636
+
637
+ if (!episode) {
638
+ throw new BadDataException("Episode not found.");
639
+ }
640
+
641
+ if (onlyIfHigher && episode.alertSeverity?.order !== undefined) {
642
+ // Get the new severity to check its order
643
+ const newSeverity: AlertSeverity | null =
644
+ await AlertSeverityService.findOneById({
645
+ id: severityId,
646
+ select: {
647
+ order: true,
648
+ },
649
+ props: {
650
+ isRoot: true,
651
+ },
652
+ });
653
+
654
+ if (newSeverity && newSeverity.order !== undefined) {
655
+ // Lower order = higher severity
656
+ if (newSeverity.order >= episode.alertSeverity.order) {
657
+ return; // Don't update if new severity is not higher
658
+ }
659
+ }
660
+ }
661
+
662
+ await this.updateOneById({
663
+ id: episodeId,
664
+ data: {
665
+ alertSeverityId: severityId,
666
+ },
667
+ props: {
668
+ isRoot: true,
669
+ },
670
+ });
671
+ }
672
+
673
+ @CaptureSpan()
674
+ public async updateAlertCount(episodeId: ObjectID): Promise<void> {
675
+ const count: PositiveNumber = await AlertEpisodeMemberService.countBy({
676
+ query: {
677
+ alertEpisodeId: episodeId,
678
+ },
679
+ props: {
680
+ isRoot: true,
681
+ },
682
+ });
683
+
684
+ const alertCount: number = count.toNumber();
685
+
686
+ // Get the episode to check for templates
687
+ const episode: Model | null = await this.findOneById({
688
+ id: episodeId,
689
+ select: {
690
+ titleTemplate: true,
691
+ descriptionTemplate: true,
692
+ title: true,
693
+ description: true,
694
+ },
695
+ props: {
696
+ isRoot: true,
697
+ },
698
+ });
699
+
700
+ const updateData: {
701
+ alertCount: number;
702
+ title?: string;
703
+ description?: string;
704
+ } = {
705
+ alertCount: alertCount,
706
+ };
707
+
708
+ // Update title with dynamic variables if template exists
709
+ if (episode?.titleTemplate) {
710
+ updateData.title = this.renderTemplateWithDynamicValues(
711
+ episode.titleTemplate,
712
+ alertCount,
713
+ );
714
+ }
715
+
716
+ // Update description with dynamic variables if template exists
717
+ if (episode?.descriptionTemplate) {
718
+ updateData.description = this.renderTemplateWithDynamicValues(
719
+ episode.descriptionTemplate,
720
+ alertCount,
721
+ );
722
+ }
723
+
724
+ await this.updateOneById({
725
+ id: episodeId,
726
+ data: updateData,
727
+ props: {
728
+ isRoot: true,
729
+ },
730
+ });
731
+ }
732
+
733
+ private renderTemplateWithDynamicValues(
734
+ template: string,
735
+ alertCount: number,
736
+ ): string {
737
+ let result: string = template;
738
+
739
+ // Replace dynamic variables
740
+ result = result.replace(/\{\{alertCount\}\}/g, alertCount.toString());
741
+
742
+ return result;
743
+ }
744
+
745
+ @CaptureSpan()
746
+ public async updateLastAlertAddedAt(episodeId: ObjectID): Promise<void> {
747
+ await this.updateOneById({
748
+ id: episodeId,
749
+ data: {
750
+ lastAlertAddedAt: OneUptimeDate.getCurrentDate(),
751
+ },
752
+ props: {
753
+ isRoot: true,
754
+ },
755
+ });
756
+ }
757
+
758
+ @CaptureSpan()
759
+ public async isEpisodeResolved(episodeId: ObjectID): Promise<boolean> {
760
+ const episode: Model | null = await this.findOneById({
761
+ id: episodeId,
762
+ select: {
763
+ projectId: true,
764
+ currentAlertState: {
765
+ order: true,
766
+ },
767
+ },
768
+ props: {
769
+ isRoot: true,
770
+ },
771
+ });
772
+
773
+ if (!episode || !episode.projectId) {
774
+ throw new BadDataException("Episode not found.");
775
+ }
776
+
777
+ const resolvedState: AlertState =
778
+ await AlertStateService.getResolvedAlertState({
779
+ projectId: episode.projectId,
780
+ props: {
781
+ isRoot: true,
782
+ },
783
+ });
784
+
785
+ const currentOrder: number = episode.currentAlertState?.order || 0;
786
+ const resolvedOrder: number = resolvedState.order || 0;
787
+
788
+ return currentOrder >= resolvedOrder;
789
+ }
790
+
791
+ @CaptureSpan()
792
+ public async isEpisodeAcknowledged(data: {
793
+ episodeId: ObjectID;
794
+ }): Promise<boolean> {
795
+ const episode: Model | null = await this.findOneById({
796
+ id: data.episodeId,
797
+ select: {
798
+ projectId: true,
799
+ currentAlertState: {
800
+ order: true,
801
+ },
802
+ },
803
+ props: {
804
+ isRoot: true,
805
+ },
806
+ });
807
+
808
+ if (!episode || !episode.projectId) {
809
+ throw new BadDataException("Episode not found.");
810
+ }
811
+
812
+ const acknowledgedState: AlertState =
813
+ await AlertStateService.getAcknowledgedAlertState({
814
+ projectId: episode.projectId,
815
+ props: {
816
+ isRoot: true,
817
+ },
818
+ });
819
+
820
+ const currentOrder: number = episode.currentAlertState?.order || 0;
821
+ const acknowledgedOrder: number = acknowledgedState.order || 0;
822
+
823
+ return currentOrder >= acknowledgedOrder;
824
+ }
825
+
826
+ @CaptureSpan()
827
+ public async reopenEpisode(
828
+ episodeId: ObjectID,
829
+ reopenedByUserId?: ObjectID,
830
+ ): Promise<void> {
831
+ const episode: Model | null = await this.findOneById({
832
+ id: episodeId,
833
+ select: {
834
+ projectId: true,
835
+ },
836
+ props: {
837
+ isRoot: true,
838
+ },
839
+ });
840
+
841
+ if (!episode || !episode.projectId) {
842
+ throw new BadDataException("Episode not found.");
843
+ }
844
+
845
+ // Get the created state
846
+ const createdState: AlertState | null = await AlertStateService.findOneBy({
847
+ query: {
848
+ projectId: episode.projectId,
849
+ isCreatedState: true,
850
+ },
851
+ select: {
852
+ _id: true,
853
+ },
854
+ props: {
855
+ isRoot: true,
856
+ },
857
+ });
858
+
859
+ if (!createdState || !createdState.id) {
860
+ throw new BadDataException("Created state not found for this project.");
861
+ }
862
+
863
+ await this.changeEpisodeState({
864
+ projectId: episode.projectId,
865
+ episodeId: episodeId,
866
+ alertStateId: createdState.id,
867
+ notifyOwners: true,
868
+ rootCause: "Episode reopened due to new alert added.",
869
+ props: {
870
+ isRoot: true,
871
+ userId: reopenedByUserId,
872
+ },
873
+ });
874
+
875
+ // Clear resolved timestamp
876
+ await this.updateOneById({
877
+ id: episodeId,
878
+ data: {
879
+ resolvedAt: undefined as any,
880
+ },
881
+ props: {
882
+ isRoot: true,
883
+ },
884
+ });
885
+ }
886
+
887
+ @CaptureSpan()
888
+ public async getEpisodeLinkInDashboard(
889
+ projectId: ObjectID,
890
+ episodeId: ObjectID,
891
+ ): Promise<URL> {
892
+ const dashboardUrl: URL = await DatabaseConfig.getDashboardUrl();
893
+
894
+ return URL.fromString(dashboardUrl.toString()).addRoute(
895
+ `/${projectId.toString()}/alerts/episodes/${episodeId.toString()}`,
896
+ );
897
+ }
898
+
899
+ @CaptureSpan()
900
+ public async getEpisodeNumber(data: {
901
+ episodeId: ObjectID;
902
+ }): Promise<number | null> {
903
+ const episode: Model | null = await this.findOneById({
904
+ id: data.episodeId,
905
+ select: {
906
+ episodeNumber: true,
907
+ },
908
+ props: {
909
+ isRoot: true,
910
+ },
911
+ });
912
+
913
+ if (!episode) {
914
+ throw new BadDataException("Episode not found.");
915
+ }
916
+
917
+ return episode.episodeNumber ? Number(episode.episodeNumber) : null;
918
+ }
919
+
920
+ @CaptureSpan()
921
+ protected override async onUpdateSuccess(
922
+ onUpdate: OnUpdate<Model>,
923
+ updatedItemIds: ObjectID[],
924
+ ): Promise<OnUpdate<Model>> {
925
+ // Handle state changes
926
+ if (
927
+ onUpdate.updateBy.data.currentAlertStateId &&
928
+ onUpdate.updateBy.props.tenantId
929
+ ) {
930
+ for (const itemId of updatedItemIds) {
931
+ await this.changeEpisodeState({
932
+ projectId: onUpdate.updateBy.props.tenantId as ObjectID,
933
+ episodeId: itemId,
934
+ alertStateId: onUpdate.updateBy.data.currentAlertStateId as ObjectID,
935
+ notifyOwners: true,
936
+ rootCause: "State was changed when the episode was updated.",
937
+ props: {
938
+ isRoot: true,
939
+ },
940
+ });
941
+ }
942
+ }
943
+
944
+ return onUpdate;
945
+ }
946
+
947
+ @CaptureSpan()
948
+ public async findOwners(episodeId: ObjectID): Promise<Array<User>> {
949
+ // Get direct user owners
950
+ const ownerUsers: Array<AlertEpisodeOwnerUser> =
951
+ await AlertEpisodeOwnerUserService.findBy({
952
+ query: {
953
+ alertEpisodeId: episodeId,
954
+ },
955
+ select: {
956
+ _id: true,
957
+ user: {
958
+ _id: true,
959
+ email: true,
960
+ name: true,
961
+ timezone: true,
962
+ },
963
+ },
964
+ props: {
965
+ isRoot: true,
966
+ },
967
+ limit: LIMIT_PER_PROJECT,
968
+ skip: 0,
969
+ });
970
+
971
+ // Get team owners
972
+ const ownerTeams: Array<AlertEpisodeOwnerTeam> =
973
+ await AlertEpisodeOwnerTeamService.findBy({
974
+ query: {
975
+ alertEpisodeId: episodeId,
976
+ },
977
+ select: {
978
+ _id: true,
979
+ teamId: true,
980
+ },
981
+ props: {
982
+ isRoot: true,
983
+ },
984
+ limit: LIMIT_PER_PROJECT,
985
+ skip: 0,
986
+ });
987
+
988
+ const users: Array<User> = ownerUsers
989
+ .map((ownerUser: AlertEpisodeOwnerUser) => {
990
+ return ownerUser.user!;
991
+ })
992
+ .filter((user: User) => {
993
+ return Boolean(user);
994
+ });
995
+
996
+ // Expand teams to individual users
997
+ if (ownerTeams.length > 0) {
998
+ const teamIds: Array<ObjectID> = ownerTeams.map(
999
+ (ownerTeam: AlertEpisodeOwnerTeam) => {
1000
+ return ownerTeam.teamId!;
1001
+ },
1002
+ );
1003
+
1004
+ const teamUsers: Array<User> =
1005
+ await TeamMemberService.getUsersInTeams(teamIds);
1006
+
1007
+ for (const teamUser of teamUsers) {
1008
+ // Avoid duplicates
1009
+ if (
1010
+ !users.find((user: User) => {
1011
+ return user.id?.toString() === teamUser.id?.toString();
1012
+ })
1013
+ ) {
1014
+ users.push(teamUser);
1015
+ }
1016
+ }
1017
+ }
1018
+
1019
+ return users;
1020
+ }
1021
+
1022
+ @CaptureSpan()
1023
+ public async getWorkspaceChannelForEpisode(data: {
1024
+ episodeId: ObjectID;
1025
+ workspaceType?: WorkspaceType | null;
1026
+ }): Promise<Array<NotificationRuleWorkspaceChannel>> {
1027
+ const episode: Model | null = await this.findOneById({
1028
+ id: data.episodeId,
1029
+ select: {
1030
+ postUpdatesToWorkspaceChannels: true,
1031
+ },
1032
+ props: {
1033
+ isRoot: true,
1034
+ },
1035
+ });
1036
+
1037
+ if (!episode) {
1038
+ throw new BadDataException("Alert Episode not found.");
1039
+ }
1040
+
1041
+ return (episode.postUpdatesToWorkspaceChannels || []).filter(
1042
+ (channel: NotificationRuleWorkspaceChannel) => {
1043
+ if (!data.workspaceType) {
1044
+ return true;
1045
+ }
1046
+ return channel.workspaceType === data.workspaceType;
1047
+ },
1048
+ );
1049
+ }
1050
+
1051
+ @CaptureSpan()
1052
+ public async addOwners(
1053
+ projectId: ObjectID,
1054
+ episodeId: ObjectID,
1055
+ userIds: Array<ObjectID>,
1056
+ teamIds: Array<ObjectID>,
1057
+ notifyOwners: boolean,
1058
+ props: DatabaseCommonInteractionProps,
1059
+ ): Promise<void> {
1060
+ for (let teamId of teamIds) {
1061
+ if (typeof teamId === Typeof.String) {
1062
+ teamId = new ObjectID(teamId.toString());
1063
+ }
1064
+
1065
+ const teamOwner: AlertEpisodeOwnerTeam = new AlertEpisodeOwnerTeam();
1066
+ teamOwner.alertEpisodeId = episodeId;
1067
+ teamOwner.projectId = projectId;
1068
+ teamOwner.teamId = teamId;
1069
+ teamOwner.isOwnerNotified = !notifyOwners;
1070
+
1071
+ await AlertEpisodeOwnerTeamService.create({
1072
+ data: teamOwner,
1073
+ props: props,
1074
+ });
1075
+ }
1076
+
1077
+ for (let userId of userIds) {
1078
+ if (typeof userId === Typeof.String) {
1079
+ userId = new ObjectID(userId.toString());
1080
+ }
1081
+
1082
+ const userOwner: AlertEpisodeOwnerUser = new AlertEpisodeOwnerUser();
1083
+ userOwner.alertEpisodeId = episodeId;
1084
+ userOwner.projectId = projectId;
1085
+ userOwner.userId = userId;
1086
+ userOwner.isOwnerNotified = !notifyOwners;
1087
+
1088
+ await AlertEpisodeOwnerUserService.create({
1089
+ data: userOwner,
1090
+ props: props,
1091
+ });
1092
+ }
1093
+ }
1094
+ }
1095
+
1096
+ export default new Service();