mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			766 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			766 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
	
package libraryelements
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/grafana/grafana/pkg/api/dtos"
 | 
						|
	"github.com/grafana/grafana/pkg/infra/db"
 | 
						|
	"github.com/grafana/grafana/pkg/models"
 | 
						|
	"github.com/grafana/grafana/pkg/services/dashboards"
 | 
						|
	"github.com/grafana/grafana/pkg/services/org"
 | 
						|
	"github.com/grafana/grafana/pkg/services/search"
 | 
						|
	"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
 | 
						|
	"github.com/grafana/grafana/pkg/services/user"
 | 
						|
	"github.com/grafana/grafana/pkg/setting"
 | 
						|
	"github.com/grafana/grafana/pkg/util"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	selectLibraryElementDTOWithMeta = `
 | 
						|
SELECT DISTINCT
 | 
						|
	le.name, le.id, le.org_id, le.folder_id, le.uid, le.kind, le.type, le.description, le.model, le.created, le.created_by, le.updated, le.updated_by, le.version
 | 
						|
	, u1.login AS created_by_name
 | 
						|
	, u1.email AS created_by_email
 | 
						|
	, u2.login AS updated_by_name
 | 
						|
	, u2.email AS updated_by_email
 | 
						|
	, (SELECT COUNT(connection_id) FROM ` + models.LibraryElementConnectionTableName + ` WHERE element_id = le.id AND kind=1) AS connected_dashboards`
 | 
						|
)
 | 
						|
 | 
						|
// redundant SELECT to trick mysql's optimizer
 | 
						|
const deleteInvalidConnections = `
 | 
						|
DELETE FROM library_element_connection
 | 
						|
WHERE connection_id IN (
 | 
						|
	SELECT connection_id FROM (
 | 
						|
		SELECT connection_id as id FROM library_element_connection
 | 
						|
		WHERE element_id=? AND connection_id NOT IN (SELECT id as connection_id from dashboard)
 | 
						|
	) as dummy
 | 
						|
)`
 | 
						|
 | 
						|
func getFromLibraryElementDTOWithMeta(dialect migrator.Dialect) string {
 | 
						|
	user := dialect.Quote("user")
 | 
						|
	userJoin := `
 | 
						|
FROM library_element AS le
 | 
						|
LEFT JOIN ` + user + ` AS u1 ON le.created_by = u1.id
 | 
						|
LEFT JOIN ` + user + ` AS u2 ON le.updated_by = u2.id
 | 
						|
`
 | 
						|
	return userJoin
 | 
						|
}
 | 
						|
 | 
						|
