From 6e861b19fa135c665de78053e7b8d2d09d47efa5 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Mon, 30 Jan 2023 16:21:27 +0000 Subject: [PATCH] [v9.4.x] Alerting: allow to pause existing rules (#62491) Co-authored-by: gillesdemey Co-authored-by: Virginia Cepeda --- .../unified/RuleEditorExisting.test.tsx | 1 + .../unified/RuleEditorGrafanaRules.test.tsx | 1 + .../alerting/unified/RuleList.test.tsx | 4 +- .../components/rule-editor/AlertRuleForm.tsx | 1 + .../rule-editor/GrafanaEvaluationBehavior.tsx | 41 ++++++++++++++++++- .../components/rules/AlertStateTag.tsx | 5 ++- .../unified/components/rules/RuleState.tsx | 5 ++- .../unified/components/rules/RulesTable.tsx | 6 ++- .../alerting/unified/types/rule-form.ts | 1 + .../__snapshots__/rule-form.test.ts.snap | 2 + .../alerting/unified/utils/rule-form.ts | 4 +- public/app/types/unified-alerting-dto.ts | 1 + 12 files changed, 63 insertions(+), 9 deletions(-) diff --git a/public/app/features/alerting/unified/RuleEditorExisting.test.tsx b/public/app/features/alerting/unified/RuleEditorExisting.test.tsx index d0dbaf8c397..9862a96baa5 100644 --- a/public/app/features/alerting/unified/RuleEditorExisting.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorExisting.test.tsx @@ -210,6 +210,7 @@ describe('RuleEditor grafana managed rules', () => { condition: 'B', data: getDefaultQueries(), exec_err_state: GrafanaAlertStateDecision.Error, + is_paused: false, no_data_state: 'NoData', title: 'my great new rule', }, diff --git a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx index f35a627b2ca..99e943fb74f 100644 --- a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx @@ -160,6 +160,7 @@ describe('RuleEditor grafana managed rules', () => { condition: 'B', data: getDefaultQueries(), exec_err_state: GrafanaAlertStateDecision.Error, + is_paused: false, no_data_state: 'NoData', title: 'my great new rule', }, diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index 9d2aede224c..fccb1899192 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -371,8 +371,8 @@ describe('RuleList', () => { const instanceRows = byTestId('row').getAll(instancesTable); expect(instanceRows).toHaveLength(2); - expect(instanceRows![0]).toHaveTextContent('Firingfoo=barseverity=warning2021-03-18 08:47:05'); - expect(instanceRows![1]).toHaveTextContent('Firingfoo=bazseverity=error2021-03-18 08:47:05'); + expect(instanceRows![0]).toHaveTextContent('Firing foo=barseverity=warning2021-03-18 08:47:05'); + expect(instanceRows![1]).toHaveTextContent('Firing foo=bazseverity=error2021-03-18 08:47:05'); // expand details of an instance await userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0])); diff --git a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx index 83b9c87c8bf..0e7a6c485a2 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx @@ -255,6 +255,7 @@ export const AlertRuleForm: FC = ({ existing, prefill }) => { initialFolder={defaultValues.folder} evaluateEvery={evaluateEvery} setEvaluateEvery={setEvaluateEvery} + existing={Boolean(existing)} /> ) : ( diff --git a/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx b/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx index 710e584b09e..5dfae11cf77 100644 --- a/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx @@ -4,7 +4,7 @@ import { RegisterOptions, useFormContext } from 'react-hook-form'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { Stack } from '@grafana/experimental'; -import { Button, Field, InlineLabel, Input, InputControl, useStyles2 } from '@grafana/ui'; +import { Button, Field, InlineLabel, Input, InputControl, useStyles2, Switch, Tooltip, Icon } from '@grafana/ui'; import { RulerRuleDTO, RulerRuleGroupDTO, RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; import { logInfo, LogMessages } from '../../Analytics'; @@ -253,14 +253,20 @@ export function GrafanaEvaluationBehavior({ initialFolder, evaluateEvery, setEvaluateEvery, + existing, }: { initialFolder: RuleForm | null; evaluateEvery: string; setEvaluateEvery: (value: string) => void; + existing: boolean; }) { const styles = useStyles2(getStyles); const [showErrorHandling, setShowErrorHandling] = useState(false); + const { watch, setValue } = useFormContext(); + + const isPaused = watch('isPaused'); + return ( // TODO remove "and alert condition" for recording rules @@ -271,6 +277,31 @@ export function GrafanaEvaluationBehavior({ evaluateEvery={evaluateEvery} /> + + {existing && ( + + ( + + { + setValue('isPaused', value.currentTarget.checked); + }} + value={Boolean(isPaused)} + /> + + + )} + name="isPaused" + /> + + )} ({ margin-right: ${theme.spacing(1)}; color: ${theme.colors.warning.text}; `, + infoIcon: css` + margin-left: 10px; + `, warningMessage: css` color: ${theme.colors.warning.text}; `, @@ -354,4 +388,9 @@ const getStyles = (theme: GrafanaTheme2) => ({ marginTop: css` margin-top: ${theme.spacing(1)}; `, + switchLabel: css(` + color: ${theme.colors.text.primary}, + cursor: 'pointer', + fontSize: ${theme.typography.bodySmall.fontSize}, + `), }); diff --git a/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx b/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx index 510da6f2f1e..8b37765405a 100644 --- a/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx +++ b/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx @@ -8,10 +8,11 @@ import { StateTag } from '../StateTag'; interface Props { state: PromAlertingRuleState | GrafanaAlertState | GrafanaAlertStateWithReason | AlertState; size?: 'md' | 'sm'; + isPaused?: boolean; } -export const AlertStateTag: FC = ({ state, size = 'md' }) => ( +export const AlertStateTag: FC = ({ state, isPaused = false, size = 'md' }) => ( - {alertStateToReadable(state)} + {alertStateToReadable(state)} {isPaused ? ' (Paused)' : ''} ); diff --git a/public/app/features/alerting/unified/components/rules/RuleState.tsx b/public/app/features/alerting/unified/components/rules/RuleState.tsx index c94b69b5e1a..017557fc634 100644 --- a/public/app/features/alerting/unified/components/rules/RuleState.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleState.tsx @@ -14,9 +14,10 @@ interface Props { rule: CombinedRule; isDeleting: boolean; isCreating: boolean; + isPaused?: boolean; } -export const RuleState: FC = ({ rule, isDeleting, isCreating }) => { +export const RuleState: FC = ({ rule, isDeleting, isCreating, isPaused }) => { const style = useStyles2(getStyle); const { promRule } = rule; @@ -68,7 +69,7 @@ export const RuleState: FC = ({ rule, isDeleting, isCreating }) => { } else if (promRule && isAlertingRule(promRule)) { return ( - + {forTime} ); diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.tsx index 5e1c0fddb0e..04780159320 100644 --- a/public/app/features/alerting/unified/components/rules/RulesTable.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesTable.tsx @@ -114,9 +114,13 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean) { const { namespace } = rule; const { rulesSource } = namespace; const { promRule, rulerRule } = rule; + const isDeleting = !!(hasRuler(rulesSource) && rulerRulesLoaded(rulesSource) && promRule && !rulerRule); const isCreating = !!(hasRuler(rulesSource) && rulerRulesLoaded(rulesSource) && rulerRule && !promRule); - return ; + const isGrafanaManagedRule = isGrafanaRulerRule(rulerRule); + const isPaused = isGrafanaManagedRule && Boolean(rulerRule.grafana_alert.is_paused); + + return ; }, size: '165px', }, diff --git a/public/app/features/alerting/unified/types/rule-form.ts b/public/app/features/alerting/unified/types/rule-form.ts index 3a097ab778b..5a8e602b9df 100644 --- a/public/app/features/alerting/unified/types/rule-form.ts +++ b/public/app/features/alerting/unified/types/rule-form.ts @@ -29,6 +29,7 @@ export interface RuleFormValues { folder: RuleForm | null; evaluateEvery: string; evaluateFor: string; + isPaused?: boolean; // cortex / loki rules namespace: string; diff --git a/public/app/features/alerting/unified/utils/__snapshots__/rule-form.test.ts.snap b/public/app/features/alerting/unified/utils/__snapshots__/rule-form.test.ts.snap index 77658b56588..bfd1da8464e 100644 --- a/public/app/features/alerting/unified/utils/__snapshots__/rule-form.test.ts.snap +++ b/public/app/features/alerting/unified/utils/__snapshots__/rule-form.test.ts.snap @@ -12,6 +12,7 @@ exports[`formValuesToRulerGrafanaRuleDTO should correctly convert rule form valu "condition": "A", "data": [], "exec_err_state": "Error", + "is_paused": false, "no_data_state": "NoData", "title": "", }, @@ -49,6 +50,7 @@ exports[`formValuesToRulerGrafanaRuleDTO should not save both instant and range }, ], "exec_err_state": "Error", + "is_paused": false, "no_data_state": "NoData", "title": "", }, diff --git a/public/app/features/alerting/unified/utils/rule-form.ts b/public/app/features/alerting/unified/utils/rule-form.ts index a55810ab18b..1c7ac452a7f 100644 --- a/public/app/features/alerting/unified/utils/rule-form.ts +++ b/public/app/features/alerting/unified/utils/rule-form.ts @@ -96,7 +96,7 @@ function listifyLabelsOrAnnotations(item: Labels | Annotations | undefined): Arr } export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): PostableRuleGrafanaRuleDTO { - const { name, condition, noDataState, execErrState, evaluateFor, queries } = values; + const { name, condition, noDataState, execErrState, evaluateFor, queries, isPaused } = values; if (condition) { return { grafana_alert: { @@ -105,6 +105,7 @@ export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): Postabl no_data_state: noDataState, exec_err_state: execErrState, data: queries.map(fixBothInstantAndRangeQuery), + is_paused: Boolean(isPaused), }, for: evaluateFor, annotations: arrayToRecord(values.annotations || []), @@ -135,6 +136,7 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF annotations: listifyLabelsOrAnnotations(rule.annotations), labels: listifyLabelsOrAnnotations(rule.labels), folder: { title: namespace, id: ga.namespace_id }, + isPaused: ga.is_paused, }; } else { throw new Error('Unexpected type of rule for grafana rules source'); diff --git a/public/app/types/unified-alerting-dto.ts b/public/app/types/unified-alerting-dto.ts index 4d00880782e..487d8cda0a3 100644 --- a/public/app/types/unified-alerting-dto.ts +++ b/public/app/types/unified-alerting-dto.ts @@ -200,6 +200,7 @@ export interface PostableGrafanaRuleDefinition { no_data_state: GrafanaAlertStateDecision; exec_err_state: GrafanaAlertStateDecision; data: AlertQuery[]; + is_paused?: boolean; } export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition { id?: string;