From 7d80373b5e14c1d65c7c4cdfc505a9106c741e1b Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Tue, 26 Mar 2024 17:36:30 +0000 Subject: [PATCH] Move repeated menu item logic for pausing rules into separate component --- .../alerting/unified/MenuItemPauseRule.tsx | 78 +++++++++++++++++++ .../components/rules/RuleActionsButtons.tsx | 66 +++------------- 2 files changed, 90 insertions(+), 54 deletions(-) create mode 100644 public/app/features/alerting/unified/MenuItemPauseRule.tsx diff --git a/public/app/features/alerting/unified/MenuItemPauseRule.tsx b/public/app/features/alerting/unified/MenuItemPauseRule.tsx new file mode 100644 index 00000000000..a1d83103768 --- /dev/null +++ b/public/app/features/alerting/unified/MenuItemPauseRule.tsx @@ -0,0 +1,78 @@ +import React from 'react'; + +import { Menu } from '@grafana/ui'; +import { alertRuleApi } from 'app/features/alerting/unified/api/alertRuleApi'; +import { isGrafanaRulerRule, isGrafanaRulerRulePaused } from 'app/features/alerting/unified/utils/rules'; +import { CombinedRule } from 'app/types/unified-alerting'; + +const { useUpdateRuleMutation } = alertRuleApi; + +interface Props { + rule: CombinedRule; + /** + * Method invoked after the request to change the paused state has completed + */ + onPauseChange?: () => {}; +} + +/** + * Menu item to display correct text for pausing/resuming an alert, + * and triggering API call to do so + */ +const MenuItemPauseRule = ({ rule, onPauseChange }: Props) => { + const { group } = rule; + const [updateRule] = useUpdateRuleMutation(); + const isPaused = isGrafanaRulerRule(rule.rulerRule) && isGrafanaRulerRulePaused(rule.rulerRule); + const icon = isPaused ? 'play' : 'pause'; + const title = isPaused ? 'Resume alert evaluation' : 'Pause alert evaluation'; + + /** + * Triggers API call to update the current rule to the new `is_paused` state + */ + const setRulePause = async (newIsPaused: boolean) => { + if (!isGrafanaRulerRule(rule.rulerRule)) { + return; + } + const ruleUid = rule.rulerRule.grafana_alert.uid; + + // Parse the rules into correct format for API + const modifiedRules = group.rules.map((groupRule) => { + if (isGrafanaRulerRule(groupRule.rulerRule) && groupRule.rulerRule.grafana_alert.uid === ruleUid) { + return { + ...groupRule.rulerRule, + grafana_alert: { + ...groupRule.rulerRule.grafana_alert, + is_paused: newIsPaused, + }, + }; + } + + return groupRule.rulerRule!; + }); + + const payload = { + interval: group.interval!, + name: group.name, + rules: modifiedRules, + }; + + await updateRule({ + nameSpaceUID: rule.namespace.uid || rule.rulerRule.grafana_alert.namespace_uid, + payload, + }).unwrap(); + + onPauseChange?.(); + }; + + return ( + { + setRulePause(!isPaused); + }} + /> + ); +}; + +export default MenuItemPauseRule; diff --git a/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx b/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx index 1c42aa0bd18..ee7601b2a47 100644 --- a/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx @@ -17,7 +17,7 @@ import { Stack, } from '@grafana/ui'; import { useAppNotification } from 'app/core/copy/appNotification'; -import { alertRuleApi } from 'app/features/alerting/unified/api/alertRuleApi'; +import MenuItemPauseRule from 'app/features/alerting/unified/MenuItemPauseRule'; import { INSTANCES_DISPLAY_LIMIT } from 'app/features/alerting/unified/components/rules/RuleDetails'; import { useRulesFilter } from 'app/features/alerting/unified/hooks/useFilteredRules'; import { useDispatch } from 'app/types'; @@ -28,13 +28,11 @@ import { deleteRuleAction, fetchAllPromAndRulerRulesAction } from '../../state/a import { getRulesSourceName } from '../../utils/datasource'; import { createShareLink, createViewLink } from '../../utils/misc'; import * as ruleId from '../../utils/rule-id'; -import { isGrafanaRulerRule, isGrafanaRulerRulePaused } from '../../utils/rules'; +import { isGrafanaRulerRule } from '../../utils/rules'; import { createUrl } from '../../utils/url'; import { RedirectToCloneRule } from './CloneRule'; -const { useUpdateRuleMutation } = alertRuleApi; - export const matchesWidth = (width: number) => window.matchMedia(`(max-width: ${width}px)`).matches; interface Props { @@ -47,7 +45,6 @@ export const RuleActionsButtons = ({ rule, rulesSource }: Props) => { const location = useLocation(); const notifyApp = useAppNotification(); const style = useStyles2(getStyles); - const [updateRule] = useUpdateRuleMutation(); const [redirectToClone, setRedirectToClone] = useState< { identifier: RuleIdentifier; isProvisioned: boolean } | undefined @@ -75,47 +72,6 @@ export const RuleActionsButtons = ({ rule, rulesSource }: Props) => { const buttons: JSX.Element[] = []; const moreActions: JSX.Element[] = []; - /** - * Triggers API call to update the current rule to the new `is_paused` state - */ - const setRulePause = async (newIsPaused: boolean) => { - if (!isGrafanaRulerRule(rule.rulerRule)) { - return; - } - const ruleUid = rule.rulerRule.grafana_alert.uid; - - // Parse the rules into correct format for API - const modifiedRules = group.rules.map((groupRule) => { - if (isGrafanaRulerRule(groupRule.rulerRule) && groupRule.rulerRule.grafana_alert.uid === ruleUid) { - return { - ...groupRule.rulerRule, - grafana_alert: { - ...groupRule.rulerRule.grafana_alert, - is_paused: newIsPaused, - }, - }; - } - - return groupRule.rulerRule!; - }); - - const payload = { - interval: group.interval!, - name: group.name, - rules: modifiedRules, - }; - - await updateRule({ nameSpaceUID: rule.namespace.uid!, payload }).unwrap(); - - // Uses INSTANCES_DISPLAY_LIMIT + 1 here as exporting LIMIT_ALERTS from RuleList has the side effect - // of breaking some unrelated tests in Policy.test.tsx due to mocking approach - const limitAlerts = hasActiveFilters ? undefined : INSTANCES_DISPLAY_LIMIT + 1; - // Trigger a re-fetch of the rules table - // TODO: Migrate rules table functionality to RTK Query, so we instead rely - // on tag invalidation (or optimistic cache updates) for this - await dispatch(fetchAllPromAndRulerRulesAction(false, { limitAlerts })); - }; - const deleteRule = () => { if (ruleToDelete && ruleToDelete.rulerRule) { const identifier = ruleId.fromRulerRule( @@ -172,15 +128,17 @@ export const RuleActionsButtons = ({ rule, rulesSource }: Props) => { ); - const isPaused = isGrafanaRulerRule(rule.rulerRule) && isGrafanaRulerRulePaused(rule.rulerRule); - const icon = isPaused ? 'play' : 'pause'; - const title = isPaused ? 'Resume alert evaluation' : 'Pause alert evaluation'; moreActions.push( - { - setRulePause(!isPaused); + { + // Uses INSTANCES_DISPLAY_LIMIT + 1 here as exporting LIMIT_ALERTS from RuleList has the side effect + // of breaking some unrelated tests in Policy.test.tsx due to mocking approach + const limitAlerts = hasActiveFilters ? undefined : INSTANCES_DISPLAY_LIMIT + 1; + // Trigger a re-fetch of the rules table + // TODO: Migrate rules table functionality to RTK Query, so we instead rely + // on tag invalidation (or optimistic cache updates) for this + await dispatch(fetchAllPromAndRulerRulesAction(false, { limitAlerts })); }} /> );