|
@@ -4,14 +4,26 @@ import Select from 'react-select';
|
|
|
import _ from 'lodash';
|
|
import _ from 'lodash';
|
|
|
|
|
|
|
|
import { DataSource } from 'app/types/datasources';
|
|
import { DataSource } from 'app/types/datasources';
|
|
|
-import { ExploreState, ExploreUrlState, HistoryItem, Query, QueryTransaction, ResultType } from 'app/types/explore';
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ ExploreState,
|
|
|
|
|
+ ExploreUrlState,
|
|
|
|
|
+ QueryTransaction,
|
|
|
|
|
+ ResultType,
|
|
|
|
|
+ QueryHintGetter,
|
|
|
|
|
+ QueryHint,
|
|
|
|
|
+} from 'app/types/explore';
|
|
|
import { RawTimeRange, DataQuery } from 'app/types/series';
|
|
import { RawTimeRange, DataQuery } from 'app/types/series';
|
|
|
-import kbn from 'app/core/utils/kbn';
|
|
|
|
|
-import colors from 'app/core/utils/colors';
|
|
|
|
|
import store from 'app/core/store';
|
|
import store from 'app/core/store';
|
|
|
-import TimeSeries from 'app/core/time_series2';
|
|
|
|
|
-import { parse as parseDate } from 'app/core/utils/datemath';
|
|
|
|
|
-import { DEFAULT_RANGE } from 'app/core/utils/explore';
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ DEFAULT_RANGE,
|
|
|
|
|
+ ensureQueries,
|
|
|
|
|
+ getIntervals,
|
|
|
|
|
+ generateKey,
|
|
|
|
|
+ generateQueryKeys,
|
|
|
|
|
+ hasNonEmptyQuery,
|
|
|
|
|
+ makeTimeSeriesList,
|
|
|
|
|
+ updateHistory,
|
|
|
|
|
+} from 'app/core/utils/explore';
|
|
|
import ResetStyles from 'app/core/components/Picker/ResetStyles';
|
|
import ResetStyles from 'app/core/components/Picker/ResetStyles';
|
|
|
import PickerOption from 'app/core/components/Picker/PickerOption';
|
|
import PickerOption from 'app/core/components/Picker/PickerOption';
|
|
|
import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer';
|
|
import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer';
|
|
@@ -26,57 +38,6 @@ import Logs from './Logs';
|
|
|
import Table from './Table';
|
|
import Table from './Table';
|
|
|
import ErrorBoundary from './ErrorBoundary';
|
|
import ErrorBoundary from './ErrorBoundary';
|
|
|
import TimePicker from './TimePicker';
|
|
import TimePicker from './TimePicker';
|
|
|
-import { ensureQueries, generateQueryKey, hasQuery } from './utils/query';
|
|
|
|
|
-
|
|
|
|
|
-const MAX_HISTORY_ITEMS = 100;
|
|
|
|
|
-
|
|
|
|
|
-function getIntervals(range: RawTimeRange, datasource, resolution: number): { interval: string; intervalMs: number } {
|
|
|
|
|
- if (!datasource || !resolution) {
|
|
|
|
|
- return { interval: '1s', intervalMs: 1000 };
|
|
|
|
|
- }
|
|
|
|
|
- const absoluteRange: RawTimeRange = {
|
|
|
|
|
- from: parseDate(range.from, false),
|
|
|
|
|
- to: parseDate(range.to, true),
|
|
|
|
|
- };
|
|
|
|
|
- return kbn.calculateInterval(absoluteRange, resolution, datasource.interval);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function makeTimeSeriesList(dataList, options) {
|
|
|
|
|
- return dataList.map((seriesData, index) => {
|
|
|
|
|
- const datapoints = seriesData.datapoints || [];
|
|
|
|
|
- const alias = seriesData.target;
|
|
|
|
|
- const colorIndex = index % colors.length;
|
|
|
|
|
- const color = colors[colorIndex];
|
|
|
|
|
-
|
|
|
|
|
- const series = new TimeSeries({
|
|
|
|
|
- datapoints,
|
|
|
|
|
- alias,
|
|
|
|
|
- color,
|
|
|
|
|
- unit: seriesData.unit,
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- return series;
|
|
|
|
|
- });
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Update the query history. Side-effect: store history in local storage
|
|
|
|
|
- */
|
|
|
|
|
-function updateHistory(history: HistoryItem[], datasourceId: string, queries: string[]): HistoryItem[] {
|
|
|
|
|
- const ts = Date.now();
|
|
|
|
|
- queries.forEach(query => {
|
|
|
|
|
- history = [{ query, ts }, ...history];
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (history.length > MAX_HISTORY_ITEMS) {
|
|
|
|
|
- history = history.slice(0, MAX_HISTORY_ITEMS);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Combine all queries of a datasource type into one history
|
|
|
|
|
- const historyKey = `grafana.explore.history.${datasourceId}`;
|
|
|
|
|
- store.setObject(historyKey, history);
|
|
|
|
|
- return history;
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
interface ExploreProps {
|
|
interface ExploreProps {
|
|
|
datasourceSrv: DatasourceSrv;
|
|
datasourceSrv: DatasourceSrv;
|
|
@@ -89,14 +50,49 @@ interface ExploreProps {
|
|
|
urlState: ExploreUrlState;
|
|
urlState: ExploreUrlState;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Explore provides an area for quick query iteration for a given datasource.
|
|
|
|
|
+ * Once a datasource is selected it populates the query section at the top.
|
|
|
|
|
+ * When queries are run, their results are being displayed in the main section.
|
|
|
|
|
+ * The datasource determines what kind of query editor it brings, and what kind
|
|
|
|
|
+ * of results viewers it supports.
|
|
|
|
|
+ *
|
|
|
|
|
+ * QUERY HANDLING
|
|
|
|
|
+ *
|
|
|
|
|
+ * TLDR: to not re-render Explore during edits, query editing is not "controlled"
|
|
|
|
|
+ * in a React sense: values need to be pushed down via `initialQueries`, while
|
|
|
|
|
+ * edits travel up via `this.modifiedQueries`.
|
|
|
|
|
+ *
|
|
|
|
|
+ * By default the query rows start without prior state: `initialQueries` will
|
|
|
|
|
+ * contain one empty DataQuery. While the user modifies the DataQuery, the
|
|
|
|
|
+ * modifications are being tracked in `this.modifiedQueries`, which need to be
|
|
|
|
|
+ * used whenever a query is sent to the datasource to reflect what the user sees
|
|
|
|
|
+ * on the screen. Query rows can be initialized or reset using `initialQueries`,
|
|
|
|
|
+ * by giving the respective row a new key. This wipes the old row and its state.
|
|
|
|
|
+ * This property is also used to govern how many query rows there are (minimum 1).
|
|
|
|
|
+ *
|
|
|
|
|
+ * This flow makes sure that a query row can be arbitrarily complex without the
|
|
|
|
|
+ * fear of being wiped or re-initialized via props. The query row is free to keep
|
|
|
|
|
+ * its own state while the user edits or builds a query. Valid queries can be sent
|
|
|
|
|
+ * up to Explore via the `onChangeQuery` prop.
|
|
|
|
|
+ *
|
|
|
|
|
+ * DATASOURCE REQUESTS
|
|
|
|
|
+ *
|
|
|
|
|
+ * A click on Run Query creates transactions for all DataQueries for all expanded
|
|
|
|
|
+ * result viewers. New runs are discarding previous runs. Upon completion a transaction
|
|
|
|
|
+ * saves the result. The result viewers construct their data from the currently existing
|
|
|
|
|
+ * transactions.
|
|
|
|
|
+ *
|
|
|
|
|
+ * The result viewers determine some of the query options sent to the datasource, e.g.,
|
|
|
|
|
+ * `format`, to indicate eventual transformations by the datasources' result transformers.
|
|
|
|
|
+ */
|
|
|
export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
el: any;
|
|
el: any;
|
|
|
/**
|
|
/**
|
|
|
* Current query expressions of the rows including their modifications, used for running queries.
|
|
* Current query expressions of the rows including their modifications, used for running queries.
|
|
|
* Not kept in component state to prevent edit-render roundtrips.
|
|
* Not kept in component state to prevent edit-render roundtrips.
|
|
|
- * TODO: make this generic (other datasources might not have string representations of current query state)
|
|
|
|
|
*/
|
|
*/
|
|
|
- queryExpressions: string[];
|
|
|
|
|
|
|
+ modifiedQueries: DataQuery[];
|
|
|
/**
|
|
/**
|
|
|
* Local ID cache to compare requested vs selected datasource
|
|
* Local ID cache to compare requested vs selected datasource
|
|
|
*/
|
|
*/
|
|
@@ -105,11 +101,11 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
constructor(props) {
|
|
constructor(props) {
|
|
|
super(props);
|
|
super(props);
|
|
|
const splitState: ExploreState = props.splitState;
|
|
const splitState: ExploreState = props.splitState;
|
|
|
- let initialQueries: Query[];
|
|
|
|
|
|
|
+ let initialQueries: DataQuery[];
|
|
|
if (splitState) {
|
|
if (splitState) {
|
|
|
// Split state overrides everything
|
|
// Split state overrides everything
|
|
|
this.state = splitState;
|
|
this.state = splitState;
|
|
|
- initialQueries = splitState.queries;
|
|
|
|
|
|
|
+ initialQueries = splitState.initialQueries;
|
|
|
} else {
|
|
} else {
|
|
|
const { datasource, queries, range } = props.urlState as ExploreUrlState;
|
|
const { datasource, queries, range } = props.urlState as ExploreUrlState;
|
|
|
initialQueries = ensureQueries(queries);
|
|
initialQueries = ensureQueries(queries);
|
|
@@ -122,8 +118,8 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
datasourceName: datasource,
|
|
datasourceName: datasource,
|
|
|
exploreDatasources: [],
|
|
exploreDatasources: [],
|
|
|
graphRange: initialRange,
|
|
graphRange: initialRange,
|
|
|
|
|
+ initialQueries,
|
|
|
history: [],
|
|
history: [],
|
|
|
- queries: initialQueries,
|
|
|
|
|
queryTransactions: [],
|
|
queryTransactions: [],
|
|
|
range: initialRange,
|
|
range: initialRange,
|
|
|
showingGraph: true,
|
|
showingGraph: true,
|
|
@@ -135,7 +131,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
supportsTable: null,
|
|
supportsTable: null,
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
- this.queryExpressions = initialQueries.map(q => q.query);
|
|
|
|
|
|
|
+ this.modifiedQueries = initialQueries.slice();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async componentDidMount() {
|
|
async componentDidMount() {
|
|
@@ -198,32 +194,26 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Check if queries can be imported from previously selected datasource
|
|
// Check if queries can be imported from previously selected datasource
|
|
|
- let queryExpressions = this.queryExpressions;
|
|
|
|
|
|
|
+ let modifiedQueries = this.modifiedQueries;
|
|
|
if (origin) {
|
|
if (origin) {
|
|
|
if (origin.meta.id === datasource.meta.id) {
|
|
if (origin.meta.id === datasource.meta.id) {
|
|
|
// Keep same queries if same type of datasource
|
|
// Keep same queries if same type of datasource
|
|
|
- queryExpressions = [...this.queryExpressions];
|
|
|
|
|
|
|
+ modifiedQueries = [...this.modifiedQueries];
|
|
|
} else if (datasource.importQueries) {
|
|
} else if (datasource.importQueries) {
|
|
|
- // Datasource-specific importers, wrapping to satisfy interface
|
|
|
|
|
- const wrappedQueries: DataQuery[] = this.queryExpressions.map((query, index) => ({
|
|
|
|
|
- refId: String(index),
|
|
|
|
|
- expr: query,
|
|
|
|
|
- }));
|
|
|
|
|
- const modifiedQueries: DataQuery[] = await datasource.importQueries(wrappedQueries, origin.meta);
|
|
|
|
|
- queryExpressions = modifiedQueries.map(({ expr }) => expr);
|
|
|
|
|
|
|
+ // Datasource-specific importers
|
|
|
|
|
+ modifiedQueries = await datasource.importQueries(this.modifiedQueries, origin.meta);
|
|
|
} else {
|
|
} else {
|
|
|
// Default is blank queries
|
|
// Default is blank queries
|
|
|
- queryExpressions = this.queryExpressions.map(() => '');
|
|
|
|
|
|
|
+ modifiedQueries = ensureQueries();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Reset edit state with new queries
|
|
// Reset edit state with new queries
|
|
|
- const nextQueries = this.state.queries.map((q, i) => ({
|
|
|
|
|
- ...q,
|
|
|
|
|
- key: generateQueryKey(i),
|
|
|
|
|
- query: queryExpressions[i],
|
|
|
|
|
|
|
+ const nextQueries = this.state.initialQueries.map((q, i) => ({
|
|
|
|
|
+ ...modifiedQueries[i],
|
|
|
|
|
+ ...generateQueryKeys(i),
|
|
|
}));
|
|
}));
|
|
|
- this.queryExpressions = queryExpressions;
|
|
|
|
|
|
|
+ this.modifiedQueries = modifiedQueries;
|
|
|
|
|
|
|
|
// Custom components
|
|
// Custom components
|
|
|
const StartPage = datasource.pluginExports.ExploreStartPage;
|
|
const StartPage = datasource.pluginExports.ExploreStartPage;
|
|
@@ -239,7 +229,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
supportsTable,
|
|
supportsTable,
|
|
|
datasourceLoading: false,
|
|
datasourceLoading: false,
|
|
|
datasourceName: datasource.name,
|
|
datasourceName: datasource.name,
|
|
|
- queries: nextQueries,
|
|
|
|
|
|
|
+ initialQueries: nextQueries,
|
|
|
showingStartPage: Boolean(StartPage),
|
|
showingStartPage: Boolean(StartPage),
|
|
|
},
|
|
},
|
|
|
() => {
|
|
() => {
|
|
@@ -256,16 +246,15 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
|
|
|
|
|
onAddQueryRow = index => {
|
|
onAddQueryRow = index => {
|
|
|
// Local cache
|
|
// Local cache
|
|
|
- this.queryExpressions[index + 1] = '';
|
|
|
|
|
|
|
+ this.modifiedQueries[index + 1] = { ...generateQueryKeys(index + 1) };
|
|
|
|
|
|
|
|
this.setState(state => {
|
|
this.setState(state => {
|
|
|
- const { queries, queryTransactions } = state;
|
|
|
|
|
|
|
+ const { initialQueries, queryTransactions } = state;
|
|
|
|
|
|
|
|
- // Add row by generating new react key
|
|
|
|
|
const nextQueries = [
|
|
const nextQueries = [
|
|
|
- ...queries.slice(0, index + 1),
|
|
|
|
|
- { query: '', key: generateQueryKey() },
|
|
|
|
|
- ...queries.slice(index + 1),
|
|
|
|
|
|
|
+ ...initialQueries.slice(0, index + 1),
|
|
|
|
|
+ { ...this.modifiedQueries[index + 1] },
|
|
|
|
|
+ ...initialQueries.slice(index + 1),
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
// Ongoing transactions need to update their row indices
|
|
// Ongoing transactions need to update their row indices
|
|
@@ -279,7 +268,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
return qt;
|
|
return qt;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- return { queries: nextQueries, queryTransactions: nextQueryTransactions };
|
|
|
|
|
|
|
+ return { initialQueries: nextQueries, queryTransactions: nextQueryTransactions };
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -296,26 +285,32 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
this.setDatasource(datasource as any, origin);
|
|
this.setDatasource(datasource as any, origin);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- onChangeQuery = (value: string, index: number, override?: boolean) => {
|
|
|
|
|
|
|
+ onChangeQuery = (value: DataQuery, index: number, override?: boolean) => {
|
|
|
|
|
+ // Null value means reset
|
|
|
|
|
+ if (value === null) {
|
|
|
|
|
+ value = { ...generateQueryKeys(index) };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Keep current value in local cache
|
|
// Keep current value in local cache
|
|
|
- this.queryExpressions[index] = value;
|
|
|
|
|
|
|
+ this.modifiedQueries[index] = value;
|
|
|
|
|
|
|
|
if (override) {
|
|
if (override) {
|
|
|
this.setState(state => {
|
|
this.setState(state => {
|
|
|
- // Replace query row
|
|
|
|
|
- const { queries, queryTransactions } = state;
|
|
|
|
|
- const nextQuery: Query = {
|
|
|
|
|
- key: generateQueryKey(index),
|
|
|
|
|
- query: value,
|
|
|
|
|
|
|
+ // Replace query row by injecting new key
|
|
|
|
|
+ const { initialQueries, queryTransactions } = state;
|
|
|
|
|
+ const query: DataQuery = {
|
|
|
|
|
+ ...value,
|
|
|
|
|
+ ...generateQueryKeys(index),
|
|
|
};
|
|
};
|
|
|
- const nextQueries = [...queries];
|
|
|
|
|
- nextQueries[index] = nextQuery;
|
|
|
|
|
|
|
+ const nextQueries = [...initialQueries];
|
|
|
|
|
+ nextQueries[index] = query;
|
|
|
|
|
+ this.modifiedQueries = [...nextQueries];
|
|
|
|
|
|
|
|
// Discard ongoing transaction related to row query
|
|
// Discard ongoing transaction related to row query
|
|
|
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
|
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
- queries: nextQueries,
|
|
|
|
|
|
|
+ initialQueries: nextQueries,
|
|
|
queryTransactions: nextQueryTransactions,
|
|
queryTransactions: nextQueryTransactions,
|
|
|
};
|
|
};
|
|
|
}, this.onSubmit);
|
|
}, this.onSubmit);
|
|
@@ -330,10 +325,10 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
onClickClear = () => {
|
|
onClickClear = () => {
|
|
|
- this.queryExpressions = [''];
|
|
|
|
|
|
|
+ this.modifiedQueries = ensureQueries();
|
|
|
this.setState(
|
|
this.setState(
|
|
|
prevState => ({
|
|
prevState => ({
|
|
|
- queries: ensureQueries(),
|
|
|
|
|
|
|
+ initialQueries: [...this.modifiedQueries],
|
|
|
queryTransactions: [],
|
|
queryTransactions: [],
|
|
|
showingStartPage: Boolean(prevState.StartPage),
|
|
showingStartPage: Boolean(prevState.StartPage),
|
|
|
}),
|
|
}),
|
|
@@ -387,10 +382,10 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// Use this in help pages to set page to a single query
|
|
// Use this in help pages to set page to a single query
|
|
|
- onClickQuery = query => {
|
|
|
|
|
- const nextQueries = [{ query, key: generateQueryKey() }];
|
|
|
|
|
- this.queryExpressions = nextQueries.map(q => q.query);
|
|
|
|
|
- this.setState({ queries: nextQueries }, this.onSubmit);
|
|
|
|
|
|
|
+ onClickExample = (query: DataQuery) => {
|
|
|
|
|
+ const nextQueries = [{ ...query, ...generateQueryKeys() }];
|
|
|
|
|
+ this.modifiedQueries = [...nextQueries];
|
|
|
|
|
+ this.setState({ initialQueries: nextQueries }, this.onSubmit);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
onClickSplit = () => {
|
|
onClickSplit = () => {
|
|
@@ -430,28 +425,28 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
const preventSubmit = action.preventSubmit;
|
|
const preventSubmit = action.preventSubmit;
|
|
|
this.setState(
|
|
this.setState(
|
|
|
state => {
|
|
state => {
|
|
|
- const { queries, queryTransactions } = state;
|
|
|
|
|
- let nextQueries;
|
|
|
|
|
|
|
+ const { initialQueries, queryTransactions } = state;
|
|
|
|
|
+ let nextQueries: DataQuery[];
|
|
|
let nextQueryTransactions;
|
|
let nextQueryTransactions;
|
|
|
if (index === undefined) {
|
|
if (index === undefined) {
|
|
|
// Modify all queries
|
|
// Modify all queries
|
|
|
- nextQueries = queries.map((q, i) => ({
|
|
|
|
|
- key: generateQueryKey(i),
|
|
|
|
|
- query: datasource.modifyQuery(this.queryExpressions[i], action),
|
|
|
|
|
|
|
+ nextQueries = initialQueries.map((query, i) => ({
|
|
|
|
|
+ ...datasource.modifyQuery(this.modifiedQueries[i], action),
|
|
|
|
|
+ ...generateQueryKeys(i),
|
|
|
}));
|
|
}));
|
|
|
// Discard all ongoing transactions
|
|
// Discard all ongoing transactions
|
|
|
nextQueryTransactions = [];
|
|
nextQueryTransactions = [];
|
|
|
} else {
|
|
} else {
|
|
|
// Modify query only at index
|
|
// Modify query only at index
|
|
|
- nextQueries = queries.map((q, i) => {
|
|
|
|
|
|
|
+ nextQueries = initialQueries.map((query, i) => {
|
|
|
// Synchronise all queries with local query cache to ensure consistency
|
|
// Synchronise all queries with local query cache to ensure consistency
|
|
|
- q.query = this.queryExpressions[i];
|
|
|
|
|
|
|
+ // TODO still needed?
|
|
|
return i === index
|
|
return i === index
|
|
|
? {
|
|
? {
|
|
|
- key: generateQueryKey(index),
|
|
|
|
|
- query: datasource.modifyQuery(q.query, action),
|
|
|
|
|
|
|
+ ...datasource.modifyQuery(this.modifiedQueries[i], action),
|
|
|
|
|
+ ...generateQueryKeys(i),
|
|
|
}
|
|
}
|
|
|
- : q;
|
|
|
|
|
|
|
+ : query;
|
|
|
});
|
|
});
|
|
|
nextQueryTransactions = queryTransactions
|
|
nextQueryTransactions = queryTransactions
|
|
|
// Consume the hint corresponding to the action
|
|
// Consume the hint corresponding to the action
|
|
@@ -464,9 +459,9 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
// Preserve previous row query transaction to keep results visible if next query is incomplete
|
|
// Preserve previous row query transaction to keep results visible if next query is incomplete
|
|
|
.filter(qt => preventSubmit || qt.rowIndex !== index);
|
|
.filter(qt => preventSubmit || qt.rowIndex !== index);
|
|
|
}
|
|
}
|
|
|
- this.queryExpressions = nextQueries.map(q => q.query);
|
|
|
|
|
|
|
+ this.modifiedQueries = [...nextQueries];
|
|
|
return {
|
|
return {
|
|
|
- queries: nextQueries,
|
|
|
|
|
|
|
+ initialQueries: nextQueries,
|
|
|
queryTransactions: nextQueryTransactions,
|
|
queryTransactions: nextQueryTransactions,
|
|
|
};
|
|
};
|
|
|
},
|
|
},
|
|
@@ -478,22 +473,22 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
|
|
|
|
|
onRemoveQueryRow = index => {
|
|
onRemoveQueryRow = index => {
|
|
|
// Remove from local cache
|
|
// Remove from local cache
|
|
|
- this.queryExpressions = [...this.queryExpressions.slice(0, index), ...this.queryExpressions.slice(index + 1)];
|
|
|
|
|
|
|
+ this.modifiedQueries = [...this.modifiedQueries.slice(0, index), ...this.modifiedQueries.slice(index + 1)];
|
|
|
|
|
|
|
|
this.setState(
|
|
this.setState(
|
|
|
state => {
|
|
state => {
|
|
|
- const { queries, queryTransactions } = state;
|
|
|
|
|
- if (queries.length <= 1) {
|
|
|
|
|
|
|
+ const { initialQueries, queryTransactions } = state;
|
|
|
|
|
+ if (initialQueries.length <= 1) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
// Remove row from react state
|
|
// Remove row from react state
|
|
|
- const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
|
|
|
|
|
|
|
+ const nextQueries = [...initialQueries.slice(0, index), ...initialQueries.slice(index + 1)];
|
|
|
|
|
|
|
|
// Discard transactions related to row query
|
|
// Discard transactions related to row query
|
|
|
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
|
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
- queries: nextQueries,
|
|
|
|
|
|
|
+ initialQueries: nextQueries,
|
|
|
queryTransactions: nextQueryTransactions,
|
|
queryTransactions: nextQueryTransactions,
|
|
|
};
|
|
};
|
|
|
},
|
|
},
|
|
@@ -503,52 +498,68 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
|
|
|
|
|
onSubmit = () => {
|
|
onSubmit = () => {
|
|
|
const { showingLogs, showingGraph, showingTable, supportsGraph, supportsLogs, supportsTable } = this.state;
|
|
const { showingLogs, showingGraph, showingTable, supportsGraph, supportsLogs, supportsTable } = this.state;
|
|
|
|
|
+ // Keep table queries first since they need to return quickly
|
|
|
if (showingTable && supportsTable) {
|
|
if (showingTable && supportsTable) {
|
|
|
- this.runTableQuery();
|
|
|
|
|
|
|
+ this.runQueries(
|
|
|
|
|
+ 'Table',
|
|
|
|
|
+ {
|
|
|
|
|
+ format: 'table',
|
|
|
|
|
+ instant: true,
|
|
|
|
|
+ valueWithRefId: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ data => data[0]
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
if (showingGraph && supportsGraph) {
|
|
if (showingGraph && supportsGraph) {
|
|
|
- this.runGraphQueries();
|
|
|
|
|
|
|
+ this.runQueries(
|
|
|
|
|
+ 'Graph',
|
|
|
|
|
+ {
|
|
|
|
|
+ format: 'time_series',
|
|
|
|
|
+ instant: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ makeTimeSeriesList
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
if (showingLogs && supportsLogs) {
|
|
if (showingLogs && supportsLogs) {
|
|
|
- this.runLogsQuery();
|
|
|
|
|
|
|
+ this.runQueries('Logs', { format: 'logs' });
|
|
|
}
|
|
}
|
|
|
this.saveState();
|
|
this.saveState();
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- buildQueryOptions(
|
|
|
|
|
- query: string,
|
|
|
|
|
- rowIndex: number,
|
|
|
|
|
- targetOptions: { format: string; hinting?: boolean; instant?: boolean }
|
|
|
|
|
- ) {
|
|
|
|
|
|
|
+ buildQueryOptions(query: DataQuery, queryOptions: { format: string; hinting?: boolean; instant?: boolean }) {
|
|
|
const { datasource, range } = this.state;
|
|
const { datasource, range } = this.state;
|
|
|
const { interval, intervalMs } = getIntervals(range, datasource, this.el.offsetWidth);
|
|
const { interval, intervalMs } = getIntervals(range, datasource, this.el.offsetWidth);
|
|
|
- const targets = [
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const configuredQueries = [
|
|
|
{
|
|
{
|
|
|
- ...targetOptions,
|
|
|
|
|
- // Target identifier is needed for table transformations
|
|
|
|
|
- refId: rowIndex + 1,
|
|
|
|
|
- expr: query,
|
|
|
|
|
|
|
+ ...queryOptions,
|
|
|
|
|
+ ...query,
|
|
|
},
|
|
},
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
// Clone range for query request
|
|
// Clone range for query request
|
|
|
const queryRange: RawTimeRange = { ...range };
|
|
const queryRange: RawTimeRange = { ...range };
|
|
|
|
|
|
|
|
|
|
+ // Datasource is using `panelId + query.refId` for cancellation logic.
|
|
|
|
|
+ // Using `format` here because it relates to the view panel that the request is for.
|
|
|
|
|
+ const panelId = queryOptions.format;
|
|
|
|
|
+
|
|
|
return {
|
|
return {
|
|
|
interval,
|
|
interval,
|
|
|
intervalMs,
|
|
intervalMs,
|
|
|
- targets,
|
|
|
|
|
|
|
+ panelId,
|
|
|
|
|
+ targets: configuredQueries, // Datasources rely on DataQueries being passed under the targets key.
|
|
|
range: queryRange,
|
|
range: queryRange,
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- startQueryTransaction(query: string, rowIndex: number, resultType: ResultType, options: any): QueryTransaction {
|
|
|
|
|
- const queryOptions = this.buildQueryOptions(query, rowIndex, options);
|
|
|
|
|
|
|
+ startQueryTransaction(query: DataQuery, rowIndex: number, resultType: ResultType, options: any): QueryTransaction {
|
|
|
|
|
+ const queryOptions = this.buildQueryOptions(query, options);
|
|
|
const transaction: QueryTransaction = {
|
|
const transaction: QueryTransaction = {
|
|
|
query,
|
|
query,
|
|
|
resultType,
|
|
resultType,
|
|
|
rowIndex,
|
|
rowIndex,
|
|
|
- id: generateQueryKey(),
|
|
|
|
|
|
|
+ id: generateKey(), // reusing for unique ID
|
|
|
done: false,
|
|
done: false,
|
|
|
latency: 0,
|
|
latency: 0,
|
|
|
options: queryOptions,
|
|
options: queryOptions,
|
|
@@ -578,7 +589,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
transactionId: string,
|
|
transactionId: string,
|
|
|
result: any,
|
|
result: any,
|
|
|
latency: number,
|
|
latency: number,
|
|
|
- queries: string[],
|
|
|
|
|
|
|
+ queries: DataQuery[],
|
|
|
datasourceId: string
|
|
datasourceId: string
|
|
|
) {
|
|
) {
|
|
|
const { datasource } = this.state;
|
|
const { datasource } = this.state;
|
|
@@ -597,8 +608,8 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Get query hints
|
|
// Get query hints
|
|
|
- let hints;
|
|
|
|
|
- if (datasource.getQueryHints) {
|
|
|
|
|
|
|
+ let hints: QueryHint[];
|
|
|
|
|
+ if (datasource.getQueryHints as QueryHintGetter) {
|
|
|
hints = datasource.getQueryHints(transaction.query, result);
|
|
hints = datasource.getQueryHints(transaction.query, result);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -634,7 +645,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
|
|
|
|
|
failQueryTransaction(transactionId: string, response: any, datasourceId: string) {
|
|
failQueryTransaction(transactionId: string, response: any, datasourceId: string) {
|
|
|
const { datasource } = this.state;
|
|
const { datasource } = this.state;
|
|
|
- if (datasource.meta.id !== datasourceId) {
|
|
|
|
|
|
|
+ if (datasource.meta.id !== datasourceId || response.cancelled) {
|
|
|
// Navigated away, queries did not matter
|
|
// Navigated away, queries did not matter
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
@@ -678,88 +689,25 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- async runGraphQueries() {
|
|
|
|
|
- const queries = [...this.queryExpressions];
|
|
|
|
|
- if (!hasQuery(queries)) {
|
|
|
|
|
|
|
+ async runQueries(resultType: ResultType, queryOptions: any, resultGetter?: any) {
|
|
|
|
|
+ const queries = [...this.modifiedQueries];
|
|
|
|
|
+ if (!hasNonEmptyQuery(queries)) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
const { datasource } = this.state;
|
|
const { datasource } = this.state;
|
|
|
const datasourceId = datasource.meta.id;
|
|
const datasourceId = datasource.meta.id;
|
|
|
// Run all queries concurrently
|
|
// Run all queries concurrently
|
|
|
queries.forEach(async (query, rowIndex) => {
|
|
queries.forEach(async (query, rowIndex) => {
|
|
|
- if (query) {
|
|
|
|
|
- const transaction = this.startQueryTransaction(query, rowIndex, 'Graph', {
|
|
|
|
|
- format: 'time_series',
|
|
|
|
|
- instant: false,
|
|
|
|
|
- });
|
|
|
|
|
- try {
|
|
|
|
|
- const now = Date.now();
|
|
|
|
|
- const res = await datasource.query(transaction.options);
|
|
|
|
|
- const latency = Date.now() - now;
|
|
|
|
|
- const results = makeTimeSeriesList(res.data, transaction.options);
|
|
|
|
|
- this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
|
|
|
|
- this.setState({ graphRange: transaction.options.range });
|
|
|
|
|
- } catch (response) {
|
|
|
|
|
- this.failQueryTransaction(transaction.id, response, datasourceId);
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- this.discardTransactions(rowIndex);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async runTableQuery() {
|
|
|
|
|
- const queries = [...this.queryExpressions];
|
|
|
|
|
- if (!hasQuery(queries)) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- const { datasource } = this.state;
|
|
|
|
|
- const datasourceId = datasource.meta.id;
|
|
|
|
|
- // Run all queries concurrently
|
|
|
|
|
- queries.forEach(async (query, rowIndex) => {
|
|
|
|
|
- if (query) {
|
|
|
|
|
- const transaction = this.startQueryTransaction(query, rowIndex, 'Table', {
|
|
|
|
|
- format: 'table',
|
|
|
|
|
- instant: true,
|
|
|
|
|
- valueWithRefId: true,
|
|
|
|
|
- });
|
|
|
|
|
- try {
|
|
|
|
|
- const now = Date.now();
|
|
|
|
|
- const res = await datasource.query(transaction.options);
|
|
|
|
|
- const latency = Date.now() - now;
|
|
|
|
|
- const results = res.data[0];
|
|
|
|
|
- this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
|
|
|
|
- } catch (response) {
|
|
|
|
|
- this.failQueryTransaction(transaction.id, response, datasourceId);
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- this.discardTransactions(rowIndex);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async runLogsQuery() {
|
|
|
|
|
- const queries = [...this.queryExpressions];
|
|
|
|
|
- if (!hasQuery(queries)) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- const { datasource } = this.state;
|
|
|
|
|
- const datasourceId = datasource.meta.id;
|
|
|
|
|
- // Run all queries concurrently
|
|
|
|
|
- queries.forEach(async (query, rowIndex) => {
|
|
|
|
|
- if (query) {
|
|
|
|
|
- const transaction = this.startQueryTransaction(query, rowIndex, 'Logs', { format: 'logs' });
|
|
|
|
|
- try {
|
|
|
|
|
- const now = Date.now();
|
|
|
|
|
- const res = await datasource.query(transaction.options);
|
|
|
|
|
- const latency = Date.now() - now;
|
|
|
|
|
- const results = res.data;
|
|
|
|
|
- this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
|
|
|
|
- } catch (response) {
|
|
|
|
|
- this.failQueryTransaction(transaction.id, response, datasourceId);
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- this.discardTransactions(rowIndex);
|
|
|
|
|
|
|
+ const transaction = this.startQueryTransaction(query, rowIndex, resultType, queryOptions);
|
|
|
|
|
+ try {
|
|
|
|
|
+ const now = Date.now();
|
|
|
|
|
+ const res = await datasource.query(transaction.options);
|
|
|
|
|
+ const latency = Date.now() - now;
|
|
|
|
|
+ const results = resultGetter ? resultGetter(res.data) : res.data;
|
|
|
|
|
+ this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
|
|
|
|
+ this.setState({ graphRange: transaction.options.range });
|
|
|
|
|
+ } catch (response) {
|
|
|
|
|
+ this.failQueryTransaction(transaction.id, response, datasourceId);
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
@@ -769,7 +717,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
return {
|
|
return {
|
|
|
...this.state,
|
|
...this.state,
|
|
|
queryTransactions: [],
|
|
queryTransactions: [],
|
|
|
- queries: ensureQueries(this.queryExpressions.map(query => ({ query }))),
|
|
|
|
|
|
|
+ initialQueries: [...this.modifiedQueries],
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -789,7 +737,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
exploreDatasources,
|
|
exploreDatasources,
|
|
|
graphRange,
|
|
graphRange,
|
|
|
history,
|
|
history,
|
|
|
- queries,
|
|
|
|
|
|
|
+ initialQueries,
|
|
|
queryTransactions,
|
|
queryTransactions,
|
|
|
range,
|
|
range,
|
|
|
showingGraph,
|
|
showingGraph,
|
|
@@ -903,7 +851,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
<QueryRows
|
|
<QueryRows
|
|
|
datasource={datasource}
|
|
datasource={datasource}
|
|
|
history={history}
|
|
history={history}
|
|
|
- queries={queries}
|
|
|
|
|
|
|
+ initialQueries={initialQueries}
|
|
|
onAddQueryRow={this.onAddQueryRow}
|
|
onAddQueryRow={this.onAddQueryRow}
|
|
|
onChangeQuery={this.onChangeQuery}
|
|
onChangeQuery={this.onChangeQuery}
|
|
|
onClickHintFix={this.onModifyQueries}
|
|
onClickHintFix={this.onModifyQueries}
|
|
@@ -913,7 +861,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|
|
/>
|
|
/>
|
|
|
<main className="m-t-2">
|
|
<main className="m-t-2">
|
|
|
<ErrorBoundary>
|
|
<ErrorBoundary>
|
|
|
- {showingStartPage && <StartPage onClickQuery={this.onClickQuery} />}
|
|
|
|
|
|
|
+ {showingStartPage && <StartPage onClickExample={this.onClickExample} />}
|
|
|
{!showingStartPage && (
|
|
{!showingStartPage && (
|
|
|
<>
|
|
<>
|
|
|
{supportsGraph && (
|
|
{supportsGraph && (
|