@oneuptime/common 9.4.7 → 9.4.8

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