Files
Virginia Cepeda f27326f7d9 Alerting: Choose a previous valid AM configuration in case of error (#65746)
* Add new property to AlertmanagerConfig type

* Implement fetching successfully applied configurations

Added method to fetch them from the API and its corresponding action and reducer

* Extract ConfigEditor as component to avoid code duplication

* Display dropdown with valid configs upon error and allow to save them

* Fix tests

* Refactor to call new endpoint using RTK

* Improve texts

* Apply suggested refactor

* Change constant casing

* Only show config selector for Grafana AM

* Remove ts-ignore

* Move code together for simplicity

* Remove invalid mock

* Update endpoint and types based on backend changes

* Rename property

* Rename alermanager config property from backend changes

* Disable editing old configurations

Due to the latest backend changes, we no longer will provide the option to edit previous AM configurations in a textearea.
Instead users will only be allowed to reset to a specific one with the same content. For this reason the textearea for old conf
igurations is disabled and a different form action (not submit) is executed on the "reset config" button. The updateAlertManage
rConfigAction is reset to its old functionality due to these changes.

* Add id to AlertManagerCortexConfig type

We'll need it to pass as a parameter to the new reset endpoint

* Add new endpoint for resetting AM configs to an old version

* Move the "Reset to selected configuration" button next to the drop-down

* Add relative offset to configurations
2023-04-05 15:13:33 -03:00

147 lines
4.9 KiB
TypeScript

import { css } from '@emotion/css';
import React, { useEffect, useState, useMemo } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Alert, useStyles2 } from '@grafana/ui';
import { useDispatch } from 'app/types';
import { useAlertManagerSourceName } from '../../hooks/useAlertManagerSourceName';
import { useAlertManagersByPermission } from '../../hooks/useAlertManagerSources';
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
import {
deleteAlertManagerConfigAction,
fetchAlertManagerConfigAction,
updateAlertManagerConfigAction,
} from '../../state/actions';
import { GRAFANA_RULES_SOURCE_NAME, isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource';
import { initialAsyncRequestState } from '../../utils/redux';
import { AlertManagerPicker } from '../AlertManagerPicker';
import AlertmanagerConfigSelector, { ValidAmConfigOption } from './AlertmanagerConfigSelector';
import { ConfigEditor } from './ConfigEditor';
export interface FormValues {
configJSON: string;
}
export default function AlertmanagerConfig(): JSX.Element {
const dispatch = useDispatch();
const alertManagers = useAlertManagersByPermission('notification');
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers);
const [showConfirmDeleteAMConfig, setShowConfirmDeleteAMConfig] = useState(false);
const { loading: isDeleting } = useUnifiedAlertingSelector((state) => state.deleteAMConfig);
const { loading: isSaving } = useUnifiedAlertingSelector((state) => state.saveAMConfig);
const readOnly = alertManagerSourceName ? isVanillaPrometheusAlertManagerDataSource(alertManagerSourceName) : false;
const styles = useStyles2(getStyles);
const configRequests = useUnifiedAlertingSelector((state) => state.amConfigs);
const [selectedAmConfig, setSelectedAmConfig] = useState<ValidAmConfigOption | undefined>();
const {
result: config,
loading: isLoadingConfig,
error: loadingError,
} = (alertManagerSourceName && configRequests[alertManagerSourceName]) || initialAsyncRequestState;
useEffect(() => {
if (alertManagerSourceName) {
dispatch(fetchAlertManagerConfigAction(alertManagerSourceName));
}
}, [alertManagerSourceName, dispatch]);
const resetConfig = () => {
if (alertManagerSourceName) {
dispatch(deleteAlertManagerConfigAction(alertManagerSourceName));
}
setShowConfirmDeleteAMConfig(false);
};
const defaultValues = useMemo(
(): FormValues => ({
configJSON: config ? JSON.stringify(config, null, 2) : '',
}),
[config]
);
const defaultValidValues = useMemo(
(): FormValues => ({
configJSON: selectedAmConfig ? JSON.stringify(selectedAmConfig.value, null, 2) : '',
}),
[selectedAmConfig]
);
const loading = isDeleting || isLoadingConfig || isSaving;
const onSubmit = (values: FormValues) => {
if (alertManagerSourceName && config) {
dispatch(
updateAlertManagerConfigAction({
newConfig: JSON.parse(values.configJSON),
oldConfig: config,
alertManagerSourceName,
successMessage: 'Alertmanager configuration updated.',
refetch: true,
})
);
}
};
return (
<div className={styles.container}>
<AlertManagerPicker
current={alertManagerSourceName}
onChange={setAlertManagerSourceName}
dataSources={alertManagers}
/>
{loadingError && !loading && (
<>
<Alert
severity="error"
title="Your Alertmanager configuration is incorrect. These are the details of the error:"
>
{loadingError.message || 'Unknown error.'}
</Alert>
{alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME && (
<AlertmanagerConfigSelector
onChange={setSelectedAmConfig}
selectedAmConfig={selectedAmConfig}
defaultValues={defaultValidValues}
readOnly={true}
loading={loading}
onSubmit={onSubmit}
/>
)}
</>
)}
{isDeleting && alertManagerSourceName !== GRAFANA_RULES_SOURCE_NAME && (
<Alert severity="info" title="Resetting Alertmanager configuration">
It might take a while...
</Alert>
)}
{alertManagerSourceName && config && (
<ConfigEditor
defaultValues={defaultValues}
onSubmit={(values) => onSubmit(values)}
readOnly={readOnly}
loading={loading}
alertManagerSourceName={alertManagerSourceName}
showConfirmDeleteAMConfig={showConfirmDeleteAMConfig}
onReset={() => setShowConfirmDeleteAMConfig(true)}
onConfirmReset={resetConfig}
onDismiss={() => setShowConfirmDeleteAMConfig(false)}
/>
)}
</div>
);
}
const getStyles = (theme: GrafanaTheme2) => ({
container: css`
margin-bottom: ${theme.spacing(4)};
`,
});