Просмотр исходного кода

Merge remote-tracking branch 'upstream/master' into dashboard_permissions

Daniel Lee 8 лет назад
Родитель
Сommit
a9e2273064

+ 1 - 0
docs/Dockerfile

@@ -9,5 +9,6 @@ FROM grafana/docs-base:latest
 
 COPY config.toml /site
 COPY awsconfig /site
+COPY versions.json /site/static/js
 
 VOLUME ["/site/content"]

+ 1 - 1
docs/sources/administration/provisioning.md

@@ -3,6 +3,7 @@ title = "Provisioning"
 description = ""
 keywords = ["grafana", "provisioning"]
 type = "docs"
+aliases = ["/installation/provisioning"]
 [menu.docs]
 parent = "admin"
 weight = 8
@@ -66,7 +67,6 @@ Tool | Project
 -----|------------
 Puppet | [https://forge.puppet.com/puppet/grafana](https://forge.puppet.com/puppet/grafana)
 Ansible | [https://github.com/cloudalchemy/ansible-grafana](https://github.com/cloudalchemy/ansible-grafana)
-Ansible | [https://github.com/picotrading/ansible-grafana](https://github.com/picotrading/ansible-grafana)
 Chef | [https://github.com/JonathanTron/chef-grafana](https://github.com/JonathanTron/chef-grafana)
 Saltstack | [https://github.com/salt-formulas/salt-formula-grafana](https://github.com/salt-formulas/salt-formula-grafana)
 

+ 1 - 1
docs/sources/features/index.md

@@ -5,7 +5,7 @@ type = "docs"
 [menu.docs]
 name = "Features"
 identifier = "features"
-weight = 3
+weight = 4
 +++
 
 

+ 1 - 0
docs/sources/guides/basic_concepts.md

@@ -7,6 +7,7 @@ type = "docs"
 name = "Basic Concepts"
 identifier = "basic_concepts"
 parent = "guides"
+weight = 2
 +++
 
 # Basic Concepts

+ 2 - 1
docs/sources/guides/getting_started.md

@@ -8,6 +8,7 @@ aliases = ["/guides/gettingstarted"]
 name = "Getting Started"
 identifier = "getting_started_guide"
 parent = "guides"
+weight = 1
 +++
 
 # Getting started
@@ -24,7 +25,7 @@ Read the [Basic Concepts](/guides/basic_concepts) document to get a crash course
 
 ### Top header
 
-Let's start with creating a new Dashboard. You can find the new Dashboard link on the right side of the Dashboard picker. You now have a blank Dashboard. 
+Let's start with creating a new Dashboard. You can find the new Dashboard link on the right side of the Dashboard picker. You now have a blank Dashboard.
 
 <img class="no-shadow" src="/img/docs/v45/top_nav_annotated.png">
 

+ 1 - 1
docs/sources/guides/index.md

@@ -4,6 +4,6 @@ type = "docs"
 [menu.docs]
 name = "Getting Started"
 identifier = "guides"
-weight = 2
+weight = 3
 +++
 

+ 1 - 5
docs/sources/index.md

@@ -4,10 +4,6 @@ description = "Install guide for Grafana"
 keywords = ["grafana", "installation", "documentation"]
 type = "docs"
 aliases = ["v1.1", "guides/reference/admin"]
-[menu.docs]
-name = "Welcome to the Docs"
-identifier = "root"
-weight = -1
 +++
 
 # Welcome to the Grafana Documentation
@@ -22,7 +18,7 @@ other domains including industrial sensors, home automation, weather, and proces
 - [Installing on Mac OS X](installation/mac)
 - [Installing on Windows](installation/windows)
 - [Installing on Docker](installation/docker)
-- [Installing using Provisioning (Chef, Puppet, Salt, Ansible, etc)](installation/provisioning)
+- [Installing using Provisioning (Chef, Puppet, Salt, Ansible, etc)](administration/provisioning#configuration-management-tools)
 - [Nightly Builds](https://grafana.com/grafana/download)
 
 For other platforms Read the [build from source]({{< relref "project/building_from_source.md" >}})

+ 1 - 0
docs/sources/installation/index.md

@@ -7,6 +7,7 @@ aliases = ["installation/installation/", "v2.1/installation/install/"]
 [menu.docs]
 name = "Installation"
 identifier = "installation"
+weight = 1
 +++
 
 ## Installing Grafana

+ 1 - 1
docs/sources/features/whatsnew/index.md → docs/sources/whatsnew/index.md

@@ -3,7 +3,7 @@ title = "What's New in Grafana"
 [menu.docs]
 name = "What's New In Grafana"
 identifier = "whatsnew"
-weight = 2
+weight = 3
 +++
 
 

+ 9 - 0
docs/versions.json

@@ -0,0 +1,9 @@
+[
+  { "version": "v5.0", "path": "/v5.0", "archived": false },
+  { "version": "v4.6", "path": "/",     "archived": false, "current": true },
+  { "version": "v4.5", "path": "/v4.5", "archived": true },
+  { "version": "v4.4", "path": "/v4.4", "archived": true },
+  { "version": "v4.3", "path": "/v4.3", "archived": true },
+  { "version": "v4.1", "path": "/v4.1", "archived": true },
+  { "version": "v3.1", "path": "/v3.1", "archived": true }
+]

+ 1 - 0
pkg/plugins/datasource/wrapper/datasource_plugin_wrapper.go

@@ -71,6 +71,7 @@ func (tw *DatasourcePluginWrapper) Query(ctx context.Context, ds *models.DataSou
 		qr := &tsdb.QueryResult{
 			RefId:  r.RefId,
 			Series: []*tsdb.TimeSeries{},
+			Tables: []*tsdb.Table{},
 		}
 
 		if r.Error != "" {

+ 1 - 1
pkg/plugins/datasource/wrapper/datasource_plugin_wrapper_test.go

@@ -75,7 +75,7 @@ func TestMappingRowValue(t *testing.T) {
 	boolRowValue, _ := dpw.mapRowValue(&datasource.RowValue{Kind: datasource.RowValue_TYPE_BOOL, BoolValue: true})
 	haveBool, ok := boolRowValue.(bool)
 	if !ok || haveBool != true {
-		t.Fatalf("Expected true, was %s", haveBool)
+		t.Fatalf("Expected true, was %v", haveBool)
 	}
 
 	intRowValue, _ := dpw.mapRowValue(&datasource.RowValue{Kind: datasource.RowValue_TYPE_INT64, Int64Value: 42})

+ 119 - 119
pkg/services/alerting/ticker_test.go

@@ -1,121 +1,121 @@
 package alerting
 
-import (
-	"testing"
-	"time"
-
-	"github.com/benbjohnson/clock"
-)
-
-func inspectTick(tick time.Time, last time.Time, offset time.Duration, t *testing.T) {
-	if !tick.Equal(last.Add(time.Duration(1) * time.Second)) {
-		t.Fatalf("expected a tick 1 second more than prev, %s. got: %s", last, tick)
-	}
-}
-
-// returns the new last tick seen
-func assertAdvanceUntil(ticker *Ticker, last, desiredLast time.Time, offset, wait time.Duration, t *testing.T) time.Time {
-	for {
-		select {
-		case tick := <-ticker.C:
-			inspectTick(tick, last, offset, t)
-			last = tick
-		case <-time.NewTimer(wait).C:
-			if last.Before(desiredLast) {
-				t.Fatalf("waited %s for ticker to advance to %s, but only went up to %s", wait, desiredLast, last)
-			}
-			if last.After(desiredLast) {
-				t.Fatalf("timer advanced too far. should only have gone up to %s, but it went up to %s", desiredLast, last)
-			}
-			return last
-		}
-	}
-}
-
-func assertNoAdvance(ticker *Ticker, desiredLast time.Time, wait time.Duration, t *testing.T) {
-	for {
-		select {
-		case tick := <-ticker.C:
-			t.Fatalf("timer should have stayed at %s, instead it advanced to %s", desiredLast, tick)
-		case <-time.NewTimer(wait).C:
-			return
-		}
-	}
-}
-
-func TestTickerRetro1Hour(t *testing.T) {
-	offset := time.Duration(10) * time.Second
-	last := time.Unix(0, 0)
-	mock := clock.NewMock()
-	mock.Add(time.Duration(1) * time.Hour)
-	desiredLast := mock.Now().Add(-offset)
-	ticker := NewTicker(last, offset, mock)
-
-	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t)
-	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
-
-}
-
-func TestAdvanceWithUpdateOffset(t *testing.T) {
-	offset := time.Duration(10) * time.Second
-	last := time.Unix(0, 0)
-	mock := clock.NewMock()
-	mock.Add(time.Duration(1) * time.Hour)
-	desiredLast := mock.Now().Add(-offset)
-	ticker := NewTicker(last, offset, mock)
-
-	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t)
-	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
-
-	// lowering offset should see a few more ticks
-	offset = time.Duration(5) * time.Second
-	ticker.updateOffset(offset)
-	desiredLast = mock.Now().Add(-offset)
-	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(9)*time.Millisecond, t)
-	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
-
-	// advancing clock should see even more ticks
-	mock.Add(time.Duration(1) * time.Hour)
-	desiredLast = mock.Now().Add(-offset)
-	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(8)*time.Millisecond, t)
-	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
-
-}
-
-func getCase(lastSeconds, offsetSeconds int) (time.Time, time.Duration) {
-	last := time.Unix(int64(lastSeconds), 0)
-	offset := time.Duration(offsetSeconds) * time.Second
-	return last, offset
-}
-
-func TestTickerNoAdvance(t *testing.T) {
-
-	// it's 00:01:00 now. what are some cases where we don't want the ticker to advance?
-	mock := clock.NewMock()
-	mock.Add(time.Duration(60) * time.Second)
-
-	type Case struct {
-		last   int
-		offset int
-	}
-
-	// note that some cases add up to now, others go into the future
-	cases := []Case{
-		{50, 10},
-		{50, 30},
-		{59, 1},
-		{59, 10},
-		{59, 30},
-		{60, 1},
-		{60, 10},
-		{60, 30},
-		{90, 1},
-		{90, 10},
-		{90, 30},
-	}
-	for _, c := range cases {
-		last, offset := getCase(c.last, c.offset)
-		ticker := NewTicker(last, offset, mock)
-		assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
-	}
-}
+//import (
+//	"testing"
+//	"time"
+//
+//	"github.com/benbjohnson/clock"
+//)
+//
+//func inspectTick(tick time.Time, last time.Time, offset time.Duration, t *testing.T) {
+//	if !tick.Equal(last.Add(time.Duration(1) * time.Second)) {
+//		t.Fatalf("expected a tick 1 second more than prev, %s. got: %s", last, tick)
+//	}
+//}
+//
+//// returns the new last tick seen
+//func assertAdvanceUntil(ticker *Ticker, last, desiredLast time.Time, offset, wait time.Duration, t *testing.T) time.Time {
+//	for {
+//		select {
+//		case tick := <-ticker.C:
+//			inspectTick(tick, last, offset, t)
+//			last = tick
+//		case <-time.NewTimer(wait).C:
+//			if last.Before(desiredLast) {
+//				t.Fatalf("waited %s for ticker to advance to %s, but only went up to %s", wait, desiredLast, last)
+//			}
+//			if last.After(desiredLast) {
+//				t.Fatalf("timer advanced too far. should only have gone up to %s, but it went up to %s", desiredLast, last)
+//			}
+//			return last
+//		}
+//	}
+//}
+//
+//func assertNoAdvance(ticker *Ticker, desiredLast time.Time, wait time.Duration, t *testing.T) {
+//	for {
+//		select {
+//		case tick := <-ticker.C:
+//			t.Fatalf("timer should have stayed at %s, instead it advanced to %s", desiredLast, tick)
+//		case <-time.NewTimer(wait).C:
+//			return
+//		}
+//	}
+//}
+//
+//func TestTickerRetro1Hour(t *testing.T) {
+//	offset := time.Duration(10) * time.Second
+//	last := time.Unix(0, 0)
+//	mock := clock.NewMock()
+//	mock.Add(time.Duration(1) * time.Hour)
+//	desiredLast := mock.Now().Add(-offset)
+//	ticker := NewTicker(last, offset, mock)
+//
+//	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t)
+//	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
+//
+//}
+//
+//func TestAdvanceWithUpdateOffset(t *testing.T) {
+//	offset := time.Duration(10) * time.Second
+//	last := time.Unix(0, 0)
+//	mock := clock.NewMock()
+//	mock.Add(time.Duration(1) * time.Hour)
+//	desiredLast := mock.Now().Add(-offset)
+//	ticker := NewTicker(last, offset, mock)
+//
+//	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t)
+//	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
+//
+//	// lowering offset should see a few more ticks
+//	offset = time.Duration(5) * time.Second
+//	ticker.updateOffset(offset)
+//	desiredLast = mock.Now().Add(-offset)
+//	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(9)*time.Millisecond, t)
+//	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
+//
+//	// advancing clock should see even more ticks
+//	mock.Add(time.Duration(1) * time.Hour)
+//	desiredLast = mock.Now().Add(-offset)
+//	last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(8)*time.Millisecond, t)
+//	assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
+//
+//}
+//
+//func getCase(lastSeconds, offsetSeconds int) (time.Time, time.Duration) {
+//	last := time.Unix(int64(lastSeconds), 0)
+//	offset := time.Duration(offsetSeconds) * time.Second
+//	return last, offset
+//}
+//
+//func TestTickerNoAdvance(t *testing.T) {
+//
+//	// it's 00:01:00 now. what are some cases where we don't want the ticker to advance?
+//	mock := clock.NewMock()
+//	mock.Add(time.Duration(60) * time.Second)
+//
+//	type Case struct {
+//		last   int
+//		offset int
+//	}
+//
+//	// note that some cases add up to now, others go into the future
+//	cases := []Case{
+//		{50, 10},
+//		{50, 30},
+//		{59, 1},
+//		{59, 10},
+//		{59, 30},
+//		{60, 1},
+//		{60, 10},
+//		{60, 30},
+//		{90, 1},
+//		{90, 10},
+//		{90, 30},
+//	}
+//	for _, c := range cases {
+//		last, offset := getCase(c.last, c.offset)
+//		ticker := NewTicker(last, offset, mock)
+//		assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
+//	}
+//}

+ 5 - 1
public/app/core/utils/url.ts

@@ -12,7 +12,11 @@ export function toUrlParams(a) {
 
   let add = function(k, v) {
     v = typeof v === 'function' ? v() : v === null ? '' : v === undefined ? '' : v;
-    s[s.length] = encodeURIComponent(k) + '=' + encodeURIComponent(v);
+    if (typeof v !== 'boolean') {
+      s[s.length] = encodeURIComponent(k) + '=' + encodeURIComponent(v);
+    } else {
+      s[s.length] = encodeURIComponent(k);
+    }
   };
 
   let buildParams = function(prefix, obj) {

+ 21 - 9
public/app/features/dashboard/dashboard_model.ts

@@ -352,16 +352,10 @@ export class DashboardModel {
       copy.scopedVars[variable.name] = option;
 
       if (panel.repeatDirection === REPEAT_DIR_VERTICAL) {
-        copy.gridPos.y = yPos;
-        yPos += copy.gridPos.h;
-
-        // Update gridPos for panels below
-        let panelBelowIndex = panelIndex + index + 1;
-        for (let i = panelBelowIndex; i < this.panels.length; i++) {
-          if (this.panels[i].gridPos.y < yPos) {
-            this.panels[i].gridPos.y += copy.gridPos.h;
-          }
+        if (index > 0) {
+          yPos += copy.gridPos.h;
         }
+        copy.gridPos.y = yPos;
       } else {
         // set width based on how many are selected
         // assumed the repeated panels should take up full row width
@@ -378,6 +372,15 @@ export class DashboardModel {
         }
       }
     }
+
+    // Update gridPos for panels below
+    let yOffset = yPos - panel.gridPos.y;
+    if (yOffset > 0) {
+      let panelBelowIndex = panelIndex + selectedOptions.length;
+      for (let i = panelBelowIndex; i < this.panels.length; i++) {
+        this.panels[i].gridPos.y += yOffset;
+      }
+    }
   }
 
   repeatRow(panel: PanelModel, panelIndex: number, variable) {
@@ -566,6 +569,7 @@ export class DashboardModel {
 
     if (row.collapsed) {
       row.collapsed = false;
+      let hasRepeat = false;
 
       if (row.panels.length > 0) {
         // Use first panel to figure out if it was moved or pushed
@@ -586,6 +590,10 @@ export class DashboardModel {
           // update insert post and y max
           insertPos += 1;
           yMax = Math.max(yMax, panel.gridPos.y + panel.gridPos.h);
+
+          if (panel.repeat) {
+            hasRepeat = true;
+          }
         }
 
         const pushDownAmount = yMax - row.gridPos.y;
@@ -596,6 +604,10 @@ export class DashboardModel {
         }
 
         row.panels = [];
+
+        if (hasRepeat) {
+          this.processRepeats();
+        }
       }
 
       // sort panels

+ 133 - 0
public/app/features/dashboard/specs/repeat.jest.ts

@@ -4,6 +4,57 @@ import { expect } from 'test/lib/common';
 
 jest.mock('app/core/services/context_srv', () => ({}));
 
+describe('given dashboard with panel repeat', function() {
+  var dashboard;
+
+  beforeEach(function() {
+    let dashboardJSON = {
+      panels: [
+        { id: 1, type: 'row', gridPos: { x: 0, y: 0, h: 1, w: 24 } },
+        { id: 2, repeat: 'apps', repeatDirection: 'h', gridPos: { x: 0, y: 1, h: 2, w: 8 } },
+      ],
+      templating: {
+        list: [
+          {
+            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 },
+            ],
+          },
+        ],
+      },
+    };
+    dashboard = new DashboardModel(dashboardJSON);
+    dashboard.processRepeats();
+  });
+
+  it('should repeat panels when row is expanding', function() {
+    expect(dashboard.panels.length).toBe(4);
+
+    // toggle row
+    dashboard.toggleRow(dashboard.panels[0]);
+    expect(dashboard.panels.length).toBe(1);
+
+    // change variable
+    dashboard.templating.list[0].options[2].selected = false;
+    dashboard.templating.list[0].current = {
+      text: 'se1, se2',
+      value: ['se1', 'se2'],
+    };
+
+    // toggle row back
+    dashboard.toggleRow(dashboard.panels[0]);
+    expect(dashboard.panels.length).toBe(3);
+  });
+});
+
 describe('given dashboard with panel repeat in horizontal direction', function() {
   var dashboard;
 
@@ -178,6 +229,88 @@ describe('given dashboard with panel repeat in vertical direction', function() {
   });
 });
 
+describe('given dashboard with row repeat and panel repeat in horizontal direction', () => {
+  let dashboard, dashboardJSON;
+
+  beforeEach(() => {
+    dashboardJSON = {
+      panels: [
+        { id: 1, type: 'row', repeat: 'region', gridPos: { x: 0, y: 0, h: 1, w: 24 } },
+        { id: 2, type: 'graph', repeat: 'app', gridPos: { x: 0, y: 1, h: 2, w: 6 } },
+      ],
+      templating: {
+        list: [
+          {
+            name: 'region',
+            current: {
+              text: 'reg1, reg2',
+              value: ['reg1', 'reg2'],
+            },
+            options: [{ text: 'reg1', value: 'reg1', selected: true }, { text: 'reg2', value: 'reg2', selected: true }],
+          },
+          {
+            name: 'app',
+            current: {
+              text: 'se1, se2, se3, se4, se5, se6',
+              value: ['se1', 'se2', 'se3', 'se4', 'se5', 'se6'],
+            },
+            options: [
+              { text: 'se1', value: 'se1', selected: true },
+              { text: 'se2', value: 'se2', selected: true },
+              { text: 'se3', value: 'se3', selected: true },
+              { text: 'se4', value: 'se4', selected: true },
+              { text: 'se5', value: 'se5', selected: true },
+              { text: 'se6', value: 'se6', selected: true },
+            ],
+          },
+        ],
+      },
+    };
+    dashboard = new DashboardModel(dashboardJSON);
+    dashboard.processRepeats(false);
+  });
+
+  it('should panels in self row', () => {
+    const panel_types = _.map(dashboard.panels, 'type');
+    expect(panel_types).toEqual([
+      'row',
+      'graph',
+      'graph',
+      'graph',
+      'graph',
+      'graph',
+      'graph',
+      'row',
+      'graph',
+      'graph',
+      'graph',
+      'graph',
+      'graph',
+      'graph',
+    ]);
+  });
+
+  it('should be placed in their places', function() {
+    expect(dashboard.panels[0].gridPos).toMatchObject({ x: 0, y: 0, h: 1, w: 24 }); // 1st row
+
+    expect(dashboard.panels[1].gridPos).toMatchObject({ x: 0, y: 1, h: 2, w: 6 });
+    expect(dashboard.panels[2].gridPos).toMatchObject({ x: 6, y: 1, h: 2, w: 6 });
+    expect(dashboard.panels[3].gridPos).toMatchObject({ x: 12, y: 1, h: 2, w: 6 });
+    expect(dashboard.panels[4].gridPos).toMatchObject({ x: 18, y: 1, h: 2, w: 6 });
+    expect(dashboard.panels[5].gridPos).toMatchObject({ x: 0, y: 3, h: 2, w: 6 }); // next row
+    expect(dashboard.panels[6].gridPos).toMatchObject({ x: 6, y: 3, h: 2, w: 6 });
+
+    expect(dashboard.panels[7].gridPos).toMatchObject({ x: 0, y: 5, h: 1, w: 24 });
+
+    expect(dashboard.panels[8].gridPos).toMatchObject({ x: 0, y: 6, h: 2, w: 6 }); // 2nd row
+    expect(dashboard.panels[9].gridPos).toMatchObject({ x: 6, y: 6, h: 2, w: 6 });
+    expect(dashboard.panels[10].gridPos).toMatchObject({ x: 12, y: 6, h: 2, w: 6 });
+    expect(dashboard.panels[11].gridPos).toMatchObject({ x: 18, y: 6, h: 2, w: 6 }); // next row
+    expect(dashboard.panels[12].gridPos).toMatchObject({ x: 0, y: 8, h: 2, w: 6 });
+    expect(dashboard.panels[13].gridPos).toMatchObject({ x: 6, y: 8, h: 2, w: 6 });
+  });
+});
+
 describe('given dashboard with row repeat', function() {
   let dashboard, dashboardJSON;
 

+ 1 - 0
public/app/features/playlist/playlist_search.ts

@@ -12,6 +12,7 @@ export class PlaylistSearchCtrl {
 
     $timeout(() => {
       this.query.query = '';
+      this.query.type = 'dash-db';
       this.searchDashboards();
     }, 100);
   }

+ 5 - 5
public/app/plugins/datasource/cloudwatch/partials/query.parameter.html

@@ -1,6 +1,6 @@
 <div class="gf-form-inline">
 	<div class="gf-form">
-		<label class="gf-form-label query-keyword width-7">Metric</label>
+		<label class="gf-form-label query-keyword width-8">Metric</label>
 
 		<metric-segment segment="regionSegment" get-options="getRegions()" on-change="regionChanged()"></metric-segment>
 		<metric-segment segment="namespaceSegment" get-options="getNamespaces()" on-change="namespaceChanged()"></metric-segment>
@@ -22,7 +22,7 @@
 
 <div class="gf-form-inline">
 	<div class="gf-form">
-		<label class="gf-form-label query-keyword width-7">Dimensions</label>
+		<label class="gf-form-label query-keyword width-8">Dimensions</label>
 		<metric-segment ng-repeat="segment in dimSegments" segment="segment" get-options="getDimSegments(segment, $index)" on-change="dimSegmentChanged(segment, $index)"></metric-segment>
 	</div>
 
@@ -33,9 +33,9 @@
 
 <div class="gf-form-inline">
 	<div class="gf-form">
-		<label class="gf-form-label query-keyword width-7">
-			Period
-			<info-popover mode="right-normal">Interval between points in seconds</info-popover>
+		<label class="gf-form-label query-keyword width-8">
+			Min period
+			<info-popover mode="right-normal">Minimum interval between points in seconds</info-popover>
 		</label>
 		<input type="text" class="gf-form-input" ng-model="target.period" spellcheck='false' placeholder="auto" ng-model-onblur ng-change="onChange()" />
 	</div>

+ 6 - 6
public/app/plugins/panel/alertlist/module.html

@@ -1,15 +1,15 @@
 <div class="panel-alert-list">
-	<div class="panel-alert-list__no-alerts" ng-show="ctrl.noAlertsMessage">
-		{{ctrl.noAlertsMessage}}
-	</div>
+  <div class="panel-alert-list__no-alerts" ng-show="ctrl.noAlertsMessage">
+    {{ctrl.noAlertsMessage}}
+  </div>
 
   <section ng-if="ctrl.panel.show === 'current'">
     <ol class="alert-rule-list">
       <li class="alert-rule-item" ng-repeat="alert in ctrl.currentAlerts">
+        <div class="alert-rule-item__icon  {{alert.stateModel.stateClass}}">
+          <i class="{{alert.stateModel.iconClass}}"></i>
+        </div>
         <div class="alert-rule-item__body">
-          <div class="alert-rule-item__icon  {{alert.stateModel.stateClass}}">
-            <i class="{{alert.stateModel.iconClass}}"></i>
-          </div>
           <div class="alert-rule-item__header">
             <p class="alert-rule-item__name">
               <a href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&tab=alert">

+ 6 - 0
public/app/stores/ViewStore/ViewStore.jest.ts

@@ -24,4 +24,10 @@ describe('ViewStore', () => {
     expect(toJS(store.query.get('values'))).toMatchObject(['A', 'B']);
     expect(store.currentUrl).toBe('/hello?values=A&values=B');
   });
+
+  it('Query can contain boolean', () => {
+    store.updatePathAndQuery('/hello', { abool: true });
+    expect(toJS(store.query.get('abool'))).toBe(true);
+    expect(store.currentUrl).toBe('/hello?abool');
+  });
 });

+ 1 - 0
public/sass/_variables.dark.scss

@@ -135,6 +135,7 @@ $list-item-bg: $card-background;
 $list-item-hover-bg: lighten($gray-blue, 2%);
 $list-item-link-color: $text-color;
 $list-item-shadow: $card-shadow;
+$empty-list-cta-bg: $gray-blue;
 
 // Scrollbars
 $scrollbarBackground: #404357;

+ 1 - 0
public/sass/_variables.light.scss

@@ -133,6 +133,7 @@ $list-item-bg: linear-gradient(135deg, $gray-5, $gray-6); //$card-background;
 $list-item-hover-bg: darken($gray-5, 5%);
 $list-item-link-color: $text-color;
 $list-item-shadow: $card-shadow;
+$empty-list-cta-bg: $gray-6;
 
 // Tables
 // -------------------------

+ 1 - 1
public/sass/components/_empty_list_cta.scss

@@ -1,5 +1,5 @@
 .empty-list-cta {
-  background-color: $search-filter-box-bg;
+  background-color: $empty-list-cta-bg;
   text-align: center;
   padding: $spacer*2;
   border-radius: $border-radius;