mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: remove the entity kind registry (#79178)
This commit is contained in:
parent
2a2a132c61
commit
9849c954a3
@ -136,7 +136,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/store"
|
"github.com/grafana/grafana/pkg/services/store"
|
||||||
entityDB "github.com/grafana/grafana/pkg/services/store/entity/db"
|
entityDB "github.com/grafana/grafana/pkg/services/store/entity/db"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash"
|
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash"
|
||||||
"github.com/grafana/grafana/pkg/services/store/kind"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/resolver"
|
"github.com/grafana/grafana/pkg/services/store/resolver"
|
||||||
"github.com/grafana/grafana/pkg/services/store/sanitizer"
|
"github.com/grafana/grafana/pkg/services/store/sanitizer"
|
||||||
"github.com/grafana/grafana/pkg/services/supportbundles"
|
"github.com/grafana/grafana/pkg/services/supportbundles"
|
||||||
@ -342,7 +341,6 @@ var wireBasicSet = wire.NewSet(
|
|||||||
grpcserver.ProvideHealthService,
|
grpcserver.ProvideHealthService,
|
||||||
grpcserver.ProvideReflectionService,
|
grpcserver.ProvideReflectionService,
|
||||||
interceptors.ProvideAuthenticator,
|
interceptors.ProvideAuthenticator,
|
||||||
kind.ProvideService, // The registry of known kinds
|
|
||||||
entityDB.ProvideEntityDB,
|
entityDB.ProvideEntityDB,
|
||||||
wire.Bind(new(sqlstash.EntityDB), new(*entityDB.EntityDB)),
|
wire.Bind(new(sqlstash.EntityDB), new(*entityDB.EntityDB)),
|
||||||
sqlstash.ProvideSQLEntityServer,
|
sqlstash.ProvideSQLEntityServer,
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
package entity
|
package entity
|
||||||
|
|
||||||
|
import context "context"
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------------------------
|
||||||
// NOTE: the object store is in heavy development, and the locations will likely continue to move
|
// NOTE: the object store is in heavy development, and the locations will likely continue to move
|
||||||
//-----------------------------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StandardKindDashboard = "dashboard"
|
StandardKindDashboard = "dashboard"
|
||||||
StandardKindPlaylist = "playlist"
|
StandardKindPlaylist = "playlist"
|
||||||
StandardKindSnapshot = "snapshot"
|
StandardKindFolder = "folder"
|
||||||
StandardKindFolder = "folder"
|
|
||||||
StandardKindPreferences = "preferences"
|
|
||||||
|
|
||||||
// StandardKindDataSource: not a real kind yet, but used to define references from dashboards
|
// StandardKindDataSource: not a real kind yet, but used to define references from dashboards
|
||||||
// Types: influx, prometheus, testdata, ...
|
// Types: influx, prometheus, testdata, ...
|
||||||
@ -23,18 +19,6 @@ const (
|
|||||||
// Standalone panel is not an object kind yet -- library panel, or nested in dashboard
|
// Standalone panel is not an object kind yet -- library panel, or nested in dashboard
|
||||||
StandardKindPanel = "panel"
|
StandardKindPanel = "panel"
|
||||||
|
|
||||||
// entity.StandardKindSVG SVG file support
|
|
||||||
StandardKindSVG = "svg"
|
|
||||||
|
|
||||||
// StandardKindPNG PNG file support
|
|
||||||
StandardKindPNG = "png"
|
|
||||||
|
|
||||||
// StandardKindGeoJSON represents spatial data
|
|
||||||
StandardKindGeoJSON = "geojson"
|
|
||||||
|
|
||||||
// StandardKindDataFrame data frame
|
|
||||||
StandardKindDataFrame = "frame"
|
|
||||||
|
|
||||||
// StandardKindJSONObj generic json object
|
// StandardKindJSONObj generic json object
|
||||||
StandardKindJSONObj = "jsonobj"
|
StandardKindJSONObj = "jsonobj"
|
||||||
|
|
||||||
@ -64,28 +48,6 @@ const (
|
|||||||
ExternalEntityReferenceRuntime_Transformer = "transformer"
|
ExternalEntityReferenceRuntime_Transformer = "transformer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EntityKindInfo describes information needed from the object store
|
|
||||||
// All non-raw types will have a schema that can be used to validate
|
|
||||||
type EntityKindInfo struct {
|
|
||||||
// Unique short id for this kind
|
|
||||||
ID string `json:"id,omitempty"`
|
|
||||||
|
|
||||||
// Display name (may be equal to the ID)
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
|
|
||||||
// Kind description
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
|
|
||||||
// The format is not controlled by a schema
|
|
||||||
IsRaw bool `json:"isRaw,omitempty"`
|
|
||||||
|
|
||||||
// The preferred save extension (svg, png, parquet, etc) if one exists
|
|
||||||
FileExtension string `json:"fileExtension,omitempty"`
|
|
||||||
|
|
||||||
// The correct mime-type to return for raw objects
|
|
||||||
MimeType string `json:"mimeType,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// EntitySummaryBuilder will read an object, validate it, and return a summary, sanitized payload, or an error
|
// EntitySummaryBuilder will read an object, validate it, and return a summary, sanitized payload, or an error
|
||||||
// This should not include values that depend on system state, only the raw object
|
// This should not include values that depend on system state, only the raw object
|
||||||
type EntitySummaryBuilder = func(ctx context.Context, uid string, body []byte) (*EntitySummary, []byte, error)
|
type EntitySummaryBuilder = func(ctx context.Context, uid string, body []byte) (*EntitySummary, []byte, error)
|
||||||
|
@ -11,14 +11,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetEntityKindInfo() entity.EntityKindInfo {
|
|
||||||
return entity.EntityKindInfo{
|
|
||||||
ID: entity.StandardKindDashboard,
|
|
||||||
Name: "Dashboard",
|
|
||||||
Description: "Define a grafana dashboard layout",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This summary does not resolve old name as UID
|
// This summary does not resolve old name as UID
|
||||||
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
|
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
|
||||||
builder := NewStaticDashboardSummaryBuilder(&directLookup{}, true)
|
builder := NewStaticDashboardSummaryBuilder(&directLookup{}, true)
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
package dataframe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/store"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetEntityKindInfo() entity.EntityKindInfo {
|
|
||||||
return entity.EntityKindInfo{
|
|
||||||
ID: entity.StandardKindDataFrame,
|
|
||||||
Name: "Data frame",
|
|
||||||
Description: "Data frame",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
|
|
||||||
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
|
|
||||||
df := &data.Frame{}
|
|
||||||
err := json.Unmarshal(body, df)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
rows, err := df.RowLen()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := data.FrameToJSON(df, data.IncludeAll)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
summary := &entity.EntitySummary{
|
|
||||||
Kind: entity.StandardKindDataFrame,
|
|
||||||
Name: df.Name,
|
|
||||||
UID: uid,
|
|
||||||
Fields: map[string]string{
|
|
||||||
"rows": fmt.Sprint(rows),
|
|
||||||
"cols": fmt.Sprint(len(df.Fields)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if summary.Name == "" {
|
|
||||||
summary.Name = store.GuessNameFromUID(uid)
|
|
||||||
}
|
|
||||||
return summary, out, err
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package dataframe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDataFrameSummary(t *testing.T) {
|
|
||||||
df := data.NewFrame("http_requests_total",
|
|
||||||
data.NewField("timestamp", nil, []time.Time{time.Now(), time.Now(), time.Now()}).SetConfig(&data.FieldConfig{
|
|
||||||
DisplayName: "A time Column.",
|
|
||||||
}),
|
|
||||||
data.NewField("value", data.Labels{"service": "auth"}, []float64{1.0, 2.0, 3.0}),
|
|
||||||
data.NewField("category", data.Labels{"service": "auth"}, []string{"foo", "bar", "test"}),
|
|
||||||
data.NewField("valid", data.Labels{"service": "auth"}, []bool{true, false, true}),
|
|
||||||
)
|
|
||||||
|
|
||||||
in, err := data.FrameToJSON(df, data.IncludeAll)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
summary, out, err := GetEntitySummaryBuilder()(context.Background(), "somthing", in)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, in, out) // same json
|
|
||||||
|
|
||||||
asjson, err := json.MarshalIndent(summary, "", " ")
|
|
||||||
// fmt.Printf(string(asjson))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.JSONEq(t, `{
|
|
||||||
"UID": "somthing",
|
|
||||||
"kind": "frame",
|
|
||||||
"name": "http_requests_total",
|
|
||||||
"fields": {
|
|
||||||
"cols": "4",
|
|
||||||
"rows": "3"
|
|
||||||
}
|
|
||||||
}`, string(asjson))
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
// Package dummy provides a dummy kind useful for testing
|
|
||||||
//
|
|
||||||
// The dummy kind returns a complicated summary that can exercise most of the storage options
|
|
||||||
package dummy
|
|
@ -1,61 +0,0 @@
|
|||||||
package dummy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetEntityKindInfo(kind string) entity.EntityKindInfo {
|
|
||||||
return entity.EntityKindInfo{
|
|
||||||
ID: kind,
|
|
||||||
Name: kind,
|
|
||||||
Description: "Dummy kind used for testing.",
|
|
||||||
IsRaw: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEntitySummaryBuilder(kind string) entity.EntitySummaryBuilder {
|
|
||||||
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
|
|
||||||
summary := &entity.EntitySummary{
|
|
||||||
Name: fmt.Sprintf("Dummy: %s", kind),
|
|
||||||
Kind: kind,
|
|
||||||
Description: fmt.Sprintf("Wrote at %s", time.Now().Local().String()),
|
|
||||||
Labels: map[string]string{
|
|
||||||
"hello": "world",
|
|
||||||
"tag1": "",
|
|
||||||
"tag2": "",
|
|
||||||
},
|
|
||||||
Fields: map[string]string{
|
|
||||||
"field1": "a string",
|
|
||||||
"field2": "1.224",
|
|
||||||
"field4": "true",
|
|
||||||
},
|
|
||||||
Error: nil, // ignore for now
|
|
||||||
Nested: nil, // ignore for now
|
|
||||||
References: []*entity.EntityExternalReference{
|
|
||||||
{
|
|
||||||
Family: "ds",
|
|
||||||
Type: "influx",
|
|
||||||
Identifier: "xyz",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Family: entity.StandardKindPanel,
|
|
||||||
Type: "heatmap",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Family: entity.StandardKindPanel,
|
|
||||||
Type: "timeseries",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if summary.UID != "" && uid != summary.UID {
|
|
||||||
return summary, nil, fmt.Errorf("internal UID mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
return summary, body, nil
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package folder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/store"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Model struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEntityKindInfo() entity.EntityKindInfo {
|
|
||||||
return entity.EntityKindInfo{
|
|
||||||
ID: entity.StandardKindFolder,
|
|
||||||
Name: "Folder",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
|
|
||||||
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
|
|
||||||
obj := &Model{}
|
|
||||||
err := json.Unmarshal(body, obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err // unable to read object
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj.Name == "" {
|
|
||||||
obj.Name = store.GuessNameFromUID(uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
summary := &entity.EntitySummary{
|
|
||||||
Kind: entity.StandardKindFolder,
|
|
||||||
Name: obj.Name,
|
|
||||||
Description: obj.Description,
|
|
||||||
UID: uid,
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := json.MarshalIndent(obj, "", " ")
|
|
||||||
return summary, out, err
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package geojson
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/store"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetEntityKindInfo() entity.EntityKindInfo {
|
|
||||||
return entity.EntityKindInfo{
|
|
||||||
ID: entity.StandardKindGeoJSON,
|
|
||||||
Name: "GeoJSON",
|
|
||||||
Description: "JSON formatted spatial data",
|
|
||||||
FileExtension: ".geojson",
|
|
||||||
MimeType: "application/json",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Very basic geojson validator
|
|
||||||
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
|
|
||||||
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
|
|
||||||
var geojson map[string]any
|
|
||||||
err := json.Unmarshal(body, &geojson)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ftype, ok := geojson["type"].(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, fmt.Errorf("missing type")
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err = json.Marshal(geojson)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
summary := &entity.EntitySummary{
|
|
||||||
Kind: entity.StandardKindGeoJSON,
|
|
||||||
Name: store.GuessNameFromUID(uid),
|
|
||||||
UID: uid,
|
|
||||||
Fields: map[string]string{
|
|
||||||
"type": ftype,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if ftype == "FeatureCollection" {
|
|
||||||
features, ok := geojson["features"].([]any)
|
|
||||||
if ok {
|
|
||||||
summary.Fields["count"] = fmt.Sprint(len(features))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return summary, body, nil
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
package geojson
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGeoJSONSummary(t *testing.T) {
|
|
||||||
builder := GetEntitySummaryBuilder()
|
|
||||||
geo := []byte(`{"type":"FeatureCo`) // invalid
|
|
||||||
_, _, err := builder(context.Background(), "hello", geo)
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
geo = []byte(`{"type":"FeatureCollection","features":[]}`)
|
|
||||||
summary, out, err := builder(context.Background(), "hello", geo)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotEqual(t, geo, out) // wrote json
|
|
||||||
|
|
||||||
asjson, err := json.MarshalIndent(summary, "", " ")
|
|
||||||
//fmt.Printf(string(asjson))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.JSONEq(t, `{
|
|
||||||
"UID": "hello",
|
|
||||||
"kind": "geojson",
|
|
||||||
"name": "hello",
|
|
||||||
"fields": {
|
|
||||||
"type": "FeatureCollection",
|
|
||||||
"count": "0"
|
|
||||||
}
|
|
||||||
}`, string(asjson))
|
|
||||||
|
|
||||||
// Ignore gosec warning G304 since it's a test
|
|
||||||
// nolint:gosec
|
|
||||||
airports, err := os.ReadFile("../../../../../public/gazetteer/airports.geojson")
|
|
||||||
require.NoError(t, err)
|
|
||||||
summary, _, err = builder(context.Background(), "gaz/airports.geojson", airports)
|
|
||||||
require.NoError(t, err)
|
|
||||||
asjson, err = json.MarshalIndent(summary, "", " ")
|
|
||||||
//fmt.Printf(string(asjson))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.JSONEq(t, `{
|
|
||||||
"UID": "gaz/airports.geojson",
|
|
||||||
"kind": "geojson",
|
|
||||||
"name": "airports",
|
|
||||||
"fields": {
|
|
||||||
"type": "FeatureCollection",
|
|
||||||
"count": "888"
|
|
||||||
}
|
|
||||||
}`, string(asjson))
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package jsonobj
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/store"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetEntityKindInfo() entity.EntityKindInfo {
|
|
||||||
return entity.EntityKindInfo{
|
|
||||||
ID: entity.StandardKindJSONObj,
|
|
||||||
Name: "JSON Object",
|
|
||||||
Description: "JSON Object",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
|
|
||||||
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
|
|
||||||
v := make(map[string]any)
|
|
||||||
err := json.Unmarshal(body, &v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := json.MarshalIndent(v, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return &entity.EntitySummary{
|
|
||||||
Kind: entity.StandardKindJSONObj,
|
|
||||||
Name: store.GuessNameFromUID(uid),
|
|
||||||
UID: uid,
|
|
||||||
}, out, err
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package jsonobj
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDataFrameSummary(t *testing.T) {
|
|
||||||
// Just creating a JSON blob
|
|
||||||
df := data.NewFrame("http_requests_total",
|
|
||||||
data.NewField("timestamp", nil, []time.Time{time.Now(), time.Now(), time.Now()}).SetConfig(&data.FieldConfig{
|
|
||||||
DisplayName: "A time Column.",
|
|
||||||
}),
|
|
||||||
data.NewField("value", data.Labels{"service": "auth"}, []float64{1.0, 2.0, 3.0}),
|
|
||||||
data.NewField("category", data.Labels{"service": "auth"}, []string{"foo", "bar", "test"}),
|
|
||||||
data.NewField("valid", data.Labels{"service": "auth"}, []bool{true, false, true}),
|
|
||||||
)
|
|
||||||
in, err := data.FrameToJSON(df, data.IncludeAll)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
summary, out, err := GetEntitySummaryBuilder()(context.Background(), "path/to/item", in)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.JSONEq(t, string(in), string(out)) // same json
|
|
||||||
|
|
||||||
asjson, err := json.MarshalIndent(summary, "", " ")
|
|
||||||
// fmt.Printf(string(asjson))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.JSONEq(t, `{
|
|
||||||
"name": "item",
|
|
||||||
"UID": "path/to/item",
|
|
||||||
"kind": "jsonobj"
|
|
||||||
}`, string(asjson))
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package png
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"image/png"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/store"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetEntityKindInfo() entity.EntityKindInfo {
|
|
||||||
return entity.EntityKindInfo{
|
|
||||||
ID: entity.StandardKindPNG,
|
|
||||||
Name: "PNG",
|
|
||||||
Description: "PNG Image file",
|
|
||||||
IsRaw: true,
|
|
||||||
FileExtension: "png",
|
|
||||||
MimeType: "image/png",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SVG sanitizer based on the rendering service
|
|
||||||
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
|
|
||||||
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
|
|
||||||
img, err := png.Decode(bytes.NewReader(body))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
size := img.Bounds().Size()
|
|
||||||
summary := &entity.EntitySummary{
|
|
||||||
Kind: entity.StandardKindSVG,
|
|
||||||
Name: store.GuessNameFromUID(uid),
|
|
||||||
UID: uid,
|
|
||||||
Fields: map[string]string{
|
|
||||||
"width": fmt.Sprint(size.X),
|
|
||||||
"height": fmt.Sprint(size.Y),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return summary, body, nil
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package png
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPNGSummary(t *testing.T) {
|
|
||||||
const gopher = `iVBORw0KGgoAAAANSUhEUgAAAEsAAAA8CAAAAAALAhhPAAAFfUlEQVRYw62XeWwUVRzHf2+OPbo9d7tsWyiyaZti6eWGAhISoIGKECEKCAiJJkYTiUgTMYSIosYYBBIUIxoSPIINEBDi2VhwkQrVsj1ESgu9doHWdrul7ba73WNm3vOPtsseM9MdwvvrzTs+8/t95ze/33sI5BqiabU6m9En8oNjduLnAEDLUsQXFF8tQ5oxK3vmnNmDSMtrncks9Hhtt/qeWZapHb1ha3UqYSWVl2ZmpWgaXMXGohQAvmeop3bjTRtv6SgaK/Pb9/bFzUrYslbFAmHPp+3WhAYdr+7GN/YnpN46Opv55VDsJkoEpMrY/vO2BIYQ6LLvm0ThY3MzDzzeSJeeWNyTkgnIE5ePKsvKlcg/0T9QMzXalwXMlj54z4c0rh/mzEfr+FgWEz2w6uk8dkzFAgcARAgNp1ZYef8bH2AgvuStbc2/i6CiWGj98y2tw2l4FAXKkQBIf+exyRnteY83LfEwDQAYCoK+P6bxkZm/0966LxcAAILHB56kgD95PPxltuYcMtFTWw/FKkY/6Opf3GGd9ZF+Qp6mzJxzuRSractOmJrH1u8XTvWFHINNkLQLMR+XHXvfPPHw967raE1xxwtA36IMRfkAAG29/7mLuQcb2WOnsJReZGfpiHsSBX81cvMKywYZHhX5hFPtOqPGWZCXnhWGAu6lX91ElKXSalcLXu3UaOXVay57ZSe5f6Gpx7J2MXAsi7EqSp09b/MirKSyJfnfEEgeDjl8FgDAfvewP03zZ+AJ0m9aFRM8eEHBDRKjfcreDXnZdQuAxXpT2NRJ7xl3UkLBhuVGU16gZiGOgZmrSbRdqkILuL/yYoSXHHkl9KXgqNu3PB8oRg0geC5vFmLjad6mUyTKLmF3OtraWDIfACyXqmephaDABawfpi6tqqBZytfQMqOz6S09iWXhktrRaB8Xz4Yi/8gyABDm5NVe6qq/3VzPrcjELWrebVuyY2T7ar4zQyybUCtsQ5Es1FGaZVrRVQwAgHGW2ZCRZshI5bGQi7HesyE972pOSeMM0dSktlzxRdrlqb3Osa6CCS8IJoQQQgBAbTAa5l5epO34rJszibJI8rxLfGzcp1dRosutGeb2VDNgqYrwTiPNsLxXiPi3dz7LiS1WBRBDBOnqEjyy3aQb+/bLiJzz9dIkscVBBLxMfSEac7kO4Fpkngi0ruNBeSOal+u8jgOuqPz12nryMLCniEjtOOOmpt+KEIqsEdocJjYXwrh9OZqWJQyPCTo67LNS/TdxLAv6R5ZNK9npEjbYdT33gRo4o5oTqR34R+OmaSzDBWsAIPhuRcgyoteNi9gF0KzNYWVItPf2TLoXEg+7isNC7uJkgo1iQWOfRSP9NR11RtbZZ3OMG/VhL6jvx+J1m87+RCfJChAtEBQkSBX2PnSiihc/Twh3j0h7qdYQAoRVsRGmq7HU2QRbaxVGa1D6nIOqaIWRjyRZpHMQKWKpZM5feA+lzC4ZFultV8S6T0mzQGhQohi5I8iw+CsqBSxhFMuwyLgSwbghGb0AiIKkSDmGZVmJSiKihsiyOAUs70UkywooYP0bii9GdH4sfr1UNysd3fUyLLMQN+rsmo3grHl9VNJHbbwxoa47Vw5gupIqrZcjPh9R4Nye3nRDk199V+aetmvVtDRE8/+cbgAAgMIWGb3UA0MGLE9SCbWX670TDy1y98c3D27eppUjsZ6fql3jcd5rUe7+ZIlLNQny3Rd+E5Tct3WVhTM5RBCEdiEK0b6B+/ca2gYU393nFj/n1AygRQxPIUA043M42u85+z2SnssKrPl8Mx76NL3E6eXc3be7OD+H4WHbJkKI8AU8irbITQjZ+0hQcPEgId/Fn/pl9crKH02+5o2b9T/eMx7pKoskYgAAAABJRU5ErkJggg==`
|
|
||||||
img, err := base64.StdEncoding.DecodeString(gopher)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
summary, out, err := GetEntitySummaryBuilder()(context.Background(), "hello.png", img)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, img, out) // same image
|
|
||||||
|
|
||||||
asjson, err := json.MarshalIndent(summary, "", " ")
|
|
||||||
//fmt.Printf(string(asjson))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.JSONEq(t, `{
|
|
||||||
"UID": "hello.png",
|
|
||||||
"kind": "svg",
|
|
||||||
"name": "hello",
|
|
||||||
"fields": {
|
|
||||||
"height": "60",
|
|
||||||
"width": "75"
|
|
||||||
}
|
|
||||||
}`, string(asjson))
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package preferences
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/kinds/preferences"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetEntityKindInfo() entity.EntityKindInfo {
|
|
||||||
return entity.EntityKindInfo{
|
|
||||||
ID: entity.StandardKindPreferences,
|
|
||||||
Name: "Preferences",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
|
|
||||||
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
|
|
||||||
if uid != "default" {
|
|
||||||
if !(strings.HasPrefix(uid, "user-") || strings.HasPrefix(uid, "team-")) {
|
|
||||||
return nil, nil, fmt.Errorf("expecting UID: default, user-{#}, or team-{#}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
obj := &preferences.Spec{}
|
|
||||||
err := json.Unmarshal(body, obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err // unable to read object
|
|
||||||
}
|
|
||||||
|
|
||||||
summary := &entity.EntitySummary{
|
|
||||||
Kind: entity.StandardKindPreferences,
|
|
||||||
Name: uid, // team-${id} | user-${id}
|
|
||||||
UID: uid,
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj.HomeDashboardUID != nil && *obj.HomeDashboardUID != "" {
|
|
||||||
summary.References = append(summary.References, &entity.EntityExternalReference{
|
|
||||||
Family: entity.StandardKindDashboard,
|
|
||||||
Identifier: *obj.HomeDashboardUID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := json.MarshalIndent(obj, "", " ")
|
|
||||||
return summary, out, err
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,182 +0,0 @@
|
|||||||
package kind
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/rendering"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/kind/dashboard"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/kind/dataframe"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/kind/folder"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/kind/geojson"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/kind/jsonobj"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/kind/png"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/kind/preferences"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/kind/snapshot"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/kind/svg"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
)
|
|
||||||
|
|
||||||
type KindRegistry interface {
|
|
||||||
Register(info entity.EntityKindInfo, builder entity.EntitySummaryBuilder) error
|
|
||||||
GetSummaryBuilder(kind string) entity.EntitySummaryBuilder
|
|
||||||
GetInfo(kind string) (entity.EntityKindInfo, error)
|
|
||||||
GetFromExtension(suffix string) (entity.EntityKindInfo, error)
|
|
||||||
GetKinds() []entity.EntityKindInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewKindRegistry() KindRegistry {
|
|
||||||
kinds := make(map[string]*kindValues)
|
|
||||||
kinds[entity.StandardKindDashboard] = &kindValues{
|
|
||||||
info: dashboard.GetEntityKindInfo(),
|
|
||||||
builder: dashboard.GetEntitySummaryBuilder(),
|
|
||||||
}
|
|
||||||
kinds[entity.StandardKindSnapshot] = &kindValues{
|
|
||||||
info: snapshot.GetEntityKindInfo(),
|
|
||||||
builder: snapshot.GetEntitySummaryBuilder(),
|
|
||||||
}
|
|
||||||
kinds[entity.StandardKindFolder] = &kindValues{
|
|
||||||
info: folder.GetEntityKindInfo(),
|
|
||||||
builder: folder.GetEntitySummaryBuilder(),
|
|
||||||
}
|
|
||||||
kinds[entity.StandardKindPNG] = &kindValues{
|
|
||||||
info: png.GetEntityKindInfo(),
|
|
||||||
builder: png.GetEntitySummaryBuilder(),
|
|
||||||
}
|
|
||||||
kinds[entity.StandardKindGeoJSON] = &kindValues{
|
|
||||||
info: geojson.GetEntityKindInfo(),
|
|
||||||
builder: geojson.GetEntitySummaryBuilder(),
|
|
||||||
}
|
|
||||||
kinds[entity.StandardKindDataFrame] = &kindValues{
|
|
||||||
info: dataframe.GetEntityKindInfo(),
|
|
||||||
builder: dataframe.GetEntitySummaryBuilder(),
|
|
||||||
}
|
|
||||||
kinds[entity.StandardKindJSONObj] = &kindValues{
|
|
||||||
info: jsonobj.GetEntityKindInfo(),
|
|
||||||
builder: jsonobj.GetEntitySummaryBuilder(),
|
|
||||||
}
|
|
||||||
kinds[entity.StandardKindPreferences] = &kindValues{
|
|
||||||
info: preferences.GetEntityKindInfo(),
|
|
||||||
builder: preferences.GetEntitySummaryBuilder(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a registry
|
|
||||||
reg := ®istry{
|
|
||||||
mutex: sync.RWMutex{},
|
|
||||||
kinds: kinds,
|
|
||||||
}
|
|
||||||
reg.updateInfoArray()
|
|
||||||
return reg
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO? This could be a zero dependency service that others are responsible for configuring
|
|
||||||
func ProvideService(cfg *setting.Cfg, renderer rendering.Service) KindRegistry {
|
|
||||||
reg := NewKindRegistry()
|
|
||||||
|
|
||||||
// Register SVG support
|
|
||||||
//-----------------------
|
|
||||||
info := svg.GetEntityKindInfo()
|
|
||||||
allowUnsanitizedSvgUpload := cfg != nil && cfg.Storage.AllowUnsanitizedSvgUpload
|
|
||||||
support := svg.GetEntitySummaryBuilder(allowUnsanitizedSvgUpload, renderer)
|
|
||||||
_ = reg.Register(info, support)
|
|
||||||
|
|
||||||
return reg
|
|
||||||
}
|
|
||||||
|
|
||||||
type kindValues struct {
|
|
||||||
info entity.EntityKindInfo
|
|
||||||
builder entity.EntitySummaryBuilder
|
|
||||||
}
|
|
||||||
|
|
||||||
type registry struct {
|
|
||||||
mutex sync.RWMutex
|
|
||||||
kinds map[string]*kindValues
|
|
||||||
info []entity.EntityKindInfo
|
|
||||||
suffix map[string]entity.EntityKindInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *registry) updateInfoArray() {
|
|
||||||
suffix := make(map[string]entity.EntityKindInfo)
|
|
||||||
info := make([]entity.EntityKindInfo, 0, len(r.kinds))
|
|
||||||
for _, v := range r.kinds {
|
|
||||||
info = append(info, v.info)
|
|
||||||
if v.info.FileExtension != "" {
|
|
||||||
suffix[v.info.FileExtension] = v.info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Slice(info, func(i, j int) bool {
|
|
||||||
return info[i].ID < info[j].ID
|
|
||||||
})
|
|
||||||
r.info = info
|
|
||||||
r.suffix = suffix
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *registry) Register(info entity.EntityKindInfo, builder entity.EntitySummaryBuilder) error {
|
|
||||||
if info.ID == "" || builder == nil {
|
|
||||||
return fmt.Errorf("invalid kind")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.mutex.Lock()
|
|
||||||
defer r.mutex.Unlock()
|
|
||||||
|
|
||||||
if r.kinds[info.ID] != nil {
|
|
||||||
return fmt.Errorf("already exits")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.kinds[info.ID] = &kindValues{
|
|
||||||
info: info,
|
|
||||||
builder: builder,
|
|
||||||
}
|
|
||||||
r.updateInfoArray()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSummaryBuilder returns a builder or nil if not found
|
|
||||||
func (r *registry) GetSummaryBuilder(kind string) entity.EntitySummaryBuilder {
|
|
||||||
r.mutex.RLock()
|
|
||||||
defer r.mutex.RUnlock()
|
|
||||||
|
|
||||||
v, ok := r.kinds[kind]
|
|
||||||
if !ok {
|
|
||||||
// fallback to default
|
|
||||||
v, ok = r.kinds[entity.StandardKindJSONObj]
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
return v.builder
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInfo returns the registered info
|
|
||||||
func (r *registry) GetInfo(kind string) (entity.EntityKindInfo, error) {
|
|
||||||
r.mutex.RLock()
|
|
||||||
defer r.mutex.RUnlock()
|
|
||||||
|
|
||||||
v, ok := r.kinds[kind]
|
|
||||||
if ok {
|
|
||||||
return v.info, nil
|
|
||||||
}
|
|
||||||
return entity.EntityKindInfo{}, fmt.Errorf("not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInfo returns the registered info
|
|
||||||
func (r *registry) GetFromExtension(suffix string) (entity.EntityKindInfo, error) {
|
|
||||||
r.mutex.RLock()
|
|
||||||
defer r.mutex.RUnlock()
|
|
||||||
|
|
||||||
v, ok := r.suffix[suffix]
|
|
||||||
if ok {
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
return entity.EntityKindInfo{}, fmt.Errorf("not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSummaryBuilder returns a builder or nil if not found
|
|
||||||
func (r *registry) GetKinds() []entity.EntityKindInfo {
|
|
||||||
r.mutex.RLock()
|
|
||||||
defer r.mutex.RUnlock()
|
|
||||||
|
|
||||||
return r.info // returns a copy of the array
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package kind
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/kind/dummy"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestKindRegistry(t *testing.T) {
|
|
||||||
registry := NewKindRegistry()
|
|
||||||
err := registry.Register(dummy.GetEntityKindInfo("test"), dummy.GetEntitySummaryBuilder("test"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ids := []string{}
|
|
||||||
for _, k := range registry.GetKinds() {
|
|
||||||
ids = append(ids, k.ID)
|
|
||||||
}
|
|
||||||
require.Equal(t, []string{
|
|
||||||
"dashboard",
|
|
||||||
"folder",
|
|
||||||
"frame",
|
|
||||||
"geojson",
|
|
||||||
"jsonobj",
|
|
||||||
"png",
|
|
||||||
"preferences",
|
|
||||||
"snapshot",
|
|
||||||
"test",
|
|
||||||
}, ids)
|
|
||||||
|
|
||||||
// Check that we registered a test item
|
|
||||||
info, err := registry.GetInfo("test")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "test", info.Name)
|
|
||||||
require.True(t, info.IsRaw)
|
|
||||||
|
|
||||||
// Get by suffix
|
|
||||||
info, err = registry.GetFromExtension("png")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "PNG", info.Name)
|
|
||||||
require.True(t, info.IsRaw)
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
package snapshot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A snapshot is a dashboard with no external queries and a few additional properties
|
|
||||||
type Model struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
DeleteKey string `json:"deleteKey"`
|
|
||||||
ExternalURL string `json:"externalURL"`
|
|
||||||
Expires int64 `json:"expires,omitempty"` // time that this expires
|
|
||||||
DashboardUID string `json:"dashboard,omitempty"`
|
|
||||||
Snapshot json.RawMessage `json:"snapshot,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEntityKindInfo() entity.EntityKindInfo {
|
|
||||||
return entity.EntityKindInfo{
|
|
||||||
ID: entity.StandardKindSnapshot,
|
|
||||||
Name: "Snapshot",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
|
|
||||||
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
|
|
||||||
obj := &Model{}
|
|
||||||
err := json.Unmarshal(body, obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err // unable to read object
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj.Name == "" {
|
|
||||||
return nil, nil, fmt.Errorf("expected snapshot name")
|
|
||||||
}
|
|
||||||
if obj.DeleteKey == "" {
|
|
||||||
return nil, nil, fmt.Errorf("expected delete key")
|
|
||||||
}
|
|
||||||
|
|
||||||
summary := &entity.EntitySummary{
|
|
||||||
Kind: entity.StandardKindFolder,
|
|
||||||
Name: obj.Name,
|
|
||||||
Description: obj.Description,
|
|
||||||
UID: uid,
|
|
||||||
Fields: map[string]string{
|
|
||||||
"deleteKey": obj.DeleteKey,
|
|
||||||
"externalURL": obj.ExternalURL,
|
|
||||||
"expires": fmt.Sprint(obj.Expires),
|
|
||||||
},
|
|
||||||
References: []*entity.EntityExternalReference{
|
|
||||||
{Family: entity.StandardKindDashboard, Identifier: obj.DashboardUID},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep the original body
|
|
||||||
return summary, body, err
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
package svg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/rendering"
|
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetEntityKindInfo() entity.EntityKindInfo {
|
|
||||||
return entity.EntityKindInfo{
|
|
||||||
ID: entity.StandardKindSVG,
|
|
||||||
Name: "SVG",
|
|
||||||
Description: "Scalable Vector Graphics",
|
|
||||||
IsRaw: true,
|
|
||||||
FileExtension: "svg",
|
|
||||||
MimeType: "image/svg+xml",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SVG sanitizer based on the rendering service
|
|
||||||
func GetEntitySummaryBuilder(allowUnsanitizedSvgUpload bool, renderer rendering.Service) entity.EntitySummaryBuilder {
|
|
||||||
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
|
|
||||||
if !IsSVG(body) {
|
|
||||||
return nil, nil, fmt.Errorf("invalid svg")
|
|
||||||
}
|
|
||||||
|
|
||||||
// When a renderer exists, we can return a sanitized version
|
|
||||||
var sanitized []byte
|
|
||||||
if renderer != nil {
|
|
||||||
rsp, err := renderer.SanitizeSVG(ctx, &rendering.SanitizeSVGRequest{
|
|
||||||
Content: body,
|
|
||||||
})
|
|
||||||
if err != nil && !allowUnsanitizedSvgUpload {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
sanitized = rsp.Sanitized
|
|
||||||
}
|
|
||||||
if sanitized == nil {
|
|
||||||
if !allowUnsanitizedSvgUpload {
|
|
||||||
return nil, nil, fmt.Errorf("unable to sanitize svg")
|
|
||||||
}
|
|
||||||
sanitized = body
|
|
||||||
}
|
|
||||||
|
|
||||||
return &entity.EntitySummary{
|
|
||||||
Kind: entity.StandardKindSVG,
|
|
||||||
Name: guessNameFromUID(uid),
|
|
||||||
UID: uid,
|
|
||||||
}, sanitized, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func guessNameFromUID(uid string) string {
|
|
||||||
sidx := strings.LastIndex(uid, "/") + 1
|
|
||||||
didx := strings.LastIndex(uid, ".")
|
|
||||||
if didx > sidx && didx != sidx {
|
|
||||||
return uid[sidx:didx]
|
|
||||||
}
|
|
||||||
if sidx > 0 {
|
|
||||||
return uid[sidx:]
|
|
||||||
}
|
|
||||||
return uid
|
|
||||||
}
|
|
@ -9,8 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/filestorage"
|
"github.com/grafana/grafana/pkg/infra/filestorage"
|
||||||
"github.com/grafana/grafana/pkg/services/store/kind/svg"
|
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -52,7 +52,7 @@ func fail(reason string) validationResult {
|
|||||||
|
|
||||||
func (s *standardStorageService) detectMimeType(ctx context.Context, user *user.SignedInUser, uploadRequest *UploadRequest) string {
|
func (s *standardStorageService) detectMimeType(ctx context.Context, user *user.SignedInUser, uploadRequest *UploadRequest) string {
|
||||||
if strings.HasSuffix(uploadRequest.Path, ".svg") {
|
if strings.HasSuffix(uploadRequest.Path, ".svg") {
|
||||||
if svg.IsSVG(uploadRequest.Contents) {
|
if util.IsSVG(uploadRequest.Contents) {
|
||||||
return "image/svg+xml"
|
return "image/svg+xml"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
//FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
//FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
//OTHER DEALINGS IN THE SOFTWARE.
|
//OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
package svg
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
Loading…
Reference in New Issue
Block a user