From 68b588b912eedf7ed75032e00df3a7c36a19a7ae Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Thu, 9 Mar 2023 11:26:15 +0100 Subject: [PATCH] Prometheus schematization (#63878) * Schematize prometheus * revert changes * close response body * Update report.json * Update pkg/tsdb/prometheus/models/query.go Co-authored-by: sam boyer * Use without pointers * remove unused * Specify query format * Rename * Clean up schema * Update public/app/plugins/datasource/prometheus/dataquery.cue Co-authored-by: Ryan McKinley * Update pkg/tsdb/prometheus/models/query.go Co-authored-by: Ryan McKinley * Clean up tests * Update public/app/plugins/datasource/prometheus/dataquery.cue Co-authored-by: sam boyer * make gen-cue * Add comments * Make linter happy * Remove editormode override * Update --------- Co-authored-by: sam boyer Co-authored-by: Ryan McKinley --- .../prometheusdataquery/schema-reference.md | 44 +++++++++ pkg/kindsys/report.json | 20 +++-- .../kinds/dataquery/types_dataquery_gen.go | 89 +++++++++++++++++++ pkg/tsdb/prometheus/models/query.go | 85 +++++++++++------- pkg/tsdb/prometheus/models/query_test.go | 1 + .../querydata/framing_bench_test.go | 8 +- pkg/tsdb/prometheus/querydata/framing_test.go | 15 ++-- pkg/tsdb/prometheus/querydata/request_test.go | 45 +++++++--- .../datasource/prometheus/dataquery.cue | 55 ++++++++++++ .../datasource/prometheus/dataquery.gen.ts | 47 ++++++++++ .../datasource/prometheus/datasource.test.ts | 4 +- .../components/PromQueryBuilderOptions.tsx | 3 +- .../components/PromQueryEditorSelector.tsx | 3 +- .../plugins/datasource/prometheus/types.ts | 23 +++-- 14 files changed, 361 insertions(+), 81 deletions(-) create mode 100644 docs/sources/developers/kinds/composable/prometheusdataquery/schema-reference.md create mode 100644 pkg/tsdb/prometheus/kinds/dataquery/types_dataquery_gen.go create mode 100644 public/app/plugins/datasource/prometheus/dataquery.cue create mode 100644 public/app/plugins/datasource/prometheus/dataquery.gen.ts diff --git a/docs/sources/developers/kinds/composable/prometheusdataquery/schema-reference.md b/docs/sources/developers/kinds/composable/prometheusdataquery/schema-reference.md new file mode 100644 index 00000000000..2645ed1d5c9 --- /dev/null +++ b/docs/sources/developers/kinds/composable/prometheusdataquery/schema-reference.md @@ -0,0 +1,44 @@ +--- +keywords: + - grafana + - schema +title: PrometheusDataQuery kind +--- +> Both documentation generation and kinds schemas are in active development and subject to change without prior notice. + +## PrometheusDataQuery + +#### Maturity: [experimental](../../../maturity/#experimental) +#### Version: 0.0 + + + +It extends [DataQuery](#dataquery). + +| Property | Type | Required | Description | +|--------------|---------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `expr` | string | **Yes** | The actual expression/query that will be evaluated by Prometheus | +| `refId` | string | **Yes** | *(Inherited from [DataQuery](#dataquery))*
A unique identifier for the query within the list of targets.
In server side expressions, the refId is used as a variable name to identify results.
By default, the UI will assign A->Z; however setting meaningful names may be useful. | +| `datasource` | | No | *(Inherited from [DataQuery](#dataquery))*
For mixed data sources the selected datasource is on the query level.
For non mixed scenarios this is undefined.
TODO find a better way to do this ^ that's friendly to schema
TODO this shouldn't be unknown but DataSourceRef | null | +| `editorMode` | string | No | Possible values are: `code`, `builder`. | +| `exemplar` | boolean | No | Execute an additional query to identify interesting raw samples relevant for the given expr | +| `format` | string | No | Possible values are: `time_series`, `table`, `heatmap`. | +| `hide` | boolean | No | *(Inherited from [DataQuery](#dataquery))*
true if query is disabled (ie should not be returned to the dashboard)
Note this does not always imply that the query should not be executed since
the results from a hidden query may be used as the input to other queries (SSE etc) | +| `instant` | boolean | No | Returns only the latest value that Prometheus has scraped for the requested time series | +| `queryType` | string | No | *(Inherited from [DataQuery](#dataquery))*
Specify the query flavor
TODO make this required and give it a default | +| `range` | boolean | No | Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series | + +### DataQuery + +These are the common properties available to all queries in all datasources. +Specific implementations will *extend* this interface, adding the required +properties for the given context. + +| Property | Type | Required | Description | +|--------------|---------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `refId` | string | **Yes** | A unique identifier for the query within the list of targets.
In server side expressions, the refId is used as a variable name to identify results.
By default, the UI will assign A->Z; however setting meaningful names may be useful. | +| `datasource` | | No | For mixed data sources the selected datasource is on the query level.
For non mixed scenarios this is undefined.
TODO find a better way to do this ^ that's friendly to schema
TODO this shouldn't be unknown but DataSourceRef | null | +| `hide` | boolean | No | true if query is disabled (ie should not be returned to the dashboard)
Note this does not always imply that the query should not be executed since
the results from a hidden query may be used as the input to other queries (SSE etc) | +| `queryType` | string | No | Specify the query flavor
TODO make this required and give it a default | + + diff --git a/pkg/kindsys/report.json b/pkg/kindsys/report.json index 98f0af2335c..0627ecec5fc 100644 --- a/pkg/kindsys/report.json +++ b/pkg/kindsys/report.json @@ -1346,7 +1346,9 @@ }, "prometheusdataquery": { "category": "composable", - "codeowners": [], + "codeowners": [ + "grafana/observability-metrics" + ], "currentVersion": [ 0, 0 @@ -1354,13 +1356,13 @@ "grafanaMaturityCount": 0, "lineageIsGroup": false, "links": { - "docs": "n/a", - "go": "n/a", - "schema": "n/a", - "ts": "n/a" + "docs": "https://grafana.com/docs/grafana/next/developers/kinds/composable/prometheusdataquery/schema-reference", + "go": "https://github.com/grafana/grafana/tree/main/pkg/tsdb/prometheus/kinds/dataquery/types_dataquery_gen.go", + "schema": "https://github.com/grafana/grafana/tree/main/public/app/plugins/datasource/prometheus/dataquery.cue", + "ts": "https://github.com/grafana/grafana/tree/main/public/app/plugins/datasource/prometheus/dataquery.gen.ts" }, "machineName": "prometheusdataquery", - "maturity": "planned", + "maturity": "experimental", "name": "PrometheusDataQuery", "pluralMachineName": "prometheusdataquerys", "pluralName": "PrometheusDataQuerys", @@ -2053,6 +2055,7 @@ "parcadataquery", "phlaredataquery", "piechartpanelcfg", + "prometheusdataquery", "statetimelinepanelcfg", "statpanelcfg", "statushistorypanelcfg", @@ -2062,7 +2065,7 @@ "textpanelcfg", "xychartpanelcfg" ], - "count": 26 + "count": 27 }, "mature": { "name": "mature", @@ -2120,7 +2123,6 @@ "phlaredatasourcecfg", "postgresqldataquery", "postgresqldatasourcecfg", - "prometheusdataquery", "prometheusdatasourcecfg", "query", "queryhistory", @@ -2134,7 +2136,7 @@ "zipkindataquery", "zipkindatasourcecfg" ], - "count": 46 + "count": 45 }, "stable": { "name": "stable", diff --git a/pkg/tsdb/prometheus/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/prometheus/kinds/dataquery/types_dataquery_gen.go new file mode 100644 index 00000000000..4d00213adac --- /dev/null +++ b/pkg/tsdb/prometheus/kinds/dataquery/types_dataquery_gen.go @@ -0,0 +1,89 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// public/app/plugins/gen.go +// Using jennies: +// PluginGoTypesJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package dataquery + +// Defines values for PromQueryFormat. +const ( + PromQueryFormatHeatmap PromQueryFormat = "heatmap" + PromQueryFormatTable PromQueryFormat = "table" + PromQueryFormatTimeSeries PromQueryFormat = "time_series" +) + +// Defines values for EditorMode. +const ( + EditorModeBuilder EditorMode = "builder" + EditorModeCode EditorMode = "code" +) + +// Defines values for Format. +const ( + FormatHeatmap Format = "heatmap" + FormatTable Format = "table" + FormatTimeSeries Format = "time_series" +) + +// Defines values for QueryEditorMode. +const ( + QueryEditorModeBuilder QueryEditorMode = "builder" + QueryEditorModeCode QueryEditorMode = "code" +) + +// PromQueryFormat defines model for PromQueryFormat. +type PromQueryFormat string + +// PrometheusDataQuery defines model for PrometheusDataQuery. +type PrometheusDataQuery struct { + // For mixed data sources the selected datasource is on the query level. + // For non mixed scenarios this is undefined. + // TODO find a better way to do this ^ that's friendly to schema + // TODO this shouldn't be unknown but DataSourceRef | null + Datasource *interface{} `json:"datasource,omitempty"` + + // Specifies which editor is being used to prepare the query. It can be "code" or "builder" + EditorMode *EditorMode `json:"editorMode,omitempty"` + + // Execute an additional query to identify interesting raw samples relevant for the given expr + Exemplar *bool `json:"exemplar,omitempty"` + + // The actual expression/query that will be evaluated by Prometheus + Expr string `json:"expr"` + + // Query format to determine how to display data points in panel. It can be "time_series", "table", "heatmap" + Format *Format `json:"format,omitempty"` + + // Hide true if query is disabled (ie should not be returned to the dashboard) + // Note this does not always imply that the query should not be executed since + // the results from a hidden query may be used as the input to other queries (SSE etc) + Hide *bool `json:"hide,omitempty"` + + // Returns only the latest value that Prometheus has scraped for the requested time series + Instant *bool `json:"instant,omitempty"` + + // Specify the query flavor + // TODO make this required and give it a default + QueryType *string `json:"queryType,omitempty"` + + // Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series + Range *bool `json:"range,omitempty"` + + // A unique identifier for the query within the list of targets. + // In server side expressions, the refId is used as a variable name to identify results. + // By default, the UI will assign A->Z; however setting meaningful names may be useful. + RefId string `json:"refId"` +} + +// Specifies which editor is being used to prepare the query. It can be "code" or "builder" +type EditorMode string + +// Query format to determine how to display data points in panel. It can be "time_series", "table", "heatmap" +type Format string + +// QueryEditorMode defines model for QueryEditorMode. +type QueryEditorMode string diff --git a/pkg/tsdb/prometheus/models/query.go b/pkg/tsdb/prometheus/models/query.go index 233d8fddc50..741d6d4e8bc 100644 --- a/pkg/tsdb/prometheus/models/query.go +++ b/pkg/tsdb/prometheus/models/query.go @@ -8,8 +8,8 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana/pkg/tsdb/intervalv2" + "github.com/grafana/grafana/pkg/tsdb/prometheus/kinds/dataquery" ) // Internal interval and range variables @@ -45,16 +45,14 @@ const ( var safeResolution = 11000 type QueryModel struct { - Expr string `json:"expr"` - LegendFormat string `json:"legendFormat"` - Interval string `json:"interval"` - IntervalMS int64 `json:"intervalMS"` - StepMode string `json:"stepMode"` - RangeQuery bool `json:"range"` - InstantQuery bool `json:"instant"` - ExemplarQuery bool `json:"exemplar"` - IntervalFactor int64 `json:"intervalFactor"` - UtcOffsetSec int64 `json:"utcOffsetSec"` + dataquery.PrometheusDataQuery + // The following properties may be part of the request payload, however they are not saved in panel JSON + // Timezone offset to align start & end time on backend + UtcOffsetSec int64 `json:"utcOffsetSec,omitempty"` + LegendFormat string `json:"legendFormat,omitempty"` + Interval string `json:"interval,omitempty"` + IntervalMs int64 `json:"intervalMs,omitempty"` + IntervalFactor int64 `json:"intervalFactor,omitempty"` } type TimeRange struct { @@ -82,23 +80,36 @@ func Parse(query backend.DataQuery, timeInterval string, intervalCalculator inte return nil, err } - //Final interval value - interval, err := calculatePrometheusInterval(model, timeInterval, query, intervalCalculator) + // Final interval value + interval, err := calculatePrometheusInterval(model.Interval, timeInterval, model.IntervalMs, model.IntervalFactor, query, intervalCalculator) if err != nil { return nil, err } // Interpolate variables in expr timeRange := query.TimeRange.To.Sub(query.TimeRange.From) - expr := interpolateVariables(model, interval, timeRange, intervalCalculator, timeInterval) - rangeQuery := model.RangeQuery - if !model.InstantQuery && !model.RangeQuery { + expr := interpolateVariables(model.Expr, model.Interval, interval, timeRange, intervalCalculator, timeInterval) + var rangeQuery, instantQuery bool + if model.Instant == nil { + instantQuery = false + } else { + instantQuery = *model.Instant + } + if model.Range == nil { + rangeQuery = false + } else { + rangeQuery = *model.Range + } + if !instantQuery && !rangeQuery { // In older dashboards, we were not setting range query param and !range && !instant was run as range query rangeQuery = true } // We never want to run exemplar query for alerting - exemplarQuery := model.ExemplarQuery + exemplarQuery := false + if model.Exemplar != nil { + exemplarQuery = *model.Exemplar + } if fromAlert { exemplarQuery = false } @@ -110,7 +121,7 @@ func Parse(query backend.DataQuery, timeInterval string, intervalCalculator inte Start: query.TimeRange.From, End: query.TimeRange.To, RefId: query.RefID, - InstantQuery: model.InstantQuery, + InstantQuery: instantQuery, RangeQuery: rangeQuery, ExemplarQuery: exemplarQuery, UtcOffsetSec: model.UtcOffsetSec, @@ -139,15 +150,18 @@ func (query *Query) TimeRange() TimeRange { } } -func calculatePrometheusInterval(model *QueryModel, timeInterval string, query backend.DataQuery, intervalCalculator intervalv2.Calculator) (time.Duration, error) { - queryInterval := model.Interval - - //If we are using variable for interval/step, we will replace it with calculated interval +func calculatePrometheusInterval( + queryInterval, timeInterval string, + intervalMs, intervalFactor int64, + query backend.DataQuery, + intervalCalculator intervalv2.Calculator, +) (time.Duration, error) { + // If we are using variable for interval/step, we will replace it with calculated interval if isVariableInterval(queryInterval) { queryInterval = "" } - minInterval, err := intervalv2.GetIntervalFrom(timeInterval, queryInterval, model.IntervalMS, 15*time.Second) + minInterval, err := intervalv2.GetIntervalFrom(timeInterval, queryInterval, intervalMs, 15*time.Second) if err != nil { return time.Duration(0), err } @@ -159,19 +173,23 @@ func calculatePrometheusInterval(model *QueryModel, timeInterval string, query b adjustedInterval = calculatedInterval.Value } - if model.Interval == varRateInterval || model.Interval == varRateIntervalAlt { + if queryInterval == varRateInterval || queryInterval == varRateIntervalAlt { // Rate interval is final and is not affected by resolution return calculateRateInterval(adjustedInterval, timeInterval, intervalCalculator), nil } else { - intervalFactor := model.IntervalFactor - if intervalFactor == 0 { - intervalFactor = 1 + queryIntervalFactor := intervalFactor + if queryIntervalFactor == 0 { + queryIntervalFactor = 1 } - return time.Duration(int64(adjustedInterval) * intervalFactor), nil + return time.Duration(int64(adjustedInterval) * queryIntervalFactor), nil } } -func calculateRateInterval(interval time.Duration, scrapeInterval string, intervalCalculator intervalv2.Calculator) time.Duration { +func calculateRateInterval( + interval time.Duration, + scrapeInterval string, + intervalCalculator intervalv2.Calculator, +) time.Duration { scrape := scrapeInterval if scrape == "" { scrape = "15s" @@ -186,13 +204,14 @@ func calculateRateInterval(interval time.Duration, scrapeInterval string, interv return rateInterval } -func interpolateVariables(model *QueryModel, interval time.Duration, timeRange time.Duration, intervalCalculator intervalv2.Calculator, timeInterval string) string { - expr := model.Expr +func interpolateVariables(expr, queryInterval string, interval time.Duration, + timeRange time.Duration, + intervalCalculator intervalv2.Calculator, timeInterval string) string { rangeMs := timeRange.Milliseconds() rangeSRounded := int64(math.Round(float64(rangeMs) / 1000.0)) var rateInterval time.Duration - if model.Interval == varRateInterval || model.Interval == varRateIntervalAlt { + if queryInterval == varRateInterval || queryInterval == varRateIntervalAlt { rateInterval = interval } else { rateInterval = calculateRateInterval(interval, timeInterval, intervalCalculator) @@ -219,7 +238,7 @@ func isVariableInterval(interval string) bool { if interval == varInterval || interval == varIntervalMs || interval == varRateInterval { return true } - //Repetitive code, we should have functionality to unify these + // Repetitive code, we should have functionality to unify these if interval == varIntervalAlt || interval == varIntervalMsAlt || interval == varRateIntervalAlt { return true } diff --git a/pkg/tsdb/prometheus/models/query_test.go b/pkg/tsdb/prometheus/models/query_test.go index dd30c5f0b24..ed48c1d0cfa 100644 --- a/pkg/tsdb/prometheus/models/query_test.go +++ b/pkg/tsdb/prometheus/models/query_test.go @@ -376,6 +376,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "interval": "$__rate_interval", + "intervalMs": 60000, "refId": "A" }`, timeRange) diff --git a/pkg/tsdb/prometheus/querydata/framing_bench_test.go b/pkg/tsdb/prometheus/querydata/framing_bench_test.go index 7f319ea72da..798b8f5c519 100644 --- a/pkg/tsdb/prometheus/querydata/framing_bench_test.go +++ b/pkg/tsdb/prometheus/querydata/framing_bench_test.go @@ -15,6 +15,8 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/kindsys" + "github.com/grafana/grafana/pkg/tsdb/prometheus/kinds/dataquery" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/tsdb/prometheus/models" @@ -121,8 +123,10 @@ func createJsonTestData(start int64, step int64, timestampCount int, seriesCount bytes := []byte(fmt.Sprintf(`{"status":"success","data":{"resultType":"matrix","result":[%v]}}`, strings.Join(allSeries, ","))) qm := models.QueryModel{ - RangeQuery: true, - Expr: "test", + PrometheusDataQuery: dataquery.PrometheusDataQuery{ + Range: kindsys.Ptr(true), + Expr: "test", + }, } data, err := json.Marshal(&qm) diff --git a/pkg/tsdb/prometheus/querydata/framing_test.go b/pkg/tsdb/prometheus/querydata/framing_test.go index 09355702fc2..0f84e90f55d 100644 --- a/pkg/tsdb/prometheus/querydata/framing_test.go +++ b/pkg/tsdb/prometheus/querydata/framing_test.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/experimental" + "github.com/grafana/grafana/pkg/tsdb/prometheus/kinds/dataquery" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/tsdb/prometheus/models" @@ -115,12 +116,14 @@ func loadStoredQuery(fileName string) (*backend.QueryDataRequest, error) { } qm := models.QueryModel{ - RangeQuery: sq.RangeQuery, - ExemplarQuery: sq.ExemplarQuery, - Expr: sq.Expr, - Interval: fmt.Sprintf("%ds", sq.Step), - IntervalMS: sq.Step * 1000, - LegendFormat: sq.LegendFormat, + PrometheusDataQuery: dataquery.PrometheusDataQuery{ + Range: &sq.RangeQuery, + Exemplar: &sq.ExemplarQuery, + Expr: sq.Expr, + }, + Interval: fmt.Sprintf("%ds", sq.Step), + IntervalMs: sq.Step * 1000, + LegendFormat: sq.LegendFormat, } data, err := json.Marshal(&qm) diff --git a/pkg/tsdb/prometheus/querydata/request_test.go b/pkg/tsdb/prometheus/querydata/request_test.go index 942ace816a2..d3f6238098b 100644 --- a/pkg/tsdb/prometheus/querydata/request_test.go +++ b/pkg/tsdb/prometheus/querydata/request_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "io" "math" "net/http" @@ -13,6 +14,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/tsdb/prometheus/kinds/dataquery" apiv1 "github.com/prometheus/client_golang/api/prometheus/v1" p "github.com/prometheus/common/model" "github.com/stretchr/testify/require" @@ -20,6 +22,7 @@ import ( "github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/infra/log/logtest" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/kindsys" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/prometheus/client" "github.com/grafana/grafana/pkg/tsdb/prometheus/models" @@ -65,9 +68,11 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) { require.NoError(t, err) qm := models.QueryModel{ - LegendFormat: "legend {{app}}", - UtcOffsetSec: 0, - ExemplarQuery: true, + LegendFormat: "legend {{app}}", + UtcOffsetSec: 0, + PrometheusDataQuery: dataquery.PrometheusDataQuery{ + Exemplar: kindsys.Ptr(true), + }, } b, err := json.Marshal(&qm) require.NoError(t, err) @@ -112,7 +117,9 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) { qm := models.QueryModel{ LegendFormat: "legend {{app}}", UtcOffsetSec: 0, - RangeQuery: true, + PrometheusDataQuery: dataquery.PrometheusDataQuery{ + Range: kindsys.Ptr(true), + }, } b, err := json.Marshal(&qm) require.NoError(t, err) @@ -159,7 +166,9 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) { qm := models.QueryModel{ LegendFormat: "", UtcOffsetSec: 0, - RangeQuery: true, + PrometheusDataQuery: dataquery.PrometheusDataQuery{ + Range: kindsys.Ptr(true), + }, } b, err := json.Marshal(&qm) require.NoError(t, err) @@ -202,7 +211,9 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) { qm := models.QueryModel{ LegendFormat: "", UtcOffsetSec: 0, - RangeQuery: true, + PrometheusDataQuery: dataquery.PrometheusDataQuery{ + Range: kindsys.Ptr(true), + }, } b, err := json.Marshal(&qm) require.NoError(t, err) @@ -243,7 +254,9 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) { qm := models.QueryModel{ LegendFormat: "", UtcOffsetSec: 0, - RangeQuery: true, + PrometheusDataQuery: dataquery.PrometheusDataQuery{ + Range: kindsys.Ptr(true), + }, } b, err := json.Marshal(&qm) require.NoError(t, err) @@ -278,7 +291,9 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) { qm := models.QueryModel{ LegendFormat: "legend {{app}}", UtcOffsetSec: 0, - InstantQuery: true, + PrometheusDataQuery: dataquery.PrometheusDataQuery{ + Instant: kindsys.Ptr(true), + }, } b, err := json.Marshal(&qm) require.NoError(t, err) @@ -317,7 +332,9 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) { qm := models.QueryModel{ LegendFormat: "", UtcOffsetSec: 0, - InstantQuery: true, + PrometheusDataQuery: dataquery.PrometheusDataQuery{ + Instant: kindsys.Ptr(true), + }, } b, err := json.Marshal(&qm) require.NoError(t, err) @@ -354,20 +371,20 @@ func executeWithHeaders(tctx *testContext, query backend.DataQuery, qr interface } promRes, err := toAPIResponse(qr) + defer func() { + if err := promRes.Body.Close(); err != nil { + fmt.Println(fmt.Errorf("response body close error: %v", err)) + } + }() if err != nil { return nil, err } - tctx.httpProvider.setResponse(promRes) res, err := tctx.queryData.Execute(context.Background(), &req) - errClose := promRes.Body.Close() if err != nil { return nil, err } - if errClose != nil { - return nil, errClose - } return res.Responses[req.Queries[0].RefID].Frames, nil } diff --git a/public/app/plugins/datasource/prometheus/dataquery.cue b/public/app/plugins/datasource/prometheus/dataquery.cue new file mode 100644 index 00000000000..46858e6c92c --- /dev/null +++ b/public/app/plugins/datasource/prometheus/dataquery.cue @@ -0,0 +1,55 @@ +// Copyright 2023 Grafana Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package grafanaplugin + +import ( + common "github.com/grafana/grafana/packages/grafana-schema/src/common" + "github.com/grafana/grafana/pkg/plugins/pfs" +) + +// This file (with its sibling .cue files) implements pfs.GrafanaPlugin +pfs.GrafanaPlugin + +composableKinds: DataQuery: { + maturity: "experimental" + + lineage: { + seqs: [ + { + schemas: [ + { + common.DataQuery + + // The actual expression/query that will be evaluated by Prometheus + expr: string + // Returns only the latest value that Prometheus has scraped for the requested time series + instant?: bool + // Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series + range?: bool + // Execute an additional query to identify interesting raw samples relevant for the given expr + exemplar?: bool + // Specifies which editor is being used to prepare the query. It can be "code" or "builder" + editorMode?: #QueryEditorMode + // Query format to determine how to display data points in panel. It can be "time_series", "table", "heatmap" + format?: #PromQueryFormat + + #QueryEditorMode: "code" | "builder" @cuetsy(kind="enum") + #PromQueryFormat: "time_series" | "table" | "heatmap" @cuetsy(kind="type") + }, + ] + }, + ] + } +} diff --git a/public/app/plugins/datasource/prometheus/dataquery.gen.ts b/public/app/plugins/datasource/prometheus/dataquery.gen.ts new file mode 100644 index 00000000000..6ab137644ae --- /dev/null +++ b/public/app/plugins/datasource/prometheus/dataquery.gen.ts @@ -0,0 +1,47 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// public/app/plugins/gen.go +// Using jennies: +// TSTypesJenny +// PluginTSTypesJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +import * as common from '@grafana/schema'; + +export const DataQueryModelVersion = Object.freeze([0, 0]); + +export enum QueryEditorMode { + Builder = 'builder', + Code = 'code', +} + +export type PromQueryFormat = ('time_series' | 'table' | 'heatmap'); + +export interface Prometheus extends common.DataQuery { + /** + * Specifies which editor is being used to prepare the query. It can be "code" or "builder" + */ + editorMode?: QueryEditorMode; + /** + * Execute an additional query to identify interesting raw samples relevant for the given expr + */ + exemplar?: boolean; + /** + * The actual expression/query that will be evaluated by Prometheus + */ + expr: string; + /** + * Query format to determine how to display data points in panel. It can be "time_series", "table", "heatmap" + */ + format?: PromQueryFormat; + /** + * Returns only the latest value that Prometheus has scraped for the requested time series + */ + instant?: boolean; + /** + * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series + */ + range?: boolean; +} diff --git a/public/app/plugins/datasource/prometheus/datasource.test.ts b/public/app/plugins/datasource/prometheus/datasource.test.ts index 9cced6e00e7..91a75a413e8 100644 --- a/public/app/plugins/datasource/prometheus/datasource.test.ts +++ b/public/app/plugins/datasource/prometheus/datasource.test.ts @@ -190,7 +190,7 @@ describe('PrometheusDatasource', () => { }); describe('customQueryParams', () => { - const target = { expr: 'test{job="testjob"}', format: 'time_series', refId: '' }; + const target: PromQuery = { expr: 'test{job="testjob"}', format: 'time_series', refId: '' }; function makeQuery(target: PromQuery) { return { @@ -618,7 +618,7 @@ describe('PrometheusDatasource', () => { describe('interpolateVariablesInQueries', () => { it('should call replace function 2 times', () => { - const query = { + const query: PromQuery = { expr: 'test{job="testjob"}', format: 'time_series', interval: '$Interval', diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index a258a3b65d6..7f12cb2746b 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -5,6 +5,7 @@ import { EditorField, EditorRow, EditorSwitch } from '@grafana/experimental'; import { AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui'; import { getQueryTypeChangeHandler, getQueryTypeOptions } from '../../components/PromExploreExtraField'; +import { PromQueryFormat } from '../../dataquery.gen'; import { PromQuery } from '../../types'; import { QueryOptionGroup } from '../shared/QueryOptionGroup'; @@ -28,7 +29,7 @@ export interface Props { } export const PromQueryBuilderOptions = React.memo(({ query, app, onChange, onRunQuery }) => { - const onChangeFormat = (value: SelectableValue) => { + const onChangeFormat = (value: SelectableValue) => { onChange({ ...query, format: value.value }); onRunQuery(); }; diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.tsx index 1057c9ebcd4..524d1e03f10 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.tsx @@ -8,6 +8,7 @@ import { reportInteraction } from '@grafana/runtime'; import { Button, ConfirmModal } from '@grafana/ui'; import { PromQueryEditorProps } from '../../components/types'; +import { PromQueryFormat } from '../../dataquery.gen'; import { PromQuery } from '../../types'; import { QueryPatternsModal } from '../QueryPatternsModal'; import { buildVisualQueryFromString } from '../parsing'; @@ -21,7 +22,7 @@ import { PromQueryBuilderContainer } from './PromQueryBuilderContainer'; import { PromQueryBuilderOptions } from './PromQueryBuilderOptions'; import { PromQueryCodeEditor } from './PromQueryCodeEditor'; -export const FORMAT_OPTIONS: Array> = [ +export const FORMAT_OPTIONS: Array> = [ { label: 'Time series', value: 'time_series' }, { label: 'Table', value: 'table' }, { label: 'Heatmap', value: 'heatmap' }, diff --git a/public/app/plugins/datasource/prometheus/types.ts b/public/app/plugins/datasource/prometheus/types.ts index 6c324f00233..8f91e893745 100644 --- a/public/app/plugins/datasource/prometheus/types.ts +++ b/public/app/plugins/datasource/prometheus/types.ts @@ -1,27 +1,24 @@ -import { DataQuery, DataSourceJsonData, QueryResultMeta, ScopedVars } from '@grafana/data'; +import { DataSourceJsonData, QueryResultMeta, ScopedVars } from '@grafana/data'; +import { DataQuery } from '@grafana/schema'; import { PromApplication } from '../../../types/unified-alerting-dto'; +import { Prometheus as GenPromQuery } from './dataquery.gen'; import { QueryEditorMode } from './querybuilder/shared/types'; -export interface PromQuery extends DataQuery { - expr: string; - format?: string; - instant?: boolean; - range?: boolean; - exemplar?: boolean; - hinting?: boolean; - interval?: string; - intervalFactor?: number; - // Timezone offset to align start & end time on backend +export interface PromQuery extends GenPromQuery, DataQuery { + /** + * Timezone offset to align start & end time on backend + */ utcOffsetSec?: number; legendFormat?: string; valueWithRefId?: boolean; requestId?: string; showingGraph?: boolean; showingTable?: boolean; - /** Code, Builder or Explain */ - editorMode?: QueryEditorMode; + hinting?: boolean; + interval?: string; + intervalFactor?: number; } export interface PromOptions extends DataSourceJsonData {