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
pull/64779/head
Virginia Cepeda 2 years ago committed by GitHub
parent 95aa9b374a
commit 6b95b3f8aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 28
      public/app/features/alerting/unified/components/rules/RuleDetails.tsx
  2. 8
      public/app/features/alerting/unified/components/rules/RulesGroup.tsx
  3. 73
      public/app/features/alerting/unified/components/rules/RulesTable.tsx
  4. 4
      public/app/features/alerting/unified/utils/time.ts

@ -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}
</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>
{!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 && (
<EditCloudGroupModal

@ -1,14 +1,23 @@
import { css, cx } from '@emotion/css';
import React, { useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import {
GrafanaTheme2,
addDurationToDate,
isValidDate,
isValidDuration,
parseDuration,
dateTimeFormat,
dateTime,
} from '@grafana/data';
import { useStyles2, Tooltip } from '@grafana/ui';
import { CombinedRule } from 'app/types/unified-alerting';
import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants';
import { useHasRuler } from '../../hooks/useHasRuler';
import { Annotation } from '../../utils/constants';
import { isGrafanaRulerRule, isGrafanaRulerRulePaused } from '../../utils/rules';
import { isNullDate } from '../../utils/time';
import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
import { DynamicTableWithGuidelines } from '../DynamicTableWithGuidelines';
import { ProvisioningBadge } from '../Provisioning';
@ -29,6 +38,7 @@ interface Props {
showGuidelines?: boolean;
showGroupColumn?: boolean;
showSummaryColumn?: boolean;
showNextEvaluationColumn?: boolean;
emptyMessage?: string;
className?: string;
}
@ -40,6 +50,7 @@ export const RulesTable = ({
emptyMessage = 'No rules found.',
showGroupColumn = false,
showSummaryColumn = false,
showNextEvaluationColumn = false,
}: Props) => {
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 <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 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 <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) {
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,
]);
}

@ -99,3 +99,7 @@ export function parsePrometheusDuration(duration: string): number {
return totalDuration;
}
export const isNullDate = (date: string) => {
return date.includes('0001-01-01T00');
};

Loading…
Cancel
Save