@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.
- package/Models/DatabaseModels/Alert.ts +76 -0
- package/Models/DatabaseModels/AlertEpisode.ts +1201 -0
- package/Models/DatabaseModels/AlertEpisodeFeed.ts +529 -0
- package/Models/DatabaseModels/AlertEpisodeInternalNote.ts +455 -0
- package/Models/DatabaseModels/AlertEpisodeMember.ts +586 -0
- package/Models/DatabaseModels/AlertEpisodeOwnerTeam.ts +421 -0
- package/Models/DatabaseModels/AlertEpisodeOwnerUser.ts +419 -0
- package/Models/DatabaseModels/AlertEpisodeStateTimeline.ts +523 -0
- package/Models/DatabaseModels/AlertFeed.ts +1 -0
- package/Models/DatabaseModels/AlertGroupingRule.ts +1432 -0
- package/Models/DatabaseModels/Index.ts +18 -0
- package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +70 -0
- package/Models/DatabaseModels/StatusPageDomain.ts +2 -0
- package/Models/DatabaseModels/WorkspaceNotificationLog.ts +57 -0
- package/Server/API/SlackAPI.ts +21 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1768938069147-MigrationName.ts +751 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1769125561322-MigrationName.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1769170578688-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1769172358833-MigrationName.ts +177 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1769176450526-MigrationName.ts +71 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1769190495840-MigrationName.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1769199303656-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1769202898645-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1769428619414-MigrationName.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1769428821686-MigrationName.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
- package/Server/Services/AlertEpisodeFeedService.ts +94 -0
- package/Server/Services/AlertEpisodeInternalNoteService.ts +71 -0
- package/Server/Services/AlertEpisodeMemberService.ts +267 -0
- package/Server/Services/AlertEpisodeOwnerTeamService.ts +10 -0
- package/Server/Services/AlertEpisodeOwnerUserService.ts +10 -0
- package/Server/Services/AlertEpisodeService.ts +988 -0
- package/Server/Services/AlertEpisodeStateTimelineService.ts +557 -0
- package/Server/Services/AlertGroupingEngineService.ts +1120 -0
- package/Server/Services/AlertGroupingRuleService.ts +14 -0
- package/Server/Services/AlertService.ts +12 -0
- package/Server/Services/CallService.ts +2 -0
- package/Server/Services/Index.ts +21 -0
- package/Server/Services/MailService.ts +5 -0
- package/Server/Services/OnCallDutyPolicyService.ts +5 -0
- package/Server/Services/SmsService.ts +2 -0
- package/Server/Services/UserNotificationSettingService.ts +23 -0
- package/Server/Services/WhatsAppService.ts +5 -0
- package/Server/Services/WorkspaceNotificationRuleService.ts +26 -0
- package/Server/Utils/AnalyticsDatabase/Statement.ts +6 -2
- package/Server/Utils/WhatsAppTemplateUtil.ts +13 -0
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/ActionTypes.ts +18 -0
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +689 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +16 -0
- package/Server/Utils/Workspace/Slack/Actions/ActionTypes.ts +11 -0
- package/Server/Utils/Workspace/Slack/Actions/AlertEpisode.ts +915 -0
- package/Server/Utils/Workspace/Slack/Messages/AlertEpisode.ts +120 -0
- package/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.ts +74 -0
- package/Tests/Server/Services/AlertEpisodeMemberService.test.ts +200 -0
- package/Tests/Server/Services/AlertEpisodeService.test.ts +240 -0
- package/Tests/Server/Services/AlertGroupingEngineService.test.ts +542 -0
- package/Tests/Server/Services/AlertGroupingRuleService.test.ts +383 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +1 -1
- package/Tests/UI/Components/Input.test.tsx +1 -1
- package/Tests/UI/Components/TextArea.test.tsx +2 -2
- package/Types/BaseDatabase/SortOrder.ts +9 -0
- package/Types/Email/EmailTemplateType.ts +5 -0
- package/Types/NotificationRule/NotificationRuleType.ts +1 -0
- package/Types/NotificationSetting/NotificationSettingEventType.ts +7 -0
- package/Types/Permission.ts +309 -0
- package/Types/UserNotification/UserNotificationEventType.ts +1 -0
- package/Types/WhatsApp/WhatsAppTemplates.ts +20 -0
- package/Types/Workspace/NotificationRules/EventType.ts +1 -0
- package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +32 -3
- package/UI/Components/Accordion/Accordion.tsx +20 -2
- package/UI/Components/Alerts/Alert.tsx +1 -0
- package/UI/Components/Button/Button.tsx +29 -0
- package/UI/Components/CardSelect/CardSelect.tsx +5 -1
- package/UI/Components/Checkbox/Checkbox.tsx +7 -3
- package/UI/Components/ColorCircle/ColorCircle.tsx +2 -0
- package/UI/Components/ColorViewer/ColorViewer.tsx +19 -3
- package/UI/Components/CopyableButton/CopyableButton.tsx +22 -5
- package/UI/Components/Detail/Detail.tsx +1 -1
- package/UI/Components/Dropdown/Dropdown.tsx +14 -1
- package/UI/Components/Forms/Fields/FormField.tsx +28 -0
- package/UI/Components/FullPageModal/FullPageModal.tsx +35 -4
- package/UI/Components/Input/Input.tsx +14 -2
- package/UI/Components/Link/Link.tsx +1 -0
- package/UI/Components/Loader/Loader.tsx +8 -2
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +76 -1
- package/UI/Components/Modal/Modal.tsx +47 -3
- package/UI/Components/ModelTable/BaseModelTable.tsx +42 -1
- package/UI/Components/MoreMenu/MoreMenu.tsx +84 -2
- package/UI/Components/OrderedStatesList/OrderedStatesList.tsx +30 -8
- package/UI/Components/Pagination/Pagination.tsx +113 -8
- package/UI/Components/ProgressBar/ProgressBar.tsx +12 -2
- package/UI/Components/Radio/Radio.tsx +21 -3
- package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +54 -27
- package/UI/Components/StatusBubble/StatusBubble.tsx +7 -2
- package/UI/Components/Table/TableHeader.tsx +20 -3
- package/UI/Components/Tabs/Tab.tsx +16 -1
- package/UI/Components/Tabs/Tabs.tsx +12 -1
- package/UI/Components/TextArea/TextArea.tsx +12 -2
- package/UI/Components/Toggle/Toggle.tsx +14 -3
- package/UI/Components/Tooltip/Tooltip.tsx +11 -1
- package/UI/Components/TopAlert/TopAlert.tsx +2 -0
- package/build/dist/Models/DatabaseModels/Alert.js +77 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertEpisode.js +1225 -0
- package/build/dist/Models/DatabaseModels/AlertEpisode.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeFeed.js +553 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeInternalNote.js +467 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeInternalNote.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +607 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerTeam.js +437 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerUser.js +436 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js +546 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js +1 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertGroupingRule.js +1437 -0
- package/build/dist/Models/DatabaseModels/AlertGroupingRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Index.js +16 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +69 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +2 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceNotificationLog.js +58 -0
- package/build/dist/Models/DatabaseModels/WorkspaceNotificationLog.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +18 -0
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1768938069147-MigrationName.js +266 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1768938069147-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769125561322-MigrationName.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769125561322-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769170578688-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769170578688-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769172358833-MigrationName.js +68 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769172358833-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769176450526-MigrationName.js +30 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769176450526-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769190495840-MigrationName.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769190495840-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769199303656-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769199303656-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769202898645-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769202898645-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769428619414-MigrationName.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769428619414-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769428821686-MigrationName.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769428821686-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeFeedService.js +83 -0
- package/build/dist/Server/Services/AlertEpisodeFeedService.js.map +1 -0
- package/build/dist/Server/Services/AlertEpisodeInternalNoteService.js +70 -0
- package/build/dist/Server/Services/AlertEpisodeInternalNoteService.js.map +1 -0
- package/build/dist/Server/Services/AlertEpisodeMemberService.js +256 -0
- package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -0
- package/build/dist/Server/Services/AlertEpisodeOwnerTeamService.js +9 -0
- package/build/dist/Server/Services/AlertEpisodeOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/AlertEpisodeOwnerUserService.js +9 -0
- package/build/dist/Server/Services/AlertEpisodeOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/AlertEpisodeService.js +885 -0
- package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -0
- package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +494 -0
- package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -0
- package/build/dist/Server/Services/AlertGroupingEngineService.js +893 -0
- package/build/dist/Server/Services/AlertGroupingEngineService.js.map +1 -0
- package/build/dist/Server/Services/AlertGroupingRuleService.js +13 -0
- package/build/dist/Server/Services/AlertGroupingRuleService.js.map +1 -0
- package/build/dist/Server/Services/AlertService.js +11 -0
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/CallService.js +11 -10
- package/build/dist/Server/Services/CallService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +18 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MailService.js +3 -0
- package/build/dist/Server/Services/MailService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyService.js +3 -0
- package/build/dist/Server/Services/OnCallDutyPolicyService.js.map +1 -1
- package/build/dist/Server/Services/SmsService.js +11 -10
- package/build/dist/Server/Services/SmsService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +9 -0
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/WhatsAppService.js +3 -0
- package/build/dist/Server/Services/WhatsAppService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +25 -0
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +4 -2
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
- package/build/dist/Server/Utils/WhatsAppTemplateUtil.js +8 -0
- package/build/dist/Server/Utils/WhatsAppTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ActionTypes.js +17 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ActionTypes.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +545 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +13 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js +10 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js +651 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Messages/AlertEpisode.js +100 -0
- package/build/dist/Server/Utils/Workspace/Slack/Messages/AlertEpisode.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.js +70 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.js.map +1 -0
- package/build/dist/Tests/Server/Services/AlertEpisodeMemberService.test.js +165 -0
- package/build/dist/Tests/Server/Services/AlertEpisodeMemberService.test.js.map +1 -0
- package/build/dist/Tests/Server/Services/AlertEpisodeService.test.js +193 -0
- package/build/dist/Tests/Server/Services/AlertEpisodeService.test.js.map +1 -0
- package/build/dist/Tests/Server/Services/AlertGroupingEngineService.test.js +412 -0
- package/build/dist/Tests/Server/Services/AlertGroupingEngineService.test.js.map +1 -0
- package/build/dist/Tests/Server/Services/AlertGroupingRuleService.test.js +308 -0
- package/build/dist/Tests/Server/Services/AlertGroupingRuleService.test.js.map +1 -0
- package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js +1 -1
- package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js.map +1 -1
- package/build/dist/Tests/UI/Components/Input.test.js +1 -1
- package/build/dist/Tests/UI/Components/Input.test.js.map +1 -1
- package/build/dist/Tests/UI/Components/TextArea.test.js +2 -2
- package/build/dist/Tests/UI/Components/TextArea.test.js.map +1 -1
- package/build/dist/Types/BaseDatabase/SortOrder.js +5 -0
- package/build/dist/Types/BaseDatabase/SortOrder.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +4 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/NotificationRule/NotificationRuleType.js +1 -0
- package/build/dist/Types/NotificationRule/NotificationRuleType.js.map +1 -1
- package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +5 -0
- package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
- package/build/dist/Types/Permission.js +264 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/UserNotification/UserNotificationEventType.js +1 -0
- package/build/dist/Types/UserNotification/UserNotificationEventType.js.map +1 -1
- package/build/dist/Types/WhatsApp/WhatsAppTemplates.js +12 -0
- package/build/dist/Types/WhatsApp/WhatsAppTemplates.js.map +1 -1
- package/build/dist/Types/Workspace/NotificationRules/EventType.js +1 -0
- package/build/dist/Types/Workspace/NotificationRules/EventType.js.map +1 -1
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +28 -3
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
- package/build/dist/UI/Components/Accordion/Accordion.js +10 -3
- package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
- package/build/dist/UI/Components/Alerts/Alert.js +1 -1
- package/build/dist/UI/Components/Alerts/Alert.js.map +1 -1
- package/build/dist/UI/Components/Button/Button.js +8 -2
- package/build/dist/UI/Components/Button/Button.js.map +1 -1
- package/build/dist/UI/Components/CardSelect/CardSelect.js +1 -1
- package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
- package/build/dist/UI/Components/Checkbox/Checkbox.js +2 -2
- package/build/dist/UI/Components/Checkbox/Checkbox.js.map +1 -1
- package/build/dist/UI/Components/ColorCircle/ColorCircle.js +1 -1
- package/build/dist/UI/Components/ColorCircle/ColorCircle.js.map +1 -1
- package/build/dist/UI/Components/ColorViewer/ColorViewer.js +12 -3
- package/build/dist/UI/Components/ColorViewer/ColorViewer.js.map +1 -1
- package/build/dist/UI/Components/CopyableButton/CopyableButton.js +12 -5
- package/build/dist/UI/Components/CopyableButton/CopyableButton.js.map +1 -1
- package/build/dist/UI/Components/Detail/Detail.js +1 -1
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js +5 -3
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +19 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/FullPageModal/FullPageModal.js +24 -5
- package/build/dist/UI/Components/FullPageModal/FullPageModal.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +3 -3
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/Link/Link.js +1 -1
- package/build/dist/UI/Components/Link/Link.js.map +1 -1
- package/build/dist/UI/Components/Loader/Loader.js +6 -4
- package/build/dist/UI/Components/Loader/Loader.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +56 -3
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +28 -3
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +23 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +67 -6
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js +14 -3
- package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +69 -13
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/ProgressBar/ProgressBar.js +2 -2
- package/build/dist/UI/Components/ProgressBar/ProgressBar.js.map +1 -1
- package/build/dist/UI/Components/Radio/Radio.js +8 -5
- package/build/dist/UI/Components/Radio/Radio.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +23 -4
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/StatusBubble/StatusBubble.js +2 -2
- package/build/dist/UI/Components/StatusBubble/StatusBubble.js.map +1 -1
- package/build/dist/UI/Components/Table/TableHeader.js +12 -4
- package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
- package/build/dist/UI/Components/Tabs/Tab.js +8 -1
- package/build/dist/UI/Components/Tabs/Tab.js.map +1 -1
- package/build/dist/UI/Components/Tabs/Tabs.js +4 -3
- package/build/dist/UI/Components/Tabs/Tabs.js.map +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js +3 -3
- package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
- package/build/dist/UI/Components/Toggle/Toggle.js +7 -4
- package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js +4 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
- package/build/dist/UI/Components/TopAlert/TopAlert.js +1 -1
- package/build/dist/UI/Components/TopAlert/TopAlert.js.map +1 -1
- 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();
|