dashboard_service_integration_test.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  1. package sqlstore
  2. import (
  3. "testing"
  4. "github.com/grafana/grafana/pkg/components/simplejson"
  5. "github.com/grafana/grafana/pkg/services/dashboards"
  6. "github.com/grafana/grafana/pkg/services/guardian"
  7. "github.com/grafana/grafana/pkg/bus"
  8. "github.com/grafana/grafana/pkg/models"
  9. . "github.com/smartystreets/goconvey/convey"
  10. )
  11. func TestIntegratedDashboardService(t *testing.T) {
  12. Convey("Dashboard service integration tests", t, func() {
  13. InitTestDB(t)
  14. var testOrgId int64 = 1
  15. Convey("Given saved folders and dashboards in organization A", func() {
  16. bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
  17. return nil
  18. })
  19. bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
  20. return nil
  21. })
  22. bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardByDashboardId) error {
  23. return models.ErrDashboardProvisioningDoesNotExist
  24. })
  25. savedFolder := saveTestFolder("Saved folder", testOrgId)
  26. savedDashInFolder := saveTestDashboard("Saved dash in folder", testOrgId, savedFolder.Id)
  27. saveTestDashboard("Other saved dash in folder", testOrgId, savedFolder.Id)
  28. savedDashInGeneralFolder := saveTestDashboard("Saved dashboard in general folder", testOrgId, 0)
  29. otherSavedFolder := saveTestFolder("Other saved folder", testOrgId)
  30. Convey("Should return dashboard model", func() {
  31. So(savedFolder.Title, ShouldEqual, "Saved folder")
  32. So(savedFolder.Slug, ShouldEqual, "saved-folder")
  33. So(savedFolder.Id, ShouldNotEqual, 0)
  34. So(savedFolder.IsFolder, ShouldBeTrue)
  35. So(savedFolder.FolderId, ShouldEqual, 0)
  36. So(len(savedFolder.Uid), ShouldBeGreaterThan, 0)
  37. So(savedDashInFolder.Title, ShouldEqual, "Saved dash in folder")
  38. So(savedDashInFolder.Slug, ShouldEqual, "saved-dash-in-folder")
  39. So(savedDashInFolder.Id, ShouldNotEqual, 0)
  40. So(savedDashInFolder.IsFolder, ShouldBeFalse)
  41. So(savedDashInFolder.FolderId, ShouldEqual, savedFolder.Id)
  42. So(len(savedDashInFolder.Uid), ShouldBeGreaterThan, 0)
  43. })
  44. // Basic validation tests
  45. Convey("When saving a dashboard with non-existing id", func() {
  46. cmd := models.SaveDashboardCommand{
  47. OrgId: testOrgId,
  48. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  49. "id": float64(123412321),
  50. "title": "Expect error",
  51. }),
  52. }
  53. err := callSaveWithError(cmd)
  54. Convey("It should result in not found error", func() {
  55. So(err, ShouldNotBeNil)
  56. So(err, ShouldEqual, models.ErrDashboardNotFound)
  57. })
  58. })
  59. // Given other organization
  60. Convey("Given organization B", func() {
  61. var otherOrgId int64 = 2
  62. Convey("When saving a dashboard with id that are saved in organization A", func() {
  63. cmd := models.SaveDashboardCommand{
  64. OrgId: otherOrgId,
  65. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  66. "id": savedDashInFolder.Id,
  67. "title": "Expect error",
  68. }),
  69. Overwrite: false,
  70. }
  71. err := callSaveWithError(cmd)
  72. Convey("It should result in not found error", func() {
  73. So(err, ShouldNotBeNil)
  74. So(err, ShouldEqual, models.ErrDashboardNotFound)
  75. })
  76. })
  77. permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
  78. Convey("When saving a dashboard with uid that are saved in organization A", func() {
  79. var otherOrgId int64 = 2
  80. cmd := models.SaveDashboardCommand{
  81. OrgId: otherOrgId,
  82. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  83. "uid": savedDashInFolder.Uid,
  84. "title": "Dash with existing uid in other org",
  85. }),
  86. Overwrite: false,
  87. }
  88. res := callSaveWithResult(cmd)
  89. Convey("It should create dashboard in other organization", func() {
  90. So(res, ShouldNotBeNil)
  91. query := models.GetDashboardQuery{OrgId: otherOrgId, Uid: savedDashInFolder.Uid}
  92. err := bus.Dispatch(&query)
  93. So(err, ShouldBeNil)
  94. So(query.Result.Id, ShouldNotEqual, savedDashInFolder.Id)
  95. So(query.Result.Id, ShouldEqual, res.Id)
  96. So(query.Result.OrgId, ShouldEqual, otherOrgId)
  97. So(query.Result.Uid, ShouldEqual, savedDashInFolder.Uid)
  98. })
  99. })
  100. })
  101. })
  102. // Given user has no permission to save
  103. permissionScenario("Given user has no permission to save", false, func(sc *dashboardPermissionScenarioContext) {
  104. Convey("When trying to create a new dashboard in the General folder", func() {
  105. cmd := models.SaveDashboardCommand{
  106. OrgId: testOrgId,
  107. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  108. "title": "Dash",
  109. }),
  110. UserId: 10000,
  111. Overwrite: true,
  112. }
  113. err := callSaveWithError(cmd)
  114. Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
  115. So(err, ShouldNotBeNil)
  116. So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
  117. So(sc.dashboardGuardianMock.DashId, ShouldEqual, 0)
  118. So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
  119. So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
  120. })
  121. })
  122. Convey("When trying to create a new dashboard in other folder", func() {
  123. cmd := models.SaveDashboardCommand{
  124. OrgId: testOrgId,
  125. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  126. "title": "Dash",
  127. }),
  128. FolderId: otherSavedFolder.Id,
  129. UserId: 10000,
  130. Overwrite: true,
  131. }
  132. err := callSaveWithError(cmd)
  133. Convey("It should call dashboard guardian with correct arguments and rsult in access denied error", func() {
  134. So(err, ShouldNotBeNil)
  135. So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
  136. So(sc.dashboardGuardianMock.DashId, ShouldEqual, otherSavedFolder.Id)
  137. So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
  138. So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
  139. })
  140. })
  141. Convey("When trying to update a dashboard by existing id in the General folder", func() {
  142. cmd := models.SaveDashboardCommand{
  143. OrgId: testOrgId,
  144. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  145. "id": savedDashInGeneralFolder.Id,
  146. "title": "Dash",
  147. }),
  148. FolderId: savedDashInGeneralFolder.FolderId,
  149. UserId: 10000,
  150. Overwrite: true,
  151. }
  152. err := callSaveWithError(cmd)
  153. Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
  154. So(err, ShouldNotBeNil)
  155. So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
  156. So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedDashInGeneralFolder.Id)
  157. So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
  158. So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
  159. })
  160. })
  161. Convey("When trying to update a dashboard by existing id in other folder", func() {
  162. cmd := models.SaveDashboardCommand{
  163. OrgId: testOrgId,
  164. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  165. "id": savedDashInFolder.Id,
  166. "title": "Dash",
  167. }),
  168. FolderId: savedDashInFolder.FolderId,
  169. UserId: 10000,
  170. Overwrite: true,
  171. }
  172. err := callSaveWithError(cmd)
  173. Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
  174. So(err, ShouldNotBeNil)
  175. So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
  176. So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedDashInFolder.Id)
  177. So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
  178. So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
  179. })
  180. })
  181. })
  182. // Given user has permission to save
  183. permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
  184. Convey("and overwrite flag is set to false", func() {
  185. shouldOverwrite := false
  186. Convey("When creating a dashboard in General folder with same name as dashboard in other folder", func() {
  187. cmd := models.SaveDashboardCommand{
  188. OrgId: testOrgId,
  189. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  190. "id": nil,
  191. "title": savedDashInFolder.Title,
  192. }),
  193. FolderId: 0,
  194. Overwrite: shouldOverwrite,
  195. }
  196. res := callSaveWithResult(cmd)
  197. So(res, ShouldNotBeNil)
  198. Convey("It should create a new dashboard", func() {
  199. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  200. err := bus.Dispatch(&query)
  201. So(err, ShouldBeNil)
  202. So(query.Result.Id, ShouldEqual, res.Id)
  203. So(query.Result.FolderId, ShouldEqual, 0)
  204. })
  205. })
  206. Convey("When creating a dashboard in other folder with same name as dashboard in General folder", func() {
  207. cmd := models.SaveDashboardCommand{
  208. OrgId: testOrgId,
  209. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  210. "id": nil,
  211. "title": savedDashInGeneralFolder.Title,
  212. }),
  213. FolderId: savedFolder.Id,
  214. Overwrite: shouldOverwrite,
  215. }
  216. res := callSaveWithResult(cmd)
  217. So(res, ShouldNotBeNil)
  218. Convey("It should create a new dashboard", func() {
  219. So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)
  220. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  221. err := bus.Dispatch(&query)
  222. So(err, ShouldBeNil)
  223. So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
  224. })
  225. })
  226. Convey("When creating a folder with same name as dashboard in other folder", func() {
  227. cmd := models.SaveDashboardCommand{
  228. OrgId: testOrgId,
  229. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  230. "id": nil,
  231. "title": savedDashInFolder.Title,
  232. }),
  233. IsFolder: true,
  234. Overwrite: shouldOverwrite,
  235. }
  236. res := callSaveWithResult(cmd)
  237. So(res, ShouldNotBeNil)
  238. Convey("It should create a new folder", func() {
  239. So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)
  240. So(res.IsFolder, ShouldBeTrue)
  241. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  242. err := bus.Dispatch(&query)
  243. So(err, ShouldBeNil)
  244. So(query.Result.FolderId, ShouldEqual, 0)
  245. So(query.Result.IsFolder, ShouldBeTrue)
  246. })
  247. })
  248. Convey("When saving a dashboard without id and uid and unique title in folder", func() {
  249. cmd := models.SaveDashboardCommand{
  250. OrgId: testOrgId,
  251. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  252. "title": "Dash without id and uid",
  253. }),
  254. Overwrite: shouldOverwrite,
  255. }
  256. res := callSaveWithResult(cmd)
  257. So(res, ShouldNotBeNil)
  258. Convey("It should create a new dashboard", func() {
  259. So(res.Id, ShouldBeGreaterThan, 0)
  260. So(len(res.Uid), ShouldBeGreaterThan, 0)
  261. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  262. err := bus.Dispatch(&query)
  263. So(err, ShouldBeNil)
  264. So(query.Result.Id, ShouldEqual, res.Id)
  265. So(query.Result.Uid, ShouldEqual, res.Uid)
  266. })
  267. })
  268. Convey("When saving a dashboard when dashboard id is zero ", func() {
  269. cmd := models.SaveDashboardCommand{
  270. OrgId: testOrgId,
  271. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  272. "id": 0,
  273. "title": "Dash with zero id",
  274. }),
  275. Overwrite: shouldOverwrite,
  276. }
  277. res := callSaveWithResult(cmd)
  278. So(res, ShouldNotBeNil)
  279. Convey("It should create a new dashboard", func() {
  280. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  281. err := bus.Dispatch(&query)
  282. So(err, ShouldBeNil)
  283. So(query.Result.Id, ShouldEqual, res.Id)
  284. })
  285. })
  286. Convey("When saving a dashboard in non-existing folder", func() {
  287. cmd := models.SaveDashboardCommand{
  288. OrgId: testOrgId,
  289. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  290. "title": "Expect error",
  291. }),
  292. FolderId: 123412321,
  293. Overwrite: shouldOverwrite,
  294. }
  295. err := callSaveWithError(cmd)
  296. Convey("It should result in folder not found error", func() {
  297. So(err, ShouldNotBeNil)
  298. So(err, ShouldEqual, models.ErrDashboardFolderNotFound)
  299. })
  300. })
  301. Convey("When updating an existing dashboard by id without current version", func() {
  302. cmd := models.SaveDashboardCommand{
  303. OrgId: 1,
  304. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  305. "id": savedDashInGeneralFolder.Id,
  306. "title": "test dash 23",
  307. }),
  308. FolderId: savedFolder.Id,
  309. Overwrite: shouldOverwrite,
  310. }
  311. err := callSaveWithError(cmd)
  312. Convey("It should result in version mismatch error", func() {
  313. So(err, ShouldNotBeNil)
  314. So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
  315. })
  316. })
  317. Convey("When updating an existing dashboard by id with current version", func() {
  318. cmd := models.SaveDashboardCommand{
  319. OrgId: 1,
  320. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  321. "id": savedDashInGeneralFolder.Id,
  322. "title": "Updated title",
  323. "version": savedDashInGeneralFolder.Version,
  324. }),
  325. FolderId: savedFolder.Id,
  326. Overwrite: shouldOverwrite,
  327. }
  328. res := callSaveWithResult(cmd)
  329. So(res, ShouldNotBeNil)
  330. Convey("It should update dashboard", func() {
  331. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}
  332. err := bus.Dispatch(&query)
  333. So(err, ShouldBeNil)
  334. So(query.Result.Title, ShouldEqual, "Updated title")
  335. So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
  336. So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
  337. })
  338. })
  339. Convey("When updating an existing dashboard by uid without current version", func() {
  340. cmd := models.SaveDashboardCommand{
  341. OrgId: 1,
  342. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  343. "uid": savedDashInFolder.Uid,
  344. "title": "test dash 23",
  345. }),
  346. FolderId: 0,
  347. Overwrite: shouldOverwrite,
  348. }
  349. err := callSaveWithError(cmd)
  350. Convey("It should result in version mismatch error", func() {
  351. So(err, ShouldNotBeNil)
  352. So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
  353. })
  354. })
  355. Convey("When updating an existing dashboard by uid with current version", func() {
  356. cmd := models.SaveDashboardCommand{
  357. OrgId: 1,
  358. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  359. "uid": savedDashInFolder.Uid,
  360. "title": "Updated title",
  361. "version": savedDashInFolder.Version,
  362. }),
  363. FolderId: 0,
  364. Overwrite: shouldOverwrite,
  365. }
  366. res := callSaveWithResult(cmd)
  367. So(res, ShouldNotBeNil)
  368. Convey("It should update dashboard", func() {
  369. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
  370. err := bus.Dispatch(&query)
  371. So(err, ShouldBeNil)
  372. So(query.Result.Title, ShouldEqual, "Updated title")
  373. So(query.Result.FolderId, ShouldEqual, 0)
  374. So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
  375. })
  376. })
  377. Convey("When creating a dashboard with same name as dashboard in other folder", func() {
  378. cmd := models.SaveDashboardCommand{
  379. OrgId: testOrgId,
  380. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  381. "id": nil,
  382. "title": savedDashInFolder.Title,
  383. }),
  384. FolderId: savedDashInFolder.FolderId,
  385. Overwrite: shouldOverwrite,
  386. }
  387. err := callSaveWithError(cmd)
  388. Convey("It should result in dashboard with same name in folder error", func() {
  389. So(err, ShouldNotBeNil)
  390. So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
  391. })
  392. })
  393. Convey("When creating a dashboard with same name as dashboard in General folder", func() {
  394. cmd := models.SaveDashboardCommand{
  395. OrgId: testOrgId,
  396. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  397. "id": nil,
  398. "title": savedDashInGeneralFolder.Title,
  399. }),
  400. FolderId: savedDashInGeneralFolder.FolderId,
  401. Overwrite: shouldOverwrite,
  402. }
  403. err := callSaveWithError(cmd)
  404. Convey("It should result in dashboard with same name in folder error", func() {
  405. So(err, ShouldNotBeNil)
  406. So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
  407. })
  408. })
  409. Convey("When creating a folder with same name as existing folder", func() {
  410. cmd := models.SaveDashboardCommand{
  411. OrgId: testOrgId,
  412. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  413. "id": nil,
  414. "title": savedFolder.Title,
  415. }),
  416. IsFolder: true,
  417. Overwrite: shouldOverwrite,
  418. }
  419. err := callSaveWithError(cmd)
  420. Convey("It should result in dashboard with same name in folder error", func() {
  421. So(err, ShouldNotBeNil)
  422. So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
  423. })
  424. })
  425. })
  426. Convey("and overwrite flag is set to true", func() {
  427. shouldOverwrite := true
  428. Convey("When updating an existing dashboard by id without current version", func() {
  429. cmd := models.SaveDashboardCommand{
  430. OrgId: 1,
  431. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  432. "id": savedDashInGeneralFolder.Id,
  433. "title": "Updated title",
  434. }),
  435. FolderId: savedFolder.Id,
  436. Overwrite: shouldOverwrite,
  437. }
  438. res := callSaveWithResult(cmd)
  439. So(res, ShouldNotBeNil)
  440. Convey("It should update dashboard", func() {
  441. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}
  442. err := bus.Dispatch(&query)
  443. So(err, ShouldBeNil)
  444. So(query.Result.Title, ShouldEqual, "Updated title")
  445. So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
  446. So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
  447. })
  448. })
  449. Convey("When updating an existing dashboard by uid without current version", func() {
  450. cmd := models.SaveDashboardCommand{
  451. OrgId: 1,
  452. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  453. "uid": savedDashInFolder.Uid,
  454. "title": "Updated title",
  455. }),
  456. FolderId: 0,
  457. Overwrite: shouldOverwrite,
  458. }
  459. res := callSaveWithResult(cmd)
  460. So(res, ShouldNotBeNil)
  461. Convey("It should update dashboard", func() {
  462. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
  463. err := bus.Dispatch(&query)
  464. So(err, ShouldBeNil)
  465. So(query.Result.Title, ShouldEqual, "Updated title")
  466. So(query.Result.FolderId, ShouldEqual, 0)
  467. So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
  468. })
  469. })
  470. Convey("When updating uid for existing dashboard using id", func() {
  471. cmd := models.SaveDashboardCommand{
  472. OrgId: 1,
  473. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  474. "id": savedDashInFolder.Id,
  475. "uid": "new-uid",
  476. "title": savedDashInFolder.Title,
  477. }),
  478. Overwrite: shouldOverwrite,
  479. }
  480. res := callSaveWithResult(cmd)
  481. Convey("It should update dashboard", func() {
  482. So(res, ShouldNotBeNil)
  483. So(res.Id, ShouldEqual, savedDashInFolder.Id)
  484. So(res.Uid, ShouldEqual, "new-uid")
  485. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
  486. err := bus.Dispatch(&query)
  487. So(err, ShouldBeNil)
  488. So(query.Result.Uid, ShouldEqual, "new-uid")
  489. So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
  490. })
  491. })
  492. Convey("When updating uid to an existing uid for existing dashboard using id", func() {
  493. cmd := models.SaveDashboardCommand{
  494. OrgId: 1,
  495. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  496. "id": savedDashInFolder.Id,
  497. "uid": savedDashInGeneralFolder.Uid,
  498. "title": savedDashInFolder.Title,
  499. }),
  500. Overwrite: shouldOverwrite,
  501. }
  502. err := callSaveWithError(cmd)
  503. Convey("It should result in same uid exists error", func() {
  504. So(err, ShouldNotBeNil)
  505. So(err, ShouldEqual, models.ErrDashboardWithSameUIDExists)
  506. })
  507. })
  508. Convey("When creating a dashboard with same name as dashboard in other folder", func() {
  509. cmd := models.SaveDashboardCommand{
  510. OrgId: testOrgId,
  511. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  512. "id": nil,
  513. "title": savedDashInFolder.Title,
  514. }),
  515. FolderId: savedDashInFolder.FolderId,
  516. Overwrite: shouldOverwrite,
  517. }
  518. res := callSaveWithResult(cmd)
  519. Convey("It should overwrite existing dashboard", func() {
  520. So(res, ShouldNotBeNil)
  521. So(res.Id, ShouldEqual, savedDashInFolder.Id)
  522. So(res.Uid, ShouldEqual, savedDashInFolder.Uid)
  523. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  524. err := bus.Dispatch(&query)
  525. So(err, ShouldBeNil)
  526. So(query.Result.Id, ShouldEqual, res.Id)
  527. So(query.Result.Uid, ShouldEqual, res.Uid)
  528. })
  529. })
  530. Convey("When creating a dashboard with same name as dashboard in General folder", func() {
  531. cmd := models.SaveDashboardCommand{
  532. OrgId: testOrgId,
  533. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  534. "id": nil,
  535. "title": savedDashInGeneralFolder.Title,
  536. }),
  537. FolderId: savedDashInGeneralFolder.FolderId,
  538. Overwrite: shouldOverwrite,
  539. }
  540. res := callSaveWithResult(cmd)
  541. Convey("It should overwrite existing dashboard", func() {
  542. So(res, ShouldNotBeNil)
  543. So(res.Id, ShouldEqual, savedDashInGeneralFolder.Id)
  544. So(res.Uid, ShouldEqual, savedDashInGeneralFolder.Uid)
  545. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  546. err := bus.Dispatch(&query)
  547. So(err, ShouldBeNil)
  548. So(query.Result.Id, ShouldEqual, res.Id)
  549. So(query.Result.Uid, ShouldEqual, res.Uid)
  550. })
  551. })
  552. Convey("When trying to update existing folder to a dashboard using id", func() {
  553. cmd := models.SaveDashboardCommand{
  554. OrgId: 1,
  555. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  556. "id": savedFolder.Id,
  557. "title": "new title",
  558. }),
  559. IsFolder: false,
  560. Overwrite: shouldOverwrite,
  561. }
  562. err := callSaveWithError(cmd)
  563. Convey("It should result in type mismatch error", func() {
  564. So(err, ShouldNotBeNil)
  565. So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
  566. })
  567. })
  568. Convey("When trying to update existing dashboard to a folder using id", func() {
  569. cmd := models.SaveDashboardCommand{
  570. OrgId: 1,
  571. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  572. "id": savedDashInFolder.Id,
  573. "title": "new folder title",
  574. }),
  575. IsFolder: true,
  576. Overwrite: shouldOverwrite,
  577. }
  578. err := callSaveWithError(cmd)
  579. Convey("It should result in type mismatch error", func() {
  580. So(err, ShouldNotBeNil)
  581. So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
  582. })
  583. })
  584. Convey("When trying to update existing folder to a dashboard using uid", func() {
  585. cmd := models.SaveDashboardCommand{
  586. OrgId: 1,
  587. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  588. "uid": savedFolder.Uid,
  589. "title": "new title",
  590. }),
  591. IsFolder: false,
  592. Overwrite: shouldOverwrite,
  593. }
  594. err := callSaveWithError(cmd)
  595. Convey("It should result in type mismatch error", func() {
  596. So(err, ShouldNotBeNil)
  597. So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
  598. })
  599. })
  600. Convey("When trying to update existing dashboard to a folder using uid", func() {
  601. cmd := models.SaveDashboardCommand{
  602. OrgId: 1,
  603. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  604. "uid": savedDashInFolder.Uid,
  605. "title": "new folder title",
  606. }),
  607. IsFolder: true,
  608. Overwrite: shouldOverwrite,
  609. }
  610. err := callSaveWithError(cmd)
  611. Convey("It should result in type mismatch error", func() {
  612. So(err, ShouldNotBeNil)
  613. So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
  614. })
  615. })
  616. Convey("When trying to update existing folder to a dashboard using title", func() {
  617. cmd := models.SaveDashboardCommand{
  618. OrgId: 1,
  619. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  620. "title": savedFolder.Title,
  621. }),
  622. IsFolder: false,
  623. Overwrite: shouldOverwrite,
  624. }
  625. err := callSaveWithError(cmd)
  626. Convey("It should result in dashboard with same name as folder error", func() {
  627. So(err, ShouldNotBeNil)
  628. So(err, ShouldEqual, models.ErrDashboardWithSameNameAsFolder)
  629. })
  630. })
  631. Convey("When trying to update existing dashboard to a folder using title", func() {
  632. cmd := models.SaveDashboardCommand{
  633. OrgId: 1,
  634. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  635. "title": savedDashInGeneralFolder.Title,
  636. }),
  637. IsFolder: true,
  638. Overwrite: shouldOverwrite,
  639. }
  640. err := callSaveWithError(cmd)
  641. Convey("It should result in folder with same name as dashboard error", func() {
  642. So(err, ShouldNotBeNil)
  643. So(err, ShouldEqual, models.ErrDashboardFolderWithSameNameAsDashboard)
  644. })
  645. })
  646. })
  647. })
  648. })
  649. })
  650. }
  651. type scenarioContext struct {
  652. dashboardGuardianMock *guardian.FakeDashboardGuardian
  653. }
  654. type scenarioFunc func(c *scenarioContext)
  655. func dashboardGuardianScenario(desc string, mock *guardian.FakeDashboardGuardian, fn scenarioFunc) {
  656. Convey(desc, func() {
  657. origNewDashboardGuardian := guardian.New
  658. guardian.MockDashboardGuardian(mock)
  659. sc := &scenarioContext{
  660. dashboardGuardianMock: mock,
  661. }
  662. defer func() {
  663. guardian.New = origNewDashboardGuardian
  664. }()
  665. fn(sc)
  666. })
  667. }
  668. type dashboardPermissionScenarioContext struct {
  669. dashboardGuardianMock *guardian.FakeDashboardGuardian
  670. }
  671. type dashboardPermissionScenarioFunc func(sc *dashboardPermissionScenarioContext)
  672. func dashboardPermissionScenario(desc string, mock *guardian.FakeDashboardGuardian, fn dashboardPermissionScenarioFunc) {
  673. Convey(desc, func() {
  674. origNewDashboardGuardian := guardian.New
  675. guardian.MockDashboardGuardian(mock)
  676. sc := &dashboardPermissionScenarioContext{
  677. dashboardGuardianMock: mock,
  678. }
  679. defer func() {
  680. guardian.New = origNewDashboardGuardian
  681. }()
  682. fn(sc)
  683. })
  684. }
  685. func permissionScenario(desc string, canSave bool, fn dashboardPermissionScenarioFunc) {
  686. mock := &guardian.FakeDashboardGuardian{
  687. CanSaveValue: canSave,
  688. }
  689. dashboardPermissionScenario(desc, mock, fn)
  690. }
  691. func callSaveWithResult(cmd models.SaveDashboardCommand) *models.Dashboard {
  692. dto := toSaveDashboardDto(cmd)
  693. res, _ := dashboards.NewService().SaveDashboard(&dto)
  694. return res
  695. }
  696. func callSaveWithError(cmd models.SaveDashboardCommand) error {
  697. dto := toSaveDashboardDto(cmd)
  698. _, err := dashboards.NewService().SaveDashboard(&dto)
  699. return err
  700. }
  701. func dashboardServiceScenario(desc string, mock *guardian.FakeDashboardGuardian, fn scenarioFunc) {
  702. Convey(desc, func() {
  703. origNewDashboardGuardian := guardian.New
  704. guardian.MockDashboardGuardian(mock)
  705. sc := &scenarioContext{
  706. dashboardGuardianMock: mock,
  707. }
  708. defer func() {
  709. guardian.New = origNewDashboardGuardian
  710. }()
  711. fn(sc)
  712. })
  713. }
  714. func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashboard {
  715. cmd := models.SaveDashboardCommand{
  716. OrgId: orgId,
  717. FolderId: folderId,
  718. IsFolder: false,
  719. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  720. "id": nil,
  721. "title": title,
  722. }),
  723. }
  724. dto := dashboards.SaveDashboardDTO{
  725. OrgId: orgId,
  726. Dashboard: cmd.GetDashboardModel(),
  727. User: &models.SignedInUser{
  728. UserId: 1,
  729. OrgRole: models.ROLE_ADMIN,
  730. },
  731. }
  732. res, err := dashboards.NewService().SaveDashboard(&dto)
  733. So(err, ShouldBeNil)
  734. return res
  735. }
  736. func saveTestFolder(title string, orgId int64) *models.Dashboard {
  737. cmd := models.SaveDashboardCommand{
  738. OrgId: orgId,
  739. FolderId: 0,
  740. IsFolder: true,
  741. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  742. "id": nil,
  743. "title": title,
  744. }),
  745. }
  746. dto := dashboards.SaveDashboardDTO{
  747. OrgId: orgId,
  748. Dashboard: cmd.GetDashboardModel(),
  749. User: &models.SignedInUser{
  750. UserId: 1,
  751. OrgRole: models.ROLE_ADMIN,
  752. },
  753. }
  754. res, err := dashboards.NewService().SaveDashboard(&dto)
  755. So(err, ShouldBeNil)
  756. return res
  757. }
  758. func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashboards.SaveDashboardDTO {
  759. dash := (&cmd).GetDashboardModel()
  760. return dashboards.SaveDashboardDTO{
  761. Dashboard: dash,
  762. Message: cmd.Message,
  763. OrgId: cmd.OrgId,
  764. User: &models.SignedInUser{UserId: cmd.UserId},
  765. Overwrite: cmd.Overwrite,
  766. }
  767. }