DashboardQueryEditor: Refactor and convert to functional component (#45002)

* DashboardQueryEditor: Refactor and convert to functional component
This commit is contained in:
kay delaney 2022-02-09 11:07:36 +00:00 committed by GitHub
parent ef11e783f1
commit b615d0558b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 138 additions and 154 deletions

View File

@ -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(
<DashboardQueryEditor
queries={mockQueries}
@ -77,13 +77,19 @@ describe('DashboardQueryEditor', () => {
/>
);
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(
<DashboardQueryEditor
@ -94,9 +100,14 @@ describe('DashboardQueryEditor', () => {
/>
);
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();
});
});

View File

@ -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<Props, State> {
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<ResultInfo[]> => {
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 (
<VerticalGroup spacing="sm">
{results.map((target, index) => {
return <DashboardQueryRow editURL={editURL} target={target} key={`DashboardQueryRow-${index}`} />;
})}
</VerticalGroup>
);
}
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<number> | undefined;
const panels: Array<SelectableValue<number>> = [];
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 (
<div className={css({ padding: '10px' })}>
This dashboard does not have other panels. Add queries to other panels and try again
</div>
);
}
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<SelectableValue<number>> = 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 (
<div>
<div className="gf-form">
<div className="gf-form-label">Use results from panel</div>
<Select
menuShouldPortal
placeholder="Choose panel"
isSearchable={true}
options={panels}
value={selected}
onChange={(item) => this.onPanelChanged(item.value!)}
/>
</div>
<div className={css({ padding: '16px' })}>{query.panelId && this.renderQueryData(editURL)}</div>
</div>
<p className={styles.noQueriesText}>
This dashboard does not have any other panels. Add queries to other panels and try again.
</p>
);
}
const selected = panels.find((panel) => panel.value === query.panelId);
// Same as current URL, but different panelId
const editURL = `d/${dashboard.uid}/${dashboard.title}?&editPanel=${query.panelId}`;
return (
<>
<InlineField label="Use results from panel" grow>
<Select
menuShouldPortal
inputId={selectId}
placeholder="Choose panel"
isSearchable={true}
options={panels}
value={selected}
onChange={(item) => onPanelChanged(item.value!)}
/>
</InlineField>
{results && !loadingResults && (
<div className={styles.results}>
{query.panelId && (
<VerticalGroup spacing="sm">
{results.map((target, i) => (
<DashboardQueryRow editURL={editURL} target={target} key={`DashboardQueryRow-${i}`} />
))}
</VerticalGroup>
)}
</div>
)}
</>
);
}
function getStyles(theme: GrafanaTheme2) {
return {
results: css({
padding: theme.spacing(2),
}),
noQueriesText: css({
padding: theme.spacing(1.25),
}),
};
}