mirror of
https://github.com/grafana/grafana.git
synced 2025-07-28 03:52:21 +08:00
Docs: Plugins doc reorganization, part 1 (#69864)
* Initial commit * Prettier fixes * Doc-validator fixes part 1 * Doc-validator fixes part 2 * More doc-validator fixes * More doc-validator fixes * Test * link test * Linnk test * Link test * More fixes * More fixes * Doc-validator fixes * Doc-validator fixes * fix broken link * Fix * Testing * Doc fixes * Link fixes * Fix links * Update docs/sources/developers/plugins/create-a-grafana-plugin/_index.md Co-authored-by: David Harris <david.harris@grafana.com> * Testing * Testing * Testing * Testing * Doc-validator fixes * Doc-validator fixes * Doc-validator fixes * Fix broken links for plugins reorganization project * Prettier fixes * Prettier fixes * Incorporate reviewer feedback * Link fixes * Link fixes * Link fixes * Link fix * Deleted space * Codeowners fix * Change grafana.com links to absolute URLs for Hugo --------- Co-authored-by: David Harris <david.harris@grafana.com>
This commit is contained in:
@ -1,184 +0,0 @@
|
||||
---
|
||||
title: Build a data source backend plugin
|
||||
summary: Create a backend for your data source plugin.
|
||||
description: Create a backend for your data source plugin.
|
||||
id: build-a-data-source-backend-plugin
|
||||
categories: ['plugins']
|
||||
tags: ['beginner']
|
||||
status: Published
|
||||
authors: ['grafana_labs']
|
||||
Feedback Link: https://github.com/grafana/tutorials/issues/new
|
||||
weight: 75
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
For more information about backend plugins, refer to the documentation on [Backend plugins](/docs/grafana/latest/developers/plugins/backend/).
|
||||
|
||||
In this tutorial, you'll:
|
||||
|
||||
- Build a backend for your data source
|
||||
- Implement a health check for your data source
|
||||
- Enable Grafana Alerting for your data source
|
||||
|
||||
{{% class "prerequisite-section" %}}
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
- Knowledge about how data sources are implemented in the frontend.
|
||||
- Grafana 7.0
|
||||
- Go ([Version](https://github.com/grafana/plugin-tools/blob/main/packages/create-plugin/templates/backend/go.mod#L3))
|
||||
- [Mage](https://magefile.org/)
|
||||
- NodeJS ([Version](https://github.com/grafana/plugin-tools/blob/main/packages/create-plugin/templates/common/package.json#L66))
|
||||
- yarn
|
||||
{{% /class %}}
|
||||
|
||||
## Set up your environment
|
||||
|
||||
{{< docs/shared lookup="tutorials/set-up-environment.md" source="grafana" version="latest" >}}
|
||||
|
||||
## Create a new plugin
|
||||
|
||||
To build a backend for your data source plugin, Grafana requires a binary that it can execute when it loads the plugin during start-up. In this guide, we will build a binary using the [Grafana plugin SDK for Go](/docs/grafana/latest/developers/plugins/backend/grafana-plugin-sdk-for-go/).
|
||||
|
||||
The easiest way to get started is to use the Grafana [create-plugin tool](https://www.npmjs.com/package/@grafana/create-plugin). Navigate to the plugin folder that you configured in step 1 and type:
|
||||
|
||||
```
|
||||
npx @grafana/create-plugin@latest
|
||||
```
|
||||
|
||||
Follow the steps and select **datasource** as your plugin type and answer **yes** when prompted to create a backend for your plugin.
|
||||
|
||||
```bash
|
||||
cd my-plugin
|
||||
```
|
||||
|
||||
Install frontend dependencies and build frontend parts of the plugin to _dist_ directory:
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
yarn build
|
||||
```
|
||||
|
||||
Run the following to update [Grafana plugin SDK for Go](/docs/grafana/latest/developers/plugins/backend/grafana-plugin-sdk-for-go/) dependency to the latest minor version:
|
||||
|
||||
```bash
|
||||
go get -u github.com/grafana/grafana-plugin-sdk-go
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
Build backend plugin binaries for Linux, Windows and Darwin to _dist_ directory:
|
||||
|
||||
```bash
|
||||
mage -v
|
||||
```
|
||||
|
||||
Now, let's verify that the plugin you've built so far can be used in Grafana when creating a new data source:
|
||||
|
||||
1. Restart your Grafana instance.
|
||||
1. Open Grafana in your web browser.
|
||||
1. Navigate via the side-menu to **Configuration** -> **Data Sources**.
|
||||
1. Click **Add data source**.
|
||||
1. Find your newly created plugin and select it.
|
||||
1. Enter a name and then click **Save & Test** (ignore any errors reported for now).
|
||||
|
||||
You now have a new data source instance of your plugin that is ready to use in a dashboard:
|
||||
|
||||
1. Navigate via the side-menu to **Create** -> **Dashboard**.
|
||||
1. Click **Add new panel**.
|
||||
1. In the query tab, select the data source you just created.
|
||||
1. A line graph is rendered with one series consisting of two data points.
|
||||
1. Save the dashboard.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### Grafana doesn't load my plugin
|
||||
|
||||
By default, Grafana requires backend plugins to be signed. To load unsigned backend plugins, you need to
|
||||
configure Grafana to [allow unsigned plugins](/docs/grafana/latest/plugins/plugin-signature-verification/#allow-unsigned-plugins).
|
||||
For more information, refer to [Plugin signature verification](/docs/grafana/latest/plugins/plugin-signature-verification/#backend-plugins).
|
||||
|
||||
## Anatomy of a backend plugin
|
||||
|
||||
The folders and files used to build the backend for the data source are:
|
||||
|
||||
| file/folder | description |
|
||||
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Magefile.go` | It’s not a requirement to use mage build files, but we strongly recommend using it so that you can use the build targets provided by the plugin SDK. |
|
||||
| `/go.mod ` | Go modules dependencies, [reference](https://golang.org/cmd/go/#hdr-The_go_mod_file) |
|
||||
| `/src/plugin.json` | A JSON file describing the backend plugin |
|
||||
| `/pkg/main.go` | Starting point of the plugin binary. |
|
||||
|
||||
#### plugin.json
|
||||
|
||||
The [plugin.json](/docs/grafana/latest/developers/plugins/metadata/) file is required for all plugins. When building a backend plugin these properties are important:
|
||||
|
||||
| property | description |
|
||||
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| backend | Should be set to `true` for backend plugins. This tells Grafana that it should start a binary when loading the plugin. |
|
||||
| executable | This is the name of the executable that Grafana expects to start, see [plugin.json reference](/docs/grafana/latest/developers/plugins/metadata/) for details. |
|
||||
| alerting | Should be set to `true` if your backend datasource supports alerting. |
|
||||
|
||||
In the next step we will look at the query endpoint!
|
||||
|
||||
## Implement data queries
|
||||
|
||||
We begin by opening the file `/pkg/plugin/plugin.go`. In this file you will see the `SampleDatasource` struct which implements the [backend.QueryDataHandler](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/backend?tab=doc#QueryDataHandler) interface. The `QueryData` method on this struct is where the data fetching happens for a data source plugin.
|
||||
|
||||
Each request contains multiple queries to reduce traffic between Grafana and plugins. So you need to loop over the slice of queries, process each query, and then return the results of all queries.
|
||||
|
||||
In the tutorial we have extracted a method named `query` to take care of each query model. Since each plugin has their own unique query model, Grafana sends it to the backend plugin as JSON. Therefore the plugin needs to `Unmarshal` the query model into something easier to work with.
|
||||
|
||||
As you can see the sample only returns static numbers. Try to extend the plugin to return other types of data.
|
||||
|
||||
You can read more about how to [build data frames in our docs](/docs/grafana/latest/developers/plugins/data-frames/).
|
||||
|
||||
## Add support for health checks
|
||||
|
||||
Implementing the health check handler allows Grafana to verify that a data source has been configured correctly.
|
||||
|
||||
When editing a data source in Grafana's UI, you can **Save & Test** to verify that it works as expected.
|
||||
|
||||
In this sample data source, there is a 50% chance that the health check will be successful. Make sure to return appropriate error messages to the users, informing them about what is misconfigured in the data source.
|
||||
|
||||
Open `/pkg/plugin/plugin.go`. In this file you'll see that the `SampleDatasource` struct also implements the [backend.CheckHealthHandler](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/backend?tab=doc#CheckHealthHandler) interface. Navigate to the `CheckHealth` method to see how the health check for this sample plugin is implemented.
|
||||
|
||||
## Add authentication
|
||||
|
||||
Implementing authentication allows your plugin to access protected resources like databases or APIs. You can read more about how to [authenticate using a backend plugin in our docs](/docs/grafana/latest/developers/plugins/add-authentication-for-data-source-plugins/#authenticate-using-a-backend-plugin).
|
||||
|
||||
## Enable Grafana Alerting
|
||||
|
||||
1. Open _src/plugin.json_.
|
||||
1. Add the top level `backend` property with a value of `true` to specify that your plugin supports Grafana Alerting, e.g.
|
||||
```json
|
||||
{
|
||||
...
|
||||
"backend": true,
|
||||
"executable": "gpx_simple_datasource_backend",
|
||||
"alerting": true,
|
||||
"info": {
|
||||
...
|
||||
}
|
||||
```
|
||||
1. Rebuild frontend parts of the plugin to _dist_ directory:
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
|
||||
1. Restart your Grafana instance.
|
||||
1. Open Grafana in your web browser.
|
||||
1. Open the dashboard you created earlier in the _Create a new plugin_ step.
|
||||
1. Edit the existing panel.
|
||||
1. Click on the _Alert_ tab.
|
||||
1. Click on _Create Alert_ button.
|
||||
1. Edit condition and specify _IS ABOVE 10_. Change _Evaluate every_ to _10s_ and clear the _For_ field to make the alert rule evaluate quickly.
|
||||
1. Save the dashboard.
|
||||
1. After some time the alert rule evaluates and transitions into _Alerting_ state.
|
||||
|
||||
## Summary
|
||||
|
||||
In this tutorial you created a backend for your data source plugin.
|
@ -1,377 +0,0 @@
|
||||
---
|
||||
title: Build a data source plugin
|
||||
summary: Create a plugin to add support for your own data sources.
|
||||
description: Create a plugin to add support for your own data sources.
|
||||
id: build-a-data-source-plugin
|
||||
categories: ['plugins']
|
||||
tags: ['beginner']
|
||||
status: Published
|
||||
authors: ['grafana_labs']
|
||||
Feedback Link: https://github.com/grafana/tutorials/issues/new
|
||||
weight: 70
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
In this tutorial, you'll:
|
||||
|
||||
- Build a data source to visualize a sine wave
|
||||
- Construct queries using the query editor
|
||||
- Configure your data source using the config editor
|
||||
|
||||
{{% class "prerequisite-section" %}}
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Grafana >=7.0
|
||||
- NodeJS >=14
|
||||
- yarn
|
||||
{{% /class %}}
|
||||
|
||||
## Set up your environment
|
||||
|
||||
{{< docs/shared lookup="tutorials/set-up-environment.md" source="grafana" version="latest" >}}
|
||||
|
||||
## Create a new plugin
|
||||
|
||||
{{< docs/shared lookup="tutorials/create-plugin.md" source="grafana" version="latest" >}}
|
||||
|
||||
To learn how to create a backend data source plugin, see [Build a data source backend plugin](/docs/grafana/latest/tutorials/build-a-data-source-backend-plugin).
|
||||
|
||||
## Anatomy of a plugin
|
||||
|
||||
{{< docs/shared lookup="tutorials/plugin-anatomy.md" source="grafana" version="latest" >}}
|
||||
|
||||
## Data source plugins
|
||||
|
||||
A data source in Grafana must extend the `DataSourceApi` interface, which requires you to define two methods: `query` and `testDatasource`.
|
||||
|
||||
### The `query` method
|
||||
|
||||
The `query` method is the heart of any data source plugin. It accepts a query from the user, retrieves the data from an external database, and returns the data in a format that Grafana recognizes.
|
||||
|
||||
```
|
||||
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse>
|
||||
```
|
||||
|
||||
The `options` 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.
|
||||
|
||||
> 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()
|
||||
```
|
||||
|
||||
## Data frames
|
||||
|
||||
Nowadays there are countless different databases, each with their own ways of querying data. To be able to support all the different data formats, Grafana consolidates the data into a unified data structure called _data frames_.
|
||||
|
||||
Let's see how to create and return a data frame from the `query` method. In this step, you'll change the code in the starter plugin to return a [sine wave](https://en.wikipedia.org/wiki/Sine_wave).
|
||||
|
||||
1. In the current `query` method, remove the code inside the `map` function.
|
||||
|
||||
The `query` method now look like this:
|
||||
|
||||
```ts
|
||||
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
|
||||
const { range } = options;
|
||||
const from = range!.from.valueOf();
|
||||
const to = range!.to.valueOf();
|
||||
|
||||
const data = options.targets.map(target => {
|
||||
// Your code goes here.
|
||||
});
|
||||
|
||||
return { data };
|
||||
}
|
||||
```
|
||||
|
||||
1. In the `map` function, use the `lodash/defaults` package to set default values for query properties that haven't been set:
|
||||
|
||||
```ts
|
||||
const query = defaults(target, defaultQuery);
|
||||
```
|
||||
|
||||
1. Create a default query at the top of datasource.ts:
|
||||
|
||||
```ts
|
||||
export const defaultQuery: Partial<MyQuery> = {
|
||||
constant: 6.5,
|
||||
};
|
||||
```
|
||||
|
||||
1. Create a data frame with a time field and a number field:
|
||||
|
||||
```ts
|
||||
const frame = new MutableDataFrame({
|
||||
refId: query.refId,
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time },
|
||||
{ name: 'value', type: FieldType.number },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
`refId` needs to be set to tell Grafana which query that generated this date frame.
|
||||
|
||||
Next, we'll add the actual values to the data frame. Don't worry about the math used to calculate the values.
|
||||
|
||||
1. Create a couple of helper variables:
|
||||
|
||||
```ts
|
||||
// duration of the time range, in milliseconds.
|
||||
const duration = to - from;
|
||||
|
||||
// step determines how close in time (ms) the points will be to each other.
|
||||
const step = duration / 1000;
|
||||
```
|
||||
|
||||
1. Add the values to the data frame:
|
||||
|
||||
```ts
|
||||
for (let t = 0; t < duration; t += step) {
|
||||
frame.add({ time: from + t, value: Math.sin((2 * Math.PI * t) / duration) });
|
||||
}
|
||||
```
|
||||
|
||||
The `frame.add()` accepts an object where the keys corresponds to the name of each field in the data frame.
|
||||
|
||||
1. Return the data frame:
|
||||
|
||||
```ts
|
||||
return frame;
|
||||
```
|
||||
|
||||
1. Rebuild the plugin and try it out.
|
||||
|
||||
Your data source is now sending data frames that Grafana can visualize. Next, we'll look at how you can control the frequency of the sine wave by defining a _query_.
|
||||
|
||||
> In this example, we're generating timestamps from the current time range. This means that you'll get the same graph no matter what time range you're using. In practice, you'd instead use the timestamps returned by your database.
|
||||
|
||||
## Define a query
|
||||
|
||||
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 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 as 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.
|
||||
|
||||
### Define the query model
|
||||
|
||||
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.
|
||||
|
||||
We want to be able to control the frequency of the sine wave, so let's add another property.
|
||||
|
||||
1. Add a new number property called `frequency` to the query model:
|
||||
|
||||
**src/types.ts**
|
||||
|
||||
```ts
|
||||
export interface MyQuery extends DataQuery {
|
||||
queryText?: string;
|
||||
constant: number;
|
||||
frequency: number;
|
||||
}
|
||||
```
|
||||
|
||||
1. Set a default value to the new `frequency` property:
|
||||
|
||||
```ts
|
||||
export const defaultQuery: Partial<MyQuery> = {
|
||||
constant: 6.5,
|
||||
frequency: 1.0,
|
||||
};
|
||||
```
|
||||
|
||||
### Bind the model to a form
|
||||
|
||||
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.
|
||||
|
||||
1. Define the `frequency` from the `query` object and add a new form field to the query editor to control the new frequency property in the `render` method.
|
||||
|
||||
**QueryEditor.tsx**
|
||||
|
||||
```ts
|
||||
const { queryText, constant, frequency } = query;
|
||||
```
|
||||
|
||||
```ts
|
||||
<InlineField label="Frequency" labelWidth={16}>
|
||||
<Input onChange={onFrequencyChange} value={frequency} />
|
||||
</InlineField>
|
||||
```
|
||||
|
||||
1. Add a event listener for the new property.
|
||||
|
||||
```ts
|
||||
const onFrequencyChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...query, frequency: parseFloat(event.target.value) });
|
||||
// executes the query
|
||||
onRunQuery();
|
||||
};
|
||||
```
|
||||
|
||||
The registered listener, `onFrequencyChange`, calls `onChange` to update the current query with the value from the form field.
|
||||
|
||||
`onRunQuery();` tells Grafana to run the query after each change. For fast queries, this is recommended to provide a more responsive experience.
|
||||
|
||||
### Use the property
|
||||
|
||||
The new query model is now ready to use in our `query` method.
|
||||
|
||||
1. In the `query` method, use the `frequency` property to adjust our equation.
|
||||
|
||||
```ts
|
||||
frame.add({ time: from + t, value: Math.sin((2 * Math.PI * query.frequency * t) / duration) });
|
||||
```
|
||||
|
||||
## 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 your 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.
|
||||
|
||||
Since we're not actually connecting to an external database in our sine wave example, we don't really need many options. To show you how you can add an option however, we're going to add the _wave resolution_ as an option.
|
||||
|
||||
The resolution controls how close in time the data points are to each other. A higher resolution means more points closer together, at the cost of more data being processed.
|
||||
|
||||
### Define the options model
|
||||
|
||||
1. Add a new number property called `resolution` to the options model.
|
||||
|
||||
**types.ts**
|
||||
|
||||
```ts
|
||||
export interface MyDataSourceOptions extends DataSourceJsonData {
|
||||
path?: string;
|
||||
resolution?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### Bind the model to a form
|
||||
|
||||
Just like query editor, the form field in the config editor calls the registered listener whenever the value changes.
|
||||
|
||||
1. Add a new form field to the query editor to control the new resolution option.
|
||||
|
||||
**ConfigEditor.tsx**
|
||||
|
||||
```ts
|
||||
<InlineField label="Resolution" labelWidth={12}>
|
||||
<Input onChange={onResolutionChange} value={jsonData.resolution || ''} placeholder="Enter a number" width={40} />
|
||||
</InlineField>
|
||||
```
|
||||
|
||||
1. Add a event listener for the new option.
|
||||
|
||||
```ts
|
||||
const onResolutionChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const jsonData = {
|
||||
...options.jsonData,
|
||||
resolution: parseFloat(event.target.value),
|
||||
};
|
||||
onOptionsChange({ ...options, jsonData });
|
||||
};
|
||||
```
|
||||
|
||||
The `onResolutionChange` listener calls `onOptionsChange` to update the current options with the value from the form field.
|
||||
|
||||
### Use the option
|
||||
|
||||
1. Create a property called `resolution` to the `DataSource` class.
|
||||
|
||||
```ts
|
||||
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
|
||||
resolution: number;
|
||||
|
||||
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
|
||||
super(instanceSettings);
|
||||
|
||||
this.resolution = instanceSettings.jsonData.resolution || 1000.0;
|
||||
}
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
1. In the `query` method, use the `resolution` property to calculate the step size.
|
||||
|
||||
**src/datasource.ts**
|
||||
|
||||
```ts
|
||||
const step = duration / this.resolution;
|
||||
```
|
||||
|
||||
## Get data from an external API
|
||||
|
||||
So far, you've generated the data returned by the data source. A more realistic use case would be to fetch data from an external API.
|
||||
|
||||
While you can use something like [axios](https://github.com/axios/axios) or the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to make requests, we recommend using the [`getBackendSrv`](/docs/grafana/latest/packages_api/runtime/getbackendsrv/) function from the [grafana/runtime](/docs/grafana/latest/packages_api/runtime/) package.
|
||||
|
||||
The main advantage of `getBackendSrv` is that it proxies requests through the Grafana server rather making the request from the browser. This is strongly recommended when making authenticated requests to an external API. For more information on authenticating external requests, refer to [Add authentication for data source plugins](/docs/grafana/latest/developers/plugins/add-authentication-for-data-source-plugins/).
|
||||
|
||||
1. Import `getBackendSrv`.
|
||||
|
||||
**src/datasource.ts**
|
||||
|
||||
```ts
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
```
|
||||
|
||||
1. Create a helper method `doRequest` and use the `datasourceRequest` method to make a request to your API. Replace `https://api.example.com/metrics` to point to your own API endpoint.
|
||||
|
||||
```ts
|
||||
async doRequest(query: MyQuery) {
|
||||
const result = await getBackendSrv().datasourceRequest({
|
||||
method: "GET",
|
||||
url: "https://api.example.com/metrics",
|
||||
params: query,
|
||||
})
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
1. Make a request for each query. `Promises.all` waits for all requests to finish before returning the data.
|
||||
|
||||
```ts
|
||||
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
|
||||
const promises = options.targets.map((query) =>
|
||||
this.doRequest(query).then((response) => {
|
||||
const frame = new MutableDataFrame({
|
||||
refId: query.refId,
|
||||
fields: [
|
||||
{ name: "Time", type: FieldType.time },
|
||||
{ name: "Value", type: FieldType.number },
|
||||
],
|
||||
});
|
||||
|
||||
response.data.forEach((point: any) => {
|
||||
frame.appendRow([point.time, point.value]);
|
||||
});
|
||||
|
||||
return frame;
|
||||
})
|
||||
);
|
||||
|
||||
return Promise.all(promises).then((data) => ({ data }));
|
||||
}
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
In this tutorial you built a complete data source plugin for Grafana that uses a query editor to control what data to visualize. You've added a data source option, commonly used to set connection options and more.
|
||||
|
||||
### Learn more
|
||||
|
||||
Learn how you can improve your plugin even further, by reading our advanced guides:
|
||||
|
||||
- [Add support for variables](/docs/grafana/latest/developers/plugins/add-support-for-variables/)
|
||||
- [Add support for annotations](/docs/grafana/latest/developers/plugins/add-support-for-annotations/)
|
||||
- [Add support for Explore queries](/docs/grafana/latest/developers/plugins/add-support-for-explore-queries/)
|
||||
- [Build a logs data source](/docs/grafana/latest/developers/plugins/build-a-logs-data-source-plugin/)
|
@ -1,235 +0,0 @@
|
||||
---
|
||||
title: Build a panel plugin with D3.js
|
||||
summary: Learn how to use D3.js in your panel plugins.
|
||||
description: how to use D3.js in your panel plugins.
|
||||
id: build-a-panel-plugin-with-d3
|
||||
categories: ['plugins']
|
||||
tags: ['beginner']
|
||||
status: Published
|
||||
authors: ['grafana_labs']
|
||||
Feedback Link: https://github.com/grafana/tutorials/issues/new
|
||||
weight: 60
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Panels are the building blocks of Grafana, and allow you to visualize data in different ways. This tutorial gives you a hands-on walkthrough of creating your own panel using [D3.js](https://d3js.org/).
|
||||
|
||||
For more information about panels, refer to the documentation on [Panels](/docs/grafana/latest/features/panels/panels/).
|
||||
|
||||
In this tutorial, you'll:
|
||||
|
||||
- Build a simple panel plugin to visualize a bar chart.
|
||||
- Learn how to use D3.js to build a panel using data-driven transformations.
|
||||
|
||||
{{% class "prerequisite-section" %}}
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Grafana 7.0
|
||||
- NodeJS 12.x
|
||||
- yarn
|
||||
{{% /class %}}
|
||||
|
||||
## Set up your environment
|
||||
|
||||
{{< docs/shared lookup="tutorials/set-up-environment.md" source="grafana" version="latest" >}}
|
||||
|
||||
## Create a new plugin
|
||||
|
||||
{{< docs/shared lookup="tutorials/create-plugin.md" source="grafana" version="latest" >}}
|
||||
|
||||
## Data-driven documents
|
||||
|
||||
[D3.js](https://d3js.org/) is a JavaScript library for manipulating documents based on data. It lets you transform arbitrary data into HTML, and is commonly used for creating visualizations.
|
||||
|
||||
Wait a minute. Manipulating documents based on data? That's sounds an awful lot like React. In fact, much of what you can accomplish with D3 you can already do with React. So before we start looking at D3, let's see how you can create an SVG from data, using only React.
|
||||
|
||||
In **SimplePanel.tsx**, change `SimplePanel` to return an `svg` with a `rect` element.
|
||||
|
||||
```ts
|
||||
export const SimplePanel = ({ options, data, width, height }: Props) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<svg width={width} height={height}>
|
||||
<rect x={0} y={0} width={10} height={10} fill={theme.palette.greenBase} />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
One single rectangle might not be very exciting, so let's see how you can create rectangles from data.
|
||||
|
||||
1. Create some data that we can visualize.
|
||||
|
||||
```ts
|
||||
const values = [4, 8, 15, 16, 23, 42];
|
||||
```
|
||||
|
||||
1. Calculate the height of each bar based on the height of the panel.
|
||||
|
||||
```ts
|
||||
const barHeight = height / values.length;
|
||||
```
|
||||
|
||||
1. Inside a SVG group, `g`, create a `rect` element for every value in the dataset. Each rectangle uses the value as its width.
|
||||
|
||||
```ts
|
||||
return (
|
||||
<svg width={width} height={height}>
|
||||
<g>
|
||||
{values.map((value, i) => (
|
||||
<rect x={0} y={i * barHeight} width={value} height={barHeight - 1} fill={theme.palette.greenBase} />
|
||||
))}
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
```
|
||||
|
||||
1. Rebuild the plugin and reload your browser to see the changes you've made.
|
||||
|
||||
As you can see, React is perfectly capable of dynamically creating HTML elements. In fact, creating elements using React is often faster than creating them using D3.
|
||||
|
||||
So why would you use even use D3? In the next step, we'll see how you can take advantage of D3's data transformations.
|
||||
|
||||
## Transform data using D3.js
|
||||
|
||||
In this step, you'll see how you can transform data using D3 before rendering it using React.
|
||||
|
||||
D3 is already bundled with Grafana, and you can access it by importing the `d3` package. However, we're going to need the type definitions while developing.
|
||||
|
||||
1. Install the D3 type definitions:
|
||||
|
||||
```bash
|
||||
yarn add --dev @types/d3
|
||||
```
|
||||
|
||||
1. Import `d3` in **SimplePanel.tsx**.
|
||||
|
||||
```ts
|
||||
import * as d3 from 'd3';
|
||||
```
|
||||
|
||||
In the previous step, we had to define the width of each bar in pixels. Instead, let's use _scales_ from the D3 library to make the width of each bar depend on the width of the panel.
|
||||
|
||||
Scales are functions that map a range of values to another range of values. In this case, we want to map the values in our datasets to a position within our panel.
|
||||
|
||||
1. Create a scale to map a value between 0 and the maximum value in the dataset, to a value between 0 and the width of the panel. We'll be using this to calculate the width of the bar.
|
||||
|
||||
```ts
|
||||
const scale = d3
|
||||
.scaleLinear()
|
||||
.domain([0, d3.max(values) || 0.0])
|
||||
.range([0, width]);
|
||||
```
|
||||
|
||||
1. Pass the value to the scale function to calculate the width of the bar in pixels.
|
||||
|
||||
```ts
|
||||
return (
|
||||
<svg width={width} height={height}>
|
||||
<g>
|
||||
{values.map((value, i) => (
|
||||
<rect x={0} y={i * barHeight} width={scale(value)} height={barHeight - 1} fill={theme.palette.greenBase} />
|
||||
))}
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
```
|
||||
|
||||
As you can see, even if we're using React to render the actual elements, the D3 library contains useful tools that you can use to transform your data before rendering it.
|
||||
|
||||
## Add an axis
|
||||
|
||||
Another useful tool in the D3 toolbox is the ability to generate _axes_. Adding axes to our chart makes it easier for the user to understand the differences between each bar.
|
||||
|
||||
Let's see how you can use D3 to add a horizontal axis to your bar chart.
|
||||
|
||||
1. Create a D3 axis. Notice that by using the same scale as before, we make sure that the bar width aligns with the ticks on the axis.
|
||||
|
||||
```ts
|
||||
const axis = d3.axisBottom(scale);
|
||||
```
|
||||
|
||||
1. Generate the axis. While D3 needs to generate the elements for the axis, we can encapsulate it by generating them within an anonymous function which we pass as a `ref` to a group element `g`.
|
||||
|
||||
```ts
|
||||
<g
|
||||
ref={(node) => {
|
||||
d3.select(node).call(axis as any);
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
By default, the axis renders at the top of the SVG element. We'd like to move it to the bottom, but to do that, we first need to make room for it by decreasing the height of each bar.
|
||||
|
||||
1. Calculate the new bar height based on the padded height.
|
||||
|
||||
```ts
|
||||
const padding = 20;
|
||||
const chartHeight = height - padding;
|
||||
const barHeight = chartHeight / values.length;
|
||||
```
|
||||
|
||||
1. Translate the axis by adding a transform to the `g` element.
|
||||
|
||||
```ts
|
||||
<g
|
||||
transform={`translate(0, ${chartHeight})`}
|
||||
ref={(node) => {
|
||||
d3.select(node).call(axis as any);
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
Congrats! You've created a simple and responsive bar chart.
|
||||
|
||||
## Complete example
|
||||
|
||||
```ts
|
||||
import React from 'react';
|
||||
import { PanelProps } from '@grafana/data';
|
||||
import { SimpleOptions } from 'types';
|
||||
import { useTheme } from '@grafana/ui';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
interface Props extends PanelProps<SimpleOptions> {}
|
||||
|
||||
export const SimplePanel = ({ options, data, width, height }: Props) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const values = [4, 8, 15, 16, 23, 42];
|
||||
|
||||
const scale = d3
|
||||
.scaleLinear()
|
||||
.domain([0, d3.max(values) || 0.0])
|
||||
.range([0, width]);
|
||||
|
||||
const axis = d3.axisBottom(scale);
|
||||
|
||||
const padding = 20;
|
||||
const chartHeight = height - padding;
|
||||
const barHeight = chartHeight / values.length;
|
||||
|
||||
return (
|
||||
<svg width={width} height={height}>
|
||||
<g>
|
||||
{values.map((value, i) => (
|
||||
<rect x={0} y={i * barHeight} width={scale(value)} height={barHeight - 1} fill={theme.palette.greenBase} />
|
||||
))}
|
||||
</g>
|
||||
<g
|
||||
transform={`translate(0, ${chartHeight})`}
|
||||
ref={(node) => {
|
||||
d3.select(node).call(axis as any);
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
In this tutorial you built a panel plugin with D3.js.
|
@ -1,259 +0,0 @@
|
||||
---
|
||||
title: Build a panel plugin
|
||||
summary: Learn how to create a custom visualization for your dashboards.
|
||||
description: Learn how to create a custom visualization for your dashboards.
|
||||
id: build-a-panel-plugin
|
||||
categories: ['plugins']
|
||||
tags: ['beginner']
|
||||
status: Published
|
||||
authors: ['grafana_labs']
|
||||
Feedback Link: https://github.com/grafana/tutorials/issues/new
|
||||
weight: 50
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Panels are the building blocks of Grafana. They allow you to visualize data in different ways. While Grafana has several types of panels already built-in, you can also build your own panel, to add support for other visualizations.
|
||||
|
||||
For more information about panels, refer to the documentation on [Panels](/docs/grafana/latest/panels/).
|
||||
|
||||
{{% class "prerequisite-section" %}}
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Grafana >=7.0
|
||||
- NodeJS >=14
|
||||
- yarn
|
||||
{{% /class %}}
|
||||
|
||||
## Set up your environment
|
||||
|
||||
{{< docs/shared lookup="tutorials/set-up-environment.md" source="grafana" version="latest" >}}
|
||||
|
||||
## Create a new plugin
|
||||
|
||||
{{< docs/shared lookup="tutorials/create-plugin.md" source="grafana" version="latest" >}}
|
||||
|
||||
## Anatomy of a plugin
|
||||
|
||||
{{< docs/shared lookup="tutorials/plugin-anatomy.md" source="grafana" version="latest" >}}
|
||||
|
||||
## Panel plugins
|
||||
|
||||
Since Grafana 6.x, panels are [ReactJS components](https://reactjs.org/docs/components-and-props.html).
|
||||
|
||||
Prior to Grafana 6.0, plugins were written in [AngularJS](https://angular.io/). Even though we still support plugins written in AngularJS, we highly recommend that you write new plugins using ReactJS.
|
||||
|
||||
### Panel properties
|
||||
|
||||
The [PanelProps](https://github.com/grafana/grafana/blob/747b546c260f9a448e2cb56319f796d0301f4bb9/packages/grafana-data/src/types/panel.ts#L27-L40) interface exposes runtime information about the panel, such as panel dimensions, and the current time range.
|
||||
|
||||
You can access the panel properties through `props`, as seen in your plugin.
|
||||
|
||||
**src/SimplePanel.tsx**
|
||||
|
||||
```js
|
||||
const { options, data, width, height } = props;
|
||||
```
|
||||
|
||||
### Development workflow
|
||||
|
||||
Next, you'll learn the basic workflow of making a change to your panel, building it, and reloading Grafana to reflect the changes you made.
|
||||
|
||||
First, you need to add your panel to a dashboard:
|
||||
|
||||
1. Open Grafana in your browser.
|
||||
1. Create a new dashboard, and add a new panel.
|
||||
1. Select your panel from the list of visualization types.
|
||||
1. Save the dashboard.
|
||||
|
||||
Now that you can view your panel, try making a change to the panel plugin:
|
||||
|
||||
1. In `SimplePanel.tsx`, change the fill color of the circle.
|
||||
1. Run `yarn dev` to build the plugin.
|
||||
1. In the browser, reload Grafana with the new changes.
|
||||
|
||||
## Add panel options
|
||||
|
||||
Sometimes you want to offer the users of your panel an option to configure the behavior of your plugin. By configuring _panel options_ for your plugin, your panel will be able to accept user input.
|
||||
|
||||
In the previous step, you changed the fill color of the circle in the code. Let's change the code so that the plugin user can configure the color from the panel editor.
|
||||
|
||||
#### Add an option
|
||||
|
||||
Panel options are defined in a _panel options object_. `SimpleOptions` is an interface that describes the options object.
|
||||
|
||||
1. In `types.ts`, add a `CircleColor` type to hold the colors the users can choose from:
|
||||
|
||||
```
|
||||
type CircleColor = 'red' | 'green' | 'blue';
|
||||
```
|
||||
|
||||
1. In the `SimpleOptions` interface, add a new option called `color`:
|
||||
|
||||
```
|
||||
color: CircleColor;
|
||||
```
|
||||
|
||||
Here's the updated options definition:
|
||||
|
||||
**src/types.ts**
|
||||
|
||||
```ts
|
||||
type SeriesSize = 'sm' | 'md' | 'lg';
|
||||
type CircleColor = 'red' | 'green' | 'blue';
|
||||
|
||||
// interface defining panel options type
|
||||
export interface SimpleOptions {
|
||||
text: string;
|
||||
showSeriesCount: boolean;
|
||||
seriesCountSize: SeriesSize;
|
||||
color: CircleColor;
|
||||
}
|
||||
```
|
||||
|
||||
#### Add an option control
|
||||
|
||||
To change the option from the panel editor, you need to bind the `color` option to an _option control_.
|
||||
|
||||
Grafana supports a range of option controls, such as text inputs, switches, and radio groups.
|
||||
|
||||
Let's create a radio control and bind it to the `color` option.
|
||||
|
||||
1. In `src/module.ts`, add the control at the end of the builder:
|
||||
|
||||
```ts
|
||||
.addRadio({
|
||||
path: 'color',
|
||||
name: 'Circle color',
|
||||
defaultValue: 'red',
|
||||
settings: {
|
||||
options: [
|
||||
{
|
||||
value: 'red',
|
||||
label: 'Red',
|
||||
},
|
||||
{
|
||||
value: 'green',
|
||||
label: 'Green',
|
||||
},
|
||||
{
|
||||
value: 'blue',
|
||||
label: 'Blue',
|
||||
},
|
||||
],
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The `path` is used to bind the control to an option. You can bind a control to nested option by specifying the full path within a options object, for example `colors.background`.
|
||||
|
||||
Grafana builds an options editor for you and displays it in the panel editor sidebar in the **Display** section.
|
||||
|
||||
#### Use the new option
|
||||
|
||||
You're almost done. You've added a new option and a corresponding control to change the value. But the plugin isn't using the option yet. Let's change that.
|
||||
|
||||
1. To convert option value to the colors used by the current theme, add a `switch` statement right before the `return` statement in `SimplePanel.tsx`.
|
||||
|
||||
**src/SimplePanel.tsx**
|
||||
|
||||
```ts
|
||||
let color: string;
|
||||
switch (options.color) {
|
||||
case 'red':
|
||||
color = theme.palette.redBase;
|
||||
break;
|
||||
case 'green':
|
||||
color = theme.palette.greenBase;
|
||||
break;
|
||||
case 'blue':
|
||||
color = theme.palette.blue95;
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
1. Configure the circle to use the color.
|
||||
|
||||
```ts
|
||||
<g>
|
||||
<circle style={{ fill: color }} r={100} />
|
||||
</g>
|
||||
```
|
||||
|
||||
Now, when you change the color in the panel editor, the fill color of the circle changes as well.
|
||||
|
||||
## Create dynamic panels using data frames
|
||||
|
||||
Most panels visualize dynamic data from a Grafana data source. In this step, you'll create one circle per series, each with a radius equal to the last value in the series.
|
||||
|
||||
> To use data from queries in your panel, you need to set up a data source. If you don't have one available, you can use the [TestData](/docs/grafana/latest/features/datasources/testdata) data source while developing.
|
||||
|
||||
The results from a data source query within your panel are available in the `data` property inside your panel component.
|
||||
|
||||
```ts
|
||||
const { data } = props;
|
||||
```
|
||||
|
||||
`data.series` contains the series returned from a data source query. Each series is represented as a data structure called _data frame_. A data frame resembles a table, where data is stored by columns, or _fields_, instead of rows. Every value in a field share the same data type, such as string, number, or time.
|
||||
|
||||
Here's an example of a data frame with a time field, `Time`, and a number field, `Value`:
|
||||
|
||||
| Time | Value |
|
||||
| ------------- | ----- |
|
||||
| 1589189388597 | 32.4 |
|
||||
| 1589189406480 | 27.2 |
|
||||
| 1589189513721 | 15.0 |
|
||||
|
||||
Let's see how you can retrieve data from a data frame and use it in your visualization.
|
||||
|
||||
1. Get the last value of each field of type `number`, by adding the following to `SimplePanel.tsx`, before the `return` statement:
|
||||
|
||||
```ts
|
||||
const radii = data.series
|
||||
.map((series) => series.fields.find((field) => field.type === 'number'))
|
||||
.map((field) => field?.values.get(field.values.length - 1));
|
||||
```
|
||||
|
||||
`radii` will contain the last values in each of the series that are returned from a data source query. You'll use these to set the radius for each circle.
|
||||
|
||||
1. Change the `svg` element to the following:
|
||||
|
||||
```ts
|
||||
<svg
|
||||
className={styles.svg}
|
||||
width={width}
|
||||
height={height}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
viewBox={`0 -${height / 2} ${width} ${height}`}
|
||||
>
|
||||
<g fill={color}>
|
||||
{radii.map((radius, index) => {
|
||||
const step = width / radii.length;
|
||||
return <circle r={radius} transform={`translate(${index * step + step / 2}, 0)`} />;
|
||||
})}
|
||||
</g>
|
||||
</svg>
|
||||
```
|
||||
|
||||
Note how we're creating a `<circle>` element for each value in `radii`:
|
||||
|
||||
```ts
|
||||
{
|
||||
radii.map((radius, index) => {
|
||||
const step = width / radii.length;
|
||||
return <circle r={radius} transform={`translate(${index * step + step / 2}, 0)`} />;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
We use the `transform` here to distribute the circle horizontally within the panel.
|
||||
|
||||
1. Rebuild your plugin and try it out by adding multiple queries to the panel. Refresh the dashboard.
|
||||
|
||||
If you want to know more about data frames, check out our introduction to [Data frames](/docs/grafana/latest/developers/plugins/data-frames/).
|
||||
|
||||
## Summary
|
||||
|
||||
In this tutorial you learned how to create a custom visualization for your dashboards.
|
@ -1,164 +0,0 @@
|
||||
---
|
||||
title: Build a streaming data source backend plugin
|
||||
summary: Create a backend for your data source plugin with streaming capabilities.
|
||||
description: Create a backend for your data source plugin with streaming capabilities.
|
||||
id: build-a-streaming-data-source-backend-plugin
|
||||
categories: ['plugins']
|
||||
tags: ['beginner']
|
||||
status: Published
|
||||
authors: ['grafana_labs']
|
||||
Feedback Link: https://github.com/grafana/tutorials/issues/new
|
||||
weight: 75
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Grafana supports a wide range of data sources, including Prometheus, MySQL, and even Datadog. In previous tutorials we have shown how to extend Grafana capabilities to query custom data sources by [building a backend datasource plugin](/tutorials/build-a-data-source-backend-plugin/). In this tutorial we take a step further and add streaming capabilities to the backend datasource plugin. Streaming allows plugins to push data to Grafana panels as soon as data appears (without periodic polling from UI side).
|
||||
|
||||
For more information about backend plugins, refer to the documentation on [Backend plugins](/docs/grafana/latest/developers/plugins/backend/).
|
||||
|
||||
In this tutorial, you'll:
|
||||
|
||||
- Extend a backend plugin with streaming capabilities
|
||||
|
||||
{{% class "prerequisite-section" %}}
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
- Knowledge about how data sources are implemented in the frontend.
|
||||
- Knowledge about [backend datasource anatomy](/tutorials/build-a-data-source-backend-plugin/)
|
||||
- Grafana 8.0+
|
||||
- Go ([Version](https://github.com/grafana/plugin-tools/blob/main/packages/create-plugin/templates/backend/go.mod#L3))
|
||||
- [Mage](https://magefile.org/)
|
||||
- NodeJS ([Version](https://github.com/grafana/plugin-tools/blob/main/packages/create-plugin/templates/common/package.json#L66))
|
||||
- yarn
|
||||
{{% /class %}}
|
||||
|
||||
## Set up your environment
|
||||
|
||||
{{< docs/shared lookup="tutorials/set-up-environment.md" source="grafana" version="latest" >}}
|
||||
|
||||
## Create a new plugin
|
||||
|
||||
To build a backend for your data source plugin, Grafana requires a binary that it can execute when it loads the plugin during start-up. In this guide, we will build a binary using the [Grafana plugin SDK for Go](/docs/grafana/latest/developers/plugins/backend/grafana-plugin-sdk-for-go/).
|
||||
|
||||
The easiest way to get started is to use the Grafana [create-plugin tool](https://www.npmjs.com/package/@grafana/create-plugin). Navigate to the plugin folder that you configured in step 1 and enter:
|
||||
|
||||
```
|
||||
npx @grafana/create-plugin@latest
|
||||
```
|
||||
|
||||
Follow the steps and select **datasource** as your plugin type and answer **yes** when prompted to create a backend for your plugin.
|
||||
|
||||
```bash
|
||||
cd my-plugin
|
||||
```
|
||||
|
||||
Install frontend dependencies and build frontend parts of the plugin to _dist_ directory:
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
yarn build
|
||||
```
|
||||
|
||||
Run the following to update [Grafana plugin SDK for Go](/docs/grafana/latest/developers/plugins/backend/grafana-plugin-sdk-for-go/) dependency to the latest minor version:
|
||||
|
||||
```bash
|
||||
go get -u github.com/grafana/grafana-plugin-sdk-go
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
Build backend plugin binaries for Linux, Windows and Darwin to _dist_ directory:
|
||||
|
||||
```bash
|
||||
mage -v
|
||||
```
|
||||
|
||||
Now, let's verify that the plugin you've built can be used in Grafana when creating a new data source:
|
||||
|
||||
1. Restart your Grafana instance.
|
||||
1. Open Grafana in your web browser.
|
||||
1. Navigate via the side-menu to **Configuration** -> **Data Sources**.
|
||||
1. Click **Add data source**.
|
||||
1. Find your newly created plugin and select it.
|
||||
1. Enter a name and then click **Save & Test** (ignore any errors reported for now).
|
||||
|
||||
You now have a new data source instance of your plugin that is ready to use in a dashboard. To confirm, follow these steps:
|
||||
|
||||
1. Navigate via the side-menu to **Create** -> **Dashboard**.
|
||||
1. Click **Add new panel**.
|
||||
1. In the query tab, select the data source you just created.
|
||||
1. A line graph is rendered with one series consisting of two data points.
|
||||
1. Save the dashboard.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### Grafana doesn't load my plugin
|
||||
|
||||
By default, Grafana requires backend plugins to be signed. To load unsigned backend plugins, you need to
|
||||
configure Grafana to [allow unsigned plugins](/docs/grafana/latest/plugins/plugin-signature-verification/#allow-unsigned-plugins).
|
||||
For more information, refer to [Plugin signature verification](/docs/grafana/latest/plugins/plugin-signature-verification/#backend-plugins).
|
||||
|
||||
## Anatomy of a backend plugin
|
||||
|
||||
As you may notice till this moment we did the same steps described in [build a backend datasource plugin tutorial](/tutorials/build-a-data-source-backend-plugin/). At this point, you should be familiar with backend plugin structure and a way how data querying and health check capabilities could be implemented. Let's take the next step and discuss how datasource plugin can handle data streaming.
|
||||
|
||||
## Add streaming capabilities
|
||||
|
||||
What we want to achieve here is to issue a query to load initial data from a datasource plugin and then switching to data streaming mode where the plugin will push data frames to Grafana time-series panel.
|
||||
|
||||
In short – implementing a streaming plugin means implementing a `backend.StreamHandler` interface which contains `SubscribeStream`, `RunStream`, and `PublishStream` methods.
|
||||
|
||||
`SubscribeStream` is a method where the plugin has a chance to authorize user subscription requests to a channel. Users on the frontend side subscribe to different channels to consume real-time data.
|
||||
|
||||
When returning a `data.Frame` with initial data we can return a special field `Channel` to let the frontend know that we are going to stream data frames after initial data load. When the frontend receives a frame with a `Channel` set it automatically issues a subscription request to that channel.
|
||||
|
||||
Channel is a string identifier of topic to which clients can subscribe in Grafana Live. See a documentation of Grafana Live for [details about channel structure](/docs/grafana/latest/live/live-channel/).
|
||||
|
||||
As said in docs in Grafana Live channel consists of 3 parts delimited by `/`:
|
||||
|
||||
- Scope
|
||||
- Namespace
|
||||
- Path
|
||||
|
||||
For datasource plugin channels Grafana uses `ds` scope. Namespace in the case of datasource channels is a datasource unique ID (UID) which is issued by Grafana at the moment of datasource creation. The path is a custom string that plugin authors free to choose themselves (just make sure it consists of allowed symbols). I.e. datasource channel looks like `ds/<DATASOURCE_UID>/<CUSTOM_PATH>`.
|
||||
|
||||
So to let the frontend know that we are going to stream data we set a `Channel` field into frame metadata inside `QueryData` implementation. In our tutorial it's a `ds/<DATASOURCE_UID>/stream`. The frontend will issue a subscription request to this channel.
|
||||
|
||||
Inside `SubscribeStream` implementation we check whether a user allowed to subscribe on a channel path. If yes – we return an OK status code to tell Grafana user can join a channel:
|
||||
|
||||
```go
|
||||
status := backend.SubscribeStreamStatusPermissionDenied
|
||||
if req.Path == "stream" {
|
||||
// Allow subscribing only on expected path.
|
||||
status = backend.SubscribeStreamStatusOK
|
||||
}
|
||||
return &backend.SubscribeStreamResponse{
|
||||
Status: status,
|
||||
}, nil
|
||||
```
|
||||
|
||||
As soon as the first subscriber joins a channel Grafana opens a unidirectional stream to consume streaming frames from a plugin. To handle this and to push data towards clients we implement a `RunStream` method which provides a way to push JSON data into a channel. So we can push data frame like this (error handling skipped):
|
||||
|
||||
```go
|
||||
// Send frame to stream including both frame schema and data frame parts.
|
||||
_ = sender.SendFrame(frame, data.IncludeAll)
|
||||
```
|
||||
|
||||
Open example datasource query editor and make sure `With Streaming` toggle is on. After doing this you should see data displayed and then periodically updated by streaming frames coming periodically from `RunStream` method.
|
||||
|
||||
The important thing to note is that Grafana opens a unidirectional stream only once per channel upon the first subscriber joined. Every other subscription request will be still authorized by `SubscribeStream` method but the new `RunStream` won't be issued. I.e. you can have many active subscribers but only one running stream. At this moment this guarantee works for a single Grafana instance, we are planning to support this for highly-available Grafana setup (many Grafana instances behind load-balancer) in future releases.
|
||||
|
||||
The stream will be automatically closed as soon as all subscriber users left.
|
||||
|
||||
For the tutorial use case, we only need to properly implement `SubscribeStream` and `RunStream` - we don't need to handle publications to a channel from users. But we still need to write `PublishStream` method to fully implement `backend.StreamHandler` interface. Inside `PublishStream` we just do not allow any publications from users since we are pushing data from a backend:
|
||||
|
||||
```go
|
||||
return &backend.PublishStreamResponse{
|
||||
Status: backend.PublishStreamStatusPermissionDenied,
|
||||
}, nil
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
In this tutorial you created a backend for your data source plugin with streaming capabilities.
|
@ -1,208 +0,0 @@
|
||||
---
|
||||
title: Build an app plugin
|
||||
summary: Learn at how to create an app for Grafana.
|
||||
description: Learn at how to create an app for Grafana.
|
||||
id: build-an-app-plugin
|
||||
categories: ['plugins']
|
||||
tags: ['beginner']
|
||||
status: Published
|
||||
authors: ['grafana_labs']
|
||||
Feedback Link: https://github.com/grafana/tutorials/issues/new
|
||||
weight: 50
|
||||
draft: true
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
App plugins are Grafana plugins that can bundle data source and panel plugins within one package. They also let you create _custom pages_ within Grafana. Custom pages enable the plugin author to include things like documentation, sign-up forms, or to control other services over HTTP.
|
||||
|
||||
Data source and panel plugins will show up like normal plugins. The app pages will be available in the main menu.
|
||||
|
||||
{{% class "prerequisite-section" %}}
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Grafana 7.0
|
||||
- NodeJS 12.x
|
||||
- yarn
|
||||
{{% /class %}}
|
||||
|
||||
## Set up your environment
|
||||
|
||||
{{< docs/shared lookup="tutorials/set-up-environment.md" source="grafana" version="latest" >}}
|
||||
|
||||
## Create a new plugin
|
||||
|
||||
{{< docs/shared lookup="tutorials/create-plugin.md" source="grafana" version="latest" >}}
|
||||
|
||||
## Anatomy of a plugin
|
||||
|
||||
{{< docs/shared lookup="tutorials/plugin-anatomy.md" source="grafana" version="latest" >}}
|
||||
|
||||
## App plugins
|
||||
|
||||
App plugins let you bundle resources such as dashboards, panels, and data sources into a single plugin.
|
||||
|
||||
Any resource you want to include needs to be added to the `includes` property in the `plugin.json` file. To add a resource to your app plugin, you need to include it to the `plugin.json`.
|
||||
|
||||
Plugins that are included in an app plugin are available like any other plugin.
|
||||
|
||||
Dashboards and pages can be added to the app menu by setting `addToNav` to `true`.
|
||||
|
||||
By setting `"defaultNav": true`, users can navigate to the dashboard by clicking the app icon in the side menu.
|
||||
|
||||
## Add a custom page
|
||||
|
||||
App plugins let you extend the Grafana user interface through the use of _custom pages_.
|
||||
|
||||
Any requests sent to `/a/<plugin-id>`, e.g. `/a/myorgid-simple-app/`, are routed to the _root page_ of the app plugin. The root page is a React component that returns the content for a given route.
|
||||
|
||||
While you're free to implement your own routing, in this tutorial you'll use a tab-based navigation page that you can use by calling `onNavChange`.
|
||||
|
||||
Let's add a tab for managing server instances.
|
||||
|
||||
1. In the `src/pages` directory, add a new file called `Instances.tsx`. This component contains the content for the new tab.
|
||||
|
||||
```ts
|
||||
import { AppRootProps } from '@grafana/data';
|
||||
import React from 'react';
|
||||
|
||||
export const Instances = ({ query, path, meta }: AppRootProps) => {
|
||||
return <p>Hello</p>;
|
||||
};
|
||||
```
|
||||
|
||||
1. Register the page by adding it to the `pages` array in `src/pages/index.ts`.
|
||||
|
||||
**index.ts**
|
||||
|
||||
```ts
|
||||
import { Instances } from './Instances';
|
||||
```
|
||||
|
||||
```ts
|
||||
{
|
||||
component: Instances,
|
||||
icon: 'file-alt',
|
||||
id: 'instances',
|
||||
text: 'Instances',
|
||||
}
|
||||
```
|
||||
|
||||
1. Add the page to the app menu, by including it in `plugin.json`. This will be the main view of the app, so we'll set `defaultNav` to let users quickly get to it by clicking the app icon in the side menu.
|
||||
|
||||
**plugin.json**
|
||||
|
||||
```json
|
||||
"includes": [
|
||||
{
|
||||
"type": "page",
|
||||
"name": "Instances",
|
||||
"path": "/a/myorgid-simple-app?tab=instances",
|
||||
"role": "Viewer",
|
||||
"addToNav": true,
|
||||
"defaultNav": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
> **Note:** While `page` includes typically reference pages created by the app, you can set `path` to any URL, internal or external. Try setting `path` to `https://grafana.com`.
|
||||
|
||||
## Configure the app
|
||||
|
||||
Let's add a new configuration page where users are able to configure default zone and regions for any instances they create.
|
||||
|
||||
1. In `module.ts`, add new configuration page using the `addConfigPage` method. `body` is the React component that renders the page content.
|
||||
|
||||
**module.ts**
|
||||
|
||||
```ts
|
||||
.addConfigPage({
|
||||
title: 'Defaults',
|
||||
icon: 'fa fa-info',
|
||||
body: DefaultsConfigPage,
|
||||
id: 'defaults',
|
||||
})
|
||||
```
|
||||
|
||||
## Add a dashboard
|
||||
|
||||
#### Include a dashboard in your app
|
||||
|
||||
1. In `src/`, create a new directory called `dashboards`.
|
||||
1. Create a file called `overview.json` in the `dashboards` directory.
|
||||
1. Copy the JSON definition for the dashboard you want to include and paste it into `overview.json`. If you don't have one available, you can find a sample dashboard at the end of this step.
|
||||
1. In `plugin.json`, add the following object to the `includes` property.
|
||||
|
||||
- The `name` of the dashboard needs to be the same as the `title` in the dashboard JSON model.
|
||||
- `path` points out the file that contains the dashboard definition, relative to the `plugin.json` file.
|
||||
|
||||
```json
|
||||
"includes": [
|
||||
{
|
||||
"type": "dashboard",
|
||||
"name": "System overview",
|
||||
"path": "dashboards/overview.json",
|
||||
"addToNav": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
1. Save and restart Grafana to load the new changes.
|
||||
|
||||
## Bundle a plugin
|
||||
|
||||
An app plugin can contain panel and data source plugins that get installed along with the app plugin.
|
||||
|
||||
In this step, you'll add a data source to your app plugin. You can add panel plugins the same way by changing `datasource` to `panel`.
|
||||
|
||||
1. In `src/`, create a new directory called `datasources`.
|
||||
1. Create a new data source using Grafana create-plugin tool in a temporary directory.
|
||||
|
||||
```bash
|
||||
mkdir tmp
|
||||
cd tmp
|
||||
npx @grafana/create-plugin@latest
|
||||
```
|
||||
|
||||
1. Move the `src` directory in the data source plugin to `src/datasources`, and rename it to `my-datasource`.
|
||||
|
||||
```bash
|
||||
mv ./my-datasource/src ../src/datasources/my-datasource
|
||||
```
|
||||
|
||||
Any bundled plugins are built along with the app plugin. Grafana looks for any subdirectory containing a `plugin.json` file and attempts to load a plugin in that directory.
|
||||
|
||||
To let users know that your plugin bundles other plugins, you can optionally display it on the plugin configuration page. This is not done automatically, so you need to add it to the `plugin.json`.
|
||||
|
||||
1. Include the data source in the `plugin.json`. The `name` property is only used for displaying in the Grafana UI.
|
||||
|
||||
```json
|
||||
"includes": [
|
||||
{
|
||||
"type": "datasource",
|
||||
"name": "My data source"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### Include external plugins
|
||||
|
||||
If you want to let users know that your app requires an existing plugin, you can add it as a dependency in `plugin.json`. Note that they'll still need to install it themselves.
|
||||
|
||||
```json
|
||||
"dependencies": {
|
||||
"plugins": [
|
||||
{
|
||||
"type": "panel",
|
||||
"name": "Worldmap Panel",
|
||||
"id": "grafana-worldmap-panel",
|
||||
"version": "^0.3.2"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
In this tutorial you learned how to create an app plugin.
|
Reference in New Issue
Block a user