From b3186c23cde7d5010542bc3a51ba00d58953851a Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Fri, 28 Nov 2025 13:31:35 +0100 Subject: [PATCH] updated template variable support. --- .../add-support-for-variables.md | 266 +++++++++++++----- 1 file changed, 193 insertions(+), 73 deletions(-) diff --git a/docusaurus/docs/how-to-guides/data-source-plugins/add-support-for-variables.md b/docusaurus/docs/how-to-guides/data-source-plugins/add-support-for-variables.md index f4f0561680..c8e80f025d 100644 --- a/docusaurus/docs/how-to-guides/data-source-plugins/add-support-for-variables.md +++ b/docusaurus/docs/how-to-guides/data-source-plugins/add-support-for-variables.md @@ -48,19 +48,44 @@ export function SimplePanel({ options, data, width, height, replaceVariables }: For data sources, you need to use the `getTemplateSrv`, which returns an instance of `TemplateSrv`. -1. Import `getTemplateSrv` from the `runtime` package: +1. Import `getTemplateSrv` and `TemplateSrv` from the `runtime` package: ```ts - import { getTemplateSrv } from '@grafana/runtime'; + import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; + ``` + +1. Inject `TemplateSrv` in your data source constructor: + + ```ts + export class DataSource extends DataSourceApi { + constructor( + instanceSettings: DataSourceInstanceSettings, + private readonly templateSrv: TemplateSrv = getTemplateSrv() + ) { + super(instanceSettings); + } + } ``` 1. In your `query` method, call the `replace` method with a user-defined template string: ```ts async query(options: DataQueryRequest): Promise { - const query = getTemplateSrv().replace('SELECT * FROM services WHERE id = "$service"', options.scopedVars); + const targets = options.targets.filter((t) => !t.hide); + + const interpolatedTargets = targets.map((target) => { + const rawQuery = this.templateSrv.replace( + target.rawQuery ?? '', + options.scopedVars // include scoped vars for panel/time range + ); + + return { + ...target, + rawQuery, + }; + }); - const data = makeDbQuery(query); + const data = makeDbQuery(interpolatedTargets); return { data }; } @@ -93,6 +118,14 @@ Grafana queries your data source whenever you update a variable. Excessive updat A [query variable](https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables#add-a-query-variable) is a type of variable that allows you to query a data source for the values. By adding support for query variables to your data source plugin, users can create dynamic dashboards based on data from your data source. +To add support for query variables, you need to: + +1. Define a variable query model +2. Implement `metricFindQuery` in your data source +3. Create a `VariableQueryEditor` component +4. Create a `VariableSupport` class +5. Assign the `VariableSupport` to your data source + Let's start by defining a query model for the variable query: ```ts @@ -102,96 +135,183 @@ export interface MyVariableQuery { } ``` -For a data source to support query variables, override the `metricFindQuery` in your `DataSourceApi` class. The `metricFindQuery` function returns an array of `MetricFindValue` which has a single property, `text`: +:::note + +By default, Grafana provides a basic query model and editor for simple text queries. If that's all you need, then leave the query type as `string`: + +::: ```ts -async metricFindQuery(query: MyVariableQuery, options?: any) { - // Retrieve DataQueryResponse based on query. - const response = await this.fetchMetricNames(query.namespace, query.rawQuery); +async metricFindQuery(query: string, options?: any) +``` - // Convert query results to a MetricFindValue[] - const values = response.data.map(frame => ({ text: frame.name })); +#### Implement `metricFindQuery` - return values; +For a data source to support query variables, implement the `metricFindQuery` method in your `DataSourceApi` class. The `metricFindQuery` function returns an array of `MetricFindValue` which has a single property, `text`: + +```ts +import { MetricFindValue } from '@grafana/data'; +import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; +import { MyVariableQuery } from './types'; + +export class DataSource extends DataSourceApi { + constructor( + instanceSettings: DataSourceInstanceSettings, + private readonly templateSrv: TemplateSrv = getTemplateSrv() + ) { + super(instanceSettings); + } + + async metricFindQuery(variableQuery: MyVariableQuery | string, options?: any): Promise { + if (typeof variableQuery === 'string') { + const interpolated = this.templateSrv.replace(variableQuery); + const response = await this.fetchVariableValues({ rawQuery: interpolated }); + return response.map((name) => ({ text: name })); + } + + // If using MyVariableQuery model: + const namespace = this.templateSrv.replace(variableQuery.namespace); + const rawQuery = this.templateSrv.replace(variableQuery.rawQuery); + + const response = await this.fetchMetricNames(namespace, rawQuery); + + // Adapt this to match your backend response + return response.data.map((item: any) => ({ + text: item.name, + // optional: value: item.id, + })); + } + + private async fetchMetricNames(namespace: string, rawQuery: string) { + // call backend/API and return data in a consistent shape + } + + private async fetchVariableValues(args: { rawQuery: string }) { + // simplified variant if using a simple string-based query + } } ``` -:::note +Note that `getTemplateSrv().replace()` is used inside `metricFindQuery` so variable queries can themselves use other variables (e.g. cascading variables). -By default, Grafana provides a basic query model and editor for simple text queries. If that's all you need, then leave the query type as `string`: +#### Create a `VariableQueryEditor` component -::: +Create a custom query editor to allow the user to edit the query model: -```ts -async metricFindQuery(query: string, options?: any) +```tsx title="src/VariableQueryEditor.tsx" +import React, { useState } from 'react'; +import { MyVariableQuery } from './types'; +import { InlineField, InlineFieldRow, Input } from '@grafana/ui'; + +interface VariableQueryProps { + query: MyVariableQuery; + onChange: (query: MyVariableQuery, definition: string) => void; +} + +export const VariableQueryEditor = ({ query, onChange }: VariableQueryProps) => { + const [state, setState] = useState(query); + + const saveQuery = () => { + // Second argument is the human-readable label shown in the variable list + const definition = `${state.rawQuery} (${state.namespace})`; + onChange(state, definition); + }; + + const handleChange = (event: React.FormEvent) => { + const { name, value } = event.currentTarget; + + const next = { + ...state, + [name]: value, + }; + + setState(next); + }; + + return ( + <> + + + + + + + + + + + + ); +}; ``` -Let's create a custom query editor to allow the user to edit the query model. +Grafana saves the query model whenever one of the text fields loses focus (`onBlur`), and then it previews the values returned by `metricFindQuery`. -1. Create a `VariableQueryEditor` component: +The second argument to `onChange` allows you to set a text representation of the query that will appear next to the name of the variable in the variables list. - ```tsx title="src/VariableQueryEditor.tsx" - import React, { useState } from 'react'; - import { MyVariableQuery } from './types'; +#### Create a `VariableSupport` class - interface VariableQueryProps { - query: MyVariableQuery; - onChange: (query: MyVariableQuery, definition: string) => void; - } +Create a `VariableSupport` class that extends `CustomVariableSupport`: - export const VariableQueryEditor = ({ onChange, query }: VariableQueryProps) => { - const [state, setState] = useState(query); - - const saveQuery = () => { - onChange(state, `${state.query} (${state.namespace})`); - }; - - const handleChange = (event: React.FormEvent) => - setState({ - ...state, - [event.currentTarget.name]: event.currentTarget.value, - }); - - return ( - <> -
- Namespace - -
-
- Query - -
- - ); - }; - ``` +```ts title="src/variableSupport.ts" +import { CustomVariableSupport, DataQueryRequest, MetricFindValue } from '@grafana/data'; +import { Observable, from } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { DataSource } from './datasource'; +import { MyVariableQuery } from './types'; +import { VariableQueryEditor } from './VariableQueryEditor'; - Grafana saves the query model whenever one of the text fields loses focus (`onBlur`), and then it previews the values returned by `metricFindQuery`. +export class MyVariableSupport extends CustomVariableSupport { + editor = VariableQueryEditor; - The second argument to `onChange` allows you to set a text representation of the query that will appear next to the name of the variable in the variables list. + constructor(private datasource: DataSource) { + super(); + } -2. Configure your plugin to use the query editor: + query(request: DataQueryRequest): Observable<{ data: MetricFindValue[] }> { + const [query] = request.targets; + const { range, scopedVars } = request; - ```ts - import { VariableQueryEditor } from './VariableQueryEditor'; + const result = this.datasource.metricFindQuery(query, { scopedVars, range }); + return from(result).pipe(map((data) => ({ data }))); + } +} +``` - export const plugin = new DataSourcePlugin(DataSource) - .setQueryEditor(QueryEditor) - .setVariableQueryEditor(VariableQueryEditor); - ``` +#### Assign `VariableSupport` to your data source + +Finally, assign the `VariableSupport` to `this.variables` in your data source constructor: + +```ts title="src/datasource.ts" +import { MyVariableSupport } from './variableSupport'; + +export class DataSource extends DataSourceApi { + constructor( + instanceSettings: DataSourceInstanceSettings, + private readonly templateSrv: TemplateSrv = getTemplateSrv() + ) { + super(instanceSettings); + // assign the variable property to the new VariableSupport + // to add variable support for this datasource + this.variables = new MyVariableSupport(this); + } + // existing functions()… +} +``` That's it! You can now try out the plugin by adding a [query variable](https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables#add-a-query-variable) to your dashboard.