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,
}
}
// +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
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *InlineSecureValue) DeepCopyInto(out *InlineSecureValue) {
*out = *in
@ -42,79 +38,3 @@ func (in *ObjectReference) DeepCopy() *ObjectReference {
in.DeepCopyInto(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{
"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.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(),
"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),
@ -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 {
return common.OpenAPIDefinition{
Schema: spec.Schema{

View File

@ -5,6 +5,7 @@ go 1.25.2
require (
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4
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/prometheus/client_golang v1.23.2
github.com/prometheus/common v0.66.1
@ -54,6 +55,7 @@ require (
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // 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/pyroscope-go/godeltaprof v0.1.8 // 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/procfs v0.16.1 // 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/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/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // 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/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/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/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls=
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.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.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
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.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/gtime"
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/trace"
@ -68,47 +69,15 @@ type PrometheusQueryProperties struct {
LegendFormat string `json:"legendFormat,omitempty"`
// 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.
AdhocFilters []ScopeFilter `json:"adhocFilters,omitempty"`
AdhocFilters []scope.ScopeFilter `json:"adhocFilters,omitempty"`
// Group By parameters to apply to aggregate expressions in the query
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
const (
varInterval = "$__interval"
@ -175,7 +144,7 @@ type Query struct {
ExemplarQuery bool
UtcOffsetSec int64
Scopes []ScopeSpec
Scopes []scope.ScopeSpec
}
// 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 {
var scopeFilters []ScopeFilter
var scopeFilters []scope.ScopeFilter
for _, scope := range model.Scopes {
scopeFilters = append(scopeFilters, scope.Filters...)
}

View File

@ -18,7 +18,6 @@
"description": "Additional Ad-hoc filters that take precedence over Scope on conflict.",
"type": "array",
"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",
"required": [
"key",
@ -36,7 +35,6 @@
"type": "string"
},
"values": {
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
"type": "array",
"items": {
"type": "string"
@ -184,10 +182,8 @@
"description": "A set of filters applied to apply to the query",
"type": "array",
"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",
"required": [
"name",
"title"
],
"properties": {
@ -200,7 +196,6 @@
"filters": {
"type": "array",
"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",
"required": [
"key",
@ -218,7 +213,6 @@
"type": "string"
},
"values": {
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
"type": "array",
"items": {
"type": "string"
@ -228,10 +222,6 @@
"additionalProperties": false
}
},
"name": {
"description": "This is the identifier from metadata.name of the scope model.",
"type": "string"
},
"title": {
"type": "string"
}

View File

@ -28,7 +28,6 @@
"description": "Additional Ad-hoc filters that take precedence over Scope on conflict.",
"type": "array",
"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",
"required": [
"key",
@ -46,7 +45,6 @@
"type": "string"
},
"values": {
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
"type": "array",
"items": {
"type": "string"
@ -194,10 +192,8 @@
"description": "A set of filters applied to apply to the query",
"type": "array",
"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",
"required": [
"name",
"title"
],
"properties": {
@ -210,7 +206,6 @@
"filters": {
"type": "array",
"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",
"required": [
"key",
@ -228,7 +223,6 @@
"type": "string"
},
"values": {
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
"type": "array",
"items": {
"type": "string"
@ -238,10 +232,6 @@
"additionalProperties": false
}
},
"name": {
"description": "This is the identifier from metadata.name of the scope model.",
"type": "string"
},
"title": {
"type": "string"
}

View File

@ -8,7 +8,7 @@
{
"metadata": {
"name": "default",
"resourceVersion": "1758739325095",
"resourceVersion": "1759831574256",
"creationTimestamp": "2024-03-25T13:19:04Z"
},
"spec": {
@ -21,7 +21,6 @@
"description": "Additional Ad-hoc filters that take precedence over Scope on conflict.",
"items": {
"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": {
"key": {
"type": "string"
@ -33,7 +32,6 @@
"type": "string"
},
"values": {
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
"items": {
"type": "string"
},
@ -103,7 +101,6 @@
"description": "A set of filters applied to apply to the query",
"items": {
"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": {
"defaultPath": {
"items": {
@ -114,7 +111,6 @@
"filters": {
"items": {
"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": {
"key": {
"type": "string"
@ -126,7 +122,6 @@
"type": "string"
},
"values": {
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
"items": {
"type": "string"
},
@ -142,16 +137,11 @@
},
"type": "array"
},
"name": {
"description": "This is the identifier from metadata.name of the scope model.",
"type": "string"
},
"title": {
"type": "string"
}
},
"required": [
"name",
"title"
],
"type": "object"

View File

@ -4,6 +4,7 @@ import (
"fmt"
"strings"
scope "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/labels"
"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.
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)
if err != nil {
return "", err
@ -76,7 +77,7 @@ func ApplyFiltersAndGroupBy(rawExpr string, scopeFilters, adHocFilters []ScopeFi
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)
// scope filters are applied first
@ -115,25 +116,25 @@ func FiltersToMatchers(scopeFilters, adhocFilters []ScopeFilter) ([]*labels.Matc
return matchers, nil
}
func filterToMatcher(f ScopeFilter) (*labels.Matcher, error) {
func filterToMatcher(f scope.ScopeFilter) (*labels.Matcher, error) {
var mt labels.MatchType
switch f.Operator {
case FilterOperatorEquals:
case scope.FilterOperatorEquals:
mt = labels.MatchEqual
case FilterOperatorNotEquals:
case scope.FilterOperatorNotEquals:
mt = labels.MatchNotEqual
case FilterOperatorRegexMatch:
case scope.FilterOperatorRegexMatch:
mt = labels.MatchRegexp
case FilterOperatorRegexNotMatch:
case scope.FilterOperatorRegexNotMatch:
mt = labels.MatchNotRegexp
case FilterOperatorOneOf:
case scope.FilterOperatorOneOf:
mt = labels.MatchRegexp
case FilterOperatorNotOneOf:
case scope.FilterOperatorNotOneOf:
mt = labels.MatchNotRegexp
default:
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 {
return labels.NewMatcher(mt, f.Key, strings.Join(f.Values, "|"))
}

View File

@ -3,6 +3,7 @@ package models
import (
"testing"
scope "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
"github.com/stretchr/testify/require"
)
@ -10,8 +11,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
tests := []struct {
name string
query string
adhocFilters []ScopeFilter
scopeFilters []ScopeFilter
adhocFilters []scope.ScopeFilter
scopeFilters []scope.ScopeFilter
expected string
expectErr bool
}{
@ -30,8 +31,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{
name: "Adhoc filter with existing filter",
query: `http_requests_total{job="prometheus"}`,
adhocFilters: []ScopeFilter{
{Key: "method", Value: "get", Operator: FilterOperatorEquals},
adhocFilters: []scope.ScopeFilter{
{Key: "method", Value: "get", Operator: scope.FilterOperatorEquals},
},
expected: `http_requests_total{job="prometheus",method="get"}`,
expectErr: false,
@ -39,9 +40,9 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{
name: "Adhoc filter with no existing filter",
query: `http_requests_total`,
adhocFilters: []ScopeFilter{
{Key: "method", Value: "get", Operator: FilterOperatorEquals},
{Key: "job", Value: "prometheus", Operator: FilterOperatorEquals},
adhocFilters: []scope.ScopeFilter{
{Key: "method", Value: "get", Operator: scope.FilterOperatorEquals},
{Key: "job", Value: "prometheus", Operator: scope.FilterOperatorEquals},
},
expected: `http_requests_total{job="prometheus",method="get"}`,
expectErr: false,
@ -49,8 +50,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{
name: "Scope filter",
query: `http_requests_total{job="prometheus"}`,
scopeFilters: []ScopeFilter{
{Key: "status", Value: "200", Operator: FilterOperatorEquals},
scopeFilters: []scope.ScopeFilter{
{Key: "status", Value: "200", Operator: scope.FilterOperatorEquals},
},
expected: `http_requests_total{job="prometheus",status="200"}`,
expectErr: false,
@ -58,11 +59,11 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{
name: "Adhoc and Scope filter no existing filter",
query: `http_requests_total`,
scopeFilters: []ScopeFilter{
{Key: "status", Value: "200", Operator: FilterOperatorEquals},
scopeFilters: []scope.ScopeFilter{
{Key: "status", Value: "200", Operator: scope.FilterOperatorEquals},
},
adhocFilters: []ScopeFilter{
{Key: "job", Value: "prometheus", Operator: FilterOperatorEquals},
adhocFilters: []scope.ScopeFilter{
{Key: "job", Value: "prometheus", Operator: scope.FilterOperatorEquals},
},
expected: `http_requests_total{job="prometheus",status="200"}`,
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)",
query: `http_requests_total{job="prometheus"}`,
scopeFilters: []ScopeFilter{
{Key: "status", Value: "404", Operator: FilterOperatorEquals},
scopeFilters: []scope.ScopeFilter{
{Key: "status", Value: "404", Operator: scope.FilterOperatorEquals},
},
adhocFilters: []ScopeFilter{
{Key: "status", Value: "200", Operator: FilterOperatorEquals},
adhocFilters: []scope.ScopeFilter{
{Key: "status", Value: "200", Operator: scope.FilterOperatorEquals},
},
expected: `http_requests_total{job="prometheus",status="200"}`,
expectErr: false,
@ -82,8 +83,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{
name: "Adhoc filters with more complex expression",
query: `capacity_bytes{job="prometheus"} + available_bytes{job="grafana"} / 1024`,
adhocFilters: []ScopeFilter{
{Key: "job", Value: "alloy", Operator: FilterOperatorEquals},
adhocFilters: []scope.ScopeFilter{
{Key: "job", Value: "alloy", Operator: scope.FilterOperatorEquals},
},
expected: `capacity_bytes{job="alloy"} + available_bytes{job="alloy"} / 1024`,
expectErr: false,
@ -91,8 +92,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{
name: "OneOf Operator is combined into a single regex filter",
query: `http_requests_total{job="prometheus"}`,
scopeFilters: []ScopeFilter{
{Key: "status", Values: []string{"404", "400"}, Operator: FilterOperatorOneOf},
scopeFilters: []scope.ScopeFilter{
{Key: "status", Values: []string{"404", "400"}, Operator: scope.FilterOperatorOneOf},
},
expected: `http_requests_total{job="prometheus",status=~"404|400"}`,
expectErr: false,
@ -100,8 +101,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{
name: "using __name__ as part of the query",
query: `{__name__="http_requests_total"}`,
scopeFilters: []ScopeFilter{
{Key: "namespace", Value: "istio", Operator: FilterOperatorEquals},
scopeFilters: []scope.ScopeFilter{
{Key: "namespace", Value: "istio", Operator: scope.FilterOperatorEquals},
},
expected: `{__name__="http_requests_total",namespace="istio"}`,
expectErr: false,
@ -109,9 +110,9 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{
name: "merge scopes filters into using OR if they share filter key",
query: `http_requests_total{}`,
scopeFilters: []ScopeFilter{
{Key: "namespace", Value: "default", Operator: FilterOperatorEquals},
{Key: "namespace", Value: "kube-system", Operator: FilterOperatorEquals},
scopeFilters: []scope.ScopeFilter{
{Key: "namespace", Value: "default", Operator: scope.FilterOperatorEquals},
{Key: "namespace", Value: "kube-system", Operator: scope.FilterOperatorEquals},
},
expected: `http_requests_total{namespace=~"default|kube-system"}`,
expectErr: false,
@ -119,12 +120,12 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
{
name: "adhoc filters win over scope filters if they share filter key",
query: `http_requests_total{}`,
scopeFilters: []ScopeFilter{
{Key: "namespace", Value: "default", Operator: FilterOperatorEquals},
{Key: "namespace", Value: "kube-system", Operator: FilterOperatorEquals},
scopeFilters: []scope.ScopeFilter{
{Key: "namespace", Value: "default", Operator: scope.FilterOperatorEquals},
{Key: "namespace", Value: "kube-system", Operator: scope.FilterOperatorEquals},
},
adhocFilters: []ScopeFilter{
{Key: "namespace", Value: "adhoc-wins", Operator: FilterOperatorEquals},
adhocFilters: []scope.ScopeFilter{
{Key: "namespace", Value: "adhoc-wins", Operator: scope.FilterOperatorEquals},
},
expected: `http_requests_total{namespace="adhoc-wins"}`,
expectErr: false,
@ -149,8 +150,8 @@ func TestApplyQueryFiltersAndGroupBy_Filters_utf8(t *testing.T) {
tests := []struct {
name string
query string
adhocFilters []ScopeFilter
scopeFilters []ScopeFilter
adhocFilters []scope.ScopeFilter
scopeFilters []scope.ScopeFilter
expected string
expectErr bool
}{
@ -276,8 +277,8 @@ func TestApplyQueryFiltersAndGroupBy(t *testing.T) {
tests := []struct {
name string
query string
adhocFilters []ScopeFilter
scopeFilters []ScopeFilter
adhocFilters []scope.ScopeFilter
scopeFilters []scope.ScopeFilter
groupby []string
expected string
expectErr bool
@ -286,11 +287,11 @@ func TestApplyQueryFiltersAndGroupBy(t *testing.T) {
{
name: "Adhoc filters with more complex expression",
query: `sum(capacity_bytes{job="prometheus"} + available_bytes{job="grafana"}) / 1024`,
adhocFilters: []ScopeFilter{
{Key: "job", Value: "alloy", Operator: FilterOperatorEquals},
adhocFilters: []scope.ScopeFilter{
{Key: "job", Value: "alloy", Operator: scope.FilterOperatorEquals},
},
scopeFilters: []ScopeFilter{
{Key: "vol", Value: "/", Operator: FilterOperatorEquals},
scopeFilters: []scope.ScopeFilter{
{Key: "vol", Value: "/", Operator: scope.FilterOperatorEquals},
},
groupby: []string{"job"},
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 {
name string
query string
adhocFilters []ScopeFilter
scopeFilters []ScopeFilter
adhocFilters []scope.ScopeFilter
scopeFilters []scope.ScopeFilter
groupby []string
expected string
expectErr bool
@ -325,11 +326,11 @@ func TestApplyQueryFiltersAndGroupBy_utf8(t *testing.T) {
{
name: "Adhoc filters with more complex expression and utf8 metric name",
query: `sum({"capacity_bytes", job="prometheus"} + {"available_bytes", job="grafana"}) / 1024`,
adhocFilters: []ScopeFilter{
{Key: "job", Value: "alloy", Operator: FilterOperatorEquals},
adhocFilters: []scope.ScopeFilter{
{Key: "job", Value: "alloy", Operator: scope.FilterOperatorEquals},
},
scopeFilters: []ScopeFilter{
{Key: "vol", Value: "/", Operator: FilterOperatorEquals},
scopeFilters: []scope.ScopeFilter{
{Key: "vol", Value: "/", Operator: scope.FilterOperatorEquals},
},
groupby: []string{"job"},
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",
query: `sum(capacity_bytes{job="prometheus", "utf8.label"="value"} + available_bytes{job="grafana"}) / 1024`,
adhocFilters: []ScopeFilter{
{Key: "job", Value: "alloy", Operator: FilterOperatorEquals},
adhocFilters: []scope.ScopeFilter{
{Key: "job", Value: "alloy", Operator: scope.FilterOperatorEquals},
},
scopeFilters: []ScopeFilter{
{Key: "vol", Value: "/", Operator: FilterOperatorEquals},
scopeFilters: []scope.ScopeFilter{
{Key: "vol", Value: "/", Operator: scope.FilterOperatorEquals},
},
groupby: []string{"job"},
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",
query: `sum({"capacity_bytes", job="prometheus", "utf8.label"="value"} + available_bytes{job="grafana"}) / 1024`,
adhocFilters: []ScopeFilter{
{Key: "job", Value: "alloy", Operator: FilterOperatorEquals},
adhocFilters: []scope.ScopeFilter{
{Key: "job", Value: "alloy", Operator: scope.FilterOperatorEquals},
},
scopeFilters: []ScopeFilter{
{Key: "vol", Value: "/", Operator: FilterOperatorEquals},
scopeFilters: []scope.ScopeFilter{
{Key: "vol", Value: "/", Operator: scope.FilterOperatorEquals},
},
groupby: []string{"job"},
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/log"
"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/grafana/grafana/pkg/promlib/client"
@ -116,8 +117,8 @@ type SuggestionRequest struct {
Queries []string `json:"queries"`
Scopes []models.ScopeFilter `json:"scopes"`
AdhocFilters []models.ScopeFilter `json:"adhocFilters"`
Scopes []scope.ScopeFilter `json:"scopes"`
AdhocFilters []scope.ScopeFilter `json:"adhocFilters"`
// Start and End are proxied directly to the prometheus endpoint (which is rfc3339 | unix_timestamp)
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/log"
scope "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/promlib/models"
"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
suggestionReq := resource.SuggestionRequest{
Queries: []string{}, // Empty queries
Scopes: []models.ScopeFilter{
{Key: "job", Operator: models.FilterOperatorEquals, Value: "testjob"},
Scopes: []scope.ScopeFilter{
{Key: "job", Operator: scope.FilterOperatorEquals, Value: "testjob"},
},
AdhocFilters: []models.ScopeFilter{
{Key: "instance", Operator: models.FilterOperatorEquals, Value: "localhost:9090"},
AdhocFilters: []scope.ScopeFilter{
{Key: "instance", Operator: scope.FilterOperatorEquals, Value: "localhost:9090"},
},
}

View File

@ -21,8 +21,8 @@ import (
"go.opentelemetry.io/otel/trace"
"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"
)
@ -73,9 +73,9 @@ type datasourceInfo struct {
type QueryJSONModel struct {
dataquery.LokiDataQuery
Direction *string `json:"direction,omitempty"`
SupportingQueryType *string `json:"supportingQueryType"`
Scopes []models.ScopeFilter `json:"scopes"`
Direction *string `json:"direction,omitempty"`
SupportingQueryType *string `json:"supportingQueryType"`
Scopes []scope.ScopeFilter `json:"scopes"`
}
type ResponseOpts struct {

View File

@ -8,6 +8,7 @@ import (
"time"
"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/tsdb/loki/kinds/dataquery"
"github.com/grafana/loki/v3/pkg/logql/syntax"
@ -21,8 +22,8 @@ type SuggestionRequest struct {
Query string `json:"query"`
Scopes []models.ScopeFilter `json:"scopes"`
AdhocFilters []models.ScopeFilter `json:"adhocFilters"`
Scopes []scope.ScopeFilter `json:"scopes"`
AdhocFilters []scope.ScopeFilter `json:"adhocFilters"`
// Start and End are proxied directly to the prometheus endpoint (which is rfc3339 | unix_timestamp)
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.
func ApplyScopes(rawExpr string, scopeFilters []models.ScopeFilter) (string, error) {
func ApplyScopes(rawExpr string, scopeFilters []scope.ScopeFilter) (string, error) {
if len(scopeFilters) == 0 {
return rawExpr, nil
}

View File

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

View File

@ -3,7 +3,8 @@ package loki
import (
"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"
)
@ -40,5 +41,5 @@ type lokiQuery struct {
End time.Time
RefID string
SupportingQueryType SupportingQueryType
Scopes []models.ScopeFilter
Scopes []scope.ScopeFilter
}