mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Display last & next rule eval date plus eval duration (#64767)
* Display last & next rule eval date plus eval duration * Show next evaluation date in a humanized format Full date still visible on hover * Only show next evaluation column is group has an interval
This commit is contained in:
parent
95aa9b374a
commit
6b95b3f8aa
@ -1,12 +1,14 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2, dateTime, dateTimeFormat } from '@grafana/data';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2, Tooltip } from '@grafana/ui';
|
||||||
|
import { Time } from 'app/features/explore/Time';
|
||||||
import { CombinedRule } from 'app/types/unified-alerting';
|
import { CombinedRule } from 'app/types/unified-alerting';
|
||||||
|
|
||||||
import { useCleanAnnotations } from '../../utils/annotations';
|
import { useCleanAnnotations } from '../../utils/annotations';
|
||||||
import { isRecordingRulerRule } from '../../utils/rules';
|
import { isRecordingRulerRule } from '../../utils/rules';
|
||||||
|
import { isNullDate } from '../../utils/time';
|
||||||
import { AlertLabels } from '../AlertLabels';
|
import { AlertLabels } from '../AlertLabels';
|
||||||
import { DetailsField } from '../DetailsField';
|
import { DetailsField } from '../DetailsField';
|
||||||
|
|
||||||
@ -63,6 +65,8 @@ interface EvaluationBehaviorSummaryProps {
|
|||||||
const EvaluationBehaviorSummary = ({ rule }: EvaluationBehaviorSummaryProps) => {
|
const EvaluationBehaviorSummary = ({ rule }: EvaluationBehaviorSummaryProps) => {
|
||||||
let forDuration: string | undefined;
|
let forDuration: string | undefined;
|
||||||
let every = rule.group.interval;
|
let every = rule.group.interval;
|
||||||
|
let lastEvaluation = rule.promRule?.lastEvaluation;
|
||||||
|
let lastEvaluationDuration = rule.promRule?.evaluationTime;
|
||||||
|
|
||||||
// recording rules don't have a for duration
|
// recording rules don't have a for duration
|
||||||
if (!isRecordingRulerRule(rule.rulerRule)) {
|
if (!isRecordingRulerRule(rule.rulerRule)) {
|
||||||
@ -81,6 +85,26 @@ const EvaluationBehaviorSummary = ({ rule }: EvaluationBehaviorSummaryProps) =>
|
|||||||
{forDuration}
|
{forDuration}
|
||||||
</DetailsField>
|
</DetailsField>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{lastEvaluation && !isNullDate(lastEvaluation) && (
|
||||||
|
<DetailsField label="Last evaluation" horizontal={true}>
|
||||||
|
<Tooltip
|
||||||
|
placement="top"
|
||||||
|
content={`${dateTimeFormat(lastEvaluation, { format: 'YYYY-MM-DD HH:mm:ss' })}`}
|
||||||
|
theme="info"
|
||||||
|
>
|
||||||
|
<span>{`${dateTime(lastEvaluation).locale('en').fromNow(true)} ago`}</span>
|
||||||
|
</Tooltip>
|
||||||
|
</DetailsField>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{lastEvaluation && !isNullDate(lastEvaluation) && lastEvaluationDuration !== undefined && (
|
||||||
|
<DetailsField label="Evaluation time" horizontal={true}>
|
||||||
|
<Tooltip placement="top" content={`${lastEvaluationDuration}s`} theme="info">
|
||||||
|
<span>{Time({ timeInMs: lastEvaluationDuration * 1000, humanize: true })}</span>
|
||||||
|
</Tooltip>
|
||||||
|
</DetailsField>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -234,7 +234,13 @@ export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }:
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<RulesTable showSummaryColumn={true} className={styles.rulesTable} showGuidelines={true} rules={group.rules} />
|
<RulesTable
|
||||||
|
showSummaryColumn={true}
|
||||||
|
className={styles.rulesTable}
|
||||||
|
showGuidelines={true}
|
||||||
|
showNextEvaluationColumn={Boolean(group.interval)}
|
||||||
|
rules={group.rules}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{isEditingGroup && (
|
{isEditingGroup && (
|
||||||
<EditCloudGroupModal
|
<EditCloudGroupModal
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import {
|
||||||
import { useStyles2 } from '@grafana/ui';
|
GrafanaTheme2,
|
||||||
|
addDurationToDate,
|
||||||
|
isValidDate,
|
||||||
|
isValidDuration,
|
||||||
|
parseDuration,
|
||||||
|
dateTimeFormat,
|
||||||
|
dateTime,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import { useStyles2, Tooltip } from '@grafana/ui';
|
||||||
import { CombinedRule } from 'app/types/unified-alerting';
|
import { CombinedRule } from 'app/types/unified-alerting';
|
||||||
|
|
||||||
import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants';
|
import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants';
|
||||||
import { useHasRuler } from '../../hooks/useHasRuler';
|
import { useHasRuler } from '../../hooks/useHasRuler';
|
||||||
import { Annotation } from '../../utils/constants';
|
import { Annotation } from '../../utils/constants';
|
||||||
import { isGrafanaRulerRule, isGrafanaRulerRulePaused } from '../../utils/rules';
|
import { isGrafanaRulerRule, isGrafanaRulerRulePaused } from '../../utils/rules';
|
||||||
|
import { isNullDate } from '../../utils/time';
|
||||||
import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
|
import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
|
||||||
import { DynamicTableWithGuidelines } from '../DynamicTableWithGuidelines';
|
import { DynamicTableWithGuidelines } from '../DynamicTableWithGuidelines';
|
||||||
import { ProvisioningBadge } from '../Provisioning';
|
import { ProvisioningBadge } from '../Provisioning';
|
||||||
@ -29,6 +38,7 @@ interface Props {
|
|||||||
showGuidelines?: boolean;
|
showGuidelines?: boolean;
|
||||||
showGroupColumn?: boolean;
|
showGroupColumn?: boolean;
|
||||||
showSummaryColumn?: boolean;
|
showSummaryColumn?: boolean;
|
||||||
|
showNextEvaluationColumn?: boolean;
|
||||||
emptyMessage?: string;
|
emptyMessage?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
@ -40,6 +50,7 @@ export const RulesTable = ({
|
|||||||
emptyMessage = 'No rules found.',
|
emptyMessage = 'No rules found.',
|
||||||
showGroupColumn = false,
|
showGroupColumn = false,
|
||||||
showSummaryColumn = false,
|
showSummaryColumn = false,
|
||||||
|
showNextEvaluationColumn = false,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
@ -54,7 +65,7 @@ export const RulesTable = ({
|
|||||||
});
|
});
|
||||||
}, [rules]);
|
}, [rules]);
|
||||||
|
|
||||||
const columns = useColumns(showSummaryColumn, showGroupColumn);
|
const columns = useColumns(showSummaryColumn, showGroupColumn, showNextEvaluationColumn);
|
||||||
|
|
||||||
if (!rules.length) {
|
if (!rules.length) {
|
||||||
return <div className={cx(wrapperClass, styles.emptyMessage)}>{emptyMessage}</div>;
|
return <div className={cx(wrapperClass, styles.emptyMessage)}>{emptyMessage}</div>;
|
||||||
@ -101,9 +112,29 @@ export const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean) {
|
function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean, showNextEvaluationColumn: boolean) {
|
||||||
const { hasRuler, rulerRulesLoaded } = useHasRuler();
|
const { hasRuler, rulerRulesLoaded } = useHasRuler();
|
||||||
|
|
||||||
|
const calculateNextEvaluationDate = useCallback((rule: CombinedRule) => {
|
||||||
|
const isValidLastEvaluation =
|
||||||
|
rule.promRule?.lastEvaluation &&
|
||||||
|
!isNullDate(rule.promRule.lastEvaluation) &&
|
||||||
|
isValidDate(rule.promRule.lastEvaluation);
|
||||||
|
const isValidIntervalDuration = rule.group.interval && isValidDuration(rule.group.interval);
|
||||||
|
|
||||||
|
if (!isValidLastEvaluation || !isValidIntervalDuration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastEvaluationDate = Date.parse(rule.promRule?.lastEvaluation || '');
|
||||||
|
const intervalDuration = parseDuration(rule.group.interval!);
|
||||||
|
const nextEvaluationDate = addDurationToDate(lastEvaluationDate, intervalDuration);
|
||||||
|
return {
|
||||||
|
humanized: dateTime(nextEvaluationDate).locale('en').fromNow(true),
|
||||||
|
fullDate: dateTimeFormat(nextEvaluationDate, { format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return useMemo((): RuleTableColumnProps[] => {
|
return useMemo((): RuleTableColumnProps[] => {
|
||||||
const columns: RuleTableColumnProps[] = [
|
const columns: RuleTableColumnProps[] = [
|
||||||
{
|
{
|
||||||
@ -128,7 +159,7 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean) {
|
|||||||
label: 'Name',
|
label: 'Name',
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
renderCell: ({ data: rule }) => rule.name,
|
renderCell: ({ data: rule }) => rule.name,
|
||||||
size: 5,
|
size: showNextEvaluationColumn ? 4 : 5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'provisioned',
|
id: 'provisioned',
|
||||||
@ -169,9 +200,28 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean) {
|
|||||||
renderCell: ({ data: rule }) => {
|
renderCell: ({ data: rule }) => {
|
||||||
return <Tokenize input={rule.annotations[Annotation.summary] ?? ''} />;
|
return <Tokenize input={rule.annotations[Annotation.summary] ?? ''} />;
|
||||||
},
|
},
|
||||||
size: 5,
|
size: showNextEvaluationColumn ? 4 : 5,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showNextEvaluationColumn) {
|
||||||
|
columns.push({
|
||||||
|
id: 'nextEvaluation',
|
||||||
|
label: 'Next evaluation',
|
||||||
|
renderCell: ({ data: rule }) => {
|
||||||
|
const nextEvalInfo = calculateNextEvaluationDate(rule);
|
||||||
|
return (
|
||||||
|
nextEvalInfo?.fullDate && (
|
||||||
|
<Tooltip placement="top" content={`${nextEvalInfo?.fullDate}`} theme="info">
|
||||||
|
<span>in {nextEvalInfo?.humanized}</span>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
size: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (showGroupColumn) {
|
if (showGroupColumn) {
|
||||||
columns.push({
|
columns.push({
|
||||||
id: 'group',
|
id: 'group',
|
||||||
@ -203,5 +253,12 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return columns;
|
return columns;
|
||||||
}, [showSummaryColumn, showGroupColumn, hasRuler, rulerRulesLoaded]);
|
}, [
|
||||||
|
showSummaryColumn,
|
||||||
|
showGroupColumn,
|
||||||
|
showNextEvaluationColumn,
|
||||||
|
hasRuler,
|
||||||
|
rulerRulesLoaded,
|
||||||
|
calculateNextEvaluationDate,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
@ -99,3 +99,7 @@ export function parsePrometheusDuration(duration: string): number {
|
|||||||
|
|
||||||
return totalDuration;
|
return totalDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isNullDate = (date: string) => {
|
||||||
|
return date.includes('0001-01-01T00');
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user