mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Refactor fetch for receivers for using RTKQuery (#56624)
* Refactor fetch for reveivers for using RTKQuery * Simplify receiversApi contactPointsState endpoint * Fix tests and create useGetContactPointsState hook for using receiversApi.useContactPointsStateQuery
This commit is contained in:
parent
5af36e48ba
commit
277803a894
@ -17,7 +17,8 @@ import { AccessControlAction, ContactPointsState } from 'app/types';
|
||||
import Receivers from './Receivers';
|
||||
import { updateAlertManagerConfig, fetchAlertManagerConfig, fetchStatus, testReceivers } from './api/alertmanager';
|
||||
import { discoverAlertmanagerFeatures } from './api/buildInfo';
|
||||
import { fetchNotifiers, fetchContactPointsState } from './api/grafana';
|
||||
import { fetchNotifiers } from './api/grafana';
|
||||
import * as receiversApi from './api/receiversApi';
|
||||
import {
|
||||
mockDataSource,
|
||||
MockDataSourceSrv,
|
||||
@ -29,7 +30,6 @@ import { grafanaNotifiersMock } from './mocks/grafana-notifiers';
|
||||
import { getAllDataSources } from './utils/config';
|
||||
import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from './utils/constants';
|
||||
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
||||
|
||||
jest.mock('./api/alertmanager');
|
||||
jest.mock('./api/grafana');
|
||||
jest.mock('./utils/config');
|
||||
@ -46,7 +46,9 @@ const mocks = {
|
||||
fetchNotifiers: jest.mocked(fetchNotifiers),
|
||||
testReceivers: jest.mocked(testReceivers),
|
||||
discoverAlertmanagerFeatures: jest.mocked(discoverAlertmanagerFeatures),
|
||||
fetchReceivers: jest.mocked(fetchContactPointsState),
|
||||
},
|
||||
hooks: {
|
||||
useGetContactPointsState: jest.spyOn(receiversApi, 'useGetContactPointsState'),
|
||||
},
|
||||
contextSrv: jest.mocked(contextSrv),
|
||||
};
|
||||
@ -130,6 +132,7 @@ const clickSelectOption = async (selectElement: HTMLElement, optionText: string)
|
||||
};
|
||||
|
||||
document.addEventListener('click', interceptLinkClicks);
|
||||
const emptyContactPointsState: ContactPointsState = { receivers: {}, errorCount: 0 };
|
||||
|
||||
describe('Receivers', () => {
|
||||
beforeEach(() => {
|
||||
@ -137,8 +140,7 @@ describe('Receivers', () => {
|
||||
mocks.getAllDataSources.mockReturnValue(Object.values(dataSources));
|
||||
mocks.api.fetchNotifiers.mockResolvedValue(grafanaNotifiersMock);
|
||||
mocks.api.discoverAlertmanagerFeatures.mockResolvedValue({ lazyConfigInit: false });
|
||||
const emptyContactPointsState: ContactPointsState = { receivers: {}, errorCount: 0 };
|
||||
mocks.api.fetchReceivers.mockResolvedValue(emptyContactPointsState);
|
||||
mocks.hooks.useGetContactPointsState.mockReturnValue(emptyContactPointsState);
|
||||
setDataSourceSrv(new MockDataSourceSrv(dataSources));
|
||||
mocks.contextSrv.isEditor = true;
|
||||
store.delete(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY);
|
||||
@ -327,6 +329,7 @@ describe('Receivers', () => {
|
||||
(a) => a === action
|
||||
)
|
||||
);
|
||||
mocks.hooks.useGetContactPointsState.mockReturnValue(emptyContactPointsState);
|
||||
renderReceivers();
|
||||
|
||||
expect(ui.newContactPointButton.query()).not.toBeInTheDocument();
|
||||
@ -543,7 +546,7 @@ describe('Receivers', () => {
|
||||
errorCount: 1,
|
||||
};
|
||||
|
||||
mocks.api.fetchReceivers.mockResolvedValue(receiversMock);
|
||||
mocks.hooks.useGetContactPointsState.mockReturnValue(receiversMock);
|
||||
await renderReceivers();
|
||||
|
||||
//
|
||||
@ -614,7 +617,7 @@ describe('Receivers', () => {
|
||||
errorCount: 1,
|
||||
};
|
||||
|
||||
mocks.api.fetchReceivers.mockResolvedValue(receiversMock);
|
||||
mocks.hooks.useGetContactPointsState.mockReturnValue(receiversMock);
|
||||
await renderReceivers();
|
||||
|
||||
//
|
||||
@ -645,11 +648,11 @@ describe('Receivers', () => {
|
||||
expect(criticalDetailTableRows[1]).toHaveTextContent('117.2455ms');
|
||||
});
|
||||
|
||||
it('Should not render error notifications when fetchContactPointsState raises 404 error ', async () => {
|
||||
it('Should not render error notifications when fetching contact points state raises 404 error ', async () => {
|
||||
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
|
||||
mocks.api.updateConfig.mockResolvedValue();
|
||||
|
||||
mocks.api.fetchReceivers.mockRejectedValue({ status: 404 });
|
||||
mocks.hooks.useGetContactPointsState.mockReturnValue(emptyContactPointsState);
|
||||
await renderReceivers();
|
||||
|
||||
await ui.receiversTable.find();
|
||||
|
@ -7,6 +7,9 @@ import { NavModelItem, GrafanaTheme2 } from '@grafana/data';
|
||||
import { Alert, LoadingPlaceholder, withErrorBoundary, useStyles2, Icon, Stack } from '@grafana/ui';
|
||||
import { useDispatch } from 'app/types';
|
||||
|
||||
import { ContactPointsState } from '../../../types/alerting';
|
||||
|
||||
import { useGetContactPointsState } from './api/receiversApi';
|
||||
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||
import { NoAlertManagerWarning } from './components/NoAlertManagerWarning';
|
||||
@ -19,12 +22,7 @@ import { ReceiversAndTemplatesView } from './components/receivers/ReceiversAndTe
|
||||
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
||||
import { useAlertManagersByPermission } from './hooks/useAlertManagerSources';
|
||||
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
||||
import {
|
||||
fetchAlertManagerConfigAction,
|
||||
fetchContactPointsStateAction,
|
||||
fetchGrafanaNotifiersAction,
|
||||
} from './state/actions';
|
||||
import { CONTACT_POINTS_STATE_INTERVAL_MS } from './utils/constants';
|
||||
import { fetchAlertManagerConfigAction, fetchGrafanaNotifiersAction } from './state/actions';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
||||
import { initialAsyncRequestState } from './utils/redux';
|
||||
|
||||
@ -63,7 +61,6 @@ const Receivers: FC = () => {
|
||||
const isRoot = location.pathname.endsWith('/alerting/notifications');
|
||||
|
||||
const configRequests = useUnifiedAlertingSelector((state) => state.amConfigs);
|
||||
const contactPointsStateRequest = useUnifiedAlertingSelector((state) => state.contactPointsState);
|
||||
|
||||
const {
|
||||
result: config,
|
||||
@ -71,9 +68,6 @@ const Receivers: FC = () => {
|
||||
error,
|
||||
} = (alertManagerSourceName && configRequests[alertManagerSourceName]) || initialAsyncRequestState;
|
||||
|
||||
const { result: contactPointsState } =
|
||||
(alertManagerSourceName && contactPointsStateRequest) || initialAsyncRequestState;
|
||||
|
||||
const receiverTypes = useUnifiedAlertingSelector((state) => state.grafanaNotifiers);
|
||||
|
||||
const shouldLoadConfig = isRoot || !config;
|
||||
@ -93,20 +87,7 @@ const Receivers: FC = () => {
|
||||
dispatch(fetchGrafanaNotifiersAction());
|
||||
}
|
||||
}, [alertManagerSourceName, dispatch, receiverTypes]);
|
||||
|
||||
useEffect(() => {
|
||||
function fetchContactPointStates() {
|
||||
if (shouldRenderNotificationStatus && alertManagerSourceName) {
|
||||
dispatch(fetchContactPointsStateAction(alertManagerSourceName));
|
||||
}
|
||||
}
|
||||
fetchContactPointStates();
|
||||
const interval = setInterval(fetchContactPointStates, CONTACT_POINTS_STATE_INTERVAL_MS);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [shouldRenderNotificationStatus, alertManagerSourceName, dispatch]);
|
||||
|
||||
const contactPointsState: ContactPointsState = useGetContactPointsState(alertManagerSourceName ?? '');
|
||||
const integrationsErrorCount = contactPointsState?.errorCount ?? 0;
|
||||
|
||||
const disableAmSelect = !isRoot;
|
||||
|
29
public/app/features/alerting/unified/api/receiversApi.ts
Normal file
29
public/app/features/alerting/unified/api/receiversApi.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { ContactPointsState, ReceiversStateDTO } from 'app/types';
|
||||
|
||||
import { CONTACT_POINTS_STATE_INTERVAL_MS } from '../utils/constants';
|
||||
import { getDatasourceAPIUid } from '../utils/datasource';
|
||||
|
||||
import { alertingApi } from './alertingApi';
|
||||
import { contactPointsStateDtoToModel } from './grafana';
|
||||
|
||||
export const receiversApi = alertingApi.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
contactPointsState: build.query<ContactPointsState, string>({
|
||||
query: (amSourceName) => ({
|
||||
url: `/api/alertmanager/${getDatasourceAPIUid(amSourceName)}/config/api/v1/receivers`,
|
||||
}),
|
||||
transformResponse: (receivers: ReceiversStateDTO[]) => {
|
||||
return contactPointsStateDtoToModel(receivers);
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const useGetContactPointsState = (alertManagerSourceName: string) => {
|
||||
const contactPointsStateEmpty: ContactPointsState = { receivers: {}, errorCount: 0 };
|
||||
const { currentData: contactPointsState } = receiversApi.useContactPointsStateQuery(alertManagerSourceName ?? '', {
|
||||
skip: !alertManagerSourceName,
|
||||
pollingInterval: CONTACT_POINTS_STATE_INTERVAL_MS,
|
||||
});
|
||||
return contactPointsState ?? contactPointsStateEmpty;
|
||||
};
|
@ -10,8 +10,9 @@ import {
|
||||
Receiver,
|
||||
} from 'app/plugins/datasource/alertmanager/types';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { NotifierDTO, NotifierType } from 'app/types';
|
||||
import { ContactPointsState, NotifierDTO, NotifierType } from 'app/types';
|
||||
|
||||
import { useGetContactPointsState } from '../../api/receiversApi';
|
||||
import { fetchGrafanaNotifiersAction } from '../../state/actions';
|
||||
|
||||
import { ReceiversTable } from './ReceiversTable';
|
||||
@ -52,7 +53,20 @@ const mockNotifier = (type: NotifierType, name: string): NotifierDTO => ({
|
||||
options: [],
|
||||
});
|
||||
|
||||
jest.mock('../../api/receiversApi', () => ({
|
||||
...jest.requireActual('../../api/receiversApi'),
|
||||
useGetContactPointsState: jest.fn(),
|
||||
}));
|
||||
|
||||
const useGetContactPointsStateMock = jest.mocked(useGetContactPointsState);
|
||||
|
||||
describe('ReceiversTable', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
const emptyContactPointsState: ContactPointsState = { receivers: {}, errorCount: 0 };
|
||||
useGetContactPointsStateMock.mockReturnValue(emptyContactPointsState);
|
||||
});
|
||||
|
||||
it('render receivers with grafana notifiers', async () => {
|
||||
const receivers: Receiver[] = [
|
||||
{
|
||||
|
@ -8,6 +8,7 @@ import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { AlertManagerCortexConfig, Receiver } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { useDispatch, AccessControlAction, ContactPointsState, NotifiersState, ReceiversState } from 'app/types';
|
||||
|
||||
import { useGetContactPointsState } from '../../api/receiversApi';
|
||||
import { Authorize } from '../../components/Authorize';
|
||||
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
||||
import { deleteReceiverAction } from '../../state/actions';
|
||||
@ -17,7 +18,6 @@ import { isReceiverUsed } from '../../utils/alertmanager';
|
||||
import { isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource';
|
||||
import { makeAMLink } from '../../utils/misc';
|
||||
import { extractNotifierTypeCounts } from '../../utils/receivers';
|
||||
import { initialAsyncRequestState } from '../../utils/redux';
|
||||
import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
|
||||
import { ProvisioningBadge } from '../Provisioning';
|
||||
import { ActionIcon } from '../rules/ActionIcon';
|
||||
@ -122,8 +122,7 @@ function ReceiverHealth({ errorsByReceiver, someWithNoAttempt }: ReceiverHealthP
|
||||
);
|
||||
}
|
||||
const useContactPointsState = (alertManagerName: string) => {
|
||||
const contactPointsStateRequest = useUnifiedAlertingSelector((state) => state.contactPointsState);
|
||||
const { result: contactPointsState } = (alertManagerName && contactPointsStateRequest) || initialAsyncRequestState;
|
||||
const contactPointsState = useGetContactPointsState(alertManagerName ?? '');
|
||||
const receivers: ReceiversState = contactPointsState?.receivers ?? {};
|
||||
const errorStateAvailable = Object.keys(receivers).length > 0; // this logic can change depending on how we implement this in the BE
|
||||
return { contactPointsState, errorStateAvailable };
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
SilenceCreatePayload,
|
||||
TestReceiversAlert,
|
||||
} from 'app/plugins/datasource/alertmanager/types';
|
||||
import { ContactPointsState, FolderDTO, NotifierDTO, StoreState, ThunkResult } from 'app/types';
|
||||
import { FolderDTO, NotifierDTO, StoreState, ThunkResult } from 'app/types';
|
||||
import {
|
||||
CombinedRuleGroup,
|
||||
CombinedRuleNamespace,
|
||||
@ -51,7 +51,7 @@ import {
|
||||
import { fetchAnnotations } from '../api/annotations';
|
||||
import { discoverFeatures } from '../api/buildInfo';
|
||||
import { featureDiscoveryApi } from '../api/featureDiscoveryApi';
|
||||
import { fetchContactPointsState, fetchNotifiers } from '../api/grafana';
|
||||
import { fetchNotifiers } from '../api/grafana';
|
||||
import { FetchPromRulesFilter, fetchRules } from '../api/prometheus';
|
||||
import {
|
||||
deleteNamespace,
|
||||
@ -459,12 +459,6 @@ export const fetchGrafanaNotifiersAction = createAsyncThunk(
|
||||
(): Promise<NotifierDTO[]> => withSerializedError(fetchNotifiers())
|
||||
);
|
||||
|
||||
export const fetchContactPointsStateAction = createAsyncThunk(
|
||||
'unifiedalerting/fetchContactPointsState',
|
||||
(alertManagerSourceName: string): Promise<ContactPointsState> =>
|
||||
withSerializedError(fetchContactPointsState(alertManagerSourceName))
|
||||
);
|
||||
|
||||
export const fetchGrafanaAnnotationsAction = createAsyncThunk(
|
||||
'unifiedalerting/fetchGrafanaAnnotations',
|
||||
(alertId: string): Promise<StateHistoryItem[]> => withSerializedError(fetchAnnotations(alertId))
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
fetchAlertGroupsAction,
|
||||
fetchAlertManagerConfigAction,
|
||||
fetchAmAlertsAction,
|
||||
fetchContactPointsStateAction,
|
||||
fetchEditableRuleAction,
|
||||
fetchExternalAlertmanagersAction,
|
||||
fetchExternalAlertmanagersConfigAction,
|
||||
@ -46,7 +45,6 @@ export const reducer = combineReducers({
|
||||
existingRule: createAsyncSlice('existingRule', fetchEditableRuleAction).reducer,
|
||||
}),
|
||||
grafanaNotifiers: createAsyncSlice('grafanaNotifiers', fetchGrafanaNotifiersAction).reducer,
|
||||
contactPointsState: createAsyncSlice('contactPointsState', fetchContactPointsStateAction).reducer,
|
||||
saveAMConfig: createAsyncSlice('saveAMConfig', updateAlertManagerConfigAction).reducer,
|
||||
deleteAMConfig: createAsyncSlice('deleteAMConfig', deleteAlertManagerConfigAction).reducer,
|
||||
updateSilence: createAsyncSlice('updateSilence', createOrUpdateSilenceAction).reducer,
|
||||
|
Loading…
Reference in New Issue
Block a user