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 <sdboyer@grafana.com>

* 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 <ryantxu@gmail.com>

* Update pkg/tsdb/prometheus/models/query.go

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>

* Clean up tests

* Update public/app/plugins/datasource/prometheus/dataquery.cue

Co-authored-by: sam boyer <sdboyer@grafana.com>

* make gen-cue

* Add comments

* Make linter happy

* Remove editormode override

* Update

---------

Co-authored-by: sam boyer <sdboyer@grafana.com>
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
ismail simsek 2023-03-09 11:26:15 +01:00 committed by GitHub
parent 473013e3f5
commit 68b588b912
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 361 additions and 81 deletions

View File

@ -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))*<br/>A unique identifier for the query within the list of targets.<br/>In server side expressions, the refId is used as a variable name to identify results.<br/>By default, the UI will assign A->Z; however setting meaningful names may be useful. |
| `datasource` | | No | *(Inherited from [DataQuery](#dataquery))*<br/>For mixed data sources the selected datasource is on the query level.<br/>For non mixed scenarios this is undefined.<br/>TODO find a better way to do this ^ that's friendly to schema<br/>TODO this shouldn't be unknown but DataSourceRef &#124; 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))*<br/>true if query is disabled (ie should not be returned to the dashboard)<br/>Note this does not always imply that the query should not be executed since<br/>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))*<br/>Specify the query flavor<br/>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.<br/>In server side expressions, the refId is used as a variable name to identify results.<br/>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.<br/>For non mixed scenarios this is undefined.<br/>TODO find a better way to do this ^ that's friendly to schema<br/>TODO this shouldn't be unknown but DataSourceRef &#124; null |
| `hide` | boolean | No | true if query is disabled (ie should not be returned to the dashboard)<br/>Note this does not always imply that the query should not be executed since<br/>the results from a hidden query may be used as the input to other queries (SSE etc) |
| `queryType` | string | No | Specify the query flavor<br/>TODO make this required and give it a default |

View File

@ -1346,7 +1346,9 @@
}, },
"prometheusdataquery": { "prometheusdataquery": {
"category": "composable", "category": "composable",
"codeowners": [], "codeowners": [
"grafana/observability-metrics"
],
"currentVersion": [ "currentVersion": [
0, 0,
0 0
@ -1354,13 +1356,13 @@
"grafanaMaturityCount": 0, "grafanaMaturityCount": 0,
"lineageIsGroup": false, "lineageIsGroup": false,
"links": { "links": {
"docs": "n/a", "docs": "https://grafana.com/docs/grafana/next/developers/kinds/composable/prometheusdataquery/schema-reference",
"go": "n/a", "go": "https://github.com/grafana/grafana/tree/main/pkg/tsdb/prometheus/kinds/dataquery/types_dataquery_gen.go",
"schema": "n/a", "schema": "https://github.com/grafana/grafana/tree/main/public/app/plugins/datasource/prometheus/dataquery.cue",
"ts": "n/a" "ts": "https://github.com/grafana/grafana/tree/main/public/app/plugins/datasource/prometheus/dataquery.gen.ts"
}, },
"machineName": "prometheusdataquery", "machineName": "prometheusdataquery",
"maturity": "planned", "maturity": "experimental",
"name": "PrometheusDataQuery", "name": "PrometheusDataQuery",
"pluralMachineName": "prometheusdataquerys", "pluralMachineName": "prometheusdataquerys",
"pluralName": "PrometheusDataQuerys", "pluralName": "PrometheusDataQuerys",
@ -2053,6 +2055,7 @@
"parcadataquery", "parcadataquery",
"phlaredataquery", "phlaredataquery",
"piechartpanelcfg", "piechartpanelcfg",
"prometheusdataquery",
"statetimelinepanelcfg", "statetimelinepanelcfg",
"statpanelcfg", "statpanelcfg",
"statushistorypanelcfg", "statushistorypanelcfg",
@ -2062,7 +2065,7 @@
"textpanelcfg", "textpanelcfg",
"xychartpanelcfg" "xychartpanelcfg"
], ],
"count": 26 "count": 27
}, },
"mature": { "mature": {
"name": "mature", "name": "mature",
@ -2120,7 +2123,6 @@
"phlaredatasourcecfg", "phlaredatasourcecfg",
"postgresqldataquery", "postgresqldataquery",
"postgresqldatasourcecfg", "postgresqldatasourcecfg",
"prometheusdataquery",
"prometheusdatasourcecfg", "prometheusdatasourcecfg",
"query", "query",
"queryhistory", "queryhistory",
@ -2134,7 +2136,7 @@
"zipkindataquery", "zipkindataquery",
"zipkindatasourcecfg" "zipkindatasourcecfg"
], ],
"count": 46 "count": 45
}, },
"stable": { "stable": {
"name": "stable", "name": "stable",

View File

@ -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

View File

@ -8,8 +8,8 @@ import (
"time" "time"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/tsdb/intervalv2" "github.com/grafana/grafana/pkg/tsdb/intervalv2"
"github.com/grafana/grafana/pkg/tsdb/prometheus/kinds/dataquery"
) )
// Internal interval and range variables // Internal interval and range variables
@ -45,16 +45,14 @@ const (
var safeResolution = 11000 var safeResolution = 11000
type QueryModel struct { type QueryModel struct {
Expr string `json:"expr"` dataquery.PrometheusDataQuery
LegendFormat string `json:"legendFormat"` // The following properties may be part of the request payload, however they are not saved in panel JSON
Interval string `json:"interval"` // Timezone offset to align start & end time on backend
IntervalMS int64 `json:"intervalMS"` UtcOffsetSec int64 `json:"utcOffsetSec,omitempty"`
StepMode string `json:"stepMode"` LegendFormat string `json:"legendFormat,omitempty"`
RangeQuery bool `json:"range"` Interval string `json:"interval,omitempty"`
InstantQuery bool `json:"instant"` IntervalMs int64 `json:"intervalMs,omitempty"`
ExemplarQuery bool `json:"exemplar"` IntervalFactor int64 `json:"intervalFactor,omitempty"`
IntervalFactor int64 `json:"intervalFactor"`
UtcOffsetSec int64 `json:"utcOffsetSec"`
} }
type TimeRange struct { type TimeRange struct {
@ -82,23 +80,36 @@ func Parse(query backend.DataQuery, timeInterval string, intervalCalculator inte
return nil, err return nil, err
} }
//Final interval value // Final interval value
interval, err := calculatePrometheusInterval(model, timeInterval, query, intervalCalculator) interval, err := calculatePrometheusInterval(model.Interval, timeInterval, model.IntervalMs, model.IntervalFactor, query, intervalCalculator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Interpolate variables in expr // Interpolate variables in expr
timeRange := query.TimeRange.To.Sub(query.TimeRange.From) timeRange := query.TimeRange.To.Sub(query.TimeRange.From)
expr := interpolateVariables(model, interval, timeRange, intervalCalculator, timeInterval) expr := interpolateVariables(model.Expr, model.Interval, interval, timeRange, intervalCalculator, timeInterval)
rangeQuery := model.RangeQuery var rangeQuery, instantQuery bool
if !model.InstantQuery && !model.RangeQuery { 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 // In older dashboards, we were not setting range query param and !range && !instant was run as range query
rangeQuery = true rangeQuery = true
} }
// We never want to run exemplar query for alerting // We never want to run exemplar query for alerting
exemplarQuery := model.ExemplarQuery exemplarQuery := false
if model.Exemplar != nil {
exemplarQuery = *model.Exemplar
}
if fromAlert { if fromAlert {
exemplarQuery = false exemplarQuery = false
} }
@ -110,7 +121,7 @@ func Parse(query backend.DataQuery, timeInterval string, intervalCalculator inte
Start: query.TimeRange.From, Start: query.TimeRange.From,
End: query.TimeRange.To, End: query.TimeRange.To,
RefId: query.RefID, RefId: query.RefID,
InstantQuery: model.InstantQuery, InstantQuery: instantQuery,
RangeQuery: rangeQuery, RangeQuery: rangeQuery,
ExemplarQuery: exemplarQuery, ExemplarQuery: exemplarQuery,
UtcOffsetSec: model.UtcOffsetSec, 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) { func calculatePrometheusInterval(
queryInterval := model.Interval queryInterval, timeInterval string,
intervalMs, intervalFactor int64,
//If we are using variable for interval/step, we will replace it with calculated interval 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) { if isVariableInterval(queryInterval) {
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 { if err != nil {
return time.Duration(0), err return time.Duration(0), err
} }
@ -159,19 +173,23 @@ func calculatePrometheusInterval(model *QueryModel, timeInterval string, query b
adjustedInterval = calculatedInterval.Value 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 // Rate interval is final and is not affected by resolution
return calculateRateInterval(adjustedInterval, timeInterval, intervalCalculator), nil return calculateRateInterval(adjustedInterval, timeInterval, intervalCalculator), nil
} else { } else {
intervalFactor := model.IntervalFactor queryIntervalFactor := intervalFactor
if intervalFactor == 0 { if queryIntervalFactor == 0 {
intervalFactor = 1 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 scrape := scrapeInterval
if scrape == "" { if scrape == "" {
scrape = "15s" scrape = "15s"
@ -186,13 +204,14 @@ func calculateRateInterval(interval time.Duration, scrapeInterval string, interv
return rateInterval return rateInterval
} }
func interpolateVariables(model *QueryModel, interval time.Duration, timeRange time.Duration, intervalCalculator intervalv2.Calculator, timeInterval string) string { func interpolateVariables(expr, queryInterval string, interval time.Duration,
expr := model.Expr timeRange time.Duration,
intervalCalculator intervalv2.Calculator, timeInterval string) string {
rangeMs := timeRange.Milliseconds() rangeMs := timeRange.Milliseconds()
rangeSRounded := int64(math.Round(float64(rangeMs) / 1000.0)) rangeSRounded := int64(math.Round(float64(rangeMs) / 1000.0))
var rateInterval time.Duration var rateInterval time.Duration
if model.Interval == varRateInterval || model.Interval == varRateIntervalAlt { if queryInterval == varRateInterval || queryInterval == varRateIntervalAlt {
rateInterval = interval rateInterval = interval
} else { } else {
rateInterval = calculateRateInterval(interval, timeInterval, intervalCalculator) rateInterval = calculateRateInterval(interval, timeInterval, intervalCalculator)
@ -219,7 +238,7 @@ func isVariableInterval(interval string) bool {
if interval == varInterval || interval == varIntervalMs || interval == varRateInterval { if interval == varInterval || interval == varIntervalMs || interval == varRateInterval {
return true 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 { if interval == varIntervalAlt || interval == varIntervalMsAlt || interval == varRateIntervalAlt {
return true return true
} }

View File

@ -376,6 +376,7 @@ func TestParse(t *testing.T) {
"format": "time_series", "format": "time_series",
"intervalFactor": 1, "intervalFactor": 1,
"interval": "$__rate_interval", "interval": "$__rate_interval",
"intervalMs": 60000,
"refId": "A" "refId": "A"
}`, timeRange) }`, timeRange)

View File

@ -15,6 +15,8 @@ import (
"time" "time"
"github.com/grafana/grafana-plugin-sdk-go/backend" "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/stretchr/testify/require"
"github.com/grafana/grafana/pkg/tsdb/prometheus/models" "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, ","))) bytes := []byte(fmt.Sprintf(`{"status":"success","data":{"resultType":"matrix","result":[%v]}}`, strings.Join(allSeries, ",")))
qm := models.QueryModel{ qm := models.QueryModel{
RangeQuery: true, PrometheusDataQuery: dataquery.PrometheusDataQuery{
Expr: "test", Range: kindsys.Ptr(true),
Expr: "test",
},
} }
data, err := json.Marshal(&qm) data, err := json.Marshal(&qm)

View File

@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/experimental" "github.com/grafana/grafana-plugin-sdk-go/experimental"
"github.com/grafana/grafana/pkg/tsdb/prometheus/kinds/dataquery"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/tsdb/prometheus/models" "github.com/grafana/grafana/pkg/tsdb/prometheus/models"
@ -115,12 +116,14 @@ func loadStoredQuery(fileName string) (*backend.QueryDataRequest, error) {
} }
qm := models.QueryModel{ qm := models.QueryModel{
RangeQuery: sq.RangeQuery, PrometheusDataQuery: dataquery.PrometheusDataQuery{
ExemplarQuery: sq.ExemplarQuery, Range: &sq.RangeQuery,
Expr: sq.Expr, Exemplar: &sq.ExemplarQuery,
Interval: fmt.Sprintf("%ds", sq.Step), Expr: sq.Expr,
IntervalMS: sq.Step * 1000, },
LegendFormat: sq.LegendFormat, Interval: fmt.Sprintf("%ds", sq.Step),
IntervalMs: sq.Step * 1000,
LegendFormat: sq.LegendFormat,
} }
data, err := json.Marshal(&qm) data, err := json.Marshal(&qm)

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"math" "math"
"net/http" "net/http"
@ -13,6 +14,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana-plugin-sdk-go/data" "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" apiv1 "github.com/prometheus/client_golang/api/prometheus/v1"
p "github.com/prometheus/common/model" p "github.com/prometheus/common/model"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -20,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/infra/log/logtest" "github.com/grafana/grafana/pkg/infra/log/logtest"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/kindsys"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/prometheus/client" "github.com/grafana/grafana/pkg/tsdb/prometheus/client"
"github.com/grafana/grafana/pkg/tsdb/prometheus/models" "github.com/grafana/grafana/pkg/tsdb/prometheus/models"
@ -65,9 +68,11 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
qm := models.QueryModel{ qm := models.QueryModel{
LegendFormat: "legend {{app}}", LegendFormat: "legend {{app}}",
UtcOffsetSec: 0, UtcOffsetSec: 0,
ExemplarQuery: true, PrometheusDataQuery: dataquery.PrometheusDataQuery{
Exemplar: kindsys.Ptr(true),
},
} }
b, err := json.Marshal(&qm) b, err := json.Marshal(&qm)
require.NoError(t, err) require.NoError(t, err)
@ -112,7 +117,9 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
qm := models.QueryModel{ qm := models.QueryModel{
LegendFormat: "legend {{app}}", LegendFormat: "legend {{app}}",
UtcOffsetSec: 0, UtcOffsetSec: 0,
RangeQuery: true, PrometheusDataQuery: dataquery.PrometheusDataQuery{
Range: kindsys.Ptr(true),
},
} }
b, err := json.Marshal(&qm) b, err := json.Marshal(&qm)
require.NoError(t, err) require.NoError(t, err)
@ -159,7 +166,9 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
qm := models.QueryModel{ qm := models.QueryModel{
LegendFormat: "", LegendFormat: "",
UtcOffsetSec: 0, UtcOffsetSec: 0,
RangeQuery: true, PrometheusDataQuery: dataquery.PrometheusDataQuery{
Range: kindsys.Ptr(true),
},
} }
b, err := json.Marshal(&qm) b, err := json.Marshal(&qm)
require.NoError(t, err) require.NoError(t, err)
@ -202,7 +211,9 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
qm := models.QueryModel{ qm := models.QueryModel{
LegendFormat: "", LegendFormat: "",
UtcOffsetSec: 0, UtcOffsetSec: 0,
RangeQuery: true, PrometheusDataQuery: dataquery.PrometheusDataQuery{
Range: kindsys.Ptr(true),
},
} }
b, err := json.Marshal(&qm) b, err := json.Marshal(&qm)
require.NoError(t, err) require.NoError(t, err)
@ -243,7 +254,9 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
qm := models.QueryModel{ qm := models.QueryModel{
LegendFormat: "", LegendFormat: "",
UtcOffsetSec: 0, UtcOffsetSec: 0,
RangeQuery: true, PrometheusDataQuery: dataquery.PrometheusDataQuery{
Range: kindsys.Ptr(true),
},
} }
b, err := json.Marshal(&qm) b, err := json.Marshal(&qm)
require.NoError(t, err) require.NoError(t, err)
@ -278,7 +291,9 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
qm := models.QueryModel{ qm := models.QueryModel{
LegendFormat: "legend {{app}}", LegendFormat: "legend {{app}}",
UtcOffsetSec: 0, UtcOffsetSec: 0,
InstantQuery: true, PrometheusDataQuery: dataquery.PrometheusDataQuery{
Instant: kindsys.Ptr(true),
},
} }
b, err := json.Marshal(&qm) b, err := json.Marshal(&qm)
require.NoError(t, err) require.NoError(t, err)
@ -317,7 +332,9 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
qm := models.QueryModel{ qm := models.QueryModel{
LegendFormat: "", LegendFormat: "",
UtcOffsetSec: 0, UtcOffsetSec: 0,
InstantQuery: true, PrometheusDataQuery: dataquery.PrometheusDataQuery{
Instant: kindsys.Ptr(true),
},
} }
b, err := json.Marshal(&qm) b, err := json.Marshal(&qm)
require.NoError(t, err) require.NoError(t, err)
@ -354,20 +371,20 @@ func executeWithHeaders(tctx *testContext, query backend.DataQuery, qr interface
} }
promRes, err := toAPIResponse(qr) 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 { if err != nil {
return nil, err return nil, err
} }
tctx.httpProvider.setResponse(promRes) tctx.httpProvider.setResponse(promRes)
res, err := tctx.queryData.Execute(context.Background(), &req) res, err := tctx.queryData.Execute(context.Background(), &req)
errClose := promRes.Body.Close()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if errClose != nil {
return nil, errClose
}
return res.Responses[req.Queries[0].RefID].Frames, nil return res.Responses[req.Queries[0].RefID].Frames, nil
} }

View File

@ -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")
},
]
},
]
}
}

View File

@ -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;
}

View File

@ -190,7 +190,7 @@ describe('PrometheusDatasource', () => {
}); });
describe('customQueryParams', () => { 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) { function makeQuery(target: PromQuery) {
return { return {
@ -618,7 +618,7 @@ describe('PrometheusDatasource', () => {
describe('interpolateVariablesInQueries', () => { describe('interpolateVariablesInQueries', () => {
it('should call replace function 2 times', () => { it('should call replace function 2 times', () => {
const query = { const query: PromQuery = {
expr: 'test{job="testjob"}', expr: 'test{job="testjob"}',
format: 'time_series', format: 'time_series',
interval: '$Interval', interval: '$Interval',

View File

@ -5,6 +5,7 @@ import { EditorField, EditorRow, EditorSwitch } from '@grafana/experimental';
import { AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui'; import { AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui';
import { getQueryTypeChangeHandler, getQueryTypeOptions } from '../../components/PromExploreExtraField'; import { getQueryTypeChangeHandler, getQueryTypeOptions } from '../../components/PromExploreExtraField';
import { PromQueryFormat } from '../../dataquery.gen';
import { PromQuery } from '../../types'; import { PromQuery } from '../../types';
import { QueryOptionGroup } from '../shared/QueryOptionGroup'; import { QueryOptionGroup } from '../shared/QueryOptionGroup';
@ -28,7 +29,7 @@ export interface Props {
} }
export const PromQueryBuilderOptions = React.memo<Props>(({ query, app, onChange, onRunQuery }) => { export const PromQueryBuilderOptions = React.memo<Props>(({ query, app, onChange, onRunQuery }) => {
const onChangeFormat = (value: SelectableValue<string>) => { const onChangeFormat = (value: SelectableValue<PromQueryFormat>) => {
onChange({ ...query, format: value.value }); onChange({ ...query, format: value.value });
onRunQuery(); onRunQuery();
}; };

View File

@ -8,6 +8,7 @@ import { reportInteraction } from '@grafana/runtime';
import { Button, ConfirmModal } from '@grafana/ui'; import { Button, ConfirmModal } from '@grafana/ui';
import { PromQueryEditorProps } from '../../components/types'; import { PromQueryEditorProps } from '../../components/types';
import { PromQueryFormat } from '../../dataquery.gen';
import { PromQuery } from '../../types'; import { PromQuery } from '../../types';
import { QueryPatternsModal } from '../QueryPatternsModal'; import { QueryPatternsModal } from '../QueryPatternsModal';
import { buildVisualQueryFromString } from '../parsing'; import { buildVisualQueryFromString } from '../parsing';
@ -21,7 +22,7 @@ import { PromQueryBuilderContainer } from './PromQueryBuilderContainer';
import { PromQueryBuilderOptions } from './PromQueryBuilderOptions'; import { PromQueryBuilderOptions } from './PromQueryBuilderOptions';
import { PromQueryCodeEditor } from './PromQueryCodeEditor'; import { PromQueryCodeEditor } from './PromQueryCodeEditor';
export const FORMAT_OPTIONS: Array<SelectableValue<string>> = [ export const FORMAT_OPTIONS: Array<SelectableValue<PromQueryFormat>> = [
{ label: 'Time series', value: 'time_series' }, { label: 'Time series', value: 'time_series' },
{ label: 'Table', value: 'table' }, { label: 'Table', value: 'table' },
{ label: 'Heatmap', value: 'heatmap' }, { label: 'Heatmap', value: 'heatmap' },

View File

@ -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 { PromApplication } from '../../../types/unified-alerting-dto';
import { Prometheus as GenPromQuery } from './dataquery.gen';
import { QueryEditorMode } from './querybuilder/shared/types'; import { QueryEditorMode } from './querybuilder/shared/types';
export interface PromQuery extends DataQuery { export interface PromQuery extends GenPromQuery, DataQuery {
expr: string; /**
format?: string; * Timezone offset to align start & end time on backend
instant?: boolean; */
range?: boolean;
exemplar?: boolean;
hinting?: boolean;
interval?: string;
intervalFactor?: number;
// Timezone offset to align start & end time on backend
utcOffsetSec?: number; utcOffsetSec?: number;
legendFormat?: string; legendFormat?: string;
valueWithRefId?: boolean; valueWithRefId?: boolean;
requestId?: string; requestId?: string;
showingGraph?: boolean; showingGraph?: boolean;
showingTable?: boolean; showingTable?: boolean;
/** Code, Builder or Explain */ hinting?: boolean;
editorMode?: QueryEditorMode; interval?: string;
intervalFactor?: number;
} }
export interface PromOptions extends DataSourceJsonData { export interface PromOptions extends DataSourceJsonData {