Alerting: Remove CTA button from read-only notification policy page (#41096)

* don't show cta button for read-only prometheus alertmanager

* grammar fix

Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>

Co-authored-by: Peter Holmberg <peterholmberg@users.noreply.github.com>
Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
Co-authored-by: Peter Holmberg <peter.hlmbrg@gmail.com>
This commit is contained in:
Domas 2021-11-09 14:52:49 +02:00 committed by GitHub
parent 01bc59e432
commit 345af8374c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 48 deletions

View File

@ -79,6 +79,7 @@ const ui = {
editRouteButton: byLabelText('Edit route'), editRouteButton: byLabelText('Edit route'),
deleteRouteButton: byLabelText('Delete route'), deleteRouteButton: byLabelText('Delete route'),
newPolicyButton: byRole('button', { name: /New policy/ }), newPolicyButton: byRole('button', { name: /New policy/ }),
newPolicyCTAButton: byRole('button', { name: /New specific policy/ }),
receiverSelect: byTestId('am-receiver-select'), receiverSelect: byTestId('am-receiver-select'),
groupSelect: byTestId('am-group-select'), groupSelect: byTestId('am-group-select'),
@ -508,6 +509,25 @@ describe('AmRoutes', () => {
expect(mocks.api.fetchAlertManagerConfig).not.toHaveBeenCalled(); expect(mocks.api.fetchAlertManagerConfig).not.toHaveBeenCalled();
expect(mocks.api.fetchStatus).toHaveBeenCalledTimes(1); expect(mocks.api.fetchStatus).toHaveBeenCalledTimes(1);
}); });
it('Prometheus Alertmanager has no CTA button if there are no specific policies', async () => {
mocks.api.fetchStatus.mockResolvedValue({
...someCloudAlertManagerStatus,
config: {
...someCloudAlertManagerConfig.alertmanager_config,
route: {
...someCloudAlertManagerConfig.alertmanager_config.route,
routes: undefined,
},
},
});
await renderAmRoutes(dataSources.promAlertManager.name);
const rootRouteContainer = await ui.rootRouteContainer.find();
expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument();
expect(ui.newPolicyCTAButton.query()).not.toBeInTheDocument();
expect(mocks.api.fetchAlertManagerConfig).not.toHaveBeenCalled();
expect(mocks.api.fetchStatus).toHaveBeenCalledTimes(1);
});
}); });
const clickSelectOption = async (selectElement: HTMLElement, optionText: string): Promise<void> => { const clickSelectOption = async (selectElement: HTMLElement, optionText: string): Promise<void> => {

View File

@ -1,43 +1,12 @@
import React, { ButtonHTMLAttributes, FC } from 'react'; import React, { FC } from 'react';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
import { Button, ButtonVariant, IconName, useStyles } from '@grafana/ui'; import { useStyles } from '@grafana/ui';
export interface EmptyAreaProps { export const EmptyArea: FC = ({ children }) => {
buttonLabel: string;
onButtonClick: ButtonHTMLAttributes<HTMLButtonElement>['onClick'];
text: string;
buttonIcon?: IconName;
buttonSize?: 'xs' | 'sm' | 'md' | 'lg';
buttonVariant?: ButtonVariant;
}
export const EmptyArea: FC<EmptyAreaProps> = ({
buttonIcon,
buttonLabel,
buttonSize = 'lg',
buttonVariant = 'primary',
onButtonClick,
text,
}) => {
const styles = useStyles(getStyles); const styles = useStyles(getStyles);
return ( return <div className={styles.container}>{children}</div>;
<div className={styles.container}>
<p className={styles.text}>{text}</p>
<Button
className={styles.button}
icon={buttonIcon}
onClick={onButtonClick}
size={buttonSize}
type="button"
variant={buttonVariant}
>
{buttonLabel}
</Button>
</div>
);
}; };
const getStyles = (theme: GrafanaTheme) => { const getStyles = (theme: GrafanaTheme) => {
@ -48,11 +17,5 @@ const getStyles = (theme: GrafanaTheme) => {
padding: ${theme.spacing.xl}; padding: ${theme.spacing.xl};
text-align: center; text-align: center;
`, `,
text: css`
margin-bottom: ${theme.spacing.md};
`,
button: css`
margin: ${theme.spacing.md} 0 ${theme.spacing.sm};
`,
}; };
}; };

View File

@ -0,0 +1,61 @@
import React, { ButtonHTMLAttributes, FC } from 'react';
import { css } from '@emotion/css';
import { GrafanaTheme } from '@grafana/data';
import { Button, ButtonVariant, IconName, useStyles } from '@grafana/ui';
import { EmptyArea } from './EmptyArea';
export interface EmptyAreaWithCTAProps {
buttonLabel: string;
onButtonClick: ButtonHTMLAttributes<HTMLButtonElement>['onClick'];
text: string;
buttonIcon?: IconName;
buttonSize?: 'xs' | 'sm' | 'md' | 'lg';
buttonVariant?: ButtonVariant;
}
export const EmptyAreaWithCTA: FC<EmptyAreaWithCTAProps> = ({
buttonIcon,
buttonLabel,
buttonSize = 'lg',
buttonVariant = 'primary',
onButtonClick,
text,
}) => {
const styles = useStyles(getStyles);
return (
<EmptyArea>
<>
<p className={styles.text}>{text}</p>
<Button
className={styles.button}
icon={buttonIcon}
onClick={onButtonClick}
size={buttonSize}
type="button"
variant={buttonVariant}
>
{buttonLabel}
</Button>
</>
</EmptyArea>
);
};
const getStyles = (theme: GrafanaTheme) => {
return {
container: css`
background-color: ${theme.colors.bg2};
color: ${theme.colors.textSemiWeak};
padding: ${theme.spacing.xl};
text-align: center;
`,
text: css`
margin-bottom: ${theme.spacing.md};
`,
button: css`
margin: ${theme.spacing.md} 0 ${theme.spacing.sm};
`,
};
};

View File

@ -6,6 +6,7 @@ import { AmRouteReceiver, FormAmRoute } from '../../types/amroutes';
import { emptyArrayFieldMatcher, emptyRoute } from '../../utils/amroutes'; import { emptyArrayFieldMatcher, emptyRoute } from '../../utils/amroutes';
import { EmptyArea } from '../EmptyArea'; import { EmptyArea } from '../EmptyArea';
import { AmRoutesTable } from './AmRoutesTable'; import { AmRoutesTable } from './AmRoutesTable';
import { EmptyAreaWithCTA } from '../EmptyAreaWithCTA';
export interface AmSpecificRoutingProps { export interface AmSpecificRoutingProps {
onChange: (routes: FormAmRoute) => void; onChange: (routes: FormAmRoute) => void;
@ -43,12 +44,18 @@ export const AmSpecificRouting: FC<AmSpecificRoutingProps> = ({
<h5>Specific routing</h5> <h5>Specific routing</h5>
<p>Send specific alerts to chosen contact points, based on matching criteria</p> <p>Send specific alerts to chosen contact points, based on matching criteria</p>
{!routes.receiver ? ( {!routes.receiver ? (
<EmptyArea readOnly ? (
buttonIcon="rocket" <EmptyArea>
buttonLabel="Set a default contact point" <p>There is no default contact point configured for the root route.</p>
onButtonClick={onRootRouteEdit} </EmptyArea>
text="You haven't set a default contact point for the root route yet." ) : (
/> <EmptyAreaWithCTA
buttonIcon="rocket"
buttonLabel="Set a default contact point"
onButtonClick={onRootRouteEdit}
text="You haven't set a default contact point for the root route yet."
/>
)
) : actualRoutes.length > 0 ? ( ) : actualRoutes.length > 0 ? (
<> <>
{!isAddMode && !readOnly && ( {!isAddMode && !readOnly && (
@ -82,8 +89,12 @@ export const AmSpecificRouting: FC<AmSpecificRoutingProps> = ({
routes={actualRoutes} routes={actualRoutes}
/> />
</> </>
) : readOnly ? (
<EmptyArea>
<p>There are no specific policies configured.</p>
</EmptyArea>
) : ( ) : (
<EmptyArea <EmptyAreaWithCTA
buttonIcon="plus" buttonIcon="plus"
buttonLabel="New specific policy" buttonLabel="New specific policy"
onButtonClick={addNewRoute} onButtonClick={addNewRoute}