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');
+};