From 61e6f63b32dfa127c11572440f3dd5e5a67f2580 Mon Sep 17 00:00:00 2001 From: Craig Miskell Date: Fri, 5 Jan 2018 09:08:40 +1300 Subject: [PATCH 01/31] Align queries to prometheus with the step to ensure 'rate' type expressions get consistent results --- public/app/plugins/datasource/prometheus/datasource.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 122fe9601a1..0782170fbd9 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -173,6 +173,9 @@ export class PrometheusDatasource { throw { message: 'Invalid time range' }; } + start = start - (start % query.step); + end = end - (end % query.step) + query.step; + var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + From f9fb315dbd8c08ee11f80468b17b8d6a0eada89b Mon Sep 17 00:00:00 2001 From: Craig Miskell Date: Fri, 5 Jan 2018 16:20:54 +1300 Subject: [PATCH 02/31] Update tests to match new reality, and rejig the implementation a bit to truly work as desired --- .../datasource/prometheus/datasource.ts | 22 ++++++---- .../prometheus/specs/datasource_specs.ts | 41 ++++++++++--------- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 0782170fbd9..d0fd44c322a 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -72,6 +72,13 @@ export class PrometheusDatasource { return this.templateSrv.variableExists(target.expr); } + clampRange(start, end, step) { + return { + start: start - start % step, + end: end - end % step + step, + }; + } + query(options) { var self = this; var start = this.getPrometheusTime(options.range.from, false); @@ -99,7 +106,8 @@ export class PrometheusDatasource { var allQueryPromise = _.map(queries, query => { if (!query.instant) { - return this.performTimeSeriesQuery(query, start, end); + let range = this.clampRange(start, end, query.step); + return this.performTimeSeriesQuery(query, range.start, range.end); } else { return this.performInstantQuery(query, end); } @@ -118,7 +126,9 @@ export class PrometheusDatasource { } else { for (let metricData of response.data.data.result) { if (response.data.data.resultType === 'matrix') { - result.push(self.transformMetricData(metricData, activeTargets[index], start, end, queries[index].step)); + let step = queries[index].step; + let range = this.clampRange(start, end, step); + result.push(self.transformMetricData(metricData, activeTargets[index], range.start, range.end, step)); } else if (response.data.data.resultType === 'vector') { result.push(self.transformInstantMetricData(metricData, activeTargets[index])); } @@ -173,9 +183,6 @@ export class PrometheusDatasource { throw { message: 'Invalid time range' }; } - start = start - (start % query.step); - end = end - (end % query.step) + query.step; - var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + @@ -247,11 +254,12 @@ export class PrometheusDatasource { var end = this.getPrometheusTime(options.range.to, true); var query = { expr: interpolated, - step: this.adjustInterval(kbn.interval_to_seconds(step), 0, Math.ceil(end - start), 1) + 's', + step: this.adjustInterval(kbn.interval_to_seconds(step), 0, Math.ceil(end - start), 1), }; + let range = this.clampRange(start, end, query.step); var self = this; - return this.performTimeSeriesQuery(query, start, end).then(function(results) { + return this.performTimeSeriesQuery(query, range.start, range.end).then(function(results) { var eventList = []; tagKeys = tagKeys.split(','); diff --git a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts index 043bfcf25e0..c3231c1dacb 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts @@ -34,7 +34,7 @@ describe('PrometheusDatasource', function() { var urlExpected = 'proxied/api/v1/query_range?query=' + encodeURIComponent('test{job="testjob"}') + - '&start=1443438675&end=1443460275&step=60'; + '&start=1443438660&end=1443460320&step=60'; var query = { range: { from: moment(1443438674760), to: moment(1443460274760) }, targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }], @@ -69,8 +69,8 @@ describe('PrometheusDatasource', function() { }); describe('When querying prometheus with one target which return multiple series', function() { var results; - var start = 1443438675; - var end = 1443460275; + var start = 1443438660; + var end = 1443460320; var step = 60; var urlExpected = 'proxied/api/v1/query_range?query=' + @@ -102,6 +102,7 @@ describe('PrometheusDatasource', function() { ], }, }; + // console.log(util.inspect(response, {depth: null})); beforeEach(function() { ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query).then(function(data) { @@ -115,6 +116,7 @@ describe('PrometheusDatasource', function() { expect(results.data[1].datapoints.length).to.be((end - start) / step + 1); }); it('should fill null until first datapoint in response', function() { + //console.log(util.inspect(results, {depth: null})); expect(results.data[0].datapoints[0][1]).to.be(start * 1000); expect(results.data[0].datapoints[0][0]).to.be(null); expect(results.data[0].datapoints[1][1]).to.be((start + step * 1) * 1000); @@ -128,6 +130,7 @@ describe('PrometheusDatasource', function() { expect(results.data[0].datapoints[length - 1][0]).to.be(null); }); it('should fill null at gap between series', function() { + //console.log(util.inspect(results, {depth: null})); expect(results.data[0].datapoints[2][1]).to.be((start + step * 2) * 1000); expect(results.data[0].datapoints[2][0]).to.be(null); expect(results.data[1].datapoints[1][1]).to.be((start + step * 1) * 1000); @@ -176,7 +179,7 @@ describe('PrometheusDatasource', function() { var urlExpected = 'proxied/api/v1/query_range?query=' + encodeURIComponent('ALERTS{alertstate="firing"}') + - '&start=1443438675&end=1443460275&step=60s'; + '&start=1443438660&end=1443460320&step=60'; var options = { annotation: { expr: 'ALERTS{alertstate="firing"}', @@ -327,7 +330,7 @@ describe('PrometheusDatasource', function() { ], interval: '5s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438675&end=1443460275&step=10'; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438670&end=1443460280&step=10'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -340,7 +343,7 @@ describe('PrometheusDatasource', function() { targets: [{ expr: 'test' }], interval: '100ms', }; - var urlExpected = 'proxied/api/v1/query_range?query=test&start=1508318769&end=1508318771&step=1'; + var urlExpected = 'proxied/api/v1/query_range?query=test&start=1508318769&end=1508318772&step=1'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -358,7 +361,7 @@ describe('PrometheusDatasource', function() { ], interval: '10s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438675&end=1443460275&step=10'; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438670&end=1443460280&step=10'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -370,7 +373,7 @@ describe('PrometheusDatasource', function() { targets: [{ expr: 'test' }], interval: '1s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438675&end=1443460275&step=2'; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438674&end=1443460276&step=2'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -388,7 +391,7 @@ describe('PrometheusDatasource', function() { ], interval: '5s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438675&end=1443460275&step=50'; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438650&end=1443460300&step=50'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -406,7 +409,7 @@ describe('PrometheusDatasource', function() { ], interval: '5s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438675&end=1443460275&step=15'; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438675&end=1443460290&step=15'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -424,7 +427,7 @@ describe('PrometheusDatasource', function() { ], interval: '10s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438675&end=1443460275&step=100'; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438600&end=1443460300&step=100'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -441,7 +444,7 @@ describe('PrometheusDatasource', function() { ], interval: '10s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438675&end=1444043475&step=100'; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438600&end=1444043500&step=100'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -458,7 +461,7 @@ describe('PrometheusDatasource', function() { ], interval: '5s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438675&end=1444043475&step=60'; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438660&end=1444043520&step=60'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -492,7 +495,7 @@ describe('PrometheusDatasource', function() { var urlExpected = 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[10s])') + - '&start=1443438675&end=1443460275&step=10'; + '&start=1443438670&end=1443460280&step=10'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -521,7 +524,7 @@ describe('PrometheusDatasource', function() { var urlExpected = 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[10s])') + - '&start=1443438675&end=1443460275&step=10'; + '&start=1443438670&end=1443460280&step=10'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -551,7 +554,7 @@ describe('PrometheusDatasource', function() { var urlExpected = 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[100s])') + - '&start=1443438675&end=1443460275&step=100'; + '&start=1443438600&end=1443460300&step=100'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -581,7 +584,7 @@ describe('PrometheusDatasource', function() { var urlExpected = 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[50s])') + - '&start=1443438675&end=1443460275&step=50'; + '&start=1443438650&end=1443460300&step=50'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -611,7 +614,7 @@ describe('PrometheusDatasource', function() { var urlExpected = 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[15s])') + - '&start=1443438675&end=1443460275&step=15'; + '&start=1443438675&end=1443460290&step=15'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -640,7 +643,7 @@ describe('PrometheusDatasource', function() { var urlExpected = 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[60s])') + - '&start=1443438675&end=1444043475&step=60'; + '&start=1443438660&end=1444043520&step=60'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); From 2e86985d44402c19b54ffb8e31ab764a1b032e21 Mon Sep 17 00:00:00 2001 From: Craig Miskell Date: Fri, 5 Jan 2018 16:22:49 +1300 Subject: [PATCH 03/31] Remove silly noise --- .../plugins/datasource/prometheus/specs/datasource_specs.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts index c3231c1dacb..209b98efd23 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts @@ -102,7 +102,6 @@ describe('PrometheusDatasource', function() { ], }, }; - // console.log(util.inspect(response, {depth: null})); beforeEach(function() { ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query).then(function(data) { @@ -116,7 +115,6 @@ describe('PrometheusDatasource', function() { expect(results.data[1].datapoints.length).to.be((end - start) / step + 1); }); it('should fill null until first datapoint in response', function() { - //console.log(util.inspect(results, {depth: null})); expect(results.data[0].datapoints[0][1]).to.be(start * 1000); expect(results.data[0].datapoints[0][0]).to.be(null); expect(results.data[0].datapoints[1][1]).to.be((start + step * 1) * 1000); @@ -130,7 +128,6 @@ describe('PrometheusDatasource', function() { expect(results.data[0].datapoints[length - 1][0]).to.be(null); }); it('should fill null at gap between series', function() { - //console.log(util.inspect(results, {depth: null})); expect(results.data[0].datapoints[2][1]).to.be((start + step * 2) * 1000); expect(results.data[0].datapoints[2][0]).to.be(null); expect(results.data[1].datapoints[1][1]).to.be((start + step * 1) * 1000); From ca25a2538642d3195d76d81238398d88cb61f77c Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 14 May 2018 09:51:23 +0200 Subject: [PATCH 04/31] docs: removes notes about beeing introduced in 5.0 --- docs/sources/installation/configuration.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index baec76df5d9..2c2a359c7c5 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -93,8 +93,6 @@ Directory where grafana will automatically scan and look for plugins ### provisioning -> This feature is available in 5.0+ - Folder that contains [provisioning](/administration/provisioning) config files that grafana will apply on startup. Dashboards will be reloaded when the json files changes ## [server] @@ -717,7 +715,7 @@ Analytics ID here. By default this feature is disabled. ## [dashboards] -### versions_to_keep (introduced in v5.0) +### versions_to_keep Number dashboard versions to keep (per dashboard). Default: 20, Minimum: 1. From dd66cbc84707ae575ca8564a98489615f2f2facb Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 14 May 2018 11:58:15 +0200 Subject: [PATCH 05/31] Use babel and hot loader only in yarn start * moved babel rules for hot reloading into TS loader * toggling use of babel via HOT variable (true with `yarn start`) * overriding webpack output when HOT to fix asset paths in dashboard urls like http://localhost:3333/d/... --- scripts/webpack/webpack.dev.js | 38 +++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/scripts/webpack/webpack.dev.js b/scripts/webpack/webpack.dev.js index cc0751fc7d9..625f921a388 100644 --- a/scripts/webpack/webpack.dev.js +++ b/scripts/webpack/webpack.dev.js @@ -31,11 +31,24 @@ const entries = HOT ? { vendor: require('./dependencies'), }; +const output = HOT ? { + path: path.resolve(__dirname, '../../public/build'), + filename: '[name].[hash].js', + publicPath: "/public/build/", +} : { + path: path.resolve(__dirname, '../../public/build'), + filename: '[name].[hash].js', + // Keep publicPath relative for host.com/grafana/ deployments + publicPath: "public/build/", + }; + module.exports = merge(common, { devtool: "cheap-module-source-map", entry: entries, + output: output, + resolve: { extensions: ['.scss', '.ts', '.tsx', '.es6', '.js', '.json', '.svg', '.woff2', '.png'], }, @@ -66,23 +79,20 @@ module.exports = merge(common, { { test: /\.tsx?$/, exclude: /node_modules/, - use: [ - { - loader: 'babel-loader', - options: { + use: { + loader: 'awesome-typescript-loader', + options: { + useCache: true, + useBabel: HOT, + babelOptions: { + babelrc: false, plugins: [ 'syntax-dynamic-import', - 'react-hot-loader/babel', - ], - }, + 'react-hot-loader/babel' + ] + } }, - { - loader: 'awesome-typescript-loader', - options: { - useCache: true, - }, - } - ] + } }, require('./sass.rule.js')({ sourceMap: true, minimize: false, preserveUrl: HOT From e731c248d7d7885d8f2e2d81bbce3dda4b35c31e Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Wed, 9 May 2018 09:13:56 +0200 Subject: [PATCH 06/31] Prometheus step alignment: shift interval only on jitter * only increase interval by step if jitter happened * shift both start and end * simplified tests by using low epoch numbers --- .../datasource/prometheus/datasource.ts | 51 ++--- .../prometheus/partials/query.editor.html | 9 +- .../prometheus/specs/datasource_specs.ts | 184 ++++++++++-------- 3 files changed, 139 insertions(+), 105 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 73ab25081af..df1fc6053fb 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -108,16 +108,17 @@ export class PrometheusDatasource { } clampRange(start, end, step) { + const clampedEnd = Math.ceil(end / step) * step; + const clampedRange = Math.floor((end - start) / step) * step; return { - start: start - start % step, - end: end - end % step + step, + end: clampedEnd, + start: clampedEnd - clampedRange, }; } query(options) { var start = this.getPrometheusTime(options.range.from, false); var end = this.getPrometheusTime(options.range.to, true); - var range = Math.ceil(end - start); var queries = []; var activeTargets = []; @@ -130,7 +131,7 @@ export class PrometheusDatasource { } activeTargets.push(target); - queries.push(this.createQuery(target, options, range)); + queries.push(this.createQuery(target, options, start, end)); } // No valid targets, return the empty result to save a round trip. @@ -140,8 +141,7 @@ export class PrometheusDatasource { var allQueryPromise = _.map(queries, query => { if (!query.instant) { - let range = this.clampRange(start, end, query.step); - return this.performTimeSeriesQuery(query, range.start, range.end); + return this.performTimeSeriesQuery(query, query.start, query.end); } else { return this.performInstantQuery(query, end); } @@ -155,14 +155,13 @@ export class PrometheusDatasource { throw response.error; } - let step = queries[index].step; - let { start, end } = this.clampRange(start, end, step); - let transformerOptions = { + // Keeping original start/end for transformers + const transformerOptions = { format: activeTargets[index].format, - step, + step: queries[index].step, legendFormat: activeTargets[index].legendFormat, - start, - end, + start: start, + end: end, responseListLength: responseList.length, responseIndex: index, refId: activeTargets[index].refId, @@ -175,9 +174,10 @@ export class PrometheusDatasource { }); } - createQuery(target, options, range) { + createQuery(target, options, start, end) { var query: any = {}; query.instant = target.instant; + var range = Math.ceil(end - start); var interval = kbn.interval_to_seconds(options.interval); // Minimum interval ("Min step"), if specified for the query. or same as interval otherwise @@ -201,6 +201,12 @@ export class PrometheusDatasource { // Only replace vars in expression after having (possibly) updated interval vars query.expr = this.templateSrv.replace(target.expr, scopedVars, this.interpolateQueryExpr); query.requestId = options.panelId + target.refId; + + // Align query interval with step + const adjusted = this.clampRange(start, end, query.step); + query.start = adjusted.start; + query.end = adjusted.end; + return query; } @@ -280,23 +286,18 @@ export class PrometheusDatasource { return this.$q.when([]); } - var interpolated = this.templateSrv.replace(expr, {}, this.interpolateQueryExpr); - - var step = '60s'; - if (annotation.step) { - step = this.templateSrv.replace(annotation.step); - } - + var step = annotation.step || '60s'; var start = this.getPrometheusTime(options.range.from, false); var end = this.getPrometheusTime(options.range.to, true); - var query = { - expr: interpolated, - step: this.adjustInterval(kbn.interval_to_seconds(step), 0, Math.ceil(end - start), 1), + // Unsetting min interval + const queryOptions = { + ...options, + interval: '0s', }; - let range = this.clampRange(start, end, query.step); + const query = this.createQuery({ expr, interval: step }, queryOptions, start, end); var self = this; - return this.performTimeSeriesQuery(query, range.start, range.end).then(function(results) { + return this.performTimeSeriesQuery(query, query.start, query.end).then(function(results) { var eventList = []; tagKeys = tagKeys.split(','); diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index 68791d96c19..11a857f37e7 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -14,8 +14,8 @@ data-min-length=0 data-items=1000 ng-model-onblur ng-change="ctrl.refreshMetricData()"> - Controls the name of the time series, using name or pattern. For example {{hostname}} will be replaced with label value for - the label hostname. + Controls the name of the time series, using name or pattern. For example + {{hostname}} will be replaced with label value for the label hostname. @@ -25,7 +25,8 @@ placeholder="{{ctrl.panelCtrl.interval}}" data-min-length=0 data-items=100 ng-model-onblur ng-change="ctrl.refreshMetricData()" /> - Leave blank for auto handling based on time range and panel width + Leave blank for auto handling based on time range and panel width. Note that the actual dates used in the query will be adjusted + to a multiple of the interval step. @@ -57,4 +58,4 @@
- + \ No newline at end of file diff --git a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts index 2dfa88b32c5..ef51ff69206 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts @@ -4,6 +4,12 @@ import $ from 'jquery'; import helpers from 'test/specs/helpers'; import { PrometheusDatasource } from '../datasource'; +const SECOND = 1000; +const MINUTE = 60 * SECOND; +const HOUR = 60 * MINUTE; + +const time = ({ hours = 0, seconds = 0, minutes = 0 }) => moment(hours * HOUR + minutes * MINUTE + seconds * SECOND); + describe('PrometheusDatasource', function() { var ctx = new helpers.ServiceTestContext(); var instanceSettings = { @@ -29,18 +35,16 @@ describe('PrometheusDatasource', function() { $httpBackend.when('GET', /\.html$/).respond(''); }) ); - describe('When querying prometheus with one target using query editor target spec', function() { var results; - var urlExpected = - 'proxied/api/v1/query_range?query=' + - encodeURIComponent('test{job="testjob"}') + - '&start=1443438660&end=1443460320&step=60'; var query = { - range: { from: moment(1443438674760), to: moment(1443460274760) }, + range: { from: time({ seconds: 63 }), to: time({ seconds: 183 }) }, targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }], interval: '60s', }; + // Interval alignment with step + var urlExpected = + 'proxied/api/v1/query_range?query=' + encodeURIComponent('test{job="testjob"}') + '&start=120&end=240&step=60'; var response = { status: 'success', data: { @@ -48,7 +52,7 @@ describe('PrometheusDatasource', function() { result: [ { metric: { __name__: 'test', job: 'testjob' }, - values: [[1443454528, '3846']], + values: [[60, '3846']], }, ], }, @@ -70,8 +74,8 @@ describe('PrometheusDatasource', function() { }); describe('When querying prometheus with one target which return multiple series', function() { var results; - var start = 1443438660; - var end = 1443460320; + var start = 60; + var end = 360; var step = 60; var urlExpected = 'proxied/api/v1/query_range?query=' + @@ -83,7 +87,7 @@ describe('PrometheusDatasource', function() { '&step=' + step; var query = { - range: { from: moment(1443438674760), to: moment(1443460274760) }, + range: { from: time({ seconds: start }), to: time({ seconds: end }) }, targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }], interval: '60s', }; @@ -139,9 +143,9 @@ describe('PrometheusDatasource', function() { }); describe('When querying prometheus with one target and instant = true', function() { var results; - var urlExpected = 'proxied/api/v1/query?query=' + encodeURIComponent('test{job="testjob"}') + '&time=1443460275'; + var urlExpected = 'proxied/api/v1/query?query=' + encodeURIComponent('test{job="testjob"}') + '&time=123'; var query = { - range: { from: moment(1443438674760), to: moment(1443460274760) }, + range: { from: time({ seconds: 63 }), to: time({ seconds: 123 }) }, targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }], interval: '60s', }; @@ -152,7 +156,7 @@ describe('PrometheusDatasource', function() { result: [ { metric: { __name__: 'test', job: 'testjob' }, - value: [1443454528, '3846'], + value: [123, '3846'], }, ], }, @@ -177,7 +181,7 @@ describe('PrometheusDatasource', function() { var urlExpected = 'proxied/api/v1/query_range?query=' + encodeURIComponent('ALERTS{alertstate="firing"}') + - '&start=1443438660&end=1443460320&step=60'; + '&start=120&end=180&step=60'; var options = { annotation: { expr: 'ALERTS{alertstate="firing"}', @@ -186,8 +190,8 @@ describe('PrometheusDatasource', function() { textFormat: '{{instance}}', }, range: { - from: moment(1443438674760), - to: moment(1443460274760), + from: time({ seconds: 63 }), + to: time({ seconds: 123 }), }, }; var response = { @@ -203,7 +207,7 @@ describe('PrometheusDatasource', function() { instance: 'testinstance', job: 'testjob', }, - values: [[1443454528, '1']], + values: [[123, '1']], }, ], }, @@ -221,15 +225,15 @@ describe('PrometheusDatasource', function() { expect(results[0].tags).to.contain('testjob'); expect(results[0].title).to.be('InstanceDown'); expect(results[0].text).to.be('testinstance'); - expect(results[0].time).to.be(1443454528 * 1000); + expect(results[0].time).to.be(123 * 1000); }); }); describe('When resultFormat is table and instant = true', function() { var results; - var urlExpected = 'proxied/api/v1/query?query=' + encodeURIComponent('test{job="testjob"}') + '&time=1443460275'; + var urlExpected = 'proxied/api/v1/query?query=' + encodeURIComponent('test{job="testjob"}') + '&time=123'; var query = { - range: { from: moment(1443438674760), to: moment(1443460274760) }, + range: { from: time({ seconds: 63 }), to: time({ seconds: 123 }) }, targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }], interval: '60s', }; @@ -240,7 +244,7 @@ describe('PrometheusDatasource', function() { result: [ { metric: { __name__: 'test', job: 'testjob' }, - value: [1443454528, '3846'], + value: [123, '3846'], }, ], }, @@ -270,8 +274,8 @@ describe('PrometheusDatasource', function() { it('should be min interval when greater than auto interval', function() { var query = { - // 6 hour range - range: { from: moment(1443438674760), to: moment(1443460274760) }, + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, targets: [ { expr: 'test', @@ -280,7 +284,7 @@ describe('PrometheusDatasource', function() { ], interval: '5s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438670&end=1443460280&step=10'; + var urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -288,12 +292,12 @@ describe('PrometheusDatasource', function() { it('step should never go below 1', function() { var query = { - // 6 hour range - range: { from: moment(1508318768202), to: moment(1508318770118) }, + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, targets: [{ expr: 'test' }], interval: '100ms', }; - var urlExpected = 'proxied/api/v1/query_range?query=test&start=1508318769&end=1508318772&step=1'; + var urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=1'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -301,8 +305,8 @@ describe('PrometheusDatasource', function() { it('should be auto interval when greater than min interval', function() { var query = { - // 6 hour range - range: { from: moment(1443438674760), to: moment(1443460274760) }, + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, targets: [ { expr: 'test', @@ -311,7 +315,7 @@ describe('PrometheusDatasource', function() { ], interval: '10s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438670&end=1443460280&step=10'; + var urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -319,19 +323,21 @@ describe('PrometheusDatasource', function() { it('should result in querying fewer than 11000 data points', function() { var query = { // 6 hour range - range: { from: moment(1443438674760), to: moment(1443460274760) }, + range: { from: time({ hours: 1 }), to: time({ hours: 7 }) }, targets: [{ expr: 'test' }], interval: '1s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438674&end=1443460276&step=2'; + var end = 7 * 60 * 60; + var start = 60 * 60; + var urlExpected = 'proxied/api/v1/query_range?query=test&start=' + start + '&end=' + end + '&step=2'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); }); it('should not apply min interval when interval * intervalFactor greater', function() { var query = { - // 6 hour range - range: { from: moment(1443438674760), to: moment(1443460274760) }, + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, targets: [ { expr: 'test', @@ -341,15 +347,16 @@ describe('PrometheusDatasource', function() { ], interval: '5s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438650&end=1443460300&step=50'; + // times get rounded up to interval + var urlExpected = 'proxied/api/v1/query_range?query=test&start=100&end=450&step=50'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); }); it('should apply min interval when interval * intervalFactor smaller', function() { var query = { - // 6 hour range - range: { from: moment(1443438674760), to: moment(1443460274760) }, + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, targets: [ { expr: 'test', @@ -359,15 +366,15 @@ describe('PrometheusDatasource', function() { ], interval: '5s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438675&end=1443460290&step=15'; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=60&end=420&step=15'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); }); it('should apply intervalFactor to auto interval when greater', function() { var query = { - // 6 hour range - range: { from: moment(1443438674760), to: moment(1443460274760) }, + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, targets: [ { expr: 'test', @@ -377,7 +384,8 @@ describe('PrometheusDatasource', function() { ], interval: '10s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438600&end=1443460300&step=100'; + // times get rounded up to interval + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=200&end=500&step=100'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -385,7 +393,7 @@ describe('PrometheusDatasource', function() { it('should not not be affected by the 11000 data points limit when large enough', function() { var query = { // 1 week range - range: { from: moment(1443438674760), to: moment(1444043474760) }, + range: { from: time({}), to: time({ hours: 7 * 24 }) }, targets: [ { expr: 'test', @@ -394,7 +402,9 @@ describe('PrometheusDatasource', function() { ], interval: '10s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438600&end=1444043500&step=100'; + var end = 7 * 24 * 60 * 60; + var start = 0; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=100'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -402,7 +412,7 @@ describe('PrometheusDatasource', function() { it('should be determined by the 11000 data points limit when too small', function() { var query = { // 1 week range - range: { from: moment(1443438674760), to: moment(1444043474760) }, + range: { from: time({}), to: time({ hours: 7 * 24 }) }, targets: [ { expr: 'test', @@ -411,12 +421,15 @@ describe('PrometheusDatasource', function() { ], interval: '5s', }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=1443438660&end=1444043520&step=60'; + var end = 7 * 24 * 60 * 60; + var start = 0; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=60'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); }); }); + describe('The __interval and __interval_ms template variables', function() { var response = { status: 'success', @@ -428,8 +441,8 @@ describe('PrometheusDatasource', function() { it('should be unchanged when auto interval is greater than min interval', function() { var query = { - // 6 hour range - range: { from: moment(1443438674760), to: moment(1443460274760) }, + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, targets: [ { expr: 'rate(test[$__interval])', @@ -443,9 +456,7 @@ describe('PrometheusDatasource', function() { }, }; var urlExpected = - 'proxied/api/v1/query_range?query=' + - encodeURIComponent('rate(test[10s])') + - '&start=1443438670&end=1443460280&step=10'; + 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[10s])') + '&start=60&end=420&step=10'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -457,8 +468,8 @@ describe('PrometheusDatasource', function() { }); it('should be min interval when it is greater than auto interval', function() { var query = { - // 6 hour range - range: { from: moment(1443438674760), to: moment(1443460274760) }, + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, targets: [ { expr: 'rate(test[$__interval])', @@ -472,9 +483,7 @@ describe('PrometheusDatasource', function() { }, }; var urlExpected = - 'proxied/api/v1/query_range?query=' + - encodeURIComponent('rate(test[10s])') + - '&start=1443438670&end=1443460280&step=10'; + 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[10s])') + '&start=60&end=420&step=10'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -486,8 +495,8 @@ describe('PrometheusDatasource', function() { }); it('should account for intervalFactor', function() { var query = { - // 6 hour range - range: { from: moment(1443438674760), to: moment(1443460274760) }, + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, targets: [ { expr: 'rate(test[$__interval])', @@ -502,9 +511,7 @@ describe('PrometheusDatasource', function() { }, }; var urlExpected = - 'proxied/api/v1/query_range?query=' + - encodeURIComponent('rate(test[100s])') + - '&start=1443438600&end=1443460300&step=100'; + 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[100s])') + '&start=200&end=500&step=100'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -516,8 +523,8 @@ describe('PrometheusDatasource', function() { }); it('should be interval * intervalFactor when greater than min interval', function() { var query = { - // 6 hour range - range: { from: moment(1443438674760), to: moment(1443460274760) }, + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, targets: [ { expr: 'rate(test[$__interval])', @@ -532,9 +539,7 @@ describe('PrometheusDatasource', function() { }, }; var urlExpected = - 'proxied/api/v1/query_range?query=' + - encodeURIComponent('rate(test[50s])') + - '&start=1443438650&end=1443460300&step=50'; + 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[50s])') + '&start=100&end=450&step=50'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -546,8 +551,8 @@ describe('PrometheusDatasource', function() { }); it('should be min interval when greater than interval * intervalFactor', function() { var query = { - // 6 hour range - range: { from: moment(1443438674760), to: moment(1443460274760) }, + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, targets: [ { expr: 'rate(test[$__interval])', @@ -562,9 +567,7 @@ describe('PrometheusDatasource', function() { }, }; var urlExpected = - 'proxied/api/v1/query_range?query=' + - encodeURIComponent('rate(test[15s])') + - '&start=1443438675&end=1443460290&step=15'; + 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[15s])') + '&start=60&end=420&step=15'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -577,7 +580,7 @@ describe('PrometheusDatasource', function() { it('should be determined by the 11000 data points limit, accounting for intervalFactor', function() { var query = { // 1 week range - range: { from: moment(1443438674760), to: moment(1444043474760) }, + range: { from: time({}), to: time({ hours: 7 * 24 }) }, targets: [ { expr: 'rate(test[$__interval])', @@ -590,10 +593,16 @@ describe('PrometheusDatasource', function() { __interval_ms: { text: 5 * 1000, value: 5 * 1000 }, }, }; + var end = 7 * 24 * 60 * 60; + var start = 0; var urlExpected = 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[60s])') + - '&start=1443438660&end=1444043520&step=60'; + '&start=' + + start + + '&end=' + + end + + '&step=60'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -604,6 +613,29 @@ describe('PrometheusDatasource', function() { expect(query.scopedVars.__interval_ms.value).to.be(5 * 1000); }); }); + + describe('Step alignment of intervals', function() { + it('does not modify already aligned intervals with perfect step', function() { + const range = ctx.ds.clampRange(0, 3, 3); + expect(range.start).to.be(0); + expect(range.end).to.be(3); + }); + it('does modify end-aligned intervals to reflect number of steps possible', function() { + const range = ctx.ds.clampRange(1, 6, 3); + expect(range.start).to.be(3); + expect(range.end).to.be(6); + }); + it('does align intervals that are a multiple of steps', function() { + const range = ctx.ds.clampRange(1, 4, 3); + expect(range.start).to.be(3); + expect(range.end).to.be(6); + }); + it('does align intervals that are not a multiple of steps', function() { + const range = ctx.ds.clampRange(1, 5, 3); + expect(range.start).to.be(3); + expect(range.end).to.be(6); + }); + }); }); describe('PrometheusDatasource for POST', function() { @@ -635,12 +667,12 @@ describe('PrometheusDatasource for POST', function() { var urlExpected = 'proxied/api/v1/query_range'; var dataExpected = $.param({ query: 'test{job="testjob"}', - start: 1443438675, - end: 1443460275, + start: 2 * 60, + end: 3 * 60, step: 60, }); var query = { - range: { from: moment(1443438674760), to: moment(1443460274760) }, + range: { from: time({ minutes: 1, seconds: 3 }), to: time({ minutes: 2, seconds: 3 }) }, targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }], interval: '60s', }; @@ -651,7 +683,7 @@ describe('PrometheusDatasource for POST', function() { result: [ { metric: { __name__: 'test', job: 'testjob' }, - values: [[1443454528, '3846']], + values: [[2 * 60, '3846']], }, ], }, From 2dd40eb4e4b865b3769dae8eef6d0fe0a3feac68 Mon Sep 17 00:00:00 2001 From: Stuart McLean Date: Mon, 14 May 2018 20:50:08 +0200 Subject: [PATCH 07/31] improve alerting api docs sample responses --- docs/sources/http_api/alerting.md | 53 +++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/docs/sources/http_api/alerting.md b/docs/sources/http_api/alerting.md index 3860ae490b1..305bfccebe8 100644 --- a/docs/sources/http_api/alerting.md +++ b/docs/sources/http_api/alerting.md @@ -50,19 +50,16 @@ Content-Type: application/json "id": 1, "dashboardId": 1, "panelId": 1, + "panelUId": "ABcdEFghij" + "dashboardSlug": "sensors", "name": "fire place sensor", "message": "Someone is trying to break in through the fire place", "state": "alerting", + "newStateDate": "2018-05-14T05:55:20+02:00", "evalDate": "0001-01-01T00:00:00Z", - "evalData": [ - { - "metric": "fire", - "tags": null, - "value": 5.349999999999999 - } - "newStateDate": "2016-12-25", + "evalData": null, "executionError": "", - "url": "http://grafana.com/dashboard/db/sensors" + "dashboardUri": "http://grafana.com/dashboard/db/sensors" } ] ``` @@ -86,15 +83,37 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk HTTP/1.1 200 Content-Type: application/json { - "id": 1, - "dashboardId": 1, - "panelId": 1, - "name": "fire place sensor", - "message": "Someone is trying to break in through the fire place", - "state": "alerting", - "newStateDate": "2016-12-25", - "executionError": "", - "url": "http://grafana.com/dashboard/db/sensors" + "Id": 1, + "Version": 0, + "OrgId": 1, + "DashboardId": 55, + "PanelId": 2, + "Name": "my mem alert", + "Message": "", + "Severity": "", + "State": "alerting", + "Handler": 1, + "Silenced": false, + "ExecutionError": " ", + "Frequency": 60, + "EvalData": { + "evalMatches": [ + { + "metric": "mem_usage", + "tags": { + "name": "server.grafana.com" + }, + "value": 98.765 + } + ] + }, + "NewStateDate": "2018-05-14T17:12:45+02:00", + "StateChanges": 3, + "Created": "2018-05-14T17:01:25+02:00", + "Updated": "2018-05-14T17:11:18+02:00", + "Settings": { + ... + } } ``` From 295169b94f79af4d8316b9cc3a193fce0706a772 Mon Sep 17 00:00:00 2001 From: Stuart McLean Date: Mon, 14 May 2018 20:50:44 +0200 Subject: [PATCH 08/31] add useful note to alerting api docs --- docs/sources/http_api/alerting.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/sources/http_api/alerting.md b/docs/sources/http_api/alerting.md index 305bfccebe8..3b9eceddd5e 100644 --- a/docs/sources/http_api/alerting.md +++ b/docs/sources/http_api/alerting.md @@ -117,6 +117,13 @@ Content-Type: application/json } ``` +**Important Note**: +"evalMatches" data is cached in the db when and only when the state of the alert changes +(e.g. transitioning from "ok" to "alerting" state). + +If data from one server triggers the alert first and, before that server is seen leaving alerting state, +a second server also enters a state that would trigger the alert, the second server will not be visible in "evalMatches" data. + ## Pause alert `POST /api/alerts/:id/pause` From c09c00a1f5eacf6d06e57d1c4d4fe001861e182e Mon Sep 17 00:00:00 2001 From: Stuart McLean Date: Tue, 15 May 2018 10:06:26 +0200 Subject: [PATCH 09/31] fixes following first code review --- docs/sources/http_api/alerting.md | 60 +++++++++++++------------------ 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/docs/sources/http_api/alerting.md b/docs/sources/http_api/alerting.md index 3b9eceddd5e..4d52105cf3c 100644 --- a/docs/sources/http_api/alerting.md +++ b/docs/sources/http_api/alerting.md @@ -49,17 +49,17 @@ Content-Type: application/json { "id": 1, "dashboardId": 1, - "panelId": 1, - "panelUId": "ABcdEFghij" + "dashboardUId": "ABcdEFghij" "dashboardSlug": "sensors", + "panelId": 1, "name": "fire place sensor", - "message": "Someone is trying to break in through the fire place", "state": "alerting", + "message": "Someone is trying to break in through the fire place", "newStateDate": "2018-05-14T05:55:20+02:00", "evalDate": "0001-01-01T00:00:00Z", "evalData": null, "executionError": "", - "dashboardUri": "http://grafana.com/dashboard/db/sensors" + "url": "http://grafana.com/dashboard/db/sensors" } ] ``` @@ -83,37 +83,27 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk HTTP/1.1 200 Content-Type: application/json { - "Id": 1, - "Version": 0, - "OrgId": 1, - "DashboardId": 55, - "PanelId": 2, - "Name": "my mem alert", - "Message": "", - "Severity": "", - "State": "alerting", - "Handler": 1, - "Silenced": false, - "ExecutionError": " ", - "Frequency": 60, - "EvalData": { - "evalMatches": [ - { - "metric": "mem_usage", - "tags": { - "name": "server.grafana.com" - }, - "value": 98.765 - } - ] - }, - "NewStateDate": "2018-05-14T17:12:45+02:00", - "StateChanges": 3, - "Created": "2018-05-14T17:01:25+02:00", - "Updated": "2018-05-14T17:11:18+02:00", - "Settings": { - ... - } + "id": 1, + "dashboardId": 1, + "dashboardUId": "ABcdEFghij" + "dashboardSlug": "sensors", + "panelId": 1, + "name": "fire place sensor", + "state": "alerting", + "message": "Someone is trying to break in through the fire place", + "newStateDate": "2018-05-14T05:55:20+02:00", + "evalDate": "0001-01-01T00:00:00Z", + "evalData": "evalMatches": [ + { + "metric": "movement", + "tags": { + "name": "fireplace_chimney" + }, + "value": 98.765 + } + ], + "executionError": "", + "url": "http://grafana.com/dashboard/db/sensors" } ``` From 4533f22871c207764f704f6280a555febe4c7c50 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 15 May 2018 15:33:05 +0300 Subject: [PATCH 10/31] backend plugins: expose meta field --- pkg/plugins/datasource/wrapper/datasource_plugin_wrapper.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/plugins/datasource/wrapper/datasource_plugin_wrapper.go b/pkg/plugins/datasource/wrapper/datasource_plugin_wrapper.go index 170e187b282..6fe7ef7d79a 100644 --- a/pkg/plugins/datasource/wrapper/datasource_plugin_wrapper.go +++ b/pkg/plugins/datasource/wrapper/datasource_plugin_wrapper.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/grafana/grafana/pkg/components/null" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/tsdb" @@ -79,6 +80,11 @@ func (tw *DatasourcePluginWrapper) Query(ctx context.Context, ds *models.DataSou qr.ErrorString = r.Error } + if r.MetaJson != "" { + metaJson, _ := simplejson.NewJson([]byte(r.MetaJson)) + qr.Meta = metaJson + } + for _, s := range r.GetSeries() { points := tsdb.TimeSeriesPoints{} From 214b9af5a3c706ef4400d15e4a04b2dcec93f1be Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 15 May 2018 20:59:24 +0300 Subject: [PATCH 11/31] backend plugins: log an error if parsing meta field failed --- pkg/plugins/datasource/wrapper/datasource_plugin_wrapper.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/plugins/datasource/wrapper/datasource_plugin_wrapper.go b/pkg/plugins/datasource/wrapper/datasource_plugin_wrapper.go index 6fe7ef7d79a..d2cd9f63cde 100644 --- a/pkg/plugins/datasource/wrapper/datasource_plugin_wrapper.go +++ b/pkg/plugins/datasource/wrapper/datasource_plugin_wrapper.go @@ -81,7 +81,10 @@ func (tw *DatasourcePluginWrapper) Query(ctx context.Context, ds *models.DataSou } if r.MetaJson != "" { - metaJson, _ := simplejson.NewJson([]byte(r.MetaJson)) + metaJson, err := simplejson.NewJson([]byte(r.MetaJson)) + if err != nil { + tw.logger.Error("Error parsing JSON Meta field: " + err.Error()) + } qr.Meta = metaJson } From 567fec402e2fb2147e96c3d54174c3d2563c424b Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 16 May 2018 00:18:28 +0200 Subject: [PATCH 12/31] scroll: temporary fix for double scrollbar issue If #11939 is not merged in the patch release, then this is a temporary fix for 5.1.3. It sets overflow to hidden for larger screens and keeps the overflow set to auto for mobiles and tablets. Fixes #11937 --- public/sass/pages/_dashboard.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 471e90ed9cf..fb947f176dd 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -44,10 +44,18 @@ div.flot-text { padding: $panel-padding; height: calc(100% - 27px); position: relative; + // Fixes scrolling on mobile devices overflow: auto; } +// For larger screens, set back to hidden to avoid double scroll bars +@include media-breakpoint-up(md) { + .panel-content { + overflow: hidden; + } +} + .panel-title-container { min-height: 9px; cursor: move; From fe301142bab237af6b268b6267bfae17320a3019 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Wed, 25 Apr 2018 18:23:37 +0900 Subject: [PATCH 13/31] allow to add annotation for non editable dashboard --- public/app/plugins/panel/graph/graph.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 2de53b6dce0..41580c1deea 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -674,7 +674,7 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) { return; } - if ((ranges.ctrlKey || ranges.metaKey) && dashboard.meta.canEdit) { + if ((ranges.ctrlKey || ranges.metaKey) && contextSrv.isEditor) { // Add annotation setTimeout(() => { eventManager.updateTime(ranges.xaxis); @@ -695,7 +695,7 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) { return; } - if ((pos.ctrlKey || pos.metaKey) && dashboard.meta.canEdit) { + if ((pos.ctrlKey || pos.metaKey) && contextSrv.isEditor) { // Skip if range selected (added in "plotselected" event handler) let isRangeSelection = pos.x !== pos.x1; if (!isRangeSelection) { From 87c9c47579bc2c1b3cb1efa8c3d8ef7b424cf502 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Wed, 16 May 2018 11:58:51 +0900 Subject: [PATCH 14/31] use canMakeEditable --- public/app/plugins/panel/graph/graph.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 41580c1deea..9e4fb42952e 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -674,7 +674,7 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) { return; } - if ((ranges.ctrlKey || ranges.metaKey) && contextSrv.isEditor) { + if ((ranges.ctrlKey || ranges.metaKey) && (dashboard.meta.canEdit || dashboard.meta.canMakeEditable)) { // Add annotation setTimeout(() => { eventManager.updateTime(ranges.xaxis); @@ -695,7 +695,7 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) { return; } - if ((pos.ctrlKey || pos.metaKey) && contextSrv.isEditor) { + if ((pos.ctrlKey || pos.metaKey) && (dashboard.meta.canEdit || dashboard.meta.canMakeEditable)) { // Skip if range selected (added in "plotselected" event handler) let isRangeSelection = pos.x !== pos.x1; if (!isRangeSelection) { From 50026fad2ec28b850b6e1351f03aa7a792e2eb4d Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 16 May 2018 15:07:34 +0200 Subject: [PATCH 15/31] legend: fixes Firefox/baron scroll bug Compensates for Firefox scrollbar calculation error in the baron framework. Offsetwidth and clientwidth are used to find the width of the scrollbar. In the legend these differ by 9px and cause the scroll div to grow by 9px for every refresh. This fix compensates with a negative margin-right in that case. Fixes #11830 --- public/app/plugins/panel/graph/legend.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/app/plugins/panel/graph/legend.ts b/public/app/plugins/panel/graph/legend.ts index 6b6c89444dc..af61db396ba 100644 --- a/public/app/plugins/panel/graph/legend.ts +++ b/public/app/plugins/panel/graph/legend.ts @@ -287,6 +287,10 @@ module.directive('graphLegend', function(popoverSrv, $timeout) { destroyScrollbar(); legendScrollbar = baron(scrollbarParams); } + + // #11830 - compensates for Firefox scrollbar calculation error in the baron framework + scroller[0].style.marginRight = '-' + (scroller[0].offsetWidth - scroller[0].clientWidth) + 'px'; + legendScrollbar.scroll(); } From 66c4a04decf14e6bd268baa19775bd74a83afab6 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 16 May 2018 17:46:32 +0200 Subject: [PATCH 16/31] changelog: add note for #11830 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 146520d13a1..57fcbb6401a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ * **Security**: Fix XSS vulnerabilities in dashboard links [#11813](https://github.com/grafana/grafana/pull/11813) * **Singlestat**: Fix "time of last point" shows local time when dashboard timezone set to UTC [#10338](https://github.com/grafana/grafana/issues/10338) +# 5.1.3 (2018-05-16) + +* **Scroll**: Graph panel / legend texts shifts on the left each time we move scrollbar on firefox [#11830](https://github.com/grafana/grafana/issues/11830) + # 5.1.2 (2018-05-09) * **Database**: Fix MySql migration issue [#11862](https://github.com/grafana/grafana/issues/11862) From 75993971c6a2066dd6c5caca155abaa85ce96bd1 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 16 May 2018 21:48:26 +0200 Subject: [PATCH 17/31] docs: installation pages for 5.1.3 --- docs/sources/installation/debian.md | 6 +++--- docs/sources/installation/rpm.md | 10 +++++----- docs/sources/installation/windows.md | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index f0ad89a8e88..3025b2384df 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -15,7 +15,7 @@ weight = 1 Description | Download ------------ | ------------- -Stable for Debian-based Linux | [grafana_5.1.2_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.1.2_amd64.deb) +Stable for Debian-based Linux | [grafana_5.1.3_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.1.3_amd64.deb) @@ -27,9 +27,9 @@ installation. ```bash -wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.1.2_amd64.deb +wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.1.3_amd64.deb sudo apt-get install -y adduser libfontconfig -sudo dpkg -i grafana_5.1.2_amd64.deb +sudo dpkg -i grafana_5.1.3_amd64.deb ``` @@ -28,7 +28,7 @@ installation. You can install Grafana using Yum directly. ```bash -$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.2-1.x86_64.rpm +$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.3-1.x86_64.rpm ```