2018-04-26 04:58:42 -05:00
|
|
|
import React from 'react';
|
|
|
|
import { hot } from 'react-hot-loader';
|
2018-07-13 02:09:36 -05:00
|
|
|
import Select from 'react-select';
|
2018-10-22 10:51:42 -05:00
|
|
|
import _ from 'lodash';
|
2018-07-13 02:09:36 -05:00
|
|
|
|
2018-10-23 08:51:14 -05:00
|
|
|
import {
|
|
|
|
ExploreState,
|
|
|
|
ExploreUrlState,
|
|
|
|
HistoryItem,
|
|
|
|
Query,
|
|
|
|
QueryTransaction,
|
|
|
|
Range,
|
|
|
|
ResultType,
|
|
|
|
} from 'app/types/explore';
|
2018-07-17 08:13:44 -05:00
|
|
|
import kbn from 'app/core/utils/kbn';
|
2018-04-26 04:58:42 -05:00
|
|
|
import colors from 'app/core/utils/colors';
|
2018-08-02 09:43:33 -05:00
|
|
|
import store from 'app/core/store';
|
2018-04-26 04:58:42 -05:00
|
|
|
import TimeSeries from 'app/core/time_series2';
|
2018-07-17 08:13:44 -05:00
|
|
|
import { parse as parseDate } from 'app/core/utils/datemath';
|
2018-10-01 05:56:26 -05:00
|
|
|
import { DEFAULT_RANGE } from 'app/core/utils/explore';
|
2018-10-08 04:53:11 -05:00
|
|
|
import ResetStyles from 'app/core/components/Picker/ResetStyles';
|
|
|
|
import PickerOption from 'app/core/components/Picker/PickerOption';
|
|
|
|
import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer';
|
|
|
|
import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage';
|
2018-10-19 08:29:58 -05:00
|
|
|
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
|
2018-04-26 04:58:42 -05:00
|
|
|
|
2018-04-27 08:42:35 -05:00
|
|
|
import QueryRows from './QueryRows';
|
2018-04-26 04:58:42 -05:00
|
|
|
import Graph from './Graph';
|
2018-07-20 10:07:17 -05:00
|
|
|
import Logs from './Logs';
|
2018-04-26 04:58:42 -05:00
|
|
|
import Table from './Table';
|
2018-10-01 05:56:26 -05:00
|
|
|
import TimePicker from './TimePicker';
|
2018-07-17 08:13:44 -05:00
|
|
|
import { ensureQueries, generateQueryKey, hasQuery } from './utils/query';
|
2018-04-26 04:58:42 -05:00
|
|
|
|
2018-08-02 09:43:33 -05:00
|
|
|
const MAX_HISTORY_ITEMS = 100;
|
|
|
|
|
2018-04-26 04:58:42 -05:00
|
|
|
function makeTimeSeriesList(dataList, options) {
|
|
|
|
return dataList.map((seriesData, index) => {
|
|
|
|
const datapoints = seriesData.datapoints || [];
|
2018-05-01 06:27:25 -05:00
|
|
|
const alias = seriesData.target;
|
2018-04-26 04:58:42 -05:00
|
|
|
const colorIndex = index % colors.length;
|
|
|
|
const color = colors[colorIndex];
|
|
|
|
|
|
|
|
const series = new TimeSeries({
|
2018-04-30 10:25:25 -05:00
|
|
|
datapoints,
|
|
|
|
alias,
|
|
|
|
color,
|
2018-04-26 04:58:42 -05:00
|
|
|
unit: seriesData.unit,
|
|
|
|
});
|
|
|
|
|
|
|
|
return series;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-10-22 10:51:42 -05:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2018-09-28 09:44:07 -05:00
|
|
|
interface ExploreProps {
|
|
|
|
datasourceSrv: any;
|
|
|
|
onChangeSplit: (split: boolean, state?: ExploreState) => void;
|
|
|
|
onSaveState: (key: string, state: ExploreState) => void;
|
|
|
|
position: string;
|
|
|
|
split: boolean;
|
|
|
|
splitState?: ExploreState;
|
|
|
|
stateKey: string;
|
|
|
|
urlState: ExploreUrlState;
|
2018-04-27 11:21:20 -05:00
|
|
|
}
|
|
|
|
|
2018-09-28 10:38:50 -05:00
|
|
|
export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
2018-07-17 08:13:44 -05:00
|
|
|
el: any;
|
2018-10-02 08:21:46 -05:00
|
|
|
/**
|
|
|
|
* 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[];
|
2018-07-17 08:13:44 -05:00
|
|
|
|
2018-04-26 04:58:42 -05:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2018-09-28 09:44:07 -05:00
|
|
|
const splitState: ExploreState = props.splitState;
|
2018-10-02 08:21:46 -05:00
|
|
|
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);
|
2018-10-22 10:51:42 -05:00
|
|
|
const initialRange = range || { ...DEFAULT_RANGE };
|
2018-10-02 08:21:46 -05:00
|
|
|
this.state = {
|
|
|
|
datasource: null,
|
|
|
|
datasourceError: null,
|
|
|
|
datasourceLoading: null,
|
|
|
|
datasourceMissing: false,
|
|
|
|
datasourceName: datasource,
|
|
|
|
exploreDatasources: [],
|
2018-10-22 10:51:42 -05:00
|
|
|
graphRange: initialRange,
|
2018-10-02 08:21:46 -05:00
|
|
|
history: [],
|
|
|
|
queries: initialQueries,
|
2018-10-22 10:51:42 -05:00
|
|
|
queryTransactions: [],
|
|
|
|
range: initialRange,
|
2018-10-02 08:21:46 -05:00
|
|
|
showingGraph: true,
|
|
|
|
showingLogs: true,
|
|
|
|
showingTable: true,
|
|
|
|
supportsGraph: null,
|
|
|
|
supportsLogs: null,
|
|
|
|
supportsTable: null,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
this.queryExpressions = initialQueries.map(q => q.query);
|
2018-04-26 04:58:42 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
async componentDidMount() {
|
2018-07-13 02:09:36 -05:00
|
|
|
const { datasourceSrv } = this.props;
|
2018-09-28 09:44:07 -05:00
|
|
|
const { datasourceName } = this.state;
|
2018-07-13 02:09:36 -05:00
|
|
|
if (!datasourceSrv) {
|
|
|
|
throw new Error('No datasource service passed as props.');
|
|
|
|
}
|
|
|
|
const datasources = datasourceSrv.getExploreSources();
|
2018-10-01 08:09:10 -05:00
|
|
|
const exploreDatasources = datasources.map(ds => ({
|
|
|
|
value: ds.name,
|
|
|
|
label: ds.name,
|
|
|
|
}));
|
|
|
|
|
2018-07-13 02:09:36 -05:00
|
|
|
if (datasources.length > 0) {
|
2018-10-01 08:09:10 -05:00
|
|
|
this.setState({ datasourceLoading: true, exploreDatasources });
|
2018-07-13 02:45:56 -05:00
|
|
|
// Priority: datasource in url, default datasource, first explore datasource
|
|
|
|
let datasource;
|
2018-09-28 09:44:07 -05:00
|
|
|
if (datasourceName) {
|
|
|
|
datasource = await datasourceSrv.get(datasourceName);
|
2018-07-13 02:45:56 -05:00
|
|
|
} else {
|
|
|
|
datasource = await datasourceSrv.get();
|
|
|
|
}
|
2018-07-13 02:09:36 -05:00
|
|
|
if (!datasource.meta.explore) {
|
|
|
|
datasource = await datasourceSrv.get(datasources[0].name);
|
|
|
|
}
|
2018-09-28 09:44:07 -05:00
|
|
|
await this.setDatasource(datasource);
|
2018-04-26 04:58:42 -05:00
|
|
|
} else {
|
2018-07-13 02:09:36 -05:00
|
|
|
this.setState({ datasourceMissing: true });
|
2018-04-26 04:58:42 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-01 06:27:25 -05:00
|
|
|
componentDidCatch(error) {
|
2018-07-13 02:09:36 -05:00
|
|
|
this.setState({ datasourceError: error });
|
2018-05-01 06:27:25 -05:00
|
|
|
console.error(error);
|
|
|
|
}
|
|
|
|
|
2018-07-13 02:09:36 -05:00
|
|
|
async setDatasource(datasource) {
|
2018-07-20 10:07:17 -05:00
|
|
|
const supportsGraph = datasource.meta.metrics;
|
|
|
|
const supportsLogs = datasource.meta.logs;
|
|
|
|
const supportsTable = datasource.meta.metrics;
|
2018-08-02 09:43:33 -05:00
|
|
|
const datasourceId = datasource.meta.id;
|
2018-07-20 10:07:17 -05:00
|
|
|
let datasourceError = null;
|
|
|
|
|
2018-07-13 02:09:36 -05:00
|
|
|
try {
|
|
|
|
const testResult = await datasource.testDatasource();
|
2018-07-20 10:07:17 -05:00
|
|
|
datasourceError = testResult.status === 'success' ? null : testResult.message;
|
2018-07-13 02:09:36 -05:00
|
|
|
} catch (error) {
|
2018-07-20 10:07:17 -05:00
|
|
|
datasourceError = (error && error.statusText) || error;
|
2018-07-13 02:09:36 -05:00
|
|
|
}
|
2018-07-20 10:07:17 -05:00
|
|
|
|
2018-08-02 09:43:33 -05:00
|
|
|
const historyKey = `grafana.explore.history.${datasourceId}`;
|
|
|
|
const history = store.getObject(historyKey, []);
|
|
|
|
|
2018-08-08 09:56:21 -05:00
|
|
|
if (datasource.init) {
|
|
|
|
datasource.init();
|
|
|
|
}
|
|
|
|
|
2018-08-30 02:05:45 -05:00
|
|
|
// Keep queries but reset edit state
|
2018-10-02 08:21:46 -05:00
|
|
|
const nextQueries = this.state.queries.map((q, i) => ({
|
2018-08-30 02:05:45 -05:00
|
|
|
...q,
|
2018-10-02 08:21:46 -05:00
|
|
|
key: generateQueryKey(i),
|
|
|
|
query: this.queryExpressions[i],
|
2018-08-30 02:05:45 -05:00
|
|
|
}));
|
|
|
|
|
2018-07-20 10:07:17 -05:00
|
|
|
this.setState(
|
|
|
|
{
|
|
|
|
datasource,
|
|
|
|
datasourceError,
|
2018-08-02 09:43:33 -05:00
|
|
|
history,
|
2018-07-20 10:07:17 -05:00
|
|
|
supportsGraph,
|
|
|
|
supportsLogs,
|
|
|
|
supportsTable,
|
|
|
|
datasourceLoading: false,
|
2018-09-28 09:44:07 -05:00
|
|
|
datasourceName: datasource.name,
|
2018-08-30 02:05:45 -05:00
|
|
|
queries: nextQueries,
|
2018-07-20 10:07:17 -05:00
|
|
|
},
|
2018-09-28 09:44:07 -05:00
|
|
|
() => {
|
|
|
|
if (datasourceError === null) {
|
|
|
|
this.onSubmit();
|
|
|
|
}
|
|
|
|
}
|
2018-07-20 10:07:17 -05:00
|
|
|
);
|
2018-07-13 02:09:36 -05:00
|
|
|
}
|
|
|
|
|
2018-07-17 08:13:44 -05:00
|
|
|
getRef = el => {
|
|
|
|
this.el = el;
|
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onAddQueryRow = index => {
|
2018-10-22 10:51:42 -05:00
|
|
|
// Local cache
|
2018-10-02 08:21:46 -05:00
|
|
|
this.queryExpressions[index + 1] = '';
|
2018-10-22 10:51:42 -05:00
|
|
|
|
2018-10-24 04:08:15 -05:00
|
|
|
this.setState(state => {
|
|
|
|
const { queries, queryTransactions } = state;
|
2018-10-22 10:51:42 -05:00
|
|
|
|
2018-10-24 04:08:15 -05:00
|
|
|
// Add row by generating new react key
|
|
|
|
const nextQueries = [
|
|
|
|
...queries.slice(0, index + 1),
|
|
|
|
{ query: '', key: generateQueryKey() },
|
|
|
|
...queries.slice(index + 1),
|
|
|
|
];
|
2018-10-22 10:51:42 -05:00
|
|
|
|
2018-10-24 04:08:15 -05:00
|
|
|
// Ongoing transactions need to update their row indices
|
|
|
|
const nextQueryTransactions = queryTransactions.map(qt => {
|
|
|
|
if (qt.rowIndex > index) {
|
|
|
|
return {
|
|
|
|
...qt,
|
|
|
|
rowIndex: qt.rowIndex + 1,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return qt;
|
|
|
|
});
|
|
|
|
|
|
|
|
return { queries: nextQueries, queryTransactions: nextQueryTransactions };
|
|
|
|
});
|
2018-04-27 08:42:35 -05:00
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onChangeDatasource = async option => {
|
2018-07-13 02:09:36 -05:00
|
|
|
this.setState({
|
|
|
|
datasource: null,
|
|
|
|
datasourceError: null,
|
|
|
|
datasourceLoading: true,
|
2018-10-22 10:51:42 -05:00
|
|
|
queryTransactions: [],
|
2018-07-13 02:09:36 -05:00
|
|
|
});
|
2018-09-28 09:44:07 -05:00
|
|
|
const datasourceName = option.value;
|
|
|
|
const datasource = await this.props.datasourceSrv.get(datasourceName);
|
2018-07-13 02:09:36 -05:00
|
|
|
this.setDatasource(datasource);
|
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onChangeQuery = (value: string, index: number, override?: boolean) => {
|
2018-10-02 08:21:46 -05:00
|
|
|
// Keep current value in local cache
|
|
|
|
this.queryExpressions[index] = value;
|
|
|
|
|
2018-08-08 09:50:30 -05:00
|
|
|
if (override) {
|
2018-10-24 04:08:15 -05:00
|
|
|
this.setState(state => {
|
|
|
|
// Replace query row
|
|
|
|
const { queries, queryTransactions } = state;
|
|
|
|
const nextQuery: Query = {
|
|
|
|
key: generateQueryKey(index),
|
|
|
|
query: value,
|
|
|
|
};
|
|
|
|
const nextQueries = [...queries];
|
|
|
|
nextQueries[index] = nextQuery;
|
2018-10-02 08:21:46 -05:00
|
|
|
|
2018-10-24 04:08:15 -05:00
|
|
|
// Discard ongoing transaction related to row query
|
|
|
|
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
2018-10-22 10:51:42 -05:00
|
|
|
|
2018-10-24 04:08:15 -05:00
|
|
|
return {
|
2018-10-02 08:21:46 -05:00
|
|
|
queries: nextQueries,
|
2018-10-22 10:51:42 -05:00
|
|
|
queryTransactions: nextQueryTransactions,
|
2018-10-24 04:08:15 -05:00
|
|
|
};
|
|
|
|
}, this.onSubmit);
|
2018-08-08 09:50:30 -05:00
|
|
|
}
|
2018-04-27 08:42:35 -05:00
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onChangeTime = nextRange => {
|
2018-04-30 10:25:25 -05:00
|
|
|
const range = {
|
|
|
|
from: nextRange.from,
|
|
|
|
to: nextRange.to,
|
|
|
|
};
|
2018-08-04 04:58:54 -05:00
|
|
|
this.setState({ range }, () => this.onSubmit());
|
2018-04-30 10:25:25 -05:00
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onClickClear = () => {
|
2018-10-02 08:21:46 -05:00
|
|
|
this.queryExpressions = [''];
|
2018-09-28 09:44:07 -05:00
|
|
|
this.setState(
|
|
|
|
{
|
|
|
|
queries: ensureQueries(),
|
2018-10-22 10:51:42 -05:00
|
|
|
queryTransactions: [],
|
2018-09-28 09:44:07 -05:00
|
|
|
},
|
|
|
|
this.saveState
|
|
|
|
);
|
2018-08-04 04:58:54 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
onClickCloseSplit = () => {
|
2018-05-15 10:07:38 -05:00
|
|
|
const { onChangeSplit } = this.props;
|
|
|
|
if (onChangeSplit) {
|
|
|
|
onChangeSplit(false);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onClickGraphButton = () => {
|
2018-10-23 08:51:14 -05:00
|
|
|
this.setState(
|
|
|
|
state => {
|
|
|
|
const showingGraph = !state.showingGraph;
|
|
|
|
let nextQueryTransactions = state.queryTransactions;
|
|
|
|
if (!showingGraph) {
|
|
|
|
// Discard transactions related to Graph query
|
|
|
|
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph');
|
|
|
|
}
|
|
|
|
return { queryTransactions: nextQueryTransactions, showingGraph };
|
|
|
|
},
|
|
|
|
() => {
|
|
|
|
if (this.state.showingGraph) {
|
|
|
|
this.onSubmit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
2018-04-26 04:58:42 -05:00
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onClickLogsButton = () => {
|
2018-10-23 08:51:14 -05:00
|
|
|
this.setState(
|
|
|
|
state => {
|
|
|
|
const showingLogs = !state.showingLogs;
|
|
|
|
let nextQueryTransactions = state.queryTransactions;
|
|
|
|
if (!showingLogs) {
|
|
|
|
// Discard transactions related to Logs query
|
|
|
|
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs');
|
|
|
|
}
|
|
|
|
return { queryTransactions: nextQueryTransactions, showingLogs };
|
|
|
|
},
|
|
|
|
() => {
|
|
|
|
if (this.state.showingLogs) {
|
|
|
|
this.onSubmit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
2018-07-20 10:07:17 -05:00
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onClickSplit = () => {
|
2018-05-15 10:07:38 -05:00
|
|
|
const { onChangeSplit } = this.props;
|
|
|
|
if (onChangeSplit) {
|
2018-10-02 08:21:46 -05:00
|
|
|
const state = this.cloneState();
|
2018-08-10 06:37:15 -05:00
|
|
|
onChangeSplit(true, state);
|
2018-05-15 10:07:38 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onClickTableButton = () => {
|
2018-10-23 08:51:14 -05:00
|
|
|
this.setState(
|
|
|
|
state => {
|
|
|
|
const showingTable = !state.showingTable;
|
|
|
|
let nextQueryTransactions = state.queryTransactions;
|
|
|
|
if (!showingTable) {
|
|
|
|
// Discard transactions related to Table query
|
|
|
|
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Table');
|
|
|
|
}
|
|
|
|
return { queryTransactions: nextQueryTransactions, showingTable };
|
|
|
|
},
|
|
|
|
() => {
|
|
|
|
if (this.state.showingTable) {
|
|
|
|
this.onSubmit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
2018-04-26 04:58:42 -05:00
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onClickTableCell = (columnKey: string, rowValue: string) => {
|
2018-08-08 09:50:30 -05:00
|
|
|
this.onModifyQueries({ type: 'ADD_FILTER', key: columnKey, value: rowValue });
|
|
|
|
};
|
|
|
|
|
|
|
|
onModifyQueries = (action: object, index?: number) => {
|
2018-10-24 04:08:15 -05:00
|
|
|
const { datasource } = this.state;
|
2018-08-04 04:58:54 -05:00
|
|
|
if (datasource && datasource.modifyQuery) {
|
2018-10-22 10:51:42 -05:00
|
|
|
this.setState(
|
2018-10-24 04:08:15 -05:00
|
|
|
state => {
|
|
|
|
const { queries, queryTransactions } = state;
|
|
|
|
let nextQueries;
|
|
|
|
let nextQueryTransactions;
|
|
|
|
if (index === undefined) {
|
|
|
|
// Modify all queries
|
|
|
|
nextQueries = queries.map((q, i) => ({
|
|
|
|
key: generateQueryKey(i),
|
|
|
|
query: datasource.modifyQuery(this.queryExpressions[i], action),
|
|
|
|
}));
|
|
|
|
// Discard all ongoing transactions
|
|
|
|
nextQueryTransactions = [];
|
|
|
|
} else {
|
|
|
|
// Modify query only at index
|
|
|
|
nextQueries = [
|
|
|
|
...queries.slice(0, index),
|
|
|
|
{
|
|
|
|
key: generateQueryKey(index),
|
|
|
|
query: datasource.modifyQuery(this.queryExpressions[index], action),
|
|
|
|
},
|
|
|
|
...queries.slice(index + 1),
|
|
|
|
];
|
|
|
|
// Discard transactions related to row query
|
|
|
|
nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
|
|
|
}
|
|
|
|
this.queryExpressions = nextQueries.map(q => q.query);
|
|
|
|
return {
|
|
|
|
queries: nextQueries,
|
|
|
|
queryTransactions: nextQueryTransactions,
|
|
|
|
};
|
2018-10-22 10:51:42 -05:00
|
|
|
},
|
|
|
|
() => this.onSubmit()
|
|
|
|
);
|
2018-08-04 04:58:54 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
onRemoveQueryRow = index => {
|
2018-10-22 10:51:42 -05:00
|
|
|
// Remove from local cache
|
|
|
|
this.queryExpressions = [...this.queryExpressions.slice(0, index), ...this.queryExpressions.slice(index + 1)];
|
|
|
|
|
2018-10-24 04:08:15 -05:00
|
|
|
this.setState(
|
|
|
|
state => {
|
|
|
|
const { queries, queryTransactions } = state;
|
|
|
|
if (queries.length <= 1) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
// Remove row from react state
|
|
|
|
const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
|
2018-10-22 10:51:42 -05:00
|
|
|
|
2018-10-24 04:08:15 -05:00
|
|
|
// Discard transactions related to row query
|
|
|
|
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
2018-10-22 10:51:42 -05:00
|
|
|
|
2018-10-24 04:08:15 -05:00
|
|
|
return {
|
|
|
|
queries: nextQueries,
|
|
|
|
queryTransactions: nextQueryTransactions,
|
|
|
|
};
|
2018-10-22 10:51:42 -05:00
|
|
|
},
|
|
|
|
() => this.onSubmit()
|
|
|
|
);
|
2018-04-26 04:58:42 -05:00
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onSubmit = () => {
|
2018-07-20 10:07:17 -05:00
|
|
|
const { showingLogs, showingGraph, showingTable, supportsGraph, supportsLogs, supportsTable } = this.state;
|
|
|
|
if (showingTable && supportsTable) {
|
2018-04-26 04:58:42 -05:00
|
|
|
this.runTableQuery();
|
|
|
|
}
|
2018-07-20 10:07:17 -05:00
|
|
|
if (showingGraph && supportsGraph) {
|
2018-10-22 10:51:42 -05:00
|
|
|
this.runGraphQueries();
|
2018-04-26 04:58:42 -05:00
|
|
|
}
|
2018-07-20 10:07:17 -05:00
|
|
|
if (showingLogs && supportsLogs) {
|
|
|
|
this.runLogsQuery();
|
|
|
|
}
|
2018-09-28 09:44:07 -05:00
|
|
|
this.saveState();
|
2018-04-26 04:58:42 -05:00
|
|
|
};
|
|
|
|
|
2018-10-23 10:04:33 -05:00
|
|
|
buildQueryOptions(
|
|
|
|
query: string,
|
|
|
|
rowIndex: number,
|
|
|
|
targetOptions: { format: string; hinting?: boolean; instant?: boolean }
|
|
|
|
) {
|
2018-10-02 08:21:46 -05:00
|
|
|
const { datasource, range } = this.state;
|
2018-07-17 08:13:44 -05:00
|
|
|
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);
|
2018-10-22 10:51:42 -05:00
|
|
|
const targets = [
|
|
|
|
{
|
|
|
|
...targetOptions,
|
|
|
|
// Target identifier is needed for table transformations
|
|
|
|
refId: rowIndex + 1,
|
|
|
|
expr: query,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
// Clone range for query request
|
|
|
|
const queryRange: Range = { ...range };
|
|
|
|
|
2018-07-17 08:13:44 -05:00
|
|
|
return {
|
|
|
|
interval,
|
|
|
|
targets,
|
2018-10-22 10:51:42 -05:00
|
|
|
range: queryRange,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-10-23 10:04:33 -05:00
|
|
|
startQueryTransaction(query: string, rowIndex: number, resultType: ResultType, options: any): QueryTransaction {
|
2018-10-22 10:51:42 -05:00
|
|
|
const queryOptions = this.buildQueryOptions(query, rowIndex, options);
|
|
|
|
const transaction: QueryTransaction = {
|
|
|
|
query,
|
|
|
|
resultType,
|
|
|
|
rowIndex,
|
|
|
|
id: generateQueryKey(),
|
|
|
|
done: false,
|
|
|
|
latency: 0,
|
|
|
|
options: queryOptions,
|
2018-07-17 08:13:44 -05:00
|
|
|
};
|
2018-10-22 10:51:42 -05:00
|
|
|
|
|
|
|
// Using updater style because we might be modifying queryTransactions in quick succession
|
|
|
|
this.setState(state => {
|
|
|
|
const { queryTransactions } = state;
|
|
|
|
// Discarding existing transactions of same type
|
|
|
|
const remainingTransactions = queryTransactions.filter(
|
|
|
|
qt => !(qt.resultType === resultType && qt.rowIndex === rowIndex)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Append new transaction
|
|
|
|
const nextQueryTransactions = [...remainingTransactions, transaction];
|
|
|
|
|
|
|
|
return {
|
|
|
|
queryTransactions: nextQueryTransactions,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
return transaction;
|
2018-07-17 08:13:44 -05:00
|
|
|
}
|
|
|
|
|
2018-10-22 10:51:42 -05:00
|
|
|
completeQueryTransaction(
|
|
|
|
transactionId: string,
|
|
|
|
result: any,
|
|
|
|
latency: number,
|
|
|
|
queries: string[],
|
|
|
|
datasourceId: string
|
|
|
|
) {
|
2018-10-02 08:21:46 -05:00
|
|
|
const { datasource } = this.state;
|
2018-10-22 10:51:42 -05:00
|
|
|
if (datasource.meta.id !== datasourceId) {
|
|
|
|
// Navigated away, queries did not matter
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState(state => {
|
|
|
|
const { history, queryTransactions } = state;
|
|
|
|
|
|
|
|
// Transaction might have been discarded
|
2018-10-23 08:12:53 -05:00
|
|
|
const transaction = queryTransactions.find(qt => qt.id === transactionId);
|
|
|
|
if (!transaction) {
|
2018-10-22 10:51:42 -05:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-10-23 08:12:53 -05:00
|
|
|
// Get query hints
|
|
|
|
let hints;
|
|
|
|
if (datasource.getQueryHints) {
|
|
|
|
hints = datasource.getQueryHints(transaction.query, result);
|
|
|
|
}
|
|
|
|
|
2018-10-22 10:51:42 -05:00
|
|
|
// Mark transactions as complete
|
|
|
|
const nextQueryTransactions = queryTransactions.map(qt => {
|
|
|
|
if (qt.id === transactionId) {
|
|
|
|
return {
|
|
|
|
...qt,
|
2018-10-23 08:12:53 -05:00
|
|
|
hints,
|
2018-10-22 10:51:42 -05:00
|
|
|
latency,
|
|
|
|
result,
|
|
|
|
done: true,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return qt;
|
|
|
|
});
|
|
|
|
|
|
|
|
const nextHistory = updateHistory(history, datasourceId, queries);
|
|
|
|
|
|
|
|
return {
|
|
|
|
history: nextHistory,
|
|
|
|
queryTransactions: nextQueryTransactions,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-10-23 10:04:33 -05:00
|
|
|
discardTransactions(rowIndex: number) {
|
|
|
|
this.setState(state => {
|
|
|
|
const remainingTransactions = state.queryTransactions.filter(qt => qt.rowIndex !== rowIndex);
|
|
|
|
return { queryTransactions: remainingTransactions };
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-10-22 10:51:42 -05:00
|
|
|
failQueryTransaction(transactionId: string, error: string, datasourceId: string) {
|
|
|
|
const { datasource } = this.state;
|
|
|
|
if (datasource.meta.id !== datasourceId) {
|
|
|
|
// Navigated away, queries did not matter
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState(state => {
|
|
|
|
// Transaction might have been discarded
|
|
|
|
if (!state.queryTransactions.find(qt => qt.id === transactionId)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark transactions as complete
|
|
|
|
const nextQueryTransactions = state.queryTransactions.map(qt => {
|
|
|
|
if (qt.id === transactionId) {
|
|
|
|
return {
|
|
|
|
...qt,
|
|
|
|
error,
|
|
|
|
done: true,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return qt;
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
queryTransactions: nextQueryTransactions,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async runGraphQueries() {
|
2018-10-02 08:21:46 -05:00
|
|
|
const queries = [...this.queryExpressions];
|
2018-04-27 08:42:35 -05:00
|
|
|
if (!hasQuery(queries)) {
|
2018-04-26 04:58:42 -05:00
|
|
|
return;
|
|
|
|
}
|
2018-10-22 10:51:42 -05:00
|
|
|
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, '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);
|
2018-10-23 08:12:53 -05:00
|
|
|
this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
2018-10-22 10:51:42 -05:00
|
|
|
this.setState({ graphRange: transaction.options.range });
|
|
|
|
} catch (response) {
|
|
|
|
console.error(response);
|
|
|
|
const queryError = response.data ? response.data.error : response;
|
|
|
|
this.failQueryTransaction(transaction.id, queryError, datasourceId);
|
|
|
|
}
|
2018-10-23 10:04:33 -05:00
|
|
|
} else {
|
|
|
|
this.discardTransactions(rowIndex);
|
2018-10-22 10:51:42 -05:00
|
|
|
}
|
|
|
|
});
|
2018-04-26 04:58:42 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
async runTableQuery() {
|
2018-10-02 08:21:46 -05:00
|
|
|
const queries = [...this.queryExpressions];
|
2018-04-27 08:42:35 -05:00
|
|
|
if (!hasQuery(queries)) {
|
2018-04-26 04:58:42 -05:00
|
|
|
return;
|
|
|
|
}
|
2018-10-22 10:51:42 -05:00
|
|
|
const { datasource } = this.state;
|
|
|
|
const datasourceId = datasource.meta.id;
|
|
|
|
// Run all queries concurrently
|
|
|
|
queries.forEach(async (query, rowIndex) => {
|
|
|
|
if (query) {
|
2018-10-23 10:04:33 -05:00
|
|
|
const transaction = this.startQueryTransaction(query, rowIndex, 'Table', {
|
|
|
|
format: 'table',
|
|
|
|
instant: true,
|
|
|
|
valueWithRefId: true,
|
|
|
|
});
|
2018-10-22 10:51:42 -05:00
|
|
|
try {
|
|
|
|
const now = Date.now();
|
|
|
|
const res = await datasource.query(transaction.options);
|
|
|
|
const latency = Date.now() - now;
|
2018-10-23 10:04:33 -05:00
|
|
|
const results = res.data[0];
|
2018-10-23 08:12:53 -05:00
|
|
|
this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
2018-10-22 10:51:42 -05:00
|
|
|
} catch (response) {
|
|
|
|
console.error(response);
|
|
|
|
const queryError = response.data ? response.data.error : response;
|
|
|
|
this.failQueryTransaction(transaction.id, queryError, datasourceId);
|
|
|
|
}
|
2018-10-23 10:04:33 -05:00
|
|
|
} else {
|
|
|
|
this.discardTransactions(rowIndex);
|
2018-10-22 10:51:42 -05:00
|
|
|
}
|
2018-04-27 08:42:35 -05:00
|
|
|
});
|
2018-04-26 04:58:42 -05:00
|
|
|
}
|
|
|
|
|
2018-07-20 10:07:17 -05:00
|
|
|
async runLogsQuery() {
|
2018-10-02 08:21:46 -05:00
|
|
|
const queries = [...this.queryExpressions];
|
2018-07-20 10:07:17 -05:00
|
|
|
if (!hasQuery(queries)) {
|
|
|
|
return;
|
|
|
|
}
|
2018-10-22 10:51:42 -05:00
|
|
|
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;
|
2018-10-23 08:12:53 -05:00
|
|
|
this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
2018-10-22 10:51:42 -05:00
|
|
|
} catch (response) {
|
|
|
|
console.error(response);
|
|
|
|
const queryError = response.data ? response.data.error : response;
|
|
|
|
this.failQueryTransaction(transaction.id, queryError, datasourceId);
|
|
|
|
}
|
2018-10-23 10:04:33 -05:00
|
|
|
} else {
|
|
|
|
this.discardTransactions(rowIndex);
|
2018-10-22 10:51:42 -05:00
|
|
|
}
|
2018-07-20 10:07:17 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-10-02 08:21:46 -05:00
|
|
|
cloneState(): ExploreState {
|
|
|
|
// Copy state, but copy queries including modifications
|
|
|
|
return {
|
|
|
|
...this.state,
|
2018-10-24 04:08:15 -05:00
|
|
|
queryTransactions: [],
|
2018-10-02 08:21:46 -05:00
|
|
|
queries: ensureQueries(this.queryExpressions.map(query => ({ query }))),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-09-28 09:44:07 -05:00
|
|
|
saveState = () => {
|
|
|
|
const { stateKey, onSaveState } = this.props;
|
2018-10-02 08:21:46 -05:00
|
|
|
onSaveState(stateKey, this.cloneState());
|
2018-09-28 09:44:07 -05:00
|
|
|
};
|
|
|
|
|
2018-04-26 04:58:42 -05:00
|
|
|
render() {
|
2018-10-01 08:09:10 -05:00
|
|
|
const { position, split } = this.props;
|
2018-04-26 04:58:42 -05:00
|
|
|
const {
|
|
|
|
datasource,
|
|
|
|
datasourceError,
|
|
|
|
datasourceLoading,
|
2018-07-13 02:09:36 -05:00
|
|
|
datasourceMissing,
|
2018-10-01 08:09:10 -05:00
|
|
|
exploreDatasources,
|
2018-10-22 10:51:42 -05:00
|
|
|
graphRange,
|
2018-08-02 09:43:33 -05:00
|
|
|
history,
|
2018-04-27 08:42:35 -05:00
|
|
|
queries,
|
2018-10-22 10:51:42 -05:00
|
|
|
queryTransactions,
|
2018-04-30 10:25:25 -05:00
|
|
|
range,
|
2018-04-26 04:58:42 -05:00
|
|
|
showingGraph,
|
2018-07-20 10:07:17 -05:00
|
|
|
showingLogs,
|
2018-04-26 04:58:42 -05:00
|
|
|
showingTable,
|
2018-07-20 10:07:17 -05:00
|
|
|
supportsGraph,
|
|
|
|
supportsLogs,
|
|
|
|
supportsTable,
|
2018-04-26 04:58:42 -05:00
|
|
|
} = this.state;
|
|
|
|
const showingBoth = showingGraph && showingTable;
|
2018-05-01 06:27:25 -05:00
|
|
|
const graphHeight = showingBoth ? '200px' : '400px';
|
|
|
|
const graphButtonActive = showingBoth || showingGraph ? 'active' : '';
|
2018-07-20 10:07:17 -05:00
|
|
|
const logsButtonActive = showingLogs ? 'active' : '';
|
2018-05-01 06:27:25 -05:00
|
|
|
const tableButtonActive = showingBoth || showingTable ? 'active' : '';
|
2018-05-15 10:07:38 -05:00
|
|
|
const exploreClass = split ? 'explore explore-split' : 'explore';
|
2018-10-10 07:55:41 -05:00
|
|
|
const selectedDatasource = datasource ? exploreDatasources.find(d => d.label === datasource.name) : undefined;
|
2018-10-22 10:51:42 -05:00
|
|
|
const graphLoading = queryTransactions.some(qt => qt.resultType === 'Graph' && !qt.done);
|
|
|
|
const tableLoading = queryTransactions.some(qt => qt.resultType === 'Table' && !qt.done);
|
|
|
|
const logsLoading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done);
|
|
|
|
const graphResult = _.flatten(
|
|
|
|
queryTransactions.filter(qt => qt.resultType === 'Graph' && qt.done && qt.result).map(qt => qt.result)
|
|
|
|
);
|
2018-10-23 10:04:33 -05:00
|
|
|
const tableResult = mergeTablesIntoModel(
|
|
|
|
new TableModel(),
|
|
|
|
...queryTransactions.filter(qt => qt.resultType === 'Table' && qt.done).map(qt => qt.result)
|
|
|
|
);
|
2018-10-22 10:51:42 -05:00
|
|
|
const logsResult = _.flatten(
|
|
|
|
queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done).map(qt => qt.result)
|
|
|
|
);
|
|
|
|
const loading = queryTransactions.some(qt => !qt.done);
|
2018-07-13 02:09:36 -05:00
|
|
|
|
2018-04-26 04:58:42 -05:00
|
|
|
return (
|
2018-07-17 08:13:44 -05:00
|
|
|
<div className={exploreClass} ref={this.getRef}>
|
2018-05-01 06:27:25 -05:00
|
|
|
<div className="navbar">
|
2018-05-15 10:07:38 -05:00
|
|
|
{position === 'left' ? (
|
|
|
|
<div>
|
|
|
|
<a className="navbar-page-btn">
|
|
|
|
<i className="fa fa-rocket" />
|
|
|
|
Explore
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
) : (
|
2018-10-23 10:04:33 -05:00
|
|
|
<div className="navbar-buttons explore-first-button">
|
|
|
|
<button className="btn navbar-button" onClick={this.onClickCloseSplit}>
|
|
|
|
Close Split
|
2018-05-15 10:07:38 -05:00
|
|
|
</button>
|
2018-10-23 10:04:33 -05:00
|
|
|
</div>
|
|
|
|
)}
|
2018-07-13 02:09:36 -05:00
|
|
|
{!datasourceMissing ? (
|
|
|
|
<div className="navbar-buttons">
|
|
|
|
<Select
|
2018-10-10 06:30:09 -05:00
|
|
|
classNamePrefix={`gf-form-select-box`}
|
2018-10-08 04:53:11 -05:00
|
|
|
isMulti={false}
|
|
|
|
isLoading={datasourceLoading}
|
|
|
|
isClearable={false}
|
2018-09-27 04:57:28 -05:00
|
|
|
className="gf-form-input gf-form-input--form-dropdown datasource-picker"
|
2018-08-04 04:58:54 -05:00
|
|
|
onChange={this.onChangeDatasource}
|
2018-10-01 08:09:10 -05:00
|
|
|
options={exploreDatasources}
|
2018-10-08 04:53:11 -05:00
|
|
|
styles={ResetStyles}
|
|
|
|
placeholder="Select datasource"
|
|
|
|
loadingMessage={() => 'Loading datasources...'}
|
|
|
|
noOptionsMessage={() => 'No datasources found'}
|
2018-10-10 07:55:41 -05:00
|
|
|
value={selectedDatasource}
|
2018-10-08 04:53:11 -05:00
|
|
|
components={{
|
|
|
|
Option: PickerOption,
|
|
|
|
IndicatorsContainer,
|
|
|
|
NoOptionsMessage,
|
|
|
|
}}
|
2018-07-13 02:09:36 -05:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
) : null}
|
2018-05-01 06:27:25 -05:00
|
|
|
<div className="navbar__spacer" />
|
2018-05-15 10:07:38 -05:00
|
|
|
{position === 'left' && !split ? (
|
|
|
|
<div className="navbar-buttons">
|
2018-08-04 04:58:54 -05:00
|
|
|
<button className="btn navbar-button" onClick={this.onClickSplit}>
|
2018-05-15 10:07:38 -05:00
|
|
|
Split
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
) : null}
|
2018-08-04 04:58:54 -05:00
|
|
|
<TimePicker range={range} onChangeTime={this.onChangeTime} />
|
2018-05-01 06:27:25 -05:00
|
|
|
<div className="navbar-buttons">
|
2018-08-04 04:47:04 -05:00
|
|
|
<button className="btn navbar-button navbar-button--no-icon" onClick={this.onClickClear}>
|
|
|
|
Clear All
|
|
|
|
</button>
|
2018-05-01 06:27:25 -05:00
|
|
|
</div>
|
|
|
|
<div className="navbar-buttons relative">
|
2018-08-04 04:58:54 -05:00
|
|
|
<button className="btn navbar-button--primary" onClick={this.onSubmit}>
|
2018-10-22 10:51:42 -05:00
|
|
|
Run Query{' '}
|
|
|
|
{loading ? <i className="fa fa-spinner fa-spin run-icon" /> : <i className="fa fa-level-down run-icon" />}
|
2018-05-01 06:27:25 -05:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
2018-04-26 04:58:42 -05:00
|
|
|
|
2018-05-01 06:27:25 -05:00
|
|
|
{datasourceLoading ? <div className="explore-container">Loading datasource...</div> : null}
|
2018-04-26 04:58:42 -05:00
|
|
|
|
2018-07-13 02:09:36 -05:00
|
|
|
{datasourceMissing ? (
|
|
|
|
<div className="explore-container">Please add a datasource that supports Explore (e.g., Prometheus).</div>
|
|
|
|
) : null}
|
|
|
|
|
2018-05-01 06:27:25 -05:00
|
|
|
{datasourceError ? (
|
2018-07-13 02:09:36 -05:00
|
|
|
<div className="explore-container">Error connecting to datasource. [{datasourceError}]</div>
|
2018-05-01 06:27:25 -05:00
|
|
|
) : null}
|
|
|
|
|
2018-07-13 02:09:36 -05:00
|
|
|
{datasource && !datasourceError ? (
|
2018-05-01 06:27:25 -05:00
|
|
|
<div className="explore-container">
|
|
|
|
<QueryRows
|
2018-10-25 05:24:24 -05:00
|
|
|
datasource={datasource}
|
2018-08-02 09:43:33 -05:00
|
|
|
history={history}
|
2018-05-01 06:27:25 -05:00
|
|
|
queries={queries}
|
2018-08-04 04:58:54 -05:00
|
|
|
onAddQueryRow={this.onAddQueryRow}
|
|
|
|
onChangeQuery={this.onChangeQuery}
|
2018-08-08 09:50:30 -05:00
|
|
|
onClickHintFix={this.onModifyQueries}
|
2018-08-04 04:58:54 -05:00
|
|
|
onExecuteQuery={this.onSubmit}
|
|
|
|
onRemoveQueryRow={this.onRemoveQueryRow}
|
2018-08-10 09:41:21 -05:00
|
|
|
supportsLogs={supportsLogs}
|
2018-10-22 10:51:42 -05:00
|
|
|
transactions={queryTransactions}
|
2018-05-01 06:27:25 -05:00
|
|
|
/>
|
2018-08-04 04:47:04 -05:00
|
|
|
<div className="result-options">
|
|
|
|
{supportsGraph ? (
|
2018-09-27 04:57:28 -05:00
|
|
|
<button className={`btn toggle-btn ${graphButtonActive}`} onClick={this.onClickGraphButton}>
|
2018-08-04 04:47:04 -05:00
|
|
|
Graph
|
|
|
|
</button>
|
|
|
|
) : null}
|
|
|
|
{supportsTable ? (
|
2018-09-27 04:57:28 -05:00
|
|
|
<button className={`btn toggle-btn ${tableButtonActive}`} onClick={this.onClickTableButton}>
|
2018-08-04 04:47:04 -05:00
|
|
|
Table
|
|
|
|
</button>
|
|
|
|
) : null}
|
|
|
|
{supportsLogs ? (
|
2018-09-27 04:57:28 -05:00
|
|
|
<button className={`btn toggle-btn ${logsButtonActive}`} onClick={this.onClickLogsButton}>
|
2018-08-04 04:47:04 -05:00
|
|
|
Logs
|
|
|
|
</button>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
|
2018-05-01 06:27:25 -05:00
|
|
|
<main className="m-t-2">
|
2018-09-24 09:35:24 -05:00
|
|
|
{supportsGraph &&
|
2018-10-22 10:51:42 -05:00
|
|
|
showingGraph && (
|
2018-09-24 09:35:24 -05:00
|
|
|
<Graph
|
|
|
|
data={graphResult}
|
|
|
|
height={graphHeight}
|
2018-10-22 10:51:42 -05:00
|
|
|
loading={graphLoading}
|
2018-09-24 09:35:24 -05:00
|
|
|
id={`explore-graph-${position}`}
|
2018-10-22 10:51:42 -05:00
|
|
|
range={graphRange}
|
2018-09-24 09:35:24 -05:00
|
|
|
split={split}
|
|
|
|
/>
|
|
|
|
)}
|
2018-08-02 09:43:33 -05:00
|
|
|
{supportsTable && showingTable ? (
|
2018-10-22 10:51:42 -05:00
|
|
|
<div className="panel-container m-t-2">
|
|
|
|
<Table data={tableResult} loading={tableLoading} onClickCell={this.onClickTableCell} />
|
2018-10-17 07:58:04 -05:00
|
|
|
</div>
|
2018-08-02 09:43:33 -05:00
|
|
|
) : null}
|
2018-10-22 10:51:42 -05:00
|
|
|
{supportsLogs && showingLogs ? <Logs data={logsResult} loading={logsLoading} /> : null}
|
2018-05-01 06:27:25 -05:00
|
|
|
</main>
|
|
|
|
</div>
|
|
|
|
) : null}
|
2018-04-26 04:58:42 -05:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default hot(module)(Explore);
|