diff --git a/CHANGELOG.md b/CHANGELOG.md index d3532ebe640..aa089b5900b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,9 @@ * **Alerting**: Fix diff and percent_diff reducers [#11563](https://github.com/grafana/grafana/issues/11563), thx [@jessetane](https://github.com/jessetane) * **Units**: Polish złoty currency [#12691](https://github.com/grafana/grafana/pull/12691), thx [@mwegrzynek](https://github.com/mwegrzynek) * **Cloudwatch**: Improved error handling [#12489](https://github.com/grafana/grafana/issues/12489), thx [@mtanda](https://github.com/mtanda) -* **Cloudwatch**: AWS/AppSync metrics and dimensions [#12300](https://github.com/grafana/grafana/issues/12300), thx [@franciscocpg](https://github.com/franciscocpg) +* **Cloudwatch**: AppSync metrics and dimensions [#12300](https://github.com/grafana/grafana/issues/12300), thx [@franciscocpg](https://github.com/franciscocpg) +* **Cloudwatch**: Direct Connect metrics and dimensions [#12762](https://github.com/grafana/grafana/pulls/12762), thx [@mindriot88](https://github.com/mindriot88) +* **Cloudwatch**: Added BurstBalance metric to list of AWS RDS metrics [#12561](https://github.com/grafana/grafana/pulls/12561), thx [@activeshadow](https://github.com/activeshadow) * **Table**: Adjust header contrast for the light theme [#12668](https://github.com/grafana/grafana/issues/12668) * **Elasticsearch**: For alerting/backend, support having index name to the right of pattern in index pattern [#12731](https://github.com/grafana/grafana/issues/12731) * **OAuth**: Fix overriding tls_skip_verify_insecure using environment variable [#12747](https://github.com/grafana/grafana/issues/12747), thx [@jangaraj](https://github.com/jangaraj) diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index 00ad25ab8c2..f2bc79df7ad 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -99,7 +99,7 @@ func GetTestDataRandomWalk(c *m.ReqContext) Response { timeRange := tsdb.NewTimeRange(from, to) request := &tsdb.TsdbQuery{TimeRange: timeRange} - dsInfo := &m.DataSource{Type: "grafana-testdata-datasource"} + dsInfo := &m.DataSource{Type: "testdata"} request.Queries = append(request.Queries, &tsdb.Query{ RefId: "A", IntervalMs: intervalMs, diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index 12c2aba4681..3601b73cbe2 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -46,6 +46,7 @@ func init() { "AWS/CloudFront": {"Requests", "BytesDownloaded", "BytesUploaded", "TotalErrorRate", "4xxErrorRate", "5xxErrorRate"}, "AWS/CloudSearch": {"SuccessfulRequests", "SearchableDocuments", "IndexUtilization", "Partitions"}, "AWS/DMS": {"FreeableMemory", "WriteIOPS", "ReadIOPS", "WriteThroughput", "ReadThroughput", "WriteLatency", "ReadLatency", "SwapUsage", "NetworkTransmitThroughput", "NetworkReceiveThroughput", "FullLoadThroughputBandwidthSource", "FullLoadThroughputBandwidthTarget", "FullLoadThroughputRowsSource", "FullLoadThroughputRowsTarget", "CDCIncomingChanges", "CDCChangesMemorySource", "CDCChangesMemoryTarget", "CDCChangesDiskSource", "CDCChangesDiskTarget", "CDCThroughputBandwidthTarget", "CDCThroughputRowsSource", "CDCThroughputRowsTarget", "CDCLatencySource", "CDCLatencyTarget"}, + "AWS/DX": {"ConnectionState", "ConnectionBpsEgress", "ConnectionBpsIngress", "ConnectionPpsEgress", "ConnectionPpsIngress", "ConnectionCRCErrorCount", "ConnectionLightLevelTx", "ConnectionLightLevelRx"}, "AWS/DynamoDB": {"ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "OnlineIndexConsumedWriteCapacity", "OnlineIndexPercentageProgress", "OnlineIndexThrottleEvents", "ProvisionedReadCapacityUnits", "ProvisionedWriteCapacityUnits", "ReadThrottleEvents", "ReturnedBytes", "ReturnedItemCount", "ReturnedRecordsCount", "SuccessfulRequestLatency", "SystemErrors", "TimeToLiveDeletedItemCount", "ThrottledRequests", "UserErrors", "WriteThrottleEvents"}, "AWS/EBS": {"VolumeReadBytes", "VolumeWriteBytes", "VolumeReadOps", "VolumeWriteOps", "VolumeTotalReadTime", "VolumeTotalWriteTime", "VolumeIdleTime", "VolumeQueueLength", "VolumeThroughputPercentage", "VolumeConsumedReadWriteOps", "BurstBalance"}, "AWS/EC2": {"CPUCreditUsage", "CPUCreditBalance", "CPUUtilization", "DiskReadOps", "DiskWriteOps", "DiskReadBytes", "DiskWriteBytes", "NetworkIn", "NetworkOut", "NetworkPacketsIn", "NetworkPacketsOut", "StatusCheckFailed", "StatusCheckFailed_Instance", "StatusCheckFailed_System"}, @@ -93,7 +94,7 @@ func init() { "AWS/NetworkELB": {"ActiveFlowCount", "ConsumedLCUs", "HealthyHostCount", "NewFlowCount", "ProcessedBytes", "TCP_Client_Reset_Count", "TCP_ELB_Reset_Count", "TCP_Target_Reset_Count", "UnHealthyHostCount"}, "AWS/OpsWorks": {"cpu_idle", "cpu_nice", "cpu_system", "cpu_user", "cpu_waitio", "load_1", "load_5", "load_15", "memory_buffers", "memory_cached", "memory_free", "memory_swap", "memory_total", "memory_used", "procs"}, "AWS/Redshift": {"CPUUtilization", "DatabaseConnections", "HealthStatus", "MaintenanceMode", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "PercentageDiskSpaceUsed", "ReadIOPS", "ReadLatency", "ReadThroughput", "WriteIOPS", "WriteLatency", "WriteThroughput"}, - "AWS/RDS": {"ActiveTransactions", "AuroraBinlogReplicaLag", "AuroraReplicaLag", "AuroraReplicaLagMaximum", "AuroraReplicaLagMinimum", "BinLogDiskUsage", "BlockedTransactions", "BufferCacheHitRatio", "CommitLatency", "CommitThroughput", "BinLogDiskUsage", "CPUCreditBalance", "CPUCreditUsage", "CPUUtilization", "DatabaseConnections", "DDLLatency", "DDLThroughput", "Deadlocks", "DeleteLatency", "DeleteThroughput", "DiskQueueDepth", "DMLLatency", "DMLThroughput", "EngineUptime", "FailedSqlStatements", "FreeableMemory", "FreeLocalStorage", "FreeStorageSpace", "InsertLatency", "InsertThroughput", "LoginFailures", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "NetworkThroughput", "Queries", "ReadIOPS", "ReadLatency", "ReadThroughput", "ReplicaLag", "ResultSetCacheHitRatio", "SelectLatency", "SelectThroughput", "SwapUsage", "TotalConnections", "UpdateLatency", "UpdateThroughput", "VolumeBytesUsed", "VolumeReadIOPS", "VolumeWriteIOPS", "WriteIOPS", "WriteLatency", "WriteThroughput"}, + "AWS/RDS": {"ActiveTransactions", "AuroraBinlogReplicaLag", "AuroraReplicaLag", "AuroraReplicaLagMaximum", "AuroraReplicaLagMinimum", "BinLogDiskUsage", "BlockedTransactions", "BufferCacheHitRatio", "BurstBalance", "CommitLatency", "CommitThroughput", "BinLogDiskUsage", "CPUCreditBalance", "CPUCreditUsage", "CPUUtilization", "DatabaseConnections", "DDLLatency", "DDLThroughput", "Deadlocks", "DeleteLatency", "DeleteThroughput", "DiskQueueDepth", "DMLLatency", "DMLThroughput", "EngineUptime", "FailedSqlStatements", "FreeableMemory", "FreeLocalStorage", "FreeStorageSpace", "InsertLatency", "InsertThroughput", "LoginFailures", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "NetworkThroughput", "Queries", "ReadIOPS", "ReadLatency", "ReadThroughput", "ReplicaLag", "ResultSetCacheHitRatio", "SelectLatency", "SelectThroughput", "SwapUsage", "TotalConnections", "UpdateLatency", "UpdateThroughput", "VolumeBytesUsed", "VolumeReadIOPS", "VolumeWriteIOPS", "WriteIOPS", "WriteLatency", "WriteThroughput"}, "AWS/Route53": {"ChildHealthCheckHealthyCount", "HealthCheckStatus", "HealthCheckPercentageHealthy", "ConnectionTime", "SSLHandshakeTime", "TimeToFirstByte"}, "AWS/S3": {"BucketSizeBytes", "NumberOfObjects", "AllRequests", "GetRequests", "PutRequests", "DeleteRequests", "HeadRequests", "PostRequests", "ListRequests", "BytesDownloaded", "BytesUploaded", "4xxErrors", "5xxErrors", "FirstByteLatency", "TotalRequestLatency"}, "AWS/SES": {"Bounce", "Complaint", "Delivery", "Reject", "Send"}, @@ -119,6 +120,7 @@ func init() { "AWS/CloudFront": {"DistributionId", "Region"}, "AWS/CloudSearch": {}, "AWS/DMS": {"ReplicationInstanceIdentifier", "ReplicationTaskIdentifier"}, + "AWS/DX": {"ConnectionId"}, "AWS/DynamoDB": {"TableName", "GlobalSecondaryIndexName", "Operation", "StreamLabel"}, "AWS/EBS": {"VolumeId"}, "AWS/EC2": {"AutoScalingGroupName", "ImageId", "InstanceId", "InstanceType"}, diff --git a/public/app/core/components/Forms/Forms.tsx b/public/app/core/components/Forms/Forms.tsx index 4b74d48ba08..543e1a1d6df 100644 --- a/public/app/core/components/Forms/Forms.tsx +++ b/public/app/core/components/Forms/Forms.tsx @@ -12,7 +12,7 @@ export const Label: SFC = props => { {props.children} {props.tooltip && ( - + )} diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 75a946d6f36..ac8d774db59 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -175,7 +175,6 @@ export class PrometheusDatasource { responseIndex: index, refId: activeTargets[index].refId, }; - this.resultTransformer.transform(result, response, transformerOptions); }); diff --git a/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts b/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts index b8b2b50f590..aeca8d69191 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts @@ -150,49 +150,49 @@ describe('PrometheusDatasource', () => { }); }); - describe('alignRange', function() { - it('does not modify already aligned intervals with perfect step', function() { + describe('alignRange', () => { + it('does not modify already aligned intervals with perfect step', () => { const range = alignRange(0, 3, 3); expect(range.start).toEqual(0); expect(range.end).toEqual(3); }); - it('does modify end-aligned intervals to reflect number of steps possible', function() { + it('does modify end-aligned intervals to reflect number of steps possible', () => { const range = alignRange(1, 6, 3); expect(range.start).toEqual(0); expect(range.end).toEqual(6); }); - it('does align intervals that are a multiple of steps', function() { + it('does align intervals that are a multiple of steps', () => { const range = alignRange(1, 4, 3); expect(range.start).toEqual(0); expect(range.end).toEqual(6); }); - it('does align intervals that are not a multiple of steps', function() { + it('does align intervals that are not a multiple of steps', () => { const range = alignRange(1, 5, 3); expect(range.start).toEqual(0); expect(range.end).toEqual(6); }); }); - describe('Prometheus regular escaping', function() { - it('should not escape non-string', function() { + describe('Prometheus regular escaping', () => { + it('should not escape non-string', () => { expect(prometheusRegularEscape(12)).toEqual(12); }); - it('should not escape simple string', function() { + it('should not escape simple string', () => { expect(prometheusRegularEscape('cryptodepression')).toEqual('cryptodepression'); }); - it("should escape '", function() { + it("should escape '", () => { expect(prometheusRegularEscape("looking'glass")).toEqual("looking\\\\'glass"); }); - it('should escape multiple characters', function() { + it('should escape multiple characters', () => { expect(prometheusRegularEscape("'looking'glass'")).toEqual("\\\\'looking\\\\'glass\\\\'"); }); }); - describe('Prometheus regexes escaping', function() { - it('should not escape simple string', function() { + describe('Prometheus regexes escaping', () => { + it('should not escape simple string', () => { expect(prometheusSpecialRegexEscape('cryptodepression')).toEqual('cryptodepression'); }); - it('should escape $^*+?.()\\', function() { + it('should escape $^*+?.()\\', () => { expect(prometheusSpecialRegexEscape("looking'glass")).toEqual("looking\\\\'glass"); expect(prometheusSpecialRegexEscape('looking{glass')).toEqual('looking\\\\{glass'); expect(prometheusSpecialRegexEscape('looking}glass')).toEqual('looking\\\\}glass'); @@ -208,7 +208,7 @@ describe('PrometheusDatasource', () => { expect(prometheusSpecialRegexEscape('looking)glass')).toEqual('looking\\\\)glass'); expect(prometheusSpecialRegexEscape('looking\\glass')).toEqual('looking\\\\\\\\glass'); }); - it('should escape multiple special characters', function() { + it('should escape multiple special characters', () => { expect(prometheusSpecialRegexEscape('+looking$glass?')).toEqual('\\\\+looking\\\\$glass\\\\?'); }); }); @@ -246,3 +246,797 @@ describe('PrometheusDatasource', () => { }); }); }); + +const SECOND = 1000; +const MINUTE = 60 * SECOND; +const HOUR = 60 * MINUTE; + +const time = ({ hours = 0, seconds = 0, minutes = 0 }) => moment(hours * HOUR + minutes * MINUTE + seconds * SECOND); + +let ctx = {}; +let instanceSettings = { + url: 'proxied', + directUrl: 'direct', + user: 'test', + password: 'mupp', + jsonData: { httpMethod: 'GET' }, +}; +let backendSrv = { + datasourceRequest: jest.fn(), +}; + +let templateSrv = { + replace: jest.fn(str => str), +}; + +let timeSrv = { + timeRange: () => { + return { to: { diff: () => 2000 }, from: '' }; + }, +}; + +describe('PrometheusDatasource', () => { + describe('When querying prometheus with one target using query editor target spec', async () => { + var results; + var query = { + range: { from: time({ seconds: 63 }), to: time({ seconds: 183 }) }, + targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }], + interval: '60s', + }; + // Interval alignment with step + var urlExpected = + 'proxied/api/v1/query_range?query=' + encodeURIComponent('test{job="testjob"}') + '&start=60&end=240&step=60'; + + beforeEach(async () => { + let response = { + data: { + status: 'success', + data: { + resultType: 'matrix', + result: [ + { + metric: { __name__: 'test', job: 'testjob' }, + values: [[60, '3846']], + }, + ], + }, + }, + }; + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + + await ctx.ds.query(query).then(function(data) { + results = data; + }); + }); + + it('should generate the correct query', () => { + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + }); + it('should return series list', async () => { + expect(results.data.length).toBe(1); + expect(results.data[0].target).toBe('test{job="testjob"}'); + }); + }); + describe('When querying prometheus with one target which return multiple series', () => { + var results; + var start = 60; + var end = 360; + var step = 60; + + var query = { + range: { from: time({ seconds: start }), to: time({ seconds: end }) }, + targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }], + interval: '60s', + }; + + beforeEach(async () => { + let response = { + status: 'success', + data: { + data: { + resultType: 'matrix', + result: [ + { + metric: { __name__: 'test', job: 'testjob', series: 'series 1' }, + values: [[start + step * 1, '3846'], [start + step * 3, '3847'], [end - step * 1, '3848']], + }, + { + metric: { __name__: 'test', job: 'testjob', series: 'series 2' }, + values: [[start + step * 2, '4846']], + }, + ], + }, + }, + }; + + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + + await ctx.ds.query(query).then(function(data) { + results = data; + }); + }); + + it('should be same length', () => { + expect(results.data.length).toBe(2); + expect(results.data[0].datapoints.length).toBe((end - start) / step + 1); + expect(results.data[1].datapoints.length).toBe((end - start) / step + 1); + }); + + it('should fill null until first datapoint in response', () => { + expect(results.data[0].datapoints[0][1]).toBe(start * 1000); + expect(results.data[0].datapoints[0][0]).toBe(null); + expect(results.data[0].datapoints[1][1]).toBe((start + step * 1) * 1000); + expect(results.data[0].datapoints[1][0]).toBe(3846); + }); + it('should fill null after last datapoint in response', () => { + var length = (end - start) / step + 1; + expect(results.data[0].datapoints[length - 2][1]).toBe((end - step * 1) * 1000); + expect(results.data[0].datapoints[length - 2][0]).toBe(3848); + expect(results.data[0].datapoints[length - 1][1]).toBe(end * 1000); + expect(results.data[0].datapoints[length - 1][0]).toBe(null); + }); + it('should fill null at gap between series', () => { + expect(results.data[0].datapoints[2][1]).toBe((start + step * 2) * 1000); + expect(results.data[0].datapoints[2][0]).toBe(null); + expect(results.data[1].datapoints[1][1]).toBe((start + step * 1) * 1000); + expect(results.data[1].datapoints[1][0]).toBe(null); + expect(results.data[1].datapoints[3][1]).toBe((start + step * 3) * 1000); + expect(results.data[1].datapoints[3][0]).toBe(null); + }); + }); + describe('When querying prometheus with one target and instant = true', () => { + var results; + var urlExpected = 'proxied/api/v1/query?query=' + encodeURIComponent('test{job="testjob"}') + '&time=123'; + var query = { + range: { from: time({ seconds: 63 }), to: time({ seconds: 123 }) }, + targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }], + interval: '60s', + }; + + beforeEach(async () => { + let response = { + status: 'success', + data: { + data: { + resultType: 'vector', + result: [ + { + metric: { __name__: 'test', job: 'testjob' }, + value: [123, '3846'], + }, + ], + }, + }, + }; + + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + + await ctx.ds.query(query).then(function(data) { + results = data; + }); + }); + it('should generate the correct query', () => { + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + }); + it('should return series list', () => { + expect(results.data.length).toBe(1); + expect(results.data[0].target).toBe('test{job="testjob"}'); + }); + }); + describe('When performing annotationQuery', () => { + var results; + + var options = { + annotation: { + expr: 'ALERTS{alertstate="firing"}', + tagKeys: 'job', + titleFormat: '{{alertname}}', + textFormat: '{{instance}}', + }, + range: { + from: time({ seconds: 63 }), + to: time({ seconds: 123 }), + }, + }; + + beforeEach(async () => { + let response = { + status: 'success', + data: { + data: { + resultType: 'matrix', + result: [ + { + metric: { + __name__: 'ALERTS', + alertname: 'InstanceDown', + alertstate: 'firing', + instance: 'testinstance', + job: 'testjob', + }, + values: [[123, '1']], + }, + ], + }, + }, + }; + + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + + await ctx.ds.annotationQuery(options).then(function(data) { + results = data; + }); + }); + it('should return annotation list', () => { + expect(results.length).toBe(1); + expect(results[0].tags).toContain('testjob'); + expect(results[0].title).toBe('InstanceDown'); + expect(results[0].text).toBe('testinstance'); + expect(results[0].time).toBe(123 * 1000); + }); + }); + + describe('When resultFormat is table and instant = true', () => { + var results; + var query = { + range: { from: time({ seconds: 63 }), to: time({ seconds: 123 }) }, + targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }], + interval: '60s', + }; + + beforeEach(async () => { + let response = { + status: 'success', + data: { + data: { + resultType: 'vector', + result: [ + { + metric: { __name__: 'test', job: 'testjob' }, + value: [123, '3846'], + }, + ], + }, + }, + }; + + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query).then(function(data) { + results = data; + }); + }); + + it('should return result', () => { + expect(results).not.toBe(null); + }); + }); + + describe('The "step" query parameter', () => { + var response = { + status: 'success', + data: { + data: { + resultType: 'matrix', + result: [], + }, + }, + }; + + it('should be min interval when greater than auto interval', async () => { + let query = { + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, + targets: [ + { + expr: 'test', + interval: '10s', + }, + ], + interval: '5s', + }; + let urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10'; + + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + }); + + it('step should never go below 1', async () => { + var query = { + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, + targets: [{ expr: 'test' }], + interval: '100ms', + }; + var urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=1'; + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + }); + + it('should be auto interval when greater than min interval', async () => { + var query = { + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, + targets: [ + { + expr: 'test', + interval: '5s', + }, + ], + interval: '10s', + }; + var urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10'; + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + }); + it('should result in querying fewer than 11000 data points', async () => { + var query = { + // 6 hour range + range: { from: time({ hours: 1 }), to: time({ hours: 7 }) }, + targets: [{ expr: 'test' }], + interval: '1s', + }; + var end = 7 * 60 * 60; + var start = 60 * 60; + var urlExpected = 'proxied/api/v1/query_range?query=test&start=' + start + '&end=' + end + '&step=2'; + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + }); + it('should not apply min interval when interval * intervalFactor greater', async () => { + var query = { + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, + targets: [ + { + expr: 'test', + interval: '10s', + intervalFactor: 10, + }, + ], + interval: '5s', + }; + // times get rounded up to interval + var urlExpected = 'proxied/api/v1/query_range?query=test&start=50&end=450&step=50'; + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + }); + it('should apply min interval when interval * intervalFactor smaller', async () => { + var query = { + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, + targets: [ + { + expr: 'test', + interval: '15s', + intervalFactor: 2, + }, + ], + interval: '5s', + }; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=60&end=420&step=15'; + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + }); + it('should apply intervalFactor to auto interval when greater', async () => { + var query = { + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, + targets: [ + { + expr: 'test', + interval: '5s', + intervalFactor: 10, + }, + ], + interval: '10s', + }; + // times get aligned to interval + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=0&end=500&step=100'; + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + }); + it('should not not be affected by the 11000 data points limit when large enough', async () => { + var query = { + // 1 week range + range: { from: time({}), to: time({ hours: 7 * 24 }) }, + targets: [ + { + expr: 'test', + intervalFactor: 10, + }, + ], + interval: '10s', + }; + var end = 7 * 24 * 60 * 60; + var start = 0; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=100'; + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + }); + it('should be determined by the 11000 data points limit when too small', async () => { + var query = { + // 1 week range + range: { from: time({}), to: time({ hours: 7 * 24 }) }, + targets: [ + { + expr: 'test', + intervalFactor: 10, + }, + ], + interval: '5s', + }; + var end = 7 * 24 * 60 * 60; + var start = 0; + var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=60'; + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + }); + }); + + describe('The __interval and __interval_ms template variables', () => { + var response = { + status: 'success', + data: { + data: { + resultType: 'matrix', + result: [], + }, + }, + }; + + it('should be unchanged when auto interval is greater than min interval', async () => { + var query = { + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, + targets: [ + { + expr: 'rate(test[$__interval])', + interval: '5s', + }, + ], + interval: '10s', + scopedVars: { + __interval: { text: '10s', value: '10s' }, + __interval_ms: { text: 10 * 1000, value: 10 * 1000 }, + }, + }; + + var urlExpected = + 'proxied/api/v1/query_range?query=' + + encodeURIComponent('rate(test[$__interval])') + + '&start=60&end=420&step=10'; + + templateSrv.replace = jest.fn(str => str); + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + + expect(templateSrv.replace.mock.calls[0][1]).toEqual({ + __interval: { + text: '10s', + value: '10s', + }, + __interval_ms: { + text: 10000, + value: 10000, + }, + }); + }); + it('should be min interval when it is greater than auto interval', async () => { + var query = { + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, + targets: [ + { + expr: 'rate(test[$__interval])', + interval: '10s', + }, + ], + interval: '5s', + scopedVars: { + __interval: { text: '5s', value: '5s' }, + __interval_ms: { text: 5 * 1000, value: 5 * 1000 }, + }, + }; + var urlExpected = + 'proxied/api/v1/query_range?query=' + + encodeURIComponent('rate(test[$__interval])') + + '&start=60&end=420&step=10'; + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + templateSrv.replace = jest.fn(str => str); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + + expect(templateSrv.replace.mock.calls[0][1]).toEqual({ + __interval: { + text: '5s', + value: '5s', + }, + __interval_ms: { + text: 5000, + value: 5000, + }, + }); + }); + it('should account for intervalFactor', async () => { + var query = { + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, + targets: [ + { + expr: 'rate(test[$__interval])', + interval: '5s', + intervalFactor: 10, + }, + ], + interval: '10s', + scopedVars: { + __interval: { text: '10s', value: '10s' }, + __interval_ms: { text: 10 * 1000, value: 10 * 1000 }, + }, + }; + var urlExpected = + 'proxied/api/v1/query_range?query=' + + encodeURIComponent('rate(test[$__interval])') + + '&start=0&end=500&step=100'; + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + templateSrv.replace = jest.fn(str => str); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + + expect(templateSrv.replace.mock.calls[0][1]).toEqual({ + __interval: { + text: '10s', + value: '10s', + }, + __interval_ms: { + text: 10000, + value: 10000, + }, + }); + + expect(query.scopedVars.__interval.text).toBe('10s'); + expect(query.scopedVars.__interval.value).toBe('10s'); + expect(query.scopedVars.__interval_ms.text).toBe(10 * 1000); + expect(query.scopedVars.__interval_ms.value).toBe(10 * 1000); + }); + it('should be interval * intervalFactor when greater than min interval', async () => { + var query = { + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, + targets: [ + { + expr: 'rate(test[$__interval])', + interval: '10s', + intervalFactor: 10, + }, + ], + interval: '5s', + scopedVars: { + __interval: { text: '5s', value: '5s' }, + __interval_ms: { text: 5 * 1000, value: 5 * 1000 }, + }, + }; + var urlExpected = + 'proxied/api/v1/query_range?query=' + + encodeURIComponent('rate(test[$__interval])') + + '&start=50&end=450&step=50'; + + templateSrv.replace = jest.fn(str => str); + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + + expect(templateSrv.replace.mock.calls[0][1]).toEqual({ + __interval: { + text: '5s', + value: '5s', + }, + __interval_ms: { + text: 5000, + value: 5000, + }, + }); + }); + it('should be min interval when greater than interval * intervalFactor', async () => { + var query = { + // 6 minute range + range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, + targets: [ + { + expr: 'rate(test[$__interval])', + interval: '15s', + intervalFactor: 2, + }, + ], + interval: '5s', + scopedVars: { + __interval: { text: '5s', value: '5s' }, + __interval_ms: { text: 5 * 1000, value: 5 * 1000 }, + }, + }; + var urlExpected = + 'proxied/api/v1/query_range?query=' + + encodeURIComponent('rate(test[$__interval])') + + '&start=60&end=420&step=15'; + + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + + expect(templateSrv.replace.mock.calls[0][1]).toEqual({ + __interval: { + text: '5s', + value: '5s', + }, + __interval_ms: { + text: 5000, + value: 5000, + }, + }); + }); + it('should be determined by the 11000 data points limit, accounting for intervalFactor', async () => { + var query = { + // 1 week range + range: { from: time({}), to: time({ hours: 7 * 24 }) }, + targets: [ + { + expr: 'rate(test[$__interval])', + intervalFactor: 10, + }, + ], + interval: '5s', + scopedVars: { + __interval: { text: '5s', value: '5s' }, + __interval_ms: { text: 5 * 1000, value: 5 * 1000 }, + }, + }; + var end = 7 * 24 * 60 * 60; + var start = 0; + var urlExpected = + 'proxied/api/v1/query_range?query=' + + encodeURIComponent('rate(test[$__interval])') + + '&start=' + + start + + '&end=' + + end + + '&step=60'; + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + templateSrv.replace = jest.fn(str => str); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query); + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('GET'); + expect(res.url).toBe(urlExpected); + + expect(templateSrv.replace.mock.calls[0][1]).toEqual({ + __interval: { + text: '5s', + value: '5s', + }, + __interval_ms: { + text: 5000, + value: 5000, + }, + }); + }); + }); +}); + +describe('PrometheusDatasource for POST', () => { + // var ctx = new helpers.ServiceTestContext(); + let instanceSettings = { + url: 'proxied', + directUrl: 'direct', + user: 'test', + password: 'mupp', + jsonData: { httpMethod: 'POST' }, + }; + + describe('When querying prometheus with one target using query editor target spec', () => { + var results; + var urlExpected = 'proxied/api/v1/query_range'; + var dataExpected = { + query: 'test{job="testjob"}', + start: 1 * 60, + end: 3 * 60, + step: 60, + }; + var query = { + range: { from: time({ minutes: 1, seconds: 3 }), to: time({ minutes: 2, seconds: 3 }) }, + targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }], + interval: '60s', + }; + + beforeEach(async () => { + let response = { + status: 'success', + data: { + data: { + resultType: 'matrix', + result: [ + { + metric: { __name__: 'test', job: 'testjob' }, + values: [[2 * 60, '3846']], + }, + ], + }, + }, + }; + backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv, templateSrv, timeSrv); + await ctx.ds.query(query).then(function(data) { + results = data; + }); + }); + it('should generate the correct query', () => { + let res = backendSrv.datasourceRequest.mock.calls[0][0]; + expect(res.method).toBe('POST'); + expect(res.url).toBe(urlExpected); + expect(res.data).toEqual(dataExpected); + }); + it('should return series list', () => { + expect(results.data.length).toBe(1); + expect(results.data[0].target).toBe('test{job="testjob"}'); + }); + }); +}); diff --git a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts deleted file mode 100644 index c5da671b757..00000000000 --- a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts +++ /dev/null @@ -1,683 +0,0 @@ -import { describe, beforeEach, it, expect, angularMocks } from 'test/lib/common'; -import moment from 'moment'; -import $ from 'jquery'; -import helpers from 'test/specs/helpers'; -import { PrometheusDatasource } from '../datasource'; - -const SECOND = 1000; -const MINUTE = 60 * SECOND; -const HOUR = 60 * MINUTE; - -const time = ({ hours = 0, seconds = 0, minutes = 0 }) => moment(hours * HOUR + minutes * MINUTE + seconds * SECOND); - -describe('PrometheusDatasource', function() { - var ctx = new helpers.ServiceTestContext(); - var instanceSettings = { - url: 'proxied', - directUrl: 'direct', - user: 'test', - password: 'mupp', - jsonData: { httpMethod: 'GET' }, - }; - - beforeEach(angularMocks.module('grafana.core')); - beforeEach(angularMocks.module('grafana.services')); - beforeEach(ctx.providePhase(['timeSrv'])); - - beforeEach( - angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { - ctx.$q = $q; - ctx.$httpBackend = $httpBackend; - ctx.$rootScope = $rootScope; - ctx.ds = $injector.instantiate(PrometheusDatasource, { - instanceSettings: instanceSettings, - }); - $httpBackend.when('GET', /\.html$/).respond(''); - }) - ); - describe('When querying prometheus with one target using query editor target spec', function() { - var results; - var query = { - range: { from: time({ seconds: 63 }), to: time({ seconds: 183 }) }, - targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }], - interval: '60s', - }; - // Interval alignment with step - var urlExpected = - 'proxied/api/v1/query_range?query=' + encodeURIComponent('test{job="testjob"}') + '&start=60&end=240&step=60'; - var response = { - status: 'success', - data: { - resultType: 'matrix', - result: [ - { - metric: { __name__: 'test', job: 'testjob' }, - values: [[60, '3846']], - }, - ], - }, - }; - beforeEach(function() { - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query).then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - }); - it('should generate the correct query', function() { - ctx.$httpBackend.verifyNoOutstandingExpectation(); - }); - it('should return series list', function() { - expect(results.data.length).to.be(1); - expect(results.data[0].target).to.be('test{job="testjob"}'); - }); - }); - describe('When querying prometheus with one target which return multiple series', function() { - var results; - var start = 60; - var end = 360; - var step = 60; - var urlExpected = - 'proxied/api/v1/query_range?query=' + - encodeURIComponent('test{job="testjob"}') + - '&start=' + - start + - '&end=' + - end + - '&step=' + - step; - var query = { - range: { from: time({ seconds: start }), to: time({ seconds: end }) }, - targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }], - interval: '60s', - }; - var response = { - status: 'success', - data: { - resultType: 'matrix', - result: [ - { - metric: { __name__: 'test', job: 'testjob', series: 'series 1' }, - values: [[start + step * 1, '3846'], [start + step * 3, '3847'], [end - step * 1, '3848']], - }, - { - metric: { __name__: 'test', job: 'testjob', series: 'series 2' }, - values: [[start + step * 2, '4846']], - }, - ], - }, - }; - beforeEach(function() { - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query).then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - }); - it('should be same length', function() { - expect(results.data.length).to.be(2); - expect(results.data[0].datapoints.length).to.be((end - start) / step + 1); - expect(results.data[1].datapoints.length).to.be((end - start) / step + 1); - }); - it('should fill null until first datapoint in response', function() { - expect(results.data[0].datapoints[0][1]).to.be(start * 1000); - expect(results.data[0].datapoints[0][0]).to.be(null); - expect(results.data[0].datapoints[1][1]).to.be((start + step * 1) * 1000); - expect(results.data[0].datapoints[1][0]).to.be(3846); - }); - it('should fill null after last datapoint in response', function() { - var length = (end - start) / step + 1; - expect(results.data[0].datapoints[length - 2][1]).to.be((end - step * 1) * 1000); - expect(results.data[0].datapoints[length - 2][0]).to.be(3848); - expect(results.data[0].datapoints[length - 1][1]).to.be(end * 1000); - expect(results.data[0].datapoints[length - 1][0]).to.be(null); - }); - it('should fill null at gap between series', function() { - expect(results.data[0].datapoints[2][1]).to.be((start + step * 2) * 1000); - expect(results.data[0].datapoints[2][0]).to.be(null); - expect(results.data[1].datapoints[1][1]).to.be((start + step * 1) * 1000); - expect(results.data[1].datapoints[1][0]).to.be(null); - expect(results.data[1].datapoints[3][1]).to.be((start + step * 3) * 1000); - expect(results.data[1].datapoints[3][0]).to.be(null); - }); - }); - describe('When querying prometheus with one target and instant = true', function() { - var results; - var urlExpected = 'proxied/api/v1/query?query=' + encodeURIComponent('test{job="testjob"}') + '&time=123'; - var query = { - range: { from: time({ seconds: 63 }), to: time({ seconds: 123 }) }, - targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }], - interval: '60s', - }; - var response = { - status: 'success', - data: { - resultType: 'vector', - result: [ - { - metric: { __name__: 'test', job: 'testjob' }, - value: [123, '3846'], - }, - ], - }, - }; - beforeEach(function() { - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query).then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - }); - it('should generate the correct query', function() { - ctx.$httpBackend.verifyNoOutstandingExpectation(); - }); - it('should return series list', function() { - expect(results.data.length).to.be(1); - expect(results.data[0].target).to.be('test{job="testjob"}'); - }); - }); - describe('When performing annotationQuery', function() { - var results; - var urlExpected = - 'proxied/api/v1/query_range?query=' + - encodeURIComponent('ALERTS{alertstate="firing"}') + - '&start=60&end=180&step=60'; - var options = { - annotation: { - expr: 'ALERTS{alertstate="firing"}', - tagKeys: 'job', - titleFormat: '{{alertname}}', - textFormat: '{{instance}}', - }, - range: { - from: time({ seconds: 63 }), - to: time({ seconds: 123 }), - }, - }; - var response = { - status: 'success', - data: { - resultType: 'matrix', - result: [ - { - metric: { - __name__: 'ALERTS', - alertname: 'InstanceDown', - alertstate: 'firing', - instance: 'testinstance', - job: 'testjob', - }, - values: [[123, '1']], - }, - ], - }, - }; - beforeEach(function() { - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.annotationQuery(options).then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - }); - it('should return annotation list', function() { - ctx.$rootScope.$apply(); - expect(results.length).to.be(1); - expect(results[0].tags).to.contain('testjob'); - expect(results[0].title).to.be('InstanceDown'); - expect(results[0].text).to.be('testinstance'); - expect(results[0].time).to.be(123 * 1000); - }); - }); - - describe('When resultFormat is table and instant = true', function() { - var results; - var urlExpected = 'proxied/api/v1/query?query=' + encodeURIComponent('test{job="testjob"}') + '&time=123'; - var query = { - range: { from: time({ seconds: 63 }), to: time({ seconds: 123 }) }, - targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }], - interval: '60s', - }; - var response = { - status: 'success', - data: { - resultType: 'vector', - result: [ - { - metric: { __name__: 'test', job: 'testjob' }, - value: [123, '3846'], - }, - ], - }, - }; - - beforeEach(function() { - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query).then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - }); - - it('should return result', () => { - expect(results).not.to.be(null); - }); - }); - - describe('The "step" query parameter', function() { - var response = { - status: 'success', - data: { - resultType: 'matrix', - result: [], - }, - }; - - it('should be min interval when greater than auto interval', function() { - var query = { - // 6 minute range - range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, - targets: [ - { - expr: 'test', - interval: '10s', - }, - ], - interval: '5s', - }; - var urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - }); - - it('step should never go below 1', function() { - var query = { - // 6 minute range - range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, - targets: [{ expr: 'test' }], - interval: '100ms', - }; - var urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=1'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - }); - - it('should be auto interval when greater than min interval', function() { - var query = { - // 6 minute range - range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, - targets: [ - { - expr: 'test', - interval: '5s', - }, - ], - interval: '10s', - }; - var urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - }); - it('should result in querying fewer than 11000 data points', function() { - var query = { - // 6 hour range - range: { from: time({ hours: 1 }), to: time({ hours: 7 }) }, - targets: [{ expr: 'test' }], - interval: '1s', - }; - var end = 7 * 60 * 60; - var start = 60 * 60; - var urlExpected = 'proxied/api/v1/query_range?query=test&start=' + start + '&end=' + end + '&step=2'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - }); - it('should not apply min interval when interval * intervalFactor greater', function() { - var query = { - // 6 minute range - range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, - targets: [ - { - expr: 'test', - interval: '10s', - intervalFactor: 10, - }, - ], - interval: '5s', - }; - // times get rounded up to interval - var urlExpected = 'proxied/api/v1/query_range?query=test&start=50&end=450&step=50'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - }); - it('should apply min interval when interval * intervalFactor smaller', function() { - var query = { - // 6 minute range - range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, - targets: [ - { - expr: 'test', - interval: '15s', - intervalFactor: 2, - }, - ], - interval: '5s', - }; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=60&end=420&step=15'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - }); - it('should apply intervalFactor to auto interval when greater', function() { - var query = { - // 6 minute range - range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, - targets: [ - { - expr: 'test', - interval: '5s', - intervalFactor: 10, - }, - ], - interval: '10s', - }; - // times get aligned to interval - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=0&end=500&step=100'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - }); - it('should not not be affected by the 11000 data points limit when large enough', function() { - var query = { - // 1 week range - range: { from: time({}), to: time({ hours: 7 * 24 }) }, - targets: [ - { - expr: 'test', - intervalFactor: 10, - }, - ], - interval: '10s', - }; - var end = 7 * 24 * 60 * 60; - var start = 0; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=100'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - }); - it('should be determined by the 11000 data points limit when too small', function() { - var query = { - // 1 week range - range: { from: time({}), to: time({ hours: 7 * 24 }) }, - targets: [ - { - expr: 'test', - intervalFactor: 10, - }, - ], - interval: '5s', - }; - var end = 7 * 24 * 60 * 60; - var start = 0; - var urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=60'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - }); - }); - - describe('The __interval and __interval_ms template variables', function() { - var response = { - status: 'success', - data: { - resultType: 'matrix', - result: [], - }, - }; - - it('should be unchanged when auto interval is greater than min interval', function() { - var query = { - // 6 minute range - range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, - targets: [ - { - expr: 'rate(test[$__interval])', - interval: '5s', - }, - ], - interval: '10s', - scopedVars: { - __interval: { text: '10s', value: '10s' }, - __interval_ms: { text: 10 * 1000, value: 10 * 1000 }, - }, - }; - var urlExpected = - 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[10s])') + '&start=60&end=420&step=10'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - - expect(query.scopedVars.__interval.text).to.be('10s'); - expect(query.scopedVars.__interval.value).to.be('10s'); - expect(query.scopedVars.__interval_ms.text).to.be(10 * 1000); - expect(query.scopedVars.__interval_ms.value).to.be(10 * 1000); - }); - it('should be min interval when it is greater than auto interval', function() { - var query = { - // 6 minute range - range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, - targets: [ - { - expr: 'rate(test[$__interval])', - interval: '10s', - }, - ], - interval: '5s', - scopedVars: { - __interval: { text: '5s', value: '5s' }, - __interval_ms: { text: 5 * 1000, value: 5 * 1000 }, - }, - }; - var urlExpected = - 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[10s])') + '&start=60&end=420&step=10'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - - expect(query.scopedVars.__interval.text).to.be('5s'); - expect(query.scopedVars.__interval.value).to.be('5s'); - expect(query.scopedVars.__interval_ms.text).to.be(5 * 1000); - expect(query.scopedVars.__interval_ms.value).to.be(5 * 1000); - }); - it('should account for intervalFactor', function() { - var query = { - // 6 minute range - range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, - targets: [ - { - expr: 'rate(test[$__interval])', - interval: '5s', - intervalFactor: 10, - }, - ], - interval: '10s', - scopedVars: { - __interval: { text: '10s', value: '10s' }, - __interval_ms: { text: 10 * 1000, value: 10 * 1000 }, - }, - }; - var urlExpected = - 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[100s])') + '&start=0&end=500&step=100'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - - expect(query.scopedVars.__interval.text).to.be('10s'); - expect(query.scopedVars.__interval.value).to.be('10s'); - expect(query.scopedVars.__interval_ms.text).to.be(10 * 1000); - expect(query.scopedVars.__interval_ms.value).to.be(10 * 1000); - }); - it('should be interval * intervalFactor when greater than min interval', function() { - var query = { - // 6 minute range - range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, - targets: [ - { - expr: 'rate(test[$__interval])', - interval: '10s', - intervalFactor: 10, - }, - ], - interval: '5s', - scopedVars: { - __interval: { text: '5s', value: '5s' }, - __interval_ms: { text: 5 * 1000, value: 5 * 1000 }, - }, - }; - var urlExpected = - 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[50s])') + '&start=50&end=450&step=50'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - - expect(query.scopedVars.__interval.text).to.be('5s'); - expect(query.scopedVars.__interval.value).to.be('5s'); - expect(query.scopedVars.__interval_ms.text).to.be(5 * 1000); - expect(query.scopedVars.__interval_ms.value).to.be(5 * 1000); - }); - it('should be min interval when greater than interval * intervalFactor', function() { - var query = { - // 6 minute range - range: { from: time({ minutes: 1 }), to: time({ minutes: 7 }) }, - targets: [ - { - expr: 'rate(test[$__interval])', - interval: '15s', - intervalFactor: 2, - }, - ], - interval: '5s', - scopedVars: { - __interval: { text: '5s', value: '5s' }, - __interval_ms: { text: 5 * 1000, value: 5 * 1000 }, - }, - }; - var urlExpected = - 'proxied/api/v1/query_range?query=' + encodeURIComponent('rate(test[15s])') + '&start=60&end=420&step=15'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - - expect(query.scopedVars.__interval.text).to.be('5s'); - expect(query.scopedVars.__interval.value).to.be('5s'); - expect(query.scopedVars.__interval_ms.text).to.be(5 * 1000); - expect(query.scopedVars.__interval_ms.value).to.be(5 * 1000); - }); - it('should be determined by the 11000 data points limit, accounting for intervalFactor', function() { - var query = { - // 1 week range - range: { from: time({}), to: time({ hours: 7 * 24 }) }, - targets: [ - { - expr: 'rate(test[$__interval])', - intervalFactor: 10, - }, - ], - interval: '5s', - scopedVars: { - __interval: { text: '5s', value: '5s' }, - __interval_ms: { text: 5 * 1000, value: 5 * 1000 }, - }, - }; - var end = 7 * 24 * 60 * 60; - var start = 0; - var urlExpected = - 'proxied/api/v1/query_range?query=' + - encodeURIComponent('rate(test[60s])') + - '&start=' + - start + - '&end=' + - end + - '&step=60'; - ctx.$httpBackend.expect('GET', urlExpected).respond(response); - ctx.ds.query(query); - ctx.$httpBackend.verifyNoOutstandingExpectation(); - - expect(query.scopedVars.__interval.text).to.be('5s'); - expect(query.scopedVars.__interval.value).to.be('5s'); - expect(query.scopedVars.__interval_ms.text).to.be(5 * 1000); - expect(query.scopedVars.__interval_ms.value).to.be(5 * 1000); - }); - }); -}); - -describe('PrometheusDatasource for POST', function() { - var ctx = new helpers.ServiceTestContext(); - var instanceSettings = { - url: 'proxied', - directUrl: 'direct', - user: 'test', - password: 'mupp', - jsonData: { httpMethod: 'POST' }, - }; - - beforeEach(angularMocks.module('grafana.core')); - beforeEach(angularMocks.module('grafana.services')); - beforeEach(ctx.providePhase(['timeSrv'])); - - beforeEach( - angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { - ctx.$q = $q; - ctx.$httpBackend = $httpBackend; - ctx.$rootScope = $rootScope; - ctx.ds = $injector.instantiate(PrometheusDatasource, { instanceSettings: instanceSettings }); - $httpBackend.when('GET', /\.html$/).respond(''); - }) - ); - - describe('When querying prometheus with one target using query editor target spec', function() { - var results; - var urlExpected = 'proxied/api/v1/query_range'; - var dataExpected = $.param({ - query: 'test{job="testjob"}', - start: 1 * 60, - end: 3 * 60, - step: 60, - }); - var query = { - range: { from: time({ minutes: 1, seconds: 3 }), to: time({ minutes: 2, seconds: 3 }) }, - targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }], - interval: '60s', - }; - var response = { - status: 'success', - data: { - resultType: 'matrix', - result: [ - { - metric: { __name__: 'test', job: 'testjob' }, - values: [[2 * 60, '3846']], - }, - ], - }, - }; - beforeEach(function() { - ctx.$httpBackend.expectPOST(urlExpected, dataExpected).respond(response); - ctx.ds.query(query).then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - }); - it('should generate the correct query', function() { - ctx.$httpBackend.verifyNoOutstandingExpectation(); - }); - it('should return series list', function() { - expect(results.data.length).to.be(1); - expect(results.data[0].target).to.be('test{job="testjob"}'); - }); - }); -});