dashboard_service_integration_test.go 33 KB

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