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 52d279fa5c6..5ca9a009cc2 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 { findByRole, findByText, findByTitle, getByTestId, render } from '@testing-library/react';
+import { findByRole, findByText, findByTitle, getByTestId, queryByText, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
@@ -163,6 +163,71 @@ describe('AnnotationsField', function () {
expect(annotationValueElements[1]).toHaveTextContent('2');
});
+ it('should not show rows as panels', 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: 'Row panel', type: 'row' },
+ { id: 2, title: 'First panel', type: 'timeseries' },
+ ],
+ })
+ );
+
+ const user = userEvent.setup();
+
+ render();
+
+ await user.click(ui.setDashboardButton.get());
+ expect(ui.dashboardPicker.confirmButton.get()).toBeDisabled();
+
+ await user.click(await findByTitle(ui.dashboardPicker.dialog.get(), 'My dashboard'));
+
+ expect(await findByText(ui.dashboardPicker.dialog.get(), 'First panel')).toBeInTheDocument();
+ expect(await queryByText(ui.dashboardPicker.dialog.get(), 'Row panel')).not.toBeInTheDocument();
+ });
+
+ it('should show panels within collapsed rows', 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: 'timeseries' },
+ {
+ id: 2,
+ title: 'Row panel',
+ collapsed: true,
+ type: 'row',
+ panels: [{ id: 3, title: 'Panel within collapsed row', type: 'timeseries' }],
+ },
+ ],
+ })
+ );
+
+ const user = userEvent.setup();
+
+ render();
+
+ await user.click(ui.setDashboardButton.get());
+ expect(ui.dashboardPicker.confirmButton.get()).toBeDisabled();
+
+ await user.click(await findByTitle(ui.dashboardPicker.dialog.get(), 'My dashboard'));
+
+ expect(await findByText(ui.dashboardPicker.dialog.get(), 'First panel')).toBeInTheDocument();
+ expect(await queryByText(ui.dashboardPicker.dialog.get(), 'Row panel')).not.toBeInTheDocument();
+ expect(await findByText(ui.dashboardPicker.dialog.get(), 'Panel within collapsed row')).toBeInTheDocument();
+ });
+
// 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 () {
diff --git a/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.tsx
index 67406197e6c..7617ff84438 100644
--- a/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.tsx
+++ b/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.tsx
@@ -15,7 +15,7 @@ import { Annotation, annotationLabels } from '../../utils/constants';
import AnnotationHeaderField from './AnnotationHeaderField';
import DashboardAnnotationField from './DashboardAnnotationField';
-import { DashboardPicker, PanelDTO } from './DashboardPicker';
+import { DashboardPicker, mergePanels, PanelDTO } from './DashboardPicker';
import { NeedHelpInfo } from './NeedHelpInfo';
import { RuleEditorSection } from './RuleEditorSection';
@@ -53,7 +53,9 @@ const AnnotationsStep = () => {
}
setSelectedDashboard(dashboardResult?.dashboard);
- const currentPanel = dashboardResult?.dashboard?.panels?.find((panel) => panel.id.toString() === selectedPanelId);
+
+ const allPanels = mergePanels(dashboardResult);
+ const currentPanel = allPanels.find((panel) => panel.id.toString() === selectedPanelId);
setSelectedPanel(currentPanel);
}, [selectedPanelId, dashboardResult, isDashboardFetching]);
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 b78338b093a..444325c2535 100644
--- a/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx
+++ b/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx
@@ -17,6 +17,7 @@ import {
Tooltip,
useStyles2,
} from '@grafana/ui';
+import { DashboardDTO } from 'app/types';
import { dashboardApi } from '../../api/dashboardApi';
@@ -47,6 +48,18 @@ interface DashboardPickerProps {
onDismiss: () => void;
}
+export function mergePanels(dashboardResult: DashboardDTO | undefined) {
+ const panels = dashboardResult?.dashboard?.panels?.filter((panel) => panel.type !== 'row') || [];
+ const nestedPanels =
+ dashboardResult?.dashboard?.panels
+ ?.filter((row: { collapsed: boolean }) => row.collapsed)
+ .map((collapsedRow: { panels: PanelDTO[] }) => collapsedRow.panels) || [];
+
+ const allDashboardPanels = [...panels, ...nestedPanels.flat()];
+
+ return allDashboardPanels;
+}
+
export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDismiss }: DashboardPickerProps) => {
const styles = useStyles2(getPickerStyles);
@@ -72,12 +85,14 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
setSelectedPanelId(undefined);
}, []);
+ const allDashboardPanels = mergePanels(dashboardResult);
+
const filteredPanels =
- dashboardResult?.dashboard?.panels
+ allDashboardPanels
?.filter((panel) => panel.title?.toLowerCase().includes(panelFilter.toLowerCase()))
.sort(panelSort) ?? [];
- const currentPanel: PanelDTO | undefined = dashboardResult?.dashboard?.panels?.find(
+ const currentPanel: PanelDTO | undefined = allDashboardPanels.find(
(panel: PanelDTO) => isValidPanelIdentifier(panel) && panel.id?.toString() === selectedPanelId
);