mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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.
This commit is contained in:
parent
fc05fc42c9
commit
0e8377a9f4
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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() {
|
||||
|
@ -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">
|
||||
|
Loading…
Reference in New Issue
Block a user