mirror of
https://github.com/grafana/grafana.git
synced 2026-07-30 00:08:10 -05:00
Alerting: Adds evaluation interval to group view (#59974)
This commit is contained in:
@@ -322,8 +322,11 @@ describe('RuleList', () => {
|
||||
|
||||
const groups = await ui.ruleGroup.findAll();
|
||||
expect(groups).toHaveLength(2);
|
||||
expect(groups[0]).toHaveTextContent('1 rule');
|
||||
expect(groups[1]).toHaveTextContent('4 rules: 1 firing, 1 pending');
|
||||
expect(groups[0]).toHaveTextContent('1 firing');
|
||||
expect(groups[1]).toHaveTextContent('1 firing');
|
||||
expect(groups[1]).toHaveTextContent('1 pending');
|
||||
expect(groups[1]).toHaveTextContent('1 recording');
|
||||
expect(groups[1]).toHaveTextContent('1 normal');
|
||||
|
||||
// expand second group to see rules table
|
||||
expect(ui.rulesTable.query()).not.toBeInTheDocument();
|
||||
|
||||
@@ -105,7 +105,7 @@ const RuleList = withErrorBoundary(
|
||||
{expandAll ? 'Collapse all' : 'Expand all'}
|
||||
</Button>
|
||||
)}
|
||||
<RuleStats showInactive={true} showRecording={true} namespaces={filteredNamespaces} />
|
||||
<RuleStats namespaces={filteredNamespaces} includeTotal />
|
||||
</div>
|
||||
{(canCreateGrafanaRules || canCreateCloudRules) && (
|
||||
<LinkButton
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import pluralize from 'pluralize';
|
||||
import React, { FC, Fragment, useMemo } from 'react';
|
||||
|
||||
import { Stack } from '@grafana/experimental';
|
||||
import { Badge } from '@grafana/ui';
|
||||
import { CombinedRule, CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
|
||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { isAlertingRule, isRecordingRule, isRecordingRulerRule } from '../../utils/rules';
|
||||
import { StateColoredText } from '../StateColoredText';
|
||||
|
||||
interface Props {
|
||||
showInactive?: boolean;
|
||||
showRecording?: boolean;
|
||||
includeTotal?: boolean;
|
||||
group?: CombinedRuleGroup;
|
||||
namespaces?: CombinedRuleNamespace[];
|
||||
}
|
||||
@@ -23,14 +23,17 @@ const emptyStats = {
|
||||
error: 0,
|
||||
} as const;
|
||||
|
||||
export const RuleStats: FC<Props> = ({ showInactive, showRecording, group, namespaces }) => {
|
||||
export const RuleStats: FC<Props> = ({ group, namespaces, includeTotal }) => {
|
||||
const evaluationInterval = group?.interval;
|
||||
|
||||
const calculated = useMemo(() => {
|
||||
const stats = { ...emptyStats };
|
||||
|
||||
const calcRule = (rule: CombinedRule) => {
|
||||
if (rule.promRule && isAlertingRule(rule.promRule)) {
|
||||
stats[rule.promRule.state] += 1;
|
||||
}
|
||||
if (rule.promRule?.health === 'err' || rule.promRule?.health === 'error') {
|
||||
if (ruleHasError(rule)) {
|
||||
stats.error += 1;
|
||||
}
|
||||
if (
|
||||
@@ -41,75 +44,73 @@ export const RuleStats: FC<Props> = ({ showInactive, showRecording, group, names
|
||||
}
|
||||
stats.total += 1;
|
||||
};
|
||||
|
||||
if (group) {
|
||||
group.rules.forEach(calcRule);
|
||||
}
|
||||
|
||||
if (namespaces) {
|
||||
namespaces.forEach((namespace) => namespace.groups.forEach((group) => group.rules.forEach(calcRule)));
|
||||
}
|
||||
|
||||
return stats;
|
||||
}, [group, namespaces]);
|
||||
|
||||
const statsComponents: React.ReactNode[] = [];
|
||||
if (calculated[PromAlertingRuleState.Firing]) {
|
||||
|
||||
if (includeTotal) {
|
||||
statsComponents.push(
|
||||
<StateColoredText key="firing" status={PromAlertingRuleState.Firing}>
|
||||
{calculated[PromAlertingRuleState.Firing]} firing
|
||||
</StateColoredText>
|
||||
);
|
||||
}
|
||||
if (calculated.error) {
|
||||
statsComponents.push(
|
||||
<StateColoredText key="errors" status={PromAlertingRuleState.Firing}>
|
||||
{calculated.error} errors
|
||||
</StateColoredText>
|
||||
);
|
||||
}
|
||||
if (calculated[PromAlertingRuleState.Pending]) {
|
||||
statsComponents.push(
|
||||
<StateColoredText key="pending" status={PromAlertingRuleState.Pending}>
|
||||
{calculated[PromAlertingRuleState.Pending]} pending
|
||||
</StateColoredText>
|
||||
);
|
||||
}
|
||||
if (showInactive && calculated[PromAlertingRuleState.Inactive]) {
|
||||
statsComponents.push(
|
||||
<StateColoredText key="inactive" status="neutral">
|
||||
{calculated[PromAlertingRuleState.Inactive]} normal
|
||||
</StateColoredText>
|
||||
);
|
||||
}
|
||||
if (showRecording && calculated.recording) {
|
||||
statsComponents.push(
|
||||
<StateColoredText key="recording" status="neutral">
|
||||
{calculated.recording} recording
|
||||
</StateColoredText>
|
||||
<Fragment key="total">
|
||||
{calculated.total} {pluralize('rule', calculated.total)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
if (calculated[PromAlertingRuleState.Firing]) {
|
||||
statsComponents.push(
|
||||
<Badge color="red" key="firing" text={`${calculated[PromAlertingRuleState.Firing]} firing`} />
|
||||
);
|
||||
}
|
||||
|
||||
if (calculated.error) {
|
||||
statsComponents.push(<Badge color="red" key="errors" text={`${calculated.error} errors`} />);
|
||||
}
|
||||
|
||||
if (calculated[PromAlertingRuleState.Pending]) {
|
||||
statsComponents.push(
|
||||
<Badge color={'orange'} key="pending" text={`${calculated[PromAlertingRuleState.Pending]} pending`} />
|
||||
);
|
||||
}
|
||||
|
||||
if (calculated[PromAlertingRuleState.Inactive]) {
|
||||
statsComponents.push(
|
||||
<Badge color="green" key="inactive" text={`${calculated[PromAlertingRuleState.Inactive]} normal`} />
|
||||
);
|
||||
}
|
||||
|
||||
if (calculated.recording) {
|
||||
statsComponents.push(<Badge color="purple" key="recording" text={`${calculated.recording} recording`} />);
|
||||
}
|
||||
|
||||
const hasStats = Boolean(statsComponents.length);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
{calculated.total} {pluralize('rule', calculated.total)}
|
||||
</span>
|
||||
{!!statsComponents.length && (
|
||||
<Stack direction="row">
|
||||
{hasStats && (
|
||||
<div>
|
||||
<Stack gap={0.5}>{statsComponents}</Stack>
|
||||
</div>
|
||||
)}
|
||||
{evaluationInterval && (
|
||||
<>
|
||||
<span>: </span>
|
||||
{statsComponents.reduce<React.ReactNode[]>(
|
||||
(prev, curr, idx) =>
|
||||
prev.length
|
||||
? [
|
||||
prev,
|
||||
<Fragment key={idx}>
|
||||
<span>, </span>
|
||||
</Fragment>,
|
||||
curr,
|
||||
]
|
||||
: [curr],
|
||||
[]
|
||||
)}
|
||||
<div>|</div>
|
||||
<Badge text={evaluationInterval} icon="clock-nine" color={'blue'} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
function ruleHasError(rule: CombinedRule) {
|
||||
return rule.promRule?.health === 'err' || rule.promRule?.health === 'error';
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import pluralize from 'pluralize';
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Stack } from '@grafana/experimental';
|
||||
import { logInfo } from '@grafana/runtime';
|
||||
import { Badge, ConfirmModal, HorizontalGroup, Icon, Spinner, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import { useDispatch } from 'app/types';
|
||||
@@ -213,12 +214,22 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll,
|
||||
</h6>
|
||||
<div className={styles.spacer} />
|
||||
<div className={styles.headerStats}>
|
||||
<RuleStats showInactive={false} group={group} />
|
||||
<RuleStats group={group} />
|
||||
</div>
|
||||
{isProvisioned && (
|
||||
<>
|
||||
<div className={styles.actionsSeparator}>|</div>
|
||||
<div className={styles.actionIcons}>
|
||||
<Badge color="purple" text="Provisioned" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!!actionIcons.length && (
|
||||
<>
|
||||
<div className={styles.actionsSeparator}>|</div>
|
||||
<div className={styles.actionIcons}>{actionIcons}</div>
|
||||
<div className={styles.actionIcons}>
|
||||
<Stack gap={0.5}>{actionIcons}</Stack>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -313,9 +324,8 @@ export const getStyles = (theme: GrafanaTheme2) => ({
|
||||
margin: 0 ${theme.spacing(2)};
|
||||
`,
|
||||
actionIcons: css`
|
||||
& > * + * {
|
||||
margin-left: ${theme.spacing(0.5)};
|
||||
}
|
||||
width: 80px;
|
||||
align-items: center;
|
||||
`,
|
||||
rulesTable: css`
|
||||
margin-top: ${theme.spacing(3)};
|
||||
|
||||
Reference in New Issue
Block a user