Alerting: Fix display of paused recording rules (#102348)

* Fix display of paused recording rules

* Simplify paused state check for Grafana alert rules

The change eliminates redundant rule type checks by using a more generic
grafana.rule() check that covers both alerting and recording rules.

* Add test for recording rule paused state

* Add test for paused Grafana alert in Details tab

---------

Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
This commit is contained in:
Tom Ratcliffe
2025-03-18 14:10:39 +00:00
committed by GitHub
co-authored by Gilles De Mey
parent 04f20127a2
commit b8c627c566
6 changed files with 78 additions and 3 deletions
@@ -17,6 +17,7 @@ import {
mockDataSource,
mockPluginLinkExtension,
mockPromAlertingRule,
mockRulerGrafanaRecordingRule,
} from '../../mocks';
import { grafanaRulerRule } from '../../mocks/grafanaRulerApi';
import { grantPermissionsHelper } from '../../test/test-utils';
@@ -184,6 +185,30 @@ describe('RuleViewer', () => {
}
});
it('shows paused state correctly for recording rules', async () => {
const recordingRule = getGrafanaRule({
name: 'Test recording rule',
rulerRule: mockRulerGrafanaRecordingRule(
{},
{
is_paused: true,
title: 'Test recording',
record: {
metric: 'test_recording',
from: 'A',
},
}
),
});
const recordingRuleIdentifier = ruleId.fromCombinedRule('grafana', recordingRule);
await renderRuleViewer(recordingRule, recordingRuleIdentifier, ActiveTab.Details);
expect(await screen.findByText('Test recording rule')).toBeInTheDocument();
expect(await screen.findByRole('status', { name: 'Alert evaluation currently paused' })).toBeInTheDocument();
expect(screen.queryByText(/last evaluation duration/i)).not.toBeInTheDocument();
});
it('renders silencing form correctly and shows alert rule name', async () => {
await renderRuleViewer(mockRule, mockRuleIdentifier);
await openSilenceDrawer();
@@ -73,7 +73,7 @@ const RuleViewer = () => {
const isFederatedRule = isFederatedRuleGroup(rule.group);
const isProvisioned = rulerRuleType.grafana.rule(rulerRule) && Boolean(rulerRule.grafana_alert.provenance);
const isPaused = rulerRuleType.grafana.alertingRule(rulerRule) && isPausedRule(rulerRule);
const isPaused = rulerRuleType.grafana.rule(rulerRule) && isPausedRule(rulerRule);
const showError = hasError && !isPaused;
const ruleOrigin = rulerRule ? getRulePluginOrigin(rulerRule) : getRulePluginOrigin(promRule);
@@ -0,0 +1,26 @@
import { render, screen } from 'test/test-utils';
import { mockCombinedRule } from '../../../mocks';
import { alertingFactory } from '../../../mocks/server/db';
import { setupDataSources } from '../../../testSetup/datasources';
import { Details } from './Details';
beforeAll(() => {
setupDataSources();
});
describe('render details tab', () => {
it('should show paused rule', () => {
const rule = mockCombinedRule({
rulerRule: alertingFactory.ruler.grafana.recordingRule.build({
grafana_alert: {
is_paused: true,
},
}),
});
render(<Details rule={rule} />);
expect(screen.getByText(/Alert evaluation currently paused/i)).toBeInTheDocument();
});
});
@@ -74,7 +74,7 @@ export const Details = ({ rule }: DetailsProps) => {
const hasEvaluationDuration = Number.isFinite(evaluationDuration);
const updated = rulerRuleType.grafana.rule(rule.rulerRule) ? rule.rulerRule.grafana_alert.updated : undefined;
const isPaused = rulerRuleType.grafana.alertingRule(rule.rulerRule) && isPausedRule(rule.rulerRule);
const isPaused = rulerRuleType.grafana.rule(rule.rulerRule) && isPausedRule(rule.rulerRule);
const pausedIcon = (
<Stack>
@@ -353,7 +353,7 @@ function useRuleStatus(rule: CombinedRule) {
const isDeleting = Boolean(hasRuler && rulerRulesLoaded && promRule && !rulerRule);
const isCreating = Boolean(hasRuler && rulerRulesLoaded && rulerRule && !promRule);
const isPaused = rulerRuleType.grafana.alertingRule(rulerRule) && isPausedRule(rulerRule);
const isPaused = rulerRuleType.grafana.rule(rulerRule) && isPausedRule(rulerRule);
return { isDeleting, isCreating, isPaused };
}
@@ -5,11 +5,13 @@ import { DataSourceInstanceSettings, PluginType } from '@grafana/data';
import { config } from '@grafana/runtime';
import { FolderDTO } from 'app/types';
import {
GrafanaRecordingRuleDefinition,
PromAlertingRuleDTO,
PromAlertingRuleState,
PromRuleGroupDTO,
PromRuleType,
RulerAlertingRuleDTO,
RulerGrafanaRuleDTO,
RulerRecordingRuleDTO,
RulerRuleGroupDTO,
} from 'app/types/unified-alerting-dto';
@@ -114,6 +116,25 @@ const grafanaFolderFactory = Factory.define<FolderDTO>(({ sequence }) => ({
updatedBy: '',
}));
const grafanaRecordingRule = Factory.define<RulerGrafanaRuleDTO<GrafanaRecordingRuleDefinition>>(({ sequence }) => ({
grafana_alert: {
id: String(sequence),
uid: uniqueId(),
title: `Recording rule ${sequence}`,
namespace_uid: 'test-namespace',
rule_group: 'test-group',
condition: 'A',
data: [],
record: {
from: 'vector(1)',
metric: `recording_rule_${sequence}`,
},
},
for: '5m',
labels: { 'label-key-1': 'label-value-1' },
annotations: {}, // @TODO recording rules don't have annotations, we need to fix this type definition
}));
export const alertingFactory = {
folder: grafanaFolderFactory,
prometheus: {
@@ -124,6 +145,9 @@ export const alertingFactory = {
group: rulerRuleGroupFactory,
alertingRule: rulerAlertingRuleFactory,
recordingRule: rulerRecordingRuleFactory,
grafana: {
recordingRule: grafanaRecordingRule,
},
},
dataSource: dataSourceFactory,
};