From 9907191a7b23cac57b340c6f7e4207fe5ae7ad89 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Thu, 31 Jul 2025 15:32:33 +0200 Subject: [PATCH] Alerting: List V2 - Add a group link to the rule list item (#108960) * Add group link to the aler rule breadcrumbs in the new list view * Remove redundant comments --- .../rule-list/DataSourceRuleListItem.tsx | 4 ++ .../unified/rule-list/FilterView.test.tsx | 24 +++++++ .../unified/rule-list/GrafanaRuleListItem.tsx | 10 ++- .../components/AlertRuleListItem.tsx | 65 ++++++++----------- .../rule-list/components/RuleLocation.tsx | 43 ++++++++++++ 5 files changed, 108 insertions(+), 38 deletions(-) create mode 100644 public/app/features/alerting/unified/rule-list/components/RuleLocation.tsx diff --git a/public/app/features/alerting/unified/rule-list/DataSourceRuleListItem.tsx b/public/app/features/alerting/unified/rule-list/DataSourceRuleListItem.tsx index 3fc7eb8a808..3850092b40d 100644 --- a/public/app/features/alerting/unified/rule-list/DataSourceRuleListItem.tsx +++ b/public/app/features/alerting/unified/rule-list/DataSourceRuleListItem.tsx @@ -5,6 +5,7 @@ import { PromRuleType, RulerRuleDTO, RulesSourceApplication } from 'app/types/un import { createReturnTo } from '../hooks/useReturnTo'; import { Annotation } from '../utils/constants'; +import { groups } from '../utils/navigation'; import { fromRule, fromRulerRule, stringifyIdentifier } from '../utils/rule-id'; import { getRuleName, getRulePluginOrigin, rulerRuleType } from '../utils/rules'; import { createRelativeUrl } from '../utils/url'; @@ -46,11 +47,14 @@ export function DataSourceRuleListItem({ const ruleName = rulerRule ? getRuleName(rulerRule) : rule.name; const labels = rulerRule ? rulerRule.labels : rule.labels; + const groupUrl = groups.detailsPageLink(rulesSource.uid, namespace.name, groupName); + const commonProps: RuleListItemCommonProps = { name: ruleName, rulesSource: rulesSource, application: application, group: groupName, + groupUrl, namespace: namespace.name, href, health: rule.health, diff --git a/public/app/features/alerting/unified/rule-list/FilterView.test.tsx b/public/app/features/alerting/unified/rule-list/FilterView.test.tsx index 908e5978d88..3a7348048da 100644 --- a/public/app/features/alerting/unified/rule-list/FilterView.test.tsx +++ b/public/app/features/alerting/unified/rule-list/FilterView.test.tsx @@ -107,6 +107,30 @@ describe('RuleList - FilterView', () => { expect(await screen.findByText(/No matching rules found/)).toBeInTheDocument(); }); + + it('should render group names as clickable links', async () => { + render( + + ); + + await loadMoreResults(); + + const groupLink = await screen.findByRole('link', { + name: 'test-group-4501', + }); + + expect(groupLink).toBeInTheDocument(); + expect(groupLink).toHaveAttribute( + 'href', + '/alerting/mimir/namespaces/test-mimir-namespace/groups/test-group-4501/view' + ); + }); }); async function loadMoreResults() { diff --git a/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx b/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx index 3113c991e09..5667069082b 100644 --- a/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx +++ b/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx @@ -1,7 +1,8 @@ import { GrafanaRuleGroupIdentifier } from 'app/types/unified-alerting'; import { GrafanaPromRuleDTO, PromRuleType } from 'app/types/unified-alerting-dto'; -import { GrafanaRulesSource } from '../utils/datasource'; +import { GRAFANA_RULES_SOURCE_NAME, GrafanaRulesSource } from '../utils/datasource'; +import { groups } from '../utils/navigation'; import { totalFromStats } from '../utils/ruleStats'; import { prometheusRuleType } from '../utils/rules'; import { createRelativeUrl } from '../utils/url'; @@ -32,10 +33,17 @@ export function GrafanaRuleListItem({ }: GrafanaRuleListItemProps) { const { name, uid, labels, provenance } = rule; + const groupUrl = groups.detailsPageLink( + GRAFANA_RULES_SOURCE_NAME, + groupIdentifier.namespace.uid, + groupIdentifier.groupName + ); + const commonProps: RuleListItemCommonProps = { name, rulesSource: GrafanaRulesSource, group: groupIdentifier.groupName, + groupUrl, namespace: namespaceName, href: createRelativeUrl(`/alerting/grafana/${uid}/view`), health: rule?.health, diff --git a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx index 3f8c01831e6..5117a8516bd 100644 --- a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx +++ b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx @@ -4,7 +4,7 @@ import { ReactNode, useEffect, useId } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { Alert, Icon, Stack, Text, TextLink, Tooltip, useStyles2 } from '@grafana/ui'; +import { Alert, Stack, Text, TextLink, Tooltip, useStyles2 } from '@grafana/ui'; import { Rule, RuleGroupIdentifierV2, RuleHealth, RulesSourceIdentifier } from 'app/types/unified-alerting'; import { Labels, PromAlertingRuleState, RulerRuleDTO, RulesSourceApplication } from 'app/types/unified-alerting-dto'; @@ -20,8 +20,8 @@ import { createContactPointSearchLink } from '../../utils/misc'; import { RulePluginOrigin } from '../../utils/rules'; import { ListItem } from './ListItem'; -import { DataSourceIcon } from './Namespace'; import { RuleListIcon, RuleOperation } from './RuleListIcon'; +import { RuleLocation } from './RuleLocation'; import { calculateNextEvaluationEstimate } from './util'; export interface AlertRuleListItemProps { @@ -39,6 +39,7 @@ export interface AlertRuleListItemProps { instancesCount?: number; namespace?: string; group?: string; + groupUrl?: string; rulesSource?: RulesSourceIdentifier; application?: RulesSourceApplication; // used for alert rules that use simplified routing @@ -65,6 +66,7 @@ export const AlertRuleListItem = (props: AlertRuleListItemProps) => { instancesCount = 0, namespace, group, + groupUrl, rulesSource, application, contactPoint, @@ -81,7 +83,13 @@ export const AlertRuleListItem = (props: AlertRuleListItemProps) => { if (namespace && group && showLocation) { metadata.push( - + ); } @@ -160,6 +168,7 @@ export function RecordingRuleListItem({ name, namespace, group, + groupUrl, rulesSource, application, href, @@ -175,7 +184,13 @@ export function RecordingRuleListItem({ if (namespace && group && showLocation) { metadata.push( - + ); } @@ -206,6 +221,7 @@ interface RuleOperationListItemProps { name: string; namespace: string; group: string; + groupUrl?: string; rulesSource?: RulesSourceIdentifier; application?: RulesSourceApplication; operation: RuleOperation; @@ -216,6 +232,7 @@ export function RuleOperationListItem({ name, namespace, group, + groupUrl, rulesSource, application, operation, @@ -227,7 +244,13 @@ export function RuleOperationListItem({ if (namespace && group && showLocation) { metadata.push( - + ); } @@ -368,38 +391,6 @@ export const UnknownRuleListItem = ({ ruleName, groupIdentifier, ruleDefinition ); }; -interface RuleLocationProps { - namespace: string; - group: string; - rulesSource?: RulesSourceIdentifier; - application?: RulesSourceApplication; -} - -// @TODO make the datasource / namespace / group click-able to allow further filtering of the list -export const RuleLocation = ({ namespace, group, rulesSource, application }: RuleLocationProps) => { - const isGrafanaApp = application === 'grafana'; - const isDataSourceApp = !!rulesSource && !!application && !isGrafanaApp; - - return ( - - {isGrafanaApp && } - {isDataSourceApp && ( - - - - - - )} - - - {namespace} - - {group} - - - ); -}; - const getStyles = (theme: GrafanaTheme2) => ({ alertListItemContainer: css({ position: 'relative', diff --git a/public/app/features/alerting/unified/rule-list/components/RuleLocation.tsx b/public/app/features/alerting/unified/rule-list/components/RuleLocation.tsx new file mode 100644 index 00000000000..b0d4b31de5d --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/components/RuleLocation.tsx @@ -0,0 +1,43 @@ +import { Icon, Stack, TextLink, Tooltip } from '@grafana/ui'; +import { RulesSourceIdentifier } from 'app/types/unified-alerting'; +import { RulesSourceApplication } from 'app/types/unified-alerting-dto'; + +import { DataSourceIcon } from './Namespace'; + +interface RuleLocationProps { + namespace: string; + group: string; + groupUrl?: string; + rulesSource?: RulesSourceIdentifier; + application?: RulesSourceApplication; +} + +export function RuleLocation({ namespace, group, groupUrl, rulesSource, application }: RuleLocationProps) { + const isGrafanaApp = application === 'grafana'; + const isDataSourceApp = !!rulesSource && !!application && !isGrafanaApp; + + return ( + + {isGrafanaApp && } + {isDataSourceApp && ( + + + + + + )} + + + {namespace} + + {groupUrl ? ( + + {group} + + ) : ( + group + )} + + + ); +}