From 9f8af7a6538d745b188a9f5ac8d520f69e5b5afc Mon Sep 17 00:00:00 2001 From: Bailey Cash Date: Wed, 7 May 2025 23:14:12 -0400 Subject: [PATCH] [Rules] Adding actions to the rules details action menu (Part 2) (#219790) (cherry picked from commit 0129955c5d67087c1a74ca2bbf133e25866a36a7) --- .../public/hooks/use_run_rule.ts | 53 +++++ .../public/hooks/use_update_api_key.ts | 56 ++++++ .../components/header_actions.tsx | 189 +++++++++++------- 3 files changed, 222 insertions(+), 76 deletions(-) create mode 100644 x-pack/solutions/observability/plugins/observability/public/hooks/use_run_rule.ts create mode 100644 x-pack/solutions/observability/plugins/observability/public/hooks/use_update_api_key.ts diff --git a/x-pack/solutions/observability/plugins/observability/public/hooks/use_run_rule.ts b/x-pack/solutions/observability/plugins/observability/public/hooks/use_run_rule.ts new file mode 100644 index 0000000000000..fcc3774ef0349 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/hooks/use_run_rule.ts @@ -0,0 +1,53 @@ +/* + * 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 useRunRule() { + const { + http, + notifications: { toasts }, + } = useKibana().services; + + const queryClient = useQueryClient(); + + const runRule = useMutation( + ['runRule'], + ({ id }) => { + try { + return http.post(`/internal/alerting/rule/${id}/_run_soon`); + } catch (e) { + throw new Error(`Unable to parse id: ${e}`); + } + }, + { + onError: (_err) => { + toasts.addDanger( + i18n.translate( + 'xpack.observability.rules.runErrorModal.errorNotification.descriptionText', + { + defaultMessage: 'Failed to schedule rule', + } + ) + ); + }, + + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: ['fetchRule', variables.id], exact: false }); + toasts.addSuccess( + i18n.translate('xpack.observability.rules.run.successNotification.descriptionText', { + defaultMessage: 'Your rule is scheduled to run', + }) + ); + }, + } + ); + + return runRule; +} diff --git a/x-pack/solutions/observability/plugins/observability/public/hooks/use_update_api_key.ts b/x-pack/solutions/observability/plugins/observability/public/hooks/use_update_api_key.ts new file mode 100644 index 0000000000000..9470a03ee490e --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/hooks/use_update_api_key.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 useUpdateAPIKey() { + const { + http, + notifications: { toasts }, + } = useKibana().services; + + const queryClient = useQueryClient(); + + const updateAPIKey = useMutation( + ['updateAPIKey'], + ({ id }) => { + try { + return http.post(`/api/alerting/rule/${id}/_update_api_key`); + } catch (e) { + throw new Error(`Unable to parse id: ${e}`); + } + }, + { + onError: (_err) => { + toasts.addDanger( + i18n.translate( + 'xpack.observability.rules.updateAPIKey.errorNotification.descriptionText', + { + defaultMessage: 'Failed to update API key for rule', + } + ) + ); + }, + + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: ['fetchRule', variables.id], exact: false }); + toasts.addSuccess( + i18n.translate( + 'xpack.observability.rules.updateAPIKey.successNotification.descriptionText', + { + defaultMessage: 'Updated API key for rule', + } + ) + ); + }, + } + ); + + return updateAPIKey; +} 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 f267369f4f96a..5bf48fe361f40 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 @@ -6,13 +6,15 @@ */ import React, { useState } from 'react'; +import { css } from '@emotion/react'; import { EuiButton, - EuiButtonEmpty, + EuiButtonIcon, + EuiContextMenu, EuiFlexGroup, EuiFlexItem, EuiPopover, - EuiText, + useEuiTheme, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { noop } from 'lodash'; @@ -20,6 +22,8 @@ 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'; +import { useRunRule } from '../../../hooks/use_run_rule'; +import { useUpdateAPIKey } from '../../../hooks/use_update_api_key'; interface HeaderActionsProps { ruleId: string; isLoading: boolean; @@ -48,8 +52,18 @@ export function HeaderActions({ const [snoozeModalOpen, setSnoozeModalOpen] = useState(false); const [isUntrackAlertsModalOpen, setIsUntrackAlertsModalOpen] = useState(false); - const { mutateAsync: enableRule } = useEnableRule(); - const { mutateAsync: disableRule } = useDisableRule(); + const { euiTheme } = useEuiTheme(); + + const collapsedItemActionsCss = css` + .collapsedItemActions__deleteButton { + color: ${euiTheme.colors.textDanger}; + } + `; + + const { mutate: enableRule } = useEnableRule(); + const { mutate: disableRule } = useDisableRule(); + const { mutate: runRule } = useRunRule(); + const { mutate: updateAPIKey } = useUpdateAPIKey(); const onDisableModalClose = () => { setIsUntrackAlertsModalOpen(false); @@ -71,6 +85,20 @@ export function HeaderActions({ onDeleteRule(); }; + const handleRunRule = () => { + setIsRuleEditPopoverOpen(false); + runRule({ + id: ruleId, + }); + }; + + const handleUpdateAPIKey = () => { + setIsRuleEditPopoverOpen(false); + updateAPIKey({ + id: ruleId, + }); + }; + const handleEnableRule = () => { setIsRuleEditPopoverOpen(false); enableRule({ @@ -95,12 +123,73 @@ export function HeaderActions({ return null; } + const disableRuleOption = { + 'data-test-subj': 'disableRuleButton', + onClick: onDisableModalOpen, + name: i18n.translate('xpack.observability.ruleDetails.disableRule', { + defaultMessage: 'Disable', + }), + }; + + const enableRuleOption = { + 'data-test-subj': 'enableRuleButton', + onClick: handleEnableRule, + name: i18n.translate('xpack.observability.ruleDetails.enableRule', { + defaultMessage: 'Enable', + }), + }; + + const panels = [ + { + id: 0, + hasFocus: false, + items: [ + ...[rule.enabled ? disableRuleOption : enableRuleOption], + { + 'data-test-subj': 'runRuleButton', + onClick: handleRunRule, + name: i18n.translate('xpack.observability.ruleDetails.runRule', { + defaultMessage: 'Run', + }), + }, + { + 'data-test-subj': 'updateAPIKeyButton', + onClick: handleUpdateAPIKey, + name: i18n.translate('xpack.observability.ruleDetails.updateAPIkey', { + defaultMessage: 'Update API key', + }), + }, + { + isSeparator: true as const, + }, + { + icon: 'pencil', + 'data-test-subj': 'editRuleButton', + onClick: handleEditRule, + name: i18n.translate('xpack.observability.ruleDetails.editRule', { + defaultMessage: 'Edit', + }), + }, + { + icon: 'trash', + 'data-test-subj': 'deleteRuleButton', + className: 'collapsedItemActions__deleteButton', + onClick: handleRemoveRule, + name: i18n.translate('xpack.observability.ruleDetails.deleteRule', { + defaultMessage: 'Delete', + }), + }, + ], + }, + ]; + return ( <> - + } > - - { - 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', - })} - - - + + + { + setSnoozeModalOpen(true); + }} + /> + {snoozeModalOpen && ( @@ -197,9 +236,7 @@ export function HeaderActions({ setSnoozeModalOpen(false); setIsRuleEditPopoverOpen(false); }} - onRuleChanged={async () => { - refetch(); - }} + onRuleChanged={refetch} onLoading={noop} /> )}