dashboard_version.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. package sqlstore
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "time"
  6. "github.com/grafana/grafana/pkg/bus"
  7. "github.com/grafana/grafana/pkg/components/formatter"
  8. "github.com/grafana/grafana/pkg/components/simplejson"
  9. m "github.com/grafana/grafana/pkg/models"
  10. diff "github.com/yudai/gojsondiff"
  11. deltaFormatter "github.com/yudai/gojsondiff/formatter"
  12. )
  13. var (
  14. // ErrUnsupportedDiffType occurs when an invalid diff type is used.
  15. ErrUnsupportedDiffType = errors.New("sqlstore: unsupported diff type")
  16. // ErrNilDiff occurs when two compared interfaces are identical.
  17. ErrNilDiff = errors.New("sqlstore: diff is nil")
  18. )
  19. func init() {
  20. bus.AddHandler("sql", CompareDashboardVersionsCommand)
  21. bus.AddHandler("sql", GetDashboardVersion)
  22. bus.AddHandler("sql", GetDashboardVersions)
  23. bus.AddHandler("sql", RestoreDashboardVersion)
  24. }
  25. // CompareDashboardVersionsCommand computes the JSON diff of two versions,
  26. // assigning the delta of the diff to the `Delta` field.
  27. func CompareDashboardVersionsCommand(cmd *m.CompareDashboardVersionsCommand) error {
  28. original, err := getDashboardVersion(cmd.DashboardId, cmd.Original, cmd.OrgId)
  29. if err != nil {
  30. return err
  31. }
  32. newDashboard, err := getDashboardVersion(cmd.DashboardId, cmd.New, cmd.OrgId)
  33. if err != nil {
  34. return err
  35. }
  36. left, jsonDiff, err := getDiff(original, newDashboard)
  37. if err != nil {
  38. return err
  39. }
  40. switch cmd.DiffType {
  41. case m.DiffDelta:
  42. deltaOutput, err := deltaFormatter.NewDeltaFormatter().Format(jsonDiff)
  43. if err != nil {
  44. return err
  45. }
  46. cmd.Delta = []byte(deltaOutput)
  47. case m.DiffJSON:
  48. jsonOutput, err := formatter.NewJSONFormatter(left).Format(jsonDiff)
  49. if err != nil {
  50. return err
  51. }
  52. cmd.Delta = []byte(jsonOutput)
  53. case m.DiffBasic:
  54. basicOutput, err := formatter.NewBasicFormatter(left).Format(jsonDiff)
  55. if err != nil {
  56. return err
  57. }
  58. cmd.Delta = basicOutput
  59. default:
  60. return ErrUnsupportedDiffType
  61. }
  62. return nil
  63. }
  64. // GetDashboardVersion gets the dashboard version for the given dashboard ID
  65. // and version number.
  66. func GetDashboardVersion(query *m.GetDashboardVersionQuery) error {
  67. result, err := getDashboardVersion(query.DashboardId, query.Version, query.OrgId)
  68. if err != nil {
  69. return err
  70. }
  71. query.Result = result
  72. return nil
  73. }
  74. // GetDashboardVersions gets all dashboard versions for the given dashboard ID.
  75. func GetDashboardVersions(query *m.GetDashboardVersionsQuery) error {
  76. err := x.Table("dashboard_version").
  77. Select(`dashboard_version.id,
  78. dashboard_version.dashboard_id,
  79. dashboard_version.parent_version,
  80. dashboard_version.restored_from,
  81. dashboard_version.version,
  82. dashboard_version.created,
  83. dashboard_version.created_by as created_by_id,
  84. dashboard_version.message,
  85. dashboard_version.data,
  86. "user".login as created_by`).
  87. Join("LEFT", "user", `dashboard_version.created_by = "user".id`).
  88. Join("LEFT", "dashboard", `dashboard.id = "dashboard_version".dashboard_id`).
  89. Where("dashboard_version.dashboard_id=? AND dashboard.org_id=?", query.DashboardId, query.OrgId).
  90. OrderBy("dashboard_version.version DESC").
  91. Limit(query.Limit, query.Start).
  92. Find(&query.Result)
  93. if err != nil {
  94. return err
  95. }
  96. if len(query.Result) < 1 {
  97. return m.ErrNoVersionsForDashboardId
  98. }
  99. return nil
  100. }
  101. // RestoreDashboardVersion restores the dashboard data to the given version.
  102. func RestoreDashboardVersion(cmd *m.RestoreDashboardVersionCommand) error {
  103. return inTransaction(func(sess *DBSession) error {
  104. // check if dashboard version exists in dashboard_version table
  105. //
  106. // normally we could use the getDashboardVersion func here, but since
  107. // we're in a transaction, we need to run the queries using the
  108. // session instead of using the global `x`, so we copy those functions
  109. // here, replacing `x` with `sess`
  110. dashboardVersion := m.DashboardVersion{}
  111. has, err := sess.Where("dashboard_id=? AND version=? AND org_id=?", cmd.DashboardId, cmd.Version, cmd.OrgId).Get(&dashboardVersion)
  112. if err != nil {
  113. return err
  114. }
  115. if !has {
  116. return m.ErrDashboardVersionNotFound
  117. }
  118. dashboardVersion.Data.Set("id", dashboardVersion.DashboardId)
  119. dashboard := m.Dashboard{Id: cmd.DashboardId}
  120. // Get the dashboard version
  121. if has, err = sess.Get(&dashboard); err != nil {
  122. return err
  123. } else if !has {
  124. return m.ErrDashboardNotFound
  125. }
  126. version, err := getMaxVersion(sess, dashboard.Id)
  127. if err != nil {
  128. return err
  129. }
  130. // revert and save to a new dashboard version
  131. dashboard.Data = dashboardVersion.Data
  132. dashboard.Updated = time.Now()
  133. dashboard.UpdatedBy = cmd.UserId
  134. dashboard.Version = version
  135. dashboard.Data.Set("version", dashboard.Version)
  136. // Update dashboard
  137. if affectedRows, err := sess.Id(dashboard.Id).Update(dashboard); err != nil {
  138. return err
  139. } else if affectedRows == 0 {
  140. return m.ErrDashboardNotFound
  141. }
  142. // save that version a new version
  143. dashVersion := &m.DashboardVersion{
  144. DashboardId: dashboard.Id,
  145. ParentVersion: cmd.Version,
  146. RestoredFrom: cmd.Version,
  147. Version: dashboard.Version,
  148. Created: time.Now(),
  149. CreatedBy: dashboard.UpdatedBy,
  150. Message: "",
  151. Data: dashboard.Data,
  152. }
  153. if affectedRows, err := sess.Insert(dashVersion); err != nil {
  154. return err
  155. } else if affectedRows == 0 {
  156. return m.ErrDashboardNotFound
  157. }
  158. cmd.Result = &dashboard
  159. return nil
  160. })
  161. }
  162. // getDashboardVersion is a helper function that gets the dashboard version for
  163. // the given dashboard ID and version ID.
  164. func getDashboardVersion(dashboardId int64, version int, orgId int64) (*m.DashboardVersion, error) {
  165. dashboardVersion := m.DashboardVersion{}
  166. has, err := x.Where("dashboard_id=? AND version=? AND org_id=?", dashboardId, version, orgId).Get(&dashboardVersion)
  167. if err != nil {
  168. return nil, err
  169. }
  170. if !has {
  171. return nil, m.ErrDashboardVersionNotFound
  172. }
  173. dashboardVersion.Data.Set("id", dashboardVersion.DashboardId)
  174. return &dashboardVersion, nil
  175. }
  176. // getDashboard gets a dashboard by ID. Used for retrieving the dashboard
  177. // associated with dashboard versions.
  178. func getDashboard(dashboardId int64) (*m.Dashboard, error) {
  179. dashboard := m.Dashboard{Id: dashboardId}
  180. has, err := x.Get(&dashboard)
  181. if err != nil {
  182. return nil, err
  183. }
  184. if has == false {
  185. return nil, m.ErrDashboardNotFound
  186. }
  187. return &dashboard, nil
  188. }
  189. // getDiff computes the diff of two dashboard versions.
  190. func getDiff(originalDash, newDash *m.DashboardVersion) (interface{}, diff.Diff, error) {
  191. leftBytes, err := simplejson.NewFromAny(originalDash).Encode()
  192. if err != nil {
  193. return nil, nil, err
  194. }
  195. rightBytes, err := simplejson.NewFromAny(newDash).Encode()
  196. if err != nil {
  197. return nil, nil, err
  198. }
  199. jsonDiff, err := diff.New().Compare(leftBytes, rightBytes)
  200. if err != nil {
  201. return nil, nil, err
  202. }
  203. if !jsonDiff.Modified() {
  204. return nil, nil, ErrNilDiff
  205. }
  206. left := make(map[string]interface{})
  207. err = json.Unmarshal(leftBytes, &left)
  208. return left, jsonDiff, nil
  209. }
  210. type version struct {
  211. Max int
  212. }
  213. // getMaxVersion returns the highest version number in the `dashboard_version`
  214. // table.
  215. //
  216. // This is necessary because sqlite3 doesn't support autoincrement in the same
  217. // way that Postgres or MySQL do, so we use this to get around that. Since it's
  218. // impossible to delete a version in Grafana, this is believed to be a
  219. // safe-enough alternative.
  220. func getMaxVersion(sess *DBSession, dashboardId int64) (int, error) {
  221. v := version{}
  222. has, err := sess.Table("dashboard_version").
  223. Select("MAX(version) AS max").
  224. Where("dashboard_id = ?", dashboardId).
  225. Get(&v)
  226. if !has {
  227. return 0, m.ErrDashboardNotFound
  228. }
  229. if err != nil {
  230. return 0, err
  231. }
  232. v.Max++
  233. return v.Max, nil
  234. }