Browse Source

Merge remote-tracking branch 'upstream/master' into postgres-query-builder

Sven Klemm 7 years ago
parent
commit
5c3b80b492

+ 3 - 1
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)

+ 1 - 1
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,

+ 3 - 1
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"},

+ 1 - 1
public/app/core/components/Forms/Forms.tsx

@@ -12,7 +12,7 @@ export const Label: SFC<Props> = props => {
     <span className="gf-form-label width-10">
       <span>{props.children}</span>
       {props.tooltip && (
-        <Tooltip className="gf-form-help-icon--right-normal" placement="auto" content="hello">
+        <Tooltip className="gf-form-help-icon--right-normal" placement="auto" content={props.tooltip}>
           <i className="gicon gicon-question gicon--has-hover" />
         </Tooltip>
       )}

+ 0 - 1
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);
       });
 

+ 808 - 14
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 = <any>{};
+let instanceSettings = {
+  url: 'proxied',
+  directUrl: 'direct',
+  user: 'test',
+  password: 'mupp',
+  jsonData: { httpMethod: 'GET' },
+};
+let backendSrv = <any>{
+  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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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, <any>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"}');
+    });
+  });
+});

+ 0 - 683
public/app/plugins/datasource/prometheus/specs/datasource_specs.ts

@@ -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"}');
-    });
-  });
-});