diff --git a/public/app/containers/Explore/Explore.tsx b/public/app/containers/Explore/Explore.tsx index 3ee5bceae8b..dcee963e2e7 100644 --- a/public/app/containers/Explore/Explore.tsx +++ b/public/app/containers/Explore/Explore.tsx @@ -19,6 +19,16 @@ import { ensureQueries, generateQueryKey, hasQuery } from './utils/query'; const MAX_HISTORY_ITEMS = 100; +function makeHints(hints) { + const hintsByIndex = []; + hints.forEach(hint => { + if (hint) { + hintsByIndex[hint.index] = hint; + } + }); + return hintsByIndex; +} + function makeTimeSeriesList(dataList, options) { return dataList.map((seriesData, index) => { const datapoints = seriesData.datapoints || []; @@ -37,7 +47,7 @@ function makeTimeSeriesList(dataList, options) { }); } -function parseInitialState(initial: string | undefined) { +function parseUrlState(initial: string | undefined) { if (initial) { try { const parsed = JSON.parse(decodePathComponent(initial)); @@ -64,8 +74,9 @@ interface IExploreState { latency: number; loading: any; logsResult: any; - queries: any; - queryError: any; + queries: any[]; + queryErrors: any[]; + queryHints: any[]; range: any; requestOptions: any; showingGraph: boolean; @@ -82,7 +93,8 @@ export class Explore extends React.Component { constructor(props) { super(props); - const { datasource, queries, range } = parseInitialState(props.routeParams.state); + const initialState: IExploreState = props.initialState; + const { datasource, queries, range } = parseUrlState(props.routeParams.state); this.state = { datasource: null, datasourceError: null, @@ -95,7 +107,8 @@ export class Explore extends React.Component { loading: false, logsResult: null, queries: ensureQueries(queries), - queryError: null, + queryErrors: [], + queryHints: [], range: range || { ...DEFAULT_RANGE }, requestOptions: null, showingGraph: true, @@ -105,7 +118,7 @@ export class Explore extends React.Component { supportsLogs: null, supportsTable: null, tableResult: null, - ...props.initialState, + ...initialState, }; } @@ -191,6 +204,8 @@ export class Explore extends React.Component { datasourceLoading: true, graphResult: null, logsResult: null, + queryErrors: [], + queryHints: [], tableResult: null, }); const datasource = await this.props.datasourceSrv.get(option.value); @@ -199,6 +214,7 @@ export class Explore extends React.Component { onChangeQuery = (value: string, index: number, override?: boolean) => { const { queries } = this.state; + let { queryErrors, queryHints } = this.state; const prevQuery = queries[index]; const edited = override ? false : prevQuery.query !== value; const nextQuery = { @@ -208,7 +224,18 @@ export class Explore extends React.Component { }; const nextQueries = [...queries]; nextQueries[index] = nextQuery; - this.setState({ queries: nextQueries }, override ? () => this.onSubmit() : undefined); + if (override) { + queryErrors = []; + queryHints = []; + } + this.setState( + { + queryErrors, + queryHints, + queries: nextQueries, + }, + override ? () => this.onSubmit() : undefined + ); }; onChangeTime = nextRange => { @@ -255,13 +282,32 @@ export class Explore extends React.Component { }; onClickTableCell = (columnKey: string, rowValue: string) => { + this.onModifyQueries({ type: 'ADD_FILTER', key: columnKey, value: rowValue }); + }; + + onModifyQueries = (action: object, index?: number) => { const { datasource, queries } = this.state; if (datasource && datasource.modifyQuery) { - const nextQueries = queries.map(q => ({ - ...q, - edited: false, - query: datasource.modifyQuery(q.query, { addFilter: { key: columnKey, value: rowValue } }), - })); + let nextQueries; + if (index === undefined) { + // Modify all queries + nextQueries = queries.map(q => ({ + ...q, + edited: false, + query: datasource.modifyQuery(q.query, action), + })); + } else { + // Modify query only at index + nextQueries = [ + ...queries.slice(0, index), + { + ...queries[index], + edited: false, + query: datasource.modifyQuery(queries[index].query, action), + }, + ...queries.slice(index + 1), + ]; + } this.setState({ queries: nextQueries }, () => this.onSubmit()); } }; @@ -309,7 +355,7 @@ export class Explore extends React.Component { this.setState({ history }); } - buildQueryOptions(targetOptions: { format: string; instant?: boolean }) { + buildQueryOptions(targetOptions: { format: string; hinting?: boolean; instant?: boolean }) { const { datasource, queries, range } = this.state; const resolution = this.el.offsetWidth; const absoluteRange = { @@ -333,19 +379,20 @@ export class Explore extends React.Component { if (!hasQuery(queries)) { return; } - this.setState({ latency: 0, loading: true, graphResult: null, queryError: null }); + this.setState({ latency: 0, loading: true, graphResult: null, queryErrors: [], queryHints: [] }); const now = Date.now(); - const options = this.buildQueryOptions({ format: 'time_series', instant: false }); + const options = this.buildQueryOptions({ format: 'time_series', instant: false, hinting: true }); try { const res = await datasource.query(options); const result = makeTimeSeriesList(res.data, options); + const queryHints = res.hints ? makeHints(res.hints) : []; const latency = Date.now() - now; - this.setState({ latency, loading: false, graphResult: result, requestOptions: options }); + this.setState({ latency, loading: false, graphResult: result, queryHints, requestOptions: options }); this.onQuerySuccess(datasource.meta.id, queries); } catch (response) { console.error(response); const queryError = response.data ? response.data.error : response; - this.setState({ loading: false, queryError }); + this.setState({ loading: false, queryErrors: [queryError] }); } } @@ -354,7 +401,7 @@ export class Explore extends React.Component { if (!hasQuery(queries)) { return; } - this.setState({ latency: 0, loading: true, queryError: null, tableResult: null }); + this.setState({ latency: 0, loading: true, queryErrors: [], queryHints: [], tableResult: null }); const now = Date.now(); const options = this.buildQueryOptions({ format: 'table', @@ -369,7 +416,7 @@ export class Explore extends React.Component { } catch (response) { console.error(response); const queryError = response.data ? response.data.error : response; - this.setState({ loading: false, queryError }); + this.setState({ loading: false, queryErrors: [queryError] }); } } @@ -378,7 +425,7 @@ export class Explore extends React.Component { if (!hasQuery(queries)) { return; } - this.setState({ latency: 0, loading: true, queryError: null, logsResult: null }); + this.setState({ latency: 0, loading: true, queryErrors: [], queryHints: [], logsResult: null }); const now = Date.now(); const options = this.buildQueryOptions({ format: 'logs', @@ -393,7 +440,7 @@ export class Explore extends React.Component { } catch (response) { console.error(response); const queryError = response.data ? response.data.error : response; - this.setState({ loading: false, queryError }); + this.setState({ loading: false, queryErrors: [queryError] }); } } @@ -415,7 +462,8 @@ export class Explore extends React.Component { loading, logsResult, queries, - queryError, + queryErrors, + queryHints, range, requestOptions, showingGraph, @@ -449,12 +497,12 @@ export class Explore extends React.Component { ) : ( -
- -
- )} + + )} {!datasourceMissing ? (