diff --git a/public/app/containers/Explore/Explore.tsx b/public/app/containers/Explore/Explore.tsx index 66500353812..cf8cb41a593 100644 --- a/public/app/containers/Explore/Explore.tsx +++ b/public/app/containers/Explore/Explore.tsx @@ -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 { 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 { } } + componentDidCatch(error) { + console.error(error); + } + handleAddQueryRow = index => { const { queries } = this.state; const nextQueries = [ @@ -155,7 +158,7 @@ export class Explore extends React.Component { 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 { 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 { 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 { 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 { latency, loading, queries, + queryError, range, requestOptions, showingGraph, @@ -221,55 +227,63 @@ export class Explore extends React.Component { 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 (
-
-

Explore

- {datasourceLoading ?
Loading datasource...
: null} - - {datasourceError ?
Error connecting to datasource.
: null} - - {datasource ? ( -
-
-
- - -
-
- -
- -
- {loading || latency ? : null} -
- -
- {showingGraph ? ( - - ) : null} - {showingGraph ? : null} - {showingTable ? : null} - - - ) : null} +
+ +
+
+ + +
+ +
+ + {loading || latency ? : null} +
+ + {datasourceLoading ?
Loading datasource...
: null} + + {datasourceError ? ( +
+ Error connecting to datasource. +
+ ) : null} + + {datasource ? ( +
+ + {queryError ?
{queryError}
: null} +
+ {showingGraph ? ( + + ) : null} + {showingTable ?
: null} + + + ) : null} ); } diff --git a/public/app/containers/Explore/Graph.tsx b/public/app/containers/Explore/Graph.tsx index b8bda8696bc..d797a579512 100644 --- a/public/app/containers/Explore/Graph.tsx +++ b/public/app/containers/Explore/Graph.tsx @@ -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 { 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 { } render() { - const style = { - height: this.props.height || '400px', - width: this.props.width || '100%', - }; - - return
; + const { data, height } = this.props; + return ( +
+
+ +
+ ); } } diff --git a/public/app/containers/Explore/QueryField.tsx b/public/app/containers/Explore/QueryField.tsx index 816473619fd..53354584fea 100644 --- a/public/app/containers/Explore/QueryField.tsx +++ b/public/app/containers/Explore/QueryField.tsx @@ -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); } diff --git a/public/app/containers/Explore/QueryRows.tsx b/public/app/containers/Explore/QueryRows.tsx index 3940d16b2f6..74f6c28d41b 100644 --- a/public/app/containers/Explore/QueryRows.tsx +++ b/public/app/containers/Explore/QueryRows.tsx @@ -48,10 +48,10 @@ class QueryRow extends PureComponent { return (
- -
@@ -60,6 +60,7 @@ class QueryRow extends PureComponent { initialQuery={edited ? null : query} onPressEnter={this.handlePressEnter} onQueryChange={this.handleChangeQuery} + placeholder="Enter a PromQL query" request={request} />
diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index a52f3aefa2e..7470885177b 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -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, diff --git a/public/app/plugins/datasource/prometheus/result_transformer.ts b/public/app/plugins/datasource/prometheus/result_transformer.ts index d5feda7d28c..7f5430bf7d6 100644 --- a/public/app/plugins/datasource/prometheus/result_transformer.ts +++ b/public/app/plugins/datasource/prometheus/result_transformer.ts @@ -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) { diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index 200af40341e..541477877bc 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -1,14 +1,35 @@ .explore { - .navbar { - padding-left: 0; - padding-right: 0; + .explore-container { + padding: 2rem; + } + + .explore-graph { + width: 100%; + height: 100%; + } + + .panel-container { + padding: 10px 10px 5px 10px; + } + + .navbar-page-btn .fa { + position: relative; + top: -1px; + font-size: 19px; + line-height: 8px; + opacity: 0.75; + margin-right: 8px; } .elapsed-time { position: absolute; - right: -2.4rem; - top: 1.2rem; + left: 0; + right: 0; + top: 3.5rem; + text-align: center; + font-size: 0.8rem; } + .graph-legend { flex-wrap: wrap; } @@ -16,10 +37,19 @@ .timepicker { display: flex; } + + .run-icon { + margin-left: 0.5em; + transform: rotate(90deg); + } + + .relative { + position: relative; + } } .query-row { - position: relative; + display: flex; & + & { margin-top: 0.5rem; @@ -27,12 +57,7 @@ } .query-row-tools { - position: absolute; - left: -4rem; - top: 0.33rem; - > * { - margin-right: 0.25rem; - } + width: 4rem; } .query-field { @@ -49,14 +74,14 @@ cursor: text; line-height: 1.5; color: rgba(0, 0, 0, 0.65); - background-color: #fff; + background-color: $panel-bg; background-image: none; - border: 1px solid lightgray; + border: $panel-border; border-radius: 3px; transition: all 0.3s; } -.explore { +.explore-typeahead { .typeahead { position: absolute; z-index: auto; @@ -117,221 +142,223 @@ * @author Tim Shedor */ -code[class*='language-'], -pre[class*='language-'] { - color: black; - background: none; - font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - line-height: 1.5; +.explore { + code[class*='language-'], + pre[class*='language-'] { + color: black; + background: none; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; + } -/* Code blocks */ -pre[class*='language-'] { - position: relative; - margin: 0.5em 0; - overflow: visible; - padding: 0; -} -pre[class*='language-'] > code { - position: relative; - border-left: 10px solid #358ccb; - box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf; - background-color: #fdfdfd; - background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%); - background-size: 3em 3em; - background-origin: content-box; - background-attachment: local; -} + /* Code blocks */ + pre[class*='language-'] { + position: relative; + margin: 0.5em 0; + overflow: visible; + padding: 0; + } + pre[class*='language-'] > code { + position: relative; + border-left: 10px solid #358ccb; + box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf; + background-color: #fdfdfd; + background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%); + background-size: 3em 3em; + background-origin: content-box; + background-attachment: local; + } -code[class*='language'] { - max-height: inherit; - height: inherit; - padding: 0 1em; - display: block; - overflow: auto; -} + code[class*='language'] { + max-height: inherit; + height: inherit; + padding: 0 1em; + display: block; + overflow: auto; + } -/* Margin bottom to accomodate shadow */ -:not(pre) > code[class*='language-'], -pre[class*='language-'] { - background-color: #fdfdfd; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - margin-bottom: 1em; -} + /* Margin bottom to accomodate shadow */ + :not(pre) > code[class*='language-'], + pre[class*='language-'] { + background-color: #fdfdfd; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin-bottom: 1em; + } -/* Inline code */ -:not(pre) > code[class*='language-'] { - position: relative; - padding: 0.2em; - border-radius: 0.3em; - color: #c92c2c; - border: 1px solid rgba(0, 0, 0, 0.1); - display: inline; - white-space: normal; -} + /* Inline code */ + :not(pre) > code[class*='language-'] { + position: relative; + padding: 0.2em; + border-radius: 0.3em; + color: #c92c2c; + border: 1px solid rgba(0, 0, 0, 0.1); + display: inline; + white-space: normal; + } -pre[class*='language-']:before, -pre[class*='language-']:after { - content: ''; - z-index: -2; - display: block; - position: absolute; - bottom: 0.75em; - left: 0.18em; - width: 40%; - height: 20%; - max-height: 13em; - box-shadow: 0px 13px 8px #979797; - -webkit-transform: rotate(-2deg); - -moz-transform: rotate(-2deg); - -ms-transform: rotate(-2deg); - -o-transform: rotate(-2deg); - transform: rotate(-2deg); -} - -:not(pre) > code[class*='language-']:after, -pre[class*='language-']:after { - right: 0.75em; - left: auto; - -webkit-transform: rotate(2deg); - -moz-transform: rotate(2deg); - -ms-transform: rotate(2deg); - -o-transform: rotate(2deg); - transform: rotate(2deg); -} - -.token.comment, -.token.block-comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: #7d8b99; -} - -.token.punctuation { - color: #5f6364; -} - -.token.property, -.token.tag, -.token.boolean, -.token.number, -.token.function-name, -.token.constant, -.token.symbol, -.token.deleted { - color: #c92c2c; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.char, -.token.function, -.token.builtin, -.token.inserted { - color: #2f9c0a; -} - -.token.operator, -.token.entity, -.token.url, -.token.variable { - color: #a67f59; - background: rgba(255, 255, 255, 0.5); -} - -.token.atrule, -.token.attr-value, -.token.keyword, -.token.class-name { - color: #1990b8; -} - -.token.regex, -.token.important { - color: #e90; -} - -.language-css .token.string, -.style .token.string { - color: #a67f59; - background: rgba(255, 255, 255, 0.5); -} - -.token.important { - font-weight: normal; -} - -.token.bold { - font-weight: bold; -} -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} - -.namespace { - opacity: 0.7; -} - -@media screen and (max-width: 767px) { pre[class*='language-']:before, pre[class*='language-']:after { - bottom: 14px; - box-shadow: none; + content: ''; + z-index: -2; + display: block; + position: absolute; + bottom: 0.75em; + left: 0.18em; + width: 40%; + height: 20%; + max-height: 13em; + box-shadow: 0px 13px 8px #979797; + -webkit-transform: rotate(-2deg); + -moz-transform: rotate(-2deg); + -ms-transform: rotate(-2deg); + -o-transform: rotate(-2deg); + transform: rotate(-2deg); + } + + :not(pre) > code[class*='language-']:after, + pre[class*='language-']:after { + right: 0.75em; + left: auto; + -webkit-transform: rotate(2deg); + -moz-transform: rotate(2deg); + -ms-transform: rotate(2deg); + -o-transform: rotate(2deg); + transform: rotate(2deg); + } + + .token.comment, + .token.block-comment, + .token.prolog, + .token.doctype, + .token.cdata { + color: #7d8b99; + } + + .token.punctuation { + color: #5f6364; + } + + .token.property, + .token.tag, + .token.boolean, + .token.number, + .token.function-name, + .token.constant, + .token.symbol, + .token.deleted { + color: #c92c2c; + } + + .token.selector, + .token.attr-name, + .token.string, + .token.char, + .token.function, + .token.builtin, + .token.inserted { + color: #2f9c0a; + } + + .token.operator, + .token.entity, + .token.url, + .token.variable { + color: #a67f59; + background: rgba(255, 255, 255, 0.5); + } + + .token.atrule, + .token.attr-value, + .token.keyword, + .token.class-name { + color: #1990b8; + } + + .token.regex, + .token.important { + color: #e90; + } + + .language-css .token.string, + .style .token.string { + color: #a67f59; + background: rgba(255, 255, 255, 0.5); + } + + .token.important { + font-weight: normal; + } + + .token.bold { + font-weight: bold; + } + .token.italic { + font-style: italic; + } + + .token.entity { + cursor: help; + } + + .namespace { + opacity: 0.7; + } + + @media screen and (max-width: 767px) { + pre[class*='language-']:before, + pre[class*='language-']:after { + bottom: 14px; + box-shadow: none; + } + } + + /* Plugin styles */ + .token.tab:not(:empty):before, + .token.cr:before, + .token.lf:before { + color: #e0d7d1; + } + + /* Plugin styles: Line Numbers */ + pre[class*='language-'].line-numbers { + padding-left: 0; + } + + pre[class*='language-'].line-numbers code { + padding-left: 3.8em; + } + + pre[class*='language-'].line-numbers .line-numbers-rows { + left: 0; + } + + /* Plugin styles: Line Highlight */ + pre[class*='language-'][data-line] { + padding-top: 0; + padding-bottom: 0; + padding-left: 0; + } + pre[data-line] code { + position: relative; + padding-left: 4em; + } + pre .line-highlight { + margin-top: 0; } } - -/* Plugin styles */ -.token.tab:not(:empty):before, -.token.cr:before, -.token.lf:before { - color: #e0d7d1; -} - -/* Plugin styles: Line Numbers */ -pre[class*='language-'].line-numbers { - padding-left: 0; -} - -pre[class*='language-'].line-numbers code { - padding-left: 3.8em; -} - -pre[class*='language-'].line-numbers .line-numbers-rows { - left: 0; -} - -/* Plugin styles: Line Highlight */ -pre[class*='language-'][data-line] { - padding-top: 0; - padding-bottom: 0; - padding-left: 0; -} -pre[data-line] code { - position: relative; - padding-left: 4em; -} -pre .line-highlight { - margin-top: 0; -}