diff --git a/public/app/features/alerting/unified/RuleViewer.tsx b/public/app/features/alerting/unified/RuleViewer.tsx index 7ae268b8225..1737b144d92 100644 --- a/public/app/features/alerting/unified/RuleViewer.tsx +++ b/public/app/features/alerting/unified/RuleViewer.tsx @@ -37,6 +37,7 @@ import { RuleState } from './components/rules/RuleState'; import { useAlertQueriesStatus } from './hooks/useAlertQueriesStatus'; import { useCombinedRule } from './hooks/useCombinedRule'; import { AlertingQueryRunner } from './state/AlertingQueryRunner'; +import { useCleanAnnotations } from './utils/annotations'; import { getRulesSourceByName } from './utils/datasource'; import { alertRuleToQueries } from './utils/query'; import * as ruleId from './utils/rule-id'; @@ -59,6 +60,7 @@ export function RuleViewer({ match }: RuleViewerProps) { const runner = useMemo(() => new AlertingQueryRunner(), []); const data = useObservable(runner.get()); const queries = useMemo(() => alertRuleToQueries(rule), [rule]); + const annotations = useCleanAnnotations(rule?.annotations || {}); const [evaluationTimeRanges, setEvaluationTimeRanges] = useState>({}); @@ -146,7 +148,6 @@ export function RuleViewer({ match }: RuleViewerProps) { ); } - const annotations = Object.entries(rule.annotations).filter(([_, value]) => !!value.trim()); const isFederatedRule = isFederatedRuleGroup(rule.group); const isProvisioned = isGrafanaRulerRule(rule.rulerRule) && Boolean(rule.rulerRule.grafana_alert.provenance); diff --git a/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx b/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx index 615b2523148..15f65d379b3 100644 --- a/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx +++ b/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/css'; import React, { FC } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, textUtil } from '@grafana/data'; import { Tooltip, useStyles2 } from '@grafana/ui'; import { Annotation, annotationLabels } from '../utils/constants'; @@ -15,9 +15,10 @@ const wellableAnnotationKeys = ['message', 'description']; interface Props { annotationKey: string; value: string; + valueLink?: string; } -export const AnnotationDetailsField: FC = ({ annotationKey, value }) => { +export const AnnotationDetailsField: FC = ({ annotationKey, value, valueLink }) => { const label = annotationLabels[annotationKey as Annotation] ? ( {annotationLabels[annotationKey as Annotation]} @@ -28,26 +29,34 @@ export const AnnotationDetailsField: FC = ({ annotationKey, value }) => { return ( - + ); }; -const AnnotationValue: FC = ({ annotationKey, value }) => { +const AnnotationValue: FC = ({ annotationKey, value, valueLink }) => { const styles = useStyles2(getStyles); const needsWell = wellableAnnotationKeys.includes(annotationKey); - const needsLink = value && value.startsWith('http'); + const needsExternalLink = value && value.startsWith('http'); const tokenizeValue = ; + if (valueLink) { + return ( + + {value} + + ); + } + if (needsWell) { return {tokenizeValue}; } - if (needsLink) { + if (needsExternalLink) { return ( - + {value} ); diff --git a/public/app/features/alerting/unified/components/rules/AlertInstanceDetails.tsx b/public/app/features/alerting/unified/components/rules/AlertInstanceDetails.tsx index fafd89a7466..bdd04cc65f8 100644 --- a/public/app/features/alerting/unified/components/rules/AlertInstanceDetails.tsx +++ b/public/app/features/alerting/unified/components/rules/AlertInstanceDetails.tsx @@ -2,6 +2,7 @@ import React, { FC } from 'react'; import { Alert } from 'app/types/unified-alerting'; +import { useAnnotationLinks, useCleanAnnotations } from '../../utils/annotations'; import { AnnotationDetailsField } from '../AnnotationDetailsField'; import { DetailsField } from '../DetailsField'; @@ -10,7 +11,8 @@ interface Props { } export const AlertInstanceDetails: FC = ({ instance }) => { - const annotations = (Object.entries(instance.annotations || {}) || []).filter(([_, value]) => !!value.trim()); + const annotations = useCleanAnnotations(instance.annotations); + const annotationLinks = useAnnotationLinks(annotations); return (
@@ -19,9 +21,11 @@ export const AlertInstanceDetails: FC = ({ instance }) => { {instance.value} )} - {annotations.map(([key, value]) => ( - - ))} + {annotations.map(([key, value]) => { + return ( + + ); + })}
); }; diff --git a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx index 5d078a0f6a6..12be8188293 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx @@ -5,6 +5,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; import { CombinedRule } from 'app/types/unified-alerting'; +import { useCleanAnnotations } from '../../utils/annotations'; import { isRecordingRulerRule } from '../../utils/rules'; import { AlertLabels } from '../AlertLabels'; import { DetailsField } from '../DetailsField'; @@ -30,7 +31,7 @@ export const RuleDetails: FC = ({ rule }) => { namespace: { rulesSource }, } = rule; - const annotations = Object.entries(rule.annotations).filter(([_, value]) => !!value.trim()); + const annotations = useCleanAnnotations(rule.annotations); return (
diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsAnnotations.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsAnnotations.tsx index 7c9691eeac3..a8ffd526876 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsAnnotations.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsAnnotations.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { useStyles2 } from '@grafana/ui'; +import { useAnnotationLinks } from '../../utils/annotations'; import { AnnotationDetailsField } from '../AnnotationDetailsField'; type Props = { @@ -10,9 +11,11 @@ type Props = { }; export function RuleDetailsAnnotations(props: Props): JSX.Element | null { - const { annotations } = props; const styles = useStyles2(getStyles); + const { annotations } = props; + const annotationLinks = useAnnotationLinks(annotations); + if (annotations.length === 0) { return null; } @@ -20,7 +23,7 @@ export function RuleDetailsAnnotations(props: Props): JSX.Element | null { return (
{annotations.map(([key, value]) => ( - + ))}
); diff --git a/public/app/features/alerting/unified/utils/annotations.tsx b/public/app/features/alerting/unified/utils/annotations.tsx new file mode 100644 index 00000000000..ca011221428 --- /dev/null +++ b/public/app/features/alerting/unified/utils/annotations.tsx @@ -0,0 +1,40 @@ +import { useMemo } from 'react'; + +import { Annotations } from 'app/types/unified-alerting-dto'; + +import { Annotation } from './constants'; +import { makeDashboardLink, makePanelLink } from './misc'; + +export function usePanelAndDashboardIds(annotations: Array<[string, string]>): { + dashboardUID?: string; + panelId?: string; +} { + return { + dashboardUID: annotations.find(([key]) => key === Annotation.dashboardUID)?.[1], + panelId: annotations.find(([key]) => key === Annotation.panelID)?.[1], + }; +} + +/** + * Removes annotations with empty or whitespace values + */ +export function useCleanAnnotations(annotations: Annotations): Array<[string, string]> { + return useMemo(() => { + return Object.entries(annotations || {}).filter(([_, value]) => !!value.trim()); + }, [annotations]); +} + +export function useAnnotationLinks(annotations: Array<[string, string]>): Map { + const links = new Map(); + + const { panelId, dashboardUID } = usePanelAndDashboardIds(annotations); + + if (dashboardUID) { + links.set(Annotation.dashboardUID, makeDashboardLink(dashboardUID)); + } + if (dashboardUID && panelId) { + links.set(Annotation.panelID, makePanelLink(dashboardUID, panelId)); + } + + return links; +} diff --git a/public/app/features/alerting/unified/utils/misc.ts b/public/app/features/alerting/unified/utils/misc.ts index 966b6cde76a..2a49f3876f3 100644 --- a/public/app/features/alerting/unified/utils/misc.ts +++ b/public/app/features/alerting/unified/utils/misc.ts @@ -110,6 +110,14 @@ export function makeFolderSettingsLink(folder: FolderDTO): string { return createUrl(`/dashboards/f/${folder.uid}/${folder.title}/settings`); } +export function makeDashboardLink(dashboardUID: string): string { + return createUrl(`/d/${encodeURIComponent(dashboardUID)}`); +} + +export function makePanelLink(dashboardUID: string, panelId: string): string { + return createUrl(`/d/${encodeURIComponent(dashboardUID)}`, { viewPanel: panelId }); +} + // keep retrying fn if it's error passes shouldRetry(error) and timeout has not elapsed yet export function retryWhile( fn: () => Promise,