diff --git a/CHANGELOG.md b/CHANGELOG.md index d6d3653bcaf..cf1c3ecd2d8 100644 --- a/CHANGELOG.md +++ b/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) diff --git a/conf/defaults.ini b/conf/defaults.ini index e1619b57006..8b0b7db16ab 100644 --- a/conf/defaults.ini +++ b/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 diff --git a/conf/sample.ini b/conf/sample.ini index ee333be85c0..74beb9bd611 100644 --- a/conf/sample.ini +++ b/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 diff --git a/docs/sources/plugins/development.md b/docs/sources/plugins/development.md index 29dcd0568e3..6d2106fad01 100644 --- a/docs/sources/plugins/development.md +++ b/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. diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index 3cf6ecd787b..695158598ef 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/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"}, diff --git a/pkg/metrics/graphite.go b/pkg/metrics/graphite.go index 20b985e22ed..bbd5696c85d 100644 --- a/pkg/metrics/graphite.go +++ b/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) diff --git a/pkg/metrics/graphite_test.go b/pkg/metrics/graphite_test.go index 03b9917258a..8ecee9449a5 100644 --- a/pkg/metrics/graphite_test.go +++ b/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() { diff --git a/public/app/features/dashboard/dynamic_dashboard_srv.ts b/public/app/features/dashboard/dynamic_dashboard_srv.ts index 790c7310456..1e0c33abe72 100644 --- a/public/app/features/dashboard/dynamic_dashboard_srv.ts +++ b/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; } }); } diff --git a/public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts b/public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts index ee2fac16ba1..fbc1913ca30 100644 --- a/public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts +++ b/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() { diff --git a/public/app/features/dashboard/specs/exporter_specs.ts b/public/app/features/dashboard/specs/exporter_specs.ts index 470fab57447..37175fe25e6 100644 --- a/public/app/features/dashboard/specs/exporter_specs.ts +++ b/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 = { diff --git a/public/app/plugins/panel/graph/graph_tooltip.js b/public/app/plugins/panel/graph/graph_tooltip.js index 03c092fa4ac..70eef7c5fe3 100644 --- a/public/app/plugins/panel/graph/graph_tooltip.js +++ b/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({ diff --git a/public/test/specs/dynamicDashboardSrv-specs.js b/public/test/specs/dynamicDashboardSrv-specs.js deleted file mode 100644 index b6d3a6d52fb..00000000000 --- a/public/test/specs/dynamicDashboardSrv-specs.js +++ /dev/null @@ -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'); - }); - - }); - -});