dashboard_service.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. package dashboards
  2. import (
  3. "strings"
  4. "time"
  5. "github.com/grafana/grafana/pkg/bus"
  6. "github.com/grafana/grafana/pkg/log"
  7. "github.com/grafana/grafana/pkg/models"
  8. "github.com/grafana/grafana/pkg/services/guardian"
  9. "github.com/grafana/grafana/pkg/util"
  10. "github.com/grafana/grafana/pkg/util/errutil"
  11. )
  12. // DashboardService service for operating on dashboards
  13. type DashboardService interface {
  14. SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error)
  15. ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error)
  16. DeleteDashboard(dashboardId int64, orgId int64) error
  17. }
  18. // DashboardProvisioningService service for operating on provisioned dashboards
  19. type DashboardProvisioningService interface {
  20. SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
  21. SaveFolderForProvisionedDashboards(*SaveDashboardDTO) (*models.Dashboard, error)
  22. GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
  23. GetProvisionedDashboardDataByDashboardId(dashboardId int64) (*models.DashboardProvisioning, error)
  24. UnprovisionDashboard(dashboardId int64) error
  25. DeleteProvisionedDashboard(dashboardId int64, orgId int64) error
  26. }
  27. // NewService factory for creating a new dashboard service
  28. var NewService = func() DashboardService {
  29. return &dashboardServiceImpl{
  30. log: log.New("dashboard-service"),
  31. }
  32. }
  33. // NewProvisioningService factory for creating a new dashboard provisioning service
  34. var NewProvisioningService = func() DashboardProvisioningService {
  35. return &dashboardServiceImpl{
  36. log: log.New("dashboard-provisioning-service"),
  37. }
  38. }
  39. type SaveDashboardDTO struct {
  40. OrgId int64
  41. UpdatedAt time.Time
  42. User *models.SignedInUser
  43. Message string
  44. Overwrite bool
  45. Dashboard *models.Dashboard
  46. }
  47. type dashboardServiceImpl struct {
  48. orgId int64
  49. user *models.SignedInUser
  50. log log.Logger
  51. }
  52. func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
  53. cmd := &models.GetProvisionedDashboardDataQuery{Name: name}
  54. err := bus.Dispatch(cmd)
  55. if err != nil {
  56. return nil, err
  57. }
  58. return cmd.Result, nil
  59. }
  60. func (dr *dashboardServiceImpl) GetProvisionedDashboardDataByDashboardId(dashboardId int64) (*models.DashboardProvisioning, error) {
  61. cmd := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: dashboardId}
  62. err := bus.Dispatch(cmd)
  63. if err != nil {
  64. return nil, err
  65. }
  66. return cmd.Result, nil
  67. }
  68. func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, validateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error) {
  69. dash := dto.Dashboard
  70. dash.Title = strings.TrimSpace(dash.Title)
  71. dash.Data.Set("title", dash.Title)
  72. dash.SetUid(strings.TrimSpace(dash.Uid))
  73. if dash.Title == "" {
  74. return nil, models.ErrDashboardTitleEmpty
  75. }
  76. if dash.IsFolder && dash.FolderId > 0 {
  77. return nil, models.ErrDashboardFolderCannotHaveParent
  78. }
  79. if dash.IsFolder && strings.EqualFold(dash.Title, models.RootFolderName) {
  80. return nil, models.ErrDashboardFolderNameExists
  81. }
  82. if !util.IsValidShortUID(dash.Uid) {
  83. return nil, models.ErrDashboardInvalidUid
  84. } else if len(dash.Uid) > 40 {
  85. return nil, models.ErrDashboardUidToLong
  86. }
  87. if validateAlerts {
  88. validateAlertsCmd := models.ValidateDashboardAlertsCommand{
  89. OrgId: dto.OrgId,
  90. Dashboard: dash,
  91. User: dto.User,
  92. }
  93. if err := bus.Dispatch(&validateAlertsCmd); err != nil {
  94. return nil, err
  95. }
  96. }
  97. validateBeforeSaveCmd := models.ValidateDashboardBeforeSaveCommand{
  98. OrgId: dto.OrgId,
  99. Dashboard: dash,
  100. Overwrite: dto.Overwrite,
  101. }
  102. if err := bus.Dispatch(&validateBeforeSaveCmd); err != nil {
  103. return nil, err
  104. }
  105. if validateBeforeSaveCmd.Result.IsParentFolderChanged {
  106. folderGuardian := guardian.New(dash.FolderId, dto.OrgId, dto.User)
  107. if canSave, err := folderGuardian.CanSave(); err != nil || !canSave {
  108. if err != nil {
  109. return nil, err
  110. }
  111. return nil, models.ErrDashboardUpdateAccessDenied
  112. }
  113. }
  114. if validateProvisionedDashboard {
  115. provisionedData, err := dr.GetProvisionedDashboardDataByDashboardId(dash.Id)
  116. if err != nil {
  117. return nil, err
  118. }
  119. if provisionedData != nil {
  120. return nil, models.ErrDashboardCannotSaveProvisionedDashboard
  121. }
  122. }
  123. guard := guardian.New(dash.GetDashboardIdForSavePermissionCheck(), dto.OrgId, dto.User)
  124. if canSave, err := guard.CanSave(); err != nil || !canSave {
  125. if err != nil {
  126. return nil, err
  127. }
  128. return nil, models.ErrDashboardUpdateAccessDenied
  129. }
  130. cmd := &models.SaveDashboardCommand{
  131. Dashboard: dash.Data,
  132. Message: dto.Message,
  133. OrgId: dto.OrgId,
  134. Overwrite: dto.Overwrite,
  135. UserId: dto.User.UserId,
  136. FolderId: dash.FolderId,
  137. IsFolder: dash.IsFolder,
  138. PluginId: dash.PluginId,
  139. }
  140. if !dto.UpdatedAt.IsZero() {
  141. cmd.UpdatedAt = dto.UpdatedAt
  142. }
  143. return cmd, nil
  144. }
  145. func (dr *dashboardServiceImpl) updateAlerting(cmd *models.SaveDashboardCommand, dto *SaveDashboardDTO) error {
  146. alertCmd := models.UpdateDashboardAlertsCommand{
  147. OrgId: dto.OrgId,
  148. Dashboard: cmd.Result,
  149. User: dto.User,
  150. }
  151. return bus.Dispatch(&alertCmd)
  152. }
  153. func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
  154. dto.User = &models.SignedInUser{
  155. UserId: 0,
  156. OrgRole: models.ROLE_ADMIN,
  157. OrgId: dto.OrgId,
  158. }
  159. cmd, err := dr.buildSaveDashboardCommand(dto, true, false)
  160. if err != nil {
  161. return nil, err
  162. }
  163. saveCmd := &models.SaveProvisionedDashboardCommand{
  164. DashboardCmd: cmd,
  165. DashboardProvisioning: provisioning,
  166. }
  167. // dashboard
  168. err = bus.Dispatch(saveCmd)
  169. if err != nil {
  170. return nil, err
  171. }
  172. //alerts
  173. err = dr.updateAlerting(cmd, dto)
  174. if err != nil {
  175. return nil, err
  176. }
  177. return cmd.Result, nil
  178. }
  179. func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDashboardDTO) (*models.Dashboard, error) {
  180. dto.User = &models.SignedInUser{
  181. UserId: 0,
  182. OrgRole: models.ROLE_ADMIN,
  183. }
  184. cmd, err := dr.buildSaveDashboardCommand(dto, false, false)
  185. if err != nil {
  186. return nil, err
  187. }
  188. err = bus.Dispatch(cmd)
  189. if err != nil {
  190. return nil, err
  191. }
  192. err = dr.updateAlerting(cmd, dto)
  193. if err != nil {
  194. return nil, err
  195. }
  196. return cmd.Result, nil
  197. }
  198. func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
  199. cmd, err := dr.buildSaveDashboardCommand(dto, true, true)
  200. if err != nil {
  201. return nil, err
  202. }
  203. err = bus.Dispatch(cmd)
  204. if err != nil {
  205. return nil, err
  206. }
  207. err = dr.updateAlerting(cmd, dto)
  208. if err != nil {
  209. return nil, err
  210. }
  211. return cmd.Result, nil
  212. }
  213. // DeleteDashboard removes dashboard from the DB. Errors out if the dashboard was provisioned. Should be used for
  214. // operations by the user where we want to make sure user does not delete provisioned dashboard.
  215. func (dr *dashboardServiceImpl) DeleteDashboard(dashboardId int64, orgId int64) error {
  216. return dr.deleteDashboard(dashboardId, orgId, true)
  217. }
  218. // DeleteProvisionedDashboard removes dashboard from the DB even if it is provisioned.
  219. func (dr *dashboardServiceImpl) DeleteProvisionedDashboard(dashboardId int64, orgId int64) error {
  220. return dr.deleteDashboard(dashboardId, orgId, false)
  221. }
  222. func (dr *dashboardServiceImpl) deleteDashboard(dashboardId int64, orgId int64, validateProvisionedDashboard bool) error {
  223. if validateProvisionedDashboard {
  224. provisionedData, err := dr.GetProvisionedDashboardDataByDashboardId(dashboardId)
  225. if err != nil {
  226. return errutil.Wrap("failed to check if dashboard is provisioned", err)
  227. }
  228. if provisionedData != nil {
  229. return models.ErrDashboardCannotDeleteProvisionedDashboard
  230. }
  231. }
  232. cmd := &models.DeleteDashboardCommand{OrgId: orgId, Id: dashboardId}
  233. return bus.Dispatch(cmd)
  234. }
  235. func (dr *dashboardServiceImpl) ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
  236. cmd, err := dr.buildSaveDashboardCommand(dto, false, true)
  237. if err != nil {
  238. return nil, err
  239. }
  240. err = bus.Dispatch(cmd)
  241. if err != nil {
  242. return nil, err
  243. }
  244. return cmd.Result, nil
  245. }
  246. // UnprovisionDashboard removes info about dashboard being provisioned. Used after provisioning configs are changed
  247. // and provisioned dashboards are left behind but not deleted.
  248. func (dr *dashboardServiceImpl) UnprovisionDashboard(dashboardId int64) error {
  249. cmd := &models.UnprovisionDashboardCommand{Id: dashboardId}
  250. return bus.Dispatch(cmd)
  251. }
  252. type FakeDashboardService struct {
  253. SaveDashboardResult *models.Dashboard
  254. SaveDashboardError error
  255. SavedDashboards []*SaveDashboardDTO
  256. }
  257. func (s *FakeDashboardService) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
  258. s.SavedDashboards = append(s.SavedDashboards, dto)
  259. if s.SaveDashboardResult == nil && s.SaveDashboardError == nil {
  260. s.SaveDashboardResult = dto.Dashboard
  261. }
  262. return s.SaveDashboardResult, s.SaveDashboardError
  263. }
  264. func (s *FakeDashboardService) ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
  265. return s.SaveDashboard(dto)
  266. }
  267. func (s *FakeDashboardService) DeleteDashboard(dashboardId int64, orgId int64) error {
  268. for index, dash := range s.SavedDashboards {
  269. if dash.Dashboard.Id == dashboardId && dash.OrgId == orgId {
  270. s.SavedDashboards = append(s.SavedDashboards[:index], s.SavedDashboards[index+1:]...)
  271. break
  272. }
  273. }
  274. return nil
  275. }
  276. func MockDashboardService(mock *FakeDashboardService) {
  277. NewService = func() DashboardService {
  278. return mock
  279. }
  280. }