dashboard_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. package sqlstore
  2. import (
  3. "fmt"
  4. "testing"
  5. "github.com/go-xorm/xorm"
  6. "github.com/grafana/grafana/pkg/components/simplejson"
  7. m "github.com/grafana/grafana/pkg/models"
  8. "github.com/grafana/grafana/pkg/services/search"
  9. "github.com/grafana/grafana/pkg/setting"
  10. "github.com/grafana/grafana/pkg/util"
  11. . "github.com/smartystreets/goconvey/convey"
  12. )
  13. func TestDashboardDataAccess(t *testing.T) {
  14. var x *xorm.Engine
  15. Convey("Testing DB", t, func() {
  16. x = InitTestDB(t)
  17. Convey("Given saved dashboard", func() {
  18. savedFolder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp")
  19. savedDash := insertTestDashboard("test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
  20. insertTestDashboard("test dash 45", 1, savedFolder.Id, false, "prod")
  21. insertTestDashboard("test dash 67", 1, 0, false, "prod", "webapp")
  22. Convey("Should return dashboard model", func() {
  23. So(savedDash.Title, ShouldEqual, "test dash 23")
  24. So(savedDash.Slug, ShouldEqual, "test-dash-23")
  25. So(savedDash.Id, ShouldNotEqual, 0)
  26. So(savedDash.IsFolder, ShouldBeFalse)
  27. So(savedDash.FolderId, ShouldBeGreaterThan, 0)
  28. So(len(savedDash.Uid), ShouldBeGreaterThan, 0)
  29. So(savedFolder.Title, ShouldEqual, "1 test dash folder")
  30. So(savedFolder.Slug, ShouldEqual, "1-test-dash-folder")
  31. So(savedFolder.Id, ShouldNotEqual, 0)
  32. So(savedFolder.IsFolder, ShouldBeTrue)
  33. So(savedFolder.FolderId, ShouldEqual, 0)
  34. So(len(savedFolder.Uid), ShouldBeGreaterThan, 0)
  35. })
  36. Convey("Should be able to get dashboard by id", func() {
  37. query := m.GetDashboardQuery{
  38. Id: savedDash.Id,
  39. OrgId: 1,
  40. }
  41. err := GetDashboard(&query)
  42. So(err, ShouldBeNil)
  43. So(query.Result.Title, ShouldEqual, "test dash 23")
  44. So(query.Result.Slug, ShouldEqual, "test-dash-23")
  45. So(query.Result.Id, ShouldEqual, savedDash.Id)
  46. So(query.Result.Uid, ShouldEqual, savedDash.Uid)
  47. So(query.Result.IsFolder, ShouldBeFalse)
  48. })
  49. Convey("Should be able to get dashboard by slug", func() {
  50. query := m.GetDashboardQuery{
  51. Slug: "test-dash-23",
  52. OrgId: 1,
  53. }
  54. err := GetDashboard(&query)
  55. So(err, ShouldBeNil)
  56. So(query.Result.Title, ShouldEqual, "test dash 23")
  57. So(query.Result.Slug, ShouldEqual, "test-dash-23")
  58. So(query.Result.Id, ShouldEqual, savedDash.Id)
  59. So(query.Result.Uid, ShouldEqual, savedDash.Uid)
  60. So(query.Result.IsFolder, ShouldBeFalse)
  61. })
  62. Convey("Should be able to get dashboard by uid", func() {
  63. query := m.GetDashboardQuery{
  64. Uid: savedDash.Uid,
  65. OrgId: 1,
  66. }
  67. err := GetDashboard(&query)
  68. So(err, ShouldBeNil)
  69. So(query.Result.Title, ShouldEqual, "test dash 23")
  70. So(query.Result.Slug, ShouldEqual, "test-dash-23")
  71. So(query.Result.Id, ShouldEqual, savedDash.Id)
  72. So(query.Result.Uid, ShouldEqual, savedDash.Uid)
  73. So(query.Result.IsFolder, ShouldBeFalse)
  74. })
  75. Convey("Should be able to delete dashboard", func() {
  76. dash := insertTestDashboard("delete me", 1, 0, false, "delete this")
  77. err := DeleteDashboard(&m.DeleteDashboardCommand{
  78. Id: dash.Id,
  79. OrgId: 1,
  80. })
  81. So(err, ShouldBeNil)
  82. })
  83. Convey("Should return error if no dashboard is updated", func() {
  84. cmd := m.SaveDashboardCommand{
  85. OrgId: 1,
  86. Overwrite: true,
  87. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  88. "id": float64(123412321),
  89. "title": "Expect error",
  90. "tags": []interface{}{},
  91. }),
  92. }
  93. err := SaveDashboard(&cmd)
  94. So(err, ShouldNotBeNil)
  95. })
  96. Convey("Should not be able to overwrite dashboard in another org", func() {
  97. query := m.GetDashboardQuery{Slug: "test-dash-23", OrgId: 1}
  98. GetDashboard(&query)
  99. cmd := m.SaveDashboardCommand{
  100. OrgId: 2,
  101. Overwrite: true,
  102. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  103. "id": float64(query.Result.Id),
  104. "title": "Expect error",
  105. "tags": []interface{}{},
  106. }),
  107. }
  108. err := SaveDashboard(&cmd)
  109. So(err, ShouldNotBeNil)
  110. })
  111. Convey("Should be able to search for dashboard folder", func() {
  112. query := search.FindPersistedDashboardsQuery{
  113. Title: "1 test dash folder",
  114. OrgId: 1,
  115. SignedInUser: &m.SignedInUser{OrgId: 1},
  116. }
  117. err := SearchDashboards(&query)
  118. So(err, ShouldBeNil)
  119. So(len(query.Result), ShouldEqual, 1)
  120. hit := query.Result[0]
  121. So(hit.Type, ShouldEqual, search.DashHitFolder)
  122. So(hit.Url, ShouldEqual, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug))
  123. So(hit.FolderTitle, ShouldEqual, "")
  124. })
  125. Convey("Should be able to search for a dashboard folder's children", func() {
  126. query := search.FindPersistedDashboardsQuery{
  127. OrgId: 1,
  128. FolderIds: []int64{savedFolder.Id},
  129. SignedInUser: &m.SignedInUser{OrgId: 1},
  130. }
  131. err := SearchDashboards(&query)
  132. So(err, ShouldBeNil)
  133. So(len(query.Result), ShouldEqual, 2)
  134. hit := query.Result[0]
  135. So(hit.Id, ShouldEqual, savedDash.Id)
  136. So(hit.Url, ShouldEqual, fmt.Sprintf("/d/%s/%s", savedDash.Uid, savedDash.Slug))
  137. So(hit.FolderId, ShouldEqual, savedFolder.Id)
  138. So(hit.FolderUid, ShouldEqual, savedFolder.Uid)
  139. So(hit.FolderTitle, ShouldEqual, savedFolder.Title)
  140. So(hit.FolderUrl, ShouldEqual, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug))
  141. })
  142. Convey("Should be able to search for dashboard by dashboard ids", func() {
  143. Convey("should be able to find two dashboards by id", func() {
  144. query := search.FindPersistedDashboardsQuery{
  145. DashboardIds: []int64{2, 3},
  146. SignedInUser: &m.SignedInUser{OrgId: 1},
  147. }
  148. err := SearchDashboards(&query)
  149. So(err, ShouldBeNil)
  150. So(len(query.Result), ShouldEqual, 2)
  151. hit := query.Result[0]
  152. So(len(hit.Tags), ShouldEqual, 2)
  153. hit2 := query.Result[1]
  154. So(len(hit2.Tags), ShouldEqual, 1)
  155. })
  156. Convey("DashboardIds that does not exists should not cause errors", func() {
  157. query := search.FindPersistedDashboardsQuery{
  158. DashboardIds: []int64{1000},
  159. SignedInUser: &m.SignedInUser{OrgId: 1},
  160. }
  161. err := SearchDashboards(&query)
  162. So(err, ShouldBeNil)
  163. So(len(query.Result), ShouldEqual, 0)
  164. })
  165. })
  166. Convey("Should be able to save dashboards with same name in different folders", func() {
  167. firstSaveCmd := m.SaveDashboardCommand{
  168. OrgId: 1,
  169. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  170. "id": nil,
  171. "title": "test dash folder and title",
  172. "tags": []interface{}{},
  173. "uid": "randomHash",
  174. }),
  175. FolderId: 3,
  176. }
  177. err := SaveDashboard(&firstSaveCmd)
  178. So(err, ShouldBeNil)
  179. secondSaveCmd := m.SaveDashboardCommand{
  180. OrgId: 1,
  181. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  182. "id": nil,
  183. "title": "test dash folder and title",
  184. "tags": []interface{}{},
  185. "uid": "moreRandomHash",
  186. }),
  187. FolderId: 1,
  188. }
  189. err = SaveDashboard(&secondSaveCmd)
  190. So(err, ShouldBeNil)
  191. })
  192. Convey("Should not be able to save dashboard with same name in the same folder", func() {
  193. firstSaveCmd := m.SaveDashboardCommand{
  194. OrgId: 1,
  195. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  196. "id": nil,
  197. "title": "test dash folder and title",
  198. "tags": []interface{}{},
  199. "uid": "randomHash",
  200. }),
  201. FolderId: 3,
  202. }
  203. err := SaveDashboard(&firstSaveCmd)
  204. So(err, ShouldBeNil)
  205. secondSaveCmd := m.SaveDashboardCommand{
  206. OrgId: 1,
  207. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  208. "id": nil,
  209. "title": "test dash folder and title",
  210. "tags": []interface{}{},
  211. "uid": "moreRandomHash",
  212. }),
  213. FolderId: 3,
  214. }
  215. err = SaveDashboard(&secondSaveCmd)
  216. So(err, ShouldEqual, m.ErrDashboardWithSameNameInFolderExists)
  217. })
  218. Convey("Should not be able to save dashboard with same uid", func() {
  219. cmd := m.SaveDashboardCommand{
  220. OrgId: 1,
  221. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  222. "id": nil,
  223. "title": "test dash 23",
  224. "uid": "dsfalkjngailuedt",
  225. }),
  226. }
  227. err := SaveDashboard(&cmd)
  228. So(err, ShouldBeNil)
  229. err = SaveDashboard(&cmd)
  230. So(err, ShouldNotBeNil)
  231. })
  232. Convey("Should be able to update dashboard with the same title and folder id", func() {
  233. cmd := m.SaveDashboardCommand{
  234. OrgId: 1,
  235. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  236. "uid": "randomHash",
  237. "title": "folderId",
  238. "style": "light",
  239. "tags": []interface{}{},
  240. }),
  241. FolderId: 2,
  242. }
  243. err := SaveDashboard(&cmd)
  244. So(err, ShouldBeNil)
  245. So(cmd.Result.FolderId, ShouldEqual, 2)
  246. cmd = m.SaveDashboardCommand{
  247. OrgId: 1,
  248. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  249. "id": cmd.Result.Id,
  250. "uid": "randomHash",
  251. "title": "folderId",
  252. "style": "dark",
  253. "version": cmd.Result.Version,
  254. "tags": []interface{}{},
  255. }),
  256. FolderId: 2,
  257. }
  258. err = SaveDashboard(&cmd)
  259. So(err, ShouldBeNil)
  260. })
  261. Convey("Should not be able to update using just uid", func() {
  262. cmd := m.SaveDashboardCommand{
  263. OrgId: 1,
  264. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  265. "uid": savedDash.Uid,
  266. "title": "folderId",
  267. "version": savedDash.Version,
  268. "tags": []interface{}{},
  269. }),
  270. FolderId: savedDash.FolderId,
  271. }
  272. err := SaveDashboard(&cmd)
  273. So(err, ShouldEqual, m.ErrDashboardWithSameUIDExists)
  274. })
  275. Convey("Should be able to update using just uid with overwrite", func() {
  276. cmd := m.SaveDashboardCommand{
  277. OrgId: 1,
  278. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  279. "uid": savedDash.Uid,
  280. "title": "folderId",
  281. "version": savedDash.Version,
  282. "tags": []interface{}{},
  283. }),
  284. FolderId: savedDash.FolderId,
  285. Overwrite: true,
  286. }
  287. err := SaveDashboard(&cmd)
  288. So(err, ShouldBeNil)
  289. })
  290. Convey("Should retry generation of uid once if it fails.", func() {
  291. timesCalled := 0
  292. generateNewUid = func() string {
  293. timesCalled += 1
  294. if timesCalled <= 2 {
  295. return savedDash.Uid
  296. } else {
  297. return util.GenerateShortUid()
  298. }
  299. }
  300. cmd := m.SaveDashboardCommand{
  301. OrgId: 1,
  302. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  303. "title": "new dash 12334",
  304. "tags": []interface{}{},
  305. }),
  306. }
  307. err := SaveDashboard(&cmd)
  308. So(err, ShouldBeNil)
  309. generateNewUid = util.GenerateShortUid
  310. })
  311. Convey("Should be able to update dashboard and remove folderId", func() {
  312. cmd := m.SaveDashboardCommand{
  313. OrgId: 1,
  314. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  315. "id": 1,
  316. "title": "folderId",
  317. "tags": []interface{}{},
  318. }),
  319. Overwrite: true,
  320. FolderId: 2,
  321. }
  322. err := SaveDashboard(&cmd)
  323. So(err, ShouldBeNil)
  324. So(cmd.Result.FolderId, ShouldEqual, 2)
  325. cmd = m.SaveDashboardCommand{
  326. OrgId: 1,
  327. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  328. "id": 1,
  329. "title": "folderId",
  330. "tags": []interface{}{},
  331. }),
  332. FolderId: 0,
  333. Overwrite: true,
  334. }
  335. err = SaveDashboard(&cmd)
  336. So(err, ShouldBeNil)
  337. query := m.GetDashboardQuery{
  338. Slug: cmd.Result.Slug,
  339. OrgId: 1,
  340. }
  341. err = GetDashboard(&query)
  342. So(err, ShouldBeNil)
  343. So(query.Result.FolderId, ShouldEqual, 0)
  344. })
  345. Convey("Should be able to delete a dashboard folder and its children", func() {
  346. deleteCmd := &m.DeleteDashboardCommand{Id: savedFolder.Id}
  347. err := DeleteDashboard(deleteCmd)
  348. So(err, ShouldBeNil)
  349. query := search.FindPersistedDashboardsQuery{
  350. OrgId: 1,
  351. FolderIds: []int64{savedFolder.Id},
  352. SignedInUser: &m.SignedInUser{},
  353. }
  354. err = SearchDashboards(&query)
  355. So(err, ShouldBeNil)
  356. So(len(query.Result), ShouldEqual, 0)
  357. })
  358. Convey("Should be able to get dashboard tags", func() {
  359. query := m.GetDashboardTagsQuery{OrgId: 1}
  360. err := GetDashboardTags(&query)
  361. So(err, ShouldBeNil)
  362. So(len(query.Result), ShouldEqual, 2)
  363. })
  364. Convey("Given two dashboards, one is starred dashboard by user 10, other starred by user 1", func() {
  365. starredDash := insertTestDashboard("starred dash", 1, 0, false)
  366. StarDashboard(&m.StarDashboardCommand{
  367. DashboardId: starredDash.Id,
  368. UserId: 10,
  369. })
  370. StarDashboard(&m.StarDashboardCommand{
  371. DashboardId: savedDash.Id,
  372. UserId: 1,
  373. })
  374. Convey("Should be able to search for starred dashboards", func() {
  375. query := search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: 10, OrgId: 1}, IsStarred: true}
  376. err := SearchDashboards(&query)
  377. So(err, ShouldBeNil)
  378. So(len(query.Result), ShouldEqual, 1)
  379. So(query.Result[0].Title, ShouldEqual, "starred dash")
  380. })
  381. })
  382. })
  383. Convey("Given a plugin with imported dashboards", func() {
  384. pluginId := "test-app"
  385. appFolder := insertTestDashboardForPlugin("app-test", 1, 0, true, pluginId)
  386. insertTestDashboardForPlugin("app-dash1", 1, appFolder.Id, false, pluginId)
  387. insertTestDashboardForPlugin("app-dash2", 1, appFolder.Id, false, pluginId)
  388. Convey("Should return imported dashboard", func() {
  389. query := m.GetDashboardsByPluginIdQuery{
  390. PluginId: pluginId,
  391. OrgId: 1,
  392. }
  393. err := GetDashboardsByPluginId(&query)
  394. So(err, ShouldBeNil)
  395. So(len(query.Result), ShouldEqual, 2)
  396. })
  397. })
  398. })
  399. }
  400. func insertTestDashboard(title string, orgId int64, folderId int64, isFolder bool, tags ...interface{}) *m.Dashboard {
  401. cmd := m.SaveDashboardCommand{
  402. OrgId: orgId,
  403. FolderId: folderId,
  404. IsFolder: isFolder,
  405. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  406. "id": nil,
  407. "title": title,
  408. "tags": tags,
  409. }),
  410. }
  411. err := SaveDashboard(&cmd)
  412. So(err, ShouldBeNil)
  413. return cmd.Result
  414. }
  415. func insertTestDashboardForPlugin(title string, orgId int64, folderId int64, isFolder bool, pluginId string) *m.Dashboard {
  416. cmd := m.SaveDashboardCommand{
  417. OrgId: orgId,
  418. FolderId: folderId,
  419. IsFolder: isFolder,
  420. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  421. "id": nil,
  422. "title": title,
  423. }),
  424. PluginId: pluginId,
  425. }
  426. err := SaveDashboard(&cmd)
  427. So(err, ShouldBeNil)
  428. return cmd.Result
  429. }
  430. func createUser(name string, role string, isAdmin bool) m.User {
  431. setting.AutoAssignOrg = true
  432. setting.AutoAssignOrgRole = role
  433. currentUserCmd := m.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: isAdmin}
  434. err := CreateUser(&currentUserCmd)
  435. So(err, ShouldBeNil)
  436. q1 := m.GetUserOrgListQuery{UserId: currentUserCmd.Result.Id}
  437. GetUserOrgList(&q1)
  438. So(q1.Result[0].Role, ShouldEqual, role)
  439. return currentUserCmd.Result
  440. }
  441. func updateTestDashboardWithAcl(dashId int64, userId int64, permissions m.PermissionType) int64 {
  442. cmd := &m.SetDashboardAclCommand{
  443. OrgId: 1,
  444. UserId: userId,
  445. DashboardId: dashId,
  446. Permission: permissions,
  447. }
  448. err := SetDashboardAcl(cmd)
  449. So(err, ShouldBeNil)
  450. return cmd.Result.Id
  451. }
  452. func removeAcl(aclId int64) {
  453. err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{AclId: aclId, OrgId: 1})
  454. So(err, ShouldBeNil)
  455. }
  456. func moveDashboard(orgId int64, dashboard *simplejson.Json, newFolderId int64) *m.Dashboard {
  457. cmd := m.SaveDashboardCommand{
  458. OrgId: orgId,
  459. FolderId: newFolderId,
  460. Dashboard: dashboard,
  461. Overwrite: true,
  462. }
  463. err := SaveDashboard(&cmd)
  464. So(err, ShouldBeNil)
  465. return cmd.Result
  466. }