mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Support alertingDisableSendAlertsExternal
feature toggle (#88384)
This commit is contained in:
parent
e93169b66c
commit
781f67dfb6
@ -221,10 +221,7 @@ export function getNavSubTitle(navId: string | undefined) {
|
|||||||
'Upgrade your existing legacy alerts and notification channels to the new Grafana Alerting'
|
'Upgrade your existing legacy alerts and notification channels to the new Grafana Alerting'
|
||||||
);
|
);
|
||||||
case 'alerting-admin':
|
case 'alerting-admin':
|
||||||
return t(
|
return t('nav.alerting-admin.subtitle', 'Manage Alertmanager configurations');
|
||||||
'nav.alerting-admin.subtitle',
|
|
||||||
'Manage Alertmanager configurations and configure where alert instances generated from Grafana managed alert rules are sent'
|
|
||||||
);
|
|
||||||
case 'alert-list':
|
case 'alert-list':
|
||||||
return t('nav.alerting-list.subtitle', 'Rules that determine whether an alert will fire');
|
return t('nav.alerting-list.subtitle', 'Rules that determine whether an alert will fire');
|
||||||
case 'receivers':
|
case 'receivers':
|
||||||
|
@ -147,6 +147,24 @@ describe('Alertmanager card', () => {
|
|||||||
render(cardWithStatus('inconclusive'));
|
render(cardWithStatus('inconclusive'));
|
||||||
expect(screen.getByText(/Inconclusive/)).toBeInTheDocument();
|
expect(screen.getByText(/Inconclusive/)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not render the enable / disable buttons or status when disabled', () => {
|
||||||
|
render(
|
||||||
|
<AlertmanagerCard
|
||||||
|
name="Foo"
|
||||||
|
receiving={true}
|
||||||
|
status="active"
|
||||||
|
showStatus={false}
|
||||||
|
onEditConfiguration={jest.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const enableButton = screen.queryByRole('button', { name: 'Enable' });
|
||||||
|
expect(enableButton).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
// should also not show the status for external alertmanagers
|
||||||
|
expect(screen.queryByText(/Receiving/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const cardWithStatus = (status: ConnectionStatus) => (
|
const cardWithStatus = (status: ConnectionStatus) => (
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { capitalize } from 'lodash';
|
import { capitalize } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Card, Badge, Button, Stack, Text, TextLink } from '@grafana/ui';
|
import { Badge, Button, Card, Stack, Text, TextLink } from '@grafana/ui';
|
||||||
|
|
||||||
import { ConnectionStatus } from '../../hooks/useExternalAmSelector';
|
import { ConnectionStatus } from '../../hooks/useExternalAmSelector';
|
||||||
import { ProvisioningBadge } from '../Provisioning';
|
import { ProvisioningBadge } from '../Provisioning';
|
||||||
@ -14,13 +14,14 @@ interface Props {
|
|||||||
logo?: string;
|
logo?: string;
|
||||||
provisioned?: boolean;
|
provisioned?: boolean;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
showStatus?: boolean;
|
||||||
implementation?: string;
|
implementation?: string;
|
||||||
receiving?: boolean;
|
receiving?: boolean;
|
||||||
status?: ConnectionStatus;
|
status?: ConnectionStatus;
|
||||||
// functions
|
// functions
|
||||||
onEditConfiguration: () => void;
|
onEditConfiguration: () => void;
|
||||||
onDisable: () => void;
|
onDisable?: () => void;
|
||||||
onEnable: () => void;
|
onEnable?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AlertmanagerCard({
|
export function AlertmanagerCard({
|
||||||
@ -30,6 +31,7 @@ export function AlertmanagerCard({
|
|||||||
logo = 'public/app/plugins/datasource/alertmanager/img/logo.svg',
|
logo = 'public/app/plugins/datasource/alertmanager/img/logo.svg',
|
||||||
provisioned = false,
|
provisioned = false,
|
||||||
readOnly = provisioned,
|
readOnly = provisioned,
|
||||||
|
showStatus = true,
|
||||||
implementation,
|
implementation,
|
||||||
receiving = false,
|
receiving = false,
|
||||||
status = 'unknown',
|
status = 'unknown',
|
||||||
@ -37,6 +39,8 @@ export function AlertmanagerCard({
|
|||||||
onEnable,
|
onEnable,
|
||||||
onDisable,
|
onDisable,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const showActions = !provisioned && Boolean(onEnable) && Boolean(onDisable);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card data-testid={`alertmanager-card-${name}`}>
|
<Card data-testid={`alertmanager-card-${name}`}>
|
||||||
<Card.Heading>
|
<Card.Heading>
|
||||||
@ -60,16 +64,21 @@ export function AlertmanagerCard({
|
|||||||
{implementation && capitalize(implementation)}
|
{implementation && capitalize(implementation)}
|
||||||
{url && url}
|
{url && url}
|
||||||
</Card.Meta>
|
</Card.Meta>
|
||||||
{!receiving ? (
|
{/* if forwarding is diabled, still show status for the "canonical" Alertmanager */}
|
||||||
<Text variant="bodySmall">Not receiving Grafana managed alerts</Text>
|
{showStatus ? (
|
||||||
) : (
|
|
||||||
<>
|
<>
|
||||||
{status === 'pending' && <Badge text="Activation in progress" color="orange" />}
|
{!receiving ? (
|
||||||
{status === 'active' && <Badge text="Receiving Grafana-managed alerts" color="green" />}
|
<Text variant="bodySmall">Not receiving Grafana managed alerts</Text>
|
||||||
{status === 'dropped' && <Badge text="Failed to adopt Alertmanager" color="red" />}
|
) : (
|
||||||
{status === 'inconclusive' && <Badge text="Inconclusive" color="orange" />}
|
<>
|
||||||
|
{status === 'pending' && <Badge text="Activation in progress" color="orange" />}
|
||||||
|
{status === 'active' && <Badge text="Receiving Grafana-managed alerts" color="green" />}
|
||||||
|
{status === 'dropped' && <Badge text="Failed to adopt Alertmanager" color="red" />}
|
||||||
|
{status === 'inconclusive' && <Badge text="Inconclusive" color="orange" />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card.Meta>
|
</Card.Meta>
|
||||||
|
|
||||||
@ -80,7 +89,7 @@ export function AlertmanagerCard({
|
|||||||
<Button onClick={onEditConfiguration} icon={readOnly ? 'eye' : 'edit'} variant="secondary" fill="outline">
|
<Button onClick={onEditConfiguration} icon={readOnly ? 'eye' : 'edit'} variant="secondary" fill="outline">
|
||||||
{readOnly ? 'View configuration' : 'Edit configuration'}
|
{readOnly ? 'View configuration' : 'Edit configuration'}
|
||||||
</Button>
|
</Button>
|
||||||
{provisioned ? null : (
|
{showActions ? (
|
||||||
<>
|
<>
|
||||||
{receiving ? (
|
{receiving ? (
|
||||||
<Button icon="times" variant="destructive" fill="outline" onClick={onDisable}>
|
<Button icon="times" variant="destructive" fill="outline" onClick={onDisable}>
|
||||||
@ -92,7 +101,7 @@ export function AlertmanagerCard({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card.Tags>
|
</Card.Tags>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -20,8 +20,13 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ExternalAlertmanagers = ({ onEditConfiguration }: Props) => {
|
export const ExternalAlertmanagers = ({ onEditConfiguration }: Props) => {
|
||||||
const { externalAlertmanagerDataSourcesWithStatus, configuration, enableAlertmanager, disableAlertmanager } =
|
const {
|
||||||
useSettings();
|
externalAlertmanagerDataSourcesWithStatus,
|
||||||
|
configuration,
|
||||||
|
enableAlertmanager,
|
||||||
|
disableAlertmanager,
|
||||||
|
forwardingDisabled,
|
||||||
|
} = useSettings();
|
||||||
|
|
||||||
// determine if the alertmanger is receiving alerts
|
// determine if the alertmanger is receiving alerts
|
||||||
// this is true if Grafana is configured to send to either "both" or "external" and the Alertmanager datasource _wants_ to receive alerts.
|
// this is true if Grafana is configured to send to either "both" or "external" and the Alertmanager datasource _wants_ to receive alerts.
|
||||||
@ -51,6 +56,8 @@ export const ExternalAlertmanagers = ({ onEditConfiguration }: Props) => {
|
|||||||
const detailHref = createUrl(DATASOURCES_ROUTES.Edit.replace(/:uid/gi, uid));
|
const detailHref = createUrl(DATASOURCES_ROUTES.Edit.replace(/:uid/gi, uid));
|
||||||
|
|
||||||
const handleEditConfiguration = () => onEditConfiguration(name);
|
const handleEditConfiguration = () => onEditConfiguration(name);
|
||||||
|
const handleEnable = forwardingDisabled ? undefined : () => enableAlertmanager(uid);
|
||||||
|
const handleDisable = forwardingDisabled ? undefined : () => disableAlertmanager(uid);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertmanagerCard
|
<AlertmanagerCard
|
||||||
@ -60,12 +67,13 @@ export const ExternalAlertmanagers = ({ onEditConfiguration }: Props) => {
|
|||||||
url={url}
|
url={url}
|
||||||
provisioned={isProvisioned}
|
provisioned={isProvisioned}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
showStatus={!forwardingDisabled}
|
||||||
implementation={jsonData.implementation ?? 'Prometheus'}
|
implementation={jsonData.implementation ?? 'Prometheus'}
|
||||||
receiving={isReceiving}
|
receiving={isReceiving}
|
||||||
status={status}
|
status={status}
|
||||||
onEditConfiguration={handleEditConfiguration}
|
onEditConfiguration={handleEditConfiguration}
|
||||||
onDisable={() => disableAlertmanager(uid)}
|
onDisable={handleDisable}
|
||||||
onEnable={() => enableAlertmanager(uid)}
|
onEnable={handleEnable}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -11,22 +11,27 @@ interface Props {
|
|||||||
onEditConfiguration: (dataSourceName: string) => void;
|
onEditConfiguration: (dataSourceName: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BUILTIN_ALERTMANAGER_NAME = 'Grafana built-in';
|
||||||
|
|
||||||
export default function InternalAlertmanager({ onEditConfiguration }: Props) {
|
export default function InternalAlertmanager({ onEditConfiguration }: Props) {
|
||||||
const { configuration, enableAlertmanager, disableAlertmanager } = useSettings();
|
const { configuration, enableAlertmanager, disableAlertmanager, forwardingDisabled } = useSettings();
|
||||||
|
|
||||||
const isReceiving = isInternalAlertmanagerInterestedInAlerts(configuration);
|
const isReceiving = isInternalAlertmanagerInterestedInAlerts(configuration);
|
||||||
const status: ConnectionStatus = isReceiving ? 'active' : 'uninterested';
|
const status: ConnectionStatus = isReceiving ? 'active' : 'uninterested';
|
||||||
|
|
||||||
const handleEditConfiguration = () => onEditConfiguration(GRAFANA_RULES_SOURCE_NAME);
|
const handleEditConfiguration = () => onEditConfiguration(GRAFANA_RULES_SOURCE_NAME);
|
||||||
|
const handleEnable = forwardingDisabled ? undefined : () => enableAlertmanager(GRAFANA_RULES_SOURCE_NAME);
|
||||||
|
const handleDisable = forwardingDisabled ? undefined : () => disableAlertmanager(GRAFANA_RULES_SOURCE_NAME);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertmanagerCard
|
<AlertmanagerCard
|
||||||
name="Grafana built-in"
|
name={BUILTIN_ALERTMANAGER_NAME}
|
||||||
logo="public/img/grafana_icon.svg"
|
logo="public/img/grafana_icon.svg"
|
||||||
status={status}
|
status={status}
|
||||||
receiving={isReceiving}
|
receiving={isReceiving}
|
||||||
onEditConfiguration={handleEditConfiguration}
|
onEditConfiguration={handleEditConfiguration}
|
||||||
onEnable={() => enableAlertmanager(GRAFANA_RULES_SOURCE_NAME)}
|
onEnable={handleEnable}
|
||||||
onDisable={() => disableAlertmanager(GRAFANA_RULES_SOURCE_NAME)}
|
onDisable={handleDisable}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { union, without, debounce } from 'lodash';
|
import { debounce, union, without } from 'lodash';
|
||||||
import React, { PropsWithChildren, useEffect, useRef } from 'react';
|
import React, { PropsWithChildren, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { AppEvents } from '@grafana/data';
|
import { AppEvents } from '@grafana/data';
|
||||||
import { getAppEvents } from '@grafana/runtime';
|
import { config, getAppEvents } from '@grafana/runtime';
|
||||||
import { AlertmanagerChoice, GrafanaAlertingConfiguration } from 'app/plugins/datasource/alertmanager/types';
|
import { AlertmanagerChoice, GrafanaAlertingConfiguration } from 'app/plugins/datasource/alertmanager/types';
|
||||||
import { dispatch } from 'app/store/store';
|
import { dispatch } from 'app/store/store';
|
||||||
|
|
||||||
@ -34,6 +34,9 @@ interface Context {
|
|||||||
// for updating or resetting the configuration for an Alertmanager
|
// for updating or resetting the configuration for an Alertmanager
|
||||||
updateAlertmanagerSettings: (name: string, oldConfig: string, newConfig: string) => void;
|
updateAlertmanagerSettings: (name: string, oldConfig: string, newConfig: string) => void;
|
||||||
resetAlertmanagerSettings: (name: string) => void;
|
resetAlertmanagerSettings: (name: string) => void;
|
||||||
|
|
||||||
|
// this feature toggle is for disabling the "send to external Alertmanagers" feature
|
||||||
|
forwardingDisabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsContext = React.createContext<Context | undefined>(undefined);
|
const SettingsContext = React.createContext<Context | undefined>(undefined);
|
||||||
@ -44,6 +47,8 @@ export const SettingsProvider = (props: PropsWithChildren) => {
|
|||||||
// this will be used to infer the correct "delivery mode" and update the correct list of datasources with "wantsAlertsReceived"
|
// this will be used to infer the correct "delivery mode" and update the correct list of datasources with "wantsAlertsReceived"
|
||||||
let interestedAlertmanagers: string[] = [];
|
let interestedAlertmanagers: string[] = [];
|
||||||
|
|
||||||
|
const forwardingDisabled = config.featureToggles.alertingDisableSendAlertsExternal === true;
|
||||||
|
|
||||||
const { currentData: configuration, isLoading: isLoadingConfiguration } =
|
const { currentData: configuration, isLoading: isLoadingConfiguration } =
|
||||||
alertmanagerApi.endpoints.getGrafanaAlertingConfiguration.useQuery();
|
alertmanagerApi.endpoints.getGrafanaAlertingConfiguration.useQuery();
|
||||||
|
|
||||||
@ -115,9 +120,12 @@ export const SettingsProvider = (props: PropsWithChildren) => {
|
|||||||
|
|
||||||
const value: Context = {
|
const value: Context = {
|
||||||
configuration,
|
configuration,
|
||||||
|
forwardingDisabled,
|
||||||
externalAlertmanagerDataSourcesWithStatus: externalAlertmanagersWithStatus,
|
externalAlertmanagerDataSourcesWithStatus: externalAlertmanagersWithStatus,
|
||||||
|
|
||||||
enableAlertmanager,
|
enableAlertmanager,
|
||||||
disableAlertmanager,
|
disableAlertmanager,
|
||||||
|
|
||||||
isLoading: isLoadingConfiguration,
|
isLoading: isLoadingConfiguration,
|
||||||
isUpdating: updateConfigurationState.isLoading || enableOrDisableHandlingGrafanaManagedAlertsState.isLoading,
|
isUpdating: updateConfigurationState.isLoading || enableOrDisableHandlingGrafanaManagedAlertsState.isLoading,
|
||||||
|
|
||||||
|
@ -894,7 +894,7 @@
|
|||||||
"title": "Alerting"
|
"title": "Alerting"
|
||||||
},
|
},
|
||||||
"alerting-admin": {
|
"alerting-admin": {
|
||||||
"subtitle": "Manage Alertmanager configurations and configure where alert instances generated from Grafana managed alert rules are sent",
|
"subtitle": "Manage Alertmanager configurations",
|
||||||
"title": "Settings"
|
"title": "Settings"
|
||||||
},
|
},
|
||||||
"alerting-am-routes": {
|
"alerting-am-routes": {
|
||||||
|
@ -894,7 +894,7 @@
|
|||||||
"title": "Åľęřŧįʼnģ"
|
"title": "Åľęřŧįʼnģ"
|
||||||
},
|
},
|
||||||
"alerting-admin": {
|
"alerting-admin": {
|
||||||
"subtitle": "Mäʼnäģę Åľęřŧmäʼnäģęř čőʼnƒįģūřäŧįőʼnş äʼnđ čőʼnƒįģūřę ŵĥęřę äľęřŧ įʼnşŧäʼnčęş ģęʼnęřäŧęđ ƒřőm Ğřäƒäʼnä mäʼnäģęđ äľęřŧ řūľęş äřę şęʼnŧ",
|
"subtitle": "Mäʼnäģę Åľęřŧmäʼnäģęř čőʼnƒįģūřäŧįőʼnş",
|
||||||
"title": "Ŝęŧŧįʼnģş"
|
"title": "Ŝęŧŧįʼnģş"
|
||||||
},
|
},
|
||||||
"alerting-am-routes": {
|
"alerting-am-routes": {
|
||||||
|
Loading…
Reference in New Issue
Block a user