diff --git a/public/app/features/alerting/unified/Silences.test.tsx b/public/app/features/alerting/unified/Silences.test.tsx index 204ed9f9bbd..a0e6303c1e3 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 fa93bfd0e2d..c7ede90eea5 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 49235dd22e4..1e1b85da218 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 37f7f8e1c46..6677e2cace2 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 }); }