Using Dashboard Variables in Query Editor

Hi everyone,

First of all, thank you so much for the hard work on Grafana, the platform is awesome.

I’m using Grafana 7.3.6 and I’m building a datasource plugin with a backend.

My plugin looks as follow:

 export const plugin = new DataSourcePlugin<DataSource, MyDataQuery, MyOptions>(DataSource)
    .setConfigEditor(ConfigEditor)
    .setQueryEditor(QueryEditor)
    .setVariableQueryEditor(VariablesEditor);

What I’m trying to achieve is to use the dashboard variables in the query editor.

This idea is to allow users to either use the dashboard variables or a query editor configuration as a parameter to query the data.

In the query editor, I added a switch “Use dashboard variables” so users decide whether they want to use the template variables or the configuration.

export type Props = QueryEditorProps<DataSource, MyDataQuery, MyOptions>;

export default class QueryEditor extends PureComponent<Props, State> {
  async componentDidMount() {
    // fetch the different values of the query parameter
    // so users can select one in the query editor
    const values = await this.props.datasource.fetchValues(); 
    this.setState({ values });
  }

  render() {
    const { values } = this.state;

    let { value, useDashboardVar } = query;
    if (useDashboardVar) {
      value = this.props.datasource.getSelectedValue();
    }

    return (
      <>
        <div className="gf-form">
          <InlineField label="Use dashboard variables">
            <div className="gf-form-switch">
              <Switch
                value={!!useDashboardVar}
                onChange={handleChange}
              />
            </div>
          </InlineField>
          {!useDashboardVar && (
            <InlineField label="A value">
              <Select
                value={value}
                options={values}
                placeholder="Select a value"
              />
            </InlineField>
          )}
        </div>
        {/* ... */}
      </>
    );
  }
}

The frontend datasource class

export class DataSource extends DataSourceWithBackend<MyDataQuery, MyOptions> {
  
  // retrieve the value selected at the dashboard level
  getSelectedValue(): string {
    const ds = getDataSourceSrv();
    if ('templateSrv' in ds && Array.isArray((ds as DataSourceSrv).templateSrv?.variables)) {
      const variable = ds.templateSrv.variables.find(({ name }) => name === 'var-name');
      if (variable && typeof variable.current !== 'undefined') {
        return variable.current.value;
      }
    }
    return '';
  }

  async fetchValues(): Promise<string[]> {
    // fetch the data from the datasource
  }

  // returns the values when users them as dashboard variables 
  async metricFindQuery({ queryType }: MyVariableQuery, options?: any): Promise<MetricFindValue[]> {
    const values = await this.fetchValues();
    return values.map((value) => ({ text: value }));
  }

  query(request: DataQueryRequest<MyDataQuery>): Observable<DataQueryResponse> {
    const value = this.getSelectedValue();
    const { targets } = request;
    // if users have selected "useDashboardVar",
    // we replace the myParam value with the template variable
    // otherwise we send the query without modification
    // it will use the value selected in the query editor
    const data = targets.map(query => (query.useDashboardVar && value ? { ...query, myParam: value } : query));
    return super.query({ ...request, targets: data });
  }

  //...
}

So this works but it feels kind of hacky and I was wondering whether there was a more straightforward solution. The query editor doesn’t seem to have any knowledge of the dashboard variables.

I tried to use applyTemplateVariables but I only get the “__interval” and “__interval_ms” properties in the ScopedVars objects.

Am I missing something?
Is there a better approach?

I think you should be able to access the templateSrv in the QueryEditor using the getTemplateSrv() singleton, as listed here:

Haven’t tried this in the query editor though, but I don’t see why it wouldn’t work.

Since you’re using dashboard variables with a backend plugin, I’ll warn you about using dashboard variables with alerting queries. Dashboard variables are currently only available in the browser, and alerting queries are made on the server side, which may lead to unexpected results. This is a limitation in Grafana though, not in your plugin.

Thank you for your answer.

If I use “getTemplateSrv”, I would also need to have access to “scopedVars” and do something like :

const value = getTemplateSrv().replace('$varname', options.scopedVars);

but it doesn’t seem to be possible to do it in the query editor.

Ideally if I could do something like in panels :

const value = replaceVariables('$varname')

to get the value.

My use case might be a bit odd though. I’d like users to be able to define this query parameter:

  • either with a dashboard variables, so they can display the panels with this same value. In this case, they wouldn’t be able to set up the alerting.
  • or at the datasource level, and in that case, they would be able to set up the alerting.

Anyway, I might just hide the dropdown in the query editor for now when users choose to use the dashboard variable.

Thank you so much for your help.

I also need access to the variables inside the query editor, but have the same issue. I tried using getTemplateSrv().getVariables(). When I print out to the console, I can see the values exist in the structure. However, getVariables returns VariableModel which has name and label, but no value.

mickaelvieira, how are you able get templateSrv from the ds. Is this the object you get from getDataSourceSrv()?

I get an error when trying to access templateSrv as it is not part of the API.

You should be able to retrieve the current value of the template variables with “getVariables”.

I think “getVariables” does not have the correct signature.

It returns more information and you can access the “current” property which holds the selected value.

import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime'
        
// Extend VariableModel  to avoid ts errors
interface ExtendedVariableModel extends VariableModel {
  current: {
	selected: boolean,
	value: string;
	text: string;
  }
}

export class DataSource extends DataSourceWithBackend<DataQuery, Options> {
  (...)

  getSelectedValue(): string {
	const variables = getTemplateSrv().getVariables() as ExtendedVariableModel[];
	const variable = variables.find(({ name }) => name === 'var-name');
	if (variable && typeof variable.current !== 'undefined') {
	  return variable.current.value;
	}
	return '';
  }

  (...)
}

Thank you, worked perfectly.

1 Like