dashboard.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. package sqlstore
  2. import (
  3. "time"
  4. "github.com/grafana/grafana/pkg/bus"
  5. "github.com/grafana/grafana/pkg/metrics"
  6. m "github.com/grafana/grafana/pkg/models"
  7. "github.com/grafana/grafana/pkg/services/search"
  8. "github.com/grafana/grafana/pkg/util"
  9. )
  10. func init() {
  11. bus.AddHandler("sql", SaveDashboard)
  12. bus.AddHandler("sql", GetDashboard)
  13. bus.AddHandler("sql", GetDashboards)
  14. bus.AddHandler("sql", DeleteDashboard)
  15. bus.AddHandler("sql", SearchDashboards)
  16. bus.AddHandler("sql", GetDashboardTags)
  17. bus.AddHandler("sql", GetDashboardSlugById)
  18. bus.AddHandler("sql", GetDashboardsByPluginId)
  19. }
  20. var generateNewUid func() string = util.GenerateShortUid
  21. func SaveDashboard(cmd *m.SaveDashboardCommand) error {
  22. return inTransaction(func(sess *DBSession) error {
  23. dash := cmd.GetDashboardModel()
  24. // try get existing dashboard
  25. var existing m.Dashboard
  26. if dash.Id != 0 {
  27. dashWithIdExists, err := sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existing)
  28. if err != nil {
  29. return err
  30. }
  31. if !dashWithIdExists {
  32. return m.ErrDashboardNotFound
  33. }
  34. // check for is someone else has written in between
  35. if dash.Version != existing.Version {
  36. if cmd.Overwrite {
  37. dash.Version = existing.Version
  38. } else {
  39. return m.ErrDashboardVersionMismatch
  40. }
  41. }
  42. // do not allow plugin dashboard updates without overwrite flag
  43. if existing.PluginId != "" && cmd.Overwrite == false {
  44. return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
  45. }
  46. } else if dash.Uid != "" {
  47. var sameUid m.Dashboard
  48. sameUidExists, err := sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&sameUid)
  49. if err != nil {
  50. return err
  51. }
  52. if sameUidExists {
  53. // another dashboard with same uid
  54. if dash.Id != sameUid.Id {
  55. if cmd.Overwrite {
  56. dash.Id = sameUid.Id
  57. dash.Version = sameUid.Version
  58. } else {
  59. return m.ErrDashboardWithSameUIDExists
  60. }
  61. }
  62. }
  63. }
  64. if dash.Uid == "" {
  65. uid, err := generateNewDashboardUid(sess, dash.OrgId)
  66. if err != nil {
  67. return err
  68. }
  69. dash.Uid = uid
  70. dash.Data.Set("uid", uid)
  71. }
  72. err := guaranteeDashboardNameIsUniqueInFolder(sess, dash)
  73. if err != nil {
  74. return err
  75. }
  76. err = setHasAcl(sess, dash)
  77. if err != nil {
  78. return err
  79. }
  80. parentVersion := dash.Version
  81. affectedRows := int64(0)
  82. if dash.Id == 0 {
  83. dash.Version = 1
  84. metrics.M_Api_Dashboard_Insert.Inc()
  85. dash.Data.Set("version", dash.Version)
  86. affectedRows, err = sess.Insert(dash)
  87. } else {
  88. dash.Version++
  89. dash.Data.Set("version", dash.Version)
  90. if !cmd.UpdatedAt.IsZero() {
  91. dash.Updated = cmd.UpdatedAt
  92. }
  93. affectedRows, err = sess.MustCols("folder_id", "has_acl").ID(dash.Id).Update(dash)
  94. }
  95. if err != nil {
  96. return err
  97. }
  98. if affectedRows == 0 {
  99. return m.ErrDashboardNotFound
  100. }
  101. dashVersion := &m.DashboardVersion{
  102. DashboardId: dash.Id,
  103. ParentVersion: parentVersion,
  104. RestoredFrom: cmd.RestoredFrom,
  105. Version: dash.Version,
  106. Created: time.Now(),
  107. CreatedBy: dash.UpdatedBy,
  108. Message: cmd.Message,
  109. Data: dash.Data,
  110. }
  111. // insert version entry
  112. if affectedRows, err = sess.Insert(dashVersion); err != nil {
  113. return err
  114. } else if affectedRows == 0 {
  115. return m.ErrDashboardNotFound
  116. }
  117. // delete existing tags
  118. _, err = sess.Exec("DELETE FROM dashboard_tag WHERE dashboard_id=?", dash.Id)
  119. if err != nil {
  120. return err
  121. }
  122. // insert new tags
  123. tags := dash.GetTags()
  124. if len(tags) > 0 {
  125. for _, tag := range tags {
  126. if _, err := sess.Insert(&DashboardTag{DashboardId: dash.Id, Term: tag}); err != nil {
  127. return err
  128. }
  129. }
  130. }
  131. cmd.Result = dash
  132. return err
  133. })
  134. }
  135. func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
  136. for i := 0; i < 3; i++ {
  137. uid := generateNewUid()
  138. exists, err := sess.Where("org_id=? AND uid=?", orgId, uid).Get(&m.Dashboard{})
  139. if err != nil {
  140. return "", err
  141. }
  142. if !exists {
  143. return uid, nil
  144. }
  145. }
  146. return "", m.ErrDashboardFailedGenerateUniqueUid
  147. }
  148. func guaranteeDashboardNameIsUniqueInFolder(sess *DBSession, dash *m.Dashboard) error {
  149. var sameNameInFolder m.Dashboard
  150. sameNameInFolderExist, err := sess.Where("org_id=? AND title=? AND folder_id = ? AND uid <> ?",
  151. dash.OrgId, dash.Title, dash.FolderId, dash.Uid).
  152. Get(&sameNameInFolder)
  153. if err != nil {
  154. return err
  155. }
  156. if sameNameInFolderExist {
  157. return m.ErrDashboardWithSameNameInFolderExists
  158. }
  159. return nil
  160. }
  161. func setHasAcl(sess *DBSession, dash *m.Dashboard) error {
  162. // check if parent has acl
  163. if dash.FolderId > 0 {
  164. var parent m.Dashboard
  165. if hasParent, err := sess.Where("folder_id=?", dash.FolderId).Get(&parent); err != nil {
  166. return err
  167. } else if hasParent && parent.HasAcl {
  168. dash.HasAcl = true
  169. }
  170. }
  171. // check if dash has its own acl
  172. if dash.Id > 0 {
  173. if res, err := sess.Query("SELECT 1 from dashboard_acl WHERE dashboard_id =?", dash.Id); err != nil {
  174. return err
  175. } else {
  176. if len(res) > 0 {
  177. dash.HasAcl = true
  178. }
  179. }
  180. }
  181. return nil
  182. }
  183. func GetDashboard(query *m.GetDashboardQuery) error {
  184. dashboard := m.Dashboard{Slug: query.Slug, OrgId: query.OrgId, Id: query.Id, Uid: query.Uid}
  185. has, err := x.Get(&dashboard)
  186. if err != nil {
  187. return err
  188. } else if has == false {
  189. return m.ErrDashboardNotFound
  190. }
  191. dashboard.Data.Set("id", dashboard.Id)
  192. dashboard.Data.Set("uid", dashboard.Uid)
  193. query.Result = &dashboard
  194. return nil
  195. }
  196. type DashboardSearchProjection struct {
  197. Id int64
  198. Uid string
  199. Title string
  200. Slug string
  201. Term string
  202. IsFolder bool
  203. FolderId int64
  204. FolderSlug string
  205. FolderTitle string
  206. }
  207. func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
  208. limit := query.Limit
  209. if limit == 0 {
  210. limit = 1000
  211. }
  212. sb := NewSearchBuilder(query.SignedInUser, limit).
  213. WithTags(query.Tags).
  214. WithDashboardIdsIn(query.DashboardIds)
  215. if query.IsStarred {
  216. sb.IsStarred()
  217. }
  218. if len(query.Title) > 0 {
  219. sb.WithTitle(query.Title)
  220. }
  221. if len(query.Type) > 0 {
  222. sb.WithType(query.Type)
  223. }
  224. if len(query.FolderIds) > 0 {
  225. sb.WithFolderIds(query.FolderIds)
  226. }
  227. var res []DashboardSearchProjection
  228. sql, params := sb.ToSql()
  229. err := x.Sql(sql, params...).Find(&res)
  230. if err != nil {
  231. return nil, err
  232. }
  233. return res, nil
  234. }
  235. func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
  236. res, err := findDashboards(query)
  237. if err != nil {
  238. return err
  239. }
  240. makeQueryResult(query, res)
  241. return nil
  242. }
  243. func getHitType(item DashboardSearchProjection) search.HitType {
  244. var hitType search.HitType
  245. if item.IsFolder {
  246. hitType = search.DashHitFolder
  247. } else {
  248. hitType = search.DashHitDB
  249. }
  250. return hitType
  251. }
  252. func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []DashboardSearchProjection) {
  253. query.Result = make([]*search.Hit, 0)
  254. hits := make(map[int64]*search.Hit)
  255. for _, item := range res {
  256. hit, exists := hits[item.Id]
  257. if !exists {
  258. hit = &search.Hit{
  259. Id: item.Id,
  260. Title: item.Title,
  261. Uri: "db/" + item.Slug,
  262. Url: m.GetDashboardFolderUrl(item.IsFolder, item.Uid, item.Slug),
  263. Slug: item.Slug,
  264. Type: getHitType(item),
  265. FolderId: item.FolderId,
  266. FolderTitle: item.FolderTitle,
  267. FolderSlug: item.FolderSlug,
  268. Tags: []string{},
  269. }
  270. query.Result = append(query.Result, hit)
  271. hits[item.Id] = hit
  272. }
  273. if len(item.Term) > 0 {
  274. hit.Tags = append(hit.Tags, item.Term)
  275. }
  276. }
  277. }
  278. func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
  279. sql := `SELECT
  280. COUNT(*) as count,
  281. term
  282. FROM dashboard
  283. INNER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id
  284. WHERE dashboard.org_id=?
  285. GROUP BY term`
  286. query.Result = make([]*m.DashboardTagCloudItem, 0)
  287. sess := x.Sql(sql, query.OrgId)
  288. err := sess.Find(&query.Result)
  289. return err
  290. }
  291. func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
  292. return inTransaction(func(sess *DBSession) error {
  293. dashboard := m.Dashboard{Id: cmd.Id, OrgId: cmd.OrgId}
  294. has, err := sess.Get(&dashboard)
  295. if err != nil {
  296. return err
  297. } else if has == false {
  298. return m.ErrDashboardNotFound
  299. }
  300. deletes := []string{
  301. "DELETE FROM dashboard_tag WHERE dashboard_id = ? ",
  302. "DELETE FROM star WHERE dashboard_id = ? ",
  303. "DELETE FROM dashboard WHERE id = ?",
  304. "DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?",
  305. "DELETE FROM dashboard_version WHERE dashboard_id = ?",
  306. "DELETE FROM dashboard WHERE folder_id = ?",
  307. "DELETE FROM annotation WHERE dashboard_id = ?",
  308. }
  309. for _, sql := range deletes {
  310. _, err := sess.Exec(sql, dashboard.Id)
  311. if err != nil {
  312. return err
  313. }
  314. }
  315. if err := DeleteAlertDefinition(dashboard.Id, sess); err != nil {
  316. return nil
  317. }
  318. return nil
  319. })
  320. }
  321. func GetDashboards(query *m.GetDashboardsQuery) error {
  322. if len(query.DashboardIds) == 0 {
  323. return m.ErrCommandValidationFailed
  324. }
  325. var dashboards = make([]*m.Dashboard, 0)
  326. err := x.In("id", query.DashboardIds).Find(&dashboards)
  327. query.Result = dashboards
  328. if err != nil {
  329. return err
  330. }
  331. return nil
  332. }
  333. func GetDashboardsByPluginId(query *m.GetDashboardsByPluginIdQuery) error {
  334. var dashboards = make([]*m.Dashboard, 0)
  335. whereExpr := "org_id=? AND plugin_id=? AND is_folder=" + dialect.BooleanStr(false)
  336. err := x.Where(whereExpr, query.OrgId, query.PluginId).Find(&dashboards)
  337. query.Result = dashboards
  338. if err != nil {
  339. return err
  340. }
  341. return nil
  342. }
  343. type DashboardSlugDTO struct {
  344. Slug string
  345. }
  346. func GetDashboardSlugById(query *m.GetDashboardSlugByIdQuery) error {
  347. var rawSql = `SELECT slug from dashboard WHERE Id=?`
  348. var slug = DashboardSlugDTO{}
  349. exists, err := x.Sql(rawSql, query.Id).Get(&slug)
  350. if err != nil {
  351. return err
  352. } else if exists == false {
  353. return m.ErrDashboardNotFound
  354. }
  355. query.Result = slug.Slug
  356. return nil
  357. }