{panelTitle}
@@ -187,11 +175,11 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
contentClassName={styles.modalContent}
>
{/* This alert shows if the selected dashboard is not found in the first page of dashboards */}
- {!selectedDashboardIsInPageResult && dashboardUid && (
+ {!selectedDashboardIsInPageResult && dashboardUid && dashboardModel && (
- Dashboard: {dashboardResult?.dashboard.title} ({dashboardResult?.dashboard.uid}) in folder{' '}
- {dashboardResult?.meta.folderTitle ?? 'General'}
+ Dashboard: {dashboardModel.title} ({dashboardModel.uid}) in folder{' '}
+ {dashboardModel.meta?.folderTitle ?? 'General'}
{currentPanel && (
@@ -274,6 +262,20 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
);
};
+export function getVisualPanels(dashboardModel: DashboardModel | undefined) {
+ if (!dashboardModel) {
+ return [];
+ }
+
+ const panelsWithoutRows = dashboardModel.panels.filter((panel) => panel.type !== 'row');
+ const panelsNestedInRows = dashboardModel.panels
+ .filter((rowPanel) => rowPanel.collapsed)
+ .flatMap((collapsedRow) => collapsedRow.panels ?? []);
+
+ const allDashboardPanels = [...panelsWithoutRows, ...panelsNestedInRows];
+ return allDashboardPanels;
+}
+
const isValidPanelIdentifier = (panel: PanelDTO): boolean => {
return typeof panel.id === 'number' && typeof panel.type === 'string';
};
diff --git a/public/app/features/alerting/unified/components/rule-editor/useDashboardQuery.ts b/public/app/features/alerting/unified/components/rule-editor/useDashboardQuery.ts
new file mode 100644
index 00000000000..56db0ce526f
--- /dev/null
+++ b/public/app/features/alerting/unified/components/rule-editor/useDashboardQuery.ts
@@ -0,0 +1,27 @@
+import memoizeOne from 'memoize-one';
+
+import { DashboardDTO } from '../../../../../types';
+import { DashboardModel } from '../../../../dashboard/state';
+import { dashboardApi } from '../../api/dashboardApi';
+
+const convertToDashboardModel = memoizeOne((dashboardDTO: DashboardDTO) => {
+ // RTKQuery freezes all returned objects. DashboardModel constructor runs migrations which might change the internal object
+ // Hence we need to add structuredClone to make a deep copy of the API response object
+ const { dashboard, meta } = structuredClone(dashboardDTO);
+ return new DashboardModel(dashboard, meta);
+});
+
+export function useDashboardQuery(dashboardUid?: string) {
+ const queryData = dashboardApi.endpoints.dashboard.useQuery(
+ { uid: dashboardUid ?? '' },
+ {
+ skip: !dashboardUid,
+ selectFromResult: ({ currentData, data, ...rest }) => ({
+ dashboardModel: currentData ? convertToDashboardModel(currentData) : undefined,
+ ...rest,
+ }),
+ }
+ );
+
+ return queryData;
+}
diff --git a/public/app/features/alerting/unified/mockApi.ts b/public/app/features/alerting/unified/mockApi.ts
index 87814055a13..63cca9d20a9 100644
--- a/public/app/features/alerting/unified/mockApi.ts
+++ b/public/app/features/alerting/unified/mockApi.ts
@@ -22,8 +22,8 @@ import {
MatcherOperator,
Route,
} from '../../../plugins/datasource/alertmanager/types';
-import { FolderDTO, NotifierDTO } from '../../../types';
-import { DashboardSearchHit } from '../../search/types';
+import { DashboardDTO, FolderDTO, NotifierDTO } from '../../../types';
+import { DashboardSearchItem } from '../../search/types';
import { CreateIntegrationDTO, NewOnCallIntegrationDTO, OnCallIntegrationDTO } from './api/onCallApi';
import { AlertingQueryResponse } from './state/AlertingQueryRunner';
@@ -397,12 +397,27 @@ export function mockFolderApi(server: SetupServer) {
export function mockSearchApi(server: SetupServer) {
return {
- search: (results: DashboardSearchHit[]) => {
+ search: (results: DashboardSearchItem[]) => {
server.use(rest.get(`/api/search`, (_, res, ctx) => res(ctx.status(200), ctx.json(results))));
},
};
}
+export function mockDashboardApi(server: SetupServer) {
+ return {
+ search: (results: DashboardSearchItem[]) => {
+ server.use(rest.get(`/api/search`, (_, res, ctx) => res(ctx.status(200), ctx.json(results))));
+ },
+ dashboard: (response: DashboardDTO) => {
+ server.use(
+ rest.get(`/api/dashboards/uid/${response.dashboard.uid}`, (_, res, ctx) =>
+ res(ctx.status(200), ctx.json(response))
+ )
+ );
+ },
+ };
+}
+
// Creates a MSW server and sets up beforeAll, afterAll and beforeEach handlers for it
export function setupMswServer() {
const server = setupServer();
diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts
index 132aba7d0a7..2e0ea529721 100644
--- a/public/app/features/alerting/unified/mocks.ts
+++ b/public/app/features/alerting/unified/mocks.ts
@@ -16,6 +16,7 @@ import {
TestDataSourceResponse,
} from '@grafana/data';
import { config, DataSourceSrv, GetDataSourceListFilters } from '@grafana/runtime';
+import { defaultDashboard } from '@grafana/schema';
import { contextSrv } from 'app/core/services/context_srv';
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
import {
@@ -30,7 +31,7 @@ import {
SilenceState,
} from 'app/plugins/datasource/alertmanager/types';
import { configureStore } from 'app/store/configureStore';
-import { AccessControlAction, FolderDTO, NotifiersState, ReceiversState, StoreState } from 'app/types';
+import { AccessControlAction, DashboardDTO, FolderDTO, NotifiersState, ReceiversState, StoreState } from 'app/types';
import {
Alert,
AlertingRule,
@@ -55,6 +56,8 @@ import {
RulerRulesConfigDTO,
} from 'app/types/unified-alerting-dto';
+import { DashboardSearchItem, DashboardSearchItemType } from '../../search/types';
+
let nextDataSourceId = 1;
export function mockDataSource(
@@ -684,6 +687,36 @@ export function mockAlertWithState(state: GrafanaAlertState, labels?: {}): Alert
return { activeAt: '', annotations: {}, labels: labels || {}, state: state, value: '' };
}
+export function mockDashboardSearchItem(searchItem: Partial) {
+ return {
+ title: '',
+ uid: '',
+ type: DashboardSearchItemType.DashDB,
+ url: '',
+ uri: '',
+ items: [],
+ tags: [],
+ slug: '',
+ isStarred: false,
+ ...searchItem,
+ };
+}
+
+export function mockDashboardDto(
+ dashboard: Partial,
+ meta?: Partial
+): DashboardDTO {
+ return {
+ dashboard: {
+ uid: 'dashboard-test',
+ title: 'Dashboard test',
+ schemaVersion: defaultDashboard.schemaVersion,
+ ...dashboard,
+ },
+ meta: { ...meta },
+ };
+}
+
export const onCallPluginMetaMock: PluginMeta = {
name: 'Grafana OnCall',
id: 'grafana-oncall-app',
diff --git a/public/app/features/alerting/unified/mocks/grafanaApi.ts b/public/app/features/alerting/unified/mocks/grafanaApi.ts
deleted file mode 100644
index 1062de6749f..00000000000
--- a/public/app/features/alerting/unified/mocks/grafanaApi.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { rest } from 'msw';
-import { SetupServer } from 'msw/node';
-
-import { DashboardSearchItem } from '../../../search/types';
-
-export function mockSearchApiResponse(server: SetupServer, searchResult: DashboardSearchItem[]) {
- server.use(rest.get('/api/search', (req, res, ctx) => res(ctx.json(searchResult))));
-}