From 3947c77b10d1244969ad98dbc2fffbfaf158b142 Mon Sep 17 00:00:00 2001 From: Joel Takvorian Date: Mon, 24 Jul 2017 16:39:53 +0200 Subject: [PATCH 1/5] Allow per-query tenant configuration Feat. #62 Notes: - There's a limitation with templating vars. I'm not sure that grafana allows to edit that screen layout. A solution for later would be to somehow integrate the tenant in the query string, but that requires a little more reflection, so I'll open another ticket for that - In the same time I've added the ability to fetch availability metrics in annotations (very little work was needed) --- spec/datasource_spec.js | 27 ++++++----- src/config_ctrl.js | 8 ++++ src/datasource.js | 71 ++++++++++++++++++---------- src/module.js | 4 +- src/partials/annotations.editor.html | 19 +++++++- src/partials/config.html | 3 +- src/partials/query.editor.html | 2 + src/partials/query.options.html | 2 +- src/queryProcessor.js | 16 +++---- src/query_ctrl.js | 2 +- src/tagsKVPairsController.js | 4 +- src/tagsQLController.js | 6 +-- 12 files changed, 105 insertions(+), 59 deletions(-) create mode 100644 src/config_ctrl.js diff --git a/spec/datasource_spec.js b/spec/datasource_spec.js index fa78d81..8451ef2 100644 --- a/spec/datasource_spec.js +++ b/spec/datasource_spec.js @@ -471,7 +471,8 @@ describe('HawkularDatasource', () => { }, annotation: { query: "my.timeline", - name: "Timeline" + name: "Timeline", + type: "strings" } }; @@ -498,13 +499,13 @@ describe('HawkularDatasource', () => { ctx.ds.annotationQuery(options).then(result => { expect(result).to.have.length(2); - expect(result[0].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline" }); + expect(result[0].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); expect(result[0].time).to.equal(13); expect(result[0].title).to.equal("Timeline"); expect(result[0].tags).to.be.undefined; expect(result[0].text).to.equal("start"); - expect(result[1].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline" }); + expect(result[1].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); expect(result[1].time).to.equal(19); expect(result[1].title).to.equal("Timeline"); expect(result[1].tags).to.be.undefined; @@ -521,7 +522,8 @@ describe('HawkularDatasource', () => { }, annotation: { query: "my.timeline", - name: "Timeline" + name: "Timeline", + type: "strings" } }; @@ -556,13 +558,13 @@ describe('HawkularDatasource', () => { ctx.ds.annotationQuery(options).then(result => { expect(result).to.have.length(2); - expect(result[0].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline" }); + expect(result[0].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); expect(result[0].time).to.equal(13); expect(result[0].title).to.equal("Timeline"); expect(result[0].tags).to.equal("myItem start"); expect(result[0].text).to.equal("start"); - expect(result[1].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline" }); + expect(result[1].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); expect(result[1].time).to.equal(19); expect(result[1].title).to.equal("Timeline"); expect(result[1].tags).to.equal("myItem stop"); @@ -587,7 +589,7 @@ describe('HawkularDatasource', () => { }); }; - ctx.ds.suggestTags('gauge', 'host').then(result => { + ctx.ds.suggestTags({type: 'gauge'}, 'host').then(result => { expect(result).to.have.length(2); expect(result[0]).to.deep.equal({ text: 'cartago', value: 'cartago' }); expect(result[1]).to.deep.equal({ text: 'rio', value: 'rio' }); @@ -607,7 +609,7 @@ describe('HawkularDatasource', () => { data: {} }); }; - ctx.ds.suggestTags('gauge', 'host').then(result => { + ctx.ds.suggestTags({type: 'gauge'}, 'host').then(result => { expect(result).to.have.length(0); }).then(v => done(), err => done(err)); }); @@ -626,7 +628,7 @@ describe('HawkularDatasource', () => { }); }; - ctx.ds.suggestTagKeys().then(result => { + ctx.ds.suggestTagKeys({}).then(result => { expect(result).to.have.length(2); expect(result[0]).to.deep.equal({ text: 'host', value: 'host' }); expect(result[1]).to.deep.equal({ text: 'app', value: 'app' }); @@ -641,7 +643,8 @@ describe('HawkularDatasource', () => { }, annotation: { query: "$who.timeline", - name: "Timeline" + name: "Timeline", + type: "strings" } }; @@ -680,13 +683,13 @@ describe('HawkularDatasource', () => { ctx.ds.annotationQuery(options).then(result => { expect(result).to.have.length(2); - expect(result[0].annotation).to.deep.equal({ query: "$who.timeline", name: "Timeline" }); + expect(result[0].annotation).to.deep.equal({ query: "$who.timeline", name: "Timeline", type: "strings" }); expect(result[0].time).to.equal(15); expect(result[0].title).to.equal("Timeline"); expect(result[0].tags).to.equal('your.timeline'); expect(result[0].text).to.equal("start"); - expect(result[1].annotation).to.deep.equal({ query: "$who.timeline", name: "Timeline" }); + expect(result[1].annotation).to.deep.equal({ query: "$who.timeline", name: "Timeline", type: "strings" }); expect(result[1].time).to.equal(13); expect(result[1].title).to.equal("Timeline"); expect(result[1].tags).to.equal('my.timeline'); diff --git a/src/config_ctrl.js b/src/config_ctrl.js new file mode 100644 index 0000000..01609ce --- /dev/null +++ b/src/config_ctrl.js @@ -0,0 +1,8 @@ +export class HawkularConfigCtrl { + + constructor() { + this.current.url = this.current.url || 'http://your_server:8080/hawkular/metrics' + } +} + +HawkularConfigCtrl.templateUrl = 'partials/config.html'; diff --git a/src/datasource.js b/src/datasource.js index 65bd05b..839ddde 100644 --- a/src/datasource.js +++ b/src/datasource.js @@ -10,25 +10,40 @@ export class HawkularDatasource { this.type = instanceSettings.type; this.url = instanceSettings.url; this.name = instanceSettings.name; - this.q = $q; - this.backendSrv = backendSrv; - this.headers = { - 'Content-Type': 'application/json', - 'Hawkular-Tenant': instanceSettings.jsonData.tenant - }; + this.tenant = instanceSettings.jsonData.tenant; + this.isTenantPerQuery = instanceSettings.jsonData.isTenantPerQuery; + this.authorization = null; if (typeof instanceSettings.basicAuth === 'string' && instanceSettings.basicAuth.length > 0) { - this.headers['Authorization'] = instanceSettings.basicAuth; + this.authorization = instanceSettings.basicAuth; } else if (typeof instanceSettings.jsonData.token === 'string' && instanceSettings.jsonData.token.length > 0) { - this.headers['Authorization'] = 'Bearer ' + instanceSettings.jsonData.token; + this.authorization = 'Bearer ' + instanceSettings.jsonData.token; } + this.q = $q; + this.backendSrv = backendSrv; this.typeResources = { - "gauge": "gauges", - "counter": "counters", - "availability": "availability" + 'gauge': 'gauges', + 'counter': 'counters', + 'availability': 'availability' }; this.variablesHelper = new VariablesHelper(templateSrv); this.capabilitiesPromise = this.queryVersion().then(version => new Capabilities(version)); - this.queryProcessor = new QueryProcessor($q, backendSrv, this.variablesHelper, this.capabilitiesPromise, this.url, this.headers, this.typeResources); + this.queryProcessor = new QueryProcessor($q, backendSrv, this.variablesHelper, this.capabilitiesPromise, this.url, + this.getHeaders.bind(this), this.typeResources); + } + + getHeaders(tenant) { + const headers = { + 'Content-Type': 'application/json' + } + if (tenant && this.isTenantPerQuery) { + headers['Hawkular-Tenant'] = tenant; + } else { + headers['Hawkular-Tenant'] = this.tenant; + } + if (this.authorization) { + headers['Authorization'] = this.authorization; + } + return headers; } query(options) { @@ -74,15 +89,19 @@ export class HawkularDatasource { } testDatasource() { + // If tenants is unknown at this point (when having per-query tenants) + // We do a more basic check to status endpoint + // Else, it's full connectivity with tenant check + const endpoint = this.isTenantPerQuery ? '/status' : '/metrics'; return this.backendSrv.datasourceRequest({ - url: this.url + '/metrics', + url: this.url + endpoint, method: 'GET', - headers: this.headers + headers: this.getHeaders() }).then(response => { if (response.status === 200 || response.status === 204) { - return { status: "success", message: "Data source is working", title: "Success" }; + return { status: 'success', message: 'Data source is working', title: 'Success' }; } else { - return { status: "error", message: "Connection failed (" + response.status + ")", title: "Error" }; + return { status: 'error', message: 'Connection failed (' + response.status + ')', title: 'Error' }; } }); } @@ -90,7 +109,7 @@ export class HawkularDatasource { annotationQuery(options) { const metricIds = this.variablesHelper.resolve(options.annotation.query, options); return this.backendSrv.datasourceRequest({ - url: this.url + '/strings/raw/query', + url: `${this.url}/${options.annotation.type}/raw/query`, data: { start: options.range.from.valueOf(), end: options.range.to.valueOf(), @@ -98,7 +117,7 @@ export class HawkularDatasource { ids: metricIds }, method: 'POST', - headers: this.headers + headers: this.getHeaders(options.annotation.tenant) }).then(response => response.status == 200 ? response.data : []) .then(metrics => { let allAnnotations = []; @@ -141,7 +160,7 @@ export class HawkularDatasource { return this.backendSrv.datasourceRequest({ url: url, method: 'GET', - headers: this.headers + headers: this.getHeaders(target.tenant) }).then(response => response.status == 200 ? response.data : []) .then(result => { return result.map(m => m.id) @@ -152,25 +171,25 @@ export class HawkularDatasource { }); } - suggestTags(type, key) { + suggestTags(target, key) { if (!key) { return this.q.when([]); } return this.backendSrv.datasourceRequest({ - url: this.url + '/' + this.typeResources[type] + '/tags/' + key + ':*', + url: this.url + '/' + this.typeResources[target.type] + '/tags/' + key + ':*', method: 'GET', - headers: this.headers + headers: this.getHeaders(target.tenant) }).then(result => result.data.hasOwnProperty(key) ? result.data[key] : []) .then(tags => tags.map(tag => { return {text: tag, value: tag}; })); } - suggestTagKeys(type) { + suggestTagKeys(target) { return this.backendSrv.datasourceRequest({ url: this.url + '/metrics/tags', method: 'GET', - headers: this.headers + headers: this.getHeaders(target.tenant) }).then(response => response.status == 200 ? response.data : []) .then(result => result.map(key => ({text: key, value: key}))); } @@ -190,7 +209,7 @@ export class HawkularDatasource { return this.runWithResolvedVariables(params, p => this.backendSrv.datasourceRequest({ url: this.url + '/metrics' + p, method: 'GET', - headers: this.headers + headers: this.getHeaders() }).then(result => { return _.map(result.data, metric => { return {text: metric.id, value: metric.id}; @@ -202,7 +221,7 @@ export class HawkularDatasource { return this.runWithResolvedVariables(pattern, p => this.backendSrv.datasourceRequest({ url: this.url + '/metrics/tags/' + p, method: 'GET', - headers: this.headers + headers: this.getHeaders() }).then(result => { let flatTags = []; if (result.data) { diff --git a/src/module.js b/src/module.js index 79b2b11..4fb90ed 100644 --- a/src/module.js +++ b/src/module.js @@ -1,8 +1,6 @@ import {HawkularDatasource} from './datasource'; import {HawkularDatasourceQueryCtrl} from './query_ctrl'; - -class HawkularConfigCtrl {} -HawkularConfigCtrl.templateUrl = 'partials/config.html'; +import {HawkularConfigCtrl} from './config_ctrl'; class HawkularQueryOptionsCtrl {} HawkularQueryOptionsCtrl.templateUrl = 'partials/query.options.html'; diff --git a/src/partials/annotations.editor.html b/src/partials/annotations.editor.html index 4b7dec5..ae2b801 100644 --- a/src/partials/annotations.editor.html +++ b/src/partials/annotations.editor.html @@ -1,7 +1,22 @@ - -
Query
+
+
+ + Tenant  + + + +
+
+ Type + +
+
+ Metric name
diff --git a/src/partials/config.html b/src/partials/config.html index f17bc93..bdde330 100644 --- a/src/partials/config.html +++ b/src/partials/config.html @@ -4,7 +4,8 @@

