mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
01bc59e432
commit
345af8374c
@ -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> => {
|
||||||
|
@ -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};
|
|
||||||
`,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
@ -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}
|
||||||
|
Loading…
Reference in New Issue
Block a user