diff --git a/public/app/core/utils/errors.ts b/public/app/core/utils/errors.ts index 5446e7d475f..ba27911a82c 100644 --- a/public/app/core/utils/errors.ts +++ b/public/app/core/utils/errors.ts @@ -1,4 +1,4 @@ -import { isFetchError } from '@grafana/runtime'; +import { FetchError, isFetchError } from '@grafana/runtime'; export function getMessageFromError(err: unknown): string { if (typeof err === 'string') { @@ -60,3 +60,10 @@ export function getMessageIdFromError(err: unknown): string | undefined { return undefined; } + +export function getRequestConfigFromError(err: FetchError): string { + const method = err.config?.method ?? 'GET'; + const url = err.config?.url; + + return method && url ? `${method} ${url}` : 'request'; +} diff --git a/public/app/features/alerting/unified/NotificationPoliciesPage.test.tsx b/public/app/features/alerting/unified/NotificationPoliciesPage.test.tsx index c70d50a114e..00be5c8167b 100644 --- a/public/app/features/alerting/unified/NotificationPoliciesPage.test.tsx +++ b/public/app/features/alerting/unified/NotificationPoliciesPage.test.tsx @@ -284,7 +284,7 @@ describe('NotificationPolicies', () => { renderNotificationPolicies(); const alert = await screen.findByRole('alert', { name: /error loading alertmanager config/i }); - expect(await within(alert).findByText(errMessage)).toBeInTheDocument(); + expect(await within(alert).findByText(new RegExp(errMessage))).toBeInTheDocument(); expect(ui.rootRouteContainer.query()).not.toBeInTheDocument(); }); diff --git a/public/app/features/alerting/unified/group-details/GroupDetailsPage.test.tsx b/public/app/features/alerting/unified/group-details/GroupDetailsPage.test.tsx index 93a9e332588..a1606965fd2 100644 --- a/public/app/features/alerting/unified/group-details/GroupDetailsPage.test.tsx +++ b/public/app/features/alerting/unified/group-details/GroupDetailsPage.test.tsx @@ -135,7 +135,7 @@ describe('GroupDetailsPage', () => { // Assert expect(await screen.findByText('Error loading the group')).toBeInTheDocument(); - expect(await screen.findByText('Failed to fetch rule group')).toBeInTheDocument(); + expect(await screen.findByText(/Failed to fetch rule group/)).toBeInTheDocument(); }); it('should render "not found" when group does not exist', async () => { diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/usePauseAlertRule.test.tsx b/public/app/features/alerting/unified/hooks/ruleGroup/usePauseAlertRule.test.tsx index 71e8df7a04d..1b7f1f89db3 100644 --- a/public/app/features/alerting/unified/hooks/ruleGroup/usePauseAlertRule.test.tsx +++ b/public/app/features/alerting/unified/hooks/ruleGroup/usePauseAlertRule.test.tsx @@ -70,7 +70,7 @@ describe('pause rule', () => { await userEvent.click(byRole('button').get()); expect(await byText(/loading/i).find()).toBeInTheDocument(); expect(byText(/success/i).query()).not.toBeInTheDocument(); - expect(await byText(/error: oops/i).find()).toBeInTheDocument(); + expect(await byText(/error:(.+)oops/i).find()).toBeInTheDocument(); }); }); 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 22576d70b9f..1750b3c68dc 100644 --- a/public/app/features/alerting/unified/rule-list/components/DataSourceSection.tsx +++ b/public/app/features/alerting/unified/rule-list/components/DataSourceSection.tsx @@ -77,9 +77,9 @@ export const DataSourceSection = ({ {Boolean(error) && ( {stringifyErrorLike(error)}} + content={{stringifyErrorLike(error)}} > - diff --git a/public/app/features/alerting/unified/utils/misc.test.ts b/public/app/features/alerting/unified/utils/misc.test.ts index 8f1fb19e7e5..8dae9443827 100644 --- a/public/app/features/alerting/unified/utils/misc.test.ts +++ b/public/app/features/alerting/unified/utils/misc.test.ts @@ -1,3 +1,4 @@ +import { FetchError } from '@grafana/runtime'; import { createExploreLink, makeDashboardLink, @@ -140,7 +141,7 @@ describe('stringifyErrorLike', () => { it('should stringify Fetch error with message embedded in HTTP response', () => { const error = { status: 404, data: { message: 'message from the API' } }; - expect(stringifyErrorLike(error)).toBe('message from the API'); + expect(stringifyErrorLike(error)).toBe('request failed with 404: message from the API'); }); it('should stringify Fetch error with status text as fallback', () => { @@ -164,7 +165,7 @@ describe('stringifyErrorLike', () => { reason: 'Conflict', }; - expect(stringifyErrorLike({ status: 409, data: error })).toBe('some message'); + expect(stringifyErrorLike({ status: 409, data: error })).toBe('request failed with 409: some message'); }); it('should stringify ApiMachineryError with known code', () => { @@ -180,4 +181,19 @@ describe('stringifyErrorLike', () => { expect(stringifyErrorLike({ status: 409, data: error })).toBe(getErrorMessageFromCode(ERROR_NEWER_CONFIGURATION)); }); + + it('should stringify fetchh error with status code and URL', () => { + const error = { + status: 404, + data: { + message: 'not found', + }, + config: { + url: '/my/url', + method: 'POST', + }, + } satisfies FetchError; + + expect(stringifyErrorLike(error)).toBe('POST /my/url failed with 404: not found'); + }); }); diff --git a/public/app/features/alerting/unified/utils/misc.ts b/public/app/features/alerting/unified/utils/misc.ts index 7b4d5229ba0..561e5be930d 100644 --- a/public/app/features/alerting/unified/utils/misc.ts +++ b/public/app/features/alerting/unified/utils/misc.ts @@ -2,9 +2,11 @@ import { sortBy } from 'lodash'; import { Labels, UrlQueryMap } from '@grafana/data'; import { GrafanaEdition } from '@grafana/data/internal'; +import { t } from '@grafana/i18n'; import { config, isFetchError } from '@grafana/runtime'; import { DataSourceRef } from '@grafana/schema'; import { contextSrv } from 'app/core/services/context_srv'; +import { getMessageFromError, getRequestConfigFromError, getStatusFromError } from 'app/core/utils/errors'; import { escapePathSeparators } from 'app/features/alerting/unified/utils/rule-id'; import { alertInstanceKey, @@ -309,9 +311,20 @@ export function stringifyErrorLike(error: unknown): string { if (error.message) { return error.message; } + if ('message' in error.data && typeof error.data.message === 'string') { - return error.data.message; + const status = getStatusFromError(error); + const message = getMessageFromError(error); + + const config = getRequestConfigFromError(error); + + return t('alerting.errors.failedWith', '{{-config}} failed with {{status}}: {{-message}}', { + config, + status, + message, + }); } + if (error.statusText) { return error.statusText; } diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index d6f400baac2..4d6566e16e6 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1065,6 +1065,9 @@ "error-summary-button": { "content-show-all-errors": "Show all errors" }, + "errors": { + "failedWith": "{{-config}} failed with {{status}}: {{-message}}" + }, "evaluate-every-validation-options": { "message": { "required": "Required."