package database import ( "context" "encoding/json" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/publicdashboards" . "github.com/grafana/grafana/pkg/services/publicdashboards/models" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/setting" ) // Define the storage implementation. We're generating the mock implementation // automatically type PublicDashboardStoreImpl struct { sqlStore db.DB log log.Logger cfg *setting.Cfg features featuremgmt.FeatureToggles } var LogPrefix = "publicdashboards.store" // Gives us a compile time error if our database does not adhere to contract of // the interface var _ publicdashboards.Store = (*PublicDashboardStoreImpl)(nil) // Factory used by wire to dependency injection func ProvideStore(sqlStore db.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles) *PublicDashboardStoreImpl { return &PublicDashboardStoreImpl{ sqlStore: sqlStore, log: log.New(LogPrefix), cfg: cfg, features: features, } } // FindAllWithPagination Returns a list of public dashboards by orgId, based on permissions and with pagination func (d *PublicDashboardStoreImpl) FindAllWithPagination(ctx context.Context, query *PublicDashboardListQuery) (*PublicDashboardListResponseWithPagination, error) { resp := &PublicDashboardListResponseWithPagination{ PublicDashboards: make([]*PublicDashboardListResponse, 0), TotalCount: 0, } recursiveQueriesAreSupported, err := d.sqlStore.RecursiveQueriesAreSupported() if err != nil { return nil, err } pubdashBuilder := db.NewSqlBuilder(d.cfg, d.features, d.sqlStore.GetDialect(), recursiveQueriesAreSupported) pubdashBuilder.Write("SELECT dashboard_public.uid, dashboard_public.access_token, dashboard.uid as dashboard_uid, dashboard_public.is_enabled, dashboard.title") pubdashBuilder.Write(" FROM dashboard_public") pubdashBuilder.Write(" JOIN dashboard ON dashboard.uid = dashboard_public.dashboard_uid AND dashboard.org_id = dashboard_public.org_id") pubdashBuilder.Write(` WHERE dashboard_public.org_id = ?`, query.OrgID) if query.User.OrgRole != org.RoleAdmin { pubdashBuilder.WriteDashboardPermissionFilter(query.User, dashboards.PERMISSION_VIEW, searchstore.TypeDashboard) } pubdashBuilder.Write(" ORDER BY dashboard.title") pubdashBuilder.Write(d.sqlStore.GetDialect().LimitOffset(int64(query.Limit), int64(query.Offset))) counterBuilder := db.NewSqlBuilder(d.cfg, d.features, d.sqlStore.GetDialect(), recursiveQueriesAreSupported) counterBuilder.Write("SELECT COUNT(*)") counterBuilder.Write(" FROM dashboard_public") counterBuilder.Write(" JOIN dashboard ON dashboard.uid = dashboard_public.dashboard_uid AND dashboard.org_id = dashboard_public.org_id") counterBuilder.Write(` WHERE dashboard_public.org_id = ?`, query.OrgID) if query.User.OrgRole != org.RoleAdmin { counterBuilder.WriteDashboardPermissionFilter(query.User, dashboards.PERMISSION_VIEW, searchstore.TypeDashboard) } err = d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { err := sess.SQL(pubdashBuilder.GetSQLString(), pubdashBuilder.GetParams()...).Find(&resp.PublicDashboards) if err != nil { return err } _, err = sess.SQL(counterBuilder.GetSQLString(), counterBuilder.GetParams()...).Get(&resp.TotalCount) return err }) if err != nil { return nil, err } return resp, nil } // FindDashboard returns a dashboard by orgId and dashboardUid func (d *PublicDashboardStoreImpl) FindDashboard(ctx context.Context, orgId int64, dashboardUid string) (*dashboards.Dashboard, error) { dashboard := &dashboards.Dashboard{OrgID: orgId, UID: dashboardUid} var found bool err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { var err error found, err = sess.Get(dashboard) return err }) if err != nil { return nil, err } if !found { return nil, nil } return dashboard, nil } // Find Returns public dashboard by Uid or nil if not found func (d *PublicDashboardStoreImpl) Find(ctx context.Context, uid string) (*PublicDashboard, error) { if uid == "" { return nil, nil } var found bool publicDashboard := &PublicDashboard{Uid: uid} err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { var err error found, err = sess.Get(publicDashboard) return err }) if err != nil { return nil, err } if !found { return nil, nil } return publicDashboard, nil } // FindByAccessToken Returns public dashboard by access token or nil if not found func (d *PublicDashboardStoreImpl) FindByAccessToken(ctx context.Context, accessToken string) (*PublicDashboard, error) { if accessToken == "" { return nil, nil } var found bool publicDashboard := &PublicDashboard{AccessToken: accessToken} err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { var err error found, err = sess.Get(publicDashboard) return err }) if err != nil { return nil, err } if !found { return nil, nil } return publicDashboard, nil } // FindByDashboardUid Retrieves public dashboard by dashboard uid or nil if not found func (d *PublicDashboardStoreImpl) FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error) { if dashboardUid == "" || orgId == 0 { return nil, nil } var found bool publicDashboard := &PublicDashboard{OrgId: orgId, DashboardUid: dashboardUid} err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { var err error found, err = sess.Get(publicDashboard) if err != nil { return err } return nil }) if err != nil { return nil, err } if !found { return nil, nil } return publicDashboard, nil } // ExistsEnabledByDashboardUid Responds true if there is an enabled public dashboard for a dashboard uid func (d *PublicDashboardStoreImpl) ExistsEnabledByDashboardUid(ctx context.Context, dashboardUid string) (bool, error) { hasPublicDashboard := false err := d.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error { sql := "SELECT COUNT(*) FROM dashboard_public WHERE dashboard_uid=? AND is_enabled=true" result, err := dbSession.SQL(sql, dashboardUid).Count() if err != nil { return err } hasPublicDashboard = result > 0 return err }) return hasPublicDashboard, err } // ExistsEnabledByAccessToken Responds true if the accessToken exists and the public dashboard is enabled func (d *PublicDashboardStoreImpl) ExistsEnabledByAccessToken(ctx context.Context, accessToken string) (bool, error) { hasPublicDashboard := false err := d.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error { sql := "SELECT COUNT(*) FROM dashboard_public WHERE access_token=? AND is_enabled=true" result, err := dbSession.SQL(sql, accessToken).Count() if err != nil { return err } hasPublicDashboard = result > 0 return err }) return hasPublicDashboard, err } // GetOrgIdByAccessToken Returns the public dashboard OrgId if exists. func (d *PublicDashboardStoreImpl) GetOrgIdByAccessToken(ctx context.Context, accessToken string) (int64, error) { var orgId int64 err := d.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error { sql := "SELECT org_id FROM dashboard_public WHERE access_token=?" _, err := dbSession.SQL(sql, accessToken).Get(&orgId) if err != nil { return err } return err }) return orgId, err } // Creates a public dashboard func (d *PublicDashboardStoreImpl) Create(ctx context.Context, cmd SavePublicDashboardCommand) (int64, error) { if cmd.PublicDashboard.DashboardUid == "" { return 0, dashboards.ErrDashboardIdentifierNotSet } var affectedRows int64 err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { var err error affectedRows, err = sess.UseBool("is_enabled").Insert(&cmd.PublicDashboard) return err }) return affectedRows, err } // Updates existing public dashboard func (d *PublicDashboardStoreImpl) Update(ctx context.Context, cmd SavePublicDashboardCommand) (int64, error) { var affectedRows int64 err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { timeSettingsJSON, err := json.Marshal(cmd.PublicDashboard.TimeSettings) if err != nil { return err } sqlResult, err := sess.Exec("UPDATE dashboard_public SET is_enabled = ?, annotations_enabled = ?, time_selection_enabled = ?, share = ?, time_settings = ?, updated_by = ?, updated_at = ? WHERE uid = ?", cmd.PublicDashboard.IsEnabled, cmd.PublicDashboard.AnnotationsEnabled, cmd.PublicDashboard.TimeSelectionEnabled, cmd.PublicDashboard.Share, string(timeSettingsJSON), cmd.PublicDashboard.UpdatedBy, cmd.PublicDashboard.UpdatedAt.UTC().Format("2006-01-02 15:04:05"), cmd.PublicDashboard.Uid) if err != nil { return err } affectedRows, err = sqlResult.RowsAffected() return err }) return affectedRows, err } // Deletes a public dashboard func (d *PublicDashboardStoreImpl) Delete(ctx context.Context, uid string) (int64, error) { dashboard := &PublicDashboard{Uid: uid} var affectedRows int64 err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { var err error affectedRows, err = sess.Delete(dashboard) return err }) return affectedRows, err } func (d *PublicDashboardStoreImpl) FindByDashboardFolder(ctx context.Context, dashboard *dashboards.Dashboard) ([]*PublicDashboard, error) { if dashboard == nil || !dashboard.IsFolder { return nil, nil } var pubdashes []*PublicDashboard err := d.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { return sess.SQL("SELECT * from dashboard_public WHERE (dashboard_uid, org_id) IN (SELECT uid, org_id FROM dashboard WHERE folder_id = ?)", dashboard.ID).Find(&pubdashes) }) if err != nil { return nil, err } return pubdashes, nil } func (d *PublicDashboardStoreImpl) GetMetrics(ctx context.Context) (*Metrics, error) { metrics := &Metrics{ TotalPublicDashboards: []*TotalPublicDashboard{}, } err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { return sess.SQL("SELECT COUNT(*) as total_count, is_enabled, share as share_type FROM dashboard_public GROUP BY is_enabled, share").Find(&metrics.TotalPublicDashboards) }) if err != nil { return nil, err } return metrics, nil }