Files
grafana/pkg/services/sqlstore/dashboard_version_test.go
Ben Tranter b6e46c9eb8 History and Version Control for Dashboard Updates
A simple version control system for dashboards. Closes #1504.

Goals

1. To create a new dashboard version every time a dashboard is saved.
2. To allow users to view all versions of a given dashboard.
3. To allow users to rollback to a previous version of a dashboard.
4. To allow users to compare two versions of a dashboard.

Usage

Navigate to a dashboard, and click the settings cog. From there, click
the "Changelog" button to be brought to the Changelog view. In this
view, a table containing each version of a dashboard can be seen. Each
entry in the table represents a dashboard version. A selectable
checkbox, the version number, date created, name of the user who created
that version, and commit message is shown in the table, along with a
button that allows a user to restore to a previous version of that
dashboard. If a user wants to restore to a previous version of their
dashboard, they can do so by clicking the previously mentioned button.
If a user wants to compare two different versions of a dashboard, they
can do so by clicking the checkbox of two different dashboard versions,
then clicking the "Compare versions" button located below the dashboard.
From there, the user is brought to a view showing a summary of the
dashboard differences. Each summarized change contains a link that can
be clicked to take the user a JSON diff highlighting the changes line by
line.

Overview of Changes

Backend Changes

- A `dashboard_version` table was created to store each dashboard
  version, along with a dashboard version model and structs to represent
  the queries and commands necessary for the dashboard version API
  methods.
- API endpoints were created to support working with dashboard
  versions.
- Methods were added to create, update, read, and destroy dashboard
  versions in the database.
  - Logic was added to compute the diff between two versions, and
  display it to the user.
  - The dashboard migration logic was updated to save a "Version
  1" of each existing dashboard in the database.

Frontend Changes

- New views
- Methods to pull JSON and HTML from endpoints

New API Endpoints

Each endpoint requires the authorization header to be sent in
the format,

```
Authorization: Bearer <jwt>
```

where `<jwt>` is a JSON web token obtained from the Grafana
admin panel.

`GET "/api/dashboards/db/:dashboardId/versions?orderBy=<string>&limit=<int>&start=<int>"`

Get all dashboard versions for the given dashboard ID. Accepts
three URL parameters:

- `orderBy` String to order the results by. Possible values
  are `version`, `created`, `created_by`, `message`. Default
  is `versions`. Ordering is always in descending order.
- `limit` Maximum number of results to return
- `start` Position in results to start from

`GET "/api/dashboards/db/:dashboardId/versions/:id"`

Get an individual dashboard version by ID, for the given
dashboard ID.

`POST "/api/dashboards/db/:dashboardId/restore"`

Restore to the given dashboard version. Post body is of
content-type `application/json`, and must contain.

```json
{
  "dashboardId": <int>,
  "version": <int>
}
```

`GET "/api/dashboards/db/:dashboardId/compare/:versionA...:versionB"`