func syncFieldsWithModel(libraryElement *LibraryElement) error {
 | 
						|
	var model map[string]interface{}
 | 
						|
	if err := json.Unmarshal(libraryElement.Model, &model); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if models.LibraryElementKind(libraryElement.Kind) == models.VariableElement {
 | 
						|
		model["name"] = libraryElement.Name
 | 
						|
	}
 | 
						|
	if model["type"] != nil {
 | 
						|
		libraryElement.Type = model["type"].(string)
 | 
						|
	} else {
 | 
						|
		model["type"] = libraryElement.Type
 | 
						|
	}
 | 
						|
	if model["description"] != nil {
 | 
						|
		libraryElement.Description = model["description"].(string)
 | 
						|
	} else {
 | 
						|
		model["description"] = libraryElement.Description
 | 
						|
	}
 | 
						|
	syncedModel, err := json.Marshal(&model)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	libraryElement.Model = syncedModel
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func getLibraryElement(dialect migrator.Dialect, session *db.Session, uid string, orgID int64) (LibraryElementWithMeta, error) {
 | 
						|
	elements := make([]LibraryElementWithMeta, 0)
 | 
						|
	sql := selectLibraryElementDTOWithMeta +
 | 
						|
		", coalesce(dashboard.title, 'General') AS folder_name" +
 | 
						|
		", coalesce(dashboard.uid, '') AS folder_uid" +
 | 
						|
		getFromLibraryElementDTOWithMeta(dialect) +
 | 
						|
		" LEFT JOIN dashboard AS dashboard ON dashboard.id = le.folder_id" +
 | 
						|
		" WHERE le.uid=? AND le.org_id=?"
 | 
						|
	sess := session.SQL(sql, uid, orgID)
 | 
						|
	err := sess.Find(&elements)
 | 
						|
	if err != nil {
 | 
						|
		return LibraryElementWithMeta{}, err
 | 
						|
	}
 | 
						|
	if len(elements) == 0 {
 | 
						|
		return LibraryElementWithMeta{}, ErrLibraryElementNotFound
 | 
						|
	}
 | 
						|
	if len(elements) > 1 {
 | 
						|
		return LibraryElementWithMeta{}, fmt.Errorf("found %d elements, while expecting at most one", len(elements))
 | 
						|
	}
 | 
						|
 | 
						|
	return elements[0], nil
 | 
						|
}
 | 
						|
 | 
						|
// createLibraryElement adds a library element.
 | 
						|
func (l *LibraryElementService) createLibraryElement(c context.Context, signedInUser *user.SignedInUser, cmd CreateLibraryElementCommand) (LibraryElementDTO, error) {
 | 
						|
	if err := l.requireSupportedElementKind(cmd.Kind); err != nil {
 | 
						|
		return LibraryElementDTO{}, err
 | 
						|
	}
 | 
						|
	createUID := cmd.UID
 | 
						|
	if len(createUID) == 0 {
 | 
						|
		createUID = util.GenerateShortUID()
 | 
						|
	} else {
 | 
						|
		if !util.IsValidShortUID(createUID) {
 | 
						|
			return LibraryElementDTO{}, errLibraryElementInvalidUID
 | 
						|
		} else if util.IsShortUIDTooLong(createUID) {
 | 
						|
			return LibraryElementDTO{}, errLibraryElementUIDTooLong
 | 
						|
		}
 | 
						|
	}
 | 
						|
	element := LibraryElement{
 | 
						|
		OrgID:    signedInUser.OrgID,
 | 
						|
		FolderID: cmd.FolderID,
 | 
						|
		UID:      createUID,
 | 
						|
		Name:     cmd.Name,
 | 
						|
		Model:    cmd.Model,
 | 
						|
		Version:  1,
 | 
						|
		Kind:     cmd.Kind,
 | 
						|
 | 
						|
		Created: time.Now(),
 | 
						|
		Updated: time.Now(),
 | 
						|
 | 
						|
		CreatedBy: signedInUser.UserID,
 | 
						|
		UpdatedBy: signedInUser.UserID,
 | 
						|
	}
 | 
						|
 | 
						|
	if err := syncFieldsWithModel(&element); err != nil {
 | 
						|
		return LibraryElementDTO{}, err
 | 
						|
	}
 | 
						|
 | 
						|
	err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
 | 
						|
		if err := l.requireEditPermissionsOnFolder(c, signedInUser, cmd.FolderID); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if _, err := session.Insert(&element); err != nil {
 | 
						|
			if l.SQLStore.GetDialect().IsUniqueConstraintViolation(err) {
 | 
						|
				return errLibraryElementAlreadyExists
 | 
						|
			}
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
 | 
						|
	dto := LibraryElementDTO{
 | 
						|
		ID:          element.ID,
 | 
						|
		OrgID:       element.OrgID,
 | 
						|
		FolderID:    element.FolderID,
 | 
						|
		UID:         element.UID,
 | 
						|
		Name:        element.Name,
 | 
						|
		Kind:        element.Kind,
 | 
						|
		Type:        element.Type,
 | 
						|
		Description: element.Description,
 | 
						|
		Model:       element.Model,
 | 
						|
		Version:     element.Version,
 | 
						|
		Meta: LibraryElementDTOMeta{
 | 
						|
			ConnectedDashboards: 0,
 | 
						|
			Created:             element.Created,
 | 
						|
			Updated:             element.Updated,
 | 
						|
			CreatedBy: LibraryElementDTOMetaUser{
 | 
						|
				ID:        element.CreatedBy,
 | 
						|
				Name:      signedInUser.Login,
 | 
						|
				AvatarURL: dtos.GetGravatarUrl(signedInUser.Email),
 | 
						|
			},
 | 
						|
			UpdatedBy: LibraryElementDTOMetaUser{
 | 
						|
				ID:        element.UpdatedBy,
 | 
						|
				Name:      signedInUser.Login,
 | 
						|
				AvatarURL: dtos.GetGravatarUrl(signedInUser.Email),
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	return dto, err
 | 
						|
}
 | 
						|
 | 
						|
// deleteLibraryElement deletes a library element.
 | 
						|
func (l *LibraryElementService) deleteLibraryElement(c context.Context, signedInUser *user.SignedInUser, uid string) (int64, error) {
 | 
						|
	var elementID int64
 | 
						|
	err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
 | 
						|
		element, err := getLibraryElement(l.SQLStore.GetDialect(), session, uid, signedInUser.OrgID)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if err := l.requireEditPermissionsOnFolder(c, signedInUser, element.FolderID); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		// Delete any hanging/invalid connections
 | 
						|
		if _, err = session.Exec(deleteInvalidConnections, element.ID); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		var connectionIDs []struct {
 | 
						|
			ConnectionID int64 `xorm:"connection_id"`
 | 
						|
		}
 | 
						|
		sql := "SELECT connection_id FROM library_element_connection WHERE element_id=?"
 | 
						|
		if err := session.SQL(sql, element.ID).Find(&connectionIDs); err != nil {
 | 
						|
			return err
 | 
						|
		} else if len(connectionIDs) > 0 {
 | 
						|
			return errLibraryElementHasConnections
 | 
						|
		}
 | 
						|
 | 
						|
		result, err := session.Exec("DELETE FROM library_element WHERE id=?", element.ID)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if rowsAffected, err := result.RowsAffected(); err != nil {
 | 
						|
			return err
 | 
						|
		} else if rowsAffected != 1 {
 | 
						|
			return ErrLibraryElementNotFound
 | 
						|
		}
 | 
						|
 | 
						|
		elementID = element.ID
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
	return elementID, err
 | 
						|
}
 | 
						|
 | 
						|
// getLibraryElements gets a Library Element where param == value
 | 
						|
func getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signedInUser *user.SignedInUser, params []Pair) ([]LibraryElementDTO, error) {
 | 
						|
	libraryElements := make([]LibraryElementWithMeta, 0)
 | 
						|
	err := store.WithDbSession(c, func(session *db.Session) error {
 | 
						|
		builder := db.NewSqlBuilder(cfg, store.GetDialect())
 | 
						|
		builder.Write(selectLibraryElementDTOWithMeta)
 | 
						|
		builder.Write(", 'General' as folder_name ")
 | 
						|
		builder.Write(", '' as folder_uid ")
 | 
						|
		builder.Write(getFromLibraryElementDTOWithMeta(store.GetDialect()))
 | 
						|
		writeParamSelectorSQL(&builder, append(params, Pair{"folder_id", 0})...)
 | 
						|
		builder.Write(" UNION ")
 | 
						|
		builder.Write(selectLibraryElementDTOWithMeta)
 | 
						|
		builder.Write(", dashboard.title as folder_name ")
 | 
						|
		builder.Write(", dashboard.uid as folder_uid ")
 | 
						|
		builder.Write(getFromLibraryElementDTOWithMeta(store.GetDialect()))
 | 
						|
		builder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id AND le.folder_id <> 0")
 | 
						|
		writeParamSelectorSQL(&builder, params...)
 | 
						|
		if signedInUser.OrgRole != org.RoleAdmin {
 | 
						|
			builder.WriteDashboardPermissionFilter(signedInUser, models.PERMISSION_VIEW)
 | 
						|
		}
 | 
						|
		builder.Write(` OR dashboard.id=0`)
 | 
						|
		if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&libraryElements); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if len(libraryElements) == 0 {
 | 
						|
			return ErrLibraryElementNotFound
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return []LibraryElementDTO{}, err
 | 
						|
	}
 | 
						|
 | 
						|
	leDtos := make([]LibraryElementDTO, len(libraryElements))
 | 
						|
	for i, libraryElement := range libraryElements {
 | 
						|
		leDtos[i] = LibraryElementDTO{
 | 
						|
			ID:          libraryElement.ID,
 | 
						|
			OrgID:       libraryElement.OrgID,
 | 
						|
			FolderID:    libraryElement.FolderID,
 | 
						|
			FolderUID:   libraryElement.FolderUID,
 | 
						|
			UID:         libraryElement.UID,
 | 
						|
			Name:        libraryElement.Name,
 | 
						|
			Kind:        libraryElement.Kind,
 | 
						|
			Type:        libraryElement.Type,
 | 
						|
			Description: libraryElement.Description,
 | 
						|
			Model:       libraryElement.Model,
 | 
						|
			Version:     libraryElement.Version,
 | 
						|
			Meta: LibraryElementDTOMeta{
 | 
						|
				FolderName:          libraryElement.FolderName,
 | 
						|
				FolderUID:           libraryElement.FolderUID,
 | 
						|
				ConnectedDashboards: libraryElement.ConnectedDashboards,
 | 
						|
				Created:             libraryElement.Created,
 | 
						|
				Updated:             libraryElement.Updated,
 | 
						|
				CreatedBy: LibraryElementDTOMetaUser{
 | 
						|
					ID:        libraryElement.CreatedBy,
 | 
						|
					Name:      libraryElement.CreatedByName,
 | 
						|
					AvatarURL: dtos.GetGravatarUrl(libraryElement.CreatedByEmail),
 | 
						|
				},
 | 
						|
				UpdatedBy: LibraryElementDTOMetaUser{
 | 
						|
					ID:        libraryElement.UpdatedBy,
 | 
						|
					Name:      libraryElement.UpdatedByName,
 | 
						|
					AvatarURL: dtos.GetGravatarUrl(libraryElement.UpdatedByEmail),
 | 
						|
				},
 | 
						|
			},
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return leDtos, nil
 | 
						|
}
 | 
						|
 | 
						|
// getLibraryElementByUid gets a Library Element by uid.
 | 
						|
func (l *LibraryElementService) getLibraryElementByUid(c context.Context, signedInUser *user.SignedInUser, UID string) (LibraryElementDTO, error) {
 | 
						|
	libraryElements, err := getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{key: "org_id", value: signedInUser.OrgID}, {key: "uid", value: UID}})
 | 
						|
	if err != nil {
 | 
						|
		return LibraryElementDTO{}, err
 | 
						|
	}
 | 
						|
	if len(libraryElements) > 1 {
 | 
						|
		return LibraryElementDTO{}, fmt.Errorf("found %d elements, while expecting at most one", len(libraryElements))
 | 
						|
	}
 | 
						|
 | 
						|
	return libraryElements[0], nil
 | 
						|
}
 | 
						|
 | 
						|
// getLibraryElementByName gets a Library Element by name.
 | 
						|
func (l *LibraryElementService) getLibraryElementsByName(c context.Context, signedInUser *user.SignedInUser, name string) ([]LibraryElementDTO, error) {
 | 
						|
	return getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{"org_id", signedInUser.OrgID}, {"name", name}})
 | 
						|
}
 | 
						|
 | 
						|
// getAllLibraryElements gets all Library Elements.
 | 
						|
func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedInUser *user.SignedInUser, query searchLibraryElementsQuery) (LibraryElementSearchResult, error) {
 | 
						|
	elements := make([]LibraryElementWithMeta, 0)
 | 
						|
	result := LibraryElementSearchResult{}
 | 
						|
	if query.perPage <= 0 {
 | 
						|
		query.perPage = 100
 | 
						|
	}
 | 
						|
	if query.page <= 0 {
 | 
						|
		query.page = 1
 | 
						|
	}
 | 
						|
	var typeFilter []string
 | 
						|
	if len(strings.TrimSpace(query.typeFilter)) > 0 {
 | 
						|
		typeFilter = strings.Split(query.typeFilter, ",")
 | 
						|
	}
 | 
						|
	folderFilter := parseFolderFilter(query)
 | 
						|
	if folderFilter.parseError != nil {
 | 
						|
		return LibraryElementSearchResult{}, folderFilter.parseError
 | 
						|
	}
 | 
						|
	err := l.SQLStore.WithDbSession(c, func(session *db.Session) error {
 | 
						|
		builder := db.NewSqlBuilder(l.Cfg, l.SQLStore.GetDialect())
 | 
						|
		if folderFilter.includeGeneralFolder {
 | 
						|
			builder.Write(selectLibraryElementDTOWithMeta)
 | 
						|
			builder.Write(", 'General' as folder_name ")
 | 
						|
			builder.Write(", '' as folder_uid ")
 | 
						|
			builder.Write(getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect()))
 | 
						|
			builder.Write(` WHERE le.org_id=?  AND le.folder_id=0`, signedInUser.OrgID)
 | 
						|
			writeKindSQL(query, &builder)
 | 
						|
			writeSearchStringSQL(query, l.SQLStore, &builder)
 | 
						|
			writeExcludeSQL(query, &builder)
 | 
						|
			writeTypeFilterSQL(typeFilter, &builder)
 | 
						|
			builder.Write(" UNION ")
 | 
						|
		}
 | 
						|
		builder.Write(selectLibraryElementDTOWithMeta)
 | 
						|
		builder.Write(", dashboard.title as folder_name ")
 | 
						|
		builder.Write(", dashboard.uid as folder_uid ")
 | 
						|
		builder.Write(getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect()))
 | 
						|
		builder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id AND le.folder_id<>0")
 | 
						|
		builder.Write(` WHERE le.org_id=?`, signedInUser.OrgID)
 | 
						|
		writeKindSQL(query, &builder)
 | 
						|
		writeSearchStringSQL(query, l.SQLStore, &builder)
 | 
						|
		writeExcludeSQL(query, &builder)
 | 
						|
		writeTypeFilterSQL(typeFilter, &builder)
 | 
						|
		if err := folderFilter.writeFolderFilterSQL(false, &builder); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if signedInUser.OrgRole != org.RoleAdmin {
 | 
						|
			builder.WriteDashboardPermissionFilter(signedInUser, models.PERMISSION_VIEW)
 | 
						|
		}
 | 
						|
		if query.sortDirection == search.SortAlphaDesc.Name {
 | 
						|
			builder.Write(" ORDER BY 1 DESC")
 | 
						|
		} else {
 | 
						|
			builder.Write(" ORDER BY 1 ASC")
 | 
						|
		}
 | 
						|
		writePerPageSQL(query, l.SQLStore, &builder)
 | 
						|
		if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&elements); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		retDTOs := make([]LibraryElementDTO, 0)
 | 
						|
		for _, element := range elements {
 | 
						|
			retDTOs = append(retDTOs, LibraryElementDTO{
 | 
						|
				ID:          element.ID,
 | 
						|
				OrgID:       element.OrgID,
 | 
						|
				FolderID:    element.FolderID,
 | 
						|
				FolderUID:   element.FolderUID,
 | 
						|
				UID:         element.UID,
 | 
						|
				Name:        element.Name,
 | 
						|
				Kind:        element.Kind,
 | 
						|
				Type:        element.Type,
 | 
						|
				Description: element.Description,
 | 
						|
				Model:       element.Model,
 | 
						|
				Version:     element.Version,
 | 
						|
				Meta: LibraryElementDTOMeta{
 | 
						|
					FolderName:          element.FolderName,
 | 
						|
					FolderUID:           element.FolderUID,
 | 
						|
					ConnectedDashboards: element.ConnectedDashboards,
 | 
						|
					Created:             element.Created,
 | 
						|
					Updated:             element.Updated,
 | 
						|
					CreatedBy: LibraryElementDTOMetaUser{
 | 
						|
						ID:        element.CreatedBy,
 | 
						|
						Name:      element.CreatedByName,
 | 
						|
						AvatarURL: dtos.GetGravatarUrl(element.CreatedByEmail),
 | 
						|
					},
 | 
						|
					UpdatedBy: LibraryElementDTOMetaUser{
 | 
						|
						ID:        element.UpdatedBy,
 | 
						|
						Name:      element.UpdatedByName,
 | 
						|
						AvatarURL: dtos.GetGravatarUrl(element.UpdatedByEmail),
 | 
						|
					},
 | 
						|
				},
 | 
						|
			})
 | 
						|
		}
 | 
						|
 | 
						|
		var libraryElements []LibraryElement
 | 
						|
		countBuilder := db.SQLBuilder{}
 | 
						|
		countBuilder.Write("SELECT * FROM library_element AS le")
 | 
						|
		countBuilder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id")
 | 
						|
		countBuilder.Write(` WHERE le.org_id=?`, signedInUser.OrgID)
 | 
						|
		writeKindSQL(query, &countBuilder)
 | 
						|
		writeSearchStringSQL(query, l.SQLStore, &countBuilder)
 | 
						|
		writeExcludeSQL(query, &countBuilder)
 | 
						|
		writeTypeFilterSQL(typeFilter, &countBuilder)
 | 
						|
		if err := folderFilter.writeFolderFilterSQL(true, &countBuilder); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if err := session.SQL(countBuilder.GetSQLString(), countBuilder.GetParams()...).Find(&libraryElements); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		result = LibraryElementSearchResult{
 | 
						|
			TotalCount: int64(len(libraryElements)),
 | 
						|
			Elements:   retDTOs,
 | 
						|
			Page:       query.page,
 | 
						|
			PerPage:    query.perPage,
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
 | 
						|
	return result, err
 | 
						|
}
 | 
						|
 | 
						|
func (l *LibraryElementService) handleFolderIDPatches(ctx context.Context, elementToPatch *LibraryElement, fromFolderID int64, toFolderID int64, user *user.SignedInUser) error {
 | 
						|
	// FolderID was not provided in the PATCH request
 | 
						|
	if toFolderID == -1 {
 | 
						|
		toFolderID = fromFolderID
 | 
						|
	}
 | 
						|
 | 
						|
	// FolderID was provided in the PATCH request
 | 
						|
	if toFolderID != -1 && toFolderID != fromFolderID {
 | 
						|
		if err := l.requireEditPermissionsOnFolder(ctx, user, toFolderID); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Always check permissions for the folder where library element resides
 | 
						|
	if err := l.requireEditPermissionsOnFolder(ctx, user, fromFolderID); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	elementToPatch.FolderID = toFolderID
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// patchLibraryElement updates a Library Element.
 | 
						|
func (l *LibraryElementService) patchLibraryElement(c context.Context, signedInUser *user.SignedInUser, cmd PatchLibraryElementCommand, uid string) (LibraryElementDTO, error) {
 | 
						|
	var dto LibraryElementDTO
 | 
						|
	if err := l.requireSupportedElementKind(cmd.Kind); err != nil {
 | 
						|
		return LibraryElementDTO{}, err
 | 
						|
	}
 | 
						|
	err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
 | 
						|
		elementInDB, err := getLibraryElement(l.SQLStore.GetDialect(), session, uid, signedInUser.OrgID)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if elementInDB.Version != cmd.Version {
 | 
						|
			return errLibraryElementVersionMismatch
 | 
						|
		}
 | 
						|
		updateUID := cmd.UID
 | 
						|
		if len(updateUID) == 0 {
 | 
						|
			updateUID = uid
 | 
						|
		} else if updateUID != uid {
 | 
						|
			if !util.IsValidShortUID(updateUID) {
 | 
						|
				return errLibraryElementInvalidUID
 | 
						|
			} else if util.IsShortUIDTooLong(updateUID) {
 | 
						|
				return errLibraryElementUIDTooLong
 | 
						|
			}
 | 
						|
 | 
						|
			_, err := getLibraryElement(l.SQLStore.GetDialect(), session, updateUID, signedInUser.OrgID)
 | 
						|
			if !errors.Is(err, ErrLibraryElementNotFound) {
 | 
						|
				return errLibraryElementAlreadyExists
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		var libraryElement = LibraryElement{
 | 
						|
			ID:          elementInDB.ID,
 | 
						|
			OrgID:       signedInUser.OrgID,
 | 
						|
			FolderID:    cmd.FolderID,
 | 
						|
			UID:         updateUID,
 | 
						|
			Name:        cmd.Name,
 | 
						|
			Kind:        elementInDB.Kind,
 | 
						|
			Type:        elementInDB.Type,
 | 
						|
			Description: elementInDB.Description,
 | 
						|
			Model:       cmd.Model,
 | 
						|
			Version:     elementInDB.Version + 1,
 | 
						|
			Created:     elementInDB.Created,
 | 
						|
			CreatedBy:   elementInDB.CreatedBy,
 | 
						|
			Updated:     time.Now(),
 | 
						|
			UpdatedBy:   signedInUser.UserID,
 | 
						|
		}
 | 
						|
 | 
						|
		if cmd.Name == "" {
 | 
						|
			libraryElement.Name = elementInDB.Name
 | 
						|
		}
 | 
						|
		if cmd.Model == nil {
 | 
						|
			libraryElement.Model = elementInDB.Model
 | 
						|
		}
 | 
						|
		if err := l.handleFolderIDPatches(c, &libraryElement, elementInDB.FolderID, cmd.FolderID, signedInUser); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if err := syncFieldsWithModel(&libraryElement); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if rowsAffected, err := session.ID(elementInDB.ID).Update(&libraryElement); err != nil {
 | 
						|
			if l.SQLStore.GetDialect().IsUniqueConstraintViolation(err) {
 | 
						|
				return errLibraryElementAlreadyExists
 | 
						|
			}
 | 
						|
			return err
 | 
						|
		} else if rowsAffected != 1 {
 | 
						|
			return ErrLibraryElementNotFound
 | 
						|
		}
 | 
						|
 | 
						|
		dto = LibraryElementDTO{
 | 
						|
			ID:          libraryElement.ID,
 | 
						|
			OrgID:       libraryElement.OrgID,
 | 
						|
			FolderID:    libraryElement.FolderID,
 | 
						|
			UID:         libraryElement.UID,
 | 
						|
			Name:        libraryElement.Name,
 | 
						|
			Kind:        libraryElement.Kind,
 | 
						|
			Type:        libraryElement.Type,
 | 
						|
			Description: libraryElement.Description,
 | 
						|
			Model:       libraryElement.Model,
 | 
						|
			Version:     libraryElement.Version,
 | 
						|
			Meta: LibraryElementDTOMeta{
 | 
						|
				ConnectedDashboards: elementInDB.ConnectedDashboards,
 | 
						|
				Created:             libraryElement.Created,
 | 
						|
				Updated:             libraryElement.Updated,
 | 
						|
				CreatedBy: LibraryElementDTOMetaUser{
 | 
						|
					ID:        elementInDB.CreatedBy,
 | 
						|
					Name:      elementInDB.CreatedByName,
 | 
						|
					AvatarURL: dtos.GetGravatarUrl(elementInDB.CreatedByEmail),
 | 
						|
				},
 | 
						|
				UpdatedBy: LibraryElementDTOMetaUser{
 | 
						|
					ID:        libraryElement.UpdatedBy,
 | 
						|
					Name:      signedInUser.Login,
 | 
						|
					AvatarURL: dtos.GetGravatarUrl(signedInUser.Email),
 | 
						|
				},
 | 
						|
			},
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
 | 
						|
	return dto, err
 | 
						|
}
 | 
						|
 | 
						|
// getConnections gets all connections for a Library Element.
 | 
						|
func (l *LibraryElementService) getConnections(c context.Context, signedInUser *user.SignedInUser, uid string) ([]LibraryElementConnectionDTO, error) {
 | 
						|
	connections := make([]LibraryElementConnectionDTO, 0)
 | 
						|
	err := l.SQLStore.WithDbSession(c, func(session *db.Session) error {
 | 
						|
		element, err := getLibraryElement(l.SQLStore.GetDialect(), session, uid, signedInUser.OrgID)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		var libraryElementConnections []libraryElementConnectionWithMeta
 | 
						|
		builder := db.NewSqlBuilder(l.Cfg, l.SQLStore.GetDialect())
 | 
						|
		builder.Write("SELECT lec.*, u1.login AS created_by_name, u1.email AS created_by_email, dashboard.uid AS connection_uid")
 | 
						|
		builder.Write(" FROM " + models.LibraryElementConnectionTableName + " AS lec")
 | 
						|
		builder.Write(" LEFT JOIN " + l.SQLStore.GetDialect().Quote("user") + " AS u1 ON lec.created_by = u1.id")
 | 
						|
		builder.Write(" INNER JOIN dashboard AS dashboard on lec.connection_id = dashboard.id")
 | 
						|
		builder.Write(` WHERE lec.element_id=?`, element.ID)
 | 
						|
		if signedInUser.OrgRole != org.RoleAdmin {
 | 
						|
			builder.WriteDashboardPermissionFilter(signedInUser, models.PERMISSION_VIEW)
 | 
						|
		}
 | 
						|
		if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&libraryElementConnections); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		for _, connection := range libraryElementConnections {
 | 
						|
			connections = append(connections, LibraryElementConnectionDTO{
 | 
						|
				ID:            connection.ID,
 | 
						|
				Kind:          connection.Kind,
 | 
						|
				ElementID:     connection.ElementID,
 | 
						|
				ConnectionID:  connection.ConnectionID,
 | 
						|
				ConnectionUID: connection.ConnectionUID,
 | 
						|
				Created:       connection.Created,
 | 
						|
				CreatedBy: LibraryElementDTOMetaUser{
 | 
						|
					ID:        connection.CreatedBy,
 | 
						|
					Name:      connection.CreatedByName,
 | 
						|
					AvatarURL: dtos.GetGravatarUrl(connection.CreatedByEmail),
 | 
						|
				},
 | 
						|
			})
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
 | 
						|
	return connections, err
 | 
						|
}
 | 
						|
 | 
						|
// getElementsForDashboardID gets all elements for a specific dashboard
 | 
						|
func (l *LibraryElementService) getElementsForDashboardID(c context.Context, dashboardID int64) (map[string]LibraryElementDTO, error) {
 | 
						|
	libraryElementMap := make(map[string]LibraryElementDTO)
 | 
						|
	err := l.SQLStore.WithDbSession(c, func(session *db.Session) error {
 | 
						|
		var libraryElements []LibraryElementWithMeta
 | 
						|
		sql := selectLibraryElementDTOWithMeta +
 | 
						|
			", coalesce(dashboard.title, 'General') AS folder_name" +
 | 
						|
			", coalesce(dashboard.uid, '') AS folder_uid" +
 | 
						|
			getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect()) +
 | 
						|
			" LEFT JOIN dashboard AS dashboard ON dashboard.id = le.folder_id" +
 | 
						|
			" INNER JOIN " + models.LibraryElementConnectionTableName + " AS lce ON lce.element_id = le.id AND lce.kind=1 AND lce.connection_id=?"
 | 
						|
		sess := session.SQL(sql, dashboardID)
 | 
						|
		err := sess.Find(&libraryElements)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		for _, element := range libraryElements {
 | 
						|
			libraryElementMap[element.UID] = LibraryElementDTO{
 | 
						|
				ID:          element.ID,
 | 
						|
				OrgID:       element.OrgID,
 | 
						|
				FolderID:    element.FolderID,
 | 
						|
				UID:         element.UID,
 | 
						|
				Name:        element.Name,
 | 
						|
				Kind:        element.Kind,
 | 
						|
				Type:        element.Type,
 | 
						|
				Description: element.Description,
 | 
						|
				Model:       element.Model,
 | 
						|
				Version:     element.Version,
 | 
						|
				Meta: LibraryElementDTOMeta{
 | 
						|
					FolderName:          element.FolderName,
 | 
						|
					FolderUID:           element.FolderUID,
 | 
						|
					ConnectedDashboards: element.ConnectedDashboards,
 | 
						|
					Created:             element.Created,
 | 
						|
					Updated:             element.Updated,
 | 
						|
					CreatedBy: LibraryElementDTOMetaUser{
 | 
						|
						ID:        element.CreatedBy,
 | 
						|
						Name:      element.CreatedByName,
 | 
						|
						AvatarURL: dtos.GetGravatarUrl(element.CreatedByEmail),
 | 
						|
					},
 | 
						|
					UpdatedBy: LibraryElementDTOMetaUser{
 | 
						|
						ID:        element.UpdatedBy,
 | 
						|
						Name:      element.UpdatedByName,
 | 
						|
						AvatarURL: dtos.GetGravatarUrl(element.UpdatedByEmail),
 | 
						|
					},
 | 
						|
				},
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
 | 
						|
	return libraryElementMap, err
 | 
						|
}
 | 
						|
 | 
						|
// connectElementsToDashboardID adds connections for all elements Library Elements in a Dashboard.
 | 
						|
func (l *LibraryElementService) connectElementsToDashboardID(c context.Context, signedInUser *user.SignedInUser, elementUIDs []string, dashboardID int64) error {
 | 
						|
	err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
 | 
						|
		_, err := session.Exec("DELETE FROM "+models.LibraryElementConnectionTableName+" WHERE kind=1 AND connection_id=?", dashboardID)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		for _, elementUID := range elementUIDs {
 | 
						|
			element, err := getLibraryElement(l.SQLStore.GetDialect(), session, elementUID, signedInUser.OrgID)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			if err := l.requireViewPermissionsOnFolder(c, signedInUser, element.FolderID); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			connection := libraryElementConnection{
 | 
						|
				ElementID:    element.ID,
 | 
						|
				Kind:         1,
 | 
						|
				ConnectionID: dashboardID,
 | 
						|
				Created:      time.Now(),
 | 
						|
				CreatedBy:    signedInUser.UserID,
 | 
						|
			}
 | 
						|
			if _, err := session.Insert(&connection); err != nil {
 | 
						|
				if l.SQLStore.GetDialect().IsUniqueConstraintViolation(err) {
 | 
						|
					return nil
 | 
						|
				}
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// disconnectElementsFromDashboardID deletes connections for all Library Elements in a Dashboard.
 | 
						|
func (l *LibraryElementService) disconnectElementsFromDashboardID(c context.Context, dashboardID int64) error {
 | 
						|
	return l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
 | 
						|
		_, err := session.Exec("DELETE FROM "+models.LibraryElementConnectionTableName+" WHERE kind=1 AND connection_id=?", dashboardID)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// deleteLibraryElementsInFolderUID deletes all Library Elements in a folder.
 | 
						|
func (l *LibraryElementService) deleteLibraryElementsInFolderUID(c context.Context, signedInUser *user.SignedInUser, folderUID string) error {
 | 
						|
	return l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
 | 
						|
		var folderUIDs []struct {
 | 
						|
			ID int64 `xorm:"id"`
 | 
						|
		}
 | 
						|
		err := session.SQL("SELECT id from dashboard WHERE uid=? AND org_id=? AND is_folder=?", folderUID, signedInUser.OrgID, l.SQLStore.GetDialect().BooleanStr(true)).Find(&folderUIDs)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		if len(folderUIDs) == 0 {
 | 
						|
			return dashboards.ErrFolderNotFound
 | 
						|
		}
 | 
						|
 | 
						|
		if len(folderUIDs) != 1 {
 | 
						|
			return fmt.Errorf("found %d folders, while expecting at most one", len(folderUIDs))
 | 
						|
		}
 | 
						|
 | 
						|
		folderID := folderUIDs[0].ID
 | 
						|
 | 
						|
		if err := l.requireEditPermissionsOnFolder(c, signedInUser, folderID); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		var connectionIDs []struct {
 | 
						|
			ConnectionID int64 `xorm:"connection_id"`
 | 
						|
		}
 | 
						|
		sql := "SELECT lec.connection_id FROM library_element AS le"
 | 
						|
		sql += " INNER JOIN " + models.LibraryElementConnectionTableName + " AS lec on le.id = lec.element_id"
 | 
						|
		sql += " WHERE le.folder_id=? AND le.org_id=?"
 | 
						|
		err = session.SQL(sql, folderID, signedInUser.OrgID).Find(&connectionIDs)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if len(connectionIDs) > 0 {
 | 
						|
			return ErrFolderHasConnectedLibraryElements
 | 
						|
		}
 | 
						|
 | 
						|
		var elementIDs []struct {
 | 
						|
			ID int64 `xorm:"id"`
 | 
						|
		}
 | 
						|
		err = session.SQL("SELECT id from library_element WHERE folder_id=? AND org_id=?", folderID, signedInUser.OrgID).Find(&elementIDs)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		for _, elementID := range elementIDs {
 | 
						|
			_, err := session.Exec("DELETE FROM "+models.LibraryElementConnectionTableName+" WHERE element_id=?", elementID.ID)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if _, err := session.Exec("DELETE FROM library_element WHERE folder_id=? AND org_id=?", folderID, signedInUser.OrgID); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
}
 |