Forráskód Böngészése

react: progress on react containers and mobx-state-tree store

Torkel Ödegaard 8 éve
szülő
commit
4181602b78
100 módosított fájl, 2445 hozzáadás és 3025 törlés
  1. 2 0
      .editorconfig
  2. 1 1
      docs/sources/features/panels/singlestat.md
  3. 2 2
      docs/sources/http_api/user.md
  4. 5 0
      package.json
  5. 3 0
      pkg/api/api.go
  6. 17 0
      pkg/api/dtos/models.go
  7. 5 0
      pkg/api/team.go
  8. 5 0
      pkg/api/team_members.go
  9. 7 2
      pkg/models/team.go
  10. 6 5
      pkg/models/team_member.go
  11. 2 1
      pkg/services/sqlstore/dashboard.go
  12. 6 1
      pkg/services/sqlstore/dashboard_acl.go
  13. 5 0
      pkg/services/sqlstore/migrations/team_mig.go
  14. 4 4
      pkg/services/sqlstore/search_builder.go
  15. 1 1
      pkg/services/sqlstore/stats.go
  16. 5 0
      pkg/services/sqlstore/team.go
  17. 4 2
      pkg/services/sqlstore/team_test.go
  18. 73 89
      public/app/app.ts
  19. 49 0
      public/app/containers/ServerStats.tsx
  20. 1 1
      public/app/core/app_events.ts
  21. 37 21
      public/app/core/components/PageHeader/PageHeader.tsx
  22. 40 42
      public/app/core/components/code_editor/code_editor.ts
  23. 6 7
      public/app/core/components/colorpicker/spectrum_picker.ts
  24. 7 7
      public/app/core/components/dashboard_selector.ts
  25. 33 45
      public/app/core/components/form_dropdown/form_dropdown.ts
  26. 7 7
      public/app/core/components/gf_page.ts
  27. 105 121
      public/app/core/components/grafana_app.ts
  28. 39 39
      public/app/core/components/help/help.ts
  29. 22 22
      public/app/core/components/info_popover.ts
  30. 15 19
      public/app/core/components/json_explorer/helpers.ts
  31. 40 75
      public/app/core/components/json_explorer/json_explorer.ts
  32. 10 10
      public/app/core/components/jsontree/jsontree.ts
  33. 20 20
      public/app/core/components/layout_selector/layout_selector.ts
  34. 17 15
      public/app/core/components/manage_dashboards/manage_dashboards.html
  35. 54 68
      public/app/core/components/manage_dashboards/manage_dashboards.ts
  36. 14 14
      public/app/core/components/navbar/navbar.ts
  37. 9 11
      public/app/core/components/org_switcher.ts
  38. 17 17
      public/app/core/components/query_part/query_part.ts
  39. 33 42
      public/app/core/components/query_part/query_part_editor.ts
  40. 8 8
      public/app/core/components/scroll/scroll.ts
  41. 22 38
      public/app/core/components/search/search.ts
  42. 14 14
      public/app/core/components/search/search_results.ts
  43. 27 41
      public/app/core/components/sidemenu/sidemenu.ts
  44. 11 11
      public/app/core/components/switch.ts
  45. 15 17
      public/app/core/components/team_picker.ts
  46. 15 17
      public/app/core/components/user_picker.ts
  47. 5 5
      public/app/core/config.ts
  48. 1 1
      public/app/core/constants.ts
  49. 7 7
      public/app/core/controllers/error_ctrl.ts
  50. 12 20
      public/app/core/controllers/inspect_ctrl.ts
  51. 19 23
      public/app/core/controllers/invited_ctrl.ts
  52. 4 5
      public/app/core/controllers/json_editor_ctrl.ts
  53. 21 23
      public/app/core/controllers/login_ctrl.ts
  54. 15 22
      public/app/core/controllers/reset_password_ctrl.ts
  55. 16 24
      public/app/core/controllers/signup_ctrl.ts
  56. 58 59
      public/app/core/core.ts
  57. 9 9
      public/app/core/directives/array_join.ts
  58. 16 16
      public/app/core/directives/diff-view.ts
  59. 2 2
      public/app/core/directives/give_focus.ts
  60. 58 74
      public/app/core/directives/misc.ts
  61. 18 18
      public/app/core/directives/ng_model_on_blur.ts
  62. 7 12
      public/app/core/directives/rebuild_on_change.ts
  63. 83 83
      public/app/core/directives/tags.ts
  64. 16 16
      public/app/core/filters/filters.ts
  65. 17 22
      public/app/core/live/live_srv.ts
  66. 3 3
      public/app/core/mod_defs.d.ts
  67. 9 9
      public/app/core/nav_model_srv.ts
  68. 20 44
      public/app/core/profiler.ts
  69. 0 56
      public/app/core/routes/dashboard_loaders.ts
  70. 0 304
      public/app/core/routes/routes.ts
  71. 33 40
      public/app/core/services/alert_srv.ts
  72. 9 9
      public/app/core/services/analytics.ts
  73. 53 66
      public/app/core/services/backend_srv.ts
  74. 9 12
      public/app/core/services/context_srv.ts
  75. 8 14
      public/app/core/services/dynamic_directive_srv.ts
  76. 15 11
      public/app/core/services/global_event_srv.ts
  77. 5 5
      public/app/core/services/impression_srv.ts
  78. 65 65
      public/app/core/services/keybindingSrv.ts
  79. 28 49
      public/app/core/services/ng_react.ts
  80. 9 9
      public/app/core/services/popover_srv.ts
  81. 32 32
      public/app/core/services/search_srv.ts
  82. 3 3
      public/app/core/services/timer.ts
  83. 7 7
      public/app/core/services/util_srv.ts
  84. 9 15
      public/app/core/specs/backend_srv_specs.ts
  85. 51 72
      public/app/core/specs/datemath.jest.ts
  86. 17 17
      public/app/core/specs/emitter.jest.ts
  87. 11 11
      public/app/core/specs/flatten.jest.ts
  88. 10 10
      public/app/core/specs/global_event_srv.jest.ts
  89. 230 235
      public/app/core/specs/kbn.jest.ts
  90. 164 168
      public/app/core/specs/manage_dashboards.jest.ts
  91. 13 16
      public/app/core/specs/org_switcher.jest.ts
  92. 66 69
      public/app/core/specs/rangeutil.jest.ts
  93. 56 61
      public/app/core/specs/search.jest.ts
  94. 30 30
      public/app/core/specs/search_results.jest.ts
  95. 75 89
      public/app/core/specs/search_srv.jest.ts
  96. 21 21
      public/app/core/specs/store.jest.ts
  97. 8 8
      public/app/core/specs/table_model.jest.ts
  98. 99 107
      public/app/core/specs/time_series.jest.ts
  99. 52 59
      public/app/core/specs/value_select_dropdown_specs.ts
  100. 1 1
      public/app/core/store.ts

+ 2 - 0
.editorconfig

@@ -7,6 +7,8 @@ indent_size = 2
 charset = utf-8
 trim_trailing_whitespace = true
 insert_final_newline = true
+max_line_length = 120
+insert_final_newline = true
 
 [*.go]
 indent_style = tab

+ 1 - 1
docs/sources/features/panels/singlestat.md

@@ -47,7 +47,7 @@ The coloring options of the Singlestat Panel config allow you to dynamically cha
 2. **Thresholds**: Change the background and value colors dynamically within the panel, depending on the Singlestat value. The threshold field accepts **2 comma-separated** values which represent 3 ranges that correspond to the three colors directly to the right. For example: if the thresholds are 70, 90 then the first color represents < 70, the second color represents between 70 and 90 and the third color represents > 90.
 3. **Colors**: Select a color and opacity
 4. **Value**: This checkbox applies the configured thresholds and colors to the summary stat.
-5. **Invert order**: This link toggles the threshold color order.</br>For example: Green, Orange, Red (<img class="no-shadow" src="/img/docs(v1/gyr.png">) will become Red, Orange, Green (<img class="no-shadow" src="/img/docs/v1/ryg.png">).
+5. **Invert order**: This link toggles the threshold color order.</br>For example: Green, Orange, Red (<img class="no-shadow" src="/img/docs/v1/gyr.png">) will become Red, Orange, Green (<img class="no-shadow" src="/img/docs/v1/ryg.png">).
 
 ### Spark Lines
 

+ 2 - 2
docs/sources/http_api/user.md

@@ -156,7 +156,7 @@ HTTP/1.1 200
 Content-Type: application/json
 
 {
-  "email": "user@mygraf.com"
+  "email": "user@mygraf.com",
   "name": "admin",
   "login": "admin",
   "theme": "light",
@@ -409,4 +409,4 @@ HTTP/1.1 200
 Content-Type: application/json
 
 {"message":"Dashboard unstarred"}
-```
+```

+ 5 - 0
package.json

@@ -115,6 +115,11 @@
       "git add"
     ]
   },
+  "prettier": {
+    "trailingComma": "es5",
+    "singleQuote": true,
+    "printWidth": 120
+  },
   "license": "Apache-2.0",
   "dependencies": {
     "angular": "^1.6.6",

+ 3 - 0
pkg/api/api.go

@@ -40,8 +40,11 @@ func (hs *HttpServer) registerRoutes() {
 	r.Get("/datasources/", reqSignedIn, Index)
 	r.Get("/datasources/new", reqSignedIn, Index)
 	r.Get("/datasources/edit/*", reqSignedIn, Index)
+	r.Get("/org/users", reqSignedIn, Index)
 	r.Get("/org/users/new", reqSignedIn, Index)
 	r.Get("/org/users/invite", reqSignedIn, Index)
+	r.Get("/org/teams", reqSignedIn, Index)
+	r.Get("/org/teams/*", reqSignedIn, Index)
 	r.Get("/org/apikeys/", reqSignedIn, Index)
 	r.Get("/dashboard/import/", reqSignedIn, Index)
 	r.Get("/configuration", reqGrafanaAdmin, Index)

+ 17 - 0
pkg/api/dtos/models.go

@@ -3,6 +3,7 @@ package dtos
 import (
 	"crypto/md5"
 	"fmt"
+	"regexp"
 	"strings"
 
 	"github.com/grafana/grafana/pkg/components/simplejson"
@@ -57,3 +58,19 @@ func GetGravatarUrl(text string) string {
 	hasher.Write([]byte(strings.ToLower(text)))
 	return fmt.Sprintf(setting.AppSubUrl+"/avatar/%x", hasher.Sum(nil))
 }
+
+func GetGravatarUrlWithDefault(text string, defaultText string) string {
+	if text != "" {
+		return GetGravatarUrl(text)
+	}
+
+	reg, err := regexp.Compile("[^a-zA-Z0-9]+")
+
+	if err != nil {
+		return ""
+	}
+
+	text = reg.ReplaceAllString(defaultText, "") + "@localhost"
+
+	return GetGravatarUrl(text)
+}

+ 5 - 0
pkg/api/team.go

@@ -1,6 +1,7 @@
 package api
 
 import (
+	"github.com/grafana/grafana/pkg/api/dtos"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
@@ -70,6 +71,10 @@ func SearchTeams(c *middleware.Context) Response {
 		return ApiError(500, "Failed to search Teams", err)
 	}
 
+	for _, team := range query.Result.Teams {
+		team.AvatarUrl = dtos.GetGravatarUrlWithDefault(team.Email, team.Name)
+	}
+
 	query.Result.Page = page
 	query.Result.PerPage = perPage
 

+ 5 - 0
pkg/api/team_members.go

@@ -1,6 +1,7 @@
 package api
 
 import (
+	"github.com/grafana/grafana/pkg/api/dtos"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
@@ -15,6 +16,10 @@ func GetTeamMembers(c *middleware.Context) Response {
 		return ApiError(500, "Failed to get Team Members", err)
 	}
 
+	for _, member := range query.Result {
+		member.AvatarUrl = dtos.GetGravatarUrl(member.Email)
+	}
+
 	return Json(200, query.Result)
 }
 

+ 7 - 2
pkg/models/team.go

@@ -16,6 +16,7 @@ type Team struct {
 	Id    int64  `json:"id"`
 	OrgId int64  `json:"orgId"`
 	Name  string `json:"name"`
+	Email string `json:"email"`
 
 	Created time.Time `json:"created"`
 	Updated time.Time `json:"updated"`
@@ -26,14 +27,16 @@ type Team struct {
 
 type CreateTeamCommand struct {
 	Name  string `json:"name" binding:"Required"`
+	Email string `json:"email"`
 	OrgId int64  `json:"-"`
 
 	Result Team `json:"-"`
 }
 
 type UpdateTeamCommand struct {
-	Id   int64
-	Name string
+	Id    int64
+	Name  string
+	Email string
 }
 
 type DeleteTeamCommand struct {
@@ -64,6 +67,8 @@ type SearchTeamDto struct {
 	Id          int64  `json:"id"`
 	OrgId       int64  `json:"orgId"`
 	Name        string `json:"name"`
+	Email       string `json:"email"`
+	AvatarUrl   string `json:"avatarUrl"`
 	MemberCount int64  `json:"memberCount"`
 }
 

+ 6 - 5
pkg/models/team_member.go

@@ -47,9 +47,10 @@ type GetTeamMembersQuery struct {
 // Projections and DTOs
 
 type TeamMemberDTO struct {
-	OrgId  int64  `json:"orgId"`
-	TeamId int64  `json:"teamId"`
-	UserId int64  `json:"userId"`
-	Email  string `json:"email"`
-	Login  string `json:"login"`
+	OrgId     int64  `json:"orgId"`
+	TeamId    int64  `json:"teamId"`
+	UserId    int64  `json:"userId"`
+	Email     string `json:"email"`
+	Login     string `json:"login"`
+	AvatarUrl string `json:"avatarUrl"`
 }

+ 2 - 1
pkg/services/sqlstore/dashboard.go

@@ -345,8 +345,9 @@ func GetDashboards(query *m.GetDashboardsQuery) error {
 
 func GetDashboardsByPluginId(query *m.GetDashboardsByPluginIdQuery) error {
 	var dashboards = make([]*m.Dashboard, 0)
+	whereExpr := "org_id=? AND plugin_id=? AND is_folder=" + dialect.BooleanStr(false)
 
-	err := x.Where("org_id=? AND plugin_id=? AND is_folder=0", query.OrgId, query.PluginId).Find(&dashboards)
+	err := x.Where(whereExpr, query.OrgId, query.PluginId).Find(&dashboards)
 	query.Result = dashboards
 
 	if err != nil {

+ 6 - 1
pkg/services/sqlstore/dashboard_acl.go

@@ -170,7 +170,12 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
 			FROM dashboard_acl as da,
         dashboard as dash
         LEFT JOIN dashboard folder on dash.folder_id = folder.id
-			WHERE dash.id = ? AND (dash.has_acl = 0 or folder.has_acl = 0) AND da.dashboard_id = -1
+			WHERE
+				dash.id = ? AND (
+					dash.has_acl = ` + dialect.BooleanStr(false) + ` or
+					folder.has_acl = ` + dialect.BooleanStr(false) + `
+				) AND
+				da.dashboard_id = -1
 	`
 
 	query.Result = make([]*m.DashboardAclInfoDTO, 0)

+ 5 - 0
pkg/services/sqlstore/migrations/team_mig.go

@@ -45,4 +45,9 @@ func addTeamMigrations(mg *Migrator) {
 	//-------  indexes ------------------
 	mg.AddMigration("add index team_member.org_id", NewAddIndexMigration(teamMemberV1, teamMemberV1.Indices[0]))
 	mg.AddMigration("add unique index team_member_org_id_team_id_user_id", NewAddIndexMigration(teamMemberV1, teamMemberV1.Indices[1]))
+
+	// add column email
+	mg.AddMigration("Add column email to team table", NewAddColumnMigration(teamV1, &Column{
+		Name: "email", Type: DB_NVarchar, Nullable: true, Length: 190,
+	}))
 }

+ 4 - 4
pkg/services/sqlstore/search_builder.go

@@ -175,14 +175,14 @@ func (sb *SearchBuilder) buildSearchWhereClause() {
 	}
 
 	if sb.signedInUser.OrgRole != m.ROLE_ADMIN {
-		allowedDashboardsSubQuery := ` AND (dashboard.has_acl = 0 OR dashboard.id in (
+		allowedDashboardsSubQuery := ` AND (dashboard.has_acl = ` + dialect.BooleanStr(false) + ` OR dashboard.id in (
 			SELECT distinct d.id AS DashboardId
 			FROM dashboard AS d
 	      		LEFT JOIN dashboard_acl as da on d.folder_id = da.dashboard_id or d.id = da.dashboard_id
 	      		LEFT JOIN team_member as ugm on ugm.team_id =  da.team_id
 	      		LEFT JOIN org_user ou on ou.role = da.role
 			WHERE
-			  d.has_acl = 1 and
+			  d.has_acl = ` + dialect.BooleanStr(true) + ` and
 				(da.user_id = ? or ugm.user_id = ? or ou.id is not null)
 			  and d.org_id = ?
 			)
@@ -198,11 +198,11 @@ func (sb *SearchBuilder) buildSearchWhereClause() {
 	}
 
 	if sb.whereTypeFolder {
-		sb.sql.WriteString(" AND dashboard.is_folder = 1")
+		sb.sql.WriteString(" AND dashboard.is_folder = " + dialect.BooleanStr(true))
 	}
 
 	if sb.whereTypeDash {
-		sb.sql.WriteString(" AND dashboard.is_folder = 0")
+		sb.sql.WriteString(" AND dashboard.is_folder = " + dialect.BooleanStr(false))
 	}
 
 	if len(sb.whereFolderIds) > 0 {

+ 1 - 1
pkg/services/sqlstore/stats.go

@@ -13,7 +13,7 @@ func init() {
 	bus.AddHandler("sql", GetAdminStats)
 }
 
-var activeUserTimeLimit time.Duration = time.Hour * 24 * 14
+var activeUserTimeLimit time.Duration = time.Hour * 24 * 30
 
 func GetDataSourceStats(query *m.GetDataSourceStatsQuery) error {
 	var rawSql = `SELECT COUNT(*) as count, type FROM data_source GROUP BY type`

+ 5 - 0
pkg/services/sqlstore/team.go

@@ -33,6 +33,7 @@ func CreateTeam(cmd *m.CreateTeamCommand) error {
 
 		team := m.Team{
 			Name:    cmd.Name,
+			Email:   cmd.Email,
 			OrgId:   cmd.OrgId,
 			Created: time.Now(),
 			Updated: time.Now(),
@@ -57,9 +58,12 @@ func UpdateTeam(cmd *m.UpdateTeamCommand) error {
 
 		team := m.Team{
 			Name:    cmd.Name,
+			Email:   cmd.Email,
 			Updated: time.Now(),
 		}
 
+		sess.MustCols("email")
+
 		affectedRows, err := sess.Id(cmd.Id).Update(&team)
 
 		if err != nil {
@@ -125,6 +129,7 @@ func SearchTeams(query *m.SearchTeamsQuery) error {
 	sql.WriteString(`select
 		team.id as id,
 		team.name as name,
+		team.email as email,
 		(select count(*) from team_member where team_member.team_id = team.id) as member_count
 		from team as team
 		where team.org_id = ?`)

+ 4 - 2
pkg/services/sqlstore/team_test.go

@@ -27,8 +27,8 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 				userIds = append(userIds, userCmd.Result.Id)
 			}
 
-			group1 := m.CreateTeamCommand{Name: "group1 name"}
-			group2 := m.CreateTeamCommand{Name: "group2 name"}
+			group1 := m.CreateTeamCommand{Name: "group1 name", Email: "test1@test.com"}
+			group2 := m.CreateTeamCommand{Name: "group2 name", Email: "test2@test.com"}
 
 			err := CreateTeam(&group1)
 			So(err, ShouldBeNil)
@@ -43,6 +43,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 
 				team1 := query.Result.Teams[0]
 				So(team1.Name, ShouldEqual, "group1 name")
+				So(team1.Email, ShouldEqual, "test1@test.com")
 
 				err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: team1.Id, UserId: userIds[0]})
 				So(err, ShouldBeNil)
@@ -76,6 +77,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 				So(err, ShouldBeNil)
 				So(len(query.Result), ShouldEqual, 1)
 				So(query.Result[0].Name, ShouldEqual, "group2 name")
+				So(query.Result[0].Email, ShouldEqual, "test2@test.com")
 			})
 
 			Convey("Should be able to remove users from a group", func() {

+ 73 - 89
public/app/app.ts

@@ -1,25 +1,24 @@
-import "babel-polyfill";
-import "file-saver";
-import "lodash";
-import "jquery";
-import "angular";
-import "angular-route";
-import "angular-sanitize";
-import "angular-native-dragdrop";
-import "angular-bindonce";
-import "react";
-import "react-dom";
-
-import "vendor/bootstrap/bootstrap";
-import "vendor/angular-ui/ui-bootstrap-tpls";
-import "vendor/angular-other/angular-strap";
-
-import $ from "jquery";
-import angular from "angular";
-import config from "app/core/config";
-import _ from "lodash";
-import moment from "moment";
-import { createStore } from "app/stores/store";
+import 'babel-polyfill';
+import 'file-saver';
+import 'lodash';
+import 'jquery';
+import 'angular';
+import 'angular-route';
+import 'angular-sanitize';
+import 'angular-native-dragdrop';
+import 'angular-bindonce';
+import 'react';
+import 'react-dom';
+
+import 'vendor/bootstrap/bootstrap';
+import 'vendor/angular-ui/ui-bootstrap-tpls';
+import 'vendor/angular-other/angular-strap';
+
+import $ from 'jquery';
+import angular from 'angular';
+import config from 'app/core/config';
+import _ from 'lodash';
+import moment from 'moment';
 
 // add move to lodash for backward compatabiltiy
 _.move = function(array, fromIndex, toIndex) {
@@ -27,7 +26,8 @@ _.move = function(array, fromIndex, toIndex) {
   return array;
 };
 
-import { coreModule, registerAngularDirectives } from "./core/core";
+import { coreModule, registerAngularDirectives } from './core/core';
+import { setupAngularRoutes } from './routes/routes';
 
 export class GrafanaApp {
   registerFunctions: any;
@@ -51,78 +51,62 @@ export class GrafanaApp {
   }
 
   init() {
-    var app = angular.module("grafana", []);
+    var app = angular.module('grafana', []);
 
     moment.locale(config.bootData.user.locale);
 
-    app.config(
-      (
-        $locationProvider,
-        $controllerProvider,
-        $compileProvider,
-        $filterProvider,
-        $httpProvider,
-        $provide
-      ) => {
-        // pre assing bindings before constructor calls
-        $compileProvider.preAssignBindingsEnabled(true);
-
-        if (config.buildInfo.env !== "development") {
-          $compileProvider.debugInfoEnabled(false);
-        }
-
-        $httpProvider.useApplyAsync(true);
-
-        this.registerFunctions.controller = $controllerProvider.register;
-        this.registerFunctions.directive = $compileProvider.directive;
-        this.registerFunctions.factory = $provide.factory;
-        this.registerFunctions.service = $provide.service;
-        this.registerFunctions.filter = $filterProvider.register;
-
-        $provide.decorator("$http", [
-          "$delegate",
-          "$templateCache",
-          function($delegate, $templateCache) {
-            var get = $delegate.get;
-            $delegate.get = function(url, config) {
-              if (url.match(/\.html$/)) {
-                // some template's already exist in the cache
-                if (!$templateCache.get(url)) {
-                  url += "?v=" + new Date().getTime();
-                }
-              }
-              return get(url, config);
-            };
-            return $delegate;
-          }
-        ]);
+    app.config(($locationProvider, $controllerProvider, $compileProvider, $filterProvider, $httpProvider, $provide) => {
+      // pre assing bindings before constructor calls
+      $compileProvider.preAssignBindingsEnabled(true);
+
+      if (config.buildInfo.env !== 'development') {
+        $compileProvider.debugInfoEnabled(false);
       }
-    );
+
+      $httpProvider.useApplyAsync(true);
+
+      this.registerFunctions.controller = $controllerProvider.register;
+      this.registerFunctions.directive = $compileProvider.directive;
+      this.registerFunctions.factory = $provide.factory;
+      this.registerFunctions.service = $provide.service;
+      this.registerFunctions.filter = $filterProvider.register;
+
+      $provide.decorator('$http', [
+        '$delegate',
+        '$templateCache',
+        function($delegate, $templateCache) {
+          var get = $delegate.get;
+          $delegate.get = function(url, config) {
+            if (url.match(/\.html$/)) {
+              // some template's already exist in the cache
+              if (!$templateCache.get(url)) {
+                url += '?v=' + new Date().getTime();
+              }
+            }
+            return get(url, config);
+          };
+          return $delegate;
+        },
+      ]);
+    });
 
     this.ngModuleDependencies = [
-      "grafana.core",
-      "ngRoute",
-      "ngSanitize",
-      "$strap.directives",
-      "ang-drag-drop",
-      "grafana",
-      "pasvaz.bindonce",
-      "ui.bootstrap",
-      "ui.bootstrap.tpls",
-      "react"
+      'grafana.core',
+      'ngRoute',
+      'ngSanitize',
+      '$strap.directives',
+      'ang-drag-drop',
+      'grafana',
+      'pasvaz.bindonce',
+      'ui.bootstrap',
+      'ui.bootstrap.tpls',
+      'react',
     ];
 
-    var module_types = [
-      "controllers",
-      "directives",
-      "factories",
-      "services",
-      "filters",
-      "routes"
-    ];
+    var module_types = ['controllers', 'directives', 'factories', 'services', 'filters', 'routes'];
 
     _.each(module_types, type => {
-      var moduleName = "grafana." + type;
+      var moduleName = 'grafana.' + type;
       this.useModule(angular.module(moduleName, []));
     });
 
@@ -130,16 +114,16 @@ export class GrafanaApp {
     this.useModule(coreModule);
 
     // register react angular wrappers
+    coreModule.config(setupAngularRoutes);
     registerAngularDirectives();
 
-    var preBootRequires = [System.import("app/features/all")];
+    var preBootRequires = [System.import('app/features/all')];
 
     Promise.all(preBootRequires)
       .then(() => {
-        createStore();
-
         // disable tool tip animation
         $.fn.tooltip.defaults.animation = false;
+
         // bootstrap the app
         angular.bootstrap(document, this.ngModuleDependencies).invoke(() => {
           _.each(this.preBootModules, module => {
@@ -150,7 +134,7 @@ export class GrafanaApp {
         });
       })
       .catch(function(err) {
-        console.log("Application boot failed:", err);
+        console.log('Application boot failed:', err);
       });
   }
 }

+ 49 - 0
public/app/containers/ServerStats.tsx

@@ -0,0 +1,49 @@
+import React from 'react';
+import { inject, observer } from 'mobx-react';
+import PageHeader from 'app/core/components/PageHeader/PageHeader';
+import { NavModel, NavModelSrv } from 'app/core/nav_model_srv';
+
+export interface IProps {
+  store: any;
+}
+
+@inject('store')
+@observer
+export default class ServerStats extends React.Component<IProps, any> {
+  navModel: NavModel;
+
+  constructor(props) {
+    super(props);
+
+    this.navModel = new NavModelSrv().getNav('cfg', 'admin', 'server-stats', 1);
+    this.props.store.serverStats.load();
+  }
+
+  render() {
+    return (
+      <div>
+        <PageHeader model={this.navModel} />
+        <div className="page-container page-body">
+          <table className="filter-table form-inline">
+            <thead>
+              <tr>
+                <th>Name</th>
+                <th>Value</th>
+              </tr>
+            </thead>
+            <tbody>{this.props.store.serverStats.stats.map(StatItem)}</tbody>
+          </table>
+        </div>
+      </div>
+    );
+  }
+}
+
+function StatItem(stat) {
+  return (
+    <tr key={stat.name}>
+      <td>{stat.name}</td>
+      <td>{stat.value}</td>
+    </tr>
+  );
+}

+ 1 - 1
public/app/core/app_events.ts

@@ -1,4 +1,4 @@
-import { Emitter } from "./utils/emitter";
+import { Emitter } from './utils/emitter';
 
 var appEvents = new Emitter();
 export default appEvents;

+ 37 - 21
public/app/core/components/PageHeader/PageHeader.tsx

@@ -9,7 +9,7 @@ export interface IProps {
 
 function TabItem(tab: NavModelItem) {
   if (tab.hideFromTabs) {
-    return (null);
+    return null;
   }
 
   let tabClasses = classNames({
@@ -28,8 +28,9 @@ function TabItem(tab: NavModelItem) {
 }
 
 function SelectOption(navItem: NavModelItem) {
-  if (navItem.hideFromTabs) { // TODO: Rename hideFromTabs => hideFromNav
-    return (null);
+  if (navItem.hideFromTabs) {
+    // TODO: Rename hideFromTabs => hideFromNav
+    return null;
   }
 
   return (
@@ -39,14 +40,16 @@ function SelectOption(navItem: NavModelItem) {
   );
 }
 
-function Navigation({main}: {main: NavModelItem}) {
-  return (<nav>
-    <SelectNav customCss="page-header__select_nav" main={main} />
-    <Tabs customCss="page-header__tabs" main={main} />
-  </nav>);
+function Navigation({ main }: { main: NavModelItem }) {
+  return (
+    <nav>
+      <SelectNav customCss="page-header__select-nav" main={main} />
+      <Tabs customCss="page-header__tabs" main={main} />
+    </nav>
+  );
 }
 
-function SelectNav({main, customCss}: {main: NavModelItem, customCss: string}) {
+function SelectNav({ main, customCss }: { main: NavModelItem; customCss: string }) {
   const defaultSelectedItem = main.children.find(navItem => {
     return navItem.active === true;
   });
@@ -54,16 +57,26 @@ function SelectNav({main, customCss}: {main: NavModelItem, customCss: string}) {
   const gotoUrl = evt => {
     var element = evt.target;
     var url = element.options[element.selectedIndex].value;
-    appEvents.emit('location-change', {href: url});
+    appEvents.emit('location-change', { href: url });
   };
 
-  return (<select
-    className={`gf-select-nav ${customCss}`}
-    defaultValue={defaultSelectedItem.url}
-    onChange={gotoUrl}>{main.children.map(SelectOption)}</select>);
+  return (
+    <div className={`gf-form-select-wrapper width-20 ${customCss}`}>
+      <label className={`gf-form-select-icon ${defaultSelectedItem.icon}`} htmlFor="page-header-select-nav" />
+      {/* Label to make it clickable */}
+      <select
+        className="gf-select-nav gf-form-input"
+        defaultValue={defaultSelectedItem.url}
+        onChange={gotoUrl}
+        id="page-header-select-nav"
+      >
+        {main.children.map(SelectOption)}
+      </select>
+    </div>
+  );
 }
 
-function Tabs({main, customCss}: {main: NavModelItem, customCss: string}) {
+function Tabs({ main, customCss }: { main: NavModelItem; customCss: string }) {
   return <ul className={`gf-tabs ${customCss}`}>{main.children.map(TabItem)}</ul>;
 }
 
@@ -77,7 +90,11 @@ export default class PageHeader extends React.Component<IProps, any> {
     for (let i = 0; i < breadcrumbs.length; i++) {
       const bc = breadcrumbs[i];
       if (bc.url) {
-        breadcrumbsResult.push(<a className="text-link" key={i} href={bc.url}>{bc.title}</a>);
+        breadcrumbsResult.push(
+          <a className="text-link" key={i} href={bc.url}>
+            {bc.title}
+          </a>
+        );
       } else {
         breadcrumbsResult.push(<span key={i}> / {bc.title}</span>);
       }
@@ -95,11 +112,10 @@ export default class PageHeader extends React.Component<IProps, any> {
 
         <div className="page-header__info-block">
           {main.text && <h1 className="page-header__title">{main.text}</h1>}
-          {main.breadcrumbs && main.breadcrumbs.length > 0 && (
-            <h1 className="page-header__title">
-              {this.renderBreadcrumb(main.breadcrumbs)}
-            </h1>)
-          }
+          {main.breadcrumbs &&
+            main.breadcrumbs.length > 0 && (
+              <h1 className="page-header__title">{this.renderBreadcrumb(main.breadcrumbs)}</h1>
+            )}
           {main.subTitle && <div className="page-header__sub-title">{main.subTitle}</div>}
           {main.subType && (
             <div className="page-header__stamps">

+ 40 - 42
public/app/core/components/code_editor/code_editor.ts

@@ -26,24 +26,24 @@
  * Ctrl-Enter (Command-Enter): run onChange() function
  */
 
-import coreModule from "app/core/core_module";
-import config from "app/core/config";
-import ace from "brace";
-import "./theme-grafana-dark";
-import "brace/ext/language_tools";
-import "brace/theme/textmate";
-import "brace/mode/text";
-import "brace/snippets/text";
-import "brace/mode/sql";
-import "brace/snippets/sql";
-import "brace/mode/markdown";
-import "brace/snippets/markdown";
-import "brace/mode/json";
-import "brace/snippets/json";
-
-const DEFAULT_THEME_DARK = "ace/theme/grafana-dark";
-const DEFAULT_THEME_LIGHT = "ace/theme/textmate";
-const DEFAULT_MODE = "text";
+import coreModule from 'app/core/core_module';
+import config from 'app/core/config';
+import ace from 'brace';
+import './theme-grafana-dark';
+import 'brace/ext/language_tools';
+import 'brace/theme/textmate';
+import 'brace/mode/text';
+import 'brace/snippets/text';
+import 'brace/mode/sql';
+import 'brace/snippets/sql';
+import 'brace/mode/markdown';
+import 'brace/snippets/markdown';
+import 'brace/mode/json';
+import 'brace/snippets/json';
+
+const DEFAULT_THEME_DARK = 'ace/theme/grafana-dark';
+const DEFAULT_THEME_LIGHT = 'ace/theme/textmate';
+const DEFAULT_MODE = 'text';
 const DEFAULT_MAX_LINES = 10;
 const DEFAULT_TAB_SIZE = 2;
 const DEFAULT_BEHAVIOURS = true;
@@ -56,9 +56,7 @@ function link(scope, elem, attrs) {
   let maxLines = attrs.maxLines || DEFAULT_MAX_LINES;
   let showGutter = attrs.showGutter !== undefined;
   let tabSize = attrs.tabSize || DEFAULT_TAB_SIZE;
-  let behavioursEnabled = attrs.behavioursEnabled
-    ? attrs.behavioursEnabled === "true"
-    : DEFAULT_BEHAVIOURS;
+  let behavioursEnabled = attrs.behavioursEnabled ? attrs.behavioursEnabled === 'true' : DEFAULT_BEHAVIOURS;
 
   // Initialize editor
   let aceElem = elem.get(0);
@@ -72,7 +70,7 @@ function link(scope, elem, attrs) {
     behavioursEnabled: behavioursEnabled,
     highlightActiveLine: false,
     showPrintMargin: false,
-    autoScrollEditorIntoView: true // this is needed if editor is inside scrollable page
+    autoScrollEditorIntoView: true, // this is needed if editor is inside scrollable page
   };
 
   // Set options
@@ -88,9 +86,9 @@ function link(scope, elem, attrs) {
   setEditorContent(scope.content);
 
   // Add classes
-  elem.addClass("gf-code-editor");
-  let textarea = elem.find("textarea");
-  textarea.addClass("gf-form-input");
+  elem.addClass('gf-code-editor');
+  let textarea = elem.find('textarea');
+  textarea.addClass('gf-form-input');
 
   if (scope.codeEditorFocus) {
     setTimeout(function() {
@@ -104,7 +102,7 @@ function link(scope, elem, attrs) {
   }
 
   // Event handlers
-  editorSession.on("change", e => {
+  editorSession.on('change', e => {
     scope.$apply(() => {
       let newValue = codeEditor.getValue();
       scope.content = newValue;
@@ -112,7 +110,7 @@ function link(scope, elem, attrs) {
   });
 
   // Sync with outer scope - update editor content if model has been changed from outside of directive.
-  scope.$watch("content", (newValue, oldValue) => {
+  scope.$watch('content', (newValue, oldValue) => {
     let editorValue = codeEditor.getValue();
     if (newValue !== editorValue && newValue !== oldValue) {
       scope.$$postDigest(function() {
@@ -121,29 +119,29 @@ function link(scope, elem, attrs) {
     }
   });
 
-  codeEditor.on("blur", () => {
+  codeEditor.on('blur', () => {
     scope.onChange();
   });
 
-  scope.$on("$destroy", () => {
+  scope.$on('$destroy', () => {
     codeEditor.destroy();
   });
 
   // Keybindings
   codeEditor.commands.addCommand({
-    name: "executeQuery",
-    bindKey: { win: "Ctrl-Enter", mac: "Command-Enter" },
+    name: 'executeQuery',
+    bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' },
     exec: () => {
       scope.onChange();
-    }
+    },
   });
 
   function setLangMode(lang) {
-    ace.acequire("ace/ext/language_tools");
+    ace.acequire('ace/ext/language_tools');
     codeEditor.setOptions({
       enableBasicAutocompletion: true,
       enableLiveAutocompletion: true,
-      enableSnippets: true
+      enableSnippets: true,
     });
 
     if (scope.getCompleter()) {
@@ -174,17 +172,17 @@ function link(scope, elem, attrs) {
 
 export function codeEditorDirective() {
   return {
-    restrict: "E",
+    restrict: 'E',
     template: editorTemplate,
     scope: {
-      content: "=",
-      datasource: "=",
-      codeEditorFocus: "<",
-      onChange: "&",
-      getCompleter: "&"
+      content: '=',
+      datasource: '=',
+      codeEditorFocus: '<',
+      onChange: '&',
+      getCompleter: '&',
     },
-    link: link
+    link: link,
   };
 }
 
-coreModule.directive("codeEditor", codeEditorDirective);
+coreModule.directive('codeEditor', codeEditorDirective);

+ 6 - 7
public/app/core/components/colorpicker/spectrum_picker.ts

@@ -3,23 +3,22 @@
  * Allows remaining <spectrum-picker> untouched in outdated plugins.
  * Technically, it's just a wrapper for react component with two-way data binding support.
  */
-import coreModule from "../../core_module";
+import coreModule from '../../core_module';
 
 /** @ngInject */
 export function spectrumPicker() {
   return {
-    restrict: "E",
-    require: "ngModel",
+    restrict: 'E',
+    require: 'ngModel',
     scope: true,
     replace: true,
-    template:
-      '<color-picker color="ngModel.$viewValue" onChange="onColorChange"></color-picker>',
+    template: '<color-picker color="ngModel.$viewValue" onChange="onColorChange"></color-picker>',
     link: function(scope, element, attrs, ngModel) {
       scope.ngModel = ngModel;
       scope.onColorChange = color => {
         ngModel.$setViewValue(color);
       };
-    }
+    },
   };
 }
-coreModule.directive("spectrumPicker", spectrumPicker);
+coreModule.directive('spectrumPicker', spectrumPicker);

+ 7 - 7
public/app/core/components/dashboard_selector.ts

@@ -1,4 +1,4 @@
-import coreModule from "app/core/core_module";
+import coreModule from 'app/core/core_module';
 
 var template = `
 <select class="gf-form-input" ng-model="ctrl.model" ng-options="f.value as f.text for f in ctrl.options"></select>
@@ -12,7 +12,7 @@ export class DashboardSelectorCtrl {
   constructor(private backendSrv) {}
 
   $onInit() {
-    this.options = [{ value: 0, text: "Default" }];
+    this.options = [{ value: 0, text: 'Default' }];
 
     return this.backendSrv.search({ starred: true }).then(res => {
       res.forEach(dash => {
@@ -24,15 +24,15 @@ export class DashboardSelectorCtrl {
 
 export function dashboardSelector() {
   return {
-    restrict: "E",
+    restrict: 'E',
     controller: DashboardSelectorCtrl,
     bindToController: true,
-    controllerAs: "ctrl",
+    controllerAs: 'ctrl',
     template: template,
     scope: {
-      model: "="
-    }
+      model: '=',
+    },
   };
 }
 
-coreModule.directive("dashboardSelector", dashboardSelector);
+coreModule.directive('dashboardSelector', dashboardSelector);

+ 33 - 45
public/app/core/components/form_dropdown/form_dropdown.ts

@@ -1,13 +1,13 @@
-import _ from "lodash";
-import $ from "jquery";
-import coreModule from "../../core_module";
+import _ from 'lodash';
+import $ from 'jquery';
+import coreModule from '../../core_module';
 
 function typeaheadMatcher(item) {
   var str = this.query;
-  if (str[0] === "/") {
+  if (str[0] === '/') {
     str = str.substring(1);
   }
-  if (str[str.length - 1] === "/") {
+  if (str[str.length - 1] === '/') {
     str = str.substring(0, str.length - 1);
   }
   return item.toLowerCase().match(str.toLowerCase());
@@ -32,42 +32,35 @@ export class FormDropdownCtrl {
   lookupText: boolean;
 
   /** @ngInject **/
-  constructor(
-    private $scope,
-    $element,
-    private $sce,
-    private templateSrv,
-    private $q
-  ) {
-    this.inputElement = $element.find("input").first();
-    this.linkElement = $element.find("a").first();
+  constructor(private $scope, $element, private $sce, private templateSrv, private $q) {
+    this.inputElement = $element.find('input').first();
+    this.linkElement = $element.find('a').first();
     this.linkMode = true;
     this.cancelBlur = null;
 
     // listen to model changes
-    $scope.$watch("ctrl.model", this.modelChanged.bind(this));
+    $scope.$watch('ctrl.model', this.modelChanged.bind(this));
 
     if (this.labelMode) {
-      this.cssClasses = "gf-form-label " + this.cssClass;
+      this.cssClasses = 'gf-form-label ' + this.cssClass;
     } else {
-      this.cssClasses =
-        "gf-form-input gf-form-input--dropdown " + this.cssClass;
+      this.cssClasses = 'gf-form-input gf-form-input--dropdown ' + this.cssClass;
     }
 
-    this.inputElement.attr("data-provide", "typeahead");
+    this.inputElement.attr('data-provide', 'typeahead');
     this.inputElement.typeahead({
       source: this.typeaheadSource.bind(this),
       minLength: 0,
       items: 10000,
       updater: this.typeaheadUpdater.bind(this),
-      matcher: typeaheadMatcher
+      matcher: typeaheadMatcher,
     });
 
     // modify typeahead lookup
     // this = typeahead
-    var typeahead = this.inputElement.data("typeahead");
+    var typeahead = this.inputElement.data('typeahead');
     typeahead.lookup = function() {
-      this.query = this.$element.val() || "";
+      this.query = this.$element.val() || '';
       var items = this.source(this.query, $.proxy(this.process, this));
       return items ? this.process(items) : items;
     };
@@ -99,7 +92,7 @@ export class FormDropdownCtrl {
   }
 
   isPromiseLike(obj) {
-    return obj && typeof obj.then === "function";
+    return obj && typeof obj.then === 'function';
   }
 
   modelChanged() {
@@ -108,7 +101,7 @@ export class FormDropdownCtrl {
     } else {
       // if we have text use it
       if (this.lookupText) {
-        this.getOptionsInternal("").then(options => {
+        this.getOptionsInternal('').then(options => {
           var item = _.find(options, { value: this.model });
           this.updateDisplay(item ? item.text : this.model);
         });
@@ -172,7 +165,7 @@ export class FormDropdownCtrl {
   updateValue(text) {
     text = _.unescape(text);
 
-    if (text === "" || this.text === text) {
+    if (text === '' || this.text === text) {
       return;
     }
 
@@ -207,16 +200,11 @@ export class FormDropdownCtrl {
 
   updateDisplay(text) {
     this.text = text;
-    this.display = this.$sce.trustAsHtml(
-      this.templateSrv.highlightVariablesAsHtml(text)
-    );
+    this.display = this.$sce.trustAsHtml(this.templateSrv.highlightVariablesAsHtml(text));
   }
 
   open() {
-    this.inputElement.css(
-      "width",
-      Math.max(this.linkElement.width(), 80) + 16 + "px"
-    );
+    this.inputElement.css('width', Math.max(this.linkElement.width(), 80) + 16 + 'px');
 
     this.inputElement.show();
     this.inputElement.focus();
@@ -224,9 +212,9 @@ export class FormDropdownCtrl {
     this.linkElement.hide();
     this.linkMode = false;
 
-    var typeahead = this.inputElement.data("typeahead");
+    var typeahead = this.inputElement.data('typeahead');
     if (typeahead) {
-      this.inputElement.val("");
+      this.inputElement.val('');
       typeahead.lookup();
     }
   }
@@ -249,21 +237,21 @@ const template = `
 
 export function formDropdownDirective() {
   return {
-    restrict: "E",
+    restrict: 'E',
     template: template,
     controller: FormDropdownCtrl,
     bindToController: true,
-    controllerAs: "ctrl",
+    controllerAs: 'ctrl',
     scope: {
-      model: "=",
-      getOptions: "&",
-      onChange: "&",
-      cssClass: "@",
-      allowCustom: "@",
-      labelMode: "@",
-      lookupText: "@"
-    }
+      model: '=',
+      getOptions: '&',
+      onChange: '&',
+      cssClass: '@',
+      allowCustom: '@',
+      labelMode: '@',
+      lookupText: '@',
+    },
   };
 }
 
-coreModule.directive("gfFormDropdown", formDropdownDirective);
+coreModule.directive('gfFormDropdown', formDropdownDirective);

+ 7 - 7
public/app/core/components/gf_page.ts

@@ -1,6 +1,6 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import coreModule from "app/core/core_module";
+import coreModule from 'app/core/core_module';
 
 const template = `
 <div class="scroll-canvas">
@@ -24,19 +24,19 @@ const template = `
 
 export function gfPageDirective() {
   return {
-    restrict: "E",
+    restrict: 'E',
     template: template,
     scope: {
-      model: "="
+      model: '=',
     },
     transclude: {
-      header: "?gfPageHeader",
-      body: "gfPageBody"
+      header: '?gfPageHeader',
+      body: 'gfPageBody',
     },
     link: function(scope, elem, attrs) {
       console.log(scope);
-    }
+    },
   };
 }
 
-coreModule.directive("gfPage", gfPageDirective);
+coreModule.directive('gfPage', gfPageDirective);

+ 105 - 121
public/app/core/components/grafana_app.ts

@@ -1,23 +1,18 @@
-import config from "app/core/config";
-import _ from "lodash";
-import $ from "jquery";
+import config from 'app/core/config';
+import _ from 'lodash';
+import $ from 'jquery';
 
-import coreModule from "app/core/core_module";
-import { profiler } from "app/core/profiler";
-import appEvents from "app/core/app_events";
-import Drop from "tether-drop";
+import coreModule from 'app/core/core_module';
+import { profiler } from 'app/core/profiler';
+import appEvents from 'app/core/app_events';
+import Drop from 'tether-drop';
+import { createStore } from 'app/stores/store';
 
 export class GrafanaCtrl {
   /** @ngInject */
-  constructor(
-    $scope,
-    alertSrv,
-    utilSrv,
-    $rootScope,
-    $controller,
-    contextSrv,
-    globalEventSrv
-  ) {
+  constructor($scope, alertSrv, utilSrv, $rootScope, $controller, contextSrv, globalEventSrv, backendSrv) {
+    createStore(backendSrv);
+
     $scope.init = function() {
       $scope.contextSrv = contextSrv;
 
@@ -33,20 +28,20 @@ export class GrafanaCtrl {
     };
 
     $scope.initDashboard = function(dashboardData, viewScope) {
-      $scope.appEvent("dashboard-fetch-end", dashboardData);
-      $controller("DashboardCtrl", { $scope: viewScope }).init(dashboardData);
+      $scope.appEvent('dashboard-fetch-end', dashboardData);
+      $controller('DashboardCtrl', { $scope: viewScope }).init(dashboardData);
     };
 
     $rootScope.onAppEvent = function(name, callback, localScope) {
       var unbind = $rootScope.$on(name, callback);
       var callerScope = this;
       if (callerScope.$id === 1 && !localScope) {
-        console.log("warning rootScope onAppEvent called without localscope");
+        console.log('warning rootScope onAppEvent called without localscope');
       }
       if (localScope) {
         callerScope = localScope;
       }
-      callerScope.$on("$destroy", unbind);
+      callerScope.$on('$destroy', unbind);
     };
 
     $rootScope.appEvent = function(name, payload) {
@@ -55,62 +50,62 @@ export class GrafanaCtrl {
     };
 
     $rootScope.colors = [
-      "#7EB26D",
-      "#EAB839",
-      "#6ED0E0",
-      "#EF843C",
-      "#E24D42",
-      "#1F78C1",
-      "#BA43A9",
-      "#705DA0",
-      "#508642",
-      "#CCA300",
-      "#447EBC",
-      "#C15C17",
-      "#890F02",
-      "#0A437C",
-      "#6D1F62",
-      "#584477",
-      "#B7DBAB",
-      "#F4D598",
-      "#70DBED",
-      "#F9BA8F",
-      "#F29191",
-      "#82B5D8",
-      "#E5A8E2",
-      "#AEA2E0",
-      "#629E51",
-      "#E5AC0E",
-      "#64B0C8",
-      "#E0752D",
-      "#BF1B00",
-      "#0A50A1",
-      "#962D82",
-      "#614D93",
-      "#9AC48A",
-      "#F2C96D",
-      "#65C5DB",
-      "#F9934E",
-      "#EA6460",
-      "#5195CE",
-      "#D683CE",
-      "#806EB7",
-      "#3F6833",
-      "#967302",
-      "#2F575E",
-      "#99440A",
-      "#58140C",
-      "#052B51",
-      "#511749",
-      "#3F2B5B",
-      "#E0F9D7",
-      "#FCEACA",
-      "#CFFAFF",
-      "#F9E2D2",
-      "#FCE2DE",
-      "#BADFF4",
-      "#F9D9F9",
-      "#DEDAF7"
+      '#7EB26D',
+      '#EAB839',
+      '#6ED0E0',
+      '#EF843C',
+      '#E24D42',
+      '#1F78C1',
+      '#BA43A9',
+      '#705DA0',
+      '#508642',
+      '#CCA300',
+      '#447EBC',
+      '#C15C17',
+      '#890F02',
+      '#0A437C',
+      '#6D1F62',
+      '#584477',
+      '#B7DBAB',
+      '#F4D598',
+      '#70DBED',
+      '#F9BA8F',
+      '#F29191',
+      '#82B5D8',
+      '#E5A8E2',
+      '#AEA2E0',
+      '#629E51',
+      '#E5AC0E',
+      '#64B0C8',
+      '#E0752D',
+      '#BF1B00',
+      '#0A50A1',
+      '#962D82',
+      '#614D93',
+      '#9AC48A',
+      '#F2C96D',
+      '#65C5DB',
+      '#F9934E',
+      '#EA6460',
+      '#5195CE',
+      '#D683CE',
+      '#806EB7',
+      '#3F6833',
+      '#967302',
+      '#2F575E',
+      '#99440A',
+      '#58140C',
+      '#052B51',
+      '#511749',
+      '#3F2B5B',
+      '#E0F9D7',
+      '#FCEACA',
+      '#CFFAFF',
+      '#F9E2D2',
+      '#FCE2DE',
+      '#BADFF4',
+      '#F9D9F9',
+      '#DEDAF7',
     ];
 
     $scope.init();
@@ -118,41 +113,36 @@ export class GrafanaCtrl {
 }
 
 /** @ngInject */
-export function grafanaAppDirective(
-  playlistSrv,
-  contextSrv,
-  $timeout,
-  $rootScope
-) {
+export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScope) {
   return {
-    restrict: "E",
+    restrict: 'E',
     controller: GrafanaCtrl,
     link: (scope, elem) => {
       var sidemenuOpen;
-      var body = $("body");
+      var body = $('body');
 
       // see https://github.com/zenorocha/clipboard.js/issues/155
       $.fn.modal.Constructor.prototype.enforceFocus = function() {};
 
       sidemenuOpen = scope.contextSrv.sidemenu;
-      body.toggleClass("sidemenu-open", sidemenuOpen);
+      body.toggleClass('sidemenu-open', sidemenuOpen);
 
-      appEvents.on("toggle-sidemenu", () => {
-        body.toggleClass("sidemenu-open");
+      appEvents.on('toggle-sidemenu', () => {
+        body.toggleClass('sidemenu-open');
       });
 
-      appEvents.on("toggle-sidemenu-mobile", () => {
-        body.toggleClass("sidemenu-open--xs");
+      appEvents.on('toggle-sidemenu-mobile', () => {
+        body.toggleClass('sidemenu-open--xs');
       });
 
-      appEvents.on("toggle-sidemenu-hidden", () => {
-        body.toggleClass("sidemenu-hidden");
+      appEvents.on('toggle-sidemenu-hidden', () => {
+        body.toggleClass('sidemenu-hidden');
       });
 
       // tooltip removal fix
       // manage page classes
       var pageClass;
-      scope.$on("$routeChangeSuccess", function(evt, data) {
+      scope.$on('$routeChangeSuccess', function(evt, data) {
         if (pageClass) {
           body.removeClass(pageClass);
         }
@@ -165,13 +155,13 @@ export function grafanaAppDirective(
         }
 
         // clear body class sidemenu states
-        body.removeClass("sidemenu-open--xs");
+        body.removeClass('sidemenu-open--xs');
 
-        $("#tooltip, .tooltip").remove();
+        $('#tooltip, .tooltip').remove();
 
         // check for kiosk url param
         if (data.params.kiosk) {
-          appEvents.emit("toggle-kiosk-mode");
+          appEvents.emit('toggle-kiosk-mode');
         }
 
         // close all drops
@@ -181,8 +171,8 @@ export function grafanaAppDirective(
       });
 
       // handle kiosk mode
-      appEvents.on("toggle-kiosk-mode", () => {
-        body.toggleClass("page-kiosk-mode");
+      appEvents.on('toggle-kiosk-mode', () => {
+        body.toggleClass('page-kiosk-mode');
       });
 
       // handle in active view state class
@@ -196,19 +186,19 @@ export function grafanaAppDirective(
           return;
         }
         // only go to activity low mode on dashboard page
-        if (!body.hasClass("page-dashboard")) {
+        if (!body.hasClass('page-dashboard')) {
           return;
         }
 
         if (new Date().getTime() - lastActivity > inActiveTimeLimit) {
           activeUser = false;
-          body.addClass("user-activity-low");
+          body.addClass('user-activity-low');
           // hide sidemenu
           if (sidemenuOpen) {
             sidemenuHidden = true;
-            body.removeClass("sidemenu-open");
+            body.removeClass('sidemenu-open');
             $timeout(function() {
-              $rootScope.$broadcast("render");
+              $rootScope.$broadcast('render');
             }, 100);
           }
         }
@@ -218,14 +208,14 @@ export function grafanaAppDirective(
         lastActivity = new Date().getTime();
         if (!activeUser) {
           activeUser = true;
-          body.removeClass("user-activity-low");
+          body.removeClass('user-activity-low');
 
           // restore sidemenu
           if (sidemenuHidden) {
             sidemenuHidden = false;
-            body.addClass("sidemenu-open");
+            body.addClass('sidemenu-open');
             $timeout(function() {
-              $rootScope.$broadcast("render");
+              $rootScope.$broadcast('render');
             }, 100);
           }
         }
@@ -235,12 +225,12 @@ export function grafanaAppDirective(
       body.mousemove(userActivityDetected);
       body.keydown(userActivityDetected);
       // treat tab change as activity
-      document.addEventListener("visibilitychange", userActivityDetected);
+      document.addEventListener('visibilitychange', userActivityDetected);
 
       // check every 2 seconds
       setInterval(checkForInActiveUser, 2000);
 
-      appEvents.on("toggle-view-mode", () => {
+      appEvents.on('toggle-view-mode', () => {
         lastActivity = 0;
         checkForInActiveUser();
       });
@@ -254,7 +244,7 @@ export function grafanaAppDirective(
 
         // for stuff that animates, slides out etc, clicking it needs to
         // hide it right away
-        var clickAutoHide = target.closest("[data-click-hide]");
+        var clickAutoHide = target.closest('[data-click-hide]');
         if (clickAutoHide.length) {
           var clickAutoHideParent = clickAutoHide.parent();
           clickAutoHide.detach();
@@ -263,33 +253,27 @@ export function grafanaAppDirective(
           }, 100);
         }
 
-        if (target.parents(".navbar-buttons--playlist").length === 0) {
+        if (target.parents('.navbar-buttons--playlist').length === 0) {
           playlistSrv.stop();
         }
 
         // hide search
-        if (body.find(".search-container").length > 0) {
-          if (
-            target.parents(".search-results-container, .search-field-wrapper")
-              .length === 0
-          ) {
+        if (body.find('.search-container').length > 0) {
+          if (target.parents('.search-results-container, .search-field-wrapper').length === 0) {
             scope.$apply(function() {
-              scope.appEvent("hide-dash-search");
+              scope.appEvent('hide-dash-search');
             });
           }
         }
 
         // hide popovers
-        var popover = elem.find(".popover");
-        if (
-          popover.length > 0 &&
-          target.parents(".graph-legend").length === 0
-        ) {
+        var popover = elem.find('.popover');
+        if (popover.length > 0 && target.parents('.graph-legend').length === 0) {
           popover.hide();
         }
       });
-    }
+    },
   };
 }
 
-coreModule.directive("grafanaApp", grafanaAppDirective);
+coreModule.directive('grafanaApp', grafanaAppDirective);

+ 39 - 39
public/app/core/components/help/help.ts

@@ -1,7 +1,7 @@
 ///<reference path="../../../headers/common.d.ts" />
 
-import coreModule from "../../core_module";
-import appEvents from "app/core/app_events";
+import coreModule from '../../core_module';
+import appEvents from 'app/core/app_events';
 
 export class HelpCtrl {
   tabIndex: any;
@@ -12,63 +12,63 @@ export class HelpCtrl {
     this.tabIndex = 0;
     this.shortcuts = {
       Global: [
-        { keys: ["g", "h"], description: "Go to Home Dashboard" },
-        { keys: ["g", "p"], description: "Go to Profile" },
-        { keys: ["s", "o"], description: "Open search" },
-        { keys: ["s", "s"], description: "Open search with starred filter" },
-        { keys: ["s", "t"], description: "Open search in tags view" },
-        { keys: ["esc"], description: "Exit edit/setting views" }
+        { keys: ['g', 'h'], description: 'Go to Home Dashboard' },
+        { keys: ['g', 'p'], description: 'Go to Profile' },
+        { keys: ['s', 'o'], description: 'Open search' },
+        { keys: ['s', 's'], description: 'Open search with starred filter' },
+        { keys: ['s', 't'], description: 'Open search in tags view' },
+        { keys: ['esc'], description: 'Exit edit/setting views' },
       ],
       Dashboard: [
-        { keys: ["mod+s"], description: "Save dashboard" },
-        { keys: ["mod+h"], description: "Hide row controls" },
-        { keys: ["d", "r"], description: "Refresh all panels" },
-        { keys: ["d", "s"], description: "Dashboard settings" },
-        { keys: ["d", "v"], description: "Toggle in-active / view mode" },
-        { keys: ["d", "k"], description: "Toggle kiosk mode (hides top nav)" },
-        { keys: ["d", "E"], description: "Expand all rows" },
-        { keys: ["d", "C"], description: "Collapse all rows" },
-        { keys: ["mod+o"], description: "Toggle shared graph crosshair" }
+        { keys: ['mod+s'], description: 'Save dashboard' },
+        { keys: ['mod+h'], description: 'Hide row controls' },
+        { keys: ['d', 'r'], description: 'Refresh all panels' },
+        { keys: ['d', 's'], description: 'Dashboard settings' },
+        { keys: ['d', 'v'], description: 'Toggle in-active / view mode' },
+        { keys: ['d', 'k'], description: 'Toggle kiosk mode (hides top nav)' },
+        { keys: ['d', 'E'], description: 'Expand all rows' },
+        { keys: ['d', 'C'], description: 'Collapse all rows' },
+        { keys: ['mod+o'], description: 'Toggle shared graph crosshair' },
       ],
-      "Focused Panel": [
-        { keys: ["e"], description: "Toggle panel edit view" },
-        { keys: ["v"], description: "Toggle panel fullscreen view" },
-        { keys: ["p", "s"], description: "Open Panel Share Modal" },
-        { keys: ["p", "r"], description: "Remove Panel" }
+      'Focused Panel': [
+        { keys: ['e'], description: 'Toggle panel edit view' },
+        { keys: ['v'], description: 'Toggle panel fullscreen view' },
+        { keys: ['p', 's'], description: 'Open Panel Share Modal' },
+        { keys: ['p', 'r'], description: 'Remove Panel' },
       ],
-      "Focused Row": [
-        { keys: ["r", "c"], description: "Collapse Row" },
-        { keys: ["r", "r"], description: "Remove Row" }
+      'Focused Row': [
+        { keys: ['r', 'c'], description: 'Collapse Row' },
+        { keys: ['r', 'r'], description: 'Remove Row' },
       ],
-      "Time Range": [
-        { keys: ["t", "z"], description: "Zoom out time range" },
+      'Time Range': [
+        { keys: ['t', 'z'], description: 'Zoom out time range' },
         {
-          keys: ["t", '<i class="fa fa-long-arrow-left"></i>'],
-          description: "Move time range back"
+          keys: ['t', '<i class="fa fa-long-arrow-left"></i>'],
+          description: 'Move time range back',
         },
         {
-          keys: ["t", '<i class="fa fa-long-arrow-right"></i>'],
-          description: "Move time range forward"
-        }
-      ]
+          keys: ['t', '<i class="fa fa-long-arrow-right"></i>'],
+          description: 'Move time range forward',
+        },
+      ],
     };
   }
 
   dismiss() {
-    appEvents.emit("hide-modal");
+    appEvents.emit('hide-modal');
   }
 }
 
 export function helpModal() {
   return {
-    restrict: "E",
-    templateUrl: "public/app/core/components/help/help.html",
+    restrict: 'E',
+    templateUrl: 'public/app/core/components/help/help.html',
     controller: HelpCtrl,
     bindToController: true,
     transclude: true,
-    controllerAs: "ctrl",
-    scope: {}
+    controllerAs: 'ctrl',
+    scope: {},
   };
 }
 
-coreModule.directive("helpModal", helpModal);
+coreModule.directive('helpModal', helpModal);

+ 22 - 22
public/app/core/components/info_popover.ts

@@ -1,33 +1,33 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import _ from "lodash";
-import coreModule from "app/core/core_module";
-import Drop from "tether-drop";
+import _ from 'lodash';
+import coreModule from 'app/core/core_module';
+import Drop from 'tether-drop';
 
 export function infoPopover() {
   return {
-    restrict: "E",
+    restrict: 'E',
     template: '<i class="fa fa-info-circle"></i>',
     transclude: true,
     link: function(scope, elem, attrs, ctrl, transclude) {
-      var offset = attrs.offset || "0 -10px";
-      var position = attrs.position || "right middle";
-      var classes = "drop-help drop-hide-out-of-bounds";
-      var openOn = "hover";
+      var offset = attrs.offset || '0 -10px';
+      var position = attrs.position || 'right middle';
+      var classes = 'drop-help drop-hide-out-of-bounds';
+      var openOn = 'hover';
 
-      elem.addClass("gf-form-help-icon");
+      elem.addClass('gf-form-help-icon');
 
       if (attrs.wide) {
-        classes += " drop-wide";
+        classes += ' drop-wide';
       }
 
       if (attrs.mode) {
-        elem.addClass("gf-form-help-icon--" + attrs.mode);
+        elem.addClass('gf-form-help-icon--' + attrs.mode);
       }
 
       transclude(function(clone, newScope) {
-        var content = document.createElement("div");
-        content.className = "markdown-html";
+        var content = document.createElement('div');
+        content.className = 'markdown-html';
 
         _.each(clone, node => {
           content.appendChild(node);
@@ -44,21 +44,21 @@ export function infoPopover() {
             offset: offset,
             constraints: [
               {
-                to: "window",
-                attachment: "together",
-                pin: true
-              }
-            ]
-          }
+                to: 'window',
+                attachment: 'together',
+                pin: true,
+              },
+            ],
+          },
         });
 
-        var unbind = scope.$on("$destroy", function() {
+        var unbind = scope.$on('$destroy', function() {
           drop.destroy();
           unbind();
         });
       });
-    }
+    },
   };
 }
 
-coreModule.directive("infoPopover", infoPopover);
+coreModule.directive('infoPopover', infoPopover);

+ 15 - 19
public/app/core/components/json_explorer/helpers.ts

@@ -13,7 +13,7 @@ function escapeString(str: string): string {
 */
 export function isObject(value: any): boolean {
   var type = typeof value;
-  return !!value && type === "object";
+  return !!value && type === 'object';
 }
 
 /*
@@ -23,13 +23,13 @@ export function isObject(value: any): boolean {
 */
 export function getObjectName(object: Object): string {
   if (object === undefined) {
-    return "";
+    return '';
   }
   if (object === null) {
-    return "Object";
+    return 'Object';
   }
-  if (typeof object === "object" && !object.constructor) {
-    return "Object";
+  if (typeof object === 'object' && !object.constructor) {
+    return 'Object';
   }
 
   const funcNameRegex = /function ([^(]*)/;
@@ -37,7 +37,7 @@ export function getObjectName(object: Object): string {
   if (results && results.length > 1) {
     return results[1];
   } else {
-    return "";
+    return '';
   }
 }
 
@@ -46,7 +46,7 @@ export function getObjectName(object: Object): string {
 */
 export function getType(object: Object): string {
   if (object === null) {
-    return "null";
+    return 'null';
   }
   return typeof object;
 }
@@ -57,20 +57,20 @@ export function getType(object: Object): string {
 export function getValuePreview(object: Object, value: string): string {
   var type = getType(object);
 
-  if (type === "null" || type === "undefined") {
+  if (type === 'null' || type === 'undefined') {
     return type;
   }
 
-  if (type === "string") {
+  if (type === 'string') {
     value = '"' + escapeString(value) + '"';
   }
-  if (type === "function") {
+  if (type === 'function') {
     // Remove content of the function
     return (
       object
         .toString()
-        .replace(/[\r\n]/g, "")
-        .replace(/\{.*\}/, "") + "{…}"
+        .replace(/[\r\n]/g, '')
+        .replace(/\{.*\}/, '') + '{…}'
     );
   }
   return value;
@@ -80,11 +80,11 @@ export function getValuePreview(object: Object, value: string): string {
  * Generates inline preview for a JavaScript object
 */
 export function getPreview(object: string): string {
-  let value = "";
+  let value = '';
   if (isObject(object)) {
     value = getObjectName(object);
     if (Array.isArray(object)) {
-      value += "[" + object.length + "]";
+      value += '[' + object.length + ']';
     }
   } else {
     value = getValuePreview(object, object);
@@ -103,11 +103,7 @@ export function cssClass(className: string): string {
   * Creates a new DOM element wiht given type and class
   * TODO: move me to helpers
 */
-export function createElement(
-  type: string,
-  className?: string,
-  content?: Element | string
-): Element {
+export function createElement(type: string, className?: string, content?: Element | string): Element {
   const el = document.createElement(type);
   if (className) {
     el.classList.add(cssClass(className));

+ 40 - 75
public/app/core/components/json_explorer/json_explorer.ts

@@ -1,16 +1,9 @@
 // Based on work https://github.com/mohsen1/json-formatter-js
 // Licence MIT, Copyright (c) 2015 Mohsen Azimi
 
-import {
-  isObject,
-  getObjectName,
-  getType,
-  getValuePreview,
-  cssClass,
-  createElement
-} from "./helpers";
+import { isObject, getObjectName, getType, getValuePreview, cssClass, createElement } from './helpers';
 
-import _ from "lodash";
+import _ from 'lodash';
 
 const DATE_STRING_REGEX = /(^\d{1,4}[\.|\\/|-]\d{1,2}[\.|\\/|-]\d{1,4})(\s*(?:0?[1-9]:[0-5]|1(?=[012])\d:[0-5])\d\s*[ap]m)?$/;
 const PARTIAL_DATE_REGEX = /\d{2}:\d{2}:\d{2} GMT-\d{4}/;
@@ -35,7 +28,7 @@ export interface JsonExplorerConfig {
 const _defaultConfig: JsonExplorerConfig = {
   animateOpen: true,
   animateClose: true,
-  theme: null
+  theme: null,
 };
 
 /**
@@ -111,10 +104,8 @@ export class JsonExplorer {
   */
   private get isDate(): boolean {
     return (
-      this.type === "string" &&
-      (DATE_STRING_REGEX.test(this.json) ||
-        JSON_DATE_REGEX.test(this.json) ||
-        PARTIAL_DATE_REGEX.test(this.json))
+      this.type === 'string' &&
+      (DATE_STRING_REGEX.test(this.json) || JSON_DATE_REGEX.test(this.json) || PARTIAL_DATE_REGEX.test(this.json))
     );
   }
 
@@ -122,7 +113,7 @@ export class JsonExplorer {
    * is this a URL string?
   */
   private get isUrl(): boolean {
-    return this.type === "string" && this.json.indexOf("http") === 0;
+    return this.type === 'string' && this.json.indexOf('http') === 0;
   }
 
   /*
@@ -151,9 +142,7 @@ export class JsonExplorer {
    * is this an empty object or array?
   */
   private get isEmpty(): boolean {
-    return (
-      this.isEmptyObject || (this.keys && !this.keys.length && this.isArray)
-    );
+    return this.isEmptyObject || (this.keys && !this.keys.length && this.isArray);
   }
 
   /*
@@ -161,7 +150,7 @@ export class JsonExplorer {
    * This means that the formatter was called as a sub formatter of a parent formatter
   */
   private get hasKey(): boolean {
-    return typeof this.key !== "undefined";
+    return typeof this.key !== 'undefined';
   }
 
   /*
@@ -204,7 +193,7 @@ export class JsonExplorer {
       } else {
         this.removeChildren(this.config.animateClose);
       }
-      this.element.classList.toggle(cssClass("open"));
+      this.element.classList.toggle(cssClass('open'));
     }
   }
 
@@ -225,44 +214,36 @@ export class JsonExplorer {
       this.removeChildren(false);
 
       if (depth === 0) {
-        this.element.classList.remove(cssClass("open"));
+        this.element.classList.remove(cssClass('open'));
       } else {
         this.appendChildren(this.config.animateOpen);
-        this.element.classList.add(cssClass("open"));
+        this.element.classList.add(cssClass('open'));
       }
     }
   }
 
   isNumberArray() {
-    return (
-      this.json.length > 0 &&
-      this.json.length < 4 &&
-      (_.isNumber(this.json[0]) || _.isNumber(this.json[1]))
-    );
+    return this.json.length > 0 && this.json.length < 4 && (_.isNumber(this.json[0]) || _.isNumber(this.json[1]));
   }
 
   renderArray() {
-    const arrayWrapperSpan = createElement("span");
-    arrayWrapperSpan.appendChild(createElement("span", "bracket", "["));
+    const arrayWrapperSpan = createElement('span');
+    arrayWrapperSpan.appendChild(createElement('span', 'bracket', '['));
 
     // some pretty handling of number arrays
     if (this.isNumberArray()) {
       this.json.forEach((val, index) => {
         if (index > 0) {
-          arrayWrapperSpan.appendChild(
-            createElement("span", "array-comma", ",")
-          );
+          arrayWrapperSpan.appendChild(createElement('span', 'array-comma', ','));
         }
-        arrayWrapperSpan.appendChild(createElement("span", "number", val));
+        arrayWrapperSpan.appendChild(createElement('span', 'number', val));
       });
       this.skipChildren = true;
     } else {
-      arrayWrapperSpan.appendChild(
-        createElement("span", "number", this.json.length)
-      );
+      arrayWrapperSpan.appendChild(createElement('span', 'number', this.json.length));
     }
 
-    arrayWrapperSpan.appendChild(createElement("span", "bracket", "]"));
+    arrayWrapperSpan.appendChild(createElement('span', 'bracket', ']'));
     return arrayWrapperSpan;
   }
 
@@ -273,11 +254,11 @@ export class JsonExplorer {
    */
   render(skipRoot = false): HTMLDivElement {
     // construct the root element and assign it to this.element
-    this.element = createElement("div", "row");
+    this.element = createElement('div', 'row');
 
     // construct the toggler link
-    const togglerLink = createElement("a", "toggler-link");
-    const togglerIcon = createElement("span", "toggler");
+    const togglerLink = createElement('a', 'toggler-link');
+    const togglerIcon = createElement('span', 'toggler');
 
     // if this is an object we need a wrapper span (toggler)
     if (this.isObject) {
@@ -286,23 +267,19 @@ export class JsonExplorer {
 
     // if this is child of a parent formatter we need to append the key
     if (this.hasKey) {
-      togglerLink.appendChild(createElement("span", "key", `${this.key}:`));
+      togglerLink.appendChild(createElement('span', 'key', `${this.key}:`));
     }
 
     // Value for objects and arrays
     if (this.isObject) {
       // construct the value holder element
-      const value = createElement("span", "value");
+      const value = createElement('span', 'value');
 
       // we need a wrapper span for objects
-      const objectWrapperSpan = createElement("span");
+      const objectWrapperSpan = createElement('span');
 
       // get constructor name and append it to wrapper span
-      var constructorName = createElement(
-        "span",
-        "constructor-name",
-        this.constructorName
-      );
+      var constructorName = createElement('span', 'constructor-name', this.constructorName);
       objectWrapperSpan.appendChild(constructorName);
 
       // if it's an array append the array specific elements like brackets and length
@@ -317,16 +294,16 @@ export class JsonExplorer {
       // Primitive values
     } else {
       // make a value holder element
-      const value = this.isUrl ? createElement("a") : createElement("span");
+      const value = this.isUrl ? createElement('a') : createElement('span');
 
       // add type and other type related CSS classes
       value.classList.add(cssClass(this.type));
       if (this.isDate) {
-        value.classList.add(cssClass("date"));
+        value.classList.add(cssClass('date'));
       }
       if (this.isUrl) {
-        value.classList.add(cssClass("url"));
-        value.setAttribute("href", this.json);
+        value.classList.add(cssClass('url'));
+        value.setAttribute('href', this.json);
       }
 
       // Append value content to value element
@@ -338,17 +315,17 @@ export class JsonExplorer {
     }
 
     // construct a children element
-    const children = createElement("div", "children");
+    const children = createElement('div', 'children');
 
     // set CSS classes for children
     if (this.isObject) {
-      children.classList.add(cssClass("object"));
+      children.classList.add(cssClass('object'));
     }
     if (this.isArray) {
-      children.classList.add(cssClass("array"));
+      children.classList.add(cssClass('array'));
     }
     if (this.isEmpty) {
-      children.classList.add(cssClass("empty"));
+      children.classList.add(cssClass('empty'));
     }
 
     // set CSS classes for root element
@@ -356,7 +333,7 @@ export class JsonExplorer {
       this.element.classList.add(cssClass(this.config.theme));
     }
     if (this.isOpen) {
-      this.element.classList.add(cssClass("open"));
+      this.element.classList.add(cssClass('open'));
     }
 
     // append toggler and children elements to root element
@@ -378,7 +355,7 @@ export class JsonExplorer {
 
     // add event listener for toggling
     if (this.isObject) {
-      togglerLink.addEventListener("click", this.toggleOpen.bind(this));
+      togglerLink.addEventListener('click', this.toggleOpen.bind(this));
     }
 
     return this.element as HTMLDivElement;
@@ -389,7 +366,7 @@ export class JsonExplorer {
    * Animated option is used when user triggers this via a click
    */
   appendChildren(animated = false) {
-    const children = this.element.querySelector(`div.${cssClass("children")}`);
+    const children = this.element.querySelector(`div.${cssClass('children')}`);
 
     if (!children || this.isEmpty) {
       return;
@@ -399,12 +376,7 @@ export class JsonExplorer {
       let index = 0;
       const addAChild = () => {
         const key = this.keys[index];
-        const formatter = new JsonExplorer(
-          this.json[key],
-          this.open - 1,
-          this.config,
-          key
-        );
+        const formatter = new JsonExplorer(this.json[key], this.open - 1, this.config, key);
         children.appendChild(formatter.render());
 
         index += 1;
@@ -421,12 +393,7 @@ export class JsonExplorer {
       requestAnimationFrame(addAChild);
     } else {
       this.keys.forEach(key => {
-        const formatter = new JsonExplorer(
-          this.json[key],
-          this.open - 1,
-          this.config,
-          key
-        );
+        const formatter = new JsonExplorer(this.json[key], this.open - 1, this.config, key);
         children.appendChild(formatter.render());
       });
     }
@@ -437,9 +404,7 @@ export class JsonExplorer {
    * Animated option is used when user triggers this via a click
    */
   removeChildren(animated = false) {
-    const childrenElement = this.element.querySelector(
-      `div.${cssClass("children")}`
-    ) as HTMLDivElement;
+    const childrenElement = this.element.querySelector(`div.${cssClass('children')}`) as HTMLDivElement;
 
     if (animated) {
       let childrenRemoved = 0;
@@ -457,7 +422,7 @@ export class JsonExplorer {
       requestAnimationFrame(removeAChild);
     } else {
       if (childrenElement) {
-        childrenElement.innerHTML = "";
+        childrenElement.innerHTML = '';
       }
     }
   }

+ 10 - 10
public/app/core/components/jsontree/jsontree.ts

@@ -1,23 +1,23 @@
-import coreModule from "app/core/core_module";
-import { JsonExplorer } from "../json_explorer/json_explorer";
+import coreModule from 'app/core/core_module';
+import { JsonExplorer } from '../json_explorer/json_explorer';
 
-coreModule.directive("jsonTree", [
+coreModule.directive('jsonTree', [
   function jsonTreeDirective() {
     return {
-      restrict: "E",
+      restrict: 'E',
       scope: {
-        object: "=",
-        startExpanded: "@",
-        rootName: "@"
+        object: '=',
+        startExpanded: '@',
+        rootName: '@',
       },
       link: function(scope, elem) {
         var jsonExp = new JsonExplorer(scope.object, 3, {
-          animateOpen: true
+          animateOpen: true,
         });
 
         const html = jsonExp.render(true);
         elem.html(html);
-      }
+      },
     };
-  }
+  },
 ]);

+ 20 - 20
public/app/core/components/layout_selector/layout_selector.ts

@@ -1,5 +1,5 @@
-import store from "app/core/store";
-import coreModule from "app/core/core_module";
+import store from 'app/core/store';
+import coreModule from 'app/core/core_module';
 
 var template = `
 <div class="layout-selector">
@@ -17,56 +17,56 @@ export class LayoutSelectorCtrl {
 
   /** @ngInject **/
   constructor(private $rootScope) {
-    this.mode = store.get("grafana.list.layout.mode") || "grid";
+    this.mode = store.get('grafana.list.layout.mode') || 'grid';
   }
 
   listView() {
-    this.mode = "list";
-    store.set("grafana.list.layout.mode", "list");
-    this.$rootScope.appEvent("layout-mode-changed", "list");
+    this.mode = 'list';
+    store.set('grafana.list.layout.mode', 'list');
+    this.$rootScope.appEvent('layout-mode-changed', 'list');
   }
 
   gridView() {
-    this.mode = "grid";
-    store.set("grafana.list.layout.mode", "grid");
-    this.$rootScope.appEvent("layout-mode-changed", "grid");
+    this.mode = 'grid';
+    store.set('grafana.list.layout.mode', 'grid');
+    this.$rootScope.appEvent('layout-mode-changed', 'grid');
   }
 }
 
 /** @ngInject **/
 export function layoutSelector() {
   return {
-    restrict: "E",
+    restrict: 'E',
     controller: LayoutSelectorCtrl,
     bindToController: true,
-    controllerAs: "ctrl",
+    controllerAs: 'ctrl',
     scope: {},
-    template: template
+    template: template,
   };
 }
 
 /** @ngInject **/
 export function layoutMode($rootScope) {
   return {
-    restrict: "A",
+    restrict: 'A',
     scope: {},
     link: function(scope, elem) {
-      var layout = store.get("grafana.list.layout.mode") || "grid";
-      var className = "card-list-layout-" + layout;
+      var layout = store.get('grafana.list.layout.mode') || 'grid';
+      var className = 'card-list-layout-' + layout;
       elem.addClass(className);
 
       $rootScope.onAppEvent(
-        "layout-mode-changed",
+        'layout-mode-changed',
         (evt, newLayout) => {
           elem.removeClass(className);
-          className = "card-list-layout-" + newLayout;
+          className = 'card-list-layout-' + newLayout;
           elem.addClass(className);
         },
         scope
       );
-    }
+    },
   };
 }
 
-coreModule.directive("layoutSelector", layoutSelector);
-coreModule.directive("layoutMode", layoutMode);
+coreModule.directive('layoutSelector', layoutSelector);
+coreModule.directive('layoutMode', layoutMode);

+ 17 - 15
public/app/core/components/manage_dashboards/manage_dashboards.html

@@ -5,7 +5,7 @@
       <i class="gf-form-input-icon fa fa-search"></i>
     </label>
     <div class="page-action-bar__spacer"></div>
-    <a class="btn btn-success" href="/dashboard/new?folderId={{ctrl.folderId}}">
+    <a class="btn btn-success" ng-href="{{ctrl.createDashboardUrl()}}">
       <i class="fa fa-plus"></i>
       Dashboard
     </a>
@@ -60,20 +60,22 @@
         switch-class="gf-form-switch--transparent gf-form-switch--search-result-filter-row__checkbox"
       />
       <div class="search-results-filter-row__filters">
-        <select
-          class="search-results-filter-row__filters-item gf-form-input"
-          ng-model="ctrl.selectedStarredFilter"
-          ng-options="t.text disable when t.disabled for t in ctrl.starredFilterOptions"
-          ng-change="ctrl.onStarredFilterChange()"
-          ng-show="!(ctrl.canMove || ctrl.canDelete)"
-        />
-        <select
-          class="search-results-filter-row__filters-item gf-form-input"
-          ng-model="ctrl.selectedTagFilter"
-          ng-options="t.term disable when t.disabled for t in ctrl.tagFilterOptions"
-          ng-change="ctrl.onTagFilterChange()"
-          ng-show="!(ctrl.canMove || ctrl.canDelete)"
-        />
+        <div class="gf-form-select-wrapper" ng-show="!(ctrl.canMove || ctrl.canDelete)">
+          <select
+            class="search-results-filter-row__filters-item gf-form-input"
+            ng-model="ctrl.selectedStarredFilter"
+            ng-options="t.text disable when t.disabled for t in ctrl.starredFilterOptions"
+            ng-change="ctrl.onStarredFilterChange()"
+          />
+        </div>
+        <div class="gf-form-select-wrapper" ng-show="!(ctrl.canMove || ctrl.canDelete)">
+          <select
+            class="search-results-filter-row__filters-item gf-form-input"
+            ng-model="ctrl.selectedTagFilter"
+            ng-options="t.term disable when t.disabled for t in ctrl.tagFilterOptions"
+            ng-change="ctrl.onTagFilterChange()"
+          />
+        </div>
         <div class="gf-form-button-row" ng-show="ctrl.canMove || ctrl.canDelete">
           <button	type="button"
             class="btn gf-form-button btn-inverse"

+ 54 - 68
public/app/core/components/manage_dashboards/manage_dashboards.ts

@@ -1,7 +1,7 @@
-import _ from "lodash";
-import coreModule from "app/core/core_module";
-import appEvents from "app/core/app_events";
-import { SearchSrv } from "app/core/services/search_srv";
+import _ from 'lodash';
+import coreModule from 'app/core/core_module';
+import appEvents from 'app/core/app_events';
+import { SearchSrv } from 'app/core/services/search_srv';
 
 export class ManageDashboardsCtrl {
   public sections: any[];
@@ -13,23 +13,19 @@ export class ManageDashboardsCtrl {
   canMove = false;
   hasFilters = false;
   selectAllChecked = false;
-  starredFilterOptions = [
-    { text: "Filter by Starred", disabled: true },
-    { text: "Yes" },
-    { text: "No" }
-  ];
+  starredFilterOptions = [{ text: 'Filter by Starred', disabled: true }, { text: 'Yes' }, { text: 'No' }];
   selectedStarredFilter: any;
   folderId?: number;
 
   /** @ngInject */
   constructor(private backendSrv, navModelSrv, private searchSrv: SearchSrv) {
     this.query = {
-      query: "",
-      mode: "tree",
+      query: '',
+      mode: 'tree',
       tag: [],
       starred: false,
       skipRecent: true,
-      skipStarred: true
+      skipStarred: true,
     };
 
     if (this.folderId) {
@@ -53,10 +49,7 @@ export class ManageDashboardsCtrl {
     this.canMove = false;
     this.canDelete = false;
     this.selectAllChecked = false;
-    this.hasFilters =
-      this.query.query.length > 0 ||
-      this.query.tag.length > 0 ||
-      this.query.starred;
+    this.hasFilters = this.query.query.length > 0 || this.query.tag.length > 0 || this.query.starred;
 
     if (!result) {
       this.sections = [];
@@ -93,7 +86,7 @@ export class ManageDashboardsCtrl {
   getFoldersAndDashboardsToDelete() {
     let selectedDashboards = {
       folders: [],
-      dashboards: []
+      dashboards: [],
     };
 
     for (const section of this.sections) {
@@ -101,7 +94,7 @@ export class ManageDashboardsCtrl {
         selectedDashboards.folders.push(section.slug);
       } else {
         const selected = _.filter(section.items, { checked: true });
-        selectedDashboards.dashboards.push(..._.map(selected, "slug"));
+        selectedDashboards.dashboards.push(..._.map(selected, 'slug'));
       }
     }
 
@@ -122,34 +115,28 @@ export class ManageDashboardsCtrl {
     const data = this.getFoldersAndDashboardsToDelete();
     const folderCount = data.folders.length;
     const dashCount = data.dashboards.length;
-    let text = "Do you want to delete the ";
+    let text = 'Do you want to delete the ';
     let text2;
 
     if (folderCount > 0 && dashCount > 0) {
-      text += `selected folder${folderCount === 1 ? "" : "s"} and dashboard${
-        dashCount === 1 ? "" : "s"
-      }?`;
-      text2 = `All dashboards of the selected folder${
-        folderCount === 1 ? "" : "s"
-      } will also be deleted`;
+      text += `selected folder${folderCount === 1 ? '' : 's'} and dashboard${dashCount === 1 ? '' : 's'}?`;
+      text2 = `All dashboards of the selected folder${folderCount === 1 ? '' : 's'} will also be deleted`;
     } else if (folderCount > 0) {
-      text += `selected folder${
-        folderCount === 1 ? "" : "s"
-      } and all its dashboards?`;
+      text += `selected folder${folderCount === 1 ? '' : 's'} and all its dashboards?`;
     } else {
-      text += `selected dashboard${dashCount === 1 ? "" : "s"}?`;
+      text += `selected dashboard${dashCount === 1 ? '' : 's'}?`;
     }
 
-    appEvents.emit("confirm-modal", {
-      title: "Delete",
+    appEvents.emit('confirm-modal', {
+      title: 'Delete',
       text: text,
       text2: text2,
-      icon: "fa-trash",
-      yesText: "Delete",
+      icon: 'fa-trash',
+      yesText: 'Delete',
       onConfirm: () => {
         const foldersAndDashboards = data.folders.concat(data.dashboards);
         this.deleteFoldersAndDashboards(foldersAndDashboards);
-      }
+      },
     });
   }
 
@@ -165,36 +152,28 @@ export class ManageDashboardsCtrl {
         let msg;
 
         if (folderCount > 0 && dashCount > 0) {
-          header = `Folder${folderCount === 1 ? "" : "s"} And Dashboard${
-            dashCount === 1 ? "" : "s"
-          } Deleted`;
-          msg = `${folderCount} folder${folderCount === 1 ? "" : "s"} `;
-          msg += `and ${dashCount} dashboard${
-            dashCount === 1 ? "" : "s"
-          } has been deleted`;
+          header = `Folder${folderCount === 1 ? '' : 's'} And Dashboard${dashCount === 1 ? '' : 's'} Deleted`;
+          msg = `${folderCount} folder${folderCount === 1 ? '' : 's'} `;
+          msg += `and ${dashCount} dashboard${dashCount === 1 ? '' : 's'} has been deleted`;
         } else if (folderCount > 0) {
-          header = `Folder${folderCount === 1 ? "" : "s"} Deleted`;
+          header = `Folder${folderCount === 1 ? '' : 's'} Deleted`;
 
           if (folderCount === 1) {
             msg = `${folders[0].dashboard.title} has been deleted`;
           } else {
-            msg = `${folderCount} folder${
-              folderCount === 1 ? "" : "s"
-            } has been deleted`;
+            msg = `${folderCount} folder${folderCount === 1 ? '' : 's'} has been deleted`;
           }
         } else if (dashCount > 0) {
-          header = `Dashboard${dashCount === 1 ? "" : "s"} Deleted`;
+          header = `Dashboard${dashCount === 1 ? '' : 's'} Deleted`;
 
           if (dashCount === 1) {
             msg = `${dashboards[0].dashboard.title} has been deleted`;
           } else {
-            msg = `${dashCount} dashboard${
-              dashCount === 1 ? "" : "s"
-            } has been deleted`;
+            msg = `${dashCount} dashboard${dashCount === 1 ? '' : 's'} has been deleted`;
           }
         }
 
-        appEvents.emit("alert-success", [header, msg]);
+        appEvents.emit('alert-success', [header, msg]);
       }
 
       this.getDashboards();
@@ -206,7 +185,7 @@ export class ManageDashboardsCtrl {
 
     for (const section of this.sections) {
       const selected = _.filter(section.items, { checked: true });
-      selectedDashboards.push(..._.map(selected, "slug"));
+      selectedDashboards.push(..._.map(selected, 'slug'));
     }
 
     return selectedDashboards;
@@ -218,22 +197,20 @@ export class ManageDashboardsCtrl {
     const template =
       '<move-to-folder-modal dismiss="dismiss()" ' +
       'dashboards="model.dashboards" after-save="model.afterSave()">' +
-      "</move-to-folder-modal>`";
-    appEvents.emit("show-modal", {
+      '</move-to-folder-modal>`';
+    appEvents.emit('show-modal', {
       templateHtml: template,
-      modalClass: "modal--narrow",
+      modalClass: 'modal--narrow',
       model: {
         dashboards: selectedDashboards,
-        afterSave: this.getDashboards.bind(this)
-      }
+        afterSave: this.getDashboards.bind(this),
+      },
     });
   }
 
   getTags() {
     return this.searchSrv.getDashboardTags().then(results => {
-      this.tagFilterOptions = [
-        { term: "Filter By Tag", disabled: true }
-      ].concat(results);
+      this.tagFilterOptions = [{ term: 'Filter By Tag', disabled: true }].concat(results);
       this.selectedTagFilter = this.tagFilterOptions[0];
     });
   }
@@ -271,7 +248,7 @@ export class ManageDashboardsCtrl {
   }
 
   onStarredFilterChange() {
-    this.query.starred = this.selectedStarredFilter.text === "Yes";
+    this.query.starred = this.selectedStarredFilter.text === 'Yes';
     this.selectedStarredFilter = this.starredFilterOptions[0];
     return this.getDashboards();
   }
@@ -292,25 +269,34 @@ export class ManageDashboardsCtrl {
   }
 
   clearFilters() {
-    this.query.query = "";
+    this.query.query = '';
     this.query.tag = [];
     this.query.starred = false;
     this.getDashboards();
   }
+
+  createDashboardUrl() {
+    let url = '/dashboard/new';
+
+    if (this.folderId) {
+      url += `?folderId=${this.folderId}`;
+    }
+
+    return url;
+  }
 }
 
 export function manageDashboardsDirective() {
   return {
-    restrict: "E",
-    templateUrl:
-      "public/app/core/components/manage_dashboards/manage_dashboards.html",
+    restrict: 'E',
+    templateUrl: 'public/app/core/components/manage_dashboards/manage_dashboards.html',
     controller: ManageDashboardsCtrl,
     bindToController: true,
-    controllerAs: "ctrl",
+    controllerAs: 'ctrl',
     scope: {
-      folderId: "="
-    }
+      folderId: '=',
+    },
   };
 }
 
-coreModule.directive("manageDashboards", manageDashboardsDirective);
+coreModule.directive('manageDashboards', manageDashboardsDirective);

+ 14 - 14
public/app/core/components/navbar/navbar.ts

@@ -1,6 +1,6 @@
-import coreModule from "../../core_module";
-import { NavModel } from "../../nav_model_srv";
-import appEvents from "app/core/app_events";
+import coreModule from '../../core_module';
+import { NavModel } from '../../nav_model_srv';
+import appEvents from 'app/core/app_events';
 
 export class NavbarCtrl {
   model: NavModel;
@@ -9,7 +9,7 @@ export class NavbarCtrl {
   constructor() {}
 
   showSearch() {
-    appEvents.emit("show-dash-search");
+    appEvents.emit('show-dash-search');
   }
 
   navItemClicked(navItem, evt) {
@@ -22,21 +22,21 @@ export class NavbarCtrl {
 
 export function navbarDirective() {
   return {
-    restrict: "E",
-    templateUrl: "public/app/core/components/navbar/navbar.html",
+    restrict: 'E',
+    templateUrl: 'public/app/core/components/navbar/navbar.html',
     controller: NavbarCtrl,
     bindToController: true,
-    controllerAs: "ctrl",
+    controllerAs: 'ctrl',
     scope: {
-      model: "="
+      model: '=',
     },
-    link: function(scope, elem) {}
+    link: function(scope, elem) {},
   };
 }
 
 export function pageH1() {
   return {
-    restrict: "E",
+    restrict: 'E',
     template: `
     <h1 class="page-header__title">
       <i class="page-header__icon {{::model.header.icon}}" ng-if="::model.header.icon"></i>
@@ -45,10 +45,10 @@ export function pageH1() {
     </h1>
     `,
     scope: {
-      model: "="
-    }
+      model: '=',
+    },
   };
 }
 
-coreModule.directive("pageH1", pageH1);
-coreModule.directive("navbar", navbarDirective);
+coreModule.directive('pageH1', pageH1);
+coreModule.directive('navbar', navbarDirective);

+ 9 - 11
public/app/core/components/org_switcher.ts

@@ -1,7 +1,7 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import coreModule from "app/core/core_module";
-import { contextSrv } from "app/core/services/context_srv";
+import coreModule from 'app/core/core_module';
+import { contextSrv } from 'app/core/services/context_srv';
 
 const template = `
 <div class="modal-body">
@@ -55,17 +55,15 @@ export class OrgSwitchCtrl {
   }
 
   getUserOrgs() {
-    this.backendSrv.get("/api/user/orgs").then(orgs => {
+    this.backendSrv.get('/api/user/orgs').then(orgs => {
       this.orgs = orgs;
     });
   }
 
   setUsingOrg(org) {
-    return this.backendSrv.post("/api/user/using/" + org.orgId).then(() => {
+    return this.backendSrv.post('/api/user/using/' + org.orgId).then(() => {
       const re = /orgId=\d+/gi;
-      this.setWindowLocationHref(
-        this.getWindowLocationHref().replace(re, "orgId=" + org.orgId)
-      );
+      this.setWindowLocationHref(this.getWindowLocationHref().replace(re, 'orgId=' + org.orgId));
     });
   }
 
@@ -80,13 +78,13 @@ export class OrgSwitchCtrl {
 
 export function orgSwitcher() {
   return {
-    restrict: "E",
+    restrict: 'E',
     template: template,
     controller: OrgSwitchCtrl,
     bindToController: true,
-    controllerAs: "ctrl",
-    scope: { dismiss: "&" }
+    controllerAs: 'ctrl',
+    scope: { dismiss: '&' },
   };
 }
 
-coreModule.directive("orgSwitcher", orgSwitcher);
+coreModule.directive('orgSwitcher', orgSwitcher);

+ 17 - 17
public/app/core/components/query_part/query_part.ts

@@ -1,6 +1,6 @@
 ///<reference path="../../../headers/common.d.ts" />
 
-import _ from "lodash";
+import _ from 'lodash';
 
 export class QueryPartDef {
   type: string;
@@ -30,7 +30,7 @@ export class QueryPart {
     this.part = part;
     this.def = def;
     if (!this.def) {
-      throw { message: "Could not find query part " + part.type };
+      throw { message: 'Could not find query part ' + part.type };
     }
 
     part.params = part.params || _.clone(this.def.defaultParams);
@@ -43,7 +43,7 @@ export class QueryPart {
   }
 
   hasMultipleParamsInString(strValue, index) {
-    if (strValue.indexOf(",") === -1) {
+    if (strValue.indexOf(',') === -1) {
       return false;
     }
 
@@ -54,13 +54,13 @@ export class QueryPart {
     // handle optional parameters
     // if string contains ',' and next param is optional, split and update both
     if (this.hasMultipleParamsInString(strValue, index)) {
-      _.each(strValue.split(","), (partVal, idx) => {
+      _.each(strValue.split(','), (partVal, idx) => {
         this.updateParam(partVal.trim(), idx);
       });
       return;
     }
 
-    if (strValue === "" && this.def.params[index].optional) {
+    if (strValue === '' && this.def.params[index].optional) {
       this.params.splice(index, 1);
     } else {
       this.params[index] = strValue;
@@ -72,29 +72,29 @@ export class QueryPart {
 
   updateText() {
     if (this.params.length === 0) {
-      this.text = this.def.type + "()";
+      this.text = this.def.type + '()';
       return;
     }
 
-    var text = this.def.type + "(";
-    text += this.params.join(", ");
-    text += ")";
+    var text = this.def.type + '(';
+    text += this.params.join(', ');
+    text += ')';
     this.text = text;
   }
 }
 
 export function functionRenderer(part, innerExpr) {
-  var str = part.def.type + "(";
+  var str = part.def.type + '(';
   var parameters = _.map(part.params, (value, index) => {
     var paramType = part.def.params[index];
-    if (paramType.type === "time") {
-      if (value === "auto") {
-        value = "$__interval";
+    if (paramType.type === 'time') {
+      if (value === 'auto') {
+        value = '$__interval';
       }
     }
-    if (paramType.quote === "single") {
+    if (paramType.quote === 'single') {
       return "'" + value + "'";
-    } else if (paramType.quote === "double") {
+    } else if (paramType.quote === 'double') {
       return '"' + value + '"';
     }
 
@@ -104,11 +104,11 @@ export function functionRenderer(part, innerExpr) {
   if (innerExpr) {
     parameters.unshift(innerExpr);
   }
-  return str + parameters.join(", ") + ")";
+  return str + parameters.join(', ') + ')';
 }
 
 export function suffixRenderer(part, innerExpr) {
-  return innerExpr + " " + part.params[0];
+  return innerExpr + ' ' + part.params[0];
 }
 
 export function identityRenderer(part, innerExpr) {

+ 33 - 42
public/app/core/components/query_part/query_part_editor.ts

@@ -1,8 +1,8 @@
 ///<reference path="../../../headers/common.d.ts" />
 
-import _ from "lodash";
-import $ from "jquery";
-import coreModule from "app/core/core_module";
+import _ from 'lodash';
+import $ from 'jquery';
+import coreModule from 'app/core/core_module';
 
 var template = `
 <div class="dropdown cascade-open">
@@ -17,20 +17,19 @@ var template = `
 
 /** @ngInject */
 export function queryPartEditorDirective($compile, templateSrv) {
-  var paramTemplate =
-    '<input type="text" class="hide input-mini tight-form-func-param"></input>';
+  var paramTemplate = '<input type="text" class="hide input-mini tight-form-func-param"></input>';
 
   return {
-    restrict: "E",
+    restrict: 'E',
     template: template,
     scope: {
-      part: "=",
-      handleEvent: "&"
+      part: '=',
+      handleEvent: '&',
     },
     link: function postLink($scope, elem) {
       var part = $scope.part;
       var partDef = part.def;
-      var $paramsContainer = elem.find(".query-part-parameters");
+      var $paramsContainer = elem.find('.query-part-parameters');
 
       $scope.partActions = [];
 
@@ -40,16 +39,16 @@ export function queryPartEditorDirective($compile, templateSrv) {
         var $input = $link.next();
 
         $input.val(part.params[paramIndex]);
-        $input.css("width", $link.width() + 16 + "px");
+        $input.css('width', $link.width() + 16 + 'px');
 
         $link.hide();
         $input.show();
         $input.focus();
         $input.select();
 
-        var typeahead = $input.data("typeahead");
+        var typeahead = $input.data('typeahead');
         if (typeahead) {
-          $input.val("");
+          $input.val('');
           typeahead.lookup();
         }
       }
@@ -60,12 +59,12 @@ export function queryPartEditorDirective($compile, templateSrv) {
         var $link = $input.prev();
         var newValue = $input.val();
 
-        if (newValue !== "" || part.def.params[paramIndex].optional) {
+        if (newValue !== '' || part.def.params[paramIndex].optional) {
           $link.html(templateSrv.highlightVariablesAsHtml(newValue));
 
           part.updateParam($input.val(), paramIndex);
           $scope.$apply(() => {
-            $scope.handleEvent({ $event: { name: "part-param-changed" } });
+            $scope.handleEvent({ $event: { name: 'part-param-changed' } });
           });
         }
 
@@ -82,7 +81,7 @@ export function queryPartEditorDirective($compile, templateSrv) {
 
       function inputKeyDown() {
         /*jshint validthis:true */
-        this.style.width = (3 + this.value.length) * 8 + "px";
+        this.style.width = (3 + this.value.length) * 8 + 'px';
       }
 
       function addTypeahead($input, param, paramIndex) {
@@ -93,7 +92,7 @@ export function queryPartEditorDirective($compile, templateSrv) {
         var typeaheadSource = function(query, callback) {
           if (param.options) {
             var options = param.options;
-            if (param.type === "int") {
+            if (param.type === 'int') {
               options = _.map(options, function(val) {
                 return val.toString();
               });
@@ -102,18 +101,16 @@ export function queryPartEditorDirective($compile, templateSrv) {
           }
 
           $scope.$apply(function() {
-            $scope
-              .handleEvent({ $event: { name: "get-param-options" } })
-              .then(function(result) {
-                var dynamicOptions = _.map(result, function(op) {
-                  return op.value;
-                });
-                callback(dynamicOptions);
+            $scope.handleEvent({ $event: { name: 'get-param-options' } }).then(function(result) {
+              var dynamicOptions = _.map(result, function(op) {
+                return op.value;
               });
+              callback(dynamicOptions);
+            });
           });
         };
 
-        $input.attr("data-provide", "typeahead");
+        $input.attr('data-provide', 'typeahead');
 
         $input.typeahead({
           source: typeaheadSource,
@@ -124,27 +121,25 @@ export function queryPartEditorDirective($compile, templateSrv) {
               inputBlur.call($input[0], paramIndex);
             }, 0);
             return value;
-          }
+          },
         });
 
-        var typeahead = $input.data("typeahead");
+        var typeahead = $input.data('typeahead');
         typeahead.lookup = function() {
-          this.query = this.$element.val() || "";
+          this.query = this.$element.val() || '';
           var items = this.source(this.query, $.proxy(this.process, this));
           return items ? this.process(items) : items;
         };
       }
 
       $scope.showActionsMenu = function() {
-        $scope
-          .handleEvent({ $event: { name: "get-part-actions" } })
-          .then(res => {
-            $scope.partActions = res;
-          });
+        $scope.handleEvent({ $event: { name: 'get-part-actions' } }).then(res => {
+          $scope.partActions = res;
+        });
       };
 
       $scope.triggerPartAction = function(action) {
-        $scope.handleEvent({ $event: { name: "action", action: action } });
+        $scope.handleEvent({ $event: { name: 'action', action: action } });
       };
 
       function addElementsAndCompile() {
@@ -154,15 +149,11 @@ export function queryPartEditorDirective($compile, templateSrv) {
           }
 
           if (index > 0) {
-            $("<span>, </span>").appendTo($paramsContainer);
+            $('<span>, </span>').appendTo($paramsContainer);
           }
 
-          var paramValue = templateSrv.highlightVariablesAsHtml(
-            part.params[index]
-          );
-          var $paramLink = $(
-            '<a class="graphite-func-param-link pointer">' + paramValue + "</a>"
-          );
+          var paramValue = templateSrv.highlightVariablesAsHtml(part.params[index]);
+          var $paramLink = $('<a class="graphite-func-param-link pointer">' + paramValue + '</a>');
           var $input = $(paramTemplate);
 
           $paramLink.appendTo($paramsContainer);
@@ -183,8 +174,8 @@ export function queryPartEditorDirective($compile, templateSrv) {
       }
 
       relink();
-    }
+    },
   };
 }
 
-coreModule.directive("queryPartEditor", queryPartEditorDirective);
+coreModule.directive('queryPartEditor', queryPartEditorDirective);

+ 8 - 8
public/app/core/components/scroll/scroll.ts

@@ -1,25 +1,25 @@
-import PerfectScrollbar from "perfect-scrollbar";
-import coreModule from "app/core/core_module";
+import PerfectScrollbar from 'perfect-scrollbar';
+import coreModule from 'app/core/core_module';
 
 export function geminiScrollbar() {
   return {
-    restrict: "A",
+    restrict: 'A',
     link: function(scope, elem, attrs) {
       let scrollbar = new PerfectScrollbar(elem[0]);
 
-      scope.$on("$routeChangeSuccess", () => {
+      scope.$on('$routeChangeSuccess', () => {
         elem[0].scrollTop = 0;
       });
 
-      scope.$on("$routeUpdate", () => {
+      scope.$on('$routeUpdate', () => {
         elem[0].scrollTop = 0;
       });
 
-      scope.$on("$destroy", () => {
+      scope.$on('$destroy', () => {
         scrollbar.destroy();
       });
-    }
+    },
   };
 }
 
-coreModule.directive("grafanaScrollbar", geminiScrollbar);
+coreModule.directive('grafanaScrollbar', geminiScrollbar);

+ 22 - 38
public/app/core/components/search/search.ts

@@ -1,7 +1,7 @@
-import _ from "lodash";
-import coreModule from "../../core_module";
-import { SearchSrv } from "app/core/services/search_srv";
-import appEvents from "app/core/app_events";
+import _ from 'lodash';
+import coreModule from '../../core_module';
+import { SearchSrv } from 'app/core/services/search_srv';
+import appEvents from 'app/core/app_events';
 
 export class SearchCtrl {
   isOpen: boolean;
@@ -17,16 +17,11 @@ export class SearchCtrl {
   initialFolderFilterTitle: string;
 
   /** @ngInject */
-  constructor(
-    $scope,
-    private $location,
-    private $timeout,
-    private searchSrv: SearchSrv
-  ) {
-    appEvents.on("show-dash-search", this.openSearch.bind(this), $scope);
-    appEvents.on("hide-dash-search", this.closeSearch.bind(this), $scope);
-
-    this.initialFolderFilterTitle = "All";
+  constructor($scope, private $location, private $timeout, private searchSrv: SearchSrv) {
+    appEvents.on('show-dash-search', this.openSearch.bind(this), $scope);
+    appEvents.on('hide-dash-search', this.closeSearch.bind(this), $scope);
+
+    this.initialFolderFilterTitle = 'All';
   }
 
   closeSearch() {
@@ -43,7 +38,7 @@ export class SearchCtrl {
     this.giveSearchFocus = 0;
     this.selectedIndex = -1;
     this.results = [];
-    this.query = { query: "", tag: [], starred: false };
+    this.query = { query: '', tag: [], starred: false };
     this.currentSearchId = 0;
     this.ignoreClose = true;
     this.isLoading = true;
@@ -75,9 +70,7 @@ export class SearchCtrl {
 
       if (currentItem) {
         if (currentItem.dashboardIndex !== undefined) {
-          const selectedDash = this.results[currentItem.folderIndex].items[
-            currentItem.dashboardIndex
-          ];
+          const selectedDash = this.results[currentItem.folderIndex].items[currentItem.dashboardIndex];
 
           if (selectedDash) {
             this.$location.search({});
@@ -105,9 +98,7 @@ export class SearchCtrl {
 
     if (currentItem) {
       if (currentItem.dashboardIndex !== undefined) {
-        this.results[currentItem.folderIndex].items[
-          currentItem.dashboardIndex
-        ].selected = false;
+        this.results[currentItem.folderIndex].items[currentItem.dashboardIndex].selected = false;
       } else {
         this.results[currentItem.folderIndex].selected = false;
       }
@@ -123,10 +114,7 @@ export class SearchCtrl {
     this.selectedIndex = (newIndex %= max) < 0 ? newIndex + max : newIndex;
     const selectedItem = flattenedResult[this.selectedIndex];
 
-    if (
-      selectedItem.dashboardIndex === undefined &&
-      this.results[selectedItem.folderIndex].id === 0
-    ) {
+    if (selectedItem.dashboardIndex === undefined && this.results[selectedItem.folderIndex].id === 0) {
       this.moveSelection(direction);
       return;
     }
@@ -137,9 +125,7 @@ export class SearchCtrl {
         return;
       }
 
-      this.results[selectedItem.folderIndex].items[
-        selectedItem.dashboardIndex
-      ].selected = true;
+      this.results[selectedItem.folderIndex].items[selectedItem.dashboardIndex].selected = true;
       return;
     }
 
@@ -167,9 +153,7 @@ export class SearchCtrl {
 
   queryHasNoFilters() {
     var query = this.query;
-    return (
-      query.query === "" && query.starred === false && query.tag.length === 0
-    );
+    return query.query === '' && query.starred === false && query.tag.length === 0;
   }
 
   filterByTag(tag) {
@@ -218,7 +202,7 @@ export class SearchCtrl {
       let result = [];
 
       result.push({
-        folderIndex: folderIndex
+        folderIndex: folderIndex,
       });
 
       let dashboardIndex = 0;
@@ -227,7 +211,7 @@ export class SearchCtrl {
         _.map(s.items || [], i => {
           return {
             folderIndex: folderIndex,
-            dashboardIndex: dashboardIndex++
+            dashboardIndex: dashboardIndex++,
           };
         })
       );
@@ -240,13 +224,13 @@ export class SearchCtrl {
 
 export function searchDirective() {
   return {
-    restrict: "E",
-    templateUrl: "public/app/core/components/search/search.html",
+    restrict: 'E',
+    templateUrl: 'public/app/core/components/search/search.html',
     controller: SearchCtrl,
     bindToController: true,
-    controllerAs: "ctrl",
-    scope: {}
+    controllerAs: 'ctrl',
+    scope: {},
   };
 }
 
-coreModule.directive("dashboardSearch", searchDirective);
+coreModule.directive('dashboardSearch', searchDirective);

+ 14 - 14
public/app/core/components/search/search_results.ts

@@ -1,6 +1,6 @@
-import _ from "lodash";
-import coreModule from "../../core_module";
-import appEvents from "app/core/app_events";
+import _ from 'lodash';
+import coreModule from '../../core_module';
+import appEvents from 'app/core/app_events';
 
 export class SearchResultsCtrl {
   results: any;
@@ -64,7 +64,7 @@ export class SearchResultsCtrl {
 
   onItemClick(item) {
     if (this.$location.path().indexOf(item.url) > -1) {
-      appEvents.emit("hide-dash-search");
+      appEvents.emit('hide-dash-search');
     }
   }
 
@@ -82,19 +82,19 @@ export class SearchResultsCtrl {
 
 export function searchResultsDirective() {
   return {
-    restrict: "E",
-    templateUrl: "public/app/core/components/search/search_results.html",
+    restrict: 'E',
+    templateUrl: 'public/app/core/components/search/search_results.html',
     controller: SearchResultsCtrl,
     bindToController: true,
-    controllerAs: "ctrl",
+    controllerAs: 'ctrl',
     scope: {
-      editable: "@",
-      results: "=",
-      onSelectionChanged: "&",
-      onTagSelected: "&",
-      onFolderExpanding: "&"
-    }
+      editable: '@',
+      results: '=',
+      onSelectionChanged: '&',
+      onTagSelected: '&',
+      onFolderExpanding: '&',
+    },
   };
 }
 
-coreModule.directive("dashboardSearchResults", searchResultsDirective);
+coreModule.directive('dashboardSearchResults', searchResultsDirective);

+ 27 - 41
public/app/core/components/sidemenu/sidemenu.ts

@@ -1,8 +1,8 @@
-import _ from "lodash";
-import config from "app/core/config";
-import $ from "jquery";
-import coreModule from "../../core_module";
-import appEvents from "app/core/app_events";
+import _ from 'lodash';
+import config from 'app/core/config';
+import $ from 'jquery';
+import coreModule from '../../core_module';
+import appEvents from 'app/core/app_events';
 
 export class SideMenuCtrl {
   user: any;
@@ -13,62 +13,48 @@ export class SideMenuCtrl {
   isOpenMobile: boolean;
 
   /** @ngInject */
-  constructor(
-    private $scope,
-    private $rootScope,
-    private $location,
-    private contextSrv,
-    private $timeout
-  ) {
+  constructor(private $scope, private $rootScope, private $location, private contextSrv, private $timeout) {
     this.isSignedIn = contextSrv.isSignedIn;
     this.user = contextSrv.user;
-    this.mainLinks = _.filter(
-      config.bootData.navTree,
-      item => !item.hideFromMenu
-    );
-    this.bottomNav = _.filter(
-      config.bootData.navTree,
-      item => item.hideFromMenu
-    );
-    this.loginUrl =
-      "login?redirect=" + encodeURIComponent(this.$location.path());
+    this.mainLinks = _.filter(config.bootData.navTree, item => !item.hideFromMenu);
+    this.bottomNav = _.filter(config.bootData.navTree, item => item.hideFromMenu);
+    this.loginUrl = 'login?redirect=' + encodeURIComponent(this.$location.path());
 
     if (contextSrv.user.orgCount > 1) {
-      let profileNode = _.find(this.bottomNav, { id: "profile" });
+      let profileNode = _.find(this.bottomNav, { id: 'profile' });
       if (profileNode) {
         profileNode.showOrgSwitcher = true;
       }
     }
 
-    this.$scope.$on("$routeChangeSuccess", () => {
-      this.loginUrl =
-        "login?redirect=" + encodeURIComponent(this.$location.path());
+    this.$scope.$on('$routeChangeSuccess', () => {
+      this.loginUrl = 'login?redirect=' + encodeURIComponent(this.$location.path());
     });
   }
 
   toggleSideMenu() {
     this.contextSrv.toggleSideMenu();
-    appEvents.emit("toggle-sidemenu");
+    appEvents.emit('toggle-sidemenu');
 
     this.$timeout(() => {
-      this.$rootScope.$broadcast("render");
+      this.$rootScope.$broadcast('render');
     });
   }
 
   toggleSideMenuSmallBreakpoint() {
-    appEvents.emit("toggle-sidemenu-mobile");
+    appEvents.emit('toggle-sidemenu-mobile');
   }
 
   switchOrg() {
-    this.$rootScope.appEvent("show-modal", {
-      templateHtml: '<org-switcher dismiss="dismiss()"></org-switcher>'
+    this.$rootScope.appEvent('show-modal', {
+      templateHtml: '<org-switcher dismiss="dismiss()"></org-switcher>',
     });
   }
 
   itemClicked(item, evt) {
-    if (item.url === "/shortcuts") {
-      appEvents.emit("show-modal", {
-        templateHtml: "<help-modal></help-modal>"
+    if (item.url === '/shortcuts') {
+      appEvents.emit('show-modal', {
+        templateHtml: '<help-modal></help-modal>',
       });
       evt.preventDefault();
     }
@@ -77,16 +63,16 @@ export class SideMenuCtrl {
 
 export function sideMenuDirective() {
   return {
-    restrict: "E",
-    templateUrl: "public/app/core/components/sidemenu/sidemenu.html",
+    restrict: 'E',
+    templateUrl: 'public/app/core/components/sidemenu/sidemenu.html',
     controller: SideMenuCtrl,
     bindToController: true,
-    controllerAs: "ctrl",
+    controllerAs: 'ctrl',
     scope: {},
     link: function(scope, elem) {
       // hack to hide dropdown menu
-      elem.on("click.dropdown", ".dropdown-menu a", function(evt) {
-        var menu = $(evt.target).parents(".dropdown-menu");
+      elem.on('click.dropdown', '.dropdown-menu a', function(evt) {
+        var menu = $(evt.target).parents('.dropdown-menu');
         var parent = menu.parent();
         menu.detach();
 
@@ -94,8 +80,8 @@ export function sideMenuDirective() {
           parent.append(menu);
         }, 100);
       });
-    }
+    },
   };
 }
 
-coreModule.directive("sidemenu", sideMenuDirective);
+coreModule.directive('sidemenu', sideMenuDirective);

+ 11 - 11
public/app/core/components/switch.ts

@@ -1,6 +1,6 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import coreModule from "app/core/core_module";
+import coreModule from 'app/core/core_module';
 
 var template = `
 <label for="check-{{ctrl.id}}" class="gf-form-label {{ctrl.labelClass}} pointer" ng-show="ctrl.label">
@@ -37,20 +37,20 @@ export class SwitchCtrl {
 
 export function switchDirective() {
   return {
-    restrict: "E",
+    restrict: 'E',
     controller: SwitchCtrl,
-    controllerAs: "ctrl",
+    controllerAs: 'ctrl',
     bindToController: true,
     scope: {
-      checked: "=",
-      label: "@",
-      labelClass: "@",
-      tooltip: "@",
-      switchClass: "@",
-      onChange: "&"
+      checked: '=',
+      label: '@',
+      labelClass: '@',
+      tooltip: '@',
+      switchClass: '@',
+      onChange: '&',
     },
-    template: template
+    template: template,
   };
 }
 
-coreModule.directive("gfFormSwitch", switchDirective);
+coreModule.directive('gfFormSwitch', switchDirective);

+ 15 - 17
public/app/core/components/team_picker.ts

@@ -1,5 +1,5 @@
-import coreModule from "app/core/core_module";
-import _ from "lodash";
+import coreModule from 'app/core/core_module';
+import _ from 'lodash';
 
 const template = `
 <div class="dropdown">
@@ -19,24 +19,22 @@ export class TeamPickerCtrl {
   constructor(private backendSrv) {
     this.debouncedSearchGroups = _.debounce(this.searchGroups, 500, {
       leading: true,
-      trailing: false
+      trailing: false,
     });
     this.reset();
   }
 
   reset() {
-    this.group = { text: "Choose", value: null };
+    this.group = { text: 'Choose', value: null };
   }
 
   searchGroups(query: string) {
     return Promise.resolve(
-      this.backendSrv
-        .get("/api/teams/search?perpage=10&page=1&query=" + query)
-        .then(result => {
-          return _.map(result.teams, ug => {
-            return { text: ug.name, value: ug };
-          });
-        })
+      this.backendSrv.get('/api/teams/search?perpage=10&page=1&query=' + query).then(result => {
+        return _.map(result.teams, ug => {
+          return { text: ug.name, value: ug };
+        });
+      })
     );
   }
 
@@ -47,20 +45,20 @@ export class TeamPickerCtrl {
 
 export function teamPicker() {
   return {
-    restrict: "E",
+    restrict: 'E',
     template: template,
     controller: TeamPickerCtrl,
     bindToController: true,
-    controllerAs: "ctrl",
+    controllerAs: 'ctrl',
     scope: {
-      teamPicked: "&"
+      teamPicked: '&',
     },
     link: function(scope, elem, attrs, ctrl) {
-      scope.$on("team-picker-reset", () => {
+      scope.$on('team-picker-reset', () => {
         ctrl.reset();
       });
-    }
+    },
   };
 }
 
-coreModule.directive("teamPicker", teamPicker);
+coreModule.directive('teamPicker', teamPicker);

+ 15 - 17
public/app/core/components/user_picker.ts

@@ -1,5 +1,5 @@
-import coreModule from "app/core/core_module";
-import _ from "lodash";
+import coreModule from 'app/core/core_module';
+import _ from 'lodash';
 
 const template = `
 <div class="dropdown">
@@ -20,19 +20,17 @@ export class UserPickerCtrl {
     this.reset();
     this.debouncedSearchUsers = _.debounce(this.searchUsers, 500, {
       leading: true,
-      trailing: false
+      trailing: false,
     });
   }
 
   searchUsers(query: string) {
     return Promise.resolve(
-      this.backendSrv
-        .get("/api/users/search?perpage=10&page=1&query=" + query)
-        .then(result => {
-          return _.map(result.users, user => {
-            return { text: user.login + " -  " + user.email, value: user };
-          });
-        })
+      this.backendSrv.get('/api/users/search?perpage=10&page=1&query=' + query).then(result => {
+        return _.map(result.users, user => {
+          return { text: user.login + ' -  ' + user.email, value: user };
+        });
+      })
     );
   }
 
@@ -41,7 +39,7 @@ export class UserPickerCtrl {
   }
 
   reset() {
-    this.user = { text: "Choose", value: null };
+    this.user = { text: 'Choose', value: null };
   }
 }
 
@@ -54,20 +52,20 @@ export interface User {
 
 export function userPicker() {
   return {
-    restrict: "E",
+    restrict: 'E',
     template: template,
     controller: UserPickerCtrl,
     bindToController: true,
-    controllerAs: "ctrl",
+    controllerAs: 'ctrl',
     scope: {
-      userPicked: "&"
+      userPicked: '&',
     },
     link: function(scope, elem, attrs, ctrl) {
-      scope.$on("user-picker-reset", () => {
+      scope.$on('user-picker-reset', () => {
         ctrl.reset();
       });
-    }
+    },
   };
 }
 
-coreModule.directive("userPicker", userPicker);
+coreModule.directive('userPicker', userPicker);

+ 5 - 5
public/app/core/config.ts

@@ -1,4 +1,4 @@
-import _ from "lodash";
+import _ from 'lodash';
 
 class Settings {
   datasources: any;
@@ -25,12 +25,12 @@ class Settings {
   constructor(options) {
     var defaults = {
       datasources: {},
-      window_title_prefix: "Grafana - ",
+      window_title_prefix: 'Grafana - ',
       panels: {},
-      new_panel_title: "Panel Title",
-      playlist_timespan: "1m",
+      new_panel_title: 'Panel Title',
+      playlist_timespan: '1m',
       unsaved_changes_warning: true,
-      appSubUrl: ""
+      appSubUrl: '',
     };
     _.extend(this, defaults, options);
   }

+ 1 - 1
public/app/core/constants.ts

@@ -1,7 +1,7 @@
 export const GRID_CELL_HEIGHT = 30;
 export const GRID_CELL_VMARGIN = 10;
 export const GRID_COLUMN_COUNT = 24;
-export const REPEAT_DIR_VERTICAL = "v";
+export const REPEAT_DIR_VERTICAL = 'v';
 
 export const DEFAULT_PANEL_SPAN = 4;
 export const DEFAULT_ROW_HEIGHT = 250;

+ 7 - 7
public/app/core/controllers/error_ctrl.ts

@@ -1,6 +1,6 @@
-import config from "app/core/config";
-import coreModule from "../core_module";
-import appEvents from "app/core/app_events";
+import config from 'app/core/config';
+import coreModule from '../core_module';
+import appEvents from 'app/core/app_events';
 
 export class ErrorCtrl {
   /** @ngInject */
@@ -9,15 +9,15 @@ export class ErrorCtrl {
     $scope.appSubUrl = config.appSubUrl;
 
     if (!contextSrv.isSignedIn) {
-      appEvents.emit("toggle-sidemenu-hidden");
+      appEvents.emit('toggle-sidemenu-hidden');
     }
 
-    $scope.$on("destroy", () => {
+    $scope.$on('destroy', () => {
       if (!contextSrv.isSignedIn) {
-        appEvents.emit("toggle-sidemenu-hidden");
+        appEvents.emit('toggle-sidemenu-hidden');
       }
     });
   }
 }
 
-coreModule.controller("ErrorCtrl", ErrorCtrl);
+coreModule.controller('ErrorCtrl', ErrorCtrl);

+ 12 - 20
public/app/core/controllers/inspect_ctrl.ts

@@ -1,7 +1,7 @@
-import angular from "angular";
-import _ from "lodash";
-import $ from "jquery";
-import coreModule from "../core_module";
+import angular from 'angular';
+import _ from 'lodash';
+import $ from 'jquery';
+import coreModule from '../core_module';
 
 export class InspectCtrl {
   /** @ngInject */
@@ -16,7 +16,7 @@ export class InspectCtrl {
       }
 
       if (_.isString(model.error.data)) {
-        $scope.response = $("<div>" + model.error.data + "</div>").text();
+        $scope.response = $('<div>' + model.error.data + '</div>').text();
       } else if (model.error.data) {
         if (model.error.data.response) {
           $scope.response = $sanitize(model.error.data.response);
@@ -28,10 +28,7 @@ export class InspectCtrl {
       }
 
       if (model.error.config && model.error.config.params) {
-        $scope.request_parameters = _.map(model.error.config.params, function(
-          value,
-          key
-        ) {
+        $scope.request_parameters = _.map(model.error.config.params, function(value, key) {
           return { key: key, value: value };
         });
       }
@@ -46,14 +43,9 @@ export class InspectCtrl {
         $scope.editor.index = 2;
 
         if (_.isString(model.error.config.data)) {
-          $scope.request_parameters = this.getParametersFromQueryString(
-            model.error.config.data
-          );
+          $scope.request_parameters = this.getParametersFromQueryString(model.error.config.data);
         } else {
-          $scope.request_parameters = _.map(model.error.config.data, function(
-            value,
-            key
-          ) {
+          $scope.request_parameters = _.map(model.error.config.data, function(value, key) {
             return { key: key, value: angular.toJson(value, true) };
           });
         }
@@ -62,13 +54,13 @@ export class InspectCtrl {
   }
   getParametersFromQueryString(queryString) {
     var result = [];
-    var parameters = queryString.split("&");
+    var parameters = queryString.split('&');
     for (var i = 0; i < parameters.length; i++) {
-      var keyValue = parameters[i].split("=");
+      var keyValue = parameters[i].split('=');
       if (keyValue[1].length > 0) {
         result.push({
           key: keyValue[0],
-          value: (<any>window).unescape(keyValue[1])
+          value: (<any>window).unescape(keyValue[1]),
         });
       }
     }
@@ -76,4 +68,4 @@ export class InspectCtrl {
   }
 }
 
-coreModule.controller("InspectCtrl", InspectCtrl);
+coreModule.controller('InspectCtrl', InspectCtrl);

+ 19 - 23
public/app/core/controllers/invited_ctrl.ts

@@ -1,5 +1,5 @@
-import coreModule from "../core_module";
-import config from "app/core/config";
+import coreModule from '../core_module';
+import config from 'app/core/config';
 
 export class InvitedCtrl {
   /** @ngInject */
@@ -9,24 +9,22 @@ export class InvitedCtrl {
 
     $scope.navModel = {
       main: {
-        icon: "gicon gicon-branding",
-        subTitle: "Register your Grafana account",
-        breadcrumbs: [{ title: "Login", url: "/login" }, { title: "Invite" }]
-      }
+        icon: 'gicon gicon-branding',
+        subTitle: 'Register your Grafana account',
+        breadcrumbs: [{ title: 'Login', url: '/login' }, { title: 'Invite' }],
+      },
     };
 
     $scope.init = function() {
-      backendSrv
-        .get("/api/user/invite/" + $routeParams.code)
-        .then(function(invite) {
-          $scope.formModel.name = invite.name;
-          $scope.formModel.email = invite.email;
-          $scope.formModel.username = invite.email;
-          $scope.formModel.inviteCode = $routeParams.code;
-
-          $scope.greeting = invite.name || invite.email || invite.username;
-          $scope.invitedBy = invite.invitedBy;
-        });
+      backendSrv.get('/api/user/invite/' + $routeParams.code).then(function(invite) {
+        $scope.formModel.name = invite.name;
+        $scope.formModel.email = invite.email;
+        $scope.formModel.username = invite.email;
+        $scope.formModel.inviteCode = $routeParams.code;
+
+        $scope.greeting = invite.name || invite.email || invite.username;
+        $scope.invitedBy = invite.invitedBy;
+      });
     };
 
     $scope.submit = function() {
@@ -34,15 +32,13 @@ export class InvitedCtrl {
         return;
       }
 
-      backendSrv
-        .post("/api/user/invite/complete", $scope.formModel)
-        .then(function() {
-          window.location.href = config.appSubUrl + "/";
-        });
+      backendSrv.post('/api/user/invite/complete', $scope.formModel).then(function() {
+        window.location.href = config.appSubUrl + '/';
+      });
     };
 
     $scope.init();
   }
 }
 
-coreModule.controller("InvitedCtrl", InvitedCtrl);
+coreModule.controller('InvitedCtrl', InvitedCtrl);

+ 4 - 5
public/app/core/controllers/json_editor_ctrl.ts

@@ -1,12 +1,11 @@
-import angular from "angular";
-import coreModule from "../core_module";
+import angular from 'angular';
+import coreModule from '../core_module';
 
 export class JsonEditorCtrl {
   /** @ngInject */
   constructor($scope) {
     $scope.json = angular.toJson($scope.object, true);
-    $scope.canUpdate =
-      $scope.updateHandler !== void 0 && $scope.contextSrv.isEditor;
+    $scope.canUpdate = $scope.updateHandler !== void 0 && $scope.contextSrv.isEditor;
 
     $scope.update = function() {
       var newObject = angular.fromJson($scope.json);
@@ -15,4 +14,4 @@ export class JsonEditorCtrl {
   }
 }
 
-coreModule.controller("JsonEditorCtrl", JsonEditorCtrl);
+coreModule.controller('JsonEditorCtrl', JsonEditorCtrl);

+ 21 - 23
public/app/core/controllers/login_ctrl.ts

@@ -1,14 +1,14 @@
-import _ from "lodash";
-import coreModule from "../core_module";
-import config from "app/core/config";
+import _ from 'lodash';
+import coreModule from '../core_module';
+import config from 'app/core/config';
 
 export class LoginCtrl {
   /** @ngInject */
   constructor($scope, backendSrv, contextSrv, $location) {
     $scope.formModel = {
-      user: "",
-      email: "",
-      password: ""
+      user: '',
+      email: '',
+      password: '',
     };
 
     contextSrv.sidemenu = false;
@@ -21,13 +21,13 @@ export class LoginCtrl {
     $scope.loginHint = config.loginHint;
 
     $scope.loginMode = true;
-    $scope.submitBtnText = "Log in";
+    $scope.submitBtnText = 'Log in';
 
     $scope.init = function() {
-      $scope.$watch("loginMode", $scope.loginModeChanged);
+      $scope.$watch('loginMode', $scope.loginModeChanged);
 
       if (config.loginError) {
-        $scope.appEvent("alert-warning", ["Login Failed", config.loginError]);
+        $scope.appEvent('alert-warning', ['Login Failed', config.loginError]);
       }
     };
 
@@ -40,7 +40,7 @@ export class LoginCtrl {
     };
 
     $scope.loginModeChanged = function(newValue) {
-      $scope.submitBtnText = newValue ? "Log in" : "Sign up";
+      $scope.submitBtnText = newValue ? 'Log in' : 'Sign up';
     };
 
     $scope.signUp = function() {
@@ -48,15 +48,13 @@ export class LoginCtrl {
         return;
       }
 
-      backendSrv
-        .post("/api/user/signup", $scope.formModel)
-        .then(function(result) {
-          if (result.status === "SignUpCreated") {
-            $location.path("/signup").search({ email: $scope.formModel.email });
-          } else {
-            window.location.href = config.appSubUrl + "/";
-          }
-        });
+      backendSrv.post('/api/user/signup', $scope.formModel).then(function(result) {
+        if (result.status === 'SignUpCreated') {
+          $location.path('/signup').search({ email: $scope.formModel.email });
+        } else {
+          window.location.href = config.appSubUrl + '/';
+        }
+      });
     };
 
     $scope.login = function() {
@@ -66,15 +64,15 @@ export class LoginCtrl {
         return;
       }
 
-      backendSrv.post("/login", $scope.formModel).then(function(result) {
+      backendSrv.post('/login', $scope.formModel).then(function(result) {
         var params = $location.search();
 
-        if (params.redirect && params.redirect[0] === "/") {
+        if (params.redirect && params.redirect[0] === '/') {
           window.location.href = config.appSubUrl + params.redirect;
         } else if (result.redirectUrl) {
           window.location.href = result.redirectUrl;
         } else {
-          window.location.href = config.appSubUrl + "/";
+          window.location.href = config.appSubUrl + '/';
         }
       });
     };
@@ -83,4 +81,4 @@ export class LoginCtrl {
   }
 }
 
-coreModule.controller("LoginCtrl", LoginCtrl);
+coreModule.controller('LoginCtrl', LoginCtrl);

+ 15 - 22
public/app/core/controllers/reset_password_ctrl.ts

@@ -1,38 +1,33 @@
-import coreModule from "../core_module";
+import coreModule from '../core_module';
 
 export class ResetPasswordCtrl {
   /** @ngInject */
   constructor($scope, contextSrv, backendSrv, $location) {
     contextSrv.sidemenu = false;
     $scope.formModel = {};
-    $scope.mode = "send";
+    $scope.mode = 'send';
 
     var params = $location.search();
     if (params.code) {
-      $scope.mode = "reset";
+      $scope.mode = 'reset';
       $scope.formModel.code = params.code;
     }
 
     $scope.navModel = {
       main: {
-        icon: "gicon gicon-branding",
-        subTitle: "Reset your Grafana password",
-        breadcrumbs: [
-          { title: "Login", url: "/login" },
-          { title: "Reset Password" }
-        ]
-      }
+        icon: 'gicon gicon-branding',
+        subTitle: 'Reset your Grafana password',
+        breadcrumbs: [{ title: 'Login', url: '/login' }, { title: 'Reset Password' }],
+      },
     };
 
     $scope.sendResetEmail = function() {
       if (!$scope.sendResetForm.$valid) {
         return;
       }
-      backendSrv
-        .post("/api/user/password/send-reset-email", $scope.formModel)
-        .then(function() {
-          $scope.mode = "email-sent";
-        });
+      backendSrv.post('/api/user/password/send-reset-email', $scope.formModel).then(function() {
+        $scope.mode = 'email-sent';
+      });
     };
 
     $scope.submitReset = function() {
@@ -41,17 +36,15 @@ export class ResetPasswordCtrl {
       }
 
       if ($scope.formModel.newPassword !== $scope.formModel.confirmPassword) {
-        $scope.appEvent("alert-warning", ["New passwords do not match", ""]);
+        $scope.appEvent('alert-warning', ['New passwords do not match', '']);
         return;
       }
 
-      backendSrv
-        .post("/api/user/password/reset", $scope.formModel)
-        .then(function() {
-          $location.path("login");
-        });
+      backendSrv.post('/api/user/password/reset', $scope.formModel).then(function() {
+        $location.path('login');
+      });
     };
   }
 }
 
-coreModule.controller("ResetPasswordCtrl", ResetPasswordCtrl);
+coreModule.controller('ResetPasswordCtrl', ResetPasswordCtrl);

+ 16 - 24
public/app/core/controllers/signup_ctrl.ts

@@ -1,16 +1,11 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import config from "app/core/config";
-import coreModule from "../core_module";
+import config from 'app/core/config';
+import coreModule from '../core_module';
 
 export class SignUpCtrl {
   /** @ngInject */
-  constructor(
-    private $scope: any,
-    private backendSrv: any,
-    $location: any,
-    contextSrv: any
-  ) {
+  constructor(private $scope: any, private backendSrv: any, $location: any, contextSrv: any) {
     contextSrv.sidemenu = false;
     $scope.ctrl = this;
 
@@ -27,13 +22,13 @@ export class SignUpCtrl {
 
     $scope.navModel = {
       main: {
-        icon: "gicon gicon-branding",
-        subTitle: "Register your Grafana account",
-        breadcrumbs: [{ title: "Login", url: "/login" }, { title: "Sign Up" }]
-      }
+        icon: 'gicon gicon-branding',
+        subTitle: 'Register your Grafana account',
+        breadcrumbs: [{ title: 'Login', url: '/login' }, { title: 'Sign Up' }],
+      },
     };
 
-    backendSrv.get("/api/user/signup/options").then(options => {
+    backendSrv.get('/api/user/signup/options').then(options => {
       $scope.verifyEmailEnabled = options.verifyEmailEnabled;
       $scope.autoAssignOrg = options.autoAssignOrg;
     });
@@ -44,17 +39,14 @@ export class SignUpCtrl {
       return;
     }
 
-    this.backendSrv
-      .post("/api/user/signup/step2", this.$scope.formModel)
-      .then(rsp => {
-        if (rsp.code === "redirect-to-select-org") {
-          window.location.href =
-            config.appSubUrl + "/profile/select-org?signup=1";
-        } else {
-          window.location.href = config.appSubUrl + "/";
-        }
-      });
+    this.backendSrv.post('/api/user/signup/step2', this.$scope.formModel).then(rsp => {
+      if (rsp.code === 'redirect-to-select-org') {
+        window.location.href = config.appSubUrl + '/profile/select-org?signup=1';
+      } else {
+        window.location.href = config.appSubUrl + '/';
+      }
+    });
   }
 }
 
-coreModule.controller("SignUpCtrl", SignUpCtrl);
+coreModule.controller('SignUpCtrl', SignUpCtrl);

+ 58 - 59
public/app/core/core.ts

@@ -1,62 +1,61 @@
-import "./directives/dash_class";
-import "./directives/dash_edit_link";
-import "./directives/dropdown_typeahead";
-import "./directives/metric_segment";
-import "./directives/misc";
-import "./directives/ng_model_on_blur";
-import "./directives/tags";
-import "./directives/value_select_dropdown";
-import "./directives/rebuild_on_change";
-import "./directives/give_focus";
-import "./directives/diff-view";
-import "./jquery_extended";
-import "./partials";
-import "./components/jsontree/jsontree";
-import "./components/code_editor/code_editor";
-import "./utils/outline";
-import "./components/colorpicker/ColorPicker";
-import "./components/colorpicker/SeriesColorPicker";
-import "./components/colorpicker/spectrum_picker";
-import "./services/search_srv";
-import "./services/ng_react";
+import './directives/dash_class';
+import './directives/dash_edit_link';
+import './directives/dropdown_typeahead';
+import './directives/metric_segment';
+import './directives/misc';
+import './directives/ng_model_on_blur';
+import './directives/tags';
+import './directives/value_select_dropdown';
+import './directives/rebuild_on_change';
+import './directives/give_focus';
+import './directives/diff-view';
+import './jquery_extended';
+import './partials';
+import './components/jsontree/jsontree';
+import './components/code_editor/code_editor';
+import './utils/outline';
+import './components/colorpicker/ColorPicker';
+import './components/colorpicker/SeriesColorPicker';
+import './components/colorpicker/spectrum_picker';
+import './services/search_srv';
+import './services/ng_react';
 
-import { grafanaAppDirective } from "./components/grafana_app";
-import { sideMenuDirective } from "./components/sidemenu/sidemenu";
-import { searchDirective } from "./components/search/search";
-import { infoPopover } from "./components/info_popover";
-import { navbarDirective } from "./components/navbar/navbar";
-import { arrayJoin } from "./directives/array_join";
-import { liveSrv } from "./live/live_srv";
-import { Emitter } from "./utils/emitter";
-import { layoutSelector } from "./components/layout_selector/layout_selector";
-import { switchDirective } from "./components/switch";
-import { dashboardSelector } from "./components/dashboard_selector";
-import { queryPartEditorDirective } from "./components/query_part/query_part_editor";
-import { formDropdownDirective } from "./components/form_dropdown/form_dropdown";
-import "app/core/controllers/all";
-import "app/core/services/all";
-import "app/core/routes/routes";
-import "./filters/filters";
-import coreModule from "./core_module";
-import appEvents from "./app_events";
-import colors from "./utils/colors";
-import { assignModelProperties } from "./utils/model_utils";
-import { contextSrv } from "./services/context_srv";
-import { KeybindingSrv } from "./services/keybindingSrv";
-import { helpModal } from "./components/help/help";
-import { JsonExplorer } from "./components/json_explorer/json_explorer";
-import { NavModelSrv, NavModel } from "./nav_model_srv";
-import { userPicker } from "./components/user_picker";
-import { teamPicker } from "./components/team_picker";
-import { geminiScrollbar } from "./components/scroll/scroll";
-import { gfPageDirective } from "./components/gf_page";
-import { orgSwitcher } from "./components/org_switcher";
-import { profiler } from "./profiler";
-import { registerAngularDirectives } from "./angular_wrappers";
-import { updateLegendValues } from "./time_series2";
-import TimeSeries from "./time_series2";
-import { searchResultsDirective } from "./components/search/search_results";
-import { manageDashboardsDirective } from "./components/manage_dashboards/manage_dashboards";
+import { grafanaAppDirective } from './components/grafana_app';
+import { sideMenuDirective } from './components/sidemenu/sidemenu';
+import { searchDirective } from './components/search/search';
+import { infoPopover } from './components/info_popover';
+import { navbarDirective } from './components/navbar/navbar';
+import { arrayJoin } from './directives/array_join';
+import { liveSrv } from './live/live_srv';
+import { Emitter } from './utils/emitter';
+import { layoutSelector } from './components/layout_selector/layout_selector';
+import { switchDirective } from './components/switch';
+import { dashboardSelector } from './components/dashboard_selector';
+import { queryPartEditorDirective } from './components/query_part/query_part_editor';
+import { formDropdownDirective } from './components/form_dropdown/form_dropdown';
+import 'app/core/controllers/all';
+import 'app/core/services/all';
+import './filters/filters';
+import coreModule from './core_module';
+import appEvents from './app_events';
+import colors from './utils/colors';
+import { assignModelProperties } from './utils/model_utils';
+import { contextSrv } from './services/context_srv';
+import { KeybindingSrv } from './services/keybindingSrv';
+import { helpModal } from './components/help/help';
+import { JsonExplorer } from './components/json_explorer/json_explorer';
+import { NavModelSrv, NavModel } from './nav_model_srv';
+import { userPicker } from './components/user_picker';
+import { teamPicker } from './components/team_picker';
+import { geminiScrollbar } from './components/scroll/scroll';
+import { gfPageDirective } from './components/gf_page';
+import { orgSwitcher } from './components/org_switcher';
+import { profiler } from './profiler';
+import { registerAngularDirectives } from './angular_wrappers';
+import { updateLegendValues } from './time_series2';
+import TimeSeries from './time_series2';
+import { searchResultsDirective } from './components/search/search_results';
+import { manageDashboardsDirective } from './components/manage_dashboards/manage_dashboards';
 
 export {
   profiler,
@@ -92,5 +91,5 @@ export {
   manageDashboardsDirective,
   TimeSeries,
   updateLegendValues,
-  searchResultsDirective
+  searchResultsDirective,
 };

+ 9 - 9
public/app/core/directives/array_join.ts

@@ -1,22 +1,22 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import _ from "lodash";
-import coreModule from "../core_module";
+import _ from 'lodash';
+import coreModule from '../core_module';
 
 export function arrayJoin() {
-  "use strict";
+  'use strict';
 
   return {
-    restrict: "A",
-    require: "ngModel",
+    restrict: 'A',
+    require: 'ngModel',
     link: function(scope, element, attr, ngModel) {
       function split_array(text) {
-        return (text || "").split(",");
+        return (text || '').split(',');
       }
 
       function join_array(text) {
         if (_.isArray(text)) {
-          return (text || "").join(",");
+          return (text || '').join(',');
         } else {
           return text;
         }
@@ -24,8 +24,8 @@ export function arrayJoin() {
 
       ngModel.$parsers.push(split_array);
       ngModel.$formatters.push(join_array);
-    }
+    },
   };
 }
 
-coreModule.directive("arrayJoin", arrayJoin);
+coreModule.directive('arrayJoin', arrayJoin);

+ 16 - 16
public/app/core/directives/diff-view.ts

@@ -1,7 +1,7 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import angular from "angular";
-import coreModule from "../core_module";
+import angular from 'angular';
+import coreModule from '../core_module';
 
 export class DeltaCtrl {
   observer: any;
@@ -10,7 +10,7 @@ export class DeltaCtrl {
   constructor(private $rootScope) {
     const waitForCompile = mutations => {
       if (mutations.length === 1) {
-        this.$rootScope.appEvent("json-diff-ready");
+        this.$rootScope.appEvent('json-diff-ready');
       }
     };
 
@@ -18,13 +18,13 @@ export class DeltaCtrl {
 
     const observerConfig = {
       attributes: true,
-      attributeFilter: ["class"],
+      attributeFilter: ['class'],
       characterData: false,
       childList: true,
-      subtree: false
+      subtree: false,
     };
 
-    this.observer.observe(angular.element(".delta-html")[0], observerConfig);
+    this.observer.observe(angular.element('.delta-html')[0], observerConfig);
   }
 
   $onDestroy() {
@@ -36,10 +36,10 @@ export function delta() {
   return {
     controller: DeltaCtrl,
     replace: false,
-    restrict: "A"
+    restrict: 'A',
   };
 }
-coreModule.directive("diffDelta", delta);
+coreModule.directive('diffDelta', delta);
 
 // Link to JSON line number
 export class LinkJSONCtrl {
@@ -55,7 +55,7 @@ export class LinkJSONCtrl {
     };
 
     this.$scope.switchView().then(() => {
-      unbind = this.$rootScope.$on("json-diff-ready", scroll.bind(this));
+      unbind = this.$rootScope.$on('json-diff-ready', scroll.bind(this));
     });
   }
 }
@@ -63,15 +63,15 @@ export class LinkJSONCtrl {
 export function linkJson() {
   return {
     controller: LinkJSONCtrl,
-    controllerAs: "ctrl",
+    controllerAs: 'ctrl',
     replace: true,
-    restrict: "E",
+    restrict: 'E',
     scope: {
-      line: "@lineDisplay",
-      link: "@lineLink",
-      switchView: "&"
+      line: '@lineDisplay',
+      link: '@lineLink',
+      switchView: '&',
     },
-    template: `<a class="diff-linenum btn btn-inverse btn-small" ng-click="ctrl.goToLine(link)">Line {{ line }}</a>`
+    template: `<a class="diff-linenum btn btn-inverse btn-small" ng-click="ctrl.goToLine(link)">Line {{ line }}</a>`,
   };
 }
-coreModule.directive("diffLinkJson", linkJson);
+coreModule.directive('diffLinkJson', linkJson);

+ 2 - 2
public/app/core/directives/give_focus.ts

@@ -1,8 +1,8 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import coreModule from "../core_module";
+import coreModule from '../core_module';
 
-coreModule.directive("giveFocus", function() {
+coreModule.directive('giveFocus', function() {
   return function(scope, element, attrs) {
     element.click(function(e) {
       e.stopPropagation();

+ 58 - 74
public/app/core/directives/misc.ts

@@ -1,50 +1,50 @@
-import angular from "angular";
-import Clipboard from "clipboard";
-import coreModule from "../core_module";
-import kbn from "app/core/utils/kbn";
+import angular from 'angular';
+import Clipboard from 'clipboard';
+import coreModule from '../core_module';
+import kbn from 'app/core/utils/kbn';
 
 /** @ngInject */
 function tip($compile) {
   return {
-    restrict: "E",
+    restrict: 'E',
     link: function(scope, elem, attrs) {
       var _t =
         '<i class="grafana-tip fa fa-' +
-        (attrs.icon || "question-circle") +
+        (attrs.icon || 'question-circle') +
         '" bs-tooltip="\'' +
         kbn.addslashes(elem.text()) +
-        "'\"></i>";
-      _t = _t.replace(/{/g, "\\{").replace(/}/g, "\\}");
+        '\'"></i>';
+      _t = _t.replace(/{/g, '\\{').replace(/}/g, '\\}');
       elem.replaceWith($compile(angular.element(_t))(scope));
-    }
+    },
   };
 }
 
 function clipboardButton() {
   return {
     scope: {
-      getText: "&clipboardButton"
+      getText: '&clipboardButton',
     },
     link: function(scope, elem) {
       scope.clipboard = new Clipboard(elem[0], {
         text: function() {
           return scope.getText();
-        }
+        },
       });
 
-      scope.$on("$destroy", function() {
+      scope.$on('$destroy', function() {
         if (scope.clipboard) {
           scope.clipboard.destroy();
         }
       });
-    }
+    },
   };
 }
 
 /** @ngInject */
 function compile($compile) {
   return {
-    restrict: "A",
+    restrict: 'A',
     link: function(scope, element, attrs) {
       scope.$watch(
         function(scope) {
@@ -55,42 +55,42 @@ function compile($compile) {
           $compile(element.contents())(scope);
         }
       );
-    }
+    },
   };
 }
 
 function watchChange() {
   return {
-    scope: { onchange: "&watchChange" },
+    scope: { onchange: '&watchChange' },
     link: function(scope, element) {
-      element.on("input", function() {
+      element.on('input', function() {
         scope.$apply(function() {
           scope.onchange({ inputValue: element.val() });
         });
       });
-    }
+    },
   };
 }
 
 /** @ngInject */
 function editorOptBool($compile) {
   return {
-    restrict: "E",
+    restrict: 'E',
     link: function(scope, elem, attrs) {
-      var ngchange = attrs.change ? ' ng-change="' + attrs.change + '"' : "";
-      var tip = attrs.tip ? " <tip>" + attrs.tip + "</tip>" : "";
-      var showIf = attrs.showIf ? ' ng-show="' + attrs.showIf + '" ' : "";
+      var ngchange = attrs.change ? ' ng-change="' + attrs.change + '"' : '';
+      var tip = attrs.tip ? ' <tip>' + attrs.tip + '</tip>' : '';
+      var showIf = attrs.showIf ? ' ng-show="' + attrs.showIf + '" ' : '';
 
       var template =
         '<div class="editor-option gf-form-checkbox text-center"' +
         showIf +
-        ">" +
+        '>' +
         ' <label for="' +
         attrs.model +
         '" class="small">' +
         attrs.text +
         tip +
-        "</label>" +
+        '</label>' +
         '<input class="cr1" id="' +
         attrs.model +
         '" type="checkbox" ' +
@@ -105,27 +105,20 @@ function editorOptBool($compile) {
         attrs.model +
         '" class="cr1"></label>';
       elem.replaceWith($compile(angular.element(template))(scope));
-    }
+    },
   };
 }
 
 /** @ngInject */
 function editorCheckbox($compile, $interpolate) {
   return {
-    restrict: "E",
+    restrict: 'E',
     link: function(scope, elem, attrs) {
       var text = $interpolate(attrs.text)(scope);
       var model = $interpolate(attrs.model)(scope);
-      var ngchange = attrs.change ? ' ng-change="' + attrs.change + '"' : "";
-      var tip = attrs.tip ? " <tip>" + attrs.tip + "</tip>" : "";
-      var label =
-        '<label for="' +
-        scope.$id +
-        model +
-        '" class="checkbox-label">' +
-        text +
-        tip +
-        "</label>";
+      var ngchange = attrs.change ? ' ng-change="' + attrs.change + '"' : '';
+      var tip = attrs.tip ? ' <tip>' + attrs.tip + '</tip>' : '';
+      var label = '<label for="' + scope.$id + model + '" class="checkbox-label">' + text + tip + '</label>';
 
       var template =
         '<input class="cr1" id="' +
@@ -145,22 +138,17 @@ function editorCheckbox($compile, $interpolate) {
         '" class="cr1"></label>';
 
       template = template + label;
-      elem.addClass("gf-form-checkbox");
+      elem.addClass('gf-form-checkbox');
       elem.html($compile(angular.element(template))(scope));
-    }
+    },
   };
 }
 
 /** @ngInject */
 function gfDropdown($parse, $compile, $timeout) {
   function buildTemplate(items, placement?) {
-    var upclass = placement === "top" ? "dropup" : "";
-    var ul = [
-      '<ul class="dropdown-menu ' +
-        upclass +
-        '" role="menu" aria-labelledby="drop1">',
-      "</ul>"
-    ];
+    var upclass = placement === 'top' ? 'dropup' : '';
+    var ul = ['<ul class="dropdown-menu ' + upclass + '" role="menu" aria-labelledby="drop1">', '</ul>'];
 
     for (let index = 0; index < items.length; index++) {
       let item = items[index];
@@ -171,26 +159,24 @@ function gfDropdown($parse, $compile, $timeout) {
       }
 
       var li =
-        "<li" +
-        (item.submenu && item.submenu.length
-          ? ' class="dropdown-submenu"'
-          : "") +
-        ">" +
+        '<li' +
+        (item.submenu && item.submenu.length ? ' class="dropdown-submenu"' : '') +
+        '>' +
         '<a tabindex="-1" ng-href="' +
-        (item.href || "") +
+        (item.href || '') +
         '"' +
-        (item.click ? ' ng-click="' + item.click + '"' : "") +
-        (item.target ? ' target="' + item.target + '"' : "") +
-        (item.method ? ' data-method="' + item.method + '"' : "") +
-        ">" +
-        (item.text || "") +
-        "</a>";
+        (item.click ? ' ng-click="' + item.click + '"' : '') +
+        (item.target ? ' target="' + item.target + '"' : '') +
+        (item.method ? ' data-method="' + item.method + '"' : '') +
+        '>' +
+        (item.text || '') +
+        '</a>';
 
       if (item.submenu && item.submenu.length) {
-        li += buildTemplate(item.submenu).join("\n");
+        li += buildTemplate(item.submenu).join('\n');
       }
 
-      li += "</li>";
+      li += '</li>';
       ul.splice(index + 1, 0, li);
     }
 
@@ -198,29 +184,27 @@ function gfDropdown($parse, $compile, $timeout) {
   }
 
   return {
-    restrict: "EA",
+    restrict: 'EA',
     scope: true,
     link: function postLink(scope, iElement, iAttrs) {
       var getter = $parse(iAttrs.gfDropdown),
         items = getter(scope);
       $timeout(function() {
-        var placement = iElement.data("placement");
-        var dropdown = angular.element(
-          buildTemplate(items, placement).join("")
-        );
+        var placement = iElement.data('placement');
+        var dropdown = angular.element(buildTemplate(items, placement).join(''));
         dropdown.insertAfter(iElement);
-        $compile(iElement.next("ul.dropdown-menu"))(scope);
+        $compile(iElement.next('ul.dropdown-menu'))(scope);
       });
 
-      iElement.addClass("dropdown-toggle").attr("data-toggle", "dropdown");
-    }
+      iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown');
+    },
   };
 }
 
-coreModule.directive("tip", tip);
-coreModule.directive("clipboardButton", clipboardButton);
-coreModule.directive("compile", compile);
-coreModule.directive("watchChange", watchChange);
-coreModule.directive("editorOptBool", editorOptBool);
-coreModule.directive("editorCheckbox", editorCheckbox);
-coreModule.directive("gfDropdown", gfDropdown);
+coreModule.directive('tip', tip);
+coreModule.directive('clipboardButton', clipboardButton);
+coreModule.directive('compile', compile);
+coreModule.directive('watchChange', watchChange);
+coreModule.directive('editorOptBool', editorOptBool);
+coreModule.directive('editorCheckbox', editorCheckbox);
+coreModule.directive('gfDropdown', gfDropdown);

+ 18 - 18
public/app/core/directives/ng_model_on_blur.ts

@@ -1,59 +1,59 @@
-import coreModule from "../core_module";
-import * as rangeUtil from "app/core/utils/rangeutil";
+import coreModule from '../core_module';
+import * as rangeUtil from 'app/core/utils/rangeutil';
 
 function ngModelOnBlur() {
   return {
-    restrict: "A",
+    restrict: 'A',
     priority: 1,
-    require: "ngModel",
+    require: 'ngModel',
     link: function(scope, elm, attr, ngModelCtrl) {
-      if (attr.type === "radio" || attr.type === "checkbox") {
+      if (attr.type === 'radio' || attr.type === 'checkbox') {
         return;
       }
 
-      elm.off("input keydown change");
-      elm.bind("blur", function() {
+      elm.off('input keydown change');
+      elm.bind('blur', function() {
         scope.$apply(function() {
           ngModelCtrl.$setViewValue(elm.val());
         });
       });
-    }
+    },
   };
 }
 
 function emptyToNull() {
   return {
-    restrict: "A",
-    require: "ngModel",
+    restrict: 'A',
+    require: 'ngModel',
     link: function(scope, elm, attrs, ctrl) {
       ctrl.$parsers.push(function(viewValue) {
-        if (viewValue === "") {
+        if (viewValue === '') {
           return null;
         }
         return viewValue;
       });
-    }
+    },
   };
 }
 
 function validTimeSpan() {
   return {
-    require: "ngModel",
+    require: 'ngModel',
     link: function(scope, elm, attrs, ctrl) {
       ctrl.$validators.integer = function(modelValue, viewValue) {
         if (ctrl.$isEmpty(modelValue)) {
           return true;
         }
-        if (viewValue.indexOf("$") === 0 || viewValue.indexOf("+$") === 0) {
+        if (viewValue.indexOf('$') === 0 || viewValue.indexOf('+$') === 0) {
           return true; // allow template variable
         }
         var info = rangeUtil.describeTextRange(viewValue);
         return info.invalid !== true;
       };
-    }
+    },
   };
 }
 
-coreModule.directive("ngModelOnblur", ngModelOnBlur);
-coreModule.directive("emptyToNull", emptyToNull);
-coreModule.directive("validTimeSpan", validTimeSpan);
+coreModule.directive('ngModelOnblur', ngModelOnBlur);
+coreModule.directive('emptyToNull', emptyToNull);
+coreModule.directive('validTimeSpan', validTimeSpan);

+ 7 - 12
public/app/core/directives/rebuild_on_change.ts

@@ -1,5 +1,5 @@
-import $ from "jquery";
-import coreModule from "../core_module";
+import $ from 'jquery';
+import coreModule from '../core_module';
 
 function getBlockNodes(nodes) {
   var node = nodes[0];
@@ -25,7 +25,7 @@ function rebuildOnChange($animate) {
     terminal: true,
     transclude: true,
     priority: 600,
-    restrict: "E",
+    restrict: 'E',
     link: function(scope, elem, attrs, ctrl, transclude) {
       var block, childScope, previousElements;
 
@@ -47,10 +47,7 @@ function rebuildOnChange($animate) {
         }
       }
 
-      scope.$watch(attrs.property, function rebuildOnChangeAction(
-        value,
-        oldValue
-      ) {
+      scope.$watch(attrs.property, function rebuildOnChangeAction(value, oldValue) {
         if (childScope && value !== oldValue) {
           cleanUp();
         }
@@ -58,9 +55,7 @@ function rebuildOnChange($animate) {
         if (!childScope && (value || attrs.showNull)) {
           transclude(function(clone, newScope) {
             childScope = newScope;
-            clone[clone.length++] = document.createComment(
-              " end rebuild on change "
-            );
+            clone[clone.length++] = document.createComment(' end rebuild on change ');
             block = { clone: clone };
             $animate.enter(clone, elem.parent(), elem);
           });
@@ -68,8 +63,8 @@ function rebuildOnChange($animate) {
           cleanUp();
         }
       });
-    }
+    },
   };
 }
 
-coreModule.directive("rebuildOnChange", rebuildOnChange);
+coreModule.directive('rebuildOnChange', rebuildOnChange);

+ 83 - 83
public/app/core/directives/tags.ts

@@ -1,7 +1,7 @@
-import angular from "angular";
-import $ from "jquery";
-import coreModule from "../core_module";
-import "vendor/tagsinput/bootstrap-tagsinput.js";
+import angular from 'angular';
+import $ from 'jquery';
+import coreModule from '../core_module';
+import 'vendor/tagsinput/bootstrap-tagsinput.js';
 
 function djb2(str) {
   var hash = 5381;
@@ -14,79 +14,79 @@ function djb2(str) {
 function setColor(name, element) {
   var hash = djb2(name.toLowerCase());
   var colors = [
-    "#E24D42",
-    "#1F78C1",
-    "#BA43A9",
-    "#705DA0",
-    "#466803",
-    "#508642",
-    "#447EBC",
-    "#C15C17",
-    "#890F02",
-    "#757575",
-    "#0A437C",
-    "#6D1F62",
-    "#584477",
-    "#629E51",
-    "#2F4F4F",
-    "#BF1B00",
-    "#806EB7",
-    "#8a2eb8",
-    "#699e00",
-    "#000000",
-    "#3F6833",
-    "#2F575E",
-    "#99440A",
-    "#E0752D",
-    "#0E4AB4",
-    "#58140C",
-    "#052B51",
-    "#511749",
-    "#3F2B5B"
+    '#E24D42',
+    '#1F78C1',
+    '#BA43A9',
+    '#705DA0',
+    '#466803',
+    '#508642',
+    '#447EBC',
+    '#C15C17',
+    '#890F02',
+    '#757575',
+    '#0A437C',
+    '#6D1F62',
+    '#584477',
+    '#629E51',
+    '#2F4F4F',
+    '#BF1B00',
+    '#806EB7',
+    '#8a2eb8',
+    '#699e00',
+    '#000000',
+    '#3F6833',
+    '#2F575E',
+    '#99440A',
+    '#E0752D',
+    '#0E4AB4',
+    '#58140C',
+    '#052B51',
+    '#511749',
+    '#3F2B5B',
   ];
   var borderColors = [
-    "#FF7368",
-    "#459EE7",
-    "#E069CF",
-    "#9683C6",
-    "#6C8E29",
-    "#76AC68",
-    "#6AA4E2",
-    "#E7823D",
-    "#AF3528",
-    "#9B9B9B",
-    "#3069A2",
-    "#934588",
-    "#7E6A9D",
-    "#88C477",
-    "#557575",
-    "#E54126",
-    "#A694DD",
-    "#B054DE",
-    "#8FC426",
-    "#262626",
-    "#658E59",
-    "#557D84",
-    "#BF6A30",
-    "#FF9B53",
-    "#3470DA",
-    "#7E3A32",
-    "#2B5177",
-    "#773D6F",
-    "#655181"
+    '#FF7368',
+    '#459EE7',
+    '#E069CF',
+    '#9683C6',
+    '#6C8E29',
+    '#76AC68',
+    '#6AA4E2',
+    '#E7823D',
+    '#AF3528',
+    '#9B9B9B',
+    '#3069A2',
+    '#934588',
+    '#7E6A9D',
+    '#88C477',
+    '#557575',
+    '#E54126',
+    '#A694DD',
+    '#B054DE',
+    '#8FC426',
+    '#262626',
+    '#658E59',
+    '#557D84',
+    '#BF6A30',
+    '#FF9B53',
+    '#3470DA',
+    '#7E3A32',
+    '#2B5177',
+    '#773D6F',
+    '#655181',
   ];
   var color = colors[Math.abs(hash % colors.length)];
   var borderColor = borderColors[Math.abs(hash % borderColors.length)];
-  element.css("background-color", color);
-  element.css("border-color", borderColor);
+  element.css('background-color', color);
+  element.css('border-color', borderColor);
 }
 
 function tagColorFromName() {
   return {
-    scope: { tagColorFromName: "=" },
+    scope: { tagColorFromName: '=' },
     link: function(scope, element) {
       setColor(scope.tagColorFromName, element);
-    }
+    },
   };
 }
 
@@ -106,29 +106,29 @@ function bootstrapTagsinput() {
   }
 
   return {
-    restrict: "EA",
+    restrict: 'EA',
     scope: {
-      model: "=ngModel",
-      onTagsUpdated: "&"
+      model: '=ngModel',
+      onTagsUpdated: '&',
     },
-    template: "<select multiple></select>",
+    template: '<select multiple></select>',
     replace: false,
     link: function(scope, element, attrs) {
       if (!angular.isArray(scope.model)) {
         scope.model = [];
       }
 
-      var select = $("select", element);
+      var select = $('select', element);
 
       if (attrs.placeholder) {
-        select.attr("placeholder", attrs.placeholder);
+        select.attr('placeholder', attrs.placeholder);
       }
 
       select.tagsinput({
         typeahead: {
           source: angular.isFunction(scope.$parent[attrs.typeaheadSource])
             ? scope.$parent[attrs.typeaheadSource]
-            : null
+            : null,
         },
         widthClass: attrs.widthClass,
         itemValue: getItemProperty(scope, attrs.itemvalue),
@@ -137,10 +137,10 @@ function bootstrapTagsinput() {
           ? scope.$parent[attrs.tagclass]
           : function() {
               return attrs.tagclass;
-            }
+            },
       });
 
-      select.on("itemAdded", function(event) {
+      select.on('itemAdded', function(event) {
         if (scope.model.indexOf(event.item) === -1) {
           scope.model.push(event.item);
           if (scope.onTagsUpdated) {
@@ -149,14 +149,14 @@ function bootstrapTagsinput() {
         }
         var tagElement = select
           .next()
-          .children("span")
+          .children('span')
           .filter(function() {
             return $(this).text() === event.item;
           });
         setColor(event.item, tagElement);
       });
 
-      select.on("itemRemoved", function(event) {
+      select.on('itemRemoved', function(event) {
         var idx = scope.model.indexOf(event.item);
         if (idx !== -1) {
           scope.model.splice(idx, 1);
@@ -167,23 +167,23 @@ function bootstrapTagsinput() {
       });
 
       scope.$watch(
-        "model",
+        'model',
         function() {
           if (!angular.isArray(scope.model)) {
             scope.model = [];
           }
 
-          select.tagsinput("removeAll");
+          select.tagsinput('removeAll');
 
           for (var i = 0; i < scope.model.length; i++) {
-            select.tagsinput("add", scope.model[i]);
+            select.tagsinput('add', scope.model[i]);
           }
         },
         true
       );
-    }
+    },
   };
 }
 
-coreModule.directive("tagColorFromName", tagColorFromName);
-coreModule.directive("bootstrapTagsinput", bootstrapTagsinput);
+coreModule.directive('tagColorFromName', tagColorFromName);
+coreModule.directive('bootstrapTagsinput', bootstrapTagsinput);

+ 16 - 16
public/app/core/filters/filters.ts

@@ -1,17 +1,17 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import _ from "lodash";
-import angular from "angular";
-import moment from "moment";
-import coreModule from "../core_module";
+import _ from 'lodash';
+import angular from 'angular';
+import moment from 'moment';
+import coreModule from '../core_module';
 
-coreModule.filter("stringSort", function() {
+coreModule.filter('stringSort', function() {
   return function(input) {
     return input.sort();
   };
 });
 
-coreModule.filter("slice", function() {
+coreModule.filter('slice', function() {
   return function(arr, start, end) {
     if (!_.isUndefined(arr)) {
       return arr.slice(start, end);
@@ -19,7 +19,7 @@ coreModule.filter("slice", function() {
   };
 });
 
-coreModule.filter("stringify", function() {
+coreModule.filter('stringify', function() {
   return function(arr) {
     if (_.isObject(arr) && !_.isArray(arr)) {
       return angular.toJson(arr);
@@ -29,25 +29,25 @@ coreModule.filter("stringify", function() {
   };
 });
 
-coreModule.filter("moment", function() {
+coreModule.filter('moment', function() {
   return function(date, mode) {
     switch (mode) {
-      case "ago":
+      case 'ago':
         return moment(date).fromNow();
     }
     return moment(date).fromNow();
   };
 });
 
-coreModule.filter("noXml", function() {
+coreModule.filter('noXml', function() {
   var noXml = function(text) {
     return _.isString(text)
       ? text
-          .replace(/&/g, "&amp;")
-          .replace(/</g, "&lt;")
-          .replace(/>/g, "&gt;")
-          .replace(/'/g, "&#39;")
-          .replace(/"/g, "&quot;")
+          .replace(/&/g, '&amp;')
+          .replace(/</g, '&lt;')
+          .replace(/>/g, '&gt;')
+          .replace(/'/g, '&#39;')
+          .replace(/"/g, '&quot;')
       : text;
   };
   return function(text) {
@@ -72,5 +72,5 @@ function interpolateTemplateVars(templateSrv) {
   return filterFunc;
 }
 
-coreModule.filter("interpolateTemplateVars", interpolateTemplateVars);
+coreModule.filter('interpolateTemplateVars', interpolateTemplateVars);
 export default {};

+ 17 - 22
public/app/core/live/live_srv.ts

@@ -1,7 +1,7 @@
-import _ from "lodash";
-import config from "app/core/config";
+import _ from 'lodash';
+import config from 'app/core/config';
 
-import { Observable } from "rxjs/Observable";
+import { Observable } from 'rxjs/Observable';
 
 export class LiveSrv {
   conn: any;
@@ -14,12 +14,7 @@ export class LiveSrv {
 
   getWebSocketUrl() {
     var l = window.location;
-    return (
-      (l.protocol === "https:" ? "wss://" : "ws://") +
-      l.host +
-      config.appSubUrl +
-      "/ws"
-    );
+    return (l.protocol === 'https:' ? 'wss://' : 'ws://') + l.host + config.appSubUrl + '/ws';
   }
 
   getConnection() {
@@ -32,12 +27,12 @@ export class LiveSrv {
     }
 
     this.initPromise = new Promise((resolve, reject) => {
-      console.log("Live: connecting...");
+      console.log('Live: connecting...');
       this.conn = new WebSocket(this.getWebSocketUrl());
 
       this.conn.onclose = evt => {
-        console.log("Live: websocket onclose", evt);
-        reject({ message: "Connection closed" });
+        console.log('Live: websocket onclose', evt);
+        reject({ message: 'Connection closed' });
 
         this.initPromise = null;
         setTimeout(this.reconnect.bind(this), 2000);
@@ -49,12 +44,12 @@ export class LiveSrv {
 
       this.conn.onerror = evt => {
         this.initPromise = null;
-        reject({ message: "Connection error" });
-        console.log("Live: websocket error", evt);
+        reject({ message: 'Connection error' });
+        console.log('Live: websocket error', evt);
       };
 
       this.conn.onopen = evt => {
-        console.log("opened");
+        console.log('opened');
         this.initPromise = null;
         resolve(this.conn);
       };
@@ -67,7 +62,7 @@ export class LiveSrv {
     message = JSON.parse(message);
 
     if (!message.stream) {
-      console.log("Error: stream message without stream!", message);
+      console.log('Error: stream message without stream!', message);
       return;
     }
 
@@ -86,11 +81,11 @@ export class LiveSrv {
       return;
     }
 
-    console.log("LiveSrv: Reconnecting");
+    console.log('LiveSrv: Reconnecting');
 
     this.getConnection().then(conn => {
       _.each(this.observers, (value, key) => {
-        this.send({ action: "subscribe", stream: key });
+        this.send({ action: 'subscribe', stream: key });
       });
     });
   }
@@ -103,21 +98,21 @@ export class LiveSrv {
     this.observers[stream] = observer;
 
     this.getConnection().then(conn => {
-      this.send({ action: "subscribe", stream: stream });
+      this.send({ action: 'subscribe', stream: stream });
     });
   }
 
   removeObserver(stream, observer) {
-    console.log("unsubscribe", stream);
+    console.log('unsubscribe', stream);
     delete this.observers[stream];
 
     this.getConnection().then(conn => {
-      this.send({ action: "unsubscribe", stream: stream });
+      this.send({ action: 'unsubscribe', stream: stream });
     });
   }
 
   subscribe(streamName) {
-    console.log("LiveSrv.subscribe: " + streamName);
+    console.log('LiveSrv.subscribe: ' + streamName);
 
     return Observable.create(observer => {
       this.addObserver(streamName, observer);

+ 3 - 3
public/app/core/mod_defs.d.ts

@@ -1,14 +1,14 @@
-declare module "app/core/controllers/all" {
+declare module 'app/core/controllers/all' {
   let json: any;
   export { json };
 }
 
-declare module "app/core/routes/all" {
+declare module 'app/core/routes/all' {
   let json: any;
   export { json };
 }
 
-declare module "app/core/services/all" {
+declare module 'app/core/services/all' {
   let json: any;
   export default json;
 }

+ 9 - 9
public/app/core/nav_model_srv.ts

@@ -1,6 +1,6 @@
-import coreModule from "app/core/core_module";
-import config from "app/core/config";
-import _ from "lodash";
+import coreModule from 'app/core/core_module';
+import config from 'app/core/config';
+import _ from 'lodash';
 
 export interface NavModelItem {
   text: string;
@@ -34,7 +34,7 @@ export class NavModelSrv {
   }
 
   getCfgNode() {
-    return _.find(this.navItems, { id: "cfg" });
+    return _.find(this.navItems, { id: 'cfg' });
   }
 
   getNav(...args) {
@@ -70,17 +70,17 @@ export class NavModelSrv {
 
   getNotFoundNav() {
     var node = {
-      text: "Page not found",
-      icon: "fa fa-fw fa-warning",
-      subTitle: "404 Error"
+      text: 'Page not found',
+      icon: 'fa fa-fw fa-warning',
+      subTitle: '404 Error',
     };
 
     return {
       breadcrumbs: [node],
       node: node,
-      main: node
+      main: node,
     };
   }
 }
 
-coreModule.service("navModelSrv", NavModelSrv);
+coreModule.service('navModelSrv', NavModelSrv);

+ 20 - 44
public/app/core/profiler.ts

@@ -1,5 +1,5 @@
-import $ from "jquery";
-import angular from "angular";
+import $ from 'jquery';
+import angular from 'angular';
 
 export class Profiler {
   panelsRendered: number;
@@ -11,7 +11,7 @@ export class Profiler {
   scopeCount: any;
 
   init(config, $rootScope) {
-    this.enabled = config.buildInfo.env === "development";
+    this.enabled = config.buildInfo.env === 'development';
     this.timings = {};
     this.timings.appStart = { loadStart: new Date().getTime() };
     this.$rootScope = $rootScope;
@@ -28,22 +28,10 @@ export class Profiler {
       () => {}
     );
 
-    $rootScope.onAppEvent("refresh", this.refresh.bind(this), $rootScope);
-    $rootScope.onAppEvent(
-      "dashboard-fetch-end",
-      this.dashboardFetched.bind(this),
-      $rootScope
-    );
-    $rootScope.onAppEvent(
-      "dashboard-initialized",
-      this.dashboardInitialized.bind(this),
-      $rootScope
-    );
-    $rootScope.onAppEvent(
-      "panel-initialized",
-      this.panelInitialized.bind(this),
-      $rootScope
-    );
+    $rootScope.onAppEvent('refresh', this.refresh.bind(this), $rootScope);
+    $rootScope.onAppEvent('dashboard-fetch-end', this.dashboardFetched.bind(this), $rootScope);
+    $rootScope.onAppEvent('dashboard-initialized', this.dashboardInitialized.bind(this), $rootScope);
+    $rootScope.onAppEvent('panel-initialized', this.panelInitialized.bind(this), $rootScope);
   }
 
   refresh() {
@@ -51,10 +39,10 @@ export class Profiler {
     this.timings.render = 0;
 
     setTimeout(() => {
-      console.log("panel count: " + this.panelsInitCount);
-      console.log("total query: " + this.timings.query);
-      console.log("total render: " + this.timings.render);
-      console.log("avg render: " + this.timings.render / this.panelsInitCount);
+      console.log('panel count: ' + this.panelsInitCount);
+      console.log('total query: ' + this.timings.query);
+      console.log('total render: ' + this.timings.render);
+      console.log('avg render: ' + this.timings.render / this.panelsInitCount);
     }, 5000);
   }
 
@@ -70,21 +58,12 @@ export class Profiler {
 
   dashboardInitialized() {
     setTimeout(() => {
-      console.log(
-        "Dashboard::Performance Total Digests: " + this.digestCounter
-      );
-      console.log(
-        "Dashboard::Performance Total Watchers: " + this.getTotalWatcherCount()
-      );
-      console.log(
-        "Dashboard::Performance Total ScopeCount: " + this.scopeCount
-      );
-
-      var timeTaken =
-        this.timings.lastPanelInitializedAt - this.timings.dashboardLoadStart;
-      console.log(
-        "Dashboard::Performance All panels initialized in " + timeTaken + " ms"
-      );
+      console.log('Dashboard::Performance Total Digests: ' + this.digestCounter);
+      console.log('Dashboard::Performance Total Watchers: ' + this.getTotalWatcherCount());
+      console.log('Dashboard::Performance Total ScopeCount: ' + this.scopeCount);
+
+      var timeTaken = this.timings.lastPanelInitializedAt - this.timings.dashboardLoadStart;
+      console.log('Dashboard::Performance All panels initialized in ' + timeTaken + ' ms');
 
       // measure digest performance
       var rootDigestStart = window.performance.now();
@@ -92,20 +71,17 @@ export class Profiler {
         this.$rootScope.$apply();
       }
 
-      console.log(
-        "Dashboard::Performance Root Digest " +
-          (window.performance.now() - rootDigestStart) / 30
-      );
+      console.log('Dashboard::Performance Root Digest ' + (window.performance.now() - rootDigestStart) / 30);
     }, 3000);
   }
 
   getTotalWatcherCount() {
     var count = 0;
     var scopes = 0;
-    var root = $(document.getElementsByTagName("body"));
+    var root = $(document.getElementsByTagName('body'));
 
     var f = function(element) {
-      if (element.data().hasOwnProperty("$scope")) {
+      if (element.data().hasOwnProperty('$scope')) {
         scopes++;
         angular.forEach(element.data().$scope.$$watchers, function() {
           count++;

+ 0 - 56
public/app/core/routes/dashboard_loaders.ts

@@ -1,56 +0,0 @@
-import coreModule from "../core_module";
-
-export class LoadDashboardCtrl {
-  /** @ngInject */
-  constructor($scope, $routeParams, dashboardLoaderSrv, backendSrv, $location) {
-    $scope.appEvent("dashboard-fetch-start");
-
-    if (!$routeParams.slug) {
-      backendSrv.get("/api/dashboards/home").then(function(homeDash) {
-        if (homeDash.redirectUri) {
-          $location.path("dashboard/" + homeDash.redirectUri);
-        } else {
-          var meta = homeDash.meta;
-          meta.canSave = meta.canShare = meta.canStar = false;
-          $scope.initDashboard(homeDash, $scope);
-        }
-      });
-      return;
-    }
-
-    dashboardLoaderSrv
-      .loadDashboard($routeParams.type, $routeParams.slug)
-      .then(function(result) {
-        if ($routeParams.keepRows) {
-          result.meta.keepRows = true;
-        }
-        $scope.initDashboard(result, $scope);
-      });
-  }
-}
-
-export class NewDashboardCtrl {
-  /** @ngInject */
-  constructor($scope, $routeParams) {
-    $scope.initDashboard(
-      {
-        meta: { canStar: false, canShare: false, isNew: true },
-        dashboard: {
-          title: "New dashboard",
-          panels: [
-            {
-              type: "add-panel",
-              gridPos: { x: 0, y: 0, w: 12, h: 9 },
-              title: "Panel Title"
-            }
-          ],
-          folderId: Number($routeParams.folderId)
-        }
-      },
-      $scope
-    );
-  }
-}
-
-coreModule.controller("LoadDashboardCtrl", LoadDashboardCtrl);
-coreModule.controller("NewDashboardCtrl", NewDashboardCtrl);

+ 0 - 304
public/app/core/routes/routes.ts

@@ -1,304 +0,0 @@
-import "./dashboard_loaders";
-import coreModule from "app/core/core_module";
-
-/** @ngInject **/
-function setupAngularRoutes($routeProvider, $locationProvider) {
-  $locationProvider.html5Mode(true);
-
-  var loadOrgBundle = {
-    lazy: [
-      "$q",
-      "$route",
-      "$rootScope",
-      ($q, $route, $rootScope) => {
-        return System.import("app/features/org/all");
-      }
-    ]
-  };
-
-  var loadAdminBundle = {
-    lazy: [
-      "$q",
-      "$route",
-      "$rootScope",
-      ($q, $route, $rootScope) => {
-        return System.import("app/features/admin/admin");
-      }
-    ]
-  };
-
-  var loadAlertingBundle = {
-    lazy: [
-      "$q",
-      "$route",
-      "$rootScope",
-      ($q, $route, $rootScope) => {
-        return System.import("app/features/alerting/all");
-      }
-    ]
-  };
-
-  $routeProvider
-    .when("/", {
-      templateUrl: "public/app/partials/dashboard.html",
-      controller: "LoadDashboardCtrl",
-      reloadOnSearch: false,
-      pageClass: "page-dashboard"
-    })
-    .when("/dashboard/:type/:slug", {
-      templateUrl: "public/app/partials/dashboard.html",
-      controller: "LoadDashboardCtrl",
-      reloadOnSearch: false,
-      pageClass: "page-dashboard"
-    })
-    .when("/dashboard-solo/:type/:slug", {
-      templateUrl: "public/app/features/panel/partials/soloPanel.html",
-      controller: "SoloPanelCtrl",
-      reloadOnSearch: false,
-      pageClass: "page-dashboard"
-    })
-    .when("/dashboard/new", {
-      templateUrl: "public/app/partials/dashboard.html",
-      controller: "NewDashboardCtrl",
-      reloadOnSearch: false,
-      pageClass: "page-dashboard"
-    })
-    .when("/dashboard/import", {
-      templateUrl:
-        "public/app/features/dashboard/partials/dashboardImport.html",
-      controller: "DashboardImportCtrl",
-      controllerAs: "ctrl"
-    })
-    .when("/datasources", {
-      templateUrl: "public/app/features/plugins/partials/ds_list.html",
-      controller: "DataSourcesCtrl",
-      controllerAs: "ctrl"
-    })
-    .when("/datasources/edit/:id", {
-      templateUrl: "public/app/features/plugins/partials/ds_edit.html",
-      controller: "DataSourceEditCtrl",
-      controllerAs: "ctrl"
-    })
-    .when("/datasources/new", {
-      templateUrl: "public/app/features/plugins/partials/ds_edit.html",
-      controller: "DataSourceEditCtrl",
-      controllerAs: "ctrl"
-    })
-    .when("/dashboards", {
-      templateUrl: "public/app/features/dashboard/partials/dashboard_list.html",
-      controller: "DashboardListCtrl",
-      controllerAs: "ctrl"
-    })
-    .when("/dashboards/folder/new", {
-      templateUrl: "public/app/features/dashboard/partials/create_folder.html",
-      controller: "CreateFolderCtrl",
-      controllerAs: "ctrl"
-    })
-    .when("/dashboards/folder/:folderId/:slug/permissions", {
-      templateUrl:
-        "public/app/features/dashboard/partials/folder_permissions.html",
-      controller: "FolderPermissionsCtrl",
-      controllerAs: "ctrl"
-    })
-    .when("/dashboards/folder/:folderId/:slug/settings", {
-      templateUrl:
-        "public/app/features/dashboard/partials/folder_settings.html",
-      controller: "FolderSettingsCtrl",
-      controllerAs: "ctrl"
-    })
-    .when("/dashboards/folder/:folderId/:slug", {
-      templateUrl:
-        "public/app/features/dashboard/partials/folder_dashboards.html",
-      controller: "FolderDashboardsCtrl",
-      controllerAs: "ctrl"
-    })
-    .when("/org", {
-      templateUrl: "public/app/features/org/partials/orgDetails.html",
-      controller: "OrgDetailsCtrl",
-      resolve: loadOrgBundle
-    })
-    .when("/org/new", {
-      templateUrl: "public/app/features/org/partials/newOrg.html",
-      controller: "NewOrgCtrl",
-      resolve: loadOrgBundle
-    })
-    .when("/org/users", {
-      templateUrl: "public/app/features/org/partials/orgUsers.html",
-      controller: "OrgUsersCtrl",
-      controllerAs: "ctrl",
-      resolve: loadOrgBundle
-    })
-    .when("/org/users/invite", {
-      templateUrl: "public/app/features/org/partials/invite.html",
-      controller: "UserInviteCtrl",
-      controllerAs: "ctrl",
-      resolve: loadOrgBundle
-    })
-    .when("/org/apikeys", {
-      templateUrl: "public/app/features/org/partials/orgApiKeys.html",
-      controller: "OrgApiKeysCtrl",
-      resolve: loadOrgBundle
-    })
-    .when("/org/teams", {
-      templateUrl: "public/app/features/org/partials/teams.html",
-      controller: "TeamsCtrl",
-      controllerAs: "ctrl",
-      resolve: loadOrgBundle
-    })
-    .when("/org/teams/edit/:id", {
-      templateUrl: "public/app/features/org/partials/team_details.html",
-      controller: "TeamDetailsCtrl",
-      controllerAs: "ctrl",
-      resolve: loadOrgBundle
-    })
-    .when("/profile", {
-      templateUrl: "public/app/features/org/partials/profile.html",
-      controller: "ProfileCtrl",
-      controllerAs: "ctrl",
-      resolve: loadOrgBundle
-    })
-    .when("/profile/password", {
-      templateUrl: "public/app/features/org/partials/change_password.html",
-      controller: "ChangePasswordCtrl",
-      resolve: loadOrgBundle
-    })
-    .when("/profile/select-org", {
-      templateUrl: "public/app/features/org/partials/select_org.html",
-      controller: "SelectOrgCtrl",
-      resolve: loadOrgBundle
-    })
-    // ADMIN
-    .when("/admin", {
-      templateUrl: "public/app/features/admin/partials/admin_home.html",
-      controller: "AdminHomeCtrl",
-      controllerAs: "ctrl",
-      resolve: loadAdminBundle
-    })
-    .when("/admin/settings", {
-      templateUrl: "public/app/features/admin/partials/settings.html",
-      controller: "AdminSettingsCtrl",
-      controllerAs: "ctrl",
-      resolve: loadAdminBundle
-    })
-    .when("/admin/users", {
-      templateUrl: "public/app/features/admin/partials/users.html",
-      controller: "AdminListUsersCtrl",
-      controllerAs: "ctrl",
-      resolve: loadAdminBundle
-    })
-    .when("/admin/users/create", {
-      templateUrl: "public/app/features/admin/partials/new_user.html",
-      controller: "AdminEditUserCtrl",
-      resolve: loadAdminBundle
-    })
-    .when("/admin/users/edit/:id", {
-      templateUrl: "public/app/features/admin/partials/edit_user.html",
-      controller: "AdminEditUserCtrl",
-      resolve: loadAdminBundle
-    })
-    .when("/admin/orgs", {
-      templateUrl: "public/app/features/admin/partials/orgs.html",
-      controller: "AdminListOrgsCtrl",
-      controllerAs: "ctrl",
-      resolve: loadAdminBundle
-    })
-    .when("/admin/orgs/edit/:id", {
-      templateUrl: "public/app/features/admin/partials/edit_org.html",
-      controller: "AdminEditOrgCtrl",
-      controllerAs: "ctrl",
-      resolve: loadAdminBundle
-    })
-    .when("/admin/stats", {
-      templateUrl: "public/app/features/admin/partials/stats.html",
-      controller: "AdminStatsCtrl",
-      controllerAs: "ctrl",
-      resolve: loadAdminBundle
-    })
-    // LOGIN / SIGNUP
-    .when("/login", {
-      templateUrl: "public/app/partials/login.html",
-      controller: "LoginCtrl",
-      pageClass: "login-page sidemenu-hidden"
-    })
-    .when("/invite/:code", {
-      templateUrl: "public/app/partials/signup_invited.html",
-      controller: "InvitedCtrl",
-      pageClass: "sidemenu-hidden"
-    })
-    .when("/signup", {
-      templateUrl: "public/app/partials/signup_step2.html",
-      controller: "SignUpCtrl",
-      pageClass: "sidemenu-hidden"
-    })
-    .when("/user/password/send-reset-email", {
-      templateUrl: "public/app/partials/reset_password.html",
-      controller: "ResetPasswordCtrl",
-      pageClass: "sidemenu-hidden"
-    })
-    .when("/user/password/reset", {
-      templateUrl: "public/app/partials/reset_password.html",
-      controller: "ResetPasswordCtrl",
-      pageClass: "sidemenu-hidden"
-    })
-    .when("/dashboard/snapshots", {
-      templateUrl: "public/app/features/snapshot/partials/snapshots.html",
-      controller: "SnapshotsCtrl",
-      controllerAs: "ctrl"
-    })
-    .when("/plugins", {
-      templateUrl: "public/app/features/plugins/partials/plugin_list.html",
-      controller: "PluginListCtrl",
-      controllerAs: "ctrl"
-    })
-    .when("/plugins/:pluginId/edit", {
-      templateUrl: "public/app/features/plugins/partials/plugin_edit.html",
-      controller: "PluginEditCtrl",
-      controllerAs: "ctrl"
-    })
-    .when("/plugins/:pluginId/page/:slug", {
-      templateUrl: "public/app/features/plugins/partials/plugin_page.html",
-      controller: "AppPageCtrl",
-      controllerAs: "ctrl"
-    })
-    .when("/styleguide/:page?", {
-      controller: "StyleGuideCtrl",
-      controllerAs: "ctrl",
-      templateUrl: "public/app/features/styleguide/styleguide.html"
-    })
-    .when("/alerting", {
-      redirectTo: "/alerting/list"
-    })
-    .when("/alerting/list", {
-      templateUrl: "public/app/features/alerting/partials/alert_list.html",
-      controller: "AlertListCtrl",
-      controllerAs: "ctrl",
-      resolve: loadAlertingBundle
-    })
-    .when("/alerting/notifications", {
-      templateUrl:
-        "public/app/features/alerting/partials/notifications_list.html",
-      controller: "AlertNotificationsListCtrl",
-      controllerAs: "ctrl",
-      resolve: loadAlertingBundle
-    })
-    .when("/alerting/notification/new", {
-      templateUrl:
-        "public/app/features/alerting/partials/notification_edit.html",
-      controller: "AlertNotificationEditCtrl",
-      controllerAs: "ctrl",
-      resolve: loadAlertingBundle
-    })
-    .when("/alerting/notification/:id/edit", {
-      templateUrl:
-        "public/app/features/alerting/partials/notification_edit.html",
-      controller: "AlertNotificationEditCtrl",
-      controllerAs: "ctrl",
-      resolve: loadAlertingBundle
-    })
-    .otherwise({
-      templateUrl: "public/app/partials/error.html",
-      controller: "ErrorCtrl"
-    });
-}
-
-coreModule.config(setupAngularRoutes);

+ 33 - 40
public/app/core/services/alert_srv.ts

@@ -1,9 +1,9 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import angular from "angular";
-import _ from "lodash";
-import coreModule from "app/core/core_module";
-import appEvents from "app/core/app_events";
+import angular from 'angular';
+import _ from 'lodash';
+import coreModule from 'app/core/core_module';
+import appEvents from 'app/core/app_events';
 
 export class AlertSrv {
   list: any[];
@@ -15,65 +15,59 @@ export class AlertSrv {
 
   init() {
     this.$rootScope.onAppEvent(
-      "alert-error",
+      'alert-error',
       (e, alert) => {
-        this.set(alert[0], alert[1], "error", 12000);
+        this.set(alert[0], alert[1], 'error', 12000);
       },
       this.$rootScope
     );
 
     this.$rootScope.onAppEvent(
-      "alert-warning",
+      'alert-warning',
       (e, alert) => {
-        this.set(alert[0], alert[1], "warning", 5000);
+        this.set(alert[0], alert[1], 'warning', 5000);
       },
       this.$rootScope
     );
 
     this.$rootScope.onAppEvent(
-      "alert-success",
+      'alert-success',
       (e, alert) => {
-        this.set(alert[0], alert[1], "success", 3000);
+        this.set(alert[0], alert[1], 'success', 3000);
       },
       this.$rootScope
     );
 
-    appEvents.on("alert-warning", options =>
-      this.set(options[0], options[1], "warning", 5000)
-    );
-    appEvents.on("alert-success", options =>
-      this.set(options[0], options[1], "success", 3000)
-    );
-    appEvents.on("alert-error", options =>
-      this.set(options[0], options[1], "error", 7000)
-    );
-    appEvents.on("confirm-modal", this.showConfirmModal.bind(this));
+    appEvents.on('alert-warning', options => this.set(options[0], options[1], 'warning', 5000));
+    appEvents.on('alert-success', options => this.set(options[0], options[1], 'success', 3000));
+    appEvents.on('alert-error', options => this.set(options[0], options[1], 'error', 7000));
+    appEvents.on('confirm-modal', this.showConfirmModal.bind(this));
   }
 
   getIconForSeverity(severity) {
     switch (severity) {
-      case "success":
-        return "fa fa-check";
-      case "error":
-        return "fa fa-exclamation-triangle";
+      case 'success':
+        return 'fa fa-check';
+      case 'error':
+        return 'fa fa-exclamation-triangle';
       default:
-        return "fa fa-exclamation";
+        return 'fa fa-exclamation';
     }
   }
 
   set(title, text, severity, timeout) {
     if (_.isObject(text)) {
-      console.log("alert error", text);
+      console.log('alert error', text);
       if (text.statusText) {
         text = `HTTP Error (${text.status}) ${text.statusText}`;
       }
     }
 
     var newAlert = {
-      title: title || "",
-      text: text || "",
-      severity: severity || "info",
-      icon: this.getIconForSeverity(severity)
+      title: title || '',
+      text: text || '',
+      severity: severity || 'info',
+      icon: this.getIconForSeverity(severity),
     };
 
     var newAlertJson = angular.toJson(newAlert);
@@ -114,8 +108,7 @@ export class AlertSrv {
     };
 
     scope.updateConfirmText = function(value) {
-      scope.confirmTextValid =
-        payload.confirmText.toLowerCase() === value.toLowerCase();
+      scope.confirmTextValid = payload.confirmText.toLowerCase() === value.toLowerCase();
     };
 
     scope.title = payload.title;
@@ -126,24 +119,24 @@ export class AlertSrv {
     scope.onConfirm = payload.onConfirm;
     scope.onAltAction = payload.onAltAction;
     scope.altActionText = payload.altActionText;
-    scope.icon = payload.icon || "fa-check";
-    scope.yesText = payload.yesText || "Yes";
-    scope.noText = payload.noText || "Cancel";
+    scope.icon = payload.icon || 'fa-check';
+    scope.yesText = payload.yesText || 'Yes';
+    scope.noText = payload.noText || 'Cancel';
     scope.confirmTextValid = scope.confirmText ? false : true;
 
     var confirmModal = this.$modal({
-      template: "public/app/partials/confirm_modal.html",
+      template: 'public/app/partials/confirm_modal.html',
       persist: false,
-      modalClass: "confirm-modal",
+      modalClass: 'confirm-modal',
       show: false,
       scope: scope,
-      keyboard: false
+      keyboard: false,
     });
 
     confirmModal.then(function(modalEl) {
-      modalEl.modal("show");
+      modalEl.modal('show');
     });
   }
 }
 
-coreModule.service("alertSrv", AlertSrv);
+coreModule.service('alertSrv', AlertSrv);

+ 9 - 9
public/app/core/services/analytics.ts

@@ -1,29 +1,29 @@
-import $ from "jquery";
-import coreModule from "app/core/core_module";
-import config from "app/core/config";
+import $ from 'jquery';
+import coreModule from 'app/core/core_module';
+import config from 'app/core/config';
 
 export class Analytics {
   /** @ngInject */
   constructor(private $rootScope, private $location) {}
 
   gaInit() {
-    $.getScript("https://www.google-analytics.com/analytics.js"); // jQuery shortcut
+    $.getScript('https://www.google-analytics.com/analytics.js'); // jQuery shortcut
     var ga = ((<any>window).ga =
       (<any>window).ga ||
       function() {
         (ga.q = ga.q || []).push(arguments);
       });
     ga.l = +new Date();
-    ga("create", (<any>config).googleAnalyticsId, "auto");
+    ga('create', (<any>config).googleAnalyticsId, 'auto');
     return ga;
   }
 
   init() {
-    this.$rootScope.$on("$viewContentLoaded", () => {
+    this.$rootScope.$on('$viewContentLoaded', () => {
       var track = { page: this.$location.url() };
       var ga = (<any>window).ga || this.gaInit();
-      ga("set", track);
-      ga("send", "pageview");
+      ga('set', track);
+      ga('send', 'pageview');
     });
   }
 }
@@ -35,4 +35,4 @@ function startAnalytics(googleAnalyticsSrv) {
   }
 }
 
-coreModule.service("googleAnalyticsSrv", Analytics).run(startAnalytics);
+coreModule.service('googleAnalyticsSrv', Analytics).run(startAnalytics);

+ 53 - 66
public/app/core/services/backend_srv.ts

@@ -1,9 +1,9 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import _ from "lodash";
-import coreModule from "app/core/core_module";
-import appEvents from "app/core/app_events";
-import { DashboardModel } from "app/features/dashboard/dashboard_model";
+import _ from 'lodash';
+import coreModule from 'app/core/core_module';
+import appEvents from 'app/core/app_events';
+import { DashboardModel } from 'app/features/dashboard/dashboard_model';
 
 export class BackendSrv {
   private inFlightRequests = {};
@@ -11,32 +11,26 @@ export class BackendSrv {
   private noBackendCache: boolean;
 
   /** @ngInject */
-  constructor(
-    private $http,
-    private alertSrv,
-    private $q,
-    private $timeout,
-    private contextSrv
-  ) {}
+  constructor(private $http, private alertSrv, private $q, private $timeout, private contextSrv) {}
 
   get(url, params?) {
-    return this.request({ method: "GET", url: url, params: params });
+    return this.request({ method: 'GET', url: url, params: params });
   }
 
   delete(url) {
-    return this.request({ method: "DELETE", url: url });
+    return this.request({ method: 'DELETE', url: url });
   }
 
   post(url, data) {
-    return this.request({ method: "POST", url: url, data: data });
+    return this.request({ method: 'POST', url: url, data: data });
   }
 
   patch(url, data) {
-    return this.request({ method: "PATCH", url: url, data: data });
+    return this.request({ method: 'PATCH', url: url, data: data });
   }
 
   put(url, data) {
-    return this.request({ method: "PUT", url: url, data: data });
+    return this.request({ method: 'PUT', url: url, data: data });
   }
 
   withNoBackendCache(callback) {
@@ -51,28 +45,28 @@ export class BackendSrv {
       return;
     }
 
-    var data = err.data || { message: "Unexpected error" };
+    var data = err.data || { message: 'Unexpected error' };
     if (_.isString(data)) {
       data = { message: data };
     }
 
     if (err.status === 422) {
-      this.alertSrv.set("Validation failed", data.message, "warning", 4000);
+      this.alertSrv.set('Validation failed', data.message, 'warning', 4000);
       throw data;
     }
 
-    data.severity = "error";
+    data.severity = 'error';
 
     if (err.status < 500) {
-      data.severity = "warning";
+      data.severity = 'warning';
     }
 
     if (data.message) {
-      let description = "";
+      let description = '';
       let message = data.message;
       if (message.length > 80) {
         description = message;
-        message = "Error";
+        message = 'Error';
       }
       this.alertSrv.set(message, description, data.severity, 10000);
     }
@@ -88,20 +82,20 @@ export class BackendSrv {
     if (requestIsLocal) {
       if (this.contextSrv.user && this.contextSrv.user.orgId) {
         options.headers = options.headers || {};
-        options.headers["X-Grafana-Org-Id"] = this.contextSrv.user.orgId;
+        options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId;
       }
 
-      if (options.url.indexOf("/") === 0) {
+      if (options.url.indexOf('/') === 0) {
         options.url = options.url.substring(1);
       }
     }
 
     return this.$http(options).then(
       results => {
-        if (options.method !== "GET") {
+        if (options.method !== 'GET') {
           if (results && results.data.message) {
             if (options.showSuccessAlert !== false) {
-              this.alertSrv.set(results.data.message, "", "success", 3000);
+              this.alertSrv.set(results.data.message, '', 'success', 3000);
             }
           }
         }
@@ -109,11 +103,7 @@ export class BackendSrv {
       },
       err => {
         // handle unauthorized
-        if (
-          err.status === 401 &&
-          this.contextSrv.user.isSignedIn &&
-          firstAttempt
-        ) {
+        if (err.status === 401 && this.contextSrv.user.isSignedIn && firstAttempt) {
           return this.loginPing().then(() => {
             options.retry = 1;
             return this.request(options);
@@ -163,26 +153,26 @@ export class BackendSrv {
     if (requestIsLocal) {
       if (this.contextSrv.user && this.contextSrv.user.orgId) {
         options.headers = options.headers || {};
-        options.headers["X-Grafana-Org-Id"] = this.contextSrv.user.orgId;
+        options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId;
       }
 
-      if (options.url.indexOf("/") === 0) {
+      if (options.url.indexOf('/') === 0) {
         options.url = options.url.substring(1);
       }
 
       if (options.headers && options.headers.Authorization) {
-        options.headers["X-DS-Authorization"] = options.headers.Authorization;
+        options.headers['X-DS-Authorization'] = options.headers.Authorization;
         delete options.headers.Authorization;
       }
 
       if (this.noBackendCache) {
-        options.headers["X-Grafana-NoCache"] = "true";
+        options.headers['X-Grafana-NoCache'] = 'true';
       }
     }
 
     return this.$http(options)
       .then(response => {
-        appEvents.emit("ds-request-response", response);
+        appEvents.emit('ds-request-response', response);
         return response;
       })
       .catch(err => {
@@ -205,7 +195,7 @@ export class BackendSrv {
         if (_.isString(err.data) && err.status === 500) {
           err.data = {
             error: err.statusText,
-            response: err.data
+            response: err.data,
           };
         }
 
@@ -214,7 +204,7 @@ export class BackendSrv {
           err.data.message = err.data.error;
         }
 
-        appEvents.emit("ds-request-error", err);
+        appEvents.emit('ds-request-error', err);
         throw err;
       })
       .finally(() => {
@@ -226,49 +216,49 @@ export class BackendSrv {
   }
 
   loginPing() {
-    return this.request({ url: "/api/login/ping", method: "GET", retry: 1 });
+    return this.request({ url: '/api/login/ping', method: 'GET', retry: 1 });
   }
 
   search(query) {
-    return this.get("/api/search", query);
+    return this.get('/api/search', query);
   }
 
   getDashboard(type, slug) {
-    return this.get("/api/dashboards/" + type + "/" + slug);
+    return this.get('/api/dashboards/' + type + '/' + slug);
   }
 
   saveDashboard(dash, options) {
     options = options || {};
 
-    return this.post("/api/dashboards/db/", {
+    return this.post('/api/dashboards/db/', {
       dashboard: dash,
-      folderId: dash.folderId,
+      folderId: options.folderId,
       overwrite: options.overwrite === true,
-      message: options.message || ""
+      message: options.message || '',
     });
   }
 
   createDashboardFolder(name) {
     const dash = {
       schemaVersion: 16,
-      title: name,
+      title: name.trim(),
       editable: true,
-      panels: []
+      panels: [],
     };
 
-    return this.post("/api/dashboards/db/", {
+    return this.post('/api/dashboards/db/', {
       dashboard: dash,
       isFolder: true,
-      overwrite: false
+      overwrite: false,
     }).then(res => {
-      return this.getDashboard("db", res.slug);
+      return this.getDashboard('db', res.slug);
     });
   }
 
   deleteDashboard(slug) {
     let deferred = this.$q.defer();
 
-    this.getDashboard("db", slug).then(fullDash => {
+    this.getDashboard('db', slug).then(fullDash => {
       this.delete(`/api/dashboards/db/${slug}`)
         .then(() => {
           deferred.resolve(fullDash);
@@ -295,16 +285,14 @@ export class BackendSrv {
     const tasks = [];
 
     for (let slug of dashboardSlugs) {
-      tasks.push(
-        this.createTask(this.moveDashboard.bind(this), true, slug, toFolder)
-      );
+      tasks.push(this.createTask(this.moveDashboard.bind(this), true, slug, toFolder));
     }
 
     return this.executeInOrder(tasks, []).then(result => {
       return {
         totalCount: result.length,
         successCount: _.filter(result, { succeeded: true }).length,
-        alreadyInFolderCount: _.filter(result, { alreadyInFolder: true }).length
+        alreadyInFolderCount: _.filter(result, { alreadyInFolder: true }).length,
       };
     });
   }
@@ -312,31 +300,30 @@ export class BackendSrv {
   private moveDashboard(slug, toFolder) {
     let deferred = this.$q.defer();
 
-    this.getDashboard("db", slug).then(fullDash => {
+    this.getDashboard('db', slug).then(fullDash => {
       const model = new DashboardModel(fullDash.dashboard, fullDash.meta);
 
-      if (
-        (!fullDash.meta.folderId && toFolder.id === 0) ||
-        fullDash.meta.folderId === toFolder.id
-      ) {
+      if ((!fullDash.meta.folderId && toFolder.id === 0) || fullDash.meta.folderId === toFolder.id) {
         deferred.resolve({ alreadyInFolder: true });
         return;
       }
 
-      model.folderId = toFolder.id;
-      model.meta.folderId = toFolder.id;
-      model.meta.folderTitle = toFolder.title;
       const clone = model.getSaveModelClone();
+      let options = {
+        folderId: toFolder.id,
+        overwrite: false,
+      };
 
-      this.saveDashboard(clone, {})
+      this.saveDashboard(clone, options)
         .then(() => {
           deferred.resolve({ succeeded: true });
         })
         .catch(err => {
-          if (err.data && err.data.status === "plugin-dashboard") {
+          if (err.data && err.data.status === 'plugin-dashboard') {
             err.isHandled = true;
+            options.overwrite = true;
 
-            this.saveDashboard(clone, { overwrite: true })
+            this.saveDashboard(clone, options)
               .then(() => {
                 deferred.resolve({ succeeded: true });
               })
@@ -374,4 +361,4 @@ export class BackendSrv {
   }
 }
 
-coreModule.service("backendSrv", BackendSrv);
+coreModule.service('backendSrv', BackendSrv);

+ 9 - 12
public/app/core/services/context_srv.ts

@@ -1,7 +1,7 @@
-import config from "app/core/config";
-import _ from "lodash";
-import coreModule from "app/core/core_module";
-import store from "app/core/store";
+import config from 'app/core/config';
+import _ from 'lodash';
+import coreModule from 'app/core/core_module';
+import store from 'app/core/store';
 
 export class User {
   isGrafanaAdmin: any;
@@ -30,7 +30,7 @@ export class ContextSrv {
   sidemenuSmallBreakpoint = false;
 
   constructor() {
-    this.sidemenu = store.getBool("grafana.sidemenu", true);
+    this.sidemenu = store.getBool('grafana.sidemenu', true);
 
     if (!config.buildInfo) {
       config.buildInfo = {};
@@ -43,7 +43,7 @@ export class ContextSrv {
     this.user = new User();
     this.isSignedIn = this.user.isSignedIn;
     this.isGrafanaAdmin = this.user.isGrafanaAdmin;
-    this.isEditor = this.hasRole("Editor") || this.hasRole("Admin");
+    this.isEditor = this.hasRole('Editor') || this.hasRole('Admin');
   }
 
   hasRole(role) {
@@ -51,21 +51,18 @@ export class ContextSrv {
   }
 
   isGrafanaVisible() {
-    return !!(
-      document.visibilityState === undefined ||
-      document.visibilityState === "visible"
-    );
+    return !!(document.visibilityState === undefined || document.visibilityState === 'visible');
   }
 
   toggleSideMenu() {
     this.sidemenu = !this.sidemenu;
-    store.set("grafana.sidemenu", this.sidemenu);
+    store.set('grafana.sidemenu', this.sidemenu);
   }
 }
 
 var contextSrv = new ContextSrv();
 export { contextSrv };
 
-coreModule.factory("contextSrv", function() {
+coreModule.factory('contextSrv', function() {
   return contextSrv;
 });

+ 8 - 14
public/app/core/services/dynamic_directive_srv.ts

@@ -1,7 +1,7 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import angular from "angular";
-import coreModule from "../core_module";
+import angular from 'angular';
+import coreModule from '../core_module';
 
 class DynamicDirectiveSrv {
   /** @ngInject */
@@ -25,27 +25,21 @@ class DynamicDirectiveSrv {
         }
 
         if (!directiveInfo.fn.registered) {
-          coreModule.directive(
-            attrs.$normalize(directiveInfo.name),
-            directiveInfo.fn
-          );
+          coreModule.directive(attrs.$normalize(directiveInfo.name), directiveInfo.fn);
           directiveInfo.fn.registered = true;
         }
 
         this.addDirective(elem, directiveInfo.name, scope);
       })
       .catch(err => {
-        console.log("Plugin load:", err);
-        this.$rootScope.appEvent("alert-error", [
-          "Plugin error",
-          err.toString()
-        ]);
+        console.log('Plugin load:', err);
+        this.$rootScope.appEvent('alert-error', ['Plugin error', err.toString()]);
       });
   }
 
   create(options) {
     let directiveDef = {
-      restrict: "E",
+      restrict: 'E',
       scope: options.scope,
       link: (scope, elem, attrs) => {
         if (options.watchPath) {
@@ -60,11 +54,11 @@ class DynamicDirectiveSrv {
         } else {
           this.link(scope, elem, attrs, options);
         }
-      }
+      },
     };
 
     return directiveDef;
   }
 }
 
-coreModule.service("dynamicDirectiveSrv", DynamicDirectiveSrv);
+coreModule.service('dynamicDirectiveSrv', DynamicDirectiveSrv);

+ 15 - 11
public/app/core/services/global_event_srv.ts

@@ -1,32 +1,36 @@
-import coreModule from "app/core/core_module";
-import config from "app/core/config";
-import appEvents from "app/core/app_events";
+import coreModule from 'app/core/core_module';
+import config from 'app/core/config';
+import appEvents from 'app/core/app_events';
 
 // This service is for registering global events.
 // Good for communication react > angular and vice verse
 export class GlobalEventSrv {
   private appSubUrl;
+  private fullPageReloadRoutes;
 
   /** @ngInject */
-  constructor(private $location, private $timeout) {
+  constructor(private $location, private $timeout, private $window) {
     this.appSubUrl = config.appSubUrl;
+    this.fullPageReloadRoutes = ['/logout'];
   }
 
   // Angular's $location does not like <base href...> and absolute urls
-  stripBaseFromUrl(url = "") {
+  stripBaseFromUrl(url = '') {
     const appSubUrl = this.appSubUrl;
-    const stripExtraChars = appSubUrl.endsWith("/") ? 1 : 0;
+    const stripExtraChars = appSubUrl.endsWith('/') ? 1 : 0;
     const urlWithoutBase =
-      url.length > 0 && url.indexOf(appSubUrl) === 0
-        ? url.slice(appSubUrl.length - stripExtraChars)
-        : url;
+      url.length > 0 && url.indexOf(appSubUrl) === 0 ? url.slice(appSubUrl.length - stripExtraChars) : url;
 
     return urlWithoutBase;
   }
 
   init() {
-    appEvents.on("location-change", payload => {
+    appEvents.on('location-change', payload => {
       const urlWithoutBase = this.stripBaseFromUrl(payload.href);
+      if (this.fullPageReloadRoutes.indexOf(urlWithoutBase) > -1) {
+        this.$window.location.href = payload.href;
+        return;
+      }
 
       this.$timeout(() => {
         // A hack to use timeout when we're changing things (in this case the url) from outside of Angular.
@@ -36,4 +40,4 @@ export class GlobalEventSrv {
   }
 }
 
-coreModule.service("globalEventSrv", GlobalEventSrv);
+coreModule.service('globalEventSrv', GlobalEventSrv);

+ 5 - 5
public/app/core/services/impression_srv.ts

@@ -1,6 +1,6 @@
-import store from "app/core/store";
-import _ from "lodash";
-import config from "app/core/config";
+import store from 'app/core/store';
+import _ from 'lodash';
+import config from 'app/core/config';
 
 export class ImpressionSrv {
   constructor() {}
@@ -28,7 +28,7 @@ export class ImpressionSrv {
   }
 
   getDashboardOpened() {
-    var impressions = store.get(this.impressionKey(config)) || "[]";
+    var impressions = store.get(this.impressionKey(config)) || '[]';
 
     impressions = JSON.parse(impressions);
 
@@ -40,7 +40,7 @@ export class ImpressionSrv {
   }
 
   impressionKey(config) {
-    return "dashboard_impressions-" + config.bootData.user.orgId;
+    return 'dashboard_impressions-' + config.bootData.user.orgId;
   }
 }
 

+ 65 - 65
public/app/core/services/keybindingSrv.ts

@@ -1,10 +1,10 @@
-import $ from "jquery";
-import _ from "lodash";
+import $ from 'jquery';
+import _ from 'lodash';
 
-import coreModule from "app/core/core_module";
-import appEvents from "app/core/app_events";
+import coreModule from 'app/core/core_module';
+import appEvents from 'app/core/app_events';
 
-import Mousetrap from "mousetrap";
+import Mousetrap from 'mousetrap';
 
 export class KeybindingSrv {
   helpModal: boolean;
@@ -12,7 +12,7 @@ export class KeybindingSrv {
   /** @ngInject */
   constructor(private $rootScope, private $location) {
     // clear out all shortcuts on route change
-    $rootScope.$on("$routeChangeSuccess", () => {
+    $rootScope.$on('$routeChangeSuccess', () => {
       Mousetrap.reset();
       // rebind global shortcuts
       this.setupGlobal();
@@ -22,42 +22,42 @@ export class KeybindingSrv {
   }
 
   setupGlobal() {
-    this.bind(["?", "h"], this.showHelpModal);
-    this.bind("g h", this.goToHome);
-    this.bind("g a", this.openAlerting);
-    this.bind("g p", this.goToProfile);
-    this.bind("s s", this.openSearchStarred);
-    this.bind("s o", this.openSearch);
-    this.bind("s t", this.openSearchTags);
-    this.bind("f", this.openSearch);
+    this.bind(['?', 'h'], this.showHelpModal);
+    this.bind('g h', this.goToHome);
+    this.bind('g a', this.openAlerting);
+    this.bind('g p', this.goToProfile);
+    this.bind('s s', this.openSearchStarred);
+    this.bind('s o', this.openSearch);
+    this.bind('s t', this.openSearchTags);
+    this.bind('f', this.openSearch);
   }
 
   openSearchStarred() {
-    appEvents.emit("show-dash-search", { starred: true });
+    appEvents.emit('show-dash-search', { starred: true });
   }
 
   openSearchTags() {
-    appEvents.emit("show-dash-search", { tagsMode: true });
+    appEvents.emit('show-dash-search', { tagsMode: true });
   }
 
   openSearch() {
-    appEvents.emit("show-dash-search");
+    appEvents.emit('show-dash-search');
   }
 
   openAlerting() {
-    this.$location.url("/alerting");
+    this.$location.url('/alerting');
   }
 
   goToHome() {
-    this.$location.url("/");
+    this.$location.url('/');
   }
 
   goToProfile() {
-    this.$location.url("/profile");
+    this.$location.url('/profile');
   }
 
   showHelpModal() {
-    appEvents.emit("show-modal", { templateHtml: "<help-modal></help-modal>" });
+    appEvents.emit('show-modal', { templateHtml: '<help-modal></help-modal>' });
   }
 
   bind(keyArg, fn) {
@@ -69,68 +69,68 @@ export class KeybindingSrv {
         evt.returnValue = false;
         return this.$rootScope.$apply(fn.bind(this));
       },
-      "keydown"
+      'keydown'
     );
   }
 
   showDashEditView() {
-    var search = _.extend(this.$location.search(), { editview: "settings" });
+    var search = _.extend(this.$location.search(), { editview: 'settings' });
     this.$location.search(search);
   }
 
   setupDashboardBindings(scope, dashboard) {
-    this.bind("mod+o", () => {
+    this.bind('mod+o', () => {
       dashboard.graphTooltip = (dashboard.graphTooltip + 1) % 3;
-      appEvents.emit("graph-hover-clear");
-      this.$rootScope.$broadcast("refresh");
+      appEvents.emit('graph-hover-clear');
+      this.$rootScope.$broadcast('refresh');
     });
 
-    this.bind("mod+s", e => {
-      scope.appEvent("save-dashboard");
+    this.bind('mod+s', e => {
+      scope.appEvent('save-dashboard');
     });
 
-    this.bind("t z", () => {
-      scope.appEvent("zoom-out", 2);
+    this.bind('t z', () => {
+      scope.appEvent('zoom-out', 2);
     });
 
-    this.bind("ctrl+z", () => {
-      scope.appEvent("zoom-out", 2);
+    this.bind('ctrl+z', () => {
+      scope.appEvent('zoom-out', 2);
     });
 
-    this.bind("t left", () => {
-      scope.appEvent("shift-time-backward");
+    this.bind('t left', () => {
+      scope.appEvent('shift-time-backward');
     });
 
-    this.bind("t right", () => {
-      scope.appEvent("shift-time-forward");
+    this.bind('t right', () => {
+      scope.appEvent('shift-time-forward');
     });
 
     // edit panel
-    this.bind("e", () => {
+    this.bind('e', () => {
       if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) {
-        this.$rootScope.appEvent("panel-change-view", {
+        this.$rootScope.appEvent('panel-change-view', {
           fullscreen: true,
           edit: true,
           panelId: dashboard.meta.focusPanelId,
-          toggle: true
+          toggle: true,
         });
       }
     });
 
     // view panel
-    this.bind("v", () => {
+    this.bind('v', () => {
       if (dashboard.meta.focusPanelId) {
-        this.$rootScope.appEvent("panel-change-view", {
+        this.$rootScope.appEvent('panel-change-view', {
           fullscreen: true,
           edit: null,
           panelId: dashboard.meta.focusPanelId,
-          toggle: true
+          toggle: true,
         });
       }
     });
 
     // delete panel
-    this.bind("p r", () => {
+    this.bind('p r', () => {
       if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) {
         var panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId);
         panelInfo.row.removePanel(panelInfo.panel);
@@ -139,22 +139,22 @@ export class KeybindingSrv {
     });
 
     // share panel
-    this.bind("p s", () => {
+    this.bind('p s', () => {
       if (dashboard.meta.focusPanelId) {
         var shareScope = scope.$new();
         var panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId);
         shareScope.panel = panelInfo.panel;
         shareScope.dashboard = dashboard;
 
-        appEvents.emit("show-modal", {
-          src: "public/app/features/dashboard/partials/shareModal.html",
-          scope: shareScope
+        appEvents.emit('show-modal', {
+          src: 'public/app/features/dashboard/partials/shareModal.html',
+          scope: shareScope,
         });
       }
     });
 
     // delete row
-    this.bind("r r", () => {
+    this.bind('r r', () => {
       if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) {
         var panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId);
         dashboard.removeRow(panelInfo.row);
@@ -163,7 +163,7 @@ export class KeybindingSrv {
     });
 
     // collapse row
-    this.bind("r c", () => {
+    this.bind('r c', () => {
       if (dashboard.meta.focusPanelId) {
         var panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId);
         panelInfo.row.toggleCollapse();
@@ -172,47 +172,47 @@ export class KeybindingSrv {
     });
 
     // collapse all rows
-    this.bind("d shift+c", () => {
+    this.bind('d shift+c', () => {
       for (let row of dashboard.rows) {
         row.collapse = true;
       }
     });
 
     // expand all rows
-    this.bind("d shift+e", () => {
+    this.bind('d shift+e', () => {
       for (let row of dashboard.rows) {
         row.collapse = false;
       }
     });
 
-    this.bind("d n", e => {
-      this.$location.url("/dashboard/new");
+    this.bind('d n', e => {
+      this.$location.url('/dashboard/new');
     });
 
-    this.bind("d r", () => {
-      this.$rootScope.$broadcast("refresh");
+    this.bind('d r', () => {
+      this.$rootScope.$broadcast('refresh');
     });
 
-    this.bind("d s", () => {
+    this.bind('d s', () => {
       this.showDashEditView();
     });
 
-    this.bind("d k", () => {
-      appEvents.emit("toggle-kiosk-mode");
+    this.bind('d k', () => {
+      appEvents.emit('toggle-kiosk-mode');
     });
 
-    this.bind("d v", () => {
-      appEvents.emit("toggle-view-mode");
+    this.bind('d v', () => {
+      appEvents.emit('toggle-view-mode');
     });
 
-    this.bind("esc", () => {
-      var popups = $(".popover.in");
+    this.bind('esc', () => {
+      var popups = $('.popover.in');
       if (popups.length > 0) {
         return;
       }
 
-      scope.appEvent("hide-modal");
-      scope.appEvent("panel-change-view", { fullscreen: false, edit: false });
+      scope.appEvent('hide-modal');
+      scope.appEvent('panel-change-view', { fullscreen: false, edit: false });
 
       // close settings view
       var search = this.$location.search();
@@ -224,4 +224,4 @@ export class KeybindingSrv {
   }
 }
 
-coreModule.service("keybindingSrv", KeybindingSrv);
+coreModule.service('keybindingSrv', KeybindingSrv);

+ 28 - 49
public/app/core/services/ng_react.ts

@@ -9,9 +9,9 @@
 // - reactComponent (generic directive for delegating off to React Components)
 // - reactDirective (factory for creating specific directives that correspond to reactComponent directives)
 
-import React from "react";
-import ReactDOM from "react-dom";
-import angular from "angular";
+import React from 'react';
+import ReactDOM from 'react-dom';
+import angular from 'angular';
 
 // get a react component from name (components can be an angular injectable e.g. value, factory or
 // available on window
@@ -23,7 +23,7 @@ function getReactComponent(name, $injector) {
 
   // a React component name must be specified
   if (!name) {
-    throw new Error("ReactComponent name attribute must be specified");
+    throw new Error('ReactComponent name attribute must be specified');
   }
 
   // ensure the specified React component is accessible, and fail fast if it's not
@@ -34,14 +34,14 @@ function getReactComponent(name, $injector) {
 
   if (!reactComponent) {
     try {
-      reactComponent = name.split(".").reduce(function(current, namePart) {
+      reactComponent = name.split('.').reduce(function(current, namePart) {
         return current[namePart];
       }, window);
     } catch (e) {}
   }
 
   if (!reactComponent) {
-    throw Error("Cannot find react component " + name);
+    throw Error('Cannot find react component ' + name);
   }
 
   return reactComponent;
@@ -55,7 +55,7 @@ function applied(fn, scope) {
   var wrapped: any = function() {
     var args = arguments;
     var phase = scope.$root.$$phase;
-    if (phase === "$apply" || phase === "$digest") {
+    if (phase === '$apply' || phase === '$digest') {
       return fn.apply(null, args);
     } else {
       return scope.$apply(function() {
@@ -88,10 +88,7 @@ function applyFunctions(obj, scope, propsConfig?) {
      * ensures that when function is called from a React component
      * the Angular digest cycle is run
      */
-    prev[key] =
-      angular.isFunction(value) && config.wrapApply !== false
-        ? applied(value, scope)
-        : value;
+    prev[key] = angular.isFunction(value) && config.wrapApply !== false ? applied(value, scope) : value;
 
     return prev;
   }, {});
@@ -115,18 +112,18 @@ function watchProps(watchDepth, scope, watchExpressions, listener) {
     var actualExpr = getPropExpression(expr);
     var exprWatchDepth = getPropWatchDepth(watchDepth, expr);
 
-    if (exprWatchDepth === "collection" && supportsWatchCollection) {
+    if (exprWatchDepth === 'collection' && supportsWatchCollection) {
       scope.$watchCollection(actualExpr, listener);
-    } else if (exprWatchDepth === "reference" && supportsWatchGroup) {
+    } else if (exprWatchDepth === 'reference' && supportsWatchGroup) {
       watchGroupExpressions.push(actualExpr);
-    } else if (exprWatchDepth === "one-time") {
+    } else if (exprWatchDepth === 'one-time') {
       //do nothing because we handle our one time bindings after this
     } else {
-      scope.$watch(actualExpr, listener, exprWatchDepth !== "reference");
+      scope.$watch(actualExpr, listener, exprWatchDepth !== 'reference');
     }
   });
 
-  if (watchDepth === "one-time") {
+  if (watchDepth === 'one-time') {
     listener();
   }
 
@@ -167,8 +164,7 @@ function findAttribute(attrs, propName) {
 
 // get watch depth of prop (string or array)
 function getPropWatchDepth(defaultWatch, prop) {
-  var customWatchDepth =
-    Array.isArray(prop) && angular.isObject(prop[1]) && prop[1].watchDepth;
+  var customWatchDepth = Array.isArray(prop) && angular.isObject(prop[1]) && prop[1].watchDepth;
   return customWatchDepth || defaultWatch;
 }
 
@@ -192,7 +188,7 @@ function getPropWatchDepth(defaultWatch, prop) {
 //
 var reactComponent = function($injector) {
   return {
-    restrict: "E",
+    restrict: 'E',
     replace: true,
     link: function(scope, elem, attrs) {
       var reactComponent = getReactComponent(attrs.name, $injector);
@@ -205,24 +201,19 @@ var reactComponent = function($injector) {
       };
 
       // If there are props, re-render when they change
-      attrs.props
-        ? watchProps(attrs.watchDepth, scope, [attrs.props], renderMyComponent)
-        : renderMyComponent();
+      attrs.props ? watchProps(attrs.watchDepth, scope, [attrs.props], renderMyComponent) : renderMyComponent();
 
       // cleanup when scope is destroyed
-      scope.$on("$destroy", function() {
+      scope.$on('$destroy', function() {
         if (!attrs.onScopeDestroy) {
           ReactDOM.unmountComponentAtNode(elem[0]);
         } else {
           scope.$eval(attrs.onScopeDestroy, {
-            unmountComponent: ReactDOM.unmountComponentAtNode.bind(
-              this,
-              elem[0]
-            )
+            unmountComponent: ReactDOM.unmountComponentAtNode.bind(this, elem[0]),
           });
         }
       });
-    }
+    },
   };
 };
 
@@ -255,7 +246,7 @@ var reactComponent = function($injector) {
 var reactDirective = function($injector) {
   return function(reactComponentName, props, conf, injectableProps) {
     var directive = {
-      restrict: "E",
+      restrict: 'E',
       replace: true,
       link: function(scope, elem, attrs) {
         var reactComponent = getReactComponent(reactComponentName, $injector);
@@ -282,40 +273,28 @@ var reactDirective = function($injector) {
         // watch each property name and trigger an update whenever something changes,
         // to update scope.props with new values
         var propExpressions = props.map(function(prop) {
-          return Array.isArray(prop)
-            ? [attrs[getPropName(prop)], getPropConfig(prop)]
-            : attrs[prop];
+          return Array.isArray(prop) ? [attrs[getPropName(prop)], getPropConfig(prop)] : attrs[prop];
         });
 
         // If we don't have any props, then our watch statement won't fire.
-        props.length
-          ? watchProps(
-              attrs.watchDepth,
-              scope,
-              propExpressions,
-              renderMyComponent
-            )
-          : renderMyComponent();
+        props.length ? watchProps(attrs.watchDepth, scope, propExpressions, renderMyComponent) : renderMyComponent();
 
         // cleanup when scope is destroyed
-        scope.$on("$destroy", function() {
+        scope.$on('$destroy', function() {
           if (!attrs.onScopeDestroy) {
             ReactDOM.unmountComponentAtNode(elem[0]);
           } else {
             scope.$eval(attrs.onScopeDestroy, {
-              unmountComponent: ReactDOM.unmountComponentAtNode.bind(
-                this,
-                elem[0]
-              )
+              unmountComponent: ReactDOM.unmountComponentAtNode.bind(this, elem[0]),
             });
           }
         });
-      }
+      },
     };
     return angular.extend(directive, conf);
   };
 };
 
-let ngModule = angular.module("react", []);
-ngModule.directive("reactComponent", ["$injector", reactComponent]);
-ngModule.factory("reactDirective", ["$injector", reactDirective]);
+let ngModule = angular.module('react', []);
+ngModule.directive('reactComponent', ['$injector', reactComponent]);
+ngModule.factory('reactDirective', ['$injector', reactDirective]);

+ 9 - 9
public/app/core/services/popover_srv.ts

@@ -1,8 +1,8 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import _ from "lodash";
-import coreModule from "app/core/core_module";
-import Drop from "tether-drop";
+import _ from 'lodash';
+import coreModule from 'app/core/core_module';
+import Drop from 'tether-drop';
 
 /** @ngInject **/
 function popoverSrv($compile, $rootScope, $timeout) {
@@ -43,7 +43,7 @@ function popoverSrv($compile, $rootScope, $timeout) {
       drop.close();
     };
 
-    var contentElement = document.createElement("div");
+    var contentElement = document.createElement('div');
     contentElement.innerHTML = options.template;
 
     $compile(contentElement)(scope);
@@ -53,15 +53,15 @@ function popoverSrv($compile, $rootScope, $timeout) {
         target: options.element,
         content: contentElement,
         position: options.position,
-        classes: options.classNames || "drop-popover",
+        classes: options.classNames || 'drop-popover',
         openOn: options.openOn,
         hoverCloseDelay: 200,
         tetherOptions: {
-          constraints: [{ to: "scrollParent", attachment: "together" }]
-        }
+          constraints: [{ to: 'scrollParent', attachment: 'together' }],
+        },
       });
 
-      drop.on("close", () => {
+      drop.on('close', () => {
         cleanUp();
       });
 
@@ -78,4 +78,4 @@ function popoverSrv($compile, $rootScope, $timeout) {
   };
 }
 
-coreModule.service("popoverSrv", popoverSrv);
+coreModule.service('popoverSrv', popoverSrv);

+ 32 - 32
public/app/core/services/search_srv.ts

@@ -1,8 +1,8 @@
-import _ from "lodash";
-import coreModule from "app/core/core_module";
-import impressionSrv from "app/core/services/impression_srv";
-import store from "app/core/store";
-import { contextSrv } from "app/core/services/context_srv";
+import _ from 'lodash';
+import coreModule from 'app/core/core_module';
+import impressionSrv from 'app/core/services/impression_srv';
+import store from 'app/core/store';
+import { contextSrv } from 'app/core/services/context_srv';
 
 export class SearchSrv {
   recentIsOpen: boolean;
@@ -10,21 +10,21 @@ export class SearchSrv {
 
   /** @ngInject */
   constructor(private backendSrv, private $q) {
-    this.recentIsOpen = store.getBool("search.sections.recent", true);
-    this.starredIsOpen = store.getBool("search.sections.starred", true);
+    this.recentIsOpen = store.getBool('search.sections.recent', true);
+    this.starredIsOpen = store.getBool('search.sections.starred', true);
   }
 
   private getRecentDashboards(sections) {
     return this.queryForRecentDashboards().then(result => {
       if (result.length > 0) {
-        sections["recent"] = {
-          title: "Recent Boards",
-          icon: "fa fa-clock-o",
+        sections['recent'] = {
+          title: 'Recent',
+          icon: 'fa fa-clock-o',
           score: -1,
           removable: true,
           expanded: this.recentIsOpen,
           toggle: this.toggleRecent.bind(this),
-          items: result
+          items: result,
         };
       }
     });
@@ -50,7 +50,7 @@ export class SearchSrv {
 
   private toggleRecent(section) {
     this.recentIsOpen = section.expanded = !section.expanded;
-    store.set("search.sections.recent", this.recentIsOpen);
+    store.set('search.sections.recent', this.recentIsOpen);
 
     if (!section.expanded || section.items.length) {
       return Promise.resolve(section);
@@ -64,7 +64,7 @@ export class SearchSrv {
 
   private toggleStarred(section) {
     this.starredIsOpen = section.expanded = !section.expanded;
-    store.set("search.sections.starred", this.starredIsOpen);
+    store.set('search.sections.starred', this.starredIsOpen);
     return Promise.resolve(section);
   }
 
@@ -75,20 +75,20 @@ export class SearchSrv {
 
     return this.backendSrv.search({ starred: true, limit: 5 }).then(result => {
       if (result.length > 0) {
-        sections["starred"] = {
-          title: "Starred Boards",
-          icon: "fa fa-star-o",
+        sections['starred'] = {
+          title: 'Starred',
+          icon: 'fa fa-star-o',
           score: -2,
           expanded: this.starredIsOpen,
           toggle: this.toggleStarred.bind(this),
-          items: result.map(this.transformToViewModel)
+          items: result.map(this.transformToViewModel),
         };
       }
     });
   }
 
   private transformToViewModel(hit) {
-    hit.url = "dashboard/db/" + hit.slug;
+    hit.url = 'dashboard/db/' + hit.slug;
     return hit;
   }
 
@@ -122,7 +122,7 @@ export class SearchSrv {
     );
 
     return this.$q.all(promises).then(() => {
-      return _.sortBy(_.values(sections), "score");
+      return _.sortBy(_.values(sections), 'score');
     });
   }
 
@@ -133,7 +133,7 @@ export class SearchSrv {
 
     // create folder index
     for (let hit of results) {
-      if (hit.type === "dash-folder") {
+      if (hit.type === 'dash-folder') {
         sections[hit.id] = {
           id: hit.id,
           title: hit.title,
@@ -142,14 +142,14 @@ export class SearchSrv {
           toggle: this.toggleFolder.bind(this),
           url: `dashboards/folder/${hit.id}/${hit.slug}`,
           slug: hit.slug,
-          icon: "fa fa-folder",
-          score: _.keys(sections).length
+          icon: 'fa fa-folder',
+          score: _.keys(sections).length,
         };
       }
     }
 
     for (let hit of results) {
-      if (hit.type === "dash-folder") {
+      if (hit.type === 'dash-folder') {
         continue;
       }
 
@@ -162,18 +162,18 @@ export class SearchSrv {
             url: `dashboards/folder/${hit.folderId}/${hit.folderSlug}`,
             slug: hit.slug,
             items: [],
-            icon: "fa fa-folder-open",
+            icon: 'fa fa-folder-open',
             toggle: this.toggleFolder.bind(this),
-            score: _.keys(sections).length
+            score: _.keys(sections).length,
           };
         } else {
           section = {
             id: 0,
-            title: "Root",
+            title: 'Root',
             items: [],
-            icon: "fa fa-folder-open",
+            icon: 'fa fa-folder-open',
             toggle: this.toggleFolder.bind(this),
-            score: _.keys(sections).length
+            score: _.keys(sections).length,
           };
         }
         // add section
@@ -187,14 +187,14 @@ export class SearchSrv {
 
   private toggleFolder(section) {
     section.expanded = !section.expanded;
-    section.icon = section.expanded ? "fa fa-folder-open" : "fa fa-folder";
+    section.icon = section.expanded ? 'fa fa-folder-open' : 'fa fa-folder';
 
     if (section.items.length) {
       return Promise.resolve(section);
     }
 
     let query = {
-      folderIds: [section.id]
+      folderIds: [section.id],
     };
 
     return this.backendSrv.search(query).then(results => {
@@ -204,8 +204,8 @@ export class SearchSrv {
   }
 
   getDashboardTags() {
-    return this.backendSrv.get("/api/dashboards/tags");
+    return this.backendSrv.get('/api/dashboards/tags');
   }
 }
 
-coreModule.service("searchSrv", SearchSrv);
+coreModule.service('searchSrv', SearchSrv);

+ 3 - 3
public/app/core/services/timer.ts

@@ -1,5 +1,5 @@
-import _ from "lodash";
-import coreModule from "app/core/core_module";
+import _ from 'lodash';
+import coreModule from 'app/core/core_module';
 
 // This service really just tracks a list of $timeout promises to give us a
 // method for cancelling them all when we need to
@@ -27,4 +27,4 @@ export class Timer {
   }
 }
 
-coreModule.service("timer", Timer);
+coreModule.service('timer', Timer);

+ 7 - 7
public/app/core/services/util_srv.ts

@@ -1,7 +1,7 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import coreModule from "app/core/core_module";
-import appEvents from "app/core/app_events";
+import coreModule from 'app/core/core_module';
+import appEvents from 'app/core/app_events';
 
 export class UtilSrv {
   modalScope: any;
@@ -10,8 +10,8 @@ export class UtilSrv {
   constructor(private $rootScope, private $modal) {}
 
   init() {
-    appEvents.on("show-modal", this.showModal.bind(this), this.$rootScope);
-    appEvents.on("hide-modal", this.hideModal.bind(this), this.$rootScope);
+    appEvents.on('show-modal', this.showModal.bind(this), this.$rootScope);
+    appEvents.on('hide-modal', this.hideModal.bind(this), this.$rootScope);
   }
 
   hideModal() {
@@ -42,13 +42,13 @@ export class UtilSrv {
       show: false,
       scope: this.modalScope,
       keyboard: false,
-      backdrop: options.backdrop
+      backdrop: options.backdrop,
     });
 
     Promise.resolve(modal).then(function(modalEl) {
-      modalEl.modal("show");
+      modalEl.modal('show');
     });
   }
 }
 
-coreModule.service("utilSrv", UtilSrv);
+coreModule.service('utilSrv', UtilSrv);

+ 9 - 15
public/app/core/specs/backend_srv_specs.ts

@@ -1,18 +1,12 @@
-import {
-  describe,
-  beforeEach,
-  it,
-  expect,
-  angularMocks
-} from "test/lib/common";
-import "app/core/services/backend_srv";
+import { describe, beforeEach, it, expect, angularMocks } from 'test/lib/common';
+import 'app/core/services/backend_srv';
 
-describe("backend_srv", function() {
+describe('backend_srv', function() {
   var _backendSrv;
   var _httpBackend;
 
-  beforeEach(angularMocks.module("grafana.core"));
-  beforeEach(angularMocks.module("grafana.services"));
+  beforeEach(angularMocks.module('grafana.core'));
+  beforeEach(angularMocks.module('grafana.services'));
   beforeEach(
     angularMocks.inject(function($httpBackend, $http, backendSrv) {
       _httpBackend = $httpBackend;
@@ -20,12 +14,12 @@ describe("backend_srv", function() {
     })
   );
 
-  describe("when handling errors", function() {
-    it("should return the http status code", function(done) {
-      _httpBackend.whenGET("gateway-error").respond(502);
+  describe('when handling errors', function() {
+    it('should return the http status code', function(done) {
+      _httpBackend.whenGET('gateway-error').respond(502);
       _backendSrv
         .datasourceRequest({
-          url: "gateway-error"
+          url: 'gateway-error',
         })
         .catch(function(err) {
           expect(err.status).to.be(502);

+ 51 - 72
public/app/core/specs/datemath.jest.ts

@@ -1,73 +1,60 @@
-import sinon from "sinon";
+import sinon from 'sinon';
 
-import * as dateMath from "app/core/utils/datemath";
-import moment from "moment";
-import _ from "lodash";
+import * as dateMath from 'app/core/utils/datemath';
+import moment from 'moment';
+import _ from 'lodash';
 
-describe("DateMath", () => {
-  var spans = ["s", "m", "h", "d", "w", "M", "y"];
-  var anchor = "2014-01-01T06:06:06.666Z";
+describe('DateMath', () => {
+  var spans = ['s', 'm', 'h', 'd', 'w', 'M', 'y'];
+  var anchor = '2014-01-01T06:06:06.666Z';
   var unix = moment(anchor).valueOf();
-  var format = "YYYY-MM-DDTHH:mm:ss.SSSZ";
+  var format = 'YYYY-MM-DDTHH:mm:ss.SSSZ';
   var clock;
 
-  describe("errors", () => {
-    it("should return undefined if passed something falsy", () => {
+  describe('errors', () => {
+    it('should return undefined if passed something falsy', () => {
       expect(dateMath.parse(false)).toBe(undefined);
     });
 
-    it("should return undefined if I pass an operator besides [+-/]", () => {
-      expect(dateMath.parse("now&1d")).toBe(undefined);
+    it('should return undefined if I pass an operator besides [+-/]', () => {
+      expect(dateMath.parse('now&1d')).toBe(undefined);
     });
 
-    it(
-      "should return undefined if I pass a unit besides" + spans.toString(),
-      () => {
-        expect(dateMath.parse("now+5f")).toBe(undefined);
-      }
-    );
+    it('should return undefined if I pass a unit besides' + spans.toString(), () => {
+      expect(dateMath.parse('now+5f')).toBe(undefined);
+    });
 
-    it("should return undefined if rounding unit is not 1", () => {
-      expect(dateMath.parse("now/2y")).toBe(undefined);
-      expect(dateMath.parse("now/0.5y")).toBe(undefined);
+    it('should return undefined if rounding unit is not 1', () => {
+      expect(dateMath.parse('now/2y')).toBe(undefined);
+      expect(dateMath.parse('now/0.5y')).toBe(undefined);
     });
 
-    it("should not go into an infinite loop when missing a unit", () => {
-      expect(dateMath.parse("now-0")).toBe(undefined);
-      expect(dateMath.parse("now-00")).toBe(undefined);
+    it('should not go into an infinite loop when missing a unit', () => {
+      expect(dateMath.parse('now-0')).toBe(undefined);
+      expect(dateMath.parse('now-00')).toBe(undefined);
     });
   });
 
-  it("now/d should set to start of current day", () => {
+  it('now/d should set to start of current day', () => {
     var expected = new Date();
     expected.setHours(0);
     expected.setMinutes(0);
     expected.setSeconds(0);
     expected.setMilliseconds(0);
 
-    var startOfDay = dateMath.parse("now/d", false).valueOf();
+    var startOfDay = dateMath.parse('now/d', false).valueOf();
     expect(startOfDay).toBe(expected.getTime());
   });
 
-  it("now/d on a utc dashboard should be start of the current day in UTC time", () => {
+  it('now/d on a utc dashboard should be start of the current day in UTC time', () => {
     var today = new Date();
-    var expected = new Date(
-      Date.UTC(
-        today.getUTCFullYear(),
-        today.getUTCMonth(),
-        today.getUTCDate(),
-        0,
-        0,
-        0,
-        0
-      )
-    );
-
-    var startOfDay = dateMath.parse("now/d", false, "utc").valueOf();
+    var expected = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate(), 0, 0, 0, 0));
+
+    var startOfDay = dateMath.parse('now/d', false, 'utc').valueOf();
     expect(startOfDay).toBe(expected.getTime());
   });
 
-  describe("subtraction", () => {
+  describe('subtraction', () => {
     var now;
     var anchored;
 
@@ -78,19 +65,15 @@ describe("DateMath", () => {
     });
 
     _.each(spans, span => {
-      var nowEx = "now-5" + span;
-      var thenEx = anchor + "||-5" + span;
+      var nowEx = 'now-5' + span;
+      var thenEx = anchor + '||-5' + span;
 
-      it("should return 5" + span + " ago", () => {
-        expect(dateMath.parse(nowEx).format(format)).toEqual(
-          now.subtract(5, span).format(format)
-        );
+      it('should return 5' + span + ' ago', () => {
+        expect(dateMath.parse(nowEx).format(format)).toEqual(now.subtract(5, span).format(format));
       });
 
-      it("should return 5" + span + " before " + anchor, () => {
-        expect(dateMath.parse(thenEx).format(format)).toEqual(
-          anchored.subtract(5, span).format(format)
-        );
+      it('should return 5' + span + ' before ' + anchor, () => {
+        expect(dateMath.parse(thenEx).format(format)).toEqual(anchored.subtract(5, span).format(format));
       });
     });
 
@@ -99,7 +82,7 @@ describe("DateMath", () => {
     });
   });
 
-  describe("rounding", () => {
+  describe('rounding', () => {
     var now;
 
     beforeEach(() => {
@@ -108,16 +91,12 @@ describe("DateMath", () => {
     });
 
     _.each(spans, span => {
-      it("should round now to the beginning of the " + span, function() {
-        expect(dateMath.parse("now/" + span).format(format)).toEqual(
-          now.startOf(span).format(format)
-        );
+      it('should round now to the beginning of the ' + span, function() {
+        expect(dateMath.parse('now/' + span).format(format)).toEqual(now.startOf(span).format(format));
       });
 
-      it("should round now to the end of the " + span, function() {
-        expect(dateMath.parse("now/" + span, true).format(format)).toEqual(
-          now.endOf(span).format(format)
-        );
+      it('should round now to the end of the ' + span, function() {
+        expect(dateMath.parse('now/' + span, true).format(format)).toEqual(now.endOf(span).format(format));
       });
     });
 
@@ -126,28 +105,28 @@ describe("DateMath", () => {
     });
   });
 
-  describe("isValid", () => {
-    it("should return false when invalid date text", () => {
-      expect(dateMath.isValid("asd")).toBe(false);
+  describe('isValid', () => {
+    it('should return false when invalid date text', () => {
+      expect(dateMath.isValid('asd')).toBe(false);
     });
-    it("should return true when valid date text", () => {
-      expect(dateMath.isValid("now-1h")).toBe(true);
+    it('should return true when valid date text', () => {
+      expect(dateMath.isValid('now-1h')).toBe(true);
     });
   });
 
-  describe("relative time to date parsing", function() {
-    it("should handle negative time", function() {
-      var date = dateMath.parseDateMath("-2d", moment([2014, 1, 5]));
+  describe('relative time to date parsing', function() {
+    it('should handle negative time', function() {
+      var date = dateMath.parseDateMath('-2d', moment([2014, 1, 5]));
       expect(date.valueOf()).toEqual(moment([2014, 1, 3]).valueOf());
     });
 
-    it("should handle multiple math expressions", function() {
-      var date = dateMath.parseDateMath("-2d-6h", moment([2014, 1, 5]));
+    it('should handle multiple math expressions', function() {
+      var date = dateMath.parseDateMath('-2d-6h', moment([2014, 1, 5]));
       expect(date.valueOf()).toEqual(moment([2014, 1, 2, 18]).valueOf());
     });
 
-    it("should return false when invalid expression", function() {
-      var date = dateMath.parseDateMath("2", moment([2014, 1, 5]));
+    it('should return false when invalid expression', function() {
+      var date = dateMath.parseDateMath('2', moment([2014, 1, 5]));
       expect(date).toEqual(undefined);
     });
   });

+ 17 - 17
public/app/core/specs/emitter.jest.ts

@@ -1,26 +1,26 @@
-import { Emitter } from "../utils/emitter";
+import { Emitter } from '../utils/emitter';
 
-describe("Emitter", () => {
-  describe("given 2 subscribers", () => {
-    it("should notfiy subscribers", () => {
+describe('Emitter', () => {
+  describe('given 2 subscribers', () => {
+    it('should notfiy subscribers', () => {
       var events = new Emitter();
       var sub1Called = false;
       var sub2Called = false;
 
-      events.on("test", () => {
+      events.on('test', () => {
         sub1Called = true;
       });
-      events.on("test", () => {
+      events.on('test', () => {
         sub2Called = true;
       });
 
-      events.emit("test", null);
+      events.emit('test', null);
 
       expect(sub1Called).toBe(true);
       expect(sub2Called).toBe(true);
     });
 
-    it("when subscribing twice", () => {
+    it('when subscribing twice', () => {
       var events = new Emitter();
       var sub1Called = 0;
 
@@ -28,33 +28,33 @@ describe("Emitter", () => {
         sub1Called += 1;
       }
 
-      events.on("test", handler);
-      events.on("test", handler);
+      events.on('test', handler);
+      events.on('test', handler);
 
-      events.emit("test", null);
+      events.emit('test', null);
 
       expect(sub1Called).toBe(2);
     });
 
-    it("should handle errors", () => {
+    it('should handle errors', () => {
       var events = new Emitter();
       var sub1Called = 0;
       var sub2Called = 0;
 
-      events.on("test", () => {
+      events.on('test', () => {
         sub1Called++;
-        throw { message: "hello" };
+        throw { message: 'hello' };
       });
 
-      events.on("test", () => {
+      events.on('test', () => {
         sub2Called++;
       });
 
       try {
-        events.emit("test", null);
+        events.emit('test', null);
       } catch (_) {}
       try {
-        events.emit("test", null);
+        events.emit('test', null);
       } catch (_) {}
 
       expect(sub1Called).toBe(2);

+ 11 - 11
public/app/core/specs/flatten.jest.ts

@@ -1,22 +1,22 @@
-import flatten from "app/core/utils/flatten";
+import flatten from 'app/core/utils/flatten';
 
-describe("flatten", () => {
-  it("should return flatten object", () => {
+describe('flatten', () => {
+  it('should return flatten object', () => {
     var flattened = flatten(
       {
-        level1: "level1-value",
+        level1: 'level1-value',
         deeper: {
-          level2: "level2-value",
+          level2: 'level2-value',
           deeper: {
-            level3: "level3-value"
-          }
-        }
+            level3: 'level3-value',
+          },
+        },
       },
       null
     );
 
-    expect(flattened["level1"]).toBe("level1-value");
-    expect(flattened["deeper.level2"]).toBe("level2-value");
-    expect(flattened["deeper.deeper.level3"]).toBe("level3-value");
+    expect(flattened['level1']).toBe('level1-value');
+    expect(flattened['deeper.level2']).toBe('level2-value');
+    expect(flattened['deeper.deeper.level3']).toBe('level3-value');
   });
 });

+ 10 - 10
public/app/core/specs/global_event_srv.jest.ts

@@ -1,23 +1,23 @@
-import { GlobalEventSrv } from "app/core/services/global_event_srv";
-import { beforeEach } from "test/lib/common";
+import { GlobalEventSrv } from 'app/core/services/global_event_srv';
+import { beforeEach } from 'test/lib/common';
 
-jest.mock("app/core/config", () => {
+jest.mock('app/core/config', () => {
   return {
-    appSubUrl: "/subUrl"
+    appSubUrl: '/subUrl',
   };
 });
 
-describe("GlobalEventSrv", () => {
+describe('GlobalEventSrv', () => {
   let searchSrv;
 
   beforeEach(() => {
-    searchSrv = new GlobalEventSrv(null, null);
+    searchSrv = new GlobalEventSrv(null, null, null);
   });
 
-  describe("With /subUrl as appSubUrl", () => {
-    it("/subUrl should be stripped", () => {
-      const urlWithoutMaster = searchSrv.stripBaseFromUrl("/subUrl/grafana/");
-      expect(urlWithoutMaster).toBe("/grafana/");
+  describe('With /subUrl as appSubUrl', () => {
+    it('/subUrl should be stripped', () => {
+      const urlWithoutMaster = searchSrv.stripBaseFromUrl('/subUrl/grafana/');
+      expect(urlWithoutMaster).toBe('/grafana/');
     });
   });
 });

+ 230 - 235
public/app/core/specs/kbn.jest.ts

@@ -1,29 +1,29 @@
-import kbn from "../utils/kbn";
-import * as dateMath from "../utils/datemath";
-import moment from "moment";
+import kbn from '../utils/kbn';
+import * as dateMath from '../utils/datemath';
+import moment from 'moment';
 
-describe("unit format menu", function() {
+describe('unit format menu', function() {
   var menu = kbn.getUnitFormats();
   menu.map(function(submenu) {
-    describe("submenu " + submenu.text, function() {
-      it("should have a title", function() {
-        expect(typeof submenu.text).toBe("string");
+    describe('submenu ' + submenu.text, function() {
+      it('should have a title', function() {
+        expect(typeof submenu.text).toBe('string');
       });
 
-      it("should have a submenu", function() {
+      it('should have a submenu', function() {
         expect(Array.isArray(submenu.submenu)).toBe(true);
       });
 
       submenu.submenu.map(function(entry) {
-        describe("entry " + entry.text, function() {
-          it("should have a title", function() {
-            expect(typeof entry.text).toBe("string");
+        describe('entry ' + entry.text, function() {
+          it('should have a title', function() {
+            expect(typeof entry.text).toBe('string');
           });
-          it("should have a format", function() {
-            expect(typeof entry.value).toBe("string");
+          it('should have a format', function() {
+            expect(typeof entry.value).toBe('string');
           });
-          it("should have a valid format", function() {
-            expect(typeof kbn.valueFormats[entry.value]).toBe("function");
+          it('should have a valid format', function() {
+            expect(typeof kbn.valueFormats[entry.value]).toBe('function');
           });
         });
       });
@@ -32,324 +32,319 @@ describe("unit format menu", function() {
 });
 
 function describeValueFormat(desc, value, tickSize, tickDecimals, result) {
-  describe("value format: " + desc, function() {
-    it("should translate " + value + " as " + result, function() {
-      var scaledDecimals =
-        tickDecimals - Math.floor(Math.log(tickSize) / Math.LN10);
+  describe('value format: ' + desc, function() {
+    it('should translate ' + value + ' as ' + result, function() {
+      var scaledDecimals = tickDecimals - Math.floor(Math.log(tickSize) / Math.LN10);
       var str = kbn.valueFormats[desc](value, tickDecimals, scaledDecimals);
       expect(str).toBe(result);
     });
   });
 }
 
-describeValueFormat("ms", 0.0024, 0.0005, 4, "0.0024 ms");
-describeValueFormat("ms", 100, 1, 0, "100 ms");
-describeValueFormat("ms", 1250, 10, 0, "1.25 s");
-describeValueFormat("ms", 1250, 300, 0, "1.3 s");
-describeValueFormat("ms", 65150, 10000, 0, "1.1 min");
-describeValueFormat("ms", 6515000, 1500000, 0, "1.8 hour");
-describeValueFormat("ms", 651500000, 150000000, 0, "8 day");
-
-describeValueFormat("none", 2.75e-10, 0, 10, "3e-10");
-describeValueFormat("none", 0, 0, 2, "0");
-describeValueFormat("dB", 10, 1000, 2, "10.00 dB");
-
-describeValueFormat("percent", 0, 0, 0, "0%");
-describeValueFormat("percent", 53, 0, 1, "53.0%");
-describeValueFormat("percentunit", 0.0, 0, 0, "0%");
-describeValueFormat("percentunit", 0.278, 0, 1, "27.8%");
-describeValueFormat("percentunit", 1.0, 0, 0, "100%");
-
-describeValueFormat("currencyUSD", 7.42, 10000, 2, "$7.42");
-describeValueFormat("currencyUSD", 1532.82, 1000, 1, "$1.53K");
-describeValueFormat("currencyUSD", 18520408.7, 10000000, 0, "$19M");
-
-describeValueFormat("bytes", -1.57e308, -1.57e308, 2, "NA");
-
-describeValueFormat("ns", 25, 1, 0, "25 ns");
-describeValueFormat("ns", 2558, 50, 0, "2.56 µs");
-
-describeValueFormat("ops", 123, 1, 0, "123 ops");
-describeValueFormat("rps", 456000, 1000, -1, "456K rps");
-describeValueFormat("rps", 123456789, 1000000, 2, "123.457M rps");
-describeValueFormat("wps", 789000000, 1000000, -1, "789M wps");
-describeValueFormat("iops", 11000000000, 1000000000, -1, "11B iops");
-
-describeValueFormat("s", 1.23456789e-7, 1e-10, 8, "123.5 ns");
-describeValueFormat("s", 1.23456789e-4, 1e-7, 5, "123.5 µs");
-describeValueFormat("s", 1.23456789e-3, 1e-6, 4, "1.235 ms");
-describeValueFormat("s", 1.23456789e-2, 1e-5, 3, "12.35 ms");
-describeValueFormat("s", 1.23456789e-1, 1e-4, 2, "123.5 ms");
-describeValueFormat("s", 24, 1, 0, "24 s");
-describeValueFormat("s", 246, 1, 0, "4.1 min");
-describeValueFormat("s", 24567, 100, 0, "6.82 hour");
-describeValueFormat("s", 24567890, 10000, 0, "40.62 week");
-describeValueFormat("s", 24567890000, 1000000, 0, "778.53 year");
-
-describeValueFormat("m", 24, 1, 0, "24 min");
-describeValueFormat("m", 246, 10, 0, "4.1 hour");
-describeValueFormat("m", 6545, 10, 0, "4.55 day");
-describeValueFormat("m", 24567, 100, 0, "2.44 week");
-describeValueFormat("m", 24567892, 10000, 0, "46.7 year");
-
-describeValueFormat("h", 21, 1, 0, "21 hour");
-describeValueFormat("h", 145, 1, 0, "6.04 day");
-describeValueFormat("h", 1234, 100, 0, "7.3 week");
-describeValueFormat("h", 9458, 1000, 0, "1.08 year");
-
-describeValueFormat("d", 3, 1, 0, "3 day");
-describeValueFormat("d", 245, 100, 0, "35 week");
-describeValueFormat("d", 2456, 10, 0, "6.73 year");
-
-describe("date time formats", function() {
-  it("should format as iso date", function() {
+describeValueFormat('ms', 0.0024, 0.0005, 4, '0.0024 ms');
+describeValueFormat('ms', 100, 1, 0, '100 ms');
+describeValueFormat('ms', 1250, 10, 0, '1.25 s');
+describeValueFormat('ms', 1250, 300, 0, '1.3 s');
+describeValueFormat('ms', 65150, 10000, 0, '1.1 min');
+describeValueFormat('ms', 6515000, 1500000, 0, '1.8 hour');
+describeValueFormat('ms', 651500000, 150000000, 0, '8 day');
+
+describeValueFormat('none', 2.75e-10, 0, 10, '3e-10');
+describeValueFormat('none', 0, 0, 2, '0');
+describeValueFormat('dB', 10, 1000, 2, '10.00 dB');
+
+describeValueFormat('percent', 0, 0, 0, '0%');
+describeValueFormat('percent', 53, 0, 1, '53.0%');
+describeValueFormat('percentunit', 0.0, 0, 0, '0%');
+describeValueFormat('percentunit', 0.278, 0, 1, '27.8%');
+describeValueFormat('percentunit', 1.0, 0, 0, '100%');
+
+describeValueFormat('currencyUSD', 7.42, 10000, 2, '$7.42');
+describeValueFormat('currencyUSD', 1532.82, 1000, 1, '$1.53K');
+describeValueFormat('currencyUSD', 18520408.7, 10000000, 0, '$19M');
+
+describeValueFormat('bytes', -1.57e308, -1.57e308, 2, 'NA');
+
+describeValueFormat('ns', 25, 1, 0, '25 ns');
+describeValueFormat('ns', 2558, 50, 0, '2.56 µs');
+
+describeValueFormat('ops', 123, 1, 0, '123 ops');
+describeValueFormat('rps', 456000, 1000, -1, '456K rps');
+describeValueFormat('rps', 123456789, 1000000, 2, '123.457M rps');
+describeValueFormat('wps', 789000000, 1000000, -1, '789M wps');
+describeValueFormat('iops', 11000000000, 1000000000, -1, '11B iops');
+
+describeValueFormat('s', 1.23456789e-7, 1e-10, 8, '123.5 ns');
+describeValueFormat('s', 1.23456789e-4, 1e-7, 5, '123.5 µs');
+describeValueFormat('s', 1.23456789e-3, 1e-6, 4, '1.235 ms');
+describeValueFormat('s', 1.23456789e-2, 1e-5, 3, '12.35 ms');
+describeValueFormat('s', 1.23456789e-1, 1e-4, 2, '123.5 ms');
+describeValueFormat('s', 24, 1, 0, '24 s');
+describeValueFormat('s', 246, 1, 0, '4.1 min');
+describeValueFormat('s', 24567, 100, 0, '6.82 hour');
+describeValueFormat('s', 24567890, 10000, 0, '40.62 week');
+describeValueFormat('s', 24567890000, 1000000, 0, '778.53 year');
+
+describeValueFormat('m', 24, 1, 0, '24 min');
+describeValueFormat('m', 246, 10, 0, '4.1 hour');
+describeValueFormat('m', 6545, 10, 0, '4.55 day');
+describeValueFormat('m', 24567, 100, 0, '2.44 week');
+describeValueFormat('m', 24567892, 10000, 0, '46.7 year');
+
+describeValueFormat('h', 21, 1, 0, '21 hour');
+describeValueFormat('h', 145, 1, 0, '6.04 day');
+describeValueFormat('h', 1234, 100, 0, '7.3 week');
+describeValueFormat('h', 9458, 1000, 0, '1.08 year');
+
+describeValueFormat('d', 3, 1, 0, '3 day');
+describeValueFormat('d', 245, 100, 0, '35 week');
+describeValueFormat('d', 2456, 10, 0, '6.73 year');
+
+describe('date time formats', function() {
+  it('should format as iso date', function() {
     var str = kbn.valueFormats.dateTimeAsIso(1505634997920, 1);
-    expect(str).toBe(moment(1505634997920).format("YYYY-MM-DD HH:mm:ss"));
+    expect(str).toBe(moment(1505634997920).format('YYYY-MM-DD HH:mm:ss'));
   });
 
-  it("should format as iso date and skip date when today", function() {
+  it('should format as iso date and skip date when today', function() {
     var now = moment();
     var str = kbn.valueFormats.dateTimeAsIso(now.valueOf(), 1);
-    expect(str).toBe(now.format("HH:mm:ss"));
+    expect(str).toBe(now.format('HH:mm:ss'));
   });
 
-  it("should format as US date", function() {
+  it('should format as US date', function() {
     var str = kbn.valueFormats.dateTimeAsUS(1505634997920, 1);
-    expect(str).toBe(moment(1505634997920).format("MM/DD/YYYY h:mm:ss a"));
+    expect(str).toBe(moment(1505634997920).format('MM/DD/YYYY h:mm:ss a'));
   });
 
-  it("should format as US date and skip date when today", function() {
+  it('should format as US date and skip date when today', function() {
     var now = moment();
     var str = kbn.valueFormats.dateTimeAsUS(now.valueOf(), 1);
-    expect(str).toBe(now.format("h:mm:ss a"));
+    expect(str).toBe(now.format('h:mm:ss a'));
   });
 
-  it("should format as from now with days", function() {
-    var daysAgo = moment().add(-7, "d");
+  it('should format as from now with days', function() {
+    var daysAgo = moment().add(-7, 'd');
     var str = kbn.valueFormats.dateTimeFromNow(daysAgo.valueOf(), 1);
-    expect(str).toBe("7 days ago");
+    expect(str).toBe('7 days ago');
   });
 
-  it("should format as from now with minutes", function() {
-    var daysAgo = moment().add(-2, "m");
+  it('should format as from now with minutes', function() {
+    var daysAgo = moment().add(-2, 'm');
     var str = kbn.valueFormats.dateTimeFromNow(daysAgo.valueOf(), 1);
-    expect(str).toBe("2 minutes ago");
+    expect(str).toBe('2 minutes ago');
   });
 });
 
-describe("kbn.toFixed and negative decimals", function() {
-  it("should treat as zero decimals", function() {
+describe('kbn.toFixed and negative decimals', function() {
+  it('should treat as zero decimals', function() {
     var str = kbn.toFixed(186.123, -2);
-    expect(str).toBe("186");
+    expect(str).toBe('186');
   });
 });
 
-describe("kbn ms format when scaled decimals is null do not use it", function() {
-  it("should use specified decimals", function() {
-    var str = kbn.valueFormats["ms"](10000086.123, 1, null);
-    expect(str).toBe("2.8 hour");
+describe('kbn ms format when scaled decimals is null do not use it', function() {
+  it('should use specified decimals', function() {
+    var str = kbn.valueFormats['ms'](10000086.123, 1, null);
+    expect(str).toBe('2.8 hour');
   });
 });
 
-describe("kbn kbytes format when scaled decimals is null do not use it", function() {
-  it("should use specified decimals", function() {
-    var str = kbn.valueFormats["kbytes"](10000000, 3, null);
-    expect(str).toBe("9.537 GiB");
+describe('kbn kbytes format when scaled decimals is null do not use it', function() {
+  it('should use specified decimals', function() {
+    var str = kbn.valueFormats['kbytes'](10000000, 3, null);
+    expect(str).toBe('9.537 GiB');
   });
 });
 
-describe("kbn deckbytes format when scaled decimals is null do not use it", function() {
-  it("should use specified decimals", function() {
-    var str = kbn.valueFormats["deckbytes"](10000000, 3, null);
-    expect(str).toBe("10.000 GB");
+describe('kbn deckbytes format when scaled decimals is null do not use it', function() {
+  it('should use specified decimals', function() {
+    var str = kbn.valueFormats['deckbytes'](10000000, 3, null);
+    expect(str).toBe('10.000 GB');
   });
 });
 
-describe("kbn roundValue", function() {
-  it("should should handle null value", function() {
+describe('kbn roundValue', function() {
+  it('should should handle null value', function() {
     var str = kbn.roundValue(null, 2);
     expect(str).toBe(null);
   });
-  it("should round value", function() {
+  it('should round value', function() {
     var str = kbn.roundValue(200.877, 2);
     expect(str).toBe(200.88);
   });
 });
 
-describe("calculateInterval", function() {
-  it("1h 100 resultion", function() {
-    var range = { from: dateMath.parse("now-1h"), to: dateMath.parse("now") };
+describe('calculateInterval', function() {
+  it('1h 100 resultion', function() {
+    var range = { from: dateMath.parse('now-1h'), to: dateMath.parse('now') };
     var res = kbn.calculateInterval(range, 100, null);
-    expect(res.interval).toBe("30s");
+    expect(res.interval).toBe('30s');
   });
 
-  it("10m 1600 resolution", function() {
-    var range = { from: dateMath.parse("now-10m"), to: dateMath.parse("now") };
+  it('10m 1600 resolution', function() {
+    var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
     var res = kbn.calculateInterval(range, 1600, null);
-    expect(res.interval).toBe("500ms");
+    expect(res.interval).toBe('500ms');
     expect(res.intervalMs).toBe(500);
   });
 
-  it("fixed user min interval", function() {
-    var range = { from: dateMath.parse("now-10m"), to: dateMath.parse("now") };
-    var res = kbn.calculateInterval(range, 1600, "10s");
-    expect(res.interval).toBe("10s");
+  it('fixed user min interval', function() {
+    var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
+    var res = kbn.calculateInterval(range, 1600, '10s');
+    expect(res.interval).toBe('10s');
     expect(res.intervalMs).toBe(10000);
   });
 
-  it("short time range and user low limit", function() {
-    var range = { from: dateMath.parse("now-10m"), to: dateMath.parse("now") };
-    var res = kbn.calculateInterval(range, 1600, ">10s");
-    expect(res.interval).toBe("10s");
+  it('short time range and user low limit', function() {
+    var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
+    var res = kbn.calculateInterval(range, 1600, '>10s');
+    expect(res.interval).toBe('10s');
   });
 
-  it("large time range and user low limit", function() {
-    var range = { from: dateMath.parse("now-14d"), to: dateMath.parse("now") };
-    var res = kbn.calculateInterval(range, 1000, ">10s");
-    expect(res.interval).toBe("20m");
+  it('large time range and user low limit', function() {
+    var range = { from: dateMath.parse('now-14d'), to: dateMath.parse('now') };
+    var res = kbn.calculateInterval(range, 1000, '>10s');
+    expect(res.interval).toBe('20m');
   });
 
-  it("10s 900 resolution and user low limit in ms", function() {
-    var range = { from: dateMath.parse("now-10s"), to: dateMath.parse("now") };
-    var res = kbn.calculateInterval(range, 900, ">15ms");
-    expect(res.interval).toBe("15ms");
+  it('10s 900 resolution and user low limit in ms', function() {
+    var range = { from: dateMath.parse('now-10s'), to: dateMath.parse('now') };
+    var res = kbn.calculateInterval(range, 900, '>15ms');
+    expect(res.interval).toBe('15ms');
   });
 
-  it("1d 1 resolution", function() {
-    var range = { from: dateMath.parse("now-1d"), to: dateMath.parse("now") };
+  it('1d 1 resolution', function() {
+    var range = { from: dateMath.parse('now-1d'), to: dateMath.parse('now') };
     var res = kbn.calculateInterval(range, 1, null);
-    expect(res.interval).toBe("1d");
+    expect(res.interval).toBe('1d');
     expect(res.intervalMs).toBe(86400000);
   });
 
-  it("86399s 1 resolution", function() {
+  it('86399s 1 resolution', function() {
     var range = {
-      from: dateMath.parse("now-86390s"),
-      to: dateMath.parse("now")
+      from: dateMath.parse('now-86390s'),
+      to: dateMath.parse('now'),
     };
     var res = kbn.calculateInterval(range, 1, null);
-    expect(res.interval).toBe("12h");
+    expect(res.interval).toBe('12h');
     expect(res.intervalMs).toBe(43200000);
   });
 });
 
-describe("hex", function() {
-  it("positive integer", function() {
+describe('hex', function() {
+  it('positive integer', function() {
     var str = kbn.valueFormats.hex(100, 0);
-    expect(str).toBe("64");
+    expect(str).toBe('64');
   });
-  it("negative integer", function() {
+  it('negative integer', function() {
     var str = kbn.valueFormats.hex(-100, 0);
-    expect(str).toBe("-64");
+    expect(str).toBe('-64');
   });
-  it("null", function() {
+  it('null', function() {
     var str = kbn.valueFormats.hex(null, 0);
-    expect(str).toBe("");
+    expect(str).toBe('');
   });
-  it("positive float", function() {
+  it('positive float', function() {
     var str = kbn.valueFormats.hex(50.52, 1);
-    expect(str).toBe("32.8");
+    expect(str).toBe('32.8');
   });
-  it("negative float", function() {
+  it('negative float', function() {
     var str = kbn.valueFormats.hex(-50.333, 2);
-    expect(str).toBe("-32.547AE147AE14");
+    expect(str).toBe('-32.547AE147AE14');
   });
 });
 
-describe("hex 0x", function() {
-  it("positive integeter", function() {
+describe('hex 0x', function() {
+  it('positive integeter', function() {
     var str = kbn.valueFormats.hex0x(7999, 0);
-    expect(str).toBe("0x1F3F");
+    expect(str).toBe('0x1F3F');
   });
-  it("negative integer", function() {
+  it('negative integer', function() {
     var str = kbn.valueFormats.hex0x(-584, 0);
-    expect(str).toBe("-0x248");
+    expect(str).toBe('-0x248');
   });
-  it("null", function() {
+  it('null', function() {
     var str = kbn.valueFormats.hex0x(null, 0);
-    expect(str).toBe("");
+    expect(str).toBe('');
   });
-  it("positive float", function() {
+  it('positive float', function() {
     var str = kbn.valueFormats.hex0x(74.443, 3);
-    expect(str).toBe("0x4A.716872B020C4");
+    expect(str).toBe('0x4A.716872B020C4');
   });
-  it("negative float", function() {
+  it('negative float', function() {
     var str = kbn.valueFormats.hex0x(-65.458, 1);
-    expect(str).toBe("-0x41.8");
+    expect(str).toBe('-0x41.8');
   });
 });
 
-describe("duration", function() {
-  it("null", function() {
-    var str = kbn.toDuration(null, 0, "millisecond");
-    expect(str).toBe("");
-  });
-  it("0 milliseconds", function() {
-    var str = kbn.toDuration(0, 0, "millisecond");
-    expect(str).toBe("0 milliseconds");
-  });
-  it("1 millisecond", function() {
-    var str = kbn.toDuration(1, 0, "millisecond");
-    expect(str).toBe("1 millisecond");
-  });
-  it("-1 millisecond", function() {
-    var str = kbn.toDuration(-1, 0, "millisecond");
-    expect(str).toBe("1 millisecond ago");
-  });
-  it("seconds", function() {
-    var str = kbn.toDuration(1, 0, "second");
-    expect(str).toBe("1 second");
-  });
-  it("minutes", function() {
-    var str = kbn.toDuration(1, 0, "minute");
-    expect(str).toBe("1 minute");
-  });
-  it("hours", function() {
-    var str = kbn.toDuration(1, 0, "hour");
-    expect(str).toBe("1 hour");
-  });
-  it("days", function() {
-    var str = kbn.toDuration(1, 0, "day");
-    expect(str).toBe("1 day");
-  });
-  it("weeks", function() {
-    var str = kbn.toDuration(1, 0, "week");
-    expect(str).toBe("1 week");
-  });
-  it("months", function() {
-    var str = kbn.toDuration(1, 0, "month");
-    expect(str).toBe("1 month");
-  });
-  it("years", function() {
-    var str = kbn.toDuration(1, 0, "year");
-    expect(str).toBe("1 year");
-  });
-  it("decimal days", function() {
-    var str = kbn.toDuration(1.5, 2, "day");
-    expect(str).toBe("1 day, 12 hours, 0 minutes");
-  });
-  it("decimal months", function() {
-    var str = kbn.toDuration(1.5, 3, "month");
-    expect(str).toBe("1 month, 2 weeks, 1 day, 0 hours");
-  });
-  it("no decimals", function() {
-    var str = kbn.toDuration(38898367008, 0, "millisecond");
-    expect(str).toBe("1 year");
-  });
-  it("1 decimal", function() {
-    var str = kbn.toDuration(38898367008, 1, "millisecond");
-    expect(str).toBe("1 year, 2 months");
-  });
-  it("too many decimals", function() {
-    var str = kbn.toDuration(38898367008, 20, "millisecond");
-    expect(str).toBe(
-      "1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, 7 seconds, 8 milliseconds"
-    );
-  });
-  it("floating point error", function() {
-    var str = kbn.toDuration(36993906007, 8, "millisecond");
-    expect(str).toBe(
-      "1 year, 2 months, 0 weeks, 3 days, 4 hours, 5 minutes, 6 seconds, 7 milliseconds"
-    );
+describe('duration', function() {
+  it('null', function() {
+    var str = kbn.toDuration(null, 0, 'millisecond');
+    expect(str).toBe('');
+  });
+  it('0 milliseconds', function() {
+    var str = kbn.toDuration(0, 0, 'millisecond');
+    expect(str).toBe('0 milliseconds');
+  });
+  it('1 millisecond', function() {
+    var str = kbn.toDuration(1, 0, 'millisecond');
+    expect(str).toBe('1 millisecond');
+  });
+  it('-1 millisecond', function() {
+    var str = kbn.toDuration(-1, 0, 'millisecond');
+    expect(str).toBe('1 millisecond ago');
+  });
+  it('seconds', function() {
+    var str = kbn.toDuration(1, 0, 'second');
+    expect(str).toBe('1 second');
+  });
+  it('minutes', function() {
+    var str = kbn.toDuration(1, 0, 'minute');
+    expect(str).toBe('1 minute');
+  });
+  it('hours', function() {
+    var str = kbn.toDuration(1, 0, 'hour');
+    expect(str).toBe('1 hour');
+  });
+  it('days', function() {
+    var str = kbn.toDuration(1, 0, 'day');
+    expect(str).toBe('1 day');
+  });
+  it('weeks', function() {
+    var str = kbn.toDuration(1, 0, 'week');
+    expect(str).toBe('1 week');
+  });
+  it('months', function() {
+    var str = kbn.toDuration(1, 0, 'month');
+    expect(str).toBe('1 month');
+  });
+  it('years', function() {
+    var str = kbn.toDuration(1, 0, 'year');
+    expect(str).toBe('1 year');
+  });
+  it('decimal days', function() {
+    var str = kbn.toDuration(1.5, 2, 'day');
+    expect(str).toBe('1 day, 12 hours, 0 minutes');
+  });
+  it('decimal months', function() {
+    var str = kbn.toDuration(1.5, 3, 'month');
+    expect(str).toBe('1 month, 2 weeks, 1 day, 0 hours');
+  });
+  it('no decimals', function() {
+    var str = kbn.toDuration(38898367008, 0, 'millisecond');
+    expect(str).toBe('1 year');
+  });
+  it('1 decimal', function() {
+    var str = kbn.toDuration(38898367008, 1, 'millisecond');
+    expect(str).toBe('1 year, 2 months');
+  });
+  it('too many decimals', function() {
+    var str = kbn.toDuration(38898367008, 20, 'millisecond');
+    expect(str).toBe('1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, 7 seconds, 8 milliseconds');
+  });
+  it('floating point error', function() {
+    var str = kbn.toDuration(36993906007, 8, 'millisecond');
+    expect(str).toBe('1 year, 2 months, 0 weeks, 3 days, 4 hours, 5 minutes, 6 seconds, 7 milliseconds');
   });
 });

+ 164 - 168
public/app/core/specs/manage_dashboards.jest.ts

@@ -1,58 +1,58 @@
-import { ManageDashboardsCtrl } from "app/core/components/manage_dashboards/manage_dashboards";
-import { SearchSrv } from "app/core/services/search_srv";
-import q from "q";
+import { ManageDashboardsCtrl } from 'app/core/components/manage_dashboards/manage_dashboards';
+import { SearchSrv } from 'app/core/services/search_srv';
+import q from 'q';
 
-describe("ManageDashboards", () => {
+describe('ManageDashboards', () => {
   let ctrl;
 
-  describe("when browsing dashboards", () => {
+  describe('when browsing dashboards', () => {
     beforeEach(() => {
       const response = [
         {
           id: 410,
-          title: "afolder",
-          type: "dash-folder",
+          title: 'afolder',
+          type: 'dash-folder',
           items: [
             {
               id: 399,
-              title: "Dashboard Test",
-              url: "dashboard/db/dashboard-test",
-              icon: "fa fa-folder",
+              title: 'Dashboard Test',
+              url: 'dashboard/db/dashboard-test',
+              icon: 'fa fa-folder',
               tags: [],
               isStarred: false,
               folderId: 410,
-              folderTitle: "afolder",
-              folderSlug: "afolder"
-            }
+              folderTitle: 'afolder',
+              folderSlug: 'afolder',
+            },
           ],
           tags: [],
-          isStarred: false
+          isStarred: false,
         },
         {
           id: 0,
-          title: "Root",
-          icon: "fa fa-folder-open",
-          uri: "db/something-else",
-          type: "dash-db",
+          title: 'Root',
+          icon: 'fa fa-folder-open',
+          uri: 'db/something-else',
+          type: 'dash-db',
           items: [
             {
               id: 500,
-              title: "Dashboard Test",
-              url: "dashboard/db/dashboard-test",
-              icon: "fa fa-folder",
+              title: 'Dashboard Test',
+              url: 'dashboard/db/dashboard-test',
+              icon: 'fa fa-folder',
               tags: [],
-              isStarred: false
-            }
+              isStarred: false,
+            },
           ],
           tags: [],
-          isStarred: false
-        }
+          isStarred: false,
+        },
       ];
       ctrl = createCtrlWithStubs(response);
       return ctrl.getDashboards();
     });
 
-    it("should set checked to false on all sections and children", () => {
+    it('should set checked to false on all sections and children', () => {
       expect(ctrl.sections.length).toEqual(2);
       expect(ctrl.sections[0].checked).toEqual(false);
       expect(ctrl.sections[0].items[0].checked).toEqual(false);
@@ -62,41 +62,41 @@ describe("ManageDashboards", () => {
     });
   });
 
-  describe("when browsing dashboards for a folder", () => {
+  describe('when browsing dashboards for a folder', () => {
     beforeEach(() => {
       const response = [
         {
           id: 410,
-          title: "afolder",
-          type: "dash-folder",
+          title: 'afolder',
+          type: 'dash-folder',
           items: [
             {
               id: 399,
-              title: "Dashboard Test",
-              url: "dashboard/db/dashboard-test",
-              icon: "fa fa-folder",
+              title: 'Dashboard Test',
+              url: 'dashboard/db/dashboard-test',
+              icon: 'fa fa-folder',
               tags: [],
               isStarred: false,
               folderId: 410,
-              folderTitle: "afolder",
-              folderSlug: "afolder"
-            }
+              folderTitle: 'afolder',
+              folderSlug: 'afolder',
+            },
           ],
           tags: [],
-          isStarred: false
-        }
+          isStarred: false,
+        },
       ];
       ctrl = createCtrlWithStubs(response);
       ctrl.folderId = 410;
       return ctrl.getDashboards();
     });
 
-    it("should set hide header to true on section", () => {
+    it('should set hide header to true on section', () => {
       expect(ctrl.sections[0].hideHeader).toBeTruthy();
     });
   });
 
-  describe("when searching dashboards", () => {
+  describe('when searching dashboards', () => {
     beforeEach(() => {
       const response = [
         {
@@ -106,121 +106,121 @@ describe("ManageDashboards", () => {
           items: [
             {
               id: 399,
-              title: "Dashboard Test",
-              url: "dashboard/db/dashboard-test",
-              icon: "fa fa-folder",
+              title: 'Dashboard Test',
+              url: 'dashboard/db/dashboard-test',
+              icon: 'fa fa-folder',
               tags: [],
               isStarred: false,
               folderId: 410,
-              folderTitle: "afolder",
-              folderSlug: "afolder"
+              folderTitle: 'afolder',
+              folderSlug: 'afolder',
             },
             {
               id: 500,
-              title: "Dashboard Test",
-              url: "dashboard/db/dashboard-test",
-              icon: "fa fa-folder",
+              title: 'Dashboard Test',
+              url: 'dashboard/db/dashboard-test',
+              icon: 'fa fa-folder',
               tags: [],
               folderId: 499,
-              isStarred: false
-            }
-          ]
-        }
+              isStarred: false,
+            },
+          ],
+        },
       ];
 
       ctrl = createCtrlWithStubs(response);
     });
 
-    describe("with query filter", () => {
+    describe('with query filter', () => {
       beforeEach(() => {
-        ctrl.query.query = "d";
+        ctrl.query.query = 'd';
         ctrl.canMove = true;
         ctrl.canDelete = true;
         ctrl.selectAllChecked = true;
         return ctrl.getDashboards();
       });
 
-      it("should set checked to false on all sections and children", () => {
+      it('should set checked to false on all sections and children', () => {
         expect(ctrl.sections.length).toEqual(1);
         expect(ctrl.sections[0].checked).toEqual(false);
         expect(ctrl.sections[0].items[0].checked).toEqual(false);
         expect(ctrl.sections[0].items[1].checked).toEqual(false);
       });
 
-      it("should uncheck select all", () => {
+      it('should uncheck select all', () => {
         expect(ctrl.selectAllChecked).toBeFalsy();
       });
 
-      it("should disable Move To button", () => {
+      it('should disable Move To button', () => {
         expect(ctrl.canMove).toBeFalsy();
       });
 
-      it("should disable delete button", () => {
+      it('should disable delete button', () => {
         expect(ctrl.canDelete).toBeFalsy();
       });
 
-      it("should have active filters", () => {
+      it('should have active filters', () => {
         expect(ctrl.hasFilters).toBeTruthy();
       });
 
-      describe("when select all is checked", () => {
+      describe('when select all is checked', () => {
         beforeEach(() => {
           ctrl.selectAllChecked = true;
           ctrl.onSelectAllChanged();
         });
 
-        it("should select all dashboards", () => {
+        it('should select all dashboards', () => {
           expect(ctrl.sections[0].checked).toBeFalsy();
           expect(ctrl.sections[0].items[0].checked).toBeTruthy();
           expect(ctrl.sections[0].items[1].checked).toBeTruthy();
         });
 
-        it("should enable Move To button", () => {
+        it('should enable Move To button', () => {
           expect(ctrl.canMove).toBeTruthy();
         });
 
-        it("should enable delete button", () => {
+        it('should enable delete button', () => {
           expect(ctrl.canDelete).toBeTruthy();
         });
 
-        describe("when clearing filters", () => {
+        describe('when clearing filters', () => {
           beforeEach(() => {
             return ctrl.clearFilters();
           });
 
-          it("should reset query filter", () => {
-            expect(ctrl.query.query).toEqual("");
+          it('should reset query filter', () => {
+            expect(ctrl.query.query).toEqual('');
           });
         });
       });
     });
 
-    describe("with tag filter", () => {
+    describe('with tag filter', () => {
       beforeEach(() => {
-        return ctrl.filterByTag("test");
+        return ctrl.filterByTag('test');
       });
 
-      it("should set tag filter", () => {
+      it('should set tag filter', () => {
         expect(ctrl.sections.length).toEqual(1);
-        expect(ctrl.query.tag[0]).toEqual("test");
+        expect(ctrl.query.tag[0]).toEqual('test');
       });
 
-      it("should have active filters", () => {
+      it('should have active filters', () => {
         expect(ctrl.hasFilters).toBeTruthy();
       });
 
-      describe("when clearing filters", () => {
+      describe('when clearing filters', () => {
         beforeEach(() => {
           return ctrl.clearFilters();
         });
 
-        it("should reset tag filter", () => {
+        it('should reset tag filter', () => {
           expect(ctrl.query.tag.length).toEqual(0);
         });
       });
     });
 
-    describe("with starred filter", () => {
+    describe('with starred filter', () => {
       beforeEach(() => {
         const yesOption: any = ctrl.starredFilterOptions[1];
 
@@ -228,253 +228,253 @@ describe("ManageDashboards", () => {
         return ctrl.onStarredFilterChange();
       });
 
-      it("should set starred filter", () => {
+      it('should set starred filter', () => {
         expect(ctrl.sections.length).toEqual(1);
         expect(ctrl.query.starred).toEqual(true);
       });
 
-      it("should have active filters", () => {
+      it('should have active filters', () => {
         expect(ctrl.hasFilters).toBeTruthy();
       });
 
-      describe("when clearing filters", () => {
+      describe('when clearing filters', () => {
         beforeEach(() => {
           return ctrl.clearFilters();
         });
 
-        it("should reset starred filter", () => {
+        it('should reset starred filter', () => {
           expect(ctrl.query.starred).toEqual(false);
         });
       });
     });
   });
 
-  describe("when selecting dashboards", () => {
+  describe('when selecting dashboards', () => {
     let ctrl;
 
     beforeEach(() => {
       ctrl = createCtrlWithStubs([]);
     });
 
-    describe("and no dashboards are selected", () => {
+    describe('and no dashboards are selected', () => {
       beforeEach(() => {
         ctrl.sections = [
           {
             id: 1,
             items: [{ id: 2, checked: false }],
-            checked: false
+            checked: false,
           },
           {
             id: 0,
             items: [{ id: 3, checked: false }],
-            checked: false
-          }
+            checked: false,
+          },
         ];
         ctrl.selectionChanged();
       });
 
-      it("should disable Move To button", () => {
+      it('should disable Move To button', () => {
         expect(ctrl.canMove).toBeFalsy();
       });
 
-      it("should disable delete button", () => {
+      it('should disable delete button', () => {
         expect(ctrl.canDelete).toBeFalsy();
       });
 
-      describe("when select all is checked", () => {
+      describe('when select all is checked', () => {
         beforeEach(() => {
           ctrl.selectAllChecked = true;
           ctrl.onSelectAllChanged();
         });
 
-        it("should select all folders and dashboards", () => {
+        it('should select all folders and dashboards', () => {
           expect(ctrl.sections[0].checked).toBeTruthy();
           expect(ctrl.sections[0].items[0].checked).toBeTruthy();
           expect(ctrl.sections[1].checked).toBeTruthy();
           expect(ctrl.sections[1].items[0].checked).toBeTruthy();
         });
 
-        it("should enable Move To button", () => {
+        it('should enable Move To button', () => {
           expect(ctrl.canMove).toBeTruthy();
         });
 
-        it("should enable delete button", () => {
+        it('should enable delete button', () => {
           expect(ctrl.canDelete).toBeTruthy();
         });
       });
     });
 
-    describe("and all folders and dashboards are selected", () => {
+    describe('and all folders and dashboards are selected', () => {
       beforeEach(() => {
         ctrl.sections = [
           {
             id: 1,
             items: [{ id: 2, checked: true }],
-            checked: true
+            checked: true,
           },
           {
             id: 0,
             items: [{ id: 3, checked: true }],
-            checked: true
-          }
+            checked: true,
+          },
         ];
         ctrl.selectionChanged();
       });
 
-      it("should enable Move To button", () => {
+      it('should enable Move To button', () => {
         expect(ctrl.canMove).toBeTruthy();
       });
 
-      it("should enable delete button", () => {
+      it('should enable delete button', () => {
         expect(ctrl.canDelete).toBeTruthy();
       });
 
-      describe("when select all is unchecked", () => {
+      describe('when select all is unchecked', () => {
         beforeEach(() => {
           ctrl.selectAllChecked = false;
           ctrl.onSelectAllChanged();
         });
 
-        it("should uncheck all checked folders and dashboards", () => {
+        it('should uncheck all checked folders and dashboards', () => {
           expect(ctrl.sections[0].checked).toBeFalsy();
           expect(ctrl.sections[0].items[0].checked).toBeFalsy();
           expect(ctrl.sections[1].checked).toBeFalsy();
           expect(ctrl.sections[1].items[0].checked).toBeFalsy();
         });
 
-        it("should disable Move To button", () => {
+        it('should disable Move To button', () => {
           expect(ctrl.canMove).toBeFalsy();
         });
 
-        it("should disable delete button", () => {
+        it('should disable delete button', () => {
           expect(ctrl.canDelete).toBeFalsy();
         });
       });
     });
 
-    describe("and one dashboard in root is selected", () => {
+    describe('and one dashboard in root is selected', () => {
       beforeEach(() => {
         ctrl.sections = [
           {
             id: 1,
-            title: "folder",
+            title: 'folder',
             items: [{ id: 2, checked: false }],
-            checked: false
+            checked: false,
           },
           {
             id: 0,
-            title: "Root",
+            title: 'Root',
             items: [{ id: 3, checked: true }],
-            checked: false
-          }
+            checked: false,
+          },
         ];
         ctrl.selectionChanged();
       });
 
-      it("should enable Move To button", () => {
+      it('should enable Move To button', () => {
         expect(ctrl.canMove).toBeTruthy();
       });
 
-      it("should enable delete button", () => {
+      it('should enable delete button', () => {
         expect(ctrl.canDelete).toBeTruthy();
       });
     });
 
-    describe("and one child dashboard is selected", () => {
+    describe('and one child dashboard is selected', () => {
       beforeEach(() => {
         ctrl.sections = [
           {
             id: 1,
-            title: "folder",
+            title: 'folder',
             items: [{ id: 2, checked: true }],
-            checked: false
+            checked: false,
           },
           {
             id: 0,
-            title: "Root",
+            title: 'Root',
             items: [{ id: 3, checked: false }],
-            checked: false
-          }
+            checked: false,
+          },
         ];
 
         ctrl.selectionChanged();
       });
 
-      it("should enable Move To button", () => {
+      it('should enable Move To button', () => {
         expect(ctrl.canMove).toBeTruthy();
       });
 
-      it("should enable delete button", () => {
+      it('should enable delete button', () => {
         expect(ctrl.canDelete).toBeTruthy();
       });
     });
 
-    describe("and one child dashboard and one dashboard is selected", () => {
+    describe('and one child dashboard and one dashboard is selected', () => {
       beforeEach(() => {
         ctrl.sections = [
           {
             id: 1,
-            title: "folder",
+            title: 'folder',
             items: [{ id: 2, checked: true }],
-            checked: false
+            checked: false,
           },
           {
             id: 0,
-            title: "Root",
+            title: 'Root',
             items: [{ id: 3, checked: true }],
-            checked: false
-          }
+            checked: false,
+          },
         ];
 
         ctrl.selectionChanged();
       });
 
-      it("should enable Move To button", () => {
+      it('should enable Move To button', () => {
         expect(ctrl.canMove).toBeTruthy();
       });
 
-      it("should enable delete button", () => {
+      it('should enable delete button', () => {
         expect(ctrl.canDelete).toBeTruthy();
       });
     });
 
-    describe("and one child dashboard and one folder is selected", () => {
+    describe('and one child dashboard and one folder is selected', () => {
       beforeEach(() => {
         ctrl.sections = [
           {
             id: 1,
-            title: "folder",
+            title: 'folder',
             items: [{ id: 2, checked: false }],
-            checked: true
+            checked: true,
           },
           {
             id: 3,
-            title: "folder",
+            title: 'folder',
             items: [{ id: 4, checked: true }],
-            checked: false
+            checked: false,
           },
           {
             id: 0,
-            title: "Root",
+            title: 'Root',
             items: [{ id: 3, checked: false }],
-            checked: false
-          }
+            checked: false,
+          },
         ];
 
         ctrl.selectionChanged();
       });
 
-      it("should enable Move To button", () => {
+      it('should enable Move To button', () => {
         expect(ctrl.canMove).toBeTruthy();
       });
 
-      it("should enable delete button", () => {
+      it('should enable delete button', () => {
         expect(ctrl.canDelete).toBeTruthy();
       });
     });
   });
 
-  describe("when deleting dashboards", () => {
+  describe('when deleting dashboards', () => {
     let toBeDeleted: any;
 
     beforeEach(() => {
@@ -483,76 +483,76 @@ describe("ManageDashboards", () => {
       ctrl.sections = [
         {
           id: 1,
-          title: "folder",
-          items: [{ id: 2, checked: true, slug: "folder-dash" }],
+          title: 'folder',
+          items: [{ id: 2, checked: true, slug: 'folder-dash' }],
           checked: true,
-          slug: "folder"
+          slug: 'folder',
         },
         {
           id: 3,
-          title: "folder-2",
-          items: [{ id: 3, checked: true, slug: "folder-2-dash" }],
+          title: 'folder-2',
+          items: [{ id: 3, checked: true, slug: 'folder-2-dash' }],
           checked: false,
-          slug: "folder-2"
+          slug: 'folder-2',
         },
         {
           id: 0,
-          title: "Root",
-          items: [{ id: 3, checked: true, slug: "root-dash" }],
-          checked: true
-        }
+          title: 'Root',
+          items: [{ id: 3, checked: true, slug: 'root-dash' }],
+          checked: true,
+        },
       ];
 
       toBeDeleted = ctrl.getFoldersAndDashboardsToDelete();
     });
 
-    it("should return 1 folder", () => {
+    it('should return 1 folder', () => {
       expect(toBeDeleted.folders.length).toEqual(1);
     });
 
-    it("should return 2 dashboards", () => {
+    it('should return 2 dashboards', () => {
       expect(toBeDeleted.dashboards.length).toEqual(2);
     });
 
-    it("should filter out children if parent is checked", () => {
-      expect(toBeDeleted.folders[0]).toEqual("folder");
+    it('should filter out children if parent is checked', () => {
+      expect(toBeDeleted.folders[0]).toEqual('folder');
     });
 
-    it("should not filter out children if parent not is checked", () => {
-      expect(toBeDeleted.dashboards[0]).toEqual("folder-2-dash");
+    it('should not filter out children if parent not is checked', () => {
+      expect(toBeDeleted.dashboards[0]).toEqual('folder-2-dash');
     });
 
-    it("should not filter out children if parent is checked and root", () => {
-      expect(toBeDeleted.dashboards[1]).toEqual("root-dash");
+    it('should not filter out children if parent is checked and root', () => {
+      expect(toBeDeleted.dashboards[1]).toEqual('root-dash');
     });
   });
 
-  describe("when moving dashboards", () => {
+  describe('when moving dashboards', () => {
     beforeEach(() => {
       ctrl = createCtrlWithStubs([]);
 
       ctrl.sections = [
         {
           id: 1,
-          title: "folder",
-          items: [{ id: 2, checked: true, slug: "dash" }],
+          title: 'folder',
+          items: [{ id: 2, checked: true, slug: 'dash' }],
           checked: false,
-          slug: "folder"
+          slug: 'folder',
         },
         {
           id: 0,
-          title: "Root",
-          items: [{ id: 3, checked: true, slug: "dash-2" }],
-          checked: false
-        }
+          title: 'Root',
+          items: [{ id: 3, checked: true, slug: 'dash-2' }],
+          checked: false,
+        },
       ];
     });
 
-    it("should get selected dashboards", () => {
+    it('should get selected dashboards', () => {
       const toBeMove = ctrl.getDashboardsToMove();
       expect(toBeMove.length).toEqual(2);
-      expect(toBeMove[0]).toEqual("dash");
-      expect(toBeMove[1]).toEqual("dash-2");
+      expect(toBeMove[0]).toEqual('dash');
+      expect(toBeMove[1]).toEqual('dash-2');
     });
   });
 });
@@ -564,12 +564,8 @@ function createCtrlWithStubs(searchResponse: any, tags?: any) {
     },
     getDashboardTags: () => {
       return q.resolve(tags || []);
-    }
+    },
   };
 
-  return new ManageDashboardsCtrl(
-    {},
-    { getNav: () => {} },
-    <SearchSrv>searchSrvStub
-  );
+  return new ManageDashboardsCtrl({}, { getNav: () => {} }, <SearchSrv>searchSrvStub);
 }

+ 13 - 16
public/app/core/specs/org_switcher.jest.ts

@@ -1,14 +1,14 @@
-import { OrgSwitchCtrl } from "../components/org_switcher";
-import q from "q";
+import { OrgSwitchCtrl } from '../components/org_switcher';
+import q from 'q';
 
-jest.mock("app/core/services/context_srv", () => ({
+jest.mock('app/core/services/context_srv', () => ({
   contextSrv: {
-    user: { orgId: 1 }
-  }
+    user: { orgId: 1 },
+  },
 }));
 
-describe("OrgSwitcher", () => {
-  describe("when switching org", () => {
+describe('OrgSwitcher', () => {
+  describe('when switching org', () => {
     let expectedHref;
     let expectedUsingUrl;
 
@@ -20,26 +20,23 @@ describe("OrgSwitcher", () => {
         post: url => {
           expectedUsingUrl = url;
           return q.resolve({});
-        }
+        },
       };
 
       const orgSwitcherCtrl = new OrgSwitchCtrl(backendSrvStub);
 
-      orgSwitcherCtrl.getWindowLocationHref = () =>
-        "http://localhost:3000?orgId=1&from=now-3h&to=now";
+      orgSwitcherCtrl.getWindowLocationHref = () => 'http://localhost:3000?orgId=1&from=now-3h&to=now';
       orgSwitcherCtrl.setWindowLocationHref = href => (expectedHref = href);
 
       return orgSwitcherCtrl.setUsingOrg({ orgId: 2 });
     });
 
-    it("should switch orgId in call to backend", () => {
-      expect(expectedUsingUrl).toBe("/api/user/using/2");
+    it('should switch orgId in call to backend', () => {
+      expect(expectedUsingUrl).toBe('/api/user/using/2');
     });
 
-    it("should switch orgId in url", () => {
-      expect(expectedHref).toBe(
-        "http://localhost:3000?orgId=2&from=now-3h&to=now"
-      );
+    it('should switch orgId in url', () => {
+      expect(expectedHref).toBe('http://localhost:3000?orgId=2&from=now-3h&to=now');
     });
   });
 });

+ 66 - 69
public/app/core/specs/rangeutil.jest.ts

@@ -1,120 +1,117 @@
-import * as rangeUtil from "app/core/utils/rangeutil";
-import _ from "lodash";
-import moment from "moment";
-
-describe("rangeUtil", () => {
-  describe("Can get range grouped list of ranges", () => {
-    it("when custom settings should return default range list", () => {
-      var groups = rangeUtil.getRelativeTimesList(
-        { time_options: [] },
-        "Last 5 minutes"
-      );
+import * as rangeUtil from 'app/core/utils/rangeutil';
+import _ from 'lodash';
+import moment from 'moment';
+
+describe('rangeUtil', () => {
+  describe('Can get range grouped list of ranges', () => {
+    it('when custom settings should return default range list', () => {
+      var groups = rangeUtil.getRelativeTimesList({ time_options: [] }, 'Last 5 minutes');
       expect(_.keys(groups).length).toBe(4);
       expect(groups[3][0].active).toBe(true);
     });
   });
 
-  describe("Can get range text described", () => {
-    it("should handle simple old expression with only amount and unit", () => {
-      var info = rangeUtil.describeTextRange("5m");
-      expect(info.display).toBe("Last 5 minutes");
+  describe('Can get range text described', () => {
+    it('should handle simple old expression with only amount and unit', () => {
+      var info = rangeUtil.describeTextRange('5m');
+      expect(info.display).toBe('Last 5 minutes');
     });
 
-    it("should have singular when amount is 1", () => {
-      var info = rangeUtil.describeTextRange("1h");
-      expect(info.display).toBe("Last 1 hour");
+    it('should have singular when amount is 1', () => {
+      var info = rangeUtil.describeTextRange('1h');
+      expect(info.display).toBe('Last 1 hour');
     });
 
-    it("should handle non default amount", () => {
-      var info = rangeUtil.describeTextRange("13h");
-      expect(info.display).toBe("Last 13 hours");
-      expect(info.from).toBe("now-13h");
+    it('should handle non default amount', () => {
+      var info = rangeUtil.describeTextRange('13h');
+      expect(info.display).toBe('Last 13 hours');
+      expect(info.from).toBe('now-13h');
     });
 
-    it("should handle non default future amount", () => {
-      var info = rangeUtil.describeTextRange("+3h");
-      expect(info.display).toBe("Next 3 hours");
-      expect(info.from).toBe("now");
-      expect(info.to).toBe("now+3h");
+    it('should handle non default future amount', () => {
+      var info = rangeUtil.describeTextRange('+3h');
+      expect(info.display).toBe('Next 3 hours');
+      expect(info.from).toBe('now');
+      expect(info.to).toBe('now+3h');
     });
 
-    it("should handle now/d", () => {
-      var info = rangeUtil.describeTextRange("now/d");
-      expect(info.display).toBe("Today so far");
+    it('should handle now/d', () => {
+      var info = rangeUtil.describeTextRange('now/d');
+      expect(info.display).toBe('Today so far');
     });
 
-    it("should handle now/w", () => {
-      var info = rangeUtil.describeTextRange("now/w");
-      expect(info.display).toBe("This week so far");
+    it('should handle now/w', () => {
+      var info = rangeUtil.describeTextRange('now/w');
+      expect(info.display).toBe('This week so far');
     });
 
-    it("should handle now/M", () => {
-      var info = rangeUtil.describeTextRange("now/M");
-      expect(info.display).toBe("This month so far");
+    it('should handle now/M', () => {
+      var info = rangeUtil.describeTextRange('now/M');
+      expect(info.display).toBe('This month so far');
     });
 
-    it("should handle now/y", () => {
-      var info = rangeUtil.describeTextRange("now/y");
-      expect(info.display).toBe("This year so far");
+    it('should handle now/y', () => {
+      var info = rangeUtil.describeTextRange('now/y');
+      expect(info.display).toBe('This year so far');
     });
   });
 
-  describe("Can get date range described", () => {
-    it("Date range with simple ranges", () => {
-      var text = rangeUtil.describeTimeRange({ from: "now-1h", to: "now" });
-      expect(text).toBe("Last 1 hour");
+  describe('Can get date range described', () => {
+    it('Date range with simple ranges', () => {
+      var text = rangeUtil.describeTimeRange({ from: 'now-1h', to: 'now' });
+      expect(text).toBe('Last 1 hour');
     });
 
-    it("Date range with rounding ranges", () => {
-      var text = rangeUtil.describeTimeRange({ from: "now/d+6h", to: "now" });
-      expect(text).toBe("now/d+6h to now");
+    it('Date range with rounding ranges', () => {
+      var text = rangeUtil.describeTimeRange({ from: 'now/d+6h', to: 'now' });
+      expect(text).toBe('now/d+6h to now');
     });
 
-    it("Date range with absolute to now", () => {
+    it('Date range with absolute to now', () => {
       var text = rangeUtil.describeTimeRange({
         from: moment([2014, 10, 10, 2, 3, 4]),
-        to: "now"
+        to: 'now',
       });
-      expect(text).toBe("Nov 10, 2014 02:03:04 to a few seconds ago");
+      expect(text).toBe('Nov 10, 2014 02:03:04 to a few seconds ago');
     });
 
-    it("Date range with absolute to relative", () => {
+    it('Date range with absolute to relative', () => {
       var text = rangeUtil.describeTimeRange({
         from: moment([2014, 10, 10, 2, 3, 4]),
-        to: "now-1d"
+        to: 'now-1d',
       });
-      expect(text).toBe("Nov 10, 2014 02:03:04 to a day ago");
+      expect(text).toBe('Nov 10, 2014 02:03:04 to a day ago');
     });
 
-    it("Date range with relative to absolute", () => {
+    it('Date range with relative to absolute', () => {
       var text = rangeUtil.describeTimeRange({
-        from: "now-7d",
-        to: moment([2014, 10, 10, 2, 3, 4])
+        from: 'now-7d',
+        to: moment([2014, 10, 10, 2, 3, 4]),
       });
-      expect(text).toBe("7 days ago to Nov 10, 2014 02:03:04");
+      expect(text).toBe('7 days ago to Nov 10, 2014 02:03:04');
     });
 
-    it("Date range with non matching default ranges", () => {
-      var text = rangeUtil.describeTimeRange({ from: "now-13h", to: "now" });
-      expect(text).toBe("Last 13 hours");
+    it('Date range with non matching default ranges', () => {
+      var text = rangeUtil.describeTimeRange({ from: 'now-13h', to: 'now' });
+      expect(text).toBe('Last 13 hours');
     });
 
-    it("Date range with from and to both are in now-* format", () => {
-      var text = rangeUtil.describeTimeRange({ from: "now-6h", to: "now-3h" });
-      expect(text).toBe("now-6h to now-3h");
+    it('Date range with from and to both are in now-* format', () => {
+      var text = rangeUtil.describeTimeRange({ from: 'now-6h', to: 'now-3h' });
+      expect(text).toBe('now-6h to now-3h');
     });
 
-    it("Date range with from and to both are either in now-* or now/* format", () => {
+    it('Date range with from and to both are either in now-* or now/* format', () => {
       var text = rangeUtil.describeTimeRange({
-        from: "now/d+6h",
-        to: "now-3h"
+        from: 'now/d+6h',
+        to: 'now-3h',
       });
-      expect(text).toBe("now/d+6h to now-3h");
+      expect(text).toBe('now/d+6h to now-3h');
     });
 
-    it("Date range with from and to both are either in now-* or now+* format", () => {
-      var text = rangeUtil.describeTimeRange({ from: "now-6h", to: "now+1h" });
-      expect(text).toBe("now-6h to now+1h");
+    it('Date range with from and to both are either in now-* or now+* format', () => {
+      var text = rangeUtil.describeTimeRange({ from: 'now-6h', to: 'now+1h' });
+      expect(text).toBe('now-6h to now+1h');
     });
   });
 });

+ 56 - 61
public/app/core/specs/search.jest.ts

@@ -1,75 +1,70 @@
-import { SearchCtrl } from "../components/search/search";
-import { SearchSrv } from "../services/search_srv";
+import { SearchCtrl } from '../components/search/search';
+import { SearchSrv } from '../services/search_srv';
 
-describe("SearchCtrl", () => {
+describe('SearchCtrl', () => {
   const searchSrvStub = {
     search: (options: any) => {},
-    getDashboardTags: () => {}
+    getDashboardTags: () => {},
   };
-  let ctrl = new SearchCtrl(
-    { $on: () => {} },
-    {},
-    {},
-    <SearchSrv>searchSrvStub
-  );
-
-  describe("Given an empty result", () => {
+  let ctrl = new SearchCtrl({ $on: () => {} }, {}, {}, <SearchSrv>searchSrvStub);
+
+  describe('Given an empty result', () => {
     beforeEach(() => {
       ctrl.results = [];
     });
 
-    describe("When navigating down one step", () => {
+    describe('When navigating down one step', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 0;
         ctrl.moveSelection(1);
       });
 
-      it("should not navigate", () => {
+      it('should not navigate', () => {
         expect(ctrl.selectedIndex).toBe(0);
       });
     });
 
-    describe("When navigating up one step", () => {
+    describe('When navigating up one step', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 0;
         ctrl.moveSelection(-1);
       });
 
-      it("should not navigate", () => {
+      it('should not navigate', () => {
         expect(ctrl.selectedIndex).toBe(0);
       });
     });
   });
 
-  describe("Given a result of one selected collapsed folder with no dashboards and a root folder with 2 dashboards", () => {
+  describe('Given a result of one selected collapsed folder with no dashboards and a root folder with 2 dashboards', () => {
     beforeEach(() => {
       ctrl.results = [
         {
           id: 1,
-          title: "folder",
+          title: 'folder',
           items: [],
           selected: true,
           expanded: false,
-          toggle: i => (i.expanded = !i.expanded)
+          toggle: i => (i.expanded = !i.expanded),
         },
         {
           id: 0,
-          title: "Root",
+          title: 'Root',
           items: [{ id: 3, selected: false }, { id: 5, selected: false }],
           selected: false,
           expanded: true,
-          toggle: i => (i.expanded = !i.expanded)
-        }
+          toggle: i => (i.expanded = !i.expanded),
+        },
       ];
     });
 
-    describe("When navigating down one step", () => {
+    describe('When navigating down one step', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 0;
         ctrl.moveSelection(1);
       });
 
-      it("should select first dashboard in root folder", () => {
+      it('should select first dashboard in root folder', () => {
         expect(ctrl.results[0].selected).toBeFalsy();
         expect(ctrl.results[1].selected).toBeFalsy();
         expect(ctrl.results[1].items[0].selected).toBeTruthy();
@@ -77,14 +72,14 @@ describe("SearchCtrl", () => {
       });
     });
 
-    describe("When navigating down two steps", () => {
+    describe('When navigating down two steps', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 0;
         ctrl.moveSelection(1);
         ctrl.moveSelection(1);
       });
 
-      it("should select last dashboard in root folder", () => {
+      it('should select last dashboard in root folder', () => {
         expect(ctrl.results[0].selected).toBeFalsy();
         expect(ctrl.results[1].selected).toBeFalsy();
         expect(ctrl.results[1].items[0].selected).toBeFalsy();
@@ -92,7 +87,7 @@ describe("SearchCtrl", () => {
       });
     });
 
-    describe("When navigating down three steps", () => {
+    describe('When navigating down three steps', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 0;
         ctrl.moveSelection(1);
@@ -100,7 +95,7 @@ describe("SearchCtrl", () => {
         ctrl.moveSelection(1);
       });
 
-      it("should select first folder", () => {
+      it('should select first folder', () => {
         expect(ctrl.results[0].selected).toBeTruthy();
         expect(ctrl.results[1].selected).toBeFalsy();
         expect(ctrl.results[1].items[0].selected).toBeFalsy();
@@ -108,13 +103,13 @@ describe("SearchCtrl", () => {
       });
     });
 
-    describe("When navigating up one step", () => {
+    describe('When navigating up one step', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 0;
         ctrl.moveSelection(-1);
       });
 
-      it("should select last dashboard in root folder", () => {
+      it('should select last dashboard in root folder', () => {
         expect(ctrl.results[0].selected).toBeFalsy();
         expect(ctrl.results[1].selected).toBeFalsy();
         expect(ctrl.results[1].items[0].selected).toBeFalsy();
@@ -122,14 +117,14 @@ describe("SearchCtrl", () => {
       });
     });
 
-    describe("When navigating up two steps", () => {
+    describe('When navigating up two steps', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 0;
         ctrl.moveSelection(-1);
         ctrl.moveSelection(-1);
       });
 
-      it("should select first dashboard in root folder", () => {
+      it('should select first dashboard in root folder', () => {
         expect(ctrl.results[0].selected).toBeFalsy();
         expect(ctrl.results[1].selected).toBeFalsy();
         expect(ctrl.results[1].items[0].selected).toBeTruthy();
@@ -138,35 +133,35 @@ describe("SearchCtrl", () => {
     });
   });
 
-  describe("Given a result of one selected collapsed folder with 2 dashboards and a root folder with 2 dashboards", () => {
+  describe('Given a result of one selected collapsed folder with 2 dashboards and a root folder with 2 dashboards', () => {
     beforeEach(() => {
       ctrl.results = [
         {
           id: 1,
-          title: "folder",
+          title: 'folder',
           items: [{ id: 2, selected: false }, { id: 4, selected: false }],
           selected: true,
           expanded: false,
-          toggle: i => (i.expanded = !i.expanded)
+          toggle: i => (i.expanded = !i.expanded),
         },
         {
           id: 0,
-          title: "Root",
+          title: 'Root',
           items: [{ id: 3, selected: false }, { id: 5, selected: false }],
           selected: false,
           expanded: true,
-          toggle: i => (i.expanded = !i.expanded)
-        }
+          toggle: i => (i.expanded = !i.expanded),
+        },
       ];
     });
 
-    describe("When navigating down one step", () => {
+    describe('When navigating down one step', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 0;
         ctrl.moveSelection(1);
       });
 
-      it("should select first dashboard in root folder", () => {
+      it('should select first dashboard in root folder', () => {
         expect(ctrl.results[0].selected).toBeFalsy();
         expect(ctrl.results[1].selected).toBeFalsy();
         expect(ctrl.results[0].items[0].selected).toBeFalsy();
@@ -176,14 +171,14 @@ describe("SearchCtrl", () => {
       });
     });
 
-    describe("When navigating down two steps", () => {
+    describe('When navigating down two steps', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 0;
         ctrl.moveSelection(1);
         ctrl.moveSelection(1);
       });
 
-      it("should select last dashboard in root folder", () => {
+      it('should select last dashboard in root folder', () => {
         expect(ctrl.results[0].selected).toBeFalsy();
         expect(ctrl.results[1].selected).toBeFalsy();
         expect(ctrl.results[0].items[0].selected).toBeFalsy();
@@ -193,7 +188,7 @@ describe("SearchCtrl", () => {
       });
     });
 
-    describe("When navigating down three steps", () => {
+    describe('When navigating down three steps', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 0;
         ctrl.moveSelection(1);
@@ -201,7 +196,7 @@ describe("SearchCtrl", () => {
         ctrl.moveSelection(1);
       });
 
-      it("should select first folder", () => {
+      it('should select first folder', () => {
         expect(ctrl.results[0].selected).toBeTruthy();
         expect(ctrl.results[1].selected).toBeFalsy();
         expect(ctrl.results[0].items[0].selected).toBeFalsy();
@@ -211,13 +206,13 @@ describe("SearchCtrl", () => {
       });
     });
 
-    describe("When navigating up one step", () => {
+    describe('When navigating up one step', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 0;
         ctrl.moveSelection(-1);
       });
 
-      it("should select last dashboard in root folder", () => {
+      it('should select last dashboard in root folder', () => {
         expect(ctrl.results[0].selected).toBeFalsy();
         expect(ctrl.results[1].selected).toBeFalsy();
         expect(ctrl.results[0].items[0].selected).toBeFalsy();
@@ -227,14 +222,14 @@ describe("SearchCtrl", () => {
       });
     });
 
-    describe("When navigating up two steps", () => {
+    describe('When navigating up two steps', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 0;
         ctrl.moveSelection(-1);
         ctrl.moveSelection(-1);
       });
 
-      it("should select first dashboard in root folder", () => {
+      it('should select first dashboard in root folder', () => {
         expect(ctrl.results[0].selected).toBeFalsy();
         expect(ctrl.results[1].selected).toBeFalsy();
         expect(ctrl.results[1].items[0].selected).toBeTruthy();
@@ -243,7 +238,7 @@ describe("SearchCtrl", () => {
     });
   });
 
-  describe("Given a result of a search with 2 dashboards where the first is selected", () => {
+  describe('Given a result of a search with 2 dashboards where the first is selected', () => {
     beforeEach(() => {
       ctrl.results = [
         {
@@ -251,39 +246,39 @@ describe("SearchCtrl", () => {
           items: [{ id: 3, selected: true }, { id: 5, selected: false }],
           selected: false,
           expanded: true,
-          toggle: i => (i.expanded = !i.expanded)
-        }
+          toggle: i => (i.expanded = !i.expanded),
+        },
       ];
     });
 
-    describe("When navigating down one step", () => {
+    describe('When navigating down one step', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 1;
         ctrl.moveSelection(1);
       });
 
-      it("should select last dashboard", () => {
+      it('should select last dashboard', () => {
         expect(ctrl.results[0].selected).toBeFalsy();
         expect(ctrl.results[0].items[0].selected).toBeFalsy();
         expect(ctrl.results[0].items[1].selected).toBeTruthy();
       });
     });
 
-    describe("When navigating down two steps", () => {
+    describe('When navigating down two steps', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 1;
         ctrl.moveSelection(1);
         ctrl.moveSelection(1);
       });
 
-      it("should select first dashboard", () => {
+      it('should select first dashboard', () => {
         expect(ctrl.results[0].selected).toBeFalsy();
         expect(ctrl.results[0].items[0].selected).toBeTruthy();
         expect(ctrl.results[0].items[1].selected).toBeFalsy();
       });
     });
 
-    describe("When navigating down three steps", () => {
+    describe('When navigating down three steps', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 1;
         ctrl.moveSelection(1);
@@ -291,34 +286,34 @@ describe("SearchCtrl", () => {
         ctrl.moveSelection(1);
       });
 
-      it("should select last dashboard", () => {
+      it('should select last dashboard', () => {
         expect(ctrl.results[0].selected).toBeFalsy();
         expect(ctrl.results[0].items[0].selected).toBeFalsy();
         expect(ctrl.results[0].items[1].selected).toBeTruthy();
       });
     });
 
-    describe("When navigating up one step", () => {
+    describe('When navigating up one step', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 1;
         ctrl.moveSelection(-1);
       });
 
-      it("should select last dashboard", () => {
+      it('should select last dashboard', () => {
         expect(ctrl.results[0].selected).toBeFalsy();
         expect(ctrl.results[0].items[0].selected).toBeFalsy();
         expect(ctrl.results[0].items[1].selected).toBeTruthy();
       });
     });
 
-    describe("When navigating up two steps", () => {
+    describe('When navigating up two steps', () => {
       beforeEach(() => {
         ctrl.selectedIndex = 1;
         ctrl.moveSelection(-1);
         ctrl.moveSelection(-1);
       });
 
-      it("should select first dashboard", () => {
+      it('should select first dashboard', () => {
         expect(ctrl.results[0].selected).toBeFalsy();
         expect(ctrl.results[0].items[0].selected).toBeTruthy();
         expect(ctrl.results[0].items[1].selected).toBeFalsy();

+ 30 - 30
public/app/core/specs/search_results.jest.ts

@@ -1,17 +1,17 @@
-import { SearchResultsCtrl } from "../components/search/search_results";
-import { beforeEach, afterEach } from "test/lib/common";
-import appEvents from "app/core/app_events";
+import { SearchResultsCtrl } from '../components/search/search_results';
+import { beforeEach, afterEach } from 'test/lib/common';
+import appEvents from 'app/core/app_events';
 
-jest.mock("app/core/app_events", () => {
+jest.mock('app/core/app_events', () => {
   return {
-    emit: jest.fn<any>()
+    emit: jest.fn<any>(),
   };
 });
 
-describe("SearchResultsCtrl", () => {
+describe('SearchResultsCtrl', () => {
   let ctrl;
 
-  describe("when checking an item that is not checked", () => {
+  describe('when checking an item that is not checked', () => {
     let item = { checked: false };
     let selectionChanged = false;
 
@@ -21,16 +21,16 @@ describe("SearchResultsCtrl", () => {
       ctrl.toggleSelection(item);
     });
 
-    it("should set checked to true", () => {
+    it('should set checked to true', () => {
       expect(item.checked).toBeTruthy();
     });
 
-    it("should trigger selection changed callback", () => {
+    it('should trigger selection changed callback', () => {
       expect(selectionChanged).toBeTruthy();
     });
   });
 
-  describe("when checking an item that is checked", () => {
+  describe('when checking an item that is checked', () => {
     let item = { checked: true };
     let selectionChanged = false;
 
@@ -40,30 +40,30 @@ describe("SearchResultsCtrl", () => {
       ctrl.toggleSelection(item);
     });
 
-    it("should set checked to false", () => {
+    it('should set checked to false', () => {
       expect(item.checked).toBeFalsy();
     });
 
-    it("should trigger selection changed callback", () => {
+    it('should trigger selection changed callback', () => {
       expect(selectionChanged).toBeTruthy();
     });
   });
 
-  describe("when selecting a tag", () => {
+  describe('when selecting a tag', () => {
     let selectedTag = null;
 
     beforeEach(() => {
       ctrl = new SearchResultsCtrl({});
       ctrl.onTagSelected = tag => (selectedTag = tag);
-      ctrl.selectTag("tag-test");
+      ctrl.selectTag('tag-test');
     });
 
-    it("should trigger tag selected callback", () => {
-      expect(selectedTag["$tag"]).toBe("tag-test");
+    it('should trigger tag selected callback', () => {
+      expect(selectedTag['$tag']).toBe('tag-test');
     });
   });
 
-  describe("when toggle a collapsed folder", () => {
+  describe('when toggle a collapsed folder', () => {
     let folderExpanded = false;
 
     beforeEach(() => {
@@ -74,18 +74,18 @@ describe("SearchResultsCtrl", () => {
 
       let folder = {
         expanded: false,
-        toggle: () => Promise.resolve(folder)
+        toggle: () => Promise.resolve(folder),
       };
 
       ctrl.toggleFolderExpand(folder);
     });
 
-    it("should trigger folder expanding callback", () => {
+    it('should trigger folder expanding callback', () => {
       expect(folderExpanded).toBeTruthy();
     });
   });
 
-  describe("when toggle an expanded folder", () => {
+  describe('when toggle an expanded folder', () => {
     let folderExpanded = false;
 
     beforeEach(() => {
@@ -96,43 +96,43 @@ describe("SearchResultsCtrl", () => {
 
       let folder = {
         expanded: true,
-        toggle: () => Promise.resolve(folder)
+        toggle: () => Promise.resolve(folder),
       };
 
       ctrl.toggleFolderExpand(folder);
     });
 
-    it("should not trigger folder expanding callback", () => {
+    it('should not trigger folder expanding callback', () => {
       expect(folderExpanded).toBeFalsy();
     });
   });
 
-  describe("when clicking on a link in search result", () => {
-    const dashPath = "dashboard/path";
+  describe('when clicking on a link in search result', () => {
+    const dashPath = 'dashboard/path';
     const $location = { path: () => dashPath };
     const appEventsMock = appEvents as any;
 
-    describe("with the same url as current path", () => {
+    describe('with the same url as current path', () => {
       beforeEach(() => {
         ctrl = new SearchResultsCtrl($location);
         const item = { url: dashPath };
         ctrl.onItemClick(item);
       });
 
-      it("should close the search", () => {
+      it('should close the search', () => {
         expect(appEventsMock.emit.mock.calls.length).toBe(1);
-        expect(appEventsMock.emit.mock.calls[0][0]).toBe("hide-dash-search");
+        expect(appEventsMock.emit.mock.calls[0][0]).toBe('hide-dash-search');
       });
     });
 
-    describe("with a different url than current path", () => {
+    describe('with a different url than current path', () => {
       beforeEach(() => {
         ctrl = new SearchResultsCtrl($location);
-        const item = { url: "another/path" };
+        const item = { url: 'another/path' };
         ctrl.onItemClick(item);
       });
 
-      it("should do nothing", () => {
+      it('should do nothing', () => {
         expect(appEventsMock.emit.mock.calls.length).toBe(0);
       });
     });

+ 75 - 89
public/app/core/specs/search_srv.jest.ts

@@ -1,23 +1,23 @@
-import { SearchSrv } from "app/core/services/search_srv";
-import { BackendSrvMock } from "test/mocks/backend_srv";
-import impressionSrv from "app/core/services/impression_srv";
-import { contextSrv } from "app/core/services/context_srv";
-import { beforeEach } from "test/lib/common";
+import { SearchSrv } from 'app/core/services/search_srv';
+import { BackendSrvMock } from 'test/mocks/backend_srv';
+import impressionSrv from 'app/core/services/impression_srv';
+import { contextSrv } from 'app/core/services/context_srv';
+import { beforeEach } from 'test/lib/common';
 
-jest.mock("app/core/store", () => {
+jest.mock('app/core/store', () => {
   return {
     getBool: jest.fn(),
-    set: jest.fn()
+    set: jest.fn(),
   };
 });
 
-jest.mock("app/core/services/impression_srv", () => {
+jest.mock('app/core/services/impression_srv', () => {
   return {
-    getDashboardOpened: jest.fn
+    getDashboardOpened: jest.fn,
   };
 });
 
-describe("SearchSrv", () => {
+describe('SearchSrv', () => {
   let searchSrv, backendSrvMock;
 
   beforeEach(() => {
@@ -28,57 +28,50 @@ describe("SearchSrv", () => {
     impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([]);
   });
 
-  describe("With recent dashboards", () => {
+  describe('With recent dashboards', () => {
     let results;
 
     beforeEach(() => {
       backendSrvMock.search = jest
         .fn()
         .mockReturnValueOnce(
-          Promise.resolve([
-            { id: 2, title: "second but first" },
-            { id: 1, title: "first but second" }
-          ])
+          Promise.resolve([{ id: 2, title: 'second but first' }, { id: 1, title: 'first but second' }])
         )
         .mockReturnValue(Promise.resolve([]));
 
       impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([1, 2]);
 
-      return searchSrv.search({ query: "" }).then(res => {
+      return searchSrv.search({ query: '' }).then(res => {
         results = res;
       });
     });
 
-    it("should include recent dashboards section", () => {
-      expect(results[0].title).toBe("Recent Boards");
+    it('should include recent dashboards section', () => {
+      expect(results[0].title).toBe('Recent');
     });
 
-    it("should return order decided by impressions store not api", () => {
-      expect(results[0].items[0].title).toBe("first but second");
-      expect(results[0].items[1].title).toBe("second but first");
+    it('should return order decided by impressions store not api', () => {
+      expect(results[0].items[0].title).toBe('first but second');
+      expect(results[0].items[1].title).toBe('second but first');
     });
 
-    describe("and 3 recent dashboards removed in backend", () => {
+    describe('and 3 recent dashboards removed in backend', () => {
       let results;
 
       beforeEach(() => {
         backendSrvMock.search = jest
           .fn()
-          .mockReturnValueOnce(
-            Promise.resolve([{ id: 2, title: "two" }, { id: 1, title: "one" }])
-          )
+          .mockReturnValueOnce(Promise.resolve([{ id: 2, title: 'two' }, { id: 1, title: 'one' }]))
           .mockReturnValue(Promise.resolve([]));
 
-        impressionSrv.getDashboardOpened = jest
-          .fn()
-          .mockReturnValue([4, 5, 1, 2, 3]);
+        impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([4, 5, 1, 2, 3]);
 
-        return searchSrv.search({ query: "" }).then(res => {
+        return searchSrv.search({ query: '' }).then(res => {
           results = res;
         });
       });
 
-      it("should return 2 dashboards", () => {
+      it('should return 2 dashboards', () => {
         expect(results[0].items.length).toBe(2);
         expect(results[0].items[0].id).toBe(1);
         expect(results[0].items[1].id).toBe(2);
@@ -86,59 +79,52 @@ describe("SearchSrv", () => {
     });
   });
 
-  describe("With starred dashboards", () => {
+  describe('With starred dashboards', () => {
     let results;
 
     beforeEach(() => {
-      backendSrvMock.search = jest
-        .fn()
-        .mockReturnValue(Promise.resolve([{ id: 1, title: "starred" }]));
+      backendSrvMock.search = jest.fn().mockReturnValue(Promise.resolve([{ id: 1, title: 'starred' }]));
 
-      return searchSrv.search({ query: "" }).then(res => {
+      return searchSrv.search({ query: '' }).then(res => {
         results = res;
       });
     });
 
-    it("should include starred dashboards section", () => {
-      expect(results[0].title).toBe("Starred Boards");
+    it('should include starred dashboards section', () => {
+      expect(results[0].title).toBe('Starred');
       expect(results[0].items.length).toBe(1);
     });
   });
 
-  describe("With starred dashboards and recent", () => {
+  describe('With starred dashboards and recent', () => {
     let results;
 
     beforeEach(() => {
       backendSrvMock.search = jest
         .fn()
         .mockReturnValueOnce(
-          Promise.resolve([
-            { id: 1, title: "starred and recent", isStarred: true },
-            { id: 2, title: "recent" }
-          ])
+          Promise.resolve([{ id: 1, title: 'starred and recent', isStarred: true }, { id: 2, title: 'recent' }])
         )
-        .mockReturnValue(
-          Promise.resolve([{ id: 1, title: "starred and recent" }])
-        );
+        .mockReturnValue(Promise.resolve([{ id: 1, title: 'starred and recent' }]));
 
       impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([1, 2]);
-      return searchSrv.search({ query: "" }).then(res => {
+      return searchSrv.search({ query: '' }).then(res => {
         results = res;
       });
     });
 
-    it("should not show starred in recent", () => {
-      expect(results[1].title).toBe("Recent Boards");
-      expect(results[1].items[0].title).toBe("recent");
+    it('should not show starred in recent', () => {
+      expect(results[1].title).toBe('Recent');
+      expect(results[1].items[0].title).toBe('recent');
     });
 
-    it("should show starred", () => {
-      expect(results[0].title).toBe("Starred Boards");
-      expect(results[0].items[0].title).toBe("starred and recent");
+    it('should show starred', () => {
+      expect(results[0].title).toBe('Starred');
+      expect(results[0].items[0].title).toBe('starred and recent');
     });
   });
 
-  describe("with no query string and dashboards with folders returned", () => {
+  describe('with no query string and dashboards with folders returned', () => {
     let results;
 
     beforeEach(() => {
@@ -148,45 +134,45 @@ describe("SearchSrv", () => {
         .mockReturnValue(
           Promise.resolve([
             {
-              title: "folder1",
-              type: "dash-folder",
-              id: 1
+              title: 'folder1',
+              type: 'dash-folder',
+              id: 1,
             },
             {
-              title: "dash with no folder",
-              type: "dash-db",
-              id: 2
+              title: 'dash with no folder',
+              type: 'dash-db',
+              id: 2,
             },
             {
-              title: "dash in folder1 1",
-              type: "dash-db",
+              title: 'dash in folder1 1',
+              type: 'dash-db',
               id: 3,
-              folderId: 1
+              folderId: 1,
             },
             {
-              title: "dash in folder1 2",
-              type: "dash-db",
+              title: 'dash in folder1 2',
+              type: 'dash-db',
               id: 4,
-              folderId: 1
-            }
+              folderId: 1,
+            },
           ])
         );
 
-      return searchSrv.search({ query: "" }).then(res => {
+      return searchSrv.search({ query: '' }).then(res => {
         results = res;
       });
     });
 
-    it("should create sections for each folder and root", () => {
+    it('should create sections for each folder and root', () => {
       expect(results).toHaveLength(2);
     });
 
-    it("should place folders first", () => {
-      expect(results[0].title).toBe("folder1");
+    it('should place folders first', () => {
+      expect(results[0].title).toBe('folder1');
     });
   });
 
-  describe("with query string and dashboards with folders returned", () => {
+  describe('with query string and dashboards with folders returned', () => {
     let results;
 
     beforeEach(() => {
@@ -196,47 +182,47 @@ describe("SearchSrv", () => {
         Promise.resolve([
           {
             id: 2,
-            title: "dash with no folder",
-            type: "dash-db"
+            title: 'dash with no folder',
+            type: 'dash-db',
           },
           {
             id: 3,
-            title: "dash in folder1 1",
-            type: "dash-db",
+            title: 'dash in folder1 1',
+            type: 'dash-db',
             folderId: 1,
-            folderTitle: "folder1"
-          }
+            folderTitle: 'folder1',
+          },
         ])
       );
 
-      return searchSrv.search({ query: "search" }).then(res => {
+      return searchSrv.search({ query: 'search' }).then(res => {
         results = res;
       });
     });
 
-    it("should not specify folder ids", () => {
+    it('should not specify folder ids', () => {
       expect(backendSrvMock.search.mock.calls[0][0].folderIds).toHaveLength(0);
     });
 
-    it("should group results by folder", () => {
+    it('should group results by folder', () => {
       expect(results).toHaveLength(2);
     });
   });
 
-  describe("with tags", () => {
+  describe('with tags', () => {
     beforeEach(() => {
       backendSrvMock.search = jest.fn();
       backendSrvMock.search.mockReturnValue(Promise.resolve([]));
 
-      return searchSrv.search({ tag: ["atag"] }).then(() => {});
+      return searchSrv.search({ tag: ['atag'] }).then(() => {});
     });
 
-    it("should send tags query to backend search", () => {
+    it('should send tags query to backend search', () => {
       expect(backendSrvMock.search.mock.calls[0][0].tag).toHaveLength(1);
     });
   });
 
-  describe("with starred", () => {
+  describe('with starred', () => {
     beforeEach(() => {
       backendSrvMock.search = jest.fn();
       backendSrvMock.search.mockReturnValue(Promise.resolve([]));
@@ -244,12 +230,12 @@ describe("SearchSrv", () => {
       return searchSrv.search({ starred: true }).then(() => {});
     });
 
-    it("should send starred query to backend search", () => {
+    it('should send starred query to backend search', () => {
       expect(backendSrvMock.search.mock.calls[0][0].starred).toEqual(true);
     });
   });
 
-  describe("when skipping recent dashboards", () => {
+  describe('when skipping recent dashboards', () => {
     let getRecentDashboardsCalled = false;
 
     beforeEach(() => {
@@ -263,12 +249,12 @@ describe("SearchSrv", () => {
       return searchSrv.search({ skipRecent: true }).then(() => {});
     });
 
-    it("should not fetch recent dashboards", () => {
+    it('should not fetch recent dashboards', () => {
       expect(getRecentDashboardsCalled).toBeFalsy();
     });
   });
 
-  describe("when skipping starred dashboards", () => {
+  describe('when skipping starred dashboards', () => {
     let getStarredCalled = false;
 
     beforeEach(() => {
@@ -283,7 +269,7 @@ describe("SearchSrv", () => {
       return searchSrv.search({ skipStarred: true }).then(() => {});
     });
 
-    it("should not fetch starred dashboards", () => {
+    it('should not fetch starred dashboards', () => {
       expect(getStarredCalled).toBeFalsy();
     });
   });

+ 21 - 21
public/app/core/specs/store.jest.ts

@@ -1,40 +1,40 @@
-import store from "../store";
+import store from '../store';
 
 Object.assign(window, {
   localStorage: {
     removeItem(key) {
       delete window.localStorage[key];
-    }
-  }
+    },
+  },
 });
 
-describe("store", () => {
-  it("should store", () => {
-    store.set("key1", "123");
-    expect(store.get("key1")).toBe("123");
+describe('store', () => {
+  it('should store', () => {
+    store.set('key1', '123');
+    expect(store.get('key1')).toBe('123');
   });
 
-  it("get key when undefined", () => {
-    expect(store.get("key2")).toBe(undefined);
+  it('get key when undefined', () => {
+    expect(store.get('key2')).toBe(undefined);
   });
 
-  it("check if key exixts", () => {
-    store.set("key3", "123");
-    expect(store.exists("key3")).toBe(true);
+  it('check if key exixts', () => {
+    store.set('key3', '123');
+    expect(store.exists('key3')).toBe(true);
   });
 
-  it("get boolean when no key", () => {
-    expect(store.getBool("key4", false)).toBe(false);
+  it('get boolean when no key', () => {
+    expect(store.getBool('key4', false)).toBe(false);
   });
 
-  it("get boolean", () => {
-    store.set("key5", "true");
-    expect(store.getBool("key5", false)).toBe(true);
+  it('get boolean', () => {
+    store.set('key5', 'true');
+    expect(store.getBool('key5', false)).toBe(true);
   });
 
-  it("key should be deleted", () => {
-    store.set("key6", "123");
-    store.delete("key6");
-    expect(store.exists("key6")).toBe(false);
+  it('key should be deleted', () => {
+    store.set('key6', '123');
+    store.delete('key6');
+    expect(store.exists('key6')).toBe(false);
   });
 });

+ 8 - 8
public/app/core/specs/table_model.jest.ts

@@ -1,9 +1,9 @@
-import TableModel from "app/core/table_model";
+import TableModel from 'app/core/table_model';
 
-describe("when sorting table desc", () => {
+describe('when sorting table desc', () => {
   var table;
   var panel = {
-    sort: { col: 0, desc: true }
+    sort: { col: 0, desc: true },
   };
 
   beforeEach(() => {
@@ -13,22 +13,22 @@ describe("when sorting table desc", () => {
     table.sort(panel.sort);
   });
 
-  it("should sort by time", () => {
+  it('should sort by time', () => {
     expect(table.rows[0][0]).toBe(105);
     expect(table.rows[1][0]).toBe(103);
     expect(table.rows[2][0]).toBe(100);
   });
 
-  it("should mark column being sorted", () => {
+  it('should mark column being sorted', () => {
     expect(table.columns[0].sort).toBe(true);
     expect(table.columns[0].desc).toBe(true);
   });
 });
 
-describe("when sorting table asc", () => {
+describe('when sorting table asc', () => {
   var table;
   var panel = {
-    sort: { col: 1, desc: false }
+    sort: { col: 1, desc: false },
   };
 
   beforeEach(() => {
@@ -38,7 +38,7 @@ describe("when sorting table asc", () => {
     table.sort(panel.sort);
   });
 
-  it("should sort by time", () => {
+  it('should sort by time', () => {
     expect(table.rows[0][1]).toBe(10);
     expect(table.rows[1][1]).toBe(11);
     expect(table.rows[2][1]).toBe(15);

+ 99 - 107
public/app/core/specs/time_series.jest.ts

@@ -1,308 +1,300 @@
-import TimeSeries from "app/core/time_series2";
+import TimeSeries from 'app/core/time_series2';
 
-describe("TimeSeries", function() {
+describe('TimeSeries', function() {
   var points, series;
-  var yAxisFormats = ["short", "ms"];
+  var yAxisFormats = ['short', 'ms'];
   var testData;
 
   beforeEach(function() {
     testData = {
-      alias: "test",
-      datapoints: [[1, 2], [null, 3], [10, 4], [8, 5]]
+      alias: 'test',
+      datapoints: [[1, 2], [null, 3], [10, 4], [8, 5]],
     };
   });
 
-  describe("when getting flot pairs", function() {
-    it("with connected style, should ignore nulls", function() {
+  describe('when getting flot pairs', function() {
+    it('with connected style, should ignore nulls', function() {
       series = new TimeSeries(testData);
-      points = series.getFlotPairs("connected", yAxisFormats);
+      points = series.getFlotPairs('connected', yAxisFormats);
       expect(points.length).toBe(3);
     });
 
-    it("with null as zero style, should replace nulls with zero", function() {
+    it('with null as zero style, should replace nulls with zero', function() {
       series = new TimeSeries(testData);
-      points = series.getFlotPairs("null as zero", yAxisFormats);
+      points = series.getFlotPairs('null as zero', yAxisFormats);
       expect(points.length).toBe(4);
       expect(points[1][1]).toBe(0);
     });
 
-    it("if last is null current should pick next to last", function() {
+    it('if last is null current should pick next to last', function() {
       series = new TimeSeries({
-        datapoints: [[10, 1], [null, 2]]
+        datapoints: [[10, 1], [null, 2]],
       });
-      series.getFlotPairs("null", yAxisFormats);
+      series.getFlotPairs('null', yAxisFormats);
       expect(series.stats.current).toBe(10);
     });
 
-    it("max value should work for negative values", function() {
+    it('max value should work for negative values', function() {
       series = new TimeSeries({
-        datapoints: [[-10, 1], [-4, 2]]
+        datapoints: [[-10, 1], [-4, 2]],
       });
-      series.getFlotPairs("null", yAxisFormats);
+      series.getFlotPairs('null', yAxisFormats);
       expect(series.stats.max).toBe(-4);
     });
 
-    it("average value should ignore nulls", function() {
+    it('average value should ignore nulls', function() {
       series = new TimeSeries(testData);
-      series.getFlotPairs("null", yAxisFormats);
+      series.getFlotPairs('null', yAxisFormats);
       expect(series.stats.avg).toBe(6.333333333333333);
     });
 
-    it("the delta value should account for nulls", function() {
+    it('the delta value should account for nulls', function() {
       series = new TimeSeries({
-        datapoints: [[1, 2], [3, 3], [null, 4], [10, 5], [15, 6]]
+        datapoints: [[1, 2], [3, 3], [null, 4], [10, 5], [15, 6]],
       });
-      series.getFlotPairs("null", yAxisFormats);
+      series.getFlotPairs('null', yAxisFormats);
       expect(series.stats.delta).toBe(14);
     });
 
-    it("the delta value should account for nulls on first", function() {
+    it('the delta value should account for nulls on first', function() {
       series = new TimeSeries({
-        datapoints: [[null, 2], [1, 3], [10, 4], [15, 5]]
+        datapoints: [[null, 2], [1, 3], [10, 4], [15, 5]],
       });
-      series.getFlotPairs("null", yAxisFormats);
+      series.getFlotPairs('null', yAxisFormats);
       expect(series.stats.delta).toBe(14);
     });
 
-    it("the delta value should account for nulls on last", function() {
+    it('the delta value should account for nulls on last', function() {
       series = new TimeSeries({
-        datapoints: [[1, 2], [5, 3], [10, 4], [null, 5]]
+        datapoints: [[1, 2], [5, 3], [10, 4], [null, 5]],
       });
-      series.getFlotPairs("null", yAxisFormats);
+      series.getFlotPairs('null', yAxisFormats);
       expect(series.stats.delta).toBe(9);
     });
 
-    it("the delta value should account for resets", function() {
+    it('the delta value should account for resets', function() {
       series = new TimeSeries({
-        datapoints: [[1, 2], [5, 3], [10, 4], [0, 5], [10, 6]]
+        datapoints: [[1, 2], [5, 3], [10, 4], [0, 5], [10, 6]],
       });
-      series.getFlotPairs("null", yAxisFormats);
+      series.getFlotPairs('null', yAxisFormats);
       expect(series.stats.delta).toBe(19);
     });
 
-    it("the delta value should account for resets on last", function() {
+    it('the delta value should account for resets on last', function() {
       series = new TimeSeries({
-        datapoints: [[1, 2], [2, 3], [10, 4], [8, 5]]
+        datapoints: [[1, 2], [2, 3], [10, 4], [8, 5]],
       });
-      series.getFlotPairs("null", yAxisFormats);
+      series.getFlotPairs('null', yAxisFormats);
       expect(series.stats.delta).toBe(17);
     });
 
-    it("the range value should be max - min", function() {
+    it('the range value should be max - min', function() {
       series = new TimeSeries(testData);
-      series.getFlotPairs("null", yAxisFormats);
+      series.getFlotPairs('null', yAxisFormats);
       expect(series.stats.range).toBe(9);
     });
 
-    it("first value should ingone nulls", function() {
+    it('first value should ingone nulls', function() {
       series = new TimeSeries(testData);
-      series.getFlotPairs("null", yAxisFormats);
+      series.getFlotPairs('null', yAxisFormats);
       expect(series.stats.first).toBe(1);
       series = new TimeSeries({
-        datapoints: [[null, 2], [1, 3], [10, 4], [8, 5]]
+        datapoints: [[null, 2], [1, 3], [10, 4], [8, 5]],
       });
-      series.getFlotPairs("null", yAxisFormats);
+      series.getFlotPairs('null', yAxisFormats);
       expect(series.stats.first).toBe(1);
     });
 
-    it("with null as zero style, average value should treat nulls as 0", function() {
+    it('with null as zero style, average value should treat nulls as 0', function() {
       series = new TimeSeries(testData);
-      series.getFlotPairs("null as zero", yAxisFormats);
+      series.getFlotPairs('null as zero', yAxisFormats);
       expect(series.stats.avg).toBe(4.75);
     });
 
-    it("average value should be null if all values is null", function() {
+    it('average value should be null if all values is null', function() {
       series = new TimeSeries({
-        datapoints: [[null, 2], [null, 3], [null, 4], [null, 5]]
+        datapoints: [[null, 2], [null, 3], [null, 4], [null, 5]],
       });
-      series.getFlotPairs("null");
+      series.getFlotPairs('null');
       expect(series.stats.avg).toBe(null);
     });
   });
 
-  describe("When checking if ms resolution is needed", function() {
-    describe("msResolution with second resolution timestamps", function() {
+  describe('When checking if ms resolution is needed', function() {
+    describe('msResolution with second resolution timestamps', function() {
       beforeEach(function() {
         series = new TimeSeries({
-          datapoints: [[45, 1234567890], [60, 1234567899]]
+          datapoints: [[45, 1234567890], [60, 1234567899]],
         });
       });
 
-      it("should set hasMsResolution to false", function() {
+      it('should set hasMsResolution to false', function() {
         expect(series.hasMsResolution).toBe(false);
       });
     });
 
-    describe("msResolution with millisecond resolution timestamps", function() {
+    describe('msResolution with millisecond resolution timestamps', function() {
       beforeEach(function() {
         series = new TimeSeries({
-          datapoints: [[55, 1236547890001], [90, 1234456709000]]
+          datapoints: [[55, 1236547890001], [90, 1234456709000]],
         });
       });
 
-      it("should show millisecond resolution tooltip", function() {
+      it('should show millisecond resolution tooltip', function() {
         expect(series.hasMsResolution).toBe(true);
       });
     });
 
-    describe("msResolution with millisecond resolution timestamps but with trailing zeroes", function() {
+    describe('msResolution with millisecond resolution timestamps but with trailing zeroes', function() {
       beforeEach(function() {
         series = new TimeSeries({
-          datapoints: [[45, 1234567890000], [60, 1234567899000]]
+          datapoints: [[45, 1234567890000], [60, 1234567899000]],
         });
       });
 
-      it("should not show millisecond resolution tooltip", function() {
+      it('should not show millisecond resolution tooltip', function() {
         expect(series.hasMsResolution).toBe(false);
       });
     });
   });
 
-  describe("can detect if series contains ms precision", function() {
+  describe('can detect if series contains ms precision', function() {
     var fakedata;
 
     beforeEach(function() {
       fakedata = testData;
     });
 
-    it("missing datapoint with ms precision", function() {
+    it('missing datapoint with ms precision', function() {
       fakedata.datapoints[0] = [1337, 1234567890000];
       series = new TimeSeries(fakedata);
       expect(series.isMsResolutionNeeded()).toBe(false);
     });
 
-    it("contains datapoint with ms precision", function() {
+    it('contains datapoint with ms precision', function() {
       fakedata.datapoints[0] = [1337, 1236547890001];
       series = new TimeSeries(fakedata);
       expect(series.isMsResolutionNeeded()).toBe(true);
     });
   });
 
-  describe("series overrides", function() {
+  describe('series overrides', function() {
     var series;
     beforeEach(function() {
       series = new TimeSeries(testData);
     });
 
-    describe("fill & points", function() {
+    describe('fill & points', function() {
       beforeEach(function() {
-        series.alias = "test";
-        series.applySeriesOverrides([{ alias: "test", fill: 0, points: true }]);
+        series.alias = 'test';
+        series.applySeriesOverrides([{ alias: 'test', fill: 0, points: true }]);
       });
 
-      it("should set fill zero, and enable points", function() {
+      it('should set fill zero, and enable points', function() {
         expect(series.lines.fill).toBe(0.001);
         expect(series.points.show).toBe(true);
       });
     });
 
-    describe("series option overrides, bars, true & lines false", function() {
+    describe('series option overrides, bars, true & lines false', function() {
       beforeEach(function() {
-        series.alias = "test";
-        series.applySeriesOverrides([
-          { alias: "test", bars: true, lines: false }
-        ]);
+        series.alias = 'test';
+        series.applySeriesOverrides([{ alias: 'test', bars: true, lines: false }]);
       });
 
-      it("should disable lines, and enable bars", function() {
+      it('should disable lines, and enable bars', function() {
         expect(series.lines.show).toBe(false);
         expect(series.bars.show).toBe(true);
       });
     });
 
-    describe("series option overrides, linewidth, stack", function() {
+    describe('series option overrides, linewidth, stack', function() {
       beforeEach(function() {
-        series.alias = "test";
-        series.applySeriesOverrides([
-          { alias: "test", linewidth: 5, stack: false }
-        ]);
+        series.alias = 'test';
+        series.applySeriesOverrides([{ alias: 'test', linewidth: 5, stack: false }]);
       });
 
-      it("should disable stack, and set lineWidth", function() {
+      it('should disable stack, and set lineWidth', function() {
         expect(series.stack).toBe(false);
         expect(series.lines.lineWidth).toBe(5);
       });
     });
 
-    describe("series option overrides, dashes and lineWidth", function() {
+    describe('series option overrides, dashes and lineWidth', function() {
       beforeEach(function() {
-        series.alias = "test";
-        series.applySeriesOverrides([
-          { alias: "test", linewidth: 5, dashes: true }
-        ]);
+        series.alias = 'test';
+        series.applySeriesOverrides([{ alias: 'test', linewidth: 5, dashes: true }]);
       });
 
-      it("should enable dashes, set dashes lineWidth to 5 and lines lineWidth to 0", function() {
+      it('should enable dashes, set dashes lineWidth to 5 and lines lineWidth to 0', function() {
         expect(series.dashes.show).toBe(true);
         expect(series.dashes.lineWidth).toBe(5);
         expect(series.lines.lineWidth).toBe(0);
       });
     });
 
-    describe("series option overrides, fill below to", function() {
+    describe('series option overrides, fill below to', function() {
       beforeEach(function() {
-        series.alias = "test";
-        series.applySeriesOverrides([{ alias: "test", fillBelowTo: "min" }]);
+        series.alias = 'test';
+        series.applySeriesOverrides([{ alias: 'test', fillBelowTo: 'min' }]);
       });
 
-      it("should disable line fill and add fillBelowTo", function() {
-        expect(series.fillBelowTo).toBe("min");
+      it('should disable line fill and add fillBelowTo', function() {
+        expect(series.fillBelowTo).toBe('min');
       });
     });
 
-    describe("series option overrides, pointradius, steppedLine", function() {
+    describe('series option overrides, pointradius, steppedLine', function() {
       beforeEach(function() {
-        series.alias = "test";
-        series.applySeriesOverrides([
-          { alias: "test", pointradius: 5, steppedLine: true }
-        ]);
+        series.alias = 'test';
+        series.applySeriesOverrides([{ alias: 'test', pointradius: 5, steppedLine: true }]);
       });
 
-      it("should set pointradius, and set steppedLine", function() {
+      it('should set pointradius, and set steppedLine', function() {
         expect(series.points.radius).toBe(5);
         expect(series.lines.steps).toBe(true);
       });
     });
 
-    describe("override match on regex", function() {
+    describe('override match on regex', function() {
       beforeEach(function() {
-        series.alias = "test_01";
-        series.applySeriesOverrides([{ alias: "/.*01/", lines: false }]);
+        series.alias = 'test_01';
+        series.applySeriesOverrides([{ alias: '/.*01/', lines: false }]);
       });
 
-      it("should match second series", function() {
+      it('should match second series', function() {
         expect(series.lines.show).toBe(false);
       });
     });
 
-    describe("override series y-axis, and z-index", function() {
+    describe('override series y-axis, and z-index', function() {
       beforeEach(function() {
-        series.alias = "test";
-        series.applySeriesOverrides([{ alias: "test", yaxis: 2, zindex: 2 }]);
+        series.alias = 'test';
+        series.applySeriesOverrides([{ alias: 'test', yaxis: 2, zindex: 2 }]);
       });
 
-      it("should set yaxis", function() {
+      it('should set yaxis', function() {
         expect(series.yaxis).toBe(2);
       });
 
-      it("should set zindex", function() {
+      it('should set zindex', function() {
         expect(series.zindex).toBe(2);
       });
     });
   });
 
-  describe("value formatter", function() {
+  describe('value formatter', function() {
     var series;
     beforeEach(function() {
       series = new TimeSeries(testData);
     });
 
-    it("should format non-numeric values as empty string", function() {
-      expect(series.formatValue(null)).toBe("");
-      expect(series.formatValue(undefined)).toBe("");
-      expect(series.formatValue(NaN)).toBe("");
-      expect(series.formatValue(Infinity)).toBe("");
-      expect(series.formatValue(-Infinity)).toBe("");
+    it('should format non-numeric values as empty string', function() {
+      expect(series.formatValue(null)).toBe('');
+      expect(series.formatValue(undefined)).toBe('');
+      expect(series.formatValue(NaN)).toBe('');
+      expect(series.formatValue(Infinity)).toBe('');
+      expect(series.formatValue(-Infinity)).toBe('');
     });
   });
 });

+ 52 - 59
public/app/core/specs/value_select_dropdown_specs.ts

@@ -1,177 +1,170 @@
-import {
-  describe,
-  beforeEach,
-  it,
-  expect,
-  angularMocks,
-  sinon
-} from "test/lib/common";
-import "app/core/directives/value_select_dropdown";
-
-describe("SelectDropdownCtrl", function() {
+import { describe, beforeEach, it, expect, angularMocks, sinon } from 'test/lib/common';
+import 'app/core/directives/value_select_dropdown';
+
+describe('SelectDropdownCtrl', function() {
   var scope;
   var ctrl;
   var tagValuesMap: any = {};
   var rootScope;
   var q;
 
-  beforeEach(angularMocks.module("grafana.core"));
+  beforeEach(angularMocks.module('grafana.core'));
   beforeEach(
     angularMocks.inject(function($controller, $rootScope, $q, $httpBackend) {
       rootScope = $rootScope;
       q = $q;
       scope = $rootScope.$new();
-      ctrl = $controller("ValueSelectDropdownCtrl", { $scope: scope });
+      ctrl = $controller('ValueSelectDropdownCtrl', { $scope: scope });
       ctrl.onUpdated = sinon.spy();
-      $httpBackend.when("GET", /\.html$/).respond("");
+      $httpBackend.when('GET', /\.html$/).respond('');
     })
   );
 
-  describe("Given simple variable", function() {
+  describe('Given simple variable', function() {
     beforeEach(function() {
       ctrl.variable = {
-        current: { text: "hej", value: "hej" },
+        current: { text: 'hej', value: 'hej' },
         getValuesForTag: function(key) {
           return q.when(tagValuesMap[key]);
-        }
+        },
       };
       ctrl.init();
     });
 
-    it("Should init labelText and linkText", function() {
-      expect(ctrl.linkText).to.be("hej");
+    it('Should init labelText and linkText', function() {
+      expect(ctrl.linkText).to.be('hej');
     });
   });
 
-  describe("Given variable with tags and dropdown is opened", function() {
+  describe('Given variable with tags and dropdown is opened', function() {
     beforeEach(function() {
       ctrl.variable = {
-        current: { text: "server-1", value: "server-1" },
+        current: { text: 'server-1', value: 'server-1' },
         options: [
-          { text: "server-1", value: "server-1", selected: true },
-          { text: "server-2", value: "server-2" },
-          { text: "server-3", value: "server-3" }
+          { text: 'server-1', value: 'server-1', selected: true },
+          { text: 'server-2', value: 'server-2' },
+          { text: 'server-3', value: 'server-3' },
         ],
-        tags: ["key1", "key2", "key3"],
+        tags: ['key1', 'key2', 'key3'],
         getValuesForTag: function(key) {
           return q.when(tagValuesMap[key]);
         },
-        multi: true
+        multi: true,
       };
-      tagValuesMap.key1 = ["server-1", "server-3"];
-      tagValuesMap.key2 = ["server-2", "server-3"];
-      tagValuesMap.key3 = ["server-1", "server-2", "server-3"];
+      tagValuesMap.key1 = ['server-1', 'server-3'];
+      tagValuesMap.key2 = ['server-2', 'server-3'];
+      tagValuesMap.key3 = ['server-1', 'server-2', 'server-3'];
       ctrl.init();
       ctrl.show();
     });
 
-    it("should init tags model", function() {
+    it('should init tags model', function() {
       expect(ctrl.tags.length).to.be(3);
-      expect(ctrl.tags[0].text).to.be("key1");
+      expect(ctrl.tags[0].text).to.be('key1');
     });
 
-    it("should init options model", function() {
+    it('should init options model', function() {
       expect(ctrl.options.length).to.be(3);
     });
 
-    it("should init selected values array", function() {
+    it('should init selected values array', function() {
       expect(ctrl.selectedValues.length).to.be(1);
     });
 
-    it("should set linkText", function() {
-      expect(ctrl.linkText).to.be("server-1");
+    it('should set linkText', function() {
+      expect(ctrl.linkText).to.be('server-1');
     });
 
-    describe("after adititional value is selected", function() {
+    describe('after adititional value is selected', function() {
       beforeEach(function() {
         ctrl.selectValue(ctrl.options[2], {});
         ctrl.commitChanges();
       });
 
-      it("should update link text", function() {
-        expect(ctrl.linkText).to.be("server-1 + server-3");
+      it('should update link text', function() {
+        expect(ctrl.linkText).to.be('server-1 + server-3');
       });
     });
 
-    describe("When tag is selected", function() {
+    describe('When tag is selected', function() {
       beforeEach(function() {
         ctrl.selectTag(ctrl.tags[0]);
         rootScope.$digest();
         ctrl.commitChanges();
       });
 
-      it("should select tag", function() {
+      it('should select tag', function() {
         expect(ctrl.selectedTags.length).to.be(1);
       });
 
-      it("should select values", function() {
+      it('should select values', function() {
         expect(ctrl.options[0].selected).to.be(true);
         expect(ctrl.options[2].selected).to.be(true);
       });
 
-      it("link text should not include tag values", function() {
-        expect(ctrl.linkText).to.be("");
+      it('link text should not include tag values', function() {
+        expect(ctrl.linkText).to.be('');
       });
 
-      describe("and then dropdown is opened and closed without changes", function() {
+      describe('and then dropdown is opened and closed without changes', function() {
         beforeEach(function() {
           ctrl.show();
           ctrl.commitChanges();
           rootScope.$digest();
         });
 
-        it("should still have selected tag", function() {
+        it('should still have selected tag', function() {
           expect(ctrl.selectedTags.length).to.be(1);
         });
       });
 
-      describe("and then unselected", function() {
+      describe('and then unselected', function() {
         beforeEach(function() {
           ctrl.selectTag(ctrl.tags[0]);
           rootScope.$digest();
         });
 
-        it("should deselect tag", function() {
+        it('should deselect tag', function() {
           expect(ctrl.selectedTags.length).to.be(0);
         });
       });
 
-      describe("and then value is unselected", function() {
+      describe('and then value is unselected', function() {
         beforeEach(function() {
           ctrl.selectValue(ctrl.options[0], {});
         });
 
-        it("should deselect tag", function() {
+        it('should deselect tag', function() {
           expect(ctrl.selectedTags.length).to.be(0);
         });
       });
     });
   });
 
-  describe("Given variable with selected tags", function() {
+  describe('Given variable with selected tags', function() {
     beforeEach(function() {
       ctrl.variable = {
         current: {
-          text: "server-1",
-          value: "server-1",
-          tags: [{ text: "key1", selected: true }]
+          text: 'server-1',
+          value: 'server-1',
+          tags: [{ text: 'key1', selected: true }],
         },
         options: [
-          { text: "server-1", value: "server-1" },
-          { text: "server-2", value: "server-2" },
-          { text: "server-3", value: "server-3" }
+          { text: 'server-1', value: 'server-1' },
+          { text: 'server-2', value: 'server-2' },
+          { text: 'server-3', value: 'server-3' },
         ],
-        tags: ["key1", "key2", "key3"],
+        tags: ['key1', 'key2', 'key3'],
         getValuesForTag: function(key) {
           return q.when(tagValuesMap[key]);
         },
-        multi: true
+        multi: true,
       };
       ctrl.init();
       ctrl.show();
     });
 
-    it("should set tag as selected", function() {
+    it('should set tag as selected', function() {
       expect(ctrl.tags[0].selected).to.be(true);
     });
   });

+ 1 - 1
public/app/core/store.ts

@@ -11,7 +11,7 @@ export class Store {
     if (def !== void 0 && !this.exists(key)) {
       return def;
     }
-    return window.localStorage[key] === "true";
+    return window.localStorage[key] === 'true';
   }
 
   exists(key) {

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott