|
|
|
@ -1,8 +1,17 @@ |
|
|
|
|
import React from 'react'; |
|
|
|
|
import { hot } from 'react-hot-loader'; |
|
|
|
|
import Select from 'react-select'; |
|
|
|
|
|
|
|
|
|
import { ExploreState, ExploreUrlState, Query } from 'app/types/explore'; |
|
|
|
|
import _ from 'lodash'; |
|
|
|
|
|
|
|
|
|
import { |
|
|
|
|
ExploreState, |
|
|
|
|
ExploreUrlState, |
|
|
|
|
HistoryItem, |
|
|
|
|
Query, |
|
|
|
|
QueryTransaction, |
|
|
|
|
Range, |
|
|
|
|
ResultType, |
|
|
|
|
} from 'app/types/explore'; |
|
|
|
|
import kbn from 'app/core/utils/kbn'; |
|
|
|
|
import colors from 'app/core/utils/colors'; |
|
|
|
|
import store from 'app/core/store'; |
|
|
|
@ -15,7 +24,6 @@ import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer' |
|
|
|
|
import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage'; |
|
|
|
|
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model'; |
|
|
|
|
|
|
|
|
|
import ElapsedTime from './ElapsedTime'; |
|
|
|
|
import QueryRows from './QueryRows'; |
|
|
|
|
import Graph from './Graph'; |
|
|
|
|
import Logs from './Logs'; |
|
|
|
@ -25,16 +33,6 @@ import { ensureQueries, generateQueryKey, hasQuery } from './utils/query'; |
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
return dataList.map((seriesData, index) => { |
|
|
|
|
const datapoints = seriesData.datapoints || []; |
|
|
|
@ -53,6 +51,25 @@ function makeTimeSeriesList(dataList, options) { |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 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 { |
|
|
|
|
datasourceSrv: any; |
|
|
|
|
onChangeSplit: (split: boolean, state?: ExploreState) => void; |
|
|
|
@ -83,6 +100,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
} else { |
|
|
|
|
const { datasource, queries, range } = props.urlState as ExploreUrlState; |
|
|
|
|
initialQueries = ensureQueries(queries); |
|
|
|
|
const initialRange = range || { ...DEFAULT_RANGE }; |
|
|
|
|
this.state = { |
|
|
|
|
datasource: null, |
|
|
|
|
datasourceError: null, |
|
|
|
@ -90,23 +108,17 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
datasourceMissing: false, |
|
|
|
|
datasourceName: datasource, |
|
|
|
|
exploreDatasources: [], |
|
|
|
|
graphResult: null, |
|
|
|
|
graphRange: initialRange, |
|
|
|
|
history: [], |
|
|
|
|
latency: 0, |
|
|
|
|
loading: false, |
|
|
|
|
logsResult: null, |
|
|
|
|
queries: initialQueries, |
|
|
|
|
queryErrors: [], |
|
|
|
|
queryHints: [], |
|
|
|
|
range: range || { ...DEFAULT_RANGE }, |
|
|
|
|
requestOptions: null, |
|
|
|
|
queryTransactions: [], |
|
|
|
|
range: initialRange, |
|
|
|
|
showingGraph: true, |
|
|
|
|
showingLogs: true, |
|
|
|
|
showingTable: true, |
|
|
|
|
supportsGraph: null, |
|
|
|
|
supportsLogs: null, |
|
|
|
|
supportsTable: null, |
|
|
|
|
tableResult: null, |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
this.queryExpressions = initialQueries.map(q => q.query); |
|
|
|
@ -200,14 +212,32 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
onAddQueryRow = index => { |
|
|
|
|
const { queries } = this.state; |
|
|
|
|
// Local cache
|
|
|
|
|
this.queryExpressions[index + 1] = ''; |
|
|
|
|
const nextQueries = [ |
|
|
|
|
...queries.slice(0, index + 1), |
|
|
|
|
{ query: '', key: generateQueryKey() }, |
|
|
|
|
...queries.slice(index + 1), |
|
|
|
|
]; |
|
|
|
|
this.setState({ queries: nextQueries }); |
|
|
|
|
|
|
|
|
|
this.setState(state => { |
|
|
|
|
const { queries, queryTransactions } = state; |
|
|
|
|
|
|
|
|
|
// Add row by generating new react key
|
|
|
|
|
const nextQueries = [ |
|
|
|
|
...queries.slice(0, index + 1), |
|
|
|
|
{ query: '', key: generateQueryKey() }, |
|
|
|
|
...queries.slice(index + 1), |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
// 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 }; |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
onChangeDatasource = async option => { |
|
|
|
@ -215,12 +245,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
datasource: null, |
|
|
|
|
datasourceError: null, |
|
|
|
|
datasourceLoading: true, |
|
|
|
|
graphResult: null, |
|
|
|
|
latency: 0, |
|
|
|
|
logsResult: null, |
|
|
|
|
queryErrors: [], |
|
|
|
|
queryHints: [], |
|
|
|
|
tableResult: null, |
|
|
|
|
queryTransactions: [], |
|
|
|
|
}); |
|
|
|
|
const datasourceName = option.value; |
|
|
|
|
const datasource = await this.props.datasourceSrv.get(datasourceName); |
|
|
|
@ -231,24 +256,25 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
// Keep current value in local cache
|
|
|
|
|
this.queryExpressions[index] = value; |
|
|
|
|
|
|
|
|
|
// Replace query row on override
|
|
|
|
|
if (override) { |
|
|
|
|
const { queries } = this.state; |
|
|
|
|
const nextQuery: Query = { |
|
|
|
|
key: generateQueryKey(index), |
|
|
|
|
query: value, |
|
|
|
|
}; |
|
|
|
|
const nextQueries = [...queries]; |
|
|
|
|
nextQueries[index] = nextQuery; |
|
|
|
|
|
|
|
|
|
this.setState( |
|
|
|
|
{ |
|
|
|
|
queryErrors: [], |
|
|
|
|
queryHints: [], |
|
|
|
|
this.setState(state => { |
|
|
|
|
// Replace query row
|
|
|
|
|
const { queries, queryTransactions } = state; |
|
|
|
|
const nextQuery: Query = { |
|
|
|
|
key: generateQueryKey(index), |
|
|
|
|
query: value, |
|
|
|
|
}; |
|
|
|
|
const nextQueries = [...queries]; |
|
|
|
|
nextQueries[index] = nextQuery; |
|
|
|
|
|
|
|
|
|
// Discard ongoing transaction related to row query
|
|
|
|
|
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index); |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
queries: nextQueries, |
|
|
|
|
}, |
|
|
|
|
this.onSubmit |
|
|
|
|
); |
|
|
|
|
queryTransactions: nextQueryTransactions, |
|
|
|
|
}; |
|
|
|
|
}, this.onSubmit); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -264,13 +290,8 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
this.queryExpressions = ['']; |
|
|
|
|
this.setState( |
|
|
|
|
{ |
|
|
|
|
graphResult: null, |
|
|
|
|
logsResult: null, |
|
|
|
|
latency: 0, |
|
|
|
|
queries: ensureQueries(), |
|
|
|
|
queryErrors: [], |
|
|
|
|
queryHints: [], |
|
|
|
|
tableResult: null, |
|
|
|
|
queryTransactions: [], |
|
|
|
|
}, |
|
|
|
|
this.saveState |
|
|
|
|
); |
|
|
|
@ -284,11 +305,41 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
onClickGraphButton = () => { |
|
|
|
|
this.setState(state => ({ showingGraph: !state.showingGraph })); |
|
|
|
|
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(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
onClickLogsButton = () => { |
|
|
|
|
this.setState(state => ({ showingLogs: !state.showingLogs })); |
|
|
|
|
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(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
onClickSplit = () => { |
|
|
|
@ -300,7 +351,22 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
onClickTableButton = () => { |
|
|
|
|
this.setState(state => ({ showingTable: !state.showingTable })); |
|
|
|
|
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(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
onClickTableCell = (columnKey: string, rowValue: string) => { |
|
|
|
@ -308,39 +374,68 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
onModifyQueries = (action: object, index?: number) => { |
|
|
|
|
const { datasource, queries } = this.state; |
|
|
|
|
const { datasource } = this.state; |
|
|
|
|
if (datasource && datasource.modifyQuery) { |
|
|
|
|
let nextQueries; |
|
|
|
|
if (index === undefined) { |
|
|
|
|
// Modify all queries
|
|
|
|
|
nextQueries = queries.map((q, i) => ({ |
|
|
|
|
key: generateQueryKey(i), |
|
|
|
|
query: datasource.modifyQuery(this.queryExpressions[i], action), |
|
|
|
|
})); |
|
|
|
|
} else { |
|
|
|
|
// Modify query only at index
|
|
|
|
|
nextQueries = [ |
|
|
|
|
...queries.slice(0, index), |
|
|
|
|
{ |
|
|
|
|
key: generateQueryKey(index), |
|
|
|
|
query: datasource.modifyQuery(this.queryExpressions[index], action), |
|
|
|
|
}, |
|
|
|
|
...queries.slice(index + 1), |
|
|
|
|
]; |
|
|
|
|
} |
|
|
|
|
this.queryExpressions = nextQueries.map(q => q.query); |
|
|
|
|
this.setState({ queries: nextQueries }, () => this.onSubmit()); |
|
|
|
|
this.setState( |
|
|
|
|
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, |
|
|
|
|
}; |
|
|
|
|
}, |
|
|
|
|
() => this.onSubmit() |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
onRemoveQueryRow = index => { |
|
|
|
|
const { queries } = this.state; |
|
|
|
|
if (queries.length <= 1) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)]; |
|
|
|
|
this.queryExpressions = nextQueries.map(q => q.query); |
|
|
|
|
this.setState({ queries: nextQueries }, () => this.onSubmit()); |
|
|
|
|
// Remove from local cache
|
|
|
|
|
this.queryExpressions = [...this.queryExpressions.slice(0, index), ...this.queryExpressions.slice(index + 1)]; |
|
|
|
|
|
|
|
|
|
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)]; |
|
|
|
|
|
|
|
|
|
// Discard transactions related to row query
|
|
|
|
|
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index); |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
queries: nextQueries, |
|
|
|
|
queryTransactions: nextQueryTransactions, |
|
|
|
|
}; |
|
|
|
|
}, |
|
|
|
|
() => this.onSubmit() |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
onSubmit = () => { |
|
|
|
@ -349,7 +444,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
this.runTableQuery(); |
|
|
|
|
} |
|
|
|
|
if (showingGraph && supportsGraph) { |
|
|
|
|
this.runGraphQuery(); |
|
|
|
|
this.runGraphQueries(); |
|
|
|
|
} |
|
|
|
|
if (showingLogs && supportsLogs) { |
|
|
|
|
this.runLogsQuery(); |
|
|
|
@ -357,32 +452,11 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
this.saveState(); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
onQuerySuccess(datasourceId: string, queries: string[]): void { |
|
|
|
|
// save queries to history
|
|
|
|
|
let { history } = this.state; |
|
|
|
|
const { datasource } = this.state; |
|
|
|
|
|
|
|
|
|
if (datasource.meta.id !== datasourceId) { |
|
|
|
|
// Navigated away, queries did not matter
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
this.setState({ history }); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
buildQueryOptions(targetOptions: { format: string; hinting?: boolean; instant?: boolean }) { |
|
|
|
|
buildQueryOptions( |
|
|
|
|
query: string, |
|
|
|
|
rowIndex: number, |
|
|
|
|
targetOptions: { format: string; hinting?: boolean; instant?: boolean } |
|
|
|
|
) { |
|
|
|
|
const { datasource, range } = this.state; |
|
|
|
|
const resolution = this.el.offsetWidth; |
|
|
|
|
const absoluteRange = { |
|
|
|
@ -390,90 +464,235 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
to: parseDate(range.to, true), |
|
|
|
|
}; |
|
|
|
|
const { interval } = kbn.calculateInterval(absoluteRange, resolution, datasource.interval); |
|
|
|
|
const targets = this.queryExpressions.map((q, i) => ({ |
|
|
|
|
...targetOptions, |
|
|
|
|
// Target identifier is needed for table transformations
|
|
|
|
|
refId: i + 1, |
|
|
|
|
expr: q, |
|
|
|
|
})); |
|
|
|
|
const targets = [ |
|
|
|
|
{ |
|
|
|
|
...targetOptions, |
|
|
|
|
// Target identifier is needed for table transformations
|
|
|
|
|
refId: rowIndex + 1, |
|
|
|
|
expr: query, |
|
|
|
|
}, |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
// Clone range for query request
|
|
|
|
|
const queryRange: Range = { ...range }; |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
interval, |
|
|
|
|
range, |
|
|
|
|
targets, |
|
|
|
|
range: queryRange, |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async runGraphQuery() { |
|
|
|
|
startQueryTransaction(query: string, rowIndex: number, resultType: ResultType, options: any): QueryTransaction { |
|
|
|
|
const queryOptions = this.buildQueryOptions(query, rowIndex, options); |
|
|
|
|
const transaction: QueryTransaction = { |
|
|
|
|
query, |
|
|
|
|
resultType, |
|
|
|
|
rowIndex, |
|
|
|
|
id: generateQueryKey(), |
|
|
|
|
done: false, |
|
|
|
|
latency: 0, |
|
|
|
|
options: queryOptions, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// 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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
completeQueryTransaction( |
|
|
|
|
transactionId: string, |
|
|
|
|
result: any, |
|
|
|
|
latency: number, |
|
|
|
|
queries: string[], |
|
|
|
|
datasourceId: string |
|
|
|
|
) { |
|
|
|
|
const { datasource } = this.state; |
|
|
|
|
if (datasource.meta.id !== datasourceId) { |
|
|
|
|
// Navigated away, queries did not matter
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.setState(state => { |
|
|
|
|
const { history, queryTransactions } = state; |
|
|
|
|
|
|
|
|
|
// Transaction might have been discarded
|
|
|
|
|
const transaction = queryTransactions.find(qt => qt.id === transactionId); |
|
|
|
|
if (!transaction) { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Get query hints
|
|
|
|
|
let hints; |
|
|
|
|
if (datasource.getQueryHints) { |
|
|
|
|
hints = datasource.getQueryHints(transaction.query, result); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Mark transactions as complete
|
|
|
|
|
const nextQueryTransactions = queryTransactions.map(qt => { |
|
|
|
|
if (qt.id === transactionId) { |
|
|
|
|
return { |
|
|
|
|
...qt, |
|
|
|
|
hints, |
|
|
|
|
latency, |
|
|
|
|
result, |
|
|
|
|
done: true, |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
return qt; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const nextHistory = updateHistory(history, datasourceId, queries); |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
history: nextHistory, |
|
|
|
|
queryTransactions: nextQueryTransactions, |
|
|
|
|
}; |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
discardTransactions(rowIndex: number) { |
|
|
|
|
this.setState(state => { |
|
|
|
|
const remainingTransactions = state.queryTransactions.filter(qt => qt.rowIndex !== rowIndex); |
|
|
|
|
return { queryTransactions: remainingTransactions }; |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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() { |
|
|
|
|
const queries = [...this.queryExpressions]; |
|
|
|
|
if (!hasQuery(queries)) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
this.setState({ latency: 0, loading: true, graphResult: null, queryErrors: [], queryHints: [] }); |
|
|
|
|
const now = Date.now(); |
|
|
|
|
const options = this.buildQueryOptions({ format: 'time_series', instant: false, hinting: true }); |
|
|
|
|
try { |
|
|
|
|
const res = await datasource.query(options); |
|
|
|
|
const result = makeTimeSeriesList(res.data, options); |
|
|
|
|
const queryHints = res.hints ? makeHints(res.hints) : []; |
|
|
|
|
const latency = Date.now() - now; |
|
|
|
|
this.setState({ latency, loading: false, graphResult: result, queryHints, requestOptions: options }); |
|
|
|
|
this.onQuerySuccess(datasource.meta.id, queries); |
|
|
|
|
} catch (response) { |
|
|
|
|
console.error(response); |
|
|
|
|
const queryError = response.data ? response.data.error : response; |
|
|
|
|
this.setState({ loading: false, queryErrors: [queryError] }); |
|
|
|
|
} |
|
|
|
|
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); |
|
|
|
|
this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId); |
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
this.discardTransactions(rowIndex); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async runTableQuery() { |
|
|
|
|
const queries = [...this.queryExpressions]; |
|
|
|
|
const { datasource } = this.state; |
|
|
|
|
if (!hasQuery(queries)) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
this.setState({ latency: 0, loading: true, queryErrors: [], queryHints: [], tableResult: null }); |
|
|
|
|
const now = Date.now(); |
|
|
|
|
const options = this.buildQueryOptions({ |
|
|
|
|
format: 'table', |
|
|
|
|
instant: true, |
|
|
|
|
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) { |
|
|
|
|
console.error(response); |
|
|
|
|
const queryError = response.data ? response.data.error : response; |
|
|
|
|
this.failQueryTransaction(transaction.id, queryError, datasourceId); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
this.discardTransactions(rowIndex); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
try { |
|
|
|
|
const res = await datasource.query(options); |
|
|
|
|
const tableModel = mergeTablesIntoModel(new TableModel(), ...res.data); |
|
|
|
|
const latency = Date.now() - now; |
|
|
|
|
this.setState({ latency, loading: false, tableResult: tableModel, requestOptions: options }); |
|
|
|
|
this.onQuerySuccess(datasource.meta.id, queries); |
|
|
|
|
} catch (response) { |
|
|
|
|
console.error(response); |
|
|
|
|
const queryError = response.data ? response.data.error : response; |
|
|
|
|
this.setState({ loading: false, queryErrors: [queryError] }); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async runLogsQuery() { |
|
|
|
|
const queries = [...this.queryExpressions]; |
|
|
|
|
const { datasource } = this.state; |
|
|
|
|
if (!hasQuery(queries)) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
this.setState({ latency: 0, loading: true, queryErrors: [], queryHints: [], logsResult: null }); |
|
|
|
|
const now = Date.now(); |
|
|
|
|
const options = this.buildQueryOptions({ |
|
|
|
|
format: 'logs', |
|
|
|
|
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) { |
|
|
|
|
console.error(response); |
|
|
|
|
const queryError = response.data ? response.data.error : response; |
|
|
|
|
this.failQueryTransaction(transaction.id, queryError, datasourceId); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
this.discardTransactions(rowIndex); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
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 }); |
|
|
|
|
this.onQuerySuccess(datasource.meta.id, queries); |
|
|
|
|
} catch (response) { |
|
|
|
|
console.error(response); |
|
|
|
|
const queryError = response.data ? response.data.error : response; |
|
|
|
|
this.setState({ loading: false, queryErrors: [queryError] }); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
request = url => { |
|
|
|
@ -485,6 +704,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
// Copy state, but copy queries including modifications
|
|
|
|
|
return { |
|
|
|
|
...this.state, |
|
|
|
|
queryTransactions: [], |
|
|
|
|
queries: ensureQueries(this.queryExpressions.map(query => ({ query }))), |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
@ -502,23 +722,17 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
datasourceLoading, |
|
|
|
|
datasourceMissing, |
|
|
|
|
exploreDatasources, |
|
|
|
|
graphResult, |
|
|
|
|
graphRange, |
|
|
|
|
history, |
|
|
|
|
latency, |
|
|
|
|
loading, |
|
|
|
|
logsResult, |
|
|
|
|
queries, |
|
|
|
|
queryErrors, |
|
|
|
|
queryHints, |
|
|
|
|
queryTransactions, |
|
|
|
|
range, |
|
|
|
|
requestOptions, |
|
|
|
|
showingGraph, |
|
|
|
|
showingLogs, |
|
|
|
|
showingTable, |
|
|
|
|
supportsGraph, |
|
|
|
|
supportsLogs, |
|
|
|
|
supportsTable, |
|
|
|
|
tableResult, |
|
|
|
|
} = this.state; |
|
|
|
|
const showingBoth = showingGraph && showingTable; |
|
|
|
|
const graphHeight = showingBoth ? '200px' : '400px'; |
|
|
|
@ -527,6 +741,20 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
const tableButtonActive = showingBoth || showingTable ? 'active' : ''; |
|
|
|
|
const exploreClass = split ? 'explore explore-split' : 'explore'; |
|
|
|
|
const selectedDatasource = datasource ? exploreDatasources.find(d => d.label === datasource.name) : undefined; |
|
|
|
|
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) |
|
|
|
|
); |
|
|
|
|
const tableResult = mergeTablesIntoModel( |
|
|
|
|
new TableModel(), |
|
|
|
|
...queryTransactions.filter(qt => qt.resultType === 'Table' && qt.done).map(qt => qt.result) |
|
|
|
|
); |
|
|
|
|
const logsResult = _.flatten( |
|
|
|
|
queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done).map(qt => qt.result) |
|
|
|
|
); |
|
|
|
|
const loading = queryTransactions.some(qt => !qt.done); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<div className={exploreClass} ref={this.getRef}> |
|
|
|
@ -584,9 +812,9 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
</div> |
|
|
|
|
<div className="navbar-buttons relative"> |
|
|
|
|
<button className="btn navbar-button--primary" onClick={this.onSubmit}> |
|
|
|
|
Run Query <i className="fa fa-level-down run-icon" /> |
|
|
|
|
Run Query{' '} |
|
|
|
|
{loading ? <i className="fa fa-spinner fa-spin run-icon" /> : <i className="fa fa-level-down run-icon" />} |
|
|
|
|
</button> |
|
|
|
|
{loading || latency ? <ElapsedTime time={latency} className="text-info" /> : null} |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
@ -605,8 +833,6 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
<QueryRows |
|
|
|
|
history={history} |
|
|
|
|
queries={queries} |
|
|
|
|
queryErrors={queryErrors} |
|
|
|
|
queryHints={queryHints} |
|
|
|
|
request={this.request} |
|
|
|
|
onAddQueryRow={this.onAddQueryRow} |
|
|
|
|
onChangeQuery={this.onChangeQuery} |
|
|
|
@ -614,6 +840,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
onExecuteQuery={this.onSubmit} |
|
|
|
|
onRemoveQueryRow={this.onRemoveQueryRow} |
|
|
|
|
supportsLogs={supportsLogs} |
|
|
|
|
transactions={queryTransactions} |
|
|
|
|
/> |
|
|
|
|
<div className="result-options"> |
|
|
|
|
{supportsGraph ? ( |
|
|
|
@ -635,23 +862,22 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { |
|
|
|
|
|
|
|
|
|
<main className="m-t-2"> |
|
|
|
|
{supportsGraph && |
|
|
|
|
showingGraph && |
|
|
|
|
graphResult && ( |
|
|
|
|
showingGraph && ( |
|
|
|
|
<Graph |
|
|
|
|
data={graphResult} |
|
|
|
|
height={graphHeight} |
|
|
|
|
loading={loading} |
|
|
|
|
loading={graphLoading} |
|
|
|
|
id={`explore-graph-${position}`} |
|
|
|
|
options={requestOptions} |
|
|
|
|
range={graphRange} |
|
|
|
|
split={split} |
|
|
|
|
/> |
|
|
|
|
)} |
|
|
|
|
{supportsTable && showingTable ? ( |
|
|
|
|
<div className="panel-container"> |
|
|
|
|
<Table data={tableResult} loading={loading} onClickCell={this.onClickTableCell} /> |
|
|
|
|
<div className="panel-container m-t-2"> |
|
|
|
|
<Table data={tableResult} loading={tableLoading} onClickCell={this.onClickTableCell} /> |
|
|
|
|
</div> |
|
|
|
|
) : null} |
|
|
|
|
{supportsLogs && showingLogs ? <Logs data={logsResult} loading={loading} /> : null} |
|
|
|
|
{supportsLogs && showingLogs ? <Logs data={logsResult} loading={logsLoading} /> : null} |
|
|
|
|
</main> |
|
|
|
|
</div> |
|
|
|
|
) : null} |
|
|
|
|