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);