mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 18:30:41 -06:00
Alerting: Disable alerting incompatible panels in the dashboard picker (#65341)
* Hide dashboard hint when dashboard already selected * Disable panels of types other than graph and timeseries * Add a test checking disabled panels * Make all panels selectable * Fix tests
This commit is contained in:
parent
bf4906ed34
commit
bde77e4f79
@ -1,4 +1,4 @@
|
||||
import { findByText, findByTitle, render } from '@testing-library/react';
|
||||
import { findByRole, findByText, findByTitle, getByTestId, render } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
@ -107,8 +107,8 @@ describe('AnnotationsField', function () {
|
||||
title: 'My dashboard',
|
||||
uid: 'dash-test-uid',
|
||||
panels: [
|
||||
{ id: 1, title: 'First panel' },
|
||||
{ id: 2, title: 'Second panel' },
|
||||
{ id: 1, title: 'First panel', type: 'timeseries' },
|
||||
{ id: 2, title: 'Second panel', type: 'timeseries' },
|
||||
],
|
||||
})
|
||||
);
|
||||
@ -137,8 +137,8 @@ describe('AnnotationsField', function () {
|
||||
title: 'My dashboard',
|
||||
uid: 'dash-test-uid',
|
||||
panels: [
|
||||
{ id: 1, title: 'First panel' },
|
||||
{ id: 2, title: 'Second panel' },
|
||||
{ id: 1, title: 'First panel', type: 'graph' },
|
||||
{ id: 2, title: 'Second panel', type: 'graph' },
|
||||
],
|
||||
})
|
||||
);
|
||||
@ -171,7 +171,7 @@ describe('AnnotationsField', function () {
|
||||
|
||||
// this test _should_ work in theory but something is stopping the 'onClick' function on the dashboard item
|
||||
// to trigger "handleDashboardChange" – skipping it for now but has been manually tested.
|
||||
it.skip('should update existing dashboard and panel identifies', async function () {
|
||||
it('should update existing dashboard and panel identifies', async function () {
|
||||
mockSearchApiResponse(server, [
|
||||
mockDashboardSearchItem({ title: 'My dashboard', uid: 'dash-test-uid', type: DashboardSearchItemType.DashDB }),
|
||||
mockDashboardSearchItem({
|
||||
@ -186,8 +186,8 @@ describe('AnnotationsField', function () {
|
||||
title: 'My dashboard',
|
||||
uid: 'dash-test-uid',
|
||||
panels: [
|
||||
{ id: 1, title: 'First panel' },
|
||||
{ id: 2, title: 'Second panel' },
|
||||
{ id: 1, title: 'First panel', type: 'timeseries' },
|
||||
{ id: 2, title: 'Second panel', type: 'timeseries' },
|
||||
],
|
||||
})
|
||||
);
|
||||
@ -195,7 +195,7 @@ describe('AnnotationsField', function () {
|
||||
mockDashboardDto({
|
||||
title: 'My other dashboard',
|
||||
uid: 'dash-other-uid',
|
||||
panels: [{ id: 3, title: 'Third panel' }],
|
||||
panels: [{ id: 3, title: 'Third panel', type: 'timeseries' }],
|
||||
})
|
||||
);
|
||||
|
||||
@ -216,10 +216,12 @@ describe('AnnotationsField', function () {
|
||||
expect(annotationValueElements[0]).toHaveTextContent('dash-test-uid');
|
||||
expect(annotationValueElements[1]).toHaveTextContent('1');
|
||||
|
||||
const { confirmButton, dialog } = ui.dashboardPicker;
|
||||
|
||||
await user.click(ui.setDashboardButton.get());
|
||||
await user.click(await findByTitle(ui.dashboardPicker.dialog.get(), 'My other dashboard'));
|
||||
await user.click(await findByText(ui.dashboardPicker.dialog.get(), 'Third panel'));
|
||||
await user.click(ui.dashboardPicker.confirmButton.get());
|
||||
await user.click(await findByRole(dialog.get(), 'button', { name: /My other dashboard/ }));
|
||||
await user.click(await findByRole(dialog.get(), 'button', { name: /Third panel/ }));
|
||||
await user.click(confirmButton.get());
|
||||
|
||||
expect(ui.dashboardPicker.dialog.query()).not.toBeInTheDocument();
|
||||
|
||||
@ -235,6 +237,36 @@ describe('AnnotationsField', function () {
|
||||
expect(annotationKeyElements[1]).toHaveTextContent('Panel ID');
|
||||
expect(annotationValueElements[1]).toHaveTextContent('3');
|
||||
});
|
||||
|
||||
it('should render warning icon for panels of type other than graph and timeseries', async function () {
|
||||
mockSearchApiResponse(server, [
|
||||
mockDashboardSearchItem({ title: 'My dashboard', uid: 'dash-test-uid', type: DashboardSearchItemType.DashDB }),
|
||||
]);
|
||||
|
||||
mockGetDashboardResponse(
|
||||
mockDashboardDto({
|
||||
title: 'My dashboard',
|
||||
uid: 'dash-test-uid',
|
||||
panels: [
|
||||
{ id: 1, title: 'First panel', type: 'bar' },
|
||||
{ id: 2, title: 'Second panel', type: 'graph' },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<FormWrapper formValues={{ annotations: [] }} />);
|
||||
|
||||
const { dialog } = ui.dashboardPicker;
|
||||
|
||||
await user.click(ui.setDashboardButton.get());
|
||||
await user.click(await findByTitle(dialog.get(), 'My dashboard'));
|
||||
|
||||
const warnedPanel = await findByRole(dialog.get(), 'button', { name: /First panel/ });
|
||||
|
||||
expect(getByTestId(warnedPanel, 'warning-icon')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
Button,
|
||||
Alert,
|
||||
clearButtonStyles,
|
||||
Tooltip,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import { dashboardApi } from '../../api/dashboardApi';
|
||||
@ -21,6 +22,7 @@ import { dashboardApi } from '../../api/dashboardApi';
|
||||
export interface PanelDTO {
|
||||
id: number;
|
||||
title?: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
function panelSort(a: PanelDTO, b: PanelDTO) {
|
||||
@ -71,7 +73,7 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
|
||||
|
||||
const filteredPanels =
|
||||
dashboardResult?.dashboard?.panels
|
||||
?.filter((panel): panel is PanelDTO => typeof panel.id === 'number')
|
||||
?.filter((panel): panel is PanelDTO => typeof panel.id === 'number' && typeof panel.type === 'string')
|
||||
?.filter((panel) => panel.title?.toLowerCase().includes(panelFilter.toLowerCase()))
|
||||
.sort(panelSort) ?? [];
|
||||
|
||||
@ -115,7 +117,7 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
|
||||
className={cx(styles.rowButton, { [styles.rowOdd]: index % 2 === 1, [styles.rowSelected]: isSelected })}
|
||||
onClick={() => handleDashboardChange(dashboard.uid)}
|
||||
>
|
||||
<div className={styles.dashboardTitle}>{dashboard.title}</div>
|
||||
<div className={cx(styles.dashboardTitle, styles.rowButtonTitle)}>{dashboard.title}</div>
|
||||
<div className={styles.dashboardFolder}>
|
||||
<Icon name="folder" /> {dashboard.folderTitle ?? 'General'}
|
||||
</div>
|
||||
@ -125,16 +127,28 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
|
||||
|
||||
const PanelRow = ({ index, style }: { index: number; style: CSSProperties }) => {
|
||||
const panel = filteredPanels[index];
|
||||
const panelTitle = panel.title || '<No title>';
|
||||
const isSelected = selectedPanelId === panel.id.toString();
|
||||
const isAlertingCompatible = panel.type === 'graph' || panel.type === 'timeseries';
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
style={style}
|
||||
className={cx(styles.rowButton, { [styles.rowOdd]: index % 2 === 1, [styles.rowSelected]: isSelected })}
|
||||
className={cx(styles.rowButton, styles.panelButton, {
|
||||
[styles.rowOdd]: index % 2 === 1,
|
||||
[styles.rowSelected]: isSelected,
|
||||
})}
|
||||
onClick={() => setSelectedPanelId(panel.id.toString())}
|
||||
>
|
||||
{panel.title || '<No title>'}
|
||||
<div className={styles.rowButtonTitle} title={panelTitle}>
|
||||
{panelTitle}
|
||||
</div>
|
||||
{!isAlertingCompatible && (
|
||||
<Tooltip content="Alert tab will be disabled for this panel. It is only supported on graph and timeseries panels">
|
||||
<Icon name="exclamation-triangle" className={styles.warnIcon} data-testid="warning-icon" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@ -195,12 +209,16 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
|
||||
</div>
|
||||
|
||||
<div className={styles.column}>
|
||||
{!dashboardUid && !isDashboardFetching && <div>Select a dashboard to get a list of available panels</div>}
|
||||
{!selectedDashboardUid && !isDashboardFetching && (
|
||||
<div className={styles.selectDashboardPlaceholder}>
|
||||
<div>Select a dashboard to get a list of available panels</div>
|
||||
</div>
|
||||
)}
|
||||
{isDashboardFetching && (
|
||||
<LoadingPlaceholder text="Loading dashboard..." className={styles.loadingPlaceholder} />
|
||||
)}
|
||||
|
||||
{!isDashboardFetching && (
|
||||
{selectedDashboardUid && !isDashboardFetching && (
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
<FixedSizeList itemSize={32} height={height} width={width} itemCount={filteredPanels.length}>
|
||||
@ -269,6 +287,15 @@ const getPickerStyles = (theme: GrafanaTheme2) => {
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
color: ${theme.colors.text.disabled};
|
||||
}
|
||||
`,
|
||||
rowButtonTitle: css`
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
`,
|
||||
rowSelected: css`
|
||||
border-color: ${theme.colors.primary.border};
|
||||
@ -276,12 +303,27 @@ const getPickerStyles = (theme: GrafanaTheme2) => {
|
||||
rowOdd: css`
|
||||
background-color: ${theme.colors.background.secondary};
|
||||
`,
|
||||
panelButton: css`
|
||||
display: flex;
|
||||
gap: ${theme.spacing(1)};
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`,
|
||||
loadingPlaceholder: css`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`,
|
||||
selectDashboardPlaceholder: css`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-weight: ${theme.typography.fontWeightBold};
|
||||
`,
|
||||
modal: css`
|
||||
height: 100%;
|
||||
`,
|
||||
@ -293,5 +335,8 @@ const getPickerStyles = (theme: GrafanaTheme2) => {
|
||||
modalAlert: css`
|
||||
flex-grow: 0;
|
||||
`,
|
||||
warnIcon: css`
|
||||
fill: ${theme.colors.warning.main};
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user