Build a Data Source Plugin

Introduction

Grafana supports a wide range of data sources, including Prometheus, MySQL, and even Datadog. There’s a good chance you can already visualize metrics from the systems you have set up. In some cases, though, you already have an in-house metrics solution that you’d like to add to your Grafana dashboards. This tutorial teaches you to build a support for your data source.

What you’ll learn

  • How to build a data source
  • How to construct queries using the query editor
  • How to configure your data source using the config editor

What you’ll need

  • Grafana version 7.0+
  • NodeJS
  • yarn

Set up your environment

Before you can get started building plugins, you need to set up your environment for plugin development.

To discover plugins, Grafana scans a plugin directory, the location of which depends on your operating system.

1. Create a directory called grafana-plugins in your preferred workspace.

2. Find the plugins property in the Grafana configuration file and set the plugins property to the path of your grafana-plugins directory. Refer to the Grafana configuration documentation for more information.

[paths]
plugins = "/path/to/grafana-plugins"

Restart Grafana if it’s already running, to load the new configuration.

Alternative method: Docker

If you don’t want to install Grafana on your local machine, you can use Docker. To set up Grafana for plugin development using Docker, run the following command:

docker run -d -p 3000:3000 -v "$(pwd)"/grafana-plugins:/var/lib/grafana/plugins --name=grafana grafana/grafana

Since Grafana only loads plugins on start-up, you need to restart the container whenever you add or remove a plugin.

docker restart grafana

Create a new plugin

Tooling for modern web development can be tricky to wrap your head around. While you certainly can write you own webpack configuration, for this guide, you’ll be using grafana-toolkit.

grafana-toolkit is a CLI application that simplifies Grafana plugin development, so that you can focus on code. The toolkit takes care of building and testing for you.

1. In the plugin directory, create a panel plugin from template using the plugin:create command:

npx @grafana/toolkit plugin:create my-plugin

2. Change directory.

cd my-plugin

3. Download necessary dependencies:

yarn install

4. Build the plugin:

yarn dev

5. Restart the Grafana server for Grafana to discover your plugin.

6. Open Grafana and go to Configuration -> Plugins. Make sure that your plugin is there.

By default, Grafana logs whenever it discovers a plugin:

INFO[01-01|12:00:00] Registering plugin       logger=plugins name=my-plugin

Anatomy of a plugin

Plugins come in different shapes and sizes. Before we dive deeper, let’s look at some of the properties that are shared by all of them.

Every plugin you create will require at least two files: plugin.json and module.ts.

plugin.json

When Grafana starts, it scans the plugin directory for any subdirectory that contains a plugin.json file. The plugin.json file contains information about your plugin, and tells Grafana about what capabilities and dependencies your plugin needs.

While certain plugin types can have specific configuration options, let’s look at the mandatory ones:

  • type tells Grafana what type of plugin to expect. Grafana supports three types of plugins: panel, datasource, and app.
  • name is what users will see in the list of plugins. If you’re creating a data source, this is typically the name of the database it connects to, such as Prometheus, PostgreSQL, or Stackdriver.
  • id uniquely identifies your plugin, and should start with your GitHub username, to avoid clashing with other plugins.

To see all the available configuration settings for the plugin.json, refer to the plugin.json Schema.

module.ts

After discovering your plugin, Grafana loads the module.ts file, the entrypoint for your plugin. module.ts exposes the implementation of your plugin, which depends on the type of plugin you’re building.

Specifically, module.ts needs to expose an object that extends GrafanaPlugin, and can be any of the following:

Data source plugins

A data source in Grafana must extend the DataSourceApi interface, which requires you to defines two methods: query and testDatasource.

Query data

The query method accepts a query from the user, and returns the data in a format that Grafana recognizes. This is where most of the work happens.

async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse>

The DataQueryRequest object contains the queries, or targets, that the user made, along with context information, like the current time interval. Use this information to query an external database.

After you receive the results from your database query, you need to return it as a _data frame_—the data format that Grafana uses internally.

Positive
The term target originates from Graphite, and the earlier days of Grafana when Graphite was the only supported data source. As Grafana gained support for more data sources, the term “target” became synonymous with any type of query.

Test your data source

testDatasource implements a health check for your data source. For example, Grafana calls this method whenever the user clicks the Save & Test button, after changing the connection settings.

async testDatasource()

Support custom queries

Most data sources offer a way to query specific data. MySQL and PostgreSQL use SQL, while Prometheus has its own query language, called PromQL. No matter what query language your databases are using, Grafana lets you build support for it.

Add support for custom queries to your data source, by implementing a your own query editor, a React component that enables users to build their own queries, through a user-friendly graphical interface.

A query editor can be as simple a text field where the user edits the raw query text, or it can provide a more user-friendly form with drop-down menus and switches, that later gets converted into the raw query text before it gets sent off to the database.

The first step in designing your query editor is to define its query model. The query model defines the user input to your data source. The query model in the starter plugin you created, defines two values: the query text, and a constant.

types.ts

export interface MyQuery extends DataQuery {
  queryText?: string;
  constant: number;
}

Now that you’ve defined the query model you wish to support, the next step is to bind the model to a form. The FormField is a text field component from grafana/ui that lets you register a listener, which will be invoked whenever the form field value changes.

QueryEditor.tsx

<FormField value={queryText || ''} onChange={this.onQueryTextChange} label="Query Text"></FormField>

The registered listener, onQueryTextChange, calls onChange to update the current query:

onQueryTextChange = (event: ChangeEvent<HTMLInputElement>) => {
    const { onChange, query } = this.props;
    onChange({ ...query, queryText: event.target.value });
  };

If you want the query to run automatically after updating the query, you can add a call to onRunQuery:

onRunQuery();

Configure your data source

To access a specific data source, you often need to configure things like hostname, credentials, or authentication method. A config editor lets you users configure your data source plugin to fit their needs.

The config editor looks similar to the query editor, in that it defines a model and binds it to a form.

types.ts

export interface MyDataSourceOptions extends DataSourceJsonData {
  path?: string;
}

Just like query editor, the form field in the config editor calls the registered listener whenever the value changes.

ConfigEditor.tsx

<FormField
  label="Path"
  onChange={this.onPathChange}
  value={jsonData.path || ''}
  placeholder="json field returned to frontend"
/>

To update the options for the data source, call the onOptionsChange method:

onPathChange = (event: ChangeEvent<HTMLInputElement>) => {
    const { onOptionsChange, options } = this.props;
    const jsonData = {
      ...options.jsonData,
      path: event.target.value,
    };
    onOptionsChange({ ...options, jsonData });
  };

Congratulations

Congratulations, you made it to the end of this tutorial!