dashboard_version.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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)
  29. if err != nil {
  30. return err
  31. }
  32. newDashboard, err := getDashboardVersion(cmd.DashboardId, cmd.New)
  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.GetDashboardVersionCommand) error {
  67. result, err := getDashboardVersion(query.DashboardId, query.Version)
  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.GetDashboardVersionsCommand) error {
  76. if query.OrderBy == "" {
  77. query.OrderBy = "version"
  78. }
  79. query.OrderBy += " desc"
  80. err := x.Table("dashboard_version").
  81. Select(`dashboard_version.id,
  82. dashboard_version.dashboard_id,
  83. dashboard_version.parent_version,
  84. dashboard_version.restored_from,
  85. dashboard_version.version,
  86. dashboard_version.created,
  87. dashboard_version.created_by as created_by_id,
  88. dashboard_version.message,
  89. dashboard_version.data,
  90. "user".login as created_by`).
  91. Join("LEFT", "user", `dashboard_version.created_by = "user".id`).
  92. Where("dashboard_version.dashboard_id=?", query.DashboardId).
  93. OrderBy("dashboard_version."+query.OrderBy).
  94. Limit(query.Limit, query.Start).
  95. Find(&query.Result)
  96. if err != nil {
  97. return err
  98. }
  99. if len(query.Result) < 1 {
  100. return m.ErrNoVersionsForDashboardId
  101. }
  102. return nil
  103. }
  104. // RestoreDashboardVersion restores the dashboard data to the given version.
  105. func RestoreDashboardVersion(cmd *m.RestoreDashboardVersionCommand) error {
  106. return inTransaction(func(sess *DBSession) error {
  107. // check if dashboard version exists in dashboard_version table
  108. //
  109. // normally we could use the getDashboardVersion func here, but since
  110. // we're in a transaction, we need to run the queries using the
  111. // session instead of using the global `x`, so we copy those functions
  112. // here, replacing `x` with `sess`
  113. dashboardVersion := m.DashboardVersion{}
  114. has, err := sess.Where(
  115. "dashboard_id=? AND version=?",
  116. cmd.DashboardId,
  117. cmd.Version,
  118. ).Get(&dashboardVersion)
  119. if err != nil {
  120. return err
  121. }
  122. if !has {
  123. return m.ErrDashboardVersionNotFound
  124. }
  125. dashboardVersion.Data.Set("id", dashboardVersion.DashboardId)
  126. // get the dashboard version
  127. dashboard := m.Dashboard{Id: cmd.DashboardId}
  128. has, err = sess.Get(&dashboard)
  129. if err != nil {
  130. return err
  131. }
  132. if has == false {
  133. return m.ErrDashboardNotFound
  134. }
  135. version, err := getMaxVersion(sess, dashboard.Id)
  136. if err != nil {
  137. return err
  138. }
  139. // revert and save to a new dashboard version
  140. dashboard.Data = dashboardVersion.Data
  141. dashboard.Updated = time.Now()
  142. dashboard.UpdatedBy = cmd.UserId
  143. dashboard.Version = version
  144. dashboard.Data.Set("version", dashboard.Version)
  145. affectedRows, err := sess.Id(dashboard.Id).Update(dashboard)
  146. if err != nil {
  147. return err
  148. }
  149. if affectedRows == 0 {
  150. return m.ErrDashboardNotFound
  151. }
  152. // save that version a new version
  153. dashVersion := &m.DashboardVersion{
  154. DashboardId: dashboard.Id,
  155. ParentVersion: cmd.Version,
  156. RestoredFrom: cmd.Version,
  157. Version: dashboard.Version,
  158. Created: time.Now(),
  159. CreatedBy: dashboard.UpdatedBy,
  160. Message: "",
  161. Data: dashboard.Data,
  162. }
  163. affectedRows, err = sess.Insert(dashVersion)
  164. if err != nil {
  165. return err
  166. }
  167. if affectedRows == 0 {
  168. return m.ErrDashboardNotFound
  169. }
  170. cmd.Result = &dashboard
  171. return nil
  172. })
  173. }
  174. // getDashboardVersion is a helper function that gets the dashboard version for
  175. // the given dashboard ID and version ID.
  176. func getDashboardVersion(dashboardId int64, version int) (*m.DashboardVersion, error) {
  177. dashboardVersion := m.DashboardVersion{}
  178. has, err := x.Where("dashboard_id=? AND version=?", dashboardId, version).Get(&dashboardVersion)
  179. if err != nil {
  180. return nil, err
  181. }
  182. if !has {
  183. return nil, m.ErrDashboardVersionNotFound
  184. }
  185. dashboardVersion.Data.Set("id", dashboardVersion.DashboardId)
  186. return &dashboardVersion, nil
  187. }
  188. // getDashboard gets a dashboard by ID. Used for retrieving the dashboard
  189. // associated with dashboard versions.
  190. func getDashboard(dashboardId int64) (*m.Dashboard, error) {
  191. dashboard := m.Dashboard{Id: dashboardId}
  192. has, err := x.Get(&dashboard)
  193. if err != nil {
  194. return nil, err
  195. }
  196. if has == false {
  197. return nil, m.ErrDashboardNotFound
  198. }
  199. return &dashboard, nil
  200. }
  201. // getDiff computes the diff of two dashboard versions.
  202. func getDiff(originalDash, newDash *m.DashboardVersion) (interface{}, diff.Diff, error) {
  203. leftBytes, err := simplejson.NewFromAny(originalDash).Encode()
  204. if err != nil {
  205. return nil, nil, err
  206. }
  207. rightBytes, err := simplejson.NewFromAny(newDash).Encode()
  208. if err != nil {
  209. return nil, nil, err
  210. }
  211. jsonDiff, err := diff.New().Compare(leftBytes, rightBytes)
  212. if err != nil {
  213. return nil, nil, err
  214. }
  215. if !jsonDiff.Modified() {
  216. return nil, nil, ErrNilDiff
  217. }
  218. left := make(map[string]interface{})
  219. err = json.Unmarshal(leftBytes, &left)
  220. return left, jsonDiff, nil
  221. }
  222. type version struct {
  223. Max int
  224. }
  225. // getMaxVersion returns the highest version number in the `dashboard_version`
  226. // table.
  227. //
  228. // This is necessary because sqlite3 doesn't support autoincrement in the same
  229. // way that Postgres or MySQL do, so we use this to get around that. Since it's
  230. // impossible to delete a version in Grafana, this is believed to be a
  231. // safe-enough alternative.
  232. func getMaxVersion(sess *DBSession, dashboardId int64) (int, error) {
  233. v := version{}
  234. has, err := sess.Table("dashboard_version").
  235. Select("MAX(version) AS max").
  236. Where("dashboard_id = ?", dashboardId).
  237. Get(&v)
  238. if !has {
  239. return 0, m.ErrDashboardNotFound
  240. }
  241. if err != nil {
  242. return 0, err
  243. }
  244. v.Max++
  245. return v.Max, nil
  246. }