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