diff --git a/CHANGELOG.md b/CHANGELOG.md
index 930a91af1da..e9beca0c761 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,9 @@
-# 3.0.0 stable (unreleased)
+# 3.0.2 Stable (unreleased)
+
+* **Templating**: Fixed issue mixing row repeat and panel repeats, fixes [#4988](https://github.com/grafana/grafana/issues/4988)
+* **Templating**: Fixed issue detecting dependencies in nested variables, fixes [#4987](https://github.com/grafana/grafana/issues/4987), fixes [#4986](https://github.com/grafana/grafana/issues/4986)
+
+# 3.0.1 Stable (2016-05-11)
* **Templating**: Fixed issue with new data source variable not persisting current selected value, fixes [#4934](https://github.com/grafana/grafana/issues/4934)
diff --git a/build.go b/build.go
index e1fc3599aa8..4347c486063 100644
--- a/build.go
+++ b/build.go
@@ -132,12 +132,10 @@ func readVersionFromPackageJson() {
if len(parts) > 1 {
linuxPackageVersion = parts[0]
linuxPackageIteration = parts[1]
- if linuxPackageIteration != "" {
- // add timestamp to iteration
- linuxPackageIteration = fmt.Sprintf("%s%v", linuxPackageIteration, time.Now().Unix())
- }
- log.Println(fmt.Sprintf("Iteration %v", linuxPackageIteration))
}
+
+ // add timestamp to iteration
+ linuxPackageIteration = fmt.Sprintf("%d%s", time.Now().Unix(), linuxPackageIteration)
}
type linuxPackageOptions struct {
diff --git a/docs/sources/guides/whats-new-in-v3.md b/docs/sources/guides/whats-new-in-v3.md
index eb1030f8f31..051691e0384 100644
--- a/docs/sources/guides/whats-new-in-v3.md
+++ b/docs/sources/guides/whats-new-in-v3.md
@@ -39,12 +39,13 @@ entire experience right within Grafana.
-A preview of [Grafana.net](http://grafana.net) is launching along with this release. We
-think it’s the perfect compliment to Grafana.
+[Grafana.net](https://grafana.net) offers a central repository where the community can come together to discover, create and
+share plugins (data sources, panels, apps) and dashboards.
-Grafana.net currently offers a central repository where the community
-can come together to discover and share plugins (Data Sources, Panels,
-Apps) and Dashboards for Grafana 3.0 and above.
+We are also working on a hosted Graphite-compatible data source that will be optimized for use with Grafana.
+It’ll be easy to combine your existing data source(s) with this OpenSaaS option. Finally, Grafana.net can
+also be a hub to manage all your Grafana instances. You’ll be able to monitor their health and availability,
+perform dashboard backups, and more.
We are also working on a hosted Graphite-compatible Data Source that
will be optimized for use with Grafana. It’ll be easy to combine your
@@ -65,7 +66,6 @@ Grafana 3.0 comes with a new command line tool called grafana-cli. You
can easily install plugins from Grafana.net with it. For
example:
-
```
grafana-cli install grafana-pie-chart-panel
```
@@ -188,6 +188,33 @@ you can still install manually from [Grafana.net](http://grafana.net)
* KairosDB: This data source has also no longer shipped with Grafana,
you can install it manually from [Grafana.net](http://grafana.net)
+## Plugin showcase
+
+Discovering and installing plugins is very quick and easy with Grafana 3.0 and [Grafana.net](https://grafana.net). Here
+are a couple that I incurage you try!
+
+#### [Clock Panel](https://grafana.net/plugins/grafana-clock-panel)
+Support's both current time and count down mode.
+
+
+#### [Pie Chart Panel](https://grafana.net/plugins/grafana-piechart-panel)
+A simple pie chart panel is now available as an external plugin.
+
+
+#### [WorldPing App](https://grafana.net/plugins/raintank-worldping-app)
+This is full blown Grafana App that adds new panels, data sources and pages to give
+feature rich global performance monitoring directly from your on-prem Grafana.
+
+
+
+#### [Zabbix App](https://grafana.net/plugins/alexanderzobnin-zabbix-app)
+This app contains the already very pouplar Zabbix data source plugin, 2 dashboards and a triggers panel. It is
+created and maintained by [Alexander Zobnin](https://github.com/alexanderzobnin/grafana-zabbix).
+
+
+
+Checkout the full list of plugins on [Grafana.net](https://grafana.net/plugins)
+
## CHANGELOG
For a detailed list and link to github issues for everything included
diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md
index cac6a7c92b8..363cd4dcf7e 100644
--- a/docs/sources/installation/debian.md
+++ b/docs/sources/installation/debian.md
@@ -10,20 +10,13 @@ page_keywords: grafana, installation, debian, ubuntu, guide
Description | Download
------------ | -------------
-Stable .deb for Debian-based Linux | [grafana_2.6.0_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb)
-Beta .deb for Debian-based Linux | [grafana_3.0.0-beta71462173753_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta71462173753_amd64.deb)
+Stable .deb for Debian-based Linux | [grafana_3.0.1_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.1_amd64.deb)
## Install Stable
- $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb
+ $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.1_amd64.deb
$ sudo apt-get install -y adduser libfontconfig
- $ sudo dpkg -i grafana_2.6.0_amd64.deb
-
-## Install 3.0 Beta
-
- $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta71462173753_amd64.deb
- $ sudo apt-get install -y adduser libfontconfig
- $ sudo dpkg -i grafana_3.0.0-beta71462173753_amd64.deb
+ $ sudo dpkg -i grafana_3.0.1_amd64.deb
## APT Repository
diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md
index 744cafe93db..a49378203c7 100644
--- a/docs/sources/installation/rpm.md
+++ b/docs/sources/installation/rpm.md
@@ -10,43 +10,24 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
Description | Download
------------ | -------------
-Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-2.6.0-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm)
-Beta .RPM for CentOS / Fedor / OpenSuse / Redhat Linux | [grafana-3.0.0-beta71462173753.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta71462173753.x86_64.rpm)
+Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-3.0.1-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.1-1.x86_64.rpm)
## Install Stable Release from package file
You can install Grafana using Yum directly.
- $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm
+ $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.1-1.x86_64.rpm
Or install manually using `rpm`.
#### On CentOS / Fedora / Redhat:
$ sudo yum install initscripts fontconfig
- $ sudo rpm -Uvh grafana-2.6.0-1.x86_64.rpm
+ $ sudo rpm -Uvh grafana-3.0.1-1.x86_64.rpm
#### On OpenSuse:
- $ sudo rpm -i --nodeps grafana-2.6.0-1.x86_64.rpm
-
-## Install Beta Release from package file
-
-You can install Grafana using Yum directly.
-
- $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta71462173753.x86_64.rpm
-
-Or install manually using `rpm`.
-
-#### On CentOS / Fedora / Redhat:
-
- $ sudo yum install initscripts fontconfig
- $ sudo rpm -Uvh grafana-3.0.0-beta71462173753.x86_64.rpm
-
-#### On OpenSuse:
-
- $ sudo rpm -i --nodeps grafana-3.0.0-beta71462173753.x86_64.rpm
-
+ $ sudo rpm -i --nodeps grafana-3.0.1-1.x86_64.rpm
## Install via YUM Repository
diff --git a/latest.json b/latest.json
index 8ddb446ec44..4afdb750b3f 100644
--- a/latest.json
+++ b/latest.json
@@ -1,4 +1,4 @@
{
- "stable": "2.6.0",
- "testing": "3.0.0-beta7"
+ "stable": "3.0.1",
+ "testing": "3.0.1"
}
diff --git a/package.json b/package.json
index 8b9ec906ed9..b9b87ab6e75 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"company": "Coding Instinct AB"
},
"name": "grafana",
- "version": "3.0.0-beta7",
+ "version": "3.0.2",
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git"
diff --git a/packaging/deb/default/grafana-server b/packaging/deb/default/grafana-server
index dd06906b903..cc5ee866f7d 100644
--- a/packaging/deb/default/grafana-server
+++ b/packaging/deb/default/grafana-server
@@ -14,6 +14,6 @@ CONF_DIR=/etc/grafana
CONF_FILE=/etc/grafana/grafana.ini
-RESTART_ON_UPGRADE=false
+RESTART_ON_UPGRADE=true
PLUGINS_DIR=/var/lib/grafana/plugins
diff --git a/packaging/publish/publish.sh b/packaging/publish/publish.sh
index 23d20b5a613..79da73d441a 100755
--- a/packaging/publish/publish.sh
+++ b/packaging/publish/publish.sh
@@ -1,7 +1,7 @@
#! /usr/bin/env bash
-deb_ver=3.0.0-beta51460658374
-rpm_ver=3.0.0-beta51460658374
+deb_ver=3.0.1
+rpm_ver=3.0.1-1
#rpm_ver=3.0.0-1
@@ -16,7 +16,7 @@ rpm_ver=3.0.0-beta51460658374
#wget https://grafanarel.s3.amazonaws.com/builds/grafana-${rpm_ver}.x86_64.rpm
#package_cloud push grafana/testing/el/6 grafana-${rpm_ver}.x86_64.rpm
-package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm
+#package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm
-# package_cloud push grafana/stable/el/7 grafana-${version}-1.x86_64.rpm
-# package_cloud push grafana/stable/el/6 grafana-${version}-1.x86_64.rpm
+package_cloud push grafana/stable/el/7 grafana-${rpm_ver}.x86_64.rpm
+package_cloud push grafana/stable/el/6 grafana-${rpm_ver}.x86_64.rpm
diff --git a/packaging/rpm/sysconfig/grafana-server b/packaging/rpm/sysconfig/grafana-server
index dd06906b903..cc5ee866f7d 100644
--- a/packaging/rpm/sysconfig/grafana-server
+++ b/packaging/rpm/sysconfig/grafana-server
@@ -14,6 +14,6 @@ CONF_DIR=/etc/grafana
CONF_FILE=/etc/grafana/grafana.ini
-RESTART_ON_UPGRADE=false
+RESTART_ON_UPGRADE=true
PLUGINS_DIR=/var/lib/grafana/plugins
diff --git a/pkg/plugins/update_checker.go b/pkg/plugins/update_checker.go
index 36169434096..ed43398357e 100644
--- a/pkg/plugins/update_checker.go
+++ b/pkg/plugins/update_checker.go
@@ -91,14 +91,14 @@ func checkForUpdates() {
resp2, err := client.Get("https://raw.githubusercontent.com/grafana/grafana/master/latest.json")
if err != nil {
- log.Trace("Failed to get lates.json repo from github: %v", err.Error())
+ log.Trace("Failed to get latest.json repo from github: %v", err.Error())
return
}
defer resp2.Body.Close()
body, err = ioutil.ReadAll(resp2.Body)
if err != nil {
- log.Trace("Update check failed, reading response from github.net, %v", err.Error())
+ log.Trace("Update check failed, reading response from github.com, %v", err.Error())
return
}
diff --git a/public/app/features/dashboard/dynamicDashboardSrv.js b/public/app/features/dashboard/dynamicDashboardSrv.js
new file mode 100644
index 00000000000..f131b47b557
--- /dev/null
+++ b/public/app/features/dashboard/dynamicDashboardSrv.js
@@ -0,0 +1,182 @@
+define([
+ 'angular',
+ 'lodash',
+],
+function (angular, _) {
+ 'use strict';
+
+ var module = angular.module('grafana.services');
+
+ module.service('dynamicDashboardSrv', function() {
+ var self = this;
+
+ this.init = function(dashboard) {
+ if (dashboard.snapshot) { return; }
+
+ this.iteration = new Date().getTime();
+ this.process(dashboard);
+ };
+
+ this.update = function(dashboard) {
+ if (dashboard.snapshot) { return; }
+
+ this.iteration = this.iteration + 1;
+ this.process(dashboard);
+ };
+
+ this.process = function(dashboard) {
+ if (dashboard.templating.list.length === 0) { return; }
+ this.dashboard = dashboard;
+
+ var i, j, row, panel;
+ for (i = 0; i < this.dashboard.rows.length; i++) {
+ row = this.dashboard.rows[i];
+ // handle row repeats
+ if (row.repeat) {
+ this.repeatRow(row, i);
+ }
+ // clean up old left overs
+ else if (row.repeatRowId && row.repeatIteration !== this.iteration) {
+ this.dashboard.rows.splice(i, 1);
+ i = i - 1;
+ continue;
+ }
+
+ // repeat panels
+ for (j = 0; j < row.panels.length; j++) {
+ panel = row.panels[j];
+ if (panel.repeat) {
+ this.repeatPanel(panel, row);
+ }
+ // clean up old left overs
+ else if (panel.repeatPanelId && panel.repeatIteration !== this.iteration) {
+ row.panels = _.without(row.panels, panel);
+ j = j - 1;
+ } else if (row.repeat || row.repeatRowId) {
+ continue;
+ } else if (!_.isEmpty(panel.scopedVars) && panel.repeatIteration !== this.iteration) {
+ panel.scopedVars = {};
+ }
+ }
+ }
+ };
+
+ // returns a new row clone or reuses a clone from previous iteration
+ this.getRowClone = function(sourceRow, repeatIndex, sourceRowIndex) {
+ if (repeatIndex === 0) {
+ return sourceRow;
+ }
+
+ var i, panel, row, copy;
+ var sourceRowId = sourceRowIndex + 1;
+
+ // look for row to reuse
+ for (i = 0; i < this.dashboard.rows.length; i++) {
+ row = this.dashboard.rows[i];
+ if (row.repeatRowId === sourceRowId && row.repeatIteration !== this.iteration) {
+ copy = row;
+ break;
+ }
+ }
+
+ if (!copy) {
+ copy = angular.copy(sourceRow);
+ this.dashboard.rows.splice(sourceRowIndex + repeatIndex, 0, copy);
+
+ // set new panel ids
+ for (i = 0; i < copy.panels.length; i++) {
+ panel = copy.panels[i];
+ panel.id = this.dashboard.getNextPanelId();
+ }
+ }
+
+ copy.repeat = null;
+ copy.repeatRowId = sourceRowId;
+ copy.repeatIteration = this.iteration;
+ return copy;
+ };
+
+ // returns a new row clone or reuses a clone from previous iteration
+ this.repeatRow = function(row, rowIndex) {
+ var variables = this.dashboard.templating.list;
+ var variable = _.findWhere(variables, {name: row.repeat});
+ if (!variable) {
+ return;
+ }
+
+ var selected, copy, i, panel;
+ if (variable.current.text === 'All') {
+ selected = variable.options.slice(1, variable.options.length);
+ } else {
+ selected = _.filter(variable.options, {selected: true});
+ }
+
+ _.each(selected, function(option, index) {
+ copy = self.getRowClone(row, index, rowIndex);
+ copy.scopedVars = {};
+ copy.scopedVars[variable.name] = option;
+
+ for (i = 0; i < copy.panels.length; i++) {
+ panel = copy.panels[i];
+ panel.scopedVars = {};
+ panel.scopedVars[variable.name] = option;
+ }
+ }, this);
+ };
+
+ this.getPanelClone = function(sourcePanel, row, index) {
+ // if first clone return source
+ if (index === 0) {
+ return sourcePanel;
+ }
+
+ var i, tmpId, panel, clone;
+
+ // first try finding an existing clone to use
+ for (i = 0; i < row.panels.length; i++) {
+ panel = row.panels[i];
+ if (panel.repeatIteration !== this.iteration && panel.repeatPanelId === sourcePanel.id) {
+ clone = panel;
+ break;
+ }
+ }
+
+ if (!clone) {
+ clone = { id: this.dashboard.getNextPanelId() };
+ row.panels.push(clone);
+ }
+
+ // save id
+ tmpId = clone.id;
+ // copy properties from source
+ angular.copy(sourcePanel, clone);
+ // restore id
+ clone.id = tmpId;
+ clone.repeatIteration = this.iteration;
+ clone.repeatPanelId = sourcePanel.id;
+ clone.repeat = null;
+ return clone;
+ };
+
+ this.repeatPanel = function(panel, row) {
+ var variables = this.dashboard.templating.list;
+ var variable = _.findWhere(variables, {name: panel.repeat});
+ if (!variable) { return; }
+
+ var selected;
+ if (variable.current.text === 'All') {
+ selected = variable.options.slice(1, variable.options.length);
+ } else {
+ selected = _.filter(variable.options, {selected: true});
+ }
+
+ _.each(selected, function(option, index) {
+ var copy = self.getPanelClone(panel, row, index);
+ copy.span = Math.max(12 / selected.length, panel.minSpan);
+ copy.scopedVars = copy.scopedVars || {};
+ copy.scopedVars[variable.name] = option;
+ });
+ };
+
+ });
+});
diff --git a/public/app/features/templating/templateSrv.js b/public/app/features/templating/templateSrv.js
index f60414eac43..7e96af22e2a 100644
--- a/public/app/features/templating/templateSrv.js
+++ b/public/app/features/templating/templateSrv.js
@@ -97,7 +97,8 @@ function (angular, _) {
if (!str) {
return false;
}
- return str.indexOf('$' + variableName) !== -1 || str.indexOf('[[' + variableName + ']]') !== -1;
+ var match = this._regex.exec(str);
+ return match && (match[1] === variableName || match[2] === variableName);
};
this.highlightVariablesAsHtml = function(str) {
diff --git a/public/app/features/templating/templateValuesSrv.js b/public/app/features/templating/templateValuesSrv.js
index 472c24aef61..d44f07ed366 100644
--- a/public/app/features/templating/templateValuesSrv.js
+++ b/public/app/features/templating/templateValuesSrv.js
@@ -204,7 +204,7 @@ function (angular, _, kbn) {
}
if (options.length === 0) {
- options.push({text: 'No datasurces found', value: ''});
+ options.push({text: 'No data sources found', value: ''});
}
variable.options = options;
diff --git a/public/fonts/grafana-icons.eot b/public/fonts/grafana-icons.eot
index 4e594eca65d..88bc142e205 100755
Binary files a/public/fonts/grafana-icons.eot and b/public/fonts/grafana-icons.eot differ
diff --git a/public/fonts/grafana-icons.svg b/public/fonts/grafana-icons.svg
index 319e1739324..9006ea24078 100755
--- a/public/fonts/grafana-icons.svg
+++ b/public/fonts/grafana-icons.svg
@@ -31,10 +31,10 @@
-
-
+
+
-
+
@@ -46,14 +46,17 @@
-
+
-
+
+
+
+
\ No newline at end of file
diff --git a/public/fonts/grafana-icons.ttf b/public/fonts/grafana-icons.ttf
index 2cfefbff196..50b2a0303f6 100755
Binary files a/public/fonts/grafana-icons.ttf and b/public/fonts/grafana-icons.ttf differ
diff --git a/public/fonts/grafana-icons.woff b/public/fonts/grafana-icons.woff
index 9c1f1bc848c..a4dde357152 100755
Binary files a/public/fonts/grafana-icons.woff and b/public/fonts/grafana-icons.woff differ
diff --git a/public/sass/base/_grafana_icons.scss b/public/sass/base/_grafana_icons.scss
index f7f3a0368ee..f3264d409cd 100644
--- a/public/sass/base/_grafana_icons.scss
+++ b/public/sass/base/_grafana_icons.scss
@@ -1,10 +1,10 @@
@font-face {
font-family: 'grafana-icons';
- src: url('../fonts/grafana-icons.eot?h6rv8b');
- src: url('../fonts/grafana-icons.eot?h6rv8b#iefix') format('embedded-opentype'),
- url('../fonts/grafana-icons.ttf?h6rv8b') format('truetype'),
- url('../fonts/grafana-icons.woff?h6rv8b') format('woff'),
- url('../fonts/grafana-icons.svg?h6rv8b#grafana-icons') format('svg');
+ src: url('../fonts/grafana-icons.eot?okx5td');
+ src: url('../fonts/grafana-icons.eot?okx5td#iefix') format('embedded-opentype'),
+ url('../fonts/grafana-icons.ttf?okx5td') format('truetype'),
+ url('../fonts/grafana-icons.woff?okx5td') format('woff'),
+ url('../fonts/grafana-icons.svg?okx5td#grafana-icons') format('svg');
font-weight: normal;
font-style: normal;
}
@@ -61,6 +61,9 @@
.icon-gf-endpoint:before {
content: "\e609";
}
+.icon-gf-page:before {
+ content: "\e908";
+}
.icon-gf-filter:before {
content: "\e60a";
}
@@ -112,9 +115,6 @@
.icon-gf-save:before {
content: "\e614";
}
-.icon-gf-settings:before {
- content: "\e615";
-}
.icon-gf-share:before {
content: "\e616";
}
@@ -124,10 +124,13 @@
.icon-gf-search:before {
content: "\e618";
}
-.icon-gf-tag-add:before {
+.icon-gf-settings:before {
+ content: "\e615";
+}
+.icon-gf-add:before {
content: "\e619";
}
-.icon-gf-tag-remove:before {
+.icon-gf-remove:before {
content: "\e61a";
}
.icon-gf-video:before {
@@ -169,6 +172,12 @@
.icon-gf-scale:before {
content: "\e906";
}
+.icon-gf-pending:before {
+ content: "\e909";
+}
+.icon-gf-verified:before {
+ content: "\e90a";
+}
.icon-gf-worldping:before {
content: "\e627";
}
@@ -176,10 +185,3 @@
content: "\e903";
}
-.icon-gf-app:before {
- content: "\e902";
-}
-.icon-gf-datasource:before {
- content: "\e607";
-}
-
diff --git a/public/test/specs/dynamicDashboardSrv-specs.js b/public/test/specs/dynamicDashboardSrv-specs.js
new file mode 100644
index 00000000000..a09c3788377
--- /dev/null
+++ b/public/test/specs/dynamicDashboardSrv-specs.js
@@ -0,0 +1,268 @@
+define([
+ 'app/features/dashboard/dynamicDashboardSrv',
+ '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(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');
+ });
+
+ });
+
+});
diff --git a/public/test/specs/templateSrv-specs.js b/public/test/specs/templateSrv-specs.js
index 3334d7a7bfe..fd7247cbd81 100644
--- a/public/test/specs/templateSrv-specs.js
+++ b/public/test/specs/templateSrv-specs.js
@@ -190,6 +190,11 @@ define([
expect(contains).to.be(true);
});
+ it('should not find it if only part matches with $var syntax', function() {
+ var contains = _templateSrv.containsVariable('this.$ServerDomain.filters', 'Server');
+ expect(contains).to.be(false);
+ });
+
it('should find it with [[var]] syntax', function() {
var contains = _templateSrv.containsVariable('this.[[test]].filters', 'test');
expect(contains).to.be(true);