Search: Include managedBy in DashboardHit result (#111600)
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -640,6 +641,57 @@ func (b *DashboardsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefiniti
|
||||
|
||||
func (b *DashboardsAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
||||
oas.Info.Description = "Grafana dashboards as resources"
|
||||
|
||||
// Add dashboard hits manually
|
||||
if oas.Info.Title == "dashboard.grafana.app/v0alpha1" {
|
||||
defs := b.GetOpenAPIDefinitions()(func(path string) spec.Ref { return spec.Ref{} })
|
||||
defsBase := "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1."
|
||||
refsBase := "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v0alpha1."
|
||||
|
||||
kinds := []string{"SearchResults", "DashboardHit", "ManagedBy", "FacetResult", "TermFacet", "SortBy"}
|
||||
|
||||
// Add any missing definitions
|
||||
//-----------------------------
|
||||
for _, k := range kinds {
|
||||
v := defs[defsBase+k]
|
||||
clean := strings.Replace(k, defsBase, refsBase, 1)
|
||||
if oas.Components.Schemas[clean] == nil {
|
||||
switch k {
|
||||
case "SearchResults":
|
||||
v.Schema.Properties["sortBy"] = *spec.RefProperty(
|
||||
"#/components/schemas/SortBy")
|
||||
v.Schema.Properties["hits"] = *spec.ArrayProperty(
|
||||
spec.RefProperty("#/components/schemas/DashboardHit"),
|
||||
)
|
||||
v.Schema.Properties["facets"] = *spec.MapProperty(
|
||||
spec.RefProperty("#/components/schemas/FacetResult"),
|
||||
)
|
||||
case "DashboardHit":
|
||||
v.Schema.Properties["managedBy"] = *spec.RefProperty(
|
||||
"#/components/schemas/ManagedBy")
|
||||
case "FacetResult":
|
||||
v.Schema.Properties["terms"] = *spec.ArrayProperty(
|
||||
spec.RefProperty("#/components/schemas/TermFacet"),
|
||||
)
|
||||
}
|
||||
oas.Components.Schemas[clean] = &v.Schema
|
||||
}
|
||||
}
|
||||
|
||||
p := oas.Paths.Paths["/apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search"]
|
||||
p.Get.Responses.StatusCodeResponses[200] = &spec3.Response{
|
||||
ResponseProps: spec3.ResponseProps{
|
||||
Content: map[string]*spec3.MediaType{
|
||||
"application/json": {
|
||||
MediaTypeProps: spec3.MediaTypeProps{
|
||||
Schema: spec.RefSchema("#/components/schemas/SearchResults"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return oas, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -291,7 +291,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
|
||||
Page: int64(page), // for modes 0-2 (legacy)
|
||||
Explain: queryParams.Has("explain") && queryParams.Get("explain") != "false",
|
||||
}
|
||||
fields := []string{"title", "folder", "tags", "description"}
|
||||
fields := []string{"title", "folder", "tags", "description", "manager.kind", "manager.id"}
|
||||
if queryParams.Has("field") {
|
||||
// add fields to search and exclude duplicates
|
||||
for _, f := range queryParams["field"] {
|
||||
|
||||
@@ -213,7 +213,7 @@ func TestSearchHandler(t *testing.T) {
|
||||
if mockClient.LastSearchRequest == nil {
|
||||
t.Fatalf("expected Search to be called, but it was not")
|
||||
}
|
||||
expectedFields := []string{"title", "folder", "tags", "description", "field1", "field2", "field3"}
|
||||
expectedFields := []string{"title", "folder", "tags", "description", "manager.kind", "manager.id", "field1", "field2", "field3"}
|
||||
if fmt.Sprintf("%v", mockClient.LastSearchRequest.Fields) != fmt.Sprintf("%v", expectedFields) {
|
||||
t.Errorf("expected fields %v, got %v", expectedFields, mockClient.LastSearchRequest.Fields)
|
||||
}
|
||||
@@ -242,7 +242,7 @@ func TestSearchHandler(t *testing.T) {
|
||||
if mockClient.LastSearchRequest == nil {
|
||||
t.Fatalf("expected Search to be called, but it was not")
|
||||
}
|
||||
expectedFields := []string{"title", "folder", "tags", "description", "field1"}
|
||||
expectedFields := []string{"title", "folder", "tags", "description", "manager.kind", "manager.id", "field1"}
|
||||
if fmt.Sprintf("%v", mockClient.LastSearchRequest.Fields) != fmt.Sprintf("%v", expectedFields) {
|
||||
t.Errorf("expected fields %v, got %v", expectedFields, mockClient.LastSearchRequest.Fields)
|
||||
}
|
||||
@@ -271,7 +271,7 @@ func TestSearchHandler(t *testing.T) {
|
||||
if mockClient.LastSearchRequest == nil {
|
||||
t.Fatalf("expected Search to be called, but it was not")
|
||||
}
|
||||
expectedFields := []string{"title", "folder", "tags", "description"}
|
||||
expectedFields := []string{"title", "folder", "tags", "description", "manager.kind", "manager.id"}
|
||||
if fmt.Sprintf("%v", mockClient.LastSearchRequest.Fields) != fmt.Sprintf("%v", expectedFields) {
|
||||
t.Errorf("expected fields %v, got %v", expectedFields, mockClient.LastSearchRequest.Fields)
|
||||
}
|
||||
|
||||
@@ -2043,13 +2043,13 @@ func (dr *DashboardServiceImpl) searchProvisionedDashboardsThroughK8s(ctx contex
|
||||
|
||||
dashs := make([]*dashboardProvisioningWithUID, 0)
|
||||
for _, hit := range searchResults.Hits {
|
||||
if utils.ParseManagerKindString(hit.Field.GetNestedString(resource.SEARCH_FIELD_MANAGER_KIND)) != utils.ManagerKindClassicFP { // nolint:staticcheck
|
||||
if utils.ParseManagerKindString(string(hit.ManagedBy.Kind)) != utils.ManagerKindClassicFP { // nolint:staticcheck
|
||||
continue
|
||||
}
|
||||
|
||||
provisioning := &dashboardProvisioningWithUID{
|
||||
DashboardProvisioning: dashboards.DashboardProvisioning{
|
||||
Name: hit.Field.GetNestedString(resource.SEARCH_FIELD_MANAGER_ID),
|
||||
Name: hit.ManagedBy.ID,
|
||||
ExternalID: hit.Field.GetNestedString(resource.SEARCH_FIELD_SOURCE_PATH),
|
||||
CheckSum: hit.Field.GetNestedString(resource.SEARCH_FIELD_SOURCE_CHECKSUM),
|
||||
Updated: hit.Field.GetNestedInt64(resource.SEARCH_FIELD_SOURCE_TIME),
|
||||
|
||||
@@ -5,21 +5,24 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
|
||||
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
|
||||
)
|
||||
|
||||
var (
|
||||
excludedFields = map[string]string{
|
||||
resource.SEARCH_FIELD_EXPLAIN: "",
|
||||
resource.SEARCH_FIELD_SCORE: "",
|
||||
resource.SEARCH_FIELD_TITLE: "",
|
||||
resource.SEARCH_FIELD_FOLDER: "",
|
||||
resource.SEARCH_FIELD_TAGS: "",
|
||||
resource.SEARCH_FIELD_DESCRIPTION: "",
|
||||
// These fields exist at the top-level of DashboardHit
|
||||
standardFields = map[string]string{
|
||||
resource.SEARCH_FIELD_EXPLAIN: "",
|
||||
resource.SEARCH_FIELD_SCORE: "",
|
||||
resource.SEARCH_FIELD_TITLE: "",
|
||||
resource.SEARCH_FIELD_FOLDER: "",
|
||||
resource.SEARCH_FIELD_TAGS: "",
|
||||
resource.SEARCH_FIELD_DESCRIPTION: "",
|
||||
resource.SEARCH_FIELD_MANAGER_ID: "",
|
||||
resource.SEARCH_FIELD_MANAGER_KIND: "",
|
||||
}
|
||||
|
||||
IncludeFields = []string{
|
||||
@@ -56,6 +59,8 @@ func ParseResults(result *resourcepb.ResourceSearchResponse, offset int64) (v0al
|
||||
descriptionIDX := -1
|
||||
scoreIDX := -1
|
||||
explainIDX := -1
|
||||
managerKindIDX := -1
|
||||
managerIdIDX := -1
|
||||
|
||||
for i, v := range result.Results.Columns {
|
||||
switch v.Name {
|
||||
@@ -69,6 +74,10 @@ func ParseResults(result *resourcepb.ResourceSearchResponse, offset int64) (v0al
|
||||
folderIDX = i
|
||||
case resource.SEARCH_FIELD_TAGS:
|
||||
tagsIDX = i
|
||||
case resource.SEARCH_FIELD_MANAGER_ID:
|
||||
managerIdIDX = i
|
||||
case resource.SEARCH_FIELD_MANAGER_KIND:
|
||||
managerKindIDX = i
|
||||
case resource.SEARCH_FIELD_DESCRIPTION:
|
||||
descriptionIDX = i
|
||||
}
|
||||
@@ -89,9 +98,10 @@ func ParseResults(result *resourcepb.ResourceSearchResponse, offset int64) (v0al
|
||||
return v0alpha1.SearchResults{}, fmt.Errorf("error parsing Search Response: mismatch number of columns and cells")
|
||||
}
|
||||
|
||||
// Dynamically defined fields
|
||||
fields := &common.Unstructured{}
|
||||
for colIndex, col := range result.Results.Columns {
|
||||
if _, ok := excludedFields[col.Name]; !ok {
|
||||
if _, ok := standardFields[col.Name]; !ok {
|
||||
val, err := resource.DecodeCell(col, colIndex, row.Cells[colIndex])
|
||||
if err != nil {
|
||||
return v0alpha1.SearchResults{}, err
|
||||
@@ -122,6 +132,12 @@ func ParseResults(result *resourcepb.ResourceSearchResponse, offset int64) (v0al
|
||||
if descriptionIDX >= 0 && row.Cells[descriptionIDX] != nil {
|
||||
hit.Description = string(row.Cells[descriptionIDX])
|
||||
}
|
||||
if managerIdIDX >= 0 && row.Cells[managerIdIDX] != nil {
|
||||
hit.ManagedBy.ID = string(row.Cells[managerIdIDX])
|
||||
}
|
||||
if managerKindIDX >= 0 && row.Cells[managerKindIDX] != nil {
|
||||
hit.ManagedBy.Kind = utils.ManagerKind(row.Cells[managerKindIDX])
|
||||
}
|
||||
if tagsIDX >= 0 && row.Cells[tagsIDX] != nil {
|
||||
_ = json.Unmarshal(row.Cells[tagsIDX], &hit.Tags)
|
||||
}
|
||||
|
||||
@@ -276,12 +276,8 @@ func (ss *FolderUnifiedStoreImpl) GetChildren(ctx context.Context, q folder.GetC
|
||||
UID: item.Name,
|
||||
Title: item.Title,
|
||||
ParentUID: item.Folder,
|
||||
ManagedBy: item.ManagedBy.Kind,
|
||||
}
|
||||
|
||||
if item.Field.GetNestedString(resource.SEARCH_FIELD_MANAGER_KIND) != "" {
|
||||
f.ManagedBy = utils.ParseManagerKindString(item.Field.GetNestedString(resource.SEARCH_FIELD_MANAGER_KIND))
|
||||
}
|
||||
|
||||
hits = append(hits, f)
|
||||
}
|
||||
|
||||
|
||||
@@ -1856,59 +1856,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"totalHits",
|
||||
"hits"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
"type": "string"
|
||||
},
|
||||
"facets": {
|
||||
"description": "Facet results",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"default": {}
|
||||
}
|
||||
},
|
||||
"hits": {
|
||||
"description": "The dashboard body (unstructured for now)",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"default": {}
|
||||
}
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
"type": "string"
|
||||
},
|
||||
"maxScore": {
|
||||
"description": "Max score",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"offset": {
|
||||
"description": "Where the query started from",
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"queryCost": {
|
||||
"description": "Cost of running the query",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"sortBy": {
|
||||
"description": "How are the results sorted"
|
||||
},
|
||||
"totalHits": {
|
||||
"description": "The number of matching results",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"default": 0
|
||||
}
|
||||
}
|
||||
"$ref": "#/components/schemas/SearchResults"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1970,6 +1918,180 @@
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"DashboardHit": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resource",
|
||||
"name",
|
||||
"title"
|
||||
],
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "Dashboard description",
|
||||
"type": "string"
|
||||
},
|
||||
"explain": {
|
||||
"description": "Explain the score (if possible)"
|
||||
},
|
||||
"field": {
|
||||
"description": "Stick untyped extra fields in this object (including the sort value)"
|
||||
},
|
||||
"folder": {
|
||||
"description": "The k8s name (eg, grafana UID) for the parent folder",
|
||||
"type": "string"
|
||||
},
|
||||
"managedBy": {
|
||||
"$ref": "#/components/schemas/ManagedBy"
|
||||
},
|
||||
"name": {
|
||||
"description": "The k8s \"name\" (eg, grafana UID)",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"resource": {
|
||||
"description": "Dashboard or folder",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"score": {
|
||||
"description": "When using \"real\" search, this is the score",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"tags": {
|
||||
"description": "Filter tags",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"description": "The display name",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"FacetResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string"
|
||||
},
|
||||
"missing": {
|
||||
"description": "The number of documents that do *not* have this field",
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"terms": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/TermFacet"
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"description": "The distinct terms",
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ManagedBy": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"SearchResults": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"totalHits",
|
||||
"hits"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
"type": "string"
|
||||
},
|
||||
"facets": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/FacetResult"
|
||||
}
|
||||
},
|
||||
"hits": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DashboardHit"
|
||||
}
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
"type": "string"
|
||||
},
|
||||
"maxScore": {
|
||||
"description": "Max score",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"offset": {
|
||||
"description": "Where the query started from",
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"queryCost": {
|
||||
"description": "Cost of running the query",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"sortBy": {
|
||||
"$ref": "#/components/schemas/SortBy"
|
||||
},
|
||||
"totalHits": {
|
||||
"description": "The number of matching results",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"default": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"SortBy": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"field"
|
||||
],
|
||||
"properties": {
|
||||
"desc": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"field": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"TermFacet": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"term": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"com.github.grafana.grafana-plugin-sdk-go.experimental.apis.data.v0alpha1.DataQuery": {
|
||||
"description": "Generic query properties",
|
||||
"type": "object",
|
||||
|
||||
Reference in New Issue
Block a user