From 0e8377a9f4cc736e59588c70b0128f66dea2ee01 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 8 Feb 2018 12:48:38 +0100 Subject: [PATCH] Update logic for create/update dashboard, validation and plugin dashboard links (#10809) * enables overwrite if dashboard allready exist in folder * dashboard: Don't allow creating a folder named General * dashboards: update logic for save/update dashboard No id and uid creates a new dashboard/folder. No id and uid, with an existing title in folder allows overwrite of dashboard. Id without uid, allows update of existing dashboard/folder without overwrite. Uid without id allows update of existing dashboard/folder without overwrite. Id without uid, with an existing title in folder allows overwrite of dashboard/folder and updated will have the uid of overwritten. Uid without id, with an existing title in folder allows overwrite of dashboard/folder and new will have the same uid as provided. Trying to change an existing folder to a dashboard yields error. Trying to change an existing dashboard to a folder yields error. * dashboards: include folder id when confirmed to save with overwrite * dashboards: fixes due to new url structure Return importedUrl property in response to importing dashboards and getting plugin dashboards and use this for redirects/links in the frontend. --- pkg/api/dashboard.go | 12 +- pkg/models/dashboards.go | 46 +- pkg/plugins/dashboard_importer.go | 1 + pkg/plugins/dashboards.go | 2 + pkg/services/sqlstore/dashboard.go | 149 +++-- pkg/services/sqlstore/dashboard_test.go | 624 +++++++++++------- .../dashboard/dashboard_import_ctrl.ts | 5 +- .../app/features/dashboard/dashboard_srv.ts | 13 +- .../specs/dashboard_import_ctrl.jest.ts | 2 +- .../plugins/import_list/import_list.html | 2 +- 10 files changed, 520 insertions(+), 336 deletions(-) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index d7676899eb2..1ecf16b8cb0 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 12216718b44..866d10850dc 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 { @@ -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 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() @@ -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 } 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 be8b11b1f5b..1445d25432a 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/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 { @@ -518,9 +551,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/plugins/import_list/import_list.html b/public/app/features/plugins/import_list/import_list.html index ff655f0c33a..fec7ba190ec 100644 --- a/public/app/features/plugins/import_list/import_list.html +++ b/public/app/features/plugins/import_list/import_list.html @@ -6,7 +6,7 @@ - + {{dash.title}}