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 for the custom query:

<?php

use Grafana\Foundation\Cog;

class CustomQuery implements \JsonSerializable, Cog\Dataquery
{
    // RefId and Hide are expected on all queries
    public string $refId;
    public ?bool $hide;

    // Query is specific to the CustomQuery type
    public string $expr;

    /**
     * @param string|null $expr
     * @param string|null $refId
     * @param bool|null $hide
     */
    public function __construct(?string $expr = null, ?string $refId = null, ?bool $hide = null)
    {
        $this->expr = $expr ?: "";
        $this->refId = $refId ?: "";
        $this->hide = $hide;
    }

    /**
     * @param array{expr?: string, refId?: string, hide?: bool} $data
     */
    public static function fromArray(array $data): self
    {
        return new self(
            expr: $data["expr"] ?? null,
            refId: $data["refId"] ?? null,
            hide: $data["hide"] ?? null,
        );
    }

    public function jsonSerialize(): array
    {
        $data = [
            "expr" => $this->expr,
            "refId" => $this->refId,
        ];
        if (isset($this->hide)) {
            $data["hide"] = $this->hide;
        }
        return $data;
    }

    public function dataqueryType(): string
    {
        return "custom";
    }
}

Now, let's define a builder for that type:

<?php

use Grafana\Foundation\Cog;

/**
 * @implements Cog\Builder<CustomQuery>
 */
class CustomQueryBuilder implements Cog\Builder
{
    protected CustomQuery $internal;

    public function __construct(string $query)
    {
        $this->internal = new CustomQuery(expr: $query);
    }

    /**
     * @return CustomQuery
     */
    public function build()
    {
        return $this->internal;
    }

    /**
     * The actual expression/query that will be evaluated by Prometheus
     */
    public function expr(string $expr): static
    {
        $this->internal->expr = $expr;
        return $this;
    }

    /**
     * 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.
     */
    public function refId(string $refId): static
    {
        $this->internal->refId = $refId;
        return $this;
    }

    /**
     * If hide is set to true, Grafana will filter out the response(s) associated with this query before returning it to the panel.
     */
    public function hide(bool $hide): static
    {
        $this->internal->hide = $hide;
        return $this;
    }
}

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

<?php

use Grafana\Foundation\Cog;
use Grafana\Foundation\Dashboard\DashboardBuilder;
use Grafana\Foundation\Dashboard\RowBuilder;
use Grafana\Foundation\Timeseries;

require_once __DIR__.'/vendor/autoload.php';

// This lets cog know about the newly created query type and how to unmarshal it.
Cog\Runtime::get()->registerDataqueryVariant(new Cog\DataqueryConfig(
    identifier: 'custom', // datasource plugin ID
    fromArray: [CustomQuery::class, 'fromArray'],
));

$builder = (new DashboardBuilder(title: 'Custom query type'))
    ->uid('test-custom-query-type')
    ->refresh('1m')
    ->time('now-30m', 'now')
    ->withRow(new RowBuilder('Overview'))
    ->withPanel(
        (new Timeseries\PanelBuilder())
            ->title('Sample panel')
            ->withTarget(new CustomQueryBuilder('query here'))
    )
;

var_dump($builder->build());