Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<MyQuery> {
constructor(
instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>,
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<MyQuery>): Promise<DataQueryResponse> {
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 };
}
Expand Down Expand Up @@ -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
Expand All @@ -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<MyQuery> {
constructor(
instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>,
private readonly templateSrv: TemplateSrv = getTemplateSrv()
) {
super(instanceSettings);
}

async metricFindQuery(variableQuery: MyVariableQuery | string, options?: any): Promise<MetricFindValue[]> {
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<MyVariableQuery>(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<HTMLInputElement>) => {
const { name, value } = event.currentTarget;

const next = {
...state,
[name]: value,
};

setState(next);
};

return (
<>
<InlineFieldRow>
<InlineField label="Namespace" labelWidth={20}>
<Input
type="text"
aria-label="Namespace selector"
placeholder="Enter namespace"
value={state.namespace}
onChange={handleChange}
onBlur={saveQuery}
/>
</InlineField>
</InlineFieldRow>
<InlineFieldRow>
<InlineField label="Query" labelWidth={20}>
<Input
type="text"
aria-label="Query selector"
placeholder="Enter query"
value={state.rawQuery}
onChange={handleChange}
onBlur={saveQuery}
/>
</InlineField>
</InlineFieldRow>
</>
);
};
```

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<HTMLInputElement>) =>
setState({
...state,
[event.currentTarget.name]: event.currentTarget.value,
});

return (
<>
<div className="gf-form">
<span className="gf-form-label width-10">Namespace</span>
<input
name="namespace"
className="gf-form-input"
onBlur={saveQuery}
onChange={handleChange}
value={state.namespace}
/>
</div>
<div className="gf-form">
<span className="gf-form-label width-10">Query</span>
<input
name="rawQuery"
className="gf-form-input"
onBlur={saveQuery}
onChange={handleChange}
value={state.rawQuery}
/>
</div>
</>
);
};
```
```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<DataSource, MyVariableQuery> {
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<MyVariableQuery>): 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, MyQuery, MyDataSourceOptions>(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<MyQuery> {
constructor(
instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>,
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.

Expand Down
Loading