mirror of
https://github.com/grafana/grafana.git
synced 2025-08-03 01:02:42 +08:00
Plugins doc review chunk 2 (#67691)
* Doc style edit for 7 topics Signed-off-by: Joe Perez <joseph.perez@grafana.com> * Proofread topics Signed-off-by: Joe Perez <joseph.perez@grafana.com> * Prettier Signed-off-by: Joe Perez <joseph.perez@grafana.com> * Doc fix Signed-off-by: Joe Perez <joseph.perez@grafana.com> * Update docs/sources/developers/plugins/add-query-editor-help.md Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com> * Doc fixes Signed-off-by: Joe Perez <joseph.perez@grafana.com> * Changes from doc review Signed-off-by: Joe Perez <joseph.perez@grafana.com> * Incorporate review feedback Signed-off-by: Joe Perez <joseph.perez@grafana.com> * More fixes Signed-off-by: Joe Perez <joseph.perez@grafana.com> * More doc fixes Signed-off-by: Joe Perez <joseph.perez@grafana.com> --------- Signed-off-by: Joe Perez <joseph.perez@grafana.com> Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
This commit is contained in:
@ -2,13 +2,17 @@
|
|||||||
title: Add anonymous usage reporting
|
title: Add anonymous usage reporting
|
||||||
---
|
---
|
||||||
|
|
||||||
# Add anonymous usage reporting to you plugin
|
# Add anonymous usage reporting
|
||||||
|
|
||||||
The Grafana server administrator has the possibility to configure [anonymous usage tracking]({{< relref "../../setup-grafana/configure-grafana/#reporting_enabled" >}}).
|
Add anonymous usage tracking to your plugin to send [reporting events]({{< relref "../../setup-grafana/configure-grafana/#reporting_enabled" >}}) that describe how your plugin is being used to a tracking system configured by your Grafana server administrator.
|
||||||
|
|
||||||
By adding usage tracking to your plugin you will send events of how your plugin is being used to the configured tracking system.
|
## Event reporting
|
||||||
|
|
||||||
Lets say we have a QueryEditor that looks something like the example below. It has an editor field where you can write your query and a query type selector so you can select what kind of query result you are expecting that query to return.
|
In this section, we show an example of tracking usage data from a query editor and receiving a report back from the analytics service.
|
||||||
|
|
||||||
|
### Sample query editor
|
||||||
|
|
||||||
|
Let's say you have a `QueryEditor` that looks similar to the example below. It has a `CodeEditor` field where you can write your query and a query type selector so you can select the kind of query result that you expect to return:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import React, { ReactElement } from 'react';
|
import React, { ReactElement } from 'react';
|
||||||
@ -17,11 +21,11 @@ import type { EditorProps } from './types';
|
|||||||
|
|
||||||
export function QueryEditor(props: EditorProps): ReactElement {
|
export function QueryEditor(props: EditorProps): ReactElement {
|
||||||
const { datasource, query, onChange, onRunQuery } = props;
|
const { datasource, query, onChange, onRunQuery } = props;
|
||||||
const queryType = { value: query.value ?? 'timeserie' };
|
const queryType = { value: query.value ?? 'timeseries' };
|
||||||
const queryTypes = [
|
const queryTypes = [
|
||||||
{
|
{
|
||||||
label: 'Timeserie',
|
label: 'Timeseries',
|
||||||
value: 'timeserie',
|
value: 'timeseries',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Table',
|
label: 'Table',
|
||||||
@ -66,7 +70,14 @@ export function QueryEditor(props: EditorProps): ReactElement {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Lets say that we would like to track how the usage looks between time series and table queries. All you need to do is to add the `usePluginInteractionReporter` to fetch a report function which takes two arguments. The first one is the event name which is used to identify the interaction being made. It need to start with `grafana_plugin_` which makes it easier to differentiate plugin events from Grafana core events. The second argument is optional and should be used to attach contextual data to the event. In our example, that would be the query type. It is optional because it does not make sense to pass contextual data for all user interactions.
|
### Track usage with `usePluginInteractionReporter`
|
||||||
|
|
||||||
|
Let's say that you want to track how the usage looks between time series and table queries.
|
||||||
|
|
||||||
|
What you want to do is to add the `usePluginInteractionReporter` to fetch a report function that takes two arguments:
|
||||||
|
|
||||||
|
- Required: An event name that begins with `grafana_plugin_`. It is used to identify the interaction being made.
|
||||||
|
- Optional: Attached contextual data. In our example, that is the query type.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import React, { ReactElement } from 'react';
|
import React, { ReactElement } from 'react';
|
||||||
@ -78,11 +89,11 @@ export function QueryEditor(props: EditorProps): ReactElement {
|
|||||||
const { datasource, query, onChange, onRunQuery } = props;
|
const { datasource, query, onChange, onRunQuery } = props;
|
||||||
const report = usePluginInteractionReporter();
|
const report = usePluginInteractionReporter();
|
||||||
|
|
||||||
const queryType = { value: query.value ?? 'timeserie' };
|
const queryType = { value: query.value ?? 'timeseries' };
|
||||||
const queryTypes = [
|
const queryTypes = [
|
||||||
{
|
{
|
||||||
label: 'Timeserie',
|
label: 'Timeseries',
|
||||||
value: 'timeserie',
|
value: 'timeseries',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Table',
|
label: 'Table',
|
||||||
@ -132,7 +143,11 @@ export function QueryEditor(props: EditorProps): ReactElement {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Another benefit of using the `usePluginInteractionReporter` is that the report function that is handed back to you will automatically attach contextual data about the plugin you are tracking to every event. In our example the following information will be sent to the analytics service configured by the Grafana server administrator.
|
### Data returned from the analytics service
|
||||||
|
|
||||||
|
When you use `usePluginInteractionReporter`, the report function that is handed back to you automatically attaches contextual data about the plugin you are tracking to the events.
|
||||||
|
|
||||||
|
In our example, the following information is sent to the analytics service configured by the Grafana server administrator:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
{
|
{
|
||||||
@ -145,7 +160,7 @@ Another benefit of using the `usePluginInteractionReporter` is that the report f
|
|||||||
plugin_id: 'grafana-example-datasource',
|
plugin_id: 'grafana-example-datasource',
|
||||||
plugin_name: 'Example',
|
plugin_name: 'Example',
|
||||||
datasource_uid: 'qeSI8VV7z', // will only be added for datasources
|
datasource_uid: 'qeSI8VV7z', // will only be added for datasources
|
||||||
query_type: 'timeserie'
|
query_type: 'timeseries'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -7,112 +7,121 @@ title: Add authentication for data source plugins
|
|||||||
|
|
||||||
# Add authentication for data source plugins
|
# Add authentication for data source plugins
|
||||||
|
|
||||||
This page explains how to configure your data source plugin to authenticate against a third-party API.
|
Grafana plugins can perform authenticated requests against a third-party API by using the _data source proxy_ or through a custom a _backend plugin_.
|
||||||
|
|
||||||
There are two ways you can perform authenticated requests from your plugin—using the [_data source proxy_](#authenticate-using-the-data-source-proxy), or by building a [_backend plugin_](#authenticate-using-a-backend-plugin). The one you choose depends on how your plugin authenticates against the third-party API.
|
## Choose an authentication method
|
||||||
|
|
||||||
- Use the data source proxy if you need to authenticate using Basic Auth or API keys
|
Configure your data source plugin to authenticate against a third-party API in one of either of two ways:
|
||||||
- Use the data source proxy if the API supports OAuth 2.0 using client credentials
|
|
||||||
- Use a backend plugin if the API uses a custom authentication method that isn't supported by the data source proxy, or if your API communicates over a different protocol than HTTP
|
|
||||||
|
|
||||||
Regardless of which approach you use, you first need to encrypt any sensitive information that the plugin needs to store.
|
- Use the [_data source proxy_](#authenticate-using-the-data-source-proxy) method, or
|
||||||
|
- Build a [_backend plugin_](#authenticate-using-a-backend-plugin).
|
||||||
|
|
||||||
|
| Case | Use |
|
||||||
|
| ----------------------------------------------------------------------------------------------- | ------------------------------- |
|
||||||
|
| Do you need to authenticate your plugin using Basic Auth or API keys? | Use the data source proxy. |
|
||||||
|
| Does your API support OAuth 2.0 using client credentials? | Use the data source proxy. |
|
||||||
|
| Does your API use a custom authentication method that isn't supported by the data source proxy? | Use a backend plugin. |
|
||||||
|
| Does your API communicate over a protocol other than HTTP? | Build and use a backend plugin. |
|
||||||
|
| Does your plugin require alerting support? | Build and use a backend plugin. |
|
||||||
|
|
||||||
## Encrypt data source configuration
|
## Encrypt data source configuration
|
||||||
|
|
||||||
Data source plugins have two ways of storing custom configuration: `jsonData` and `secureJsonData`.
|
Data source plugins have two ways of storing custom configuration: `jsonData` and `secureJsonData`.
|
||||||
|
|
||||||
Users with the _Viewer_ role can access data source configuration—such as the contents of `jsonData`—in cleartext. If you've enabled anonymous access, anyone that can access Grafana in their browser can see the contents of `jsonData`. **Only use `jsonData` to store non-sensitive configuration.**
|
Users with the Viewer role can access data source configuration such as the contents of `jsonData` in cleartext. If you've enabled anonymous access, anyone who can access Grafana in their browser can see the contents of `jsonData`.
|
||||||
|
|
||||||
|
Users of [Grafana Enterprise](https://grafana.com/products/enterprise/grafana/) can restrict access to data sources to specific users and teams. For more information, refer to [Data source permissions](https://grafana.com/docs/grafana/latest/enterprise/datasource_permissions).
|
||||||
|
|
||||||
|
> **Important:** Do not use `jsonData` with sensitive data such as password, tokens, and API keys. If you need to store sensitive information, use `secureJsonData` instead.
|
||||||
|
|
||||||
> **Note:** You can see the settings that the current user has access to by entering `window.grafanaBootData` in the developer console of your browser.
|
> **Note:** You can see the settings that the current user has access to by entering `window.grafanaBootData` in the developer console of your browser.
|
||||||
|
|
||||||
> **Note:** Users of [Grafana Enterprise](https://grafana.com/products/enterprise/grafana/) can restrict access to data sources to specific users and teams. For more information, refer to [Data source permissions](https://grafana.com/docs/grafana/latest/enterprise/datasource_permissions).
|
### Store configuration in `secureJsonData`
|
||||||
|
|
||||||
If you need to store sensitive information, such as passwords, tokens and API keys, use `secureJsonData` instead. Whenever the user saves the data source configuration, the secrets in `secureJsonData` are sent to the Grafana server and encrypted before they're stored.
|
If you need to store sensitive information, use `secureJsonData` instead of `jsonData`. Whenever the user saves the data source configuration, the secrets in `secureJsonData` are sent to the Grafana server and encrypted before they're stored.
|
||||||
|
|
||||||
Once the secure configuration has been encrypted, it can no longer be accessed from the browser. The only way to access secrets after they've been saved is by using the [_data source proxy_](#authenticate-using-the-data-source-proxy).
|
Once you have encrypted the secure configuration, it can no longer be accessed from the browser. The only way to access secrets after they've been saved is by using the [_data source proxy_](#authenticate-using-the-data-source-proxy).
|
||||||
|
|
||||||
#### Add secret configuration to your data source plugin
|
### Add secret configuration to your data source plugin
|
||||||
|
|
||||||
To demonstrate how you can add secrets to a data source plugin, let's add support for configuring an API key.
|
To demonstrate how you can add secrets to a data source plugin, let's add support for configuring an API key.
|
||||||
|
|
||||||
Create a new interface in `types.ts` to hold the API key.
|
1. Create a new interface in `types.ts` to hold the API key:
|
||||||
|
```ts
|
||||||
|
export interface MySecureJsonData {
|
||||||
|
apiKey?: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
1. Add type information to your `secureJsonData` object by updating the props for your `ConfigEditor` to accept the interface as a second type parameter. Access the value of the secret from the `options` prop inside your `ConfigEditor`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
export interface MySecureJsonData {
|
interface Props extends DataSourcePluginOptionsEditorProps<MyDataSourceOptions, MySecureJsonData> {}
|
||||||
apiKey?: string;
|
```
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Add type information to your `secureJsonData` object by updating the props for your `ConfigEditor` to accept the interface as a second type parameter.
|
```ts
|
||||||
|
const { secureJsonData, secureJsonFields } = options;
|
||||||
|
const { apiKey } = secureJsonData;
|
||||||
|
```
|
||||||
|
|
||||||
```ts
|
> **Note:** You can do this until the user saves the configuration; when the user saves the configuration, Grafana clears the value. After that, you can use `secureJsonFields` to determine whether the property has been configured.
|
||||||
interface Props extends DataSourcePluginOptionsEditorProps<MyDataSourceOptions, MySecureJsonData> {}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can access the value of the secret from the `options` prop inside your `ConfigEditor` until the user saves the configuration. When the user saves the configuration, Grafana clears the value. After that, you can use the `secureJsonFields` to determine whether the property has been configured.
|
1. To securely update the secret in your plugin's configuration editor, update the `secureJsonData` object using the `onOptionsChange` prop:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const { secureJsonData, secureJsonFields } = options;
|
const onAPIKeyChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
const { apiKey } = secureJsonData;
|
onOptionsChange({
|
||||||
```
|
...options,
|
||||||
|
secureJsonData: {
|
||||||
|
apiKey: event.target.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
To securely update the secret in your plugin's configuration editor, update the `secureJsonData` object using the `onOptionsChange` prop.
|
1. Define a component that can accept user input:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const onAPIKeyChange = (event: ChangeEvent<HTMLInputElement>) => {
|
<Input
|
||||||
onOptionsChange({
|
type="password"
|
||||||
...options,
|
placeholder={secureJsonFields?.apiKey ? 'configured' : ''}
|
||||||
secureJsonData: {
|
value={secureJsonData.apiKey ?? ''}
|
||||||
apiKey: event.target.value,
|
onChange={onAPIKeyChange}
|
||||||
},
|
/>
|
||||||
});
|
```
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, define a component that can accept user input.
|
1. Optional: If you want the user to be able to reset the API key, then you need to set the property to `false` in the `secureJsonFields` object:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
<Input
|
const onResetAPIKey = () => {
|
||||||
type="password"
|
onOptionsChange({
|
||||||
placeholder={secureJsonFields?.apiKey ? 'configured' : ''}
|
...options,
|
||||||
value={secureJsonData.apiKey ?? ''}
|
secureJsonFields: {
|
||||||
onChange={onAPIKeyChange}
|
...options.secureJsonFields,
|
||||||
/>
|
apiKey: false,
|
||||||
```
|
},
|
||||||
|
secureJsonData: {
|
||||||
Finally, if you want the user to be able to reset the API key, then you need to set the property to `false` in the `secureJsonFields` object.
|
...options.secureJsonData,
|
||||||
|
apiKey: '',
|
||||||
```ts
|
},
|
||||||
const onResetAPIKey = () => {
|
});
|
||||||
onOptionsChange({
|
};
|
||||||
...options,
|
```
|
||||||
secureJsonFields: {
|
|
||||||
...options.secureJsonFields,
|
|
||||||
apiKey: false,
|
|
||||||
},
|
|
||||||
secureJsonData: {
|
|
||||||
...options.secureJsonData,
|
|
||||||
apiKey: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
Now that users can configure secrets, the next step is to see how we can add them to our requests.
|
Now that users can configure secrets, the next step is to see how we can add them to our requests.
|
||||||
|
|
||||||
## Authenticate using the data source proxy
|
## Authenticate using the data source proxy
|
||||||
|
|
||||||
Once the user has saved the configuration for a data source, any secret data source configuration will no longer be available in the browser. Encrypted secrets can only be accessed on the server. So how do you add them to you request?
|
Once the user has saved the configuration for a data source, the secret data source configuration will no longer be available in the browser. Encrypted secrets can only be accessed on the server. So how do you add them to your request?
|
||||||
|
|
||||||
The Grafana server comes with a proxy that lets you define templates for your requests. We call them _proxy routes_. Grafana sends the proxy route to the server, decrypts the secrets along with other configuration, and adds them to the request before sending it off.
|
The Grafana server comes with a proxy that lets you define templates for your requests: _proxy routes_. Grafana sends the proxy route to the server, decrypts the secrets along with other configuration, and adds them to the request before sending it.
|
||||||
|
|
||||||
> **Note:** Be sure not to confuse the data source proxy with the [auth proxy]({{< relref "../../setup-grafana/configure-security/configure-authentication/auth-proxy/" >}}). The data source proxy is used to authenticate a data source, while the auth proxy is used to log into Grafana itself.
|
> **Note:** Be sure not to confuse the data source proxy with the [auth proxy]({{< relref "../../setup-grafana/configure-security/configure-authentication/auth-proxy/" >}}). The data source proxy is used to authenticate a data source, while the auth proxy is used to log into Grafana itself.
|
||||||
|
|
||||||
### Add a proxy route to your plugin
|
### Add a proxy route to your plugin
|
||||||
|
|
||||||
To forward requests through the Grafana proxy, you need to configure one or more proxy routes. A proxy route is a template for any outgoing request that is handled by the proxy. You can configure proxy routes in the [plugin.json](https://grafana.com/docs/grafana/latest/developers/plugins/metadata/) file.
|
To forward requests through the Grafana proxy, you need to configure one or more _proxy routes_. A proxy route is a template for any outgoing request that is handled by the proxy. You can configure proxy routes in the [plugin.json](https://grafana.com/docs/grafana/latest/developers/plugins/metadata/) file.
|
||||||
|
|
||||||
1. Add the route to plugin.json. Note that you need to restart the Grafana server every time you make a change to your plugin.json file.
|
1. Add the route to `plugin.json`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"routes": [
|
"routes": [
|
||||||
@ -123,7 +132,9 @@ To forward requests through the Grafana proxy, you need to configure one or more
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
1. In the `DataSource`, extract the proxy URL from `instanceSettings` to a class property called `url`.
|
> **Note:** You need to restart the Grafana server every time you make a change to your `plugin.json` file.
|
||||||
|
|
||||||
|
1. In the `DataSource`, extract the proxy URL from `instanceSettings` to a class property called `url`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
|
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
|
||||||
@ -139,7 +150,7 @@ To forward requests through the Grafana proxy, you need to configure one or more
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
1. In the `query` method, make a request using `BackendSrv`. The first section of the URL path needs to match the `path` of your proxy route. The data source proxy replaces `this.url + routePath` with the `url` of the route. The following request will be made to `https://api.example.com/v1/users`.
|
1. In the `query` method, make a request using `BackendSrv`. The first section of the URL path needs to match the `path` of your proxy route. The data source proxy replaces `this.url + routePath` with the `url` of the route. Based on our example, the URL for the request would be `https://api.example.com/v1/users`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { getBackendSrv } from '@grafana/runtime';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
@ -158,32 +169,36 @@ To forward requests through the Grafana proxy, you need to configure one or more
|
|||||||
|
|
||||||
Grafana sends the proxy route to the server, where the data source proxy decrypts any sensitive data and interpolates the template variables with the decrypted data before making the request.
|
Grafana sends the proxy route to the server, where the data source proxy decrypts any sensitive data and interpolates the template variables with the decrypted data before making the request.
|
||||||
|
|
||||||
To add user-defined configuration to your routes, for example, add `{{ .JsonData.projectId }}` to the route, where `projectId` is the name of a property in the `jsonData` object.
|
To add user-defined configuration to your routes:
|
||||||
|
|
||||||
```json
|
- Use `.JsonData` for configuration stored in `jsonData`. For example, where `projectId` is the name of a property in the `jsonData` object:
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"path": "example",
|
|
||||||
"url": "https://api.example.com/projects/{{ .JsonData.projectId }}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also configure your route to use sensitive data by using `.SecureJsonData`.
|
```json
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"path": "example",
|
||||||
|
"url": "https://api.example.com/projects/{{ .JsonData.projectId }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
```json
|
- Use `.SecureJsonData` for sensitive data stored in `secureJsonData`. For example, where `password` is the name of a property in the `secureJsonData` object:
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"path": "example",
|
|
||||||
"url": "https://{{ .JsonData.username }}:{{ .SecureJsonData.password }}@api.example.com"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
In addition to the URL, you can also add headers, URL parameters, and a request body, to a proxy route.
|
```json
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"path": "example",
|
||||||
|
"url": "https://{{ .JsonData.username }}:{{ .SecureJsonData.password }}@api.example.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition to adding the URL to the proxy route, you can also add headers, URL parameters, and a request body.
|
||||||
|
|
||||||
#### Add HTTP headers to a proxy route
|
#### Add HTTP headers to a proxy route
|
||||||
|
|
||||||
|
Here's an example of adding `name` and `content` as HTTP headers:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
@ -201,6 +216,8 @@ In addition to the URL, you can also add headers, URL parameters, and a request
|
|||||||
|
|
||||||
#### Add URL parameters to a proxy route
|
#### Add URL parameters to a proxy route
|
||||||
|
|
||||||
|
Here's an example of adding `name` and `content` as URL parameters:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
@ -218,6 +235,8 @@ In addition to the URL, you can also add headers, URL parameters, and a request
|
|||||||
|
|
||||||
#### Add a request body to a proxy route
|
#### Add a request body to a proxy route
|
||||||
|
|
||||||
|
Here's an example of adding `username` and `password` to the request body:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
@ -231,11 +250,9 @@ In addition to the URL, you can also add headers, URL parameters, and a request
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add a OAuth 2.0 proxy route to your plugin
|
### Add an OAuth 2.0 proxy route to your plugin
|
||||||
|
|
||||||
The data source proxy supports OAuth 2.0 authentication.
|
Since your request to each route is made server-side with OAuth 2.0 authentication, only machine-to-machine requests are supported. In order words, if you need to use a different grant than client credentials, you need to implement it yourself.
|
||||||
|
|
||||||
Since the request to each route is made server-side, only machine-to-machine authentication is supported. In order words, if you need to use a different grant than client credentials, you need to implement it yourself.
|
|
||||||
|
|
||||||
To authenticate using OAuth 2.0, add a `tokenAuth` object to the proxy route definition. If necessary, Grafana performs a request to the URL defined in `tokenAuth` to retrieve a token before making the request to the URL in your proxy route. Grafana automatically renews the token when it expires.
|
To authenticate using OAuth 2.0, add a `tokenAuth` object to the proxy route definition. If necessary, Grafana performs a request to the URL defined in `tokenAuth` to retrieve a token before making the request to the URL in your proxy route. Grafana automatically renews the token when it expires.
|
||||||
|
|
||||||
@ -264,10 +281,10 @@ Any parameters defined in `tokenAuth.params` are encoded as `application/x-www-f
|
|||||||
|
|
||||||
While the data source proxy supports the most common authentication methods for HTTP APIs, using proxy routes has a few limitations:
|
While the data source proxy supports the most common authentication methods for HTTP APIs, using proxy routes has a few limitations:
|
||||||
|
|
||||||
- Proxy routes only support HTTP or HTTPS
|
- Proxy routes only support HTTP or HTTPS.
|
||||||
- Proxy routes don't support custom token authentication
|
- Proxy routes don't support custom token authentication.
|
||||||
|
|
||||||
If any of these limitations apply to your plugin, you need to add a [backend plugin]({{< relref "backend/" >}}). Since backend plugins run on the server they can access decrypted secrets, which makes it easier to implement custom authentication methods.
|
If any of these limitations apply to your plugin, you need to add a [backend plugin]({{< relref "backend/" >}}). Because backend plugins run on the server, they can access decrypted secrets, which makes it easier to implement custom authentication methods.
|
||||||
|
|
||||||
The decrypted secrets are available from the `DecryptedSecureJSONData` field in the instance settings.
|
The decrypted secrets are available from the `DecryptedSecureJSONData` field in the instance settings.
|
||||||
|
|
||||||
@ -285,9 +302,9 @@ func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataReque
|
|||||||
|
|
||||||
## Forward OAuth identity for the logged-in user
|
## Forward OAuth identity for the logged-in user
|
||||||
|
|
||||||
If your data source uses the same OAuth provider as Grafana itself, for example using [Generic OAuth Authentication]({{< relref "../../setup-grafana/configure-security/configure-authentication/generic-oauth/" >}}), your data source plugin can reuse the access token for the logged-in Grafana user.
|
If your data source uses the same OAuth provider as Grafana itself, for example using [Generic OAuth Authentication]({{< relref "../../setup-grafana/configure-security/configure-authentication/generic-oauth/" >}}), then your data source plugin can reuse the access token for the logged-in Grafana user.
|
||||||
|
|
||||||
To allow Grafana to pass the access token to the plugin, update the data source configuration and set the` jsonData.oauthPassThru` property to `true`. The [DataSourceHttpSettings](https://developers.grafana.com/ui/latest/index.html?path=/story/data-source-datasourcehttpsettings--basic) provides a toggle, the **Forward OAuth Identity** option, for this. You can also build an appropriate toggle to set `jsonData.oauthPassThru` in your data source configuration page UI.
|
To allow Grafana to pass the access token to the plugin, update the data source configuration and set the `jsonData.oauthPassThru` property to `true`. The [DataSourceHttpSettings](https://developers.grafana.com/ui/latest/index.html?path=/story/data-source-datasourcehttpsettings--basic) settings provide a toggle, the **Forward OAuth Identity** option, for this. You can also build an appropriate toggle to set `jsonData.oauthPassThru` in your data source configuration page UI.
|
||||||
|
|
||||||
When configured, Grafana can forward authorization HTTP headers such as `Authorization` or `X-ID-Token` to a backend data source. This information is available across the `QueryData`, `CallResource` and `CheckHealth` requests.
|
When configured, Grafana can forward authorization HTTP headers such as `Authorization` or `X-ID-Token` to a backend data source. This information is available across the `QueryData`, `CallResource` and `CheckHealth` requests.
|
||||||
|
|
||||||
@ -295,14 +312,15 @@ To get Grafana to forward the headers, create a HTTP client using the [Grafana p
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
func NewDatasource(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
|
func NewDatasource(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
|
||||||
opts, err := settings.HTTPClientOptions()
|
opts, err := settings.HTTPClientOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("http client options: %w", err)
|
return nil, fmt.Errorf("http client options: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Important: Reuse the same client for each query to avoid using all available connections on a host.
|
||||||
|
|
||||||
opts.ForwardHTTPHeaders = true
|
opts.ForwardHTTPHeaders = true
|
||||||
|
|
||||||
// Important to reuse the same client for each query, to avoid using all available connections on a host
|
|
||||||
cl, err := httpclient.New(opts)
|
cl, err := httpclient.New(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("httpclient new: %w", err)
|
return nil, fmt.Errorf("httpclient new: %w", err)
|
||||||
@ -313,7 +331,7 @@ func NewDatasource(settings backend.DataSourceInstanceSettings) (instancemgmt.In
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||||
// Important to keep the Context, since the injected middleware is configured there
|
// Necessary to keep the Context, since the injected middleware is configured there
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://some-url", nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://some-url", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("new request with context: %w", err)
|
return nil, fmt.Errorf("new request with context: %w", err)
|
||||||
@ -324,9 +342,11 @@ func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataReque
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can see a full working example here: [datasource-http-backend](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-http-backend).
|
You can see a full working plugin example here: [datasource-http-backend](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-http-backend).
|
||||||
|
|
||||||
If you need to access HTTP header information directory, you can also extract that information from the request:
|
### Extract a header from an HTTP request
|
||||||
|
|
||||||
|
If you need to access the HTTP header information directly, you can also extract that information from the request:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||||
@ -362,27 +382,41 @@ func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourc
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Forward cookies for the logged-in user
|
## Work with cookies
|
||||||
|
|
||||||
|
### Forward cookies for the logged-in user
|
||||||
|
|
||||||
Your data source plugin can forward cookies for the logged-in Grafana user to the data source. Use the [DataSourceHttpSettings](https://developers.grafana.com/ui/latest/index.html?path=/story/data-source-datasourcehttpsettings--basic) component on the data source's configuration page. It provides the **Allowed cookies** option, where you can specify the cookie names.
|
Your data source plugin can forward cookies for the logged-in Grafana user to the data source. Use the [DataSourceHttpSettings](https://developers.grafana.com/ui/latest/index.html?path=/story/data-source-datasourcehttpsettings--basic) component on the data source's configuration page. It provides the **Allowed cookies** option, where you can specify the cookie names.
|
||||||
|
|
||||||
When configured, as with [authorization headers](#forward-oauth-identity-for-the-logged-in-user), these cookies are automatically injected if the SDK HTTP client is used.
|
When configured, as with [authorization headers](#forward-oauth-identity-for-the-logged-in-user), these cookies are automatically injected if you use the SDK HTTP client.
|
||||||
|
|
||||||
|
### Extract cookies for the logged-in user
|
||||||
|
|
||||||
You can also extract the cookies in the `QueryData`, `CallResource` and `CheckHealth` requests if required.
|
You can also extract the cookies in the `QueryData`, `CallResource` and `CheckHealth` requests if required.
|
||||||
|
|
||||||
|
**`QueryData`**
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||||
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
|
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**`CallResource`**
|
||||||
|
|
||||||
|
```go
|
||||||
func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||||
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
|
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**`CheckHealth`**
|
||||||
|
|
||||||
|
```go
|
||||||
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||||
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
|
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
|
||||||
|
|
||||||
@ -392,7 +426,9 @@ func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthR
|
|||||||
|
|
||||||
## Forward user header for the logged-in user
|
## Forward user header for the logged-in user
|
||||||
|
|
||||||
When [send_user_header]({{< relref "../../setup-grafana/configure-grafana/_index.md#send_user_header" >}}) is enabled, Grafana will pass the user header to the plugin using the `X-Grafana-User` header. You can forward this header as well as [authorization headers](#forward-oauth-identity-for-the-logged-in-user) or [configured cookies](#forward-cookies-for-the-logged-in-user).
|
When [send_user_header]({{< relref "../../setup-grafana/configure-grafana/_index.md#send_user_header" >}}) is enabled, Grafana passes the user header to the plugin using the `X-Grafana-User` header. You can forward this header as well as [authorization headers](#forward-oauth-identity-for-the-logged-in-user) or [configured cookies](#forward-cookies-for-the-logged-in-user).
|
||||||
|
|
||||||
|
**`QueryData`**
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||||
@ -400,13 +436,21 @@ func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataReque
|
|||||||
|
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**`CallResource`**
|
||||||
|
|
||||||
|
```go
|
||||||
func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||||
u := req.GetHTTPHeader("X-Grafana-User")
|
u := req.GetHTTPHeader("X-Grafana-User")
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**`CheckHealth`**
|
||||||
|
|
||||||
|
```go
|
||||||
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||||
u := req.GetHTTPHeader("X-Grafana-User")
|
u := req.GetHTTPHeader("X-Grafana-User")
|
||||||
|
|
||||||
|
@ -4,42 +4,39 @@ title: Add distributed tracing for backend plugins
|
|||||||
|
|
||||||
# Add distributed tracing for backend plugins
|
# Add distributed tracing for backend plugins
|
||||||
|
|
||||||
> **Note:** This feature requires at least Grafana 9.5.0, and your plugin needs to be built at least with
|
> **Note:** This feature requires at least Grafana 9.5.0, and your plugin needs to be built at least with grafana-plugins-sdk-go v0.157.0. If you run a plugin with tracing features on an older version of Grafana, tracing is disabled.
|
||||||
> grafana-plugins-sdk-go v0.157.0. If you run a plugin with tracing features on an older version of Grafana,
|
|
||||||
> tracing will be disabled.
|
|
||||||
|
|
||||||
## Introduction
|
Distributed tracing allows backend plugin developers to create custom spans in their plugins, and send them to the same endpoint and with the same propagation format as the main Grafana instance. The tracing context is also propagated from the Grafana instance to the plugin, so the plugin's spans will be correlated to the correct trace.
|
||||||
|
|
||||||
Distributed tracing allows backend plugin developers to create custom spans in their plugins, and send them to the same endpoint
|
## Plugin configuration
|
||||||
and with the same propagation format as the main Grafana instance. The tracing context is also propagated from the Grafana instance
|
|
||||||
to the plugin, so the plugin's spans will be correlated to the correct trace.
|
|
||||||
|
|
||||||
## Configuration
|
Plugin tracing must be enabled manually on a per-plugin basis, by specifying `tracing = true` in the plugin's config section:
|
||||||
|
|
||||||
> **Note:** Only OpenTelemetry is supported. If Grafana is configured to use a deprecated tracing system (Jaeger or OpenTracing),
|
|
||||||
> tracing will be disabled in the plugin. Please note that OpenTelemetry + Jaeger propagator is supported.
|
|
||||||
|
|
||||||
OpenTelemetry must be enabled and configured for the Grafana instance. Please refer to [this section](
|
|
||||||
{{< relref "../../setup-grafana/configure-grafana/#tracingopentelemetry" >}}) for more information.
|
|
||||||
|
|
||||||
As of Grafana 9.5.0, plugins tracing must be enabled manually on a per-plugin basis, by specifying `tracing = true` in the plugin's config section:
|
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[plugin.myorg-myplugin-datasource]
|
[plugin.myorg-myplugin-datasource]
|
||||||
tracing = true
|
tracing = true
|
||||||
```
|
```
|
||||||
|
|
||||||
## Implementing tracing in your plugin
|
## OpenTelemetry configuration in Grafana
|
||||||
|
|
||||||
|
Grafana supports [OpenTelemetry](https://opentelemetry.io/) for distributed tracing. If Grafana is configured to use a deprecated tracing system (Jaeger or OpenTracing), then tracing is disabled in the plugin provided by the SDK and configured when calling `datasource.Manage | app.Manage`.
|
||||||
|
|
||||||
|
OpenTelemetry must be enabled and configured for the Grafana instance. Please refer to the [Grafana configuration documentation](
|
||||||
|
{{< relref "../../setup-grafana/configure-grafana/#tracingopentelemetry" >}}) for more information.
|
||||||
|
|
||||||
|
Refer to the [OpenTelemetry Go SDK](https://pkg.go.dev/go.opentelemetry.io/otel) for in-depth documentation about all the features provided by OpenTelemetry.
|
||||||
|
|
||||||
|
> **Note:** If tracing is disabled in Grafana, `backend.DefaultTracer()` returns a no-op tracer.
|
||||||
|
|
||||||
|
## Implement tracing in your plugin
|
||||||
|
|
||||||
> **Note:** Make sure you are using at least grafana-plugin-sdk-go v0.157.0. You can update with `go get -u github.com/grafana/grafana-plugin-sdk-go`.
|
> **Note:** Make sure you are using at least grafana-plugin-sdk-go v0.157.0. You can update with `go get -u github.com/grafana/grafana-plugin-sdk-go`.
|
||||||
|
|
||||||
When OpenTelemetry tracing is enabled on the main Grafana instance and tracing is enabled for a plugin,
|
### Configure a global tracer
|
||||||
the OpenTelemetry endpoint address and propagation format will be passed to the plugin during startup,
|
|
||||||
which will be used to configure a global tracer.
|
|
||||||
|
|
||||||
1. The global tracer is configured automatically if you use <code>datasource.Manage</code> or <code>app.Manage</code> to run your plugin.
|
When OpenTelemetry tracing is enabled on the main Grafana instance and tracing is enabled for a plugin, the OpenTelemetry endpoint address and propagation format is passed to the plugin during startup. These parameters are used to configure a global tracer.
|
||||||
|
|
||||||
This also allows you to specify custom attributes for the default tracer:
|
1. Use `datasource.Manage` or `app.Manage` to run your plugin to automatically configure the global tracer. Specify any custom attributes for the default tracer using `CustomAttributes`:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@ -58,15 +55,15 @@ which will be used to configure a global tracer.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Once tracing is configured, you can access the global tracer with:
|
1. Once you have configured tracing, use the global tracer like this:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
tracing.DefaultTracer()
|
tracing.DefaultTracer()
|
||||||
```
|
```
|
||||||
|
|
||||||
this returns an [OpenTelemetry trace.Tracer](https://pkg.go.dev/go.opentelemetry.io/otel/trace#Tracer), and can be used to create spans.
|
This returns an [OpenTelemetry `trace.Tracer`](https://pkg.go.dev/go.opentelemetry.io/otel/trace#Tracer) for creating spans.
|
||||||
|
|
||||||
For example:
|
**Example:**
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, query backend.DataQuery) (backend.DataResponse, error) {
|
func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, query backend.DataQuery) (backend.DataResponse, error) {
|
||||||
@ -89,18 +86,11 @@ which will be used to configure a global tracer.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Refer to the [OpenTelemetry Go SDK](https://pkg.go.dev/go.opentelemetry.io/otel) for in-depth documentation about all the features provided by OpenTelemetry.
|
|
||||||
|
|
||||||
If tracing is disabled in Grafana, `backend.DefaultTracer()` returns a no-op tracer.
|
|
||||||
|
|
||||||
### Tracing gRPC calls
|
### Tracing gRPC calls
|
||||||
|
|
||||||
A new span is created automatically for each gRPC call (`QueryData`, `CheckHealth`, etc), both on Grafana's side and
|
When tracing is enabled, a new span is created automatically for each gRPC call (`QueryData`, `CheckHealth`, etc.), both on Grafana's side and on the plugin's side. The plugin SDK also injects the trace context into the `context.Context` that is passed to those methods.
|
||||||
on the plugin's side.
|
|
||||||
|
|
||||||
This also injects the trace context into the `context.Context` passed to those methods.
|
You can retrieve the [trace.SpanContext](https://pkg.go.dev/go.opentelemetry.io/otel/trace#SpanContext) with `tracing.SpanContextFromContext` by passing the original `context.Context` to it:
|
||||||
|
|
||||||
This allows you to retrieve the [trace.SpanContext](https://pkg.go.dev/go.opentelemetry.io/otel/trace#SpanContext) by using `tracing.SpanContextFromContext` by passing the original `context.Context` to it:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, query backend.DataQuery) (backend.DataResponse, error) {
|
func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, query backend.DataQuery) (backend.DataResponse, error) {
|
||||||
@ -113,11 +103,8 @@ func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, quer
|
|||||||
|
|
||||||
### Tracing HTTP requests
|
### Tracing HTTP requests
|
||||||
|
|
||||||
When tracing is enabled, a `TracingMiddleware` is also added to the default middleware stack to all HTTP clients created
|
When tracing is enabled, a `TracingMiddleware` is also added to the default middleware stack to all HTTP clients created using the `httpclient.New` or `httpclient.NewProvider`, unless you specify custom middleware. This middleware creates spans for each outgoing HTTP request and provides some useful attributes and events related to the request's lifecycle.
|
||||||
using the `httpclient.New` or `httpclient.NewProvider`, unless custom middlewares are specified.
|
|
||||||
|
|
||||||
This middleware creates spans for each outgoing HTTP request and provides some useful attributes and events related to the request's lifecycle.
|
## Plugin example
|
||||||
|
|
||||||
## Complete plugin example
|
Refer to the [datasource-http-backend plugin example](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-http-backend) for a complete example of a plugin with full distributed tracing support.
|
||||||
|
|
||||||
You can refer to the [datasource-http-backend plugin example](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-http-backend) for a complete example of a plugin that has full tracing support.
|
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
title: Add query editor help
|
title: Add query editor help
|
||||||
---
|
---
|
||||||
|
|
||||||
# Add a query editor help component
|
# Add query editor help
|
||||||
|
|
||||||
By adding a help component to your plugin, you can for example create "cheat sheets" with commonly used queries. When the user clicks on one of the examples, it automatically updates the query editor. It's a great way to increase productivity for your users.
|
Query editors support the addition of a help component to display examples of potential queries. When the user clicks on one of the examples, the query editor is automatically updated. This helps the user to make faster queries.
|
||||||
|
|
||||||
1. Create a file `QueryEditorHelp.tsx` in the `src` directory of your plugin, with the following content:
|
1. In the `src` directory of your plugin, create a file `QueryEditorHelp.tsx` with the following content:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -17,7 +17,7 @@ By adding a help component to your plugin, you can for example create "cheat she
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Configure the plugin to use the `QueryEditorHelp`.
|
1. Configure the plugin to use `QueryEditorHelp`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import QueryEditorHelp from './QueryEditorHelp';
|
import QueryEditorHelp from './QueryEditorHelp';
|
||||||
@ -30,7 +30,7 @@ By adding a help component to your plugin, you can for example create "cheat she
|
|||||||
.setQueryEditorHelp(QueryEditorHelp);
|
.setQueryEditorHelp(QueryEditorHelp);
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Create a few examples.
|
1. Create a few examples of potential queries:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
@ -4,19 +4,15 @@ title: Add support for annotations
|
|||||||
|
|
||||||
# Add support for annotations
|
# Add support for annotations
|
||||||
|
|
||||||
This guide explains how to add support for [annotations]({{< relref "../../dashboards/build-dashboards/annotate-visualizations" >}}) to an existing data source plugin.
|
You can add support to your plugin for annotations that will insert information into Grafana alerts. This guide explains how to add support for [annotations]({{< relref "../../dashboards/build-dashboards/annotate-visualizations/#querying-other-data-sources " >}}) to a data source plugin.
|
||||||
|
|
||||||
This guide assumes that you're already familiar with how to [Build a data source plugin](/tutorials/build-a-data-source-plugin/).
|
## Support annotations in your data source plugin
|
||||||
|
|
||||||
> **Note:** Annotation support for React plugins was released in Grafana 7.2. To support earlier versions, refer to [Add support for annotation for Grafana 7.1](https://grafana.com/docs/grafana/v7.1/developers/plugins/add-support-for-annotations/).
|
To enable annotations, simply add two lines of code to your plugin. Grafana uses your default query editor for editing annotation queries.
|
||||||
|
|
||||||
## Add annotations support to your data source
|
|
||||||
|
|
||||||
To enable annotation support for your data source, add the following two lines of code. Grafana uses your default query editor for editing annotation queries.
|
|
||||||
|
|
||||||
1. Add `"annotations": true` to the [plugin.json]({{< relref "metadata/" >}}) file to let Grafana know that your plugin supports annotations.
|
1. Add `"annotations": true` to the [plugin.json]({{< relref "metadata/" >}}) file to let Grafana know that your plugin supports annotations.
|
||||||
|
|
||||||
**plugin.json**
|
**In `plugin.json`:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -24,9 +20,9 @@ To enable annotation support for your data source, add the following two lines o
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
2. In `datasource.ts`, override the `annotations` property from `DataSourceApi` (or `DataSourceWithBackend` for backend data sources). For the default behavior, you can set `annotations` to an empty object.
|
2. In `datasource.ts`, override the `annotations` property from `DataSourceApi` (or `DataSourceWithBackend` for backend data sources). For the default behavior, set `annotations` to an empty object.
|
||||||
|
|
||||||
**datasource.ts**
|
**In `datasource.ts`:**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
annotations: {
|
annotations: {
|
||||||
|
@ -1,22 +1,18 @@
|
|||||||
---
|
---
|
||||||
title: Add support for Explore queries
|
title: Add features to Explore queries
|
||||||
---
|
---
|
||||||
|
|
||||||
# Add support for Explore queries
|
# Add features for Explore queries
|
||||||
|
|
||||||
This guide explains how to improve support for [Explore]({{< relref "../../explore/" >}}) in an existing data source plugin.
|
[Explore]({{< relref "../../explore/" >}}) allows users can make ad-hoc queries without the use of a dashboard. This is useful when they want to troubleshoot or learn more about the data.
|
||||||
|
|
||||||
This guide assumes that you're already familiar with how to [Build a data source plugin](/tutorials/build-a-data-source-plugin/).
|
Your data source supports Explore by default and uses the existing query editor for the data source. This guide explains how to extend functionality for Explore queries in a data source plugin.
|
||||||
|
|
||||||
With Explore, users can make ad-hoc queries without the use of a dashboard. This is useful when users want to troubleshoot or to learn more about the data.
|
|
||||||
|
|
||||||
Your data source supports Explore by default and uses the existing query editor for the data source.
|
|
||||||
|
|
||||||
## Add an Explore-specific query editor
|
## Add an Explore-specific query editor
|
||||||
|
|
||||||
To extend Explore functionality for your data source, you can define an Explore-specific query editor.
|
To extend Explore functionality for your data source, define an Explore-specific query editor.
|
||||||
|
|
||||||
1. Create a file `ExploreQueryEditor.tsx` in the `src` directory of your plugin, with the following content:
|
1. Create a file `ExploreQueryEditor.tsx` in the `src` directory of your plugin, with content similar to this:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -54,13 +50,13 @@ To extend Explore functionality for your data source, you can define an Explore-
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Selecting preferred visualisation
|
## Select a preferred visualization type
|
||||||
|
|
||||||
Explore should by default select a reasonable visualization for your data so users do not have to tweak and play with the visualizations and just focus on querying. This usually works fairly well and Explore can figure out whether the returned data is time series data or logs or something else.
|
By default, Explore should select an appropriate and useful visualization for your data. It can figure out whether the returned data is time series data or logs or something else, and creates the right type of visualization.
|
||||||
|
|
||||||
If this does not work for you or you want to show some data in a specific visualization, add a hint to your returned data frame using the `preferredVisualisationType` meta attribute.
|
However, if you want a custom visualization, you can add a hint to your returned data frame by setting the `meta' attribute to `preferredVisualisationType`.
|
||||||
|
|
||||||
You can construct a data frame with specific metadata:
|
Construct a data frame with specific metadata like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
const firstResult = new MutableDataFrame({
|
const firstResult = new MutableDataFrame({
|
||||||
|
@ -4,11 +4,9 @@ title: Add support for variables in plugins
|
|||||||
|
|
||||||
# Add support for variables in plugins
|
# Add support for variables in plugins
|
||||||
|
|
||||||
Variables are placeholders for values, and can be used to create things like templated queries and dashboard or panel links. For more information on variables, refer to [Templates and variables]({{< relref "../../dashboards/variables/" >}}).
|
Variables are placeholders for values, and you can use them to create templated queries, and dashboard or panel links. For more information on variables, refer to [Templates and variables]({{< relref "../../dashboards/variables/" >}}).
|
||||||
|
|
||||||
This guide explains how to leverage template variables in your panel plugins and data source plugins.
|
In this guide, you'll see how you can turn a query string like this:
|
||||||
|
|
||||||
We'll see how you can turn a string like this:
|
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT * FROM services WHERE id = "$service"
|
SELECT * FROM services WHERE id = "$service"
|
||||||
@ -24,9 +22,9 @@ Grafana provides a couple of helper functions to interpolate variables in a stri
|
|||||||
|
|
||||||
## Interpolate variables in panel plugins
|
## Interpolate variables in panel plugins
|
||||||
|
|
||||||
For panels, the `replaceVariables` function is available in the PanelProps.
|
For panels, the `replaceVariables` function is available in the `PanelProps`.
|
||||||
|
|
||||||
Add `replaceVariables` to the argument list, and pass it a user-defined template string.
|
Add `replaceVariables` to the argument list, and pass a user-defined template string to it:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
export function SimplePanel({ options, data, width, height, replaceVariables }: Props) {
|
export function SimplePanel({ options, data, width, height, replaceVariables }: Props) {
|
||||||
@ -38,15 +36,15 @@ export function SimplePanel({ options, data, width, height, replaceVariables }:
|
|||||||
|
|
||||||
## Interpolate variables in data source plugins
|
## Interpolate variables in data source plugins
|
||||||
|
|
||||||
For data sources, you need to use the getTemplateSrv, which returns an instance of TemplateSrv.
|
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` from the `runtime` package:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { getTemplateSrv } from '@grafana/runtime';
|
import { getTemplateSrv } from '@grafana/runtime';
|
||||||
```
|
```
|
||||||
|
|
||||||
1. In your `query` method, call the `replace` method with a user-defined template string.
|
1. In your `query` method, call the `replace` method with a user-defined template string:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
|
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
|
||||||
@ -60,9 +58,9 @@ For data sources, you need to use the getTemplateSrv, which returns an instance
|
|||||||
|
|
||||||
## Format multi-value variables
|
## Format multi-value variables
|
||||||
|
|
||||||
When a user selects multiple values for variable, the value of the interpolated variable depends on the [variable format]({{< relref "../../dashboards/variables/variable-syntax/#advanced-variable-format-options" >}}).
|
When a user selects multiple values for a variable, the value of the interpolated variable depends on the [variable format]({{< relref "../../dashboards/variables/variable-syntax/#advanced-variable-format-options" >}}).
|
||||||
|
|
||||||
A data source can define the default format option when no format is specified by adding a third argument to the interpolation function.
|
A data source plugin can define the default format option when no format is specified by adding a third argument to the interpolation function.
|
||||||
|
|
||||||
Let's change the SQL query to use CSV format by default:
|
Let's change the SQL query to use CSV format by default:
|
||||||
|
|
||||||
@ -84,8 +82,8 @@ Not only can you read the value of a variable, you can also update the variable
|
|||||||
|
|
||||||
The following example shows how to update a variable called `service`.
|
The following example shows how to update a variable called `service`.
|
||||||
|
|
||||||
- `query` contains the query parameters you want to update. Query parameters controlling variables are prefixed with `var-`.
|
- `query` contains the query parameters you want to update. The query parameters that control variables are prefixed with `var-`.
|
||||||
- `replace: true` tells Grafana to update the current URL state, rather than creating a new history entry.
|
- `replace: true` tells Grafana to update the current URL state rather than creating a new history entry.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { locationService } from '@grafana/runtime';
|
import { locationService } from '@grafana/runtime';
|
||||||
@ -99,9 +97,9 @@ locationService.partial({ 'var-service': 'billing' }, true);
|
|||||||
|
|
||||||
## Add support for query variables to your data source
|
## Add support for query variables to your data source
|
||||||
|
|
||||||
[Query variables]({{< relref "../../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.
|
A [query variable]({{< relref "../../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.
|
||||||
|
|
||||||
Let's start by defining a query model for the variable query.
|
Let's start by defining a query model for the variable query:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
export interface MyVariableQuery {
|
export interface MyVariableQuery {
|
||||||
@ -110,7 +108,7 @@ export interface MyVariableQuery {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For a data source to support query variables, you must override the `metricFindQuery` in your `DataSourceApi` class. `metricFindQuery` returns an array of `MetricFindValue` which has a single property, `text`:
|
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`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
async metricFindQuery(query: MyVariableQuery, options?: any) {
|
async metricFindQuery(query: MyVariableQuery, options?: any) {
|
||||||
@ -124,15 +122,15 @@ async metricFindQuery(query: MyVariableQuery, options?: any) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note:** By default, Grafana provides a default query model and editor for simple text queries. If that's all you need, then you can leave the query type as `string`.
|
> **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
|
```ts
|
||||||
> async metricFindQuery(query: string, options?: any)
|
async metricFindQuery(query: string, options?: any)
|
||||||
> ```
|
```
|
||||||
|
|
||||||
Let's create a custom query editor to allow the user to edit the query model.
|
Let's create a custom query editor to allow the user to edit the query model.
|
||||||
|
|
||||||
1. Create a `VariableQueryEditor` component.
|
1. Create a `VariableQueryEditor` component:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
@ -185,9 +183,9 @@ 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 previews the values returned by `metricFindQuery`.
|
Grafana saves the query model whenever one of the text fields loses focus (`onBlur`) and then previews the values returned by `metricFindQuery`.
|
||||||
|
|
||||||
The second argument to `onChange` allows you to set a text representation of the query which will appear next to the name of the variable in the variables list.
|
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.
|
||||||
|
|
||||||
1. Finally, configure your plugin to use the query editor.
|
1. Configure your plugin to use the query editor:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { VariableQueryEditor } from './VariableQueryEditor';
|
import { VariableQueryEditor } from './VariableQueryEditor';
|
||||||
|
Reference in New Issue
Block a user