dashboard_service.go 8.9 KB

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