mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: add support for multiple queries
* adds +/- buttons to query rows in the Explore section * on Run Query all query expressions are submitted * `generateQueryKey` and `ensureQueries` are helpers to ensure each query field has a unique key for react.
This commit is contained in:
parent
25d3ec5bbf
commit
949e3d29e8
@ -5,29 +5,11 @@ import TimeSeries from 'app/core/time_series2';
|
|||||||
|
|
||||||
import ElapsedTime from './ElapsedTime';
|
import ElapsedTime from './ElapsedTime';
|
||||||
import Legend from './Legend';
|
import Legend from './Legend';
|
||||||
import QueryField from './QueryField';
|
import QueryRows from './QueryRows';
|
||||||
import Graph from './Graph';
|
import Graph from './Graph';
|
||||||
import Table from './Table';
|
import Table from './Table';
|
||||||
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
|
import { buildQueryOptions, ensureQueries, generateQueryKey, hasQuery } from './utils/query';
|
||||||
function buildQueryOptions({ format, interval, instant, now, query }) {
|
|
||||||
const to = now;
|
|
||||||
const from = to - 1000 * 60 * 60 * 3;
|
|
||||||
return {
|
|
||||||
interval,
|
|
||||||
range: {
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
},
|
|
||||||
targets: [
|
|
||||||
{
|
|
||||||
expr: query,
|
|
||||||
format,
|
|
||||||
instant,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeTimeSeriesList(dataList, options) {
|
function makeTimeSeriesList(dataList, options) {
|
||||||
return dataList.map((seriesData, index) => {
|
return dataList.map((seriesData, index) => {
|
||||||
@ -63,6 +45,7 @@ interface IExploreState {
|
|||||||
graphResult: any;
|
graphResult: any;
|
||||||
latency: number;
|
latency: number;
|
||||||
loading: any;
|
loading: any;
|
||||||
|
queries: any;
|
||||||
requestOptions: any;
|
requestOptions: any;
|
||||||
showingGraph: boolean;
|
showingGraph: boolean;
|
||||||
showingTable: boolean;
|
showingTable: boolean;
|
||||||
@ -72,7 +55,6 @@ interface IExploreState {
|
|||||||
// @observer
|
// @observer
|
||||||
export class Explore extends React.Component<any, IExploreState> {
|
export class Explore extends React.Component<any, IExploreState> {
|
||||||
datasourceSrv: DatasourceSrv;
|
datasourceSrv: DatasourceSrv;
|
||||||
query: string;
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -83,6 +65,7 @@ export class Explore extends React.Component<any, IExploreState> {
|
|||||||
graphResult: null,
|
graphResult: null,
|
||||||
latency: 0,
|
latency: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
queries: ensureQueries(),
|
||||||
requestOptions: null,
|
requestOptions: null,
|
||||||
showingGraph: true,
|
showingGraph: true,
|
||||||
showingTable: true,
|
showingTable: true,
|
||||||
@ -100,6 +83,27 @@ export class Explore extends React.Component<any, IExploreState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleAddQueryRow = index => {
|
||||||
|
const { queries } = this.state;
|
||||||
|
const nextQueries = [
|
||||||
|
...queries.slice(0, index + 1),
|
||||||
|
{ query: '', key: generateQueryKey() },
|
||||||
|
...queries.slice(index + 1),
|
||||||
|
];
|
||||||
|
this.setState({ queries: nextQueries });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChangeQuery = (query, index) => {
|
||||||
|
const { queries } = this.state;
|
||||||
|
const nextQuery = {
|
||||||
|
...queries[index],
|
||||||
|
query,
|
||||||
|
};
|
||||||
|
const nextQueries = [...queries];
|
||||||
|
nextQueries[index] = nextQuery;
|
||||||
|
this.setState({ queries: nextQueries });
|
||||||
|
};
|
||||||
|
|
||||||
handleClickGraphButton = () => {
|
handleClickGraphButton = () => {
|
||||||
this.setState(state => ({ showingGraph: !state.showingGraph }));
|
this.setState(state => ({ showingGraph: !state.showingGraph }));
|
||||||
};
|
};
|
||||||
@ -108,12 +112,13 @@ export class Explore extends React.Component<any, IExploreState> {
|
|||||||
this.setState(state => ({ showingTable: !state.showingTable }));
|
this.setState(state => ({ showingTable: !state.showingTable }));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleRequestError({ error }) {
|
handleRemoveQueryRow = index => {
|
||||||
console.error(error);
|
const { queries } = this.state;
|
||||||
}
|
if (queries.length <= 1) {
|
||||||
|
return;
|
||||||
handleQueryChange = query => {
|
}
|
||||||
this.query = query;
|
const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
|
||||||
|
this.setState({ queries: nextQueries }, () => this.handleSubmit());
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSubmit = () => {
|
handleSubmit = () => {
|
||||||
@ -127,9 +132,8 @@ export class Explore extends React.Component<any, IExploreState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async runGraphQuery() {
|
async runGraphQuery() {
|
||||||
const { query } = this;
|
const { datasource, queries } = this.state;
|
||||||
const { datasource } = this.state;
|
if (!hasQuery(queries)) {
|
||||||
if (!query) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({ latency: 0, loading: true, graphResult: null });
|
this.setState({ latency: 0, loading: true, graphResult: null });
|
||||||
@ -139,7 +143,7 @@ export class Explore extends React.Component<any, IExploreState> {
|
|||||||
interval: datasource.interval,
|
interval: datasource.interval,
|
||||||
instant: false,
|
instant: false,
|
||||||
now,
|
now,
|
||||||
query,
|
queries: queries.map(q => q.query),
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const res = await datasource.query(options);
|
const res = await datasource.query(options);
|
||||||
@ -153,14 +157,19 @@ export class Explore extends React.Component<any, IExploreState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async runTableQuery() {
|
async runTableQuery() {
|
||||||
const { query } = this;
|
const { datasource, queries } = this.state;
|
||||||
const { datasource } = this.state;
|
if (!hasQuery(queries)) {
|
||||||
if (!query) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({ latency: 0, loading: true, tableResult: null });
|
this.setState({ latency: 0, loading: true, tableResult: null });
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const options = buildQueryOptions({ format: 'table', interval: datasource.interval, instant: true, now, query });
|
const options = buildQueryOptions({
|
||||||
|
format: 'table',
|
||||||
|
interval: datasource.interval,
|
||||||
|
instant: true,
|
||||||
|
now,
|
||||||
|
queries: queries.map(q => q.query),
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
const res = await datasource.query(options);
|
const res = await datasource.query(options);
|
||||||
const tableModel = res.data[0];
|
const tableModel = res.data[0];
|
||||||
@ -182,10 +191,11 @@ export class Explore extends React.Component<any, IExploreState> {
|
|||||||
datasource,
|
datasource,
|
||||||
datasourceError,
|
datasourceError,
|
||||||
datasourceLoading,
|
datasourceLoading,
|
||||||
|
graphResult,
|
||||||
latency,
|
latency,
|
||||||
loading,
|
loading,
|
||||||
|
queries,
|
||||||
requestOptions,
|
requestOptions,
|
||||||
graphResult,
|
|
||||||
showingGraph,
|
showingGraph,
|
||||||
showingTable,
|
showingTable,
|
||||||
tableResult,
|
tableResult,
|
||||||
@ -205,7 +215,8 @@ export class Explore extends React.Component<any, IExploreState> {
|
|||||||
{datasource ? (
|
{datasource ? (
|
||||||
<div className="m-r-3">
|
<div className="m-r-3">
|
||||||
<div className="nav m-b-1">
|
<div className="nav m-b-1">
|
||||||
<div className="pull-right" style={{ paddingRight: '6rem' }}>
|
<div className="pull-right">
|
||||||
|
{loading || latency ? <ElapsedTime time={latency} className="" /> : null}
|
||||||
<button type="submit" className="m-l-1 btn btn-primary" onClick={this.handleSubmit}>
|
<button type="submit" className="m-l-1 btn btn-primary" onClick={this.handleSubmit}>
|
||||||
<i className="fa fa-return" /> Run Query
|
<i className="fa fa-return" /> Run Query
|
||||||
</button>
|
</button>
|
||||||
@ -219,15 +230,14 @@ export class Explore extends React.Component<any, IExploreState> {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="query-field-wrapper">
|
<QueryRows
|
||||||
<QueryField
|
queries={queries}
|
||||||
request={this.request}
|
request={this.request}
|
||||||
onPressEnter={this.handleSubmit}
|
onAddQueryRow={this.handleAddQueryRow}
|
||||||
onQueryChange={this.handleQueryChange}
|
onChangeQuery={this.handleChangeQuery}
|
||||||
onRequestError={this.handleRequestError}
|
onExecuteQuery={this.handleSubmit}
|
||||||
/>
|
onRemoveQueryRow={this.handleRemoveQueryRow}
|
||||||
</div>
|
/>
|
||||||
{loading || latency ? <ElapsedTime time={latency} className="m-l-1" /> : null}
|
|
||||||
<main className="m-t-2">
|
<main className="m-t-2">
|
||||||
{showingGraph ? (
|
{showingGraph ? (
|
||||||
<Graph data={graphResult} id="explore-1" options={requestOptions} height={graphHeight} />
|
<Graph data={graphResult} id="explore-1" options={requestOptions} height={graphHeight} />
|
||||||
|
69
public/app/containers/Explore/QueryRows.tsx
Normal file
69
public/app/containers/Explore/QueryRows.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
import QueryField from './QueryField';
|
||||||
|
|
||||||
|
class QueryRow extends PureComponent<any, any> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
query: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChangeQuery = value => {
|
||||||
|
const { index, onChangeQuery } = this.props;
|
||||||
|
this.setState({ query: value });
|
||||||
|
if (onChangeQuery) {
|
||||||
|
onChangeQuery(value, index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClickAddButton = () => {
|
||||||
|
const { index, onAddQueryRow } = this.props;
|
||||||
|
if (onAddQueryRow) {
|
||||||
|
onAddQueryRow(index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClickRemoveButton = () => {
|
||||||
|
const { index, onRemoveQueryRow } = this.props;
|
||||||
|
if (onRemoveQueryRow) {
|
||||||
|
onRemoveQueryRow(index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePressEnter = () => {
|
||||||
|
const { onExecuteQuery } = this.props;
|
||||||
|
if (onExecuteQuery) {
|
||||||
|
onExecuteQuery();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { request } = this.props;
|
||||||
|
return (
|
||||||
|
<div className="query-row">
|
||||||
|
<div className="query-row-tools">
|
||||||
|
<button className="btn btn-small btn-inverse" onClick={this.handleClickAddButton}>
|
||||||
|
<i className="fa fa-plus" />
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-small btn-inverse" onClick={this.handleClickRemoveButton}>
|
||||||
|
<i className="fa fa-minus" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="query-field-wrapper">
|
||||||
|
<QueryField onPressEnter={this.handlePressEnter} onQueryChange={this.handleChangeQuery} request={request} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class QueryRows extends PureComponent<any, any> {
|
||||||
|
render() {
|
||||||
|
const { className = '', queries, ...handlers } = this.props;
|
||||||
|
return (
|
||||||
|
<div className={className}>{queries.map((q, index) => <QueryRow key={q.key} index={index} {...handlers} />)}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
31
public/app/containers/Explore/utils/query.ts
Normal file
31
public/app/containers/Explore/utils/query.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export function buildQueryOptions({ format, interval, instant, now, queries }) {
|
||||||
|
const to = now;
|
||||||
|
const from = to - 1000 * 60 * 60 * 3;
|
||||||
|
return {
|
||||||
|
interval,
|
||||||
|
range: {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
},
|
||||||
|
targets: queries.map(expr => ({
|
||||||
|
expr,
|
||||||
|
format,
|
||||||
|
instant,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateQueryKey(index = 0) {
|
||||||
|
return `Q-${Date.now()}-${Math.random()}-${index}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureQueries(queries?) {
|
||||||
|
if (queries && typeof queries === 'object' && queries.length > 0 && typeof queries[0] === 'string') {
|
||||||
|
return queries.map((query, i) => ({ key: generateQueryKey(i), query }));
|
||||||
|
}
|
||||||
|
return [{ key: generateQueryKey(), query: '' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasQuery(queries) {
|
||||||
|
return queries.some(q => q.query);
|
||||||
|
}
|
@ -4,6 +4,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.query-row {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& + & {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-row-tools {
|
||||||
|
position: absolute;
|
||||||
|
left: -4rem;
|
||||||
|
top: 0.33rem;
|
||||||
|
> * {
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.query-field {
|
.query-field {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: Consolas, Menlo, Courier, monospace;
|
font-family: Consolas, Menlo, Courier, monospace;
|
||||||
@ -14,14 +31,14 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 6px 7px 4px;
|
padding: 6px 7px 4px;
|
||||||
width: calc(100% - 6rem);
|
width: 100%;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: rgba(0, 0, 0, 0.65);
|
color: rgba(0, 0, 0, 0.65);
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
background-image: none;
|
background-image: none;
|
||||||
border: 1px solid lightgray;
|
border: 1px solid lightgray;
|
||||||
border-radius: 4px;
|
border-radius: 3px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user