2022-02-16 07:15:44 -06:00
package database
import (
"context"
2022-02-23 04:12:37 -06:00
"errors"
2022-02-16 07:15:44 -06:00
"fmt"
2024-01-30 10:26:34 -06:00
"strings"
2022-02-16 07:15:44 -06:00
"time"
2022-05-19 09:59:12 -05:00
"xorm.io/xorm"
2022-10-19 08:02:15 -05:00
"github.com/grafana/grafana/pkg/infra/db"
2022-02-16 07:15:44 -06:00
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
2022-03-22 08:36:50 -05:00
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
2022-06-08 05:22:55 -05:00
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
2022-08-01 10:56:36 -05:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2022-11-14 13:08:10 -06:00
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/sqlstore"
2022-02-16 07:15:44 -06:00
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
2022-05-23 10:14:27 -05:00
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
2023-01-10 08:56:33 -06:00
"github.com/grafana/grafana/pkg/services/star"
2022-08-01 10:56:36 -05:00
"github.com/grafana/grafana/pkg/services/store"
2022-09-21 07:04:01 -05:00
"github.com/grafana/grafana/pkg/services/tag"
2022-10-14 14:33:06 -05:00
"github.com/grafana/grafana/pkg/setting"
2022-02-16 07:15:44 -06:00
"github.com/grafana/grafana/pkg/util"
)
2023-03-01 09:52:16 -06:00
type dashboardStore struct {
2022-10-19 08:02:15 -05:00
store db . DB
2022-10-14 14:33:06 -05:00
cfg * setting . Cfg
2022-09-21 07:04:01 -05:00
log log . Logger
features featuremgmt . FeatureToggles
tagService tag . Service
2022-02-16 07:15:44 -06:00
}
2023-03-01 09:52:16 -06:00
// SQL bean helper to save tags
type dashboardTag struct {
2022-10-19 08:02:15 -05:00
Id int64
DashboardId int64
Term string
}
2022-05-17 13:52:22 -05:00
// DashboardStore implements the Store interface
2023-03-01 09:52:16 -06:00
var _ dashboards . Store = ( * dashboardStore ) ( nil )
2022-05-17 13:52:22 -05:00
2023-03-01 09:52:16 -06:00
func ProvideDashboardStore ( sqlStore db . DB , cfg * setting . Cfg , features featuremgmt . FeatureToggles , tagService tag . Service , quotaService quota . Service ) ( dashboards . Store , error ) {
s := & dashboardStore { store : sqlStore , cfg : cfg , log : log . New ( "dashboard-store" ) , features : features , tagService : tagService }
2022-11-14 13:08:10 -06:00
defaultLimits , err := readQuotaConfig ( cfg )
if err != nil {
return nil , err
}
if err := quotaService . RegisterQuotaReporter ( & quota . NewUsageReporter {
TargetSrv : dashboards . QuotaTargetSrv ,
DefaultLimits : defaultLimits ,
Reporter : s . Count ,
} ) ; err != nil {
return nil , err
}
return s , nil
2022-08-01 10:56:36 -05:00
}
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) emitEntityEvent ( ) bool {
2023-11-14 14:50:27 -06:00
return d . features != nil && d . features . IsEnabledGlobally ( featuremgmt . FlagPanelTitleSearch )
2022-02-16 07:15:44 -06:00
}
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) ValidateDashboardBeforeSave ( ctx context . Context , dashboard * dashboards . Dashboard , overwrite bool ) ( bool , error ) {
2022-02-16 07:15:44 -06:00
isParentFolderChanged := false
2022-10-19 08:02:15 -05:00
err := d . store . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2022-02-16 07:15:44 -06:00
var err error
2023-01-16 09:33:55 -06:00
isParentFolderChanged , err = getExistingDashboardByIDOrUIDForUpdate ( sess , dashboard , d . store . GetDialect ( ) , overwrite )
2022-02-16 07:15:44 -06:00
if err != nil {
return err
}
2022-10-19 08:02:15 -05:00
isParentFolderChanged , err = getExistingDashboardByTitleAndFolder ( sess , dashboard , d . store . GetDialect ( ) , overwrite ,
2022-02-16 07:15:44 -06:00
isParentFolderChanged )
if err != nil {
return err
}
return nil
} )
if err != nil {
return false , err
}
return isParentFolderChanged , nil
}
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) GetProvisionedDataByDashboardID ( ctx context . Context , dashboardID int64 ) ( * dashboards . DashboardProvisioning , error ) {
2023-01-18 06:52:41 -06:00
var data dashboards . DashboardProvisioning
2022-10-19 08:02:15 -05:00
err := d . store . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2022-02-16 07:15:44 -06:00
_ , err := sess . Where ( "dashboard_id = ?" , dashboardID ) . Get ( & data )
return err
} )
2023-01-18 06:52:41 -06:00
if data . DashboardID == 0 {
2022-02-16 07:15:44 -06:00
return nil , nil
}
return & data , err
}
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) GetProvisionedDataByDashboardUID ( ctx context . Context , orgID int64 , dashboardUID string ) ( * dashboards . DashboardProvisioning , error ) {
2023-01-18 06:52:41 -06:00
var provisionedDashboard dashboards . DashboardProvisioning
2022-10-19 08:02:15 -05:00
err := d . store . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2023-01-18 06:52:41 -06:00
var dashboard dashboards . Dashboard
2022-02-16 07:15:44 -06:00
exists , err := sess . Where ( "org_id = ? AND uid = ?" , orgID , dashboardUID ) . Get ( & dashboard )
if err != nil {
return err
}
if ! exists {
2022-06-30 08:31:54 -05:00
return dashboards . ErrDashboardNotFound
2022-02-16 07:15:44 -06:00
}
2023-01-18 06:52:41 -06:00
exists , err = sess . Where ( "dashboard_id = ?" , dashboard . ID ) . Get ( & provisionedDashboard )
2022-02-16 07:15:44 -06:00
if err != nil {
return err
}
if ! exists {
2022-06-30 08:31:54 -05:00
return dashboards . ErrProvisionedDashboardNotFound
2022-02-16 07:15:44 -06:00
}
return nil
} )
return & provisionedDashboard , err
}
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) GetProvisionedDashboardData ( ctx context . Context , name string ) ( [ ] * dashboards . DashboardProvisioning , error ) {
2023-01-18 06:52:41 -06:00
var result [ ] * dashboards . DashboardProvisioning
2022-10-19 08:02:15 -05:00
err := d . store . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2022-02-16 07:15:44 -06:00
return sess . Where ( "name = ?" , name ) . Find ( & result )
} )
return result , err
}
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) SaveProvisionedDashboard ( ctx context . Context , cmd dashboards . SaveDashboardCommand , provisioning * dashboards . DashboardProvisioning ) ( * dashboards . Dashboard , error ) {
2023-01-25 03:36:26 -06:00
var result * dashboards . Dashboard
var err error
err = d . store . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
result , err = saveDashboard ( sess , & cmd , d . emitEntityEvent ( ) )
if err != nil {
2022-02-16 07:15:44 -06:00
return err
}
if provisioning . Updated == 0 {
2023-01-25 03:36:26 -06:00
provisioning . Updated = result . Updated . Unix ( )
2022-02-16 07:15:44 -06:00
}
2023-01-25 03:36:26 -06:00
return saveProvisionedData ( sess , provisioning , result )
2022-02-16 07:15:44 -06:00
} )
2023-01-25 03:36:26 -06:00
return result , err
2022-02-16 07:15:44 -06:00
}
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) SaveDashboard ( ctx context . Context , cmd dashboards . SaveDashboardCommand ) ( * dashboards . Dashboard , error ) {
2023-01-25 03:36:26 -06:00
var result * dashboards . Dashboard
var err error
err = d . store . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
result , err = saveDashboard ( sess , & cmd , d . emitEntityEvent ( ) )
if err != nil {
return err
}
return nil
2022-02-16 07:15:44 -06:00
} )
2023-01-25 03:36:26 -06:00
if err != nil {
return nil , err
}
return result , err
2022-02-16 07:15:44 -06:00
}
// UnprovisionDashboard removes row in dashboard_provisioning for the dashboard making it seem as if manually created.
// The dashboard will still have `created_by = -1` to see it was not created by any particular user.
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) UnprovisionDashboard ( ctx context . Context , id int64 ) error {
2022-10-19 08:02:15 -05:00
return d . store . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2023-01-18 06:52:41 -06:00
_ , err := sess . Where ( "dashboard_id = ?" , id ) . Delete ( & dashboards . DashboardProvisioning { } )
2022-02-16 07:15:44 -06:00
return err
} )
}
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) DeleteOrphanedProvisionedDashboards ( ctx context . Context , cmd * dashboards . DeleteOrphanedProvisionedDashboardsCommand ) error {
2022-10-19 08:02:15 -05:00
return d . store . WithDbSession ( ctx , func ( sess * db . Session ) error {
2023-01-18 06:52:41 -06:00
var result [ ] * dashboards . DashboardProvisioning
2022-02-23 04:12:37 -06:00
2023-08-30 10:46:47 -05:00
convertedReaderNames := make ( [ ] any , len ( cmd . ReaderNames ) )
2022-02-23 04:12:37 -06:00
for index , readerName := range cmd . ReaderNames {
convertedReaderNames [ index ] = readerName
}
err := sess . NotIn ( "name" , convertedReaderNames ... ) . Find ( & result )
if err != nil {
return err
}
for _ , deleteDashCommand := range result {
2023-01-18 06:52:41 -06:00
err := d . DeleteDashboard ( ctx , & dashboards . DeleteDashboardCommand { ID : deleteDashCommand . DashboardID } )
2022-06-30 08:31:54 -05:00
if err != nil && ! errors . Is ( err , dashboards . ErrDashboardNotFound ) {
2022-02-23 04:12:37 -06:00
return err
}
}
return nil
} )
}
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) Count ( ctx context . Context , scopeParams * quota . ScopeParameters ) ( * quota . Map , error ) {
2022-11-14 13:08:10 -06:00
u := & quota . Map { }
type result struct {
Count int64
}
r := result { }
if err := d . store . WithDbSession ( ctx , func ( sess * sqlstore . DBSession ) error {
rawSQL := fmt . Sprintf ( "SELECT COUNT(*) AS count FROM dashboard WHERE is_folder=%s" , d . store . GetDialect ( ) . BooleanStr ( false ) )
if _ , err := sess . SQL ( rawSQL ) . Get ( & r ) ; err != nil {
return err
}
return nil
} ) ; err != nil {
return u , err
} else {
tag , err := quota . NewTag ( dashboards . QuotaTargetSrv , dashboards . QuotaTarget , quota . GlobalScope )
if err != nil {
return nil , err
}
u . Set ( tag , r . Count )
}
2023-01-16 04:54:15 -06:00
if scopeParams != nil && scopeParams . OrgID != 0 {
2022-11-14 13:08:10 -06:00
if err := d . store . WithDbSession ( ctx , func ( sess * sqlstore . DBSession ) error {
rawSQL := fmt . Sprintf ( "SELECT COUNT(*) AS count FROM dashboard WHERE org_id=? AND is_folder=%s" , d . store . GetDialect ( ) . BooleanStr ( false ) )
if _ , err := sess . SQL ( rawSQL , scopeParams . OrgID ) . Get ( & r ) ; err != nil {
return err
}
return nil
} ) ; err != nil {
return u , err
} else {
tag , err := quota . NewTag ( dashboards . QuotaTargetSrv , dashboards . QuotaTarget , quota . OrgScope )
if err != nil {
return nil , err
}
u . Set ( tag , r . Count )
}
}
return u , nil
}
2023-01-16 09:33:55 -06:00
func getExistingDashboardByIDOrUIDForUpdate ( sess * db . Session , dash * dashboards . Dashboard , dialect migrator . Dialect , overwrite bool ) ( bool , error ) {
2022-02-16 07:15:44 -06:00
dashWithIdExists := false
isParentFolderChanged := false
2023-01-18 06:52:41 -06:00
var existingById dashboards . Dashboard
2022-02-16 07:15:44 -06:00
2023-01-16 09:33:55 -06:00
if dash . ID > 0 {
2022-02-16 07:15:44 -06:00
var err error
2023-01-16 09:33:55 -06:00
dashWithIdExists , err = sess . Where ( "id=? AND org_id=?" , dash . ID , dash . OrgID ) . Get ( & existingById )
2022-02-16 07:15:44 -06:00
if err != nil {
return false , fmt . Errorf ( "SQL query for existing dashboard by ID failed: %w" , err )
}
if ! dashWithIdExists {
2022-06-30 08:31:54 -05:00
return false , dashboards . ErrDashboardNotFound
2022-02-16 07:15:44 -06:00
}
2023-01-16 09:33:55 -06:00
if dash . UID == "" {
2023-01-18 06:52:41 -06:00
dash . SetUID ( existingById . UID )
2022-02-16 07:15:44 -06:00
}
}
dashWithUidExists := false
2023-01-18 06:52:41 -06:00
var existingByUid dashboards . Dashboard
2022-02-16 07:15:44 -06:00
2023-01-16 09:33:55 -06:00
if dash . UID != "" {
2022-02-16 07:15:44 -06:00
var err error
2023-01-16 09:33:55 -06:00
dashWithUidExists , err = sess . Where ( "org_id=? AND uid=?" , dash . OrgID , dash . UID ) . Get ( & existingByUid )
2022-02-16 07:15:44 -06:00
if err != nil {
return false , fmt . Errorf ( "SQL query for existing dashboard by UID failed: %w" , err )
}
}
if ! dashWithIdExists && ! dashWithUidExists {
return false , nil
}
2023-01-18 06:52:41 -06:00
if dashWithIdExists && dashWithUidExists && existingById . ID != existingByUid . ID {
2022-06-30 08:31:54 -05:00
return false , dashboards . ErrDashboardWithSameUIDExists
2022-02-16 07:15:44 -06:00
}
existing := existingById
if ! dashWithIdExists && dashWithUidExists {
2023-01-18 06:52:41 -06:00
dash . SetID ( existingByUid . ID )
dash . SetUID ( existingByUid . UID )
2022-02-16 07:15:44 -06:00
existing = existingByUid
}
if ( existing . IsFolder && ! dash . IsFolder ) ||
( ! existing . IsFolder && dash . IsFolder ) {
2022-06-30 08:31:54 -05:00
return isParentFolderChanged , dashboards . ErrDashboardTypeMismatch
2022-02-16 07:15:44 -06:00
}
2023-11-16 05:11:35 -06:00
if ! dash . IsFolder && dash . FolderUID != existing . FolderUID {
2022-02-16 07:15:44 -06:00
isParentFolderChanged = true
}
// check for is someone else has written in between
if dash . Version != existing . Version {
if overwrite {
dash . SetVersion ( existing . Version )
} else {
2022-06-30 08:31:54 -05:00
return isParentFolderChanged , dashboards . ErrDashboardVersionMismatch
2022-02-16 07:15:44 -06:00
}
}
// do not allow plugin dashboard updates without overwrite flag
2023-01-18 06:52:41 -06:00
if existing . PluginID != "" && ! overwrite {
return isParentFolderChanged , dashboards . UpdatePluginDashboardError { PluginId : existing . PluginID }
2022-02-16 07:15:44 -06:00
}
return isParentFolderChanged , nil
}
2023-06-29 15:15:38 -05:00
// getExistingDashboardByTitleAndFolder returns a boolean (on whether the parent folder changed) and an error for if the dashboard already exists.
2023-01-16 09:33:55 -06:00
func getExistingDashboardByTitleAndFolder ( sess * db . Session , dash * dashboards . Dashboard , dialect migrator . Dialect , overwrite ,
2022-02-16 07:15:44 -06:00
isParentFolderChanged bool ) ( bool , error ) {
2023-01-18 06:52:41 -06:00
var existing dashboards . Dashboard
2024-01-25 03:29:56 -06:00
condition := "org_id=? AND title=?"
args := [ ] any { dash . OrgID , dash . Title }
if dash . FolderUID != "" {
condition += " AND folder_uid=?"
args = append ( args , dash . FolderUID )
} else {
condition += " AND folder_uid IS NULL"
}
exists , err := sess . Where ( condition , args ... ) . Get ( & existing )
2022-02-16 07:15:44 -06:00
if err != nil {
return isParentFolderChanged , fmt . Errorf ( "SQL query for existing dashboard by org ID or folder ID failed: %w" , err )
}
2023-01-18 06:52:41 -06:00
if exists && dash . ID != existing . ID {
2022-02-16 07:15:44 -06:00
if existing . IsFolder && ! dash . IsFolder {
2022-06-30 08:31:54 -05:00
return isParentFolderChanged , dashboards . ErrDashboardWithSameNameAsFolder
2022-02-16 07:15:44 -06:00
}
if ! existing . IsFolder && dash . IsFolder {
2022-06-30 08:31:54 -05:00
return isParentFolderChanged , dashboards . ErrDashboardFolderWithSameNameAsDashboard
2022-02-16 07:15:44 -06:00
}
2024-01-25 04:10:35 -06:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Dashboard ) . Inc ( )
2023-11-15 09:28:50 -06:00
// nolint:staticcheck
2023-01-18 06:52:41 -06:00
if ! dash . IsFolder && ( dash . FolderID != existing . FolderID || dash . ID == 0 ) {
2022-02-16 07:15:44 -06:00
isParentFolderChanged = true
}
if overwrite {
2023-01-18 06:52:41 -06:00
dash . SetID ( existing . ID )
dash . SetUID ( existing . UID )
2022-02-16 07:15:44 -06:00
dash . SetVersion ( existing . Version )
} else {
2022-06-30 08:31:54 -05:00
return isParentFolderChanged , dashboards . ErrDashboardWithSameNameInFolderExists
2022-02-16 07:15:44 -06:00
}
}
return isParentFolderChanged , nil
}
2023-01-25 03:36:26 -06:00
func saveDashboard ( sess * db . Session , cmd * dashboards . SaveDashboardCommand , emitEntityEvent bool ) ( * dashboards . Dashboard , error ) {
2022-02-16 07:15:44 -06:00
dash := cmd . GetDashboardModel ( )
2023-01-16 09:33:55 -06:00
userId := cmd . UserID
2022-02-16 07:15:44 -06:00
if userId == 0 {
userId = - 1
}
2023-01-16 09:33:55 -06:00
if dash . ID > 0 {
2023-01-18 06:52:41 -06:00
var existing dashboards . Dashboard
2023-01-16 09:33:55 -06:00
dashWithIdExists , err := sess . Where ( "id=? AND org_id=?" , dash . ID , dash . OrgID ) . Get ( & existing )
2022-02-16 07:15:44 -06:00
if err != nil {
2023-01-25 03:36:26 -06:00
return nil , err
2022-02-16 07:15:44 -06:00
}
if ! dashWithIdExists {
2023-01-25 03:36:26 -06:00
return nil , dashboards . ErrDashboardNotFound
2022-02-16 07:15:44 -06:00
}
// check for is someone else has written in between
if dash . Version != existing . Version {
if cmd . Overwrite {
dash . SetVersion ( existing . Version )
} else {
2023-01-25 03:36:26 -06:00
return nil , dashboards . ErrDashboardVersionMismatch
2022-02-16 07:15:44 -06:00
}
}
// do not allow plugin dashboard updates without overwrite flag
2023-01-18 06:52:41 -06:00
if existing . PluginID != "" && ! cmd . Overwrite {
2023-01-25 03:36:26 -06:00
return nil , dashboards . UpdatePluginDashboardError { PluginId : existing . PluginID }
2022-02-16 07:15:44 -06:00
}
}
2023-01-16 09:33:55 -06:00
if dash . UID == "" {
2023-02-06 19:44:37 -06:00
dash . SetUID ( util . GenerateShortUID ( ) )
2022-02-16 07:15:44 -06:00
}
parentVersion := dash . Version
var affectedRows int64
var err error
2023-01-16 09:33:55 -06:00
if dash . ID == 0 {
2022-02-16 07:15:44 -06:00
dash . SetVersion ( 1 )
dash . Created = time . Now ( )
dash . CreatedBy = userId
dash . Updated = time . Now ( )
dash . UpdatedBy = userId
metrics . MApiDashboardInsert . Inc ( )
2023-10-24 02:04:45 -05:00
affectedRows , err = sess . Nullable ( "folder_uid" ) . Insert ( dash )
2022-02-16 07:15:44 -06:00
} else {
dash . SetVersion ( dash . Version + 1 )
if ! cmd . UpdatedAt . IsZero ( ) {
dash . Updated = cmd . UpdatedAt
} else {
dash . Updated = time . Now ( )
}
dash . UpdatedBy = userId
2023-10-24 02:04:45 -05:00
affectedRows , err = sess . MustCols ( "folder_id" , "folder_uid" ) . Nullable ( "folder_uid" ) . ID ( dash . ID ) . Update ( dash )
2022-02-16 07:15:44 -06:00
}
if err != nil {
2023-01-25 03:36:26 -06:00
return nil , err
2022-02-16 07:15:44 -06:00
}
if affectedRows == 0 {
2023-01-25 03:36:26 -06:00
return nil , dashboards . ErrDashboardNotFound
2022-02-16 07:15:44 -06:00
}
2022-06-08 05:22:55 -05:00
dashVersion := & dashver . DashboardVersion {
2023-01-16 09:33:55 -06:00
DashboardID : dash . ID ,
2022-02-16 07:15:44 -06:00
ParentVersion : parentVersion ,
RestoredFrom : cmd . RestoredFrom ,
Version : dash . Version ,
Created : time . Now ( ) ,
CreatedBy : dash . UpdatedBy ,
Message : cmd . Message ,
Data : dash . Data ,
}
// insert version entry
if affectedRows , err = sess . Insert ( dashVersion ) ; err != nil {
2023-01-25 03:36:26 -06:00
return nil , err
2022-02-16 07:15:44 -06:00
} else if affectedRows == 0 {
2023-01-25 03:36:26 -06:00
return nil , dashboards . ErrDashboardNotFound
2022-02-16 07:15:44 -06:00
}
// delete existing tags
2023-01-16 09:33:55 -06:00
if _ , err = sess . Exec ( "DELETE FROM dashboard_tag WHERE dashboard_id=?" , dash . ID ) ; err != nil {
2023-01-25 03:36:26 -06:00
return nil , err
2022-02-16 07:15:44 -06:00
}
// insert new tags
tags := dash . GetTags ( )
if len ( tags ) > 0 {
for _ , tag := range tags {
2023-03-01 09:52:16 -06:00
if _ , err := sess . Insert ( dashboardTag { DashboardId : dash . ID , Term : tag } ) ; err != nil {
2023-01-25 03:36:26 -06:00
return nil , err
2022-02-16 07:15:44 -06:00
}
}
}
2022-08-01 10:56:36 -05:00
if emitEntityEvent {
_ , err := sess . Insert ( createEntityEvent ( dash , store . EntityEventTypeUpdate ) )
if err != nil {
2023-01-25 03:36:26 -06:00
return dash , err
2022-08-01 10:56:36 -05:00
}
}
2023-01-25 03:36:26 -06:00
return dash , nil
2022-02-16 07:15:44 -06:00
}
2023-01-16 09:33:55 -06:00
func saveProvisionedData ( sess * db . Session , provisioning * dashboards . DashboardProvisioning , dashboard * dashboards . Dashboard ) error {
2023-01-18 06:52:41 -06:00
result := & dashboards . DashboardProvisioning { }
2022-02-16 07:15:44 -06:00
2023-01-16 09:33:55 -06:00
exist , err := sess . Where ( "dashboard_id=? AND name = ?" , dashboard . ID , provisioning . Name ) . Get ( result )
2022-02-16 07:15:44 -06:00
if err != nil {
return err
}
2023-01-18 06:52:41 -06:00
provisioning . ID = result . ID
2023-01-16 09:33:55 -06:00
provisioning . DashboardID = dashboard . ID
2022-02-16 07:15:44 -06:00
if exist {
2023-01-18 06:52:41 -06:00
_ , err = sess . ID ( result . ID ) . Update ( provisioning )
2022-02-16 07:15:44 -06:00
} else {
_ , err = sess . Insert ( provisioning )
}
return err
}
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) GetDashboardsByPluginID ( ctx context . Context , query * dashboards . GetDashboardsByPluginIDQuery ) ( [ ] * dashboards . Dashboard , error ) {
2023-01-25 03:36:26 -06:00
var dashboards = make ( [ ] * dashboards . Dashboard , 0 )
err := d . store . WithDbSession ( ctx , func ( dbSession * db . Session ) error {
2022-10-19 08:02:15 -05:00
whereExpr := "org_id=? AND plugin_id=? AND is_folder=" + d . store . GetDialect ( ) . BooleanStr ( false )
2022-03-10 11:38:04 -06:00
2023-01-18 06:52:41 -06:00
err := dbSession . Where ( whereExpr , query . OrgID , query . PluginID ) . Find ( & dashboards )
2022-03-10 11:38:04 -06:00
return err
} )
2023-01-25 03:36:26 -06:00
if err != nil {
return nil , err
}
return dashboards , nil
2022-03-10 11:38:04 -06:00
}
2022-03-22 08:36:50 -05:00
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) DeleteDashboard ( ctx context . Context , cmd * dashboards . DeleteDashboardCommand ) error {
2022-10-19 08:02:15 -05:00
return d . store . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2022-08-01 10:56:36 -05:00
return d . deleteDashboard ( cmd , sess , d . emitEntityEvent ( ) )
2022-03-22 08:36:50 -05:00
} )
}
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) deleteDashboard ( cmd * dashboards . DeleteDashboardCommand , sess * db . Session , emitEntityEvent bool ) error {
2024-01-30 10:26:34 -06:00
dashboard := dashboards . Dashboard { OrgID : cmd . OrgID }
if cmd . UID != "" {
dashboard . UID = cmd . UID
} else {
dashboard . ID = cmd . ID
}
2022-03-22 08:36:50 -05:00
has , err := sess . Get ( & dashboard )
if err != nil {
return err
} else if ! has {
2022-06-30 08:31:54 -05:00
return dashboards . ErrDashboardNotFound
2022-03-22 08:36:50 -05:00
}
deletes := [ ] string {
"DELETE FROM dashboard_tag WHERE dashboard_id = ? " ,
"DELETE FROM star WHERE dashboard_id = ? " ,
"DELETE FROM dashboard WHERE id = ?" ,
"DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?" ,
"DELETE FROM dashboard_version WHERE dashboard_id = ?" ,
"DELETE FROM dashboard_provisioning WHERE dashboard_id = ?" ,
"DELETE FROM dashboard_acl WHERE dashboard_id = ?" ,
}
if dashboard . IsFolder {
deletes = append ( deletes , "DELETE FROM dashboard WHERE folder_id = ?" )
2023-07-10 04:12:04 -05:00
if err := d . deleteChildrenDashboardAssociations ( sess , & dashboard ) ; err != nil {
2022-03-22 08:36:50 -05:00
return err
}
// remove all access control permission with folder scope
2023-07-10 04:12:04 -05:00
err := d . deleteResourcePermissions ( sess , dashboard . OrgID , dashboards . ScopeFoldersProvider . GetResourceScopeUID ( dashboard . UID ) )
2022-03-22 08:36:50 -05:00
if err != nil {
return err
}
} else {
2023-07-10 04:12:04 -05:00
if err := d . deleteResourcePermissions ( sess , dashboard . OrgID , ac . GetResourceScopeUID ( "dashboards" , dashboard . UID ) ) ; err != nil {
2022-03-22 08:36:50 -05:00
return err
}
}
2023-06-01 07:31:03 -05:00
_ , err = sess . Exec ( "DELETE FROM annotation WHERE dashboard_id = ? AND org_id = ?" , dashboard . ID , dashboard . OrgID )
if err != nil {
return err
}
2022-03-22 08:36:50 -05:00
for _ , sql := range deletes {
2023-01-16 09:33:55 -06:00
_ , err := sess . Exec ( sql , dashboard . ID )
2022-03-22 08:36:50 -05:00
if err != nil {
return err
}
}
2022-08-01 10:56:36 -05:00
if emitEntityEvent {
_ , err := sess . Insert ( createEntityEvent ( & dashboard , store . EntityEventTypeDelete ) )
if err != nil {
return err
}
}
2022-03-22 08:36:50 -05:00
return nil
}
2023-07-10 04:12:04 -05:00
// FIXME: Remove me and handle nested deletions in the service with the DashboardPermissionsService
func ( d * dashboardStore ) deleteResourcePermissions ( sess * db . Session , orgID int64 , resourceScope string ) error {
// retrieve all permissions for the resource scope and org id
var permissionIDs [ ] int64
err := sess . SQL ( "SELECT permission.id FROM permission INNER JOIN role ON permission.role_id = role.id WHERE permission.scope = ? AND role.org_id = ?" , resourceScope , orgID ) . Find ( & permissionIDs )
if err != nil {
return err
}
if len ( permissionIDs ) == 0 {
return nil
}
// delete the permissions
_ , err = sess . In ( "id" , permissionIDs ) . Delete ( & ac . Permission { } )
return err
}
func ( d * dashboardStore ) deleteChildrenDashboardAssociations ( sess * db . Session , dashboard * dashboards . Dashboard ) error {
2023-04-14 04:17:23 -05:00
var dashIds [ ] struct {
Id int64
Uid string
}
err := sess . SQL ( "SELECT id, uid FROM dashboard WHERE folder_id = ?" , dashboard . ID ) . Find ( & dashIds )
if err != nil {
return err
}
if len ( dashIds ) > 0 {
for _ , dash := range dashIds {
// remove all access control permission with child dashboard scopes
2023-07-10 04:12:04 -05:00
if err := d . deleteResourcePermissions ( sess , dashboard . OrgID , ac . GetResourceScopeUID ( "dashboards" , dash . Uid ) ) ; err != nil {
2023-04-14 04:17:23 -05:00
return err
}
}
childrenDeletes := [ ] string {
"DELETE FROM dashboard_tag WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)" ,
"DELETE FROM star WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)" ,
"DELETE FROM dashboard_version WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)" ,
"DELETE FROM dashboard_provisioning WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)" ,
"DELETE FROM dashboard_acl WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)" ,
}
2023-06-01 07:31:03 -05:00
_ , err = sess . Exec ( "DELETE FROM annotation WHERE org_id = ? AND dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)" , dashboard . OrgID , dashboard . OrgID , dashboard . ID )
if err != nil {
return err
}
2023-04-14 04:17:23 -05:00
for _ , sql := range childrenDeletes {
_ , err := sess . Exec ( sql , dashboard . OrgID , dashboard . ID )
if err != nil {
return err
}
}
}
return nil
}
2023-01-16 09:33:55 -06:00
func createEntityEvent ( dashboard * dashboards . Dashboard , eventType store . EntityEventType ) * store . EntityEvent {
2022-08-01 10:56:36 -05:00
var entityEvent * store . EntityEvent
if dashboard . IsFolder {
entityEvent = & store . EntityEvent {
EventType : eventType ,
2023-01-16 09:33:55 -06:00
EntityId : store . CreateDatabaseEntityId ( dashboard . UID , dashboard . OrgID , store . EntityTypeFolder ) ,
2022-08-01 10:56:36 -05:00
Created : time . Now ( ) . Unix ( ) ,
}
} else {
entityEvent = & store . EntityEvent {
EventType : eventType ,
2023-01-16 09:33:55 -06:00
EntityId : store . CreateDatabaseEntityId ( dashboard . UID , dashboard . OrgID , store . EntityTypeDashboard ) ,
2022-08-01 10:56:36 -05:00
Created : time . Now ( ) . Unix ( ) ,
}
}
return entityEvent
}
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) GetDashboard ( ctx context . Context , query * dashboards . GetDashboardQuery ) ( * dashboards . Dashboard , error ) {
2023-01-25 03:36:26 -06:00
var queryResult * dashboards . Dashboard
2022-10-19 08:02:15 -05:00
err := d . store . WithDbSession ( ctx , func ( sess * db . Session ) error {
2024-01-25 04:10:35 -06:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Dashboard ) . Inc ( )
2023-11-09 09:53:39 -06:00
// nolint:staticcheck
2024-02-02 03:55:29 -06:00
if query . ID == 0 && len ( query . UID ) == 0 && ( query . Title == nil || ( query . FolderID == nil && query . FolderUID == nil ) ) {
2022-06-30 08:31:54 -05:00
return dashboards . ErrDashboardIdentifierNotSet
2022-05-17 13:52:22 -05:00
}
2023-03-28 06:24:19 -05:00
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" )
}
2024-01-11 08:02:13 -06:00
2024-02-02 03:55:29 -06:00
if query . FolderUID != nil {
dashboard . FolderUID = * query . FolderUID
2024-01-11 08:02:13 -06:00
mustCols = append ( mustCols , "folder_uid" )
} else if query . FolderID != nil { // nolint:staticcheck
2023-11-15 09:28:50 -06:00
// nolint:staticcheck
2023-03-28 06:24:19 -05:00
dashboard . FolderID = * query . FolderID
mustCols = append ( mustCols , "folder_id" )
2024-01-25 04:10:35 -06:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Dashboard ) . Inc ( )
2023-03-28 06:24:19 -05:00
}
2024-02-02 03:55:29 -06:00
has , err := sess . MustCols ( mustCols ... ) . Nullable ( "folder_uid" ) . Get ( & dashboard )
2022-05-17 13:52:22 -05:00
if err != nil {
return err
} else if ! has {
2022-06-30 08:31:54 -05:00
return dashboards . ErrDashboardNotFound
2022-05-17 13:52:22 -05:00
}
2023-01-16 09:33:55 -06:00
dashboard . SetID ( dashboard . ID )
dashboard . SetUID ( dashboard . UID )
2023-01-25 03:36:26 -06:00
queryResult = & dashboard
2022-05-17 13:52:22 -05:00
return nil
} )
2022-06-07 04:02:20 -05:00
2023-01-25 03:36:26 -06:00
return queryResult , err
2022-05-17 13:52:22 -05:00
}
2022-05-19 09:13:02 -05:00
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) GetDashboardUIDByID ( ctx context . Context , query * dashboards . GetDashboardRefByIDQuery ) ( * dashboards . DashboardRef , error ) {
2023-01-25 03:36:26 -06:00
us := & dashboards . DashboardRef { }
err := d . store . WithDbSession ( ctx , func ( sess * db . Session ) error {
2022-05-19 09:13:02 -05:00
var rawSQL = ` SELECT uid, slug from dashboard WHERE Id=? `
2023-01-16 09:33:55 -06:00
exists , err := sess . SQL ( rawSQL , query . ID ) . Get ( us )
2022-05-19 09:13:02 -05:00
if err != nil {
return err
} else if ! exists {
2022-06-30 08:31:54 -05:00
return dashboards . ErrDashboardNotFound
2022-05-19 09:13:02 -05:00
}
return nil
} )
2023-01-25 03:36:26 -06:00
if err != nil {
return nil , err
}
return us , nil
2022-05-19 09:13:02 -05:00
}
2022-05-19 09:59:12 -05:00
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) GetDashboards ( ctx context . Context , query * dashboards . GetDashboardsQuery ) ( [ ] * dashboards . Dashboard , error ) {
2023-01-25 03:36:26 -06:00
var dashboards = make ( [ ] * dashboards . Dashboard , 0 )
err := d . store . WithDbSession ( ctx , func ( sess * db . Session ) error {
2023-01-16 09:33:55 -06:00
if len ( query . DashboardIDs ) == 0 && len ( query . DashboardUIDs ) == 0 {
2023-01-10 08:56:33 -06:00
return star . ErrCommandValidationFailed
2022-05-19 09:59:12 -05:00
}
var session * xorm . Session
2023-01-16 09:33:55 -06:00
if len ( query . DashboardIDs ) > 0 {
session = sess . In ( "id" , query . DashboardIDs )
2022-05-19 09:59:12 -05:00
} else {
2023-01-16 09:33:55 -06:00
session = sess . In ( "uid" , query . DashboardUIDs )
2022-05-19 09:59:12 -05:00
}
2023-01-16 14:59:43 -06:00
if query . OrgID > 0 {
session = sess . Where ( "org_id = ?" , query . OrgID )
}
2022-05-19 09:59:12 -05:00
err := session . Find ( & dashboards )
return err
} )
2023-01-25 03:36:26 -06:00
if err != nil {
return nil , err
}
return dashboards , nil
2022-05-19 09:59:12 -05:00
}
2022-05-23 10:14:27 -05:00
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) FindDashboards ( ctx context . Context , query * dashboards . FindPersistedDashboardsQuery ) ( [ ] dashboards . DashboardSearchProjection , error ) {
2023-07-19 04:37:27 -05:00
recursiveQueriesAreSupported , err := d . store . RecursiveQueriesAreSupported ( )
if err != nil {
return nil , err
2022-05-23 10:14:27 -05:00
}
2023-08-30 10:46:47 -05:00
filters := [ ] any {
2023-07-19 04:37:27 -05:00
permissions . NewAccessControlDashboardPermissionFilter ( query . SignedInUser , query . Permission , query . Type , d . features , recursiveQueriesAreSupported ) ,
2022-05-23 10:14:27 -05:00
}
for _ , filter := range query . Sort . Filter {
filters = append ( filters , filter )
}
filters = append ( filters , query . Filters ... )
2023-08-04 04:43:47 -05:00
var orgID int64
2022-05-23 10:14:27 -05:00
if query . OrgId != 0 {
2023-08-04 04:43:47 -05:00
orgID = query . OrgId
filters = append ( filters , searchstore . OrgFilter { OrgId : orgID } )
2023-11-14 08:47:34 -06:00
} else if query . SignedInUser . GetOrgID ( ) != 0 {
orgID = query . SignedInUser . GetOrgID ( )
2023-08-04 04:43:47 -05:00
filters = append ( filters , searchstore . OrgFilter { OrgId : orgID } )
2022-05-23 10:14:27 -05:00
}
if len ( query . Tags ) > 0 {
filters = append ( filters , searchstore . TagsFilter { Tags : query . Tags } )
}
2022-06-02 14:56:01 -05:00
if len ( query . DashboardUIDs ) > 0 {
filters = append ( filters , searchstore . DashboardFilter { UIDs : query . DashboardUIDs } )
} else if len ( query . DashboardIds ) > 0 {
filters = append ( filters , searchstore . DashboardIDFilter { IDs : query . DashboardIds } )
2022-05-23 10:14:27 -05:00
}
if len ( query . Title ) > 0 {
2022-10-19 08:02:15 -05:00
filters = append ( filters , searchstore . TitleFilter { Dialect : d . store . GetDialect ( ) , Title : query . Title } )
2022-05-23 10:14:27 -05:00
}
if len ( query . Type ) > 0 {
2022-10-19 08:02:15 -05:00
filters = append ( filters , searchstore . TypeFilter { Dialect : d . store . GetDialect ( ) , Type : query . Type } )
2022-05-23 10:14:27 -05:00
}
2024-01-25 04:10:35 -06:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Dashboard ) . Inc ( )
2023-11-09 10:07:10 -06:00
// nolint:staticcheck
2022-05-23 10:14:27 -05:00
if len ( query . FolderIds ) > 0 {
filters = append ( filters , searchstore . FolderFilter { IDs : query . FolderIds } )
}
2023-08-04 04:43:47 -05:00
if len ( query . FolderUIDs ) > 0 {
2023-11-14 14:50:27 -06:00
filters = append ( filters , searchstore . FolderUIDFilter {
Dialect : d . store . GetDialect ( ) ,
OrgID : orgID ,
UIDs : query . FolderUIDs ,
NestedFoldersEnabled : d . features . IsEnabled ( ctx , featuremgmt . FlagNestedFolders ) ,
} )
2023-08-04 04:43:47 -05:00
}
2022-05-23 10:14:27 -05:00
var res [ ] dashboards . DashboardSearchProjection
2023-10-24 02:04:45 -05:00
sb := & searchstore . Builder { Dialect : d . store . GetDialect ( ) , Filters : filters , Features : d . features }
2022-05-23 10:14:27 -05:00
limit := query . Limit
if limit < 1 {
limit = 1000
}
page := query . Page
if page < 1 {
page = 1
}
sql , params := sb . ToSQL ( limit , page )
2023-07-19 04:37:27 -05:00
err = d . store . WithDbSession ( ctx , func ( sess * db . Session ) error {
2022-05-23 10:14:27 -05:00
return sess . SQL ( sql , params ... ) . Find ( & res )
} )
if err != nil {
return nil , err
}
return res , nil
}
2022-06-02 09:00:47 -05:00
2023-03-01 09:52:16 -06:00
func ( d * dashboardStore ) GetDashboardTags ( ctx context . Context , query * dashboards . GetDashboardTagsQuery ) ( [ ] * dashboards . DashboardTagCloudItem , error ) {
2023-01-25 03:36:26 -06:00
queryResult := make ( [ ] * dashboards . DashboardTagCloudItem , 0 )
err := d . store . WithDbSession ( ctx , func ( dbSession * db . Session ) error {
2022-06-02 09:00:47 -05:00
sql := ` SELECT
COUNT ( * ) as count ,
term
FROM dashboard
INNER JOIN dashboard_tag on dashboard_tag . dashboard_id = dashboard . id
WHERE dashboard . org_id = ?
GROUP BY term
ORDER BY term `
2023-01-18 06:52:41 -06:00
sess := dbSession . SQL ( sql , query . OrgID )
2023-01-25 03:36:26 -06:00
err := sess . Find ( & queryResult )
2022-06-02 09:00:47 -05:00
return err
} )
2023-01-25 03:36:26 -06:00
if err != nil {
return nil , err
}
return queryResult , nil
2022-06-02 09:00:47 -05:00
}
2022-11-02 08:15:50 -05:00
2022-11-08 04:51:00 -06:00
// CountDashboardsInFolder returns a count of all dashboards associated with the
// given parent folder ID.
2024-01-30 10:26:34 -06:00
func ( d * dashboardStore ) CountDashboardsInFolders (
2022-11-02 08:15:50 -05:00
ctx context . Context , req * dashboards . CountDashboardsInFolderRequest ) ( int64 , error ) {
2024-01-30 10:26:34 -06:00
if len ( req . FolderUIDs ) == 0 {
return 0 , nil
}
2022-11-08 04:51:00 -06:00
var count int64
2024-01-30 10:26:34 -06:00
err := d . store . WithDbSession ( ctx , func ( sess * db . Session ) error {
2024-01-25 04:10:35 -06:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Dashboard ) . Inc ( )
2024-01-30 10:26:34 -06:00
s := strings . Builder { }
args := make ( [ ] any , 0 , 3 )
s . WriteString ( "SELECT COUNT(*) FROM dashboard WHERE " )
if len ( req . FolderUIDs ) == 1 && req . FolderUIDs [ 0 ] == "" {
s . WriteString ( "folder_uid IS NULL" )
} else {
s . WriteString ( fmt . Sprintf ( "folder_uid IN (%s)" , strings . Repeat ( "?," , len ( req . FolderUIDs ) - 1 ) + "?" ) )
for _ , folderUID := range req . FolderUIDs {
args = append ( args , folderUID )
}
}
s . WriteString ( " AND org_id = ? AND is_folder = ?" )
args = append ( args , req . OrgID , d . store . GetDialect ( ) . BooleanStr ( false ) )
sql := s . String ( )
_ , err := sess . SQL ( sql , args ... ) . Get ( & count )
2022-11-02 08:15:50 -05:00
return err
} )
2022-11-08 04:51:00 -06:00
return count , err
2022-11-02 08:15:50 -05:00
}
2022-11-14 13:08:10 -06:00
2024-01-30 10:26:34 -06:00
func ( d * dashboardStore ) DeleteDashboardsInFolders (
2023-04-14 04:17:23 -05:00
ctx context . Context , req * dashboards . DeleteDashboardsInFolderRequest ) error {
return d . store . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2024-01-30 10:26:34 -06:00
// TODO delete all dashboards in the folder in a bulk query
for _ , folderUID := range req . FolderUIDs {
dashboard := dashboards . Dashboard { OrgID : req . OrgID }
has , err := sess . Where ( "org_id = ? AND uid = ?" , req . OrgID , folderUID ) . Get ( & dashboard )
if err != nil {
return err
}
if ! has {
return dashboards . ErrFolderNotFound
}
2023-04-14 04:17:23 -05:00
2024-01-30 10:26:34 -06:00
if err := d . deleteChildrenDashboardAssociations ( sess , & dashboard ) ; err != nil {
return err
}
2023-04-14 04:17:23 -05:00
2024-01-30 10:26:34 -06:00
_ , err = sess . Where ( "folder_id = ? AND org_id = ? AND is_folder = ?" , dashboard . ID , dashboard . OrgID , false ) . Delete ( & dashboards . Dashboard { } )
if err != nil {
return err
}
}
return nil
2023-04-14 04:17:23 -05:00
} )
}
2022-11-14 13:08:10 -06:00
func readQuotaConfig ( cfg * setting . Cfg ) ( * quota . Map , error ) {
limits := & quota . Map { }
if cfg == nil {
return limits , nil
}
globalQuotaTag , err := quota . NewTag ( dashboards . QuotaTargetSrv , dashboards . QuotaTarget , quota . GlobalScope )
if err != nil {
return & quota . Map { } , err
}
orgQuotaTag , err := quota . NewTag ( dashboards . QuotaTargetSrv , dashboards . QuotaTarget , quota . OrgScope )
if err != nil {
return & quota . Map { } , err
}
limits . Set ( globalQuotaTag , cfg . Quota . Global . Dashboard )
limits . Set ( orgQuotaTag , cfg . Quota . Org . Dashboard )
return limits , nil
}