mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* Implement UI for grafana-managed recording rules * use undefined for the duration instead of null , for recording rules * Fix tests * add tests * Add pause functionality for grafana recording rules * update translations * remove obsolete snapshot * use createUrl instead of renderUrl * refactor * Add validation for grafana recording rule name * create util functions for rule types and add record field in mock function * add util isDatatSourceManagedRuleByType * refactor * Add metric field in alert rule form * fix alert name component * update width for alert name and metric * fix test * add validation back to cloud recording rules name * Alerting: Recording rules PR review (#90654) Update type helper methods * add slash in createUrl * fix baseurl in the returnTo * nits * Add metric on expanded row in the alert list view * nits Co-authored-by: Tom Ratcliffe <tom.ratcliffe@grafana.com> * update snapshot --------- Co-authored-by: Tom Ratcliffe <tom.ratcliffe@grafana.com>
171 lines
5.3 KiB
TypeScript
171 lines
5.3 KiB
TypeScript
import { css } from '@emotion/css';
|
|
import { formatDistanceToNowStrict } from 'date-fns';
|
|
import { useCallback } from 'react';
|
|
|
|
import { GrafanaTheme2 } from '@grafana/data';
|
|
import { ClipboardButton, Stack, Text, TextLink, useStyles2 } from '@grafana/ui';
|
|
import { CombinedRule } from 'app/types/unified-alerting';
|
|
import { Annotations } from 'app/types/unified-alerting-dto';
|
|
|
|
import { isGrafanaRulerRule, isRecordingRulerRule } from '../../../utils/rules';
|
|
import { MetaText } from '../../MetaText';
|
|
import { Tokenize } from '../../Tokenize';
|
|
|
|
interface DetailsProps {
|
|
rule: CombinedRule;
|
|
}
|
|
|
|
enum RuleType {
|
|
GrafanaManagedAlertRule = 'Grafana-managed alert rule',
|
|
CloudAlertRule = 'Cloud alert rule',
|
|
CloudRecordingRule = 'Cloud recording rule',
|
|
}
|
|
|
|
const Details = ({ rule }: DetailsProps) => {
|
|
const styles = useStyles2(getStyles);
|
|
|
|
let ruleType: RuleType;
|
|
|
|
if (isGrafanaRulerRule(rule.rulerRule)) {
|
|
ruleType = RuleType.GrafanaManagedAlertRule;
|
|
} else if (isRecordingRulerRule(rule.rulerRule)) {
|
|
ruleType = RuleType.CloudRecordingRule;
|
|
} else {
|
|
// probably not the greatest assumption
|
|
ruleType = RuleType.CloudAlertRule;
|
|
}
|
|
|
|
const evaluationDuration = rule.promRule?.evaluationTime;
|
|
const evaluationTimestamp = rule.promRule?.lastEvaluation;
|
|
|
|
const copyRuleUID = useCallback(() => {
|
|
if (isGrafanaRulerRule(rule.rulerRule)) {
|
|
return rule.rulerRule.grafana_alert.uid;
|
|
} else {
|
|
return '';
|
|
}
|
|
}, [rule.rulerRule]);
|
|
|
|
const annotations: Annotations | undefined = !isRecordingRulerRule(rule.rulerRule)
|
|
? (rule.annotations ?? [])
|
|
: undefined;
|
|
|
|
const hasEvaluationDuration = Number.isFinite(evaluationDuration);
|
|
|
|
return (
|
|
<Stack direction="column" gap={3}>
|
|
<div className={styles.metadataWrapper}>
|
|
{/* type and identifier (optional) */}
|
|
<MetaText direction="column">
|
|
Rule type
|
|
<Text color="primary">{ruleType}</Text>
|
|
</MetaText>
|
|
<MetaText direction="column">
|
|
{isGrafanaRulerRule(rule.rulerRule) && (
|
|
<>
|
|
Rule Identifier
|
|
<Stack direction="row" alignItems="center" gap={0.5}>
|
|
<Text color="primary">
|
|
{rule.rulerRule.grafana_alert.uid}
|
|
<ClipboardButton fill="text" variant="secondary" icon="copy" size="sm" getText={copyRuleUID} />
|
|
</Text>
|
|
</Stack>
|
|
</>
|
|
)}
|
|
</MetaText>
|
|
|
|
{/* evaluation duration and pending period */}
|
|
<MetaText direction="column">
|
|
{hasEvaluationDuration && (
|
|
<>
|
|
Last evaluation
|
|
{evaluationTimestamp && evaluationDuration ? (
|
|
<span>
|
|
<Text color="primary">{formatDistanceToNowStrict(new Date(evaluationTimestamp))} ago</Text>, took{' '}
|
|
<Text color="primary">{evaluationDuration}ms</Text>
|
|
</span>
|
|
) : null}
|
|
</>
|
|
)}
|
|
</MetaText>
|
|
<MetaText direction="column">
|
|
{!isRecordingRulerRule(rule.rulerRule) && (
|
|
<>
|
|
Pending period
|
|
<Text color="primary">{rule.rulerRule?.for ?? '0s'}</Text>
|
|
</>
|
|
)}
|
|
</MetaText>
|
|
|
|
{/* nodata and execution error state mapping */}
|
|
{isGrafanaRulerRule(rule.rulerRule) &&
|
|
// grafana recording rules don't have these fields
|
|
rule.rulerRule.grafana_alert.no_data_state &&
|
|
rule.rulerRule.grafana_alert.exec_err_state && (
|
|
<>
|
|
<MetaText direction="column">
|
|
Alert state if no data or all values are null
|
|
<Text color="primary">{rule.rulerRule.grafana_alert.no_data_state}</Text>
|
|
</MetaText>
|
|
<MetaText direction="column">
|
|
Alert state if execution error or timeout
|
|
<Text color="primary">{rule.rulerRule.grafana_alert.exec_err_state}</Text>
|
|
</MetaText>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{/* annotations go here */}
|
|
{annotations && (
|
|
<>
|
|
<Text variant="h4">Annotations</Text>
|
|
{Object.keys(annotations).length === 0 ? (
|
|
<Text variant="bodySmall" color="secondary" italic>
|
|
No annotations
|
|
</Text>
|
|
) : (
|
|
<div className={styles.metadataWrapper}>
|
|
{Object.entries(annotations).map(([name, value]) => (
|
|
<MetaText direction="column" key={name}>
|
|
{name}
|
|
<AnnotationValue value={value} />
|
|
</MetaText>
|
|
))}
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</Stack>
|
|
);
|
|
};
|
|
|
|
interface AnnotationValueProps {
|
|
value: string;
|
|
}
|
|
|
|
export function AnnotationValue({ value }: AnnotationValueProps) {
|
|
const needsExternalLink = value && value.startsWith('http');
|
|
const tokenizeValue = <Tokenize input={value} delimiter={['{{', '}}']} />;
|
|
|
|
if (needsExternalLink) {
|
|
return (
|
|
<TextLink variant="bodySmall" href={value} external>
|
|
{value}
|
|
</TextLink>
|
|
);
|
|
}
|
|
|
|
return <Text color="primary">{tokenizeValue}</Text>;
|
|
}
|
|
|
|
const getStyles = (theme: GrafanaTheme2) => ({
|
|
metadataWrapper: css({
|
|
display: 'grid',
|
|
gridTemplateColumns: 'auto auto',
|
|
rowGap: theme.spacing(3),
|
|
columnGap: theme.spacing(12),
|
|
}),
|
|
});
|
|
|
|
export { Details };
|