diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index b0a38c63eb1..736461cabae 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/dashdiffs" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/metrics" @@ -324,8 +325,7 @@ func GetDashboardVersion(c *middleware.Context) Response { return Json(200, dashVersionMeta) } -func createCompareDashboardVersionCommand(c *middleware.Context) (*m.CompareDashboardVersionsCommand, error) { - cmd := &m.CompareDashboardVersionsCommand{} +func getDashboardVersionDiffOptions(c *middleware.Context, diffType dashdiffs.DiffType) (*dashdiffs.Options, error) { dashId := c.ParamsInt64(":dashboardId") if dashId == 0 { @@ -347,39 +347,42 @@ func createCompareDashboardVersionCommand(c *middleware.Context) (*m.CompareDash return nil, fmt.Errorf("bad format: second argument is not of type int") } - cmd.DashboardId = dashId - cmd.OrgId = c.OrgId - cmd.BaseVersion = BaseVersion - cmd.NewVersion = newVersion + options := &dashdiffs.Options{} + options.DashboardId = dashId + options.OrgId = c.OrgId + options.BaseVersion = BaseVersion + options.NewVersion = newVersion + options.DiffType = diffType - return cmd, nil + return options, nil } // CompareDashboardVersions compares dashboards the way the GitHub API does. func CompareDashboardVersions(c *middleware.Context) Response { - cmd, err := createCompareDashboardVersionCommand(c) + options, err := getDashboardVersionDiffOptions(c, dashdiffs.DiffDelta) + if err != nil { return ApiError(500, err.Error(), err) } - cmd.DiffType = m.DiffDelta - - if err := bus.Dispatch(&cmd); err != nil { + result, err := dashdiffs.GetVersionDiff(options) + if err != nil { return ApiError(500, "Unable to compute diff", err) } // here the output is already JSON, so we need to unmarshal it into a // map before marshaling the entire response + deltaMap := make(map[string]interface{}) - err = json.Unmarshal(cmd.Delta, &deltaMap) + err = json.Unmarshal(result.Delta, &deltaMap) if err != nil { return ApiError(500, err.Error(), err) } return Json(200, util.DynMap{ "meta": util.DynMap{ - "baseVersion": cmd.BaseVersion, - "newVersion": cmd.NewVersion, + "baseVersion": options.BaseVersion, + "newVersion": options.NewVersion, }, "delta": deltaMap, }) @@ -388,34 +391,35 @@ func CompareDashboardVersions(c *middleware.Context) Response { // CompareDashboardVersionsJSON compares dashboards the way the GitHub API does, // returning a human-readable JSON diff. func CompareDashboardVersionsJSON(c *middleware.Context) Response { - cmd, err := createCompareDashboardVersionCommand(c) + options, err := getDashboardVersionDiffOptions(c, dashdiffs.DiffJSON) + if err != nil { return ApiError(500, err.Error(), err) } - cmd.DiffType = m.DiffJSON - if err := bus.Dispatch(cmd); err != nil { + result, err := dashdiffs.GetVersionDiff(options) + if err != nil { return ApiError(500, err.Error(), err) } - return Respond(200, cmd.Delta).Header("Content-Type", "text/html") + return Respond(200, result.Delta).Header("Content-Type", "text/html") } // CompareDashboardVersionsBasic compares dashboards the way the GitHub API does, // returning a human-readable diff. func CompareDashboardVersionsBasic(c *middleware.Context) Response { - cmd, err := createCompareDashboardVersionCommand(c) + options, err := getDashboardVersionDiffOptions(c, dashdiffs.DiffBasic) + if err != nil { return ApiError(500, err.Error(), err) } - cmd.DiffType = m.DiffBasic - - if err := bus.Dispatch(cmd); err != nil { + result, err := dashdiffs.GetVersionDiff(options) + if err != nil { return ApiError(500, err.Error(), err) } - return Respond(200, cmd.Delta).Header("Content-Type", "text/html") + return Respond(200, result.Delta).Header("Content-Type", "text/html") } // RestoreDashboardVersion restores a dashboard to the given version. diff --git a/pkg/components/dashdiffs/compare.go b/pkg/components/dashdiffs/compare.go new file mode 100644 index 00000000000..a559c5b16f9 --- /dev/null +++ b/pkg/components/dashdiffs/compare.go @@ -0,0 +1,124 @@ +package dashdiffs + +import ( + "encoding/json" + "errors" + + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/models" + diff "github.com/yudai/gojsondiff" + deltaFormatter "github.com/yudai/gojsondiff/formatter" +) + +type DiffType int + +const ( + DiffJSON DiffType = iota + DiffBasic + DiffDelta +) + +var ( + // ErrUnsupportedDiffType occurs when an invalid diff type is used. + ErrUnsupportedDiffType = errors.New("dashdiff: unsupported diff type") + + // ErrNilDiff occurs when two compared interfaces are identical. + ErrNilDiff = errors.New("dashdiff: diff is nil") +) + +type Options struct { + OrgId int64 + DashboardId int64 + BaseVersion int + NewVersion int + DiffType DiffType +} + +type Result struct { + Delta []byte `json:"delta"` +} + +// CompareDashboardVersionsCommand computes the JSON diff of two versions, +// assigning the delta of the diff to the `Delta` field. +func GetVersionDiff(options *Options) (*Result, error) { + baseVersionQuery := models.GetDashboardVersionQuery{ + DashboardId: options.DashboardId, + Version: options.BaseVersion, + } + + if err := bus.Dispatch(&baseVersionQuery); err != nil { + return nil, err + } + + newVersionQuery := models.GetDashboardVersionQuery{ + DashboardId: options.DashboardId, + Version: options.NewVersion, + } + + if err := bus.Dispatch(&newVersionQuery); err != nil { + return nil, err + } + + left, jsonDiff, err := getDiff(baseVersionQuery.Result, newVersionQuery.Result) + if err != nil { + return nil, err + } + + result := &Result{} + + switch options.DiffType { + case DiffDelta: + + deltaOutput, err := deltaFormatter.NewDeltaFormatter().Format(jsonDiff) + if err != nil { + return nil, err + } + result.Delta = []byte(deltaOutput) + + case DiffJSON: + jsonOutput, err := NewJSONFormatter(left).Format(jsonDiff) + if err != nil { + return nil, err + } + result.Delta = []byte(jsonOutput) + + case DiffBasic: + basicOutput, err := NewBasicFormatter(left).Format(jsonDiff) + if err != nil { + return nil, err + } + result.Delta = basicOutput + + default: + return nil, ErrUnsupportedDiffType + } + + return result, nil +} + +// getDiff computes the diff of two dashboard versions. +func getDiff(originalDash, newDash *models.DashboardVersion) (interface{}, diff.Diff, error) { + leftBytes, err := simplejson.NewFromAny(originalDash).Encode() + if err != nil { + return nil, nil, err + } + + rightBytes, err := simplejson.NewFromAny(newDash).Encode() + if err != nil { + return nil, nil, err + } + + jsonDiff, err := diff.New().Compare(leftBytes, rightBytes) + if err != nil { + return nil, nil, err + } + + if !jsonDiff.Modified() { + return nil, nil, ErrNilDiff + } + + left := make(map[string]interface{}) + err = json.Unmarshal(leftBytes, &left) + return left, jsonDiff, nil +} diff --git a/pkg/components/formatter/formatter_basic.go b/pkg/components/dashdiffs/formatter_basic.go similarity index 99% rename from pkg/components/formatter/formatter_basic.go rename to pkg/components/dashdiffs/formatter_basic.go index bf460f95f90..ea935604349 100644 --- a/pkg/components/formatter/formatter_basic.go +++ b/pkg/components/dashdiffs/formatter_basic.go @@ -1,4 +1,4 @@ -package formatter +package dashdiffs import ( "bytes" @@ -261,7 +261,7 @@ var ( line-display="{{ .LineStart }}{{ if .LineEnd }} - {{ .LineEnd }}{{ end }}" switch-view="ctrl.getDiff('html')" /> - {{ end }} + {{ end }} @@ -286,7 +286,7 @@ var (