From ac5a38708695d7b9589219527a4529577a9f9c31 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 8 Feb 2024 09:27:03 -0800 Subject: [PATCH] Peakq: move templates into query service (#82193) --- hack/update-codegen.sh | 35 ++- pkg/apis/peakq/v0alpha1/types.go | 109 +------- .../peakq/v0alpha1/zz_generated.deepcopy.go | 126 +-------- .../peakq/v0alpha1/zz_generated.openapi.go | 250 +---------------- ...enerated.openapi_violation_exceptions.list | 2 - pkg/apis/query/v0alpha1/template/doc.go | 6 + .../query/v0alpha1/template}/format.go | 16 +- .../query/v0alpha1/template/format_test.go | 81 ++++++ pkg/apis/query/v0alpha1/template/render.go | 116 ++++++++ .../query/v0alpha1/template}/render_test.go | 65 +++-- pkg/apis/query/v0alpha1/template/types.go | 112 ++++++++ .../template/zz_generated.deepcopy.go | 131 +++++++++ .../template/zz_generated.defaults.go | 19 ++ .../query/v0alpha1/zz_generated.openapi.go | 257 +++++++++++++++++- ...enerated.openapi_violation_exceptions.list | 1 + pkg/registry/apis/peakq/format_test.go | 83 ------ pkg/registry/apis/peakq/render.go | 125 +-------- pkg/registry/apis/peakq/render_examples.go | 16 +- .../apis/peakq/render_examples_test.go | 6 +- 19 files changed, 812 insertions(+), 744 deletions(-) delete mode 100644 pkg/apis/peakq/v0alpha1/zz_generated.openapi_violation_exceptions.list create mode 100644 pkg/apis/query/v0alpha1/template/doc.go rename pkg/{registry/apis/peakq => apis/query/v0alpha1/template}/format.go (79%) create mode 100644 pkg/apis/query/v0alpha1/template/format_test.go create mode 100644 pkg/apis/query/v0alpha1/template/render.go rename pkg/{registry/apis/peakq => apis/query/v0alpha1/template}/render_test.go (67%) create mode 100644 pkg/apis/query/v0alpha1/template/types.go create mode 100644 pkg/apis/query/v0alpha1/template/zz_generated.deepcopy.go create mode 100644 pkg/apis/query/v0alpha1/template/zz_generated.defaults.go delete mode 100644 pkg/registry/apis/peakq/format_test.go diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index c63dfe37a55..37c8439ea94 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -18,11 +18,6 @@ OPENAPI_VIOLATION_EXCEPTIONS_FILENAME="zz_generated.openapi_violation_exceptions source "${CODEGEN_PKG}/kube_codegen.sh" source "$(dirname "${BASH_SOURCE[0]}")/openapi-codegen.sh" -kube::codegen::gen_helpers \ - --input-pkg-root github.com/grafana/grafana/pkg/apis \ - --output-base "${OUTDIR}" \ - --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" - for api_pkg in $(ls ./pkg/apis); do if [[ "${1-}" != "" && ${api_pkg} != $1 ]]; then @@ -30,6 +25,15 @@ for api_pkg in $(ls ./pkg/apis); do fi include_common_input_dirs=$([[ ${api_pkg} == "common" ]] && echo "true" || echo "false") for pkg_version in $(ls ./pkg/apis/${api_pkg}); do + echo "API: ${api_pkg}/${pkg_version}" + echo "-------------------------------------------" + + kube::codegen::gen_helpers \ + --input-pkg-root github.com/grafana/grafana/pkg/apis/${api_pkg}/${pkg_version} \ + --output-base "${OUTDIR}" \ + --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" + + echo "Generating openapi package for ${api_pkg}, version=${pkg_version} ..." grafana::codegen::gen_openapi \ @@ -39,16 +43,21 @@ for api_pkg in $(ls ./pkg/apis); do --update-report \ --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ --include-common-input-dirs ${include_common_input_dirs} - done - violations_file="${OUTDIR}/github.com/grafana/grafana/pkg/apis/${api_pkg}/${pkg_version}/${OPENAPI_VIOLATION_EXCEPTIONS_FILENAME}" - # delete violation exceptions file, if empty - if ! grep -q . "${violations_file}"; then - echo "Deleting ${violations_file} since it is empty" - rm ${violations_file} - fi + violations_file="${OUTDIR}/github.com/grafana/grafana/pkg/apis/${api_pkg}/${pkg_version}/${OPENAPI_VIOLATION_EXCEPTIONS_FILENAME}" + # delete violation exceptions file, if empty + if ! grep -q . "${violations_file}"; then + echo "Deleting ${violations_file} since it is empty" + rm ${violations_file} + fi + + echo "" + done done +echo "Generating client code..." +echo "---------------------------" + kube::codegen::gen_client \ --with-watch \ --with-applyconfig \ @@ -56,3 +65,5 @@ kube::codegen::gen_client \ --output-pkg-root github.com/grafana/grafana/pkg/generated \ --output-base "${OUTDIR}" \ --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" + +echo "done." diff --git a/pkg/apis/peakq/v0alpha1/types.go b/pkg/apis/peakq/v0alpha1/types.go index 3e6ab0c937c..0c67564df35 100644 --- a/pkg/apis/peakq/v0alpha1/types.go +++ b/pkg/apis/peakq/v0alpha1/types.go @@ -3,10 +3,7 @@ package v0alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/grafana/grafana-plugin-sdk-go/data" - - common "github.com/grafana/grafana/pkg/apis/common/v0alpha1" - query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" + "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -14,107 +11,7 @@ type QueryTemplate struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec QueryTemplateSpec `json:"spec,omitempty"` -} - -type QueryTemplateSpec struct { - Title string `json:"title"` - - // The variables that can be used to render - // +listType=map - // +listMapKey=key - Variables []TemplateVariable `json:"vars,omitempty"` - - // Output variables - // +listType=set - Targets []Target `json:"targets"` -} - -type Target struct { - // DataType is the returned Dataplane type from the query. - DataType data.FrameType `json:"dataType,omitempty"` - - // DataTypeVersion is the version for the Dataplane type. - // TODO 2[uint] seems to panic, maybe implement DeepCopy on data.FrameTypeVersion? - // DataTypeVersion data.FrameTypeVersion `json:"dataTypeVersion,omitempty"` - - // Variables that will be replaced in the query - Variables map[string][]VariableReplacement `json:"variables"` - - // Query target - Properties query.GenericDataQuery `json:"properties"` -} - -// TemplateVariable is the definition of a variable that will be interpolated -// in targets. -type TemplateVariable struct { - // Key is the name of the variable. - Key string `json:"key"` - - // DefaultValue is the value to be used when there is no selected value - // during render. - // +listType=atomic - DefaultValues []string `json:"defaultValue"` - - // ValueListDefinition is the object definition used by the FE - // to get a list of possible values to select for render. - ValueListDefinition common.Unstructured `json:"valueListDefinition"` -} - -// QueryVariable is the definition of a variable that will be interpolated -// in targets. -type VariableReplacement struct { - // Path is the location of the property within a target. - // The format for this is not figured out yet (Maybe JSONPath?). - // Idea: ["string", int, "string"] where int indicates array offset - Path string `json:"path"` - - // Positions is a list of where to perform the interpolation - // within targets during render. - // The first string is the Idx of the target as a string, since openAPI - // does not support ints as map keys - Position *Position `json:"position,omitempty"` - - // How values should be interpolated - Format VariableFormat `json:"format,omitempty"` -} - -// Define how to format values in the template. -// See: https://grafana.com/docs/grafana/latest/dashboards/variables/variable-syntax/#advanced-variable-format-options -// +enum -type VariableFormat string - -// Defines values for ItemType. -const ( - // Formats variables with multiple values as a comma-separated string. - FormatCSV VariableFormat = "csv" - - // Formats variables with multiple values as a comma-separated string. - FormatJSON VariableFormat = "json" - - // Formats single- and multi-valued variables into a comma-separated string - FormatDoubleQuote VariableFormat = "doublequote" - - // Formats single- and multi-valued variables into a comma-separated string - FormatSingleQuote VariableFormat = "singlequote" - - // Formats variables with multiple values into a pipe-separated string. - FormatPipe VariableFormat = "pipe" - - // Formats variables with multiple values into comma-separated string. - // This is the default behavior when no format is specified - FormatRaw VariableFormat = "raw" -) - -// Position is where to do replacement in the targets -// during render. -type Position struct { - // Start is the byte offset within TargetKey's property of the variable. - // It is the start location for replacements). - Start int64 `json:"start"` // TODO: byte, rune? - - // End is the byte offset of the end of the variable. - End int64 `json:"end"` + Spec template.QueryTemplate `json:"spec,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -131,5 +28,5 @@ type RenderedQuery struct { metav1.TypeMeta `json:",inline"` // +listType=atomic - Targets []Target `json:"targets,omitempty"` + Targets []template.Target `json:"targets,omitempty"` } diff --git a/pkg/apis/peakq/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/peakq/v0alpha1/zz_generated.deepcopy.go index 80d6da6e2e2..6f50c376eb6 100644 --- a/pkg/apis/peakq/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/peakq/v0alpha1/zz_generated.deepcopy.go @@ -8,25 +8,10 @@ package v0alpha1 import ( + template "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template" 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 *Position) DeepCopyInto(out *Position) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Position. -func (in *Position) DeepCopy() *Position { - if in == nil { - return nil - } - out := new(Position) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *QueryTemplate) DeepCopyInto(out *QueryTemplate) { *out = *in @@ -87,43 +72,13 @@ func (in *QueryTemplateList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *QueryTemplateSpec) DeepCopyInto(out *QueryTemplateSpec) { - *out = *in - if in.Variables != nil { - in, out := &in.Variables, &out.Variables - *out = make([]TemplateVariable, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Targets != nil { - in, out := &in.Targets, &out.Targets - *out = make([]Target, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QueryTemplateSpec. -func (in *QueryTemplateSpec) DeepCopy() *QueryTemplateSpec { - if in == nil { - return nil - } - out := new(QueryTemplateSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RenderedQuery) DeepCopyInto(out *RenderedQuery) { *out = *in out.TypeMeta = in.TypeMeta if in.Targets != nil { in, out := &in.Targets, &out.Targets - *out = make([]Target, len(*in)) + *out = make([]template.Target, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -148,80 +103,3 @@ func (in *RenderedQuery) DeepCopyObject() runtime.Object { } return nil } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Target) DeepCopyInto(out *Target) { - *out = *in - if in.Variables != nil { - in, out := &in.Variables, &out.Variables - *out = make(map[string][]VariableReplacement, len(*in)) - for key, val := range *in { - var outVal []VariableReplacement - if val == nil { - (*out)[key] = nil - } else { - in, out := &val, &outVal - *out = make([]VariableReplacement, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - (*out)[key] = outVal - } - } - in.Properties.DeepCopyInto(&out.Properties) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Target. -func (in *Target) DeepCopy() *Target { - if in == nil { - return nil - } - out := new(Target) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TemplateVariable) DeepCopyInto(out *TemplateVariable) { - *out = *in - if in.DefaultValues != nil { - in, out := &in.DefaultValues, &out.DefaultValues - *out = make([]string, len(*in)) - copy(*out, *in) - } - in.ValueListDefinition.DeepCopyInto(&out.ValueListDefinition) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TemplateVariable. -func (in *TemplateVariable) DeepCopy() *TemplateVariable { - if in == nil { - return nil - } - out := new(TemplateVariable) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VariableReplacement) DeepCopyInto(out *VariableReplacement) { - *out = *in - if in.Position != nil { - in, out := &in.Position, &out.Position - *out = new(Position) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VariableReplacement. -func (in *VariableReplacement) DeepCopy() *VariableReplacement { - if in == nil { - return nil - } - out := new(VariableReplacement) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/apis/peakq/v0alpha1/zz_generated.openapi.go b/pkg/apis/peakq/v0alpha1/zz_generated.openapi.go index b0edddee0f3..42bdb435e50 100644 --- a/pkg/apis/peakq/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/peakq/v0alpha1/zz_generated.openapi.go @@ -16,44 +16,9 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.Position": schema_pkg_apis_peakq_v0alpha1_Position(ref), - "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.QueryTemplate": schema_pkg_apis_peakq_v0alpha1_QueryTemplate(ref), - "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.QueryTemplateList": schema_pkg_apis_peakq_v0alpha1_QueryTemplateList(ref), - "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.QueryTemplateSpec": schema_pkg_apis_peakq_v0alpha1_QueryTemplateSpec(ref), - "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.RenderedQuery": schema_pkg_apis_peakq_v0alpha1_RenderedQuery(ref), - "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.Target": schema_pkg_apis_peakq_v0alpha1_Target(ref), - "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.TemplateVariable": schema_pkg_apis_peakq_v0alpha1_TemplateVariable(ref), - "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.VariableReplacement": schema_pkg_apis_peakq_v0alpha1_VariableReplacement(ref), - } -} - -func schema_pkg_apis_peakq_v0alpha1_Position(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "Position is where to do replacement in the targets during render.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "start": { - SchemaProps: spec.SchemaProps{ - Description: "Start is the byte offset within TargetKey's property of the variable. It is the start location for replacements).", - Default: 0, - Type: []string{"integer"}, - Format: "int64", - }, - }, - "end": { - SchemaProps: spec.SchemaProps{ - Description: "End is the byte offset of the end of the variable.", - Default: 0, - Type: []string{"integer"}, - Format: "int64", - }, - }, - }, - Required: []string{"start", "end"}, - }, - }, + "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.QueryTemplate": schema_pkg_apis_peakq_v0alpha1_QueryTemplate(ref), + "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.QueryTemplateList": schema_pkg_apis_peakq_v0alpha1_QueryTemplateList(ref), + "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.RenderedQuery": schema_pkg_apis_peakq_v0alpha1_RenderedQuery(ref), } } @@ -86,14 +51,14 @@ func schema_pkg_apis_peakq_v0alpha1_QueryTemplate(ref common.ReferenceCallback) "spec": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.QueryTemplateSpec"), + Ref: ref("github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.QueryTemplate"), }, }, }, }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.QueryTemplateSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.QueryTemplate", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -144,69 +109,6 @@ func schema_pkg_apis_peakq_v0alpha1_QueryTemplateList(ref common.ReferenceCallba } } -func schema_pkg_apis_peakq_v0alpha1_QueryTemplateSpec(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: "", - }, - }, - "vars": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-map-keys": []interface{}{ - "key", - }, - "x-kubernetes-list-type": "map", - }, - }, - SchemaProps: spec.SchemaProps{ - Description: "The variables that can be used to render", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.TemplateVariable"), - }, - }, - }, - }, - }, - "targets": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-type": "set", - }, - }, - SchemaProps: spec.SchemaProps{ - Description: "Output variables", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.Target"), - }, - }, - }, - }, - }, - }, - Required: []string{"title", "targets"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.Target", "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.TemplateVariable"}, - } -} - func schema_pkg_apis_peakq_v0alpha1_RenderedQuery(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -240,7 +142,7 @@ func schema_pkg_apis_peakq_v0alpha1_RenderedQuery(ref common.ReferenceCallback) Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.Target"), + Ref: ref("github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.Target"), }, }, }, @@ -250,144 +152,6 @@ func schema_pkg_apis_peakq_v0alpha1_RenderedQuery(ref common.ReferenceCallback) }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.Target"}, - } -} - -func schema_pkg_apis_peakq_v0alpha1_Target(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "dataType": { - SchemaProps: spec.SchemaProps{ - Description: "DataType is the returned Dataplane type from the query.", - Type: []string{"string"}, - Format: "", - }, - }, - "variables": { - SchemaProps: spec.SchemaProps{ - Description: "Variables that will be replaced in the query", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - 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/apis/peakq/v0alpha1.VariableReplacement"), - }, - }, - }, - }, - }, - }, - }, - }, - "properties": { - SchemaProps: spec.SchemaProps{ - Description: "Query target", - Ref: ref("github.com/grafana/grafana/pkg/apis/query/v0alpha1.GenericDataQuery"), - }, - }, - }, - Required: []string{"variables", "properties"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.VariableReplacement", "github.com/grafana/grafana/pkg/apis/query/v0alpha1.GenericDataQuery"}, - } -} - -func schema_pkg_apis_peakq_v0alpha1_TemplateVariable(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "TemplateVariable is the definition of a variable that will be interpolated in targets.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "key": { - SchemaProps: spec.SchemaProps{ - Description: "Key is the name of the variable.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "defaultValue": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-type": "atomic", - }, - }, - SchemaProps: spec.SchemaProps{ - Description: "DefaultValue is the value to be used when there is no selected value during render.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - "valueListDefinition": { - SchemaProps: spec.SchemaProps{ - Description: "ValueListDefinition is the object definition used by the FE to get a list of possible values to select for render.", - Ref: ref("github.com/grafana/grafana/pkg/apis/common/v0alpha1.Unstructured"), - }, - }, - }, - Required: []string{"key", "defaultValue", "valueListDefinition"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/common/v0alpha1.Unstructured"}, - } -} - -func schema_pkg_apis_peakq_v0alpha1_VariableReplacement(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "QueryVariable is the definition of a variable that will be interpolated in targets.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "path": { - SchemaProps: spec.SchemaProps{ - Description: "Path is the location of the property within a target. The format for this is not figured out yet (Maybe JSONPath?). Idea: [\"string\", int, \"string\"] where int indicates array offset", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "position": { - SchemaProps: spec.SchemaProps{ - Description: "Positions is a list of where to perform the interpolation within targets during render. The first string is the Idx of the target as a string, since openAPI does not support ints as map keys", - Ref: ref("github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.Position"), - }, - }, - "format": { - SchemaProps: spec.SchemaProps{ - Description: "How values should be interpolated\n\nPossible enum values:\n - `\"csv\"` Formats variables with multiple values as a comma-separated string.\n - `\"doublequote\"` Formats single- and multi-valued variables into a comma-separated string\n - `\"json\"` Formats variables with multiple values as a comma-separated string.\n - `\"pipe\"` Formats variables with multiple values into a pipe-separated string.\n - `\"raw\"` Formats variables with multiple values into comma-separated string. This is the default behavior when no format is specified\n - `\"singlequote\"` Formats single- and multi-valued variables into a comma-separated string", - Type: []string{"string"}, - Format: "", - Enum: []interface{}{"csv", "doublequote", "json", "pipe", "raw", "singlequote"}, - }, - }, - }, - Required: []string{"path"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1.Position"}, + "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.Target"}, } } diff --git a/pkg/apis/peakq/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/peakq/v0alpha1/zz_generated.openapi_violation_exceptions.list deleted file mode 100644 index 91e16558329..00000000000 --- a/pkg/apis/peakq/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ /dev/null @@ -1,2 +0,0 @@ -API rule violation: names_match,github.com/grafana/grafana/pkg/apis/peakq/v0alpha1,QueryTemplateSpec,Variables -API rule violation: names_match,github.com/grafana/grafana/pkg/apis/peakq/v0alpha1,TemplateVariable,DefaultValues diff --git a/pkg/apis/query/v0alpha1/template/doc.go b/pkg/apis/query/v0alpha1/template/doc.go new file mode 100644 index 00000000000..3585a3a5806 --- /dev/null +++ b/pkg/apis/query/v0alpha1/template/doc.go @@ -0,0 +1,6 @@ +// +k8s:deepcopy-gen=package +// +k8s:openapi-gen=true +// +k8s:defaulter-gen=TypeMeta +// +groupName=query.grafana.app + +package template // import "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template" diff --git a/pkg/registry/apis/peakq/format.go b/pkg/apis/query/v0alpha1/template/format.go similarity index 79% rename from pkg/registry/apis/peakq/format.go rename to pkg/apis/query/v0alpha1/template/format.go index f88968d7f48..353a49ef2d3 100644 --- a/pkg/registry/apis/peakq/format.go +++ b/pkg/apis/query/v0alpha1/template/format.go @@ -1,15 +1,13 @@ -package peakq +package template import ( "bytes" "encoding/csv" "encoding/json" "strings" - - peakq "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1" ) -func formatVariables(fmt peakq.VariableFormat, input []string) string { +func FormatVariables(fmt VariableFormat, input []string) string { if len(input) < 1 { return "" } @@ -17,11 +15,11 @@ func formatVariables(fmt peakq.VariableFormat, input []string) string { // MultiValued formats // nolint: exhaustive switch fmt { - case peakq.FormatJSON: + case FormatJSON: v, _ := json.Marshal(input) return string(v) - case peakq.FormatDoubleQuote: + case FormatDoubleQuote: sb := bytes.NewBufferString("") for idx, val := range input { if idx > 0 { @@ -33,7 +31,7 @@ func formatVariables(fmt peakq.VariableFormat, input []string) string { } return sb.String() - case peakq.FormatSingleQuote: + case FormatSingleQuote: sb := bytes.NewBufferString("") for idx, val := range input { if idx > 0 { @@ -45,7 +43,7 @@ func formatVariables(fmt peakq.VariableFormat, input []string) string { } return sb.String() - case peakq.FormatCSV: + case FormatCSV: sb := bytes.NewBufferString("") w := csv.NewWriter(sb) _ = w.Write(input) @@ -61,7 +59,7 @@ func formatVariables(fmt peakq.VariableFormat, input []string) string { // nolint: exhaustive switch fmt { - case peakq.FormatPipe: + case FormatPipe: return strings.Join(input, "|") } diff --git a/pkg/apis/query/v0alpha1/template/format_test.go b/pkg/apis/query/v0alpha1/template/format_test.go new file mode 100644 index 00000000000..ace0a499efe --- /dev/null +++ b/pkg/apis/query/v0alpha1/template/format_test.go @@ -0,0 +1,81 @@ +package template + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFormat(t *testing.T) { + // Invalid input + require.Equal(t, "", FormatVariables(FormatCSV, nil)) + require.Equal(t, "", FormatVariables(FormatCSV, []string{})) + + type check struct { + name string + input []string + output map[VariableFormat]string + } + + tests := []check{ + { + name: "three simple variables", + input: []string{"a", "b", "c"}, + output: map[VariableFormat]string{ + FormatCSV: "a,b,c", + FormatJSON: `["a","b","c"]`, + FormatDoubleQuote: `"a","b","c"`, + FormatSingleQuote: `'a','b','c'`, + FormatPipe: `a|b|c`, + FormatRaw: "a,b,c", + }, + }, + { + name: "single value", + input: []string{"a"}, + output: map[VariableFormat]string{ + FormatCSV: "a", + FormatJSON: `["a"]`, + FormatDoubleQuote: `"a"`, + FormatSingleQuote: `'a'`, + FormatPipe: "a", + FormatRaw: "a", + }, + }, + { + name: "value with quote", + input: []string{`hello "world"`}, + output: map[VariableFormat]string{ + FormatCSV: `"hello ""world"""`, // note the double quotes + FormatJSON: `["hello \"world\""]`, + FormatDoubleQuote: `"hello \"world\""`, + FormatSingleQuote: `'hello "world"'`, + FormatPipe: `hello "world"`, + FormatRaw: `hello "world"`, + }, + }, + } + for _, test := range tests { + // Make sure all keys are set in tests + all := map[VariableFormat]bool{ + FormatRaw: true, + FormatCSV: true, + FormatJSON: true, + FormatDoubleQuote: true, + FormatSingleQuote: true, + FormatPipe: true, + } + + // Check the default (no format) matches CSV + require.Equal(t, test.output[FormatRaw], + FormatVariables("", test.input), + "test %s default values are not raw", test.name) + + // Check each input value + for format, v := range test.output { + require.Equal(t, v, FormatVariables(format, test.input), "Test: %s (format:%s)", test.name, format) + delete(all, format) + } + require.Empty(t, all, "test %s is missing cases for: %v", test.name, all) + } +} diff --git a/pkg/apis/query/v0alpha1/template/render.go b/pkg/apis/query/v0alpha1/template/render.go new file mode 100644 index 00000000000..0f0b6f39428 --- /dev/null +++ b/pkg/apis/query/v0alpha1/template/render.go @@ -0,0 +1,116 @@ +package template + +import ( + "fmt" + "sort" + + "github.com/spyzhov/ajson" + + query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" +) + +// RenderTemplate applies selected values into a query template +func RenderTemplate(qt QueryTemplate, selectedValues map[string][]string) ([]Target, error) { + targets := qt.DeepCopy().Targets + + rawTargetObjects := make([]*ajson.Node, len(qt.Targets)) + for i, t := range qt.Targets { + b, err := t.Properties.MarshalJSON() + if err != nil { + return nil, err + } + rawTargetObjects[i], err = ajson.Unmarshal(b) + if err != nil { + return nil, err + } + } + + rm := getReplacementMap(qt) + for targetIdx, byTargetIdx := range rm { + for path, reps := range byTargetIdx { + o := rawTargetObjects[targetIdx] + nodes, err := o.JSONPath(path) + if err != nil { + return nil, fmt.Errorf("failed to find path %v: %w", path, err) + } + if len(nodes) != 1 { + return nil, fmt.Errorf("expected one lead node at path %v but got %v", path, len(nodes)) + } + n := nodes[0] + if !n.IsString() { + return nil, fmt.Errorf("only string type leaf notes supported currently, %v is not a string", path) + } + s := []rune(n.String()) + s = s[1 : len(s)-1] + var offSet int64 + for _, r := range reps { + value := []rune(FormatVariables(r.format, selectedValues[r.Key])) + if r.Position == nil { + return nil, fmt.Errorf("nil position not support yet, will be full replacement") + } + s = append(s[:r.Start+offSet], append(value, s[r.End+offSet:]...)...) + offSet += int64(len(value)) - (r.End - r.Start) + } + if err = n.SetString(string(s)); err != nil { + return nil, err + } + } + } + + for i, aT := range rawTargetObjects { + raw, err := ajson.Marshal(aT) + if err != nil { + return nil, err + } + u := query.GenericDataQuery{} + err = u.UnmarshalJSON(raw) + if err != nil { + return nil, err + } + targets[i].Properties = u + } + + return targets, nil +} + +type replacement struct { + *Position + *TemplateVariable + format VariableFormat +} + +func getReplacementMap(qt QueryTemplate) map[int]map[string][]replacement { + byTargetPath := make(map[int]map[string][]replacement) + + varMap := make(map[string]*TemplateVariable, len(qt.Variables)) + for i, v := range qt.Variables { + varMap[v.Key] = &qt.Variables[i] + } + + for i, target := range qt.Targets { + if byTargetPath[i] == nil { + byTargetPath[i] = make(map[string][]replacement) + } + for k, vReps := range target.Variables { + for rI, rep := range vReps { + byTargetPath[i][rep.Path] = append(byTargetPath[i][rep.Path], + replacement{ + Position: vReps[rI].Position, + TemplateVariable: varMap[k], + format: rep.Format, + }, + ) + } + } + } + + for idx, byTargetIdx := range byTargetPath { + for path := range byTargetIdx { + sort.Slice(byTargetPath[idx][path], func(i, j int) bool { + return byTargetPath[idx][path][i].Start < byTargetPath[idx][path][j].Start + }) + } + } + + return byTargetPath +} diff --git a/pkg/registry/apis/peakq/render_test.go b/pkg/apis/query/v0alpha1/template/render_test.go similarity index 67% rename from pkg/registry/apis/peakq/render_test.go rename to pkg/apis/query/v0alpha1/template/render_test.go index 30b7a1403a2..ecec4596c8a 100644 --- a/pkg/registry/apis/peakq/render_test.go +++ b/pkg/apis/query/v0alpha1/template/render_test.go @@ -1,4 +1,4 @@ -package peakq +package template import ( "testing" @@ -6,27 +6,26 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/stretchr/testify/require" - peakq "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1" query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" ) -var nestedFieldRender = peakq.QueryTemplateSpec{ +var nestedFieldRender = QueryTemplate{ Title: "Test", - Variables: []peakq.TemplateVariable{ + Variables: []TemplateVariable{ { Key: "metricName", }, }, - Targets: []peakq.Target{ + Targets: []Target{ { DataType: data.FrameTypeUnknown, //DataTypeVersion: data.FrameTypeVersion{0, 0}, - Variables: map[string][]peakq.VariableReplacement{ + Variables: map[string][]VariableReplacement{ "metricName": { { Path: "$.nestedObject.anArray[0]", - Position: &peakq.Position{ + Position: &Position{ Start: 0, End: 3, }, @@ -42,14 +41,14 @@ var nestedFieldRender = peakq.QueryTemplateSpec{ }, } -var nestedFieldRenderedTargets = []peakq.Target{ +var nestedFieldRenderedTargets = []Target{ { DataType: data.FrameTypeUnknown, - Variables: map[string][]peakq.VariableReplacement{ + Variables: map[string][]VariableReplacement{ "metricName": { { Path: "$.nestedObject.anArray[0]", - Position: &peakq.Position{ + Position: &Position{ Start: 0, End: 3, }, @@ -67,17 +66,17 @@ var nestedFieldRenderedTargets = []peakq.Target{ } func TestNestedFieldRender(t *testing.T) { - rT, err := Render(nestedFieldRender, map[string][]string{"metricName": {"up"}}) + rT, err := RenderTemplate(nestedFieldRender, map[string][]string{"metricName": {"up"}}) require.NoError(t, err) require.Equal(t, nestedFieldRenderedTargets, - rT.Targets, + rT, ) } -var multiVarTemplate = peakq.QueryTemplateSpec{ +var multiVarTemplate = QueryTemplate{ Title: "Test", - Variables: []peakq.TemplateVariable{ + Variables: []TemplateVariable{ { Key: "metricName", }, @@ -85,23 +84,23 @@ var multiVarTemplate = peakq.QueryTemplateSpec{ Key: "anotherMetric", }, }, - Targets: []peakq.Target{ + Targets: []Target{ { DataType: data.FrameTypeUnknown, //DataTypeVersion: data.FrameTypeVersion{0, 0}, - Variables: map[string][]peakq.VariableReplacement{ + Variables: map[string][]VariableReplacement{ "metricName": { { Path: "$.expr", - Position: &peakq.Position{ + Position: &Position{ Start: 4, End: 14, }, }, { Path: "$.expr", - Position: &peakq.Position{ + Position: &Position{ Start: 37, End: 47, }, @@ -110,7 +109,7 @@ var multiVarTemplate = peakq.QueryTemplateSpec{ "anotherMetric": { { Path: "$.expr", - Position: &peakq.Position{ + Position: &Position{ Start: 21, End: 34, }, @@ -125,21 +124,21 @@ var multiVarTemplate = peakq.QueryTemplateSpec{ }, } -var multiVarRenderedTargets = []peakq.Target{ +var multiVarRenderedTargets = []Target{ { DataType: data.FrameTypeUnknown, - Variables: map[string][]peakq.VariableReplacement{ + Variables: map[string][]VariableReplacement{ "metricName": { { Path: "$.expr", - Position: &peakq.Position{ + Position: &Position{ Start: 4, End: 14, }, }, { Path: "$.expr", - Position: &peakq.Position{ + Position: &Position{ Start: 37, End: 47, }, @@ -148,7 +147,7 @@ var multiVarRenderedTargets = []peakq.Target{ "anotherMetric": { { Path: "$.expr", - Position: &peakq.Position{ + Position: &Position{ Start: 21, End: 34, }, @@ -163,34 +162,34 @@ var multiVarRenderedTargets = []peakq.Target{ } func TestMultiVarTemplate(t *testing.T) { - rT, err := Render(multiVarTemplate, map[string][]string{ + rT, err := RenderTemplate(multiVarTemplate, map[string][]string{ "metricName": {"up"}, "anotherMetric": {"sloths_do_like_a_good_nap"}, }) require.NoError(t, err) require.Equal(t, multiVarRenderedTargets, - rT.Targets, + rT, ) } func TestRenderWithRune(t *testing.T) { - qt := peakq.QueryTemplateSpec{ - Variables: []peakq.TemplateVariable{ + qt := QueryTemplate{ + Variables: []TemplateVariable{ { Key: "name", }, }, - Targets: []peakq.Target{ + Targets: []Target{ { Properties: query.NewGenericDataQuery(map[string]any{ "message": "🐦 name!", }), - Variables: map[string][]peakq.VariableReplacement{ + Variables: map[string][]VariableReplacement{ "name": { { Path: "$.message", - Position: &peakq.Position{ + Position: &Position{ Start: 2, End: 6, }, @@ -205,8 +204,8 @@ func TestRenderWithRune(t *testing.T) { "name": {"🦥"}, } - rq, err := Render(qt, selectedValues) + rq, err := RenderTemplate(qt, selectedValues) require.NoError(t, err) - require.Equal(t, "🐦 🦥!", rq.Targets[0].Properties.AdditionalProperties()["message"]) + require.Equal(t, "🐦 🦥!", rq[0].Properties.AdditionalProperties()["message"]) } diff --git a/pkg/apis/query/v0alpha1/template/types.go b/pkg/apis/query/v0alpha1/template/types.go new file mode 100644 index 00000000000..56fc48e2130 --- /dev/null +++ b/pkg/apis/query/v0alpha1/template/types.go @@ -0,0 +1,112 @@ +package template + +import ( + "github.com/grafana/grafana-plugin-sdk-go/data" + + common "github.com/grafana/grafana/pkg/apis/common/v0alpha1" + query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" +) + +type QueryTemplate struct { + // A display name + Title string `json:"title,omitempty"` + + // Longer description for why it is interesting + Description string `json:"description,omitempty"` + + // The variables that can be used to render + // +listType=map + // +listMapKey=key + Variables []TemplateVariable `json:"vars,omitempty"` + + // Output variables + // +listType=set + Targets []Target `json:"targets"` +} + +type Target struct { + // DataType is the returned Dataplane type from the query. + DataType data.FrameType `json:"dataType,omitempty"` + + // DataTypeVersion is the version for the Dataplane type. + // TODO 2[uint] seems to panic, maybe implement DeepCopy on data.FrameTypeVersion? + // DataTypeVersion data.FrameTypeVersion `json:"dataTypeVersion,omitempty"` + + // Variables that will be replaced in the query + Variables map[string][]VariableReplacement `json:"variables"` + + // Query target + Properties query.GenericDataQuery `json:"properties"` +} + +// TemplateVariable is the definition of a variable that will be interpolated +// in targets. +type TemplateVariable struct { + // Key is the name of the variable. + Key string `json:"key"` + + // DefaultValue is the value to be used when there is no selected value + // during render. + // +listType=atomic + DefaultValues []string `json:"defaultValues,omitempty"` + + // ValueListDefinition is the object definition used by the FE + // to get a list of possible values to select for render. + ValueListDefinition common.Unstructured `json:"valueListDefinition,omitempty"` +} + +// QueryVariable is the definition of a variable that will be interpolated +// in targets. +type VariableReplacement struct { + // Path is the location of the property within a target. + // The format for this is not figured out yet (Maybe JSONPath?). + // Idea: ["string", int, "string"] where int indicates array offset + Path string `json:"path"` + + // Positions is a list of where to perform the interpolation + // within targets during render. + // The first string is the Idx of the target as a string, since openAPI + // does not support ints as map keys + Position *Position `json:"position,omitempty"` + + // How values should be interpolated + Format VariableFormat `json:"format,omitempty"` +} + +// Define how to format values in the template. +// See: https://grafana.com/docs/grafana/latest/dashboards/variables/variable-syntax/#advanced-variable-format-options +// +enum +type VariableFormat string + +// Defines values for ItemType. +const ( + // Formats variables with multiple values as a comma-separated string. + FormatCSV VariableFormat = "csv" + + // Formats variables with multiple values as a comma-separated string. + FormatJSON VariableFormat = "json" + + // Formats single- and multi-valued variables into a comma-separated string + FormatDoubleQuote VariableFormat = "doublequote" + + // Formats single- and multi-valued variables into a comma-separated string + FormatSingleQuote VariableFormat = "singlequote" + + // Formats variables with multiple values into a pipe-separated string. + FormatPipe VariableFormat = "pipe" + + // Formats variables with multiple values into comma-separated string. + // This is the default behavior when no format is specified + FormatRaw VariableFormat = "raw" +) + +// Position is where to do replacement in the targets +// during render. +type Position struct { + // Start is the byte offset within TargetKey's property of the variable. + // It is the start location for replacements). + Start int64 `json:"start"` // TODO: byte, rune? + + // End is the byte offset of the end of the variable. + End int64 `json:"end"` +} diff --git a/pkg/apis/query/v0alpha1/template/zz_generated.deepcopy.go b/pkg/apis/query/v0alpha1/template/zz_generated.deepcopy.go new file mode 100644 index 00000000000..e8337fcdc34 --- /dev/null +++ b/pkg/apis/query/v0alpha1/template/zz_generated.deepcopy.go @@ -0,0 +1,131 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package template + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Position) DeepCopyInto(out *Position) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Position. +func (in *Position) DeepCopy() *Position { + if in == nil { + return nil + } + out := new(Position) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *QueryTemplate) DeepCopyInto(out *QueryTemplate) { + *out = *in + if in.Variables != nil { + in, out := &in.Variables, &out.Variables + *out = make([]TemplateVariable, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Targets != nil { + in, out := &in.Targets, &out.Targets + *out = make([]Target, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QueryTemplate. +func (in *QueryTemplate) DeepCopy() *QueryTemplate { + if in == nil { + return nil + } + out := new(QueryTemplate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Target) DeepCopyInto(out *Target) { + *out = *in + if in.Variables != nil { + in, out := &in.Variables, &out.Variables + *out = make(map[string][]VariableReplacement, len(*in)) + for key, val := range *in { + var outVal []VariableReplacement + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make([]VariableReplacement, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + (*out)[key] = outVal + } + } + in.Properties.DeepCopyInto(&out.Properties) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Target. +func (in *Target) DeepCopy() *Target { + if in == nil { + return nil + } + out := new(Target) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TemplateVariable) DeepCopyInto(out *TemplateVariable) { + *out = *in + if in.DefaultValues != nil { + in, out := &in.DefaultValues, &out.DefaultValues + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.ValueListDefinition.DeepCopyInto(&out.ValueListDefinition) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TemplateVariable. +func (in *TemplateVariable) DeepCopy() *TemplateVariable { + if in == nil { + return nil + } + out := new(TemplateVariable) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VariableReplacement) DeepCopyInto(out *VariableReplacement) { + *out = *in + if in.Position != nil { + in, out := &in.Position, &out.Position + *out = new(Position) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VariableReplacement. +func (in *VariableReplacement) DeepCopy() *VariableReplacement { + if in == nil { + return nil + } + out := new(VariableReplacement) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/query/v0alpha1/template/zz_generated.defaults.go b/pkg/apis/query/v0alpha1/template/zz_generated.defaults.go new file mode 100644 index 00000000000..f50cedd6396 --- /dev/null +++ b/pkg/apis/query/v0alpha1/template/zz_generated.defaults.go @@ -0,0 +1,19 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by defaulter-gen. DO NOT EDIT. + +package template + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + return nil +} diff --git a/pkg/apis/query/v0alpha1/zz_generated.openapi.go b/pkg/apis/query/v0alpha1/zz_generated.openapi.go index c3a66de455c..a0339d88049 100644 --- a/pkg/apis/query/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/query/v0alpha1/zz_generated.openapi.go @@ -16,13 +16,18 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/grafana/grafana/pkg/apis/query/v0alpha1.DataSourceApiServer": schema_pkg_apis_query_v0alpha1_DataSourceApiServer(ref), - "github.com/grafana/grafana/pkg/apis/query/v0alpha1.DataSourceApiServerList": schema_pkg_apis_query_v0alpha1_DataSourceApiServerList(ref), - "github.com/grafana/grafana/pkg/apis/query/v0alpha1.DataSourceRef": schema_pkg_apis_query_v0alpha1_DataSourceRef(ref), - "github.com/grafana/grafana/pkg/apis/query/v0alpha1.GenericDataQuery": schema_pkg_apis_query_v0alpha1_GenericDataQuery(ref), - "github.com/grafana/grafana/pkg/apis/query/v0alpha1.GenericQueryRequest": schema_pkg_apis_query_v0alpha1_GenericQueryRequest(ref), - "github.com/grafana/grafana/pkg/apis/query/v0alpha1.QueryDataResponse": QueryDataResponse{}.OpenAPIDefinition(), - "github.com/grafana/grafana/pkg/apis/query/v0alpha1.TimeRange": schema_pkg_apis_query_v0alpha1_TimeRange(ref), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1.DataSourceApiServer": schema_pkg_apis_query_v0alpha1_DataSourceApiServer(ref), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1.DataSourceApiServerList": schema_pkg_apis_query_v0alpha1_DataSourceApiServerList(ref), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1.DataSourceRef": schema_pkg_apis_query_v0alpha1_DataSourceRef(ref), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1.GenericDataQuery": schema_pkg_apis_query_v0alpha1_GenericDataQuery(ref), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1.GenericQueryRequest": schema_pkg_apis_query_v0alpha1_GenericQueryRequest(ref), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1.QueryDataResponse": QueryDataResponse{}.OpenAPIDefinition(), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1.TimeRange": schema_pkg_apis_query_v0alpha1_TimeRange(ref), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.Position": schema_apis_query_v0alpha1_template_Position(ref), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.QueryTemplate": schema_apis_query_v0alpha1_template_QueryTemplate(ref), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.Target": schema_apis_query_v0alpha1_template_Target(ref), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.TemplateVariable": schema_apis_query_v0alpha1_template_TemplateVariable(ref), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.VariableReplacement": schema_apis_query_v0alpha1_template_VariableReplacement(ref), } } @@ -341,3 +346,241 @@ func schema_pkg_apis_query_v0alpha1_TimeRange(ref common.ReferenceCallback) comm }, } } + +func schema_apis_query_v0alpha1_template_Position(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Position is where to do replacement in the targets during render.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "start": { + SchemaProps: spec.SchemaProps{ + Description: "Start is the byte offset within TargetKey's property of the variable. It is the start location for replacements).", + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "end": { + SchemaProps: spec.SchemaProps{ + Description: "End is the byte offset of the end of the variable.", + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + Required: []string{"start", "end"}, + }, + }, + } +} + +func schema_apis_query_v0alpha1_template_QueryTemplate(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{ + Description: "A display name", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "Longer description for why it is interesting", + Type: []string{"string"}, + Format: "", + }, + }, + "vars": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "key", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "The variables that can be used to render", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.TemplateVariable"), + }, + }, + }, + }, + }, + "targets": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Output variables", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.Target"), + }, + }, + }, + }, + }, + }, + Required: []string{"targets"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.Target", "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.TemplateVariable"}, + } +} + +func schema_apis_query_v0alpha1_template_Target(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "dataType": { + SchemaProps: spec.SchemaProps{ + Description: "DataType is the returned Dataplane type from the query.", + Type: []string{"string"}, + Format: "", + }, + }, + "variables": { + SchemaProps: spec.SchemaProps{ + Description: "Variables that will be replaced in the query", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + 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/apis/query/v0alpha1/template.VariableReplacement"), + }, + }, + }, + }, + }, + }, + }, + }, + "properties": { + SchemaProps: spec.SchemaProps{ + Description: "Query target", + Ref: ref("github.com/grafana/grafana/pkg/apis/query/v0alpha1.GenericDataQuery"), + }, + }, + }, + Required: []string{"variables", "properties"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/query/v0alpha1.GenericDataQuery", "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.VariableReplacement"}, + } +} + +func schema_apis_query_v0alpha1_template_TemplateVariable(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TemplateVariable is the definition of a variable that will be interpolated in targets.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "Key is the name of the variable.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "defaultValues": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "DefaultValue is the value to be used when there is no selected value during render.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "valueListDefinition": { + SchemaProps: spec.SchemaProps{ + Description: "ValueListDefinition is the object definition used by the FE to get a list of possible values to select for render.", + Ref: ref("github.com/grafana/grafana/pkg/apis/common/v0alpha1.Unstructured"), + }, + }, + }, + Required: []string{"key"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/common/v0alpha1.Unstructured"}, + } +} + +func schema_apis_query_v0alpha1_template_VariableReplacement(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "QueryVariable is the definition of a variable that will be interpolated in targets.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path is the location of the property within a target. The format for this is not figured out yet (Maybe JSONPath?). Idea: [\"string\", int, \"string\"] where int indicates array offset", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "position": { + SchemaProps: spec.SchemaProps{ + Description: "Positions is a list of where to perform the interpolation within targets during render. The first string is the Idx of the target as a string, since openAPI does not support ints as map keys", + Ref: ref("github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.Position"), + }, + }, + "format": { + SchemaProps: spec.SchemaProps{ + Description: "How values should be interpolated\n\nPossible enum values:\n - `\"csv\"` Formats variables with multiple values as a comma-separated string.\n - `\"doublequote\"` Formats single- and multi-valued variables into a comma-separated string\n - `\"json\"` Formats variables with multiple values as a comma-separated string.\n - `\"pipe\"` Formats variables with multiple values into a pipe-separated string.\n - `\"raw\"` Formats variables with multiple values into comma-separated string. This is the default behavior when no format is specified\n - `\"singlequote\"` Formats single- and multi-valued variables into a comma-separated string", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"csv", "doublequote", "json", "pipe", "raw", "singlequote"}, + }, + }, + }, + Required: []string{"path"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template.Position"}, + } +} diff --git a/pkg/apis/query/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/query/v0alpha1/zz_generated.openapi_violation_exceptions.list index b6d2a25a924..00e608f512e 100644 --- a/pkg/apis/query/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/pkg/apis/query/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -3,3 +3,4 @@ API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/query/ API rule violation: names_match,github.com/grafana/grafana/pkg/apis/query/v0alpha1,GenericDataQuery,IntervalMS API rule violation: names_match,github.com/grafana/grafana/pkg/apis/query/v0alpha1,GenericDataQuery,RefID API rule violation: names_match,github.com/grafana/grafana/pkg/apis/query/v0alpha1,QueryDataResponse,QueryDataResponse +API rule violation: names_match,github.com/grafana/grafana/pkg/apis/query/v0alpha1/template,QueryTemplate,Variables diff --git a/pkg/registry/apis/peakq/format_test.go b/pkg/registry/apis/peakq/format_test.go deleted file mode 100644 index 930b5e3ddd3..00000000000 --- a/pkg/registry/apis/peakq/format_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package peakq - -import ( - "testing" - - "github.com/stretchr/testify/require" - - peakq "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1" -) - -func TestFormat(t *testing.T) { - // Invalid input - require.Equal(t, "", formatVariables(peakq.FormatCSV, nil)) - require.Equal(t, "", formatVariables(peakq.FormatCSV, []string{})) - - type check struct { - name string - input []string - output map[peakq.VariableFormat]string - } - - tests := []check{ - { - name: "three simple variables", - input: []string{"a", "b", "c"}, - output: map[peakq.VariableFormat]string{ - peakq.FormatCSV: "a,b,c", - peakq.FormatJSON: `["a","b","c"]`, - peakq.FormatDoubleQuote: `"a","b","c"`, - peakq.FormatSingleQuote: `'a','b','c'`, - peakq.FormatPipe: `a|b|c`, - peakq.FormatRaw: "a,b,c", - }, - }, - { - name: "single value", - input: []string{"a"}, - output: map[peakq.VariableFormat]string{ - peakq.FormatCSV: "a", - peakq.FormatJSON: `["a"]`, - peakq.FormatDoubleQuote: `"a"`, - peakq.FormatSingleQuote: `'a'`, - peakq.FormatPipe: "a", - peakq.FormatRaw: "a", - }, - }, - { - name: "value with quote", - input: []string{`hello "world"`}, - output: map[peakq.VariableFormat]string{ - peakq.FormatCSV: `"hello ""world"""`, // note the double quotes - peakq.FormatJSON: `["hello \"world\""]`, - peakq.FormatDoubleQuote: `"hello \"world\""`, - peakq.FormatSingleQuote: `'hello "world"'`, - peakq.FormatPipe: `hello "world"`, - peakq.FormatRaw: `hello "world"`, - }, - }, - } - for _, test := range tests { - // Make sure all keys are set in tests - all := map[peakq.VariableFormat]bool{ - peakq.FormatRaw: true, - peakq.FormatCSV: true, - peakq.FormatJSON: true, - peakq.FormatDoubleQuote: true, - peakq.FormatSingleQuote: true, - peakq.FormatPipe: true, - } - - // Check the default (no format) matches CSV - require.Equal(t, test.output[peakq.FormatRaw], - formatVariables("", test.input), - "test %s default values are not raw", test.name) - - // Check each input value - for format, v := range test.output { - require.Equal(t, v, formatVariables(format, test.input), "Test: %s (format:%s)", test.name, format) - delete(all, format) - } - require.Empty(t, all, "test %s is missing cases for: %v", test.name, all) - } -} diff --git a/pkg/registry/apis/peakq/render.go b/pkg/registry/apis/peakq/render.go index efa54389ffc..fd0791b4905 100644 --- a/pkg/registry/apis/peakq/render.go +++ b/pkg/registry/apis/peakq/render.go @@ -6,16 +6,14 @@ import ( "fmt" "net/http" "net/url" - "sort" "strings" - "github.com/spyzhov/ajson" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" peakq "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1" - query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" + "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template" ) type renderREST struct { @@ -44,7 +42,7 @@ func (r *renderREST) Connect(ctx context.Context, name string, opts runtime.Obje if err != nil { return nil, err } - template, ok := obj.(*peakq.QueryTemplate) + t, ok := obj.(*peakq.QueryTemplate) if !ok { return nil, fmt.Errorf("expected template") } @@ -55,12 +53,14 @@ func (r *renderREST) Connect(ctx context.Context, name string, opts runtime.Obje responder.Error(err) return } - rq, err := Render(template.Spec, input) + out, err := template.RenderTemplate(t.Spec, input) if err != nil { responder.Error(fmt.Errorf("failed to render: %w", err)) return } - responder.Object(http.StatusOK, rq) + responder.Object(http.StatusOK, &peakq.RenderedQuery{ + Targets: out, + }) }), nil } @@ -79,7 +79,7 @@ func renderPOSTHandler(w http.ResponseWriter, req *http.Request) { w.WriteHeader(500) return } - results, err := Render(qT.Spec, input) + results, err := template.RenderTemplate(qT.Spec, input) if err != nil { _, _ = w.Write([]byte("ERROR: " + err.Error())) w.WriteHeader(500) @@ -88,7 +88,9 @@ func renderPOSTHandler(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(results) + _ = json.NewEncoder(w).Encode(peakq.RenderedQuery{ + Targets: results, + }) } // Replicate the grafana dashboard URL syntax @@ -103,110 +105,3 @@ func makeVarMapFromParams(v url.Values) (map[string][]string, error) { } return input, nil } - -type replacement struct { - *peakq.Position - *peakq.TemplateVariable - format peakq.VariableFormat -} - -func getReplacementMap(qt peakq.QueryTemplateSpec) map[int]map[string][]replacement { - byTargetPath := make(map[int]map[string][]replacement) - - varMap := make(map[string]*peakq.TemplateVariable, len(qt.Variables)) - for i, v := range qt.Variables { - varMap[v.Key] = &qt.Variables[i] - } - - for i, target := range qt.Targets { - if byTargetPath[i] == nil { - byTargetPath[i] = make(map[string][]replacement) - } - for k, vReps := range target.Variables { - for rI, rep := range vReps { - byTargetPath[i][rep.Path] = append(byTargetPath[i][rep.Path], - replacement{ - Position: vReps[rI].Position, - TemplateVariable: varMap[k], - format: rep.Format, - }, - ) - } - } - } - - for idx, byTargetIdx := range byTargetPath { - for path := range byTargetIdx { - sort.Slice(byTargetPath[idx][path], func(i, j int) bool { - return byTargetPath[idx][path][i].Start < byTargetPath[idx][path][j].Start - }) - } - } - - return byTargetPath -} - -func Render(qt peakq.QueryTemplateSpec, selectedValues map[string][]string) (*peakq.RenderedQuery, error) { - targets := qt.DeepCopy().Targets - - rawTargetObjects := make([]*ajson.Node, len(qt.Targets)) - for i, t := range qt.Targets { - b, err := t.Properties.MarshalJSON() - if err != nil { - return nil, err - } - rawTargetObjects[i], err = ajson.Unmarshal(b) - if err != nil { - return nil, err - } - } - - rm := getReplacementMap(qt) - for targetIdx, byTargetIdx := range rm { - for path, reps := range byTargetIdx { - o := rawTargetObjects[targetIdx] - nodes, err := o.JSONPath(path) - if err != nil { - return nil, fmt.Errorf("failed to find path %v: %w", path, err) - } - if len(nodes) != 1 { - return nil, fmt.Errorf("expected one lead node at path %v but got %v", path, len(nodes)) - } - n := nodes[0] - if !n.IsString() { - return nil, fmt.Errorf("only string type leaf notes supported currently, %v is not a string", path) - } - s := []rune(n.String()) - s = s[1 : len(s)-1] - var offSet int64 - for _, r := range reps { - value := []rune(formatVariables(r.format, selectedValues[r.Key])) - if r.Position == nil { - return nil, fmt.Errorf("nil position not support yet, will be full replacement") - } - s = append(s[:r.Start+offSet], append(value, s[r.End+offSet:]...)...) - offSet += int64(len(value)) - (r.End - r.Start) - } - if err = n.SetString(string(s)); err != nil { - return nil, err - } - } - } - - for i, aT := range rawTargetObjects { - raw, err := ajson.Marshal(aT) - if err != nil { - return nil, err - } - u := query.GenericDataQuery{} - err = u.UnmarshalJSON(raw) - if err != nil { - return nil, err - } - targets[i].Properties = u - } - - return &peakq.RenderedQuery{ - Targets: targets, - }, nil -} diff --git a/pkg/registry/apis/peakq/render_examples.go b/pkg/registry/apis/peakq/render_examples.go index c1b5ff75e5c..dbf7df88a51 100644 --- a/pkg/registry/apis/peakq/render_examples.go +++ b/pkg/registry/apis/peakq/render_examples.go @@ -3,34 +3,34 @@ package peakq import ( "github.com/grafana/grafana-plugin-sdk-go/data" - peakq "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1" query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" + "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template" ) -var basicTemplateSpec = peakq.QueryTemplateSpec{ +var basicTemplateSpec = template.QueryTemplate{ Title: "Test", - Variables: []peakq.TemplateVariable{ + Variables: []template.TemplateVariable{ { Key: "metricName", DefaultValues: []string{`down`}, }, }, - Targets: []peakq.Target{ + Targets: []template.Target{ { DataType: data.FrameTypeUnknown, //DataTypeVersion: data.FrameTypeVersion{0, 0}, - Variables: map[string][]peakq.VariableReplacement{ + Variables: map[string][]template.VariableReplacement{ "metricName": { { Path: "$.expr", - Position: &peakq.Position{ + Position: &template.Position{ Start: 0, End: 10, }, }, { Path: "$.expr", - Position: &peakq.Position{ + Position: &template.Position{ Start: 13, End: 23, }, @@ -54,7 +54,7 @@ var basicTemplateSpec = peakq.QueryTemplateSpec{ }, } -var basicTemplateRenderedTargets = []peakq.Target{ +var basicTemplateRenderedTargets = []template.Target{ { DataType: data.FrameTypeUnknown, //DataTypeVersion: data.FrameTypeVersion{0, 0}, diff --git a/pkg/registry/apis/peakq/render_examples_test.go b/pkg/registry/apis/peakq/render_examples_test.go index 22bd71e1459..d9739c92abe 100644 --- a/pkg/registry/apis/peakq/render_examples_test.go +++ b/pkg/registry/apis/peakq/render_examples_test.go @@ -6,14 +6,16 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/apis/query/v0alpha1/template" ) func TestRender(t *testing.T) { - rT, err := Render(basicTemplateSpec, map[string][]string{"metricName": {"up"}}) + rT, err := template.RenderTemplate(basicTemplateSpec, map[string][]string{"metricName": {"up"}}) require.NoError(t, err) require.Equal(t, basicTemplateRenderedTargets[0].Properties.AdditionalProperties()["expr"], - rT.Targets[0].Properties.AdditionalProperties()["expr"]) + rT[0].Properties.AdditionalProperties()["expr"]) b, _ := json.MarshalIndent(basicTemplateSpec, "", " ") fmt.Println(string(b)) }