UnifiedSearch: Use ResourceIndex from dashboards apiserver (v0alpha1 only) (#96939)

This commit is contained in:
Ryan McKinley
2024-11-27 07:57:53 +02:00
committed by GitHub
parent 104f795156
commit f6ccf976e5
46 changed files with 1611 additions and 3447 deletions
+5 -1
View File
@@ -4,10 +4,11 @@ import (
"fmt"
"time"
"github.com/grafana/grafana/pkg/apimachinery/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/grafana/grafana/pkg/apimachinery/utils"
)
const (
@@ -93,6 +94,9 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&LibraryPanelList{},
&metav1.PartialObjectMetadata{},
&metav1.PartialObjectMetadataList{},
&metav1.Table{},
&SearchResults{},
&SortableFields{},
)
metav1.AddToGroupVersion(scheme, schemeGroupVersion)
return nil
+96
View File
@@ -0,0 +1,96 @@
package v0alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type SearchResults struct {
metav1.TypeMeta `json:",inline"`
// Where the query started from
Offset int64 `json:"offset,omitempty"`
// The number of matching results
TotalHits int64 `json:"totalHits"`
// The dashboard body (unstructured for now)
Hits []DashboardHit `json:"hits"`
// Cost of running the query
QueryCost float64 `json:"queryCost,omitempty"`
// Max score
MaxScore float64 `json:"maxScore,omitempty"`
// How are the results sorted
SortBy *SortBy `json:"sortBy,omitempty"`
// Facet results
Facets map[string]FacetResult `json:"facets,omitempty"`
}
type SortBy struct {
Field string `json:"field"`
Descending bool `json:"desc,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type SortableFields struct {
metav1.TypeMeta `json:",inline"`
// Sortable fields (depends on backend support)
Fields []SortableField `json:"fields"`
}
type SortableField struct {
Field string `json:"string,omitempty"`
Display string `json:"display,omitempty"`
Type string `json:"type,omitempty"` // string or number
}
// Dashboard or folder hit
// +enum
type HitKind string
// PluginType values
const (
HitTypeDash HitKind = "Dashboard"
HitTypeFolder HitKind = "Folder"
)
type DashboardHit struct {
// Dashboard or folder
Kind HitKind `json:"kind"`
// The k8s "name" (eg, grafana UID)
Name string `json:"name"`
// The display nam
Title string `json:"title"`
// Filter tags
Tags []string `json:"tags,omitempty"`
// The UID/name for the folder
Folder string `json:"folder,omitempty"`
// Stick untyped extra fields in this object (including the sort value)
Field *common.Unstructured `json:"field,omitempty"`
// Explain the score (if possible)
Explain *common.Unstructured `json:"explain,omitempty"`
// When using "real" search, this is the score
Score float64 `json:"score,omitempty"`
}
type FacetResult struct {
Field string `json:"field,omitempty"`
// The distinct terms
Total int64 `json:"total,omitempty"`
// The number of documents that do *not* have this field
Missing int64 `json:"missing,omitempty"`
// Term facets
Terms []TermFacet `json:"terms,omitempty"`
}
type TermFacet struct {
Term string `json:"term,omitempty"`
Count int64 `json:"count,omitempty"`
}
@@ -94,6 +94,35 @@ func (in *DashboardAccess) DeepCopy() *DashboardAccess {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DashboardHit) DeepCopyInto(out *DashboardHit) {
*out = *in
if in.Tags != nil {
in, out := &in.Tags, &out.Tags
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Field != nil {
in, out := &in.Field, &out.Field
*out = (*in).DeepCopy()
}
if in.Explain != nil {
in, out := &in.Explain, &out.Explain
*out = (*in).DeepCopy()
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DashboardHit.
func (in *DashboardHit) DeepCopy() *DashboardHit {
if in == nil {
return nil
}
out := new(DashboardHit)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DashboardList) DeepCopyInto(out *DashboardList) {
*out = *in
@@ -200,6 +229,27 @@ func (in *DashboardWithAccessInfo) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FacetResult) DeepCopyInto(out *FacetResult) {
*out = *in
if in.Terms != nil {
in, out := &in.Terms, &out.Terms
*out = make([]TermFacet, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FacetResult.
func (in *FacetResult) DeepCopy() *FacetResult {
if in == nil {
return nil
}
out := new(FacetResult)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LibraryPanel) DeepCopyInto(out *LibraryPanel) {
*out = *in
@@ -317,6 +367,128 @@ func (in *LibraryPanelStatus) DeepCopy() *LibraryPanelStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SearchResults) DeepCopyInto(out *SearchResults) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Hits != nil {
in, out := &in.Hits, &out.Hits
*out = make([]DashboardHit, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.SortBy != nil {
in, out := &in.SortBy, &out.SortBy
*out = new(SortBy)
**out = **in
}
if in.Facets != nil {
in, out := &in.Facets, &out.Facets
*out = make(map[string]FacetResult, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SearchResults.
func (in *SearchResults) DeepCopy() *SearchResults {
if in == nil {
return nil
}
out := new(SearchResults)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *SearchResults) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SortBy) DeepCopyInto(out *SortBy) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SortBy.
func (in *SortBy) DeepCopy() *SortBy {
if in == nil {
return nil
}
out := new(SortBy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SortableField) DeepCopyInto(out *SortableField) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SortableField.
func (in *SortableField) DeepCopy() *SortableField {
if in == nil {
return nil
}
out := new(SortableField)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SortableFields) DeepCopyInto(out *SortableFields) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Fields != nil {
in, out := &in.Fields, &out.Fields
*out = make([]SortableField, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SortableFields.
func (in *SortableFields) DeepCopy() *SortableFields {
if in == nil {
return nil
}
out := new(SortableFields)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *SortableFields) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TermFacet) DeepCopyInto(out *TermFacet) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TermFacet.
func (in *TermFacet) DeepCopy() *TermFacet {
if in == nil {
return nil
}
out := new(TermFacet)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VersionsQueryOptions) DeepCopyInto(out *VersionsQueryOptions) {
*out = *in
@@ -18,14 +18,21 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.AnnotationPermission": schema_pkg_apis_dashboard_v0alpha1_AnnotationPermission(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.Dashboard": schema_pkg_apis_dashboard_v0alpha1_Dashboard(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.DashboardAccess": schema_pkg_apis_dashboard_v0alpha1_DashboardAccess(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.DashboardHit": schema_pkg_apis_dashboard_v0alpha1_DashboardHit(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.DashboardList": schema_pkg_apis_dashboard_v0alpha1_DashboardList(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.DashboardVersionInfo": schema_pkg_apis_dashboard_v0alpha1_DashboardVersionInfo(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.DashboardVersionList": schema_pkg_apis_dashboard_v0alpha1_DashboardVersionList(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.DashboardWithAccessInfo": schema_pkg_apis_dashboard_v0alpha1_DashboardWithAccessInfo(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.FacetResult": schema_pkg_apis_dashboard_v0alpha1_FacetResult(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanel": schema_pkg_apis_dashboard_v0alpha1_LibraryPanel(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanelList": schema_pkg_apis_dashboard_v0alpha1_LibraryPanelList(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanelSpec": schema_pkg_apis_dashboard_v0alpha1_LibraryPanelSpec(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanelStatus": schema_pkg_apis_dashboard_v0alpha1_LibraryPanelStatus(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.SearchResults": schema_pkg_apis_dashboard_v0alpha1_SearchResults(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.SortBy": schema_pkg_apis_dashboard_v0alpha1_SortBy(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.SortableField": schema_pkg_apis_dashboard_v0alpha1_SortableField(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.SortableFields": schema_pkg_apis_dashboard_v0alpha1_SortableFields(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.TermFacet": schema_pkg_apis_dashboard_v0alpha1_TermFacet(ref),
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.VersionsQueryOptions": schema_pkg_apis_dashboard_v0alpha1_VersionsQueryOptions(ref),
}
}
@@ -203,6 +210,87 @@ func schema_pkg_apis_dashboard_v0alpha1_DashboardAccess(ref common.ReferenceCall
}
}
func schema_pkg_apis_dashboard_v0alpha1_DashboardHit(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Dashboard or folder\n\nPossible enum values:\n - `\"Dashboard\"`\n - `\"Folder\"`",
Default: "",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"Dashboard", "Folder"},
},
},
"name": {
SchemaProps: spec.SchemaProps{
Description: "The k8s \"name\" (eg, grafana UID)",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"title": {
SchemaProps: spec.SchemaProps{
Description: "The display nam",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"tags": {
SchemaProps: spec.SchemaProps{
Description: "Filter tags",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"folder": {
SchemaProps: spec.SchemaProps{
Description: "The UID/name for the folder",
Type: []string{"string"},
Format: "",
},
},
"field": {
SchemaProps: spec.SchemaProps{
Description: "Stick untyped extra fields in this object (including the sort value)",
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"),
},
},
"explain": {
SchemaProps: spec.SchemaProps{
Description: "Explain the score (if possible)",
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"),
},
},
"score": {
SchemaProps: spec.SchemaProps{
Description: "When using \"real\" search, this is the score",
Type: []string{"number"},
Format: "double",
},
},
},
Required: []string{"kind", "name", "title"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"},
}
}
func schema_pkg_apis_dashboard_v0alpha1_DashboardList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -396,6 +484,54 @@ func schema_pkg_apis_dashboard_v0alpha1_DashboardWithAccessInfo(ref common.Refer
}
}
func schema_pkg_apis_dashboard_v0alpha1_FacetResult(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"field": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"total": {
SchemaProps: spec.SchemaProps{
Description: "The distinct terms",
Type: []string{"integer"},
Format: "int64",
},
},
"missing": {
SchemaProps: spec.SchemaProps{
Description: "The number of documents that do *not* have this field",
Type: []string{"integer"},
Format: "int64",
},
},
"terms": {
SchemaProps: spec.SchemaProps{
Description: "Term facets",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.TermFacet"),
},
},
},
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.TermFacet"},
}
}
func schema_pkg_apis_dashboard_v0alpha1_LibraryPanel(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -607,6 +743,222 @@ func schema_pkg_apis_dashboard_v0alpha1_LibraryPanelStatus(ref common.ReferenceC
}
}
func schema_pkg_apis_dashboard_v0alpha1_SearchResults(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
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{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
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{"string"},
Format: "",
},
},
"offset": {
SchemaProps: spec.SchemaProps{
Description: "Where the query started from",
Type: []string{"integer"},
Format: "int64",
},
},
"totalHits": {
SchemaProps: spec.SchemaProps{
Description: "The number of matching results",
Default: 0,
Type: []string{"integer"},
Format: "int64",
},
},
"hits": {
SchemaProps: spec.SchemaProps{
Description: "The dashboard body (unstructured for now)",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.DashboardHit"),
},
},
},
},
},
"queryCost": {
SchemaProps: spec.SchemaProps{
Description: "Cost of running the query",
Type: []string{"number"},
Format: "double",
},
},
"maxScore": {
SchemaProps: spec.SchemaProps{
Description: "Max score",
Type: []string{"number"},
Format: "double",
},
},
"sortBy": {
SchemaProps: spec.SchemaProps{
Description: "How are the results sorted",
Ref: ref("github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.SortBy"),
},
},
"facets": {
SchemaProps: spec.SchemaProps{
Description: "Facet results",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.FacetResult"),
},
},
},
},
},
},
Required: []string{"totalHits", "hits"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.DashboardHit", "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.FacetResult", "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.SortBy"},
}
}
func schema_pkg_apis_dashboard_v0alpha1_SortBy(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"field": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"desc": {
SchemaProps: spec.SchemaProps{
Type: []string{"boolean"},
Format: "",
},
},
},
Required: []string{"field"},
},
},
}
}
func schema_pkg_apis_dashboard_v0alpha1_SortableField(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"string": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"display": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"type": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
}
}
func schema_pkg_apis_dashboard_v0alpha1_SortableFields(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
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{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
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{"string"},
Format: "",
},
},
"fields": {
SchemaProps: spec.SchemaProps{
Description: "Sortable fields (depends on backend support)",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.SortableField"),
},
},
},
},
},
},
Required: []string{"fields"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.SortableField"},
}
}
func schema_pkg_apis_dashboard_v0alpha1_TermFacet(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"term": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"count": {
SchemaProps: spec.SchemaProps{
Type: []string{"integer"},
Format: "int64",
},
},
},
},
},
}
}
func schema_pkg_apis_dashboard_v0alpha1_VersionsQueryOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -1 +1,7 @@
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1,DashboardHit,Tags
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1,FacetResult,Terms
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1,LibraryPanelStatus,Warnings
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1,SearchResults,Hits
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1,SortableFields,Fields
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1,SortBy,Descending
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1,SortableField,Field