diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/index.tsx b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/index.tsx index 53e218b01705d..3b41a20c32e35 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/index.tsx +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/index.tsx @@ -50,6 +50,15 @@ export const RuleTagBadge = suspendedComponentWithProps( export const RuleStatusPanel = suspendedComponentWithProps( lazy(() => import('./rule_details/components/rule_status_panel')) ); + +export const UntrackAlertsModal = suspendedComponentWithProps( + lazy(() => + import('./common/components/untrack_alerts_modal').then((module) => ({ + default: module.UntrackAlertsModal, + })) + ) +); + export const GlobalRuleEventLogList = suspendedComponentWithProps( lazy(() => import('./rule_details/components/global_rule_event_log_list')) ); diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/common/get_untrack_modal.tsx b/x-pack/platform/plugins/shared/triggers_actions_ui/public/common/get_untrack_modal.tsx new file mode 100644 index 0000000000000..3e51f40d1296f --- /dev/null +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/common/get_untrack_modal.tsx @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { UntrackAlertsModal } from '../application/sections/common/components/untrack_alerts_modal'; +import type { UntrackAlertsModalProps } from '../application/sections/common/components/untrack_alerts_modal'; + +export const getUntrackModalLazy = (props: UntrackAlertsModalProps) => { + return ; +}; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/mocks.ts b/x-pack/platform/plugins/shared/triggers_actions_ui/public/mocks.ts index 53a2ee91e2414..1db7fa448f5bc 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/mocks.ts +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/mocks.ts @@ -42,6 +42,9 @@ import { getRuleStatusPanelLazy } from './common/get_rule_status_panel'; import { getRuleSnoozeModalLazy } from './common/get_rule_snooze_modal'; import { getRulesSettingsLinkLazy } from './common/get_rules_settings_link'; import type { AlertSummaryWidgetDependencies } from './application/sections/alert_summary_widget/types'; +import { isRuleSnoozed } from './application/lib'; +import { getNextRuleSnoozeSchedule } from './application/sections/rules_list/components/notify_badge/helpers'; +import { getUntrackModalLazy } from './common/get_untrack_modal'; function createStartMock(): TriggersAndActionsUIPublicPluginStart { const actionTypeRegistry = new TypeRegistry(); @@ -122,6 +125,20 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart { getRulesSettingsLink: () => { return getRulesSettingsLinkLazy(); }, + getUntrackModal: (props) => { + return getUntrackModalLazy(props); + }, + getRuleHelpers: (rule) => { + return { + isRuleSnoozed: isRuleSnoozed({ + isSnoozedUntil: rule.isSnoozedUntil, + muteAll: rule.muteAll, + }), + getNextRuleSnoozeSchedule: getNextRuleSnoozeSchedule({ + snoozeSchedule: rule.snoozeSchedule, + }), + }; + }, }; } diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/plugin.ts b/x-pack/platform/plugins/shared/triggers_actions_ui/public/plugin.ts index 912ae58aef551..92b131920302a 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/plugin.ts +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/plugin.ts @@ -30,11 +30,11 @@ import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; -import type { RuleAction } from '@kbn/alerting-plugin/common'; +import type { RRuleParams, RuleAction, RuleTypeParams } from '@kbn/alerting-plugin/common'; import { TypeRegistry } from '@kbn/alerts-ui-shared/src/common/type_registry'; import type { CloudSetup } from '@kbn/cloud-plugin/public'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; -import type { RuleUiAction } from './types'; +import type { Rule, RuleUiAction } from './types'; import type { AlertsSearchBarProps } from './application/sections/alerts_search_bar'; import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout'; @@ -84,6 +84,10 @@ import type { RulesListNotifyBadgePropsWithApi, RulesListProps, } from './types'; +import type { UntrackAlertsModalProps } from './application/sections/common/components/untrack_alerts_modal'; +import { isRuleSnoozed } from './application/lib'; +import { getNextRuleSnoozeSchedule } from './application/sections/rules_list/components/notify_badge/helpers'; +import { getUntrackModalLazy } from './common/get_untrack_modal'; export interface TriggersAndActionsUIPublicPluginSetup { actionTypeRegistry: TypeRegistry; @@ -122,7 +126,17 @@ export interface TriggersAndActionsUIPublicPluginStart { getRuleStatusPanel: (props: RuleStatusPanelProps) => ReactElement; getAlertSummaryWidget: (props: AlertSummaryWidgetProps) => ReactElement; getRuleSnoozeModal: (props: RuleSnoozeModalProps) => ReactElement; + getUntrackModal: (props: UntrackAlertsModalProps) => ReactElement; getRulesSettingsLink: () => ReactElement; + getRuleHelpers: (rule: Rule) => { + isRuleSnoozed: boolean; + getNextRuleSnoozeSchedule: { + duration: number; + rRule: RRuleParams; + id?: string | undefined; + skipRecurrences?: string[] | undefined; + } | null; + }; getGlobalRuleEventLogList: ( props: GlobalRuleEventLogListProps ) => ReactElement; @@ -493,9 +507,23 @@ export class Plugin getRuleSnoozeModal: (props: RuleSnoozeModalProps) => { return getRuleSnoozeModalLazy(props); }, + getUntrackModal: (props: UntrackAlertsModalProps) => { + return getUntrackModalLazy(props); + }, getRulesSettingsLink: () => { return getRulesSettingsLinkLazy(); }, + getRuleHelpers: (rule: Rule) => { + return { + isRuleSnoozed: isRuleSnoozed({ + isSnoozedUntil: rule.isSnoozedUntil, + muteAll: rule.muteAll, + }), + getNextRuleSnoozeSchedule: getNextRuleSnoozeSchedule({ + snoozeSchedule: rule.snoozeSchedule, + }), + }; + }, }; } diff --git a/x-pack/solutions/observability/plugins/observability/public/hooks/use_disable_rule.ts b/x-pack/solutions/observability/plugins/observability/public/hooks/use_disable_rule.ts new file mode 100644 index 0000000000000..92753e7c98287 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/hooks/use_disable_rule.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from '../utils/kibana_react'; + +export function useDisableRule() { + const { + http, + notifications: { toasts }, + } = useKibana().services; + + const queryClient = useQueryClient(); + + const disableRule = useMutation( + ['disableRule'], + ({ id, untrack }) => { + const body = JSON.stringify({ + ...(untrack ? { untrack } : {}), + }); + try { + return http.post(`/api/alerting/rule/${id}/_disable`, { body }); + } catch (e) { + throw new Error(`Unable to parse id: ${e}`); + } + }, + { + onError: (_err) => { + toasts.addDanger( + i18n.translate( + 'xpack.observability.rules.disableErrorModal.errorNotification.descriptionText', + { + defaultMessage: 'Failed to disable rule', + } + ) + ); + }, + + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: ['fetchRule', variables.id], exact: false }); + toasts.addSuccess( + i18n.translate( + 'xpack.observability.rules.disableConfirmationModal.successNotification.descriptionText', + { + defaultMessage: 'Disabled rule', + } + ) + ); + }, + } + ); + + return disableRule; +} diff --git a/x-pack/solutions/observability/plugins/observability/public/hooks/use_enable_rule.ts b/x-pack/solutions/observability/plugins/observability/public/hooks/use_enable_rule.ts new file mode 100644 index 0000000000000..44911c6d2e5a4 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/hooks/use_enable_rule.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from '../utils/kibana_react'; + +export function useEnableRule() { + const { + http, + notifications: { toasts }, + } = useKibana().services; + + const queryClient = useQueryClient(); + + const enableRule = useMutation( + ['enableRule'], + ({ id }) => { + try { + return http.post(`/api/alerting/rule/${id}/_enable`); + } catch (e) { + throw new Error(`Unable to parse id: ${e}`); + } + }, + { + onError: (_err) => { + toasts.addDanger( + i18n.translate( + 'xpack.observability.rules.enableErrorModal.errorNotification.descriptionText', + { + defaultMessage: 'Failed to enable rule', + } + ) + ); + }, + + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: ['fetchRule', variables.id], exact: false }); + toasts.addSuccess( + i18n.translate( + 'xpack.observability.rules.enableConfirmationModal.successNotification.descriptionText', + { + defaultMessage: 'Enabled rule', + } + ) + ); + }, + } + ); + + return enableRule; +} diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/rule_details/components/header_actions.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/rule_details/components/header_actions.tsx index 9fa0a7631a2ea..f267369f4f96a 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/rule_details/components/header_actions.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/rule_details/components/header_actions.tsx @@ -15,8 +15,13 @@ import { EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - +import { noop } from 'lodash'; +import { useFetchRule } from '../../../hooks/use_fetch_rule'; +import { useKibana } from '../../../utils/kibana_react'; +import { useEnableRule } from '../../../hooks/use_enable_rule'; +import { useDisableRule } from '../../../hooks/use_disable_rule'; interface HeaderActionsProps { + ruleId: string; isLoading: boolean; isRuleEditable: boolean; onDeleteRule: () => void; @@ -24,12 +29,35 @@ interface HeaderActionsProps { } export function HeaderActions({ + ruleId, isLoading, isRuleEditable, onDeleteRule, onEditRule, }: HeaderActionsProps) { + const { services } = useKibana(); + const { + triggersActionsUi: { + getRuleSnoozeModal: RuleSnoozeModal, + getUntrackModal: UntrackAlertsModal, + getRuleHelpers, + }, + } = services; + const [isRuleEditPopoverOpen, setIsRuleEditPopoverOpen] = useState(false); + const [snoozeModalOpen, setSnoozeModalOpen] = useState(false); + const [isUntrackAlertsModalOpen, setIsUntrackAlertsModalOpen] = useState(false); + + const { mutateAsync: enableRule } = useEnableRule(); + const { mutateAsync: disableRule } = useDisableRule(); + + const onDisableModalClose = () => { + setIsUntrackAlertsModalOpen(false); + }; + + const onDisableModalOpen = () => { + setIsUntrackAlertsModalOpen(true); + }; const togglePopover = () => setIsRuleEditPopoverOpen(!isRuleEditPopoverOpen); @@ -43,57 +71,142 @@ export function HeaderActions({ onDeleteRule(); }; - return isRuleEditable ? ( - - - - {i18n.translate('xpack.observability.ruleDetails.actionsButtonLabel', { - defaultMessage: 'Actions', - })} - - } - > - - - - {i18n.translate('xpack.observability.ruleDetails.editRule', { - defaultMessage: 'Edit rule', - })} - - - - - {i18n.translate('xpack.observability.ruleDetails.deleteRule', { - defaultMessage: 'Delete rule', + const handleEnableRule = () => { + setIsRuleEditPopoverOpen(false); + enableRule({ + id: ruleId, + }); + }; + + const handleDisableRule = (untrack: boolean) => { + setIsRuleEditPopoverOpen(false); + onDisableModalClose(); + disableRule({ + id: ruleId, + untrack, + }); + }; + + const { rule, refetch } = useFetchRule({ + ruleId, + }); + + if (!isRuleEditable || !rule) { + return null; + } + + return ( + <> + + + + {i18n.translate('xpack.observability.ruleDetails.actionsButtonLabel', { + defaultMessage: 'Actions', })} - - - - - - - ) : null; + + } + > + + { + setSnoozeModalOpen(true); + }} + > + + {i18n.translate('xpack.observability.ruleDetails.snoozeButton.snoozeSchedule', { + defaultMessage: 'Update snooze schedule', + })} + + + {rule.enabled ? ( + + + {i18n.translate('xpack.observability.ruleDetails.disableRule', { + defaultMessage: 'Disable', + })} + + + ) : ( + + + {i18n.translate('xpack.observability.ruleDetails.enableRule', { + defaultMessage: 'Enable', + })} + + + )} + + + {i18n.translate('xpack.observability.ruleDetails.editRule', { + defaultMessage: 'Edit rule', + })} + + + + + {i18n.translate('xpack.observability.ruleDetails.deleteRule', { + defaultMessage: 'Delete rule', + })} + + + + + + + + {snoozeModalOpen && ( + { + setSnoozeModalOpen(false); + setIsRuleEditPopoverOpen(false); + }} + onRuleChanged={async () => { + refetch(); + }} + onLoading={noop} + /> + )} + + {isUntrackAlertsModalOpen && ( + + )} + + ); } diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/rule_details/rule_details.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/rule_details/rule_details.tsx index 449ae1009fd30..b1f6258f8fb15 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/rule_details/rule_details.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/rule_details/rule_details.tsx @@ -206,14 +206,17 @@ export function RuleDetailsPage() { }, children: , bottomBorder: false, - rightSideItems: [ - , - ], + rightSideItems: ruleId + ? [ + , + ] + : [], }} >