Alerting: Stop rule state view from crashing when prom rule state is missing (#91483)

This commit is contained in:
Tom Ratcliffe
2024-08-30 14:47:28 +01:00
committed by GitHub
parent a0c1fc20ec
commit 172662af58
3 changed files with 80 additions and 2 deletions
@@ -1,9 +1,15 @@
import { lastValueFrom } from 'rxjs';
import { getBackendSrv } from '@grafana/runtime';
import { logInfo } from 'app/features/alerting/unified/Analytics';
import { Matcher } from 'app/plugins/datasource/alertmanager/types';
import { RuleGroup, RuleIdentifier, RuleNamespace } from 'app/types/unified-alerting';
import { PromRuleGroupDTO, PromRulesResponse } from 'app/types/unified-alerting-dto';
import {
PromAlertingRuleState,
PromRuleGroupDTO,
PromRulesResponse,
PromRuleType,
} from 'app/types/unified-alerting-dto';
import { getDatasourceAPIUid, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
import { isCloudRuleIdentifier, isPrometheusRuleIdentifier } from '../utils/rules';
@@ -88,6 +94,16 @@ export const groupRulesByFileName = (groups: PromRuleGroupDTO[], dataSourceName:
groups.forEach((group) => {
group.rules.forEach((rule) => {
rule.query = rule.query || '';
if (rule.type === PromRuleType.Alerting) {
// There's a possibility that a custom/unexpected datasource might response with
// `type: alerting` but no state
// In this case, we fall back to `Inactive` state so that elsewhere in the UI we don't fail/have to handle the edge case
// and log a message so we can identify how frequently this might be happening
if (!rule.state) {
logInfo('prom rule with type=alerting is missing a state', { dataSourceName, ruleName: rule.name });
rule.state = PromAlertingRuleState.Inactive;
}
}
});
if (!nsMap[group.file]) {
nsMap[group.file] = {
@@ -0,0 +1,59 @@
import { render, screen } from 'test/test-utils';
import { setPluginExtensionsHook } from '@grafana/runtime';
import { RuleListStateView } from 'app/features/alerting/unified/components/rules/RuleListStateView';
import {
mockCombinedRule,
mockCombinedRuleGroup,
mockCombinedRuleNamespace,
mockPromAlertingRule,
} from 'app/features/alerting/unified/mocks';
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
setPluginExtensionsHook(() => ({
extensions: [],
isLoading: false,
}));
const namespaces = [
mockCombinedRuleNamespace({
groups: [
mockCombinedRuleGroup('Group with a missing state', [
mockCombinedRule({
name: 'Rule in firing state',
promRule: mockPromAlertingRule({
state: PromAlertingRuleState.Firing,
}),
}),
mockCombinedRule({
name: 'Rule in pending state',
promRule: mockPromAlertingRule({
state: PromAlertingRuleState.Pending,
}),
}),
mockCombinedRule({
name: 'Rule in inactive state',
promRule: mockPromAlertingRule({
state: PromAlertingRuleState.Inactive,
}),
}),
mockCombinedRule({
name: 'Rule with missing prom state',
promRule: mockPromAlertingRule({
state: undefined,
}),
}),
]),
],
}),
];
describe('RuleListStateView', () => {
it('renders differing prom rule states correctly and does not crash with missing state', () => {
render(<RuleListStateView namespaces={namespaces} />);
expect(screen.getByText(/firing \(1\)/i)).toBeInTheDocument();
expect(screen.getByText(/pending \(1\)/i)).toBeInTheDocument();
expect(screen.getByText(/normal \(1\)/i)).toBeInTheDocument();
});
});
@@ -28,7 +28,10 @@ export const RuleListStateView = ({ namespaces }: Props) => {
namespaces.forEach((namespace) =>
namespace.groups.forEach((group) =>
group.rules.forEach((rule) => {
if (rule.promRule && isAlertingRule(rule.promRule)) {
// We might hit edge cases where there type = alerting, but there is no state.
// In this case, we shouldn't try to group these alerts in the state view
// Even though we handle this at the API layer, this is a last catch point for any edge cases
if (rule.promRule && isAlertingRule(rule.promRule) && rule.promRule.state) {
result[rule.promRule.state].push(rule);
}
})