mirror of https://github.com/grafana/grafana
Chore: remove the entity kind registry (#79178)
parent
2a2a132c61
commit
9849c954a3
@ -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 |
||||
} |
Loading…
Reference in new issue