Skip to content

Commit

Permalink
Merge remote-tracking branch 'grafana/master'
Browse files Browse the repository at this point in the history
* grafana/master:
  new dashboard is now hidden from viewer, fixes grafana#10815 (grafana#10854)
  fixed bg gradient, fixes grafana#10869 (grafana#10875)
  login: fix broken reset password form (grafana#10881)
  docs: spelling.
  dashboard: always make sure dashboard exist in dashboard acl http api (grafana#10856)
  Fix grafana#10823 (grafana#10851)
  provisioning: better variable naming
  provisioning: dont return error unless you want to cancel all operations
  provisioning: createWalkFn doesnt have to be attached to the filereader anymore
  provisioning: update sample config to use path
  provisioning: avoid caching and use updated field from db
  provisioning: delete dashboards before insert/update
  provisioning: fixed bug in saving dashboards.
  provisioning: delete dashboards from db when file is missing
  provisioning: enables title changes for dashboards
  dashboards: save provisioning meta data
  provisioing: add lookup table provisioned dashboards
  codestyle: extract code into methods
  • Loading branch information
ryantxu committed Feb 13, 2018
2 parents 836a8d4 + aaf4e76 commit d3efb57
Show file tree
Hide file tree
Showing 26 changed files with 572 additions and 264 deletions.
2 changes: 1 addition & 1 deletion conf/provisioning/dashboards/sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
# folder: ''
# type: file
# options:
# folder: /var/lib/grafana/dashboards
# path: /var/lib/grafana/dashboards
2 changes: 1 addition & 1 deletion docs/sources/features/datasources/cloudwatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ weight = 10

# Using AWS CloudWatch in Grafana

Grafana ships with built in support for CloudWatch. You just have to add it as a data source and you will be ready to build dashboards for you CloudWatch metrics.
Grafana ships with built in support for CloudWatch. You just have to add it as a data source and you will be ready to build dashboards for your CloudWatch metrics.

## Adding the data source to Grafana

Expand Down
2 changes: 1 addition & 1 deletion pkg/api/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
}
}

dashItem := &dashboards.SaveDashboardItem{
dashItem := &dashboards.SaveDashboardDTO{
Dashboard: dash,
Message: cmd.Message,
OrgId: c.OrgId,
Expand Down
15 changes: 15 additions & 0 deletions pkg/api/dashboard_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import (
func GetDashboardAclList(c *middleware.Context) Response {
dashId := c.ParamsInt64(":dashboardId")

_, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
if rsp != nil {
return rsp
}

guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)

if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
Expand All @@ -36,6 +41,11 @@ func GetDashboardAclList(c *middleware.Context) Response {
func UpdateDashboardAcl(c *middleware.Context, apiCmd dtos.UpdateDashboardAclCommand) Response {
dashId := c.ParamsInt64(":dashboardId")

_, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
if rsp != nil {
return rsp
}

guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err)
Expand Down Expand Up @@ -79,6 +89,11 @@ func DeleteDashboardAcl(c *middleware.Context) Response {
dashId := c.ParamsInt64(":dashboardId")
aclId := c.ParamsInt64(":aclId")

_, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
if rsp != nil {
return rsp
}

guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err)
Expand Down
42 changes: 42 additions & 0 deletions pkg/api/dashboard_acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
}
dtoRes := transformDashboardAclsToDTOs(mockResult)

getDashboardQueryResult := m.NewDashboard("Dash")
var getDashboardNotFoundError error

bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = getDashboardQueryResult
return getDashboardNotFoundError
})

bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = dtoRes
return nil
Expand Down Expand Up @@ -60,6 +68,40 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
So(respJSON.GetIndex(0).Get("permission").MustInt(), ShouldEqual, m.PERMISSION_VIEW)
})
})

loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_ADMIN, func(sc *scenarioContext) {
getDashboardNotFoundError = m.ErrDashboardNotFound
sc.handlerFunc = GetDashboardAclList
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()

Convey("Should not be able to access ACL", func() {
So(sc.resp.Code, ShouldEqual, 404)
})
})

Convey("Should not be able to update permissions for non-existing dashboard", func() {
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: m.PERMISSION_ADMIN},
},
}

postAclScenario("When calling POST on", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_ADMIN, cmd, func(sc *scenarioContext) {
getDashboardNotFoundError = m.ErrDashboardNotFound
CallPostAcl(sc)
So(sc.resp.Code, ShouldEqual, 404)
})
})

loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/2/acl/6", "/api/dashboards/id/:dashboardId/acl/:aclId", m.ROLE_ADMIN, func(sc *scenarioContext) {
getDashboardNotFoundError = m.ErrDashboardNotFound
sc.handlerFunc = DeleteDashboardAcl
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()

Convey("Should not be able to delete non-existing dashboard", func() {
So(sc.resp.Code, ShouldEqual, 404)
})
})
})

