From 12013e9aa4ae8e1d33a09441c2f4390f2591c895 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Tue, 14 Mar 2023 18:49:25 -0300 Subject: [PATCH] Alerting: Display last & next rule eval date plus eval duration (#64767) (#64772) * 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 (cherry picked from commit 6b95b3f8aa44665ae7c119bf38a791bfdc7d7729) --- .../unified/components/rules/RuleDetails.tsx | 28 ++++++- .../unified/components/rules/RulesGroup.tsx | 8 +- .../unified/components/rules/RulesTable.tsx | 73 +++++++++++++++++-- .../features/alerting/unified/utils/time.ts | 4 + 4 files changed, 102 insertions(+), 11 deletions(-) diff --git a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx index 5d078a0f6a6..e13a118f42f 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx @@ -1,11 +1,13 @@ import { css } from '@emotion/css'; import React, { FC } 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 { isRecordingRulerRule } from '../../utils/rules'; +import { isNullDate } from '../../utils/time'; import { AlertLabels } from '../AlertLabels'; import { DetailsField } from '../DetailsField'; @@ -62,6 +64,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)) { @@ -80,6 +84,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 9924f6fbe73..1c833d2d8bf 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: FC = React.memo(({ group, namespace, expandAll, )} {!isCollapsed && ( - + )} {isEditingGroup && ( = ({ emptyMessage = 'No rules found.', showGroupColumn = false, showSummaryColumn = false, + showNextEvaluationColumn = false, }) => { const styles = useStyles2(getStyles); @@ -54,7 +65,7 @@ export const RulesTable: FC = ({ }); }, [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[] = [ { @@ -129,7 +160,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', @@ -170,9 +201,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', @@ -204,5 +254,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'); +};