From cd4cb967a5247a857031b66908b5f6ca5a08d5f1 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Tue, 4 Apr 2023 09:54:01 +0200 Subject: [PATCH] =?UTF-8?q?[v9.4.x]=20Alerting:=20Disable=20alerting=20inc?= =?UTF-8?q?ompatible=20panels=20in=20the=20dashboard=20picke=E2=80=A6=20(#?= =?UTF-8?q?65780)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 (cherry picked from commit bde77e4f79bc5984e436824b00dfe58ff2098833) * Skip flakey test --- .../rule-editor/AnnotationsField.test.tsx | 54 ++++- .../rule-editor/DashboardPicker.tsx | 194 ++++++++++++------ 2 files changed, 170 insertions(+), 78 deletions(-) diff --git a/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.test.tsx b/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.test.tsx index a5645d048cf..430c84bd53b 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.test.tsx @@ -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' }, ], }) ); @@ -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(); + + 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(); + }); }); }); diff --git a/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx b/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx index ab0fd01aa52..a26569be67c 100644 --- a/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx @@ -5,13 +5,24 @@ import AutoSizer from 'react-virtualized-auto-sizer'; import { FixedSizeList } from 'react-window'; import { GrafanaTheme2 } from '@grafana/data/src'; -import { FilterInput, LoadingPlaceholder, useStyles2, Icon, Modal, Button, Alert } from '@grafana/ui'; +import { + FilterInput, + LoadingPlaceholder, + useStyles2, + Icon, + Modal, + Button, + Alert, + clearButtonStyles, + Tooltip, +} from '@grafana/ui'; import { dashboardApi } from '../../api/dashboardApi'; export interface PanelDTO { id: number; title?: string; + type: string; } function panelSort(a: PanelDTO, b: PanelDTO) { @@ -62,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) ?? []; @@ -102,10 +113,10 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
handleDashboardChange(dashboard.uid)} > -
{dashboard.title}
+
{dashboard.title}
{dashboard.folderTitle ?? 'General'}
@@ -115,16 +126,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 || ''; const isSelected = selectedPanelId === panel.id.toString(); + const isAlertingCompatible = panel.type === 'graph' || panel.type === 'timeseries'; return ( -
setSelectedPanelId(panel.id.toString())} > - {panel.title || ''} -
+
+ {panelTitle} +
+ {!isAlertingCompatible && ( + + + + )} + ); }; @@ -184,12 +207,16 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
- {!dashboardUid && !isDashboardFetching &&
Select a dashboard to get a list of available panels
} + {!selectedDashboardUid && !isDashboardFetching && ( +
+
Select a dashboard to get a list of available panels
+
+ )} {isDashboardFetching && ( )} - {!isDashboardFetching && ( + {selectedDashboardUid && !isDashboardFetching && ( {({ width, height }) => ( @@ -221,60 +248,93 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis ); }; -const getPickerStyles = (theme: GrafanaTheme2) => ({ - container: css` - display: grid; - grid-template-columns: 1fr 1fr; - grid-template-rows: min-content auto; - gap: ${theme.spacing(2)}; - flex: 1; - `, - column: css` - flex: 1 1 auto; - `, - dashboardTitle: css` - height: 22px; - font-weight: ${theme.typography.fontWeightBold}; - `, - dashboardFolder: css` - height: 20px; - font-size: ${theme.typography.bodySmall.fontSize}; - color: ${theme.colors.text.secondary}; - display: flex; - flex-direction: row; - justify-content: flex-start; - column-gap: ${theme.spacing(1)}; - align-items: center; - `, - row: css` - padding: ${theme.spacing(0.5)}; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - cursor: pointer; - border: 2px solid transparent; - `, - rowSelected: css` - border-color: ${theme.colors.primary.border}; - `, - rowOdd: css` - background-color: ${theme.colors.background.secondary}; - `, - loadingPlaceholder: css` - height: 100%; - display: flex; - justify-content: center; - align-items: center; - `, - modal: css` - height: 100%; - `, - modalContent: css` - flex: 1; - display: flex; - flex-direction: column; - `, - modalAlert: css` - flex-grow: 0; - `, -}); +const getPickerStyles = (theme: GrafanaTheme2) => { + const clearButton = clearButtonStyles(theme); + + return { + container: css` + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: min-content auto; + gap: ${theme.spacing(2)}; + flex: 1; + `, + column: css` + flex: 1 1 auto; + `, + dashboardTitle: css` + height: 22px; + font-weight: ${theme.typography.fontWeightBold}; + `, + dashboardFolder: css` + height: 20px; + font-size: ${theme.typography.bodySmall.fontSize}; + color: ${theme.colors.text.secondary}; + display: flex; + flex-direction: row; + justify-content: flex-start; + column-gap: ${theme.spacing(1)}; + align-items: center; + `, + rowButton: css` + ${clearButton}; + padding: ${theme.spacing(0.5)}; + overflow: hidden; + text-overflow: ellipsis; + text-align: left; + 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}; + `, + 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%; + `, + modalContent: css` + flex: 1; + display: flex; + flex-direction: column; + `, + modalAlert: css` + flex-grow: 0; + `, + warnIcon: css` + fill: ${theme.colors.warning.main}; + `, + }; +};