diff --git a/docs/sources/http_api/dashboard_versions.md b/docs/sources/http_api/dashboard_versions.md index d603811eccf..0a439aeedf0 100644 --- a/docs/sources/http_api/dashboard_versions.md +++ b/docs/sources/http_api/dashboard_versions.md @@ -9,6 +9,8 @@ title = "Dashboard Versions HTTP API " ## Get all dashboard versions +> **Warning:** This API is deprecated since Grafana v9.0.0 and will be removed in a future release. Refer to the [new dashboard versions API](#get-all-dashboard-versions-by-dashboard-uid). + Query parameters: - **limit** - Maximum number of results to return @@ -65,6 +67,66 @@ Status Codes: - **401** - Unauthorized - **404** - Dashboard version not found +## Get all dashboard versions by dashboard UID + +Query parameters: + +- **limit** - Maximum number of results to return +- **start** - Version to start from when returning queries + +`GET /api/dashboards/uid/:uid/versions` + +Gets all existing dashboard versions for the dashboard with the given `uid`. + +**Example request for getting all dashboard versions**: + +```http +GET /api/dashboards/uid/QA7wKklGz/versions?limit=2?start=0 HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response** + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 428 + +[ + { + "id": 2, + "dashboardId": 1, + "uid": "QA7wKklGz", + "parentVersion": 1, + "restoredFrom": 0, + "version": 2, + "created": "2017-06-08T17:24:33-04:00", + "createdBy": "admin", + "message": "Updated panel title" + }, + { + "id": 1, + "dashboardId": 1, + "uid": "QA7wKklGz", + "parentVersion": 0, + "restoredFrom": 0, + "version": 1, + "created": "2017-06-08T17:23:33-04:00", + "createdBy": "admin", + "message": "Initial save" + } +] +``` + +Status Codes: + +- **200** - Ok +- **400** - Errors +- **401** - Unauthorized +- **404** - Dashboard version not found + ## Get dashboard version > **Warning:** This API is deprecated since Grafana v9.0.0 and will be removed in a future release. Refer to the [new get dashboard version API](#get-dashboard-version-by-dashboard-uid). @@ -294,6 +356,8 @@ Status Codes: ## Restore dashboard +> **Warning:** This API is deprecated since Grafana v9.0.0 and will be removed in a future release. Refer to the [new restore dashboard API](#restore-dashboard-by-dashboard-uid). + `POST /api/dashboards/id/:dashboardId/restore` Restores a dashboard to a given dashboard version. @@ -358,6 +422,75 @@ JSON response body schema: - **message** - Message explaining the reason for the request failure. +## Restore dashboard by dashboard UID + +`POST /api/dashboards/uid/:uid/restore` + +Restores a dashboard to a given dashboard version using `uid`. + +**Example request for restoring a dashboard version**: + +```http +POST /api/dashboards/uid/QA7wKklGz/restore +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +{ + "version": 1 +} +``` + +JSON body schema: + +- **version** - The dashboard version to restore to + +**Example response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 67 + +{ + "id": 70, + "slug": "my-dashboard", + "status": "success", + "uid": "QA7wKklGz", + "url": "/d/QA7wKklGz/my-dashboard", + "version": 3 +} +``` + +JSON response body schema: + +- **slug** - the URL friendly slug of the dashboard's title +- **status** - whether the restoration was successful or not +- **version** - the new dashboard version, following the restoration + +Status codes: + +- **200** - OK +- **401** - Unauthorized +- **404** - Not found (dashboard not found or dashboard version not found) +- **500** - Internal server error (indicates issue retrieving dashboard tags from database) + +**Example error response** + +```http +HTTP/1.1 404 Not Found +Content-Type: application/json; charset=UTF-8 +Content-Length: 46 + +{ + "message": "Dashboard version not found" +} +``` + +JSON response body schema: + +- **message** - Message explaining the reason for the request failure. + ## Compare dashboard versions `POST /api/dashboards/calculate-diff` diff --git a/pkg/api/api.go b/pkg/api/api.go index 08bbb4ce42c..c6918288743 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -369,6 +369,8 @@ func (hs *HTTPServer) registerRoutes() { dashboardRoute.Get("/uid/:uid", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionDashboardsRead)), routing.Wrap(hs.GetDashboard)) dashboardRoute.Delete("/uid/:uid", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionDashboardsDelete)), routing.Wrap(hs.DeleteDashboardByUID)) dashboardRoute.Group("/uid/:uid", func(dashUidRoute routing.RouteRegister) { + dashUidRoute.Get("/versions", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.GetDashboardVersions)) + dashUidRoute.Post("/restore", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.RestoreDashboardVersion)) dashUidRoute.Get("/versions/:id", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.GetDashboardVersion)) dashUidRoute.Group("/permissions", func(dashboardPermissionRoute routing.RouteRegister) { dashboardPermissionRoute.Get("/", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionDashboardsPermissionsRead)), routing.Wrap(hs.GetDashboardPermissionList)) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 60a99d289d8..e08c49b89c4 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -516,21 +516,37 @@ func (hs *HTTPServer) addGettingStartedPanelToHomeDashboard(c *models.ReqContext // GetDashboardVersions returns all dashboard versions as JSON func (hs *HTTPServer) GetDashboardVersions(c *models.ReqContext) response.Response { - dashID, err := strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) - if err != nil { - return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) - } + var dashID int64 + var err error + dashUID := web.Params(c.Req)[":uid"] + + if dashUID == "" { + dashID, err = strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) + if err != nil { + return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) + } + } else { + q := models.GetDashboardQuery{ + OrgId: c.SignedInUser.OrgId, + Uid: dashUID, + } + if err := hs.SQLStore.GetDashboard(c.Req.Context(), &q); err != nil { + return response.Error(http.StatusBadRequest, "failed to get dashboard by UID", err) + } + dashID = q.Result.Id + } guardian := guardian.New(c.Req.Context(), dashID, c.OrgId, c.SignedInUser) if canSave, err := guardian.CanSave(); err != nil || !canSave { return dashboardGuardianResponse(err) } query := models.GetDashboardVersionsQuery{ - OrgId: c.OrgId, - DashboardId: dashID, - Limit: c.QueryInt("limit"), - Start: c.QueryInt("start"), + OrgId: c.OrgId, + DashboardId: dashID, + DashboardUID: dashUID, + Limit: c.QueryInt("limit"), + Start: c.QueryInt("start"), } if err := hs.SQLStore.GetDashboardVersions(c.Req.Context(), &query); err != nil { @@ -696,26 +712,37 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *models.ReqContext) response.Resp // RestoreDashboardVersion restores a dashboard to the given version. func (hs *HTTPServer) RestoreDashboardVersion(c *models.ReqContext) response.Response { + var dashID int64 + + var err error + dashUID := web.Params(c.Req)[":uid"] + apiCmd := dtos.RestoreDashboardVersionCommand{} if err := web.Bind(c.Req, &apiCmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } - dashboardId, err := strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) - if err != nil { - return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) + if dashUID == "" { + dashID, err = strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) + if err != nil { + return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) + } } - dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgId, dashboardId, "") + dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgId, dashID, dashUID) if rsp != nil { return rsp } - guardian := guardian.New(c.Req.Context(), dash.Id, c.OrgId, c.SignedInUser) + if dash != nil && dash.Id != 0 { + dashID = dash.Id + } + + guardian := guardian.New(c.Req.Context(), dashID, c.OrgId, c.SignedInUser) if canSave, err := guardian.CanSave(); err != nil || !canSave { return dashboardGuardianResponse(err) } - versionQuery := models.GetDashboardVersionQuery{DashboardId: dash.Id, Version: apiCmd.Version, OrgId: c.OrgId} + versionQuery := models.GetDashboardVersionQuery{DashboardId: dashID, Version: apiCmd.Version, OrgId: c.OrgId} if err := hs.SQLStore.GetDashboardVersion(c.Req.Context(), &versionQuery); err != nil { return response.Error(404, "Dashboard version not found", nil) } diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 6250433791a..95e9ffd24ae 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -1064,7 +1064,6 @@ func postDiffScenario(t *testing.T, desc string, url string, routePattern string func restoreDashboardVersionScenario(t *testing.T, desc string, url string, routePattern string, mock *dashboards.FakeDashboardService, cmd dtos.RestoreDashboardVersionCommand, fn scenarioFunc, sqlStore sqlstore.Store) { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { cfg := setting.NewCfg() - mockSQLStore := mockstore.NewSQLStoreMock() hs := HTTPServer{ Cfg: cfg, ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()), @@ -1078,7 +1077,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout } sc := setupScenarioContext(t, url) - sc.sqlStore = mockSQLStore + sc.sqlStore = sqlStore sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { c.Req.Body = mockRequestBody(cmd) c.Req.Header.Add("Content-Type", "application/json") diff --git a/pkg/api/docs/definitions/dashboard.go b/pkg/api/docs/definitions/dashboard.go index 5884a8ba818..bac5d3b2e08 100644 --- a/pkg/api/docs/definitions/dashboard.go +++ b/pkg/api/docs/definitions/dashboard.go @@ -102,6 +102,7 @@ import ( // 500: internalServerError // swagger:parameters getDashboardByUID deleteDashboardByUID getDashboardPermissionsWithUid postDashboardPermissionsWithUid getDashboardVersionByUID +// swagger:parameters getDashboardVersionsByUID restoreDashboardVersionByUID type UID struct { // in:path // required:true diff --git a/pkg/api/docs/definitions/dashboard_versions.go b/pkg/api/docs/definitions/dashboard_versions.go index 498028903c9..f486e9a0b31 100644 --- a/pkg/api/docs/definitions/dashboard_versions.go +++ b/pkg/api/docs/definitions/dashboard_versions.go @@ -1,11 +1,29 @@ package definitions -import "github.com/grafana/grafana/pkg/models" +import ( + "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/models" +) // swagger:route GET /dashboards/id/{DashboardID}/versions dashboard_versions getDashboardVersions // // Gets all existing versions for the dashboard. // +// Please refer to [updated API](#/dashboard_versions/getDashboardVersionsByUID) instead +// +// Deprecated: true +// +// Responses: +// 200: dashboardVersionsResponse +// 401: unauthorisedError +// 403: forbiddenError +// 404: notFoundError +// 500: internalServerError + +// swagger:route GET /dashboards/uid/{uid}/versions dashboard_versions getDashboardVersionsByUID +// +// Gets all existing versions for the dashboard using UID. +// // Responses: // 200: dashboardVersionsResponse // 401: unauthorisedError @@ -43,6 +61,21 @@ import "github.com/grafana/grafana/pkg/models" // // Restore a dashboard to a given dashboard version. // +// Please refer to [updated API](#/dashboard_versions/restoreDashboardVersionByUID) instead +// +// Deprecated: true +// +// Responses: +// 200: postDashboardResponse +// 401: unauthorisedError +// 403: forbiddenError +// 404: notFoundError +// 500: internalServerError + +// swagger:route POST /dashboards/uid/{uid}/restore dashboard_versions restoreDashboardVersionByUID +// +// Restore a dashboard to a given dashboard version using UID. +// // Responses: // 200: postDashboardResponse // 401: unauthorisedError @@ -64,7 +97,14 @@ type DashboardVersionIdParam struct { DashboardVersionID int64 } -// swagger:parameters getDashboardVersions +// swagger:parameters restoreDashboardVersion restoreDashboardVersionByUID +type RestoreVersionBodyParam struct { + // in:body + // required:true + Body dtos.RestoreDashboardVersionCommand +} + +// swagger:parameters getDashboardVersions getDashboardVersionsByUID type GetDashboardVersionsParams struct { // Maximum number of results to return // in:query diff --git a/pkg/models/dashboard_version.go b/pkg/models/dashboard_version.go index 98ed35d66a5..ba99d246db8 100644 --- a/pkg/models/dashboard_version.go +++ b/pkg/models/dashboard_version.go @@ -49,6 +49,7 @@ type DashboardVersionMeta struct { type DashboardVersionDTO struct { Id int64 `json:"id"` DashboardId int64 `json:"dashboardId"` + DashboardUID string `json:"dashboardUid"` ParentVersion int `json:"parentVersion"` RestoredFrom int `json:"restoredFrom"` Version int `json:"version"` @@ -70,10 +71,11 @@ type GetDashboardVersionQuery struct { } type GetDashboardVersionsQuery struct { - DashboardId int64 - OrgId int64 - Limit int - Start int + DashboardId int64 + DashboardUID string + OrgId int64 + Limit int + Start int Result []*DashboardVersionDTO } diff --git a/public/api-merged.json b/public/api-merged.json index bc29164e5dc..4e42f61d725 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -3631,9 +3631,11 @@ }, "/dashboards/id/{DashboardID}/restore": { "post": { + "description": "Please refer to [updated API](#/dashboard_versions/restoreDashboardVersionByUID) instead", "tags": ["dashboard_versions"], "summary": "Restore a dashboard to a given dashboard version.", "operationId": "restoreDashboardVersion", + "deprecated": true, "parameters": [ { "type": "integer", @@ -3641,6 +3643,14 @@ "name": "DashboardID", "in": "path", "required": true + }, + { + "name": "Body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/RestoreDashboardVersionCommand" + } } ], "responses": { @@ -3664,9 +3674,11 @@ }, "/dashboards/id/{DashboardID}/versions": { "get": { + "description": "Please refer to [updated API](#/dashboard_versions/getDashboardVersionsByUID) instead", "tags": ["dashboard_versions"], "summary": "Gets all existing versions for the dashboard.", "operationId": "getDashboardVersions", + "deprecated": true, "parameters": [ { "type": "integer", @@ -3980,11 +3992,60 @@ } } }, +<<<<<<< HEAD + "/dashboards/uid/{uid}/restore": { + "post": { + "tags": ["dashboard_versions"], + "summary": "Restore a dashboard to a given dashboard version using UID.", + "operationId": "restoreDashboardVersionByUID", + "parameters": [ + { + "type": "string", + "x-go-name": "UID", + "name": "uid", + "in": "path", + "required": true + }, + { + "name": "Body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/RestoreDashboardVersionCommand" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/postDashboardResponse" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, + "/dashboards/uid/{uid}/versions": { + "get": { + "tags": ["dashboard_versions"], + "summary": "Gets all existing versions for the dashboard using UID.", + "operationId": "getDashboardVersionsByUID", +======= "/dashboards/uid/{uid}/versions/{DashboardVersionID}": { "get": { "tags": ["dashboard_versions"], "summary": "Get a specific dashboard version using UID.", "operationId": "getDashboardVersionByUID", +>>>>>>> main "parameters": [ { "type": "string", @@ -3996,14 +4057,35 @@ { "type": "integer", "format": "int64", +<<<<<<< HEAD + "default": 0, + "x-go-name": "Limit", + "description": "Maximum number of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "format": "int64", + "default": 0, + "x-go-name": "Start", + "description": "Version to start from when returning queries", + "name": "start", + "in": "query" +======= "name": "DashboardVersionID", "in": "path", "required": true +>>>>>>> main } ], "responses": { "200": { +<<<<<<< HEAD + "$ref": "#/responses/dashboardVersionsResponse" +======= "$ref": "#/responses/dashboardVersionResponse" +>>>>>>> main }, "401": { "$ref": "#/responses/unauthorisedError" @@ -15012,6 +15094,17 @@ }, "x-go-package": "github.com/grafana/grafana-plugin-sdk-go/backend" }, + "RestoreDashboardVersionCommand": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "format": "int64", + "x-go-name": "Version" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, "RevokeAuthTokenCmd": { "type": "object", "properties": { diff --git a/public/api-spec.json b/public/api-spec.json index 2366bec4863..a363952961e 100644 --- a/public/api-spec.json +++ b/public/api-spec.json @@ -2693,9 +2693,11 @@ }, "/dashboards/id/{DashboardID}/restore": { "post": { + "description": "Please refer to [updated API](#/dashboard_versions/restoreDashboardVersionByUID) instead", "tags": ["dashboard_versions"], "summary": "Restore a dashboard to a given dashboard version.", "operationId": "restoreDashboardVersion", + "deprecated": true, "parameters": [ { "type": "integer", @@ -2703,6 +2705,14 @@ "name": "DashboardID", "in": "path", "required": true + }, + { + "name": "Body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/RestoreDashboardVersionCommand" + } } ], "responses": { @@ -2726,9 +2736,11 @@ }, "/dashboards/id/{DashboardID}/versions": { "get": { + "description": "Please refer to [updated API](#/dashboard_versions/getDashboardVersionsByUID) instead", "tags": ["dashboard_versions"], "summary": "Gets all existing versions for the dashboard.", "operationId": "getDashboardVersions", + "deprecated": true, "parameters": [ { "type": "integer", @@ -3042,6 +3054,52 @@ } } }, + "/dashboards/uid/{uid}/restore": { + "post": { + "tags": ["dashboard_versions"], + "summary": "Restore a dashboard to a given dashboard version using UID.", + "operationId": "restoreDashboardVersionByUID", + "parameters": [ + { + "type": "string", + "x-go-name": "UID", + "name": "uid", + "in": "path", + "required": true + }, + { + "name": "Body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/RestoreDashboardVersionCommand" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/postDashboardResponse" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, + "/dashboards/uid/{uid}/versions": { + "get": { + "tags": ["dashboard_versions"], + "summary": "Gets all existing versions for the dashboard using UID.", + "operationId": "getDashboardVersionsByUID", "/dashboards/uid/{uid}/versions/{DashboardVersionID}": { "get": { "tags": ["dashboard_versions"], @@ -3058,14 +3116,35 @@ { "type": "integer", "format": "int64", +<<<<<<< HEAD + "default": 0, + "x-go-name": "Limit", + "description": "Maximum number of results to return", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "format": "int64", + "default": 0, + "x-go-name": "Start", + "description": "Version to start from when returning queries", + "name": "start", + "in": "query" +======= "name": "DashboardVersionID", "in": "path", "required": true +>>>>>>> main } ], "responses": { "200": { +<<<<<<< HEAD + "$ref": "#/responses/dashboardVersionsResponse" +======= "$ref": "#/responses/dashboardVersionResponse" +>>>>>>> main }, "401": { "$ref": "#/responses/unauthorisedError" @@ -11366,6 +11445,17 @@ }, "x-go-package": "github.com/grafana/grafana-plugin-sdk-go/backend" }, + "RestoreDashboardVersionCommand": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "format": "int64", + "x-go-name": "Version" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, "RevokeAuthTokenCmd": { "type": "object", "properties": {