mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
236 lines
6.5 KiB
Go
236 lines
6.5 KiB
Go
|
package queryschema
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
"k8s.io/kube-openapi/pkg/spec3"
|
||
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||
|
|
||
|
"github.com/grafana/grafana-plugin-sdk-go/experimental/schemabuilder"
|
||
|
|
||
|
data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
|
||
|
|
||
|
query "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
|
||
|
"github.com/grafana/grafana/pkg/plugins"
|
||
|
)
|
||
|
|
||
|
const QueryRequestSchemaKey = "QueryRequestSchema"
|
||
|
|
||
|
// const QueryPayloadSchemaKey = "QueryPayloadSchema"
|
||
|
// const QuerySaveModelSchemaKey = "QuerySaveModelSchema"
|
||
|
|
||
|
type OASQueryOptions struct {
|
||
|
Swagger *spec3.OpenAPI
|
||
|
PluginJSON *plugins.JSONData
|
||
|
QueryTypes *query.QueryTypeDefinitionList
|
||
|
|
||
|
Root string
|
||
|
QueryPath string // eg "namespaces/{namespace}/query/{name}"
|
||
|
QueryDescription string
|
||
|
QueryExamples map[string]*spec3.Example
|
||
|
}
|
||
|
|
||
|
func AddQueriesToOpenAPI(options OASQueryOptions) error {
|
||
|
oas := options.Swagger
|
||
|
root := options.Root
|
||
|
examples := options.QueryExamples
|
||
|
resourceName := query.QueryTypeDefinitionResourceInfo.GroupResource().Resource
|
||
|
|
||
|
builder := schemabuilder.QuerySchemaOptions{
|
||
|
PluginID: []string{""},
|
||
|
QueryTypes: []data.QueryTypeDefinition{},
|
||
|
}
|
||
|
if options.PluginJSON != nil {
|
||
|
builder.PluginID = []string{options.PluginJSON.ID}
|
||
|
if options.PluginJSON.AliasIDs != nil {
|
||
|
builder.PluginID = append(builder.PluginID, options.PluginJSON.AliasIDs...)
|
||
|
}
|
||
|
}
|
||
|
if options.QueryTypes != nil {
|
||
|
// The SDK type and api type are not the same so we just recreate it here
|
||
|
for _, qt := range options.QueryTypes.Items {
|
||
|
builder.QueryTypes = append(builder.QueryTypes, data.QueryTypeDefinition{
|
||
|
ObjectMeta: data.ObjectMeta{
|
||
|
Name: qt.Name,
|
||
|
},
|
||
|
Spec: qt.Spec,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
if examples == nil {
|
||
|
examples = getExamples(options.QueryTypes)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Rewrite the query path
|
||
|
query := oas.Paths.Paths[root+options.QueryPath]
|
||
|
if query != nil && query.Post != nil {
|
||
|
query.Post.Tags = []string{"Query"}
|
||
|
query.Parameters = []*spec3.Parameter{
|
||
|
{
|
||
|
ParameterProps: spec3.ParameterProps{
|
||
|
Name: "namespace",
|
||
|
In: "path",
|
||
|
Description: "object name and auth scope, such as for teams and projects",
|
||
|
Example: "default",
|
||
|
Required: true,
|
||
|
Schema: spec.StringProperty().UniqueValues(),
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
query.Post.Description = options.QueryDescription
|
||
|
query.Post.Parameters = nil //
|
||
|
query.Post.RequestBody = &spec3.RequestBody{
|
||
|
RequestBodyProps: spec3.RequestBodyProps{
|
||
|
Content: map[string]*spec3.MediaType{
|
||
|
"application/json": {
|
||
|
MediaTypeProps: spec3.MediaTypeProps{
|
||
|
Schema: spec.RefSchema("#/components/schemas/" + QueryRequestSchemaKey),
|
||
|
Examples: examples,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// Remove the {name} hack from from the query
|
||
|
if strings.HasSuffix(options.QueryPath, "/{name}") {
|
||
|
delete(oas.Paths.Paths, root+options.QueryPath)
|
||
|
oas.Paths.Paths[root+strings.TrimSuffix(options.QueryPath, "/{name}")] = query
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Update the validate endpoint
|
||
|
validate := oas.Paths.Paths[root+resourceName+"/{name}/validate"]
|
||
|
if validate != nil && validate.Post != nil {
|
||
|
validate.Post.Description = "Verify if a query payload matches the expected value and return a clean version"
|
||
|
validate.Parameters = []*spec3.Parameter{
|
||
|
{
|
||
|
ParameterProps: spec3.ParameterProps{
|
||
|
Name: "name",
|
||
|
In: "path",
|
||
|
Description: "The query type name, or {any}",
|
||
|
Example: "{any}",
|
||
|
Required: true,
|
||
|
Schema: spec.StringProperty().UniqueValues(),
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// Accept the same payload as the query type
|
||
|
validate.Post.RequestBody = query.Post.RequestBody
|
||
|
}
|
||
|
|
||
|
// Query Request
|
||
|
builder.Mode = schemabuilder.SchemaTypeQueryRequest
|
||
|
s, err := schemabuilder.GetQuerySchema(builder)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// The schema requires some munging to pass validation
|
||
|
// This should likely be fixed in the upstream "GetQuerySchema" function
|
||
|
removeSchemaRefs(s)
|
||
|
s.Description = "Schema for a set of queries sent to the query method"
|
||
|
oas.Components.Schemas[QueryRequestSchemaKey] = s
|
||
|
|
||
|
// // Query Payload (is this useful?)
|
||
|
|
||
|
// opts.Mode = schemabuilder.SchemaTypeQueryPayload
|
||
|
// s, err = schemabuilder.GetQuerySchema(opts)
|
||
|
// if err != nil {
|
||
|
// return err
|
||
|
// }
|
||
|
// delete(s.ExtraProps, "$schema")
|
||
|
// s.Description = "Schema for a single query object including all runtime properties"
|
||
|
// oas.Components.Schemas[QueryPayloadSchemaKey] = s
|
||
|
|
||
|
// // Query Save Model
|
||
|
// opts.Mode = schemabuilder.SchemaTypeSaveModel
|
||
|
// s, err = schemabuilder.GetQuerySchema(opts)
|
||
|
// if err != nil {
|
||
|
// return err
|
||
|
// }
|
||
|
// s.Extensions = nil // remove the $schema
|
||
|
// s.Description = "Valid save model for a single query target"
|
||
|
// oas.Components.Schemas[QuerySaveModelSchemaKey] = s
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func getExamples(queryTypes *query.QueryTypeDefinitionList) map[string]*spec3.Example {
|
||
|
if queryTypes == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
tr := data.TimeRange{From: "now-1h", To: "now"}
|
||
|
examples := map[string]*spec3.Example{}
|
||
|
for _, queryType := range queryTypes.Items {
|
||
|
for idx, example := range queryType.Spec.Examples {
|
||
|
q := data.NewDataQuery(example.SaveModel.Object)
|
||
|
q.RefID = "A"
|
||
|
for _, dis := range queryType.Spec.Discriminators {
|
||
|
_ = q.Set(dis.Field, dis.Value)
|
||
|
}
|
||
|
if q.MaxDataPoints < 1 {
|
||
|
q.MaxDataPoints = 1000
|
||
|
}
|
||
|
if q.IntervalMS < 1 {
|
||
|
q.IntervalMS = 5000 // 5s
|
||
|
}
|
||
|
examples[fmt.Sprintf("%s-%d", example.Name, idx)] = &spec3.Example{
|
||
|
ExampleProps: spec3.ExampleProps{
|
||
|
Summary: example.Name,
|
||
|
Description: example.Description,
|
||
|
Value: data.QueryDataRequest{
|
||
|
TimeRange: tr,
|
||
|
Queries: []data.DataQuery{q},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return examples
|
||
|
}
|
||
|
|
||
|
func removeSchemaRefs(s *spec.Schema) {
|
||
|
if s == nil {
|
||
|
return
|
||
|
}
|
||
|
if s.Schema != "" {
|
||
|
s.Schema = ""
|
||
|
}
|
||
|
// Examples is invalid -- only use the first example
|
||
|
examples, ok := s.ExtraProps["examples"]
|
||
|
if ok && examples != nil {
|
||
|
//fmt.Printf("TODO, use reflection to get first element from: %+v\n", examples)
|
||
|
//s.Example = examples[0]
|
||
|
delete(s.ExtraProps, "examples")
|
||
|
}
|
||
|
|
||
|
removeSchemaRefs(s.Not)
|
||
|
for idx := range s.AllOf {
|
||
|
removeSchemaRefs(&s.AllOf[idx])
|
||
|
}
|
||
|
for idx := range s.AnyOf {
|
||
|
removeSchemaRefs(&s.AnyOf[idx])
|
||
|
}
|
||
|
for k := range s.Properties {
|
||
|
v := s.Properties[k]
|
||
|
removeSchemaRefs(&v)
|
||
|
s.Properties[k] = v
|
||
|
}
|
||
|
if s.Items != nil {
|
||
|
removeSchemaRefs(s.Items.Schema)
|
||
|
for idx := range s.Items.Schemas {
|
||
|
removeSchemaRefs(&s.Items.Schemas[idx])
|
||
|
}
|
||
|
}
|
||
|
if s.AdditionalProperties != nil {
|
||
|
removeSchemaRefs(s.AdditionalProperties.Schema)
|
||
|
}
|
||
|
if s.AdditionalItems != nil {
|
||
|
removeSchemaRefs(s.AdditionalItems.Schema)
|
||
|
}
|
||
|
}
|