diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go
index 100fd41aad5..0c826f32dae 100644
--- a/pkg/api/dashboard.go
+++ b/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 {
diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go
index adb310bd3f9..a072d9f5eea 100644
--- a/pkg/models/dashboards.go
+++ b/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 {
@@ -100,14 +105,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 version, err := dash.Data.Get("version").Float64(); err == nil {
- dash.Version = int(version)
- dash.Updated = time.Now()
- }
+ 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 && update {
+ dash.Version = int(version)
+ dash.Updated = time.Now()
} else {
dash.Data.Set("version", 0)
dash.Created = time.Now()
@@ -118,10 +130,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
}
diff --git a/pkg/plugins/dashboard_importer.go b/pkg/plugins/dashboard_importer.go
index bf516818e3c..9036b943b30 100644
--- a/pkg/plugins/dashboard_importer.go
+++ b/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,
}
diff --git a/pkg/plugins/dashboards.go b/pkg/plugins/dashboards.go
index 37e3d8c0076..d15bcdd6db5 100644
--- a/pkg/plugins/dashboards.go
+++ b/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
}
diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go
index 3ede1a03e8c..a9ca8446f5d 100644
--- a/pkg/services/sqlstore/dashboard.go
+++ b/pkg/services/sqlstore/dashboard.go
@@ -37,47 +37,36 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) 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
}
}
}
@@ -91,11 +80,6 @@ func saveDashboard(sess *DBSession, 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
@@ -167,6 +151,72 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
return err
}
+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()
@@ -184,23 +234,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 {
@@ -524,9 +557,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()
diff --git a/pkg/services/sqlstore/dashboard_test.go b/pkg/services/sqlstore/dashboard_test.go
index 97ab5472d94..bd769d307eb 100644
--- a/pkg/services/sqlstore/dashboard_test.go
+++ b/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,7 +130,382 @@ func TestDashboardDataAccess(t *testing.T) {
}
err := SaveDashboard(&cmd)
- So(err, ShouldNotBeNil)
+ So(err, ShouldEqual, m.ErrDashboardNotFound)
+ })
+
+ 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 := SaveDashboard(&firstSaveCmd)
+ So(err, ShouldBeNil)
+
+ 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 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 := SaveDashboard(&cmd)
+ So(err, ShouldBeNil)
+ So(cmd.Result.Id, ShouldEqual, dashInFolder.Id)
+ So(cmd.Result.Uid, ShouldEqual, dashInFolder.Uid)
+ })
+
+ 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 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 := SaveDashboard(&cmd)
+ So(err, ShouldEqual, m.ErrDashboardWithSameNameAsFolder)
+ })
+
+ 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,
+ }
+
+ err := SaveDashboard(&cmd)
+ So(err, ShouldEqual, m.ErrDashboardWithSameNameAsFolder)
+ })
+
+ 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,
+ }
+
+ err := SaveDashboard(&cmd)
+ So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
+ })
+
+ 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 not be able to overwrite folder with dashboard using uid", func() {
+ cmd := m.SaveDashboardCommand{
+ OrgId: 1,
+ Dashboard: simplejson.NewFromAny(map[string]interface{}{
+ "uid": savedFolder.Uid,
+ "title": "new title",
+ }),
+ IsFolder: false,
+ Overwrite: true,
+ }
+
+ err := SaveDashboard(&cmd)
+ So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
+ })
+
+ Convey("Should not be able to overwrite dashboard with folder using uid", func() {
+ cmd := m.SaveDashboardCommand{
+ OrgId: 1,
+ Dashboard: simplejson.NewFromAny(map[string]interface{}{
+ "uid": savedDash.Uid,
+ "title": "new folder title",
+ }),
+ IsFolder: true,
+ Overwrite: true,
+ }
+
+ err := SaveDashboard(&cmd)
+ So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
+ })
+
+ 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{}{
+ "id": nil,
+ "title": "test dash folder and title",
+ "tags": []interface{}{},
+ "uid": "randomHash",
+ }),
+ FolderId: 3,
+ }
+
+ err := SaveDashboard(&firstSaveCmd)
+ So(err, ShouldBeNil)
+
+ secondSaveCmd := m.SaveDashboardCommand{
+ OrgId: 1,
+ Dashboard: simplejson.NewFromAny(map[string]interface{}{
+ "id": nil,
+ "title": "test dash folder and title",
+ "tags": []interface{}{},
+ "uid": "moreRandomHash",
+ }),
+ FolderId: 3,
+ }
+
+ err = SaveDashboard(&secondSaveCmd)
+ So(err, ShouldEqual, m.ErrDashboardWithSameNameInFolderExists)
+ })
+
+ 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,
+ "uid": "dsfalkjngailuedt",
+ "title": "test dash 23",
+ }),
+ }
+
+ err := SaveDashboard(&cmd)
+ So(err, ShouldBeNil)
+ err = SaveDashboard(&cmd)
+ 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() {
+ cmd := m.SaveDashboardCommand{
+ OrgId: 1,
+ Dashboard: simplejson.NewFromAny(map[string]interface{}{
+ "uid": "randomHash",
+ "title": "folderId",
+ "style": "light",
+ "tags": []interface{}{},
+ }),
+ FolderId: 2,
+ }
+
+ err := SaveDashboard(&cmd)
+ So(err, ShouldBeNil)
+ So(cmd.Result.FolderId, ShouldEqual, 2)
+
+ cmd = m.SaveDashboardCommand{
+ OrgId: 1,
+ Dashboard: simplejson.NewFromAny(map[string]interface{}{
+ "id": cmd.Result.Id,
+ "uid": "randomHash",
+ "title": "folderId",
+ "style": "dark",
+ "version": cmd.Result.Version,
+ "tags": []interface{}{},
+ }),
+ FolderId: 2,
+ }
+
+ err = SaveDashboard(&cmd)
+ So(err, ShouldBeNil)
+ })
+
+ Convey("Should be able to update using uid without id and 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,
+ }
+
+ err := SaveDashboard(&cmd)
+ So(err, ShouldBeNil)
+ })
+
+ Convey("Should retry generation of uid once if it fails.", func() {
+ timesCalled := 0
+ generateNewUid = func() string {
+ timesCalled += 1
+ if timesCalled <= 2 {
+ return savedDash.Uid
+ } else {
+ return util.GenerateShortUid()
+ }
+ }
+ cmd := m.SaveDashboardCommand{
+ OrgId: 1,
+ Dashboard: simplejson.NewFromAny(map[string]interface{}{
+ "title": "new dash 12334",
+ "tags": []interface{}{},
+ }),
+ }
+
+ err := SaveDashboard(&cmd)
+ So(err, ShouldBeNil)
+
+ generateNewUid = util.GenerateShortUid
+ })
+
+ 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": savedDash.Id,
+ "title": "folderId",
+ "tags": []interface{}{},
+ }),
+ Overwrite: true,
+ FolderId: 2,
+ }
+
+ err := SaveDashboard(&cmd)
+ So(err, ShouldBeNil)
+ So(cmd.Result.FolderId, ShouldEqual, 2)
+
+ cmd = m.SaveDashboardCommand{
+ OrgId: 1,
+ Dashboard: simplejson.NewFromAny(map[string]interface{}{
+ "id": savedDash.Id,
+ "title": "folderId",
+ "tags": []interface{}{},
+ }),
+ FolderId: 0,
+ Overwrite: true,
+ }
+
+ err = SaveDashboard(&cmd)
+ So(err, ShouldBeNil)
+
+ query := m.GetDashboardQuery{
+ Id: savedDash.Id,
+ OrgId: 1,
+ }
+
+ err = GetDashboard(&query)
+ So(err, ShouldBeNil)
+ So(query.Result.FolderId, ShouldEqual, 0)
+ })
+
+ Convey("Should be able to delete a dashboard folder and its children", func() {
+ deleteCmd := &m.DeleteDashboardCommand{Id: savedFolder.Id}
+ err := DeleteDashboard(deleteCmd)
+ So(err, ShouldBeNil)
+
+ query := search.FindPersistedDashboardsQuery{
+ OrgId: 1,
+ FolderIds: []int64{savedFolder.Id},
+ SignedInUser: &m.SignedInUser{},
+ }
+
+ err = SearchDashboards(&query)
+ So(err, ShouldBeNil)
+
+ So(len(query.Result), ShouldEqual, 0)
+ })
+
+ Convey("Should be able to get dashboard tags", func() {
+ query := m.GetDashboardTagsQuery{OrgId: 1}
+
+ err := GetDashboardTags(&query)
+ So(err, ShouldBeNil)
+
+ So(len(query.Result), ShouldEqual, 2)
})
Convey("Should be able to search for dashboard folder", func() {
@@ -188,249 +563,6 @@ func TestDashboardDataAccess(t *testing.T) {
hit2 := query.Result[1]
So(len(hit2.Tags), ShouldEqual, 1)
})
-
- Convey("DashboardIds that does not exists should not cause errors", func() {
- query := search.FindPersistedDashboardsQuery{
- DashboardIds: []int64{1000},
- SignedInUser: &m.SignedInUser{OrgId: 1},
- }
-
- err := SearchDashboards(&query)
- So(err, ShouldBeNil)
- So(len(query.Result), ShouldEqual, 0)
- })
- })
-
- 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 := SaveDashboard(&firstSaveCmd)
- So(err, ShouldBeNil)
-
- 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)
- })
-
- Convey("Should not be able to save dashboard with same name in the same folder", 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 := SaveDashboard(&firstSaveCmd)
- So(err, ShouldBeNil)
-
- secondSaveCmd := m.SaveDashboardCommand{
- OrgId: 1,
- Dashboard: simplejson.NewFromAny(map[string]interface{}{
- "id": nil,
- "title": "test dash folder and title",
- "tags": []interface{}{},
- "uid": "moreRandomHash",
- }),
- FolderId: 3,
- }
-
- err = SaveDashboard(&secondSaveCmd)
- So(err, ShouldEqual, m.ErrDashboardWithSameNameInFolderExists)
- })
-
- Convey("Should not be able to save dashboard with same uid", func() {
- cmd := m.SaveDashboardCommand{
- OrgId: 1,
- Dashboard: simplejson.NewFromAny(map[string]interface{}{
- "id": nil,
- "title": "test dash 23",
- "uid": "dsfalkjngailuedt",
- }),
- }
-
- err := SaveDashboard(&cmd)
- So(err, ShouldBeNil)
- err = SaveDashboard(&cmd)
- So(err, ShouldNotBeNil)
- })
-
- Convey("Should be able to update dashboard with the same title and folder id", func() {
- cmd := m.SaveDashboardCommand{
- OrgId: 1,
- Dashboard: simplejson.NewFromAny(map[string]interface{}{
- "uid": "randomHash",
- "title": "folderId",
- "style": "light",
- "tags": []interface{}{},
- }),
- FolderId: 2,
- }
-
- err := SaveDashboard(&cmd)
- So(err, ShouldBeNil)
- So(cmd.Result.FolderId, ShouldEqual, 2)
-
- cmd = m.SaveDashboardCommand{
- OrgId: 1,
- Dashboard: simplejson.NewFromAny(map[string]interface{}{
- "id": cmd.Result.Id,
- "uid": "randomHash",
- "title": "folderId",
- "style": "dark",
- "version": cmd.Result.Version,
- "tags": []interface{}{},
- }),
- FolderId: 2,
- }
-
- err = SaveDashboard(&cmd)
- So(err, ShouldBeNil)
- })
-
- Convey("Should not be able to update using just uid", 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,
- }
-
- 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)
- })
-
- Convey("Should retry generation of uid once if it fails.", func() {
- timesCalled := 0
- generateNewUid = func() string {
- timesCalled += 1
- if timesCalled <= 2 {
- return savedDash.Uid
- } else {
- return util.GenerateShortUid()
- }
- }
- cmd := m.SaveDashboardCommand{
- OrgId: 1,
- Dashboard: simplejson.NewFromAny(map[string]interface{}{
- "title": "new dash 12334",
- "tags": []interface{}{},
- }),
- }
-
- err := SaveDashboard(&cmd)
- So(err, ShouldBeNil)
-
- generateNewUid = util.GenerateShortUid
- })
-
- Convey("Should be able to update dashboard and remove folderId", func() {
- cmd := m.SaveDashboardCommand{
- OrgId: 1,
- Dashboard: simplejson.NewFromAny(map[string]interface{}{
- "id": 1,
- "title": "folderId",
- "tags": []interface{}{},
- }),
- Overwrite: true,
- FolderId: 2,
- }
-
- err := SaveDashboard(&cmd)
- So(err, ShouldBeNil)
- So(cmd.Result.FolderId, ShouldEqual, 2)
-
- cmd = m.SaveDashboardCommand{
- OrgId: 1,
- Dashboard: simplejson.NewFromAny(map[string]interface{}{
- "id": 1,
- "title": "folderId",
- "tags": []interface{}{},
- }),
- FolderId: 0,
- Overwrite: true,
- }
-
- err = SaveDashboard(&cmd)
- So(err, ShouldBeNil)
-
- query := m.GetDashboardQuery{
- Slug: cmd.Result.Slug,
- OrgId: 1,
- }
-
- err = GetDashboard(&query)
- So(err, ShouldBeNil)
- So(query.Result.FolderId, ShouldEqual, 0)
- })
-
- Convey("Should be able to delete a dashboard folder and its children", func() {
- deleteCmd := &m.DeleteDashboardCommand{Id: savedFolder.Id}
- err := DeleteDashboard(deleteCmd)
- So(err, ShouldBeNil)
-
- query := search.FindPersistedDashboardsQuery{
- OrgId: 1,
- FolderIds: []int64{savedFolder.Id},
- SignedInUser: &m.SignedInUser{},
- }
-
- err = SearchDashboards(&query)
- So(err, ShouldBeNil)
-
- So(len(query.Result), ShouldEqual, 0)
- })
-
- Convey("Should be able to get dashboard tags", func() {
- query := m.GetDashboardTagsQuery{OrgId: 1}
-
- err := GetDashboardTags(&query)
- So(err, ShouldBeNil)
-
- So(len(query.Result), ShouldEqual, 2)
})
Convey("Given two dashboards, one is starred dashboard by user 10, other starred by user 1", func() {
diff --git a/public/app/features/dashboard/dashboard_import_ctrl.ts b/public/app/features/dashboard/dashboard_import_ctrl.ts
index c3af0a51a19..d127e628a77 100644
--- a/public/app/features/dashboard/dashboard_import_ctrl.ts
+++ b/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);
});
}
diff --git a/public/app/features/dashboard/dashboard_srv.ts b/public/app/features/dashboard/dashboard_srv.ts
index 8d00dc4fc60..a1ff99007e3 100644
--- a/public/app/features/dashboard/dashboard_srv.ts
+++ b/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) {
diff --git a/public/app/features/dashboard/specs/dashboard_import_ctrl.jest.ts b/public/app/features/dashboard/specs/dashboard_import_ctrl.jest.ts
index 3d311b8b4f8..1cb59ef5bac 100644
--- a/public/app/features/dashboard/specs/dashboard_import_ctrl.jest.ts
+++ b/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() {
diff --git a/public/app/features/org/partials/team_details.html b/public/app/features/org/partials/team_details.html
index 9f06ebdb017..3fce8b3c720 100644
--- a/public/app/features/org/partials/team_details.html
+++ b/public/app/features/org/partials/team_details.html
@@ -33,7 +33,7 @@
Old picker