Hawkular settings

-
+ +
Tenant diff --git a/src/partials/query.editor.html b/src/partials/query.editor.html index 290f0e5..f409486 100644 --- a/src/partials/query.editor.html +++ b/src/partials/query.editor.html @@ -1,6 +1,8 @@
+ + diff --git a/src/partials/query.options.html b/src/partials/query.options.html index b460011..ac478ee 100644 --- a/src/partials/query.options.html +++ b/src/partials/query.options.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/queryProcessor.js b/src/queryProcessor.js index 959bc4e..2b9012d 100644 --- a/src/queryProcessor.js +++ b/src/queryProcessor.js @@ -4,13 +4,13 @@ const STATS_BUCKETS = 60; export class QueryProcessor { - constructor(q, backendSrv, variablesHelper, capabilities, url, headers, typeResources) { + constructor(q, backendSrv, variablesHelper, capabilities, url, getHeaders, typeResources) { this.q = q; this.backendSrv = backendSrv; this.variablesHelper = variablesHelper; this.capabilities = capabilities; this.url = url; - this.headers = headers; + this.getHeaders = getHeaders; this.typeResources = typeResources; this.numericMapping = point => [point.value, point.timestamp]; this.availMapping = point => [point.value == 'up' ? 1 : 0, point.timestamp]; @@ -87,7 +87,7 @@ export class QueryProcessor { url: url, data: postData, method: 'POST', - headers: this.headers + headers: this.getHeaders(target.tenant) }).then(response => this.processRawResponse(target, response.status == 200 ? response.data : [])); } @@ -106,7 +106,7 @@ export class QueryProcessor { end: range.to.valueOf() }, method: 'GET', - headers: this.headers + headers: this.getHeaders(target.tenant) }).then(response => this.processRawResponseLegacy(target, metric, response.status == 200 ? response.data : [])); })); } @@ -168,7 +168,7 @@ export class QueryProcessor { url: url, data: postData, method: 'POST', - headers: this.headers + headers: this.getHeaders(target.tenant) }).then(response => this.processStatsResponse(target, response.status == 200 ? response.data : [])); } @@ -211,7 +211,7 @@ export class QueryProcessor { url: url, data: postData, method: 'POST', - headers: this.headers + headers: this.getHeaders(target.tenant) }).then(response => this.processUnmergedStatsResponse(target, response.status == 200 ? response.data : [])); } @@ -285,7 +285,7 @@ export class QueryProcessor { url: url, data: postData, method: 'POST', - headers: this.headers + headers: this.getHeaders(target.tenant) }).then(response => this.processSingleStatResponse(target, fnBucket, response.status == 200 ? response.data : [])); } @@ -312,7 +312,7 @@ export class QueryProcessor { url: url, data: postData, method: 'POST', - headers: this.headers + headers: this.getHeaders(target.tenant) }).then(response => this.processSingleStatLiveResponse(target, response.status == 200 ? response.data : [])); } diff --git a/src/query_ctrl.js b/src/query_ctrl.js index c69fe80..24f2737 100644 --- a/src/query_ctrl.js +++ b/src/query_ctrl.js @@ -6,7 +6,7 @@ import {TagsQLController} from './tagsQLController'; export class HawkularDatasourceQueryCtrl extends QueryCtrl { - constructor($scope, $injector, uiSegmentSrv, $q) { + constructor($scope, $injector, uiSegmentSrv, $q) { super($scope, $injector); this.scope = $scope; diff --git a/src/tagsKVPairsController.js b/src/tagsKVPairsController.js index 5f28c85..01770d3 100644 --- a/src/tagsKVPairsController.js +++ b/src/tagsKVPairsController.js @@ -27,7 +27,7 @@ export class TagsKVPairsController { .then(keys => [angular.copy(this.removeTagsSegment), ...keys]); } else if (segment.type === 'value') { let key = segments[$index-2].value; - return this.datasource.suggestTags(this.targetSupplier().type, key) + return this.datasource.suggestTags(this.targetSupplier(), key) .then(tags => [{text: ' *', value: ' *'}, ...tags]) .then(this.uiSegmentSrv.transformToSegments(false)); } @@ -35,7 +35,7 @@ export class TagsKVPairsController { getTagKeys() { if (this.fetchAllTagsCapability) { - return this.datasource.suggestTagKeys() + return this.datasource.suggestTagKeys(this.targetSupplier()) .then(this.uiSegmentSrv.transformToSegments(false)); } else { return this.$q.when([]); diff --git a/src/tagsQLController.js b/src/tagsQLController.js index bb46ddd..b7e6f4a 100644 --- a/src/tagsQLController.js +++ b/src/tagsQLController.js @@ -53,7 +53,7 @@ export class TagsQLController { let i = this.getContainingEnum(segments, $index); if (i > 0) { const key = segments[i-1].value; - return this.datasource.suggestTags(this.targetSupplier().type, key) + return this.datasource.suggestTags(this.targetSupplier(), key) .then(this.uiSegmentSrv.transformToSegments(false)); } else { return this.getTagKeys(); @@ -68,7 +68,7 @@ export class TagsQLController { i--; } const key = segments[i].value; - let promise = this.datasource.suggestTags(this.targetSupplier().type, key) + let promise = this.datasource.suggestTags(this.targetSupplier(), key) .then(this.uiSegmentSrv.transformToSegments(false)); if (segments[$index-1].type === 'value') { // We're in an enumeration @@ -99,7 +99,7 @@ export class TagsQLController { } getTagKeys() { - return this.datasource.suggestTagKeys() + return this.datasource.suggestTagKeys(this.targetSupplier()) .then(this.uiSegmentSrv.transformToSegments(false)); } From 8a4218c1ce5c5d2e806de2f6c9f221e75e586401 Mon Sep 17 00:00:00 2001 From: Joel Takvorian Date: Tue, 25 Jul 2017 10:10:38 +0200 Subject: [PATCH 2/5] Reorganize tests (split in several files) --- spec/datasource-annotations_spec.js | 183 +++++++++ spec/datasource-singlestat_spec.js | 334 ++++++++++++++++ spec/datasource-tagsQL_spec.js | 42 +- spec/datasource_spec.js | 579 +--------------------------- spec/test-util.js | 27 ++ 5 files changed, 564 insertions(+), 601 deletions(-) create mode 100644 spec/datasource-annotations_spec.js create mode 100644 spec/datasource-singlestat_spec.js create mode 100644 spec/test-util.js diff --git a/spec/datasource-annotations_spec.js b/spec/datasource-annotations_spec.js new file mode 100644 index 0000000..1f6319e --- /dev/null +++ b/spec/datasource-annotations_spec.js @@ -0,0 +1,183 @@ +import {Datasource} from "../module"; +import Q from "q"; +import {getSettings, expectRequest} from './test-util'; + +describe('HawkularDatasource annotations', () => { + let ctx = {}; + const instanceSettings = getSettings(); + + beforeEach(() => { + ctx.$q = Q; + ctx.backendSrv = {}; + ctx.backendSrv.datasourceRequest = request => { + return ctx.$q.when({data: {'Implementation-Version': '0.22.0'}}) + }; + ctx.templateSrv = { + replace: (target, vars) => target + }; + ctx.ds = new Datasource(instanceSettings, ctx.$q, ctx.backendSrv, ctx.templateSrv); + }); + + it('should query annotations without tags', done => { + + let options = { + range: { + from: 15, + to: 30 + }, + annotation: { + query: "my.timeline", + name: "Timeline", + type: "strings" + } + }; + + ctx.backendSrv.datasourceRequest = request => { + expectRequest(request, 'POST', '/hawkular/metrics/strings/raw/query'); + + return ctx.$q.when({ + status: 200, + data: [{ + id: "my.timeline", + data: [{ + timestamp: 13, + value: 'start' + }, { + timestamp: 19, + value: 'stop' + }] + }] + }); + }; + + ctx.ds.annotationQuery(options).then(result => { + expect(result).to.have.length(2); + expect(result[0].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); + expect(result[0].time).to.equal(13); + expect(result[0].title).to.equal("Timeline"); + expect(result[0].tags).to.be.undefined; + expect(result[0].text).to.equal("start"); + + expect(result[1].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); + expect(result[1].time).to.equal(19); + expect(result[1].title).to.equal("Timeline"); + expect(result[1].tags).to.be.undefined; + expect(result[1].text).to.equal("stop"); + }).then(v => done(), err => done(err)); + }); + + it('should query annotations with tags', done => { + + let options = { + range: { + from: 15, + to: 30 + }, + annotation: { + query: "my.timeline", + name: "Timeline", + type: "strings" + } + }; + + ctx.backendSrv.datasourceRequest = request => { + expectRequest(request, 'POST', '/hawkular/metrics/strings/raw/query'); + + return ctx.$q.when({ + status: 200, + data: [{ + id: "my.timeline", + data: [{ + timestamp: 13, + value: 'start', + tags: { + 'item': 'myItem', + 'step': 'start' + } + }, { + timestamp: 19, + value: 'stop', + tags: { + 'item': 'myItem', + 'step': 'stop' + } + }] + }] + }); + }; + + ctx.ds.annotationQuery(options).then(result => { + expect(result).to.have.length(2); + expect(result[0].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); + expect(result[0].time).to.equal(13); + expect(result[0].title).to.equal("Timeline"); + expect(result[0].tags).to.equal("myItem start"); + expect(result[0].text).to.equal("start"); + + expect(result[1].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); + expect(result[1].time).to.equal(19); + expect(result[1].title).to.equal("Timeline"); + expect(result[1].tags).to.equal("myItem stop"); + expect(result[1].text).to.equal("stop"); + }).then(v => done(), err => done(err)); + }); + + it('should resolve variables in annotations', done => { + let options = { + range: { + from: 15, + to: 30 + }, + annotation: { + query: "$who.timeline", + name: "Timeline", + type: "strings" + } + }; + + ctx.templateSrv.variables = [{ + name: 'who' + }]; + ctx.templateSrv.replace = (target, vars) => { + expect(target).to.equal('$who'); + return "{your,my}"; + }; + + ctx.backendSrv.datasourceRequest = request => { + expectRequest(request, 'POST', '/hawkular/metrics/strings/raw/query'); + expect(request.data.ids).to.deep.equal(['your.timeline', 'my.timeline']); + + return ctx.$q.when({ + status: 200, + data: [{ + id: "your.timeline", + data: [{ + timestamp: 15, + value: 'start' + }] + },{ + id: "my.timeline", + data: [{ + timestamp: 13, + value: 'start' + }] + }] + }); + }; + + ctx.ds.annotationQuery(options).then(result => { + expect(result).to.have.length(2); + expect(result[0].annotation).to.deep.equal({ query: "$who.timeline", name: "Timeline", type: "strings" }); + expect(result[0].time).to.equal(15); + expect(result[0].title).to.equal("Timeline"); + expect(result[0].tags).to.equal('your.timeline'); + expect(result[0].text).to.equal("start"); + + expect(result[1].annotation).to.deep.equal({ query: "$who.timeline", name: "Timeline", type: "strings" }); + expect(result[1].time).to.equal(13); + expect(result[1].title).to.equal("Timeline"); + expect(result[1].tags).to.equal('my.timeline'); + expect(result[1].text).to.equal("start"); + }).then(v => done(), err => done(err)); + }); +}); diff --git a/spec/datasource-singlestat_spec.js b/spec/datasource-singlestat_spec.js new file mode 100644 index 0000000..5a7016d --- /dev/null +++ b/spec/datasource-singlestat_spec.js @@ -0,0 +1,334 @@ +import {Datasource} from "../module"; +import Q from "q"; +import {getSettings, expectRequest} from './test-util'; + +describe('HawkularDatasource for downsamples', () => { + let ctx = {}; + const instanceSettings = getSettings(); + + beforeEach(() => { + ctx.$q = Q; + ctx.backendSrv = {}; + ctx.backendSrv.datasourceRequest = request => { + return ctx.$q.when({data: {'Implementation-Version': '0.22.0'}}) + }; + ctx.templateSrv = { + replace: (target, vars) => target + }; + ctx.ds = new Datasource(instanceSettings, ctx.$q, ctx.backendSrv, ctx.templateSrv); + }); + + it('should return aggregated stats max/stacked', done => { + + let options = { + range: { + from: 15, + to: 30 + }, + targets: [{ + seriesAggFn: 'sum', + timeAggFn: 'max', + tags: [{name: 'type', value: 'memory'}], + type: 'gauge', + rate: false + }] + }; + + ctx.backendSrv.datasourceRequest = request => { + expectRequest(request, 'POST', '/hawkular/metrics/gauges/stats/query'); + expect(request.data).to.deep.equal({ + start: options.range.from, + end: options.range.to, + tags: "type:memory", + buckets: 1, + stacked: true + }); + + return ctx.$q.when({ + status: 200, + data: [{ + start: 13, + end: 19, + min: 35, + max: 46, + avg: 40.5 + }] + }); + }; + + ctx.ds.query(options).then(result => { + expect(result.data).to.have.length(1); + expect(result.data[0].datapoints).to.deep.equal([[46, 13]]); + }).then(v => done(), err => done(err)); + }); + + it('should return aggregated stats avg/not stacked', done => { + + let options = { + range: { + from: 15, + to: 30 + }, + targets: [{ + seriesAggFn: 'avg', + timeAggFn: 'avg', + tags: [{name: 'type', value: 'memory'}], + type: 'gauge', + rate: false + }] + }; + + ctx.backendSrv.datasourceRequest = request => { + expectRequest(request, 'POST', '/hawkular/metrics/gauges/stats/query'); + expect(request.data).to.deep.equal({ + start: options.range.from, + end: options.range.to, + tags: "type:memory", + buckets: 1, + stacked: false + }); + + return ctx.$q.when({ + status: 200, + data: [{ + start: 13, + end: 19, + min: 15, + max: 25, + avg: 20.25 + }] + }); + }; + + ctx.ds.query(options).then(result => { + expect(result.data).to.have.length(1); + expect(result.data[0].datapoints).to.deep.equal([[20.25, 13]]); + }).then(v => done(), err => done(err)); + }); + + it('should return live stats stacked', done => { + + let options = { + range: { + from: 15, + to: 30 + }, + targets: [{ + seriesAggFn: 'sum', + timeAggFn: 'live', + tags: [{name: 'type', value: 'memory'}], + type: 'gauge', + rate: false + }] + }; + + ctx.backendSrv.datasourceRequest = request => { + expectRequest(request, 'POST', '/hawkular/metrics/gauges/raw/query'); + expect(request.data.limit).to.equal(1); + expect(request.data.tags).to.equal("type:memory"); + + return ctx.$q.when({ + status: 200, + data: [{ + id: "myhost.metric.memory.1", + data: [{ + timestamp: 18, + value: 21 + }] + },{ + id: "myhost.metric.memory.2", + data: [{ + timestamp: 19, + value: 25 + }] + }] + }); + }; + + ctx.ds.query(options).then(result => { + expect(result.data).to.have.length(1); + expect(result.data[0].datapoints).to.deep.equal([[46, 18]]); + }).then(v => done(), err => done(err)); + }); + + it('should return live stats not stacked', done => { + + let options = { + range: { + from: 15, + to: 30 + }, + targets: [{ + seriesAggFn: 'avg', + timeAggFn: 'live', + tags: [{name: 'type', value: 'memory'}], + type: 'gauge', + rate: false + }] + }; + + ctx.backendSrv.datasourceRequest = request => { + expectRequest(request, 'POST', '/hawkular/metrics/gauges/raw/query'); + expect(request.data.limit).to.equal(1); + expect(request.data.tags).to.equal("type:memory"); + + return ctx.$q.when({ + status: 200, + data: [{ + id: "myhost.metric.memory.1", + data: [{ + timestamp: 18, + value: 21 + }] + },{ + id: "myhost.metric.memory.2", + data: [{ + timestamp: 19, + value: 25 + }] + }] + }); + }; + + ctx.ds.query(options).then(result => { + expect(result.data).to.have.length(1); + expect(result.data[0].datapoints).to.deep.equal([[23, 18]]); + }).then(v => done(), err => done(err)); + }); + + it('should query summed stats avg and percentile', done => { + let options = { + range: { + from: 20, + to: 30 + }, + targets: [{ + seriesAggFn: 'sum', + stats: ['avg', '90 %ile'], + tags: [{name: 'type', value: 'memory'}], + type: 'gauge', + rate: false, + raw: false + }] + }; + + ctx.backendSrv.datasourceRequest = request => { + expectRequest(request, 'POST', '/hawkular/metrics/gauges/stats/query'); + expect(request.data).to.deep.equal({ + start: options.range.from, + end: options.range.to, + tags: "type:memory", + percentiles: "90", + buckets: 60, + stacked: true + }); + + return ctx.$q.when({ + status: 200, + data: [{ + start: 20, + end: 25, + min: 15, + max: 25, + avg: 20.25, + percentiles: [{"value":23.1,"originalQuantile":"90","quantile":90.0}] + }, { + start: 25, + end: 30, + min: 18, + max: 28, + avg: 23.25, + percentiles: [{"value":26.1,"originalQuantile":"90","quantile":90.0}] + }] + }); + }; + + ctx.ds.query(options).then(result => { + expect(result.data).to.have.length(2); + expect(result.data[1].target).to.equal("avg"); + expect(result.data[1].datapoints).to.deep.equal([[20.25, 20], [23.25, 25]]); + expect(result.data[0].target).to.equal("90 %ile"); + expect(result.data[0].datapoints).to.deep.equal([[23.1, 20], [26.1, 25]]); + }).then(v => done(), err => done(err)); + }); + + it('should query unmerged stats min and percentile', done => { + let options = { + range: { + from: 20, + to: 30 + }, + targets: [{ + seriesAggFn: 'none', + stats: ['min', '95 %ile'], + tags: [{name: 'type', value: 'memory'}], + type: 'gauge', + rate: false, + raw: false + }] + }; + + ctx.backendSrv.datasourceRequest = request => { + expectRequest(request, 'POST', '/hawkular/metrics/metrics/stats/query'); + expect(request.data).to.deep.equal({ + start: options.range.from, + end: options.range.to, + tags: "type:memory", + percentiles: "95", + buckets: 60, + types: ["gauge"] + }); + + return ctx.$q.when({ + status: 200, + data: {"gauge": + { "gauge_1": + [{ + start: 20, + end: 25, + min: 15, + max: 25, + avg: 20.25, + percentiles: [{"value":23.1,"originalQuantile":"95","quantile":95.0}] + }, { + start: 25, + end: 30, + min: 18, + max: 28, + avg: 23.25, + percentiles: [{"value":26.1,"originalQuantile":"95","quantile":95.0}] + }], + "gauge_2": + [{ + start: 20, + end: 25, + min: 20, + max: 30, + avg: 25.25, + percentiles: [{"value":28.1,"originalQuantile":"95","quantile":95.0}] + }, { + start: 25, + end: 30, + min: 23, + max: 33, + avg: 28.25, + percentiles: [{"value":31.1,"originalQuantile":"95","quantile":95.0}] + }] + } + } + }); + }; + + ctx.ds.query(options).then(result => { + expect(result.data).to.have.length(4); + expect(result.data[1].target).to.equal("gauge_1 [min]"); + expect(result.data[1].datapoints).to.deep.equal([[15, 20], [18, 25]]); + expect(result.data[0].target).to.equal("gauge_1 [95 %ile]"); + expect(result.data[0].datapoints).to.deep.equal([[23.1, 20], [26.1, 25]]); + expect(result.data[3].target).to.equal("gauge_2 [min]"); + expect(result.data[3].datapoints).to.deep.equal([[20, 20], [23, 25]]); + expect(result.data[2].target).to.equal("gauge_2 [95 %ile]"); + expect(result.data[2].datapoints).to.deep.equal([[28.1, 20], [31.1, 25]]); + }).then(v => done(), err => done(err)); + }); +}); diff --git a/spec/datasource-tagsQL_spec.js b/spec/datasource-tagsQL_spec.js index a33b0d7..31381bc 100644 --- a/spec/datasource-tagsQL_spec.js +++ b/spec/datasource-tagsQL_spec.js @@ -1,33 +1,10 @@ import {Datasource} from "../module"; import Q from "q"; +import {getSettings, expectRequest} from './test-util'; describe('HawkularDatasource with tagsQL', () => { const ctx = {}; - const hProtocol = 'https'; - const hHostname = 'test.com'; - const hPort = '876'; - const hPath = 'hawkular/metrics'; - const instanceSettings = { - url: hProtocol + '://' + hHostname + ':' + hPort + '/' + hPath, - jsonData: { - tenant: 'test-tenant' - } - }; - - const parsePathElements = request => { - expect(request.method).to.equal('POST'); - expect(request.headers).to.have.property('Hawkular-Tenant', instanceSettings.jsonData.tenant); - - const parser = document.createElement('a'); - parser.href = request.url; - - expect(parser).to.have.property('protocol', hProtocol + ':'); - expect(parser).to.have.property('hostname', hHostname); - expect(parser).to.have.property('port', hPort); - expect(parser).to.have.property('pathname'); - - return parser.pathname.split('/').filter(e => e.length != 0); - } + const instanceSettings = getSettings(); beforeEach(() => { ctx.$q = Q; @@ -61,10 +38,7 @@ describe('HawkularDatasource with tagsQL', () => { }; ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['gauges', 'raw', 'query']); + expectRequest(request, 'POST', '/hawkular/metrics/gauges/raw/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, @@ -98,10 +72,7 @@ describe('HawkularDatasource with tagsQL', () => { }; ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['gauges', 'stats', 'query']); + expectRequest(request, 'POST', '/hawkular/metrics/gauges/stats/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, @@ -136,10 +107,7 @@ describe('HawkularDatasource with tagsQL', () => { }; ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['gauges', 'raw', 'query']); + expectRequest(request, 'POST', '/hawkular/metrics/gauges/raw/query'); expect(request.data.limit).to.equal(1); expect(request.data.tags).to.equal('type=memory'); diff --git a/spec/datasource_spec.js b/spec/datasource_spec.js index 8451ef2..7ab1a1e 100644 --- a/spec/datasource_spec.js +++ b/spec/datasource_spec.js @@ -1,33 +1,10 @@ import {Datasource} from "../module"; import Q from "q"; +import {getSettings, expectRequest} from './test-util'; describe('HawkularDatasource', () => { let ctx = {}; - let hProtocol = 'https'; - let hHostname = 'test.com'; - let hPort = '876'; - let hPath = 'hawkular/metrics'; - let instanceSettings = { - url: hProtocol + '://' + hHostname + ':' + hPort + '/' + hPath, - jsonData: { - tenant: 'test-tenant' - } - }; - - let parsePathElements = request => { - expect(request.method).to.equal('POST'); - expect(request.headers).to.have.property('Hawkular-Tenant', instanceSettings.jsonData.tenant); - - let parser = document.createElement('a'); - parser.href = request.url; - - expect(parser).to.have.property('protocol', hProtocol + ':'); - expect(parser).to.have.property('hostname', hHostname); - expect(parser).to.have.property('port', hPort); - expect(parser).to.have.property('pathname'); - - return parser.pathname.split('/').filter(e => e.length != 0); - } + const instanceSettings = getSettings(); beforeEach(() => { ctx.$q = Q; @@ -65,15 +42,13 @@ describe('HawkularDatasource', () => { }] }; + let first = true; + let id; ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - let id = pathElements[2] == 'gauges' ? 'memory' : 'packets'; - - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements[2]).to.be.oneOf(['gauges', 'counters']); - if (pathElements[2] == 'gauges') { - expect(pathElements.slice(3)).to.deep.equal(['raw', 'query']); + if (first) { + first = false; + id = 'memory'; + expectRequest(request, 'POST', '/hawkular/metrics/gauges/raw/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, @@ -81,7 +56,8 @@ describe('HawkularDatasource', () => { order: 'ASC' }); } else { - expect(pathElements.slice(3)).to.deep.equal(['rate', 'query']); + id = 'packets'; + expectRequest(request, 'POST', '/hawkular/metrics/counters/rate/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, @@ -188,10 +164,7 @@ describe('HawkularDatasource', () => { }; ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['gauges', 'raw', 'query']); + expectRequest(request, 'POST', '/hawkular/metrics/gauges/raw/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, @@ -231,196 +204,6 @@ describe('HawkularDatasource', () => { }).then(v => done(), err => done(err)); }); - it('should return aggregated stats max/stacked', done => { - - let options = { - range: { - from: 15, - to: 30 - }, - targets: [{ - seriesAggFn: 'sum', - timeAggFn: 'max', - tags: [{name: 'type', value: 'memory'}], - type: 'gauge', - rate: false - }] - }; - - ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['gauges', 'stats', 'query']); - expect(request.data).to.deep.equal({ - start: options.range.from, - end: options.range.to, - tags: "type:memory", - buckets: 1, - stacked: true - }); - - return ctx.$q.when({ - status: 200, - data: [{ - start: 13, - end: 19, - min: 35, - max: 46, - avg: 40.5 - }] - }); - }; - - ctx.ds.query(options).then(result => { - expect(result.data).to.have.length(1); - expect(result.data[0].datapoints).to.deep.equal([[46, 13]]); - }).then(v => done(), err => done(err)); - }); - - it('should return aggregated stats avg/not stacked', done => { - - let options = { - range: { - from: 15, - to: 30 - }, - targets: [{ - seriesAggFn: 'avg', - timeAggFn: 'avg', - tags: [{name: 'type', value: 'memory'}], - type: 'gauge', - rate: false - }] - }; - - ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['gauges', 'stats', 'query']); - expect(request.data).to.deep.equal({ - start: options.range.from, - end: options.range.to, - tags: "type:memory", - buckets: 1, - stacked: false - }); - - return ctx.$q.when({ - status: 200, - data: [{ - start: 13, - end: 19, - min: 15, - max: 25, - avg: 20.25 - }] - }); - }; - - ctx.ds.query(options).then(result => { - expect(result.data).to.have.length(1); - expect(result.data[0].datapoints).to.deep.equal([[20.25, 13]]); - }).then(v => done(), err => done(err)); - }); - - it('should return live stats stacked', done => { - - let options = { - range: { - from: 15, - to: 30 - }, - targets: [{ - seriesAggFn: 'sum', - timeAggFn: 'live', - tags: [{name: 'type', value: 'memory'}], - type: 'gauge', - rate: false - }] - }; - - ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['gauges', 'raw', 'query']); - expect(request.data.limit).to.equal(1); - expect(request.data.tags).to.equal("type:memory"); - - return ctx.$q.when({ - status: 200, - data: [{ - id: "myhost.metric.memory.1", - data: [{ - timestamp: 18, - value: 21 - }] - },{ - id: "myhost.metric.memory.2", - data: [{ - timestamp: 19, - value: 25 - }] - }] - }); - }; - - ctx.ds.query(options).then(result => { - expect(result.data).to.have.length(1); - expect(result.data[0].datapoints).to.deep.equal([[46, 18]]); - }).then(v => done(), err => done(err)); - }); - - it('should return live stats not stacked', done => { - - let options = { - range: { - from: 15, - to: 30 - }, - targets: [{ - seriesAggFn: 'avg', - timeAggFn: 'live', - tags: [{name: 'type', value: 'memory'}], - type: 'gauge', - rate: false - }] - }; - - ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['gauges', 'raw', 'query']); - expect(request.data.limit).to.equal(1); - expect(request.data.tags).to.equal("type:memory"); - - return ctx.$q.when({ - status: 200, - data: [{ - id: "myhost.metric.memory.1", - data: [{ - timestamp: 18, - value: 21 - }] - },{ - id: "myhost.metric.memory.2", - data: [{ - timestamp: 19, - value: 25 - }] - }] - }); - }; - - ctx.ds.query(options).then(result => { - expect(result.data).to.have.length(1); - expect(result.data[0].datapoints).to.deep.equal([[23, 18]]); - }).then(v => done(), err => done(err)); - }); - it('should query availability', done => { let options = { @@ -435,10 +218,7 @@ describe('HawkularDatasource', () => { }; ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['availability', 'raw', 'query']); + expectRequest(request, 'POST', '/hawkular/metrics/availability/raw/query'); return ctx.$q.when({ status: 200, @@ -462,124 +242,9 @@ describe('HawkularDatasource', () => { }).then(v => done(), err => done(err)); }); - it('should query annotations without tags', done => { - - let options = { - range: { - from: 15, - to: 30 - }, - annotation: { - query: "my.timeline", - name: "Timeline", - type: "strings" - } - }; - - ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['strings', 'raw', 'query']); - - return ctx.$q.when({ - status: 200, - data: [{ - id: "my.timeline", - data: [{ - timestamp: 13, - value: 'start' - }, { - timestamp: 19, - value: 'stop' - }] - }] - }); - }; - - ctx.ds.annotationQuery(options).then(result => { - expect(result).to.have.length(2); - expect(result[0].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); - expect(result[0].time).to.equal(13); - expect(result[0].title).to.equal("Timeline"); - expect(result[0].tags).to.be.undefined; - expect(result[0].text).to.equal("start"); - - expect(result[1].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); - expect(result[1].time).to.equal(19); - expect(result[1].title).to.equal("Timeline"); - expect(result[1].tags).to.be.undefined; - expect(result[1].text).to.equal("stop"); - }).then(v => done(), err => done(err)); - }); - - it('should query annotations with tags', done => { - - let options = { - range: { - from: 15, - to: 30 - }, - annotation: { - query: "my.timeline", - name: "Timeline", - type: "strings" - } - }; - - ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['strings', 'raw', 'query']); - - return ctx.$q.when({ - status: 200, - data: [{ - id: "my.timeline", - data: [{ - timestamp: 13, - value: 'start', - tags: { - 'item': 'myItem', - 'step': 'start' - } - }, { - timestamp: 19, - value: 'stop', - tags: { - 'item': 'myItem', - 'step': 'stop' - } - }] - }] - }); - }; - - ctx.ds.annotationQuery(options).then(result => { - expect(result).to.have.length(2); - expect(result[0].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); - expect(result[0].time).to.equal(13); - expect(result[0].title).to.equal("Timeline"); - expect(result[0].tags).to.equal("myItem start"); - expect(result[0].text).to.equal("start"); - - expect(result[1].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); - expect(result[1].time).to.equal(19); - expect(result[1].title).to.equal("Timeline"); - expect(result[1].tags).to.equal("myItem stop"); - expect(result[1].text).to.equal("stop"); - }).then(v => done(), err => done(err)); - }); - it('should get tags suggestions', done => { ctx.backendSrv.datasourceRequest = request => { - let parser = document.createElement('a'); - parser.href = request.url; - const pathElements = parser.pathname.split('/').filter(e => e.length != 0); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['gauges', 'tags', 'host:*']); + expectRequest(request, 'GET', '/hawkular/metrics/gauges/tags/host:*'); return ctx.$q.when({ status: 200, @@ -598,12 +263,7 @@ describe('HawkularDatasource', () => { it('should get no suggestions on unknown tag', done => { ctx.backendSrv.datasourceRequest = request => { - let parser = document.createElement('a'); - parser.href = request.url; - const pathElements = parser.pathname.split('/').filter(e => e.length != 0); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['gauges', 'tags', 'host:*']); + expectRequest(request, 'GET', '/hawkular/metrics/gauges/tags/host:*'); return ctx.$q.when({ status: 204, data: {} @@ -616,12 +276,7 @@ describe('HawkularDatasource', () => { it('should get tag keys suggestions', done => { ctx.backendSrv.datasourceRequest = request => { - let parser = document.createElement('a'); - parser.href = request.url; - const pathElements = parser.pathname.split('/').filter(e => e.length != 0); - expect(pathElements).to.have.length(4); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['metrics', 'tags']); + expectRequest(request, 'GET', '/hawkular/metrics/metrics/tags'); return ctx.$q.when({ status: 200, data: ['host', 'app'] @@ -634,208 +289,4 @@ describe('HawkularDatasource', () => { expect(result[1]).to.deep.equal({ text: 'app', value: 'app' }); }).then(v => done(), err => done(err)); }); - - it('should resolve variables in annotations', done => { - let options = { - range: { - from: 15, - to: 30 - }, - annotation: { - query: "$who.timeline", - name: "Timeline", - type: "strings" - } - }; - - ctx.templateSrv.variables = [{ - name: 'who' - }]; - ctx.templateSrv.replace = (target, vars) => { - expect(target).to.equal('$who'); - return "{your,my}"; - }; - - ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['strings', 'raw', 'query']); - expect(request.data.ids).to.deep.equal(['your.timeline', 'my.timeline']); - - return ctx.$q.when({ - status: 200, - data: [{ - id: "your.timeline", - data: [{ - timestamp: 15, - value: 'start' - }] - },{ - id: "my.timeline", - data: [{ - timestamp: 13, - value: 'start' - }] - }] - }); - }; - - ctx.ds.annotationQuery(options).then(result => { - expect(result).to.have.length(2); - expect(result[0].annotation).to.deep.equal({ query: "$who.timeline", name: "Timeline", type: "strings" }); - expect(result[0].time).to.equal(15); - expect(result[0].title).to.equal("Timeline"); - expect(result[0].tags).to.equal('your.timeline'); - expect(result[0].text).to.equal("start"); - - expect(result[1].annotation).to.deep.equal({ query: "$who.timeline", name: "Timeline", type: "strings" }); - expect(result[1].time).to.equal(13); - expect(result[1].title).to.equal("Timeline"); - expect(result[1].tags).to.equal('my.timeline'); - expect(result[1].text).to.equal("start"); - }).then(v => done(), err => done(err)); - }); - - it('should query summed stats avg and percentile', done => { - let options = { - range: { - from: 20, - to: 30 - }, - targets: [{ - seriesAggFn: 'sum', - stats: ['avg', '90 %ile'], - tags: [{name: 'type', value: 'memory'}], - type: 'gauge', - rate: false, - raw: false - }] - }; - - ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['gauges', 'stats', 'query']); - expect(request.data).to.deep.equal({ - start: options.range.from, - end: options.range.to, - tags: "type:memory", - percentiles: "90", - buckets: 60, - stacked: true - }); - - return ctx.$q.when({ - status: 200, - data: [{ - start: 20, - end: 25, - min: 15, - max: 25, - avg: 20.25, - percentiles: [{"value":23.1,"originalQuantile":"90","quantile":90.0}] - }, { - start: 25, - end: 30, - min: 18, - max: 28, - avg: 23.25, - percentiles: [{"value":26.1,"originalQuantile":"90","quantile":90.0}] - }] - }); - }; - - ctx.ds.query(options).then(result => { - expect(result.data).to.have.length(2); - expect(result.data[1].target).to.equal("avg"); - expect(result.data[1].datapoints).to.deep.equal([[20.25, 20], [23.25, 25]]); - expect(result.data[0].target).to.equal("90 %ile"); - expect(result.data[0].datapoints).to.deep.equal([[23.1, 20], [26.1, 25]]); - }).then(v => done(), err => done(err)); - }); - - it('should query unmerged stats min and percentile', done => { - let options = { - range: { - from: 20, - to: 30 - }, - targets: [{ - seriesAggFn: 'none', - stats: ['min', '95 %ile'], - tags: [{name: 'type', value: 'memory'}], - type: 'gauge', - rate: false, - raw: false - }] - }; - - ctx.backendSrv.datasourceRequest = request => { - const pathElements = parsePathElements(request); - expect(pathElements).to.have.length(5); - expect(pathElements.slice(0, 2)).to.deep.equal(hPath.split('/')); - expect(pathElements.slice(2)).to.deep.equal(['metrics', 'stats', 'query']); - expect(request.data).to.deep.equal({ - start: options.range.from, - end: options.range.to, - tags: "type:memory", - percentiles: "95", - buckets: 60, - types: ["gauge"] - }); - - return ctx.$q.when({ - status: 200, - data: {"gauge": - { "gauge_1": - [{ - start: 20, - end: 25, - min: 15, - max: 25, - avg: 20.25, - percentiles: [{"value":23.1,"originalQuantile":"95","quantile":95.0}] - }, { - start: 25, - end: 30, - min: 18, - max: 28, - avg: 23.25, - percentiles: [{"value":26.1,"originalQuantile":"95","quantile":95.0}] - }], - "gauge_2": - [{ - start: 20, - end: 25, - min: 20, - max: 30, - avg: 25.25, - percentiles: [{"value":28.1,"originalQuantile":"95","quantile":95.0}] - }, { - start: 25, - end: 30, - min: 23, - max: 33, - avg: 28.25, - percentiles: [{"value":31.1,"originalQuantile":"95","quantile":95.0}] - }] - } - } - }); - }; - - ctx.ds.query(options).then(result => { - expect(result.data).to.have.length(4); - expect(result.data[1].target).to.equal("gauge_1 [min]"); - expect(result.data[1].datapoints).to.deep.equal([[15, 20], [18, 25]]); - expect(result.data[0].target).to.equal("gauge_1 [95 %ile]"); - expect(result.data[0].datapoints).to.deep.equal([[23.1, 20], [26.1, 25]]); - expect(result.data[3].target).to.equal("gauge_2 [min]"); - expect(result.data[3].datapoints).to.deep.equal([[20, 20], [23, 25]]); - expect(result.data[2].target).to.equal("gauge_2 [95 %ile]"); - expect(result.data[2].datapoints).to.deep.equal([[28.1, 20], [31.1, 25]]); - }).then(v => done(), err => done(err)); - }); }); diff --git a/spec/test-util.js b/spec/test-util.js new file mode 100644 index 0000000..1582766 --- /dev/null +++ b/spec/test-util.js @@ -0,0 +1,27 @@ +const hProtocol = 'https'; +const hHostname = 'test.com'; +const hPort = '876'; +const hPath = 'hawkular/metrics'; +const instanceSettings = { + url: hProtocol + '://' + hHostname + ':' + hPort + '/' + hPath, + jsonData: { + tenant: 'test-tenant' + } +}; + +export function expectRequest(request, verb, path) { + expect(request.method).to.equal(verb); + expect(request.headers).to.have.property('Hawkular-Tenant', instanceSettings.jsonData.tenant); + + const parser = document.createElement('a'); + parser.href = request.url; + + expect(parser).to.have.property('protocol', hProtocol + ':'); + expect(parser).to.have.property('hostname', hHostname); + expect(parser).to.have.property('port', hPort); + expect(parser).to.have.property('pathname', path); +} + +export function getSettings() { + return instanceSettings; +} From 474e9e2f975e24db2f530afe962d8ecf6f79f187 Mon Sep 17 00:00:00 2001 From: Joel Takvorian Date: Tue, 25 Jul 2017 11:35:02 +0200 Subject: [PATCH 3/5] Add unit tests for per query tenants also some refactoring in tests for easier/more readable assertions on requests --- spec/datasource-annotations_spec.js | 126 +++++++--- ...spec.js => datasource-downsamples_spec.js} | 72 +++--- spec/datasource-tagsQL_spec.js | 10 +- spec/datasource-tenant-per-query_spec.js | 233 ++++++++++++++++++ spec/datasource_spec.js | 67 +++-- spec/test-util.js | 15 +- 6 files changed, 415 insertions(+), 108 deletions(-) rename spec/{datasource-singlestat_spec.js => datasource-downsamples_spec.js} (78%) create mode 100644 spec/datasource-tenant-per-query_spec.js diff --git a/spec/datasource-annotations_spec.js b/spec/datasource-annotations_spec.js index 1f6319e..b68132a 100644 --- a/spec/datasource-annotations_spec.js +++ b/spec/datasource-annotations_spec.js @@ -1,5 +1,5 @@ -import {Datasource} from "../module"; -import Q from "q"; +import {Datasource} from '../module'; +import Q from 'q'; import {getSettings, expectRequest} from './test-util'; describe('HawkularDatasource annotations', () => { @@ -26,19 +26,19 @@ describe('HawkularDatasource annotations', () => { to: 30 }, annotation: { - query: "my.timeline", - name: "Timeline", - type: "strings" + query: 'my.timeline', + name: 'Timeline', + type: 'strings' } }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/strings/raw/query'); + expectRequest(request, 'POST', 'strings/raw/query'); return ctx.$q.when({ status: 200, data: [{ - id: "my.timeline", + id: 'my.timeline', data: [{ timestamp: 13, value: 'start' @@ -52,17 +52,17 @@ describe('HawkularDatasource annotations', () => { ctx.ds.annotationQuery(options).then(result => { expect(result).to.have.length(2); - expect(result[0].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); + expect(result[0].annotation).to.deep.equal({ query: 'my.timeline', name: 'Timeline', type: 'strings' }); expect(result[0].time).to.equal(13); - expect(result[0].title).to.equal("Timeline"); + expect(result[0].title).to.equal('Timeline'); expect(result[0].tags).to.be.undefined; - expect(result[0].text).to.equal("start"); + expect(result[0].text).to.equal('start'); - expect(result[1].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); + expect(result[1].annotation).to.deep.equal({ query: 'my.timeline', name: 'Timeline', type: 'strings' }); expect(result[1].time).to.equal(19); - expect(result[1].title).to.equal("Timeline"); + expect(result[1].title).to.equal('Timeline'); expect(result[1].tags).to.be.undefined; - expect(result[1].text).to.equal("stop"); + expect(result[1].text).to.equal('stop'); }).then(v => done(), err => done(err)); }); @@ -74,19 +74,19 @@ describe('HawkularDatasource annotations', () => { to: 30 }, annotation: { - query: "my.timeline", - name: "Timeline", - type: "strings" + query: 'my.timeline', + name: 'Timeline', + type: 'strings' } }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/strings/raw/query'); + expectRequest(request, 'POST', 'strings/raw/query'); return ctx.$q.when({ status: 200, data: [{ - id: "my.timeline", + id: 'my.timeline', data: [{ timestamp: 13, value: 'start', @@ -108,17 +108,17 @@ describe('HawkularDatasource annotations', () => { ctx.ds.annotationQuery(options).then(result => { expect(result).to.have.length(2); - expect(result[0].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); + expect(result[0].annotation).to.deep.equal({ query: 'my.timeline', name: 'Timeline', type: 'strings' }); expect(result[0].time).to.equal(13); - expect(result[0].title).to.equal("Timeline"); - expect(result[0].tags).to.equal("myItem start"); - expect(result[0].text).to.equal("start"); + expect(result[0].title).to.equal('Timeline'); + expect(result[0].tags).to.equal('myItem start'); + expect(result[0].text).to.equal('start'); - expect(result[1].annotation).to.deep.equal({ query: "my.timeline", name: "Timeline", type: "strings" }); + expect(result[1].annotation).to.deep.equal({ query: 'my.timeline', name: 'Timeline', type: 'strings' }); expect(result[1].time).to.equal(19); - expect(result[1].title).to.equal("Timeline"); - expect(result[1].tags).to.equal("myItem stop"); - expect(result[1].text).to.equal("stop"); + expect(result[1].title).to.equal('Timeline'); + expect(result[1].tags).to.equal('myItem stop'); + expect(result[1].text).to.equal('stop'); }).then(v => done(), err => done(err)); }); @@ -129,9 +129,9 @@ describe('HawkularDatasource annotations', () => { to: 30 }, annotation: { - query: "$who.timeline", - name: "Timeline", - type: "strings" + query: '$who.timeline', + name: 'Timeline', + type: 'strings' } }; @@ -140,23 +140,23 @@ describe('HawkularDatasource annotations', () => { }]; ctx.templateSrv.replace = (target, vars) => { expect(target).to.equal('$who'); - return "{your,my}"; + return '{your,my}'; }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/strings/raw/query'); + expectRequest(request, 'POST', 'strings/raw/query'); expect(request.data.ids).to.deep.equal(['your.timeline', 'my.timeline']); return ctx.$q.when({ status: 200, data: [{ - id: "your.timeline", + id: 'your.timeline', data: [{ timestamp: 15, value: 'start' }] },{ - id: "my.timeline", + id: 'my.timeline', data: [{ timestamp: 13, value: 'start' @@ -167,17 +167,65 @@ describe('HawkularDatasource annotations', () => { ctx.ds.annotationQuery(options).then(result => { expect(result).to.have.length(2); - expect(result[0].annotation).to.deep.equal({ query: "$who.timeline", name: "Timeline", type: "strings" }); + expect(result[0].annotation).to.deep.equal({ query: '$who.timeline', name: 'Timeline', type: 'strings' }); expect(result[0].time).to.equal(15); - expect(result[0].title).to.equal("Timeline"); + expect(result[0].title).to.equal('Timeline'); expect(result[0].tags).to.equal('your.timeline'); - expect(result[0].text).to.equal("start"); + expect(result[0].text).to.equal('start'); - expect(result[1].annotation).to.deep.equal({ query: "$who.timeline", name: "Timeline", type: "strings" }); + expect(result[1].annotation).to.deep.equal({ query: '$who.timeline', name: 'Timeline', type: 'strings' }); expect(result[1].time).to.equal(13); - expect(result[1].title).to.equal("Timeline"); + expect(result[1].title).to.equal('Timeline'); expect(result[1].tags).to.equal('my.timeline'); - expect(result[1].text).to.equal("start"); + expect(result[1].text).to.equal('start'); + }).then(v => done(), err => done(err)); + }); + + it('should query annotations from avails', done => { + + let options = { + range: { + from: 15, + to: 30 + }, + annotation: { + query: 'my.avail', + name: 'Avail', + type: 'availability' + } + }; + + ctx.backendSrv.datasourceRequest = request => { + expectRequest(request, 'POST', 'availability/raw/query'); + + return ctx.$q.when({ + status: 200, + data: [{ + id: 'my.avail', + data: [{ + timestamp: 13, + value: 'up' + }, { + timestamp: 19, + value: 'down' + }] + }] + }); + }; + + ctx.ds.annotationQuery(options).then(result => { + expect(result).to.have.length(2); + expect(result[0].annotation).to.deep.equal({ query: 'my.avail', name: 'Avail', type: 'availability' }); + expect(result[0].time).to.equal(13); + expect(result[0].title).to.equal('Avail'); + expect(result[0].tags).to.be.undefined; + expect(result[0].text).to.equal('up'); + + expect(result[1].annotation).to.deep.equal({ query: 'my.avail', name: 'Avail', type: 'availability' }); + expect(result[1].time).to.equal(19); + expect(result[1].title).to.equal('Avail'); + expect(result[1].tags).to.be.undefined; + expect(result[1].text).to.equal('down'); }).then(v => done(), err => done(err)); }); }); diff --git a/spec/datasource-singlestat_spec.js b/spec/datasource-downsamples_spec.js similarity index 78% rename from spec/datasource-singlestat_spec.js rename to spec/datasource-downsamples_spec.js index 5a7016d..ffb3c95 100644 --- a/spec/datasource-singlestat_spec.js +++ b/spec/datasource-downsamples_spec.js @@ -1,5 +1,5 @@ -import {Datasource} from "../module"; -import Q from "q"; +import {Datasource} from '../module'; +import Q from 'q'; import {getSettings, expectRequest} from './test-util'; describe('HawkularDatasource for downsamples', () => { @@ -35,11 +35,11 @@ describe('HawkularDatasource for downsamples', () => { }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/gauges/stats/query'); + expectRequest(request, 'POST', 'gauges/stats/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, - tags: "type:memory", + tags: 'type:memory', buckets: 1, stacked: true }); @@ -79,11 +79,11 @@ describe('HawkularDatasource for downsamples', () => { }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/gauges/stats/query'); + expectRequest(request, 'POST', 'gauges/stats/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, - tags: "type:memory", + tags: 'type:memory', buckets: 1, stacked: false }); @@ -123,20 +123,20 @@ describe('HawkularDatasource for downsamples', () => { }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/gauges/raw/query'); + expectRequest(request, 'POST', 'gauges/raw/query'); expect(request.data.limit).to.equal(1); - expect(request.data.tags).to.equal("type:memory"); + expect(request.data.tags).to.equal('type:memory'); return ctx.$q.when({ status: 200, data: [{ - id: "myhost.metric.memory.1", + id: 'myhost.metric.memory.1', data: [{ timestamp: 18, value: 21 }] },{ - id: "myhost.metric.memory.2", + id: 'myhost.metric.memory.2', data: [{ timestamp: 19, value: 25 @@ -168,20 +168,20 @@ describe('HawkularDatasource for downsamples', () => { }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/gauges/raw/query'); + expectRequest(request, 'POST', 'gauges/raw/query'); expect(request.data.limit).to.equal(1); - expect(request.data.tags).to.equal("type:memory"); + expect(request.data.tags).to.equal('type:memory'); return ctx.$q.when({ status: 200, data: [{ - id: "myhost.metric.memory.1", + id: 'myhost.metric.memory.1', data: [{ timestamp: 18, value: 21 }] },{ - id: "myhost.metric.memory.2", + id: 'myhost.metric.memory.2', data: [{ timestamp: 19, value: 25 @@ -213,12 +213,12 @@ describe('HawkularDatasource for downsamples', () => { }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/gauges/stats/query'); + expectRequest(request, 'POST', 'gauges/stats/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, - tags: "type:memory", - percentiles: "90", + tags: 'type:memory', + percentiles: '90', buckets: 60, stacked: true }); @@ -231,23 +231,23 @@ describe('HawkularDatasource for downsamples', () => { min: 15, max: 25, avg: 20.25, - percentiles: [{"value":23.1,"originalQuantile":"90","quantile":90.0}] + percentiles: [{'value':23.1,'originalQuantile':'90','quantile':90.0}] }, { start: 25, end: 30, min: 18, max: 28, avg: 23.25, - percentiles: [{"value":26.1,"originalQuantile":"90","quantile":90.0}] + percentiles: [{'value':26.1,'originalQuantile':'90','quantile':90.0}] }] }); }; ctx.ds.query(options).then(result => { expect(result.data).to.have.length(2); - expect(result.data[1].target).to.equal("avg"); + expect(result.data[1].target).to.equal('avg'); expect(result.data[1].datapoints).to.deep.equal([[20.25, 20], [23.25, 25]]); - expect(result.data[0].target).to.equal("90 %ile"); + expect(result.data[0].target).to.equal('90 %ile'); expect(result.data[0].datapoints).to.deep.equal([[23.1, 20], [26.1, 25]]); }).then(v => done(), err => done(err)); }); @@ -269,50 +269,50 @@ describe('HawkularDatasource for downsamples', () => { }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/metrics/stats/query'); + expectRequest(request, 'POST', 'metrics/stats/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, - tags: "type:memory", - percentiles: "95", + tags: 'type:memory', + percentiles: '95', buckets: 60, - types: ["gauge"] + types: ['gauge'] }); return ctx.$q.when({ status: 200, - data: {"gauge": - { "gauge_1": + data: {'gauge': + { 'gauge_1': [{ start: 20, end: 25, min: 15, max: 25, avg: 20.25, - percentiles: [{"value":23.1,"originalQuantile":"95","quantile":95.0}] + percentiles: [{'value':23.1,'originalQuantile':'95','quantile':95.0}] }, { start: 25, end: 30, min: 18, max: 28, avg: 23.25, - percentiles: [{"value":26.1,"originalQuantile":"95","quantile":95.0}] + percentiles: [{'value':26.1,'originalQuantile':'95','quantile':95.0}] }], - "gauge_2": + 'gauge_2': [{ start: 20, end: 25, min: 20, max: 30, avg: 25.25, - percentiles: [{"value":28.1,"originalQuantile":"95","quantile":95.0}] + percentiles: [{'value':28.1,'originalQuantile':'95','quantile':95.0}] }, { start: 25, end: 30, min: 23, max: 33, avg: 28.25, - percentiles: [{"value":31.1,"originalQuantile":"95","quantile":95.0}] + percentiles: [{'value':31.1,'originalQuantile':'95','quantile':95.0}] }] } } @@ -321,13 +321,13 @@ describe('HawkularDatasource for downsamples', () => { ctx.ds.query(options).then(result => { expect(result.data).to.have.length(4); - expect(result.data[1].target).to.equal("gauge_1 [min]"); + expect(result.data[1].target).to.equal('gauge_1 [min]'); expect(result.data[1].datapoints).to.deep.equal([[15, 20], [18, 25]]); - expect(result.data[0].target).to.equal("gauge_1 [95 %ile]"); + expect(result.data[0].target).to.equal('gauge_1 [95 %ile]'); expect(result.data[0].datapoints).to.deep.equal([[23.1, 20], [26.1, 25]]); - expect(result.data[3].target).to.equal("gauge_2 [min]"); + expect(result.data[3].target).to.equal('gauge_2 [min]'); expect(result.data[3].datapoints).to.deep.equal([[20, 20], [23, 25]]); - expect(result.data[2].target).to.equal("gauge_2 [95 %ile]"); + expect(result.data[2].target).to.equal('gauge_2 [95 %ile]'); expect(result.data[2].datapoints).to.deep.equal([[28.1, 20], [31.1, 25]]); }).then(v => done(), err => done(err)); }); diff --git a/spec/datasource-tagsQL_spec.js b/spec/datasource-tagsQL_spec.js index 31381bc..dc5194c 100644 --- a/spec/datasource-tagsQL_spec.js +++ b/spec/datasource-tagsQL_spec.js @@ -1,5 +1,5 @@ -import {Datasource} from "../module"; -import Q from "q"; +import {Datasource} from '../module'; +import Q from 'q'; import {getSettings, expectRequest} from './test-util'; describe('HawkularDatasource with tagsQL', () => { @@ -38,7 +38,7 @@ describe('HawkularDatasource with tagsQL', () => { }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/gauges/raw/query'); + expectRequest(request, 'POST', 'gauges/raw/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, @@ -72,7 +72,7 @@ describe('HawkularDatasource with tagsQL', () => { }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/gauges/stats/query'); + expectRequest(request, 'POST', 'gauges/stats/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, @@ -107,7 +107,7 @@ describe('HawkularDatasource with tagsQL', () => { }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/gauges/raw/query'); + expectRequest(request, 'POST', 'gauges/raw/query'); expect(request.data.limit).to.equal(1); expect(request.data.tags).to.equal('type=memory'); diff --git a/spec/datasource-tenant-per-query_spec.js b/spec/datasource-tenant-per-query_spec.js new file mode 100644 index 0000000..4ddaef0 --- /dev/null +++ b/spec/datasource-tenant-per-query_spec.js @@ -0,0 +1,233 @@ +import {Datasource} from '../module'; +import Q from 'q'; +import {getSettings, expectRequestWithTenant} from './test-util'; + +describe('HawkularDatasource tenant per query', () => { + let ctx = {}; + const instanceSettings = getSettings(); + // Remove global tenant + delete instanceSettings.jsonData.tenant; + instanceSettings.jsonData.isTenantPerQuery = true; + + beforeEach(() => { + ctx.$q = Q; + ctx.backendSrv = {}; + ctx.backendSrv.datasourceRequest = request => { + return ctx.$q.when({data: {'Implementation-Version': '0.22.0'}}) + }; + ctx.templateSrv = { + replace: (target, vars) => target + }; + ctx.ds = new Datasource(instanceSettings, ctx.$q, ctx.backendSrv, ctx.templateSrv); + }); + + it('should query raw data with ad-hoc tenant', done => { + + let options = { + range: { + from: 15, + to: 30 + }, + targets: [{ + id: 'memory', + type: 'gauge', + rate: false, + tenant: 'ad-hoc' + }] + }; + + ctx.backendSrv.datasourceRequest = request => { + expectRequestWithTenant(request, 'POST', 'gauges/raw/query', 'ad-hoc'); + expect(request.data).to.deep.equal({ + start: options.range.from, + end: options.range.to, + ids: ['memory'], + order: 'ASC' + }); + + return ctx.$q.when({ + status: 200, + data: [{ + id: 'memory', + data: [{ + timestamp: 13, + value: 15 + }, { + timestamp: 19, + value: 21 + }] + }] + }); + }; + + ctx.ds.query(options).then(result => { + expect(result.data).to.have.length(1); + expect(result.data[0].target).to.equal('memory'); + expect(result.data[0].datapoints).to.deep.equal([[15, 13], [21, 19]]); + }).then(v => done(), err => done(err)); + }); + + it('should query annotations with ad-hoc tenant', done => { + + let options = { + range: { + from: 15, + to: 30 + }, + annotation: { + query: 'my.timeline', + name: 'Timeline', + type: 'strings', + tenant: 'ad-hoc' + } + }; + + ctx.backendSrv.datasourceRequest = request => { + expectRequestWithTenant(request, 'POST', 'strings/raw/query', 'ad-hoc'); + + return ctx.$q.when({ + status: 200, + data: [{ + id: 'my.timeline', + data: [{ + timestamp: 13, + value: 'start' + }, { + timestamp: 19, + value: 'stop' + }] + }] + }); + }; + + ctx.ds.annotationQuery(options).then(result => { + expect(result).to.have.length(2); + expect(result[0].annotation).to.deep.equal(options.annotation); + expect(result[0].time).to.equal(13); + expect(result[0].title).to.equal('Timeline'); + expect(result[0].tags).to.be.undefined; + expect(result[0].text).to.equal('start'); + + expect(result[1].annotation).to.deep.equal(options.annotation); + expect(result[1].time).to.equal(19); + expect(result[1].title).to.equal('Timeline'); + expect(result[1].tags).to.be.undefined; + expect(result[1].text).to.equal('stop'); + }).then(v => done(), err => done(err)); + }); + + it('should query stats with ad-hoc tenant', done => { + let options = { + range: { + from: 20, + to: 30 + }, + targets: [{ + seriesAggFn: 'none', + stats: ['min'], + tags: [{name: 'type', value: 'memory'}], + type: 'gauge', + rate: false, + raw: false, + tenant: 'ad-hoc' + }] + }; + + ctx.backendSrv.datasourceRequest = request => { + expectRequestWithTenant(request, 'POST', 'metrics/stats/query', 'ad-hoc'); + expect(request.data).to.deep.equal({ + start: options.range.from, + end: options.range.to, + tags: 'type:memory', + buckets: 60, + types: ['gauge'] + }); + + return ctx.$q.when({ + status: 200, + data: {'gauge': + { 'gauge_1': + [{ + start: 20, + end: 25, + min: 15, + max: 25, + avg: 20.25 + }] + } + } + }); + }; + + ctx.ds.query(options).then(result => { + expect(result.data).to.have.length(1); + expect(result.data[0].target).to.equal('gauge_1 [min]'); + expect(result.data[0].datapoints).to.deep.equal([[15, 20]]); + }).then(v => done(), err => done(err)); + }); + + it('should suggest metrics with ad-hoc tenant', done => { + ctx.backendSrv.datasourceRequest = request => { + expectRequestWithTenant(request, 'GET', 'metrics?type=gauge&tags=host=cartago', 'ad-hoc'); + return ctx.$q.when({ + status: 200, + data: [{ + id: 'gauge_1', + tags: { + 'host': 'cartago' + }, + dataRetention: 7, + type: 'gauge' + },{ + id: 'gauge_2', + tags: { + 'host': 'cartago' + }, + dataRetention: 7, + type: 'gauge' + }] + }); + }; + + ctx.ds.suggestMetrics({type: 'gauge', tagsQL: 'host=cartago', tenant: 'ad-hoc'}).then(result => { + expect(result).to.have.length(2); + expect(result[0]).to.deep.equal({ text: 'gauge_1', value: 'gauge_1' }); + expect(result[1]).to.deep.equal({ text: 'gauge_2', value: 'gauge_2' }); + }).then(v => done(), err => done(err)); + }); + + it('should get tags suggestions with ad-hoc tenant', done => { + ctx.backendSrv.datasourceRequest = request => { + expectRequestWithTenant(request, 'GET', 'gauges/tags/host:*', 'ad-hoc'); + + return ctx.$q.when({ + status: 200, + data: { + 'host': ['cartago', 'rio'] + } + }); + }; + + ctx.ds.suggestTags({type: 'gauge', tenant: 'ad-hoc'}, 'host').then(result => { + expect(result).to.have.length(2); + expect(result[0]).to.deep.equal({ text: 'cartago', value: 'cartago' }); + expect(result[1]).to.deep.equal({ text: 'rio', value: 'rio' }); + }).then(v => done(), err => done(err)); + }); + + it('should get tag keys suggestions with ad-hoc tenant', done => { + ctx.backendSrv.datasourceRequest = request => { + expectRequestWithTenant(request, 'GET', 'metrics/tags', 'ad-hoc'); + return ctx.$q.when({ + status: 200, + data: ['host', 'app'] + }); + }; + + ctx.ds.suggestTagKeys({tenant: 'ad-hoc'}).then(result => { + expect(result).to.have.length(2); + expect(result[0]).to.deep.equal({ text: 'host', value: 'host' }); + expect(result[1]).to.deep.equal({ text: 'app', value: 'app' }); + }).then(v => done(), err => done(err)); + }); +}); diff --git a/spec/datasource_spec.js b/spec/datasource_spec.js index 7ab1a1e..c9eff8f 100644 --- a/spec/datasource_spec.js +++ b/spec/datasource_spec.js @@ -1,5 +1,5 @@ -import {Datasource} from "../module"; -import Q from "q"; +import {Datasource} from '../module'; +import Q from 'q'; import {getSettings, expectRequest} from './test-util'; describe('HawkularDatasource', () => { @@ -48,7 +48,7 @@ describe('HawkularDatasource', () => { if (first) { first = false; id = 'memory'; - expectRequest(request, 'POST', '/hawkular/metrics/gauges/raw/query'); + expectRequest(request, 'POST', 'gauges/raw/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, @@ -57,7 +57,7 @@ describe('HawkularDatasource', () => { }); } else { id = 'packets'; - expectRequest(request, 'POST', '/hawkular/metrics/counters/rate/query'); + expectRequest(request, 'POST', 'counters/rate/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, @@ -108,16 +108,16 @@ describe('HawkularDatasource', () => { }]; ctx.templateSrv.replace = (target, vars) => { expect(target).to.equal('$app'); - return "{app_1,app_2}"; + return '{app_1,app_2}'; }; ctx.backendSrv.datasourceRequest = request => { - expect(request.url).to.have.string("/gauges/raw/query"); + expect(request.url).to.have.string('gauges/raw/query'); expect(request.data.ids).to.include.members(['app_1/memory', 'app_2/memory']); return ctx.$q.when({ status: 200, data: [{ - id: "app_1/memory", + id: 'app_1/memory', data: [{ timestamp: 13, value: 15 @@ -126,7 +126,7 @@ describe('HawkularDatasource', () => { value: 21 }] },{ - id: "app_2/memory", + id: 'app_2/memory', data: [{ timestamp: 13, value: 28 @@ -147,7 +147,6 @@ describe('HawkularDatasource', () => { }); it('should query by tags', done => { - let options = { range: { from: 15, @@ -164,18 +163,18 @@ describe('HawkularDatasource', () => { }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/gauges/raw/query'); + expectRequest(request, 'POST', 'gauges/raw/query'); expect(request.data).to.deep.equal({ start: options.range.from, end: options.range.to, - tags: "type:memory,host:myhost", + tags: 'type:memory,host:myhost', order: 'ASC' }); return ctx.$q.when({ status: 200, data: [{ - id: "myhost.metric.memory.1", + id: 'myhost.metric.memory.1', data: [{ timestamp: 13, value: 15 @@ -184,7 +183,7 @@ describe('HawkularDatasource', () => { value: 21 }] },{ - id: "myhost.metric.memory.2", + id: 'myhost.metric.memory.2', data: [{ timestamp: 13, value: 20 @@ -205,7 +204,6 @@ describe('HawkularDatasource', () => { }); it('should query availability', done => { - let options = { range: { from: 15, @@ -218,12 +216,11 @@ describe('HawkularDatasource', () => { }; ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'POST', '/hawkular/metrics/availability/raw/query'); - + expectRequest(request, 'POST', 'availability/raw/query'); return ctx.$q.when({ status: 200, data: [{ - id: "myapp/health", + id: 'myapp/health', data: [{ timestamp: 13, value: 'up' @@ -242,9 +239,39 @@ describe('HawkularDatasource', () => { }).then(v => done(), err => done(err)); }); + it('should suggest metrics', done => { + ctx.backendSrv.datasourceRequest = request => { + expectRequest(request, 'GET', 'metrics?type=gauge&tags=host=cartago'); + return ctx.$q.when({ + status: 200, + data: [{ + id: 'gauge_1', + tags: { + 'host': 'cartago' + }, + dataRetention: 7, + type: 'gauge' + },{ + id: 'gauge_2', + tags: { + 'host': 'cartago' + }, + dataRetention: 7, + type: 'gauge' + }] + }); + }; + + ctx.ds.suggestMetrics({type: 'gauge', tagsQL: 'host=cartago'}).then(result => { + expect(result).to.have.length(2); + expect(result[0]).to.deep.equal({ text: 'gauge_1', value: 'gauge_1' }); + expect(result[1]).to.deep.equal({ text: 'gauge_2', value: 'gauge_2' }); + }).then(v => done(), err => done(err)); + }); + it('should get tags suggestions', done => { ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'GET', '/hawkular/metrics/gauges/tags/host:*'); + expectRequest(request, 'GET', 'gauges/tags/host:*'); return ctx.$q.when({ status: 200, @@ -263,7 +290,7 @@ describe('HawkularDatasource', () => { it('should get no suggestions on unknown tag', done => { ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'GET', '/hawkular/metrics/gauges/tags/host:*'); + expectRequest(request, 'GET', 'gauges/tags/host:*'); return ctx.$q.when({ status: 204, data: {} @@ -276,7 +303,7 @@ describe('HawkularDatasource', () => { it('should get tag keys suggestions', done => { ctx.backendSrv.datasourceRequest = request => { - expectRequest(request, 'GET', '/hawkular/metrics/metrics/tags'); + expectRequest(request, 'GET', 'metrics/tags'); return ctx.$q.when({ status: 200, data: ['host', 'app'] diff --git a/spec/test-util.js b/spec/test-util.js index 1582766..c48c0ac 100644 --- a/spec/test-util.js +++ b/spec/test-util.js @@ -3,7 +3,7 @@ const hHostname = 'test.com'; const hPort = '876'; const hPath = 'hawkular/metrics'; const instanceSettings = { - url: hProtocol + '://' + hHostname + ':' + hPort + '/' + hPath, + url: `${hProtocol}://${hHostname}:${hPort}/${hPath}`, jsonData: { tenant: 'test-tenant' } @@ -12,14 +12,13 @@ const instanceSettings = { export function expectRequest(request, verb, path) { expect(request.method).to.equal(verb); expect(request.headers).to.have.property('Hawkular-Tenant', instanceSettings.jsonData.tenant); + expect(request.url).to.equal(`${instanceSettings.url}/${path}`); +} - const parser = document.createElement('a'); - parser.href = request.url; - - expect(parser).to.have.property('protocol', hProtocol + ':'); - expect(parser).to.have.property('hostname', hHostname); - expect(parser).to.have.property('port', hPort); - expect(parser).to.have.property('pathname', path); +export function expectRequestWithTenant(request, verb, path, tenant) { + expect(request.method).to.equal(verb); + expect(request.headers).to.have.property('Hawkular-Tenant', tenant); + expect(request.url).to.equal(`${instanceSettings.url}/${path}`); } export function getSettings() { From 4f67105030ee30ddc667913157f849d0958c2607 Mon Sep 17 00:00:00 2001 From: Joel Takvorian Date: Tue, 25 Jul 2017 11:37:03 +0200 Subject: [PATCH 4/5] Code style: single quotes and string interpolation --- spec/tags-kvpairs_spec.js | 10 +++++----- spec/variables_spec.js | 3 +-- src/datasource.js | 24 ++++++++++++------------ src/queryProcessor.js | 22 ++++++---------------- src/query_ctrl.js | 4 ++-- src/tagsKVPairsController.js | 4 ++-- src/tagsQLController.js | 26 +++++++++++++------------- src/variablesHelper.js | 8 ++++---- 8 files changed, 45 insertions(+), 56 deletions(-) diff --git a/spec/tags-kvpairs_spec.js b/spec/tags-kvpairs_spec.js index 057513a..87f490c 100644 --- a/spec/tags-kvpairs_spec.js +++ b/spec/tags-kvpairs_spec.js @@ -1,5 +1,5 @@ import {segmentsToModel, modelToSegments} from '../tagsKVPairsController'; -import Q from "q"; +import Q from 'q'; describe('TagsKVPairs', () => { @@ -30,8 +30,8 @@ describe('TagsKVPairs', () => { ]; const result = segmentsToModel(segments); expect(result).to.deep.equal([ - { name: "hostname", value: "*" }, - { name: "pod", value: "unknown" } + { name: 'hostname', value: '*' }, + { name: 'pod', value: 'unknown' } ]); done(); }); @@ -50,8 +50,8 @@ describe('TagsKVPairs', () => { it('should convert model to segments', done => { const result = modelToSegments([ - { name: "hostname", value: "*" }, - { name: "pod", value: "unknown" } + { name: 'hostname', value: '*' }, + { name: 'pod', value: 'unknown' } ], segmentFactory); expect(result).to.deep.equal([ { type: 'key', value: 'hostname' }, diff --git a/spec/variables_spec.js b/spec/variables_spec.js index 482aee3..2a5773e 100644 --- a/spec/variables_spec.js +++ b/spec/variables_spec.js @@ -18,8 +18,7 @@ describe('Variables', () => { // Quick & simple emulation of the real templateSrv.replace let result = target; if (!fmt) { - fmt = values => (typeof values == "string") ? values - : '{' + values.join(',') + '}'; + fmt = values => (typeof values == "string") ? values : `{${values.join(',')}}`; } ctx.templateSrv.variables.forEach(v => { const values = scopedVars[v.name] ? scopedVars[v.name].value : v.values; diff --git a/src/datasource.js b/src/datasource.js index 839ddde..d246a06 100644 --- a/src/datasource.js +++ b/src/datasource.js @@ -1,4 +1,4 @@ -import _ from "lodash"; +import _ from 'lodash'; import {VariablesHelper} from './variablesHelper'; import {Capabilities} from './capabilities'; import {QueryProcessor} from './queryProcessor'; @@ -101,7 +101,7 @@ export class HawkularDatasource { if (response.status === 200 || response.status === 204) { return { status: 'success', message: 'Data source is working', title: 'Success' }; } else { - return { status: 'error', message: 'Connection failed (' + response.status + ')', title: 'Error' }; + return { status: 'error', message: `Connection failed (${response.status})`, title: 'Error' }; } }); } @@ -150,12 +150,12 @@ export class HawkularDatasource { }); } - suggestQueries(target) { + suggestMetrics(target) { let url = this.url + '/metrics?type=' + target.type; if (target.tagsQL && target.tagsQL.length > 0) { - url += "&tags=" + this.variablesHelper.resolveForQL(target.tagsQL, {}); + url += '&tags=' + this.variablesHelper.resolveForQL(target.tagsQL, {}); } else if (target.tags && target.tags.length > 0) { - url += "&tags=" + tagsModelToString(target.tags, this.variablesHelper, {}); + url += '&tags=' + tagsModelToString(target.tags, this.variablesHelper, {}); } return this.backendSrv.datasourceRequest({ url: url, @@ -176,7 +176,7 @@ export class HawkularDatasource { return this.q.when([]); } return this.backendSrv.datasourceRequest({ - url: this.url + '/' + this.typeResources[target.type] + '/tags/' + key + ':*', + url: `${this.url}/${this.typeResources[target.type]}/tags/${key}:*`, method: 'GET', headers: this.getHeaders(target.tenant) }).then(result => result.data.hasOwnProperty(key) ? result.data[key] : []) @@ -195,19 +195,19 @@ export class HawkularDatasource { } metricFindQuery(query) { - let params = ""; + let params = ''; if (query !== undefined) { - if (query.substr(0, 5) === "tags/") { + if (query.substr(0, 5) === 'tags/') { return this.findTags(query.substr(5).trim()); } if (query.charAt(0) === '?') { params = query; } else { - params = "?" + query; + params = '?' + query; } } return this.runWithResolvedVariables(params, p => this.backendSrv.datasourceRequest({ - url: this.url + '/metrics' + p, + url: `${this.url}/metrics${p}`, method: 'GET', headers: this.getHeaders() }).then(result => { @@ -219,7 +219,7 @@ export class HawkularDatasource { findTags(pattern) { return this.runWithResolvedVariables(pattern, p => this.backendSrv.datasourceRequest({ - url: this.url + '/metrics/tags/' + p, + url: `${this.url}/metrics/tags/${p}`, method: 'GET', headers: this.getHeaders() }).then(result => { @@ -250,7 +250,7 @@ export class HawkularDatasource { method: 'GET', headers: {'Content-Type': 'application/json'} }).then(response => response.data['Implementation-Version']) - .catch(response => "Unknown"); + .catch(response => 'Unknown'); } getCapabilities() { diff --git a/src/queryProcessor.js b/src/queryProcessor.js index 2b9012d..017f818 100644 --- a/src/queryProcessor.js +++ b/src/queryProcessor.js @@ -76,12 +76,7 @@ export class QueryProcessor { } rawQuery(target, postData) { - const uri = [ - this.typeResources[target.type], // gauges or counters - target.rate ? 'rate' : 'raw', // raw or rate - 'query' - ]; - const url = this.url + '/' + uri.join('/'); + const url = `${this.url}/${this.typeResources[target.type]}/${target.rate ? 'rate' : 'raw'}/query`; return this.backendSrv.datasourceRequest({ url: url, @@ -97,7 +92,7 @@ export class QueryProcessor { this.typeResources[target.type], // gauges, counters or availability encodeURIComponent(metric).replace('+', '%20'), // metric name 'data']; - const url = this.url + '/' + uri.join('/'); + const url = `${this.url}/${this.typeResources[target.type]}/${encodeURIComponent(metric).replace('+', '%20')}/data`; return this.backendSrv.datasourceRequest({ url: url, @@ -277,7 +272,7 @@ export class QueryProcessor { } else if (target.timeAggFn == 'max') { fnBucket = bucket => bucket.max; } // no else case. "live" case was handled before - const url = this.url + '/' + this.typeResources[target.type] + '/stats/query'; + const url = `${this.url}/${this.typeResources[target.type]}/stats/query`; delete postData.order; postData.buckets = 1; postData.stacked = target.seriesAggFn === 'sum'; @@ -293,19 +288,14 @@ export class QueryProcessor { return data.map(bucket => { return { refId: target.refId, - target: "Aggregate", + target: 'Aggregate', datapoints: [[fnBucket(bucket), bucket.start]] }; }); } singleStatLiveQuery(target, postData) { - const uri = [ - this.typeResources[target.type], // gauges, counters or availability - target.rate ? 'rate' : 'raw', // raw or rate - 'query' - ]; - const url = this.url + '/' + uri.join('/'); + const url = `${this.url}/${this.typeResources[target.type]}/${target.rate ? 'rate' : 'raw'}/query`; // Set start to now - 5m postData.start = Date.now() - 300000; return this.backendSrv.datasourceRequest({ @@ -333,7 +323,7 @@ export class QueryProcessor { } return [{ refId: target.refId, - target: "Aggregate", + target: 'Aggregate', datapoints: datapoints }]; } diff --git a/src/query_ctrl.js b/src/query_ctrl.js index 24f2737..236980e 100644 --- a/src/query_ctrl.js +++ b/src/query_ctrl.js @@ -14,7 +14,7 @@ export class HawkularDatasourceQueryCtrl extends QueryCtrl { this.$q = $q; this.target = this.datasource.sanitizeTarget(this.target); - this.caps = new Capabilities(""); + this.caps = new Capabilities(''); this.datasource.getCapabilities().then(caps => { this.caps = caps; if (caps.TAGS_QUERY_LANGUAGE) { @@ -99,7 +99,7 @@ export class HawkularDatasourceQueryCtrl extends QueryCtrl { } getMetricOptions() { - return this.datasource.suggestQueries(this.target) + return this.datasource.suggestMetrics(this.target) .then(metrics => [{value: '-- none --', text: '-- none --'}].concat(metrics)) .then(this.uiSegmentSrv.transformToSegments(false)); // Options have to be transformed by uiSegmentSrv to be usable by metric-segment-model directive diff --git a/src/tagsKVPairsController.js b/src/tagsKVPairsController.js index 01770d3..4b4e788 100644 --- a/src/tagsKVPairsController.js +++ b/src/tagsKVPairsController.js @@ -1,4 +1,4 @@ -import _ from "lodash"; +import _ from 'lodash'; export class TagsKVPairsController { @@ -101,6 +101,6 @@ export function modelToString(tags, variablesHelper, options) { } else { value = tag.value; } - return tag.name + ':' + value; + return `${tag.name}:${value}`; }).join(','); } diff --git a/src/tagsQLController.js b/src/tagsQLController.js index b7e6f4a..c202990 100644 --- a/src/tagsQLController.js +++ b/src/tagsQLController.js @@ -197,9 +197,9 @@ export function convertFromKVPairs(kvTags) { } if (tag.value.charAt(0) === '$') { // it's a variable - return tag.name + " IN [" + tag.value + "]"; + return `${tag.name} IN [${tag.value}]`; } - return tag.name + "='" + tag.value + "'"; + return `${tag.name}='${tag.value}'`; }).join(' AND '); } @@ -207,7 +207,7 @@ export function convertFromKVPairs(kvTags) { // Input segment values: ["fruit", "is in", "pear", "apple", "peach", "", "AND", "color", "=", "green", ""] // Output string: "fruit IN [pear, apple, peach] AND color=green" export function segmentsToString(segments) { - let strTags = ""; + let strTags = ''; let i = 0; while (i < segments.length) { if (segments[i].type === 'plus-button') { @@ -216,7 +216,7 @@ export function segmentsToString(segments) { } if (i != 0) { // AND/OR - strTags += " " + segments[i++].value + " "; + strTags += ' ' + segments[i++].value + ' '; } // Tag name const tagName = segments[i++].value; @@ -231,22 +231,22 @@ export function segmentsToString(segments) { } else if (op === OPERATOR_IN) { const v = valuesToString(segments, i); i = v.i; - strTags += tagName + ' IN [' + v.values + ']'; + strTags += `${tagName} IN [${v.values}]`; } else if (op === OPERATOR_NOTIN) { const v = valuesToString(segments, i); i = v.i; - strTags += tagName + ' NOT IN [' + v.values + ']'; + strTags += `${tagName} NOT IN [${v.values}]`; } } return strTags; } function valuesToString(segments, i) { - let values = ""; - let sep = ""; + let values = ''; + let sep = ''; while (i < segments.length && segments[i].type === 'value') { values += sep + valueToString(segments[i++].value); - sep = ","; + sep = ','; } return { values: values, @@ -260,7 +260,7 @@ function valueToString(value) { // Variable, simple literal or already single-quoted => keep as is return value; } - return "'" + value + "'"; + return `'${value}'`; } // Example: @@ -327,7 +327,7 @@ function readLogicalOp(strTags, cursor) { if (strTags.substr(cursor, 3).toUpperCase() === 'AND') { return { cursor: cursor + 3, value: OPERATOR_AND }; } - throw "Cannot parse tags string: logical operator expected near '" + strTags.substr(cursor, 15) + "'"; + throw `Cannot parse tags string: logical operator expected near '${strTags.substr(cursor, 15)}'`; } function readWord(strTags, cursor) { @@ -357,7 +357,7 @@ function readRelationalOp(strTags, cursor) { if (strTags.substr(cursor, 6).toUpperCase() === 'NOT IN') { return { cursor: cursor + 6, value: OPERATOR_NOTIN }; } - throw "Cannot parse tags string: relational operator expected near '" + strTags.substr(cursor, 15) + "'"; + throw `Cannot parse tags string: relational operator expected near '${strTags.substr(cursor, 15)}'`; } function readEnumeration(strTags, cursor) { @@ -374,7 +374,7 @@ function readEnumeration(strTags, cursor) { if (strTags.charAt(cursor) === ',') { cursor++; } else { - throw "Cannot parse tags string: unexpected token in enumeration near '" + strTags.substr(cursor, 15) + "'"; + throw `Cannot parse tags string: unexpected token in enumeration near '${strTags.substr(cursor, 15)}'`; } } return { cursor: cursor, values: values }; diff --git a/src/variablesHelper.js b/src/variablesHelper.js index 7eb22db..cc76d74 100644 --- a/src/variablesHelper.js +++ b/src/variablesHelper.js @@ -1,4 +1,4 @@ -import _ from "lodash"; +import _ from 'lodash'; export class VariablesHelper { @@ -15,7 +15,7 @@ export class VariablesHelper { if (target.indexOf(name) >= 0) { const values = this.getVarValues(name, options.scopedVars); const newResolved = []; - const regex = new RegExp("\\" + name, "g"); + const regex = new RegExp('\\' + name, 'g'); values.forEach(val => { resolved.forEach(newTarget => { newResolved.push(newTarget.replace(regex, val)); @@ -31,9 +31,9 @@ export class VariablesHelper { resolveForQL(target, options) { return this.templateSrv.replace(target, options.scopedVars, values => { if (_.isArray(values)) { - return values.map(v => "'" + v + "'").join(','); + return values.map(v => `'${v}'`).join(','); } - return "'" + values + "'"; + return `'${values}'`; }); } From 30620b60e8e982c93fdbd97ad54b4067fe7d239f Mon Sep 17 00:00:00 2001 From: Joel Takvorian Date: Wed, 26 Jul 2017 09:44:12 +0200 Subject: [PATCH 5/5] Test datasource on "/" when tenant is unknown Unlike /status this endpoint requires auth in basic-auth mode, though not with openshift (special message is then displayed) --- src/datasource.js | 4 ++-- src/partials/config.html | 4 ++++ src/queryProcessor.js | 5 ----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/datasource.js b/src/datasource.js index d246a06..c658e4b 100644 --- a/src/datasource.js +++ b/src/datasource.js @@ -90,9 +90,9 @@ export class HawkularDatasource { testDatasource() { // If tenants is unknown at this point (when having per-query tenants) - // We do a more basic check to status endpoint + // We do a more basic check to / endpoint, which checks authentication in basic-auth mode but not with token/OpenShift // Else, it's full connectivity with tenant check - const endpoint = this.isTenantPerQuery ? '/status' : '/metrics'; + const endpoint = this.isTenantPerQuery ? '/' : '/metrics'; return this.backendSrv.datasourceRequest({ url: this.url + endpoint, method: 'GET', diff --git a/src/partials/config.html b/src/partials/config.html index bdde330..b8e31c0 100644 --- a/src/partials/config.html +++ b/src/partials/config.html @@ -17,4 +17,8 @@

Hawkular settings

+
+ + With tenant per query enabled, the token authentication cannot be tested now because it likely depends on the tenant. +
diff --git a/src/queryProcessor.js b/src/queryProcessor.js index 017f818..d0e38ef 100644 --- a/src/queryProcessor.js +++ b/src/queryProcessor.js @@ -88,12 +88,7 @@ export class QueryProcessor { rawQueryLegacy(target, range, metricIds) { return this.q.all(metricIds.map(metric => { - const uri = [ - this.typeResources[target.type], // gauges, counters or availability - encodeURIComponent(metric).replace('+', '%20'), // metric name - 'data']; const url = `${this.url}/${this.typeResources[target.type]}/${encodeURIComponent(metric).replace('+', '%20')}/data`; - return this.backendSrv.datasourceRequest({ url: url, params: {