Skip to content

Defining a custom query type🔗

While the SDK ships with support for all core datasources and their query types, it can be extended for private/third-party plugins.

To do so, define a type and a builder for the custom query:

package main

import (
    "encoding/json"

    "github.com/grafana/grafana-foundation-sdk/go/cog"
    "github.com/grafana/grafana-foundation-sdk/go/cog/variants"
)

type CustomQuery struct {
    // RefId and Hide are expected on all queries
    RefId string `json:"refId"`
    Hide  *bool  `json:"hide,omitempty"`

    // Query is specific to the CustomQuery type
    Query string `json:"query,omitempty"`
}

func (resource CustomQuery) Equals(otherCandidate variants.Dataquery) bool {
    if otherCandidate == nil {
        return false
    }

    other, ok := otherCandidate.(CustomQuery)
    if !ok {
        return false
    }

    if resource.RefId != other.RefId {
        return false
    }

    if resource.Hide == nil && other.Hide != nil || resource.Hide != nil && other.Hide == nil {
        return false
    }
    if resource.Hide != nil && *resource.Hide != *other.Hide {
        return false
    }

    return resource.Query == other.Query
}

func (resource CustomQuery) Validate() error {
    return nil
}

// Let cog know that CustomQuery is a Dataquery variant
func (resource CustomQuery) ImplementsDataqueryVariant() {}

func (resource CustomQuery) DataqueryType() string {
    return "custom"
}

func CustomQueryVariantConfig() variants.DataqueryConfig {
    return variants.DataqueryConfig{
        Identifier: "custom", // datasource plugin ID
        DataqueryUnmarshaler: func(raw []byte) (variants.Dataquery, error) {
            dataquery := &CustomQuery{}

            if err := json.Unmarshal(raw, dataquery); err != nil {
                return nil, err
            }

            return dataquery, nil
        },
    }
}

// Compile-time check to ensure that CustomQueryBuilder indeed is
// a builder for variants.Dataquery
var _ cog.Builder[variants.Dataquery] = (*CustomQueryBuilder)(nil)

type CustomQueryBuilder struct {
    internal *CustomQuery
}

func NewCustomQueryBuilder(query string) *CustomQueryBuilder {
    return &CustomQueryBuilder{
        internal: &CustomQuery{Query: query},
    }
}

func (builder *CustomQueryBuilder) Build() (variants.Dataquery, error) {
    if err := builder.internal.Validate(); err != nil {
        return CustomQuery{}, err
    }

    return *builder.internal, nil
}

func (builder *CustomQueryBuilder) RefId(refId string) *CustomQueryBuilder {
    builder.internal.RefId = refId
    return builder
}

func (builder *CustomQueryBuilder) Hide(hide bool) *CustomQueryBuilder {
    builder.internal.Hide = &hide
    return builder
}

Register the type with cog, and use it as usual to build a dashboard:

package main

import (
    "encoding/json"
    "fmt"

    "github.com/grafana/grafana-foundation-sdk/go/cog"
    "github.com/grafana/grafana-foundation-sdk/go/cog/plugins"
    "github.com/grafana/grafana-foundation-sdk/go/dashboard"
    "github.com/grafana/grafana-foundation-sdk/go/timeseries"
)

func main() {
    // Required to correctly unmarshal panels and dataqueries
    plugins.RegisterDefaultPlugins()

    // This lets cog know about the newly created query type and how to unmarshal it.
    cog.NewRuntime().RegisterDataqueryVariant(CustomQueryVariantConfig())

    sampleDashboard, err := dashboard.NewDashboardBuilder("Custom query type").
        Uid("test-custom-query-type").
        Refresh("1m").
        Time("now-30m", "now").
        WithRow(dashboard.NewRowBuilder("Overview")).
        WithPanel(
            timeseries.NewPanelBuilder().
                Title("Sample panel").
                WithTarget(
                    NewCustomQueryBuilder("query here"),
                ),
        ).
        Build()
    if err != nil {
        panic(err)
    }

    dashboardJson, err := json.MarshalIndent(sampleDashboard, "", "  ")
    if err != nil {
        panic(err)
    }

    fmt.Println(string(dashboardJson))
}