Convey("When user is org editor and has admin permission in the ACL", func() {
Expand Down
14 changes: 12 additions & 2 deletions pkg/api/dashboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,25 @@ import (
)

type fakeDashboardRepo struct {
inserted []*dashboards.SaveDashboardItem
inserted []*dashboards.SaveDashboardDTO
provisioned []*m.DashboardProvisioning
getDashboard []*m.Dashboard
}

func (repo *fakeDashboardRepo) SaveDashboard(json *dashboards.SaveDashboardItem) (*m.Dashboard, error) {
func (repo *fakeDashboardRepo) SaveDashboard(json *dashboards.SaveDashboardDTO) (*m.Dashboard, error) {
repo.inserted = append(repo.inserted, json)
return json.Dashboard, nil
}

func (repo *fakeDashboardRepo) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *m.DashboardProvisioning) (*m.Dashboard, error) {
repo.inserted = append(repo.inserted, dto)
return dto.Dashboard, nil
}

func (repo *fakeDashboardRepo) GetProvisionedDashboardData(name string) ([]*m.DashboardProvisioning, error) {
return repo.provisioned, nil
}

var fakeRepo *fakeDashboardRepo

// This tests two main scenarios. If a user has access to execute an action on a dashboard:
Expand Down
26 changes: 26 additions & 0 deletions pkg/models/dashboards.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ type Dashboard struct {
Data *simplejson.Json
}

func (d *Dashboard) SetId(id int64) {
d.Id = id
d.Data.Set("id", id)
}

// NewDashboard creates a new dashboard
func NewDashboard(title string) *Dashboard {
dash := &Dashboard{}
Expand Down Expand Up @@ -219,6 +224,21 @@ type SaveDashboardCommand struct {
Result *Dashboard
}

type DashboardProvisioning struct {
Id int64
DashboardId int64
Name string
ExternalId string
Updated time.Time
}

type SaveProvisionedDashboardCommand struct {
DashboardCmd *SaveDashboardCommand
DashboardProvisioning *DashboardProvisioning

Result *Dashboard
}

type DeleteDashboardCommand struct {
Id int64
OrgId int64
Expand Down Expand Up @@ -271,6 +291,12 @@ type GetDashboardSlugByIdQuery struct {
Result string
}

type GetProvisionedDashboardDataQuery struct {
Name string

Result []*DashboardProvisioning
}

type GetDashboardsBySlugQuery struct {
OrgId int64
Slug string
Expand Down
94 changes: 75 additions & 19 deletions pkg/services/dashboards/dashboards.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (
)

type Repository interface {
SaveDashboard(*SaveDashboardItem) (*models.Dashboard, error)
SaveDashboard(*SaveDashboardDTO) (*models.Dashboard, error)
SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
}

var repositoryInstance Repository
Expand All @@ -22,7 +24,7 @@ func SetRepository(rep Repository) {
repositoryInstance = rep
}

type SaveDashboardItem struct {
type SaveDashboardDTO struct {
OrgId int64
UpdatedAt time.Time
UserId int64
Expand All @@ -33,49 +35,103 @@ type SaveDashboardItem struct {

type DashboardRepository struct{}

func (dr *DashboardRepository) SaveDashboard(json *SaveDashboardItem) (*models.Dashboard, error) {
dashboard := json.Dashboard
func (dr *DashboardRepository) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
cmd := &models.GetProvisionedDashboardDataQuery{Name: name}
err := bus.Dispatch(cmd)
if err != nil {
return nil, err
}

return cmd.Result, nil
}

func (dr *DashboardRepository) buildSaveDashboardCommand(dto *SaveDashboardDTO) (*models.SaveDashboardCommand, error) {
dashboard := dto.Dashboard

if dashboard.Title == "" {
return nil, models.ErrDashboardTitleEmpty
}

validateAlertsCmd := alerting.ValidateDashboardAlertsCommand{
OrgId: json.OrgId,
OrgId: dto.OrgId,
Dashboard: dashboard,
}

if err := bus.Dispatch(&validateAlertsCmd); err != nil {
return nil, models.ErrDashboardContainsInvalidAlertData
}

cmd := models.SaveDashboardCommand{
cmd := &models.SaveDashboardCommand{
Dashboard: dashboard.Data,
Message: json.Message,
OrgId: json.OrgId,
Overwrite: json.Overwrite,
UserId: json.UserId,
Message: dto.Message,
OrgId: dto.OrgId,
Overwrite: dto.Overwrite,
UserId: dto.UserId,
FolderId: dashboard.FolderId,
IsFolder: dashboard.IsFolder,
}

if !json.UpdatedAt.IsZero() {
cmd.UpdatedAt = json.UpdatedAt
if !dto.UpdatedAt.IsZero() {
cmd.UpdatedAt = dto.UpdatedAt
}

err := bus.Dispatch(&cmd)
if err != nil {
return nil, err
}
return cmd, nil
}

func (dr *DashboardRepository) updateAlerting(cmd *models.SaveDashboardCommand, dto *SaveDashboardDTO) error {
alertCmd := alerting.UpdateDashboardAlertsCommand{
OrgId: json.OrgId,
UserId: json.UserId,
OrgId: dto.OrgId,
UserId: dto.UserId,
Dashboard: cmd.Result,
}

if err := bus.Dispatch(&alertCmd); err != nil {
return nil, models.ErrDashboardFailedToUpdateAlertData
return models.ErrDashboardFailedToUpdateAlertData
}

return nil
}

func (dr *DashboardRepository) SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
cmd, err := dr.buildSaveDashboardCommand(dto)
if err != nil {
return nil, err
}

saveCmd := &models.SaveProvisionedDashboardCommand{
DashboardCmd: cmd,
DashboardProvisioning: provisioning,
}

// dashboard
err = bus.Dispatch(saveCmd)
if err != nil {
return nil, err
}

//alerts
err = dr.updateAlerting(cmd, dto)
if err != nil {
return nil, err
}

return cmd.Result, nil
}

func (dr *DashboardRepository) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
cmd, err := dr.buildSaveDashboardCommand(dto)
if err != nil {
return nil, err
}

err = bus.Dispatch(cmd)
if err != nil {
return nil, err
}

err = dr.updateAlerting(cmd, dto)
if err != nil {
return nil, err
}

return cmd.Result, nil
Expand Down
33 changes: 0 additions & 33 deletions pkg/services/provisioning/dashboards/dashboard_cache.go

This file was deleted.

Loading

0 comments on commit d3efb57

Please sign in to comment.