Scopes: Remove all duplicated types for single source of truth (#112100)

* apimachinery: Remove unused scope types

* tsdb/loki: use scope types from apps/scope

* promlib: use scope types from apps/scope
This commit is contained in:
Matheus Macabu 2025-10-09 10:56:12 +02:00 committed by GitHub
parent f4cd46504b
commit 3ad54c0c1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 115 additions and 447 deletions

View File

@ -42,46 +42,3 @@ func (r ObjectReference) ToOwnerReference() metav1.OwnerReference {
UID: r.UID, UID: r.UID,
} }
} }
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type Scope struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ScopeSpec `json:"spec,omitempty"`
}
type ScopeSpec struct {
Title string `json:"title"`
// Provides a default path for the scope. This refers to a list of nodes in the selector. This is used to display the title next to the selected scope and expand the selector to the proper path.
// This will override whichever is selected from in the selector.
// The path is a list of node ids, starting at the direct parent of the selected node towards the root.
// +listType=atomic
DefaultPath []string `json:"defaultPath,omitempty"`
// +listType=atomic
Filters []ScopeFilter `json:"filters,omitempty"`
}
type ScopeFilter struct {
Key string `json:"key"`
Value string `json:"value"`
// Values is used for operators that require multiple values (e.g. one-of and not-one-of).
// +listType=atomic
Values []string `json:"values,omitempty"`
Operator FilterOperator `json:"operator"`
}
// Type of the filter operator.
// +enum
type FilterOperator string
// Defines values for FilterOperator.
const (
FilterOperatorEquals FilterOperator = "equals"
FilterOperatorNotEquals FilterOperator = "not-equals"
FilterOperatorRegexMatch FilterOperator = "regex-match"
FilterOperatorRegexNotMatch FilterOperator = "regex-not-match"
FilterOperatorOneOf FilterOperator = "one-of"
FilterOperatorNotOneOf FilterOperator = "not-one-of"
)

View File

