|
@@ -19,6 +19,16 @@ import { ensureQueries, generateQueryKey, hasQuery } from './utils/query';
|
|
|
|
|
|
|
|
const MAX_HISTORY_ITEMS = 100;
|
|
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) {
|
|
function makeTimeSeriesList(dataList, options) {
|
|
|
return dataList.map((seriesData, index) => {
|
|
return dataList.map((seriesData, index) => {
|
|
|
const datapoints = seriesData.datapoints || [];
|
|
const datapoints = seriesData.datapoints || [];
|
|
@@ -37,7 +47,7 @@ function makeTimeSeriesList(dataList, options) {
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function parseInitialState(initial: string | undefined) {
|
|
|
|
|
|
|
+function parseUrlState(initial: string | undefined) {
|
|
|
if (initial) {
|
|
if (initial) {
|
|
|
try {
|
|
try {
|
|
|
const parsed = JSON.parse(decodePathComponent(initial));
|
|
const parsed = JSON.parse(decodePathComponent(initial));
|
|
@@ -64,8 +74,9 @@ interface IExploreState {
|
|
|
latency: number;
|
|
latency: number;
|
|
|
loading: any;
|
|
loading: any;
|
|
|
logsResult: any;
|
|
logsResult: any;
|
|
|
- queries: any;
|
|
|
|
|
- queryError: any;
|
|
|
|
|
|
|
+ queries: any[];
|
|
|
|
|
+ queryErrors: any[];
|
|
|
|
|
+ queryHints: any[];
|
|
|
range: any;
|
|
range: any;
|
|
|
requestOptions: any;
|
|
requestOptions: any;
|
|
|
showingGraph: boolean;
|
|
showingGraph: boolean;
|
|
@@ -82,7 +93,8 @@ export class Explore extends React.Component<any, IExploreState> {
|
|
|
|
|
|
|
|
constructor(props) {
|
|
constructor(props) {
|
|
|
super(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 = {
|
|
this.state = {
|
|
|
datasource: null,
|
|
datasource: null,
|
|
|
datasourceError: null,
|
|
datasourceError: null,
|
|
@@ -95,7 +107,8 @@ export class Explore extends React.Component<any, IExploreState> {
|
|
|
loading: false,
|
|
loading: false,
|
|
|
logsResult: null,
|
|
logsResult: null,
|
|
|
queries: ensureQueries(queries),
|
|
queries: ensureQueries(queries),
|
|
|
- queryError: null,
|
|
|
|
|
|
|
+ queryErrors: [],
|
|
|
|
|
+ queryHints: [],
|
|
|
range: range || { ...DEFAULT_RANGE },
|
|
range: range || { ...DEFAULT_RANGE },
|
|
|
requestOptions: null,
|
|
requestOptions: null,
|
|
|
showingGraph: true,
|
|
showingGraph: true,
|
|
@@ -105,7 +118,7 @@ export class Explore extends React.Component<any, IExploreState> {
|
|
|
supportsLogs: null,
|
|
supportsLogs: null,
|
|
|
supportsTable: null,
|
|
supportsTable: null,
|
|
|
tableResult: null,
|
|
tableResult: null,
|
|
|
- ...props.initialState,
|
|
|
|
|
|
|
+ ...initialState,
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -191,6 +204,8 @@ export class Explore extends React.Component<any, IExploreState> {
|
|
|
datasourceLoading: true,
|
|
datasourceLoading: true,
|
|
|
graphResult: null,
|
|
graphResult: null,
|
|
|
logsResult: null,
|
|
logsResult: null,
|
|
|
|
|
+ queryErrors: [],
|
|
|
|
|
+ queryHints: [],
|
|
|
tableResult: null,
|
|
tableResult: null,
|
|
|
});
|
|
});
|
|
|
const datasource = await this.props.datasourceSrv.get(option.value);
|
|
const datasource = await this.props.datasourceSrv.get(option.value);
|
|
@@ -199,6 +214,7 @@ export class Explore extends React.Component<any, IExploreState> {
|
|
|
|
|
|
|
|
onChangeQuery = (value: string, index: number, override?: boolean) => {
|
|
onChangeQuery = (value: string, index: number, override?: boolean) => {
|
|
|
const { queries } = this.state;
|
|
const { queries } = this.state;
|
|
|
|
|
+ let { queryErrors, queryHints } = this.state;
|
|
|
const prevQuery = queries[index];
|
|
const prevQuery = queries[index];
|
|
|
const edited = override ? false : prevQuery.query !== value;
|
|
const edited = override ? false : prevQuery.query !== value;
|
|
|
const nextQuery = {
|
|
const nextQuery = {
|
|
@@ -208,7 +224,18 @@ export class Explore extends React.Component<any, IExploreState> {
|
|
|
};
|
|
};
|
|
|
const nextQueries = [...queries];
|
|
const nextQueries = [...queries];
|
|
|
nextQueries[index] = nextQuery;
|
|
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 => {
|
|
onChangeTime = nextRange => {
|
|
@@ -255,13 +282,32 @@ export class Explore extends React.Component<any, IExploreState> {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
onClickTableCell = (columnKey: string, rowValue: string) => {
|
|
onClickTableCell = (columnKey: string, rowValue: string) => {
|
|
|
|
|
+ this.onModifyQueries({ type: 'ADD_FILTER', key: columnKey, value: rowValue });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ onModifyQueries = (action: object, index?: number) => {
|
|
|
const { datasource, queries } = this.state;
|
|
const { datasource, queries } = this.state;
|
|
|
if (datasource && datasource.modifyQuery) {
|
|
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());
|
|
this.setState({ queries: nextQueries }, () => this.onSubmit());
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
@@ -309,7 +355,7 @@ export class Explore extends React.Component<any, IExploreState> {
|
|
|
this.setState({ history });
|
|
this.setState({ history });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- buildQueryOptions(targetOptions: { format: string; instant?: boolean }) {
|
|
|
|
|
|
|
+ buildQueryOptions(targetOptions: { format: string; hinting?: boolean; instant?: boolean }) {
|
|
|
const { datasource, queries, range } = this.state;
|
|
const { datasource, queries, range } = this.state;
|
|
|
const resolution = this.el.offsetWidth;
|
|
const resolution = this.el.offsetWidth;
|
|
|
const absoluteRange = {
|
|
const absoluteRange = {
|
|
@@ -333,19 +379,20 @@ export class Explore extends React.Component<any, IExploreState> {
|
|
|
if (!hasQuery(queries)) {
|
|
if (!hasQuery(queries)) {
|
|
|
return;
|
|
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 now = Date.now();
|
|
|
- const options = this.buildQueryOptions({ format: 'time_series', instant: false });
|
|
|
|
|
|
|
+ const options = this.buildQueryOptions({ format: 'time_series', instant: false, hinting: true });
|
|
|
try {
|
|
try {
|
|
|
const res = await datasource.query(options);
|
|
const res = await datasource.query(options);
|
|
|
const result = makeTimeSeriesList(res.data, options);
|
|
const result = makeTimeSeriesList(res.data, options);
|
|
|
|
|
+ const queryHints = res.hints ? makeHints(res.hints) : [];
|
|
|
const latency = Date.now() - now;
|
|
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);
|
|
this.onQuerySuccess(datasource.meta.id, queries);
|
|
|
} catch (response) {
|
|
} catch (response) {
|
|
|
console.error(response);
|
|
console.error(response);
|
|
|
const queryError = response.data ? response.data.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<any, IExploreState> {
|
|
|
if (!hasQuery(queries)) {
|
|
if (!hasQuery(queries)) {
|
|
|
return;
|
|
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 now = Date.now();
|
|
|
const options = this.buildQueryOptions({
|
|
const options = this.buildQueryOptions({
|
|
|
format: 'table',
|
|
format: 'table',
|
|
@@ -369,7 +416,7 @@ export class Explore extends React.Component<any, IExploreState> {
|
|
|
} catch (response) {
|
|
} catch (response) {
|
|
|
console.error(response);
|
|
console.error(response);
|
|
|
const queryError = response.data ? response.data.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<any, IExploreState> {
|
|
|
if (!hasQuery(queries)) {
|
|
if (!hasQuery(queries)) {
|
|
|
return;
|
|
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 now = Date.now();
|
|
|
const options = this.buildQueryOptions({
|
|
const options = this.buildQueryOptions({
|
|
|
format: 'logs',
|
|
format: 'logs',
|
|
@@ -393,7 +440,7 @@ export class Explore extends React.Component<any, IExploreState> {
|
|
|
} catch (response) {
|
|
} catch (response) {
|
|
|
console.error(response);
|
|
console.error(response);
|
|
|
const queryError = response.data ? response.data.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<any, IExploreState> {
|
|
|
loading,
|
|
loading,
|
|
|
logsResult,
|
|
logsResult,
|
|
|
queries,
|
|
queries,
|
|
|
- queryError,
|
|
|
|
|
|
|
+ queryErrors,
|
|
|
|
|
+ queryHints,
|
|
|
range,
|
|
range,
|
|
|
requestOptions,
|
|
requestOptions,
|
|
|
showingGraph,
|
|
showingGraph,
|
|
@@ -449,12 +497,12 @@ export class Explore extends React.Component<any, IExploreState> {
|
|
|
</a>
|
|
</a>
|
|
|
</div>
|
|
</div>
|
|
|
) : (
|
|
) : (
|
|
|
- <div className="navbar-buttons explore-first-button">
|
|
|
|
|
- <button className="btn navbar-button" onClick={this.onClickCloseSplit}>
|
|
|
|
|
- Close Split
|
|
|
|
|
|
|
+ <div className="navbar-buttons explore-first-button">
|
|
|
|
|
+ <button className="btn navbar-button" onClick={this.onClickCloseSplit}>
|
|
|
|
|
+ Close Split
|
|
|
</button>
|
|
</button>
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
{!datasourceMissing ? (
|
|
{!datasourceMissing ? (
|
|
|
<div className="navbar-buttons">
|
|
<div className="navbar-buttons">
|
|
|
<Select
|
|
<Select
|
|
@@ -504,14 +552,15 @@ export class Explore extends React.Component<any, IExploreState> {
|
|
|
<QueryRows
|
|
<QueryRows
|
|
|
history={history}
|
|
history={history}
|
|
|
queries={queries}
|
|
queries={queries}
|
|
|
|
|
+ queryErrors={queryErrors}
|
|
|
|
|
+ queryHints={queryHints}
|
|
|
request={this.request}
|
|
request={this.request}
|
|
|
onAddQueryRow={this.onAddQueryRow}
|
|
onAddQueryRow={this.onAddQueryRow}
|
|
|
onChangeQuery={this.onChangeQuery}
|
|
onChangeQuery={this.onChangeQuery}
|
|
|
|
|
+ onClickHintFix={this.onModifyQueries}
|
|
|
onExecuteQuery={this.onSubmit}
|
|
onExecuteQuery={this.onSubmit}
|
|
|
onRemoveQueryRow={this.onRemoveQueryRow}
|
|
onRemoveQueryRow={this.onRemoveQueryRow}
|
|
|
/>
|
|
/>
|
|
|
- {queryError && !loading ? <div className="text-warning m-a-2">{queryError}</div> : null}
|
|
|
|
|
-
|
|
|
|
|
<div className="result-options">
|
|
<div className="result-options">
|
|
|
{supportsGraph ? (
|
|
{supportsGraph ? (
|
|
|
<button className={`btn navbar-button ${graphButtonActive}`} onClick={this.onClickGraphButton}>
|
|
<button className={`btn navbar-button ${graphButtonActive}`} onClick={this.onClickGraphButton}>
|