Commit eadaff61 by David Kaltschmidt

Explore: Design integration

* style header like other grafana components
* use panel container for graph and same styles for query field
* fix typeahead CSS selector (was created outside of .explore)
* use navbar buttons for +/- of rows
* moved elapsed time under run query button
* fix JS error on multiple timeseries being returned
* fix color for graph lines
* show prometheus query errors
parent 0d3f24ce
......@@ -4,7 +4,6 @@ import colors from 'app/core/utils/colors';
import TimeSeries from 'app/core/time_series2';
import ElapsedTime from './ElapsedTime';
import Legend from './Legend';
import QueryRows from './QueryRows';
import Graph from './Graph';
import Table from './Table';
......@@ -16,9 +15,7 @@ import { decodePathComponent } from 'app/core/utils/location_util';
function makeTimeSeriesList(dataList, options) {
return dataList.map((seriesData, index) => {
const datapoints = seriesData.datapoints || [];
const responseAlias = seriesData.target;
const query = options.targets[index].expr;
const alias = responseAlias && responseAlias !== '{}' ? responseAlias : query;
const alias = seriesData.target;
const colorIndex = index % colors.length;
const color = colors[colorIndex];
......@@ -54,6 +51,7 @@ interface IExploreState {
latency: number;
loading: any;
queries: any;
queryError: any;
range: any;
requestOptions: any;
showingGraph: boolean;
......@@ -76,6 +74,7 @@ export class Explore extends React.Component<any, IExploreState> {
latency: 0,
loading: false,
queries: ensureQueries(queries),
queryError: null,
range: range || { ...DEFAULT_RANGE },
requestOptions: null,
showingGraph: true,
......@@ -94,6 +93,10 @@ export class Explore extends React.Component<any, IExploreState> {
}
}
componentDidCatch(error) {
console.error(error);
}
handleAddQueryRow = index => {
const { queries } = this.state;
const nextQueries = [
......@@ -155,7 +158,7 @@ export class Explore extends React.Component<any, IExploreState> {
if (!hasQuery(queries)) {
return;
}
this.setState({ latency: 0, loading: true, graphResult: null });
this.setState({ latency: 0, loading: true, graphResult: null, queryError: null });
const now = Date.now();
const options = buildQueryOptions({
format: 'time_series',
......@@ -169,9 +172,10 @@ export class Explore extends React.Component<any, IExploreState> {
const result = makeTimeSeriesList(res.data, options);
const latency = Date.now() - now;
this.setState({ latency, loading: false, graphResult: result, requestOptions: options });
} catch (error) {
console.error(error);
this.setState({ loading: false, graphResult: error });
} catch (response) {
console.error(response);
const queryError = response.data ? response.data.error : response;
this.setState({ loading: false, queryError });
}
}
......@@ -180,7 +184,7 @@ export class Explore extends React.Component<any, IExploreState> {
if (!hasQuery(queries)) {
return;
}
this.setState({ latency: 0, loading: true, tableResult: null });
this.setState({ latency: 0, loading: true, queryError: null, tableResult: null });
const now = Date.now();
const options = buildQueryOptions({
format: 'table',
......@@ -194,9 +198,10 @@ export class Explore extends React.Component<any, IExploreState> {
const tableModel = res.data[0];
const latency = Date.now() - now;
this.setState({ latency, loading: false, tableResult: tableModel, requestOptions: options });
} catch (error) {
console.error(error);
this.setState({ loading: false, tableResult: null });
} catch (response) {
console.error(response);
const queryError = response.data ? response.data.error : response;
this.setState({ loading: false, queryError });
}
}
......@@ -214,6 +219,7 @@ export class Explore extends React.Component<any, IExploreState> {
latency,
loading,
queries,
queryError,
range,
requestOptions,
showingGraph,
......@@ -221,55 +227,63 @@ export class Explore extends React.Component<any, IExploreState> {
tableResult,
} = this.state;
const showingBoth = showingGraph && showingTable;
const graphHeight = showingBoth ? '200px' : null;
const graphButtonClassName = showingBoth || showingGraph ? 'btn m-r-1' : 'btn btn-inverse m-r-1';
const tableButtonClassName = showingBoth || showingTable ? 'btn m-r-1' : 'btn btn-inverse m-r-1';
const graphHeight = showingBoth ? '200px' : '400px';
const graphButtonActive = showingBoth || showingGraph ? 'active' : '';
const tableButtonActive = showingBoth || showingTable ? 'active' : '';
return (
<div className="explore">
<div className="page-body page-full">
<h2 className="page-sub-heading">Explore</h2>
{datasourceLoading ? <div>Loading datasource...</div> : null}
<div className="navbar">
<div>
<a className="navbar-page-btn">
<i className="fa fa-rocket" />
Explore
</a>
</div>
<div className="navbar__spacer" />
<div className="navbar-buttons">
<button className={`btn navbar-button ${graphButtonActive}`} onClick={this.handleClickGraphButton}>
Graph
</button>
<button className={`btn navbar-button ${tableButtonActive}`} onClick={this.handleClickTableButton}>
Table
</button>
</div>
<TimePicker range={range} onChangeTime={this.handleChangeTime} />
<div className="navbar-buttons relative">
<button className="btn navbar-button--primary" onClick={this.handleSubmit}>
Run Query <i className="fa fa-level-down run-icon" />
</button>
{loading || latency ? <ElapsedTime time={latency} className="text-info" /> : null}
</div>
</div>
{datasourceError ? <div title={datasourceError}>Error connecting to datasource.</div> : null}
{datasourceLoading ? <div className="explore-container">Loading datasource...</div> : null}
{datasource ? (
<div className="m-r-3">
<div className="nav m-b-1 navbar">
<div className="navbar-buttons">
<button className={graphButtonClassName} onClick={this.handleClickGraphButton}>
Graph
</button>
<button className={tableButtonClassName} onClick={this.handleClickTableButton}>
Table
</button>
</div>
<div className="navbar__spacer" />
<TimePicker range={range} onChangeTime={this.handleChangeTime} />
<div className="navbar-buttons">
<button type="submit" className="btn btn-primary" onClick={this.handleSubmit}>
<i className="fa fa-return" /> Run Query
</button>
</div>
{loading || latency ? <ElapsedTime time={latency} className="" /> : null}
</div>
<QueryRows
queries={queries}
request={this.request}
onAddQueryRow={this.handleAddQueryRow}
onChangeQuery={this.handleChangeQuery}
onExecuteQuery={this.handleSubmit}
onRemoveQueryRow={this.handleRemoveQueryRow}
/>
<main className="m-t-2">
{showingGraph ? (
<Graph data={graphResult} id="explore-1" options={requestOptions} height={graphHeight} />
) : null}
{showingGraph ? <Legend data={graphResult} /> : null}
{showingTable ? <Table data={tableResult} className="m-t-3" /> : null}
</main>
</div>
) : null}
</div>
{datasourceError ? (
<div className="explore-container" title={datasourceError}>
Error connecting to datasource.
</div>
) : null}
{datasource ? (
<div className="explore-container">
<QueryRows
queries={queries}
request={this.request}
onAddQueryRow={this.handleAddQueryRow}
onChangeQuery={this.handleChangeQuery}
onExecuteQuery={this.handleSubmit}
onRemoveQueryRow={this.handleRemoveQueryRow}
/>
{queryError ? <div className="text-warning m-a-2">{queryError}</div> : null}
<main className="m-t-2">
{showingGraph ? (
<Graph data={graphResult} id="explore-1" options={requestOptions} height={graphHeight} />
) : null}
{showingTable ? <Table data={tableResult} className="m-t-3" /> : null}
</main>
</div>
) : null}
</div>
);
}
......
......@@ -2,11 +2,12 @@ import $ from 'jquery';
import React, { Component } from 'react';
import moment from 'moment';
import 'vendor/flot/jquery.flot';
import 'vendor/flot/jquery.flot.time';
import * as dateMath from 'app/core/utils/datemath';
import TimeSeries from 'app/core/time_series2';
import 'vendor/flot/jquery.flot';
import 'vendor/flot/jquery.flot.time';
import Legend from './Legend';
// Copied from graph.ts
function time_format(ticks, min, max) {
......@@ -86,6 +87,7 @@ class Graph extends Component<any, any> {
return;
}
const series = data.map((ts: TimeSeries) => ({
color: ts.color,
label: ts.label,
data: ts.getFlotPairs('null'),
}));
......@@ -120,12 +122,13 @@ class Graph extends Component<any, any> {
}
render() {
const style = {
height: this.props.height || '400px',
width: this.props.width || '100%',
};
return <div id={this.props.id} style={style} />;
const { data, height } = this.props;
return (
<div className="panel-container">
<div id={this.props.id} className="explore-graph" style={{ height }} />
<Legend data={data} />
</div>
);
}
}
......
......@@ -50,7 +50,7 @@ class Portal extends React.Component {
constructor(props) {
super(props);
this.node = document.createElement('div');
this.node.classList.add(`query-field-portal-${props.index}`);
this.node.classList.add('explore-typeahead', `explore-typeahead-${props.index}`);
document.body.appendChild(this.node);
}
......
......@@ -48,10 +48,10 @@ class QueryRow extends PureComponent<any, any> {
return (
<div className="query-row">
<div className="query-row-tools">
<button className="btn btn-small btn-inverse" onClick={this.handleClickAddButton}>
<button className="btn navbar-button navbar-button--tight" onClick={this.handleClickAddButton}>
<i className="fa fa-plus" />
</button>
<button className="btn btn-small btn-inverse" onClick={this.handleClickRemoveButton}>
<button className="btn navbar-button navbar-button--tight" onClick={this.handleClickRemoveButton}>
<i className="fa fa-minus" />
</button>
</div>
......@@ -60,6 +60,7 @@ class QueryRow extends PureComponent<any, any> {
initialQuery={edited ? null : query}
onPressEnter={this.handlePressEnter}
onQueryChange={this.handleChangeQuery}
placeholder="Enter a PromQL query"
request={request}
/>
</div>
......
......@@ -164,6 +164,7 @@ export class PrometheusDatasource {
legendFormat: activeTargets[index].legendFormat,
start: start,
end: end,
query: queries[index].expr,
responseListLength: responseList.length,
responseIndex: index,
refId: activeTargets[index].refId,
......
......@@ -123,11 +123,16 @@ export class ResultTransformer {
}
createMetricLabel(labelData, options) {
let label = '';
if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) {
return this.getOriginalMetricName(labelData);
label = this.getOriginalMetricName(labelData);
} else {
label = this.renderTemplate(this.templateSrv.replace(options.legendFormat), labelData);
}
return this.renderTemplate(this.templateSrv.replace(options.legendFormat), labelData) || '{}';
if (!label || label === '{}') {
label = options.query;
}
return label;
}
renderTemplate(aliasPattern, aliasData) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment