diff --git a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx index 7d02c0b036d..7899ac4ac30 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx @@ -1,12 +1,14 @@ import { css } from '@emotion/css'; import React from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; -import { useStyles2 } from '@grafana/ui'; +import { GrafanaTheme2, dateTime, dateTimeFormat } from '@grafana/data'; +import { useStyles2, Tooltip } from '@grafana/ui'; +import { Time } from 'app/features/explore/Time'; import { CombinedRule } from 'app/types/unified-alerting'; import { useCleanAnnotations } from '../../utils/annotations'; import { isRecordingRulerRule } from '../../utils/rules'; +import { isNullDate } from '../../utils/time'; import { AlertLabels } from '../AlertLabels'; import { DetailsField } from '../DetailsField'; @@ -63,6 +65,8 @@ interface EvaluationBehaviorSummaryProps { const EvaluationBehaviorSummary = ({ rule }: EvaluationBehaviorSummaryProps) => { let forDuration: string | undefined; let every = rule.group.interval; + let lastEvaluation = rule.promRule?.lastEvaluation; + let lastEvaluationDuration = rule.promRule?.evaluationTime; // recording rules don't have a for duration if (!isRecordingRulerRule(rule.rulerRule)) { @@ -81,6 +85,26 @@ const EvaluationBehaviorSummary = ({ rule }: EvaluationBehaviorSummaryProps) => {forDuration} )} + + {lastEvaluation && !isNullDate(lastEvaluation) && ( + + + {`${dateTime(lastEvaluation).locale('en').fromNow(true)} ago`} + + + )} + + {lastEvaluation && !isNullDate(lastEvaluation) && lastEvaluationDuration !== undefined && ( + + + {Time({ timeInMs: lastEvaluationDuration * 1000, humanize: true })} + + + )} ); }; diff --git a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx index 5daa1edcfe2..007e6032d1b 100644 --- a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx @@ -234,7 +234,13 @@ export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }: )} {!isCollapsed && ( - + )} {isEditingGroup && ( { const styles = useStyles2(getStyles); @@ -54,7 +65,7 @@ export const RulesTable = ({ }); }, [rules]); - const columns = useColumns(showSummaryColumn, showGroupColumn); + const columns = useColumns(showSummaryColumn, showGroupColumn, showNextEvaluationColumn); if (!rules.length) { return
{emptyMessage}
; @@ -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 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[] => { const columns: RuleTableColumnProps[] = [ { @@ -128,7 +159,7 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean) { label: 'Name', // eslint-disable-next-line react/display-name renderCell: ({ data: rule }) => rule.name, - size: 5, + size: showNextEvaluationColumn ? 4 : 5, }, { id: 'provisioned', @@ -169,9 +200,28 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean) { renderCell: ({ data: rule }) => { return ; }, - 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 && ( + + in {nextEvalInfo?.humanized} + + ) + ); + }, + size: 2, + }); + } + if (showGroupColumn) { columns.push({ id: 'group', @@ -203,5 +253,12 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean) { }); return columns; - }, [showSummaryColumn, showGroupColumn, hasRuler, rulerRulesLoaded]); + }, [ + showSummaryColumn, + showGroupColumn, + showNextEvaluationColumn, + hasRuler, + rulerRulesLoaded, + calculateNextEvaluationDate, + ]); } diff --git a/public/app/features/alerting/unified/utils/time.ts b/public/app/features/alerting/unified/utils/time.ts index a6958b9d6ab..491c5b790ee 100644 --- a/public/app/features/alerting/unified/utils/time.ts +++ b/public/app/features/alerting/unified/utils/time.ts @@ -99,3 +99,7 @@ export function parsePrometheusDuration(duration: string): number { return totalDuration; } + +export const isNullDate = (date: string) => { + return date.includes('0001-01-01T00'); +};