Compare two dashboard versions by ID for the given
dashboard ID, returning a JSON delta formatted
representation of the diff. The URL format follows
what GitHub does. For example, visiting
[/api/dashboards/db/18/compare/22...33](http://ec2-54-80-139-44.compute-1.amazonaws.com:3000/api/dashboards/db/18/compare/22...33)
will return the diff between versions 22 and 33 for
the dashboard ID 18.

Dependencies Added

- The Go package [gojsondiff](https://github.com/yudai/gojsondiff)
  was added and vendored.
2017-05-24 19:14:39 -04:00

189 lines
4.8 KiB
Go

package sqlstore
import (
"reflect"
"testing"
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
)
func updateTestDashboard(dashboard *m.Dashboard, data map[string]interface{}) {
data["title"] = dashboard.Title
saveCmd := m.SaveDashboardCommand{
OrgId: dashboard.OrgId,
Overwrite: true,
Dashboard: simplejson.NewFromAny(data),
}
err := SaveDashboard(&saveCmd)
So(err, ShouldBeNil)
}
func TestGetDashboardVersion(t *testing.T) {
Convey("Testing dashboard version retrieval", t, func() {
InitTestDB(t)
Convey("Get a Dashboard ID and version ID", func() {
savedDash := insertTestDashboard("test dash 26", 1, "diff")
cmd := m.GetDashboardVersionCommand{
DashboardId: savedDash.Id,
Version: savedDash.Version,
}
err := GetDashboardVersion(&cmd)
So(err, ShouldBeNil)
So(savedDash.Id, ShouldEqual, cmd.DashboardId)
So(savedDash.Version, ShouldEqual, cmd.Version)
dashCmd := m.GetDashboardQuery{
OrgId: savedDash.OrgId,
Slug: savedDash.Slug,
}
err = GetDashboard(&dashCmd)
So(err, ShouldBeNil)
eq := reflect.DeepEqual(dashCmd.Result.Data, cmd.Result.Data)
So(eq, ShouldEqual, true)
})
Convey("Attempt to get a version that doesn't exist", func() {
cmd := m.GetDashboardVersionCommand{
DashboardId: int64(999),
Version: 123,
}
err := GetDashboardVersion(&cmd)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, m.ErrDashboardVersionNotFound)
})
})
}
func TestGetDashboardVersions(t *testing.T) {
Convey("Testing dashboard versions retrieval", t, func() {
InitTestDB(t)
savedDash := insertTestDashboard("test dash 43", 1, "diff-all")
Convey("Get all versions for a given Dashboard ID", func() {
cmd := m.GetDashboardVersionsCommand{
DashboardId: savedDash.Id,
}
err := GetDashboardVersions(&cmd)
So(err, ShouldBeNil)
So(len(cmd.Result), ShouldEqual, 1)
})
Convey("Attempt to get the versions for a non-existent Dashboard ID", func() {
cmd := m.GetDashboardVersionsCommand{
DashboardId: int64(999),
}
err := GetDashboardVersions(&cmd)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, m.ErrNoVersionsForDashboardId)
So(len(cmd.Result), ShouldEqual, 0)
})
Convey("Get all versions for an updated dashboard", func() {
updateTestDashboard(savedDash, map[string]interface{}{
"tags": "different-tag",
})
cmd := m.GetDashboardVersionsCommand{
DashboardId: savedDash.Id,
}
err := GetDashboardVersions(&cmd)
So(err, ShouldBeNil)
So(len(cmd.Result), ShouldEqual, 2)
})
})
}
func TestCompareDashboardVersions(t *testing.T) {
Convey("Testing dashboard version comparison", t, func() {
InitTestDB(t)
savedDash := insertTestDashboard("test dash 43", 1, "x")
updateTestDashboard(savedDash, map[string]interface{}{
"tags": "y",
})
Convey("Compare two versions that are different", func() {
getVersionCmd := m.GetDashboardVersionsCommand{
DashboardId: savedDash.Id,
}
err := GetDashboardVersions(&getVersionCmd)
So(err, ShouldBeNil)
So(len(getVersionCmd.Result), ShouldEqual, 2)
cmd := m.CompareDashboardVersionsCommand{
DashboardId: savedDash.Id,
Original: getVersionCmd.Result[0].Version,
New: getVersionCmd.Result[1].Version,
DiffType: m.DiffDelta,
}
err = CompareDashboardVersionsCommand(&cmd)
So(err, ShouldBeNil)
So(cmd.Delta, ShouldNotBeNil)
})
Convey("Compare two versions that are the same", func() {
cmd := m.CompareDashboardVersionsCommand{
DashboardId: savedDash.Id,
Original: savedDash.Version,
New: savedDash.Version,
DiffType: m.DiffDelta,
}
err := CompareDashboardVersionsCommand(&cmd)
So(err, ShouldNotBeNil)
So(cmd.Delta, ShouldBeNil)
})
Convey("Compare two versions that don't exist", func() {
cmd := m.CompareDashboardVersionsCommand{
DashboardId: savedDash.Id,
Original: 123,
New: 456,
DiffType: m.DiffDelta,
}
err := CompareDashboardVersionsCommand(&cmd)
So(err, ShouldNotBeNil)
So(cmd.Delta, ShouldBeNil)
})
})
}
func TestRestoreDashboardVersion(t *testing.T) {
Convey("Testing dashboard version restoration", t, func() {
InitTestDB(t)
savedDash := insertTestDashboard("test dash 26", 1, "restore")
updateTestDashboard(savedDash, map[string]interface{}{
"tags": "not restore",
})
Convey("Restore dashboard to a previous version", func() {
versionsCmd := m.GetDashboardVersionsCommand{
DashboardId: savedDash.Id,
}
err := GetDashboardVersions(&versionsCmd)
So(err, ShouldBeNil)
cmd := m.RestoreDashboardVersionCommand{
DashboardId: savedDash.Id,
Version: savedDash.Version,
UserId: 0,
}
err = RestoreDashboardVersion(&cmd)
So(err, ShouldBeNil)
})
})
}