Prometheus: Create jsonschema spec (#85077)

This commit is contained in:
Ryan McKinley 2024-03-26 16:36:39 +03:00 committed by GitHub
parent b47dc7e8ac
commit e5cf863973
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 679 additions and 14 deletions

View File

@ -8,6 +8,7 @@ import (
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
sdkapi "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
"github.com/grafana/grafana/pkg/promlib/models"
)
@ -55,8 +56,8 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
func healthcheck(ctx context.Context, req *backend.CheckHealthRequest, i *instance) (*backend.CheckHealthResult, error) {
qm := models.QueryModel{
UtcOffsetSec: 0,
CommonQueryProperties: models.CommonQueryProperties{
RefId: refID,
CommonQueryProperties: sdkapi.CommonQueryProperties{
RefID: refID,
},
PrometheusQueryProperties: models.PrometheusQueryProperties{
Expr: "1+1",

View File

@ -1,6 +1,7 @@
package models
import (
"embed"
"encoding/json"
"math"
"strconv"
@ -9,6 +10,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
sdkapi "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
"github.com/prometheus/prometheus/model/labels"
"github.com/grafana/grafana/pkg/promlib/intervalv2"
@ -120,7 +122,7 @@ var safeResolution = 11000
// QueryModel includes both the common and specific values
type QueryModel struct {
PrometheusQueryProperties `json:",inline"`
CommonQueryProperties `json:",inline"`
sdkapi.CommonQueryProperties `json:",inline"`
// 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
@ -128,13 +130,6 @@ type QueryModel struct {
Interval string `json:"interval,omitempty"`
}
// CommonQueryProperties is properties applied to all queries
// NOTE: this will soon be replaced with a struct from the SDK
type CommonQueryProperties struct {
RefId string `json:"refId,omitempty"`
IntervalMs int64 `json:"intervalMs,omitempty"`
}
type TimeRange struct {
Start time.Time
End time.Time
@ -167,7 +162,7 @@ func Parse(query backend.DataQuery, dsScrapeInterval string, intervalCalculator
}
// Final step value for prometheus
calculatedStep, err := calculatePrometheusInterval(model.Interval, dsScrapeInterval, model.IntervalMs, model.IntervalFactor, query, intervalCalculator)
calculatedStep, err := calculatePrometheusInterval(model.Interval, dsScrapeInterval, int64(model.IntervalMS), model.IntervalFactor, query, intervalCalculator)
if err != nil {
return nil, err
}
@ -368,3 +363,11 @@ func AlignTimeRange(t time.Time, step time.Duration, offset int64) time.Time {
stepNano := float64(step.Nanoseconds())
return time.Unix(0, int64(math.Floor((float64(t.UnixNano())+offsetNano)/stepNano)*stepNano-offsetNano)).UTC()
}
//go:embed query.types.json
var f embed.FS
// QueryTypeDefinitionsJSON returns the query type definitions
func QueryTypeDefinitionsJSON() (json.RawMessage, error) {
return f.ReadFile("query.types.json")
}

View File

@ -0,0 +1,13 @@
{
"type": "table",
"targets": [
{
"refId": "A",
"datasource": {
"type": "prometheus",
"uid": "TheUID"
},
"expr": "1+1"
}
]
}

View File

@ -0,0 +1,229 @@
{
"type": "object",
"required": [
"targets",
"type"
],
"properties": {
"targets": {
"type": "array",
"items": {
"description": "PrometheusQueryProperties defines the specific properties used for prometheus",
"type": "object",
"required": [
"expr"
],
"properties": {
"datasource": {
"description": "The datasource",
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"description": "The datasource plugin type",
"type": "string",
"pattern": "^prometheus$"
},
"uid": {
"description": "Datasource UID",
"type": "string"
}
},
"additionalProperties": false
},
"editorMode": {
"description": "what we should show in the editor\n\n\nPossible enum values:\n - `\"builder\"` \n - `\"code\"` ",
"type": "string",
"enum": [
"builder",
"code"
],
"x-enum-description": {}
},
"exemplar": {
"description": "Execute an additional query to identify interesting raw samples relevant for the given expr",
"type": "boolean"
},
"expr": {
"description": "The actual expression/query that will be evaluated by Prometheus",
"type": "string"
},
"format": {
"description": "The response format\n\n\nPossible enum values:\n - `\"time_series\"` \n - `\"table\"` \n - `\"heatmap\"` ",
"type": "string",
"enum": [
"time_series",
"table",
"heatmap"
],
"x-enum-description": {}
},
"hide": {
"description": "true if query is disabled (ie should not be returned to the dashboard)\nNOTE: this does not always imply that the query should not be executed since\nthe results from a hidden query may be used as the input to other queries (SSE etc)",
"type": "boolean"
},
"instant": {
"description": "Returns only the latest value that Prometheus has scraped for the requested time series",
"type": "boolean"
},
"intervalFactor": {
"description": "Used to specify how many times to divide max data points by. We use max data points under query options\nSee https://github.com/grafana/grafana/issues/48081\nDeprecated: use interval",
"type": "integer"
},
"intervalMs": {
"description": "Interval is the suggested duration between time points in a time series query.\nNOTE: the values for intervalMs is not saved in the query model. It is typically calculated\nfrom the interval required to fill a pixels in the visualization",
"type": "number"
},
"legendFormat": {
"description": "Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname",
"type": "string"
},
"maxDataPoints": {
"description": "MaxDataPoints is the maximum number of data points that should be returned from a time series query.\nNOTE: the values for maxDataPoints is not saved in the query model. It is typically calculated\nfrom the number of pixels visible in a visualization",
"type": "integer"
},
"queryType": {
"description": "QueryType is an optional identifier for the type of query.\nIt can be used to distinguish different types of queries.",
"type": "string"
},
"range": {
"description": "Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series",
"type": "boolean"
},
"refId": {
"description": "RefID is the unique identifier of the query, set by the frontend call.",
"type": "string"
},
"resultAssertions": {
"description": "Optionally define expected query result behavior",
"type": "object",
"required": [
"typeVersion"
],
"properties": {
"maxFrames": {
"description": "Maximum frame count",
"type": "integer"
},
"type": {
"description": "Type asserts that the frame matches a known type structure.\n\n\nPossible enum values:\n - `\"\"` \n - `\"timeseries-wide\"` \n - `\"timeseries-long\"` \n - `\"timeseries-many\"` \n - `\"timeseries-multi\"` \n - `\"directory-listing\"` \n - `\"table\"` \n - `\"numeric-wide\"` \n - `\"numeric-multi\"` \n - `\"numeric-long\"` \n - `\"log-lines\"` ",
"type": "string",
"enum": [
"",
"timeseries-wide",
"timeseries-long",
"timeseries-many",
"timeseries-multi",
"directory-listing",
"table",
"numeric-wide",
"numeric-multi",
"numeric-long",
"log-lines"
],
"x-enum-description": {}
},
"typeVersion": {
"description": "TypeVersion is the version of the Type property. Versions greater than 0.0 correspond to the dataplane\ncontract documentation https://grafana.github.io/dataplane/contract/.",
"type": "array",
"maxItems": 2,
"minItems": 2,
"items": {
"type": "integer"
}
}
},
"additionalProperties": false
},
"scope": {
"description": "???",
"type": "object",
"required": [
"title",
"type",
"description",
"category",
"filters"
],
"properties": {
"category": {
"type": "string"
},
"description": {
"type": "string"
},
"filters": {
"type": "array",
"items": {
"description": "ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix)",
"type": "object",
"required": [
"key",
"value",
"operator"
],
"properties": {
"key": {
"type": "string"
},
"operator": {
"type": "string"
},
"value": {
"type": "string"
}
},
"additionalProperties": false
}
},
"title": {
"type": "string"
},
"type": {
"type": "string"
}
},
"additionalProperties": false
},
"timeRange": {
"description": "TimeRange represents the query range\nNOTE: unlike generic /ds/query, we can now send explicit time values in each query\nNOTE: the values for timeRange are not saved in a dashboard, they are constructed on the fly",
"type": "object",
"required": [
"from",
"to"
],
"properties": {
"from": {
"description": "From is the start time of the query.",
"type": "string",
"default": "now-6h",
"examples": [
"now-1h"
]
},
"to": {
"description": "To is the end time of the query.",
"type": "string",
"default": "now",
"examples": [
"now"
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false,
"$schema": "https://json-schema.org/draft-04/schema#"
}
},
"type": {
"description": "the panel type",
"type": "string"
}
},
"additionalProperties": true,
"$schema": "https://json-schema.org/draft-04/schema#"
}

View File

@ -0,0 +1,12 @@
{
"from": "now-1h",
"to": "now",
"queries": [
{
"refId": "A",
"maxDataPoints": 1000,
"intervalMs": 5,
"expr": "1+1"
}
]
}

View File

@ -0,0 +1,239 @@
{
"type": "object",
"required": [
"queries"
],
"properties": {
"$schema": {
"description": "helper",
"type": "string"
},
"debug": {
"type": "boolean"
},
"from": {
"description": "From Start time in epoch timestamps in milliseconds or relative using Grafana time units.",
"type": "string"
},
"queries": {
"type": "array",
"items": {
"description": "PrometheusQueryProperties defines the specific properties used for prometheus",
"type": "object",
"required": [
"expr"
],
"properties": {
"datasource": {
"description": "The datasource",
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"description": "The datasource plugin type",
"type": "string",
"pattern": "^prometheus$"
},
"uid": {
"description": "Datasource UID",
"type": "string"
}
},
"additionalProperties": false
},
"editorMode": {
"description": "what we should show in the editor\n\n\nPossible enum values:\n - `\"builder\"` \n - `\"code\"` ",
"type": "string",
"enum": [
"builder",
"code"
],
"x-enum-description": {}
},
"exemplar": {
"description": "Execute an additional query to identify interesting raw samples relevant for the given expr",
"type": "boolean"
},
"expr": {
"description": "The actual expression/query that will be evaluated by Prometheus",
"type": "string"
},
"format": {
"description": "The response format\n\n\nPossible enum values:\n - `\"time_series\"` \n - `\"table\"` \n - `\"heatmap\"` ",
"type": "string",
"enum": [
"time_series",
"table",
"heatmap"
],
"x-enum-description": {}
},
"hide": {
"description": "true if query is disabled (ie should not be returned to the dashboard)\nNOTE: this does not always imply that the query should not be executed since\nthe results from a hidden query may be used as the input to other queries (SSE etc)",
"type": "boolean"
},
"instant": {
"description": "Returns only the latest value that Prometheus has scraped for the requested time series",
"type": "boolean"
},
"intervalFactor": {
"description": "Used to specify how many times to divide max data points by. We use max data points under query options\nSee https://github.com/grafana/grafana/issues/48081\nDeprecated: use interval",
"type": "integer"
},
"intervalMs": {
"description": "Interval is the suggested duration between time points in a time series query.\nNOTE: the values for intervalMs is not saved in the query model. It is typically calculated\nfrom the interval required to fill a pixels in the visualization",
"type": "number"
},
"legendFormat": {
"description": "Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname",
"type": "string"
},
"maxDataPoints": {
"description": "MaxDataPoints is the maximum number of data points that should be returned from a time series query.\nNOTE: the values for maxDataPoints is not saved in the query model. It is typically calculated\nfrom the number of pixels visible in a visualization",
"type": "integer"
},
"queryType": {
"description": "QueryType is an optional identifier for the type of query.\nIt can be used to distinguish different types of queries.",
"type": "string"
},
"range": {
"description": "Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series",
"type": "boolean"
},
"refId": {
"description": "RefID is the unique identifier of the query, set by the frontend call.",
"type": "string"
},
"resultAssertions": {
"description": "Optionally define expected query result behavior",
"type": "object",
"required": [
"typeVersion"
],
"properties": {
"maxFrames": {
"description": "Maximum frame count",
"type": "integer"
},
"type": {
"description": "Type asserts that the frame matches a known type structure.\n\n\nPossible enum values:\n - `\"\"` \n - `\"timeseries-wide\"` \n - `\"timeseries-long\"` \n - `\"timeseries-many\"` \n - `\"timeseries-multi\"` \n - `\"directory-listing\"` \n - `\"table\"` \n - `\"numeric-wide\"` \n - `\"numeric-multi\"` \n - `\"numeric-long\"` \n - `\"log-lines\"` ",
"type": "string",
"enum": [
"",
"timeseries-wide",
"timeseries-long",
"timeseries-many",
"timeseries-multi",
"directory-listing",
"table",
"numeric-wide",
"numeric-multi",
"numeric-long",
"log-lines"
],
"x-enum-description": {}
},
"typeVersion": {
"description": "TypeVersion is the version of the Type property. Versions greater than 0.0 correspond to the dataplane\ncontract documentation https://grafana.github.io/dataplane/contract/.",
"type": "array",
"maxItems": 2,
"minItems": 2,
"items": {
"type": "integer"
}
}
},
"additionalProperties": false
},
"scope": {
"description": "???",
"type": "object",
"required": [
"title",
"type",
"description",
"category",
"filters"
],
"properties": {
"category": {
"type": "string"
},
"description": {
"type": "string"
},
"filters": {
"type": "array",
"items": {
"description": "ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix)",
"type": "object",
"required": [
"key",
"value",
"operator"
],
"properties": {
"key": {
"type": "string"
},
"operator": {
"type": "string"
},
"value": {
"type": "string"
}
},
"additionalProperties": false
}
},
"title": {
"type": "string"
},
"type": {
"type": "string"
}
},
"additionalProperties": false
},
"timeRange": {
"description": "TimeRange represents the query range\nNOTE: unlike generic /ds/query, we can now send explicit time values in each query\nNOTE: the values for timeRange are not saved in a dashboard, they are constructed on the fly",
"type": "object",
"required": [
"from",
"to"
],
"properties": {
"from": {
"description": "From is the start time of the query.",
"type": "string",
"default": "now-6h",
"examples": [
"now-1h"
]
},
"to": {
"description": "To is the end time of the query.",
"type": "string",
"default": "now",
"examples": [
"now"
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false,
"$schema": "https://json-schema.org/draft-04/schema#"
}
},
"to": {
"description": "To end time in epoch timestamps in milliseconds or relative using Grafana time units.",
"type": "string"
}
},
"additionalProperties": false,
"$schema": "https://json-schema.org/draft-04/schema#"
}

View File

@ -0,0 +1,130 @@
{
"kind": "QueryTypeDefinitionList",
"apiVersion": "query.grafana.app/v0alpha1",
"metadata": {
"resourceVersion": "1711372744903"
},
"items": [
{
"metadata": {
"name": "default",
"resourceVersion": "1711374012365",
"creationTimestamp": "2024-03-25T13:19:04Z"
},
"spec": {
"schema": {
"$schema": "https://json-schema.org/draft-04/schema",
"additionalProperties": false,
"description": "PrometheusQueryProperties defines the specific properties used for prometheus",
"properties": {
"editorMode": {
"description": "what we should show in the editor\n\n\nPossible enum values:\n - `\"builder\"` \n - `\"code\"` ",
"enum": [
"builder",
"code"
],
"type": "string",
"x-enum-description": {}
},
"exemplar": {
"description": "Execute an additional query to identify interesting raw samples relevant for the given expr",
"type": "boolean"
},
"expr": {
"description": "The actual expression/query that will be evaluated by Prometheus",
"type": "string"
},
"format": {
"description": "The response format\n\n\nPossible enum values:\n - `\"time_series\"` \n - `\"table\"` \n - `\"heatmap\"` ",
"enum": [
"time_series",
"table",
"heatmap"
],
"type": "string",
"x-enum-description": {}
},
"instant": {
"description": "Returns only the latest value that Prometheus has scraped for the requested time series",
"type": "boolean"
},
"intervalFactor": {
"description": "Used to specify how many times to divide max data points by. We use max data points under query options\nSee https://github.com/grafana/grafana/issues/48081\nDeprecated: use interval",
"type": "integer"
},
"legendFormat": {
"description": "Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname",
"type": "string"
},
"range": {
"description": "Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series",
"type": "boolean"
},
"scope": {
"additionalProperties": false,
"description": "???",
"properties": {
"category": {
"type": "string"
},
"description": {
"type": "string"
},
"filters": {
"items": {
"additionalProperties": false,
"description": "ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go to avoid import (temp fix)",
"properties": {
"key": {
"type": "string"
},
"operator": {
"type": "string"
},
"value": {
"type": "string"
}
},
"required": [
"key",
"value",
"operator"
],
"type": "object"
},
"type": "array"
},
"title": {
"type": "string"
},
"type": {
"type": "string"
}
},
"required": [
"title",
"type",
"description",
"category",
"filters"
],
"type": "object"
}
},
"required": [
"expr"
],
"type": "object"
},
"examples": [
{
"name": "simple health check",
"saveModel": {
"expr": "1+1"
}
}
]
}
}
]
}

View File

@ -7,6 +7,8 @@ import (
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
sdkapi "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
"github.com/grafana/grafana-plugin-sdk-go/experimental/schemabuilder"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/promlib/intervalv2"
@ -782,3 +784,38 @@ func TestAlignTimeRange(t *testing.T) {
})
}
}
func TestQueryTypeDefinitions(t *testing.T) {
builder, err := schemabuilder.NewSchemaBuilder(
schemabuilder.BuilderOptions{
PluginID: []string{"prometheus"},
ScanCode: []schemabuilder.CodePaths{{
BasePackage: "github.com/grafana/grafana/pkg/promlib/models",
CodePath: "./",
}},
Enums: []reflect.Type{
reflect.TypeOf(models.PromQueryFormatTimeSeries), // pick an example value (not the root)
reflect.TypeOf(models.QueryEditorModeBuilder),
},
})
require.NoError(t, err)
err = builder.AddQueries(
schemabuilder.QueryTypeInfo{
Name: "default",
GoType: reflect.TypeOf(&models.PrometheusQueryProperties{}),
Examples: []sdkapi.QueryExample{
{
Name: "simple health check",
SaveModel: sdkapi.AsUnstructured(
models.PrometheusQueryProperties{
Expr: "1+1",
},
),
},
},
},
)
require.NoError(t, err)
builder.UpdateQueryDefinition(t, "./")
}

View File

@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/experimental"
sdkapi "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/promlib/models"
@ -113,8 +114,8 @@ func loadStoredQuery(fileName string) (*backend.QueryDataRequest, error) {
Expr: sq.Expr,
LegendFormat: sq.LegendFormat,
},
CommonQueryProperties: models.CommonQueryProperties{
IntervalMs: sq.Step * 1000,
CommonQueryProperties: sdkapi.CommonQueryProperties{
IntervalMS: float64(sq.Step * 1000),
},
Interval: fmt.Sprintf("%ds", sq.Step),
}