@ -7,10 +7,6 @@
package v0alpha1 package v0alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *InlineSecureValue) DeepCopyInto(out *InlineSecureValue) { func (in *InlineSecureValue) DeepCopyInto(out *InlineSecureValue) {
*out = *in *out = *in
@ -42,79 +38,3 @@ func (in *ObjectReference) DeepCopy() *ObjectReference {
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Scope) DeepCopyInto(out *Scope) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Scope.
func (in *Scope) DeepCopy() *Scope {
if in == nil {
return nil
}
out := new(Scope)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Scope) 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 *ScopeFilter) DeepCopyInto(out *ScopeFilter) {
*out = *in
if in.Values != nil {
in, out := &in.Values, &out.Values
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScopeFilter.
func (in *ScopeFilter) DeepCopy() *ScopeFilter {
if in == nil {
return nil
}
out := new(ScopeFilter)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ScopeSpec) DeepCopyInto(out *ScopeSpec) {
*out = *in
if in.DefaultPath != nil {
in, out := &in.DefaultPath, &out.DefaultPath
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Filters != nil {
in, out := &in.Filters, &out.Filters
*out = make([]ScopeFilter, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScopeSpec.
func (in *ScopeSpec) DeepCopy() *ScopeSpec {
if in == nil {
return nil
}
out := new(ScopeSpec)
in.DeepCopyInto(out)
return out
}

View File

@ -17,9 +17,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
return map[string]common.OpenAPIDefinition{ return map[string]common.OpenAPIDefinition{
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue": InlineSecureValue{}.OpenAPIDefinition(), "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue": InlineSecureValue{}.OpenAPIDefinition(),
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.ObjectReference": schema_apimachinery_apis_common_v0alpha1_ObjectReference(ref), "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.ObjectReference": schema_apimachinery_apis_common_v0alpha1_ObjectReference(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Scope": schema_apimachinery_apis_common_v0alpha1_Scope(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.ScopeFilter": schema_apimachinery_apis_common_v0alpha1_ScopeFilter(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.ScopeSpec": schema_apimachinery_apis_common_v0alpha1_ScopeSpec(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured": Unstructured{}.OpenAPIDefinition(), "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured": Unstructured{}.OpenAPIDefinition(),
"k8s.io/apimachinery/pkg/apis/meta/v1.APIGroup": schema_pkg_apis_meta_v1_APIGroup(ref), "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroup": schema_pkg_apis_meta_v1_APIGroup(ref),
"k8s.io/apimachinery/pkg/apis/meta/v1.APIGroupList": schema_pkg_apis_meta_v1_APIGroupList(ref), "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroupList": schema_pkg_apis_meta_v1_APIGroupList(ref),
@ -139,162 +136,6 @@ func schema_apimachinery_apis_common_v0alpha1_ObjectReference(ref common.Referen
} }
} }
func schema_apimachinery_apis_common_v0alpha1_Scope(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: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
},
},
"spec": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.ScopeSpec"),
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.ScopeSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
func schema_apimachinery_apis_common_v0alpha1_ScopeFilter(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"key": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"value": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"values": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"operator": {
SchemaProps: spec.SchemaProps{
Description: "Possible enum values:\n - `\"equals\"`\n - `\"not-equals\"`\n - `\"not-one-of\"`\n - `\"one-of\"`\n - `\"regex-match\"`\n - `\"regex-not-match\"`",
Default: "",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"equals", "not-equals", "not-one-of", "one-of", "regex-match", "regex-not-match"},
},
},
},
Required: []string{"key", "value", "operator"},
},
},
}
}
func schema_apimachinery_apis_common_v0alpha1_ScopeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"title": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"defaultPath": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "Provides a default path for the scope. This refers to a list of nodes in the selector. This is used to display the title next to the selected scope and expand the selector to the proper path. This will override whichever is selected from in the selector. The path is a list of node ids, starting at the direct parent of the selected node towards the root.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"filters": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.ScopeFilter"),
},
},
},
},
},
},
Required: []string{"title"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.ScopeFilter"},
}
}
func schema_pkg_apis_meta_v1_APIGroup(ref common.ReferenceCallback) common.OpenAPIDefinition { func schema_pkg_apis_meta_v1_APIGroup(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{ return common.OpenAPIDefinition{
Schema: spec.Schema{ Schema: spec.Schema{

View File

@ -5,6 +5,7 @@ go 1.25.2
require ( require (
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4
github.com/grafana/grafana-plugin-sdk-go v0.279.0 github.com/grafana/grafana-plugin-sdk-go v0.279.0
github.com/grafana/grafana/apps/scope v0.0.0-20251007093103-792853df9134
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_golang v1.23.2
github.com/prometheus/common v0.66.1 github.com/prometheus/common v0.66.1
@ -54,6 +55,7 @@ require (
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/mux v1.8.1 // indirect
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251007081214-26e147d01f0a // indirect
github.com/grafana/otel-profiling-go v0.5.1 // indirect github.com/grafana/otel-profiling-go v0.5.1 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
@ -91,10 +93,9 @@ require (
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/smartystreets/goconvey v1.8.1 // indirect github.com/smartystreets/goconvey v1.8.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect github.com/ugorji/go/codec v1.2.11 // indirect
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect
github.com/unknwon/com v1.0.1 // indirect github.com/unknwon/com v1.0.1 // indirect

View File

@ -134,6 +134,10 @@ github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz2
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4=
github.com/grafana/grafana-plugin-sdk-go v0.279.0 h1:/KCrsZkj9pEGwIGovqAz1A8rjI2A2YT+ZpvgfZN0LAA= github.com/grafana/grafana-plugin-sdk-go v0.279.0 h1:/KCrsZkj9pEGwIGovqAz1A8rjI2A2YT+ZpvgfZN0LAA=
github.com/grafana/grafana-plugin-sdk-go v0.279.0/go.mod h1:/7oGN6Z7DGTGaLHhgIYrRr6Wvmdsb3BLw5hL4Kbjy88= github.com/grafana/grafana-plugin-sdk-go v0.279.0/go.mod h1:/7oGN6Z7DGTGaLHhgIYrRr6Wvmdsb3BLw5hL4Kbjy88=
github.com/grafana/grafana/apps/scope v0.0.0-20251007093103-792853df9134 h1:xly75v5lFNR37q+wXnwA5yU/fPW9IOSYbhFpt4tQyt8=
github.com/grafana/grafana/apps/scope v0.0.0-20251007093103-792853df9134/go.mod h1:zijsUNa1zi476JJIR2Lcm/Paz1nRCno9XCp6hbS6G9o=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251007081214-26e147d01f0a h1:L7xgV9mP6MRF3L2/vDOjNR7heaBPbXPMGTDN9/jXSFQ=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251007081214-26e147d01f0a/go.mod h1:OK8NwS87D5YphchOcAsiIWk/feMZ0EzfAGME1Kff860=
github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls=
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg=
@ -278,6 +282,7 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

View File

@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime" "github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
sdkapi "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" sdkapi "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
scope "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
@ -68,47 +69,15 @@ type PrometheusQueryProperties struct {
LegendFormat string `json:"legendFormat,omitempty"` LegendFormat string `json:"legendFormat,omitempty"`
// A set of filters applied to apply to the query // A set of filters applied to apply to the query
Scopes []ScopeSpec `json:"scopes,omitempty"` Scopes []scope.ScopeSpec `json:"scopes,omitempty"`
// Additional Ad-hoc filters that take precedence over Scope on conflict. // Additional Ad-hoc filters that take precedence over Scope on conflict.
AdhocFilters []ScopeFilter `json:"adhocFilters,omitempty"` AdhocFilters []scope.ScopeFilter `json:"adhocFilters,omitempty"`
// Group By parameters to apply to aggregate expressions in the query // Group By parameters to apply to aggregate expressions in the query
GroupByKeys []string `json:"groupByKeys,omitempty"` GroupByKeys []string `json:"groupByKeys,omitempty"`
} }
// ScopeSpec is a hand copy of the ScopeSpec struct from pkg/apis/scope/v0alpha1/types.go
// to avoid import (temp fix). This also has metadata.name inlined.
type ScopeSpec struct {
Name string `json:"name"` // This is the identifier from metadata.name of the scope model.
Title string `json:"title"`
DefaultPath []string `json:"defaultPath,omitempty"`
Filters []ScopeFilter `json:"filters,omitempty"`
}
// ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go
// to avoid import (temp fix)
type ScopeFilter struct {
Key string `json:"key"`
Value string `json:"value"`
// Values is used for operators that require multiple values (e.g. one-of and not-one-of).
Values []string `json:"values,omitempty"`
Operator FilterOperator `json:"operator"`
}
// FilterOperator is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go
type FilterOperator string
// Hand copy of enum from pkg/apis/scope/v0alpha1/types.go
const (
FilterOperatorEquals FilterOperator = "equals"
FilterOperatorNotEquals FilterOperator = "not-equals"
FilterOperatorRegexMatch FilterOperator = "regex-match"
FilterOperatorRegexNotMatch FilterOperator = "regex-not-match"
FilterOperatorOneOf FilterOperator = "one-of"
FilterOperatorNotOneOf FilterOperator = "not-one-of"
)
// Internal interval and range variables // Internal interval and range variables
const ( const (
varInterval = "$__interval" varInterval = "$__interval"
@ -175,7 +144,7 @@ type Query struct {
ExemplarQuery bool ExemplarQuery bool
UtcOffsetSec int64 UtcOffsetSec int64
Scopes []ScopeSpec Scopes []scope.ScopeSpec
} }
// This internal query struct is just like QueryModel, except it does not include: // This internal query struct is just like QueryModel, except it does not include:
@ -217,7 +186,7 @@ func Parse(ctx context.Context, log glog.Logger, span trace.Span, query backend.
) )
if enableScope { if enableScope {
var scopeFilters []ScopeFilter var scopeFilters []scope.ScopeFilter
for _, scope := range model.Scopes { for _, scope := range model.Scopes {
scopeFilters = append(scopeFilters, scope.Filters...) scopeFilters = append(scopeFilters, scope.Filters...)
} }

View File

@ -18,7 +18,6 @@
"description": "Additional Ad-hoc filters that take precedence over Scope on conflict.", "description": "Additional Ad-hoc filters that take precedence over Scope on conflict.",
"type": "array", "type": "array",
"items": { "items": {
"description": "ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix)",
"type": "object", "type": "object",
"required": [ "required": [
"key", "key",
@ -36,7 +35,6 @@
"type": "string" "type": "string"
}, },
"values": { "values": {
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
@ -184,10 +182,8 @@
"description": "A set of filters applied to apply to the query", "description": "A set of filters applied to apply to the query",
"type": "array", "type": "array",
"items": { "items": {
"description": "ScopeSpec is a hand copy of the ScopeSpec struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix).",
"type": "object", "type": "object",
"required": [ "required": [
"name",
"title" "title"
], ],
"properties": { "properties": {
@ -200,7 +196,6 @@
"filters": { "filters": {
"type": "array", "type": "array",
"items": { "items": {
"description": "ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix)",
"type": "object", "type": "object",
"required": [ "required": [
"key", "key",
@ -218,7 +213,6 @@
"type": "string" "type": "string"
}, },
"values": { "values": {
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
@ -228,10 +222,6 @@
"additionalProperties": false "additionalProperties": false
} }
}, },
"name": {
"description": "This is the identifier from metadata.name of the scope model.",
"type": "string"
},
"title": { "title": {
"type": "string" "type": "string"
} }

View File

@ -28,7 +28,6 @@
"description": "Additional Ad-hoc filters that take precedence over Scope on conflict.", "description": "Additional Ad-hoc filters that take precedence over Scope on conflict.",
"type": "array", "type": "array",
"items": { "items": {
"description": "ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix)",
"type": "object", "type": "object",
"required": [ "required": [
"key", "key",
@ -46,7 +45,6 @@
"type": "string" "type": "string"
}, },
"values": { "values": {
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
@ -194,10 +192,8 @@
"description": "A set of filters applied to apply to the query", "description": "A set of filters applied to apply to the query",
"type": "array", "type": "array",
"items": { "items": {
"description": "ScopeSpec is a hand copy of the ScopeSpec struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix).",
"type": "object", "type": "object",
"required": [ "required": [
"name",
"title" "title"
], ],
"properties": { "properties": {
@ -210,7 +206,6 @@
"filters": { "filters": {
"type": "array", "type": "array",
"items": { "items": {
"description": "ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix)",
"type": "object", "type": "object",
"required": [ "required": [
"key", "key",
@ -228,7 +223,6 @@
"type": "string" "type": "string"
}, },
"values": { "values": {
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
@ -238,10 +232,6 @@
"additionalProperties": false "additionalProperties": false
} }
}, },
"name": {
"description": "This is the identifier from metadata.name of the scope model.",
"type": "string"
},
"title": { "title": {
"type": "string" "type": "string"
} }

View File

@ -8,7 +8,7 @@
{ {
"metadata": { "metadata": {
"name": "default", "name": "default",
"resourceVersion": "1758739325095", "resourceVersion": "1759831574256",
"creationTimestamp": "2024-03-25T13:19:04Z" "creationTimestamp": "2024-03-25T13:19:04Z"
}, },
"spec": { "spec": {
@ -21,7 +21,6 @@
"description": "Additional Ad-hoc filters that take precedence over Scope on conflict.", "description": "Additional Ad-hoc filters that take precedence over Scope on conflict.",
"items": { "items": {
"additionalProperties": false, "additionalProperties": false,
"description": "ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix)",
"properties": { "properties": {
"key": { "key": {
"type": "string" "type": "string"
@ -33,7 +32,6 @@
"type": "string" "type": "string"
}, },
"values": { "values": {
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
"items": { "items": {
"type": "string" "type": "string"
}, },
@ -103,7 +101,6 @@
"description": "A set of filters applied to apply to the query", "description": "A set of filters applied to apply to the query",
"items": { "items": {
"additionalProperties": false, "additionalProperties": false,
"description": "ScopeSpec is a hand copy of the ScopeSpec struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix).",
"properties": { "properties": {
"defaultPath": { "defaultPath": {
"items": { "items": {
@ -114,7 +111,6 @@
"filters": { "filters": {
"items": { "items": {
"additionalProperties": false, "additionalProperties": false,
"description": "ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix)",
"properties": { "properties": {
"key": { "key": {
"type": "string" "type": "string"
@ -126,7 +122,6 @@
"type": "string" "type": "string"
}, },
"values": { "values": {
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
"items": { "items": {
"type": "string" "type": "string"
}, },
@ -142,16 +137,11 @@
}, },
"type": "array" "type": "array"
}, },
"name": {
"description": "This is the identifier from metadata.name of the scope model.",
"type": "string"
},
"title": { "title": {
"type": "string" "type": "string"
} }
}, },
"required": [ "required": [
"name",
"title" "title"
], ],
"type": "object" "type": "object"

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
scope "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/parser"
@ -15,7 +16,7 @@ func init() {
} }
// ApplyFiltersAndGroupBy takes a raw promQL expression, converts the filters into PromQL matchers, and applies these matchers to the parsed expression. It also applies the group by clause to any aggregate expressions in the parsed expression. // ApplyFiltersAndGroupBy takes a raw promQL expression, converts the filters into PromQL matchers, and applies these matchers to the parsed expression. It also applies the group by clause to any aggregate expressions in the parsed expression.
func ApplyFiltersAndGroupBy(rawExpr string, scopeFilters, adHocFilters []ScopeFilter, groupBy []string) (string, error) { func ApplyFiltersAndGroupBy(rawExpr string, scopeFilters, adHocFilters []scope.ScopeFilter, groupBy []string) (string, error) {
expr, err := parser.ParseExpr(rawExpr) expr, err := parser.ParseExpr(rawExpr)
if err != nil { if err != nil {
return "", err return "", err
@ -76,7 +77,7 @@ func ApplyFiltersAndGroupBy(rawExpr string, scopeFilters, adHocFilters []ScopeFi
return expr.String(), nil return expr.String(), nil
} }
func FiltersToMatchers(scopeFilters, adhocFilters []ScopeFilter) ([]*labels.Matcher, error) { func FiltersToMatchers(scopeFilters, adhocFilters []scope.ScopeFilter) ([]*labels.Matcher, error) {
filterMap := make(map[string]*labels.Matcher) filterMap := make(map[string]*labels.Matcher)
// scope filters are applied first // scope filters are applied first
@ -115,25 +116,25 @@ func FiltersToMatchers(scopeFilters, adhocFilters []ScopeFilter) ([]*labels.Matc
return matchers, nil return matchers, nil
} }
func filterToMatcher(f ScopeFilter) (*labels.Matcher, error) { func filterToMatcher(f scope.ScopeFilter) (*labels.Matcher, error) {
var mt labels.MatchType var mt labels.MatchType
switch f.Operator { switch f.Operator {
case FilterOperatorEquals: case scope.FilterOperatorEquals:
mt = labels.MatchEqual mt = labels.MatchEqual
case FilterOperatorNotEquals: case scope.FilterOperatorNotEquals:
mt = labels.MatchNotEqual mt = labels.MatchNotEqual
case FilterOperatorRegexMatch: case scope.FilterOperatorRegexMatch:
mt = labels.MatchRegexp mt = labels.MatchRegexp
case FilterOperatorRegexNotMatch: case scope.FilterOperatorRegexNotMatch:
mt = labels.MatchNotRegexp mt = labels.MatchNotRegexp
case FilterOperatorOneOf: case scope.FilterOperatorOneOf:
mt = labels.MatchRegexp mt = labels.MatchRegexp
case FilterOperatorNotOneOf: case scope.FilterOperatorNotOneOf:
mt = labels.MatchNotRegexp mt = labels.MatchNotRegexp
default: default:
return nil, fmt.Errorf("unknown operator %q", f.Operator) return nil, fmt.Errorf("unknown operator %q", f.Operator)
} }
if f.Operator == FilterOperatorOneOf || f.Operator == FilterOperatorNotOneOf { if f.Operator == scope.FilterOperatorOneOf || f.Operator == scope.FilterOperatorNotOneOf {
if len(f.Values) > 0 { if len(f.Values) > 0 {
return labels.NewMatcher(mt, f.Key, strings.Join(f.Values, "|")) return labels.NewMatcher(mt, f.Key, strings.Join(f.Values, "|"))
} }

View File

@ -3,6 +3,7 @@ package models
import ( import (
"testing" "testing"
scope "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -10,8 +11,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
query string query string
adhocFilters []ScopeFilter adhocFilters []scope.ScopeFilter
scopeFilters []ScopeFilter scopeFilters []scope.ScopeFilter
expected string expected string
expectErr bool expectErr bool
}{ }{
@ -30,8 +31,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{ {
name: "Adhoc filter with existing filter", name: "Adhoc filter with existing filter",
query: `http_requests_total{job="prometheus"}`, query: `http_requests_total{job="prometheus"}`,
adhocFilters: []ScopeFilter{ adhocFilters: []scope.ScopeFilter{
{Key: "method", Value: "get", Operator: FilterOperatorEquals}, {Key: "method", Value: "get", Operator: scope.FilterOperatorEquals},
}, },
expected: `http_requests_total{job="prometheus",method="get"}`, expected: `http_requests_total{job="prometheus",method="get"}`,
expectErr: false, expectErr: false,
@ -39,9 +40,9 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{ {
name: "Adhoc filter with no existing filter", name: "Adhoc filter with no existing filter",
query: `http_requests_total`, query: `http_requests_total`,
adhocFilters: []ScopeFilter{ adhocFilters: []scope.ScopeFilter{
{Key: "method", Value: "get", Operator: FilterOperatorEquals}, {Key: "method", Value: "get", Operator: scope.FilterOperatorEquals},
{Key: "job", Value: "prometheus", Operator: FilterOperatorEquals}, {Key: "job", Value: "prometheus", Operator: scope.FilterOperatorEquals},
}, },
expected: `http_requests_total{job="prometheus",method="get"}`, expected: `http_requests_total{job="prometheus",method="get"}`,
expectErr: false, expectErr: false,
@ -49,8 +50,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{ {
name: "Scope filter", name: "Scope filter",
query: `http_requests_total{job="prometheus"}`, query: `http_requests_total{job="prometheus"}`,
scopeFilters: []ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "status", Value: "200", Operator: FilterOperatorEquals}, {Key: "status", Value: "200", Operator: scope.FilterOperatorEquals},
}, },
expected: `http_requests_total{job="prometheus",status="200"}`, expected: `http_requests_total{job="prometheus",status="200"}`,
expectErr: false, expectErr: false,
@ -58,11 +59,11 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{ {
name: "Adhoc and Scope filter no existing filter", name: "Adhoc and Scope filter no existing filter",
query: `http_requests_total`, query: `http_requests_total`,
scopeFilters: []ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "status", Value: "200", Operator: FilterOperatorEquals}, {Key: "status", Value: "200", Operator: scope.FilterOperatorEquals},
}, },
adhocFilters: []ScopeFilter{ adhocFilters: []scope.ScopeFilter{
{Key: "job", Value: "prometheus", Operator: FilterOperatorEquals}, {Key: "job", Value: "prometheus", Operator: scope.FilterOperatorEquals},
}, },
expected: `http_requests_total{job="prometheus",status="200"}`, expected: `http_requests_total{job="prometheus",status="200"}`,
expectErr: false, expectErr: false,
@ -70,11 +71,11 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{ {
name: "Adhoc and Scope filter conflict - adhoc wins (if not oneOf or notOneOf)", name: "Adhoc and Scope filter conflict - adhoc wins (if not oneOf or notOneOf)",
query: `http_requests_total{job="prometheus"}`, query: `http_requests_total{job="prometheus"}`,
scopeFilters: []ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "status", Value: "404", Operator: FilterOperatorEquals}, {Key: "status", Value: "404", Operator: scope.FilterOperatorEquals},
}, },
adhocFilters: []ScopeFilter{ adhocFilters: []scope.ScopeFilter{
{Key: "status", Value: "200", Operator: FilterOperatorEquals}, {Key: "status", Value: "200", Operator: scope.FilterOperatorEquals},
}, },
expected: `http_requests_total{job="prometheus",status="200"}`, expected: `http_requests_total{job="prometheus",status="200"}`,
expectErr: false, expectErr: false,
@ -82,8 +83,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{ {
name: "Adhoc filters with more complex expression", name: "Adhoc filters with more complex expression",
query: `capacity_bytes{job="prometheus"} + available_bytes{job="grafana"} / 1024`, query: `capacity_bytes{job="prometheus"} + available_bytes{job="grafana"} / 1024`,
adhocFilters: []ScopeFilter{ adhocFilters: []scope.ScopeFilter{
{Key: "job", Value: "alloy", Operator: FilterOperatorEquals}, {Key: "job", Value: "alloy", Operator: scope.FilterOperatorEquals},
}, },
expected: `capacity_bytes{job="alloy"} + available_bytes{job="alloy"} / 1024`, expected: `capacity_bytes{job="alloy"} + available_bytes{job="alloy"} / 1024`,
expectErr: false, expectErr: false,
@ -91,8 +92,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{ {
name: "OneOf Operator is combined into a single regex filter", name: "OneOf Operator is combined into a single regex filter",
query: `http_requests_total{job="prometheus"}`, query: `http_requests_total{job="prometheus"}`,
scopeFilters: []ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "status", Values: []string{"404", "400"}, Operator: FilterOperatorOneOf}, {Key: "status", Values: []string{"404", "400"}, Operator: scope.FilterOperatorOneOf},
}, },
expected: `http_requests_total{job="prometheus",status=~"404|400"}`, expected: `http_requests_total{job="prometheus",status=~"404|400"}`,
expectErr: false, expectErr: false,
@ -100,8 +101,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{ {
name: "using __name__ as part of the query", name: "using __name__ as part of the query",
query: `{__name__="http_requests_total"}`, query: `{__name__="http_requests_total"}`,
scopeFilters: []ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "namespace", Value: "istio", Operator: FilterOperatorEquals}, {Key: "namespace", Value: "istio", Operator: scope.FilterOperatorEquals},
}, },
expected: `{__name__="http_requests_total",namespace="istio"}`, expected: `{__name__="http_requests_total",namespace="istio"}`,
expectErr: false, expectErr: false,
@ -109,9 +110,9 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{ {
name: "merge scopes filters into using OR if they share filter key", name: "merge scopes filters into using OR if they share filter key",
query: `http_requests_total{}`, query: `http_requests_total{}`,
scopeFilters: []ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "namespace", Value: "default", Operator: FilterOperatorEquals}, {Key: "namespace", Value: "default", Operator: scope.FilterOperatorEquals},
{Key: "namespace", Value: "kube-system", Operator: FilterOperatorEquals}, {Key: "namespace", Value: "kube-system", Operator: scope.FilterOperatorEquals},
}, },
expected: `http_requests_total{namespace=~"default|kube-system"}`, expected: `http_requests_total{namespace=~"default|kube-system"}`,
expectErr: false, expectErr: false,
@ -119,12 +120,12 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{ {
name: "adhoc filters win over scope filters if they share filter key", name: "adhoc filters win over scope filters if they share filter key",
query: `http_requests_total{}`, query: `http_requests_total{}`,
scopeFilters: []ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "namespace", Value: "default", Operator: FilterOperatorEquals}, {Key: "namespace", Value: "default", Operator: scope.FilterOperatorEquals},
{Key: "namespace", Value: "kube-system", Operator: FilterOperatorEquals}, {Key: "namespace", Value: "kube-system", Operator: scope.FilterOperatorEquals},
}, },
adhocFilters: []ScopeFilter{ adhocFilters: []scope.ScopeFilter{
{Key: "namespace", Value: "adhoc-wins", Operator: FilterOperatorEquals}, {Key: "namespace", Value: "adhoc-wins", Operator: scope.FilterOperatorEquals},
}, },
expected: `http_requests_total{namespace="adhoc-wins"}`, expected: `http_requests_total{namespace="adhoc-wins"}`,
expectErr: false, expectErr: false,
@ -149,8 +150,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters_utf8(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
query string query string
adhocFilters []ScopeFilter adhocFilters []scope.ScopeFilter
scopeFilters []ScopeFilter scopeFilters []scope.ScopeFilter
expected string expected string
expectErr bool expectErr bool
}{ }{
@ -276,8 +277,8 @@ func TestApplyQueryFiltersAndGroupBy(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
query string query string
adhocFilters []ScopeFilter adhocFilters []scope.ScopeFilter
scopeFilters []ScopeFilter scopeFilters []scope.ScopeFilter
groupby []string groupby []string
expected string expected string
expectErr bool expectErr bool
@ -286,11 +287,11 @@ func TestApplyQueryFiltersAndGroupBy(t *testing.T) {
{ {
name: "Adhoc filters with more complex expression", name: "Adhoc filters with more complex expression",
query: `sum(capacity_bytes{job="prometheus"} + available_bytes{job="grafana"}) / 1024`, query: `sum(capacity_bytes{job="prometheus"} + available_bytes{job="grafana"}) / 1024`,
adhocFilters: []ScopeFilter{ adhocFilters: []scope.ScopeFilter{
{Key: "job", Value: "alloy", Operator: FilterOperatorEquals}, {Key: "job", Value: "alloy", Operator: scope.FilterOperatorEquals},
}, },
scopeFilters: []ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "vol", Value: "/", Operator: FilterOperatorEquals}, {Key: "vol", Value: "/", Operator: scope.FilterOperatorEquals},
}, },
groupby: []string{"job"}, groupby: []string{"job"},
expected: `sum by (job) (capacity_bytes{job="alloy",vol="/"} + available_bytes{job="alloy",vol="/"}) / 1024`, expected: `sum by (job) (capacity_bytes{job="alloy",vol="/"} + available_bytes{job="alloy",vol="/"}) / 1024`,
@ -316,8 +317,8 @@ func TestApplyQueryFiltersAndGroupBy_utf8(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
query string query string
adhocFilters []ScopeFilter adhocFilters []scope.ScopeFilter
scopeFilters []ScopeFilter scopeFilters []scope.ScopeFilter
groupby []string groupby []string
expected string expected string
expectErr bool expectErr bool
@ -325,11 +326,11 @@ func TestApplyQueryFiltersAndGroupBy_utf8(t *testing.T) {
{ {
name: "Adhoc filters with more complex expression and utf8 metric name", name: "Adhoc filters with more complex expression and utf8 metric name",
query: `sum({"capacity_bytes", job="prometheus"} + {"available_bytes", job="grafana"}) / 1024`, query: `sum({"capacity_bytes", job="prometheus"} + {"available_bytes", job="grafana"}) / 1024`,
adhocFilters: []ScopeFilter{ adhocFilters: []scope.ScopeFilter{
{Key: "job", Value: "alloy", Operator: FilterOperatorEquals}, {Key: "job", Value: "alloy", Operator: scope.FilterOperatorEquals},
}, },
scopeFilters: []ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "vol", Value: "/", Operator: FilterOperatorEquals}, {Key: "vol", Value: "/", Operator: scope.FilterOperatorEquals},
}, },
groupby: []string{"job"}, groupby: []string{"job"},
expected: `sum by (job) ({__name__="capacity_bytes",job="alloy",vol="/"} + {__name__="available_bytes",job="alloy",vol="/"}) / 1024`, expected: `sum by (job) ({__name__="capacity_bytes",job="alloy",vol="/"} + {__name__="available_bytes",job="alloy",vol="/"}) / 1024`,
@ -338,11 +339,11 @@ func TestApplyQueryFiltersAndGroupBy_utf8(t *testing.T) {
{ {
name: "Adhoc filters with more complex expression with utf8 label", name: "Adhoc filters with more complex expression with utf8 label",
query: `sum(capacity_bytes{job="prometheus", "utf8.label"="value"} + available_bytes{job="grafana"}) / 1024`, query: `sum(capacity_bytes{job="prometheus", "utf8.label"="value"} + available_bytes{job="grafana"}) / 1024`,
adhocFilters: []ScopeFilter{ adhocFilters: []scope.ScopeFilter{
{Key: "job", Value: "alloy", Operator: FilterOperatorEquals}, {Key: "job", Value: "alloy", Operator: scope.FilterOperatorEquals},
}, },
scopeFilters: []ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "vol", Value: "/", Operator: FilterOperatorEquals}, {Key: "vol", Value: "/", Operator: scope.FilterOperatorEquals},
}, },
groupby: []string{"job"}, groupby: []string{"job"},
expected: `sum by (job) (capacity_bytes{"utf8.label"="value",job="alloy",vol="/"} + available_bytes{job="alloy",vol="/"}) / 1024`, expected: `sum by (job) (capacity_bytes{"utf8.label"="value",job="alloy",vol="/"} + available_bytes{job="alloy",vol="/"}) / 1024`,
@ -351,11 +352,11 @@ func TestApplyQueryFiltersAndGroupBy_utf8(t *testing.T) {
{ {
name: "Adhoc filters with more complex expression with utf8 metric and label", name: "Adhoc filters with more complex expression with utf8 metric and label",
query: `sum({"capacity_bytes", job="prometheus", "utf8.label"="value"} + available_bytes{job="grafana"}) / 1024`, query: `sum({"capacity_bytes", job="prometheus", "utf8.label"="value"} + available_bytes{job="grafana"}) / 1024`,
adhocFilters: []ScopeFilter{ adhocFilters: []scope.ScopeFilter{
{Key: "job", Value: "alloy", Operator: FilterOperatorEquals}, {Key: "job", Value: "alloy", Operator: scope.FilterOperatorEquals},
}, },
scopeFilters: []ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "vol", Value: "/", Operator: FilterOperatorEquals}, {Key: "vol", Value: "/", Operator: scope.FilterOperatorEquals},
}, },
groupby: []string{"job"}, groupby: []string{"job"},
expected: `sum by (job) ({"utf8.label"="value",__name__="capacity_bytes",job="alloy",vol="/"} + available_bytes{job="alloy",vol="/"}) / 1024`, expected: `sum by (job) ({"utf8.label"="value",__name__="capacity_bytes",job="alloy",vol="/"} + available_bytes{job="alloy",vol="/"}) / 1024`,

View File

@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/data/utils/maputil" "github.com/grafana/grafana-plugin-sdk-go/data/utils/maputil"
scope "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
"github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/parser"
"github.com/grafana/grafana/pkg/promlib/client" "github.com/grafana/grafana/pkg/promlib/client"
@ -116,8 +117,8 @@ type SuggestionRequest struct {
Queries []string `json:"queries"` Queries []string `json:"queries"`
Scopes []models.ScopeFilter `json:"scopes"` Scopes []scope.ScopeFilter `json:"scopes"`
AdhocFilters []models.ScopeFilter `json:"adhocFilters"` AdhocFilters []scope.ScopeFilter `json:"adhocFilters"`
// Start and End are proxied directly to the prometheus endpoint (which is rfc3339 | unix_timestamp) // Start and End are proxied directly to the prometheus endpoint (which is rfc3339 | unix_timestamp)
Start string `json:"start"` Start string `json:"start"`

View File

@ -11,10 +11,10 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/log"
scope "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/promlib/models"
"github.com/grafana/grafana/pkg/promlib/resource" "github.com/grafana/grafana/pkg/promlib/resource"
) )
@ -151,11 +151,11 @@ func TestResource_GetSuggestionsWithEmptyQueriesButFilters(t *testing.T) {
// Create a request with empty queries but with filters // Create a request with empty queries but with filters
suggestionReq := resource.SuggestionRequest{ suggestionReq := resource.SuggestionRequest{
Queries: []string{}, // Empty queries Queries: []string{}, // Empty queries
Scopes: []models.ScopeFilter{ Scopes: []scope.ScopeFilter{
{Key: "job", Operator: models.FilterOperatorEquals, Value: "testjob"}, {Key: "job", Operator: scope.FilterOperatorEquals, Value: "testjob"},
}, },
AdhocFilters: []models.ScopeFilter{ AdhocFilters: []scope.ScopeFilter{
{Key: "instance", Operator: models.FilterOperatorEquals, Value: "localhost:9090"}, {Key: "instance", Operator: scope.FilterOperatorEquals, Value: "localhost:9090"},
}, },
} }

View File

@ -21,8 +21,8 @@ import (
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
scope "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
"github.com/grafana/grafana/pkg/promlib/models"
"github.com/grafana/grafana/pkg/tsdb/loki/kinds/dataquery" "github.com/grafana/grafana/pkg/tsdb/loki/kinds/dataquery"
) )
@ -75,7 +75,7 @@ type QueryJSONModel struct {
dataquery.LokiDataQuery dataquery.LokiDataQuery
Direction *string `json:"direction,omitempty"` Direction *string `json:"direction,omitempty"`
SupportingQueryType *string `json:"supportingQueryType"` SupportingQueryType *string `json:"supportingQueryType"`
Scopes []models.ScopeFilter `json:"scopes"` Scopes []scope.ScopeFilter `json:"scopes"`
} }
type ResponseOpts struct { type ResponseOpts struct {

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
scope "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
"github.com/grafana/grafana/pkg/promlib/models" "github.com/grafana/grafana/pkg/promlib/models"
"github.com/grafana/grafana/pkg/tsdb/loki/kinds/dataquery" "github.com/grafana/grafana/pkg/tsdb/loki/kinds/dataquery"
"github.com/grafana/loki/v3/pkg/logql/syntax" "github.com/grafana/loki/v3/pkg/logql/syntax"
@ -21,8 +22,8 @@ type SuggestionRequest struct {
Query string `json:"query"` Query string `json:"query"`
Scopes []models.ScopeFilter `json:"scopes"` Scopes []scope.ScopeFilter `json:"scopes"`
AdhocFilters []models.ScopeFilter `json:"adhocFilters"` AdhocFilters []scope.ScopeFilter `json:"adhocFilters"`
// Start and End are proxied directly to the prometheus endpoint (which is rfc3339 | unix_timestamp) // Start and End are proxied directly to the prometheus endpoint (which is rfc3339 | unix_timestamp)
Start string `json:"start"` Start string `json:"start"`
@ -79,7 +80,7 @@ func GetSuggestions(ctx context.Context, lokiAPI *LokiAPI, req *backend.CallReso
} }
// ApplyScopes applies the given scope filters to the given raw expression. // ApplyScopes applies the given scope filters to the given raw expression.
func ApplyScopes(rawExpr string, scopeFilters []models.ScopeFilter) (string, error) { func ApplyScopes(rawExpr string, scopeFilters []scope.ScopeFilter) (string, error) {
if len(scopeFilters) == 0 { if len(scopeFilters) == 0 {
return rawExpr, nil return rawExpr, nil
} }

View File

@ -3,7 +3,7 @@ package loki
import ( import (
"testing" "testing"
"github.com/grafana/grafana/pkg/promlib/models" scope "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -14,7 +14,7 @@ func TestInjectScopesIntoLokiQuery(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
query string query string
scopeFilters []models.ScopeFilter scopeFilters []scope.ScopeFilter
expected string expected string
expectErr bool expectErr bool
}{ }{
@ -33,8 +33,8 @@ func TestInjectScopesIntoLokiQuery(t *testing.T) {
{ {
name: "scopes with existing filter", name: "scopes with existing filter",
query: `{namespace="default"} |= "an unexpected error"`, query: `{namespace="default"} |= "an unexpected error"`,
scopeFilters: []models.ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "cluster", Value: "us-central-1", Operator: models.FilterOperatorEquals}, {Key: "cluster", Value: "us-central-1", Operator: scope.FilterOperatorEquals},
}, },
expected: `{namespace="default", cluster="us-central-1"} |= "an unexpected error"`, expected: `{namespace="default", cluster="us-central-1"} |= "an unexpected error"`,
expectErr: false, expectErr: false,
@ -42,8 +42,8 @@ func TestInjectScopesIntoLokiQuery(t *testing.T) {
{ {
name: "scopes without existing label matchers", name: "scopes without existing label matchers",
query: `{} |= "an unexpected error"`, query: `{} |= "an unexpected error"`,
scopeFilters: []models.ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "cluster", Value: "us-central-1", Operator: models.FilterOperatorEquals}, {Key: "cluster", Value: "us-central-1", Operator: scope.FilterOperatorEquals},
}, },
expected: `{cluster="us-central-1"} |= "an unexpected error"`, expected: `{cluster="us-central-1"} |= "an unexpected error"`,
expectErr: false, expectErr: false,
@ -51,9 +51,9 @@ func TestInjectScopesIntoLokiQuery(t *testing.T) {
{ {
name: "scopes with multiple filters", name: "scopes with multiple filters",
query: `{} |= "an unexpected error"`, query: `{} |= "an unexpected error"`,
scopeFilters: []models.ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "cluster", Value: "us-central-1", Operator: models.FilterOperatorEquals}, {Key: "cluster", Value: "us-central-1", Operator: scope.FilterOperatorEquals},
{Key: "namespace", Value: "default", Operator: models.FilterOperatorEquals}, {Key: "namespace", Value: "default", Operator: scope.FilterOperatorEquals},
}, },
expected: `{cluster="us-central-1", namespace="default"} |= "an unexpected error"`, expected: `{cluster="us-central-1", namespace="default"} |= "an unexpected error"`,
expectErr: false, expectErr: false,
@ -61,8 +61,8 @@ func TestInjectScopesIntoLokiQuery(t *testing.T) {
{ {
name: "metric query with scopes filters", name: "metric query with scopes filters",
query: `count_over_time({} |= "error" [1m])`, query: `count_over_time({} |= "error" [1m])`,
scopeFilters: []models.ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "namespace", Value: "default", Operator: models.FilterOperatorEquals}, {Key: "namespace", Value: "default", Operator: scope.FilterOperatorEquals},
}, },
expected: `count_over_time({namespace="default"} |= "error"[1m])`, expected: `count_over_time({namespace="default"} |= "error"[1m])`,
expectErr: false, expectErr: false,
@ -70,9 +70,9 @@ func TestInjectScopesIntoLokiQuery(t *testing.T) {
{ {
name: "multi range metric query operation", name: "multi range metric query operation",
query: `count_over_time({} |= "error" [1m])/count_over_time({} [1m])`, query: `count_over_time({} |= "error" [1m])/count_over_time({} [1m])`,
scopeFilters: []models.ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "cluster", Value: "us-central-1", Operator: models.FilterOperatorEquals}, {Key: "cluster", Value: "us-central-1", Operator: scope.FilterOperatorEquals},
{Key: "namespace", Value: "default", Operator: models.FilterOperatorEquals}, {Key: "namespace", Value: "default", Operator: scope.FilterOperatorEquals},
}, },
expected: `(count_over_time({cluster="us-central-1", namespace="default"} |= "error"[1m]) / count_over_time({cluster="us-central-1", namespace="default"}[1m]))`, expected: `(count_over_time({cluster="us-central-1", namespace="default"} |= "error"[1m]) / count_over_time({cluster="us-central-1", namespace="default"}[1m]))`,
expectErr: false, expectErr: false,
@ -80,9 +80,9 @@ func TestInjectScopesIntoLokiQuery(t *testing.T) {
{ {
name: "multi range metric query operation with existing label matchers", name: "multi range metric query operation with existing label matchers",
query: `count_over_time({a="bar"} |= "error" [1m])/count_over_time({a="bar"} [1m])`, query: `count_over_time({a="bar"} |= "error" [1m])/count_over_time({a="bar"} [1m])`,
scopeFilters: []models.ScopeFilter{ scopeFilters: []scope.ScopeFilter{
{Key: "cluster", Value: "us-central-1", Operator: models.FilterOperatorEquals}, {Key: "cluster", Value: "us-central-1", Operator: scope.FilterOperatorEquals},
{Key: "namespace", Value: "default", Operator: models.FilterOperatorEquals}, {Key: "namespace", Value: "default", Operator: scope.FilterOperatorEquals},
}, },
expected: `(count_over_time({a="bar", cluster="us-central-1", namespace="default"} |= "error"[1m]) / count_over_time({a="bar", cluster="us-central-1", namespace="default"}[1m]))`, expected: `(count_over_time({a="bar", cluster="us-central-1", namespace="default"} |= "error"[1m]) / count_over_time({a="bar", cluster="us-central-1", namespace="default"}[1m]))`,
expectErr: false, expectErr: false,

View File

@ -3,7 +3,8 @@ package loki
import ( import (
"time" "time"
"github.com/grafana/grafana/pkg/promlib/models" scope "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
"github.com/grafana/grafana/pkg/tsdb/loki/kinds/dataquery" "github.com/grafana/grafana/pkg/tsdb/loki/kinds/dataquery"
) )
@ -40,5 +41,5 @@ type lokiQuery struct {
End time.Time End time.Time
RefID string RefID string
SupportingQueryType SupportingQueryType SupportingQueryType SupportingQueryType
Scopes []models.ScopeFilter Scopes []scope.ScopeFilter
} }