mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #11398 from bergquist/readonly_dashboards
improved workflow for provisioned dashboards
This commit is contained in:
@@ -102,6 +102,16 @@ func GetDashboard(c *m.ReqContext) Response {
|
|||||||
meta.FolderUrl = query.Result.GetUrl()
|
meta.FolderUrl = query.Result.GetUrl()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isDashboardProvisioned := &m.IsDashboardProvisionedQuery{DashboardId: dash.Id}
|
||||||
|
err = bus.Dispatch(isDashboardProvisioned)
|
||||||
|
if err != nil {
|
||||||
|
return Error(500, "Error while checking if dashboard is provisioned", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDashboardProvisioned.Result {
|
||||||
|
meta.Provisioned = true
|
||||||
|
}
|
||||||
|
|
||||||
// make sure db version is in sync with json model version
|
// make sure db version is in sync with json model version
|
||||||
dash.Data.Set("version", dash.Version)
|
dash.Data.Set("version", dash.Version)
|
||||||
|
|
||||||
@@ -228,7 +238,8 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
|
|||||||
err == m.ErrDashboardWithSameUIDExists ||
|
err == m.ErrDashboardWithSameUIDExists ||
|
||||||
err == m.ErrFolderNotFound ||
|
err == m.ErrFolderNotFound ||
|
||||||
err == m.ErrDashboardFolderCannotHaveParent ||
|
err == m.ErrDashboardFolderCannotHaveParent ||
|
||||||
err == m.ErrDashboardFolderNameExists {
|
err == m.ErrDashboardFolderNameExists ||
|
||||||
|
err == m.ErrDashboardCannotSaveProvisionedDashboard {
|
||||||
return Error(400, err.Error(), nil)
|
return Error(400, err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(query *m.IsDashboardProvisionedQuery) error {
|
||||||
|
query.Result = false
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
viewerRole := m.ROLE_VIEWER
|
viewerRole := m.ROLE_VIEWER
|
||||||
editorRole := m.ROLE_EDITOR
|
editorRole := m.ROLE_EDITOR
|
||||||
|
|
||||||
@@ -192,6 +197,11 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
|||||||
fakeDash.HasAcl = true
|
fakeDash.HasAcl = true
|
||||||
setting.ViewersCanEdit = false
|
setting.ViewersCanEdit = false
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(query *m.IsDashboardProvisionedQuery) error {
|
||||||
|
query.Result = false
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
bus.AddHandler("test", func(query *m.GetDashboardsBySlugQuery) error {
|
bus.AddHandler("test", func(query *m.GetDashboardsBySlugQuery) error {
|
||||||
dashboards := []*m.Dashboard{fakeDash}
|
dashboards := []*m.Dashboard{fakeDash}
|
||||||
query.Result = dashboards
|
query.Result = dashboards
|
||||||
@@ -625,6 +635,11 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
|||||||
dashTwo.FolderId = 3
|
dashTwo.FolderId = 3
|
||||||
dashTwo.HasAcl = false
|
dashTwo.HasAcl = false
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(query *m.IsDashboardProvisionedQuery) error {
|
||||||
|
query.Result = false
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
bus.AddHandler("test", func(query *m.GetDashboardsBySlugQuery) error {
|
bus.AddHandler("test", func(query *m.GetDashboardsBySlugQuery) error {
|
||||||
dashboards := []*m.Dashboard{dashOne, dashTwo}
|
dashboards := []*m.Dashboard{dashOne, dashTwo}
|
||||||
query.Result = dashboards
|
query.Result = dashboards
|
||||||
@@ -720,6 +735,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
|||||||
{SaveError: m.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403},
|
{SaveError: m.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403},
|
||||||
{SaveError: m.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
|
{SaveError: m.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
|
||||||
{SaveError: m.ErrDashboardUidToLong, ExpectedStatusCode: 400},
|
{SaveError: m.ErrDashboardUidToLong, ExpectedStatusCode: 400},
|
||||||
|
{SaveError: m.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: 400},
|
||||||
{SaveError: m.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412},
|
{SaveError: m.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,6 +766,11 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(query *m.IsDashboardProvisionedQuery) error {
|
||||||
|
query.Result = false
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
bus.AddHandler("test", func(query *m.GetDashboardVersionQuery) error {
|
bus.AddHandler("test", func(query *m.GetDashboardVersionQuery) error {
|
||||||
query.Result = &m.DashboardVersion{
|
query.Result = &m.DashboardVersion{
|
||||||
Data: simplejson.NewFromAny(map[string]interface{}{
|
Data: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type DashboardMeta struct {
|
|||||||
FolderId int64 `json:"folderId"`
|
FolderId int64 `json:"folderId"`
|
||||||
FolderTitle string `json:"folderTitle"`
|
FolderTitle string `json:"folderTitle"`
|
||||||
FolderUrl string `json:"folderUrl"`
|
FolderUrl string `json:"folderUrl"`
|
||||||
|
Provisioned bool `json:"provisioned"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DashboardFullWithMeta struct {
|
type DashboardFullWithMeta struct {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ var (
|
|||||||
ErrDashboardUpdateAccessDenied = errors.New("Access denied to save dashboard")
|
ErrDashboardUpdateAccessDenied = errors.New("Access denied to save dashboard")
|
||||||
ErrDashboardInvalidUid = errors.New("uid contains illegal characters")
|
ErrDashboardInvalidUid = errors.New("uid contains illegal characters")
|
||||||
ErrDashboardUidToLong = errors.New("uid to long. max 40 characters")
|
ErrDashboardUidToLong = errors.New("uid to long. max 40 characters")
|
||||||
|
ErrDashboardCannotSaveProvisionedDashboard = errors.New("Cannot save provisioned dashboard")
|
||||||
RootFolderName = "General"
|
RootFolderName = "General"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -322,6 +323,12 @@ type GetDashboardSlugByIdQuery struct {
|
|||||||
Result string
|
Result string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IsDashboardProvisionedQuery struct {
|
||||||
|
DashboardId int64
|
||||||
|
|
||||||
|
Result bool
|
||||||
|
}
|
||||||
|
|
||||||
type GetProvisionedDashboardDataQuery struct {
|
type GetProvisionedDashboardDataQuery struct {
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*mod
|
|||||||
return cmd.Result, nil
|
return cmd.Result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, validateAlerts bool) (*models.SaveDashboardCommand, error) {
|
func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, validateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error) {
|
||||||
dash := dto.Dashboard
|
dash := dto.Dashboard
|
||||||
|
|
||||||
dash.Title = strings.TrimSpace(dash.Title)
|
dash.Title = strings.TrimSpace(dash.Title)
|
||||||
@@ -113,6 +113,19 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if validateProvisionedDashboard {
|
||||||
|
isDashboardProvisioned := &models.IsDashboardProvisionedQuery{DashboardId: dash.Id}
|
||||||
|
err := bus.Dispatch(isDashboardProvisioned)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDashboardProvisioned.Result {
|
||||||
|
return nil, models.ErrDashboardCannotSaveProvisionedDashboard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
guard := guardian.New(dash.GetDashboardIdForSavePermissionCheck(), dto.OrgId, dto.User)
|
guard := guardian.New(dash.GetDashboardIdForSavePermissionCheck(), dto.OrgId, dto.User)
|
||||||
if canSave, err := guard.CanSave(); err != nil || !canSave {
|
if canSave, err := guard.CanSave(); err != nil || !canSave {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -158,7 +171,7 @@ func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO,
|
|||||||
UserId: 0,
|
UserId: 0,
|
||||||
OrgRole: models.ROLE_ADMIN,
|
OrgRole: models.ROLE_ADMIN,
|
||||||
}
|
}
|
||||||
cmd, err := dr.buildSaveDashboardCommand(dto, true)
|
cmd, err := dr.buildSaveDashboardCommand(dto, true, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -188,7 +201,7 @@ func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDash
|
|||||||
UserId: 0,
|
UserId: 0,
|
||||||
OrgRole: models.ROLE_ADMIN,
|
OrgRole: models.ROLE_ADMIN,
|
||||||
}
|
}
|
||||||
cmd, err := dr.buildSaveDashboardCommand(dto, false)
|
cmd, err := dr.buildSaveDashboardCommand(dto, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -207,7 +220,7 @@ func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDash
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
|
func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
|
||||||
cmd, err := dr.buildSaveDashboardCommand(dto, true)
|
cmd, err := dr.buildSaveDashboardCommand(dto, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -226,7 +239,7 @@ func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO) (*models.Da
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dr *dashboardServiceImpl) ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
|
func (dr *dashboardServiceImpl) ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
|
||||||
cmd, err := dr.buildSaveDashboardCommand(dto, false)
|
cmd, err := dr.buildSaveDashboardCommand(dto, false, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import (
|
|||||||
|
|
||||||
func TestDashboardService(t *testing.T) {
|
func TestDashboardService(t *testing.T) {
|
||||||
Convey("Dashboard service tests", t, func() {
|
Convey("Dashboard service tests", t, func() {
|
||||||
service := dashboardServiceImpl{}
|
bus.ClearBusHandlers()
|
||||||
|
|
||||||
|
service := &dashboardServiceImpl{}
|
||||||
|
|
||||||
origNewDashboardGuardian := guardian.New
|
origNewDashboardGuardian := guardian.New
|
||||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true})
|
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true})
|
||||||
@@ -55,6 +57,11 @@ func TestDashboardService(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
|
||||||
|
cmd.Result = false
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
Uid string
|
Uid string
|
||||||
Error error
|
Error error
|
||||||
@@ -73,12 +80,42 @@ func TestDashboardService(t *testing.T) {
|
|||||||
dto.Dashboard.SetUid(tc.Uid)
|
dto.Dashboard.SetUid(tc.Uid)
|
||||||
dto.User = &models.SignedInUser{}
|
dto.User = &models.SignedInUser{}
|
||||||
|
|
||||||
_, err := service.buildSaveDashboardCommand(dto, true)
|
_, err := service.buildSaveDashboardCommand(dto, true, false)
|
||||||
So(err, ShouldEqual, tc.Error)
|
So(err, ShouldEqual, tc.Error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("Should return validation error if dashboard is provisioned", func() {
|
||||||
|
provisioningValidated := false
|
||||||
|
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
|
||||||
|
provisioningValidated = true
|
||||||
|
cmd.Result = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
|
||||||
|
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
dto.Dashboard = models.NewDashboard("Dash")
|
||||||
|
dto.Dashboard.SetId(3)
|
||||||
|
dto.User = &models.SignedInUser{UserId: 1}
|
||||||
|
_, err := service.SaveDashboard(dto)
|
||||||
|
So(provisioningValidated, ShouldBeTrue)
|
||||||
|
So(err, ShouldEqual, models.ErrDashboardCannotSaveProvisionedDashboard)
|
||||||
|
})
|
||||||
|
|
||||||
Convey("Should return validation error if alert data is invalid", func() {
|
Convey("Should return validation error if alert data is invalid", func() {
|
||||||
|
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
|
||||||
|
cmd.Result = false
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
|
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
|
||||||
return errors.New("error")
|
return errors.New("error")
|
||||||
})
|
})
|
||||||
@@ -89,6 +126,80 @@ func TestDashboardService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("Save provisioned dashboard validation", func() {
|
||||||
|
dto := &SaveDashboardDTO{}
|
||||||
|
|
||||||
|
Convey("Should not return validation error if dashboard is provisioned", func() {
|
||||||
|
provisioningValidated := false
|
||||||
|
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
|
||||||
|
provisioningValidated = true
|
||||||
|
cmd.Result = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
|
||||||
|
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *models.SaveProvisionedDashboardCommand) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
dto.Dashboard = models.NewDashboard("Dash")
|
||||||
|
dto.Dashboard.SetId(3)
|
||||||
|
dto.User = &models.SignedInUser{UserId: 1}
|
||||||
|
_, err := service.SaveProvisionedDashboard(dto, nil)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(provisioningValidated, ShouldBeFalse)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Import dashboard validation", func() {
|
||||||
|
dto := &SaveDashboardDTO{}
|
||||||
|
|
||||||
|
Convey("Should return validation error if dashboard is provisioned", func() {
|
||||||
|
provisioningValidated := false
|
||||||
|
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
|
||||||
|
provisioningValidated = true
|
||||||
|
cmd.Result = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
|
||||||
|
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *models.SaveProvisionedDashboardCommand) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
dto.Dashboard = models.NewDashboard("Dash")
|
||||||
|
dto.Dashboard.SetId(3)
|
||||||
|
dto.User = &models.SignedInUser{UserId: 1}
|
||||||
|
_, err := service.ImportDashboard(dto)
|
||||||
|
So(provisioningValidated, ShouldBeTrue)
|
||||||
|
So(err, ShouldEqual, models.ErrDashboardCannotSaveProvisionedDashboard)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Reset(func() {
|
Reset(func() {
|
||||||
guardian.New = origNewDashboardGuardian
|
guardian.New = origNewDashboardGuardian
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ func (dr *dashboardServiceImpl) CreateFolder(cmd *models.CreateFolderCommand) er
|
|||||||
User: dr.user,
|
User: dr.user,
|
||||||
}
|
}
|
||||||
|
|
||||||
saveDashboardCmd, err := dr.buildSaveDashboardCommand(dto, false)
|
saveDashboardCmd, err := dr.buildSaveDashboardCommand(dto, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toFolderError(err)
|
return toFolderError(err)
|
||||||
}
|
}
|
||||||
@@ -141,7 +141,7 @@ func (dr *dashboardServiceImpl) UpdateFolder(existingUid string, cmd *models.Upd
|
|||||||
Overwrite: cmd.Overwrite,
|
Overwrite: cmd.Overwrite,
|
||||||
}
|
}
|
||||||
|
|
||||||
saveDashboardCmd, err := dr.buildSaveDashboardCommand(dto, false)
|
saveDashboardCmd, err := dr.buildSaveDashboardCommand(dto, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toFolderError(err)
|
return toFolderError(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,11 +110,19 @@ func TestFolderService(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
provisioningValidated := false
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(query *models.IsDashboardProvisionedQuery) error {
|
||||||
|
provisioningValidated = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
Convey("When creating folder should not return access denied error", func() {
|
Convey("When creating folder should not return access denied error", func() {
|
||||||
err := service.CreateFolder(&models.CreateFolderCommand{
|
err := service.CreateFolder(&models.CreateFolderCommand{
|
||||||
Title: "Folder",
|
Title: "Folder",
|
||||||
})
|
})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
So(provisioningValidated, ShouldBeFalse)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("When updating folder should not return access denied error", func() {
|
Convey("When updating folder should not return access denied error", func() {
|
||||||
@@ -123,6 +131,7 @@ func TestFolderService(t *testing.T) {
|
|||||||
Title: "Folder",
|
Title: "Folder",
|
||||||
})
|
})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
So(provisioningValidated, ShouldBeFalse)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("When deleting folder by uid should not return access denied error", func() {
|
Convey("When deleting folder by uid should not return access denied error", func() {
|
||||||
|
|||||||
@@ -55,9 +55,6 @@ func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *Das
|
|||||||
dash.OrgId = cfg.OrgId
|
dash.OrgId = cfg.OrgId
|
||||||
dash.Dashboard.OrgId = cfg.OrgId
|
dash.Dashboard.OrgId = cfg.OrgId
|
||||||
dash.Dashboard.FolderId = folderId
|
dash.Dashboard.FolderId = folderId
|
||||||
if !cfg.Editable {
|
|
||||||
dash.Dashboard.Data.Set("editable", cfg.Editable)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dash.Dashboard.Title == "" {
|
if dash.Dashboard.Title == "" {
|
||||||
return nil, models.ErrDashboardTitleEmpty
|
return nil, models.ErrDashboardTitleEmpty
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
bus.AddHandler("sql", GetProvisionedDashboardDataQuery)
|
bus.AddHandler("sql", GetProvisionedDashboardDataQuery)
|
||||||
bus.AddHandler("sql", SaveProvisionedDashboard)
|
bus.AddHandler("sql", SaveProvisionedDashboard)
|
||||||
|
bus.AddHandler("sql", GetProvisionedDataByDashboardId)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DashboardExtras struct {
|
type DashboardExtras struct {
|
||||||
@@ -17,6 +18,19 @@ type DashboardExtras struct {
|
|||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetProvisionedDataByDashboardId(cmd *models.IsDashboardProvisionedQuery) error {
|
||||||
|
result := &models.DashboardProvisioning{}
|
||||||
|
|
||||||
|
exist, err := x.Where("dashboard_id = ?", cmd.DashboardId).Get(result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Result = exist
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func SaveProvisionedDashboard(cmd *models.SaveProvisionedDashboardCommand) error {
|
func SaveProvisionedDashboard(cmd *models.SaveProvisionedDashboardCommand) error {
|
||||||
return inTransaction(func(sess *DBSession) error {
|
return inTransaction(func(sess *DBSession) error {
|
||||||
err := saveDashboard(sess, cmd.DashboardCmd)
|
err := saveDashboard(sess, cmd.DashboardCmd)
|
||||||
|
|||||||
@@ -50,6 +50,23 @@ func TestDashboardProvisioningTest(t *testing.T) {
|
|||||||
So(query.Result[0].DashboardId, ShouldEqual, dashId)
|
So(query.Result[0].DashboardId, ShouldEqual, dashId)
|
||||||
So(query.Result[0].Updated, ShouldEqual, now.Unix())
|
So(query.Result[0].Updated, ShouldEqual, now.Unix())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("Can query for one provisioned dashboard", func() {
|
||||||
|
query := &models.IsDashboardProvisionedQuery{DashboardId: cmd.Result.Id}
|
||||||
|
|
||||||
|
err := GetProvisionedDataByDashboardId(query)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(query.Result, ShouldBeTrue)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can query for none provisioned dashboard", func() {
|
||||||
|
query := &models.IsDashboardProvisionedQuery{DashboardId: 3000}
|
||||||
|
|
||||||
|
err := GetProvisionedDataByDashboardId(query)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(query.Result, ShouldBeFalse)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ func TestIntegratedDashboardService(t *testing.T) {
|
|||||||
var testOrgId int64 = 1
|
var testOrgId int64 = 1
|
||||||
|
|
||||||
Convey("Given saved folders and dashboards in organization A", func() {
|
Convey("Given saved folders and dashboards in organization A", func() {
|
||||||
|
|
||||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
|
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -28,6 +27,11 @@ func TestIntegratedDashboardService(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
|
||||||
|
cmd.Result = false
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
savedFolder := saveTestFolder("Saved folder", testOrgId)
|
savedFolder := saveTestFolder("Saved folder", testOrgId)
|
||||||
savedDashInFolder := saveTestDashboard("Saved dash in folder", testOrgId, savedFolder.Id)
|
savedDashInFolder := saveTestDashboard("Saved dash in folder", testOrgId, savedFolder.Id)
|
||||||
saveTestDashboard("Other saved dash in folder", testOrgId, savedFolder.Id)
|
saveTestDashboard("Other saved dash in folder", testOrgId, savedFolder.Id)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import './dashnav/dashnav';
|
|||||||
import './submenu/submenu';
|
import './submenu/submenu';
|
||||||
import './save_as_modal';
|
import './save_as_modal';
|
||||||
import './save_modal';
|
import './save_modal';
|
||||||
|
import './save_provisioned_modal';
|
||||||
import './shareModalCtrl';
|
import './shareModalCtrl';
|
||||||
import './share_snapshot_ctrl';
|
import './share_snapshot_ctrl';
|
||||||
import './dashboard_srv';
|
import './dashboard_srv';
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ export class DashboardSrv {
|
|||||||
this.setCurrent(this.create(clone, this.dash.meta));
|
this.setCurrent(this.create(clone, this.dash.meta));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.dash.meta.provisioned) {
|
||||||
|
return this.showDashboardProvisionedModal();
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.dash.meta.canSave && options.makeEditable !== true) {
|
if (!this.dash.meta.canSave && options.makeEditable !== true) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
@@ -120,6 +124,12 @@ export class DashboardSrv {
|
|||||||
return this.save(this.dash.getSaveModelClone(), options);
|
return this.save(this.dash.getSaveModelClone(), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showDashboardProvisionedModal() {
|
||||||
|
this.$rootScope.appEvent('show-modal', {
|
||||||
|
templateHtml: '<save-provisioned-dashboard-modal dismiss="dismiss()"></save-provisioned-dashboard-modal>',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
showSaveAsModal() {
|
showSaveAsModal() {
|
||||||
this.$rootScope.appEvent('show-modal', {
|
this.$rootScope.appEvent('show-modal', {
|
||||||
templateHtml: '<save-dashboard-as-modal dismiss="dismiss()"></save-dashboard-as-modal>',
|
templateHtml: '<save-dashboard-as-modal dismiss="dismiss()"></save-dashboard-as-modal>',
|
||||||
|
|||||||
77
public/app/features/dashboard/save_provisioned_modal.ts
Normal file
77
public/app/features/dashboard/save_provisioned_modal.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import angular from 'angular';
|
||||||
|
import { saveAs } from 'file-saver';
|
||||||
|
import coreModule from 'app/core/core_module';
|
||||||
|
|
||||||
|
const template = `
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-header-title">
|
||||||
|
<i class="fa fa-save"></i><span class="p-l-1">Cannot save provisioned dashboard</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<a class="modal-header-close" ng-click="ctrl.dismiss();">
|
||||||
|
<i class="fa fa-remove"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-content">
|
||||||
|
<small>
|
||||||
|
This dashboard cannot be saved from Grafana's UI since it has been provisioned from another source.
|
||||||
|
Copy the JSON or save it to a file below. Then you can update your dashboard in corresponding provisioning source.<br/>
|
||||||
|
<i>See <a class="external-link" href="http://docs.grafana.org/administration/provisioning/#dashboards" target="_blank">
|
||||||
|
documentation</a> for more information about provisioning.</i>
|
||||||
|
</small>
|
||||||
|
<div class="p-t-2">
|
||||||
|
<div class="gf-form">
|
||||||
|
<code-editor content="ctrl.dashboardJson" data-mode="json" data-max-lines=15></code-editor>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form-button-row">
|
||||||
|
<button class="btn btn-success" clipboard-button="ctrl.getJsonForClipboard()">
|
||||||
|
<i class="fa fa-clipboard"></i> Copy JSON to Clipboard
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" clipboard-button="ctrl.save()">
|
||||||
|
<i class="fa fa-save"></i> Save JSON to file
|
||||||
|
</button>
|
||||||
|
<a class="btn btn-link" ng-click="ctrl.dismiss();">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export class SaveProvisionedDashboardModalCtrl {
|
||||||
|
dash: any;
|
||||||
|
dashboardJson: string;
|
||||||
|
dismiss: () => void;
|
||||||
|
|
||||||
|
/** @ngInject */
|
||||||
|
constructor(dashboardSrv) {
|
||||||
|
this.dash = dashboardSrv.getCurrent().getSaveModelClone();
|
||||||
|
delete this.dash.id;
|
||||||
|
this.dashboardJson = JSON.stringify(this.dash, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
var blob = new Blob([angular.toJson(this.dash, true)], {
|
||||||
|
type: 'application/json;charset=utf-8',
|
||||||
|
});
|
||||||
|
saveAs(blob, this.dash.title + '-' + new Date().getTime() + '.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
getJsonForClipboard() {
|
||||||
|
return this.dashboardJson;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveProvisionedDashboardModalDirective() {
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
template: template,
|
||||||
|
controller: SaveProvisionedDashboardModalCtrl,
|
||||||
|
bindToController: true,
|
||||||
|
controllerAs: 'ctrl',
|
||||||
|
scope: { dismiss: '&' },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
coreModule.directive('saveProvisionedDashboardModal', saveProvisionedDashboardModalDirective);
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { SaveProvisionedDashboardModalCtrl } from '../save_provisioned_modal';
|
||||||
|
|
||||||
|
describe('SaveProvisionedDashboardModalCtrl', () => {
|
||||||
|
var json = {
|
||||||
|
title: 'name',
|
||||||
|
id: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
var mockDashboardSrv = {
|
||||||
|
getCurrent: function() {
|
||||||
|
return {
|
||||||
|
id: 5,
|
||||||
|
meta: {},
|
||||||
|
getSaveModelClone: function() {
|
||||||
|
return json;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var ctrl = new SaveProvisionedDashboardModalCtrl(mockDashboardSrv);
|
||||||
|
|
||||||
|
it('should remove id from dashboard model', () => {
|
||||||
|
expect(ctrl.dash.id).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove id from dashboard model in clipboard json', () => {
|
||||||
|
expect(ctrl.getJsonForClipboard()).toBe(JSON.stringify({ title: 'name' }, null, 2));
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user