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-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-13 02:09:36 -05:00
|
|
|
import { decodePathComponent } from 'app/core/utils/location_util';
|
2018-07-17 08:13:44 -05:00
|
|
|
import { parse as parseDate } from 'app/core/utils/datemath';
|
2018-04-26 04:58:42 -05:00
|
|
|
|
|
|
|
import ElapsedTime from './ElapsedTime';
|
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-04-30 10:25:25 -05:00
|
|
|
import TimePicker, { DEFAULT_RANGE } 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-08-08 09:50:30 -05:00
|
|
|
function makeHints(hints) {
|
|
|
|
const hintsByIndex = [];
|
|
|
|
hints.forEach(hint => {
|
|
|
|
if (hint) {
|
|
|
|
hintsByIndex[hint.index] = hint;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return hintsByIndex;
|
|
|
|
}
|
|
|
|
|
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-08-08 09:50:30 -05:00
|
|
|
function parseUrlState(initial: string | undefined) {
|
2018-07-17 05:24:04 -05:00
|
|
|
if (initial) {
|
|
|
|
try {
|
|
|
|
const parsed = JSON.parse(decodePathComponent(initial));
|
|
|
|
return {
|
|
|
|
datasource: parsed.datasource,
|
|
|
|
queries: parsed.queries.map(q => q.query),
|
|
|
|
range: parsed.range,
|
|
|
|
};
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
}
|
2018-04-27 11:21:20 -05:00
|
|
|
}
|
2018-07-17 05:24:04 -05:00
|
|
|
return { datasource: null, queries: [], range: DEFAULT_RANGE };
|
2018-04-27 11:21:20 -05:00
|
|
|
}
|
|
|
|
|
2018-08-24 09:48:47 -05:00
|
|
|
interface ExploreState {
|
2018-04-26 04:58:42 -05:00
|
|
|
datasource: any;
|
|
|
|
datasourceError: any;
|
2018-07-13 02:09:36 -05:00
|
|
|
datasourceLoading: boolean | null;
|
|
|
|
datasourceMissing: boolean;
|
2018-04-26 04:58:42 -05:00
|
|
|
graphResult: any;
|
2018-08-02 09:43:33 -05:00
|
|
|
history: any[];
|
2018-07-13 02:45:56 -05:00
|
|
|
initialDatasource?: string;
|
2018-04-26 04:58:42 -05:00
|
|
|
latency: number;
|
|
|
|
loading: any;
|
2018-07-20 10:07:17 -05:00
|
|
|
logsResult: any;
|
2018-08-08 09:50:30 -05:00
|
|
|
queries: any[];
|
|
|
|
queryErrors: any[];
|
|
|
|
queryHints: any[];
|
2018-04-30 10:25:25 -05:00
|
|
|
range: any;
|
2018-04-26 04:58:42 -05:00
|
|
|
requestOptions: any;
|
|
|
|
showingGraph: boolean;
|
2018-07-20 10:07:17 -05:00
|
|
|
showingLogs: boolean;
|
2018-04-26 04:58:42 -05:00
|
|
|
showingTable: boolean;
|
2018-07-20 10:07:17 -05:00
|
|
|
supportsGraph: boolean | null;
|
|
|
|
supportsLogs: boolean | null;
|
|
|
|
supportsTable: boolean | null;
|
2018-04-26 04:58:42 -05:00
|
|
|
tableResult: any;
|
|
|
|
}
|
|
|
|
|
2018-08-24 09:48:47 -05:00
|
|
|
export class Explore extends React.Component<any, ExploreState> {
|
2018-07-17 08:13:44 -05:00
|
|
|
el: any;
|
|
|
|
|
2018-04-26 04:58:42 -05:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2018-08-24 09:48:47 -05:00
|
|
|
const initialState: ExploreState = props.initialState;
|
2018-08-08 09:50:30 -05:00
|
|
|
const { datasource, queries, range } = parseUrlState(props.routeParams.state);
|
2018-04-26 04:58:42 -05:00
|
|
|
this.state = {
|
|
|
|
datasource: null,
|
|
|
|
datasourceError: null,
|
2018-07-13 02:09:36 -05:00
|
|
|
datasourceLoading: null,
|
|
|
|
datasourceMissing: false,
|
2018-04-26 04:58:42 -05:00
|
|
|
graphResult: null,
|
2018-07-13 02:45:56 -05:00
|
|
|
initialDatasource: datasource,
|
2018-08-02 09:43:33 -05:00
|
|
|
history: [],
|
2018-04-26 04:58:42 -05:00
|
|
|
latency: 0,
|
|
|
|
loading: false,
|
2018-07-20 10:07:17 -05:00
|
|
|
logsResult: null,
|
2018-04-30 10:25:25 -05:00
|
|
|
queries: ensureQueries(queries),
|
2018-08-08 09:50:30 -05:00
|
|
|
queryErrors: [],
|
|
|
|
queryHints: [],
|
2018-04-30 10:25:25 -05:00
|
|
|
range: range || { ...DEFAULT_RANGE },
|
2018-04-26 04:58:42 -05:00
|
|
|
requestOptions: null,
|
|
|
|
showingGraph: true,
|
2018-07-20 10:07:17 -05:00
|
|
|
showingLogs: true,
|
2018-04-26 04:58:42 -05:00
|
|
|
showingTable: true,
|
2018-07-20 10:07:17 -05:00
|
|
|
supportsGraph: null,
|
|
|
|
supportsLogs: null,
|
|
|
|
supportsTable: null,
|
2018-04-26 04:58:42 -05:00
|
|
|
tableResult: null,
|
2018-08-08 09:50:30 -05:00
|
|
|
...initialState,
|
2018-04-26 04:58:42 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
async componentDidMount() {
|
2018-07-13 02:09:36 -05:00
|
|
|
const { datasourceSrv } = this.props;
|
2018-07-13 02:45:56 -05:00
|
|
|
const { initialDatasource } = 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();
|
|
|
|
if (datasources.length > 0) {
|
|
|
|
this.setState({ datasourceLoading: true });
|
2018-07-13 02:45:56 -05:00
|
|
|
// Priority: datasource in url, default datasource, first explore datasource
|
|
|
|
let datasource;
|
|
|
|
if (initialDatasource) {
|
|
|
|
datasource = await datasourceSrv.get(initialDatasource);
|
|
|
|
} else {
|
|
|
|
datasource = await datasourceSrv.get();
|
|
|
|
}
|
2018-07-13 02:09:36 -05:00
|
|
|
if (!datasource.meta.explore) {
|
|
|
|
datasource = await datasourceSrv.get(datasources[0].name);
|
|
|
|
}
|
|
|
|
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
|
|
|
|
const nextQueries = this.state.queries.map(q => ({
|
|
|
|
...q,
|
|
|
|
edited: false,
|
|
|
|
}));
|
|
|
|
|
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-08-30 02:05:45 -05:00
|
|
|
queries: nextQueries,
|
2018-07-20 10:07:17 -05:00
|
|
|
},
|
2018-08-04 04:58:54 -05:00
|
|
|
() => 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-04-27 08:42:35 -05:00
|
|
|
const { queries } = this.state;
|
|
|
|
const nextQueries = [
|
|
|
|
...queries.slice(0, index + 1),
|
|
|
|
{ query: '', key: generateQueryKey() },
|
|
|
|
...queries.slice(index + 1),
|
|
|
|
];
|
|
|
|
this.setState({ queries: nextQueries });
|
|
|
|
};
|
|
|
|
|
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,
|
|
|
|
graphResult: null,
|
2018-08-10 06:37:15 -05:00
|
|
|
latency: 0,
|
2018-07-20 10:07:17 -05:00
|
|
|
logsResult: null,
|
2018-08-08 09:50:30 -05:00
|
|
|
queryErrors: [],
|
|
|
|
queryHints: [],
|
2018-07-13 02:09:36 -05:00
|
|
|
tableResult: null,
|
|
|
|
});
|
|
|
|
const datasource = await this.props.datasourceSrv.get(option.value);
|
|
|
|
this.setDatasource(datasource);
|
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onChangeQuery = (value: string, index: number, override?: boolean) => {
|
2018-04-27 08:42:35 -05:00
|
|
|
const { queries } = this.state;
|
2018-08-08 09:50:30 -05:00
|
|
|
let { queryErrors, queryHints } = this.state;
|
2018-08-03 03:20:13 -05:00
|
|
|
const prevQuery = queries[index];
|
2018-08-04 04:58:54 -05:00
|
|
|
const edited = override ? false : prevQuery.query !== value;
|
2018-04-27 08:42:35 -05:00
|
|
|
const nextQuery = {
|
|
|
|
...queries[index],
|
2018-08-03 03:20:13 -05:00
|
|
|
edited,
|
|
|
|
query: value,
|
2018-04-27 08:42:35 -05:00
|
|
|
};
|
|
|
|
const nextQueries = [...queries];
|
|
|
|
nextQueries[index] = nextQuery;
|
2018-08-08 09:50:30 -05:00
|
|
|
if (override) {
|
|
|
|
queryErrors = [];
|
|
|
|
queryHints = [];
|
|
|
|
}
|
|
|
|
this.setState(
|
|
|
|
{
|
|
|
|
queryErrors,
|
|
|
|
queryHints,
|
|
|
|
queries: nextQueries,
|
|
|
|
},
|
|
|
|
override ? () => this.onSubmit() : undefined
|
|
|
|
);
|
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 = () => {
|
|
|
|
this.setState({
|
|
|
|
graphResult: null,
|
|
|
|
logsResult: null,
|
2018-08-10 06:37:15 -05:00
|
|
|
latency: 0,
|
2018-08-04 04:58:54 -05:00
|
|
|
queries: ensureQueries(),
|
2018-08-10 06:37:15 -05:00
|
|
|
queryErrors: [],
|
|
|
|
queryHints: [],
|
2018-08-04 04:58:54 -05:00
|
|
|
tableResult: null,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
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-04-26 04:58:42 -05:00
|
|
|
this.setState(state => ({ showingGraph: !state.showingGraph }));
|
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onClickLogsButton = () => {
|
2018-07-20 10:07:17 -05:00
|
|
|
this.setState(state => ({ showingLogs: !state.showingLogs }));
|
|
|
|
};
|
|
|
|
|
2018-08-04 04:58:54 -05:00
|
|
|
onClickSplit = () => {
|
2018-05-15 10:07:38 -05:00
|
|
|
const { onChangeSplit } = this.props;
|
2018-08-10 06:37:15 -05:00
|
|
|
const state = { ...this.state };
|
|
|
|
state.queries = state.queries.map(({ edited, ...rest }) => rest);
|
2018-05-15 10:07:38 -05:00
|
|
|
if (onChangeSplit) {
|
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-04-26 04:58:42 -05:00
|
|
|
this.setState(state => ({ showingTable: !state.showingTable }));
|
|
|
|
};
|
|
|
|
|
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-08-04 04:58:54 -05:00
|
|
|
const { datasource, queries } = this.state;
|
|
|
|
if (datasource && datasource.modifyQuery) {
|
2018-08-08 09:50:30 -05:00
|
|
|
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),
|
|
|
|
];
|
|
|
|
}
|
2018-08-04 04:58:54 -05:00
|
|
|
this.setState({ queries: nextQueries }, () => this.onSubmit());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
onRemoveQueryRow = index => {
|
2018-04-27 08:42:35 -05:00
|
|
|
const { queries } = this.state;
|
|
|
|
if (queries.length <= 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
|
2018-08-04 04:58:54 -05:00
|
|
|
this.setState({ queries: nextQueries }, () => 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-04-26 04:58:42 -05:00
|
|
|
this.runGraphQuery();
|
|
|
|
}
|
2018-07-20 10:07:17 -05:00
|
|
|
if (showingLogs && supportsLogs) {
|
|
|
|
this.runLogsQuery();
|
|
|
|
}
|
2018-04-26 04:58:42 -05:00
|
|
|
};
|
|
|
|
|
2018-08-02 09:43:33 -05:00
|
|
|
onQuerySuccess(datasourceId: string, queries: any[]): void {
|
|
|
|
// save queries to history
|
2018-08-26 14:52:57 -05:00
|
|
|
let { history } = this.state;
|
|
|
|
const { datasource } = this.state;
|
|
|
|
|
2018-08-02 09:43:33 -05:00
|
|
|
if (datasource.meta.id !== datasourceId) {
|
|
|
|
// Navigated away, queries did not matter
|
|
|
|
return;
|
|
|
|
}
|
2018-08-26 14:52:57 -05:00
|
|
|
|
2018-08-02 09:43:33 -05:00
|
|
|
const ts = Date.now();
|
|
|
|
queries.forEach(q => {
|
|
|
|
const { query } = q;
|
2018-08-03 04:40:44 -05:00
|
|
|
history = [{ query, ts }, ...history];
|
2018-08-02 09:43:33 -05:00
|
|
|
});
|
2018-08-26 14:52:57 -05:00
|
|
|
|
2018-08-02 09:43:33 -05:00
|
|
|
if (history.length > MAX_HISTORY_ITEMS) {
|
2018-08-03 04:40:44 -05:00
|
|
|
history = history.slice(0, MAX_HISTORY_ITEMS);
|
2018-08-02 09:43:33 -05:00
|
|
|
}
|
2018-08-26 14:52:57 -05:00
|
|
|
|
2018-08-02 09:43:33 -05:00
|
|
|
// Combine all queries of a datasource type into one history
|
|
|
|
const historyKey = `grafana.explore.history.${datasourceId}`;
|
|
|
|
store.setObject(historyKey, history);
|
|
|
|
this.setState({ history });
|
|
|
|
}
|
|
|
|
|
2018-08-08 09:50:30 -05:00
|
|
|
buildQueryOptions(targetOptions: { format: string; hinting?: boolean; instant?: boolean }) {
|
2018-04-30 10:25:25 -05:00
|
|
|
const { datasource, queries, 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);
|
|
|
|
const targets = queries.map(q => ({
|
|
|
|
...targetOptions,
|
|
|
|
expr: q.query,
|
|
|
|
}));
|
|
|
|
return {
|
|
|
|
interval,
|
|
|
|
range,
|
|
|
|
targets,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
async runGraphQuery() {
|
|
|
|
const { datasource, queries } = this.state;
|
2018-04-27 08:42:35 -05:00
|
|
|
if (!hasQuery(queries)) {
|
2018-04-26 04:58:42 -05:00
|
|
|
return;
|
|
|
|
}
|
2018-08-08 09:50:30 -05:00
|
|
|
this.setState({ latency: 0, loading: true, graphResult: null, queryErrors: [], queryHints: [] });
|
2018-04-26 04:58:42 -05:00
|
|
|
const now = Date.now();
|
2018-08-08 09:50:30 -05:00
|
|
|
const options = this.buildQueryOptions({ format: 'time_series', instant: false, hinting: true });
|
2018-04-26 04:58:42 -05:00
|
|
|
try {
|
|
|
|
const res = await datasource.query(options);
|
|
|
|
const result = makeTimeSeriesList(res.data, options);
|
2018-08-08 09:50:30 -05:00
|
|
|
const queryHints = res.hints ? makeHints(res.hints) : [];
|
2018-04-26 04:58:42 -05:00
|
|
|
const latency = Date.now() - now;
|
2018-08-08 09:50:30 -05:00
|
|
|
this.setState({ latency, loading: false, graphResult: result, queryHints, requestOptions: options });
|
2018-08-02 09:43:33 -05:00
|
|
|
this.onQuerySuccess(datasource.meta.id, queries);
|
2018-05-01 06:27:25 -05:00
|
|
|
} catch (response) {
|
|
|
|
console.error(response);
|
|
|
|
const queryError = response.data ? response.data.error : response;
|
2018-08-08 09:50:30 -05:00
|
|
|
this.setState({ loading: false, queryErrors: [queryError] });
|
2018-04-26 04:58:42 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async runTableQuery() {
|
2018-07-17 08:13:44 -05:00
|
|
|
const { datasource, queries } = this.state;
|
2018-04-27 08:42:35 -05:00
|
|
|
if (!hasQuery(queries)) {
|
2018-04-26 04:58:42 -05:00
|
|
|
return;
|
|
|
|
}
|
2018-08-08 09:50:30 -05:00
|
|
|
this.setState({ latency: 0, loading: true, queryErrors: [], queryHints: [], tableResult: null });
|
2018-04-26 04:58:42 -05:00
|
|
|
const now = Date.now();
|
2018-07-17 08:13:44 -05:00
|
|
|
const options = this.buildQueryOptions({
|
2018-04-27 08:42:35 -05:00
|
|
|
format: 'table',
|
|
|
|
instant: true,
|
|
|
|
});
|
2018-04-26 04:58:42 -05:00
|
|
|
try {
|
|
|
|
const res = await datasource.query(options);
|
|
|
|
const tableModel = res.data[0];
|
|
|
|
const latency = Date.now() - now;
|
|
|
|
this.setState({ latency, loading: false, tableResult: tableModel, requestOptions: options });
|
2018-08-02 09:43:33 -05:00
|
|
|
this.onQuerySuccess(datasource.meta.id, queries);
|
2018-05-01 06:27:25 -05:00
|
|
|
} catch (response) {
|
|
|
|
console.error(response);
|
|
|
|
const queryError = response.data ? response.data.error : response;
|
2018-08-08 09:50:30 -05:00
|
|
|
this.setState({ loading: false, queryErrors: [queryError] });
|
2018-04-26 04:58:42 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-20 10:07:17 -05:00
|
|
|
async runLogsQuery() {
|
|
|
|
const { datasource, queries } = this.state;
|
|
|
|
if (!hasQuery(queries)) {
|
|
|
|
return;
|
|
|
|
}
|
2018-08-08 09:50:30 -05:00
|
|
|
this.setState({ latency: 0, loading: true, queryErrors: [], queryHints: [], logsResult: null });
|
2018-07-20 10:07:17 -05:00
|
|
|
const now = Date.now();
|
|
|
|
const options = this.buildQueryOptions({
|
|
|
|
format: 'logs',
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
const res = await datasource.query(options);
|
|
|
|
const logsData = res.data;
|
|
|
|
const latency = Date.now() - now;
|
|
|
|
this.setState({ latency, loading: false, logsResult: logsData, requestOptions: options });
|
2018-08-02 09:43:33 -05:00
|
|
|
this.onQuerySuccess(datasource.meta.id, queries);
|
2018-07-20 10:07:17 -05:00
|
|
|
} catch (response) {
|
|
|
|
console.error(response);
|
|
|
|
const queryError = response.data ? response.data.error : response;
|
2018-08-08 09:50:30 -05:00
|
|
|
this.setState({ loading: false, queryErrors: [queryError] });
|
2018-07-20 10:07:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-26 04:58:42 -05:00
|
|
|
request = url => {
|
|
|
|
const { datasource } = this.state;
|
|
|
|
return datasource.metadataRequest(url);
|
|
|
|
};
|
|
|
|
|
|
|
|
render() {
|
2018-07-13 02:09:36 -05:00
|
|
|
const { datasourceSrv, 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-04-27 08:42:35 -05:00
|
|
|
graphResult,
|
2018-08-02 09:43:33 -05:00
|
|
|
history,
|
2018-04-26 04:58:42 -05:00
|
|
|
latency,
|
|
|
|
loading,
|
2018-07-20 10:07:17 -05:00
|
|
|
logsResult,
|
2018-04-27 08:42:35 -05:00
|
|
|
queries,
|
2018-08-08 09:50:30 -05:00
|
|
|
queryErrors,
|
|
|
|
queryHints,
|
2018-04-30 10:25:25 -05:00
|
|
|
range,
|
2018-04-26 04:58:42 -05:00
|
|
|
requestOptions,
|
|
|
|
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
|
|
|
tableResult,
|
|
|
|
} = 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-07-13 02:09:36 -05:00
|
|
|
const datasources = datasourceSrv.getExploreSources().map(ds => ({
|
|
|
|
value: ds.name,
|
|
|
|
label: ds.name,
|
|
|
|
}));
|
|
|
|
const selectedDatasource = datasource ? datasource.name : undefined;
|
|
|
|
|
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-08-08 09:50:30 -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-08-08 09:50:30 -05:00
|
|
|
</div>
|
|
|
|
)}
|
2018-07-13 02:09:36 -05:00
|
|
|
{!datasourceMissing ? (
|
|
|
|
<div className="navbar-buttons">
|
|
|
|
<Select
|
|
|
|
clearable={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-07-13 02:09:36 -05:00
|
|
|
options={datasources}
|
2018-09-27 04:57:28 -05:00
|
|
|
isOpen={true}
|
2018-07-13 02:09:36 -05:00
|
|
|
placeholder="Loading datasources..."
|
|
|
|
value={selectedDatasource}
|
|
|
|
/>
|
|
|
|
</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-05-01 06:27:25 -05:00
|
|
|
Run Query <i className="fa fa-level-down run-icon" />
|
|
|
|
</button>
|
|
|
|
{loading || latency ? <ElapsedTime time={latency} className="text-info" /> : null}
|
|
|
|
</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-08-02 09:43:33 -05:00
|
|
|
history={history}
|
2018-05-01 06:27:25 -05:00
|
|
|
queries={queries}
|
2018-08-08 09:50:30 -05:00
|
|
|
queryErrors={queryErrors}
|
|
|
|
queryHints={queryHints}
|
2018-05-01 06:27:25 -05:00
|
|
|
request={this.request}
|
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-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 &&
|
|
|
|
showingGraph &&
|
|
|
|
graphResult && (
|
|
|
|
<Graph
|
|
|
|
data={graphResult}
|
|
|
|
height={graphHeight}
|
|
|
|
loading={loading}
|
|
|
|
id={`explore-graph-${position}`}
|
|
|
|
options={requestOptions}
|
|
|
|
split={split}
|
|
|
|
/>
|
|
|
|
)}
|
2018-08-02 09:43:33 -05:00
|
|
|
{supportsTable && showingTable ? (
|
2018-08-04 04:07:48 -05:00
|
|
|
<Table className="m-t-3" data={tableResult} loading={loading} onClickCell={this.onClickTableCell} />
|
2018-08-02 09:43:33 -05:00
|
|
|
) : null}
|
2018-08-04 04:07:48 -05:00
|
|
|
{supportsLogs && showingLogs ? <Logs data={logsResult} loading={loading} /> : 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);
|