Support dashboard restore across API versions (#110694)
What This commit refactors the logic to restore a dashboard from a version. The logic is moved from the API handler to the dashboard versions service, which now supports restoring dashboards of different API versions. Why To make sure that dashboard version restoration works with v2 dashboards API, as well as future API versions. Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com>
This commit is contained in:
+45
-87
@@ -8,7 +8,6 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -28,7 +27,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
|
||||
"github.com/grafana/grafana/pkg/services/dashboardversion/dashverimpl"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@@ -68,11 +66,7 @@ func (hs *HTTPServer) isDashboardStarredByUser(c *contextmodel.ReqContext, dashU
|
||||
|
||||
func dashboardGuardianResponse(err error) response.Response {
|
||||
if err != nil {
|
||||
var dashboardErr dashboardaccess.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), err)
|
||||
}
|
||||
return response.Error(http.StatusInternalServerError, "Error while checking dashboard permissions", err)
|
||||
return dashboardErrResponse(err, "Error while checking dashboard permissions")
|
||||
}
|
||||
return response.Error(http.StatusForbidden, "Access denied to this dashboard", nil)
|
||||
}
|
||||
@@ -389,19 +383,7 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo
|
||||
|
||||
err = hs.DashboardService.DeleteDashboard(c.Req.Context(), dash.ID, dash.UID, c.GetOrgID())
|
||||
if err != nil {
|
||||
var dashboardErr dashboardaccess.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
if errors.Is(err, dashboards.ErrDashboardCannotDeleteProvisionedDashboard) {
|
||||
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), err)
|
||||
}
|
||||
}
|
||||
|
||||
var statusErr *k8serrors.StatusError
|
||||
if errors.As(err, &statusErr) {
|
||||
return response.Error(int(statusErr.ErrStatus.Code), statusErr.ErrStatus.Message, err)
|
||||
}
|
||||
|
||||
return response.Error(http.StatusInternalServerError, "Failed to delete dashboard", err)
|
||||
return dashboardErrResponse(err, "Failed to delete dashboard")
|
||||
}
|
||||
|
||||
if hs.Live != nil {
|
||||
@@ -949,27 +931,13 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons
|
||||
return response.Respond(http.StatusOK, result.Delta).SetHeader("Content-Type", "text/html")
|
||||
}
|
||||
|
||||
// swagger:route POST /dashboards/id/{DashboardID}/restore dashboards versions restoreDashboardVersionByID
|
||||
//
|
||||
// Restore a dashboard to a given dashboard version.
|
||||
//
|
||||
// Please refer to [updated API](#/dashboards/restoreDashboardVersionByUID) instead
|
||||
//
|
||||
// Deprecated: true
|
||||
//
|
||||
// Responses:
|
||||
// 200: postDashboardResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
|
||||
// swagger:route POST /dashboards/uid/{uid}/restore dashboards versions restoreDashboardVersionByUID
|
||||
//
|
||||
// Restore a dashboard to a given dashboard version using UID.
|
||||
//
|
||||
// Responses:
|
||||
// 200: postDashboardResponse
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
@@ -977,73 +945,49 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons
|
||||
func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := tracer.Start(c.Req.Context(), "api.RestoreDashboardVersion")
|
||||
defer span.End()
|
||||
|
||||
c.Req = c.Req.WithContext(ctx)
|
||||
|
||||
var dashID int64
|
||||
|
||||
var err error
|
||||
dashUID := web.Params(c.Req)[":uid"]
|
||||
|
||||
apiCmd := dtos.RestoreDashboardVersionCommand{}
|
||||
var apiCmd dtos.RestoreDashboardVersionCommand
|
||||
if err := web.Bind(c.Req, &apiCmd); err != nil {
|
||||
hs.log.Error("error restoring dashboard version: invalid request", "error", err)
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
}
|
||||
|
||||
var (
|
||||
dashID int64
|
||||
err error
|
||||
)
|
||||
|
||||
dashUID := web.Params(c.Req)[":uid"]
|
||||
if dashUID == "" {
|
||||
dashID, err = strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64)
|
||||
if err != nil {
|
||||
hs.log.Error("error restoring dashboard version: invalid dashboardId", "error", err)
|
||||
return response.Error(http.StatusBadRequest, "dashboardId is invalid", err)
|
||||
}
|
||||
}
|
||||
|
||||
dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.GetOrgID(), dashID, dashUID)
|
||||
if rsp != nil {
|
||||
return rsp
|
||||
}
|
||||
|
||||
versionQuery := dashver.GetDashboardVersionQuery{DashboardID: dashID, DashboardUID: dash.UID, Version: apiCmd.Version, OrgID: c.GetOrgID()}
|
||||
version, err := hs.dashboardVersionService.Get(c.Req.Context(), &versionQuery)
|
||||
res, err := hs.dashboardVersionService.RestoreVersion(ctx, &dashver.RestoreVersionCommand{
|
||||
Requester: c.SignedInUser,
|
||||
DashboardUID: dashUID,
|
||||
DashboardID: dashID,
|
||||
Version: apiCmd.Version,
|
||||
})
|
||||
if err != nil {
|
||||
return response.Error(http.StatusNotFound, "Dashboard version not found", nil)
|
||||
hs.log.Error("error restoring dashboard version: service call failed", "error", err)
|
||||
return dashboardErrResponse(err, "Failed to restore dashboard version")
|
||||
}
|
||||
|
||||
// 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
|
||||
if id, err := identity.UserIdentifier(c.GetID()); err == nil {
|
||||
userID = id
|
||||
}
|
||||
|
||||
saveCmd := dashboards.SaveDashboardCommand{}
|
||||
saveCmd.RestoredFrom = version.Version
|
||||
saveCmd.OrgID = c.GetOrgID()
|
||||
saveCmd.UserID = userID
|
||||
saveCmd.Dashboard = version.Data
|
||||
saveCmd.Dashboard.Set("version", dash.Version)
|
||||
saveCmd.Dashboard.Set("uid", dash.UID)
|
||||
saveCmd.Message = dashverimpl.DashboardRestoreMessage(version.Version)
|
||||
// nolint:staticcheck
|
||||
saveCmd.FolderID = dash.FolderID
|
||||
metrics.MFolderIDsAPICount.WithLabelValues(metrics.RestoreDashboardVersion).Inc()
|
||||
saveCmd.FolderUID = dash.FolderUID
|
||||
|
||||
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)
|
||||
return response.JSON(http.StatusOK, util.DynMap{
|
||||
"status": "success",
|
||||
"slug": res.Slug,
|
||||
"version": res.Version,
|
||||
"id": res.ID,
|
||||
"uid": res.UID,
|
||||
"url": res.GetURL(),
|
||||
"folderUid": res.FolderUID,
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:route GET /dashboards/tags dashboards getDashboardTags
|
||||
@@ -1094,6 +1038,20 @@ func (hs *HTTPServer) GetDashboardUIDs(c *contextmodel.ReqContext) {
|
||||
c.JSON(http.StatusOK, uids)
|
||||
}
|
||||
|
||||
func dashboardErrResponse(err error, fallbackMessage string) response.Response {
|
||||
var dashboardErr dashboardaccess.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), err)
|
||||
}
|
||||
|
||||
var statusErr *k8serrors.StatusError
|
||||
if errors.As(err, &statusErr) {
|
||||
return response.Error(int(statusErr.ErrStatus.Code), statusErr.ErrStatus.Message, err)
|
||||
}
|
||||
|
||||
return response.Error(http.StatusInternalServerError, fallbackMessage, err)
|
||||
}
|
||||
|
||||
// swagger:parameters restoreDashboardVersionByID
|
||||
type RestoreDashboardVersionByIDParams struct {
|
||||
// in:body
|
||||
|
||||
+215
-75
@@ -5,9 +5,11 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -558,32 +560,27 @@ func TestIntegrationDashboardAPIEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Given dashboard in folder being restored should restore to 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)
|
||||
|
||||
cmd := dtos.RestoreDashboardVersionCommand{
|
||||
Version: 1,
|
||||
}
|
||||
fakeDashboardVersionService := dashvertest.NewDashboardVersionServiceFake()
|
||||
fakeDashboardVersionService.ExpectedDashboardVersions = []*dashver.DashboardVersionDTO{
|
||||
{
|
||||
DashboardID: 2,
|
||||
Version: 1,
|
||||
Data: simplejson.NewFromAny(map[string]any{
|
||||
"title": "Dash1",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
// Mock successful restoration
|
||||
restoredDash := dashboards.NewDashboard("Restored Dashboard")
|
||||
restoredDash.ID = 2
|
||||
restoredDash.UID = "uid"
|
||||
restoredDash.Version = 2
|
||||
restoredDash.Slug = "dash"
|
||||
restoredDash.FolderUID = "folder-uid"
|
||||
restoredDash.Data = simplejson.NewFromAny(map[string]any{
|
||||
"title": "Dash1",
|
||||
})
|
||||
|
||||
fakeDashboardVersionService.ExpectedRestoreResult = restoredDash
|
||||
fakeDashboardVersionService.ExpectedError = nil
|
||||
|
||||
mockSQLStore := dbtest.NewFakeDB()
|
||||
|
||||
restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/id/1/restore",
|
||||
@@ -596,24 +593,17 @@ func TestIntegrationDashboardAPIEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
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{
|
||||
Version: 1,
|
||||
}
|
||||
fakeDashboardVersionService := dashvertest.NewDashboardVersionServiceFake()
|
||||
fakeDashboardVersionService.ExpectedDashboardVersions = []*dashver.DashboardVersionDTO{
|
||||
{
|
||||
DashboardID: 2,
|
||||
Version: 1,
|
||||
Data: fakeDash.Data,
|
||||
},
|
||||
}
|
||||
|
||||
// Mock error for identical version
|
||||
fakeDashboardVersionService.ExpectedRestoreResult = nil
|
||||
fakeDashboardVersionService.ExpectedError = dashboards.ErrDashboardRestoreIdenticalVersion
|
||||
|
||||
mockSQLStore := dbtest.NewFakeDB()
|
||||
|
||||
restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/id/1/restore",
|
||||
@@ -626,29 +616,22 @@ func TestIntegrationDashboardAPIEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
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",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
// Mock successful restoration
|
||||
restoredDash := dashboards.NewDashboard("Restored Dashboard")
|
||||
restoredDash.ID = 2
|
||||
restoredDash.UID = "uid"
|
||||
restoredDash.Version = 2
|
||||
restoredDash.Slug = "dash"
|
||||
restoredDash.Data = simplejson.NewFromAny(map[string]any{
|
||||
"title": "Dash1",
|
||||
})
|
||||
|
||||
fakeDashboardVersionService.ExpectedRestoreResult = restoredDash
|
||||
fakeDashboardVersionService.ExpectedError = nil
|
||||
|
||||
cmd := dtos.RestoreDashboardVersionCommand{
|
||||
Version: 1,
|
||||
@@ -661,30 +644,23 @@ func TestIntegrationDashboardAPIEndpoint(t *testing.T) {
|
||||
}, 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
|
||||
|
||||
t.Run("Given dashboard in general folder being restored should restore to general folder (duplicate)", func(t *testing.T) {
|
||||
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",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
// Mock successful restoration
|
||||
restoredDash := dashboards.NewDashboard("Restored Dashboard")
|
||||
restoredDash.ID = 2
|
||||
restoredDash.UID = "uid"
|
||||
restoredDash.Version = 2
|
||||
restoredDash.Slug = "dash"
|
||||
restoredDash.Data = simplejson.NewFromAny(map[string]any{
|
||||
"title": "Dash1",
|
||||
})
|
||||
|
||||
fakeDashboardVersionService.ExpectedRestoreResult = restoredDash
|
||||
fakeDashboardVersionService.ExpectedError = nil
|
||||
|
||||
cmd := dtos.RestoreDashboardVersionCommand{
|
||||
Version: 1,
|
||||
@@ -697,6 +673,169 @@ func TestIntegrationDashboardAPIEndpoint(t *testing.T) {
|
||||
}, mockSQLStore)
|
||||
})
|
||||
|
||||
t.Run("New RestoreVersion implementation tests", func(t *testing.T) {
|
||||
t.Run("should use new RestoreVersion service method when available", func(t *testing.T) {
|
||||
dashboardService := dashboards.NewFakeDashboardService(t)
|
||||
dashboardVersionService := dashvertest.NewDashboardVersionServiceFake()
|
||||
|
||||
// Mock successful restoration
|
||||
restoredDash := dashboards.NewDashboard("Restored Dashboard")
|
||||
restoredDash.ID = 1
|
||||
restoredDash.UID = "test-uid"
|
||||
restoredDash.Version = 6
|
||||
restoredDash.Slug = "restored-dashboard"
|
||||
restoredDash.Data = simplejson.NewFromAny(map[string]any{"title": "Restored Dashboard"})
|
||||
|
||||
dashboardVersionService.ExpectedRestoreResult = restoredDash
|
||||
dashboardVersionService.ExpectedError = nil
|
||||
|
||||
cmd := dtos.RestoreDashboardVersionCommand{
|
||||
Version: 3,
|
||||
}
|
||||
|
||||
restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/uid/test-uid/restore",
|
||||
"/api/dashboards/uid/:uid/restore", dashboardService, dashboardVersionService, cmd, func(sc *scenarioContext) {
|
||||
sc.dashboardVersionService = dashboardVersionService
|
||||
callRestoreDashboardVersion(sc)
|
||||
assert.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
|
||||
// Verify response contains expected fields
|
||||
result := sc.ToJSON()
|
||||
assert.Equal(t, "success", result.Get("status").MustString())
|
||||
assert.Equal(t, "test-uid", result.Get("uid").MustString())
|
||||
assert.Equal(t, int64(6), result.Get("version").MustInt64())
|
||||
}, dbtest.NewFakeDB())
|
||||
})
|
||||
|
||||
t.Run("should return error when RestoreVersion service fails", func(t *testing.T) {
|
||||
dashboardService := dashboards.NewFakeDashboardService(t)
|
||||
dashboardVersionService := dashvertest.NewDashboardVersionServiceFake()
|
||||
|
||||
// Mock service error
|
||||
dashboardVersionService.ExpectedRestoreResult = nil
|
||||
dashboardVersionService.ExpectedError = dashboards.ErrDashboardNotFound
|
||||
|
||||
cmd := dtos.RestoreDashboardVersionCommand{
|
||||
Version: 999, // Non-existent version
|
||||
}
|
||||
|
||||
restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/uid/test-uid/restore",
|
||||
"/api/dashboards/uid/:uid/restore", dashboardService, dashboardVersionService, cmd, func(sc *scenarioContext) {
|
||||
sc.dashboardVersionService = dashboardVersionService
|
||||
callRestoreDashboardVersion(sc)
|
||||
assert.Equal(t, http.StatusNotFound, sc.resp.Code)
|
||||
}, dbtest.NewFakeDB())
|
||||
})
|
||||
|
||||
t.Run("should return error when dashboard not found", func(t *testing.T) {
|
||||
dashboardService := dashboards.NewFakeDashboardService(t)
|
||||
dashboardVersionService := dashvertest.NewDashboardVersionServiceFake()
|
||||
|
||||
// Mock service error for dashboard not found
|
||||
dashboardVersionService.ExpectedRestoreResult = nil
|
||||
dashboardVersionService.ExpectedError = dashboards.ErrDashboardNotFound
|
||||
|
||||
cmd := dtos.RestoreDashboardVersionCommand{
|
||||
Version: 3,
|
||||
}
|
||||
|
||||
restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/uid/nonexistent-uid/restore",
|
||||
"/api/dashboards/uid/:uid/restore", dashboardService, dashboardVersionService, cmd, func(sc *scenarioContext) {
|
||||
sc.dashboardVersionService = dashboardVersionService
|
||||
callRestoreDashboardVersion(sc)
|
||||
assert.Equal(t, http.StatusNotFound, sc.resp.Code)
|
||||
}, dbtest.NewFakeDB())
|
||||
})
|
||||
|
||||
t.Run("should return error for invalid request data", func(t *testing.T) {
|
||||
dashboardService := dashboards.NewFakeDashboardService(t)
|
||||
dashboardVersionService := dashvertest.NewDashboardVersionServiceFake()
|
||||
|
||||
restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/uid/test-uid/restore",
|
||||
"/api/dashboards/uid/:uid/restore", dashboardService, dashboardVersionService, dtos.RestoreDashboardVersionCommand{}, func(sc *scenarioContext) {
|
||||
sc.dashboardVersionService = dashboardVersionService
|
||||
// Create request with invalid JSON
|
||||
sc.fakeReqWithParams("POST", "/api/dashboards/uid/test-uid/restore", map[string]string{})
|
||||
sc.req.Body = io.NopCloser(strings.NewReader("invalid json"))
|
||||
sc.req.Header.Set("Content-Type", "application/json")
|
||||
callRestoreDashboardVersion(sc)
|
||||
assert.Equal(t, http.StatusBadRequest, sc.resp.Code)
|
||||
}, dbtest.NewFakeDB())
|
||||
})
|
||||
|
||||
t.Run("should handle restoration with user ID", func(t *testing.T) {
|
||||
dashboardService := dashboards.NewFakeDashboardService(t)
|
||||
dashboardVersionService := dashvertest.NewDashboardVersionServiceFake()
|
||||
|
||||
// Mock successful restoration
|
||||
restoredDash := dashboards.NewDashboard("Restored Dashboard")
|
||||
restoredDash.ID = 1
|
||||
restoredDash.UID = "test-uid"
|
||||
restoredDash.Version = 6
|
||||
restoredDash.Slug = "restored-dashboard"
|
||||
restoredDash.Data = simplejson.NewFromAny(map[string]any{"title": "Restored Dashboard"})
|
||||
|
||||
dashboardVersionService.ExpectedRestoreResult = restoredDash
|
||||
dashboardVersionService.ExpectedError = nil
|
||||
|
||||
cmd := dtos.RestoreDashboardVersionCommand{
|
||||
Version: 3,
|
||||
}
|
||||
|
||||
// Create a custom scenario that sets the user ID to 123
|
||||
t.Run("When calling POST on /api/dashboards/uid/test-uid/restore", func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
folderSvc := foldertest.NewFakeService()
|
||||
folderSvc.ExpectedFolder = &folder.Folder{}
|
||||
|
||||
hs := HTTPServer{
|
||||
Cfg: cfg,
|
||||
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
|
||||
Live: newTestLive(t, db.InitTestDB(t)),
|
||||
QuotaService: quotatest.New(false, nil),
|
||||
LibraryPanelService: &mockLibraryPanelService{},
|
||||
LibraryElementService: &libraryelementsfake.LibraryElementService{},
|
||||
DashboardService: dashboardService,
|
||||
SQLStore: dbtest.NewFakeDB(),
|
||||
Features: featuremgmt.WithFeatures(),
|
||||
dashboardVersionService: dashboardVersionService,
|
||||
accesscontrolService: actest.FakeService{},
|
||||
folderService: folderSvc,
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
log: log.New("test"),
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, "/api/dashboards/uid/test-uid/restore")
|
||||
sc.sqlStore = dbtest.NewFakeDB()
|
||||
sc.dashboardVersionService = dashboardVersionService
|
||||
sc.defaultHandler = routing.Wrap(func(c *contextmodel.ReqContext) response.Response {
|
||||
c.Req.Body = mockRequestBody(cmd)
|
||||
c.Req.Header.Add("Content-Type", "application/json")
|
||||
sc.context = c
|
||||
// Set user ID to 123 for this test
|
||||
c.SignedInUser = &user.SignedInUser{
|
||||
OrgID: testOrgID,
|
||||
UserID: 123,
|
||||
}
|
||||
c.OrgRole = org.RoleAdmin
|
||||
|
||||
return hs.RestoreDashboardVersion(c)
|
||||
})
|
||||
|
||||
sc.m.Post("/api/dashboards/uid/:uid/restore", sc.defaultHandler)
|
||||
|
||||
callRestoreDashboardVersion(sc)
|
||||
assert.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
|
||||
// Verify the service was called with correct user ID
|
||||
assert.True(t, dashboardVersionService.RestoreVersionCalled)
|
||||
userID, err := dashboardVersionService.LastRestoreCommand.Requester.GetInternalID()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(123), userID)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Given provisioned dashboard", func(t *testing.T) {
|
||||
mockSQLStore := dbtest.NewFakeDB()
|
||||
dashboardService := dashboards.NewFakeDashboardService(t)
|
||||
@@ -999,6 +1138,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
|
||||
accesscontrolService: actest.FakeService{},
|
||||
folderService: folderSvc,
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
log: log.New("test"),
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, url)
|
||||
@@ -1008,11 +1148,11 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
|
||||
c.Req.Body = mockRequestBody(cmd)
|
||||
c.Req.Header.Add("Content-Type", "application/json")
|
||||
sc.context = c
|
||||
sc.context.SignedInUser = &user.SignedInUser{
|
||||
c.SignedInUser = &user.SignedInUser{
|
||||
OrgID: testOrgID,
|
||||
UserID: testUserID,
|
||||
}
|
||||
sc.context.OrgRole = org.RoleAdmin
|
||||
c.OrgRole = org.RoleAdmin
|
||||
|
||||
return hs.RestoreDashboardVersion(c)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user