Files
grafana/pkg/registry/apis/dashboard/datasources.go
T
Oscar Kilhed e09905df35 SchemaV2: Add library panel repeat options to v2 schema during conversion (#114109)
* Add library panel repeat options to v2 schema during conversion

* use any instead of interface{}

* change to common.Unstructured instead of byte[] for model field

* Fix the tests and let the library panel behavior fetch repeat options in public and scripted dashboards

* fix library panel differences between backend and frontend conversion
2025-11-21 11:41:03 +01:00

148 lines
4.0 KiB
Go

package dashboard
import (
"context"
"encoding/json"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/libraryelements"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
)
type datasourceIndexProvider struct {
datasourceService datasources.DataSourceService
}
// Index builds a datasource index directly from the datasource service query.
// This is more efficient than GetDataSourceInfo + NewDatasourceIndex as it avoids
// creating an intermediate slice and iterates over the datasources only once.
func (d *datasourceIndexProvider) Index(ctx context.Context) *schemaversion.DatasourceIndex {
// Extract namespace info from context to get OrgID
nsInfo, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
// If namespace info is not available, return empty index
return &schemaversion.DatasourceIndex{
ByName: make(map[string]*schemaversion.DataSourceInfo),
ByUID: make(map[string]*schemaversion.DataSourceInfo),
}
}
// Use GetDataSources with OrgID query
query := datasources.GetDataSourcesQuery{
OrgID: nsInfo.OrgID,
}
dataSources, err := d.datasourceService.GetDataSources(ctx, &query)
if err != nil {
return &schemaversion.DatasourceIndex{
ByName: make(map[string]*schemaversion.DataSourceInfo),
ByUID: make(map[string]*schemaversion.DataSourceInfo),
}
}
// Build index directly without intermediate slice allocation
// Single iteration over datasources populates all maps
index := &schemaversion.DatasourceIndex{
ByName: make(map[string]*schemaversion.DataSourceInfo, len(dataSources)),
ByUID: make(map[string]*schemaversion.DataSourceInfo, len(dataSources)),
}
for _, ds := range dataSources {
dsInfo := &schemaversion.DataSourceInfo{
Name: ds.Name,
UID: ds.UID,
ID: ds.ID,
Type: ds.Type,
Default: ds.IsDefault,
APIVersion: ds.APIVersion,
}
// Index by name if present
if ds.Name != "" {
index.ByName[ds.Name] = dsInfo
}
// Index by UID if present
if ds.UID != "" {
index.ByUID[ds.UID] = dsInfo
}
// Track default datasource
if ds.IsDefault {
index.DefaultDS = dsInfo
}
}
return index
}
type libraryElementIndexProvider struct {
libraryElementService libraryelements.Service
}
func (l *libraryElementIndexProvider) GetLibraryElementInfo(ctx context.Context) []schemaversion.LibraryElementInfo {
if l.libraryElementService == nil {
return []schemaversion.LibraryElementInfo{}
}
nsInfo, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return []schemaversion.LibraryElementInfo{}
}
user := &identity.StaticRequester{
OrgID: nsInfo.OrgID,
OrgRole: identity.RoleAdmin,
}
const perPage = 1_000
info := make([]schemaversion.LibraryElementInfo, 0)
// For some reason the index starts at page 1 here:
// https://github.com/grafana/grafana/blob/main/pkg/services/libraryelements/database.go#L418
page := 1
for {
result, err := l.libraryElementService.GetAllElements(ctx, user, model.SearchLibraryElementsQuery{
PerPage: perPage,
Page: page,
})
if err != nil {
return []schemaversion.LibraryElementInfo{}
}
for _, elem := range result.Elements {
var modelUnstructured v0alpha1.Unstructured
if len(elem.Model) > 0 {
var modelObj map[string]any
if err := json.Unmarshal(elem.Model, &modelObj); err == nil {
modelUnstructured.Object = modelObj
}
}
info = append(info, schemaversion.LibraryElementInfo{
UID: elem.UID,
Name: elem.Name,
Kind: elem.Kind,
Type: elem.Type,
Description: elem.Description,
FolderUID: elem.FolderUID,
Model: modelUnstructured,
})
}
if len(result.Elements) < perPage {
break
}
page++
// Bound pages to avoid inf loops
if page > 100 {
break
}
}
return info
}