From 13ea3d85aefc9d6826072c14f7bd2b0d0ff36edb Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Tue, 3 Sep 2024 09:14:11 +0100 Subject: [PATCH] Alerting: Fix preview of silences when label name contains spaces (#92802) --- .../alerting/unified/Silences.test.tsx | 8 +++++ .../alerting/unified/api/alertmanagerApi.ts | 4 ++- .../silences/SilencedInstancesPreview.tsx | 2 +- .../mocks/server/handlers/alertmanagers.ts | 31 ++++++++++++++++++- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/public/app/features/alerting/unified/Silences.test.tsx b/public/app/features/alerting/unified/Silences.test.tsx index 204ed9f9bbd5..a0e6303c1e33 100644 --- a/public/app/features/alerting/unified/Silences.test.tsx +++ b/public/app/features/alerting/unified/Silences.test.tsx @@ -304,6 +304,14 @@ describe('Silence create/edit', () => { TEST_TIMEOUT ); + it('works when previewing alerts with spaces in label name', async () => { + renderSilences(`${baseUrlPath}?alertmanager=${GRAFANA_RULES_SOURCE_NAME}`); + + await enterSilenceLabel(0, 'label with spaces', MatcherOperator.equal, 'value with spaces'); + + expect((await screen.findAllByTestId('row'))[0]).toBeInTheDocument(); + }); + it('shows an error when existing silence cannot be found', async () => { renderSilences('/alerting/silence/foo-bar/edit'); diff --git a/public/app/features/alerting/unified/api/alertmanagerApi.ts b/public/app/features/alerting/unified/api/alertmanagerApi.ts index fa93bfd0e2d1..c7ede90eea59 100644 --- a/public/app/features/alerting/unified/api/alertmanagerApi.ts +++ b/public/app/features/alerting/unified/api/alertmanagerApi.ts @@ -72,7 +72,9 @@ export const alertmanagerApi = alertingApi.injectEndpoints({ // TODO Add support for active, silenced, inhibited, unprocessed filters const filterMatchers = filter?.matchers ?.filter((matcher) => matcher.name && matcher.value) - .map((matcher) => `${matcher.name}${matcherToOperator(matcher)}${wrapWithQuotes(matcher.value)}`); + .map( + (matcher) => `${wrapWithQuotes(matcher.name)}${matcherToOperator(matcher)}${wrapWithQuotes(matcher.value)}` + ); const { silenced, inhibited, unprocessed, active } = filter || {}; diff --git a/public/app/features/alerting/unified/components/silences/SilencedInstancesPreview.tsx b/public/app/features/alerting/unified/components/silences/SilencedInstancesPreview.tsx index 49235dd22e44..1e1b85da218f 100644 --- a/public/app/features/alerting/unified/components/silences/SilencedInstancesPreview.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencedInstancesPreview.tsx @@ -66,7 +66,7 @@ export const SilencedInstancesPreview = ({ amSourceName, matchers: inputMatchers if (isError) { return ( - Error occured when generating preview of affected alerts. Are your matchers valid? + Error occurred when generating preview of affected alerts. Are your matchers valid? ); } diff --git a/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts b/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts index 37f7f8e1c467..6677e2cace2e 100644 --- a/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts +++ b/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts @@ -12,8 +12,37 @@ export const grafanaAlertingConfigurationStatusHandler = ( response = defaultGrafanaAlertingConfigurationStatusResponse ) => http.get('/api/v1/ngalert', () => HttpResponse.json(response)); +const getInvalidMatcher = (matchers: string[]) => { + return matchers.find((matcher) => { + const split = matcher.split('='); + try { + // Try and parse as JSON, as this will fail if + // we've failed to wrap the label value in quotes + // (e.g. `foo space` can't be parsed, but `"foo space"` can) + JSON.parse(split[0]); + return false; + } catch (e) { + return true; + } + }); +}; + export const alertmanagerAlertsListHandler = () => - http.get<{ datasourceUid: string }>('/api/alertmanager/:datasourceUid/api/v2/alerts', ({ params }) => { + http.get<{ datasourceUid: string }>('/api/alertmanager/:datasourceUid/api/v2/alerts', ({ params, request }) => { + const matchers = new URL(request.url).searchParams.getAll('filter'); + + const invalidMatcher = getInvalidMatcher(matchers); + + if (invalidMatcher) { + return HttpResponse.json( + { + message: `bad matcher format: ${invalidMatcher}: unable to retrieve alerts`, + traceID: '', + }, + { status: 400 } + ); + } + if (params.datasourceUid === MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER) { return HttpResponse.json({ traceId: '' }, { status: 502 }); }