mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: hide "silence" button for external AM setups (#62133)
This commit is contained in:
parent
a190e03133
commit
26866953c1
@ -148,8 +148,15 @@ func (srv ConfigSrv) RouteGetAlertingStatus(c *contextmodel.ReqContext) response
|
||||
sendsAlertsTo = cfg.SendAlertsTo
|
||||
}
|
||||
|
||||
// handle errors
|
||||
externalAlertManagers, err := srv.externalAlertmanagers(c.Req.Context(), c.OrgID)
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
|
||||
resp := apimodels.AlertingStatus{
|
||||
AlertmanagersChoice: apimodels.AlertmanagersChoice(sendsAlertsTo.String()),
|
||||
AlertmanagersChoice: apimodels.AlertmanagersChoice(sendsAlertsTo.String()),
|
||||
NumExternalAlertmanagers: len(externalAlertManagers),
|
||||
}
|
||||
return response.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
@ -92,5 +92,6 @@ type GettableAlertmanagers struct {
|
||||
|
||||
// swagger:model
|
||||
type AlertingStatus struct {
|
||||
AlertmanagersChoice AlertmanagersChoice `json:"alertmanagersChoice"`
|
||||
AlertmanagersChoice AlertmanagersChoice `json:"alertmanagersChoice"`
|
||||
NumExternalAlertmanagers int `json:"numExternalAlertmanagers"`
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import { getFiltersFromUrlParams } from './utils/misc';
|
||||
import { initialAsyncRequestState } from './utils/redux';
|
||||
|
||||
const AlertGroups = () => {
|
||||
const { useGetAlertmanagerChoiceQuery } = alertmanagerApi;
|
||||
const { useGetAlertmanagerChoiceStatusQuery } = alertmanagerApi;
|
||||
|
||||
const alertManagers = useAlertManagersByPermission('instance');
|
||||
const [alertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||
@ -34,7 +34,7 @@ const AlertGroups = () => {
|
||||
const { groupBy = [] } = getFiltersFromUrlParams(queryParams);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const { currentData: alertmanagerChoice } = useGetAlertmanagerChoiceQuery();
|
||||
const { currentData: amConfigStatus } = useGetAlertmanagerChoiceStatusQuery();
|
||||
|
||||
const alertGroups = useUnifiedAlertingSelector((state) => state.amAlertGroups);
|
||||
const {
|
||||
@ -47,7 +47,8 @@ const AlertGroups = () => {
|
||||
const filteredAlertGroups = useFilteredAmGroups(groupedAlerts);
|
||||
|
||||
const grafanaAmDeliveryDisabled =
|
||||
alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME && alertmanagerChoice === AlertmanagerChoice.External;
|
||||
alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME &&
|
||||
amConfigStatus?.alertmanagersChoice === AlertmanagerChoice.External;
|
||||
|
||||
useEffect(() => {
|
||||
function fetchNotifications() {
|
||||
|
@ -7,7 +7,6 @@ import { useDispatch } from 'app/types';
|
||||
|
||||
import { useCleanup } from '../../../core/hooks/useCleanup';
|
||||
|
||||
import { alertmanagerApi } from './api/alertmanagerApi';
|
||||
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||
import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
|
||||
@ -29,12 +28,11 @@ import { initialAsyncRequestState } from './utils/redux';
|
||||
|
||||
const AmRoutes = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { useGetAlertmanagerChoiceQuery } = alertmanagerApi;
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
const [isRootRouteEditMode, setIsRootRouteEditMode] = useState(false);
|
||||
const alertManagers = useAlertManagersByPermission('notification');
|
||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||
const { currentData: alertmanagerChoice } = useGetAlertmanagerChoiceQuery();
|
||||
|
||||
const amConfigs = useUnifiedAlertingSelector((state) => state.amConfigs);
|
||||
|
||||
@ -130,10 +128,7 @@ const AmRoutes = () => {
|
||||
{resultError.message || 'Unknown error.'}
|
||||
</Alert>
|
||||
)}
|
||||
<GrafanaAlertmanagerDeliveryWarning
|
||||
currentAlertmanager={alertManagerSourceName}
|
||||
alertmanagerChoice={alertmanagerChoice}
|
||||
/>
|
||||
<GrafanaAlertmanagerDeliveryWarning currentAlertmanager={alertManagerSourceName} />
|
||||
{isProvisioned && <ProvisioningAlert resource={ProvisionedResource.RootNotificationPolicy} />}
|
||||
{resultLoading && <LoadingPlaceholder text="Loading Alertmanager config..." />}
|
||||
{result && !resultLoading && !resultError && (
|
||||
|
@ -24,6 +24,7 @@ import 'whatwg-fetch';
|
||||
|
||||
import Receivers from './Receivers';
|
||||
import { fetchAlertManagerConfig, fetchStatus, testReceivers, updateAlertManagerConfig } from './api/alertmanager';
|
||||
import { AlertmanagersChoiceResponse } from './api/alertmanagerApi';
|
||||
import { discoverAlertmanagerFeatures } from './api/buildInfo';
|
||||
import { fetchNotifiers } from './api/grafana';
|
||||
import * as receiversApi from './api/receiversApi';
|
||||
@ -63,6 +64,11 @@ const mocks = {
|
||||
contextSrv: jest.mocked(contextSrv),
|
||||
};
|
||||
|
||||
const alertmanagerChoiceMockedResponse: AlertmanagersChoiceResponse = {
|
||||
alertmanagersChoice: AlertmanagerChoice.Internal,
|
||||
numExternalAlertmanagers: 0,
|
||||
};
|
||||
|
||||
const renderReceivers = (alertManagerSourceName?: string) => {
|
||||
const store = configureStore();
|
||||
|
||||
@ -185,7 +191,7 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Template and receiver tables are rendered, alertmanager can be selected, no notification errors', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
|
||||
mocks.api.fetchConfig.mockImplementation((name) =>
|
||||
Promise.resolve(name === GRAFANA_RULES_SOURCE_NAME ? someGrafanaAlertManagerConfig : someCloudAlertManagerConfig)
|
||||
);
|
||||
@ -230,7 +236,7 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Grafana receiver can be tested', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
|
||||
|
||||
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
|
||||
|
||||
@ -288,7 +294,7 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Grafana receiver can be created', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
|
||||
|
||||
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
|
||||
mocks.api.updateConfig.mockResolvedValue();
|
||||
@ -352,7 +358,7 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Hides create contact point button for users without permission', () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
|
||||
|
||||
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
|
||||
mocks.api.updateConfig.mockResolvedValue();
|
||||
@ -368,7 +374,7 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Cloud alertmanager receiver can be edited', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
|
||||
|
||||
mocks.api.fetchConfig.mockResolvedValue(someCloudAlertManagerConfig);
|
||||
mocks.api.updateConfig.mockResolvedValue();
|
||||
@ -464,7 +470,7 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Prometheus Alertmanager receiver cannot be edited', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
|
||||
|
||||
mocks.api.fetchStatus.mockResolvedValue({
|
||||
...someCloudAlertManagerStatus,
|
||||
@ -503,7 +509,7 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Loads config from status endpoint if there is no user config', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
|
||||
// loading an empty config with make it fetch config from status endpoint
|
||||
mocks.api.fetchConfig.mockResolvedValue({
|
||||
template_files: {},
|
||||
@ -525,7 +531,7 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Shows an empty config when config returns an error and the AM supports lazy config initialization', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
|
||||
|
||||
mocks.api.discoverAlertmanagerFeatures.mockResolvedValue({ lazyConfigInit: true });
|
||||
mocks.api.fetchConfig.mockRejectedValue({ message: 'alertmanager storage object not found' });
|
||||
@ -542,7 +548,7 @@ describe('Receivers', () => {
|
||||
|
||||
describe('Contact points state', () => {
|
||||
it('Should render error notifications when there are some points state ', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
|
||||
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
|
||||
mocks.api.updateConfig.mockResolvedValue();
|
||||
|
||||
@ -614,7 +620,7 @@ describe('Receivers', () => {
|
||||
expect(byText('OK').getAll(criticalDetailTable)).toHaveLength(2);
|
||||
});
|
||||
it('Should render no attempt message when there are some points state with null lastNotifyAttempt, and "-" in null values', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
|
||||
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
|
||||
mocks.api.updateConfig.mockResolvedValue();
|
||||
|
||||
@ -691,7 +697,7 @@ describe('Receivers', () => {
|
||||
});
|
||||
|
||||
it('Should not render error notifications when fetching contact points state raises 404 error ', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, { alertmanagersChoice: AlertmanagerChoice.All });
|
||||
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
|
||||
mocks.api.fetchConfig.mockResolvedValue(someGrafanaAlertManagerConfig);
|
||||
mocks.api.updateConfig.mockResolvedValue();
|
||||
|
||||
|
@ -10,7 +10,6 @@ import { useDispatch } from 'app/types';
|
||||
|
||||
import { ContactPointsState } from '../../../types';
|
||||
|
||||
import { alertmanagerApi } from './api/alertmanagerApi';
|
||||
import { useGetContactPointsState } from './api/receiversApi';
|
||||
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||
@ -55,8 +54,6 @@ function NotificationError({ errorCount }: NotificationErrorProps) {
|
||||
type PageType = 'receivers' | 'templates' | 'global-config';
|
||||
|
||||
const Receivers = () => {
|
||||
const { useGetAlertmanagerChoiceQuery } = alertmanagerApi;
|
||||
|
||||
const alertManagers = useAlertManagersByPermission('notification');
|
||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||
const dispatch = useDispatch();
|
||||
@ -97,8 +94,6 @@ const Receivers = () => {
|
||||
const contactPointsState: ContactPointsState = useGetContactPointsState(alertManagerSourceName ?? '');
|
||||
const integrationsErrorCount = contactPointsState?.errorCount ?? 0;
|
||||
|
||||
const { data: alertmanagerChoice } = useGetAlertmanagerChoiceQuery();
|
||||
|
||||
const disableAmSelect = !isRoot;
|
||||
|
||||
let pageNav = getPageNavigationModel(type, id);
|
||||
@ -131,10 +126,7 @@ const Receivers = () => {
|
||||
{error.message || 'Unknown error.'}
|
||||
</Alert>
|
||||
)}
|
||||
<GrafanaAlertmanagerDeliveryWarning
|
||||
alertmanagerChoice={alertmanagerChoice}
|
||||
currentAlertmanager={alertManagerSourceName}
|
||||
/>
|
||||
<GrafanaAlertmanagerDeliveryWarning currentAlertmanager={alertManagerSourceName} />
|
||||
{loading && !config && <LoadingPlaceholder text="loading configuration..." />}
|
||||
{config && !error && (
|
||||
<Switch>
|
||||
|
@ -5,7 +5,6 @@ import { Alert, withErrorBoundary } from '@grafana/ui';
|
||||
import { Silence } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { useDispatch } from 'app/types';
|
||||
|
||||
import { alertmanagerApi } from './api/alertmanagerApi';
|
||||
import { featureDiscoveryApi } from './api/featureDiscoveryApi';
|
||||
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||
@ -26,7 +25,6 @@ const Silences = () => {
|
||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { useGetAlertmanagerChoiceQuery } = alertmanagerApi;
|
||||
const silences = useUnifiedAlertingSelector((state) => state.silences);
|
||||
const alertsRequests = useUnifiedAlertingSelector((state) => state.amAlerts);
|
||||
const alertsRequest = alertManagerSourceName
|
||||
@ -42,8 +40,6 @@ const Silences = () => {
|
||||
{ skip: !alertManagerSourceName }
|
||||
);
|
||||
|
||||
const { currentData: alertmanagerChoice } = useGetAlertmanagerChoiceQuery();
|
||||
|
||||
useEffect(() => {
|
||||
function fetchAll() {
|
||||
if (alertManagerSourceName) {
|
||||
@ -84,10 +80,7 @@ const Silences = () => {
|
||||
onChange={setAlertManagerSourceName}
|
||||
dataSources={alertManagers}
|
||||
/>
|
||||
<GrafanaAlertmanagerDeliveryWarning
|
||||
currentAlertmanager={alertManagerSourceName}
|
||||
alertmanagerChoice={alertmanagerChoice}
|
||||
/>
|
||||
<GrafanaAlertmanagerDeliveryWarning currentAlertmanager={alertManagerSourceName} />
|
||||
|
||||
{mimirLazyInitError && (
|
||||
<Alert title="The selected Alertmanager has no configuration" severity="warning">
|
||||
|
@ -9,14 +9,14 @@ import { alertingApi } from './alertingApi';
|
||||
|
||||
export interface AlertmanagersChoiceResponse {
|
||||
alertmanagersChoice: AlertmanagerChoice;
|
||||
numExternalAlertmanagers: number;
|
||||
}
|
||||
|
||||
export const alertmanagerApi = alertingApi.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
getAlertmanagerChoice: build.query<AlertmanagerChoice, void>({
|
||||
getAlertmanagerChoiceStatus: build.query<AlertmanagersChoiceResponse, void>({
|
||||
query: () => ({ url: '/api/v1/ngalert' }),
|
||||
providesTags: ['AlertmanagerChoice'],
|
||||
transformResponse: (response: AlertmanagersChoiceResponse) => response.alertmanagersChoice,
|
||||
}),
|
||||
|
||||
getExternalAlertmanagerConfig: build.query<ExternalAlertmanagerConfig, void>({
|
||||
|
@ -1,47 +1,100 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { setupServer } from 'msw/node';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import 'whatwg-fetch';
|
||||
|
||||
import { setBackendSrv } from '@grafana/runtime';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
|
||||
import { AlertmanagerChoice } from '../../../../plugins/datasource/alertmanager/types';
|
||||
import { mockAlertmanagerChoiceResponse } from '../mocks/alertmanagerApi';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||
|
||||
import { GrafanaAlertmanagerDeliveryWarning } from './GrafanaAlertmanagerDeliveryWarning';
|
||||
|
||||
describe('GrafanaAlertmanagerDeliveryWarning', () => {
|
||||
describe('When AlertmanagerChoice set to External', () => {
|
||||
it('Should not render when the datasource is not Grafana', () => {
|
||||
const { container } = render(
|
||||
<GrafanaAlertmanagerDeliveryWarning
|
||||
currentAlertmanager="custom-alertmanager"
|
||||
alertmanagerChoice={AlertmanagerChoice.External}
|
||||
/>
|
||||
);
|
||||
const server = setupServer();
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Should render warning when the datasource is Grafana', () => {
|
||||
const { container } = render(
|
||||
<GrafanaAlertmanagerDeliveryWarning
|
||||
currentAlertmanager={GRAFANA_RULES_SOURCE_NAME}
|
||||
alertmanagerChoice={AlertmanagerChoice.External}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container).toHaveTextContent('Grafana alerts are not delivered to Grafana Alertmanager');
|
||||
});
|
||||
beforeAll(() => {
|
||||
setBackendSrv(backendSrv);
|
||||
server.listen({ onUnhandledRequest: 'error' });
|
||||
});
|
||||
|
||||
it.each([AlertmanagerChoice.All, AlertmanagerChoice.Internal])(
|
||||
'Should not render when datasource is Grafana and Alertmanager choice is %s',
|
||||
(choice) => {
|
||||
const { container } = render(
|
||||
<GrafanaAlertmanagerDeliveryWarning
|
||||
currentAlertmanager={GRAFANA_RULES_SOURCE_NAME}
|
||||
alertmanagerChoice={choice}
|
||||
/>
|
||||
);
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
}
|
||||
);
|
||||
beforeEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
it('Should not render when the datasource is not Grafana', () => {
|
||||
mockAlertmanagerChoiceResponse(server, {
|
||||
alertmanagersChoice: AlertmanagerChoice.External,
|
||||
numExternalAlertmanagers: 0,
|
||||
});
|
||||
|
||||
const { container } = renderWithStore(
|
||||
<GrafanaAlertmanagerDeliveryWarning currentAlertmanager="custom-alertmanager" />
|
||||
);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Should render warning when the datasource is Grafana and using external AM', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, {
|
||||
alertmanagersChoice: AlertmanagerChoice.External,
|
||||
numExternalAlertmanagers: 1,
|
||||
});
|
||||
|
||||
renderWithStore(<GrafanaAlertmanagerDeliveryWarning currentAlertmanager={GRAFANA_RULES_SOURCE_NAME} />);
|
||||
|
||||
expect(await screen.findByText('Grafana alerts are not delivered to Grafana Alertmanager')).toBeVisible();
|
||||
});
|
||||
|
||||
it('Should render warning when the datasource is Grafana and using All AM', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, {
|
||||
alertmanagersChoice: AlertmanagerChoice.All,
|
||||
numExternalAlertmanagers: 1,
|
||||
});
|
||||
|
||||
renderWithStore(<GrafanaAlertmanagerDeliveryWarning currentAlertmanager={GRAFANA_RULES_SOURCE_NAME} />);
|
||||
|
||||
expect(await screen.findByText('You have additional Alertmanagers to configure')).toBeVisible();
|
||||
});
|
||||
|
||||
it('Should render no warning when choice is Internal', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, {
|
||||
alertmanagersChoice: AlertmanagerChoice.Internal,
|
||||
numExternalAlertmanagers: 1,
|
||||
});
|
||||
|
||||
const { container } = renderWithStore(
|
||||
<GrafanaAlertmanagerDeliveryWarning currentAlertmanager={GRAFANA_RULES_SOURCE_NAME} />
|
||||
);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Should render no warning when choice is All but no active AM instances', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, {
|
||||
alertmanagersChoice: AlertmanagerChoice.All,
|
||||
numExternalAlertmanagers: 0,
|
||||
});
|
||||
|
||||
const { container } = renderWithStore(
|
||||
<GrafanaAlertmanagerDeliveryWarning currentAlertmanager={GRAFANA_RULES_SOURCE_NAME} />
|
||||
);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
|
||||
function renderWithStore(element: JSX.Element) {
|
||||
const store = configureStore();
|
||||
|
||||
return render(<Provider store={store}>{element}</Provider>);
|
||||
}
|
||||
|
@ -5,37 +5,58 @@ import { GrafanaTheme2 } from '@grafana/data/src';
|
||||
import { Alert, useStyles2 } from '@grafana/ui/src';
|
||||
|
||||
import { AlertmanagerChoice } from '../../../../plugins/datasource/alertmanager/types';
|
||||
import { alertmanagerApi } from '../api/alertmanagerApi';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||
|
||||
interface GrafanaAlertmanagerDeliveryWarningProps {
|
||||
alertmanagerChoice?: AlertmanagerChoice;
|
||||
currentAlertmanager: string;
|
||||
}
|
||||
|
||||
export function GrafanaAlertmanagerDeliveryWarning({
|
||||
alertmanagerChoice,
|
||||
currentAlertmanager,
|
||||
}: GrafanaAlertmanagerDeliveryWarningProps) {
|
||||
export function GrafanaAlertmanagerDeliveryWarning({ currentAlertmanager }: GrafanaAlertmanagerDeliveryWarningProps) {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (currentAlertmanager !== GRAFANA_RULES_SOURCE_NAME) {
|
||||
const { useGetAlertmanagerChoiceStatusQuery } = alertmanagerApi;
|
||||
const { currentData: amChoiceStatus } = useGetAlertmanagerChoiceStatusQuery();
|
||||
|
||||
const viewingInternalAM = currentAlertmanager === GRAFANA_RULES_SOURCE_NAME;
|
||||
|
||||
const interactsWithExternalAMs =
|
||||
amChoiceStatus?.alertmanagersChoice &&
|
||||
[AlertmanagerChoice.External, AlertmanagerChoice.All].includes(amChoiceStatus?.alertmanagersChoice);
|
||||
|
||||
if (!interactsWithExternalAMs || !viewingInternalAM) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (alertmanagerChoice !== AlertmanagerChoice.External) {
|
||||
return null;
|
||||
const hasActiveExternalAMs = amChoiceStatus.numExternalAlertmanagers > 0;
|
||||
|
||||
if (amChoiceStatus.alertmanagersChoice === AlertmanagerChoice.External) {
|
||||
return (
|
||||
<Alert title="Grafana alerts are not delivered to Grafana Alertmanager">
|
||||
Grafana is configured to send alerts to external Alertmanagers only. Changing Grafana Alertmanager configuration
|
||||
will not affect delivery of your alerts.
|
||||
<div className={styles.adminHint}>
|
||||
To change your Alertmanager setup, go to the Alerting Admin page. If you do not have access, contact your
|
||||
Administrator.
|
||||
</div>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert title="Grafana alerts are not delivered to Grafana Alertmanager">
|
||||
Grafana is configured to send alerts to external Alertmanagers only. Changing Grafana Alertmanager configuration
|
||||
will not affect delivery of your alerts!
|
||||
<div className={styles.adminHint}>
|
||||
You can change the configuration on the Alerting Admin page. If you do not have access, contact your
|
||||
Administrator
|
||||
</div>
|
||||
</Alert>
|
||||
);
|
||||
if (amChoiceStatus.alertmanagersChoice === AlertmanagerChoice.All && hasActiveExternalAMs) {
|
||||
return (
|
||||
<Alert title="You have additional Alertmanagers to configure" severity="warning">
|
||||
Ensure you make configuration changes in the correct Alertmanagers; both internal and external. Changing one
|
||||
will not affect the others.
|
||||
<div className={styles.adminHint}>
|
||||
To change your Alertmanager setup, go to the Alerting Admin page. If you do not have access, contact your
|
||||
Administrator.
|
||||
</div>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { setupServer } from 'msw/node';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
@ -7,12 +8,15 @@ import { byRole } from 'testing-library-selector';
|
||||
import { setBackendSrv } from '@grafana/runtime';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { AccessControlAction } from 'app/types';
|
||||
import { CombinedRule } from 'app/types/unified-alerting';
|
||||
|
||||
import { AlertmanagersChoiceResponse } from '../../api/alertmanagerApi';
|
||||
import { useIsRuleEditable } from '../../hooks/useIsRuleEditable';
|
||||
import { getCloudRule, getGrafanaRule } from '../../mocks';
|
||||
import { mockAlertmanagerChoiceResponse } from '../../mocks/alertmanagerApi';
|
||||
|
||||
import { RuleDetails } from './RuleDetails';
|
||||
|
||||
@ -32,11 +36,27 @@ const ui = {
|
||||
|
||||
jest.spyOn(contextSrv, 'accessControlEnabled').mockReturnValue(true);
|
||||
|
||||
const server = setupServer();
|
||||
|
||||
const alertmanagerChoiceMockedResponse: AlertmanagersChoiceResponse = {
|
||||
alertmanagersChoice: AlertmanagerChoice.Internal,
|
||||
numExternalAlertmanagers: 0,
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
setBackendSrv(backendSrv);
|
||||
server.listen({ onUnhandledRequest: 'error' });
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
describe('RuleDetails RBAC', () => {
|
||||
describe('Grafana rules action buttons in details', () => {
|
||||
const grafanaRule = getGrafanaRule({ name: 'Grafana' });
|
||||
@ -68,6 +88,7 @@ describe('RuleDetails RBAC', () => {
|
||||
it('Should not render Silence button for users wihout the instance create permission', async () => {
|
||||
// Arrange
|
||||
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
|
||||
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
|
||||
|
||||
// Act
|
||||
renderRuleDetails(grafanaRule);
|
||||
@ -78,6 +99,8 @@ describe('RuleDetails RBAC', () => {
|
||||
});
|
||||
|
||||
it('Should render Silence button for users with the instance create permissions', async () => {
|
||||
mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse);
|
||||
|
||||
// Arrange
|
||||
jest
|
||||
.spyOn(contextSrv, 'hasPermission')
|
||||
@ -91,6 +114,7 @@ describe('RuleDetails RBAC', () => {
|
||||
await waitFor(() => screen.queryByRole('button', { name: 'Declare incident' }));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Cloud rules action buttons', () => {
|
||||
const cloudRule = getCloudRule({ name: 'Cloud' });
|
||||
|
||||
|
@ -7,10 +7,12 @@ import { config } from '@grafana/runtime';
|
||||
import { Button, ClipboardButton, ConfirmModal, HorizontalGroup, LinkButton, useStyles2 } from '@grafana/ui';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { AccessControlAction, useDispatch } from 'app/types';
|
||||
import { CombinedRule, RulesSource } from 'app/types/unified-alerting';
|
||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { alertmanagerApi } from '../../api/alertmanagerApi';
|
||||
import { useIsRuleEditable } from '../../hooks/useIsRuleEditable';
|
||||
import { useStateHistoryModal } from '../../hooks/useStateHistoryModal';
|
||||
import { deleteRuleAction } from '../../state/actions';
|
||||
@ -84,6 +86,7 @@ export const RuleDetailsActionButtons: FC<Props> = ({ rule, rulesSource, isViewM
|
||||
const rulesPermissions = getRulesPermissions(rulesSourceName);
|
||||
const hasCreateRulePermission = contextSrv.hasPermission(rulesPermissions.create);
|
||||
const { isEditable, isRemovable } = useIsRuleEditable(rulesSourceName, rulerRule);
|
||||
const canSilence = useCanSilence(rule);
|
||||
|
||||
const returnTo = location.pathname + location.search;
|
||||
// explore does not support grafana rule queries atm
|
||||
@ -149,7 +152,7 @@ export const RuleDetailsActionButtons: FC<Props> = ({ rule, rulesSource, isViewM
|
||||
}
|
||||
}
|
||||
|
||||
if (alertmanagerSourceName && contextSrv.hasAccess(AccessControlAction.AlertingInstanceCreate, contextSrv.isEditor)) {
|
||||
if (canSilence && alertmanagerSourceName) {
|
||||
buttons.push(
|
||||
<LinkButton
|
||||
size="sm"
|
||||
@ -263,6 +266,31 @@ export const RuleDetailsActionButtons: FC<Props> = ({ rule, rulesSource, isViewM
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* We don't want to show the silence button if either
|
||||
* 1. the user has no permissions to create silences
|
||||
* 2. the admin has configured to only send instances to external AMs
|
||||
*/
|
||||
function useCanSilence(rule: CombinedRule) {
|
||||
const isGrafanaManagedRule = isGrafanaRulerRule(rule.rulerRule);
|
||||
|
||||
const { useGetAlertmanagerChoiceStatusQuery } = alertmanagerApi;
|
||||
const { currentData: amConfigStatus, isLoading } = useGetAlertmanagerChoiceStatusQuery(undefined, {
|
||||
skip: !isGrafanaManagedRule,
|
||||
});
|
||||
|
||||
if (!isGrafanaManagedRule || isLoading) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasPermissions = contextSrv.hasAccess(AccessControlAction.AlertingInstanceCreate, contextSrv.isEditor);
|
||||
|
||||
const interactsOnlyWithExternalAMs = amConfigStatus?.alertmanagersChoice === AlertmanagerChoice.External;
|
||||
const interactsWithAll = amConfigStatus?.alertmanagersChoice === AlertmanagerChoice.All;
|
||||
|
||||
return hasPermissions && (!interactsOnlyWithExternalAMs || interactsWithAll);
|
||||
}
|
||||
|
||||
export const getStyles = (theme: GrafanaTheme2) => ({
|
||||
wrapper: css`
|
||||
padding: ${theme.spacing(2)} 0;
|
||||
|
@ -64,12 +64,12 @@ export const MatchedSilencedRules = () => {
|
||||
{matchers.every((matcher) => !matcher.value && !matcher.name) ? (
|
||||
<span>Add a valid matcher to see affected alerts</span>
|
||||
) : (
|
||||
<>
|
||||
<DynamicTable items={matchedAlertRules.slice(0, 5) ?? []} isExpandable={false} cols={columns} />
|
||||
{matchedAlertRules.length > 5 && (
|
||||
<div className={styles.moreMatches}>and {matchedAlertRules.length - 5} more</div>
|
||||
)}
|
||||
</>
|
||||
<DynamicTable
|
||||
items={matchedAlertRules}
|
||||
isExpandable={false}
|
||||
cols={columns}
|
||||
pagination={{ itemsPerPage: 5 }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@ -92,7 +92,7 @@ function useColumns(): MatchedRulesTableColumnProps[] {
|
||||
renderCell: function renderName({ data: { matchedInstance } }) {
|
||||
return <AlertLabels labels={matchedInstance.labels} />;
|
||||
},
|
||||
size: '250px',
|
||||
size: 'auto',
|
||||
},
|
||||
{
|
||||
id: 'created',
|
||||
@ -106,7 +106,7 @@ function useColumns(): MatchedRulesTableColumnProps[] {
|
||||
</>
|
||||
);
|
||||
},
|
||||
size: '400px',
|
||||
size: '180px',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -173,6 +173,25 @@ export const mockPromAlertingRule = (partial: Partial<AlertingRule> = {}): Alert
|
||||
};
|
||||
};
|
||||
|
||||
export const mockGrafanaRulerRule = (partial: Partial<RulerGrafanaRuleDTO> = {}): RulerGrafanaRuleDTO => {
|
||||
return {
|
||||
for: '',
|
||||
annotations: {},
|
||||
labels: {},
|
||||
grafana_alert: {
|
||||
...partial,
|
||||
uid: '',
|
||||
title: 'my rule',
|
||||
namespace_uid: '',
|
||||
namespace_id: 0,
|
||||
condition: '',
|
||||
no_data_state: GrafanaAlertStateDecision.NoData,
|
||||
exec_err_state: GrafanaAlertStateDecision.Error,
|
||||
data: [],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const mockPromRecordingRule = (partial: Partial<RecordingRule> = {}): RecordingRule => {
|
||||
return {
|
||||
type: PromRuleType.Recording,
|
||||
@ -570,6 +589,7 @@ export function getGrafanaRule(override?: Partial<CombinedRule>) {
|
||||
name: 'Grafana',
|
||||
rulesSource: 'grafana',
|
||||
},
|
||||
rulerRule: mockGrafanaRulerRule(),
|
||||
...override,
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user