Explorar o código

Merge branch 'master' into dashboard-search-permissions-filter

Torkel Ödegaard %!s(int64=7) %!d(string=hai) anos
pai
achega
e949eb3f58

+ 17 - 2
README.md

@@ -80,8 +80,11 @@ In your custom.ini uncomment (remove the leading `;`) sign. And set `app_mode =
 
 ### Running tests
 
-- You can run backend Golang tests using "go test ./pkg/...".
-- Execute all frontend tests with "npm run test"
+#### Frontend
+Execute all frontend tests
+```bash
+npm run test
+```
 
 Writing & watching frontend tests (we have two test runners)
 
@@ -92,6 +95,18 @@ Writing & watching frontend tests (we have two test runners)
   - Start watcher: `npm run karma`
   - Karma+Mocha runs all files that end with the name "_specs.ts".
 
+#### Backend
+```bash
+# Run Golang tests using sqlite3 as database (default)
+go test ./pkg/... 
+
+# Run Golang tests using mysql as database - convenient to use /docker/blocks/mysql_tests
+GRAFANA_TEST_DB=mysql go test ./pkg/... 
+
+# Run Golang tests using postgres as database - convenient to use /docker/blocks/postgres_tests
+GRAFANA_TEST_DB=postgres go test ./pkg/... 
+```
+
 ## Contribute
 
 If you have any idea for an improvement or found a bug, do not hesitate to open an issue.

+ 97 - 39
docs/sources/index.md

@@ -1,49 +1,107 @@
 +++
-title = "Docs Home"
-description = "Install guide for Grafana"
+title = "Grafana documentation"
+description = "Guides, Installation & Feature Documentation"
 keywords = ["grafana", "installation", "documentation"]
 type = "docs"
 aliases = ["v1.1", "guides/reference/admin"]
 +++
 
-# Welcome to the Grafana Documentation
+# Grafana Documentation
 
-Grafana is an open source metric analytics & visualization suite. It is most commonly used for
-visualizing time series data for infrastructure and application analytics but many use it in
-other domains including industrial sensors, home automation, weather, and process control.
+<h2>Installing Grafana</h2>
+<div class="nav-cards">
+    <a href="{{< relref "installation/debian.md" >}}" class="nav-cards__item nav-cards__item--install">
+        <div class="nav-cards__icon fa fa-linux">
+        </div>
+        <h5>Installing on Linux</h5>
+    </a>
+    <a href="{{< relref "installation/mac.md" >}}" class="nav-cards__item nav-cards__item--install">
+        <div class="nav-cards__icon fa fa-apple">
+        </div>
+        <h5>Installing on Mac OS X</h5>
+    </a>
+      <a href="{{< relref "installation/windows.md" >}}" class="nav-cards__item nav-cards__item--install">
+        <div class="nav-cards__icon fa fa-windows">
+        </div>
+        <h5>Installing on Windows</h5>
+    </a>
+    <a href="https://grafana.com/cloud/grafana" class="nav-cards__item nav-cards__item--install">
+        <div class="nav-cards__icon fa fa-cloud">
+        </div>
+        <h5>Grafana Cloud</h5>
+    </a>
+    <a href="https://grafana.com/grafana/download" class="nav-cards__item nav-cards__item--install">
+        <div class="nav-cards__icon fa fa-moon-o">
+        </div>
+        <h5>Nightly Builds</h5>
+    </a>
+    <div class="nav-cards__item nav-cards__item--install">
+        <h5>For other platforms Read the <a href="{{< relref "project/building_from_source.md" >}}">build from source</a>
+        instructions for more information.</h5>
+    </div>
+</div>
 
-## Installing Grafana
-- [Installing on Debian / Ubuntu](installation/debian)
-- [Installing on RPM-based Linux (CentOS, Fedora, OpenSuse, RedHat)](installation/rpm)
-- [Installing on Mac OS X](installation/mac)
-- [Installing on Windows](installation/windows)
-- [Installing on Docker](installation/docker)
-- [Installing using Provisioning (Chef, Puppet, Salt, Ansible, etc)](administration/provisioning#configuration-management-tools)
-- [Nightly Builds](https://grafana.com/grafana/download)
+<h2>Guides</h2>
 
-For other platforms Read the [build from source]({{< relref "project/building_from_source.md" >}})
-instructions for more information.
+<div class="nav-cards">
+    <a href="https://grafana.com/grafana" class="nav-cards__item nav-cards__item--guide">
+        <h4>What is Grafana?</h4>
+        <p>Grafana feature highlights.</p>
+    </a>
+    <a href="{{< relref "installation/configuration.md" >}}" class="nav-cards__item nav-cards__item--guide">
+        <h4>Configure Grafana</h4>
+        <p>Article on all the Grafana configuration and setup options.</p>
+    </a>
+    <a href="{{< relref "guides/getting_started.md" >}}" class="nav-cards__item nav-cards__item--guide">
+        <h4>Getting Started</h4>
+        <p>A guide that walks you through the basics of using Grafana</p>
+    </a>
+    <a href="{{< relref "administration/provisioning.md" >}}" class="nav-cards__item nav-cards__item--guide">
+        <h4>Provisioning</h4>
+        <p>A guide to help you automate your Grafana setup & configuration.</p>
+    </a>
+    <a href="{{< relref "guides/whats-new-in-v5.md" >}}" class="nav-cards__item nav-cards__item--guide">
+        <h4>What's new in v5.0</h4>
+        <p>Article on all the new cool features and enhancements in v5.0</p>
+    </a>
+    <a href="{{< relref "tutorials/screencasts.md" >}}" class="nav-cards__item nav-cards__item--guide">
+        <h4>Screencasts</h4>
+        <p>Video tutorials & guides</p>
+    </a>
+</div>
 
-## Configuring Grafana
-
-The back-end web server has a number of configuration options. Go the
-[Configuration]({{< relref "installation/configuration.md" >}}) page for details on all
-those options.
-
-
-## Getting Started
-
-- [Getting Started]({{< relref "guides/getting_started.md" >}})
-- [Basic Concepts]({{< relref "guides/basic_concepts.md" >}})
-- [Screencasts]({{< relref "tutorials/screencasts.md" >}})
-
-## Data Source Guides
-
-- [Graphite]({{< relref "features/datasources/graphite.md" >}})
-- [Elasticsearch]({{< relref "features/datasources/elasticsearch.md" >}})
-- [InfluxDB]({{< relref "features/datasources/influxdb.md" >}})
-- [Prometheus]({{< relref "features/datasources/prometheus.md" >}})
-- [OpenTSDB]({{< relref "features/datasources/opentsdb.md" >}})
-- [MySQL]({{< relref "features/datasources/mysql.md" >}})
-- [Postgres]({{< relref "features/datasources/postgres.md" >}})
-- [Cloudwatch]({{< relref "features/datasources/cloudwatch.md" >}})
+<h2>Data Source Guides</h2>
+<div class="nav-cards">
+    <a href="{{< relref "features/datasources/graphite.md" >}}" class="nav-cards__item nav-cards__item--ds">
+      <img src="/img/docs/logos/icon_graphite.svg" >
+      <h5>Graphite</h5>
+    </a>
+    <a href="{{< relref "features/datasources/elasticsearch.md" >}}" class="nav-cards__item nav-cards__item--ds">
+      <img src="/img/docs/logos/icon_elasticsearch.svg" >
+      <h5>Elasticsearch</h5>
+    </a>
+    <a href="{{< relref "features/datasources/influxdb.md" >}}" class="nav-cards__item nav-cards__item--ds">
+      <img src="/img/docs/logos/icon_influxdb.svg" >
+      <h5>InfluxDB</h5>
+    </a>
+    <a href="{{< relref "features/datasources/prometheus.md" >}}" class="nav-cards__item nav-cards__item--ds">
+      <img src="/img/docs/logos/icon_prometheus.svg" >
+      <h5>Prometheus</h5>
+    </a>
+    <a href="{{< relref "features/datasources/opentsdb.md" >}}" class="nav-cards__item nav-cards__item--ds">
+      <img src="/img/docs/logos/icon_opentsdb.png" >
+      <h5>OpenTSDB</h5>
+    </a>
+    <a href="{{< relref "features/datasources/mysql.md" >}}" class="nav-cards__item nav-cards__item--ds">
+      <img src="/img/docs/logos/icon_mysql.png" >
+      <h5>MySQL</h5>
+    </a>
+    <a href="{{< relref "features/datasources/postgres.md" >}}" class="nav-cards__item nav-cards__item--ds">
+      <img src="/img/docs/logos/icon_postgres.svg" >
+      <h5>Postgres</h5>
+    </a>
+    <a href="{{< relref "features/datasources/cloudwatch.md" >}}" class="nav-cards__item nav-cards__item--ds">
+      <img src="/img/docs/logos/icon_cloudwatch.svg">
+      <h5>Cloudwatch</h5>
+    </a>
+</div>

+ 10 - 2
pkg/api/dashboard.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"os"
 	"path"
+	"strings"
 
 	"github.com/grafana/grafana/pkg/services/dashboards"
 
@@ -217,6 +218,10 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
 		return ApiError(400, m.ErrDashboardTitleEmpty.Error(), nil)
 	}
 
+	if dash.IsFolder && strings.ToLower(dash.Title) == strings.ToLower(m.RootFolderName) {
+		return ApiError(400, "A folder already exists with that name", nil)
+	}
+
 	if dash.Id == 0 {
 		limitReached, err := middleware.QuotaReached(c, "dashboard")
 		if err != nil {
@@ -237,8 +242,11 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
 
 	dashboard, err := dashboards.GetRepository().SaveDashboard(dashItem)
 
-	if err == m.ErrDashboardTitleEmpty {
-		return ApiError(400, m.ErrDashboardTitleEmpty.Error(), nil)
+	if err == m.ErrDashboardTitleEmpty ||
+		err == m.ErrDashboardWithSameNameAsFolder ||
+		err == m.ErrDashboardFolderWithSameNameAsDashboard ||
+		err == m.ErrDashboardTypeMismatch {
+		return ApiError(400, err.Error(), nil)
 	}
 
 	if err == m.ErrDashboardContainsInvalidAlertData {

+ 11 - 7
pkg/api/org_users.go

@@ -46,26 +46,30 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
 
 // GET /api/org/users
 func GetOrgUsersForCurrentOrg(c *middleware.Context) Response {
-	return getOrgUsersHelper(c.OrgId)
+	return getOrgUsersHelper(c.OrgId, c.Params("query"), c.ParamsInt("limit"))
 }
 
 // GET /api/orgs/:orgId/users
 func GetOrgUsers(c *middleware.Context) Response {
-	return getOrgUsersHelper(c.ParamsInt64(":orgId"))
+	return getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0)
 }
 
-func getOrgUsersHelper(orgId int64) Response {
-	query := m.GetOrgUsersQuery{OrgId: orgId}
+func getOrgUsersHelper(orgId int64, query string, limit int) Response {
+	q := m.GetOrgUsersQuery{
+		OrgId: orgId,
+		Query: query,
+		Limit: limit,
+	}
 
-	if err := bus.Dispatch(&query); err != nil {
+	if err := bus.Dispatch(&q); err != nil {
 		return ApiError(500, "Failed to get account user", err)
 	}
 
-	for _, user := range query.Result {
+	for _, user := range q.Result {
 		user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
 	}
 
-	return Json(200, query.Result)
+	return Json(200, q.Result)
 }
 
 // PATCH /api/org/users/:userId

+ 27 - 19
pkg/models/dashboards.go

@@ -13,17 +13,22 @@ import (
 
 // Typed errors
 var (
-	ErrDashboardNotFound                   = errors.New("Dashboard not found")
-	ErrDashboardSnapshotNotFound           = errors.New("Dashboard snapshot not found")
-	ErrDashboardWithSameUIDExists          = errors.New("A dashboard with the same uid already exists")
-	ErrDashboardWithSameNameInFolderExists = errors.New("A dashboard with the same name in the folder already exists")
-	ErrDashboardVersionMismatch            = errors.New("The dashboard has been changed by someone else")
-	ErrDashboardTitleEmpty                 = errors.New("Dashboard title cannot be empty")
-	ErrDashboardFolderCannotHaveParent     = errors.New("A Dashboard Folder cannot be added to another folder")
-	ErrDashboardContainsInvalidAlertData   = errors.New("Invalid alert data. Cannot save dashboard")
-	ErrDashboardFailedToUpdateAlertData    = errors.New("Failed to save alert data")
-	ErrDashboardsWithSameSlugExists        = errors.New("Multiple dashboards with the same slug exists")
-	ErrDashboardFailedGenerateUniqueUid    = errors.New("Failed to generate unique dashboard id")
+	ErrDashboardNotFound                        = errors.New("Dashboard not found")
+	ErrDashboardSnapshotNotFound                = errors.New("Dashboard snapshot not found")
+	ErrDashboardWithSameUIDExists               = errors.New("A dashboard with the same uid already exists")
+	ErrDashboardWithSameNameInFolderExists      = errors.New("A dashboard with the same name in the folder already exists")
+	ErrDashboardVersionMismatch                 = errors.New("The dashboard has been changed by someone else")
+	ErrDashboardTitleEmpty                      = errors.New("Dashboard title cannot be empty")
+	ErrDashboardFolderCannotHaveParent          = errors.New("A Dashboard Folder cannot be added to another folder")
+	ErrDashboardContainsInvalidAlertData        = errors.New("Invalid alert data. Cannot save dashboard")
+	ErrDashboardFailedToUpdateAlertData         = errors.New("Failed to save alert data")
+	ErrDashboardsWithSameSlugExists             = errors.New("Multiple dashboards with the same slug exists")
+	ErrDashboardFailedGenerateUniqueUid         = errors.New("Failed to generate unique dashboard id")
+	ErrDashboardExistingCannotChangeToDashboard = errors.New("An existing folder cannot be changed to a dashboard")
+	ErrDashboardTypeMismatch                    = errors.New("Dashboard cannot be changed to a folder")
+	ErrDashboardFolderWithSameNameAsDashboard   = errors.New("Folder name cannot be the same as one of its dashboards")
+	ErrDashboardWithSameNameAsFolder            = errors.New("Dashboard name cannot be the same as folder")
+	RootFolderName                              = "General"
 )
 
 type UpdatePluginDashboardError struct {
@@ -95,14 +100,21 @@ func NewDashboardFromJson(data *simplejson.Json) *Dashboard {
 	dash.Data = data
 	dash.Title = dash.Data.Get("title").MustString()
 	dash.UpdateSlug()
+	update := false
 
 	if id, err := dash.Data.Get("id").Float64(); err == nil {
 		dash.Id = int64(id)
+		update = true
+	}
+
+	if uid, err := dash.Data.Get("uid").String(); err == nil {
+		dash.Uid = uid
+		update = true
+	}
 
-		if version, err := dash.Data.Get("version").Float64(); err == nil {
-			dash.Version = int(version)
-			dash.Updated = time.Now()
-		}
+	if version, err := dash.Data.Get("version").Float64(); err == nil && update {
+		dash.Version = int(version)
+		dash.Updated = time.Now()
 	} else {
 		dash.Data.Set("version", 0)
 		dash.Created = time.Now()
@@ -113,10 +125,6 @@ func NewDashboardFromJson(data *simplejson.Json) *Dashboard {
 		dash.GnetId = int64(gnetId)
 	}
 
-	if uid, err := dash.Data.Get("uid").String(); err == nil {
-		dash.Uid = uid
-	}
-
 	return dash
 }
 

+ 4 - 1
pkg/models/org_user.go

@@ -95,7 +95,10 @@ type UpdateOrgUserCommand struct {
 // QUERIES
 
 type GetOrgUsersQuery struct {
-	OrgId  int64
+	OrgId int64
+	Query string
+	Limit int
+
 	Result []*OrgUserDTO
 }
 

+ 1 - 0
pkg/plugins/dashboard_importer.go

@@ -82,6 +82,7 @@ func ImportDashboard(cmd *ImportDashboardCommand) error {
 		Path:             cmd.Path,
 		Revision:         dashboard.Data.Get("revision").MustInt64(1),
 		ImportedUri:      "db/" + saveCmd.Result.Slug,
+		ImportedUrl:      saveCmd.Result.GetUrl(),
 		ImportedRevision: dashboard.Data.Get("revision").MustInt64(1),
 		Imported:         true,
 	}

+ 2 - 0
pkg/plugins/dashboards.go

@@ -14,6 +14,7 @@ type PluginDashboardInfoDTO struct {
 	Title            string `json:"title"`
 	Imported         bool   `json:"imported"`
 	ImportedUri      string `json:"importedUri"`
+	ImportedUrl      string `json:"importedUrl"`
 	Slug             string `json:"slug"`
 	DashboardId      int64  `json:"dashboardId"`
 	ImportedRevision int64  `json:"importedRevision"`
@@ -64,6 +65,7 @@ func GetPluginDashboards(orgId int64, pluginId string) ([]*PluginDashboardInfoDT
 				res.DashboardId = existingDash.Id
 				res.Imported = true
 				res.ImportedUri = "db/" + existingDash.Slug
+				res.ImportedUrl = existingDash.GetUrl()
 				res.ImportedRevision = existingDash.Data.Get("revision").MustInt64(1)
 				existingMatches[existingDash.Id] = true
 			}

+ 90 - 59
pkg/services/sqlstore/dashboard.go

@@ -32,47 +32,36 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
 	return inTransaction(func(sess *DBSession) error {
 		dash := cmd.GetDashboardModel()
 
-		// try get existing dashboard
-		var existing m.Dashboard
+		if err := getExistingDashboardForUpdate(sess, dash, cmd); err != nil {
+			return err
+		}
 
-		if dash.Id != 0 {
-			dashWithIdExists, err := sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existing)
-			if err != nil {
-				return err
-			}
-			if !dashWithIdExists {
-				return m.ErrDashboardNotFound
-			}
+		var existingByTitleAndFolder m.Dashboard
 
-			// check for is someone else has written in between
-			if dash.Version != existing.Version {
-				if cmd.Overwrite {
-					dash.Version = existing.Version
-				} else {
-					return m.ErrDashboardVersionMismatch
+		dashWithTitleAndFolderExists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug, dialect.BooleanStr(true), dash.FolderId).Get(&existingByTitleAndFolder)
+		if err != nil {
+			return err
+		}
+
+		if dashWithTitleAndFolderExists {
+			if dash.Id != existingByTitleAndFolder.Id {
+				if existingByTitleAndFolder.IsFolder && !cmd.IsFolder {
+					return m.ErrDashboardWithSameNameAsFolder
 				}
-			}
 
-			// do not allow plugin dashboard updates without overwrite flag
-			if existing.PluginId != "" && cmd.Overwrite == false {
-				return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
-			}
-		} else if dash.Uid != "" {
-			var sameUid m.Dashboard
-			sameUidExists, err := sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&sameUid)
-			if err != nil {
-				return err
-			}
+				if !existingByTitleAndFolder.IsFolder && cmd.IsFolder {
+					return m.ErrDashboardFolderWithSameNameAsDashboard
+				}
 
-			if sameUidExists {
-				// another dashboard with same uid
-				if dash.Id != sameUid.Id {
-					if cmd.Overwrite {
-						dash.Id = sameUid.Id
-						dash.Version = sameUid.Version
-					} else {
-						return m.ErrDashboardWithSameUIDExists
+				if cmd.Overwrite {
+					dash.Id = existingByTitleAndFolder.Id
+					dash.Version = existingByTitleAndFolder.Version
+
+					if dash.Uid == "" {
+						dash.Uid = existingByTitleAndFolder.Uid
 					}
+				} else {
+					return m.ErrDashboardWithSameNameInFolderExists
 				}
 			}
 		}
@@ -86,11 +75,6 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
 			dash.Data.Set("uid", uid)
 		}
 
-		err := guaranteeDashboardNameIsUniqueInFolder(sess, dash)
-		if err != nil {
-			return err
-		}
-
 		err = setHasAcl(sess, dash)
 		if err != nil {
 			return err
@@ -162,6 +146,72 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
 	})
 }
 
+func getExistingDashboardForUpdate(sess *DBSession, dash *m.Dashboard, cmd *m.SaveDashboardCommand) (err error) {
+	dashWithIdExists := false
+	var existingById m.Dashboard
+
+	if dash.Id > 0 {
+		dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById)
+		if err != nil {
+			return err
+		}
+
+		if !dashWithIdExists {
+			return m.ErrDashboardNotFound
+		}
+
+		if dash.Uid == "" {
+			dash.Uid = existingById.Uid
+		}
+	}
+
+	dashWithUidExists := false
+	var existingByUid m.Dashboard
+
+	if dash.Uid != "" {
+		dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid)
+		if err != nil {
+			return err
+		}
+	}
+
+	if !dashWithIdExists && !dashWithUidExists {
+		return nil
+	}
+
+	if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
+		return m.ErrDashboardWithSameUIDExists
+	}
+
+	existing := existingById
+
+	if !dashWithIdExists && dashWithUidExists {
+		dash.Id = existingByUid.Id
+		existing = existingByUid
+	}
+
+	if (existing.IsFolder && !cmd.IsFolder) ||
+		(!existing.IsFolder && cmd.IsFolder) {
+		return m.ErrDashboardTypeMismatch
+	}
+
+	// check for is someone else has written in between
+	if dash.Version != existing.Version {
+		if cmd.Overwrite {
+			dash.Version = existing.Version
+		} else {
+			return m.ErrDashboardVersionMismatch
+		}
+	}
+
+	// do not allow plugin dashboard updates without overwrite flag
+	if existing.PluginId != "" && cmd.Overwrite == false {
+		return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
+	}
+
+	return nil
+}
+
 func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
 	for i := 0; i < 3; i++ {
 		uid := generateNewUid()
@@ -179,23 +229,6 @@ func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
 	return "", m.ErrDashboardFailedGenerateUniqueUid
 }
 
-func guaranteeDashboardNameIsUniqueInFolder(sess *DBSession, dash *m.Dashboard) error {
-	var sameNameInFolder m.Dashboard
-	sameNameInFolderExist, err := sess.Where("org_id=? AND title=? AND folder_id = ? AND uid <> ?",
-		dash.OrgId, dash.Title, dash.FolderId, dash.Uid).
-		Get(&sameNameInFolder)
-
-	if err != nil {
-		return err
-	}
-
-	if sameNameInFolderExist {
-		return m.ErrDashboardWithSameNameInFolderExists
-	}
-
-	return nil
-}
-
 func setHasAcl(sess *DBSession, dash *m.Dashboard) error {
 	// check if parent has acl
 	if dash.FolderId > 0 {
@@ -472,9 +505,7 @@ func GetDashboardPermissionsForUser(query *m.GetDashboardPermissionsForUserQuery
 	params = append(params, query.UserId)
 	params = append(params, dialect.BooleanStr(false))
 
-	x.ShowSQL(true)
 	err := x.Sql(sql, params...).Find(&query.Result)
-	x.ShowSQL(false)
 
 	for _, p := range query.Result {
 		p.PermissionName = p.Permission.String()

+ 226 - 94
pkg/services/sqlstore/dashboard_test.go

@@ -100,7 +100,7 @@ func TestDashboardDataAccess(t *testing.T) {
 				So(err, ShouldBeNil)
 			})
 
-			Convey("Should return error if no dashboard is updated", func() {
+			Convey("Should return not found error if no dashboard is found for update", func() {
 				cmd := m.SaveDashboardCommand{
 					OrgId:     1,
 					Overwrite: true,
@@ -112,7 +112,7 @@ func TestDashboardDataAccess(t *testing.T) {
 				}
 
 				err := SaveDashboard(&cmd)
-				So(err, ShouldNotBeNil)
+				So(err, ShouldEqual, m.ErrDashboardNotFound)
 			})
 
 			Convey("Should not be able to overwrite dashboard in another org", func() {
@@ -130,108 +130,171 @@ func TestDashboardDataAccess(t *testing.T) {
 				}
 
 				err := SaveDashboard(&cmd)
-				So(err, ShouldNotBeNil)
+				So(err, ShouldEqual, m.ErrDashboardNotFound)
 			})
 
-			Convey("Should be able to search for dashboard folder", func() {
-				query := search.FindPersistedDashboardsQuery{
-					Title:        "1 test dash folder",
-					OrgId:        1,
-					SignedInUser: &m.SignedInUser{OrgId: 1},
+			Convey("Should be able to save dashboards with same name in different folders", func() {
+				firstSaveCmd := m.SaveDashboardCommand{
+					OrgId: 1,
+					Dashboard: simplejson.NewFromAny(map[string]interface{}{
+						"id":    nil,
+						"title": "test dash folder and title",
+						"tags":  []interface{}{},
+						"uid":   "randomHash",
+					}),
+					FolderId: 3,
 				}
 
-				err := SearchDashboards(&query)
+				err := SaveDashboard(&firstSaveCmd)
 				So(err, ShouldBeNil)
 
-				So(len(query.Result), ShouldEqual, 1)
-				hit := query.Result[0]
-				So(hit.Type, ShouldEqual, search.DashHitFolder)
-				So(hit.Url, ShouldEqual, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug))
-				So(hit.FolderTitle, ShouldEqual, "")
+				secondSaveCmd := m.SaveDashboardCommand{
+					OrgId: 1,
+					Dashboard: simplejson.NewFromAny(map[string]interface{}{
+						"id":    nil,
+						"title": "test dash folder and title",
+						"tags":  []interface{}{},
+						"uid":   "moreRandomHash",
+					}),
+					FolderId: 1,
+				}
+
+				err = SaveDashboard(&secondSaveCmd)
+				So(err, ShouldBeNil)
+				So(firstSaveCmd.Result.Id, ShouldNotEqual, secondSaveCmd.Result.Id)
 			})
 
-			Convey("Should be able to search for a dashboard folder's children", func() {
-				query := search.FindPersistedDashboardsQuery{
-					OrgId:        1,
-					FolderIds:    []int64{savedFolder.Id},
-					SignedInUser: &m.SignedInUser{OrgId: 1},
+			Convey("Should be able to overwrite dashboard in same folder using title", func() {
+				insertTestDashboard("Dash", 1, 0, false, "prod", "webapp")
+				folder := insertTestDashboard("Folder", 1, 0, true, "prod", "webapp")
+				dashInFolder := insertTestDashboard("Dash", 1, folder.Id, false, "prod", "webapp")
+
+				cmd := m.SaveDashboardCommand{
+					OrgId: 1,
+					Dashboard: simplejson.NewFromAny(map[string]interface{}{
+						"title": "Dash",
+					}),
+					FolderId:  folder.Id,
+					Overwrite: true,
 				}
 
-				err := SearchDashboards(&query)
+				err := SaveDashboard(&cmd)
 				So(err, ShouldBeNil)
+				So(cmd.Result.Id, ShouldEqual, dashInFolder.Id)
+				So(cmd.Result.Uid, ShouldEqual, dashInFolder.Uid)
+			})
 
-				So(len(query.Result), ShouldEqual, 2)
-				hit := query.Result[0]
-				So(hit.Id, ShouldEqual, savedDash.Id)
-				So(hit.Url, ShouldEqual, fmt.Sprintf("/d/%s/%s", savedDash.Uid, savedDash.Slug))
-				So(hit.FolderId, ShouldEqual, savedFolder.Id)
-				So(hit.FolderUid, ShouldEqual, savedFolder.Uid)
-				So(hit.FolderTitle, ShouldEqual, savedFolder.Title)
-				So(hit.FolderUrl, ShouldEqual, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug))
+			Convey("Should be able to overwrite dashboard in General folder using title", func() {
+				dashInGeneral := insertTestDashboard("Dash", 1, 0, false, "prod", "webapp")
+				folder := insertTestDashboard("Folder", 1, 0, true, "prod", "webapp")
+				insertTestDashboard("Dash", 1, folder.Id, false, "prod", "webapp")
+
+				cmd := m.SaveDashboardCommand{
+					OrgId: 1,
+					Dashboard: simplejson.NewFromAny(map[string]interface{}{
+						"title": "Dash",
+					}),
+					FolderId:  0,
+					Overwrite: true,
+				}
+
+				err := SaveDashboard(&cmd)
+				So(err, ShouldBeNil)
+				So(cmd.Result.Id, ShouldEqual, dashInGeneral.Id)
+				So(cmd.Result.Uid, ShouldEqual, dashInGeneral.Uid)
 			})
 
-			Convey("Should be able to search for dashboard by dashboard ids", func() {
-				Convey("should be able to find two dashboards by id", func() {
-					query := search.FindPersistedDashboardsQuery{
-						DashboardIds: []int64{2, 3},
-						SignedInUser: &m.SignedInUser{OrgId: 1},
-					}
+			Convey("Should not be able to overwrite folder with dashboard in general folder using title", func() {
+				cmd := m.SaveDashboardCommand{
+					OrgId: 1,
+					Dashboard: simplejson.NewFromAny(map[string]interface{}{
+						"title": savedFolder.Title,
+					}),
+					FolderId:  0,
+					IsFolder:  false,
+					Overwrite: true,
+				}
 
-					err := SearchDashboards(&query)
-					So(err, ShouldBeNil)
+				err := SaveDashboard(&cmd)
+				So(err, ShouldEqual, m.ErrDashboardWithSameNameAsFolder)
+			})
 
-					So(len(query.Result), ShouldEqual, 2)
+			Convey("Should not be able to overwrite folder with dashboard in folder using title", func() {
+				cmd := m.SaveDashboardCommand{
+					OrgId: 1,
+					Dashboard: simplejson.NewFromAny(map[string]interface{}{
+						"title": savedFolder.Title,
+					}),
+					FolderId:  savedFolder.Id,
+					IsFolder:  false,
+					Overwrite: true,
+				}
 
-					hit := query.Result[0]
-					So(len(hit.Tags), ShouldEqual, 2)
+				err := SaveDashboard(&cmd)
+				So(err, ShouldEqual, m.ErrDashboardWithSameNameAsFolder)
+			})
 
-					hit2 := query.Result[1]
-					So(len(hit2.Tags), ShouldEqual, 1)
-				})
+			Convey("Should not be able to overwrite folder with dashboard using id", func() {
+				cmd := m.SaveDashboardCommand{
+					OrgId: 1,
+					Dashboard: simplejson.NewFromAny(map[string]interface{}{
+						"id":    savedFolder.Id,
+						"title": "new title",
+					}),
+					IsFolder:  false,
+					Overwrite: true,
+				}
 
-				Convey("DashboardIds that does not exists should not cause errors", func() {
-					query := search.FindPersistedDashboardsQuery{
-						DashboardIds: []int64{1000},
-						SignedInUser: &m.SignedInUser{OrgId: 1},
-					}
+				err := SaveDashboard(&cmd)
+				So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
+			})
 
-					err := SearchDashboards(&query)
-					So(err, ShouldBeNil)
-					So(len(query.Result), ShouldEqual, 0)
-				})
+			Convey("Should not be able to overwrite dashboard with folder using id", func() {
+				cmd := m.SaveDashboardCommand{
+					OrgId: 1,
+					Dashboard: simplejson.NewFromAny(map[string]interface{}{
+						"id":    savedDash.Id,
+						"title": "new folder title",
+					}),
+					IsFolder:  true,
+					Overwrite: true,
+				}
+
+				err := SaveDashboard(&cmd)
+				So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
 			})
 
-			Convey("Should be able to save dashboards with same name in different folders", func() {
-				firstSaveCmd := m.SaveDashboardCommand{
+			Convey("Should not be able to overwrite folder with dashboard using uid", func() {
+				cmd := m.SaveDashboardCommand{
 					OrgId: 1,
 					Dashboard: simplejson.NewFromAny(map[string]interface{}{
-						"id":    nil,
-						"title": "test dash folder and title",
-						"tags":  []interface{}{},
-						"uid":   "randomHash",
+						"uid":   savedFolder.Uid,
+						"title": "new title",
 					}),
-					FolderId: 3,
+					IsFolder:  false,
+					Overwrite: true,
 				}
 
-				err := SaveDashboard(&firstSaveCmd)
-				So(err, ShouldBeNil)
+				err := SaveDashboard(&cmd)
+				So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
+			})
 
-				secondSaveCmd := m.SaveDashboardCommand{
+			Convey("Should not be able to overwrite dashboard with folder using uid", func() {
+				cmd := m.SaveDashboardCommand{
 					OrgId: 1,
 					Dashboard: simplejson.NewFromAny(map[string]interface{}{
-						"id":    nil,
-						"title": "test dash folder and title",
-						"tags":  []interface{}{},
-						"uid":   "moreRandomHash",
+						"uid":   savedDash.Uid,
+						"title": "new folder title",
 					}),
-					FolderId: 1,
+					IsFolder:  true,
+					Overwrite: true,
 				}
 
-				err = SaveDashboard(&secondSaveCmd)
-				So(err, ShouldBeNil)
+				err := SaveDashboard(&cmd)
+				So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
 			})
 
-			Convey("Should not be able to save dashboard with same name in the same folder", func() {
+			Convey("Should not be able to save dashboard with same name in the same folder without overwrite", func() {
 				firstSaveCmd := m.SaveDashboardCommand{
 					OrgId: 1,
 					Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -261,20 +324,49 @@ func TestDashboardDataAccess(t *testing.T) {
 				So(err, ShouldEqual, m.ErrDashboardWithSameNameInFolderExists)
 			})
 
-			Convey("Should not be able to save dashboard with same uid", func() {
+			Convey("Should be able to save and update dashboard using same uid", func() {
 				cmd := m.SaveDashboardCommand{
 					OrgId: 1,
 					Dashboard: simplejson.NewFromAny(map[string]interface{}{
 						"id":    nil,
-						"title": "test dash 23",
 						"uid":   "dsfalkjngailuedt",
+						"title": "test dash 23",
 					}),
 				}
 
 				err := SaveDashboard(&cmd)
 				So(err, ShouldBeNil)
 				err = SaveDashboard(&cmd)
-				So(err, ShouldNotBeNil)
+				So(err, ShouldBeNil)
+			})
+
+			Convey("Should be able to update dashboard using uid", func() {
+				cmd := m.SaveDashboardCommand{
+					OrgId: 1,
+					Dashboard: simplejson.NewFromAny(map[string]interface{}{
+						"uid":   savedDash.Uid,
+						"title": "new title",
+					}),
+					FolderId:  0,
+					Overwrite: true,
+				}
+
+				err := SaveDashboard(&cmd)
+				So(err, ShouldBeNil)
+
+				Convey("Should be able to get updated dashboard by uid", func() {
+					query := m.GetDashboardQuery{
+						Uid:   savedDash.Uid,
+						OrgId: 1,
+					}
+
+					err := GetDashboard(&query)
+					So(err, ShouldBeNil)
+
+					So(query.Result.Id, ShouldEqual, savedDash.Id)
+					So(query.Result.Title, ShouldEqual, "new title")
+					So(query.Result.FolderId, ShouldEqual, 0)
+				})
 			})
 
 			Convey("Should be able to update dashboard with the same title and folder id", func() {
@@ -310,7 +402,7 @@ func TestDashboardDataAccess(t *testing.T) {
 				So(err, ShouldBeNil)
 			})
 
-			Convey("Should not be able to update using just uid", func() {
+			Convey("Should be able to update using uid without id and overwrite", func() {
 				cmd := m.SaveDashboardCommand{
 					OrgId: 1,
 					Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -322,23 +414,6 @@ func TestDashboardDataAccess(t *testing.T) {
 					FolderId: savedDash.FolderId,
 				}
 
-				err := SaveDashboard(&cmd)
-				So(err, ShouldEqual, m.ErrDashboardWithSameUIDExists)
-			})
-
-			Convey("Should be able to update using just uid with overwrite", func() {
-				cmd := m.SaveDashboardCommand{
-					OrgId: 1,
-					Dashboard: simplejson.NewFromAny(map[string]interface{}{
-						"uid":     savedDash.Uid,
-						"title":   "folderId",
-						"version": savedDash.Version,
-						"tags":    []interface{}{},
-					}),
-					FolderId:  savedDash.FolderId,
-					Overwrite: true,
-				}
-
 				err := SaveDashboard(&cmd)
 				So(err, ShouldBeNil)
 			})
@@ -367,11 +442,11 @@ func TestDashboardDataAccess(t *testing.T) {
 				generateNewUid = util.GenerateShortUid
 			})
 
-			Convey("Should be able to update dashboard and remove folderId", func() {
+			Convey("Should be able to update dashboard by id and remove folderId", func() {
 				cmd := m.SaveDashboardCommand{
 					OrgId: 1,
 					Dashboard: simplejson.NewFromAny(map[string]interface{}{
-						"id":    1,
+						"id":    savedDash.Id,
 						"title": "folderId",
 						"tags":  []interface{}{},
 					}),
@@ -386,7 +461,7 @@ func TestDashboardDataAccess(t *testing.T) {
 				cmd = m.SaveDashboardCommand{
 					OrgId: 1,
 					Dashboard: simplejson.NewFromAny(map[string]interface{}{
-						"id":    1,
+						"id":    savedDash.Id,
 						"title": "folderId",
 						"tags":  []interface{}{},
 					}),
@@ -398,7 +473,7 @@ func TestDashboardDataAccess(t *testing.T) {
 				So(err, ShouldBeNil)
 
 				query := m.GetDashboardQuery{
-					Slug:  cmd.Result.Slug,
+					Id:    savedDash.Id,
 					OrgId: 1,
 				}
 
@@ -433,6 +508,63 @@ func TestDashboardDataAccess(t *testing.T) {
 				So(len(query.Result), ShouldEqual, 2)
 			})
 
+			Convey("Should be able to search for dashboard folder", func() {
+				query := search.FindPersistedDashboardsQuery{
+					Title:        "1 test dash folder",
+					OrgId:        1,
+					SignedInUser: &m.SignedInUser{OrgId: 1},
+				}
+
+				err := SearchDashboards(&query)
+				So(err, ShouldBeNil)
+
+				So(len(query.Result), ShouldEqual, 1)
+				hit := query.Result[0]
+				So(hit.Type, ShouldEqual, search.DashHitFolder)
+				So(hit.Url, ShouldEqual, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug))
+				So(hit.FolderTitle, ShouldEqual, "")
+			})
+
+			Convey("Should be able to search for a dashboard folder's children", func() {
+				query := search.FindPersistedDashboardsQuery{
+					OrgId:        1,
+					FolderIds:    []int64{savedFolder.Id},
+					SignedInUser: &m.SignedInUser{OrgId: 1},
+				}
+
+				err := SearchDashboards(&query)
+				So(err, ShouldBeNil)
+
+				So(len(query.Result), ShouldEqual, 2)
+				hit := query.Result[0]
+				So(hit.Id, ShouldEqual, savedDash.Id)
+				So(hit.Url, ShouldEqual, fmt.Sprintf("/d/%s/%s", savedDash.Uid, savedDash.Slug))
+				So(hit.FolderId, ShouldEqual, savedFolder.Id)
+				So(hit.FolderUid, ShouldEqual, savedFolder.Uid)
+				So(hit.FolderTitle, ShouldEqual, savedFolder.Title)
+				So(hit.FolderUrl, ShouldEqual, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug))
+			})
+
+			Convey("Should be able to search for dashboard by dashboard ids", func() {
+				Convey("should be able to find two dashboards by id", func() {
+					query := search.FindPersistedDashboardsQuery{
+						DashboardIds: []int64{2, 3},
+						SignedInUser: &m.SignedInUser{OrgId: 1},
+					}
+
+					err := SearchDashboards(&query)
+					So(err, ShouldBeNil)
+
+					So(len(query.Result), ShouldEqual, 2)
+
+					hit := query.Result[0]
+					So(len(hit.Tags), ShouldEqual, 2)
+
+					hit2 := query.Result[1]
+					So(len(hit2.Tags), ShouldEqual, 1)
+				})
+			})
+
 			Convey("Given two dashboards, one is starred dashboard by user 10, other starred by user 1", func() {
 				starredDash := insertTestDashboard("starred dash", 1, 0, false)
 				StarDashboard(&m.StarDashboardCommand{

+ 28 - 3
pkg/services/sqlstore/datasource_test.go

@@ -1,6 +1,8 @@
 package sqlstore
 
 import (
+	"os"
+	"strings"
 	"testing"
 
 	"github.com/go-xorm/xorm"
@@ -11,10 +13,33 @@ import (
 	"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
 )
 
+var (
+	dbSqlite   = "sqlite"
+	dbMySql    = "mysql"
+	dbPostgres = "postgres"
+)
+
 func InitTestDB(t *testing.T) *xorm.Engine {
-	x, err := xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
-	//x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
-	//x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
+	selectedDb := dbSqlite
+	//selectedDb := dbMySql
+	//selectedDb := dbPostgres
+
+	var x *xorm.Engine
+	var err error
+
+	// environment variable present for test db?
+	if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
+		selectedDb = db
+	}
+
+	switch strings.ToLower(selectedDb) {
+	case dbMySql:
+		x, err = xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
+	case dbPostgres:
+		x, err = xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
+	default:
+		x, err = xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
+	}
 
 	// x.ShowSQL()
 

+ 25 - 0
pkg/services/sqlstore/org_test.go

@@ -123,6 +123,31 @@ func TestAccountDataAccess(t *testing.T) {
 					So(query.Result[0].Role, ShouldEqual, "Admin")
 				})
 
+				Convey("Can get organization users with query", func() {
+					query := m.GetOrgUsersQuery{
+						OrgId: ac1.OrgId,
+						Query: "ac1",
+					}
+					err := GetOrgUsers(&query)
+
+					So(err, ShouldBeNil)
+					So(len(query.Result), ShouldEqual, 1)
+					So(query.Result[0].Email, ShouldEqual, ac1.Email)
+				})
+
+				Convey("Can get organization users with query and limit", func() {
+					query := m.GetOrgUsersQuery{
+						OrgId: ac1.OrgId,
+						Query: "ac",
+						Limit: 1,
+					}
+					err := GetOrgUsers(&query)
+
+					So(err, ShouldBeNil)
+					So(len(query.Result), ShouldEqual, 1)
+					So(query.Result[0].Email, ShouldEqual, ac1.Email)
+				})
+
 				Convey("Can set using org", func() {
 					cmd := m.SetUsingOrgCommand{UserId: ac2.Id, OrgId: ac1.Id}
 					err := SetUsingOrg(&cmd)

+ 23 - 1
pkg/services/sqlstore/org_users.go

@@ -2,6 +2,7 @@ package sqlstore
 
 import (
 	"fmt"
+	"strings"
 	"time"
 
 	"github.com/grafana/grafana/pkg/bus"
@@ -69,9 +70,30 @@ func UpdateOrgUser(cmd *m.UpdateOrgUserCommand) error {
 
 func GetOrgUsers(query *m.GetOrgUsersQuery) error {
 	query.Result = make([]*m.OrgUserDTO, 0)
+
 	sess := x.Table("org_user")
 	sess.Join("INNER", "user", fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user")))
-	sess.Where("org_user.org_id=?", query.OrgId)
+
+	whereConditions := make([]string, 0)
+	whereParams := make([]interface{}, 0)
+
+	whereConditions = append(whereConditions, "org_user.org_id = ?")
+	whereParams = append(whereParams, query.OrgId)
+
+	if query.Query != "" {
+		queryWithWildcards := "%" + query.Query + "%"
+		whereConditions = append(whereConditions, "(user.email "+dialect.LikeStr()+" ? OR user.name "+dialect.LikeStr()+" ? OR user.login "+dialect.LikeStr()+" ?)")
+		whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)
+	}
+
+	if len(whereConditions) > 0 {
+		sess.Where(strings.Join(whereConditions, " AND "), whereParams...)
+	}
+
+	if query.Limit > 0 {
+		sess.Limit(query.Limit, 0)
+	}
+
 	sess.Cols("org_user.org_id", "org_user.user_id", "user.email", "user.login", "org_user.role", "user.last_seen_at")
 	sess.Asc("user.email", "user.login")
 

+ 4 - 4
public/app/core/components/Picker/UserPicker.tsx

@@ -31,7 +31,7 @@ class UserPicker extends Component<IProps, any> {
 
     this.debouncedSearch = debounce(this.search, 300, {
       leading: true,
-      trailing: false,
+      trailing: true,
     });
   }
 
@@ -39,10 +39,10 @@ class UserPicker extends Component<IProps, any> {
     const { toggleLoading, backendSrv } = this.props;
 
     toggleLoading(true);
-    return backendSrv.get(`/api/users/search?perpage=10&page=1&query=${query}`).then(result => {
-      const users = result.users.map(user => {
+    return backendSrv.get(`/api/org/users?query=${query}&limit=10`).then(result => {
+      const users = result.map(user => {
         return {
-          id: user.id,
+          id: user.userId,
           label: `${user.login} - ${user.email}`,
           avatarUrl: user.avatarUrl,
           login: user.login,

+ 2 - 3
public/app/features/dashboard/dashboard_import_ctrl.ts

@@ -18,7 +18,7 @@ export class DashboardImportCtrl {
   nameValidationError: any;
 
   /** @ngInject */
-  constructor(private backendSrv, private validationSrv, navModelSrv, private $location, private $scope, $routeParams) {
+  constructor(private backendSrv, private validationSrv, navModelSrv, private $location, $routeParams) {
     this.navModel = navModelSrv.getNav('create', 'import');
 
     this.step = 1;
@@ -124,8 +124,7 @@ export class DashboardImportCtrl {
         inputs: inputs,
       })
       .then(res => {
-        this.$location.url('dashboard/' + res.importedUri);
-        this.$scope.dismiss();
+        this.$location.url(res.importedUrl);
       });
   }
 

+ 8 - 5
public/app/features/dashboard/dashboard_srv.ts

@@ -20,7 +20,10 @@ export class DashboardSrv {
     return this.dash;
   }
 
-  handleSaveDashboardError(clone, err) {
+  handleSaveDashboardError(clone, options, err) {
+    options = options || {};
+    options.overwrite = true;
+
     if (err.data && err.data.status === 'version-mismatch') {
       err.isHandled = true;
 
@@ -31,7 +34,7 @@ export class DashboardSrv {
         yesText: 'Save & Overwrite',
         icon: 'fa-warning',
         onConfirm: () => {
-          this.save(clone, { overwrite: true });
+          this.save(clone, options);
         },
       });
     }
@@ -41,12 +44,12 @@ export class DashboardSrv {
 
       this.$rootScope.appEvent('confirm-modal', {
         title: 'Conflict',
-        text: 'Dashboard with the same name exists.',
+        text: 'A dashboard with the same name in selected folder already exists.',
         text2: 'Would you still like to save this dashboard?',
         yesText: 'Save & Overwrite',
         icon: 'fa-warning',
         onConfirm: () => {
-          this.save(clone, { overwrite: true });
+          this.save(clone, options);
         },
       });
     }
@@ -91,7 +94,7 @@ export class DashboardSrv {
     return this.backendSrv
       .saveDashboard(clone, options)
       .then(this.postSave.bind(this, clone))
-      .catch(this.handleSaveDashboardError.bind(this, clone));
+      .catch(this.handleSaveDashboardError.bind(this, clone, options));
   }
 
   saveDashboard(options, clone) {

+ 1 - 1
public/app/features/dashboard/specs/dashboard_import_ctrl.jest.ts

@@ -22,7 +22,7 @@ describe('DashboardImportCtrl', function() {
       validateNewDashboardName: jest.fn().mockReturnValue(Promise.resolve()),
     };
 
-    ctx.ctrl = new DashboardImportCtrl(backendSrv, validationSrv, navModelSrv, {}, {}, {});
+    ctx.ctrl = new DashboardImportCtrl(backendSrv, validationSrv, navModelSrv, {}, {});
   });
 
   describe('when uploading json', function() {

+ 1 - 1
public/app/features/org/partials/team_details.html

@@ -33,7 +33,7 @@
 				Old picker
 				<user-picker user-picked="ctrl.userPicked($user)"></user-picker>
 				-->
-				<select-user-picker handlePicked="ctrl.userPicked" backendSrv="ctrl.backendSrv"></select-user-picker>
+				<select-user-picker  class="width-7" handlePicked="ctrl.userPicked" backendSrv="ctrl.backendSrv"></select-user-picker>
       </div>
     </form>
 

+ 1 - 1
public/app/features/plugins/import_list/import_list.html

@@ -6,7 +6,7 @@
 					<i class="icon-gf icon-gf-dashboard"></i>
 				</td>
 				<td>
-					<a href="dashboard/{{dash.importedUri}}" ng-show="dash.imported">
+					<a href="{{dash.importedUrl}}" ng-show="dash.imported">
 						{{dash.title}}
 					</a>
 					<span ng-show="!dash.imported">

+ 1 - 1
public/app/stores/FolderStore/FolderStore.ts

@@ -53,6 +53,6 @@ export const FolderStore = types
     deleteFolder: flow(function* deleteFolder() {
       const backendSrv = getEnv(self).backendSrv;
 
-      return backendSrv.deleteDashboard(self.folder.url);
+      return backendSrv.deleteDashboard(self.folder.uid);
     }),
   }));