Alerting: Fix alertmanager query param when returning to silences list (#80021)

* Add alertmanager query param when returning to silences list. Sync query and storage alertmanager param

* Remove unused imports
This commit is contained in:
Konrad Lalik 2024-01-08 10:16:37 +01:00 committed by GitHub
parent 9cdd519e53
commit 6fd0ae0474
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 49 additions and 8 deletions

View File

@ -15,6 +15,8 @@ import { SilenceState } from '../../../plugins/datasource/alertmanager/types';
import Silences from './Silences'; import Silences from './Silences';
import { createOrUpdateSilence, fetchAlerts, fetchSilences } from './api/alertmanager'; import { createOrUpdateSilence, fetchAlerts, fetchSilences } from './api/alertmanager';
import { grantUserPermissions, mockAlertmanagerAlert, mockDataSource, MockDataSourceSrv, mockSilence } from './mocks'; import { grantUserPermissions, mockAlertmanagerAlert, mockDataSource, MockDataSourceSrv, mockSilence } from './mocks';
import { AlertmanagerProvider } from './state/AlertmanagerContext';
import { setupDataSources } from './testSetup/datasources';
import { parseMatchers } from './utils/alertmanager'; import { parseMatchers } from './utils/alertmanager';
import { DataSourceType } from './utils/datasource'; import { DataSourceType } from './utils/datasource';
@ -37,7 +39,9 @@ const renderSilences = (location = '/alerting/silences/') => {
return render( return render(
<TestProvider> <TestProvider>
<Silences /> <AlertmanagerProvider accessType="instance">
<Silences />
</AlertmanagerProvider>
</TestProvider> </TestProvider>
); );
}; };
@ -218,7 +222,7 @@ describe('Silence edit', () => {
beforeEach(() => { beforeEach(() => {
setUserLogged(true); setUserLogged(true);
setDataSourceSrv(new MockDataSourceSrv(dataSources)); setupDataSources(dataSources.am);
}); });
it('Should not render createdBy if user is logged in and has a name', async () => { it('Should not render createdBy if user is logged in and has a name', async () => {
@ -325,4 +329,32 @@ describe('Silence edit', () => {
}, },
TEST_TIMEOUT TEST_TIMEOUT
); );
it(
'silences page should contain alertmanager parameter after creating a silence',
async () => {
const user = userEvent.setup();
renderSilences(`${baseUrlPath}?alertmanager=Alertmanager`);
await waitFor(() => expect(ui.editor.durationField.query()).not.toBeNull());
await user.type(ui.editor.matcherName.getAll()[0], 'foo');
await user.type(ui.editor.matcherOperatorSelect.getAll()[0], '=');
await user.type(ui.editor.matcherValue.getAll()[0], 'bar');
await user.click(ui.editor.submit.get());
await waitFor(() =>
expect(mocks.api.createOrUpdateSilence).toHaveBeenCalledWith(
'Alertmanager',
expect.objectContaining({
matchers: [{ isEqual: true, isRegex: false, name: 'foo', value: 'bar' }],
})
)
);
expect(locationService.getSearch().get('alertmanager')).toBe('Alertmanager');
},
TEST_TIMEOUT
);
}); });

View File

@ -1,10 +1,10 @@
import * as React from 'react'; import * as React from 'react';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import store from 'app/core/store'; import store from 'app/core/store';
import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types'; import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types';
import { useAlertManagersByPermission } from '../hooks/useAlertManagerSources'; import { useAlertManagersByPermission } from '../hooks/useAlertManagerSources';
import { useURLSearchParams } from '../hooks/useURLSearchParams';
import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from '../utils/constants'; import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from '../utils/constants';
import { import {
AlertManagerDataSource, AlertManagerDataSource,
@ -30,7 +30,7 @@ interface Props extends React.PropsWithChildren {
} }
const AlertmanagerProvider = ({ children, accessType, alertmanagerSourceName }: Props) => { const AlertmanagerProvider = ({ children, accessType, alertmanagerSourceName }: Props) => {
const [queryParams, updateQueryParams] = useQueryParams(); const [queryParams, updateQueryParams] = useURLSearchParams();
const allAvailableAlertManagers = useAlertManagersByPermission(accessType); const allAvailableAlertManagers = useAlertManagersByPermission(accessType);
const availableAlertManagers = allAvailableAlertManagers.availableInternalDataSources.concat( const availableAlertManagers = allAvailableAlertManagers.availableInternalDataSources.concat(
allAvailableAlertManagers.availableExternalDataSources allAvailableAlertManagers.availableExternalDataSources
@ -44,7 +44,7 @@ const AlertmanagerProvider = ({ children, accessType, alertmanagerSourceName }:
if (selectedAlertManager === GRAFANA_RULES_SOURCE_NAME) { if (selectedAlertManager === GRAFANA_RULES_SOURCE_NAME) {
store.delete(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY); store.delete(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY);
updateQueryParams({ [ALERTMANAGER_NAME_QUERY_KEY]: null }); updateQueryParams({ [ALERTMANAGER_NAME_QUERY_KEY]: undefined });
} else { } else {
store.set(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, selectedAlertManager); store.set(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, selectedAlertManager);
updateQueryParams({ [ALERTMANAGER_NAME_QUERY_KEY]: selectedAlertManager }); updateQueryParams({ [ALERTMANAGER_NAME_QUERY_KEY]: selectedAlertManager });
@ -53,10 +53,19 @@ const AlertmanagerProvider = ({ children, accessType, alertmanagerSourceName }:
[availableAlertManagers, updateQueryParams] [availableAlertManagers, updateQueryParams]
); );
const sourceFromQuery = queryParams[ALERTMANAGER_NAME_QUERY_KEY]; const sourceFromQuery = queryParams.get(ALERTMANAGER_NAME_QUERY_KEY);
const sourceFromStore = store.get(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY); const sourceFromStore = store.get(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY);
const defaultSource = GRAFANA_RULES_SOURCE_NAME; const defaultSource = GRAFANA_RULES_SOURCE_NAME;
// This overrides AM in the store to be in sync with the one in the URL
// When the user uses multiple tabs with different AMs, the store will be changing all the time
// It's safest to always use URLs with alertmanager query param
React.useEffect(() => {
if (sourceFromQuery && sourceFromQuery !== sourceFromStore) {
store.set(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, sourceFromQuery);
}
}, [sourceFromQuery, sourceFromStore]);
// queryParam > localStorage > default // queryParam > localStorage > default
const desiredAlertmanager = alertmanagerSourceName ?? sourceFromQuery ?? sourceFromStore ?? defaultSource; const desiredAlertmanager = alertmanagerSourceName ?? sourceFromQuery ?? sourceFromStore ?? defaultSource;
const selectedAlertmanager = isAlertManagerAvailable(availableAlertManagers, desiredAlertmanager) const selectedAlertmanager = isAlertManagerAvailable(availableAlertManagers, desiredAlertmanager)

View File

@ -575,7 +575,7 @@ export const createOrUpdateSilenceAction = createAsyncThunk<void, UpdateSilenceA
(async () => { (async () => {
await createOrUpdateSilence(alertManagerSourceName, payload); await createOrUpdateSilence(alertManagerSourceName, payload);
if (exitOnSave) { if (exitOnSave) {
locationService.push('/alerting/silences'); locationService.push(makeAMLink('/alerting/silences', alertManagerSourceName));
} }
})() })()
), ),

View File

@ -105,7 +105,7 @@ export function makeAMLink(path: string, alertManagerName?: string, options?: UR
const search = new URLSearchParams(options); const search = new URLSearchParams(options);
if (alertManagerName) { if (alertManagerName) {
search.append(ALERTMANAGER_NAME_QUERY_KEY, alertManagerName); search.set(ALERTMANAGER_NAME_QUERY_KEY, alertManagerName);
} }
return `${path}?${search.toString()}`; return `${path}?${search.toString()}`;
} }