Alerting: fix maching loki prom rules to rule rules, rename "Inactive" to "Normal" (#34111)

This commit is contained in:
Domas 2021-05-14 13:41:13 +03:00 committed by GitHub
parent a71cebbcb1
commit ddb2fc1ae6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 66 additions and 22 deletions

View File

@ -244,16 +244,16 @@ describe('RuleList', () => {
let ruleRows = table.querySelectorAll<HTMLTableRowElement>(':scope > tbody > tr');
expect(ruleRows).toHaveLength(4);
expect(ruleRows[0]).toHaveTextContent('n/a');
expect(ruleRows[0]).toHaveTextContent('Recording rule');
expect(ruleRows[0]).toHaveTextContent('recordingrule');
expect(ruleRows[1]).toHaveTextContent('firing');
expect(ruleRows[1]).toHaveTextContent('Firing');
expect(ruleRows[1]).toHaveTextContent('alertingrule');
expect(ruleRows[2]).toHaveTextContent('pending');
expect(ruleRows[2]).toHaveTextContent('Pending');
expect(ruleRows[2]).toHaveTextContent('p-rule');
expect(ruleRows[3]).toHaveTextContent('inactive');
expect(ruleRows[3]).toHaveTextContent('Normal');
expect(ruleRows[3]).toHaveTextContent('i-rule');
expect(byText('Labels').query()).not.toBeInTheDocument();
@ -277,8 +277,8 @@ describe('RuleList', () => {
let instanceRows = instancesTable?.querySelectorAll<HTMLTableRowElement>(':scope > tbody > tr');
expect(instanceRows).toHaveLength(2);
expect(instanceRows![0]).toHaveTextContent('firingfoo=barseverity=warning2021-03-18 13:47:05');
expect(instanceRows![1]).toHaveTextContent('firingfoo=bazseverity=error2021-03-18 13:47:05');
expect(instanceRows![0]).toHaveTextContent('Firingfoo=barseverity=warning2021-03-18 13:47:05');
expect(instanceRows![1]).toHaveTextContent('Firingfoo=bazseverity=error2021-03-18 13:47:05');
// expand details of an instance
userEvent.click(ui.alertCollapseToggle.get(instanceRows![0]));

View File

@ -1,5 +1,6 @@
import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto';
import React, { FC } from 'react';
import { alertStateToReadable } from '../../utils/rules';
import { State, StateTag } from '../StateTag';
const alertStateToState: Record<PromAlertingRuleState | GrafanaAlertState, State> = {
@ -17,4 +18,6 @@ interface Props {
state: PromAlertingRuleState | GrafanaAlertState;
}
export const AlertStateTag: FC<Props> = ({ state }) => <StateTag state={alertStateToState[state]}>{state}</StateTag>;
export const AlertStateTag: FC<Props> = ({ state }) => (
<StateTag state={alertStateToState[state]}>{alertStateToReadable(state)}</StateTag>
);

View File

@ -3,8 +3,8 @@ import { GrafanaTheme } from '@grafana/data';
import { useStyles } from '@grafana/ui';
import { CombinedRule } from 'app/types/unified-alerting';
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
import { capitalize } from 'lodash';
import React, { FC, useState } from 'react';
import { alertStateToReadable } from '../../utils/rules';
import { CollapseToggle } from '../CollapseToggle';
import { RulesTable } from './RulesTable';
@ -26,7 +26,7 @@ export const RuleListStateSection: FC<Props> = ({ rules, state, defaultCollapsed
isCollapsed={collapsed}
onToggle={() => setCollapsed(!collapsed)}
/>
{capitalize(state)} ({rules.length})
{alertStateToReadable(state)} ({rules.length})
</h4>
{!collapsed && <RulesTable rules={rules} showGroupColumn={true} />}
</>

View File

@ -4,7 +4,7 @@ import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
import React, { FC, useMemo } from 'react';
import { getFiltersFromUrlParams } from '../../utils/misc';
import { isAlertingRule } from '../../utils/rules';
import { RuleListStateSection } from './RuleListSateSection';
import { RuleListStateSection } from './RuleListStateSection';
interface Props {
namespaces: CombinedRuleNamespace[];

View File

@ -8,6 +8,7 @@ import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { getFiltersFromUrlParams } from '../../utils/misc';
import { DataSourcePicker } from '@grafana/runtime';
import { alertStateToReadable } from '../../utils/rules';
const RulesFilter = () => {
const [queryParams, setQueryParams] = useQueryParams();
@ -19,7 +20,10 @@ const RulesFilter = () => {
const { dataSource, alertState, queryString } = getFiltersFromUrlParams(queryParams);
const styles = useStyles(getStyles);
const stateOptions = Object.entries(PromAlertingRuleState).map(([key, value]) => ({ label: key, value }));
const stateOptions = Object.entries(PromAlertingRuleState).map(([key, value]) => ({
label: alertStateToReadable(value),
value,
}));
const handleDataSourceChange = (dataSourceValue: DataSourceInstanceSettings) => {
setQueryParams({ dataSource: dataSourceValue.name });

View File

@ -1,7 +1,7 @@
import { GrafanaTheme2 } from '@grafana/data';
import { ConfirmModal, useStyles2 } from '@grafana/ui';
import React, { FC, Fragment, useState } from 'react';
import { getRuleIdentifier, isAlertingRule, stringifyRuleIdentifier } from '../../utils/rules';
import { getRuleIdentifier, isAlertingRule, isRecordingRule, stringifyRuleIdentifier } from '../../utils/rules';
import { CollapseToggle } from '../CollapseToggle';
import { css, cx } from '@emotion/css';
import { RuleDetails } from './RuleDetails';
@ -126,7 +126,15 @@ export const RulesTable: FC<Props> = ({
data-testid="rule-collapse-toggle"
/>
</td>
<td>{promRule && isAlertingRule(promRule) ? <AlertStateTag state={promRule.state} /> : 'n/a'}</td>
<td>
{promRule && isAlertingRule(promRule) ? (
<AlertStateTag state={promRule.state} />
) : promRule && isRecordingRule(promRule) ? (
'Recording rule'
) : (
'n/a'
)}
</td>
<td>{rule.name}</td>
{showGroupColumn && (
<td>{isCloudRulesSource(rulesSource) ? `${namespace.name} > ${group.name}` : namespace.name}</td>

View File

@ -164,23 +164,42 @@ function rulerRuleToCombinedRule(
};
}
// find existing rule in group that matches the given prom rule
function getExistingRuleInGroup(
rule: Rule,
group: CombinedRuleGroup,
rulesSource: RulesSource
): CombinedRule | undefined {
return isGrafanaRulesSource(rulesSource)
? group!.rules.find((existingRule) => existingRule.name === rule.name) // assume grafana groups have only the one rule. check name anyway because paranoid
: group!.rules.find((existingRule) => {
return !existingRule.promRule && isCombinedRuleEqualToPromRule(existingRule, rule);
});
if (isGrafanaRulesSource(rulesSource)) {
// assume grafana groups have only the one rule. check name anyway because paranoid
return group!.rules.find((existingRule) => existingRule.name === rule.name);
}
return (
// try finding a rule that matches name, labels, annotations and query
group!.rules.find(
(existingRule) => !existingRule.promRule && isCombinedRuleEqualToPromRule(existingRule, rule, true)
) ??
// if that fails, try finding a rule that only matches name, labels and annotations.
// loki & prom can sometimes modify the query so it doesnt match, eg `2 > 1` becomes `1`
group!.rules.find(
(existingRule) => !existingRule.promRule && isCombinedRuleEqualToPromRule(existingRule, rule, false)
)
);
}
function isCombinedRuleEqualToPromRule(combinedRule: CombinedRule, rule: Rule): boolean {
function isCombinedRuleEqualToPromRule(combinedRule: CombinedRule, rule: Rule, checkQuery = true): boolean {
if (combinedRule.name === rule.name) {
return (
JSON.stringify([hashQuery(combinedRule.query), combinedRule.labels, combinedRule.annotations]) ===
JSON.stringify([hashQuery(rule.query), rule.labels || {}, isAlertingRule(rule) ? rule.annotations || {} : {}])
JSON.stringify([
checkQuery ? hashQuery(combinedRule.query) : '',
combinedRule.labels,
combinedRule.annotations,
]) ===
JSON.stringify([
checkQuery ? hashQuery(rule.query) : '',
rule.labels || {},
isAlertingRule(rule) ? rule.annotations || {} : {},
])
);
}
return false;
@ -194,6 +213,6 @@ function hashQuery(query: string) {
}
// whitespace could be added or removed
query = query.replace(/\s|\n/g, '');
// labels matchers can be reordered, so sort the enitre string, esentially comparing just hte character counts
// labels matchers can be reordered, so sort the enitre string, esentially comparing just the character counts
return query.split('').sort().join('');
}

View File

@ -1,6 +1,8 @@
import {
Annotations,
GrafanaAlertState,
Labels,
PromAlertingRuleState,
PromRuleType,
RulerAlertingRuleDTO,
RulerGrafanaRuleDTO,
@ -20,6 +22,7 @@ import {
import { AsyncRequestState } from './redux';
import { RULER_NOT_SUPPORTED_MSG } from './constants';
import { hash } from './misc';
import { capitalize } from 'lodash';
export function isAlertingRule(rule: Rule): rule is AlertingRule {
return rule.type === PromRuleType.Alerting;
@ -132,3 +135,10 @@ export function ruleWithLocationToRuleIdentifier(ruleWithLocation: RuleWithLocat
ruleWithLocation.rule
);
}
export function alertStateToReadable(state: PromAlertingRuleState | GrafanaAlertState): string {
if (state === PromAlertingRuleState.Inactive) {
return 'Normal';
}
return capitalize(state);
}