2014-08-08 18:35:15 +08:00
|
|
|
package models
|
|
|
|
|
|
|
|
import (
|
2014-11-20 19:11:07 +08:00
|
|
|
"errors"
|
2014-08-22 21:32:42 +08:00
|
|
|
"strings"
|
2014-08-22 04:09:48 +08:00
|
|
|
"time"
|
2015-04-15 18:36:02 +08:00
|
|
|
|
2015-06-12 14:53:20 +08:00
|
|
|
"github.com/gosimple/slug"
|
2016-03-12 07:13:06 +08:00
|
|
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
2014-08-08 18:35:15 +08:00
|
|
|
)
|
|
|
|
|
2014-11-20 19:11:07 +08:00
|
|
|
// Typed errors
|
|
|
|
var (
|
2015-05-04 13:46:46 +08:00
|
|
|
ErrDashboardNotFound = errors.New("Dashboard not found")
|
2015-07-08 03:51:38 +08:00
|
|
|
ErrDashboardSnapshotNotFound = errors.New("Dashboard snapshot not found")
|
2015-01-06 00:04:29 +08:00
|
|
|
ErrDashboardWithSameNameExists = errors.New("A dashboard with the same name already exists")
|
2015-03-03 05:24:01 +08:00
|
|
|
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
|
2016-11-29 18:24:34 +08:00
|
|
|
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
|
2014-10-07 03:31:54 +08:00
|
|
|
)
|
|
|
|
|
2016-07-08 19:41:46 +08:00
|
|
|
type UpdatePluginDashboardError struct {
|
|
|
|
PluginId string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d UpdatePluginDashboardError) Error() string {
|
|
|
|
return "Dashboard belong to plugin"
|
|
|
|
}
|
|
|
|
|
2015-05-12 20:11:30 +08:00
|
|
|
var (
|
2015-05-13 15:58:45 +08:00
|
|
|
DashTypeJson = "file"
|
|
|
|
DashTypeDB = "db"
|
|
|
|
DashTypeScript = "script"
|
|
|
|
DashTypeSnapshot = "snapshot"
|
2015-05-12 20:11:30 +08:00
|
|
|
)
|
|
|
|
|
2015-03-19 22:32:47 +08:00
|
|
|
// Dashboard model
|
2014-08-08 18:35:15 +08:00
|
|
|
type Dashboard struct {
|
2016-07-08 15:35:06 +08:00
|
|
|
Id int64
|
|
|
|
Slug string
|
|
|
|
OrgId int64
|
|
|
|
GnetId int64
|
|
|
|
Version int
|
|
|
|
PluginId string
|
2014-11-20 19:11:07 +08:00
|
|
|
|
2015-01-07 19:37:24 +08:00
|
|
|
Created time.Time
|
|
|
|
Updated time.Time
|
2014-08-22 04:09:48 +08:00
|
|
|
|
2015-12-18 17:52:05 +08:00
|
|
|
UpdatedBy int64
|
2016-01-28 14:00:24 +08:00
|
|
|
CreatedBy int64
|
2015-12-18 16:20:23 +08:00
|
|
|
|
2014-08-22 04:09:48 +08:00
|
|
|
Title string
|
2016-03-12 07:13:06 +08:00
|
|
|
Data *simplejson.Json
|
2014-08-22 04:09:48 +08:00
|
|
|
}
|
|
|
|
|
2015-03-19 22:32:47 +08:00
|
|
|
// NewDashboard creates a new dashboard
|
2014-08-08 18:35:15 +08:00
|
|
|
func NewDashboard(title string) *Dashboard {
|
|
|
|
dash := &Dashboard{}
|
2016-03-12 07:13:06 +08:00
|
|
|
dash.Data = simplejson.New()
|
|
|
|
dash.Data.Set("title", title)
|
2014-08-22 21:32:42 +08:00
|
|
|
dash.Title = title
|
2015-11-20 20:37:24 +08:00
|
|
|
dash.Created = time.Now()
|
2015-11-20 20:52:50 +08:00
|
|
|
dash.Updated = time.Now()
|
|
|
|
dash.UpdateSlug()
|
2014-08-08 18:35:15 +08:00
|
|
|
return dash
|
|
|
|
}
|
|
|
|
|
2015-03-19 22:32:47 +08:00
|
|
|
// GetTags turns the tags in data json into go string array
|
2015-01-07 19:37:24 +08:00
|
|
|
func (dash *Dashboard) GetTags() []string {
|
2016-03-12 07:13:06 +08:00
|
|
|
return dash.Data.Get("tags").MustStringArray()
|
2015-01-07 19:37:24 +08:00
|
|
|
}
|
|
|
|
|
2016-03-12 07:13:06 +08:00
|
|
|
func NewDashboardFromJson(data *simplejson.Json) *Dashboard {
|
2014-12-22 19:25:08 +08:00
|
|
|
dash := &Dashboard{}
|
2015-05-12 18:20:03 +08:00
|
|
|
dash.Data = data
|
2016-03-12 07:13:06 +08:00
|
|
|
dash.Title = dash.Data.Get("title").MustString()
|
2015-11-20 20:52:50 +08:00
|
|
|
dash.UpdateSlug()
|
2014-08-08 18:35:15 +08:00
|
|
|
|
2016-03-12 07:13:06 +08:00
|
|
|
if id, err := dash.Data.Get("id").Float64(); err == nil {
|
|
|
|
dash.Id = int64(id)
|
2015-03-03 05:24:01 +08:00
|
|
|
|
2016-03-12 07:13:06 +08:00
|
|
|
if version, err := dash.Data.Get("version").Float64(); err == nil {
|
|
|
|
dash.Version = int(version)
|
2015-11-20 20:52:50 +08:00
|
|
|
dash.Updated = time.Now()
|
|
|
|
}
|
2015-03-19 22:32:47 +08:00
|
|
|
} else {
|
2016-03-12 07:13:06 +08:00
|
|
|
dash.Data.Set("version", 0)
|
2015-11-20 20:52:50 +08:00
|
|
|
dash.Created = time.Now()
|
|
|
|
dash.Updated = time.Now()
|
2014-08-08 18:35:15 +08:00
|
|
|
}
|
|
|
|
|
2016-05-27 22:42:32 +08:00
|
|
|
if gnetId, err := dash.Data.Get("gnetId").Float64(); err == nil {
|
|
|
|
dash.GnetId = int64(gnetId)
|
|
|
|
}
|
|
|
|
|
2014-12-22 19:25:08 +08:00
|
|
|
return dash
|
2014-08-08 18:35:15 +08:00
|
|
|
}
|
|
|
|
|
2015-05-12 18:20:03 +08:00
|
|
|
// GetDashboardModel turns the command into the savable model
|
|
|
|
func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
|
|
|
|
dash := NewDashboardFromJson(cmd.Dashboard)
|
2017-06-05 22:34:32 +08:00
|
|
|
userId := cmd.UserId
|
|
|
|
|
|
|
|
if userId == 0 {
|
|
|
|
userId = -1
|
|
|
|
}
|
2016-03-12 07:13:06 +08:00
|
|
|
|
|
|
|
if dash.Data.Get("version").MustInt(0) == 0 {
|
2017-06-05 22:34:32 +08:00
|
|
|
dash.CreatedBy = userId
|
2016-01-28 14:00:24 +08:00
|
|
|
}
|
2016-03-12 07:13:06 +08:00
|
|
|
|
2017-06-05 22:34:32 +08:00
|
|
|
dash.UpdatedBy = userId
|
2016-01-28 14:00:24 +08:00
|
|
|
dash.OrgId = cmd.OrgId
|
2016-07-08 18:26:51 +08:00
|
|
|
dash.PluginId = cmd.PluginId
|
2015-05-12 18:20:03 +08:00
|
|
|
dash.UpdateSlug()
|
|
|
|
return dash
|
|
|
|
}
|
|
|
|
|
2015-03-19 22:32:47 +08:00
|
|
|
// GetString a
|
2016-03-11 02:57:48 +08:00
|
|
|
func (dash *Dashboard) GetString(prop string, defaultValue string) string {
|
2016-03-12 07:13:06 +08:00
|
|
|
return dash.Data.Get(prop).MustString(defaultValue)
|
2014-08-08 18:35:15 +08:00
|
|
|
}
|
2014-08-22 21:32:42 +08:00
|
|
|
|
2015-03-19 22:32:47 +08:00
|
|
|
// UpdateSlug updates the slug
|
2014-08-22 21:32:42 +08:00
|
|
|
func (dash *Dashboard) UpdateSlug() {
|
2016-03-12 07:13:06 +08:00
|
|
|
title := strings.ToLower(dash.Data.Get("title").MustString())
|
2015-04-15 18:36:02 +08:00
|
|
|
dash.Slug = slug.Make(title)
|
2014-08-22 21:32:42 +08:00
|
|
|
}
|
2015-01-09 18:01:37 +08:00
|
|
|
|
|
|
|
//
|
|
|
|
// COMMANDS
|
|
|
|
//
|
|
|
|
|
|
|
|
type SaveDashboardCommand struct {
|
2016-03-12 07:13:06 +08:00
|
|
|
Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
|
|
|
|
UserId int64 `json:"userId"`
|
|
|
|
OrgId int64 `json:"-"`
|
|
|
|
Overwrite bool `json:"overwrite"`
|
2016-07-08 18:26:51 +08:00
|
|
|
PluginId string `json:"-"`
|
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-25 07:14:39 +08:00
|
|
|
Message string `json:"message"`
|
2015-01-09 18:01:37 +08:00
|
|
|
|
|
|
|
Result *Dashboard
|
|
|
|
}
|
|
|
|
|
|
|
|
type DeleteDashboardCommand struct {
|
2015-02-24 03:07:49 +08:00
|
|
|
Slug string
|
|
|
|
OrgId int64
|
2015-01-09 18:01:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// QUERIES
|
|
|
|
//
|
|
|
|
|
|
|
|
type GetDashboardQuery struct {
|
2017-06-05 23:45:27 +08:00
|
|
|
Slug string // required if no Id is specified
|
|
|
|
Id int64 // optional if slug is set
|
2015-02-24 03:07:49 +08:00
|
|
|
OrgId int64
|
2015-01-09 18:01:37 +08:00
|
|
|
|
|
|
|
Result *Dashboard
|
|
|
|
}
|
2015-05-13 19:36:13 +08:00
|
|
|
|
|
|
|
type DashboardTagCloudItem struct {
|
|
|
|
Term string `json:"term"`
|
|
|
|
Count int `json:"count"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type GetDashboardTagsQuery struct {
|
|
|
|
OrgId int64
|
|
|
|
Result []*DashboardTagCloudItem
|
|
|
|
}
|
2016-01-29 08:41:23 +08:00
|
|
|
|
|
|
|
type GetDashboardsQuery struct {
|
|
|
|
DashboardIds []int64
|
2016-07-08 15:35:06 +08:00
|
|
|
Result []*Dashboard
|
|
|
|
}
|
|
|
|
|
|
|
|
type GetDashboardsByPluginIdQuery struct {
|
|
|
|
OrgId int64
|
|
|
|
PluginId string
|
|
|
|
Result []*Dashboard
|
2016-01-29 08:41:23 +08:00
|
|
|
}
|
2016-03-17 16:01:58 +08:00
|
|
|
|
|
|
|
type GetDashboardSlugByIdQuery struct {
|
|
|
|
Id int64
|
|
|
|
Result string
|
|
|
|
}
|