diff --git a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx index 1d5a7b594b8..25e4ec6355e 100644 --- a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx +++ b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx @@ -22,7 +22,7 @@ jest.mock('app/core/config', () => ({ jest.mock('app/features/plugins/datasource_srv', () => ({ getDatasourceSrv: () => ({ - get: () => ({}), + get: () => Promise.resolve({}), getInstanceSettings: () => ({}), }), })); @@ -67,7 +67,7 @@ describe('DashboardQueryEditor', () => { jest.spyOn(getDashboardSrv(), 'getCurrent').mockImplementation(() => mockDashboard); }); - it('does not show a panel with the SHARED_DASHBOARD_QUERY datasource as an option in the dropdown', () => { + it('does not show a panel with the SHARED_DASHBOARD_QUERY datasource as an option in the dropdown', async () => { render( { /> ); const select = screen.getByText('Choose panel'); + userEvent.click(select); - expect(screen.getByText('My first panel')).toBeInTheDocument(); - expect(screen.getByText('Another panel')).toBeInTheDocument(); + + const myFirstPanel = await screen.findByText('My first panel'); + expect(myFirstPanel).toBeInTheDocument(); + + const anotherPanel = await screen.findByText('Another panel'); + expect(anotherPanel).toBeInTheDocument(); + expect(screen.queryByText('A dashboard query panel')).not.toBeInTheDocument(); }); - it('does not show the current panelInEdit as an option in the dropdown', () => { + it('does not show the current panelInEdit as an option in the dropdown', async () => { mockDashboard.initEditPanel(mockDashboard.panels[0]); render( { /> ); const select = screen.getByText('Choose panel'); + userEvent.click(select); + expect(screen.queryByText('My first panel')).not.toBeInTheDocument(); - expect(screen.getByText('Another panel')).toBeInTheDocument(); + + const anotherPanel = await screen.findByText('Another panel'); + expect(anotherPanel).toBeInTheDocument(); + expect(screen.queryByText('A dashboard query panel')).not.toBeInTheDocument(); }); }); diff --git a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx index 4705abb4f02..6ce8d461fb7 100644 --- a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx +++ b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx @@ -1,9 +1,12 @@ -import React, { PureComponent } from 'react'; -import { LegacyForms, VerticalGroup } from '@grafana/ui'; -import { DataQuery, PanelData, SelectableValue } from '@grafana/data'; +import React, { useCallback, useMemo } from 'react'; import { css } from '@emotion/css'; +import pluralize from 'pluralize'; +import { useId } from '@react-aria/utils'; +import { useAsync } from 'react-use'; + +import { InlineField, Select, useStyles2, VerticalGroup } from '@grafana/ui'; +import { DataQuery, GrafanaTheme2, PanelData, SelectableValue } from '@grafana/data'; -import { DashboardQuery, ResultInfo, SHARED_DASHBOARD_QUERY } from './types'; import config from 'app/core/config'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { PanelModel } from 'app/features/dashboard/state'; @@ -11,7 +14,7 @@ import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { filterPanelDataToQuery } from 'app/features/query/components/QueryEditorRow'; import { DashboardQueryRow } from './DashboardQueryRow'; -const { Select } = LegacyForms; +import { DashboardQuery, ResultInfo, SHARED_DASHBOARD_QUERY } from './types'; function getQueryDisplayText(query: DataQuery): string { return JSON.stringify(query); @@ -24,166 +27,136 @@ interface Props { onRunQueries: () => void; } -type State = { - defaultDatasource: string; - results: ResultInfo[]; -}; - -export class DashboardQueryEditor extends PureComponent { - constructor(props: Props) { - super(props); - this.state = { - defaultDatasource: '', - results: [], - }; - } - - getQuery(): DashboardQuery { - return this.props.queries[0] as DashboardQuery; - } - - async componentDidMount() { - await this.updateState(); - } - - async componentDidUpdate(prevProps: Props) { - const { panelData, queries } = this.props; - - if (prevProps.panelData !== panelData || prevProps.queries !== queries) { - await this.updateState(); - } - } - - async updateState() { - const { panelData, queries } = this.props; - +export function DashboardQueryEditor({ panelData, queries, onChange, onRunQueries }: Props) { + const { value: defaultDatasource } = useAsync(() => getDatasourceSrv().get()); + const { value: results, loading: loadingResults } = useAsync(async (): Promise => { const query = queries[0] as DashboardQuery; - const defaultDS = await getDatasourceSrv().get(); const dashboard = getDashboardSrv().getCurrent(); const panel = dashboard?.getPanelById(query.panelId ?? -124134); if (!panel) { - this.setState({ defaultDatasource: defaultDS.name }); - return; + return []; } const mainDS = await getDatasourceSrv().get(panel.datasource); - const info: ResultInfo[] = []; + return Promise.all( + panel.targets.map(async (query) => { + const ds = query.datasource ? await getDatasourceSrv().get(query.datasource) : mainDS; + const fmt = ds.getQueryDisplayText || getQueryDisplayText; - for (const query of panel.targets) { - const ds = query.datasource ? await getDatasourceSrv().get(query.datasource) : mainDS; - const fmt = ds.getQueryDisplayText ? ds.getQueryDisplayText : getQueryDisplayText; + const queryData = filterPanelDataToQuery(panelData, query.refId) ?? panelData; - const qData = filterPanelDataToQuery(panelData, query.refId); - const queryData = qData ? qData : panelData; - - info.push({ - refId: query.refId, - query: fmt(query), - img: ds.meta.info.logos.small, - data: queryData.series, - error: queryData.error, - }); - } - - this.setState({ defaultDatasource: defaultDS.name, results: info }); - } - - onPanelChanged = (id: number) => { - const query = this.getQuery(); - - this.props.onChange([ - { - ...query, - panelId: id, - } as DashboardQuery, - ]); - this.props.onRunQueries(); - }; - - renderQueryData(editURL: string) { - const { results } = this.state; - - return ( - - {results.map((target, index) => { - return ; - })} - - ); - } - - getPanelDescription = (panel: PanelModel): string => { - const { defaultDatasource } = this.state; - const datasource = panel.datasource ? panel.datasource : defaultDatasource; - const dsname = getDatasourceSrv().getInstanceSettings(datasource)?.name; - - if (panel.targets.length === 1) { - return '1 query to ' + dsname; - } - - return panel.targets.length + ' queries to ' + dsname; - }; - - render() { - const dashboard = getDashboardSrv().getCurrent(); - if (!dashboard) { - return null; - } - - const query = this.getQuery(); - - let selected: SelectableValue | undefined; - const panels: Array> = []; - - for (const panel of dashboard.panels) { - const plugin = config.panels[panel.type]; - if (!plugin) { - continue; - } - - if (panel.targets && panel.id !== dashboard.panelInEdit?.id && panel.datasource?.uid !== SHARED_DASHBOARD_QUERY) { - const item = { - value: panel.id, - label: panel.title ? panel.title : 'Panel ' + panel.id, - description: this.getPanelDescription(panel), - imgUrl: plugin.info.logos.small, + return { + refId: query.refId, + query: fmt(query), + img: ds.meta.info.logos.small, + data: queryData.series, + error: queryData.error, }; + }) + ); + }, [panelData, queries]); - panels.push(item); + const query = queries[0] as DashboardQuery; - if (query.panelId === panel.id) { - selected = item; - } - } - } + const onPanelChanged = useCallback( + (id: number) => { + onChange([ + { + ...query, + panelId: id, + } as DashboardQuery, + ]); + onRunQueries(); + }, + [query, onChange, onRunQueries] + ); - if (panels.length < 1) { - return ( -
- This dashboard does not have other panels. Add queries to other panels and try again -
- ); - } + const getPanelDescription = useCallback( + (panel: PanelModel): string => { + const datasource = panel.datasource ?? defaultDatasource; + const dsname = getDatasourceSrv().getInstanceSettings(datasource)?.name; + const queryCount = panel.targets.length; + return `${queryCount} ${pluralize('query', queryCount)} to ${dsname}`; + }, + [defaultDatasource] + ); - // Same as current URL, but different panelId - const editURL = `d/${dashboard.uid}/${dashboard.title}?&editPanel=${query.panelId}`; + const dashboard = getDashboardSrv().getCurrent(); + const panels: Array> = useMemo( + () => + dashboard?.panels + .filter( + (panel) => + config.panels[panel.type] && + panel.targets && + panel.id !== dashboard.panelInEdit?.id && + panel.datasource?.uid !== SHARED_DASHBOARD_QUERY + ) + .map((panel) => ({ + value: panel.id, + label: panel.title ?? 'Panel ' + panel.id, + description: getPanelDescription(panel), + imgUrl: config.panels[panel.type].info.logos.small, + })) ?? [], + [dashboard, getPanelDescription] + ); + const styles = useStyles2(getStyles); + const selectId = useId(); + + if (!dashboard) { + return null; + } + + if (panels.length < 1) { return ( -
-
-
Use results from panel
- onPanelChanged(item.value!)} + /> + + + {results && !loadingResults && ( +
+ {query.panelId && ( + + {results.map((target, i) => ( + + ))} + + )} +
+ )} + + ); +} + +function getStyles(theme: GrafanaTheme2) { + return { + results: css({ + padding: theme.spacing(2), + }), + noQueriesText: css({ + padding: theme.spacing(1.25), + }), + }; }