Ver código fonte

Merge branch 'master' into alerting

bergquist 9 anos atrás
pai
commit
4d0982a21c

+ 6 - 1
CHANGELOG.md

@@ -7,7 +7,12 @@
 * **Templating**: Update panel repeats for variables that change on time refresh, closes [#5021](https://github.com/grafana/grafana/issues/5021)
 * **Elasticsearch**: Support to set Precision Threshold for Unique Count metric, closes [#4689](https://github.com/grafana/grafana/issues/4689)
 
-# 3.1.1 (unreleased / v3.1.x branch)
+# 3.1.2 (unreleased)
+* **Templating**: Fixed issue when combining row & panel repeats, fixes [#5790](https://github.com/grafana/grafana/issues/5790)
+* **Drag&Drop**: Fixed issue with drag and drop in latest Chrome(51+), fixes [#5767](https://github.com/grafana/grafana/issues/5767)
+* **Internal Metrics**: Fixed issue with dots in instance_name when sending internal metrics to Graphitge, fixes [#5739](https://github.com/grafana/grafana/issues/5739)
+
+# 3.1.1 (2016-08-01)
 * **IFrame embedding**: Fixed issue of using full iframe height, fixes [#5605](https://github.com/grafana/grafana/issues/5606)
 * **Panel PNG rendering**: Fixed issue detecting render completion, fixes [#5605](https://github.com/grafana/grafana/issues/5606)
 * **Elasticsearch**: Fixed issue with templating query and json parse error, fixes [#5615](https://github.com/grafana/grafana/issues/5615)

+ 1 - 1
conf/defaults.ini

@@ -378,7 +378,7 @@ interval_seconds  = 60
 # Send internal Grafana metrics to graphite
 ; [metrics.graphite]
 ; address = localhost:2003
-; prefix = service.grafana.%(instance_name)s
+; prefix = service.grafana.%(instance_name)s.
 
 [grafana_net]
 url = https://grafana.net

+ 1 - 1
conf/sample.ini

@@ -306,7 +306,7 @@ enabled           = true
 # Send internal metrics to Graphite
 ; [metrics.graphite]
 ; address = localhost:2003
-; prefix = service.grafana.%(instance_name)s
+; prefix = service.grafana.%(instance_name)s.
 
 #################################### Internal Grafana Metrics ##########################
 # Url used to to import dashboards directly from Grafana.net

+ 1 - 1
docs/sources/plugins/development.md

@@ -32,7 +32,7 @@ will be expected to export different things. You can find what's expected for [d
 and [apps](./apps.md) plugins in the documentation.
 
 ## Start developing your plugin
-There are two ways that you can start developing a Grafana plugin.
+There are three ways that you can start developing a Grafana plugin.
 
 1. Setup a Grafana development environment. [(described here)](http://docs.grafana.org/project/building_from_source/) and place your plugin in the ```data/plugins``` folder.
 2. Install Grafana and place your plugin in the plugins directory which is set in your [config file](../installation/configuration.md). By default this is `/var/lib/grafana/plugins` on Linux systems.

+ 13 - 11
pkg/api/cloudwatch/metrics.go

@@ -28,17 +28,18 @@ var customMetricsDimensionsMap map[string]map[string]map[string]*CustomMetricsCa
 
 func init() {
 	metricsMap = map[string][]string{
-		"AWS/AutoScaling": {"GroupMinSize", "GroupMaxSize", "GroupDesiredCapacity", "GroupInServiceInstances", "GroupPendingInstances", "GroupStandbyInstances", "GroupTerminatingInstances", "GroupTotalInstances"},
-		"AWS/Billing":     {"EstimatedCharges"},
-		"AWS/CloudFront":  {"Requests", "BytesDownloaded", "BytesUploaded", "TotalErrorRate", "4xxErrorRate", "5xxErrorRate"},
-		"AWS/CloudSearch": {"SuccessfulRequests", "SearchableDocuments", "IndexUtilization", "Partitions"},
-		"AWS/DynamoDB":    {"ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "OnlineIndexConsumedWriteCapacity", "OnlineIndexPercentageProgress", "OnlineIndexThrottleEvents", "ProvisionedReadCapacityUnits", "ProvisionedWriteCapacityUnits", "ReadThrottleEvents", "ReturnedBytes", "ReturnedItemCount", "ReturnedRecordsCount", "SuccessfulRequestLatency", "SystemErrors", "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"},
-		"AWS/EC2Spot":     {"AvailableInstancePoolsCount", "BidsSubmittedForCapacity", "EligibleInstancePoolCount", "FulfilledCapacity", "MaxPercentCapacityAllocation", "PendingCapacity", "PercentCapacityAllocation", "TargetCapacity", "TerminatingCapacity"},
-		"AWS/ECS":         {"CPUReservation", "MemoryReservation", "CPUUtilization", "MemoryUtilization"},
-		"AWS/EFS":         {"BurstCreditBalance", "ClientConnections", "DataReadIOBytes", "DataWriteIOBytes", "MetadataIOBytes", "TotalIOBytes", "PermittedThroughput", "PercentIOLimit"},
-		"AWS/ELB":         {"HealthyHostCount", "UnHealthyHostCount", "RequestCount", "Latency", "HTTPCode_ELB_4XX", "HTTPCode_ELB_5XX", "HTTPCode_Backend_2XX", "HTTPCode_Backend_3XX", "HTTPCode_Backend_4XX", "HTTPCode_Backend_5XX", "BackendConnectionErrors", "SurgeQueueLength", "SpilloverCount"},
+		"AWS/ApplicationELB": {"ActiveConnectionCount", "ClientTLSNegotiationErrorCount", "HealthyHostCount", "HTTPCode_ELB_4XX_Count", "HTTPCode_ELB_5XX_Count", "HTTPCode_Target_2XX_Count", "HTTPCode_Target_3XX_Count", "HTTPCode_Target_4XX_Count", "HTTPCode_Target_5XX_Count", "NewConnectionCount", "ProcessedBytes", "RejectedConnectionCount", "RequestCount", "TargetConnectionErrorCount", "TargetResponseTime", "TargetTLSNegotiationErrorCount", "UnhealthyHostCount"},
+		"AWS/AutoScaling":    {"GroupMinSize", "GroupMaxSize", "GroupDesiredCapacity", "GroupInServiceInstances", "GroupPendingInstances", "GroupStandbyInstances", "GroupTerminatingInstances", "GroupTotalInstances"},
+		"AWS/Billing":        {"EstimatedCharges"},
+		"AWS/CloudFront":     {"Requests", "BytesDownloaded", "BytesUploaded", "TotalErrorRate", "4xxErrorRate", "5xxErrorRate"},
+		"AWS/CloudSearch":    {"SuccessfulRequests", "SearchableDocuments", "IndexUtilization", "Partitions"},
+		"AWS/DynamoDB":       {"ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "OnlineIndexConsumedWriteCapacity", "OnlineIndexPercentageProgress", "OnlineIndexThrottleEvents", "ProvisionedReadCapacityUnits", "ProvisionedWriteCapacityUnits", "ReadThrottleEvents", "ReturnedBytes", "ReturnedItemCount", "ReturnedRecordsCount", "SuccessfulRequestLatency", "SystemErrors", "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"},
+		"AWS/EC2Spot":        {"AvailableInstancePoolsCount", "BidsSubmittedForCapacity", "EligibleInstancePoolCount", "FulfilledCapacity", "MaxPercentCapacityAllocation", "PendingCapacity", "PercentCapacityAllocation", "TargetCapacity", "TerminatingCapacity"},
+		"AWS/ECS":            {"CPUReservation", "MemoryReservation", "CPUUtilization", "MemoryUtilization"},
+		"AWS/EFS":            {"BurstCreditBalance", "ClientConnections", "DataReadIOBytes", "DataWriteIOBytes", "MetadataIOBytes", "TotalIOBytes", "PermittedThroughput", "PercentIOLimit"},
+		"AWS/ELB":            {"HealthyHostCount", "UnHealthyHostCount", "RequestCount", "Latency", "HTTPCode_ELB_4XX", "HTTPCode_ELB_5XX", "HTTPCode_Backend_2XX", "HTTPCode_Backend_3XX", "HTTPCode_Backend_4XX", "HTTPCode_Backend_5XX", "BackendConnectionErrors", "SurgeQueueLength", "SpilloverCount"},
 		"AWS/ElastiCache": {
 			"CPUUtilization", "FreeableMemory", "NetworkBytesIn", "NetworkBytesOut", "SwapUsage",
 			"BytesUsedForCacheItems", "BytesReadIntoMemcached", "BytesWrittenOutFromMemcached", "CasBadval", "CasHits", "CasMisses", "CmdFlush", "CmdGet", "CmdSet", "CurrConnections", "CurrItems", "DecrHits", "DecrMisses", "DeleteHits", "DeleteMisses", "Evictions", "GetHits", "GetMisses", "IncrHits", "IncrMisses", "Reclaimed",
@@ -86,6 +87,7 @@ func init() {
 		"AWS/WorkSpaces": {"Available", "Unhealthy", "ConnectionAttempt", "ConnectionSuccess", "ConnectionFailure", "SessionLaunchTime", "InSessionLatency", "SessionDisconnect"},
 	}
 	dimensionsMap = map[string][]string{
+		"AWS/ApplicationELB":   {"LoadBalancer", "TargetGroup", "AvailabilityZone"},
 		"AWS/AutoScaling":      {"AutoScalingGroupName"},
 		"AWS/Billing":          {"ServiceName", "LinkedAccount", "Currency"},
 		"AWS/CloudFront":       {"DistributionId", "Region"},

+ 1 - 1
pkg/metrics/graphite.go

@@ -33,7 +33,7 @@ func CreateGraphitePublisher() (*GraphitePublisher, error) {
 	prefix := graphiteSection.Key("prefix").Value()
 
 	if prefix == "" {
-		prefix = "service.grafana.%(instance_name)s"
+		prefix = "service.grafana.%(instance_name)s."
 	}
 
 	publisher.prefix = strings.Replace(prefix, "%(instance_name)s", safeInstanceName, -1)

+ 2 - 1
pkg/metrics/graphite_test.go

@@ -20,7 +20,7 @@ func TestGraphitePublisher(t *testing.T) {
 
 		sec, err := setting.Cfg.NewSection("metrics.graphite")
 		sec.NewKey("prefix", "service.grafana.%(instance_name)s.")
-		sec.NewKey("address", "localhost:2003")
+		sec.NewKey("address", "localhost:2001")
 
 		So(err, ShouldBeNil)
 
@@ -31,6 +31,7 @@ func TestGraphitePublisher(t *testing.T) {
 		So(publisher, ShouldNotBeNil)
 
 		So(publisher.prefix, ShouldEqual, "service.grafana.hostname_with_dots_com.")
+		So(publisher.address, ShouldEqual, "localhost:2001")
 	})
 
 	Convey("Test graphite publisher default values", t, func() {

+ 10 - 4
public/app/features/dashboard/dynamic_dashboard_srv.ts

@@ -27,10 +27,19 @@ export class DynamicDashboardSrv {
     this.iteration = (this.iteration || new Date().getTime()) + 1;
 
     var cleanUpOnly = options.cleanUpOnly;
-
     var i, j, row, panel;
+
+    // cleanup scopedVars
     for (i = 0; i < this.dashboard.rows.length; i++) {
       row = this.dashboard.rows[i];
+      for (j = 0; j < row.panels.length; j++) {
+        delete row.panels[j].scopedVars;
+      }
+    }
+
+    for (i = 0; i < this.dashboard.rows.length; i++) {
+      row = this.dashboard.rows[i];
+
       // handle row repeats
       if (row.repeat) {
         if (!cleanUpOnly) {
@@ -54,8 +63,6 @@ export class DynamicDashboardSrv {
           // clean up old left overs
           row.panels = _.without(row.panels, panel);
           j = j - 1;
-        } else if (!_.isEmpty(panel.scopedVars) && panel.repeatIteration !== this.iteration) {
-          panel.scopedVars = {};
         }
       }
     }
@@ -120,7 +127,6 @@ export class DynamicDashboardSrv {
         panel = copy.panels[i];
         panel.scopedVars = {};
         panel.scopedVars[variable.name] = option;
-        panel.repeatIteration = this.iteration;
       }
     });
   }

+ 22 - 2
public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts

@@ -93,10 +93,29 @@ dynamicDashScenario('given dashboard with panel repeat', function(ctx) {
     });
   });
 
+  describe('After a second iteration with different variable', function() {
+    beforeEach(function() {
+      ctx.dash.templating.list.push({
+        name: 'server',
+        current: { text: 'se1, se2, se3', value: ['se1']},
+        options: [{text: 'se1', value: 'se1', selected: true}]
+      });
+      ctx.rows[0].panels[0].repeat = "server";
+      ctx.dynamicDashboardSrv.update(ctx.dash);
+    });
+
+    it('should remove scopedVars value for last variable', function() {
+      expect(ctx.rows[0].panels[0].scopedVars.apps).to.be(undefined);
+    });
+
+    it('should have new variable value in scopedVars', function() {
+      expect(ctx.rows[0].panels[0].scopedVars.server.value).to.be("se1");
+    });
+  });
+
   describe('After a second iteration and selected values reduced', function() {
     beforeEach(function() {
       ctx.dash.templating.list[0].options[1].selected = false;
-
       ctx.dynamicDashboardSrv.update(ctx.dash);
     });
 
@@ -116,7 +135,7 @@ dynamicDashScenario('given dashboard with panel repeat', function(ctx) {
     });
 
     it('should remove scoped vars from reused panel', function() {
-      expect(ctx.rows[0].panels[0].scopedVars).to.be.empty();
+      expect(ctx.rows[0].panels[0].scopedVars).to.be(undefined);
     });
   });
 
@@ -165,6 +184,7 @@ dynamicDashScenario('given dashboard with row repeat', function(ctx) {
 
   it('should generate a repeartRowId based on repeat row index', function() {
     expect(ctx.rows[1].repeatRowId).to.be(1);
+    expect(ctx.rows[1].repeatIteration).to.be(ctx.dynamicDashboardSrv.iteration);
   });
 
   it('should set scopedVars on row panels', function() {

+ 2 - 1
public/app/features/dashboard/specs/exporter_specs.ts

@@ -47,7 +47,8 @@ describe('given dashboard with repeated panels', function() {
     });
     dash.rows.push({
       repeat: null,
-      repeatRowId: 1
+      repeatRowId: 1,
+      panels: [],
     });
 
     var datasourceSrvStub = {

+ 0 - 1
public/app/plugins/panel/graph/graph_tooltip.js

@@ -83,7 +83,6 @@ function ($, _) {
           // Stacked series can increase its length on each new stacked serie if null points found,
           // to speed the index search we begin always on the last found hoverIndex.
           hoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex);
-          hoverDistance = Math.abs(pos.x - series.data[hoverIndex][0]);
         }
 
         results.push({

+ 0 - 269
public/test/specs/dynamicDashboardSrv-specs.js

@@ -1,269 +0,0 @@
-define([
-  'app/features/dashboard/dynamic_dashboard_srv',
-  'app/features/dashboard/dashboardSrv'
-], function() {
-  'use strict';
-
-  function dynamicDashScenario(desc, func)  {
-
-    describe(desc, function() {
-      var ctx = {};
-
-      ctx.setup = function (setupFunc) {
-
-        beforeEach(module('grafana.services'));
-        beforeEach(module('grafana.core'));
-        beforeEach(module(function($provide) {
-          $provide.value('contextSrv', {
-            user: { timezone: 'utc'}
-          });
-        }));
-
-        beforeEach(inject(function(dynamicDashboardSrv, dashboardSrv) {
-          ctx.dynamicDashboardSrv = dynamicDashboardSrv;
-          ctx.dashboardSrv = dashboardSrv;
-
-          var model = {
-            rows: [],
-            templating: { list: [] }
-          };
-
-          setupFunc(model);
-          ctx.dash = ctx.dashboardSrv.create(model);
-          ctx.dynamicDashboardSrv.init(ctx.dash);
-          ctx.rows = ctx.dash.rows;
-        }));
-      };
-
-      func(ctx);
-    });
-  }
-
-  dynamicDashScenario('given dashboard with panel repeat', function(ctx) {
-    ctx.setup(function(dash) {
-      dash.rows.push({
-        panels: [{id: 2, repeat: 'apps'}]
-      });
-      dash.templating.list.push({
-        name: 'apps',
-        current: {
-          text: 'se1, se2, se3',
-          value: ['se1', 'se2', 'se3']
-        },
-        options: [
-        {text: 'se1', value: 'se1', selected: true},
-        {text: 'se2', value: 'se2', selected: true},
-        {text: 'se3', value: 'se3', selected: true},
-        {text: 'se4', value: 'se4', selected: false}
-        ]
-      });
-    });
-
-    it('should repeat panel one time', function() {
-      expect(ctx.rows[0].panels.length).to.be(3);
-    });
-
-    it('should mark panel repeated', function() {
-      expect(ctx.rows[0].panels[0].repeat).to.be('apps');
-      expect(ctx.rows[0].panels[1].repeatPanelId).to.be(2);
-    });
-
-    it('should set scopedVars on panels', function() {
-      expect(ctx.rows[0].panels[0].scopedVars.apps.value).to.be('se1');
-      expect(ctx.rows[0].panels[1].scopedVars.apps.value).to.be('se2');
-      expect(ctx.rows[0].panels[2].scopedVars.apps.value).to.be('se3');
-    });
-
-    describe('After a second iteration', function() {
-      var repeatedPanelAfterIteration1;
-
-      beforeEach(function() {
-        repeatedPanelAfterIteration1 = ctx.rows[0].panels[1];
-        ctx.rows[0].panels[0].fill = 10;
-        ctx.dynamicDashboardSrv.update(ctx.dash);
-      });
-
-      it('should have reused same panel instances', function() {
-        expect(ctx.rows[0].panels[1]).to.be(repeatedPanelAfterIteration1);
-      });
-
-      it('reused panel should copy properties from source', function() {
-        expect(ctx.rows[0].panels[1].fill).to.be(10);
-      });
-
-      it('should have same panel count', function() {
-        expect(ctx.rows[0].panels.length).to.be(3);
-      });
-    });
-
-    describe('After a second iteration and selected values reduced', function() {
-      beforeEach(function() {
-        ctx.dash.templating.list[0].options[1].selected = false;
-
-        ctx.dynamicDashboardSrv.update(ctx.dash);
-      });
-
-      it('should clean up repeated panel', function() {
-        expect(ctx.rows[0].panels.length).to.be(2);
-      });
-    });
-
-    describe('After a second iteration and panel repeat is turned off', function() {
-      beforeEach(function() {
-        ctx.rows[0].panels[0].repeat = null;
-        ctx.dynamicDashboardSrv.update(ctx.dash);
-      });
-
-      it('should clean up repeated panel', function() {
-        expect(ctx.rows[0].panels.length).to.be(1);
-      });
-
-      it('should remove scoped vars from reused panel', function() {
-        expect(ctx.rows[0].panels[0].scopedVars).to.be.empty();
-      });
-    });
-
-  });
-
-  dynamicDashScenario('given dashboard with row repeat', function(ctx) {
-    ctx.setup(function(dash) {
-      dash.rows.push({
-        repeat: 'servers',
-        panels: [{id: 2}]
-      });
-      dash.rows.push({panels: []});
-      dash.templating.list.push({
-        name: 'servers',
-        current: {
-          text: 'se1, se2',
-          value: ['se1', 'se2']
-        },
-        options: [
-          {text: 'se1', value: 'se1', selected: true},
-          {text: 'se2', value: 'se2', selected: true},
-        ]
-      });
-    });
-
-    it('should repeat row one time', function() {
-      expect(ctx.rows.length).to.be(3);
-    });
-
-    it('should keep panel ids on first row', function() {
-      expect(ctx.rows[0].panels[0].id).to.be(2);
-    });
-
-    it('should keep first row as repeat', function() {
-      expect(ctx.rows[0].repeat).to.be('servers');
-    });
-
-    it('should clear repeat field on repeated row', function() {
-      expect(ctx.rows[1].repeat).to.be(null);
-    });
-
-    it('should add scopedVars to rows', function() {
-      expect(ctx.rows[0].scopedVars.servers.value).to.be('se1');
-      expect(ctx.rows[1].scopedVars.servers.value).to.be('se2');
-    });
-
-    it('should generate a repeartRowId based on repeat row index', function() {
-      expect(ctx.rows[1].repeatRowId).to.be(1);
-      expect(ctx.rows[1].repeatIteration).to.be(ctx.dynamicDashboardSrv.iteration);
-    });
-
-    it('should set scopedVars on row panels', function() {
-      expect(ctx.rows[0].panels[0].scopedVars.servers.value).to.be('se1');
-      expect(ctx.rows[1].panels[0].scopedVars.servers.value).to.be('se2');
-    });
-
-    describe('After a second iteration', function() {
-      var repeatedRowAfterFirstIteration;
-
-      beforeEach(function() {
-        repeatedRowAfterFirstIteration = ctx.rows[1];
-        ctx.rows[0].height = 500;
-        ctx.dynamicDashboardSrv.update(ctx.dash);
-      });
-
-      it('should still only have 2 rows', function() {
-        expect(ctx.rows.length).to.be(3);
-      });
-
-      it.skip('should have updated props from source', function() {
-        expect(ctx.rows[1].height).to.be(500);
-      });
-
-      it('should reuse row instance', function() {
-        expect(ctx.rows[1]).to.be(repeatedRowAfterFirstIteration);
-      });
-    });
-
-    describe('After a second iteration and selected values reduced', function() {
-      beforeEach(function() {
-        ctx.dash.templating.list[0].options[1].selected = false;
-        ctx.dynamicDashboardSrv.update(ctx.dash);
-      });
-
-      it('should remove repeated second row', function() {
-        expect(ctx.rows.length).to.be(2);
-      });
-    });
-  });
-
-  dynamicDashScenario('given dashboard with row repeat and panel repeat', function(ctx) {
-    ctx.setup(function(dash) {
-      dash.rows.push({
-        repeat: 'servers',
-        panels: [{id: 2, repeat: 'metric'}]
-      });
-      dash.templating.list.push({
-        name: 'servers',
-        current: { text: 'se1, se2', value: ['se1', 'se2'] },
-        options: [
-          {text: 'se1', value: 'se1', selected: true},
-          {text: 'se2', value: 'se2', selected: true},
-        ]
-      });
-      dash.templating.list.push({
-        name: 'metric',
-        current: { text: 'm1, m2', value: ['m1', 'm2'] },
-        options: [
-          {text: 'm1', value: 'm1', selected: true},
-          {text: 'm2', value: 'm2', selected: true},
-        ]
-      });
-    });
-
-    it('should repeat row one time', function() {
-      expect(ctx.rows.length).to.be(2);
-    });
-
-    it('should repeat panel on both rows', function() {
-      expect(ctx.rows[0].panels.length).to.be(2);
-      expect(ctx.rows[1].panels.length).to.be(2);
-    });
-
-    it('should keep panel ids on first row', function() {
-      expect(ctx.rows[0].panels[0].id).to.be(2);
-    });
-
-    it('should mark second row as repeated', function() {
-      expect(ctx.rows[0].repeat).to.be('servers');
-    });
-
-    it('should clear repeat field on repeated row', function() {
-      expect(ctx.rows[1].repeat).to.be(null);
-    });
-
-    it('should generate a repeartRowId based on repeat row index', function() {
-      expect(ctx.rows[1].repeatRowId).to.be(1);
-    });
-
-    it('should set scopedVars on row panels', function() {
-      expect(ctx.rows[0].panels[0].scopedVars.servers.value).to.be('se1');
-      expect(ctx.rows[1].panels[0].scopedVars.servers.value).to.be('se2');
-    });
-
-  });
-
-});