Refactor: Add UID endpoint for dashboard versions and restore (#48364)

* Refactor: Add UID endpoint for dashboard versions and restore

* Fix: User dashID instead of dash.id

* 💩

* Move apiCmd error handling outside of dashUID check

* fix the panic in test

* Fix handler and update docs

Co-authored-by: Kat Yang <yangkb09@users.noreply.github.com>

* Docs: add deprecated warning to restore and version docs

* Fix hyperlink text

* Add swagger endpoints for restore and versions

* Add deprecated tag on swagger for both endpoints

* Fix: Update access control to be dashboards

* Return UID in response; Update docs to reflect this; Implement Ying suggestion

* Update docs/sources/http_api/dashboard_versions.md

Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>

* Update pkg/models/dashboard_version.go

Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>

* Update pkg/models/dashboard_version.go

Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>

* Update query to refer to DashboardUID

Co-authored-by: Ying WANG <ying.wang@grafana.com>
Co-authored-by: Sofia Papagiannaki <sofia@grafana.com>
Co-authored-by: Kat Yang <yangkb09@users.noreply.github.com>
Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>
pull/49116/head
Kat Yang 3 years ago committed by GitHub
parent c652849303
commit 719af24235
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 133
      docs/sources/http_api/dashboard_versions.md
  2. 2
      pkg/api/api.go
  3. 55
      pkg/api/dashboard.go
  4. 3
      pkg/api/dashboard_test.go
  5. 1
      pkg/api/docs/definitions/dashboard.go
  6. 44
      pkg/api/docs/definitions/dashboard_versions.go
  7. 10
      pkg/models/dashboard_version.go
  8. 93
      public/api-merged.json
  9. 90
      public/api-spec.json

@ -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`

@ -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))

@ -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)
}

@ -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")

@ -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

@ -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

@ -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
}

@ -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": {

@ -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": {

Loading…
Cancel
Save