mirror of
https://github.com/grafana/grafana.git
synced 2025-09-19 16:47:16 +08:00
Dashboards: Prevent version restore to same data (#102665)
This commit is contained in:

committed by
GitHub

parent
55f2812466
commit
c76a681a43
@ -216,6 +216,7 @@ JSON response body schema:
|
|||||||
Status codes:
|
Status codes:
|
||||||
|
|
||||||
- **200** - OK
|
- **200** - OK
|
||||||
|
- **400** - Bad request (specified version has the same content as the current dashboard)
|
||||||
- **401** - Unauthorized
|
- **401** - Unauthorized
|
||||||
- **404** - Not found (dashboard not found or dashboard version not found)
|
- **404** - Not found (dashboard not found or dashboard version not found)
|
||||||
- **500** - Internal server error (indicates issue retrieving dashboard tags from database)
|
- **500** - Internal server error (indicates issue retrieving dashboard tags from database)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -1114,6 +1115,13 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon
|
|||||||
return response.Error(http.StatusNotFound, "Dashboard version not found", nil)
|
return response.Error(http.StatusNotFound, "Dashboard version not found", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// do not allow restores if the json data is identical
|
||||||
|
// this is needed for the k8s flow, as the generation id will be used on the
|
||||||
|
// version table, and the generation id only increments when the actual spec is changed
|
||||||
|
if compareDashboardData(version.Data.MustMap(), dash.Data.MustMap()) {
|
||||||
|
return response.Error(http.StatusBadRequest, "Current dashboard is identical to the specified version", nil)
|
||||||
|
}
|
||||||
|
|
||||||
var userID int64
|
var userID int64
|
||||||
if id, err := identity.UserIdentifier(c.SignedInUser.GetID()); err == nil {
|
if id, err := identity.UserIdentifier(c.SignedInUser.GetID()); err == nil {
|
||||||
userID = id
|
userID = id
|
||||||
@ -1135,6 +1143,18 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon
|
|||||||
return hs.postDashboard(c, saveCmd)
|
return hs.postDashboard(c, saveCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func compareDashboardData(versionData, dashData map[string]any) bool {
|
||||||
|
// these can be different but the actual data is the same
|
||||||
|
delete(versionData, "version")
|
||||||
|
delete(dashData, "version")
|
||||||
|
delete(versionData, "id")
|
||||||
|
delete(dashData, "id")
|
||||||
|
delete(versionData, "uid")
|
||||||
|
delete(dashData, "uid")
|
||||||
|
|
||||||
|
return reflect.DeepEqual(versionData, dashData)
|
||||||
|
}
|
||||||
|
|
||||||
// swagger:route GET /dashboards/tags dashboards getDashboardTags
|
// swagger:route GET /dashboards/tags dashboards getDashboardTags
|
||||||
//
|
//
|
||||||
// Get all dashboards tags of an organisation.
|
// Get all dashboards tags of an organisation.
|
||||||
|
@ -585,6 +585,38 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}).Return(nil, nil)
|
}).Return(nil, nil)
|
||||||
|
|
||||||
|
cmd := dtos.RestoreDashboardVersionCommand{
|
||||||
|
Version: 1,
|
||||||
|
}
|
||||||
|
fakeDashboardVersionService := dashvertest.NewDashboardVersionServiceFake()
|
||||||
|
fakeDashboardVersionService.ExpectedDashboardVersions = []*dashver.DashboardVersionDTO{
|
||||||
|
{
|
||||||
|
DashboardID: 2,
|
||||||
|
Version: 1,
|
||||||
|
Data: simplejson.NewFromAny(map[string]any{
|
||||||
|
"title": "Dash1",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mockSQLStore := dbtest.NewFakeDB()
|
||||||
|
|
||||||
|
restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/id/1/restore",
|
||||||
|
"/api/dashboards/id/:dashboardId/restore", dashboardService, fakeDashboardVersionService, cmd, func(sc *scenarioContext) {
|
||||||
|
sc.dashboardVersionService = fakeDashboardVersionService
|
||||||
|
|
||||||
|
callRestoreDashboardVersion(sc)
|
||||||
|
assert.Equal(t, http.StatusOK, sc.resp.Code)
|
||||||
|
}, mockSQLStore)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should not be able to restore to the same data", func(t *testing.T) {
|
||||||
|
fakeDash := dashboards.NewDashboard("Child dash")
|
||||||
|
fakeDash.ID = 2
|
||||||
|
fakeDash.HasACL = false
|
||||||
|
|
||||||
|
dashboardService := dashboards.NewFakeDashboardService(t)
|
||||||
|
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(fakeDash, nil)
|
||||||
|
|
||||||
cmd := dtos.RestoreDashboardVersionCommand{
|
cmd := dtos.RestoreDashboardVersionCommand{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
}
|
}
|
||||||
@ -602,6 +634,42 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
|||||||
"/api/dashboards/id/:dashboardId/restore", dashboardService, fakeDashboardVersionService, cmd, func(sc *scenarioContext) {
|
"/api/dashboards/id/:dashboardId/restore", dashboardService, fakeDashboardVersionService, cmd, func(sc *scenarioContext) {
|
||||||
sc.dashboardVersionService = fakeDashboardVersionService
|
sc.dashboardVersionService = fakeDashboardVersionService
|
||||||
|
|
||||||
|
callRestoreDashboardVersion(sc)
|
||||||
|
assert.Equal(t, http.StatusBadRequest, sc.resp.Code)
|
||||||
|
}, mockSQLStore)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Given dashboard in general folder being restored should restore to general folder", func(t *testing.T) {
|
||||||
|
fakeDash := dashboards.NewDashboard("Child dash")
|
||||||
|
fakeDash.ID = 2
|
||||||
|
fakeDash.HasACL = false
|
||||||
|
|
||||||
|
dashboardService := dashboards.NewFakeDashboardService(t)
|
||||||
|
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(fakeDash, nil)
|
||||||
|
dashboardService.On("SaveDashboard", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.AnythingOfType("bool")).Run(func(args mock.Arguments) {
|
||||||
|
cmd := args.Get(1).(*dashboards.SaveDashboardDTO)
|
||||||
|
cmd.Dashboard = &dashboards.Dashboard{
|
||||||
|
ID: 2, UID: "uid", Title: "Dash", Slug: "dash", Version: 1,
|
||||||
|
}
|
||||||
|
}).Return(nil, nil)
|
||||||
|
|
||||||
|
fakeDashboardVersionService := dashvertest.NewDashboardVersionServiceFake()
|
||||||
|
fakeDashboardVersionService.ExpectedDashboardVersions = []*dashver.DashboardVersionDTO{
|
||||||
|
{
|
||||||
|
DashboardID: 2,
|
||||||
|
Version: 1,
|
||||||
|
Data: simplejson.NewFromAny(map[string]any{
|
||||||
|
"title": "Dash1",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := dtos.RestoreDashboardVersionCommand{
|
||||||
|
Version: 1,
|
||||||
|
}
|
||||||
|
mockSQLStore := dbtest.NewFakeDB()
|
||||||
|
restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/id/1/restore",
|
||||||
|
"/api/dashboards/id/:dashboardId/restore", dashboardService, fakeDashboardVersionService, cmd, func(sc *scenarioContext) {
|
||||||
callRestoreDashboardVersion(sc)
|
callRestoreDashboardVersion(sc)
|
||||||
assert.Equal(t, http.StatusOK, sc.resp.Code)
|
assert.Equal(t, http.StatusOK, sc.resp.Code)
|
||||||
}, mockSQLStore)
|
}, mockSQLStore)
|
||||||
@ -626,7 +694,9 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
|||||||
{
|
{
|
||||||
DashboardID: 2,
|
DashboardID: 2,
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Data: fakeDash.Data,
|
Data: simplejson.NewFromAny(map[string]any{
|
||||||
|
"title": "Dash1",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user