mirror of
https://github.com/grafana/grafana.git
synced 2025-02-20 11:48:34 -06:00
QueryEditors: Refactoring & rewriting out dependency on PanelModel (#29419)
* Removing PanelModel usage from query rows * More work removing dependency on panel model * Before big change to query options * Query options now have no dependency on panel model * Test page is working * Shared query now works again * Rename component * fix after merge * Fixed issue with old angular editors
This commit is contained in:
parent
b8fec209a9
commit
068fef8c7c
@ -1,16 +1,96 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { QueriesTab } from 'app/features/query/components/QueriesTab';
|
||||
import { DashboardModel, PanelModel } from '../../state';
|
||||
import { QueryGroup } from 'app/features/query/components/QueryGroup';
|
||||
import { QueryGroupOptions } from 'app/features/query/components/QueryGroupOptions';
|
||||
import { PanelModel } from '../../state';
|
||||
import { DataQuery, DataSourceSelectItem } from '@grafana/data';
|
||||
import { getLocationSrv } from '@grafana/runtime';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
dashboard: DashboardModel;
|
||||
}
|
||||
|
||||
export class PanelEditorQueries extends PureComponent<Props> {
|
||||
render() {
|
||||
const { panel, dashboard } = this.props;
|
||||
interface State {
|
||||
options: QueryGroupOptions;
|
||||
}
|
||||
|
||||
return <QueriesTab panel={panel} dashboard={dashboard} />;
|
||||
export class PanelEditorQueries extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = { options: this.buildQueryOptions(props) };
|
||||
}
|
||||
|
||||
buildQueryOptions({ panel }: Props): QueryGroupOptions {
|
||||
return {
|
||||
maxDataPoints: panel.maxDataPoints,
|
||||
minInterval: panel.interval,
|
||||
timeRange: {
|
||||
from: panel.timeFrom,
|
||||
shift: panel.timeShift,
|
||||
hide: panel.hideTimeOverride,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
onDataSourceChange = (ds: DataSourceSelectItem, queries: DataQuery[]) => {
|
||||
const { panel } = this.props;
|
||||
|
||||
panel.datasource = ds.value;
|
||||
panel.targets = queries;
|
||||
panel.refresh();
|
||||
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onRunQueries = () => {
|
||||
this.props.panel.refresh();
|
||||
};
|
||||
|
||||
onQueriesChange = (queries: DataQuery[]) => {
|
||||
const { panel } = this.props;
|
||||
|
||||
panel.targets = queries;
|
||||
panel.refresh();
|
||||
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onOpenQueryInspector = () => {
|
||||
getLocationSrv().update({
|
||||
query: { inspect: this.props.panel.id, inspectTab: 'query' },
|
||||
partial: true,
|
||||
});
|
||||
};
|
||||
|
||||
onQueryOptionsChange = (options: QueryGroupOptions) => {
|
||||
const { panel } = this.props;
|
||||
|
||||
panel.timeFrom = options.timeRange?.from;
|
||||
panel.timeShift = options.timeRange?.shift;
|
||||
panel.hideTimeOverride = options.timeRange?.hide;
|
||||
panel.interval = options.minInterval;
|
||||
panel.maxDataPoints = options.maxDataPoints;
|
||||
panel.refresh();
|
||||
|
||||
this.setState({ options: options });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { panel } = this.props;
|
||||
const { options } = this.state;
|
||||
|
||||
return (
|
||||
<QueryGroup
|
||||
dataSourceName={panel.datasource}
|
||||
options={options}
|
||||
queryRunner={panel.getQueryRunner()}
|
||||
queries={panel.targets}
|
||||
onQueriesChange={this.onQueriesChange}
|
||||
onDataSourceChange={this.onDataSourceChange}
|
||||
onRunQueries={this.onRunQueries}
|
||||
onOpenQueryInspector={this.onOpenQueryInspector}
|
||||
onOptionsChange={this.onQueryOptionsChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ export class PanelEditorTabs extends PureComponent<PanelEditorTabsProps> {
|
||||
})}
|
||||
</TabsBar>
|
||||
<TabContent className={styles.tabContent}>
|
||||
{activeTab.id === PanelEditorTabId.Query && <PanelEditorQueries panel={panel} dashboard={dashboard} />}
|
||||
{activeTab.id === PanelEditorTabId.Query && <PanelEditorQueries panel={panel} />}
|
||||
{activeTab.id === PanelEditorTabId.Alert && <AlertTab panel={panel} dashboard={dashboard} />}
|
||||
{activeTab.id === PanelEditorTabId.Transform && <TransformationsEditor panel={panel} />}
|
||||
</TabContent>
|
||||
|
@ -132,8 +132,8 @@ export class PanelModel implements DataConfigSource {
|
||||
};
|
||||
fieldConfig: FieldConfigSource;
|
||||
|
||||
maxDataPoints?: number;
|
||||
interval?: string;
|
||||
maxDataPoints?: number | null;
|
||||
interval?: string | null;
|
||||
description?: string;
|
||||
links?: DataLink[];
|
||||
transparent: boolean;
|
||||
|
@ -6,9 +6,6 @@ import _ from 'lodash';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { AngularComponent, getAngularLoader } from '@grafana/runtime';
|
||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
// Types
|
||||
import { PanelModel } from '../../dashboard/state/PanelModel';
|
||||
|
||||
import { ErrorBoundaryAlert, HorizontalGroup } from '@grafana/ui';
|
||||
import {
|
||||
DataQuery,
|
||||
@ -25,12 +22,11 @@ import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOp
|
||||
import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction';
|
||||
import { DashboardModel } from '../../dashboard/state/DashboardModel';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { PanelModel } from 'app/features/dashboard/state';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
data: PanelData;
|
||||
query: DataQuery;
|
||||
dashboard?: DashboardModel;
|
||||
dataSourceValue: string | null;
|
||||
inMixedMode?: boolean;
|
||||
id: string;
|
||||
@ -38,6 +34,7 @@ interface Props {
|
||||
onAddQuery: (query?: DataQuery) => void;
|
||||
onRemoveQuery: (query: DataQuery) => void;
|
||||
onChange: (query: DataQuery) => void;
|
||||
onRunQuery: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -72,15 +69,20 @@ export class QueryEditorRow extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
getAngularQueryComponentScope(): AngularQueryComponentScope {
|
||||
const { panel, query, dashboard } = this.props;
|
||||
const { query, onChange } = this.props;
|
||||
const { datasource } = this.state;
|
||||
const panel = new PanelModel({});
|
||||
const dashboard = {} as DashboardModel;
|
||||
|
||||
return {
|
||||
datasource: datasource,
|
||||
target: query,
|
||||
panel: panel,
|
||||
dashboard: dashboard!,
|
||||
refresh: () => panel.refresh(),
|
||||
dashboard: dashboard,
|
||||
refresh: () => {
|
||||
// Old angular editors modify the query model and just call refresh
|
||||
onChange(query);
|
||||
},
|
||||
render: () => () => console.log('legacy render function called, it does nothing'),
|
||||
events: panel.events,
|
||||
range: getTimeSrv().timeRange(),
|
||||
@ -88,12 +90,12 @@ export class QueryEditorRow extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
async loadDatasource() {
|
||||
const { query, panel, dataSourceValue } = this.props;
|
||||
const { query, dataSourceValue } = this.props;
|
||||
const dataSourceSrv = getDatasourceSrv();
|
||||
let datasource;
|
||||
|
||||
try {
|
||||
const datasourceName = dataSourceValue || query.datasource || panel.datasource;
|
||||
const datasourceName = dataSourceValue || query.datasource;
|
||||
datasource = await dataSourceSrv.get(datasourceName);
|
||||
} catch (error) {
|
||||
datasource = await dataSourceSrv.get();
|
||||
@ -108,7 +110,7 @@ export class QueryEditorRow extends PureComponent<Props, State> {
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const { loadedDataSourceValue } = this.state;
|
||||
const { data, query, panel } = this.props;
|
||||
const { data, query } = this.props;
|
||||
|
||||
if (data !== prevProps.data) {
|
||||
this.setState({ data: filterPanelDataToQuery(data, query.refId) });
|
||||
@ -118,7 +120,7 @@ export class QueryEditorRow extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
if (this.angularQueryEditor) {
|
||||
notifyAngularQueryEditorsOfData(panel, data, this.angularQueryEditor);
|
||||
notifyAngularQueryEditorsOfData(this.angularScope?.panel!, data, this.angularQueryEditor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,7 +163,7 @@ export class QueryEditorRow extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
onRunQuery = () => {
|
||||
this.props.panel.refresh();
|
||||
this.props.onRunQuery();
|
||||
};
|
||||
|
||||
renderPluginEditor = () => {
|
||||
|
@ -2,11 +2,8 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Types
|
||||
import { PanelModel } from '../../dashboard/state/PanelModel';
|
||||
import { DataQuery, PanelData, DataSourceSelectItem } from '@grafana/data';
|
||||
import { DashboardModel } from '../../dashboard/state/DashboardModel';
|
||||
import { QueryEditorRow } from './QueryEditorRow';
|
||||
import { addQuery } from 'app/core/utils/query';
|
||||
import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd';
|
||||
|
||||
interface Props {
|
||||
@ -15,35 +12,21 @@ interface Props {
|
||||
datasource: DataSourceSelectItem;
|
||||
|
||||
// Query editing
|
||||
onChangeQueries: (queries: DataQuery[]) => void;
|
||||
onScrollBottom: () => void;
|
||||
|
||||
// Dashboard Configs
|
||||
panel: PanelModel;
|
||||
dashboard?: DashboardModel;
|
||||
onQueriesChange: (queries: DataQuery[]) => void;
|
||||
onAddQuery: (query: DataQuery) => void;
|
||||
onRunQueries: () => void;
|
||||
|
||||
// Query Response Data
|
||||
data: PanelData;
|
||||
}
|
||||
|
||||
export class QueryEditorRows extends PureComponent<Props> {
|
||||
onAddQuery = (query?: Partial<DataQuery>) => {
|
||||
const { queries, onChangeQueries } = this.props;
|
||||
onChangeQueries(addQuery(queries, query));
|
||||
this.props.onScrollBottom();
|
||||
};
|
||||
|
||||
onRemoveQuery = (query: DataQuery) => {
|
||||
const { queries, onChangeQueries, panel } = this.props;
|
||||
const removed = queries.filter(q => {
|
||||
return q !== query;
|
||||
});
|
||||
onChangeQueries(removed);
|
||||
panel.refresh();
|
||||
this.props.onQueriesChange(this.props.queries.filter(item => item !== query));
|
||||
};
|
||||
|
||||
onChangeQuery(query: DataQuery, index: number) {
|
||||
const { queries, onChangeQueries } = this.props;
|
||||
const { queries, onQueriesChange } = this.props;
|
||||
|
||||
const old = queries[index];
|
||||
|
||||
@ -54,7 +37,7 @@ export class QueryEditorRows extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
// update query in array
|
||||
onChangeQueries(
|
||||
onQueriesChange(
|
||||
queries.map((item, itemIndex) => {
|
||||
if (itemIndex === index) {
|
||||
return query;
|
||||
@ -65,7 +48,7 @@ export class QueryEditorRows extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
onDragEnd = (result: DropResult) => {
|
||||
const { queries, onChangeQueries, panel } = this.props;
|
||||
const { queries, onQueriesChange } = this.props;
|
||||
|
||||
if (!result || !result.destination) {
|
||||
return;
|
||||
@ -80,12 +63,12 @@ export class QueryEditorRows extends PureComponent<Props> {
|
||||
const update = Array.from(queries);
|
||||
const [removed] = update.splice(startIndex, 1);
|
||||
update.splice(endIndex, 0, removed);
|
||||
onChangeQueries(update);
|
||||
panel.refresh();
|
||||
onQueriesChange(update);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={this.onDragEnd}>
|
||||
<Droppable droppableId="transformations-list" direction="vertical">
|
||||
@ -98,13 +81,12 @@ export class QueryEditorRows extends PureComponent<Props> {
|
||||
id={query.refId}
|
||||
index={index}
|
||||
key={query.refId}
|
||||
panel={props.panel}
|
||||
dashboard={props.dashboard}
|
||||
data={props.data}
|
||||
query={query}
|
||||
onChange={query => this.onChangeQuery(query, index)}
|
||||
onRemoveQuery={this.onRemoveQuery}
|
||||
onAddQuery={this.onAddQuery}
|
||||
onAddQuery={this.props.onAddQuery}
|
||||
onRunQuery={this.props.onRunQueries}
|
||||
inMixedMode={props.datasource.meta.mixed}
|
||||
/>
|
||||
))}
|
||||
|
@ -2,17 +2,14 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
// Components
|
||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||
import { QueryOptions } from './QueryOptions';
|
||||
import { Button, CustomScrollbar, HorizontalGroup, Modal, stylesFactory, Field } from '@grafana/ui';
|
||||
import { getLocationSrv, getDataSourceSrv } from '@grafana/runtime';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { QueryEditorRows } from './QueryEditorRows';
|
||||
// Services
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import config from 'app/core/config';
|
||||
// Types
|
||||
import { PanelModel } from '../../dashboard/state/PanelModel';
|
||||
import { DashboardModel } from '../../dashboard/state/DashboardModel';
|
||||
import {
|
||||
DataQuery,
|
||||
DataSourceSelectItem,
|
||||
@ -24,14 +21,23 @@ import {
|
||||
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
|
||||
import { addQuery } from 'app/core/utils/query';
|
||||
import { Unsubscribable } from 'rxjs';
|
||||
import { DashboardQueryEditor, isSharedDashboardQuery } from 'app/plugins/datasource/dashboard';
|
||||
import { expressionDatasource, ExpressionDatasourceID } from 'app/features/expressions/ExpressionDatasource';
|
||||
import { css } from 'emotion';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { PanelQueryRunner } from '../state/PanelQueryRunner';
|
||||
import { QueryGroupOptions, QueryGroupOptionsEditor } from './QueryGroupOptions';
|
||||
import { DashboardQueryEditor, isSharedDashboardQuery } from 'app/plugins/datasource/dashboard';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
dashboard?: DashboardModel;
|
||||
queryRunner: PanelQueryRunner;
|
||||
queries: DataQuery[];
|
||||
dataSourceName: string | null;
|
||||
options: QueryGroupOptions;
|
||||
onOpenQueryInspector?: () => void;
|
||||
onRunQueries: () => void;
|
||||
onQueriesChange: (queries: DataQuery[]) => void;
|
||||
onDataSourceChange: (ds: DataSourceSelectItem, queries: DataQuery[]) => void;
|
||||
onOptionsChange: (options: QueryGroupOptions) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -47,14 +53,14 @@ interface State {
|
||||
isHelpOpen: boolean;
|
||||
}
|
||||
|
||||
export class QueriesTab extends PureComponent<Props, State> {
|
||||
export class QueryGroup extends PureComponent<Props, State> {
|
||||
datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources();
|
||||
backendSrv = backendSrv;
|
||||
querySubscription: Unsubscribable | null;
|
||||
|
||||
state: State = {
|
||||
isLoadingHelp: false,
|
||||
dataSourceItem: this.findCurrentDataSource(),
|
||||
dataSourceItem: this.findCurrentDataSource(this.props.dataSourceName),
|
||||
helpContent: null,
|
||||
isPickerOpen: false,
|
||||
isAddingMixed: false,
|
||||
@ -68,15 +74,14 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
const { panel } = this.props;
|
||||
const queryRunner = panel.getQueryRunner();
|
||||
const { queryRunner, dataSourceName: datasourceName } = this.props;
|
||||
|
||||
this.querySubscription = queryRunner.getData({ withTransforms: false, withFieldConfig: false }).subscribe({
|
||||
next: (data: PanelData) => this.onPanelDataUpdate(data),
|
||||
});
|
||||
|
||||
try {
|
||||
const ds = await getDataSourceSrv().get(panel.datasource);
|
||||
const ds = await getDataSourceSrv().get(datasourceName);
|
||||
this.setState({ dataSource: ds });
|
||||
} catch (error) {
|
||||
const ds = await getDataSourceSrv().get();
|
||||
@ -96,85 +101,62 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
this.setState({ data });
|
||||
}
|
||||
|
||||
findCurrentDataSource(dataSourceName: string | null = this.props.panel.datasource): DataSourceSelectItem {
|
||||
findCurrentDataSource(dataSourceName: string | null): DataSourceSelectItem {
|
||||
return this.datasources.find(datasource => datasource.value === dataSourceName) || this.datasources[0];
|
||||
}
|
||||
|
||||
onChangeDataSource = async (newDsItem: DataSourceSelectItem) => {
|
||||
const { panel } = this.props;
|
||||
let { queries } = this.props;
|
||||
const { dataSourceItem } = this.state;
|
||||
|
||||
// switching to mixed
|
||||
if (newDsItem.meta.mixed) {
|
||||
// Set the datasource on all targets
|
||||
panel.targets.forEach(target => {
|
||||
if (target.datasource !== ExpressionDatasourceID) {
|
||||
target.datasource = panel.datasource;
|
||||
if (!target.datasource) {
|
||||
target.datasource = config.defaultDatasource;
|
||||
for (const query of queries) {
|
||||
if (query.datasource !== ExpressionDatasourceID) {
|
||||
query.datasource = query.datasource;
|
||||
if (!query.datasource) {
|
||||
query.datasource = config.defaultDatasource;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (dataSourceItem) {
|
||||
// if switching from mixed
|
||||
if (dataSourceItem.meta.mixed) {
|
||||
// Remove the explicit datasource
|
||||
for (const target of panel.targets) {
|
||||
if (target.datasource !== ExpressionDatasourceID) {
|
||||
delete target.datasource;
|
||||
for (const query of queries) {
|
||||
if (query.datasource !== ExpressionDatasourceID) {
|
||||
delete query.datasource;
|
||||
}
|
||||
}
|
||||
} else if (dataSourceItem.meta.id !== newDsItem.meta.id) {
|
||||
// we are changing data source type, clear queries
|
||||
panel.targets = [{ refId: 'A' }];
|
||||
queries = [{ refId: 'A' }];
|
||||
}
|
||||
}
|
||||
|
||||
const dataSource = await getDataSourceSrv().get(newDsItem.value);
|
||||
|
||||
panel.datasource = newDsItem.value;
|
||||
this.props.onDataSourceChange(newDsItem, queries);
|
||||
|
||||
this.setState(
|
||||
{
|
||||
dataSourceItem: newDsItem,
|
||||
dataSource: dataSource,
|
||||
dataSourceError: undefined,
|
||||
},
|
||||
() => panel.refresh()
|
||||
);
|
||||
};
|
||||
|
||||
openQueryInspector = () => {
|
||||
const { panel } = this.props;
|
||||
|
||||
getLocationSrv().update({
|
||||
query: { inspect: panel.id, inspectTab: 'query' },
|
||||
partial: true,
|
||||
this.setState({
|
||||
dataSourceItem: newDsItem,
|
||||
dataSource: dataSource,
|
||||
dataSourceError: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the queries for the panel
|
||||
*/
|
||||
onUpdateQueries = (queries: DataQuery[]) => {
|
||||
this.props.panel.updateQueries(queries);
|
||||
|
||||
// Need to force update to rerender query rows.
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onAddQueryClick = () => {
|
||||
if (this.state.dataSourceItem.meta.mixed) {
|
||||
this.setState({ isAddingMixed: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this.onUpdateQueries(addQuery(this.props.panel.targets));
|
||||
this.props.onQueriesChange(addQuery(this.props.queries));
|
||||
this.onScrollBottom();
|
||||
};
|
||||
|
||||
onAddExpressionClick = () => {
|
||||
this.onUpdateQueries(addQuery(this.props.panel.targets, expressionDatasource.newQuery()));
|
||||
this.props.onQueriesChange(addQuery(this.props.queries, expressionDatasource.newQuery()));
|
||||
this.onScrollBottom();
|
||||
};
|
||||
|
||||
@ -183,8 +165,8 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
renderTopSection(styles: QueriesTabStyls) {
|
||||
const { panel } = this.props;
|
||||
const { dataSourceItem, data, dataSource, dataSourceError } = this.state;
|
||||
const { onOpenQueryInspector, options, onOptionsChange } = this.props;
|
||||
const { dataSourceItem, dataSource, dataSourceError, data } = this.state;
|
||||
|
||||
if (!dataSource) {
|
||||
return null;
|
||||
@ -211,17 +193,19 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.dataSourceRowItemOptions}>
|
||||
<QueryOptions panel={panel} dataSource={dataSource} data={data} />
|
||||
</div>
|
||||
<div className={styles.dataSourceRowItem}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={this.openQueryInspector}
|
||||
aria-label={selectors.components.QueryTab.queryInspectorButton}
|
||||
>
|
||||
Query inspector
|
||||
</Button>
|
||||
<QueryGroupOptionsEditor options={options} dataSource={dataSource} data={data} onChange={onOptionsChange} />
|
||||
</div>
|
||||
{onOpenQueryInspector && (
|
||||
<div className={styles.dataSourceRowItem}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onOpenQueryInspector}
|
||||
aria-label={selectors.components.QueryTab.queryInspectorButton}
|
||||
>
|
||||
Query inspector
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -253,18 +237,18 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
onAddMixedQuery = (datasource: any) => {
|
||||
this.props.panel.targets = addQuery(this.props.panel.targets, { datasource: datasource.name });
|
||||
this.onAddQuery({ datasource: datasource.name });
|
||||
this.setState({ isAddingMixed: false, scrollTop: this.state.scrollTop + 10000 });
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onMixedPickerBlur = () => {
|
||||
this.setState({ isAddingMixed: false });
|
||||
};
|
||||
|
||||
onQueryChange = (query: DataQuery, index: number) => {
|
||||
this.props.panel.changeQuery(query, index);
|
||||
this.forceUpdate();
|
||||
onAddQuery = (query: Partial<DataQuery>) => {
|
||||
const { queries, onQueriesChange } = this.props;
|
||||
onQueriesChange(addQuery(queries, query));
|
||||
this.onScrollBottom();
|
||||
};
|
||||
|
||||
setScrollTop = (event: React.MouseEvent<HTMLElement>) => {
|
||||
@ -273,22 +257,21 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
renderQueries() {
|
||||
const { panel, dashboard } = this.props;
|
||||
const { onQueriesChange, queries, onRunQueries } = this.props;
|
||||
const { dataSourceItem, data } = this.state;
|
||||
|
||||
if (isSharedDashboardQuery(dataSourceItem.name)) {
|
||||
return <DashboardQueryEditor panel={panel} panelData={data} onChange={query => this.onUpdateQueries([query])} />;
|
||||
return <DashboardQueryEditor queries={queries} panelData={data} onChange={onQueriesChange} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div aria-label={selectors.components.QueryTab.content}>
|
||||
<QueryEditorRows
|
||||
queries={panel.targets}
|
||||
queries={queries}
|
||||
datasource={dataSourceItem}
|
||||
onChangeQueries={this.onUpdateQueries}
|
||||
onScrollBottom={this.onScrollBottom}
|
||||
panel={panel}
|
||||
dashboard={dashboard}
|
||||
onQueriesChange={onQueriesChange}
|
||||
onAddQuery={this.onAddQuery}
|
||||
onRunQueries={onRunQueries}
|
||||
data={data}
|
||||
/>
|
||||
</div>
|
@ -8,51 +8,47 @@ import { rangeUtil, PanelData, DataSourceApi } from '@grafana/data';
|
||||
import { Switch, Input, InlineField, InlineFormLabel, stylesFactory } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { PanelModel } from '../../dashboard/state';
|
||||
import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow';
|
||||
import { config } from 'app/core/config';
|
||||
import { css } from 'emotion';
|
||||
|
||||
const timeRangeValidation = (value: string) => {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
return rangeUtil.isValidTimeSpan(value);
|
||||
};
|
||||
|
||||
const emptyToNull = (value: string) => {
|
||||
return value === '' ? null : value;
|
||||
};
|
||||
export interface QueryGroupOptions {
|
||||
maxDataPoints?: number | null;
|
||||
minInterval?: string | null;
|
||||
cacheTimeout?: string | null;
|
||||
timeRange?: {
|
||||
from?: string | null;
|
||||
shift?: string | null;
|
||||
hide?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
options: QueryGroupOptions;
|
||||
dataSource: DataSourceApi;
|
||||
data: PanelData;
|
||||
onChange: (options: QueryGroupOptions) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
relativeTime: string;
|
||||
timeShift: string;
|
||||
cacheTimeout: string;
|
||||
maxDataPoints: number | string;
|
||||
interval: string;
|
||||
hideTimeOverride: boolean;
|
||||
timeRangeFrom: string;
|
||||
timeRangeShift: string;
|
||||
timeRangeHide: boolean;
|
||||
isOpen: boolean;
|
||||
relativeTimeIsValid: boolean;
|
||||
timeShiftIsValid: boolean;
|
||||
}
|
||||
|
||||
export class QueryOptions extends PureComponent<Props, State> {
|
||||
export class QueryGroupOptionsEditor extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const { options } = props;
|
||||
|
||||
this.state = {
|
||||
relativeTime: props.panel.timeFrom || '',
|
||||
timeShift: props.panel.timeShift || '',
|
||||
cacheTimeout: props.panel.cacheTimeout || '',
|
||||
maxDataPoints: props.panel.maxDataPoints ?? '',
|
||||
interval: props.panel.interval || '',
|
||||
hideTimeOverride: props.panel.hideTimeOverride || false,
|
||||
timeRangeFrom: options.timeRange?.from || '',
|
||||
timeRangeShift: options.timeRange?.shift || '',
|
||||
timeRangeHide: options.timeRange?.hide ?? false,
|
||||
isOpen: false,
|
||||
relativeTimeIsValid: true,
|
||||
timeShiftIsValid: true,
|
||||
@ -61,79 +57,103 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
|
||||
onRelativeTimeChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
relativeTime: event.target.value,
|
||||
timeRangeFrom: event.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
onTimeShiftChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
timeShift: event.target.value,
|
||||
timeRangeShift: event.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
onOverrideTime = (event: FocusEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
const { panel } = this.props;
|
||||
const emptyToNullValue = emptyToNull(value);
|
||||
const isValid = timeRangeValidation(value);
|
||||
const { options, onChange } = this.props;
|
||||
|
||||
if (isValid && panel.timeFrom !== emptyToNullValue) {
|
||||
panel.timeFrom = emptyToNullValue;
|
||||
panel.refresh();
|
||||
const newValue = emptyToNull(event.target.value);
|
||||
const isValid = timeRangeValidation(newValue);
|
||||
|
||||
if (isValid && options.timeRange?.from !== newValue) {
|
||||
onChange({
|
||||
...options,
|
||||
timeRange: {
|
||||
...(options.timeRange ?? {}),
|
||||
from: newValue,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ relativeTimeIsValid: isValid });
|
||||
};
|
||||
|
||||
onTimeShift = (event: FocusEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
const { panel } = this.props;
|
||||
const emptyToNullValue = emptyToNull(value);
|
||||
const isValid = timeRangeValidation(value);
|
||||
const { options, onChange } = this.props;
|
||||
|
||||
if (isValid && panel.timeShift !== emptyToNullValue) {
|
||||
panel.timeShift = emptyToNullValue;
|
||||
panel.refresh();
|
||||
const newValue = emptyToNull(event.target.value);
|
||||
const isValid = timeRangeValidation(newValue);
|
||||
|
||||
if (isValid && options.timeRange?.shift !== newValue) {
|
||||
onChange({
|
||||
...options,
|
||||
timeRange: {
|
||||
...(options.timeRange ?? {}),
|
||||
shift: newValue,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ timeShiftIsValid: isValid });
|
||||
};
|
||||
|
||||
onToggleTimeOverride = () => {
|
||||
const { panel } = this.props;
|
||||
this.setState({ hideTimeOverride: !this.state.hideTimeOverride }, () => {
|
||||
panel.hideTimeOverride = this.state.hideTimeOverride;
|
||||
panel.refresh();
|
||||
const { onChange, options } = this.props;
|
||||
|
||||
this.setState({ timeRangeHide: !this.state.timeRangeHide }, () => {
|
||||
onChange({
|
||||
...options,
|
||||
timeRange: {
|
||||
...(options.timeRange ?? {}),
|
||||
hide: this.state.timeRangeHide,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onDataSourceOptionBlur = (panelKey: string) => () => {
|
||||
const { panel } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
panel[panelKey] = this.state[panelKey];
|
||||
panel.refresh();
|
||||
onCacheTimeoutBlur = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const { options, onChange } = this.props;
|
||||
onChange({
|
||||
...options,
|
||||
cacheTimeout: emptyToNull(event.target.value),
|
||||
});
|
||||
};
|
||||
|
||||
onDataSourceOptionChange = (panelKey: string) => (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ ...this.state, [panelKey]: event.target.value });
|
||||
};
|
||||
onMaxDataPointsBlur = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const { options, onChange } = this.props;
|
||||
|
||||
onMaxDataPointsBlur = () => {
|
||||
const { panel } = this.props;
|
||||
let maxDataPoints: number | null = parseInt(event.target.value as string, 10);
|
||||
|
||||
const maxDataPoints = parseInt(this.state.maxDataPoints as string, 10);
|
||||
|
||||
if (isNaN(maxDataPoints)) {
|
||||
delete panel.maxDataPoints;
|
||||
} else {
|
||||
panel.maxDataPoints = maxDataPoints;
|
||||
if (isNaN(maxDataPoints) || maxDataPoints === 0) {
|
||||
maxDataPoints = null;
|
||||
}
|
||||
|
||||
panel.refresh();
|
||||
onChange({
|
||||
...options,
|
||||
maxDataPoints: maxDataPoints,
|
||||
});
|
||||
};
|
||||
|
||||
onMinIntervalBlur = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const { options, onChange } = this.props;
|
||||
|
||||
onChange({
|
||||
...options,
|
||||
minInterval: emptyToNull(event.target.value),
|
||||
});
|
||||
};
|
||||
|
||||
renderCacheTimeoutOption() {
|
||||
const { dataSource } = this.props;
|
||||
const { cacheTimeout } = this.state;
|
||||
const { dataSource, options } = this.props;
|
||||
|
||||
const tooltip = `If your time series store has a query cache this option can override the default cache timeout. Specify a
|
||||
numeric value in seconds.`;
|
||||
|
||||
@ -153,9 +173,8 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
placeholder="60"
|
||||
name={name}
|
||||
spellCheck={false}
|
||||
onBlur={this.onDataSourceOptionBlur('cacheTimeout')}
|
||||
onChange={this.onDataSourceOptionChange('cacheTimeout')}
|
||||
value={cacheTimeout}
|
||||
onBlur={this.onCacheTimeoutBlur}
|
||||
defaultValue={options.cacheTimeout ?? ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -163,10 +182,10 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
renderMaxDataPointsOption() {
|
||||
const { data } = this.props;
|
||||
const { maxDataPoints } = this.state;
|
||||
const { data, options } = this.props;
|
||||
const realMd = data.request?.maxDataPoints;
|
||||
const isAuto = maxDataPoints === '';
|
||||
const value = options.maxDataPoints ?? '';
|
||||
const isAuto = value === '';
|
||||
|
||||
return (
|
||||
<div className="gf-form-inline">
|
||||
@ -189,8 +208,7 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
name={name}
|
||||
spellCheck={false}
|
||||
onBlur={this.onMaxDataPointsBlur}
|
||||
onChange={this.onDataSourceOptionChange('maxDataPoints')}
|
||||
value={maxDataPoints}
|
||||
defaultValue={value}
|
||||
/>
|
||||
{isAuto && (
|
||||
<>
|
||||
@ -204,8 +222,7 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
renderIntervalOption() {
|
||||
const { data, dataSource } = this.props;
|
||||
const { interval } = this.state;
|
||||
const { data, dataSource, options } = this.props;
|
||||
const realInterval = data.request?.interval;
|
||||
const minIntervalOnDs = dataSource.interval ?? 'No limit';
|
||||
|
||||
@ -231,9 +248,8 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
placeholder={`${minIntervalOnDs}`}
|
||||
name={name}
|
||||
spellCheck={false}
|
||||
onBlur={this.onDataSourceOptionBlur('interval')}
|
||||
onChange={this.onDataSourceOptionChange('interval')}
|
||||
value={interval}
|
||||
onBlur={this.onMinIntervalBlur}
|
||||
defaultValue={options.minInterval ?? ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -268,19 +284,19 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
renderCollapsedText(styles: StylesType): React.ReactNode | undefined {
|
||||
const { data } = this.props;
|
||||
const { isOpen, maxDataPoints, interval } = this.state;
|
||||
const { data, options } = this.props;
|
||||
const { isOpen } = this.state;
|
||||
|
||||
if (isOpen) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let mdDesc = maxDataPoints;
|
||||
if (maxDataPoints === '' && data.request) {
|
||||
let mdDesc = options.maxDataPoints ?? '';
|
||||
if (mdDesc === '' && data.request) {
|
||||
mdDesc = `auto = ${data.request.maxDataPoints}`;
|
||||
}
|
||||
|
||||
let intervalDesc = interval;
|
||||
let intervalDesc = options.minInterval;
|
||||
if (data.request) {
|
||||
intervalDesc = `${data.request.interval}`;
|
||||
}
|
||||
@ -294,8 +310,8 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { hideTimeOverride, relativeTimeIsValid, timeShiftIsValid } = this.state;
|
||||
const { relativeTime, timeShift, isOpen } = this.state;
|
||||
const { timeRangeHide: hideTimeOverride, relativeTimeIsValid, timeShiftIsValid } = this.state;
|
||||
const { timeRangeFrom: relativeTime, timeRangeShift: timeShift, isOpen } = this.state;
|
||||
const styles = getStyles();
|
||||
|
||||
return (
|
||||
@ -349,6 +365,18 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
const timeRangeValidation = (value: string | null) => {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return rangeUtil.isValidTimeSpan(value);
|
||||
};
|
||||
|
||||
const emptyToNull = (value: string) => {
|
||||
return value === '' ? null : value;
|
||||
};
|
||||
|
||||
const getStyles = stylesFactory(() => {
|
||||
const { theme } = config;
|
||||
|
@ -34,7 +34,7 @@ export interface QueryRunnerOptions<
|
||||
> {
|
||||
datasource: string | DataSourceApi<TQuery, TOptions> | null;
|
||||
queries: TQuery[];
|
||||
panelId: number;
|
||||
panelId?: number;
|
||||
dashboardId?: number;
|
||||
timezone: TimeZone;
|
||||
timeRange: TimeRange;
|
||||
|
5
public/app/features/query/state/types.ts
Normal file
5
public/app/features/query/state/types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { EventBusExtended } from '@grafana/data';
|
||||
|
||||
export interface PanelModelForLegacyQueryEditors {
|
||||
events: EventBusExtended;
|
||||
}
|
@ -1,32 +1,120 @@
|
||||
import React, { FC, useState } from 'react';
|
||||
import { PanelModel } from '../dashboard/state';
|
||||
import { QueriesTab } from '../query/components/QueriesTab';
|
||||
import {
|
||||
ApplyFieldOverrideOptions,
|
||||
DataQuery,
|
||||
DataSourceSelectItem,
|
||||
DataTransformerConfig,
|
||||
dateMath,
|
||||
FieldColorModeId,
|
||||
PanelData,
|
||||
} from '@grafana/data';
|
||||
import { GraphNG, Table } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
import { useObservable } from 'react-use';
|
||||
import { QueryGroup } from '../query/components/QueryGroup';
|
||||
import { QueryGroupOptions } from '../query/components/QueryGroupOptions';
|
||||
import { PanelQueryRunner } from '../query/state/PanelQueryRunner';
|
||||
|
||||
interface State {
|
||||
panel: PanelModel;
|
||||
queries: DataQuery[];
|
||||
queryRunner: PanelQueryRunner;
|
||||
dataSourceName: string | null;
|
||||
queryOptions: QueryGroupOptions;
|
||||
data?: PanelData;
|
||||
}
|
||||
|
||||
export const TestStuffPage: FC = () => {
|
||||
const [state] = useState<State>(getDefaultState());
|
||||
const [state, setState] = useState<State>(getDefaultState());
|
||||
const { queryOptions, queryRunner, queries, dataSourceName } = state;
|
||||
|
||||
const onDataSourceChange = (ds: DataSourceSelectItem, queries: DataQuery[]) => {
|
||||
setState({
|
||||
...state,
|
||||
dataSourceName: ds.value,
|
||||
queries: queries,
|
||||
});
|
||||
};
|
||||
|
||||
const onRunQueries = () => {
|
||||
const timeRange = { from: 'now-1h', to: 'now' };
|
||||
|
||||
queryRunner.run({
|
||||
queries,
|
||||
timezone: 'browser',
|
||||
datasource: dataSourceName,
|
||||
timeRange: { from: dateMath.parse(timeRange.from)!, to: dateMath.parse(timeRange.to)!, raw: timeRange },
|
||||
maxDataPoints: queryOptions.maxDataPoints ?? 100,
|
||||
minInterval: queryOptions.minInterval,
|
||||
});
|
||||
};
|
||||
|
||||
const onQueriesChange = (queries: DataQuery[]) => {
|
||||
setState({ ...state, queries: queries });
|
||||
};
|
||||
|
||||
const onQueryOptionsChange = (queryOptions: QueryGroupOptions) => {
|
||||
setState({ ...state, queryOptions });
|
||||
};
|
||||
|
||||
/**
|
||||
* Subscribe to data
|
||||
*/
|
||||
const observable = useMemo(() => queryRunner.getData({ withFieldConfig: true, withTransforms: true }), []);
|
||||
const data = useObservable(observable);
|
||||
|
||||
return (
|
||||
<div style={{ padding: '50px', height: '100%', flexGrow: 1 }} className="page-scrollbar-wrapper">
|
||||
<h2>Hello</h2>
|
||||
<div style={{ padding: '30px 50px' }} className="page-scrollbar-wrapper">
|
||||
<h3>New page</h3>
|
||||
<div>
|
||||
<QueryGroup
|
||||
options={queryOptions}
|
||||
dataSourceName={dataSourceName}
|
||||
queryRunner={queryRunner}
|
||||
queries={queries}
|
||||
onDataSourceChange={onDataSourceChange}
|
||||
onRunQueries={onRunQueries}
|
||||
onQueriesChange={onQueriesChange}
|
||||
onOptionsChange={onQueryOptionsChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<QueriesTab panel={state.panel} />
|
||||
{data && (
|
||||
<div style={{ padding: '16px' }}>
|
||||
<GraphNG width={1200} height={300} data={data.series} timeRange={data.timeRange} timeZone="browser" />
|
||||
<hr></hr>
|
||||
<Table data={data.series[0]} width={1200} height={300} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function getDefaultState(): State {
|
||||
const panel = new PanelModel({
|
||||
datasource: 'gdev-testdata',
|
||||
id: 10,
|
||||
targets: [],
|
||||
});
|
||||
const options: ApplyFieldOverrideOptions = {
|
||||
fieldConfig: {
|
||||
defaults: {
|
||||
color: {
|
||||
mode: FieldColorModeId.PaletteClassic,
|
||||
},
|
||||
},
|
||||
overrides: [],
|
||||
},
|
||||
replaceVariables: (v: string) => v,
|
||||
theme: config.theme,
|
||||
};
|
||||
|
||||
const dataConfig = {
|
||||
getTransformations: () => [] as DataTransformerConfig[],
|
||||
getFieldOverrideOptions: () => options,
|
||||
};
|
||||
|
||||
return {
|
||||
panel,
|
||||
queries: [],
|
||||
dataSourceName: 'gdev-testdata',
|
||||
queryRunner: new PanelQueryRunner(dataConfig),
|
||||
queryOptions: {
|
||||
maxDataPoints: 100,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -28,9 +28,9 @@ function getQueryDisplayText(query: DataQuery): string {
|
||||
}
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
queries: DataQuery[];
|
||||
panelData: PanelData;
|
||||
onChange: (query: DashboardQuery) => void;
|
||||
onChange: (queries: DataQuery[]) => void;
|
||||
}
|
||||
|
||||
type State = {
|
||||
@ -48,8 +48,7 @@ export class DashboardQueryEditor extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
getQuery(): DashboardQuery {
|
||||
const { panel } = this.props;
|
||||
return panel.targets[0] as DashboardQuery;
|
||||
return this.props.queries[0] as DashboardQuery;
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
@ -57,10 +56,14 @@ export class DashboardQueryEditor extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
async componentDidUpdate(prevProps: Props) {
|
||||
const { panelData } = this.props;
|
||||
const { panelData, queries } = this.props;
|
||||
|
||||
if (queries.length < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prevProps || prevProps.panelData !== panelData) {
|
||||
const query = this.props.panel.targets[0] as DashboardQuery;
|
||||
const query = queries[0] as DashboardQuery;
|
||||
const defaultDS = await getDatasourceSrv().get();
|
||||
const dashboard = getDashboardSrv().getCurrent();
|
||||
const panel = dashboard.getPanelById(query.panelId ?? -124134);
|
||||
@ -97,10 +100,7 @@ export class DashboardQueryEditor extends PureComponent<Props, State> {
|
||||
const { onChange } = this.props;
|
||||
const query = this.getQuery();
|
||||
query.panelId = id;
|
||||
onChange(query);
|
||||
|
||||
// Update the
|
||||
this.props.panel.refresh();
|
||||
onChange([query]);
|
||||
};
|
||||
|
||||
renderQueryData(editURL: string) {
|
||||
|
Loading…
Reference in New Issue
Block a user