mirror of
https://github.com/grafana/grafana.git
synced 2025-09-20 20:42:27 +08:00

* WIP * Add instance totals to combined rule. Use totals to display instances stats in the UI * WIP * add global summaries, fix TS errors * fix useCombined test * fix test * use activeAt from rule when available * Fix NaN in global stats * Add no data total to global summary * Add totals recalculation for filtered rules * Fix instances totals, remove instances filtering from alert list view * Update tests * Fetch alerts considering filtering label matchers * WIP - Fetch alerts appending state filter to endpoint * Fix multiple values for state in request being applyied * fix test * Calculate hidden by for grafana managed alerts * Use INSTANCES_DISPLAY_LIMIT constant for limiting alert instances instead of 1 * Rename matchers parameter according to API changes * Fix calculating total number of grafana instances * Rename matcher prop after previous change * Display button to remove max instances limit * Change matcher query param to be an array of strings * Add test for paramsWithMatcherAndState method * Refactor matcher to be an string array to be consistent with state * Use matcher query string as matcher object type (encoded JSON) * Avoind encoding matcher parameters twice * fix tests * Enable toggle for the limit/show all button and restore limit and filters when we come back from custom view * Move getMatcherListFromString method to utils/alertmanager.ts * Fix limit toggle button being shown when it's not necessary * Use filteredTotals from be response to calculate hidden by count * Fix variables not being replaced correctly * Fix total shown to be all the instances filtered without limits * Adress some PR review comments * Move paramsWithMatcherAndState inside prometheusUrlBuilder method --------- Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com> Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com> Co-authored-by: Virginia Cepeda <virginia.cepeda@grafana.com>
139 lines
3.6 KiB
TypeScript
139 lines
3.6 KiB
TypeScript
import { isUndefined, omitBy, sum } from 'lodash';
|
|
import pluralize from 'pluralize';
|
|
import React, { Fragment } from 'react';
|
|
|
|
import { Stack } from '@grafana/experimental';
|
|
import { Badge } from '@grafana/ui';
|
|
import {
|
|
AlertGroupTotals,
|
|
AlertInstanceTotalState,
|
|
CombinedRuleGroup,
|
|
CombinedRuleNamespace,
|
|
} from 'app/types/unified-alerting';
|
|
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
|
|
|
interface Props {
|
|
namespaces: CombinedRuleNamespace[];
|
|
}
|
|
|
|
// All available states for a rule need to be initialized to prevent NaN values when adding a number and undefined
|
|
const emptyStats: Required<AlertGroupTotals> = {
|
|
recording: 0,
|
|
alerting: 0,
|
|
[PromAlertingRuleState.Pending]: 0,
|
|
[PromAlertingRuleState.Inactive]: 0,
|
|
paused: 0,
|
|
error: 0,
|
|
nodata: 0,
|
|
};
|
|
|
|
export const RuleStats = ({ namespaces }: Props) => {
|
|
const stats = { ...emptyStats };
|
|
|
|
// sum all totals for all namespaces
|
|
namespaces.forEach(({ groups }) => {
|
|
groups.forEach((group) => {
|
|
const groupTotals = omitBy(group.totals, isUndefined);
|
|
for (let key in groupTotals) {
|
|
// @ts-ignore
|
|
stats[key] += groupTotals[key];
|
|
}
|
|
});
|
|
});
|
|
|
|
const statsComponents = getComponentsFromStats(stats);
|
|
const hasStats = Boolean(statsComponents.length);
|
|
|
|
const total = sum(Object.values(stats));
|
|
|
|
statsComponents.unshift(
|
|
<Fragment key="total">
|
|
{total} {pluralize('rule', total)}
|
|
</Fragment>
|
|
);
|
|
|
|
return (
|
|
<Stack direction="row">
|
|
{hasStats && (
|
|
<div>
|
|
<Stack gap={0.5}>{statsComponents}</Stack>
|
|
</div>
|
|
)}
|
|
</Stack>
|
|
);
|
|
};
|
|
|
|
interface RuleGroupStatsProps {
|
|
group: CombinedRuleGroup;
|
|
}
|
|
|
|
export const RuleGroupStats = ({ group }: RuleGroupStatsProps) => {
|
|
const stats = group.totals;
|
|
const evaluationInterval = group?.interval;
|
|
|
|
const statsComponents = getComponentsFromStats(stats);
|
|
const hasStats = Boolean(statsComponents.length);
|
|
|
|
return (
|
|
<Stack direction="row">
|
|
{hasStats && (
|
|
<div>
|
|
<Stack gap={0.5}>{statsComponents}</Stack>
|
|
</div>
|
|
)}
|
|
{evaluationInterval && (
|
|
<>
|
|
<div>|</div>
|
|
<Badge text={evaluationInterval} icon="clock-nine" color={'blue'} />
|
|
</>
|
|
)}
|
|
</Stack>
|
|
);
|
|
};
|
|
|
|
export function getComponentsFromStats(
|
|
stats: Partial<Record<AlertInstanceTotalState | 'paused' | 'recording', number>>
|
|
) {
|
|
const statsComponents: React.ReactNode[] = [];
|
|
|
|
if (stats[AlertInstanceTotalState.Alerting]) {
|
|
statsComponents.push(<Badge color="red" key="firing" text={`${stats[AlertInstanceTotalState.Alerting]} firing`} />);
|
|
}
|
|
|
|
if (stats.error) {
|
|
statsComponents.push(<Badge color="red" key="errors" text={`${stats.error} errors`} />);
|
|
}
|
|
|
|
if (stats.nodata) {
|
|
statsComponents.push(<Badge color="blue" key="nodata" text={`${stats.nodata} no data`} />);
|
|
}
|
|
|
|
if (stats[AlertInstanceTotalState.Pending]) {
|
|
statsComponents.push(
|
|
<Badge color={'orange'} key="pending" text={`${stats[AlertInstanceTotalState.Pending]} pending`} />
|
|
);
|
|
}
|
|
|
|
if (stats[AlertInstanceTotalState.Normal] && stats.paused) {
|
|
statsComponents.push(
|
|
<Badge
|
|
color="green"
|
|
key="paused"
|
|
text={`${stats[AlertInstanceTotalState.Normal]} normal (${stats.paused} paused)`}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (stats[AlertInstanceTotalState.Normal] && !stats.paused) {
|
|
statsComponents.push(
|
|
<Badge color="green" key="inactive" text={`${stats[AlertInstanceTotalState.Normal]} normal`} />
|
|
);
|
|
}
|
|
|
|
if (stats.recording) {
|
|
statsComponents.push(<Badge color="purple" key="recording" text={`${stats.recording} recording`} />);
|
|
}
|
|
|
|
return statsComponents;
|
|
}
|