diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 49fbb3bd476..c90ba0dd7cc 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { hot } from 'react-hot-loader'; import Select from 'react-select'; -import { ExploreState, ExploreUrlState } from 'app/types/explore'; +import { ExploreState, ExploreUrlState, Query } from 'app/types/explore'; import kbn from 'app/core/utils/kbn'; import colors from 'app/core/utils/colors'; import store from 'app/core/store'; @@ -61,38 +61,50 @@ interface ExploreProps { export class Explore extends React.PureComponent { el: any; + /** + * Current query expressions of the rows including their modifications, used for running queries. + * Not kept in component state to prevent edit-render roundtrips. + */ + queryExpressions: string[]; constructor(props) { super(props); - // Split state overrides everything const splitState: ExploreState = props.splitState; - const { datasource, queries, range } = props.urlState; - this.state = { - datasource: null, - datasourceError: null, - datasourceLoading: null, - datasourceMissing: false, - datasourceName: datasource, - exploreDatasources: [], - graphResult: null, - history: [], - latency: 0, - loading: false, - logsResult: null, - queries: ensureQueries(queries), - queryErrors: [], - queryHints: [], - range: range || { ...DEFAULT_RANGE }, - requestOptions: null, - showingGraph: true, - showingLogs: true, - showingTable: true, - supportsGraph: null, - supportsLogs: null, - supportsTable: null, - tableResult: null, - ...splitState, - }; + let initialQueries: Query[]; + if (splitState) { + // Split state overrides everything + this.state = splitState; + initialQueries = splitState.queries; + } else { + const { datasource, queries, range } = props.urlState as ExploreUrlState; + initialQueries = ensureQueries(queries); + this.state = { + datasource: null, + datasourceError: null, + datasourceLoading: null, + datasourceMissing: false, + datasourceName: datasource, + exploreDatasources: [], + graphResult: null, + history: [], + latency: 0, + loading: false, + logsResult: null, + queries: initialQueries, + queryErrors: [], + queryHints: [], + range: range || { ...DEFAULT_RANGE }, + requestOptions: null, + showingGraph: true, + showingLogs: true, + showingTable: true, + supportsGraph: null, + supportsLogs: null, + supportsTable: null, + tableResult: null, + }; + } + this.queryExpressions = initialQueries.map(q => q.query); } async componentDidMount() { @@ -152,9 +164,10 @@ export class Explore extends React.PureComponent { } // Keep queries but reset edit state - const nextQueries = this.state.queries.map(q => ({ + const nextQueries = this.state.queries.map((q, i) => ({ ...q, - edited: false, + key: generateQueryKey(i), + query: this.queryExpressions[i], })); this.setState( @@ -183,6 +196,7 @@ export class Explore extends React.PureComponent { onAddQueryRow = index => { const { queries } = this.state; + this.queryExpressions[index + 1] = ''; const nextQueries = [ ...queries.slice(0, index + 1), { query: '', key: generateQueryKey() }, @@ -209,29 +223,28 @@ export class Explore extends React.PureComponent { }; 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 = { - ...queries[index], - edited, - query: value, - }; - const nextQueries = [...queries]; - nextQueries[index] = nextQuery; + // Keep current value in local cache + this.queryExpressions[index] = value; + + // Replace query row on override if (override) { - queryErrors = []; - queryHints = []; + const { queries } = this.state; + const nextQuery: Query = { + key: generateQueryKey(index), + query: value, + }; + const nextQueries = [...queries]; + nextQueries[index] = nextQuery; + + this.setState( + { + queryErrors: [], + queryHints: [], + queries: nextQueries, + }, + this.onSubmit + ); } - this.setState( - { - queryErrors, - queryHints, - queries: nextQueries, - }, - override ? () => this.onSubmit() : undefined - ); }; onChangeTime = nextRange => { @@ -243,6 +256,7 @@ export class Explore extends React.PureComponent { }; onClickClear = () => { + this.queryExpressions = ['']; this.setState( { graphResult: null, @@ -275,9 +289,8 @@ export class Explore extends React.PureComponent { onClickSplit = () => { const { onChangeSplit } = this.props; - const state = { ...this.state }; - state.queries = state.queries.map(({ edited, ...rest }) => rest); if (onChangeSplit) { + const state = this.cloneState(); onChangeSplit(true, state); this.saveState(); } @@ -297,23 +310,22 @@ export class Explore extends React.PureComponent { let nextQueries; if (index === undefined) { // Modify all queries - nextQueries = queries.map(q => ({ - ...q, - edited: false, - query: datasource.modifyQuery(q.query, action), + nextQueries = queries.map((q, i) => ({ + key: generateQueryKey(i), + query: datasource.modifyQuery(this.queryExpressions[i], action), })); } else { // Modify query only at index nextQueries = [ ...queries.slice(0, index), { - ...queries[index], - edited: false, - query: datasource.modifyQuery(queries[index].query, action), + key: generateQueryKey(index), + query: datasource.modifyQuery(this.queryExpressions[index], action), }, ...queries.slice(index + 1), ]; } + this.queryExpressions = nextQueries.map(q => q.query); this.setState({ queries: nextQueries }, () => this.onSubmit()); } }; @@ -324,6 +336,7 @@ export class Explore extends React.PureComponent { return; } const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)]; + this.queryExpressions = nextQueries.map(q => q.query); this.setState({ queries: nextQueries }, () => this.onSubmit()); }; @@ -341,7 +354,7 @@ export class Explore extends React.PureComponent { this.saveState(); }; - onQuerySuccess(datasourceId: string, queries: any[]): void { + onQuerySuccess(datasourceId: string, queries: string[]): void { // save queries to history let { history } = this.state; const { datasource } = this.state; @@ -352,8 +365,7 @@ export class Explore extends React.PureComponent { } const ts = Date.now(); - queries.forEach(q => { - const { query } = q; + queries.forEach(query => { history = [{ query, ts }, ...history]; }); @@ -368,16 +380,16 @@ export class Explore extends React.PureComponent { } buildQueryOptions(targetOptions: { format: string; hinting?: boolean; instant?: boolean }) { - const { datasource, queries, range } = this.state; + const { datasource, range } = this.state; const resolution = this.el.offsetWidth; const absoluteRange = { from: parseDate(range.from, false), to: parseDate(range.to, true), }; const { interval } = kbn.calculateInterval(absoluteRange, resolution, datasource.interval); - const targets = queries.map(q => ({ + const targets = this.queryExpressions.map(q => ({ ...targetOptions, - expr: q.query, + expr: q, })); return { interval, @@ -387,7 +399,8 @@ export class Explore extends React.PureComponent { } async runGraphQuery() { - const { datasource, queries } = this.state; + const { datasource } = this.state; + const queries = [...this.queryExpressions]; if (!hasQuery(queries)) { return; } @@ -409,7 +422,8 @@ export class Explore extends React.PureComponent { } async runTableQuery() { - const { datasource, queries } = this.state; + const queries = [...this.queryExpressions]; + const { datasource } = this.state; if (!hasQuery(queries)) { return; } @@ -433,7 +447,8 @@ export class Explore extends React.PureComponent { } async runLogsQuery() { - const { datasource, queries } = this.state; + const queries = [...this.queryExpressions]; + const { datasource } = this.state; if (!hasQuery(queries)) { return; } @@ -461,9 +476,17 @@ export class Explore extends React.PureComponent { return datasource.metadataRequest(url); }; + cloneState(): ExploreState { + // Copy state, but copy queries including modifications + return { + ...this.state, + queries: ensureQueries(this.queryExpressions.map(query => ({ query }))), + }; + } + saveState = () => { const { stateKey, onSaveState } = this.props; - onSaveState(stateKey, this.state); + onSaveState(stateKey, this.cloneState()); }; render() { diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index 51adfa81c68..4dee3813ca8 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -44,14 +44,14 @@ class QueryRow extends PureComponent { }; render() { - const { edited, history, query, queryError, queryHint, request, supportsLogs } = this.props; + const { history, query, queryError, queryHint, request, supportsLogs } = this.props; return (
{ export default class QueryRows extends PureComponent { render() { - const { className = '', queries, queryErrors = [], queryHints = [], ...handlers } = this.props; + const { className = '', queries, queryErrors, queryHints, ...handlers } = this.props; return (
{queries.map((q, index) => ( @@ -89,7 +89,6 @@ export default class QueryRows extends PureComponent { query={q.query} queryError={queryErrors[index]} queryHint={queryHints[index]} - edited={q.edited} {...handlers} /> ))} diff --git a/public/app/features/explore/utils/query.ts b/public/app/features/explore/utils/query.ts index 4766b85f040..193ee2dbc52 100644 --- a/public/app/features/explore/utils/query.ts +++ b/public/app/features/explore/utils/query.ts @@ -1,14 +1,16 @@ -export function generateQueryKey(index = 0) { +import { Query } from 'app/types/explore'; + +export function generateQueryKey(index = 0): string { return `Q-${Date.now()}-${Math.random()}-${index}`; } -export function ensureQueries(queries?) { +export function ensureQueries(queries?: Query[]): Query[] { if (queries && typeof queries === 'object' && queries.length > 0 && typeof queries[0].query === 'string') { return queries.map(({ query }, i) => ({ key: generateQueryKey(i), query })); } return [{ key: generateQueryKey(), query: '' }]; } -export function hasQuery(queries) { - return queries.some(q => q.query); +export function hasQuery(queries: string[]): boolean { + return queries.some(q => Boolean(q)); } diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 774c69a40b6..d72020b6eb7 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -10,7 +10,6 @@ export interface Range { export interface Query { query: string; - edited?: boolean; key?: string; } @@ -26,8 +25,19 @@ export interface ExploreState { latency: number; loading: any; logsResult: any; + /** + * Initial rows of queries to push down the tree. + * Modifications do not end up here, but in `this.queryExpressions`. + * The only way to reset a query is to change its `key`. + */ queries: Query[]; + /** + * Errors caused by the running the query row. + */ queryErrors: any[]; + /** + * Hints gathered for the query row. + */ queryHints: any[]; range: Range; requestOptions: any;