From 7d5a26a4e723954572e88c91d7539ab49d29a49c Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Wed, 4 Jun 2025 13:36:28 +0200 Subject: [PATCH] Alerting: List V2 - Improve error handling (#106282) * Add error badge to the DataSourceSection component * Fix lint * Update translations * Fix error propagation from featureDiscoveryApi * Pass errors to GMA loader --- .../unified/api/featureDiscoveryApi.ts | 36 ++++++++++--------- .../unified/rule-list/GroupedView.tsx | 6 +++- .../rule-list/PaginatedDataSourceLoader.tsx | 4 +-- .../rule-list/PaginatedGrafanaLoader.tsx | 10 ++++-- .../components/DataSourceSection.tsx | 19 ++++++++-- .../hooks/useLazyLoadPrometheusGroups.tsx | 1 + public/locales/en-US/grafana.json | 4 +++ 7 files changed, 57 insertions(+), 23 deletions(-) diff --git a/public/app/features/alerting/unified/api/featureDiscoveryApi.ts b/public/app/features/alerting/unified/api/featureDiscoveryApi.ts index e5f90c2baf6..b60290ad481 100644 --- a/public/app/features/alerting/unified/api/featureDiscoveryApi.ts +++ b/public/app/features/alerting/unified/api/featureDiscoveryApi.ts @@ -59,24 +59,28 @@ export const featureDiscoveryApi = alertingApi.injectEndpoints({ return { error: new Error(`Missing data source configuration for ${rulesSourceIdentifier}`) }; } - const features = await discoverFeaturesByUid(dataSourceSettings.uid); + try { + const features = await discoverFeaturesByUid(dataSourceSettings.uid); - const rulerConfig = features.features.rulerApiEnabled - ? ({ - dataSourceName: dataSourceSettings.name, - dataSourceUid: dataSourceSettings.uid, - apiVersion: features.application === PromApplication.Cortex ? 'legacy' : 'config', - } satisfies RulerDataSourceConfig) - : undefined; + const rulerConfig = features.features.rulerApiEnabled + ? ({ + dataSourceName: dataSourceSettings.name, + dataSourceUid: dataSourceSettings.uid, + apiVersion: features.application === PromApplication.Cortex ? 'legacy' : 'config', + } satisfies RulerDataSourceConfig) + : undefined; - return { - data: { - name: dataSourceSettings.name, - uid: dataSourceSettings.uid, - application: features.application, - rulerConfig, - } satisfies RulesSourceFeatures, - }; + return { + data: { + name: dataSourceSettings.name, + uid: dataSourceSettings.uid, + application: features.application, + rulerConfig, + } satisfies RulesSourceFeatures, + }; + } catch (error) { + return { error: error }; + } }, }), }), diff --git a/public/app/features/alerting/unified/rule-list/GroupedView.tsx b/public/app/features/alerting/unified/rule-list/GroupedView.tsx index da79a9e6d5a..3458ad6822c 100644 --- a/public/app/features/alerting/unified/rule-list/GroupedView.tsx +++ b/public/app/features/alerting/unified/rule-list/GroupedView.tsx @@ -38,7 +38,7 @@ export function GrafanaDataSourceLoader() { } function DataSourceLoader({ rulesSourceIdentifier }: DataSourceLoaderProps) { - const { data: dataSourceInfo, isLoading } = useDiscoverDsFeaturesQuery({ uid: rulesSourceIdentifier.uid }); + const { data: dataSourceInfo, isLoading, error } = useDiscoverDsFeaturesQuery({ uid: rulesSourceIdentifier.uid }); const { uid, name } = rulesSourceIdentifier; @@ -46,6 +46,10 @@ function DataSourceLoader({ rulesSourceIdentifier }: DataSourceLoaderProps) { return } uid={uid} name={name} />; } + if (error) { + return ; + } + // 2. grab prometheus rule groups with max_groups if supported if (dataSourceInfo) { return ( diff --git a/public/app/features/alerting/unified/rule-list/PaginatedDataSourceLoader.tsx b/public/app/features/alerting/unified/rule-list/PaginatedDataSourceLoader.tsx index 950c460d1e2..161437c7528 100644 --- a/public/app/features/alerting/unified/rule-list/PaginatedDataSourceLoader.tsx +++ b/public/app/features/alerting/unified/rule-list/PaginatedDataSourceLoader.tsx @@ -41,7 +41,7 @@ export function PaginatedDataSourceLoader({ rulesSourceIdentifier, application } }; }, [groupsGenerator]); - const { isLoading, groups, hasMoreGroups, fetchMoreGroups } = useLazyLoadPrometheusGroups( + const { isLoading, groups, hasMoreGroups, fetchMoreGroups, error } = useLazyLoadPrometheusGroups( groupsGenerator.current, DATA_SOURCE_GROUP_PAGE_SIZE ); @@ -50,7 +50,7 @@ export function PaginatedDataSourceLoader({ rulesSourceIdentifier, application } const groupsByNamespace = useMemo(() => groupBy(groups, 'file'), [groups]); return ( - + {Object.entries(groupsByNamespace).map(([namespace, groups]) => ( + {Object.entries(groupsByFolder).map(([folderUid, groups]) => { // Groups are grouped by folder, so we can use the first group to get the folder name diff --git a/public/app/features/alerting/unified/rule-list/components/DataSourceSection.tsx b/public/app/features/alerting/unified/rule-list/components/DataSourceSection.tsx index c75a9ff9efb..424dfcc3c3f 100644 --- a/public/app/features/alerting/unified/rule-list/components/DataSourceSection.tsx +++ b/public/app/features/alerting/unified/rule-list/components/DataSourceSection.tsx @@ -4,13 +4,13 @@ import { useToggle } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; import { Trans, useTranslate } from '@grafana/i18n'; -import { IconButton, LinkButton, Stack, Text, useStyles2 } from '@grafana/ui'; +import { Button, IconButton, LinkButton, Stack, Text, Toggletip, useStyles2 } from '@grafana/ui'; import { GrafanaRulesSourceSymbol, RulesSourceIdentifier } from 'app/types/unified-alerting'; import { RulesSourceApplication } from 'app/types/unified-alerting-dto'; import { Spacer } from '../../components/Spacer'; import { WithReturnButton } from '../../components/WithReturnButton'; -import { isAdmin } from '../../utils/misc'; +import { isAdmin, stringifyErrorLike } from '../../utils/misc'; import { DataSourceIcon } from './Namespace'; import { LoadingIndicator } from './RuleGroup'; @@ -22,6 +22,7 @@ export interface DataSourceSectionProps extends PropsWithChildren { application?: RulesSourceApplication; isLoading?: boolean; description?: ReactNode; + error?: unknown; } export const DataSourceSection = ({ @@ -30,6 +31,7 @@ export const DataSourceSection = ({ application, children, loader, + error, isLoading = false, description = null, }: DataSourceSectionProps) => { @@ -60,6 +62,7 @@ export const DataSourceSection = ({ name={isCollapsed ? 'angle-right' : 'angle-down'} onClick={toggleCollapsed} aria-label={t('common.collapse', 'Collapse')} + disabled={Boolean(error)} /> {application && } @@ -71,6 +74,18 @@ export const DataSourceSection = ({ {'·'} {description} )} + + {Boolean(error) && ( + {stringifyErrorLike(error)}} + > + + + )} + {configureLink && ( ( return { isLoading, + error: groupsRequestState.error, groups, hasMoreGroups: !isLoading && hasMoreGroups, fetchMoreGroups, diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 38eff47641d..cddd86ac0d9 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -2364,11 +2364,15 @@ "cannot-load-rule-details-for": "Cannot load rule details for UID {{uid}}", "configure-datasource": "Configure", "draft-new-rule": "Draft a new rule", + "ds-error": { + "title": "Cannot load rules for this datasource" + }, "ds-error-boundary": { "description": "Check the data source configuration. Does the data source support Prometheus API?", "title": "Unable to load rules from this data source" }, "empty-data-source": "No rules found", + "error-button": "Error", "filter-view": { "cancel-search": "Cancel search", "no-more-results": "No more results – found {{numberOfRules}} rules",