diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index 9169581424e..a6b5725f68b 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -847,12 +847,22 @@ func (d *dashboardStore) deleteAlertDefinition(dashboardId int64, sess *db.Sessi func (d *dashboardStore) GetDashboard(ctx context.Context, query *dashboards.GetDashboardQuery) (*dashboards.Dashboard, error) { var queryResult *dashboards.Dashboard err := d.store.WithDbSession(ctx, func(sess *db.Session) error { - if query.ID == 0 && len(query.Slug) == 0 && len(query.UID) == 0 { + if query.ID == 0 && len(query.UID) == 0 && (query.Title == nil || query.FolderID == nil) { return dashboards.ErrDashboardIdentifierNotSet } - dashboard := dashboards.Dashboard{Slug: query.Slug, OrgID: query.OrgID, ID: query.ID, UID: query.UID} - has, err := sess.Get(&dashboard) + dashboard := dashboards.Dashboard{OrgID: query.OrgID, ID: query.ID, UID: query.UID} + mustCols := []string{} + if query.Title != nil { + dashboard.Title = *query.Title + mustCols = append(mustCols, "title") + } + if query.FolderID != nil { + dashboard.FolderID = *query.FolderID + mustCols = append(mustCols, "folder_id") + } + + has, err := sess.MustCols(mustCols...).Get(&dashboard) if err != nil { return err diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index 11265a95267..e5a4ba40581 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -8,11 +8,11 @@ import ( "testing" "time" - "github.com/grafana/grafana/pkg/expr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/org" @@ -23,6 +23,7 @@ import ( "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) func TestIntegrationDashboardDataAccess(t *testing.T) { @@ -81,11 +82,12 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { require.False(t, queryResult.IsFolder) }) - t.Run("Should be able to get dashboard by slug", func(t *testing.T) { + t.Run("Should be able to get dashboard by title and folderID", func(t *testing.T) { setup() query := dashboards.GetDashboardQuery{ - Slug: "test-dash-23", - OrgID: 1, + Title: util.Pointer("test dash 23"), + FolderID: &savedFolder.ID, + OrgID: 1, } queryResult, err := dashboardStore.GetDashboard(context.Background(), &query) @@ -98,6 +100,29 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { require.False(t, queryResult.IsFolder) }) + t.Run("Should not be able to get dashboard by title alone", func(t *testing.T) { + setup() + query := dashboards.GetDashboardQuery{ + Title: util.Pointer("test dash 23"), + OrgID: 1, + } + + _, err := dashboardStore.GetDashboard(context.Background(), &query) + require.ErrorIs(t, err, dashboards.ErrDashboardIdentifierNotSet) + }) + + t.Run("Folder=0 should not be able to get a dashboard in a folder", func(t *testing.T) { + setup() + query := dashboards.GetDashboardQuery{ + Title: util.Pointer("test dash 23"), + FolderID: util.Pointer(int64(0)), + OrgID: 1, + } + + _, err := dashboardStore.GetDashboard(context.Background(), &query) + require.ErrorIs(t, err, dashboards.ErrDashboardNotFound) + }) + t.Run("Should be able to get dashboard by uid", func(t *testing.T) { setup() query := dashboards.GetDashboardQuery{ diff --git a/pkg/services/dashboards/models.go b/pkg/services/dashboards/models.go index bc57eb36ae4..5495c33e5e5 100644 --- a/pkg/services/dashboards/models.go +++ b/pkg/services/dashboards/models.go @@ -233,11 +233,25 @@ type DeleteOrphanedProvisionedDashboardsCommand struct { // QUERIES // +// GetDashboardQuery is used to query for a single dashboard matching +// a unique constraint within the provided OrgID. +// +// Available constraints: +// - ID uses Grafana's internal numeric database identifier to get a +// dashboard. +// - UID use the unique identifier to get a dashboard. +// - Title + FolderID uses the combination of the dashboard's +// human-readable title and its parent folder's ID +// (or zero, for top level items). Both are required if no other +// constraint is set. +// +// Multiple constraints can be combined. type GetDashboardQuery struct { - Slug string // required if no ID or Uid is specified - ID int64 // optional if slug is set - UID string // optional if slug is set - OrgID int64 + ID int64 + UID string + Title *string + FolderID *int64 + OrgID int64 } type DashboardTagCloudItem struct { diff --git a/pkg/services/provisioning/alerting/rules_provisioner.go b/pkg/services/provisioning/alerting/rules_provisioner.go index 36fdbef76ea..7abc00f37e2 100644 --- a/pkg/services/provisioning/alerting/rules_provisioner.go +++ b/pkg/services/provisioning/alerting/rules_provisioner.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/infra/slugify" "github.com/grafana/grafana/pkg/services/dashboards" alert_models "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/provisioning" @@ -97,8 +96,9 @@ func (prov *defaultAlertRuleProvisioner) provisionRule( func (prov *defaultAlertRuleProvisioner) getOrCreateFolderUID( ctx context.Context, folderName string, orgID int64) (string, error) { cmd := &dashboards.GetDashboardQuery{ - Slug: slugify.Slugify(folderName), - OrgID: orgID, + Title: &folderName, + FolderID: util.Pointer(int64(0)), + OrgID: orgID, } cmdResult, err := prov.dashboardService.GetDashboard(ctx, cmd) if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) { diff --git a/pkg/services/provisioning/dashboards/file_reader.go b/pkg/services/provisioning/dashboards/file_reader.go index b2900b696d3..26d00d18e51 100644 --- a/pkg/services/provisioning/dashboards/file_reader.go +++ b/pkg/services/provisioning/dashboards/file_reader.go @@ -13,7 +13,6 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/infra/slugify" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/provisioning/utils" @@ -299,7 +298,11 @@ func (fr *FileReader) getOrCreateFolderID(ctx context.Context, cfg *config, serv return 0, ErrFolderNameMissing } - cmd := &dashboards.GetDashboardQuery{Slug: slugify.Slugify(folderName), OrgID: cfg.OrgID} + cmd := &dashboards.GetDashboardQuery{ + Title: &folderName, + FolderID: util.Pointer(int64(0)), + OrgID: cfg.OrgID, + } result, err := fr.dashboardStore.GetDashboard(ctx, cmd) if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) {