From b24be6c0fc6534cd0105afc80d418d518b964091 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Sat, 8 Oct 2022 09:05:46 -0700 Subject: [PATCH] ObjectStore: add a kind registry (#56507) --- .../src/raw/playlist/x/playlist.gen.ts | 1 + pkg/coremodel/playlist/coremodel.cue | 1 + pkg/coremodel/playlist/playlist_gen.go | 1 + pkg/models/object.go | 119 ++++++ pkg/server/wire.go | 2 + pkg/services/export/export_dash.go | 14 +- .../querylibrary/querylibraryimpl/service.go | 10 +- pkg/services/querylibrary/types.go | 4 +- pkg/services/searchV2/bluge.go | 10 +- pkg/services/searchV2/extract/types.go | 36 -- pkg/services/searchV2/filter.go | 12 +- pkg/services/searchV2/index.go | 22 +- pkg/services/searchV2/index_test.go | 54 +-- pkg/services/searchV2/object/dashboard.go | 71 ---- pkg/services/store/{object => }/auth.go | 2 +- .../kind/dashboard}/dashboard.go | 74 ++-- .../kind/dashboard}/dashboard_test.go | 14 +- .../kind/dashboard}/ds_lookup.go | 2 +- .../{object => kind/dashboard}/reference.go | 16 +- pkg/services/store/kind/dashboard/summary.go | 99 +++++ .../kind/dashboard/summary_test.go} | 32 +- .../kind/dashboard}/targets.go | 24 +- ...lected-multi-datasource-variable-info.json | 0 ...ll-selected-multi-datasource-variable.json | 0 ...ected-single-datasource-variable-info.json | 0 ...l-selected-single-datasource-variable.json | 0 .../check-string-datasource-id-info.json | 0 .../testdata/check-string-datasource-id.json | 0 .../testdata/datasource-variable-info.json | 0 ...asource-variable-no-curly-braces-info.json | 0 .../datasource-variable-no-curly-braces.json | 0 .../testdata/datasource-variable.json | 0 .../default-datasource-variable-info.json | 0 .../testdata/default-datasource-variable.json | 0 .../testdata/devdash-all-panels-info.json | 0 .../devdash-graph-shared-tooltips-info.json | 0 .../empty-datasource-variable-info.json | 0 .../testdata/empty-datasource-variable.json | 0 .../gdev-walk-graph-gradient-area-fills.json | 93 +++++ .../gdev-walk-graph-shared-tooltips.json | 181 ++++++++ .../gdev-walk-graph-time-regions.json | 110 +++++ .../testdata/gdev-walk-graph_tests.json | 385 ++++++++++++++++++ .../testdata/gdev-walk-graph_y_axis.json | 177 ++++++++ .../mixed-datasource-with-variable-info.json | 0 .../mixed-datasource-with-variable.json | 0 .../panels-without-datasources-info.json | 0 .../testdata/panels-without-datasources.json | 0 .../repeated-datasource-variables-info.json | 0 ...atasource-variables-with-default-info.json | 0 ...ted-datasource-variables-with-default.json | 0 .../repeated-datasource-variables.json | 0 .../special-datasource-types-info.json | 0 .../testdata/special-datasource-types.json | 0 .../string-datasource-variable-info.json | 0 .../testdata/string-datasource-variable.json | 0 pkg/services/store/kind/dashboard/types.go | 32 ++ pkg/services/store/kind/dummy/doc.go | 4 + pkg/services/store/kind/dummy/summary.go | 61 +++ pkg/services/store/kind/playlist/summary.go | 67 +++ .../store/kind/playlist/summary_test.go | 39 ++ pkg/services/store/kind/png/summary.go | 55 +++ pkg/services/store/kind/png/summary_test.go | 33 ++ pkg/services/store/kind/registry.go | 144 +++++++ pkg/services/store/kind/registry_test.go | 42 ++ pkg/services/store/kind/svg/summary.go | 66 +++ .../store/{go-is-svg => kind/svg}/svg.go | 10 +- .../store/object/dummy/dummy_server.go | 33 +- pkg/services/store/object/http.go | 17 +- pkg/services/store/object/kind.go | 63 --- pkg/services/store/object/summary.go | 43 -- .../object/tests/server_integration_test.go | 6 +- pkg/services/store/validate.go | 4 +- 72 files changed, 1883 insertions(+), 402 deletions(-) create mode 100644 pkg/models/object.go delete mode 100644 pkg/services/searchV2/extract/types.go delete mode 100644 pkg/services/searchV2/object/dashboard.go rename pkg/services/store/{object => }/auth.go (98%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/dashboard.go (81%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/dashboard_test.go (89%) rename pkg/services/{searchV2/dslookup => store/kind/dashboard}/ds_lookup.go (99%) rename pkg/services/store/{object => kind/dashboard}/reference.go (66%) create mode 100644 pkg/services/store/kind/dashboard/summary.go rename pkg/services/{searchV2/object/dashboard_test.go => store/kind/dashboard/summary_test.go} (62%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/targets.go (70%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/all-selected-multi-datasource-variable-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/all-selected-multi-datasource-variable.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/all-selected-single-datasource-variable-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/all-selected-single-datasource-variable.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/check-string-datasource-id-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/check-string-datasource-id.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/datasource-variable-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/datasource-variable-no-curly-braces-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/datasource-variable-no-curly-braces.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/datasource-variable.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/default-datasource-variable-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/default-datasource-variable.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/devdash-all-panels-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/devdash-graph-shared-tooltips-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/empty-datasource-variable-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/empty-datasource-variable.json (100%) create mode 100644 pkg/services/store/kind/dashboard/testdata/gdev-walk-graph-gradient-area-fills.json create mode 100644 pkg/services/store/kind/dashboard/testdata/gdev-walk-graph-shared-tooltips.json create mode 100644 pkg/services/store/kind/dashboard/testdata/gdev-walk-graph-time-regions.json create mode 100644 pkg/services/store/kind/dashboard/testdata/gdev-walk-graph_tests.json create mode 100644 pkg/services/store/kind/dashboard/testdata/gdev-walk-graph_y_axis.json rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/mixed-datasource-with-variable-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/mixed-datasource-with-variable.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/panels-without-datasources-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/panels-without-datasources.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/repeated-datasource-variables-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/repeated-datasource-variables-with-default-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/repeated-datasource-variables-with-default.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/repeated-datasource-variables.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/special-datasource-types-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/special-datasource-types.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/string-datasource-variable-info.json (100%) rename pkg/services/{searchV2/extract => store/kind/dashboard}/testdata/string-datasource-variable.json (100%) create mode 100644 pkg/services/store/kind/dashboard/types.go create mode 100644 pkg/services/store/kind/dummy/doc.go create mode 100644 pkg/services/store/kind/dummy/summary.go create mode 100644 pkg/services/store/kind/playlist/summary.go create mode 100644 pkg/services/store/kind/playlist/summary_test.go create mode 100644 pkg/services/store/kind/png/summary.go create mode 100644 pkg/services/store/kind/png/summary_test.go create mode 100644 pkg/services/store/kind/registry.go create mode 100644 pkg/services/store/kind/registry_test.go create mode 100644 pkg/services/store/kind/svg/summary.go rename pkg/services/store/{go-is-svg => kind/svg}/svg.go (92%) delete mode 100644 pkg/services/store/object/kind.go delete mode 100644 pkg/services/store/object/summary.go diff --git a/packages/grafana-schema/src/raw/playlist/x/playlist.gen.ts b/packages/grafana-schema/src/raw/playlist/x/playlist.gen.ts index ed13e0ef775..3a562777c0c 100644 --- a/packages/grafana-schema/src/raw/playlist/x/playlist.gen.ts +++ b/packages/grafana-schema/src/raw/playlist/x/playlist.gen.ts @@ -36,6 +36,7 @@ export interface Playlist { interval: string; /** * The ordered list of items that the playlist will iterate over. + * FIXME! This should not be optional, but changing it makes the godegen awkward */ items?: Array; /** diff --git a/pkg/coremodel/playlist/coremodel.cue b/pkg/coremodel/playlist/coremodel.cue index 4070cf3b848..f12338f1d61 100644 --- a/pkg/coremodel/playlist/coremodel.cue +++ b/pkg/coremodel/playlist/coremodel.cue @@ -22,6 +22,7 @@ seqs: [ interval: string | *"5m" // The ordered list of items that the playlist will iterate over. + // FIXME! This should not be optional, but changing it makes the godegen awkward items?: [...#PlaylistItem] /////////////////////////////////////// diff --git a/pkg/coremodel/playlist/playlist_gen.go b/pkg/coremodel/playlist/playlist_gen.go index 01f8fa2f044..c33b65311b9 100644 --- a/pkg/coremodel/playlist/playlist_gen.go +++ b/pkg/coremodel/playlist/playlist_gen.go @@ -36,6 +36,7 @@ type Model struct { Interval string `json:"interval"` // The ordered list of items that the playlist will iterate over. + // FIXME! This should not be optional, but changing it makes the godegen awkward Items *[]PlaylistItem `json:"items,omitempty"` // Name of the playlist. diff --git a/pkg/models/object.go b/pkg/models/object.go new file mode 100644 index 00000000000..0e2dc3000b8 --- /dev/null +++ b/pkg/models/object.go @@ -0,0 +1,119 @@ +package models + +//----------------------------------------------------------------------------------------------------- +// NOTE: the object store is in heavy development, and the locations will likely continue to move +//----------------------------------------------------------------------------------------------------- + +import "context" + +const ( + StandardKindDashboard = "dashboard" + StandardKindPlaylist = "playlist" + StandardKindFolder = "folder" + + // StandardKindDataSource: not a real kind yet, but used to define references from dashboards + // Types: influx, prometheus, testdata, ... + StandardKindDataSource = "ds" + + // StandardKindPanel: currently used in two ways :( in search it defines a panel within a dashboard + // This is also used to refer to a panel plugin with type: heatmap, timeseries, table, ... + StandardKindPanel = "panel" + + // StandardKindTransform: used to show that a dashboard depends on a set of transformations + // NOTE: this should likey be replaced with kind:system/type:transform/uid:joinByField or something like that + StandardKindTransform = "transform" + + // StandardKindSVG SVG file support + StandardKindSVG = "svg" + + // StandardKindPNG PNG file support + StandardKindPNG = "png" + + // StandardKindQuery early development on panel query library + // the kind may need to change to better encapsulate { targets:[], transforms:[] } + StandardKindQuery = "query" +) + +// ObjectKindInfo describes information needed from the object store +// All non-raw types will have a schema that can be used to validate +type ObjectKindInfo 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"` +} + +// ObjectSummary represents common data derived from a raw object bytes. +// The values should not depend on system state, and are derived from the raw object. +// This summary is used for a unified search and object listing +type ObjectSummary struct { + UID string `json:"uid,omitempty"` + Kind string `json:"kind,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + + // Key value pairs. Tags are are represented as keys with empty values + Labels map[string]string `json:"labels,omitempty"` + + // URL should only be set if the value is not derived directly from kind+uid + // NOTE: this may go away with a more robust GRN solution /!\ + URL string `json:"URL,omitempty"` + + // When errors exist + Error *ObjectErrorInfo `json:"error,omitempty"` + + // Optional field values. The schema will define and document possible values for a given kind + Fields map[string]interface{} `json:"fields,omitempty"` + + // eg: panels within dashboard + Nested []*ObjectSummary `json:"nested,omitempty"` + + // Optional references to external things + References []*ObjectExternalReference `json:"references,omitempty"` + + // The summary can not be extended + _ interface{} +} + +// This will likely get replaced with a more general error framework. +type ObjectErrorInfo struct { + // TODO: Match an error code registry? + Code int64 `json:"code,omitempty"` + + // Simple error display + Message string `json:"message,omitempty"` + + // Error details + Details interface{} `json:"details,omitempty"` +} + +// Reference to another object outside itself +// This message is derived from the object body and can be used to search for references. +// This does not represent a method to declare a reference to another object. +type ObjectExternalReference struct { + // datasource (instance), dashboard (instance), + Kind string `json:"kind,omitempty"` + + // prometheus / heatmap, heatamp|prometheus + Type string `json:"type,omitempty"` // flavor + + // Unique ID for this object + UID string `json:"UID,omitempty"` +} + +// ObjectSummaryBuilder 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 +type ObjectSummaryBuilder = func(ctx context.Context, uid string, body []byte) (*ObjectSummary, []byte, error) diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 83929b3ee93..51448589286 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -124,6 +124,7 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/star/starimpl" "github.com/grafana/grafana/pkg/services/store" + "github.com/grafana/grafana/pkg/services/store/kind" "github.com/grafana/grafana/pkg/services/store/object" objectdummyserver "github.com/grafana/grafana/pkg/services/store/object/dummy" "github.com/grafana/grafana/pkg/services/store/sanitizer" @@ -354,6 +355,7 @@ var wireBasicSet = wire.NewSet( grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, interceptors.ProvideAuthenticator, + kind.ProvideService, // The registry known kinds objectdummyserver.ProvideDummyObjectServer, object.ProvideHTTPObjectStore, teamimpl.ProvideService, diff --git a/pkg/services/export/export_dash.go b/pkg/services/export/export_dash.go index 00234318138..60a88ce7a52 100644 --- a/pkg/services/export/export_dash.go +++ b/pkg/services/export/export_dash.go @@ -1,7 +1,6 @@ package export import ( - "bytes" "encoding/json" "fmt" "path" @@ -11,9 +10,8 @@ import ( "github.com/google/uuid" "github.com/grafana/grafana/pkg/infra/filestorage" - "github.com/grafana/grafana/pkg/services/searchV2/dslookup" - "github.com/grafana/grafana/pkg/services/searchV2/extract" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/store/kind/dashboard" ) func exportDashboards(helper *commitHelper, job *gitExportJob) error { @@ -26,7 +24,7 @@ func exportDashboards(helper *commitHelper, job *gitExportJob) error { folders[0] = job.cfg.GeneralFolderPath // "general" } - lookup, err := dslookup.LoadDatasourceLookup(helper.ctx, helper.orgID, job.sql) + lookup, err := dashboard.LoadDatasourceLookup(helper.ctx, helper.orgID, job.sql) if err != nil { return err } @@ -60,20 +58,22 @@ func exportDashboards(helper *commitHelper, job *gitExportJob) error { return err } + reader := dashboard.NewStaticDashboardSummaryBuilder(lookup) + // Process all folders for _, row := range rows { if !row.IsFolder { continue } - dash, err := extract.ReadDashboard(bytes.NewReader(row.Data), lookup) + dash, _, err := reader(helper.ctx, row.UID, row.Data) if err != nil { return err } dash.UID = row.UID - slug := cleanFileName(dash.Title) + slug := cleanFileName(dash.Name) folder := map[string]string{ - "title": dash.Title, + "title": dash.Name, } folderStructure.body = append(folderStructure.body, commitBody{ diff --git a/pkg/services/querylibrary/querylibraryimpl/service.go b/pkg/services/querylibrary/querylibraryimpl/service.go index 1d3baceb37d..cf3cc193491 100644 --- a/pkg/services/querylibrary/querylibraryimpl/service.go +++ b/pkg/services/querylibrary/querylibraryimpl/service.go @@ -12,7 +12,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/querylibrary" - "github.com/grafana/grafana/pkg/services/searchV2/dslookup" + "github.com/grafana/grafana/pkg/services/store/kind/dashboard" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" @@ -205,7 +205,7 @@ func getDatasourceUID(q *simplejson.Json) string { return uid } -func isQueryWithMixedDataSource(q *querylibrary.Query) (isMixed bool, firstDsRef dslookup.DataSourceRef) { +func isQueryWithMixedDataSource(q *querylibrary.Query) (isMixed bool, firstDsRef dashboard.DataSourceRef) { dsRefs := extractDataSources(q) for _, dsRef := range dsRefs { @@ -226,8 +226,8 @@ func isQueryWithMixedDataSource(q *querylibrary.Query) (isMixed bool, firstDsRef return false, firstDsRef } -func extractDataSources(query *querylibrary.Query) []dslookup.DataSourceRef { - ds := make([]dslookup.DataSourceRef, 0) +func extractDataSources(query *querylibrary.Query) []dashboard.DataSourceRef { + ds := make([]dashboard.DataSourceRef, 0) for _, q := range query.Queries { dsUid := getDatasourceUID(q) @@ -236,7 +236,7 @@ func extractDataSources(query *querylibrary.Query) []dslookup.DataSourceRef { dsType = expr.DatasourceType } - ds = append(ds, dslookup.DataSourceRef{ + ds = append(ds, dashboard.DataSourceRef{ UID: dsUid, Type: dsType, }) diff --git a/pkg/services/querylibrary/types.go b/pkg/services/querylibrary/types.go index 1d38b14c8ca..2dfbb6247fa 100644 --- a/pkg/services/querylibrary/types.go +++ b/pkg/services/querylibrary/types.go @@ -7,7 +7,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/registry" - "github.com/grafana/grafana/pkg/services/searchV2/dslookup" + "github.com/grafana/grafana/pkg/services/store/kind/dashboard" "github.com/grafana/grafana/pkg/services/user" ) @@ -64,7 +64,7 @@ type QueryInfo struct { TimeTo string `json:"timeTo"` SchemaVersion int64 `json:"schemaVersion"` - Datasource []dslookup.DataSourceRef `json:"datasource,omitempty"` // UIDs + Datasource []dashboard.DataSourceRef `json:"datasource,omitempty"` // UIDs } type QuerySearchOptions struct { diff --git a/pkg/services/searchV2/bluge.go b/pkg/services/searchV2/bluge.go index f661af5a321..3a88e8b3a8b 100644 --- a/pkg/services/searchV2/bluge.go +++ b/pkg/services/searchV2/bluge.go @@ -14,7 +14,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/store/object" + "github.com/grafana/grafana/pkg/models" ) const ( @@ -168,7 +168,7 @@ func getNonFolderDashboardDoc(dash dashboard, location string) *bluge.Document { } for _, ref := range dash.summary.References { - if ref.Kind == object.StandardKindDataSource { + if ref.Kind == models.StandardKindDataSource { if ref.Type != "" { doc.AddField(bluge.NewKeywordField(documentFieldDSType, ref.Type). StoreValue(). @@ -200,7 +200,7 @@ func getDashboardPanelDocs(dash dashboard, location string) []*bluge.Document { for _, ref := range dash.summary.References { switch ref.Kind { - case object.StandardKindDashboard: + case models.StandardKindDashboard: if ref.Type != "" { doc.AddField(bluge.NewKeywordField(documentFieldDSType, ref.Type). StoreValue(). @@ -213,11 +213,11 @@ func getDashboardPanelDocs(dash dashboard, location string) []*bluge.Document { Aggregatable(). SearchTermPositions()) } - case object.StandardKindPanel: + case models.StandardKindPanel: if ref.Type != "" { doc.AddField(bluge.NewKeywordField(documentFieldPanelType, ref.Type).Aggregatable().StoreValue()) } - case object.StandardKindTransform: + case models.StandardKindTransform: if ref.Type != "" { doc.AddField(bluge.NewKeywordField(documentFieldTransformer, ref.Type).Aggregatable()) } diff --git a/pkg/services/searchV2/extract/types.go b/pkg/services/searchV2/extract/types.go deleted file mode 100644 index 70613055638..00000000000 --- a/pkg/services/searchV2/extract/types.go +++ /dev/null @@ -1,36 +0,0 @@ -package extract - -import ( - "github.com/grafana/grafana/pkg/services/searchV2/dslookup" -) - -type PanelInfo struct { - ID int64 `json:"id"` - Title string `json:"title"` - Description string `json:"description,omitempty"` - Type string `json:"type,omitempty"` // PluginID - PluginVersion string `json:"pluginVersion,omitempty"` - Datasource []dslookup.DataSourceRef `json:"datasource,omitempty"` // UIDs - Transformer []string `json:"transformer,omitempty"` // ids of the transformation steps - - // Rows define panels as sub objects - Collapsed []PanelInfo `json:"collapsed,omitempty"` -} - -type DashboardInfo struct { - UID string `json:"uid,omitempty"` - ID int64 `json:"id,omitempty"` // internal ID - Title string `json:"title"` - Description string `json:"description,omitempty"` - Tags []string `json:"tags"` - TemplateVars []string `json:"templateVars,omitempty"` // the keys used - Datasource []dslookup.DataSourceRef `json:"datasource,omitempty"` // UIDs - Panels []PanelInfo `json:"panels"` // nesed documents - SchemaVersion int64 `json:"schemaVersion"` - LinkCount int64 `json:"linkCount"` - TimeFrom string `json:"timeFrom"` - TimeTo string `json:"timeTo"` - TimeZone string `json:"timezone"` - Refresh string `json:"refresh,omitempty"` - ReadOnly bool `json:"readOnly,omitempty"` // editable = false -} diff --git a/pkg/services/searchV2/filter.go b/pkg/services/searchV2/filter.go index c82ba146db0..1a22f60c595 100644 --- a/pkg/services/searchV2/filter.go +++ b/pkg/services/searchV2/filter.go @@ -8,7 +8,7 @@ import ( "github.com/blugelabs/bluge/search/searcher" "github.com/blugelabs/bluge/search/similarity" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/store/object" + "github.com/grafana/grafana/pkg/models" ) type PermissionFilter struct { @@ -19,11 +19,11 @@ type PermissionFilter struct { type entityKind string const ( - entityKindPanel entityKind = object.StandardKindPanel - entityKindDashboard entityKind = object.StandardKindDashboard - entityKindFolder entityKind = object.StandardKindFolder - entityKindDatasource entityKind = object.StandardKindDataSource - entityKindQuery entityKind = object.StandardKindQuery + entityKindPanel entityKind = models.StandardKindPanel + entityKindDashboard entityKind = models.StandardKindDashboard + entityKindFolder entityKind = models.StandardKindFolder + entityKindDatasource entityKind = models.StandardKindDataSource + entityKindQuery entityKind = models.StandardKindQuery ) func (r entityKind) IsValid() bool { diff --git a/pkg/services/searchV2/index.go b/pkg/services/searchV2/index.go index 06ece251d7d..eccbf02839d 100644 --- a/pkg/services/searchV2/index.go +++ b/pkg/services/searchV2/index.go @@ -15,12 +15,11 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/searchV2/dslookup" - "github.com/grafana/grafana/pkg/services/searchV2/object" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/store" - obj "github.com/grafana/grafana/pkg/services/store/object" + kdash "github.com/grafana/grafana/pkg/services/store/kind/dashboard" "github.com/grafana/grafana/pkg/setting" "go.opentelemetry.io/otel/attribute" @@ -55,7 +54,7 @@ type dashboard struct { updated time.Time // Use generic structure - summary *obj.ObjectSummary + summary *models.ObjectSummary } // buildSignal is sent when search index is accessed in organization for which @@ -839,7 +838,7 @@ func (l sqlDashboardLoader) LoadDashboards(ctx context.Context, orgID int64, das slug: "", created: time.Now(), updated: time.Now(), - summary: &obj.ObjectSummary{ + summary: &models.ObjectSummary{ //ID: 0, Name: "General", }, @@ -850,7 +849,7 @@ func (l sqlDashboardLoader) LoadDashboards(ctx context.Context, orgID int64, das loadDatasourceSpan.SetAttributes("orgID", orgID, attribute.Key("orgID").Int64(orgID)) // key will allow name or uid - lookup, err := dslookup.LoadDatasourceLookup(loadDatasourceCtx, orgID, l.sql) + lookup, err := kdash.LoadDatasourceLookup(loadDatasourceCtx, orgID, l.sql) if err != nil { loadDatasourceSpan.End() return dashboards, err @@ -897,15 +896,10 @@ func (l sqlDashboardLoader) LoadDashboards(ctx context.Context, orgID int64, das readDashboardSpan.SetAttributes("orgID", orgID, attribute.Key("orgID").Int64(orgID)) readDashboardSpan.SetAttributes("dashboardCount", len(rows), attribute.Key("dashboardCount").Int(len(rows))) - reader := object.NewDashboardSummaryBuilder(lookup) + reader := kdash.NewStaticDashboardSummaryBuilder(lookup) for _, row := range rows { - obj := &obj.RawObject{ - UID: row.Uid, - Kind: "dashboard", - Body: row.Data, - } - summary, err := reader(obj) + summary, _, err := reader(ctx, row.Uid, row.Data) if err != nil { l.logger.Warn("Error indexing dashboard data", "error", err, "dashboardId", row.Id, "dashboardSlug", row.Slug) // But append info anyway for now, since we possibly extracted useful information. @@ -918,7 +912,7 @@ func (l sqlDashboardLoader) LoadDashboards(ctx context.Context, orgID int64, das slug: row.Slug, created: row.Created, updated: row.Updated, - summary: &summary, + summary: summary, }) lastID = row.Id } diff --git a/pkg/services/searchV2/index_test.go b/pkg/services/searchV2/index_test.go index 364a766776c..1ba992de27f 100644 --- a/pkg/services/searchV2/index_test.go +++ b/pkg/services/searchV2/index_test.go @@ -9,8 +9,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/store/object" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/infra/log" @@ -113,14 +113,14 @@ var testDashboards = []dashboard{ { id: 1, uid: "1", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "test", }, }, { id: 2, uid: "2", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "boom", }, }, @@ -162,7 +162,7 @@ func TestDashboardIndexUpdates(t *testing.T) { err := index.updateDashboard(context.Background(), testOrgID, orgIdx, dashboard{ id: 3, uid: "3", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "created", }, }) @@ -181,7 +181,7 @@ func TestDashboardIndexUpdates(t *testing.T) { err := index.updateDashboard(context.Background(), testOrgID, orgIdx, dashboard{ id: 2, uid: "2", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "nginx", }, }) @@ -197,14 +197,14 @@ var testSortDashboards = []dashboard{ { id: 1, uid: "1", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "a-test", }, }, { id: 2, uid: "2", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "z-test", }, }, @@ -288,14 +288,14 @@ var testPrefixDashboards = []dashboard{ { id: 1, uid: "1", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "Archer Data System", }, }, { id: 2, uid: "2", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "Document Sync repo", }, }, @@ -366,7 +366,7 @@ var longPrefixDashboards = []dashboard{ { id: 1, uid: "1", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "Eyjafjallajökull Eruption data", }, }, @@ -385,14 +385,14 @@ var scatteredTokensDashboards = []dashboard{ { id: 1, uid: "1", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "Three can keep a secret, if two of them are dead (Benjamin Franklin)", }, }, { id: 3, uid: "2", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "A secret is powerful when it is empty (Umberto Eco)", }, }, @@ -418,7 +418,7 @@ var dashboardsWithFolders = []dashboard{ id: 1, uid: "1", isFolder: true, - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "My folder", }, }, @@ -426,9 +426,9 @@ var dashboardsWithFolders = []dashboard{ id: 2, uid: "2", folderID: 1, - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "Dashboard in folder 1", - Nested: []*object.ObjectSummary{ + Nested: []*models.ObjectSummary{ newNestedPanel(1, "Panel 1"), newNestedPanel(2, "Panel 2"), }, @@ -438,9 +438,9 @@ var dashboardsWithFolders = []dashboard{ id: 3, uid: "3", folderID: 1, - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "Dashboard in folder 2", - Nested: []*object.ObjectSummary{ + Nested: []*models.ObjectSummary{ newNestedPanel(3, "Panel 3"), }, }, @@ -448,9 +448,9 @@ var dashboardsWithFolders = []dashboard{ { id: 4, uid: "4", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "One more dash", - Nested: []*object.ObjectSummary{ + Nested: []*models.ObjectSummary{ newNestedPanel(4, "Panel 4"), }, }, @@ -505,9 +505,9 @@ var dashboardsWithPanels = []dashboard{ { id: 1, uid: "1", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "My Dash", - Nested: []*object.ObjectSummary{ + Nested: []*models.ObjectSummary{ newNestedPanel(1, "Panel 1"), newNestedPanel(2, "Panel 2"), }, @@ -515,8 +515,8 @@ var dashboardsWithPanels = []dashboard{ }, } -func newNestedPanel(id int64, name string) *object.ObjectSummary { - summary := &object.ObjectSummary{ +func newNestedPanel(id int64, name string) *models.ObjectSummary { + summary := &models.ObjectSummary{ Kind: "panel", UID: fmt.Sprintf("???#%d", id), } @@ -553,14 +553,14 @@ var punctuationSplitNgramDashboards = []dashboard{ { id: 1, uid: "1", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "heat-torkel", }, }, { id: 2, uid: "2", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "topology heatmap", }, }, @@ -586,7 +586,7 @@ var camelCaseNgramDashboards = []dashboard{ { id: 1, uid: "1", - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: "heatTorkel", }, }, @@ -608,7 +608,7 @@ func dashboardsWithTitles(names ...string) []dashboard { out = append(out, dashboard{ id: no, uid: fmt.Sprintf("%d", no), - summary: &object.ObjectSummary{ + summary: &models.ObjectSummary{ Name: name, }, }) diff --git a/pkg/services/searchV2/object/dashboard.go b/pkg/services/searchV2/object/dashboard.go deleted file mode 100644 index fcadd6bb21d..00000000000 --- a/pkg/services/searchV2/object/dashboard.go +++ /dev/null @@ -1,71 +0,0 @@ -package object - -import ( - "bytes" - "fmt" - "strconv" - - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/searchV2/dslookup" - "github.com/grafana/grafana/pkg/services/searchV2/extract" - "github.com/grafana/grafana/pkg/services/store/object" -) - -func NewDashboardSummaryBuilder(lookup dslookup.DatasourceLookup) object.ObjectSummaryBuilder { - return func(obj *object.RawObject) (object.ObjectSummary, error) { - summary := object.ObjectSummary{ - Labels: make(map[string]string), - Fields: make(map[string]interface{}), - } - stream := bytes.NewBuffer(obj.Body) - dash, err := extract.ReadDashboard(stream, lookup) - if err != nil { - summary.Error = &object.ObjectErrorInfo{ - Code: 500, // generic bad error - Message: err.Error(), - } - return summary, err - } - - dashboardRefs := object.NewReferenceAccumulator() - url := fmt.Sprintf("/d/%s/%s", obj.UID, models.SlugifyTitle(dash.Title)) - summary.Name = dash.Title - summary.Description = dash.Description - summary.URL = url - for _, v := range dash.Tags { - summary.Labels[v] = "" - } - if len(dash.TemplateVars) > 0 { - summary.Fields["hasTemplateVars"] = true - } - - for _, panel := range dash.Panels { - panelRefs := object.NewReferenceAccumulator() - p := &object.ObjectSummary{ - UID: obj.UID + "#" + strconv.FormatInt(panel.ID, 10), - Kind: "panel", - } - p.Name = panel.Title - p.Description = panel.Description - p.URL = fmt.Sprintf("%s?viewPanel=%d", url, panel.ID) - p.Fields = make(map[string]interface{}, 0) - - panelRefs.Add("panel", panel.Type, "") - for _, v := range panel.Datasource { - dashboardRefs.Add(object.StandardKindDataSource, v.Type, v.UID) - panelRefs.Add(object.StandardKindDataSource, v.Type, v.UID) - } - - for _, v := range panel.Transformer { - panelRefs.Add(object.StandardKindTransform, v, "") - } - - dashboardRefs.Add(object.StandardKindPanel, panel.Type, "") - p.References = panelRefs.Get() - summary.Nested = append(summary.Nested, p) - } - - summary.References = dashboardRefs.Get() - return summary, nil - } -} diff --git a/pkg/services/store/object/auth.go b/pkg/services/store/auth.go similarity index 98% rename from pkg/services/store/object/auth.go rename to pkg/services/store/auth.go index 991e87758c0..cd4ebe8b1c1 100644 --- a/pkg/services/store/object/auth.go +++ b/pkg/services/store/auth.go @@ -1,4 +1,4 @@ -package object +package store import ( "context" diff --git a/pkg/services/searchV2/extract/dashboard.go b/pkg/services/store/kind/dashboard/dashboard.go similarity index 81% rename from pkg/services/searchV2/extract/dashboard.go rename to pkg/services/store/kind/dashboard/dashboard.go index 01b74c807fa..89bef6e5289 100644 --- a/pkg/services/searchV2/extract/dashboard.go +++ b/pkg/services/store/kind/dashboard/dashboard.go @@ -1,4 +1,4 @@ -package extract +package dashboard import ( "io" @@ -6,8 +6,6 @@ import ( "strings" jsoniter "github.com/json-iterator/go" - - "github.com/grafana/grafana/pkg/services/searchV2/dslookup" ) func logf(format string, a ...interface{}) { @@ -24,46 +22,46 @@ type templateVariable struct { } type datasourceVariableLookup struct { - variableNameToRefs map[string][]dslookup.DataSourceRef - dsLookup dslookup.DatasourceLookup + variableNameToRefs map[string][]DataSourceRef + dsLookup DatasourceLookup } -func (d *datasourceVariableLookup) getDsRefsByTemplateVariableValue(value string, datasourceType string) []dslookup.DataSourceRef { +func (d *datasourceVariableLookup) getDsRefsByTemplateVariableValue(value string, datasourceType string) []DataSourceRef { switch value { case "default": // can be the default DS, or a DS with UID="default" - candidateDs := d.dsLookup.ByRef(&dslookup.DataSourceRef{UID: value}) + candidateDs := d.dsLookup.ByRef(&DataSourceRef{UID: value}) if candidateDs == nil { // get the actual default DS candidateDs = d.dsLookup.ByRef(nil) } if candidateDs != nil { - return []dslookup.DataSourceRef{*candidateDs} + return []DataSourceRef{*candidateDs} } - return []dslookup.DataSourceRef{} + return []DataSourceRef{} case "$__all": // TODO: filter datasources by template variable's regex return d.dsLookup.ByType(datasourceType) case "": - return []dslookup.DataSourceRef{} + return []DataSourceRef{} case "No data sources found": - return []dslookup.DataSourceRef{} + return []DataSourceRef{} default: // some variables use `ds.name` rather `ds.uid` - if ref := d.dsLookup.ByRef(&dslookup.DataSourceRef{ + if ref := d.dsLookup.ByRef(&DataSourceRef{ UID: value, }); ref != nil { - return []dslookup.DataSourceRef{*ref} + return []DataSourceRef{*ref} } // discard variable - return []dslookup.DataSourceRef{} + return []DataSourceRef{} } } func (d *datasourceVariableLookup) add(templateVariable templateVariable) { - var refs []dslookup.DataSourceRef + var refs []DataSourceRef datasourceType, isDataSourceTypeValid := templateVariable.query.(string) if !isDataSourceTypeValid { @@ -86,8 +84,8 @@ func (d *datasourceVariableLookup) add(templateVariable templateVariable) { d.variableNameToRefs[templateVariable.name] = unique(refs) } -func unique(refs []dslookup.DataSourceRef) []dslookup.DataSourceRef { - var uniqueRefs []dslookup.DataSourceRef +func unique(refs []DataSourceRef) []DataSourceRef { + var uniqueRefs []DataSourceRef uidPresence := make(map[string]bool) for _, ref := range refs { if !uidPresence[ref.UID] { @@ -98,26 +96,26 @@ func unique(refs []dslookup.DataSourceRef) []dslookup.DataSourceRef { return uniqueRefs } -func (d *datasourceVariableLookup) getDatasourceRefs(name string) []dslookup.DataSourceRef { +func (d *datasourceVariableLookup) getDatasourceRefs(name string) []DataSourceRef { refs, ok := d.variableNameToRefs[name] if ok { return refs } - return []dslookup.DataSourceRef{} + return []DataSourceRef{} } -func newDatasourceVariableLookup(dsLookup dslookup.DatasourceLookup) *datasourceVariableLookup { +func newDatasourceVariableLookup(dsLookup DatasourceLookup) *datasourceVariableLookup { return &datasourceVariableLookup{ - variableNameToRefs: make(map[string][]dslookup.DataSourceRef), + variableNameToRefs: make(map[string][]DataSourceRef), dsLookup: dsLookup, } } // nolint:gocyclo // ReadDashboard will take a byte stream and return dashboard info -func ReadDashboard(stream io.Reader, lookup dslookup.DatasourceLookup) (*DashboardInfo, error) { - dash := &DashboardInfo{} +func readDashboard(stream io.Reader, lookup DatasourceLookup) (*dashboardInfo, error) { + dash := &dashboardInfo{} iter := jsoniter.Parse(jsoniter.ConfigDefault, stream, 1024) @@ -192,7 +190,7 @@ func ReadDashboard(stream io.Reader, lookup dslookup.DatasourceLookup) (*Dashboa } case "panels": for iter.ReadArray() { - dash.Panels = append(dash.Panels, readPanelInfo(iter, lookup)) + dash.Panels = append(dash.Panels, readpanelInfo(iter, lookup)) } case "rows": @@ -278,11 +276,11 @@ func ReadDashboard(stream io.Reader, lookup dslookup.DatasourceLookup) (*Dashboa return dash, iter.Error } -func panelRequiresDatasource(panel PanelInfo) bool { +func panelRequiresDatasource(panel panelInfo) bool { return panel.Type != "row" } -func fillDefaultDatasources(dash *DashboardInfo, lookup dslookup.DatasourceLookup) { +func fillDefaultDatasources(dash *dashboardInfo, lookup DatasourceLookup) { for i, panel := range dash.Panels { if len(panel.Datasource) != 0 || !panelRequiresDatasource(panel) { continue @@ -290,14 +288,14 @@ func fillDefaultDatasources(dash *DashboardInfo, lookup dslookup.DatasourceLooku defaultDs := lookup.ByRef(nil) if defaultDs != nil { - dash.Panels[i].Datasource = []dslookup.DataSourceRef{*defaultDs} + dash.Panels[i].Datasource = []DataSourceRef{*defaultDs} } } } -func filterOutSpecialDatasources(dash *DashboardInfo) { +func filterOutSpecialDatasources(dash *dashboardInfo) { for i, panel := range dash.Panels { - var dsRefs []dslookup.DataSourceRef + var dsRefs []DataSourceRef // partition into actual datasource references and variables for _, ds := range panel.Datasource { @@ -317,10 +315,10 @@ func filterOutSpecialDatasources(dash *DashboardInfo) { } } -func replaceDatasourceVariables(dash *DashboardInfo, datasourceVariablesLookup *datasourceVariableLookup) { +func replaceDatasourceVariables(dash *dashboardInfo, datasourceVariablesLookup *datasourceVariableLookup) { for i, panel := range dash.Panels { - var dsVariableRefs []dslookup.DataSourceRef - var dsRefs []dslookup.DataSourceRef + var dsVariableRefs []DataSourceRef + var dsRefs []DataSourceRef // partition into actual datasource references and variables for i := range panel.Datasource { @@ -345,7 +343,7 @@ func isVariableRef(uid string) bool { return strings.HasPrefix(uid, "$") } -func getDataSourceVariableName(dsVariableRef dslookup.DataSourceRef) string { +func getDataSourceVariableName(dsVariableRef DataSourceRef) string { if strings.HasPrefix(dsVariableRef.UID, "${") { return strings.TrimPrefix(strings.TrimSuffix(dsVariableRef.UID, "}"), "${") } @@ -353,8 +351,8 @@ func getDataSourceVariableName(dsVariableRef dslookup.DataSourceRef) string { return strings.TrimPrefix(dsVariableRef.UID, "$") } -func findDatasourceRefsForVariables(dsVariableRefs []dslookup.DataSourceRef, datasourceVariablesLookup *datasourceVariableLookup) []dslookup.DataSourceRef { - var referencedDs []dslookup.DataSourceRef +func findDatasourceRefsForVariables(dsVariableRefs []DataSourceRef, datasourceVariablesLookup *datasourceVariableLookup) []DataSourceRef { + var referencedDs []DataSourceRef for _, dsVariableRef := range dsVariableRefs { variableName := getDataSourceVariableName(dsVariableRef) refs := datasourceVariablesLookup.getDatasourceRefs(variableName) @@ -364,8 +362,8 @@ func findDatasourceRefsForVariables(dsVariableRefs []dslookup.DataSourceRef, dat } // will always return strings for now -func readPanelInfo(iter *jsoniter.Iterator, lookup dslookup.DatasourceLookup) PanelInfo { - panel := PanelInfo{} +func readpanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup) panelInfo { + panel := panelInfo{} targets := newTargetInfo(lookup) @@ -428,7 +426,7 @@ func readPanelInfo(iter *jsoniter.Iterator, lookup dslookup.DatasourceLookup) Pa // Rows have nested panels case "panels": for iter.ReadArray() { - panel.Collapsed = append(panel.Collapsed, readPanelInfo(iter, lookup)) + panel.Collapsed = append(panel.Collapsed, readpanelInfo(iter, lookup)) } case "options": diff --git a/pkg/services/searchV2/extract/dashboard_test.go b/pkg/services/store/kind/dashboard/dashboard_test.go similarity index 89% rename from pkg/services/searchV2/extract/dashboard_test.go rename to pkg/services/store/kind/dashboard/dashboard_test.go index 04a8edc3e21..94287ad69e0 100644 --- a/pkg/services/searchV2/extract/dashboard_test.go +++ b/pkg/services/store/kind/dashboard/dashboard_test.go @@ -1,4 +1,4 @@ -package extract +package dashboard import ( "encoding/json" @@ -10,12 +10,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/services/searchV2/dslookup" ) -func dsLookup() dslookup.DatasourceLookup { - return dslookup.CreateDatasourceLookup([]*dslookup.DatasourceQueryResult{ +func dsLookupForTests() DatasourceLookup { + return CreateDatasourceLookup([]*DatasourceQueryResult{ { UID: "P8045C56BDA891CB2", Type: "cloudwatch", @@ -74,7 +72,7 @@ func TestReadDashboard(t *testing.T) { "panels-without-datasources", } - devdash := "../../../../devenv/dev-dashboards/" + devdash := "../../../../../devenv/dev-dashboards/" for _, input := range inputs { // nolint:gosec @@ -90,7 +88,7 @@ func TestReadDashboard(t *testing.T) { } require.NoError(t, err) - dash, err := ReadDashboard(f, dsLookup()) + dash, err := readDashboard(f, dsLookupForTests()) sortDatasources(dash) require.NoError(t, err) @@ -115,7 +113,7 @@ func TestReadDashboard(t *testing.T) { } // assure consistent ordering of datasources to prevent random failures of `assert.JSONEq` -func sortDatasources(dash *DashboardInfo) { +func sortDatasources(dash *dashboardInfo) { sort.Slice(dash.Datasource, func(i, j int) bool { return strings.Compare(dash.Datasource[i].UID, dash.Datasource[j].UID) > 0 }) diff --git a/pkg/services/searchV2/dslookup/ds_lookup.go b/pkg/services/store/kind/dashboard/ds_lookup.go similarity index 99% rename from pkg/services/searchV2/dslookup/ds_lookup.go rename to pkg/services/store/kind/dashboard/ds_lookup.go index 5de658de293..4b5e961d763 100644 --- a/pkg/services/searchV2/dslookup/ds_lookup.go +++ b/pkg/services/store/kind/dashboard/ds_lookup.go @@ -1,4 +1,4 @@ -package dslookup +package dashboard import ( "context" diff --git a/pkg/services/store/object/reference.go b/pkg/services/store/kind/dashboard/reference.go similarity index 66% rename from pkg/services/store/object/reference.go rename to pkg/services/store/kind/dashboard/reference.go index 217e2008a08..d039fba737a 100644 --- a/pkg/services/store/object/reference.go +++ b/pkg/services/store/kind/dashboard/reference.go @@ -1,8 +1,10 @@ -package object +package dashboard import ( "fmt" "sort" + + "github.com/grafana/grafana/pkg/models" ) // A reference accumulator can combine @@ -11,24 +13,24 @@ type ReferenceAccumulator interface { Add(kind string, subtype string, uid string) // Returns the set of distinct references in a sorted order - Get() []*ExternalReference + Get() []*models.ObjectExternalReference } func NewReferenceAccumulator() ReferenceAccumulator { return &referenceAccumulator{ - refs: make(map[string]*ExternalReference), + refs: make(map[string]*models.ObjectExternalReference), } } type referenceAccumulator struct { - refs map[string]*ExternalReference + refs map[string]*models.ObjectExternalReference } func (x *referenceAccumulator) Add(kind string, sub string, uid string) { key := fmt.Sprintf("%s/%s/%s", kind, sub, uid) _, ok := x.refs[key] if !ok { - x.refs[key] = &ExternalReference{ + x.refs[key] = &models.ObjectExternalReference{ Kind: kind, Type: sub, UID: uid, @@ -36,14 +38,14 @@ func (x *referenceAccumulator) Add(kind string, sub string, uid string) { } } -func (x *referenceAccumulator) Get() []*ExternalReference { +func (x *referenceAccumulator) Get() []*models.ObjectExternalReference { keys := make([]string, 0, len(x.refs)) for k := range x.refs { keys = append(keys, k) } sort.Strings(keys) - refs := make([]*ExternalReference, len(keys)) + refs := make([]*models.ObjectExternalReference, len(keys)) for i, key := range keys { refs[i] = x.refs[key] } diff --git a/pkg/services/store/kind/dashboard/summary.go b/pkg/services/store/kind/dashboard/summary.go new file mode 100644 index 00000000000..405ca7d7a5b --- /dev/null +++ b/pkg/services/store/kind/dashboard/summary.go @@ -0,0 +1,99 @@ +package dashboard + +import ( + "bytes" + "context" + "fmt" + "strconv" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/store" +) + +func GetObjectKindInfo() models.ObjectKindInfo { + return models.ObjectKindInfo{ + ID: models.StandardKindDashboard, + Name: "Dashboard", + Description: "Define a grafana dashboard layout", + } +} + +func NewDashboardSummary(sql *sqlstore.SQLStore) models.ObjectSummaryBuilder { + return func(ctx context.Context, uid string, body []byte) (*models.ObjectSummary, []byte, error) { + // This just gets the orgID (that will soon/eventually be encoded in a GRN and passed instead of a UID) + user := store.UserFromContext(ctx) + if user == nil { + return nil, nil, fmt.Errorf("can not find user in context") + } + + // Totally inefficient to look this up every time, but for the current use case that is OK + // The lookup is currently structured to support searchV2, but I think should become a real fallback + // that is only executed when we find a legacy dashboard ref + lookup, err := LoadDatasourceLookup(ctx, user.OrgID, sql) + if err != nil { + return nil, nil, err + } + builder := NewStaticDashboardSummaryBuilder(lookup) + return builder(ctx, uid, body) + } +} + +func NewStaticDashboardSummaryBuilder(lookup DatasourceLookup) models.ObjectSummaryBuilder { + return func(ctx context.Context, uid string, body []byte) (*models.ObjectSummary, []byte, error) { + summary := &models.ObjectSummary{ + Labels: make(map[string]string), + Fields: make(map[string]interface{}), + } + stream := bytes.NewBuffer(body) + dash, err := readDashboard(stream, lookup) + if err != nil { + summary.Error = &models.ObjectErrorInfo{ + Message: err.Error(), + } + return summary, body, err + } + + dashboardRefs := NewReferenceAccumulator() + url := fmt.Sprintf("/d/%s/%s", uid, models.SlugifyTitle(dash.Title)) + summary.Name = dash.Title + summary.Description = dash.Description + summary.URL = url + for _, v := range dash.Tags { + summary.Labels[v] = "" + } + if len(dash.TemplateVars) > 0 { + summary.Fields["hasTemplateVars"] = true + } + summary.Fields["schemaVersion"] = dash.SchemaVersion + + for _, panel := range dash.Panels { + panelRefs := NewReferenceAccumulator() + p := &models.ObjectSummary{ + UID: uid + "#" + strconv.FormatInt(panel.ID, 10), + Kind: "panel", + } + p.Name = panel.Title + p.Description = panel.Description + p.URL = fmt.Sprintf("%s?viewPanel=%d", url, panel.ID) + p.Fields = make(map[string]interface{}, 0) + + panelRefs.Add("panel", panel.Type, "") + for _, v := range panel.Datasource { + dashboardRefs.Add(models.StandardKindDataSource, v.Type, v.UID) + panelRefs.Add(models.StandardKindDataSource, v.Type, v.UID) + } + + for _, v := range panel.Transformer { + panelRefs.Add(models.StandardKindTransform, v, "") + } + + dashboardRefs.Add(models.StandardKindPanel, panel.Type, "") + p.References = panelRefs.Get() + summary.Nested = append(summary.Nested, p) + } + + summary.References = dashboardRefs.Get() + return summary, body, nil + } +} diff --git a/pkg/services/searchV2/object/dashboard_test.go b/pkg/services/store/kind/dashboard/summary_test.go similarity index 62% rename from pkg/services/searchV2/object/dashboard_test.go rename to pkg/services/store/kind/dashboard/summary_test.go index 43595ad7d5b..8f43ca77c3b 100644 --- a/pkg/services/searchV2/object/dashboard_test.go +++ b/pkg/services/store/kind/dashboard/summary_test.go @@ -1,39 +1,22 @@ -package object +package dashboard import ( "bytes" + "context" "encoding/json" "os" "path/filepath" "strings" "testing" - "github.com/grafana/grafana/pkg/services/searchV2/dslookup" - "github.com/grafana/grafana/pkg/services/store/object" "github.com/stretchr/testify/require" ) -func dsLookup() dslookup.DatasourceLookup { - return dslookup.CreateDatasourceLookup([]*dslookup.DatasourceQueryResult{ - { - UID: "P8045C56BDA891CB2", - Type: "cloudwatch", - Name: "cloudwatch-name", - IsDefault: false, - }, - { - UID: "default.uid", - Type: "default.type", - Name: "default.name", - IsDefault: true, - }, - }) -} - func TestReadSummaries(t *testing.T) { - devdash := "../../../../devenv/dev-dashboards/panel-graph/" + devdash := "../../../../../devenv/dev-dashboards/panel-graph/" - reader := NewDashboardSummaryBuilder(dsLookup()) + ctx := context.Background() + reader := NewStaticDashboardSummaryBuilder(dsLookupForTests()) failed := make([]string, 0, 10) err := filepath.Walk(devdash, @@ -51,10 +34,7 @@ func TestReadSummaries(t *testing.T) { } uid := path[len(devdash):] - summary, err := reader(&object.RawObject{ - UID: uid, - Body: body, - }) + summary, _, err := reader(ctx, uid, body) if err != nil { return err } diff --git a/pkg/services/searchV2/extract/targets.go b/pkg/services/store/kind/dashboard/targets.go similarity index 70% rename from pkg/services/searchV2/extract/targets.go rename to pkg/services/store/kind/dashboard/targets.go index 659050c012b..3dcfb67790b 100644 --- a/pkg/services/searchV2/extract/targets.go +++ b/pkg/services/store/kind/dashboard/targets.go @@ -1,25 +1,23 @@ -package extract +package dashboard import ( jsoniter "github.com/json-iterator/go" - - "github.com/grafana/grafana/pkg/services/searchV2/dslookup" ) type targetInfo struct { - lookup dslookup.DatasourceLookup - uids map[string]*dslookup.DataSourceRef + lookup DatasourceLookup + uids map[string]*DataSourceRef } -func newTargetInfo(lookup dslookup.DatasourceLookup) targetInfo { +func newTargetInfo(lookup DatasourceLookup) targetInfo { return targetInfo{ lookup: lookup, - uids: make(map[string]*dslookup.DataSourceRef), + uids: make(map[string]*DataSourceRef), } } -func (s *targetInfo) GetDatasourceInfo() []dslookup.DataSourceRef { - keys := make([]dslookup.DataSourceRef, len(s.uids)) +func (s *targetInfo) GetDatasourceInfo() []DataSourceRef { + keys := make([]DataSourceRef, len(s.uids)) i := 0 for _, v := range s.uids { keys[i] = *v @@ -34,7 +32,7 @@ func (s *targetInfo) addDatasource(iter *jsoniter.Iterator) { case jsoniter.StringValue: key := iter.ReadString() - dsRef := &dslookup.DataSourceRef{UID: key} + dsRef := &DataSourceRef{UID: key} if !isVariableRef(dsRef.UID) && !isSpecialDatasource(dsRef.UID) { ds := s.lookup.ByRef(dsRef) s.addRef(ds) @@ -47,7 +45,7 @@ func (s *targetInfo) addDatasource(iter *jsoniter.Iterator) { iter.Skip() case jsoniter.ObjectValue: - ref := &dslookup.DataSourceRef{} + ref := &DataSourceRef{} iter.ReadVal(ref) if !isVariableRef(ref.UID) && !isSpecialDatasource(ref.UID) { @@ -62,7 +60,7 @@ func (s *targetInfo) addDatasource(iter *jsoniter.Iterator) { } } -func (s *targetInfo) addRef(ref *dslookup.DataSourceRef) { +func (s *targetInfo) addRef(ref *DataSourceRef) { if ref != nil && ref.UID != "" { s.uids[ref.UID] = ref } @@ -84,7 +82,7 @@ func (s *targetInfo) addTarget(iter *jsoniter.Iterator) { } } -func (s *targetInfo) addPanel(panel PanelInfo) { +func (s *targetInfo) addPanel(panel panelInfo) { for idx, v := range panel.Datasource { if v.UID != "" { s.uids[v.UID] = &panel.Datasource[idx] diff --git a/pkg/services/searchV2/extract/testdata/all-selected-multi-datasource-variable-info.json b/pkg/services/store/kind/dashboard/testdata/all-selected-multi-datasource-variable-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/all-selected-multi-datasource-variable-info.json rename to pkg/services/store/kind/dashboard/testdata/all-selected-multi-datasource-variable-info.json diff --git a/pkg/services/searchV2/extract/testdata/all-selected-multi-datasource-variable.json b/pkg/services/store/kind/dashboard/testdata/all-selected-multi-datasource-variable.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/all-selected-multi-datasource-variable.json rename to pkg/services/store/kind/dashboard/testdata/all-selected-multi-datasource-variable.json diff --git a/pkg/services/searchV2/extract/testdata/all-selected-single-datasource-variable-info.json b/pkg/services/store/kind/dashboard/testdata/all-selected-single-datasource-variable-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/all-selected-single-datasource-variable-info.json rename to pkg/services/store/kind/dashboard/testdata/all-selected-single-datasource-variable-info.json diff --git a/pkg/services/searchV2/extract/testdata/all-selected-single-datasource-variable.json b/pkg/services/store/kind/dashboard/testdata/all-selected-single-datasource-variable.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/all-selected-single-datasource-variable.json rename to pkg/services/store/kind/dashboard/testdata/all-selected-single-datasource-variable.json diff --git a/pkg/services/searchV2/extract/testdata/check-string-datasource-id-info.json b/pkg/services/store/kind/dashboard/testdata/check-string-datasource-id-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/check-string-datasource-id-info.json rename to pkg/services/store/kind/dashboard/testdata/check-string-datasource-id-info.json diff --git a/pkg/services/searchV2/extract/testdata/check-string-datasource-id.json b/pkg/services/store/kind/dashboard/testdata/check-string-datasource-id.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/check-string-datasource-id.json rename to pkg/services/store/kind/dashboard/testdata/check-string-datasource-id.json diff --git a/pkg/services/searchV2/extract/testdata/datasource-variable-info.json b/pkg/services/store/kind/dashboard/testdata/datasource-variable-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/datasource-variable-info.json rename to pkg/services/store/kind/dashboard/testdata/datasource-variable-info.json diff --git a/pkg/services/searchV2/extract/testdata/datasource-variable-no-curly-braces-info.json b/pkg/services/store/kind/dashboard/testdata/datasource-variable-no-curly-braces-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/datasource-variable-no-curly-braces-info.json rename to pkg/services/store/kind/dashboard/testdata/datasource-variable-no-curly-braces-info.json diff --git a/pkg/services/searchV2/extract/testdata/datasource-variable-no-curly-braces.json b/pkg/services/store/kind/dashboard/testdata/datasource-variable-no-curly-braces.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/datasource-variable-no-curly-braces.json rename to pkg/services/store/kind/dashboard/testdata/datasource-variable-no-curly-braces.json diff --git a/pkg/services/searchV2/extract/testdata/datasource-variable.json b/pkg/services/store/kind/dashboard/testdata/datasource-variable.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/datasource-variable.json rename to pkg/services/store/kind/dashboard/testdata/datasource-variable.json diff --git a/pkg/services/searchV2/extract/testdata/default-datasource-variable-info.json b/pkg/services/store/kind/dashboard/testdata/default-datasource-variable-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/default-datasource-variable-info.json rename to pkg/services/store/kind/dashboard/testdata/default-datasource-variable-info.json diff --git a/pkg/services/searchV2/extract/testdata/default-datasource-variable.json b/pkg/services/store/kind/dashboard/testdata/default-datasource-variable.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/default-datasource-variable.json rename to pkg/services/store/kind/dashboard/testdata/default-datasource-variable.json diff --git a/pkg/services/searchV2/extract/testdata/devdash-all-panels-info.json b/pkg/services/store/kind/dashboard/testdata/devdash-all-panels-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/devdash-all-panels-info.json rename to pkg/services/store/kind/dashboard/testdata/devdash-all-panels-info.json diff --git a/pkg/services/searchV2/extract/testdata/devdash-graph-shared-tooltips-info.json b/pkg/services/store/kind/dashboard/testdata/devdash-graph-shared-tooltips-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/devdash-graph-shared-tooltips-info.json rename to pkg/services/store/kind/dashboard/testdata/devdash-graph-shared-tooltips-info.json diff --git a/pkg/services/searchV2/extract/testdata/empty-datasource-variable-info.json b/pkg/services/store/kind/dashboard/testdata/empty-datasource-variable-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/empty-datasource-variable-info.json rename to pkg/services/store/kind/dashboard/testdata/empty-datasource-variable-info.json diff --git a/pkg/services/searchV2/extract/testdata/empty-datasource-variable.json b/pkg/services/store/kind/dashboard/testdata/empty-datasource-variable.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/empty-datasource-variable.json rename to pkg/services/store/kind/dashboard/testdata/empty-datasource-variable.json diff --git a/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph-gradient-area-fills.json b/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph-gradient-area-fills.json new file mode 100644 index 00000000000..f49b1d3f861 --- /dev/null +++ b/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph-gradient-area-fills.json @@ -0,0 +1,93 @@ +{ + "name": "Panel Tests - Graph - Gradient Area Fills", + "labels": { + "gdev": "", + "graph": "", + "panel-tests": "" + }, + "URL": "/d/graph-gradient-area-fills.json/panel-tests-graph-gradient-area-fills", + "fields": { + "schemaVersion": 18 + }, + "nested": [ + { + "uid": "graph-gradient-area-fills.json#2", + "kind": "panel", + "name": "Req/s", + "URL": "/d/graph-gradient-area-fills.json/panel-tests-graph-gradient-area-fills?viewPanel=2", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph-gradient-area-fills.json#11", + "kind": "panel", + "name": "Req/s", + "URL": "/d/graph-gradient-area-fills.json/panel-tests-graph-gradient-area-fills?viewPanel=11", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph-gradient-area-fills.json#7", + "kind": "panel", + "name": "Memory", + "URL": "/d/graph-gradient-area-fills.json/panel-tests-graph-gradient-area-fills?viewPanel=7", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph-gradient-area-fills.json#10", + "kind": "panel", + "name": "Req/s", + "URL": "/d/graph-gradient-area-fills.json/panel-tests-graph-gradient-area-fills?viewPanel=10", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + } + ], + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] +} \ No newline at end of file diff --git a/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph-shared-tooltips.json b/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph-shared-tooltips.json new file mode 100644 index 00000000000..7b637a84ab6 --- /dev/null +++ b/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph-shared-tooltips.json @@ -0,0 +1,181 @@ +{ + "name": "Panel Tests - shared tooltips", + "labels": { + "gdev": "", + "graph-ng": "", + "panel-tests": "" + }, + "URL": "/d/graph-shared-tooltips.json/panel-tests-shared-tooltips", + "fields": { + "schemaVersion": 28 + }, + "nested": [ + { + "uid": "graph-shared-tooltips.json#4", + "kind": "panel", + "name": "two units", + "URL": "/d/graph-shared-tooltips.json/panel-tests-shared-tooltips?viewPanel=4", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "timeseries" + } + ] + }, + { + "uid": "graph-shared-tooltips.json#13", + "kind": "panel", + "name": "Speed vs Temperature (XY)", + "URL": "/d/graph-shared-tooltips.json/panel-tests-shared-tooltips?viewPanel=13", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "xychart" + }, + { + "kind": "transform", + "type": "organize" + }, + { + "kind": "transform", + "type": "seriesToColumns" + } + ] + }, + { + "uid": "graph-shared-tooltips.json#2", + "kind": "panel", + "name": "Cursor info", + "URL": "/d/graph-shared-tooltips.json/panel-tests-shared-tooltips?viewPanel=2", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "debug" + } + ] + }, + { + "uid": "graph-shared-tooltips.json#5", + "kind": "panel", + "name": "Only temperature", + "URL": "/d/graph-shared-tooltips.json/panel-tests-shared-tooltips?viewPanel=5", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "timeseries" + } + ] + }, + { + "uid": "graph-shared-tooltips.json#9", + "kind": "panel", + "name": "Only Speed", + "URL": "/d/graph-shared-tooltips.json/panel-tests-shared-tooltips?viewPanel=9", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "timeseries" + } + ] + }, + { + "uid": "graph-shared-tooltips.json#11", + "kind": "panel", + "name": "Panel Title", + "URL": "/d/graph-shared-tooltips.json/panel-tests-shared-tooltips?viewPanel=11", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "timeseries" + } + ] + }, + { + "uid": "graph-shared-tooltips.json#8", + "kind": "panel", + "name": "flot panel (temperature)", + "URL": "/d/graph-shared-tooltips.json/panel-tests-shared-tooltips?viewPanel=8", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph-shared-tooltips.json#10", + "kind": "panel", + "name": "flot panel (no units)", + "URL": "/d/graph-shared-tooltips.json/panel-tests-shared-tooltips?viewPanel=10", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + } + ], + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "debug" + }, + { + "kind": "panel", + "type": "graph" + }, + { + "kind": "panel", + "type": "timeseries" + }, + { + "kind": "panel", + "type": "xychart" + } + ] +} \ No newline at end of file diff --git a/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph-time-regions.json b/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph-time-regions.json new file mode 100644 index 00000000000..a67b14bd80c --- /dev/null +++ b/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph-time-regions.json @@ -0,0 +1,110 @@ +{ + "name": "Panel Tests - Graph Time Regions", + "labels": { + "gdev": "", + "graph": "", + "panel-tests": "" + }, + "URL": "/d/graph-time-regions.json/panel-tests-graph-time-regions", + "fields": { + "schemaVersion": 18 + }, + "nested": [ + { + "uid": "graph-time-regions.json#2", + "kind": "panel", + "name": "Business Hours", + "URL": "/d/graph-time-regions.json/panel-tests-graph-time-regions?viewPanel=2", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph-time-regions.json#4", + "kind": "panel", + "name": "Sunday's 20-23", + "URL": "/d/graph-time-regions.json/panel-tests-graph-time-regions?viewPanel=4", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph-time-regions.json#3", + "kind": "panel", + "name": "Each day of week", + "URL": "/d/graph-time-regions.json/panel-tests-graph-time-regions?viewPanel=3", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph-time-regions.json#5", + "kind": "panel", + "name": "05:00", + "URL": "/d/graph-time-regions.json/panel-tests-graph-time-regions?viewPanel=5", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph-time-regions.json#7", + "kind": "panel", + "name": "From 22:00 to 00:30 (crossing midnight)", + "URL": "/d/graph-time-regions.json/panel-tests-graph-time-regions?viewPanel=7", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + } + ], + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] +} \ No newline at end of file diff --git a/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph_tests.json b/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph_tests.json new file mode 100644 index 00000000000..2c729edd679 --- /dev/null +++ b/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph_tests.json @@ -0,0 +1,385 @@ +{ + "name": "Panel Tests - Graph", + "labels": { + "gdev": "", + "graph": "", + "panel-tests": "" + }, + "URL": "/d/graph_tests.json/panel-tests-graph", + "fields": { + "schemaVersion": 16 + }, + "nested": [ + { + "uid": "graph_tests.json#1", + "kind": "panel", + "name": "No Data Points Warning", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=1", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#2", + "kind": "panel", + "name": "Datapoints Outside Range Warning", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=2", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#3", + "kind": "panel", + "name": "Random walk series", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=3", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#4", + "kind": "panel", + "name": "Millisecond res x-axis and tooltip", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=4", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#6", + "kind": "panel", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=6", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "text" + } + ] + }, + { + "uid": "graph_tests.json#5", + "kind": "panel", + "name": "2 yaxis and axis labels", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=5", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#7", + "kind": "panel", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=7", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "text" + } + ] + }, + { + "uid": "graph_tests.json#8", + "kind": "panel", + "name": "null value connected", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=8", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#10", + "kind": "panel", + "name": "null value null as zero", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=10", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#13", + "kind": "panel", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=13", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "text" + } + ] + }, + { + "uid": "graph_tests.json#9", + "kind": "panel", + "name": "Stacking value ontop of nulls", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=9", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#14", + "kind": "panel", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=14", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "text" + } + ] + }, + { + "uid": "graph_tests.json#12", + "kind": "panel", + "name": "Stacking all series null segment", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=12", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#15", + "kind": "panel", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=15", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "text" + } + ] + }, + { + "uid": "graph_tests.json#21", + "kind": "panel", + "name": "Null between points", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=21", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#22", + "kind": "panel", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=22", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "text" + } + ] + }, + { + "uid": "graph_tests.json#20", + "kind": "panel", + "name": "Legend Table Single Series Should Take Minimum Height", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=20", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#16", + "kind": "panel", + "name": "Legend Table No Scroll Visible", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=16", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#17", + "kind": "panel", + "name": "Legend Table Should Scroll", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=17", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#18", + "kind": "panel", + "name": "Legend Table No Scroll Visible", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=18", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_tests.json#19", + "kind": "panel", + "name": "Legend Table No Scroll Visible", + "URL": "/d/graph_tests.json/panel-tests-graph?viewPanel=19", + "references": [ + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + } + ] + } + ], + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "ds", + "type": "testdata", + "UID": "PD8C576611E62080A" + }, + { + "kind": "panel", + "type": "graph" + }, + { + "kind": "panel", + "type": "text" + } + ] +} \ No newline at end of file diff --git a/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph_y_axis.json b/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph_y_axis.json new file mode 100644 index 00000000000..1d1bdc17964 --- /dev/null +++ b/pkg/services/store/kind/dashboard/testdata/gdev-walk-graph_y_axis.json @@ -0,0 +1,177 @@ +{ + "name": "Panel Tests - Graph - Y axis ticks", + "labels": { + "gdev": "", + "panel-tests": "" + }, + "URL": "/d/graph_y_axis.json/panel-tests-graph-y-axis-ticks", + "fields": { + "schemaVersion": 19 + }, + "nested": [ + { + "uid": "graph_y_axis.json#7", + "kind": "panel", + "name": "Data from 0 - 10K (unit short)", + "URL": "/d/graph_y_axis.json/panel-tests-graph-y-axis-ticks?viewPanel=7", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_y_axis.json#5", + "kind": "panel", + "name": "Data from 0 - 10K (unit bytes metric)", + "URL": "/d/graph_y_axis.json/panel-tests-graph-y-axis-ticks?viewPanel=5", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_y_axis.json#4", + "kind": "panel", + "name": "Data from 0 - 10K (unit bytes IEC)", + "URL": "/d/graph_y_axis.json/panel-tests-graph-y-axis-ticks?viewPanel=4", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_y_axis.json#2", + "kind": "panel", + "name": "Data from 0 - 10K (unit short)", + "URL": "/d/graph_y_axis.json/panel-tests-graph-y-axis-ticks?viewPanel=2", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_y_axis.json#3", + "kind": "panel", + "name": "Data from 0.0002 - 0.001 (unit short)", + "URL": "/d/graph_y_axis.json/panel-tests-graph-y-axis-ticks?viewPanel=3", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_y_axis.json#6", + "kind": "panel", + "name": "Data from 12000 - 30000 (unit ms)", + "URL": "/d/graph_y_axis.json/panel-tests-graph-y-axis-ticks?viewPanel=6", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_y_axis.json#9", + "kind": "panel", + "name": "Data from 0 - 1B (unit short)", + "URL": "/d/graph_y_axis.json/panel-tests-graph-y-axis-ticks?viewPanel=9", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_y_axis.json#10", + "kind": "panel", + "name": "Data from 0 - 1B (unit bytes)", + "URL": "/d/graph_y_axis.json/panel-tests-graph-y-axis-ticks?viewPanel=10", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + }, + { + "uid": "graph_y_axis.json#8", + "kind": "panel", + "name": "Data from 12000 - 30000 (unit ms)", + "URL": "/d/graph_y_axis.json/panel-tests-graph-y-axis-ticks?viewPanel=8", + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] + } + ], + "references": [ + { + "kind": "ds", + "type": "default.type", + "UID": "default.uid" + }, + { + "kind": "panel", + "type": "graph" + } + ] +} \ No newline at end of file diff --git a/pkg/services/searchV2/extract/testdata/mixed-datasource-with-variable-info.json b/pkg/services/store/kind/dashboard/testdata/mixed-datasource-with-variable-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/mixed-datasource-with-variable-info.json rename to pkg/services/store/kind/dashboard/testdata/mixed-datasource-with-variable-info.json diff --git a/pkg/services/searchV2/extract/testdata/mixed-datasource-with-variable.json b/pkg/services/store/kind/dashboard/testdata/mixed-datasource-with-variable.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/mixed-datasource-with-variable.json rename to pkg/services/store/kind/dashboard/testdata/mixed-datasource-with-variable.json diff --git a/pkg/services/searchV2/extract/testdata/panels-without-datasources-info.json b/pkg/services/store/kind/dashboard/testdata/panels-without-datasources-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/panels-without-datasources-info.json rename to pkg/services/store/kind/dashboard/testdata/panels-without-datasources-info.json diff --git a/pkg/services/searchV2/extract/testdata/panels-without-datasources.json b/pkg/services/store/kind/dashboard/testdata/panels-without-datasources.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/panels-without-datasources.json rename to pkg/services/store/kind/dashboard/testdata/panels-without-datasources.json diff --git a/pkg/services/searchV2/extract/testdata/repeated-datasource-variables-info.json b/pkg/services/store/kind/dashboard/testdata/repeated-datasource-variables-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/repeated-datasource-variables-info.json rename to pkg/services/store/kind/dashboard/testdata/repeated-datasource-variables-info.json diff --git a/pkg/services/searchV2/extract/testdata/repeated-datasource-variables-with-default-info.json b/pkg/services/store/kind/dashboard/testdata/repeated-datasource-variables-with-default-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/repeated-datasource-variables-with-default-info.json rename to pkg/services/store/kind/dashboard/testdata/repeated-datasource-variables-with-default-info.json diff --git a/pkg/services/searchV2/extract/testdata/repeated-datasource-variables-with-default.json b/pkg/services/store/kind/dashboard/testdata/repeated-datasource-variables-with-default.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/repeated-datasource-variables-with-default.json rename to pkg/services/store/kind/dashboard/testdata/repeated-datasource-variables-with-default.json diff --git a/pkg/services/searchV2/extract/testdata/repeated-datasource-variables.json b/pkg/services/store/kind/dashboard/testdata/repeated-datasource-variables.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/repeated-datasource-variables.json rename to pkg/services/store/kind/dashboard/testdata/repeated-datasource-variables.json diff --git a/pkg/services/searchV2/extract/testdata/special-datasource-types-info.json b/pkg/services/store/kind/dashboard/testdata/special-datasource-types-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/special-datasource-types-info.json rename to pkg/services/store/kind/dashboard/testdata/special-datasource-types-info.json diff --git a/pkg/services/searchV2/extract/testdata/special-datasource-types.json b/pkg/services/store/kind/dashboard/testdata/special-datasource-types.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/special-datasource-types.json rename to pkg/services/store/kind/dashboard/testdata/special-datasource-types.json diff --git a/pkg/services/searchV2/extract/testdata/string-datasource-variable-info.json b/pkg/services/store/kind/dashboard/testdata/string-datasource-variable-info.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/string-datasource-variable-info.json rename to pkg/services/store/kind/dashboard/testdata/string-datasource-variable-info.json diff --git a/pkg/services/searchV2/extract/testdata/string-datasource-variable.json b/pkg/services/store/kind/dashboard/testdata/string-datasource-variable.json similarity index 100% rename from pkg/services/searchV2/extract/testdata/string-datasource-variable.json rename to pkg/services/store/kind/dashboard/testdata/string-datasource-variable.json diff --git a/pkg/services/store/kind/dashboard/types.go b/pkg/services/store/kind/dashboard/types.go new file mode 100644 index 00000000000..19fdfcb63c4 --- /dev/null +++ b/pkg/services/store/kind/dashboard/types.go @@ -0,0 +1,32 @@ +package dashboard + +type panelInfo struct { + ID int64 `json:"id"` + Title string `json:"title"` + Description string `json:"description,omitempty"` + Type string `json:"type,omitempty"` // PluginID + PluginVersion string `json:"pluginVersion,omitempty"` + Datasource []DataSourceRef `json:"datasource,omitempty"` // UIDs + Transformer []string `json:"transformer,omitempty"` // ids of the transformation steps + + // Rows define panels as sub objects + Collapsed []panelInfo `json:"collapsed,omitempty"` +} + +type dashboardInfo struct { + UID string `json:"uid,omitempty"` + ID int64 `json:"id,omitempty"` // internal ID + Title string `json:"title"` + Description string `json:"description,omitempty"` + Tags []string `json:"tags"` + TemplateVars []string `json:"templateVars,omitempty"` // the keys used + Datasource []DataSourceRef `json:"datasource,omitempty"` // UIDs + Panels []panelInfo `json:"panels"` // nesed documents + SchemaVersion int64 `json:"schemaVersion"` + LinkCount int64 `json:"linkCount"` + TimeFrom string `json:"timeFrom"` + TimeTo string `json:"timeTo"` + TimeZone string `json:"timezone"` + Refresh string `json:"refresh,omitempty"` + ReadOnly bool `json:"readOnly,omitempty"` // editable = false +} diff --git a/pkg/services/store/kind/dummy/doc.go b/pkg/services/store/kind/dummy/doc.go new file mode 100644 index 00000000000..465cba40540 --- /dev/null +++ b/pkg/services/store/kind/dummy/doc.go @@ -0,0 +1,4 @@ +// 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 diff --git a/pkg/services/store/kind/dummy/summary.go b/pkg/services/store/kind/dummy/summary.go new file mode 100644 index 00000000000..6fdb14f5858 --- /dev/null +++ b/pkg/services/store/kind/dummy/summary.go @@ -0,0 +1,61 @@ +package dummy + +import ( + "context" + "fmt" + "time" + + "github.com/grafana/grafana/pkg/models" +) + +func GetObjectKindInfo(kind string) models.ObjectKindInfo { + return models.ObjectKindInfo{ + ID: kind, + Name: kind, + Description: "Dummy kind used for testing.", + IsRaw: true, + } +} + +func GetObjectSummaryBuilder(kind string) models.ObjectSummaryBuilder { + return func(ctx context.Context, uid string, body []byte) (*models.ObjectSummary, []byte, error) { + summary := &models.ObjectSummary{ + 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]interface{}{ + "field1": "a string", + "field2": 1.224, + "field4": true, + }, + Error: nil, // ignore for now + Nested: nil, // ignore for now + References: []*models.ObjectExternalReference{ + { + Kind: "ds", + Type: "influx", + UID: "xyz", + }, + { + Kind: "panel", + Type: "heatmap", + }, + { + Kind: "panel", + Type: "timeseries", + }, + }, + } + + if summary.UID != "" && uid != summary.UID { + return summary, nil, fmt.Errorf("internal UID mismatch") + } + + return summary, body, nil + } +} diff --git a/pkg/services/store/kind/playlist/summary.go b/pkg/services/store/kind/playlist/summary.go new file mode 100644 index 00000000000..fe070a4ef1a --- /dev/null +++ b/pkg/services/store/kind/playlist/summary.go @@ -0,0 +1,67 @@ +package playlist + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/grafana/grafana/pkg/coremodel/playlist" + "github.com/grafana/grafana/pkg/models" +) + +func GetObjectKindInfo() models.ObjectKindInfo { + return models.ObjectKindInfo{ + ID: models.StandardKindPlaylist, + Name: "Playlist", + Description: "Cycle though a collection of dashboards automatically", + } +} + +func GetObjectSummaryBuilder() models.ObjectSummaryBuilder { + return summaryBuilder +} + +func summaryBuilder(ctx context.Context, uid string, body []byte) (*models.ObjectSummary, []byte, error) { + obj := &playlist.Model{} + err := json.Unmarshal(body, obj) + if err != nil { + return nil, nil, err // unable to read object + } + + // TODO: fix model so this is not possible + if obj.Items == nil { + temp := make([]playlist.PlaylistItem, 0) + obj.Items = &temp + } + + summary := &models.ObjectSummary{ + UID: obj.Uid, + Name: obj.Name, + Description: fmt.Sprintf("%d items, refreshed every %s", len(*obj.Items), obj.Interval), + } + + for _, item := range *obj.Items { + switch item.Type { + case playlist.PlaylistItemTypeDashboardByUid: + summary.References = append(summary.References, &models.ObjectExternalReference{ + Kind: "dashboard", + UID: item.Value, + }) + + case playlist.PlaylistItemTypeDashboardByTag: + if summary.Labels == nil { + summary.Labels = make(map[string]string, 0) + } + summary.Labels[item.Value] = "" + + case playlist.PlaylistItemTypeDashboardById: + // obviously insufficient long term... but good to have an example :) + summary.Error = &models.ObjectErrorInfo{ + Message: "Playlist uses deprecated internal id system", + } + } + } + + out, err := json.Marshal(obj) + return summary, out, err +} diff --git a/pkg/services/store/kind/playlist/summary_test.go b/pkg/services/store/kind/playlist/summary_test.go new file mode 100644 index 00000000000..49b18cead85 --- /dev/null +++ b/pkg/services/store/kind/playlist/summary_test.go @@ -0,0 +1,39 @@ +package playlist + +import ( + "context" + "encoding/json" + "testing" + + "github.com/grafana/grafana/pkg/coremodel/playlist" + "github.com/stretchr/testify/require" +) + +func TestPlaylistSummary(t *testing.T) { + builder := GetObjectSummaryBuilder() + + // Do not parse invalid input + _, _, err := builder(context.Background(), "abc", []byte("{invalid json")) + require.Error(t, err) + + playlist := playlist.Model{ + Interval: "30s", + Name: "test", + Items: &[]playlist.PlaylistItem{ + {Type: playlist.PlaylistItemTypeDashboardByUid, Value: "D1"}, + {Type: playlist.PlaylistItemTypeDashboardByTag, Value: "tagA"}, + {Type: playlist.PlaylistItemTypeDashboardByUid, Value: "D3"}, + }, + } + out, err := json.Marshal(playlist) + require.NoError(t, err) + require.NotNil(t, out) + + // Do not parse invalid input + summary, body, err := builder(context.Background(), "abc", out) + require.NoError(t, err) + require.Equal(t, "test", summary.Name) + require.Equal(t, 2, len(summary.References)) + require.Equal(t, map[string]string{"tagA": ""}, summary.Labels) + require.True(t, json.Valid(body)) +} diff --git a/pkg/services/store/kind/png/summary.go b/pkg/services/store/kind/png/summary.go new file mode 100644 index 00000000000..a1abc078443 --- /dev/null +++ b/pkg/services/store/kind/png/summary.go @@ -0,0 +1,55 @@ +package png + +import ( + "bytes" + "context" + "image/png" + "strings" + + "github.com/grafana/grafana/pkg/models" +) + +func GetObjectKindInfo() models.ObjectKindInfo { + return models.ObjectKindInfo{ + ID: models.StandardKindPNG, + Name: "PNG", + Description: "PNG Image file", + IsRaw: true, + FileExtension: "png", + MimeType: "image/png", + } +} + +// SVG sanitizer based on the rendering service +func GetObjectSummaryBuilder() models.ObjectSummaryBuilder { + return func(ctx context.Context, uid string, body []byte) (*models.ObjectSummary, []byte, error) { + img, err := png.Decode(bytes.NewReader(body)) + if err != nil { + return nil, nil, err + } + + size := img.Bounds().Size() + summary := &models.ObjectSummary{ + Kind: models.StandardKindSVG, + Name: guessNameFromUID(uid), + UID: uid, + Fields: map[string]interface{}{ + "width": int64(size.X), + "height": int64(size.Y), + }, + } + return summary, body, 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 +} diff --git a/pkg/services/store/kind/png/summary_test.go b/pkg/services/store/kind/png/summary_test.go new file mode 100644 index 00000000000..09e7158ea76 --- /dev/null +++ b/pkg/services/store/kind/png/summary_test.go @@ -0,0 +1,33 @@ +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 := GetObjectSummaryBuilder()(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)) +} diff --git a/pkg/services/store/kind/registry.go b/pkg/services/store/kind/registry.go new file mode 100644 index 00000000000..66023abbd78 --- /dev/null +++ b/pkg/services/store/kind/registry.go @@ -0,0 +1,144 @@ +package kind + +import ( + "fmt" + "sort" + "sync" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/rendering" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/store/kind/dashboard" + "github.com/grafana/grafana/pkg/services/store/kind/dummy" + "github.com/grafana/grafana/pkg/services/store/kind/playlist" + "github.com/grafana/grafana/pkg/services/store/kind/png" + "github.com/grafana/grafana/pkg/services/store/kind/svg" + "github.com/grafana/grafana/pkg/setting" +) + +type KindRegistry interface { + Register(info models.ObjectKindInfo, builder models.ObjectSummaryBuilder) error + GetSummaryBuilder(kind string) models.ObjectSummaryBuilder + GetInfo(kind string) (models.ObjectKindInfo, error) + GetKinds() []models.ObjectKindInfo +} + +func NewKindRegistry() KindRegistry { + kinds := make(map[string]*kindValues) + kinds[models.StandardKindPlaylist] = &kindValues{ + info: playlist.GetObjectKindInfo(), + builder: playlist.GetObjectSummaryBuilder(), + } + kinds[models.StandardKindPNG] = &kindValues{ + info: png.GetObjectKindInfo(), + builder: png.GetObjectSummaryBuilder(), + } + + // FIXME -- these are registered because existing tests use them + for _, k := range []string{"dummy", "kind1", "kind2", "kind3"} { + kinds[k] = &kindValues{ + info: dummy.GetObjectKindInfo(k), + builder: dummy.GetObjectSummaryBuilder(k), + } + } + + // 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, sql *sqlstore.SQLStore) KindRegistry { + reg := NewKindRegistry() + + // Register Dashboard support + //----------------------- + _ = reg.Register(dashboard.GetObjectKindInfo(), dashboard.NewDashboardSummary(sql)) + + // Register SVG support + //----------------------- + info := svg.GetObjectKindInfo() + allowUnsanitizedSvgUpload := cfg != nil && cfg.Storage.AllowUnsanitizedSvgUpload + support := svg.GetObjectSummaryBuilder(allowUnsanitizedSvgUpload, renderer) + _ = reg.Register(info, support) + + return reg +} + +type kindValues struct { + info models.ObjectKindInfo + builder models.ObjectSummaryBuilder +} + +type registry struct { + mutex sync.RWMutex + kinds map[string]*kindValues + info []models.ObjectKindInfo +} + +func (r *registry) updateInfoArray() { + info := make([]models.ObjectKindInfo, 0, len(r.kinds)) + for _, v := range r.kinds { + info = append(info, v.info) + } + sort.Slice(info, func(i, j int) bool { + return info[i].ID < info[j].ID + }) + r.info = info +} + +func (r *registry) Register(info models.ObjectKindInfo, builder models.ObjectSummaryBuilder) 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) models.ObjectSummaryBuilder { + r.mutex.RLock() + defer r.mutex.RUnlock() + + v, ok := r.kinds[kind] + if ok { + return v.builder + } + return nil +} + +// GetInfo returns the registered info +func (r *registry) GetInfo(kind string) (models.ObjectKindInfo, error) { + r.mutex.RLock() + defer r.mutex.RUnlock() + + v, ok := r.kinds[kind] + if ok { + return v.info, nil + } + return models.ObjectKindInfo{}, fmt.Errorf("not found") +} + +// GetSummaryBuilder returns a builder or nil if not found +func (r *registry) GetKinds() []models.ObjectKindInfo { + r.mutex.RLock() + defer r.mutex.RUnlock() + + return r.info // returns a copy of the array +} diff --git a/pkg/services/store/kind/registry_test.go b/pkg/services/store/kind/registry_test.go new file mode 100644 index 00000000000..d645dc8d522 --- /dev/null +++ b/pkg/services/store/kind/registry_test.go @@ -0,0 +1,42 @@ +package kind + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/store/kind/dummy" +) + +func TestKindRegistry(t *testing.T) { + registry := NewKindRegistry() + err := registry.Register(dummy.GetObjectKindInfo("test"), dummy.GetObjectSummaryBuilder("test")) + require.NoError(t, err) + + ids := []string{} + for _, k := range registry.GetKinds() { + ids = append(ids, k.ID) + } + require.Equal(t, []string{ + "dummy", + "kind1", + "kind2", + "kind3", + "playlist", + "png", + "test", + }, ids) + + // Check playlist exists + info, err := registry.GetInfo(models.StandardKindPlaylist) + require.NoError(t, err) + require.Equal(t, "Playlist", info.Name) + require.False(t, info.IsRaw) + + // 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) +} diff --git a/pkg/services/store/kind/svg/summary.go b/pkg/services/store/kind/svg/summary.go new file mode 100644 index 00000000000..2e5fab85ad4 --- /dev/null +++ b/pkg/services/store/kind/svg/summary.go @@ -0,0 +1,66 @@ +package svg + +import ( + "context" + "fmt" + "strings" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/rendering" +) + +func GetObjectKindInfo() models.ObjectKindInfo { + return models.ObjectKindInfo{ + ID: models.StandardKindSVG, + Name: "SVG", + Description: "Scalable Vector Graphics", + IsRaw: true, + FileExtension: "svg", + MimeType: "image/svg+xml", + } +} + +// SVG sanitizer based on the rendering service +func GetObjectSummaryBuilder(allowUnsanitizedSvgUpload bool, renderer rendering.Service) models.ObjectSummaryBuilder { + return func(ctx context.Context, uid string, body []byte) (*models.ObjectSummary, []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 &models.ObjectSummary{ + Kind: models.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 +} diff --git a/pkg/services/store/go-is-svg/svg.go b/pkg/services/store/kind/svg/svg.go similarity index 92% rename from pkg/services/store/go-is-svg/svg.go rename to pkg/services/store/kind/svg/svg.go index 8dd980486aa..44609814442 100644 --- a/pkg/services/store/go-is-svg/svg.go +++ b/pkg/services/store/kind/svg/svg.go @@ -23,7 +23,7 @@ //FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR //OTHER DEALINGS IN THE SOFTWARE. -package issvg +package svg import ( "regexp" @@ -51,12 +51,6 @@ func isBinary(buf []byte) bool { } // Is returns true if the given buffer is a valid SVG image. -func Is(buf []byte) bool { +func IsSVG(buf []byte) bool { return !isBinary(buf) && svgRegex.Match(htmlCommentRegex.ReplaceAll(buf, []byte{})) } - -// IsSVG returns true if the given buffer is a valid SVG image. -// Alias to: Is() -func IsSVG(buf []byte) bool { - return Is(buf) -} diff --git a/pkg/services/store/object/dummy/dummy_server.go b/pkg/services/store/object/dummy/dummy_server.go index db5defe53eb..1819bd1a42f 100644 --- a/pkg/services/store/object/dummy/dummy_server.go +++ b/pkg/services/store/object/dummy/dummy_server.go @@ -13,6 +13,8 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/x/persistentcollection" "github.com/grafana/grafana/pkg/services/grpcserver" + "github.com/grafana/grafana/pkg/services/store" + "github.com/grafana/grafana/pkg/services/store/kind" "github.com/grafana/grafana/pkg/services/store/object" "github.com/grafana/grafana/pkg/setting" ) @@ -25,7 +27,6 @@ type ObjectVersionWithBody struct { type RawObjectWithHistory struct { Object *object.RawObject `json:"object,omitempty"` - Summary *object.ObjectSummary `json:"summary,omitempty"` History []*ObjectVersionWithBody `json:"history,omitempty"` } @@ -34,10 +35,11 @@ var ( rawObjectVersion = 6 ) -func ProvideDummyObjectServer(cfg *setting.Cfg, grpcServerProvider grpcserver.Provider) object.ObjectStoreServer { +func ProvideDummyObjectServer(cfg *setting.Cfg, grpcServerProvider grpcserver.Provider, kinds kind.KindRegistry) object.ObjectStoreServer { objectServer := &dummyObjectServer{ collection: persistentcollection.NewLocalFSPersistentCollection[*RawObjectWithHistory]("raw-object", cfg.DataPath, rawObjectVersion), log: log.New("in-memory-object-server"), + kinds: kinds, } object.RegisterObjectStoreServer(grpcServerProvider.GetServer(), objectServer) return objectServer @@ -46,6 +48,7 @@ func ProvideDummyObjectServer(cfg *setting.Cfg, grpcServerProvider grpcserver.Pr type dummyObjectServer struct { log log.Logger collection persistentcollection.PersistentCollection[*RawObjectWithHistory] + kinds kind.KindRegistry } func namespaceFromUID(uid string) string { @@ -115,15 +118,15 @@ func (i dummyObjectServer) Read(ctx context.Context, r *object.ReadObjectRequest Object: objVersion, } if r.WithSummary { - summary, _, e2 := object.GetSafeSaveObject(&object.WriteObjectRequest{ - UID: r.UID, - Kind: r.Kind, - Body: objVersion.Body, - }) - if e2 != nil { - return nil, e2 + // Since we do not store the summary, we can just recreate on demand + builder := i.kinds.GetSummaryBuilder(r.Kind) + if builder != nil { + summary, _, e2 := builder(ctx, r.UID, objVersion.Body) + if e2 != nil { + return nil, e2 + } + rsp.SummaryJson, err = json.Marshal(summary) } - rsp.SummaryJson, err = json.Marshal(summary) } return rsp, err } @@ -147,6 +150,10 @@ func createContentsHash(contents []byte) string { } func (i dummyObjectServer) update(ctx context.Context, r *object.WriteObjectRequest, namespace string) (*object.WriteObjectResponse, error) { + builder := i.kinds.GetSummaryBuilder(r.Kind) + if builder == nil { + return nil, fmt.Errorf("unsupported kind: " + r.Kind) + } rsp := &object.WriteObjectResponse{} updatedCount, err := i.collection.Update(ctx, namespace, func(i *RawObjectWithHistory) (bool, *RawObjectWithHistory, error) { @@ -164,7 +171,7 @@ func (i dummyObjectServer) update(ctx context.Context, r *object.WriteObjectRequ return false, nil, err } - modifier := object.UserFromContext(ctx) + modifier := store.UserFromContext(ctx) updated := &object.RawObject{ UID: r.UID, @@ -172,7 +179,7 @@ func (i dummyObjectServer) update(ctx context.Context, r *object.WriteObjectRequ Created: i.Object.Created, CreatedBy: i.Object.CreatedBy, Updated: time.Now().Unix(), - UpdatedBy: object.GetUserIDString(modifier), + UpdatedBy: store.GetUserIDString(modifier), Size: int64(len(r.Body)), ETag: createContentsHash(r.Body), Body: r.Body, @@ -218,7 +225,7 @@ func (i dummyObjectServer) update(ctx context.Context, r *object.WriteObjectRequ } func (i dummyObjectServer) insert(ctx context.Context, r *object.WriteObjectRequest, namespace string) (*object.WriteObjectResponse, error) { - modifier := object.GetUserIDString(object.UserFromContext(ctx)) + modifier := store.GetUserIDString(store.UserFromContext(ctx)) rawObj := &object.RawObject{ UID: r.UID, Kind: r.Kind, diff --git a/pkg/services/store/object/http.go b/pkg/services/store/object/http.go index 945f7d6d5ec..6eb4a323f51 100644 --- a/pkg/services/store/object/http.go +++ b/pkg/services/store/object/http.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/services/store/kind" "github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/api/response" @@ -22,12 +23,14 @@ type HTTPObjectStore interface { type httpObjectStore struct { store ObjectStoreServer log log.Logger + kinds kind.KindRegistry } -func ProvideHTTPObjectStore(store ObjectStoreServer) HTTPObjectStore { +func ProvideHTTPObjectStore(store ObjectStoreServer, kinds kind.KindRegistry) HTTPObjectStore { return &httpObjectStore{ store: store, log: log.New("http-object-store"), + kinds: kinds, } } @@ -116,6 +119,11 @@ func (s *httpObjectStore) doGetRawObject(c *models.ReqContext) response.Response if err != nil { return response.Error(500, "?", err) } + info, err := s.kinds.GetInfo(kind) + if err != nil { + return response.Error(400, "Unsupported kind", err) + } + if rsp.Object != nil && rsp.Object.Body != nil { // Configure etag support currentEtag := rsp.Object.ETag @@ -129,10 +137,13 @@ func (s *httpObjectStore) doGetRawObject(c *models.ReqContext) response.Response http.StatusNotModified, // 304 ) } - + mime := info.MimeType + if mime == "" { + mime = "application/json" + } return response.CreateNormalResponse( http.Header{ - "Content-Type": []string{"application/json"}, // TODO, based on kind!!! + "Content-Type": []string{mime}, "ETag": []string{currentEtag}, }, rsp.Object.Body, diff --git a/pkg/services/store/object/kind.go b/pkg/services/store/object/kind.go deleted file mode 100644 index c6240103fbf..00000000000 --- a/pkg/services/store/object/kind.go +++ /dev/null @@ -1,63 +0,0 @@ -package object - -import ( - "fmt" - "time" -) - -// NOTE this is just a temporary registry/list so we can use constants -// TODO replace with codegen from kind schema system - -const StandardKindDashboard = "dashboard" -const StandardKindFolder = "folder" -const StandardKindPanel = "panel" // types: heatmap, timeseries, table, ... -const StandardKindDataSource = "ds" // types: influx, prometheus, test, ... -const StandardKindTransform = "transform" // types: joinByField, pivot, organizeFields, ... -const StandardKindQuery = "query" - -// This is a stub -- it will soon lookup in a registry of known "kinds" -// Each kind will be able to define: -// 1. sanitize/normalize function (ie get safe bytes) -// 2. SummaryProvier -func GetSafeSaveObject(r *WriteObjectRequest) (*ObjectSummary, []byte, error) { - summary := &ObjectSummary{ - Name: fmt.Sprintf("hello: %s", r.Kind), - Description: fmt.Sprintf("Wrote at %s", time.Now().Local().String()), - Labels: map[string]string{ - "hello": "world", - "tag1": "", - "tag2": "", - }, - Fields: map[string]interface{}{ - "field1": "a string", - "field2": 1.224, - "field4": true, - }, - Error: nil, // ignore for now - Nested: nil, // ignore for now - References: []*ExternalReference{ - { - Kind: "ds", - Type: "influx", - UID: "xyz", - }, - { - Kind: "panel", - Type: "heatmap", - }, - { - Kind: "panel", - Type: "timeseries", - }, - }, - } - - if summary.UID != "" && r.UID != summary.UID { - return nil, nil, fmt.Errorf("internal UID mismatch") - } - if summary.Kind != "" && r.Kind != summary.Kind { - return nil, nil, fmt.Errorf("internal Kind mismatch") - } - - return summary, r.Body, nil -} diff --git a/pkg/services/store/object/summary.go b/pkg/services/store/object/summary.go deleted file mode 100644 index d19d3a6448c..00000000000 --- a/pkg/services/store/object/summary.go +++ /dev/null @@ -1,43 +0,0 @@ -package object - -// ObjectSummary is derived from a RawObject and should not depend on system state -// The summary is used for a unified search and listings objects since the fully -type ObjectSummary struct { - UID string `json:"uid,omitempty"` - Kind string `json:"kind,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Labels map[string]string `json:"labels,omitempty"` // "tags" are represented as keys with empty values - URL string `json:"URL,omitempty"` // not great to save here, but maybe not terrible :shrug: - Error *ObjectErrorInfo `json:"error,omitempty"` - - // Optional values -- schema will define the type - Fields map[string]interface{} `json:"fields,omitempty"` // Saved as JSON, returned in results, but values not sortable - - // eg: panels within dashboard - Nested []*ObjectSummary `json:"nested,omitempty"` - - // Optional references to external things - References []*ExternalReference `json:"references,omitempty"` - - // struct can not be extended - _ interface{} -} - -// Reference to another object outside itself -// This message is derived from the object body and can be used to search for references. -// This does not represent a method to declare a reference to another object. -type ExternalReference struct { - // datasource (instance), dashboard (instance), - Kind string `json:"kind,omitempty"` - - // prometheus / heatmap, heatamp|prometheus - Type string `json:"type,omitempty"` // flavor - - // Unique ID for this object - UID string `json:"UID,omitempty"` -} - -// ObjectSummaryBuilder will read an object and create the summary. -// This should not include values that depend on system state, only the raw object -type ObjectSummaryBuilder = func(obj *RawObject) (ObjectSummary, error) diff --git a/pkg/services/store/object/tests/server_integration_test.go b/pkg/services/store/object/tests/server_integration_test.go index eb3f6c8ee2d..5baee4d99e6 100644 --- a/pkg/services/store/object/tests/server_integration_test.go +++ b/pkg/services/store/object/tests/server_integration_test.go @@ -122,7 +122,7 @@ func TestObjectServer(t *testing.T) { fakeUser := fmt.Sprintf("user:%d:%s", testCtx.user.UserID, testCtx.user.Login) firstVersion := "1" - kind := "dashboard" + kind := "dummy" uid := "my-test-entity" body := []byte("{\"name\":\"John\"}") @@ -348,7 +348,7 @@ func TestObjectServer(t *testing.T) { version = append(version, res.Version) } require.Equal(t, []string{"my-test-entity", "uid2", "uid3", "uid4"}, uids) - require.Equal(t, []string{"dashboard", "dashboard", "kind2", "kind2"}, kinds) + require.Equal(t, []string{"dummy", "dummy", "kind2", "kind2"}, kinds) require.Equal(t, []string{ w1.Object.Version, w2.Object.Version, @@ -370,7 +370,7 @@ func TestObjectServer(t *testing.T) { version = append(version, res.Version) } require.Equal(t, []string{"my-test-entity", "uid2"}, uids) - require.Equal(t, []string{"dashboard", "dashboard"}, kinds) + require.Equal(t, []string{"dummy", "dummy"}, kinds) require.Equal(t, []string{ w1.Object.Version, w2.Object.Version, diff --git a/pkg/services/store/validate.go b/pkg/services/store/validate.go index 67ce257f439..00fa3eef4a9 100644 --- a/pkg/services/store/validate.go +++ b/pkg/services/store/validate.go @@ -9,7 +9,7 @@ import ( "strings" "github.com/grafana/grafana/pkg/infra/filestorage" - issvg "github.com/grafana/grafana/pkg/services/store/go-is-svg" + "github.com/grafana/grafana/pkg/services/store/kind/svg" "github.com/grafana/grafana/pkg/services/user" ) @@ -52,7 +52,7 @@ func fail(reason string) validationResult { func (s *standardStorageService) detectMimeType(ctx context.Context, user *user.SignedInUser, uploadRequest *UploadRequest) string { if strings.HasSuffix(uploadRequest.Path, ".svg") { - if issvg.IsSVG(uploadRequest.Contents) { + if svg.IsSVG(uploadRequest.Contents) { return "image/svg+xml" } }