diff --git a/pkg/models/dashboard_thumbs.go b/pkg/models/dashboard_thumbs.go index 0935d0c16e0..0d1b8a8cdd0 100644 --- a/pkg/models/dashboard_thumbs.go +++ b/pkg/models/dashboard_thumbs.go @@ -86,6 +86,7 @@ type DashboardThumbnail struct { Image []byte `json:"image"` MimeType string `json:"mimeType"` Updated time.Time `json:"updated"` + DsUIDs string `json:"-" xorm:"ds_uids"` } // @@ -123,6 +124,7 @@ type FindDashboardThumbnailCountCommand struct { type FindDashboardsWithStaleThumbnailsCommand struct { IncludeManuallyUploadedThumbnails bool + IncludeThumbnailsWithEmptyDsUIDs bool Theme Theme Kind ThumbnailKind Result []*DashboardWithStaleThumbnail @@ -133,6 +135,7 @@ type SaveDashboardThumbnailCommand struct { DashboardVersion int Image []byte MimeType string + DatasourceUIDs []string Result *DashboardThumbnail } diff --git a/pkg/services/export/export_dash.go b/pkg/services/export/export_dash.go index fc5190f1f25..00234318138 100644 --- a/pkg/services/export/export_dash.go +++ b/pkg/services/export/export_dash.go @@ -11,7 +11,7 @@ import ( "github.com/google/uuid" "github.com/grafana/grafana/pkg/infra/filestorage" - "github.com/grafana/grafana/pkg/services/searchV2" + "github.com/grafana/grafana/pkg/services/searchV2/dslookup" "github.com/grafana/grafana/pkg/services/searchV2/extract" "github.com/grafana/grafana/pkg/services/sqlstore" ) @@ -26,7 +26,7 @@ func exportDashboards(helper *commitHelper, job *gitExportJob) error { folders[0] = job.cfg.GeneralFolderPath // "general" } - lookup, err := searchV2.LoadDatasourceLookup(helper.ctx, helper.orgID, job.sql) + lookup, err := dslookup.LoadDatasourceLookup(helper.ctx, helper.orgID, job.sql) if err != nil { return err } diff --git a/pkg/services/searchV2/allowed_actions.go b/pkg/services/searchV2/allowed_actions.go index 46d0b24ab43..bf720efb979 100644 --- a/pkg/services/searchV2/allowed_actions.go +++ b/pkg/services/searchV2/allowed_actions.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "sort" "github.com/grafana/grafana-plugin-sdk-go/backend" @@ -232,9 +233,9 @@ func getEntityReferences(resp *backend.DataResponse) ([]entityReferences, error) return nil, errors.New("invalid value in dash_uid field") } - rawDsUids, ok := dsUidField.At(i).(*json.RawMessage) + rawDsUids, ok := dsUidField.At(i).(json.RawMessage) if !ok { - return nil, errors.New("invalid value in ds_uid field") + return nil, fmt.Errorf("invalid value for uid %s in ds_uid field: %s", uidField, dsUidField.At(i)) } var uids []string diff --git a/pkg/services/searchV2/bluge.go b/pkg/services/searchV2/bluge.go index 93658636fb0..79234085b12 100644 --- a/pkg/services/searchV2/bluge.go +++ b/pkg/services/searchV2/bluge.go @@ -496,7 +496,7 @@ func doSearchQuery( fURL := data.NewFieldFromFieldType(data.FieldTypeString, 0) fLocation := data.NewFieldFromFieldType(data.FieldTypeString, 0) fTags := data.NewFieldFromFieldType(data.FieldTypeNullableJSON, 0) - fDSUIDs := data.NewFieldFromFieldType(data.FieldTypeNullableJSON, 0) + fDSUIDs := data.NewFieldFromFieldType(data.FieldTypeJSON, 0) fExplain := data.NewFieldFromFieldType(data.FieldTypeNullableJSON, 0) fScore.Name = "score" @@ -592,14 +592,14 @@ func doSearchQuery( fTags.Append(nil) } - if len(dsUIDs) > 0 { - js, _ := json.Marshal(dsUIDs) - jsb := json.RawMessage(js) - fDSUIDs.Append(&jsb) - } else { - fDSUIDs.Append(nil) + if len(dsUIDs) == 0 { + dsUIDs = []string{} } + js, _ := json.Marshal(dsUIDs) + jsb := json.RawMessage(js) + fDSUIDs.Append(jsb) + if q.Explain { if isMatchAllQuery { fScore.Append(float64(fieldLen + q.From)) diff --git a/pkg/services/searchV2/dslookup/ds_lookup.go b/pkg/services/searchV2/dslookup/ds_lookup.go new file mode 100644 index 00000000000..5de658de293 --- /dev/null +++ b/pkg/services/searchV2/dslookup/ds_lookup.go @@ -0,0 +1,136 @@ +package dslookup + +import ( + "context" + + "github.com/grafana/grafana/pkg/services/sqlstore" +) + +type DataSourceRef struct { + UID string `json:"uid,omitempty"` + Type string `json:"type,omitempty"` +} + +type DatasourceLookup interface { + // ByRef will return the default DS given empty reference (nil ref, or empty ref.uid and ref.type) + ByRef(ref *DataSourceRef) *DataSourceRef + ByType(dsType string) []DataSourceRef +} + +type DatasourceQueryResult struct { + UID string `xorm:"uid"` + Type string `xorm:"type"` + Name string `xorm:"name"` + IsDefault bool `xorm:"is_default"` +} + +func CreateDatasourceLookup(rows []*DatasourceQueryResult) DatasourceLookup { + byUID := make(map[string]*DataSourceRef, 50) + byName := make(map[string]*DataSourceRef, 50) + byType := make(map[string][]DataSourceRef, 50) + var defaultDS *DataSourceRef + + for _, row := range rows { + ref := &DataSourceRef{ + UID: row.UID, + Type: row.Type, + } + byUID[row.UID] = ref + byName[row.Name] = ref + if row.IsDefault { + defaultDS = ref + } + + if _, ok := byType[row.Type]; !ok { + byType[row.Type] = make([]DataSourceRef, 0) + } + byType[row.Type] = append(byType[row.Type], *ref) + } + + grafanaDs := &DataSourceRef{ + UID: "grafana", + Type: "datasource", + } + if defaultDS == nil { + // fallback replicated from /pkg/api/frontendsettings.go + // https://github.com/grafana/grafana/blob/7ef21662f9ad74b80d832b9f2aa9db2fb4192741/pkg/api/frontendsettings.go#L51-L56 + defaultDS = grafanaDs + } + + if _, ok := byUID[grafanaDs.UID]; !ok { + byUID[grafanaDs.UID] = grafanaDs + } + + grafanaDsName := "-- Grafana --" + if _, ok := byName[grafanaDsName]; !ok { + byName[grafanaDsName] = grafanaDs + } + + return &DsLookup{ + byName: byName, + byUID: byUID, + byType: byType, + defaultDS: defaultDS, + } +} + +type DsLookup struct { + byName map[string]*DataSourceRef + byUID map[string]*DataSourceRef + byType map[string][]DataSourceRef + defaultDS *DataSourceRef +} + +func (d *DsLookup) ByRef(ref *DataSourceRef) *DataSourceRef { + if ref == nil { + return d.defaultDS + } + + key := "" + if ref.UID != "" { + ds, ok := d.byUID[ref.UID] + if ok { + return ds + } + key = ref.UID + } + if key == "" { + return d.defaultDS + } + ds, ok := d.byUID[key] + if ok { + return ds + } + + return d.byName[key] +} + +func (d *DsLookup) ByType(dsType string) []DataSourceRef { + ds, ok := d.byType[dsType] + if !ok { + return make([]DataSourceRef, 0) + } + + return ds +} + +func LoadDatasourceLookup(ctx context.Context, orgID int64, sql *sqlstore.SQLStore) (DatasourceLookup, error) { + rows := make([]*DatasourceQueryResult, 0) + + if err := sql.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + sess.Table("data_source"). + Where("org_id = ?", orgID). + Cols("uid", "name", "type", "is_default") + + err := sess.Find(&rows) + if err != nil { + return err + } + + return nil + }); err != nil { + return nil, err + } + + return CreateDatasourceLookup(rows), nil +} diff --git a/pkg/services/searchV2/extract/dashboard.go b/pkg/services/searchV2/extract/dashboard.go index ea418973473..01b74c807fa 100644 --- a/pkg/services/searchV2/extract/dashboard.go +++ b/pkg/services/searchV2/extract/dashboard.go @@ -6,6 +6,8 @@ import ( "strings" jsoniter "github.com/json-iterator/go" + + "github.com/grafana/grafana/pkg/services/searchV2/dslookup" ) func logf(format string, a ...interface{}) { @@ -22,43 +24,46 @@ type templateVariable struct { } type datasourceVariableLookup struct { - variableNameToRefs map[string][]DataSourceRef - dsLookup DatasourceLookup + variableNameToRefs map[string][]dslookup.DataSourceRef + dsLookup dslookup.DatasourceLookup } -func (d *datasourceVariableLookup) getDsRefsByTemplateVariableValue(value string, datasourceType string) []DataSourceRef { +func (d *datasourceVariableLookup) getDsRefsByTemplateVariableValue(value string, datasourceType string) []dslookup.DataSourceRef { switch value { case "default": // can be the default DS, or a DS with UID="default" - candidateDs := d.dsLookup.ByRef(&DataSourceRef{UID: value}) + candidateDs := d.dsLookup.ByRef(&dslookup.DataSourceRef{UID: value}) if candidateDs == nil { // get the actual default DS candidateDs = d.dsLookup.ByRef(nil) } if candidateDs != nil { - return []DataSourceRef{*candidateDs} + return []dslookup.DataSourceRef{*candidateDs} } - return []DataSourceRef{} + return []dslookup.DataSourceRef{} case "$__all": // TODO: filter datasources by template variable's regex return d.dsLookup.ByType(datasourceType) case "": - return []DataSourceRef{} + return []dslookup.DataSourceRef{} case "No data sources found": - return []DataSourceRef{} + return []dslookup.DataSourceRef{} default: - return []DataSourceRef{ - { - UID: value, - Type: datasourceType, - }, + // some variables use `ds.name` rather `ds.uid` + if ref := d.dsLookup.ByRef(&dslookup.DataSourceRef{ + UID: value, + }); ref != nil { + return []dslookup.DataSourceRef{*ref} } + + // discard variable + return []dslookup.DataSourceRef{} } } func (d *datasourceVariableLookup) add(templateVariable templateVariable) { - var refs []DataSourceRef + var refs []dslookup.DataSourceRef datasourceType, isDataSourceTypeValid := templateVariable.query.(string) if !isDataSourceTypeValid { @@ -81,8 +86,8 @@ func (d *datasourceVariableLookup) add(templateVariable templateVariable) { d.variableNameToRefs[templateVariable.name] = unique(refs) } -func unique(refs []DataSourceRef) []DataSourceRef { - var uniqueRefs []DataSourceRef +func unique(refs []dslookup.DataSourceRef) []dslookup.DataSourceRef { + var uniqueRefs []dslookup.DataSourceRef uidPresence := make(map[string]bool) for _, ref := range refs { if !uidPresence[ref.UID] { @@ -93,25 +98,25 @@ func unique(refs []DataSourceRef) []DataSourceRef { return uniqueRefs } -func (d *datasourceVariableLookup) getDatasourceRefs(name string) []DataSourceRef { +func (d *datasourceVariableLookup) getDatasourceRefs(name string) []dslookup.DataSourceRef { refs, ok := d.variableNameToRefs[name] if ok { return refs } - return []DataSourceRef{} + return []dslookup.DataSourceRef{} } -func newDatasourceVariableLookup(dsLookup DatasourceLookup) *datasourceVariableLookup { +func newDatasourceVariableLookup(dsLookup dslookup.DatasourceLookup) *datasourceVariableLookup { return &datasourceVariableLookup{ - variableNameToRefs: make(map[string][]DataSourceRef), + variableNameToRefs: make(map[string][]dslookup.DataSourceRef), dsLookup: dsLookup, } } // nolint:gocyclo // ReadDashboard will take a byte stream and return dashboard info -func ReadDashboard(stream io.Reader, lookup DatasourceLookup) (*DashboardInfo, error) { +func ReadDashboard(stream io.Reader, lookup dslookup.DatasourceLookup) (*DashboardInfo, error) { dash := &DashboardInfo{} iter := jsoniter.Parse(jsoniter.ConfigDefault, stream, 1024) @@ -277,7 +282,7 @@ func panelRequiresDatasource(panel PanelInfo) bool { return panel.Type != "row" } -func fillDefaultDatasources(dash *DashboardInfo, lookup DatasourceLookup) { +func fillDefaultDatasources(dash *DashboardInfo, lookup dslookup.DatasourceLookup) { for i, panel := range dash.Panels { if len(panel.Datasource) != 0 || !panelRequiresDatasource(panel) { continue @@ -285,14 +290,14 @@ func fillDefaultDatasources(dash *DashboardInfo, lookup DatasourceLookup) { defaultDs := lookup.ByRef(nil) if defaultDs != nil { - dash.Panels[i].Datasource = []DataSourceRef{*defaultDs} + dash.Panels[i].Datasource = []dslookup.DataSourceRef{*defaultDs} } } } func filterOutSpecialDatasources(dash *DashboardInfo) { for i, panel := range dash.Panels { - var dsRefs []DataSourceRef + var dsRefs []dslookup.DataSourceRef // partition into actual datasource references and variables for _, ds := range panel.Datasource { @@ -314,24 +319,33 @@ func filterOutSpecialDatasources(dash *DashboardInfo) { func replaceDatasourceVariables(dash *DashboardInfo, datasourceVariablesLookup *datasourceVariableLookup) { for i, panel := range dash.Panels { - var dsVariableRefs []DataSourceRef - var dsRefs []DataSourceRef + var dsVariableRefs []dslookup.DataSourceRef + var dsRefs []dslookup.DataSourceRef // partition into actual datasource references and variables for i := range panel.Datasource { - isVariableRef := strings.HasPrefix(panel.Datasource[i].UID, "$") - if isVariableRef { + uid := panel.Datasource[i].UID + if isVariableRef(uid) { dsVariableRefs = append(dsVariableRefs, panel.Datasource[i]) } else { dsRefs = append(dsRefs, panel.Datasource[i]) } } - dash.Panels[i].Datasource = append(dsRefs, findDatasourceRefsForVariables(dsVariableRefs, datasourceVariablesLookup)...) + variables := findDatasourceRefsForVariables(dsVariableRefs, datasourceVariablesLookup) + dash.Panels[i].Datasource = append(dsRefs, variables...) } } -func getDataSourceVariableName(dsVariableRef DataSourceRef) string { +func isSpecialDatasource(uid string) bool { + return uid == "-- Mixed --" || uid == "-- Dashboard --" +} + +func isVariableRef(uid string) bool { + return strings.HasPrefix(uid, "$") +} + +func getDataSourceVariableName(dsVariableRef dslookup.DataSourceRef) string { if strings.HasPrefix(dsVariableRef.UID, "${") { return strings.TrimPrefix(strings.TrimSuffix(dsVariableRef.UID, "}"), "${") } @@ -339,8 +353,8 @@ func getDataSourceVariableName(dsVariableRef DataSourceRef) string { return strings.TrimPrefix(dsVariableRef.UID, "$") } -func findDatasourceRefsForVariables(dsVariableRefs []DataSourceRef, datasourceVariablesLookup *datasourceVariableLookup) []DataSourceRef { - var referencedDs []DataSourceRef +func findDatasourceRefsForVariables(dsVariableRefs []dslookup.DataSourceRef, datasourceVariablesLookup *datasourceVariableLookup) []dslookup.DataSourceRef { + var referencedDs []dslookup.DataSourceRef for _, dsVariableRef := range dsVariableRefs { variableName := getDataSourceVariableName(dsVariableRef) refs := datasourceVariablesLookup.getDatasourceRefs(variableName) @@ -350,7 +364,7 @@ func findDatasourceRefsForVariables(dsVariableRefs []DataSourceRef, datasourceVa } // will always return strings for now -func readPanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup) PanelInfo { +func readPanelInfo(iter *jsoniter.Iterator, lookup dslookup.DatasourceLookup) PanelInfo { panel := PanelInfo{} targets := newTargetInfo(lookup) diff --git a/pkg/services/searchV2/extract/dashboard_test.go b/pkg/services/searchV2/extract/dashboard_test.go index 6a82633df29..8a5eef1215a 100644 --- a/pkg/services/searchV2/extract/dashboard_test.go +++ b/pkg/services/searchV2/extract/dashboard_test.go @@ -8,41 +8,50 @@ import ( "strings" "testing" + "github.com/grafana/grafana/pkg/services/searchV2/dslookup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -type dsLookup struct { -} - -func (d *dsLookup) ByRef(ref *DataSourceRef) *DataSourceRef { - if ref == nil || ref.UID == "" { - return &DataSourceRef{ - UID: "default.uid", - Type: "default.type", - } - } - - if ref.UID == "default" { - return nil - } - return ref -} - -func (d *dsLookup) ByType(dsType string) []DataSourceRef { - if dsType == "sqlite-datasource" { - return []DataSourceRef{ - { - UID: "sqlite-1", - Type: "sqlite-datasource", - }, - { - UID: "sqlite-2", - Type: "sqlite-datasource", - }, - } - } - return make([]DataSourceRef, 0) +func dsLookup() dslookup.DatasourceLookup { + return dslookup.CreateDatasourceLookup([]*dslookup.DatasourceQueryResult{ + { + UID: "P8045C56BDA891CB2", + Type: "cloudwatch", + Name: "cloudwatch-name", + IsDefault: false, + }, + { + UID: "PD8C576611E62080A", + Type: "testdata", + Name: "gdev-testdata", + IsDefault: false, + }, + { + UID: "dgd92lq7k", + Type: "frser-sqlite-datasource", + Name: "frser-sqlite-datasource-name", + IsDefault: false, + }, + { + UID: "sqlite-1", + Type: "sqlite-datasource", + Name: "SQLite Grafana", + IsDefault: false, + }, + { + UID: "sqlite-2", + Type: "sqlite-datasource", + Name: "SQLite Grafana2", + IsDefault: false, + }, + { + UID: "default.uid", + Type: "default.type", + Name: "default.name", + IsDefault: true, + }, + }) } func TestReadDashboard(t *testing.T) { @@ -80,7 +89,7 @@ func TestReadDashboard(t *testing.T) { } require.NoError(t, err) - dash, err := ReadDashboard(f, &dsLookup{}) + dash, err := ReadDashboard(f, dsLookup()) sortDatasources(dash) require.NoError(t, err) diff --git a/pkg/services/searchV2/extract/targets.go b/pkg/services/searchV2/extract/targets.go index 7714796e129..659050c012b 100644 --- a/pkg/services/searchV2/extract/targets.go +++ b/pkg/services/searchV2/extract/targets.go @@ -2,22 +2,24 @@ package extract import ( jsoniter "github.com/json-iterator/go" + + "github.com/grafana/grafana/pkg/services/searchV2/dslookup" ) type targetInfo struct { - lookup DatasourceLookup - uids map[string]*DataSourceRef + lookup dslookup.DatasourceLookup + uids map[string]*dslookup.DataSourceRef } -func newTargetInfo(lookup DatasourceLookup) targetInfo { +func newTargetInfo(lookup dslookup.DatasourceLookup) targetInfo { return targetInfo{ lookup: lookup, - uids: make(map[string]*DataSourceRef), + uids: make(map[string]*dslookup.DataSourceRef), } } -func (s *targetInfo) GetDatasourceInfo() []DataSourceRef { - keys := make([]DataSourceRef, len(s.uids)) +func (s *targetInfo) GetDatasourceInfo() []dslookup.DataSourceRef { + keys := make([]dslookup.DataSourceRef, len(s.uids)) i := 0 for _, v := range s.uids { keys[i] = *v @@ -31,18 +33,28 @@ func (s *targetInfo) addDatasource(iter *jsoniter.Iterator) { switch iter.WhatIsNext() { case jsoniter.StringValue: key := iter.ReadString() - ds := s.lookup.ByRef(&DataSourceRef{UID: key}) - s.addRef(ds) + + dsRef := &dslookup.DataSourceRef{UID: key} + if !isVariableRef(dsRef.UID) && !isSpecialDatasource(dsRef.UID) { + ds := s.lookup.ByRef(dsRef) + s.addRef(ds) + } else { + s.addRef(dsRef) + } case jsoniter.NilValue: s.addRef(s.lookup.ByRef(nil)) iter.Skip() case jsoniter.ObjectValue: - ref := &DataSourceRef{} + ref := &dslookup.DataSourceRef{} iter.ReadVal(ref) - ds := s.lookup.ByRef(ref) - s.addRef(ds) + + if !isVariableRef(ref.UID) && !isSpecialDatasource(ref.UID) { + s.addRef(s.lookup.ByRef(ref)) + } else { + s.addRef(ref) + } default: v := iter.Read() @@ -50,7 +62,7 @@ func (s *targetInfo) addDatasource(iter *jsoniter.Iterator) { } } -func (s *targetInfo) addRef(ref *DataSourceRef) { +func (s *targetInfo) addRef(ref *dslookup.DataSourceRef) { if ref != nil && ref.UID != "" { s.uids[ref.UID] = ref } diff --git a/pkg/services/searchV2/extract/testdata/check-string-datasource-id-info.json b/pkg/services/searchV2/extract/testdata/check-string-datasource-id-info.json index ba57bdcecda..136e20a8571 100644 --- a/pkg/services/searchV2/extract/testdata/check-string-datasource-id-info.json +++ b/pkg/services/searchV2/extract/testdata/check-string-datasource-id-info.json @@ -4,7 +4,8 @@ "tags": null, "datasource": [ { - "uid": "-- Grafana --" + "uid": "grafana", + "type": "datasource" } ], "panels": [ @@ -15,7 +16,8 @@ "pluginVersion": "7.5.0-pre", "datasource": [ { - "uid": "-- Grafana --" + "uid": "grafana", + "type": "datasource" } ] } diff --git a/pkg/services/searchV2/extract/testdata/datasource-variable-info.json b/pkg/services/searchV2/extract/testdata/datasource-variable-info.json index 21f6bcc7e0f..0988c5b04fa 100644 --- a/pkg/services/searchV2/extract/testdata/datasource-variable-info.json +++ b/pkg/services/searchV2/extract/testdata/datasource-variable-info.json @@ -7,7 +7,7 @@ ], "datasource": [ { - "uid": "SQLite Grafana", + "uid": "sqlite-1", "type": "sqlite-datasource" } ], @@ -19,7 +19,7 @@ "pluginVersion": "9.1.0-pre", "datasource": [ { - "uid": "SQLite Grafana", + "uid": "sqlite-1", "type": "sqlite-datasource" } ] diff --git a/pkg/services/searchV2/extract/testdata/datasource-variable-no-curly-braces-info.json b/pkg/services/searchV2/extract/testdata/datasource-variable-no-curly-braces-info.json index 21f6bcc7e0f..0988c5b04fa 100644 --- a/pkg/services/searchV2/extract/testdata/datasource-variable-no-curly-braces-info.json +++ b/pkg/services/searchV2/extract/testdata/datasource-variable-no-curly-braces-info.json @@ -7,7 +7,7 @@ ], "datasource": [ { - "uid": "SQLite Grafana", + "uid": "sqlite-1", "type": "sqlite-datasource" } ], @@ -19,7 +19,7 @@ "pluginVersion": "9.1.0-pre", "datasource": [ { - "uid": "SQLite Grafana", + "uid": "sqlite-1", "type": "sqlite-datasource" } ] diff --git a/pkg/services/searchV2/extract/testdata/mixed-datasource-with-variable-info.json b/pkg/services/searchV2/extract/testdata/mixed-datasource-with-variable-info.json index b472a4881db..fde70a0cdb1 100644 --- a/pkg/services/searchV2/extract/testdata/mixed-datasource-with-variable-info.json +++ b/pkg/services/searchV2/extract/testdata/mixed-datasource-with-variable-info.json @@ -6,14 +6,14 @@ "dsVariable" ], "datasource": [ - { - "uid": "gdev-testdata", - "type": "testdata" - }, { "uid": "default.uid", "type": "default.type" }, + { + "uid": "PD8C576611E62080A", + "type": "testdata" + }, { "uid": "P8045C56BDA891CB2", "type": "cloudwatch" @@ -26,14 +26,14 @@ "type": "table", "pluginVersion": "9.1.0-pre", "datasource": [ - { - "uid": "gdev-testdata", - "type": "testdata" - }, { "uid": "default.uid", "type": "default.type" }, + { + "uid": "PD8C576611E62080A", + "type": "testdata" + }, { "uid": "P8045C56BDA891CB2", "type": "cloudwatch" diff --git a/pkg/services/searchV2/extract/testdata/repeated-datasource-variables-info.json b/pkg/services/searchV2/extract/testdata/repeated-datasource-variables-info.json index edb526ad04a..30de2821c43 100644 --- a/pkg/services/searchV2/extract/testdata/repeated-datasource-variables-info.json +++ b/pkg/services/searchV2/extract/testdata/repeated-datasource-variables-info.json @@ -7,11 +7,11 @@ ], "datasource": [ { - "uid": "SQLite Grafana2", + "uid": "sqlite-2", "type": "sqlite-datasource" }, { - "uid": "SQLite Grafana", + "uid": "sqlite-1", "type": "sqlite-datasource" } ], @@ -23,11 +23,11 @@ "pluginVersion": "9.1.0-pre", "datasource": [ { - "uid": "SQLite Grafana2", + "uid": "sqlite-2", "type": "sqlite-datasource" }, { - "uid": "SQLite Grafana", + "uid": "sqlite-1", "type": "sqlite-datasource" } ] diff --git a/pkg/services/searchV2/extract/testdata/repeated-datasource-variables-with-default-info.json b/pkg/services/searchV2/extract/testdata/repeated-datasource-variables-with-default-info.json index 8f461ac4a74..724d946928d 100644 --- a/pkg/services/searchV2/extract/testdata/repeated-datasource-variables-with-default-info.json +++ b/pkg/services/searchV2/extract/testdata/repeated-datasource-variables-with-default-info.json @@ -6,13 +6,13 @@ "dsVariable" ], "datasource": [ - { - "uid": "gdev-testdata", - "type": "testdata" - }, { "uid": "default.uid", "type": "default.type" + }, + { + "uid": "PD8C576611E62080A", + "type": "testdata" } ], "panels": [ @@ -22,13 +22,13 @@ "type": "table", "pluginVersion": "9.1.0-pre", "datasource": [ - { - "uid": "gdev-testdata", - "type": "testdata" - }, { "uid": "default.uid", "type": "default.type" + }, + { + "uid": "PD8C576611E62080A", + "type": "testdata" } ] } diff --git a/pkg/services/searchV2/extract/testdata/string-datasource-variable-info.json b/pkg/services/searchV2/extract/testdata/string-datasource-variable-info.json index 21f6bcc7e0f..0988c5b04fa 100644 --- a/pkg/services/searchV2/extract/testdata/string-datasource-variable-info.json +++ b/pkg/services/searchV2/extract/testdata/string-datasource-variable-info.json @@ -7,7 +7,7 @@ ], "datasource": [ { - "uid": "SQLite Grafana", + "uid": "sqlite-1", "type": "sqlite-datasource" } ], @@ -19,7 +19,7 @@ "pluginVersion": "9.1.0-pre", "datasource": [ { - "uid": "SQLite Grafana", + "uid": "sqlite-1", "type": "sqlite-datasource" } ] diff --git a/pkg/services/searchV2/extract/types.go b/pkg/services/searchV2/extract/types.go index f149ba5d3c5..70613055638 100644 --- a/pkg/services/searchV2/extract/types.go +++ b/pkg/services/searchV2/extract/types.go @@ -1,43 +1,36 @@ package extract -type DatasourceLookup interface { - // ByRef will return the default DS given empty reference (nil ref, or empty ref.uid and ref.type) - ByRef(ref *DataSourceRef) *DataSourceRef - ByType(dsType string) []DataSourceRef -} - -type DataSourceRef struct { - UID string `json:"uid,omitempty"` - Type string `json:"type,omitempty"` -} +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 []DataSourceRef `json:"datasource,omitempty"` // UIDs - Transformer []string `json:"transformer,omitempty"` // ids of the transformation steps + 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 []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 + 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/index.go b/pkg/services/searchV2/index.go index 7132735ce03..7911195b117 100644 --- a/pkg/services/searchV2/index.go +++ b/pkg/services/searchV2/index.go @@ -15,6 +15,7 @@ import ( "time" "github.com/grafana/grafana/pkg/infra/log" + "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" @@ -725,7 +726,7 @@ func (l sqlDashboardLoader) LoadDashboards(ctx context.Context, orgID int64, das } // key will allow name or uid - lookup, err := LoadDatasourceLookup(ctx, orgID, l.sql) + lookup, err := dslookup.LoadDatasourceLookup(ctx, orgID, l.sql) if err != nil { return dashboards, err } @@ -813,109 +814,3 @@ type dashboardQueryResult struct { Created time.Time Updated time.Time } - -type datasourceQueryResult struct { - UID string `xorm:"uid"` - Type string `xorm:"type"` - Name string `xorm:"name"` - IsDefault bool `xorm:"is_default"` -} - -func createDatasourceLookup(rows []*datasourceQueryResult) extract.DatasourceLookup { - byUID := make(map[string]*extract.DataSourceRef, 50) - byName := make(map[string]*extract.DataSourceRef, 50) - byType := make(map[string][]extract.DataSourceRef, 50) - var defaultDS *extract.DataSourceRef - - for _, row := range rows { - ref := &extract.DataSourceRef{ - UID: row.UID, - Type: row.Type, - } - byUID[row.UID] = ref - byName[row.Name] = ref - if row.IsDefault { - defaultDS = ref - } - - if _, ok := byType[row.Type]; !ok { - byType[row.Type] = make([]extract.DataSourceRef, 5) - } - byType[row.Type] = append(byType[row.Type], *ref) - } - - if defaultDS == nil { - // fallback replicated from /pkg/api/frontendsettings.go - // https://github.com/grafana/grafana/blob/7ef21662f9ad74b80d832b9f2aa9db2fb4192741/pkg/api/frontendsettings.go#L51-L56 - defaultDS = &extract.DataSourceRef{ - UID: "grafana", - Type: "datasource", - } - } - - return &dsLookup{ - byName: byName, - byUID: byUID, - byType: byType, - defaultDS: defaultDS, - } -} - -type dsLookup struct { - byName map[string]*extract.DataSourceRef - byUID map[string]*extract.DataSourceRef - byType map[string][]extract.DataSourceRef - defaultDS *extract.DataSourceRef -} - -func (d *dsLookup) ByRef(ref *extract.DataSourceRef) *extract.DataSourceRef { - if ref == nil { - return d.defaultDS - } - key := "" - if ref.UID != "" { - ds, ok := d.byUID[ref.UID] - if ok { - return ds - } - key = ref.UID - } - if key == "" { - return d.defaultDS - } - ds, ok := d.byUID[key] - if ok { - return ds - } - return d.byName[key] -} - -func (d *dsLookup) ByType(dsType string) []extract.DataSourceRef { - ds, ok := d.byType[dsType] - if !ok { - return make([]extract.DataSourceRef, 0) - } - - return ds -} - -func LoadDatasourceLookup(ctx context.Context, orgID int64, sql *sqlstore.SQLStore) (extract.DatasourceLookup, error) { - rows := make([]*datasourceQueryResult, 0) - - if err := sql.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { - sess.Table("data_source"). - Where("org_id = ?", orgID). - Cols("uid", "name", "type", "is_default") - - err := sess.Find(&rows) - if err != nil { - return err - } - - return nil - }); err != nil { - return nil, err - } - - return createDatasourceLookup(rows), nil -} diff --git a/pkg/services/searchV2/search_service_mock.go b/pkg/services/searchV2/search_service_mock.go new file mode 100644 index 00000000000..b9185296e4c --- /dev/null +++ b/pkg/services/searchV2/search_service_mock.go @@ -0,0 +1,70 @@ +// Code generated by mockery v2.10.6. DO NOT EDIT. + +package searchV2 + +import ( + context "context" + + backend "github.com/grafana/grafana-plugin-sdk-go/backend" + + mock "github.com/stretchr/testify/mock" +) + +// MockSearchService is an autogenerated mock type for the SearchService type +type MockSearchService struct { + mock.Mock +} + +// DoDashboardQuery provides a mock function with given fields: ctx, user, orgId, query +func (_m *MockSearchService) DoDashboardQuery(ctx context.Context, user *backend.User, orgId int64, query DashboardQuery) *backend.DataResponse { + ret := _m.Called(ctx, user, orgId, query) + + var r0 *backend.DataResponse + if rf, ok := ret.Get(0).(func(context.Context, *backend.User, int64, DashboardQuery) *backend.DataResponse); ok { + r0 = rf(ctx, user, orgId, query) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*backend.DataResponse) + } + } + + return r0 +} + +// IsDisabled provides a mock function with given fields: +func (_m *MockSearchService) IsDisabled() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// RegisterDashboardIndexExtender provides a mock function with given fields: ext +func (_m *MockSearchService) RegisterDashboardIndexExtender(ext DashboardIndexExtender) { + _m.Called(ext) +} + +// Run provides a mock function with given fields: ctx +func (_m *MockSearchService) Run(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TriggerReIndex provides a mock function with given fields: +func (_m *MockSearchService) TriggerReIndex() { + _m.Called() +} diff --git a/pkg/services/searchV2/service.go b/pkg/services/searchV2/service.go index 8f4f29c545f..0f76b7ad67f 100644 --- a/pkg/services/searchV2/service.go +++ b/pkg/services/searchV2/service.go @@ -112,7 +112,7 @@ func (s *StandardSearchService) getUser(ctx context.Context, backendUser *backen } err := s.sql.GetSignedInUser(ctx, getSignedInUserQuery) if err != nil { - s.logger.Error("Error while retrieving user", "error", err, "email", backendUser.Email) + s.logger.Error("Error while retrieving user", "error", err, "email", backendUser.Email, "login", getSignedInUserQuery.Login) return nil, errors.New("auth error") } diff --git a/pkg/services/searchV2/stub.go b/pkg/services/searchV2/stub.go index 83f4bc8cb34..95425600002 100644 --- a/pkg/services/searchV2/stub.go +++ b/pkg/services/searchV2/stub.go @@ -10,6 +10,10 @@ import ( type stubSearchService struct { } +func (s *stubSearchService) IsDisabled() bool { + return true +} + func (s *stubSearchService) TriggerReIndex() { // noop. } diff --git a/pkg/services/searchV2/testdata/allowed_actions_scope_all.golden.jsonc b/pkg/services/searchV2/testdata/allowed_actions_scope_all.golden.jsonc index 3e65fd18bfc..09c22c5b4d9 100644 --- a/pkg/services/searchV2/testdata/allowed_actions_scope_all.golden.jsonc +++ b/pkg/services/searchV2/testdata/allowed_actions_scope_all.golden.jsonc @@ -19,9 +19,9 @@ // +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ // | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: allowed_actions | // | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | Type: []json.RawMessage | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | Type: []json.RawMessage | // +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -// | folder | ujaM1h6nz | abc2 | | /dashboards/f/ujaM1h6nz/abc2 | null | null | | [{"kind":"folder","uid":"ujaM1h6nz","actions":["folders.permissions:read","folders.permissions:write","folders:create","folders:delete","folders:read","folders:write"]}] | +// | folder | ujaM1h6nz | abc2 | | /dashboards/f/ujaM1h6nz/abc2 | null | [] | | [{"kind":"folder","uid":"ujaM1h6nz","actions":["folders.permissions:read","folders.permissions:write","folders:create","folders:delete","folders:read","folders:write"]}] | // | dashboard | 7MeksYbmk | Alerting with TestData | | /d/7MeksYbmk/alerting-with-testdata | [ | [ | yboVMzb7z | [{"kind":"dashboard","uid":"7MeksYbmk","actions":["dashboards.permissions:read","dashboards.permissions:write","dashboards:create","dashboards:delete","dashboards:read","dashboards:write"]},{"kind":"datasource","uid":"datasource-1","actions":["datasources.id:read","datasources.permissions:read","datasources.permissions:write","datasources:delete","datasources:explore","datasources:query","datasources:read","datasources:write"]}] | // | | | | | | "gdev", | "datasource-1" | | | // | | | | | | "alerting" | ] | | | @@ -115,8 +115,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -183,7 +182,7 @@ ] ], [ - null, + [], [ "datasource-1" ], diff --git a/pkg/services/searchV2/testdata/allowed_actions_scope_uids.golden.jsonc b/pkg/services/searchV2/testdata/allowed_actions_scope_uids.golden.jsonc index d79fca3b0c0..3f6cbb68260 100644 --- a/pkg/services/searchV2/testdata/allowed_actions_scope_uids.golden.jsonc +++ b/pkg/services/searchV2/testdata/allowed_actions_scope_uids.golden.jsonc @@ -19,9 +19,9 @@ // +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ // | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: allowed_actions | // | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | Type: []json.RawMessage | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | Type: []json.RawMessage | // +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -// | folder | ujaM1h6nz | abc2 | | /dashboards/f/ujaM1h6nz/abc2 | null | null | | [{"kind":"folder","uid":"ujaM1h6nz","actions":["folders:read"]}] | +// | folder | ujaM1h6nz | abc2 | | /dashboards/f/ujaM1h6nz/abc2 | null | [] | | [{"kind":"folder","uid":"ujaM1h6nz","actions":["folders:read"]}] | // | dashboard | 7MeksYbmk | Alerting with TestData | | /d/7MeksYbmk/alerting-with-testdata | [ | [ | yboVMzb7z | [{"kind":"dashboard","uid":"7MeksYbmk","actions":["dashboards:write"]},{"kind":"datasource","uid":"datasource-1","actions":[]}] | // | | | | | | "gdev", | "datasource-1" | | | // | | | | | | "alerting" | ] | | | @@ -115,8 +115,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -183,7 +182,7 @@ ] ], [ - null, + [], [ "datasource-1" ], diff --git a/pkg/services/searchV2/testdata/basic-filter.jsonc b/pkg/services/searchV2/testdata/basic-filter.jsonc index a16acfe4894..7c1178331a3 100644 --- a/pkg/services/searchV2/testdata/basic-filter.jsonc +++ b/pkg/services/searchV2/testdata/basic-filter.jsonc @@ -8,12 +8,12 @@ // } // Name: Query results // Dimensions: 8 Fields by 0 Rows -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -84,8 +84,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { diff --git a/pkg/services/searchV2/testdata/basic-search.jsonc b/pkg/services/searchV2/testdata/basic-search.jsonc index 918f0ffae66..5b825816227 100644 --- a/pkg/services/searchV2/testdata/basic-search.jsonc +++ b/pkg/services/searchV2/testdata/basic-search.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 2 | boom | | /pfix/d/2/ | null | null | | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 2 | boom | | /pfix/d/2/ | null | [] | | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/dashboard-create.jsonc b/pkg/services/searchV2/testdata/dashboard-create.jsonc index 189f1f50af6..73b398f8030 100644 --- a/pkg/services/searchV2/testdata/dashboard-create.jsonc +++ b/pkg/services/searchV2/testdata/dashboard-create.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 3 | created | | /pfix/d/3/ | null | null | general | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 3 | created | | /pfix/d/3/ | null | [] | general | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "general" diff --git a/pkg/services/searchV2/testdata/dashboard-delete.jsonc b/pkg/services/searchV2/testdata/dashboard-delete.jsonc index a16acfe4894..7c1178331a3 100644 --- a/pkg/services/searchV2/testdata/dashboard-delete.jsonc +++ b/pkg/services/searchV2/testdata/dashboard-delete.jsonc @@ -8,12 +8,12 @@ // } // Name: Query results // Dimensions: 8 Fields by 0 Rows -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -84,8 +84,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { diff --git a/pkg/services/searchV2/testdata/dashboard-update.jsonc b/pkg/services/searchV2/testdata/dashboard-update.jsonc index 98b01f24093..653823378ee 100644 --- a/pkg/services/searchV2/testdata/dashboard-update.jsonc +++ b/pkg/services/searchV2/testdata/dashboard-update.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 2 | nginx | | /pfix/d/2/ | null | null | general | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 2 | nginx | | /pfix/d/2/ | null | [] | general | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "general" diff --git a/pkg/services/searchV2/testdata/folders-dashboard-removed-on-folder-removed.jsonc b/pkg/services/searchV2/testdata/folders-dashboard-removed-on-folder-removed.jsonc index 5901cc46713..2745c625d3c 100644 --- a/pkg/services/searchV2/testdata/folders-dashboard-removed-on-folder-removed.jsonc +++ b/pkg/services/searchV2/testdata/folders-dashboard-removed-on-folder-removed.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 4 | One more dash | | /pfix/d/4/ | null | null | | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 4 | One more dash | | /pfix/d/4/ | null | [] | | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/folders-indexed.jsonc b/pkg/services/searchV2/testdata/folders-indexed.jsonc index e961a5866e9..624f64c303d 100644 --- a/pkg/services/searchV2/testdata/folders-indexed.jsonc +++ b/pkg/services/searchV2/testdata/folders-indexed.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+----------------+------------------+-----------------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+----------------+------------------+-----------------------+--------------------------+--------------------------+----------------+ -// | folder | 1 | My folder | | /pfix/dashboards/f/1/ | null | null | | -// +----------------+----------------+----------------+------------------+-----------------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+----------------+------------------+-----------------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+----------------+------------------+-----------------------+--------------------------+-------------------------+----------------+ +// | folder | 1 | My folder | | /pfix/dashboards/f/1/ | null | [] | | +// +----------------+----------------+----------------+------------------+-----------------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/multiple-tokens-beginning-lower.jsonc b/pkg/services/searchV2/testdata/multiple-tokens-beginning-lower.jsonc index 8831219a6bd..5b596483cee 100644 --- a/pkg/services/searchV2/testdata/multiple-tokens-beginning-lower.jsonc +++ b/pkg/services/searchV2/testdata/multiple-tokens-beginning-lower.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | null | | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | [] | | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/multiple-tokens-beginning.jsonc b/pkg/services/searchV2/testdata/multiple-tokens-beginning.jsonc index 8831219a6bd..5b596483cee 100644 --- a/pkg/services/searchV2/testdata/multiple-tokens-beginning.jsonc +++ b/pkg/services/searchV2/testdata/multiple-tokens-beginning.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | null | | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | [] | | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/multiple-tokens-middle-lower.jsonc b/pkg/services/searchV2/testdata/multiple-tokens-middle-lower.jsonc index 2fe14bb9129..96d4579e669 100644 --- a/pkg/services/searchV2/testdata/multiple-tokens-middle-lower.jsonc +++ b/pkg/services/searchV2/testdata/multiple-tokens-middle-lower.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 2 | Document Sync repo | | /pfix/d/2/ | null | null | | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 2 | Document Sync repo | | /pfix/d/2/ | null | [] | | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/multiple-tokens-middle.jsonc b/pkg/services/searchV2/testdata/multiple-tokens-middle.jsonc index 8831219a6bd..5b596483cee 100644 --- a/pkg/services/searchV2/testdata/multiple-tokens-middle.jsonc +++ b/pkg/services/searchV2/testdata/multiple-tokens-middle.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | null | | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | [] | | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/ngram-camel-case-split.jsonc b/pkg/services/searchV2/testdata/ngram-camel-case-split.jsonc index f7c18e0f055..7718bd0f96e 100644 --- a/pkg/services/searchV2/testdata/ngram-camel-case-split.jsonc +++ b/pkg/services/searchV2/testdata/ngram-camel-case-split.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 1 | heatTorkel | | /pfix/d/1/ | null | null | | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 1 | heatTorkel | | /pfix/d/1/ | null | [] | | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/ngram-punctuation-split.jsonc b/pkg/services/searchV2/testdata/ngram-punctuation-split.jsonc index 4433aa0c09b..a8421ba62e7 100644 --- a/pkg/services/searchV2/testdata/ngram-punctuation-split.jsonc +++ b/pkg/services/searchV2/testdata/ngram-punctuation-split.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 1 | heat-torkel | | /pfix/d/1/ | null | null | | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 1 | heat-torkel | | /pfix/d/1/ | null | [] | | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/ngram-simple.jsonc b/pkg/services/searchV2/testdata/ngram-simple.jsonc index 65b27bc0727..565e3816bce 100644 --- a/pkg/services/searchV2/testdata/ngram-simple.jsonc +++ b/pkg/services/searchV2/testdata/ngram-simple.jsonc @@ -8,14 +8,14 @@ // } // Name: Query results // Dimensions: 8 Fields by 2 Rows -// +----------------+----------------+------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 1 | heat-torkel | | /pfix/d/1/ | null | null | | -// | dashboard | 2 | topology heatmap | | /pfix/d/2/ | null | null | | -// +----------------+----------------+------------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 1 | heat-torkel | | /pfix/d/1/ | null | [] | | +// | dashboard | 2 | topology heatmap | | /pfix/d/2/ | null | [] | | +// +----------------+----------------+------------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -86,8 +86,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -126,8 +125,8 @@ null ], [ - null, - null + [], + [] ], [ "", diff --git a/pkg/services/searchV2/testdata/panels-panel-removed-on-dashboard-removed.jsonc b/pkg/services/searchV2/testdata/panels-panel-removed-on-dashboard-removed.jsonc index a16acfe4894..7c1178331a3 100644 --- a/pkg/services/searchV2/testdata/panels-panel-removed-on-dashboard-removed.jsonc +++ b/pkg/services/searchV2/testdata/panels-panel-removed-on-dashboard-removed.jsonc @@ -8,12 +8,12 @@ // } // Name: Query results // Dimensions: 8 Fields by 0 Rows -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -84,8 +84,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { diff --git a/pkg/services/searchV2/testdata/prefix-search-beginning-lower.jsonc b/pkg/services/searchV2/testdata/prefix-search-beginning-lower.jsonc index 8831219a6bd..5b596483cee 100644 --- a/pkg/services/searchV2/testdata/prefix-search-beginning-lower.jsonc +++ b/pkg/services/searchV2/testdata/prefix-search-beginning-lower.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | null | | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | [] | | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/prefix-search-beginning.jsonc b/pkg/services/searchV2/testdata/prefix-search-beginning.jsonc index 8831219a6bd..5b596483cee 100644 --- a/pkg/services/searchV2/testdata/prefix-search-beginning.jsonc +++ b/pkg/services/searchV2/testdata/prefix-search-beginning.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | null | | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | [] | | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/prefix-search-middle-lower.jsonc b/pkg/services/searchV2/testdata/prefix-search-middle-lower.jsonc index 2fe14bb9129..96d4579e669 100644 --- a/pkg/services/searchV2/testdata/prefix-search-middle-lower.jsonc +++ b/pkg/services/searchV2/testdata/prefix-search-middle-lower.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 2 | Document Sync repo | | /pfix/d/2/ | null | null | | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 2 | Document Sync repo | | /pfix/d/2/ | null | [] | | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/prefix-search-middle.jsonc b/pkg/services/searchV2/testdata/prefix-search-middle.jsonc index 2fe14bb9129..96d4579e669 100644 --- a/pkg/services/searchV2/testdata/prefix-search-middle.jsonc +++ b/pkg/services/searchV2/testdata/prefix-search-middle.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 2 | Document Sync repo | | /pfix/d/2/ | null | null | | -// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 2 | Document Sync repo | | /pfix/d/2/ | null | [] | | +// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/prefix-search-ngram-exceeded.jsonc b/pkg/services/searchV2/testdata/prefix-search-ngram-exceeded.jsonc index a3c947cb032..d410e87182c 100644 --- a/pkg/services/searchV2/testdata/prefix-search-ngram-exceeded.jsonc +++ b/pkg/services/searchV2/testdata/prefix-search-ngram-exceeded.jsonc @@ -8,13 +8,13 @@ // } // Name: Query results // Dimensions: 8 Fields by 1 Rows -// +----------------+----------------+--------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+--------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 1 | Eyjafjallajâkull Eruption data | | /pfix/d/1/ | null | null | | -// +----------------+----------------+--------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+--------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+--------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 1 | Eyjafjallajâkull Eruption data | | /pfix/d/1/ | null | [] | | +// +----------------+----------------+--------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -85,8 +85,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -119,7 +118,7 @@ null ], [ - null + [] ], [ "" diff --git a/pkg/services/searchV2/testdata/scattered-tokens-match-reversed.jsonc b/pkg/services/searchV2/testdata/scattered-tokens-match-reversed.jsonc index 96b9e7262d0..1ce404b7751 100644 --- a/pkg/services/searchV2/testdata/scattered-tokens-match-reversed.jsonc +++ b/pkg/services/searchV2/testdata/scattered-tokens-match-reversed.jsonc @@ -8,14 +8,14 @@ // } // Name: Query results // Dimensions: 8 Fields by 2 Rows -// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 2 | A secret is powerful when it is empty (Umberto Eco) | | /pfix/d/2/ | null | null | | -// | dashboard | 1 | Three can keep a secret, if two of them are dead (Benjamin Franklin) | | /pfix/d/1/ | null | null | | -// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 2 | A secret is powerful when it is empty (Umberto Eco) | | /pfix/d/2/ | null | [] | | +// | dashboard | 1 | Three can keep a secret, if two of them are dead (Benjamin Franklin) | | /pfix/d/1/ | null | [] | | +// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -86,8 +86,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -126,8 +125,8 @@ null ], [ - null, - null + [], + [] ], [ "", diff --git a/pkg/services/searchV2/testdata/scattered-tokens-match.jsonc b/pkg/services/searchV2/testdata/scattered-tokens-match.jsonc index 229a22648e4..64bf3ea83e2 100644 --- a/pkg/services/searchV2/testdata/scattered-tokens-match.jsonc +++ b/pkg/services/searchV2/testdata/scattered-tokens-match.jsonc @@ -8,14 +8,14 @@ // } // Name: Query results // Dimensions: 8 Fields by 2 Rows -// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | -// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+ -// | dashboard | 1 | Three can keep a secret, if two of them are dead (Benjamin Franklin) | | /pfix/d/1/ | null | null | | -// | dashboard | 2 | A secret is powerful when it is empty (Umberto Eco) | | /pfix/d/2/ | null | null | | -// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+ +// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | +// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+ +// | dashboard | 1 | Three can keep a secret, if two of them are dead (Benjamin Franklin) | | /pfix/d/1/ | null | [] | | +// | dashboard | 2 | A secret is powerful when it is empty (Umberto Eco) | | /pfix/d/2/ | null | [] | | +// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -86,8 +86,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -126,8 +125,8 @@ null ], [ - null, - null + [], + [] ], [ "", diff --git a/pkg/services/searchV2/testdata/search_response_frame.json b/pkg/services/searchV2/testdata/search_response_frame.json index c7b37d7eafe..5e13fd31f4f 100644 --- a/pkg/services/searchV2/testdata/search_response_frame.json +++ b/pkg/services/searchV2/testdata/search_response_frame.json @@ -73,7 +73,7 @@ "type": "other", "typeInfo": { "frame": "json.RawMessage", - "nullable": true + "nullable": false } }, { @@ -133,7 +133,7 @@ ] ], [ - null, + [], [ "datasource-1" ], diff --git a/pkg/services/searchV2/testdata/sort-asc.jsonc b/pkg/services/searchV2/testdata/sort-asc.jsonc index 751292ee933..46b815ad0da 100644 --- a/pkg/services/searchV2/testdata/sort-asc.jsonc +++ b/pkg/services/searchV2/testdata/sort-asc.jsonc @@ -9,14 +9,14 @@ // } // Name: Query results // Dimensions: 9 Fields by 2 Rows -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: test num | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | Type: []float64 | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+ -// | dashboard | 1 | a-test | | /pfix/d/1/ | null | null | | 0 | -// | dashboard | 2 | z-test | | /pfix/d/2/ | null | null | | 1 | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+-----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: test num | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | Type: []float64 | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+-----------------+ +// | dashboard | 1 | a-test | | /pfix/d/1/ | null | [] | | 0 | +// | dashboard | 2 | z-test | | /pfix/d/2/ | null | [] | | 1 | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+-----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -88,8 +88,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -135,8 +134,8 @@ null ], [ - null, - null + [], + [] ], [ "", diff --git a/pkg/services/searchV2/testdata/sort-desc.jsonc b/pkg/services/searchV2/testdata/sort-desc.jsonc index 4401f08afaf..7ff307a3b95 100644 --- a/pkg/services/searchV2/testdata/sort-desc.jsonc +++ b/pkg/services/searchV2/testdata/sort-desc.jsonc @@ -9,14 +9,14 @@ // } // Name: Query results // Dimensions: 9 Fields by 2 Rows -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+ -// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: test num | -// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | -// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | Type: []float64 | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+ -// | dashboard | 2 | z-test | | /pfix/d/2/ | null | null | | 3 | -// | dashboard | 1 | a-test | | /pfix/d/1/ | null | null | | 2 | -// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+ +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+-----------------+ +// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: test num | +// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | Type: []float64 | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+-----------------+ +// | dashboard | 2 | z-test | | /pfix/d/2/ | null | [] | | 3 | +// | dashboard | 1 | a-test | | /pfix/d/1/ | null | [] | | 2 | +// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+-----------------+ // // // 🌟 This was machine generated. Do not edit. 🌟 @@ -88,8 +88,7 @@ "name": "ds_uid", "type": "other", "typeInfo": { - "frame": "json.RawMessage", - "nullable": true + "frame": "json.RawMessage" } }, { @@ -135,8 +134,8 @@ null ], [ - null, - null + [], + [] ], [ "", diff --git a/pkg/services/searchV2/types.go b/pkg/services/searchV2/types.go index e180e2ebc5f..3030b949272 100644 --- a/pkg/services/searchV2/types.go +++ b/pkg/services/searchV2/types.go @@ -31,7 +31,9 @@ type DashboardQuery struct { From int `json:"from,omitempty"` // for paging } +//go:generate mockery --name SearchService --structname MockSearchService --inpackage --filename search_service_mock.go type SearchService interface { + registry.CanBeDisabled registry.BackgroundService DoDashboardQuery(ctx context.Context, user *backend.User, orgId int64, query DashboardQuery) *backend.DataResponse RegisterDashboardIndexExtender(ext DashboardIndexExtender) diff --git a/pkg/services/sqlstore/dashboard_thumbs.go b/pkg/services/sqlstore/dashboard_thumbs.go index f4321c4406f..fbc0c2c7a88 100644 --- a/pkg/services/sqlstore/dashboard_thumbs.go +++ b/pkg/services/sqlstore/dashboard_thumbs.go @@ -2,6 +2,7 @@ package sqlstore import ( "context" + "encoding/json" "errors" "time" @@ -22,6 +23,18 @@ func (ss *SQLStore) GetThumbnail(ctx context.Context, query *models.GetDashboard return query.Result, err } +func marshalDatasourceUids(dsUids []string) (string, error) { + if dsUids == nil { + return "", nil + } + + b, err := json.Marshal(dsUids) + if err != nil { + return "", err + } + return string(b), nil +} + func (ss *SQLStore) SaveThumbnail(ctx context.Context, cmd *models.SaveDashboardThumbnailCommand) (*models.DashboardThumbnail, error) { err := ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { existing, err := findThumbnailByMeta(sess, cmd.DashboardThumbnailMeta) @@ -30,11 +43,17 @@ func (ss *SQLStore) SaveThumbnail(ctx context.Context, cmd *models.SaveDashboard return err } + dsUids, err := marshalDatasourceUids(cmd.DatasourceUIDs) + if err != nil { + return err + } + if existing != nil { existing.Image = cmd.Image existing.MimeType = cmd.MimeType existing.Updated = time.Now() existing.DashboardVersion = cmd.DashboardVersion + existing.DsUIDs = dsUids existing.State = models.ThumbnailStateDefault _, err = sess.ID(existing.Id).Update(existing) cmd.Result = existing @@ -53,6 +72,7 @@ func (ss *SQLStore) SaveThumbnail(ctx context.Context, cmd *models.SaveDashboard thumb.Theme = cmd.Theme thumb.Kind = cmd.Kind thumb.Image = cmd.Image + thumb.DsUIDs = dsUids thumb.MimeType = cmd.MimeType thumb.DashboardId = dash.Id thumb.DashboardVersion = cmd.DashboardVersion @@ -101,9 +121,17 @@ func (ss *SQLStore) FindDashboardsWithStaleThumbnails(ctx context.Context, cmd * sess.Table("dashboard") sess.Join("LEFT", "dashboard_thumbnail", "dashboard.id = dashboard_thumbnail.dashboard_id AND dashboard_thumbnail.theme = ? AND dashboard_thumbnail.kind = ?", cmd.Theme, cmd.Kind) sess.Where("dashboard.is_folder = ?", dialect.BooleanStr(false)) - sess.Where("(dashboard.version != dashboard_thumbnail.dashboard_version "+ - "OR dashboard_thumbnail.state = ? "+ - "OR dashboard_thumbnail.id IS NULL)", models.ThumbnailStateStale) + + query := "(dashboard.version != dashboard_thumbnail.dashboard_version " + + "OR dashboard_thumbnail.state = ? " + + "OR dashboard_thumbnail.id IS NULL" + args := []interface{}{models.ThumbnailStateStale} + + if cmd.IncludeThumbnailsWithEmptyDsUIDs { + query += " OR dashboard_thumbnail.ds_uids = ?" + args = append(args, "") + } + sess.Where(query+")", args...) if !cmd.IncludeManuallyUploadedThumbnails { sess.Where("(dashboard_thumbnail.id is not null AND dashboard_thumbnail.dashboard_version != ?) "+ @@ -119,13 +147,13 @@ func (ss *SQLStore) FindDashboardsWithStaleThumbnails(ctx context.Context, cmd * "dashboard.version", "dashboard.slug") - var dashboards = make([]*models.DashboardWithStaleThumbnail, 0) - err := sess.Find(&dashboards) + var result = make([]*models.DashboardWithStaleThumbnail, 0) + err := sess.Find(&result) if err != nil { return err } - cmd.Result = dashboards + cmd.Result = result return err }) @@ -145,6 +173,7 @@ func findThumbnailByMeta(sess *DBSession, meta models.DashboardThumbnailMeta) (* "dashboard_thumbnail.dashboard_version", "dashboard_thumbnail.state", "dashboard_thumbnail.kind", + "dashboard_thumbnail.ds_uids", "dashboard_thumbnail.mime_type", "dashboard_thumbnail.theme", "dashboard_thumbnail.updated") diff --git a/pkg/services/sqlstore/dashboard_thumbs_test.go b/pkg/services/sqlstore/dashboard_thumbs_test.go index 30900ec2017..7ce2ae68504 100644 --- a/pkg/services/sqlstore/dashboard_thumbs_test.go +++ b/pkg/services/sqlstore/dashboard_thumbs_test.go @@ -69,6 +69,23 @@ func TestIntegrationSqlStorage(t *testing.T) { require.Len(t, res, 0) }) + t.Run("Should return dashboards with thumbnails with empty ds_uids array", func(t *testing.T) { + setup() + dash := insertTestDashboard(t, sqlStore, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp") + + upsertTestDashboardThumbnail(t, sqlStore, dash.Uid, dash.OrgId, dash.Version) + + cmd := models.FindDashboardsWithStaleThumbnailsCommand{ + Kind: kind, + IncludeThumbnailsWithEmptyDsUIDs: true, + Theme: theme, + } + res, err := sqlStore.FindDashboardsWithStaleThumbnails(context.Background(), &cmd) + require.NoError(t, err) + require.Len(t, res, 1) + require.Equal(t, dash.Id, res[0].Id) + }) + t.Run("Should return dashboards with thumbnails marked as stale", func(t *testing.T) { setup() dash := insertTestDashboard(t, sqlStore, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp") diff --git a/pkg/services/sqlstore/migrations/dashboard_thumbs_mig.go b/pkg/services/sqlstore/migrations/dashboard_thumbs_mig.go index c8b09bcaf64..104970f2482 100644 --- a/pkg/services/sqlstore/migrations/dashboard_thumbs_mig.go +++ b/pkg/services/sqlstore/migrations/dashboard_thumbs_mig.go @@ -24,4 +24,8 @@ func addDashboardThumbsMigrations(mg *migrator.Migrator) { mg.AddMigration("create dashboard_thumbnail table", migrator.NewAddTableMigration(dashThumbs)) mg.AddMigration("add unique indexes for dashboard_thumbnail", migrator.NewAddIndexMigration(dashThumbs, dashThumbs.Indices[0])) + mg.AddMigration("Add ds_uids column to dashboard_thumbnail table", migrator.NewAddColumnMigration(dashThumbs, + // uids of datasources used in the dashboard when taking preview + &migrator.Column{Name: "ds_uids", Type: migrator.DB_Text, Nullable: false, Default: ""}, + )) } diff --git a/pkg/services/thumbs/crawler.go b/pkg/services/thumbs/crawler.go index 2d146947da0..f400d16eec9 100644 --- a/pkg/services/thumbs/crawler.go +++ b/pkg/services/thumbs/crawler.go @@ -37,9 +37,10 @@ type simpleCrawler struct { queueMutex sync.Mutex log log.Logger renderingSessionByOrgId map[int64]rendering.Session + dsUidsLookup getDatasourceUidsForDashboard } -func newSimpleCrawler(renderService rendering.Service, gl *live.GrafanaLive, repo thumbnailRepo, cfg *setting.Cfg, settings setting.DashboardPreviewsSettings) dashRenderer { +func newSimpleCrawler(renderService rendering.Service, gl *live.GrafanaLive, repo thumbnailRepo, cfg *setting.Cfg, settings setting.DashboardPreviewsSettings, dsUidsLookup getDatasourceUidsForDashboard) dashRenderer { threadCount := int(settings.CrawlThreadCount) c := &simpleCrawler{ // temporarily increases the concurrentLimit from the 'cfg.RendererConcurrentRequestLimit' to 'cfg.RendererConcurrentRequestLimit + crawlerThreadCount' @@ -48,6 +49,7 @@ func newSimpleCrawler(renderService rendering.Service, gl *live.GrafanaLive, rep renderService: renderService, threadCount: threadCount, glive: gl, + dsUidsLookup: dsUidsLookup, thumbnailRepo: repo, log: log.New("thumbnails_crawler"), status: crawlStatus{ @@ -287,6 +289,13 @@ func (r *simpleCrawler) walk(ctx context.Context, id int) { url := models.GetKioskModeDashboardUrl(item.Uid, item.Slug, r.opts.Theme) r.log.Info("Getting dashboard thumbnail", "walkerId", id, "dashboardUID", item.Uid, "url", url) + dsUids, err := r.dsUidsLookup(ctx, item.Uid, item.OrgId) + if err != nil { + r.log.Warn("Error getting datasource uids", "walkerId", id, "dashboardUID", item.Uid, "url", url, "err", err) + r.newErrorResult() + continue + } + res, err := r.renderService.Render(ctx, rendering.Opts{ Width: 320, Height: 240, @@ -321,7 +330,7 @@ func (r *simpleCrawler) walk(ctx context.Context, id int) { OrgId: item.OrgId, Theme: r.opts.Theme, Kind: r.thumbnailKind, - }, item.Version) + }, item.Version, dsUids) if err != nil { r.log.Warn("Error saving image image", "walkerId", id, "dashboardUID", item.Uid, "url", url, "err", err, "itemTime", time.Since(itemStarted)) diff --git a/pkg/services/thumbs/crawler_auth.go b/pkg/services/thumbs/crawler_auth.go index b8dd6ba334c..5ace4272695 100644 --- a/pkg/services/thumbs/crawler_auth.go +++ b/pkg/services/thumbs/crawler_auth.go @@ -2,40 +2,137 @@ package thumbs import ( "context" + "errors" + "strconv" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/serviceaccounts" + "github.com/grafana/grafana/pkg/services/serviceaccounts/database" + "github.com/grafana/grafana/pkg/services/sqlstore" ) type CrawlerAuthSetupService interface { Setup(ctx context.Context) (CrawlerAuth, error) } -func ProvideCrawlerAuthSetupService() *OSSCrawlerAuthSetupService { - return &OSSCrawlerAuthSetupService{} +func ProvideCrawlerAuthSetupService(serviceAccounts serviceaccounts.Service, serviceAccountsStore serviceaccounts.Store, sqlStore *sqlstore.SQLStore) *OSSCrawlerAuthSetupService { + return &OSSCrawlerAuthSetupService{ + serviceAccountNamePrefix: "dashboard-previews-crawler-org-", + serviceAccounts: serviceAccounts, + log: log.New("oss_crawler_account_setup_service"), + sqlStore: sqlStore, + serviceAccountsStore: serviceAccountsStore, + } } -type OSSCrawlerAuthSetupService struct{} +type OSSCrawlerAuthSetupService struct { + log log.Logger + serviceAccountNamePrefix string + serviceAccounts serviceaccounts.Service + serviceAccountsStore serviceaccounts.Store + sqlStore *sqlstore.SQLStore +} type CrawlerAuth interface { GetUserId(orgId int64) int64 + GetLogin(orgId int64) string GetOrgRole() models.RoleType } -type staticCrawlerAuth struct { - userId int64 - orgRole models.RoleType +func (o *OSSCrawlerAuthSetupService) findAllOrgIds(ctx context.Context) ([]int64, error) { + searchAllOrgsQuery := &models.SearchOrgsQuery{} + if err := o.sqlStore.SearchOrgs(ctx, searchAllOrgsQuery); err != nil { + o.log.Error("Error when searching for orgs", "err", err) + return nil, err + } + + orgIds := make([]int64, 0) + for i := range searchAllOrgsQuery.Result { + orgIds = append(orgIds, searchAllOrgsQuery.Result[i].Id) + } + + return orgIds, nil } -func (o *staticCrawlerAuth) GetOrgRole() models.RoleType { +type crawlerAuth struct { + accountIdByOrgId map[int64]int64 + loginByOrgId map[int64]string + orgRole models.RoleType +} + +func (o *crawlerAuth) GetOrgRole() models.RoleType { return o.orgRole } -func (o *staticCrawlerAuth) GetUserId(orgId int64) int64 { - return o.userId +func (o *crawlerAuth) GetUserId(orgId int64) int64 { + return o.accountIdByOrgId[orgId] +} + +func (o *crawlerAuth) GetLogin(orgId int64) string { + return o.loginByOrgId[orgId] } func (o *OSSCrawlerAuthSetupService) Setup(ctx context.Context) (CrawlerAuth, error) { + orgIds, err := o.findAllOrgIds(ctx) + if err != nil { + return nil, err + } + // userId:0 and ROLE_ADMIN grants the crawler process permissions to view all dashboards in all folders & orgs // the process doesn't and shouldn't actually need to edit/modify any resources from the UI - return &staticCrawlerAuth{userId: 0, orgRole: models.ROLE_ADMIN}, nil + orgRole := models.ROLE_ADMIN + + accountIdByOrgId := make(map[int64]int64) + loginByOrgId := make(map[int64]string) + for _, orgId := range orgIds { + o.log.Info("Creating account for org", "orgId", orgId) + + serviceAccountNameOrg := o.serviceAccountNamePrefix + strconv.FormatInt(orgId, 10) + + saForm := serviceaccounts.CreateServiceAccountForm{ + Name: serviceAccountNameOrg, + Role: &orgRole, + } + + serviceAccount, err := o.serviceAccounts.CreateServiceAccount(ctx, orgId, &saForm) + accountAlreadyExists := errors.Is(err, database.ErrServiceAccountAlreadyExists) + + if !accountAlreadyExists && err != nil { + o.log.Error("Failed to create the service account", "err", err, "accountName", serviceAccountNameOrg, "orgId", orgId) + return nil, err + } + + var serviceAccountLogin string + var serviceAccountId int64 + if accountAlreadyExists { + id, err := o.serviceAccounts.RetrieveServiceAccountIdByName(ctx, orgId, serviceAccountNameOrg) + if err != nil { + o.log.Error("Failed to retrieve service account", "err", err, "accountName", serviceAccountNameOrg) + return nil, err + } + + // update org_role to make sure everything works properly if someone has changed the role since SA's original creation + dto, err := o.serviceAccountsStore.UpdateServiceAccount(ctx, orgId, id, &serviceaccounts.UpdateServiceAccountForm{ + Name: &serviceAccountNameOrg, + Role: &orgRole, + }) + + if err != nil { + o.log.Error("Failed to update service account's role", "err", err, "accountName", serviceAccountNameOrg) + return nil, err + } + + serviceAccountLogin = dto.Login + serviceAccountId = id + } else { + serviceAccountLogin = serviceAccount.Login + serviceAccountId = serviceAccount.Id + } + + accountIdByOrgId[orgId] = serviceAccountId + loginByOrgId[orgId] = serviceAccountLogin + } + + return &crawlerAuth{accountIdByOrgId: accountIdByOrgId, loginByOrgId: loginByOrgId, orgRole: orgRole}, nil } diff --git a/pkg/services/thumbs/datasources_lookup.go b/pkg/services/thumbs/datasources_lookup.go new file mode 100644 index 00000000000..dbdca113d64 --- /dev/null +++ b/pkg/services/thumbs/datasources_lookup.go @@ -0,0 +1,91 @@ +package thumbs + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/searchV2" + "github.com/grafana/grafana/pkg/tsdb/grafanads" +) + +type getDatasourceUidsForDashboard func(ctx context.Context, dashboardUid string, orgId int64) ([]string, error) + +type dsUidsLookup struct { + searchService searchV2.SearchService + crawlerAuth CrawlerAuth + features featuremgmt.FeatureToggles +} + +func getDatasourceUIDs(resp *backend.DataResponse, uid string) ([]string, error) { + if resp == nil { + return nil, errors.New("nil response") + } + + if resp.Error != nil { + return nil, resp.Error + } + + if len(resp.Frames) == 0 { + return nil, errors.New("empty response") + } + + frame := resp.Frames[0] + field, idx := frame.FieldByName("ds_uid") + + if field.Len() == 0 || idx == -1 { + return nil, fmt.Errorf("no ds_uid field for uid %s", uid) + } + + rawValue, ok := field.At(0).(json.RawMessage) + if !ok || rawValue == nil { + return nil, fmt.Errorf("invalid value for uid %s in ds_uid field: %s", uid, field.At(0)) + } + + jsonValue, err := rawValue.MarshalJSON() + if err != nil { + return nil, err + } + + var uids []string + err = json.Unmarshal(jsonValue, &uids) + if err != nil { + return nil, err + } + + return uids, nil +} + +func filterOutGrafanaDs(uids []string) []string { + var filtered []string + for _, uid := range uids { + if uid != grafanads.DatasourceUID { + filtered = append(filtered, uid) + } + } + + return filtered +} + +func (d *dsUidsLookup) getDatasourceUidsForDashboard(ctx context.Context, dashboardUid string, orgId int64) ([]string, error) { + if d.searchService.IsDisabled() { + return nil, nil + } + + dashQueryResponse := d.searchService.DoDashboardQuery(ctx, &backend.User{ + Login: d.crawlerAuth.GetLogin(orgId), + Role: string(d.crawlerAuth.GetOrgRole()), + }, orgId, searchV2.DashboardQuery{ + UIDs: []string{dashboardUid}, + }) + + uids, err := getDatasourceUIDs(dashQueryResponse, dashboardUid) + if err != nil { + return nil, err + } + + return filterOutGrafanaDs(uids), nil +} diff --git a/pkg/services/thumbs/datasources_lookup_test.go b/pkg/services/thumbs/datasources_lookup_test.go new file mode 100644 index 00000000000..e9b5537347a --- /dev/null +++ b/pkg/services/thumbs/datasources_lookup_test.go @@ -0,0 +1,57 @@ +package thumbs + +import ( + "context" + _ "embed" + "testing" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/searchV2" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +var ( + //go:embed testdata/search_response_frame.json + exampleListFrameJSON string + exampleListFrame = &data.Frame{} + _ = exampleListFrame.UnmarshalJSON([]byte(exampleListFrameJSON)) +) + +func TestShouldParseUidFromSearchResponseFrame(t *testing.T) { + searchService := &searchV2.MockSearchService{} + dsLookup := &dsUidsLookup{ + searchService: searchService, + crawlerAuth: &crawlerAuth{}, + features: featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch), + } + + dashboardUid := "abc" + searchService.On("IsDisabled").Return(false) + searchService.On("DoDashboardQuery", mock.Anything, mock.Anything, mock.Anything, searchV2.DashboardQuery{ + UIDs: []string{dashboardUid}, + }).Return(&backend.DataResponse{ + Frames: []*data.Frame{exampleListFrame}, + }) + + uids, err := dsLookup.getDatasourceUidsForDashboard(context.Background(), dashboardUid, 1) + require.NoError(t, err) + require.Equal(t, []string{"datasource-2", "datasource-3", "datasource-4"}, uids) +} + +func TestShouldReturnNullIfSearchServiceIsDisabled(t *testing.T) { + searchService := &searchV2.MockSearchService{} + dsLookup := &dsUidsLookup{ + searchService: searchService, + crawlerAuth: &crawlerAuth{}, + features: featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch), + } + + dashboardUid := "abc" + searchService.On("IsDisabled").Return(true) + uids, err := dsLookup.getDatasourceUidsForDashboard(context.Background(), dashboardUid, 1) + require.NoError(t, err) + require.Nil(t, uids) +} diff --git a/pkg/services/thumbs/models.go b/pkg/services/thumbs/models.go index 78d30746c74..7ab47e7a9d3 100644 --- a/pkg/services/thumbs/models.go +++ b/pkg/services/thumbs/models.go @@ -79,8 +79,8 @@ type dashRenderer interface { type thumbnailRepo interface { updateThumbnailState(ctx context.Context, state models.ThumbnailState, meta models.DashboardThumbnailMeta) error doThumbnailsExist(ctx context.Context) (bool, error) - saveFromFile(ctx context.Context, filePath string, meta models.DashboardThumbnailMeta, dashboardVersion int) (int64, error) - saveFromBytes(ctx context.Context, bytes []byte, mimeType string, meta models.DashboardThumbnailMeta, dashboardVersion int) (int64, error) + saveFromFile(ctx context.Context, filePath string, meta models.DashboardThumbnailMeta, dashboardVersion int, dsUids []string) (int64, error) + saveFromBytes(ctx context.Context, bytes []byte, mimeType string, meta models.DashboardThumbnailMeta, dashboardVersion int, dsUids []string) (int64, error) getThumbnail(ctx context.Context, meta models.DashboardThumbnailMeta) (*models.DashboardThumbnail, error) findDashboardsWithStaleThumbnails(ctx context.Context, theme models.Theme, thumbnailKind models.ThumbnailKind) ([]*models.DashboardWithStaleThumbnail, error) } diff --git a/pkg/services/thumbs/repo.go b/pkg/services/thumbs/repo.go index 71f7a7459a9..1676783c703 100644 --- a/pkg/services/thumbs/repo.go +++ b/pkg/services/thumbs/repo.go @@ -9,23 +9,26 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/searchV2" "github.com/grafana/grafana/pkg/services/sqlstore" ) -func newThumbnailRepo(store *sqlstore.SQLStore) thumbnailRepo { +func newThumbnailRepo(store *sqlstore.SQLStore, search searchV2.SearchService) thumbnailRepo { repo := &sqlThumbnailRepository{ - store: store, - log: log.New("thumbnails_repo"), + store: store, + search: search, + log: log.New("thumbnails_repo"), } return repo } type sqlThumbnailRepository struct { - store *sqlstore.SQLStore - log log.Logger + store *sqlstore.SQLStore + search searchV2.SearchService + log log.Logger } -func (r *sqlThumbnailRepository) saveFromFile(ctx context.Context, filePath string, meta models.DashboardThumbnailMeta, dashboardVersion int) (int64, error) { +func (r *sqlThumbnailRepository) saveFromFile(ctx context.Context, filePath string, meta models.DashboardThumbnailMeta, dashboardVersion int, dsUids []string) (int64, error) { // the filePath variable is never set by the user. it refers to a temporary file created either in // 1. thumbs/service.go, when user uploads a thumbnail // 2. the rendering service, when image-renderer returns a screenshot @@ -42,7 +45,7 @@ func (r *sqlThumbnailRepository) saveFromFile(ctx context.Context, filePath stri return 0, err } - return r.saveFromBytes(ctx, content, getMimeType(filePath), meta, dashboardVersion) + return r.saveFromBytes(ctx, content, getMimeType(filePath), meta, dashboardVersion, dsUids) } func getMimeType(filePath string) string { @@ -53,12 +56,13 @@ func getMimeType(filePath string) string { return "image/png" } -func (r *sqlThumbnailRepository) saveFromBytes(ctx context.Context, content []byte, mimeType string, meta models.DashboardThumbnailMeta, dashboardVersion int) (int64, error) { +func (r *sqlThumbnailRepository) saveFromBytes(ctx context.Context, content []byte, mimeType string, meta models.DashboardThumbnailMeta, dashboardVersion int, dsUids []string) (int64, error) { cmd := &models.SaveDashboardThumbnailCommand{ DashboardThumbnailMeta: meta, Image: content, MimeType: mimeType, DashboardVersion: dashboardVersion, + DatasourceUIDs: dsUids, } _, err := r.store.SaveThumbnail(ctx, cmd) @@ -87,6 +91,7 @@ func (r *sqlThumbnailRepository) getThumbnail(ctx context.Context, meta models.D func (r *sqlThumbnailRepository) findDashboardsWithStaleThumbnails(ctx context.Context, theme models.Theme, kind models.ThumbnailKind) ([]*models.DashboardWithStaleThumbnail, error) { return r.store.FindDashboardsWithStaleThumbnails(ctx, &models.FindDashboardsWithStaleThumbnailsCommand{ IncludeManuallyUploadedThumbnails: false, + IncludeThumbnailsWithEmptyDsUIDs: !r.search.IsDisabled(), Theme: theme, Kind: kind, }) diff --git a/pkg/services/thumbs/service.go b/pkg/services/thumbs/service.go index c2e647b55b9..78f24a01263 100644 --- a/pkg/services/thumbs/service.go +++ b/pkg/services/thumbs/service.go @@ -9,6 +9,8 @@ import ( "net/http" "time" + "github.com/grafana/grafana/pkg/services/datasources/permissions" + "github.com/grafana/grafana/pkg/services/searchV2" "github.com/segmentio/encoding/json" "github.com/grafana/grafana/pkg/api/response" @@ -56,6 +58,10 @@ type thumbService struct { canRunCrawler bool settings setting.DashboardPreviewsSettings dashboardService dashboards.DashboardService + dsUidsLookup getDatasourceUidsForDashboard + dsPermissionsService permissions.DatasourcePermissionsService + licensing models.Licensing + searchService searchV2.SearchService } type crawlerScheduleOptions struct { @@ -71,13 +77,14 @@ type crawlerScheduleOptions struct { func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, lockService *serverlock.ServerLockService, renderService rendering.Service, gl *live.GrafanaLive, store *sqlstore.SQLStore, authSetupService CrawlerAuthSetupService, - dashboardService dashboards.DashboardService) Service { + dashboardService dashboards.DashboardService, searchService searchV2.SearchService, + dsPermissionsService permissions.DatasourcePermissionsService, licensing models.Licensing) Service { if !features.IsEnabled(featuremgmt.FlagDashboardPreviews) { return &dummyService{} } logger := log.New("previews_service") - thumbnailRepo := newThumbnailRepo(store) + thumbnailRepo := newThumbnailRepo(store, searchService) canRunCrawler := true @@ -91,17 +98,27 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, logger.Info("Crawler auth setup complete", "crawlerAuthSetupTime", time.Since(authSetupStarted)) } + dsUidsLookup := &dsUidsLookup{ + searchService: searchService, + crawlerAuth: crawlerAuth, + features: features, + } + t := &thumbService{ + licensing: licensing, renderingService: renderService, - renderer: newSimpleCrawler(renderService, gl, thumbnailRepo, cfg, cfg.DashboardPreviews), + renderer: newSimpleCrawler(renderService, gl, thumbnailRepo, cfg, cfg.DashboardPreviews, dsUidsLookup.getDatasourceUidsForDashboard), thumbnailRepo: thumbnailRepo, store: store, features: features, lockService: lockService, crawlLockServiceActionName: "dashboard-crawler", + searchService: searchService, log: logger, canRunCrawler: canRunCrawler, + dsUidsLookup: dsUidsLookup.getDatasourceUidsForDashboard, settings: cfg.DashboardPreviews, + dsPermissionsService: dsPermissionsService, scheduleOptions: crawlerScheduleOptions{ tickerInterval: 5 * time.Minute, crawlInterval: cfg.DashboardPreviews.SchedulerInterval, @@ -233,6 +250,10 @@ func (hs *thumbService) GetImage(c *models.ReqContext) { return } + if !hs.hasAccessToPreview(c, res, req) { + return + } + currentEtag := fmt.Sprintf("%d", res.Updated.Unix()) c.Resp.Header().Set("ETag", currentEtag) @@ -248,6 +269,50 @@ func (hs *thumbService) GetImage(c *models.ReqContext) { } } +func (hs *thumbService) hasAccessToPreview(c *models.ReqContext, res *models.DashboardThumbnail, req *previewRequest) bool { + if !hs.licensing.FeatureEnabled("accesscontrol.enforcement") { + return true + } + + if hs.searchService.IsDisabled() { + c.JSON(404, map[string]string{"dashboardUID": req.UID, "error": "unknown"}) + return false + } + + if res.DsUIDs == "" { + hs.log.Debug("dashboard preview is stale; no datasource uids", "dashboardUid", req.UID) + c.JSON(404, map[string]string{"dashboardUID": req.UID, "error": "unknown"}) + return false + } + + var dsUids []string + err := json.Unmarshal([]byte(res.DsUIDs), &dsUids) + + if err != nil { + hs.log.Error("Error when retrieving datasource uids", "dashboardUid", req.UID, "err", err) + c.JSON(404, map[string]string{"dashboardUID": req.UID, "error": "unknown"}) + return false + } + + accessibleDatasources, err := hs.dsPermissionsService.FilterDatasourceUidsBasedOnQueryPermissions(c.Req.Context(), c.SignedInUser, dsUids) + if err != nil && !errors.Is(err, permissions.ErrNotImplemented) { + hs.log.Error("Error when filtering datasource uids", "dashboardUid", req.UID, "err", err) + c.JSON(500, map[string]string{"dashboardUID": req.UID, "error": "unknown"}) + return false + } + + if !errors.Is(err, permissions.ErrNotImplemented) { + canQueryAllDatasources := len(accessibleDatasources) == len(dsUids) + if !canQueryAllDatasources { + hs.log.Info("Denied access to dashboard preview", "dashboardUid", req.UID, "err", err, "dashboardDatasources", dsUids, "accessibleDatasources", accessibleDatasources) + c.JSON(404, map[string]string{"dashboardUID": req.UID, "error": "unknown"}) + return false + } + } + + return true +} + func (hs *thumbService) GetDashboardPreviewsSetupSettings(c *models.ReqContext) dashboardPreviewsSetupConfig { return hs.getDashboardPreviewsSetupSettings(c.Req.Context()) } @@ -330,12 +395,19 @@ func (hs *thumbService) SetImage(c *models.ReqContext) { return } + dsUids, err := hs.dsUidsLookup(c.Req.Context(), req.UID, req.OrgID) + if err != nil { + hs.log.Error("error looking up datasource ids", "err", err, "dashboardUid", req.UID) + c.JSON(500, map[string]string{"error": "internal server error"}) + return + } + _, err = hs.thumbnailRepo.saveFromBytes(c.Req.Context(), fileBytes, getMimeType(handler.Filename), models.DashboardThumbnailMeta{ DashboardUID: req.UID, OrgId: req.OrgID, Theme: req.Theme, Kind: req.Kind, - }, models.DashboardVersionForManualThumbnailUpload) + }, models.DashboardVersionForManualThumbnailUpload, dsUids) if err != nil { c.JSON(400, map[string]string{"error": "error saving thumbnail file"}) diff --git a/pkg/services/thumbs/testdata/search_response_frame.json b/pkg/services/thumbs/testdata/search_response_frame.json new file mode 100644 index 00000000000..5ba17a6f6af --- /dev/null +++ b/pkg/services/thumbs/testdata/search_response_frame.json @@ -0,0 +1,123 @@ +{ + "schema": { + "name": "Query results", + "refId": "Search", + "meta": { + "type": "search-results", + "custom": { + "count": 106, + "locationInfo": { + "yboVMzb7z": { + "name": "gdev dashboards", + "kind": "folder", + "url": "/dashboards/f/yboVMzb7z/gdev-dashboards" + } + }, + "sortBy": "name_sort" + } + }, + "fields": [ + { + "name": "kind", + "type": "string", + "typeInfo": { + "frame": "string" + } + }, + { + "name": "uid", + "type": "string", + "typeInfo": { + "frame": "string" + } + }, + { + "name": "name", + "type": "string", + "typeInfo": { + "frame": "string" + } + }, + { + "name": "panel_type", + "type": "string", + "typeInfo": { + "frame": "string" + } + }, + { + "name": "url", + "type": "string", + "typeInfo": { + "frame": "string" + }, + "config": { + "links": [ + { + "title": "link", + "url": "${__value.text}" + } + ] + } + }, + { + "name": "tags", + "type": "other", + "typeInfo": { + "frame": "json.RawMessage", + "nullable": true + } + }, + { + "name": "ds_uid", + "type": "other", + "typeInfo": { + "frame": "json.RawMessage", + "nullable": false + } + }, + { + "name": "location", + "type": "string", + "typeInfo": { + "frame": "string" + } + } + ] + }, + "data": { + "values": [ + [ + "dashboard" + ], + [ + "ujaM1h6nz" + ], + [ + "abc2" + ], + [ + "" + ], + [ + "/dashboards/f/ujaM1h6nz/abc2" + ], + [ + [ + "gdev" + ] + ], + [ + [ + "datasource-2", + "datasource-3", + "datasource-4", + "grafana" + ] + ], + [ + "" + ] + ] + } +}