mirror of
https://github.com/grafana/grafana.git
synced 2025-07-28 08:02:25 +08:00
Restructure plugin docs (#24381)
This commit is contained in:
67
docs/sources/developers/plugins/_index.md
Normal file
67
docs/sources/developers/plugins/_index.md
Normal file
@ -0,0 +1,67 @@
|
||||
+++
|
||||
title = "Build a plugin"
|
||||
type = "docs"
|
||||
+++
|
||||
|
||||
# Build a plugin
|
||||
|
||||
For more information on the types of plugins you can build, refer to the [Plugin Overview]({{< relref "../../plugins/_index.md" >}}).
|
||||
|
||||
## Get started
|
||||
|
||||
The easiest way to start developing Grafana plugins is to use the [Grafana Toolkit](https://www.npmjs.com/package/@grafana/toolkit).
|
||||
|
||||
Open the terminal, and run the following command in your [plugin directory]({{< relref "../../installation/configuration/_index.md#plugins" >}}):
|
||||
|
||||
```bash
|
||||
npx @grafana/toolkit plugin:create my-grafana-plugin
|
||||
```
|
||||
|
||||
If you want a more guided introduction to plugin development, check out our tutorials:
|
||||
|
||||
- [Build a panel plugin]({{< relref "../../../../../tutorials/build-a-panel-plugin.md" >}})
|
||||
- [Build a data source plugin]({{< relref "../../../../../tutorials/build-a-data-source-plugin.md" >}})
|
||||
|
||||
## Go further
|
||||
|
||||
Learn more about specific areas of plugin development.
|
||||
|
||||
### Concepts
|
||||
|
||||
Deepen your knowledge through a series of high-level overviews of plugin concepts.
|
||||
|
||||
- [Data frames]({{< relref "data-frames.md" >}})
|
||||
- [Authentication for data source plugins]({{< relref "authentication.md" >}})
|
||||
|
||||
### UI library
|
||||
|
||||
Explore the many UI components in our [Grafana UI library](https://developers.grafana.com/ui).
|
||||
|
||||
### Tutorials
|
||||
|
||||
If you're looking to build your first plugin, check out these introductory tutorials:
|
||||
|
||||
- [Build a panel plugin]({{< relref "../../../../../tutorials/build-a-panel-plugin.md" >}})
|
||||
- [Build a data source plugin]({{< relref "../../../../../tutorials/build-a-data-source-plugin.md" >}})
|
||||
|
||||
Ready to learn more? Check out our other tutorials:
|
||||
|
||||
- [Build a panel plugin with D3.js]({{< relref "../../../../../tutorials/build-a-panel-plugin-with-d3.md" >}})
|
||||
|
||||
### API reference
|
||||
|
||||
Learn more about Grafana options and packages.
|
||||
|
||||
#### Metadata
|
||||
|
||||
- [Plugin metadata]({{< relref "metadata.md" >}})
|
||||
|
||||
#### Typescript
|
||||
|
||||
- [Grafana Data]({{< relref "../../packages_api/data/_index.md" >}})
|
||||
- [Grafana Runtime]({{< relref "../../packages_api/runtime/_index.md" >}})
|
||||
- [Grafana UI]({{< relref "../../packages_api/ui/_index.md" >}})
|
||||
|
||||
#### Go
|
||||
|
||||
- [Grafana Plugin SDK](https://pkg.go.dev/mod/github.com/grafana/grafana-plugin-sdk-go?tab=overview)
|
137
docs/sources/developers/plugins/authentication.md
Normal file
137
docs/sources/developers/plugins/authentication.md
Normal file
@ -0,0 +1,137 @@
|
||||
+++
|
||||
title = "Authentication for data source plugins"
|
||||
type = "docs"
|
||||
aliases = ["/docs/grafana/latest/plugins/developing/auth-for-datasources/"]
|
||||
+++
|
||||
|
||||
# Authentication for data source plugins
|
||||
|
||||
Grafana has a proxy feature that proxies all data requests through the Grafana backend. The main benefit of using the proxy is secure handling of credentials when authenticating against an external/third-party API. The Grafana proxy also adds [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) headers to the proxied requests.
|
||||
|
||||
The proxy supports:
|
||||
|
||||
- [authentication with HTTP Headers]({{< relref "#api-key-http-header-authentication" >}}).
|
||||
- [token authentication]({{< relref "#how-token-authentication-works" >}}) and can automatically renew a token for the user when the token expires.
|
||||
|
||||
## How the proxy works
|
||||
|
||||
The user saves the API key/password on the plugin config page and it is encrypted (using the `secureJsonData` feature) and saved in the Grafana database. When a request from the data source is made, the Grafana proxy will:
|
||||
|
||||
1. Intercept the original request sent from the data source plugin.
|
||||
1. Load the `secureJsonData` data from the database and decrypt the API key or password on the Grafana backend.
|
||||
1. If using token authentication, carry out authentication and generate an OAuth token that will be added as an `Authorization` HTTP header to the requests (or alternatively it will add a HTTP header with the API key).
|
||||
1. Renew the token if it has expired.
|
||||
1. After adding CORS headers and authorization headers, forward the request to the external API.
|
||||
|
||||
This means that users that access the data source config page cannot access the API key or password after they have saved it the first time and that no secret keys are sent in plain text through the browser where they can be spied on.
|
||||
|
||||
For backend authentication to work, the external/third-party API must either have an OAuth endpoint or that the API accepts an API key as a HTTP header for authentication.
|
||||
|
||||
## Encrypting sensitive data
|
||||
|
||||
When a user saves a password or secret with your data source plugin's Config page, then you can save data in an encrypted blob in the Grafana database called `secureJsonData`. Any data saved in the blob is encrypted by Grafana and can only be decrypted by the Grafana server on the backend. This means once a password is saved, no sensitive data is sent to the browser. If the password is saved in the `jsonData` blob or the `password` field then it is unencrypted and anyone with Admin access (with the help of Chrome Developer Tools) can read it.
|
||||
|
||||
This is an example of using the `secureJsonData` blob to save a property called `password` in a html input:
|
||||
|
||||
```html
|
||||
<input type="password" class="gf-form-input" ng-model="ctrl.current.secureJsonData.password" placeholder="password" />
|
||||
```
|
||||
|
||||
## Plugin routes
|
||||
|
||||
A plugin route describes where the intercepted request should be forwarded to and how to authenticate for the external API. You can define multiple routes that can match multiple external API endpoints.
|
||||
|
||||
You specify routes in the `plugin.json` file for your data source plugin. [Here is an example](https://github.com/grafana/azure-monitor-datasource/blob/d74c82145c0a4af07a7e96cc8dde231bfd449bd9/src/plugin.json#L30-L95) with lots of routes (though most plugins will just have one route).
|
||||
|
||||
When you build your URL to the third-party API in your data source class, the URL should start with the text specified in the path field for a route. The proxy will strip out the path text and replace it with the value in the URL field.
|
||||
|
||||
### Simple plugin route example
|
||||
|
||||
- If my code makes a call to URL `azuremonitor/foo/bar` with this code:
|
||||
|
||||
```js
|
||||
this.backendSrv.datasourceRequest({
|
||||
url: url,
|
||||
method: "GET",
|
||||
});
|
||||
```
|
||||
|
||||
- and the plugin has this route:
|
||||
|
||||
```json
|
||||
"routes": [{
|
||||
"path": "azuremonitor",
|
||||
"method": "GET",
|
||||
"url": "https://management.azure.com"
|
||||
}]
|
||||
```
|
||||
|
||||
- then the Grafana proxy will transform the URL from the original request into `https://management.azure.com/foo/bar`
|
||||
- finally, it will add CORS headers and forward the request to the new URL. This example does not do any authentication.
|
||||
|
||||
The `method` parameter is optional. It can be set to a specific HTTP verb to provide more fine-grained control. For example you might have two plugin routes, one for GET requests and one for POST requests.
|
||||
|
||||
### Dynamic routes
|
||||
|
||||
When using routes, you can also reference a variable stored in JsonData or SecureJsonData which is interpolated (replacing the variable text with a value) when the data source makes a request to the proxy. These are variables that were entered by the user on the data source configuration page and saved in the Grafana database.
|
||||
|
||||
In this example, the value for `dynamicUrl` comes from the JsonData blob and the api key's value is set from the SecureJsonData blob. The `urlParams` field is for query string parameters for HTTP GET requests.
|
||||
|
||||
```json
|
||||
"routes": [
|
||||
{
|
||||
"path": "custom/api/v5/*",
|
||||
"method": "GET",
|
||||
"url": "{{.JsonData.dynamicUrl}}",
|
||||
"urlParams": [
|
||||
{"name": "apiKey", "content": "{{.SecureJsonData.apiKey}}"}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Given that:
|
||||
|
||||
- `JsonData.dynamicUrl` has the value `http://example.com/api`
|
||||
- `SecureJsonData.apiKey` has the value `secretKey`
|
||||
|
||||
a call to the URL: `custom/api/v5/some/path`
|
||||
|
||||
will be proxied to the following URL: `http://example.com/api/some/path?apiKey=secretKey`
|
||||
|
||||
An app using this feature can be found [here](https://github.com/grafana/kentik-app).
|
||||
|
||||
## API key/HTTP header authentication
|
||||
|
||||
Some third-party API's accept a HTTP Header for authentication. The [example](https://github.com/grafana/azure-monitor-datasource/blob/d74c82145c0a4af07a7e96cc8dde231bfd449bd9/src/plugin.json#L91-L93) below has a `headers` section that defines the name of the HTTP Header that the API expects and it uses the `SecureJSONData` blob to fetch an encrypted API key. The Grafana server proxy will decrypt the key, add the `X-API-Key` header to the request and forward it to the third-party API.
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "appinsights",
|
||||
"method": "GET",
|
||||
"url": "https://api.applicationinsights.io",
|
||||
"headers": [{ "name": "X-API-Key", "content": "{{.SecureJsonData.appInsightsApiKey}}" }]
|
||||
}
|
||||
```
|
||||
|
||||
## How token authentication works
|
||||
|
||||
The token auth section in the `plugin.json` file looks like this:
|
||||
|
||||
```json
|
||||
"tokenAuth": {
|
||||
"url": "https://login.microsoftonline.com/{{.JsonData.tenantId}}/oauth2/token",
|
||||
"params": {
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": "{{.JsonData.clientId}}",
|
||||
"client_secret": "{{.SecureJsonData.clientSecret}}",
|
||||
"resource": "https://management.azure.com/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This interpolates in data from both `jsonData` and `secureJsonData` to generate the token request to the third-party API. It is common for tokens to have a short expiry period (30 minutes). The Grafana proxy automatically renews the token if it has expired.
|
||||
|
||||
## Always restart the Grafana server after route changes
|
||||
|
||||
The plugin.json files are only loaded when the Grafana server starts so when a route is added or changed then the Grafana server has to be restarted for the changes to take effect.
|
285
docs/sources/developers/plugins/backend.md
Normal file
285
docs/sources/developers/plugins/backend.md
Normal file
@ -0,0 +1,285 @@
|
||||
+++
|
||||
title = "Backend plugins"
|
||||
keywords = ["grafana", "plugins", "backend", "plugin", "backend-plugins", "documentation"]
|
||||
type = "docs"
|
||||
aliases = ["/docs/grafana/latest/plugins/developing/backend-plugins-guide/"]
|
||||
+++
|
||||
|
||||
# Backend plugins
|
||||
|
||||
Grafana added support for plugins in Grafana 3.0 and this enabled the Grafana community to create panel plugins and data source plugins. It was wildly successful and has made Grafana much more useful as you can integrate it with anything and do any type of custom visualization that you want. However, these plugin hooks are on the frontend only and we also want to provide hooks into the Grafana backend to allow the community to extend and improve Grafana in new ways.
|
||||
|
||||
Once Grafana introduced the alerting feature, external data source plugins needed a backend component for the Grafana server to execute queries for evaluating alert rules (as the alerting engine cannot call frontend JavaScript code). So the obvious first backend plugin type is the **Datasource backend plugin** and it is a new component for an existing data source plugin. This new plugin type will enable alerting for external data source plugins but can also be used for achieving different goals such as query caching, request proxying, custom authentication methods, and more.
|
||||
|
||||
## Grafana's Backend Plugin System
|
||||
|
||||
The backend plugin feature is implemented with the [HashiCorp plugin system](https://github.com/hashicorp/go-plugin) which is a Go plugin system over RPC. Grafana server launches each plugin as a subprocess and communicates with it over RPC. This approach has a number of benefits:
|
||||
|
||||
- Plugins can't crash your grafana process: a panic in a plugin doesn't panic the server.
|
||||
- Plugins are easy to develop: just write a Go application and `go build` (or use any other language which supports gRPC).
|
||||
- Plugins can be relatively secure: The plugin only has access to the interfaces and args given to it, not to the entire memory space of the process.
|
||||
|
||||
## Data source plugin interface
|
||||
|
||||
The plugin interface is very simple and described as a Go interface type in [Grafana](https://github.com/grafana/grafana/blob/6724aaeff9a332dc73b4ee0f8abe0621f7253142/pkg/tsdb/query_endpoint.go#L10-L12) and as a general [RPC service](https://github.com/grafana/grafana-plugin-model/blob/84176c64269d8060f99e750ee8aba6f062753336/datasource.proto#L96-L98) in the corresponding `.proto` (protocol buffer file):
|
||||
|
||||
```go
|
||||
type TsdbQueryEndpoint interface {
|
||||
Query(ctx context.Context, ds *models.DataSource, query *TsdbQuery) (*Response, error)
|
||||
}
|
||||
```
|
||||
|
||||
```protobuf
|
||||
service DatasourcePlugin {
|
||||
rpc Query(DatasourceRequest) returns (DatasourceResponse);
|
||||
}
|
||||
```
|
||||
|
||||
Thus, a datasource plugin should only implement the `Query()` method.
|
||||
|
||||
## Introduction to building a backend component for a plugin
|
||||
|
||||
The [Simple JSON backend](https://github.com/grafana/simple-json-backend-datasource) data source is a good example of writing a simple backend plugin in Go. Let's take a look at some key points.
|
||||
|
||||
### Metadata
|
||||
|
||||
The plugin needs to know it has a backend component, this is done in the `plugin.json` file by setting two fields: `backend` and `executable`. If you want to enable alerting for your data source, set the `alerting` field to `true` as well.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "grafana-simple-json-backend-datasource",
|
||||
"name": "Simple Json backend",
|
||||
"type": "datasource",
|
||||
|
||||
"metrics": true,
|
||||
"annotations": true,
|
||||
"backend": true,
|
||||
"alerting": true,
|
||||
"executable": "simple-json-plugin",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
`executable` should be the the the first part of the binary filename. The actual binary filename has 3 possible endings:
|
||||
|
||||
- \_linux_amd64
|
||||
- \_darwin_amd64
|
||||
- \_windows_amd64.exe
|
||||
|
||||
When Grafana loads the plugin binary, it uses the executable field plus the current OS (Grafana knows which OS it is running on) to load in the correct version of the plugin. So in Simple JSON the executable field is `simple-json-plugin` and the 3 binaries are named:
|
||||
|
||||
- `simple-json-plugin_darwin_amd64`
|
||||
- `simple-json-plugin_linux_amd64`
|
||||
- `simple-json-plugin_windows_amd64.exe`
|
||||
|
||||
The resulting plugin directory will look like this:
|
||||
|
||||
```text
|
||||
simple-json-backend-datasource/
|
||||
|-- dist/
|
||||
| |-- partials/
|
||||
| |-- module.js
|
||||
| |-- plugin.json
|
||||
| |-- simple-json-plugin_linux_amd64
|
||||
| |-- simple-json-plugin_darwin_amd64
|
||||
| |-- simple-json-plugin_windows_amd64.exe
|
||||
...
|
||||
```
|
||||
|
||||
### Plugin code
|
||||
|
||||
A `pkg/` directory contains three `.go` files:
|
||||
|
||||
- `plugin.go` - an entry point of the plugin. This file would be very similar for your data source - you just need to change some details like the plugin name etc.
|
||||
- `datasource.go` - contains `Query()` method implementation and other plugin logic.
|
||||
- `models.go` - types for request and response specific to your data source.
|
||||
|
||||
The data source type is declared in [`datasource.go`](https://github.com/grafana/simple-json-backend-datasource/blob/7927ff0db60c3402dbf954a454f19d7230e18deb/pkg/datasource.go#L21-L24):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
)
|
||||
|
||||
type JsonDatasource struct {
|
||||
plugin.NetRPCUnsupportedPlugin
|
||||
}
|
||||
```
|
||||
|
||||
The only requirement for the plugin type is that it should extend `plugin.NetRPCUnsupportedPlugin`. You can include more fields into your struct if you want to add some data source-specific features, like logging, cache etc:
|
||||
|
||||
```go
|
||||
type JsonDatasource struct {
|
||||
plugin.NetRPCUnsupportedPlugin
|
||||
logger hclog.Logger
|
||||
}
|
||||
```
|
||||
|
||||
The main method you should implement is the [`Query()`](https://github.com/grafana/simple-json-backend-datasource/blob/7927ff0db60c3402dbf954a454f19d7230e18deb/pkg/datasource.go#L26):
|
||||
|
||||
```go
|
||||
func (t *JsonDatasource) Query(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
|
||||
...
|
||||
```
|
||||
|
||||
#### Request format
|
||||
|
||||
In order to call this method from the [frontend part of your data source](https://github.com/grafana/simple-json-backend-datasource/blob/7927ff0db60c3402dbf954a454f19d7230e18deb/src/datasource.ts#L116), use the `/api/tsdb/query` endpoint:
|
||||
|
||||
```js
|
||||
class SimpleJSONDatasource {
|
||||
...
|
||||
|
||||
doTsdbRequest(options) {
|
||||
const tsdbRequest = {
|
||||
from: options.range.from.valueOf().toString(),
|
||||
to: options.range.to.valueOf().toString(),
|
||||
queries: options.targets,
|
||||
};
|
||||
|
||||
return this.backendSrv.datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
data: tsdbRequest
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This endpoint gets data in the following format (see [pkg/api/metrics.go](https://github.com/grafana/grafana/blob/7b63913dc1d79da07f0329cf19dc4c2704ec488f/pkg/api/metrics.go#L16) and [pkg/api/dtos/models.go](https://github.com/grafana/grafana/blob/7b63913dc1d79da07f0329cf19dc4c2704ec488f/pkg/api/dtos/models.go#L43-L47)):
|
||||
|
||||
```js
|
||||
{
|
||||
from: "1555324640782", // Optional, time range from
|
||||
to: "1555328240782", // Optional, time range to
|
||||
queries: [
|
||||
{
|
||||
datasourceId: 42, // Required
|
||||
refId: "A", // Optional, default is "A"
|
||||
maxDataPoints: 100, // Optional, default is 100
|
||||
intervalMs: 1000, // Optional, default is 1000
|
||||
|
||||
myFieldFoo: "bar", // Any other fields,
|
||||
myFieldBar: "baz", // defined by user
|
||||
...
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
There is only one query function but it is possible to move all your queries to the backend. In order to achieve this, you could add a kind of `queryType` field to your query model and check this type in the backend code. The Stackdriver and Cloudwatch core plugins have examples of supporting multiple types of queries if you need/want to do this:
|
||||
|
||||
- Stackdriver: [pkg/tsdb/stackdriver/stackdriver.go](https://github.com/grafana/grafana/blob/6724aaeff9a332dc73b4ee0f8abe0621f7253142/pkg/tsdb/stackdriver/stackdriver.go#L75-L88)
|
||||
- Cloudwatch: [pkg/tsdb/cloudwatch/cloudwatch.go](https://github.com/grafana/grafana/blob/7b63913dc1d79da07f0329cf19dc4c2704ec488f/pkg/tsdb/cloudwatch/cloudwatch.go#L62-L74)
|
||||
|
||||
#### Response format
|
||||
|
||||
Go types for the query response can be found in Grafana tsdb models ([pkg/tsdb/models.go](https://github.com/grafana/grafana/blob/7b63913dc1d79da07f0329cf19dc4c2704ec488f/pkg/tsdb/models.go#L22-L34)) or in the corresponding protocol buffer file ([datasource.proto](https://github.com/grafana/grafana-plugin-model/blob/84176c64269d8060f99e750ee8aba6f062753336/datasource.proto#L26-L36))
|
||||
|
||||
```protobuf
|
||||
// datasource.proto
|
||||
|
||||
message DatasourceResponse {
|
||||
repeated QueryResult results = 1;
|
||||
}
|
||||
|
||||
message QueryResult {
|
||||
string error = 1;
|
||||
string refId = 2;
|
||||
string metaJson = 3;
|
||||
repeated TimeSeries series = 4;
|
||||
repeated Table tables = 5;
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// pkg/tsdb/models.go
|
||||
|
||||
type Response struct {
|
||||
Results map[string]*QueryResult `json:"results"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type QueryResult struct {
|
||||
Error error `json:"-"`
|
||||
ErrorString string `json:"error,omitempty"`
|
||||
RefId string `json:"refId"`
|
||||
Meta *simplejson.Json `json:"meta,omitempty"`
|
||||
Series TimeSeriesSlice `json:"series"`
|
||||
Tables []*Table `json:"tables"`
|
||||
}
|
||||
```
|
||||
|
||||
The resulting JSON response which the frontend will receive looks like this:
|
||||
|
||||
```js
|
||||
results: {
|
||||
A: {
|
||||
refId: "A",
|
||||
series: [
|
||||
{ name: "series_1", points: [...] },
|
||||
{ name: "series_2", points: [...] },
|
||||
...
|
||||
],
|
||||
tables: null,
|
||||
// Request metadata (any arbitrary JSON).
|
||||
// Optional, empty field will be omitted.
|
||||
meta: {},
|
||||
// Error message. Optional, empty field will be omitted.
|
||||
error: "Request failed",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
Logs from the plugin will be automatically sent to the Grafana server and will appear in its log flow. Grafana server reads logs from the plugin's `stderr` stream, so with the standard `log` package you have to set output to `os.Stderr` first:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
log.SetOutput(os.Stderr)
|
||||
log.Println("from plugin!")
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Another option for logging - using [go-hclog](https://github.com/hashicorp/go-hclog) package:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
)
|
||||
|
||||
var pluginLogger = hclog.New(&hclog.LoggerOptions{
|
||||
Name: "simple-json-backend-datasource",
|
||||
Level: hclog.LevelFromString("DEBUG"),
|
||||
})
|
||||
|
||||
func main() {
|
||||
pluginLogger.Debug("Running Simple JSON backend datasource")
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Building the backend binary
|
||||
|
||||
Building the binary depends on which OS you are using.
|
||||
|
||||
For a Linux distro, the build command would be:
|
||||
|
||||
```sh
|
||||
go build -o ./dist/simple-json-plugin_linux_amd64 ./pkg
|
||||
```
|
||||
|
||||
On Windows, the command would be:
|
||||
|
||||
```sh
|
||||
go build -o ./dist/simple-json-plugin_windows_amd64.exe ./pkg
|
||||
```
|
||||
|
||||
Restart your Grafana server and then check the Grafana logs to make sure your plugin is loaded properly.
|
181
docs/sources/developers/plugins/data-frames.md
Normal file
181
docs/sources/developers/plugins/data-frames.md
Normal file
@ -0,0 +1,181 @@
|
||||
+++
|
||||
title = "Data frames"
|
||||
type = "docs"
|
||||
+++
|
||||
|
||||
# Data frames
|
||||
|
||||
Grafana supports a variety of different data sources, each with its own data model. To make this possible, Grafana consolidates the query results from each of these data sources into one unified data structure called a _data frame_.
|
||||
|
||||
The data frame structure is a concept that's borrowed from data analysis tools like the [R programming language](https://www.r-project.org), and [Pandas](https://pandas.pydata.org/).
|
||||
|
||||
> Data frames are available in Grafana 7.0+, and replaced the Time series and Table structures with a more generic data structure that can support a wider range of data types.
|
||||
|
||||
This document gives an overview of the data frame structure, and of how data is handled within Grafana.
|
||||
|
||||
## The data frame
|
||||
|
||||
A data frame is a columnar-oriented table structure, which means it stores data by column and not by row. To understand what this means, let’s look at the TypeScript definition used by Grafana:
|
||||
|
||||
```ts
|
||||
interface DataFrame {
|
||||
name?: string;
|
||||
// reference to query that create the frame
|
||||
refId?: string;
|
||||
|
||||
fields: []Field;
|
||||
}
|
||||
```
|
||||
|
||||
In essence, a data frame is a collection of _fields_, where each field corresponds to a column. Each field, in turn, consists of a collection of values, along with meta information, such as the data type of those values.
|
||||
|
||||
```ts
|
||||
interface Field {
|
||||
name: string;
|
||||
// Prometheus like Labels / Tags
|
||||
labels?: Record<string, string>;
|
||||
|
||||
// For example string, number, time (or more specific primitives in the backend)
|
||||
type: FieldType;
|
||||
// Array of values all of the same type
|
||||
values: Vector<T>;
|
||||
|
||||
// Optional display data for the field (e.g. unit, name over-ride, etc)
|
||||
config: FieldConfig;
|
||||
}
|
||||
```
|
||||
|
||||
Let's look an example. The table below demonstrates a data frame with two fields, _time_ and _temperature_.
|
||||
|
||||
| time | temperature |
|
||||
|---------------------|-------------|
|
||||
| 2020-01-02 03:04:00 | 45.0 |
|
||||
| 2020-01-02 03:05:00 | 47.0 |
|
||||
| 2020-01-02 03:06:00 | 48.0 |
|
||||
|
||||
Each field has three values, and each value in a field must share the same type. In this case, all values in the time field are timestamps, and all values in the temperature field are numbers.
|
||||
|
||||
One restriction on data frames is that all fields in the frame must be of the same length to be a valid data frame.
|
||||
|
||||
### Field configuration
|
||||
|
||||
Each field in a data frame contains optional information about the values in the field, such as units, scaling, and so on.
|
||||
|
||||
By adding field configurations to a data frame, Grafana can configure visualizations automatically. For example, you could configure Grafana to automatically set the unit provided by the data source.
|
||||
|
||||
## Transformations
|
||||
|
||||
Along with the type information, field configs enables _data transformations_ within Grafana.
|
||||
|
||||
A data transformation is any function that accepts a data frame as input, and returns another data frame as output. By using data frames in your plugin, you get a range of transformations for free.
|
||||
|
||||
## Data frames as time series
|
||||
|
||||
A data frame with at least one time field is considered a _time series_.
|
||||
|
||||
For more information on time series, refer to our [Introduction to time series](https://grafana.com/docs/grafana/latest/guides/timeseries/).
|
||||
|
||||
### Wide format
|
||||
|
||||
When a collection of time series share the same _time index_—the time fields in each time series are identical—they can be stored together, in a _wide_ format. By reusing the time field, we can reduce the amount of data being sent to the browser.
|
||||
|
||||
In this example, the `cpu` usage from each host share the time index, so we can store them in the same data frame.
|
||||
|
||||
```text
|
||||
Name: Wide
|
||||
Dimensions: 3 fields by 2 rows
|
||||
+---------------------+-----------------+-----------------+
|
||||
| Name: time | Name: cpu | Name: cpu |
|
||||
| Labels: | Labels: host=a | Labels: host=b |
|
||||
| Type: []time.Time | Type: []float64 | Type: []float64 |
|
||||
+---------------------+-----------------+-----------------+
|
||||
| 2020-01-02 03:04:00 | 3 | 4 |
|
||||
| 2020-01-02 03:05:00 | 6 | 7 |
|
||||
+---------------------+-----------------+-----------------+
|
||||
```
|
||||
|
||||
However, if the two time series don't share the same time values, they are represented as two distinct data frames.
|
||||
|
||||
```text
|
||||
Name: cpu
|
||||
Dimensions: 2 fields by 2 rows
|
||||
+---------------------+-----------------+
|
||||
| Name: time | Name: cpu |
|
||||
| Labels: | Labels: host=a |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+---------------------+-----------------+
|
||||
| 2020-01-02 03:04:00 | 3 |
|
||||
| 2020-01-02 03:05:00 | 6 |
|
||||
+---------------------+-----------------+
|
||||
|
||||
Name: cpu
|
||||
Dimensions: 2 fields by 2 rows
|
||||
+---------------------+-----------------+
|
||||
| Name: time | Name: cpu |
|
||||
| Labels: | Labels: host=b |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+---------------------+-----------------+
|
||||
| 2020-01-02 03:04:01 | 4 |
|
||||
| 2020-01-02 03:05:01 | 7 |
|
||||
+---------------------+-----------------+
|
||||
```
|
||||
|
||||
The wide format can typically be used when multiple time series are collected by the same process. In this case, every measurement is made at the same interval and will therefore share the same time values.
|
||||
|
||||
### Long format
|
||||
|
||||
Some data sources return data in a _long_ format (also called _narrow_ format). This is common format returned by, for example, SQL databases.
|
||||
|
||||
In long format, string values are represented as separate fields rather than as labels. As a result, a data form in long form may have duplicated time values.
|
||||
|
||||
Grafana can detect and convert data frames in long format into wide format.
|
||||
|
||||
> Note: Long format is currently only supported in the backend: [Grafana Issue #22219](https://github.com/grafana/grafana/issues/22219).
|
||||
|
||||
For example, the following data frame in long format:
|
||||
|
||||
```text
|
||||
Name: Long
|
||||
Dimensions: 4 fields by 4 rows
|
||||
+---------------------+-----------------+-----------------+----------------+
|
||||
| Name: time | Name: aMetric | Name: bMetric | Name: host |
|
||||
| Labels: | Labels: | Labels: | Labels: |
|
||||
| Type: []time.Time | Type: []float64 | Type: []float64 | Type: []string |
|
||||
+---------------------+-----------------+-----------------+----------------+
|
||||
| 2020-01-02 03:04:00 | 2 | 10 | foo |
|
||||
| 2020-01-02 03:04:00 | 5 | 15 | bar |
|
||||
| 2020-01-02 03:05:00 | 3 | 11 | foo |
|
||||
| 2020-01-02 03:05:00 | 6 | 16 | bar |
|
||||
+---------------------+-----------------+-----------------+----------------+
|
||||
```
|
||||
|
||||
can be converted into a data frame in wide format:
|
||||
|
||||
```text
|
||||
Name: Wide
|
||||
Dimensions: 5 fields by 2 rows
|
||||
+---------------------+------------------+------------------+------------------+------------------+
|
||||
| Name: time | Name: aMetric | Name: bMetric | Name: aMetric | Name: bMetric |
|
||||
| Labels: | Labels: host=foo | Labels: host=foo | Labels: host=bar | Labels: host=bar |
|
||||
| Type: []time.Time | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 |
|
||||
+---------------------+------------------+------------------+------------------+------------------+
|
||||
| 2020-01-02 03:04:00 | 2 | 10 | 5 | 15 |
|
||||
| 2020-01-02 03:05:00 | 3 | 11 | 6 | 16 |
|
||||
+---------------------+------------------+------------------+------------------+------------------+
|
||||
```
|
||||
|
||||
## Technical references
|
||||
|
||||
This section contains links to technical reference and implementations of data frames.
|
||||
|
||||
### Apache Arrow
|
||||
|
||||
The data frame structure is inspired by, and uses the [Apache Arrow Project](https://arrow.apache.org/). Javascript Data frames use Arrow Tables as the underlying structure, and the backend Go code serializes its Frames in Arrow Tables for transmission.
|
||||
|
||||
### Javascript
|
||||
|
||||
The Javascript implementation of data frames is in the [`/src/dataframe` folder](https://github.com/grafana/grafana/tree/master/packages/grafana-data/src/dataframe) and [`/src/types/dataframe.ts`](https://github.com/grafana/grafana/blob/master/packages/grafana-data/src/types/dataFrame.ts) of the [`@grafana/data` package](https://github.com/grafana/grafana/tree/master/packages/grafana-data).
|
||||
|
||||
### Go
|
||||
|
||||
For documentation on the Go implementation of data frames, refer to the [github.com/grafana/grafana-plugin-sdk-go/data package](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/data?tab=doc).
|
132
docs/sources/developers/plugins/legacy/_index.md
Normal file
132
docs/sources/developers/plugins/legacy/_index.md
Normal file
@ -0,0 +1,132 @@
|
||||
+++
|
||||
title = "Legacy plugins"
|
||||
type = "docs"
|
||||
aliases = ["/docs/grafana/latest/plugins/development/", "/docs/grafana/latest/plugins/datasources/", "/docs/grafana/latest/plugins/apps/", "/docs/grafana/latest/plugins/panels/", "/docs/grafana/latest/plugins/developing/development/"]
|
||||
+++
|
||||
|
||||
# Legacy plugins
|
||||
|
||||
> **Note**: Since Grafana 7.0, writing plugins using Angular is no longer recommended. If you're looking to build a new plugin, refer to [Plugins]({{< relref "../_index.md" >}}).
|
||||
|
||||
You can extend Grafana by writing your own plugins and then share them with other users in [our plugin repository](https://grafana.com/plugins).
|
||||
|
||||
Grafana already has a strong community of contributors and plugin developers. By making it easier to develop and install plugins with resources such as this guide, we hope that the community can grow even stronger and develop new plugins that we would never think about.
|
||||
|
||||
## Short version
|
||||
|
||||
1. [Set up Grafana](https://github.com/grafana/grafana/blob/master/contribute/developer-guide.md)
|
||||
2. Clone an example plugin into ```/var/lib/grafana/plugins``` or `data/plugins` (relative to grafana git repo if you're running development version from source dir)
|
||||
3. Use one of our example plugins as a starting point
|
||||
|
||||
Example plugins
|
||||
|
||||
- ["Hello World" panel using Angular](https://github.com/grafana/simple-angular-panel)
|
||||
- ["Hello World" panel using React](https://github.com/grafana/simple-react-panel)
|
||||
- [Simple json data source](https://github.com/grafana/simple-json-datasource)
|
||||
- [Clock panel](https://github.com/grafana/clock-panel)
|
||||
- [Pie chart panel](https://github.com/grafana/piechart-panel)
|
||||
|
||||
You might also be interested in the available tutorials around authoring a plugin.
|
||||
|
||||
- [Grafana Tutorials](https://grafana.com/tutorials/)
|
||||
|
||||
## What languages?
|
||||
|
||||
Since everything turns into JavaScript, it's up to you to choose which language you want. That said, it's probably a good idea to choose es6 or TypeScript, because we use es6 classes in Grafana. So it's easier to get inspiration from the Grafana repo if you choose one of those languages.
|
||||
|
||||
## Buildscript
|
||||
|
||||
You can use any build system that supports systemjs. All the built content should end up in a folder named ```dist``` and be committed to the repository. By committing the dist folder, the person who installs your plugin does not have to run any build script. All of our example plugins have a build script configured.
|
||||
|
||||
## Keep your plugin up to date
|
||||
|
||||
New versions of Grafana can sometimes cause plugins to break. Check out our [PLUGIN_DEV.md](https://github.com/grafana/grafana/blob/master/PLUGIN_DEV.md) doc for changes in
|
||||
Grafana that can impact your plugin.
|
||||
|
||||
## Metadata
|
||||
|
||||
See the [coding styleguide]({{< relref "style-guide.md" >}}) for details on the metadata.
|
||||
|
||||
## module.(js|ts)
|
||||
|
||||
This is the entry point for every plugin. This is the place where you should export
|
||||
your plugin implementation. Depending on what kind of plugin you are developing you
|
||||
will be expected to export different things. You can find what's expected for [datasource]({{< relref "data-sources.md" >}}), [panels]({{< relref "panels.md" >}})
|
||||
and [apps]({{< relref "apps.md" >}}) plugins in the documentation.
|
||||
|
||||
The Grafana SDK is quite small so far and can be found here:
|
||||
|
||||
- [SDK file in Grafana](https://github.com/grafana/grafana/blob/master/public/app/plugins/sdk.ts)
|
||||
|
||||
The SDK contains three different plugin classes: PanelCtrl, MetricsPanelCtrl and QueryCtrl. For plugins of the panel type, the module.js file should export one of these. There are some extra classes for [data sources]({{< relref "data-sources.md" >}}).
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
import {ClockCtrl} from './clock_ctrl';
|
||||
|
||||
export {
|
||||
ClockCtrl as PanelCtrl
|
||||
};
|
||||
```
|
||||
|
||||
The module class is also where css for the dark and light themes is imported:
|
||||
|
||||
```javascript
|
||||
import {loadPluginCss} from 'app/plugins/sdk';
|
||||
import WorldmapCtrl from './worldmap_ctrl';
|
||||
|
||||
loadPluginCss({
|
||||
dark: 'plugins/grafana-worldmap-panel/css/worldmap.dark.css',
|
||||
light: 'plugins/grafana-worldmap-panel/css/worldmap.light.css'
|
||||
});
|
||||
|
||||
export {
|
||||
WorldmapCtrl as PanelCtrl
|
||||
};
|
||||
```
|
||||
|
||||
## Start developing your plugin
|
||||
|
||||
There are three ways that you can start developing a Grafana plugin.
|
||||
|
||||
1. Set up a Grafana development environment. [(described here)](https://github.com/grafana/grafana/blob/master/contribute/developer-guide.md) and place your plugin in the ```data/plugins``` folder.
|
||||
2. Install Grafana and place your plugin in the plugins directory which is set in your [config file](/installation/configuration). By default this is `/var/lib/grafana/plugins` on Linux systems.
|
||||
3. Place your plugin directory anywhere you like and specify it grafana.ini.
|
||||
|
||||
We encourage people to set up the full Grafana environment so that you can get inspiration from the rest of the Grafana code base.
|
||||
|
||||
When Grafana starts, it scans the plugin folders and mounts every folder that contains a plugin.json file unless
|
||||
the folder contains a subfolder named dist. In that case, Grafana mounts the dist folder instead.
|
||||
This makes it possible to have both built and src content in the same plugin Git repo.
|
||||
|
||||
## Grafana Events
|
||||
|
||||
There are a number of Grafana events that a plugin can hook into:
|
||||
|
||||
- `init-edit-mode` can be used to add tabs when editing a panel
|
||||
- `panel-teardown` can be used for clean up
|
||||
- `data-received` is an event in that is triggered on data refresh and can be hooked into
|
||||
- `data-snapshot-load` is an event triggered to load data when in snapshot mode.
|
||||
- `data-error` is used to handle errors on dashboard refresh.
|
||||
|
||||
If a panel receives data and hooks into the `data-received` event then it should handle snapshot mode too. Otherwise the panel will not work if saved as a snapshot. [Getting Plugins to work in Snapshot Mode]({{< relref "snapshot-mode.md" >}}) describes how to add support for this.
|
||||
|
||||
## Examples
|
||||
|
||||
We have three different examples that you can fork/download to get started developing your Grafana plugin.
|
||||
|
||||
- [simple-json-datasource](https://github.com/grafana/simple-json-datasource) (small data source plugin for querying json data from backends)
|
||||
- [simple-app-plugin](https://github.com/grafana/simple-app-plugin)
|
||||
- [clock-panel](https://github.com/grafana/clock-panel)
|
||||
- [singlestat-panel](https://github.com/grafana/grafana/tree/master/public/app/plugins/panel/singlestat)
|
||||
- [piechart-panel](https://github.com/grafana/piechart-panel)
|
||||
|
||||
## Other Articles
|
||||
|
||||
- [Getting Plugins to work in Snapshot Mode]({{< relref "snapshot-mode.md" >}})
|
||||
- [Plugin Defaults and Editor Mode]({{< relref "defaults-and-editor-mode.md" >}})
|
||||
- [Grafana Plugin Code Styleguide]({{< relref "style-guide.md" >}})
|
||||
- [Grafana Apps]({{< relref "apps.md" >}})
|
||||
- [Grafana Data Sources]({{< relref "data-sources.md" >}})
|
||||
- [plugin.json Schema]({{< relref "metadata.md" >}})
|
55
docs/sources/developers/plugins/legacy/apps.md
Normal file
55
docs/sources/developers/plugins/legacy/apps.md
Normal file
@ -0,0 +1,55 @@
|
||||
+++
|
||||
title = "Legacy app plugins"
|
||||
keywords = ["grafana", "plugins", "documentation"]
|
||||
type = "docs"
|
||||
aliases = ["/docs/grafana/latest/plugins/developing/apps/"]
|
||||
+++
|
||||
|
||||
# Legacy app plugins
|
||||
|
||||
App plugins are a Grafana plugin that can bundle data source and panel plugins within one package. They also enable the plugin author to create custom pages within Grafana. The custom pages enable the plugin author to include things like documentation, sign-up forms, or to control other services with HTTP requests.
|
||||
|
||||
Data source and panel plugins will show up like normal plugins. The app pages will be available in the main menu.
|
||||
|
||||
{{< imgbox img="/img/docs/v3/app-in-main-menu.png" caption="App in Main Menu" >}}
|
||||
|
||||
## Enabling app plugins
|
||||
|
||||
After installing an app, it has to be enabled before it shows up as a data source or panel. You can do that on the app page in the config tab.
|
||||
|
||||
## Developing an App Plugin
|
||||
|
||||
An App is a bundle of panels, dashboards and/or data source(s). There is nothing different about developing panels and data sources for an app.
|
||||
|
||||
Apps have to be enabled in Grafana and should import any included dashboards when the user enables it. A ConfigCtrl class should be created and the dashboards imported in the postUpdate hook. See example below:
|
||||
|
||||
```javascript
|
||||
export class ConfigCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope, $injector, $q) {
|
||||
this.$q = $q;
|
||||
this.enabled = false;
|
||||
this.appEditCtrl.setPostUpdateHook(this.postUpdate.bind(this));
|
||||
}
|
||||
|
||||
postUpdate() {
|
||||
if (!this.appModel.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO, whatever you want
|
||||
console.log('Post Update:', this);
|
||||
}
|
||||
}
|
||||
ConfigCtrl.templateUrl = 'components/config/config.html';
|
||||
```
|
||||
|
||||
If possible a link to a dashboard or custom page should be shown after enabling the app to guide the user to the appropriate place.
|
||||
|
||||
{{< imgbox img="/img/docs/app_plugin_after_enable.png" caption="After enabling" >}}
|
||||
|
||||
### Develop your own App
|
||||
|
||||
> Our goal is not to have a very extensive documentation but rather have actual
|
||||
> code that people can look at. An example implementation of an app can be found
|
||||
> in this [example app repo](https://github.com/grafana/simple-app-plugin)
|
189
docs/sources/developers/plugins/legacy/data-sources.md
Normal file
189
docs/sources/developers/plugins/legacy/data-sources.md
Normal file
@ -0,0 +1,189 @@
|
||||
+++
|
||||
title = "Legacy data source plugins"
|
||||
keywords = ["grafana", "plugins", "documentation"]
|
||||
type = "docs"
|
||||
aliases = ["/docs/grafana/latest/plugins/developing/datasources/"]
|
||||
+++
|
||||
|
||||
# Legacy data source plugins
|
||||
|
||||
Data source plugins enable people to develop plugins for any database that
|
||||
communicates over HTTP. Its up to the plugin to transform the data into
|
||||
time series data so that any grafana panel can then show it.
|
||||
|
||||
## Data source development
|
||||
|
||||
> Our goal is not to have a very extensive documentation but rather have actual
|
||||
> code that people can look at. Example implementations of a data source can be
|
||||
> found in these repos:
|
||||
|
||||
> - [simple-json-datasource](https://github.com/grafana/simple-json-datasource)
|
||||
> - [simple-datasource](https://github.com/grafana/simple-datasource)
|
||||
> - [simple-json-backend-datasource](https://github.com/grafana/simple-json-backend-datasource)
|
||||
|
||||
To interact with the rest of grafana the plugins module file can export 4 different components.
|
||||
|
||||
- Datasource (Required)
|
||||
- QueryCtrl (Required)
|
||||
- ConfigCtrl (Required)
|
||||
- AnnotationsQueryCtrl
|
||||
|
||||
## Plugin json
|
||||
|
||||
There are two data source specific settings for the plugin.json
|
||||
|
||||
```json
|
||||
"metrics": true,
|
||||
"annotations": false,
|
||||
```
|
||||
|
||||
These settings indicate what kind of data the plugin can deliver. At least one of them has to be true.
|
||||
|
||||
## Data source
|
||||
|
||||
The javascript object that communicates with the database and transforms data to times series.
|
||||
|
||||
The Data source should contain the following functions:
|
||||
|
||||
```javascript
|
||||
query(options) // used by panels to get data
|
||||
testDatasource() // used by data source configuration page to make sure the connection is working
|
||||
annotationQuery(options) // used by dashboards to get annotations
|
||||
metricFindQuery(options) // used by query editor to get metric suggestions.
|
||||
```
|
||||
|
||||
### testDatasource
|
||||
|
||||
When a user clicks on the *Save & Test* button when adding a new data source, the details are first saved to the database and then the `testDatasource` function that is defined in your data source plugin will be called. It is recommended that this function makes a query to the data source that will also test that the authentication details are correct. This is so the data source is correctly configured when the user tries to write a query in a new dashboard.
|
||||
|
||||
### Query
|
||||
|
||||
Request object passed to datasource.query function:
|
||||
|
||||
```json
|
||||
{
|
||||
"range": { "from": "2015-12-22T03:06:13.851Z", "to": "2015-12-22T06:48:24.137Z" },
|
||||
"interval": "5s",
|
||||
"targets": [
|
||||
{ "refId": "B", "target": "upper_75" },
|
||||
{ "refId": "A", "target": "upper_90" }
|
||||
],
|
||||
"format": "json",
|
||||
"maxDataPoints": 2495 // decided by the panel
|
||||
}
|
||||
```
|
||||
|
||||
There are two different kinds of results for data sources:
|
||||
time series and table. Time series is the most common format and is supported by all data sources and panels. Table format is only supported by the InfluxDB data source and table panel. But we might see more of this in the future.
|
||||
|
||||
Time series response from datasource.query.
|
||||
An array of:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"target":"upper_75",
|
||||
"datapoints":[
|
||||
[622, 1450754160000],
|
||||
[365, 1450754220000]
|
||||
]
|
||||
},
|
||||
{
|
||||
"target":"upper_90",
|
||||
"datapoints":[
|
||||
[861, 1450754160000],
|
||||
[767, 1450754220000]
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Table response from datasource.query.
|
||||
An array of:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"columns": [
|
||||
{
|
||||
"text": "Time",
|
||||
"type": "time",
|
||||
"sort": true,
|
||||
"desc": true,
|
||||
},
|
||||
{
|
||||
"text": "mean",
|
||||
},
|
||||
{
|
||||
"text": "sum",
|
||||
}
|
||||
],
|
||||
"rows": [
|
||||
[
|
||||
1457425380000,
|
||||
null,
|
||||
null
|
||||
],
|
||||
[
|
||||
1457425370000,
|
||||
1002.76215352,
|
||||
1002.76215352
|
||||
],
|
||||
],
|
||||
"type": "table"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Annotation Query
|
||||
|
||||
Request object passed to datasource.annotationQuery function:
|
||||
|
||||
```json
|
||||
{
|
||||
"range": { "from": "2016-03-04T04:07:55.144Z", "to": "2016-03-04T07:07:55.144Z" },
|
||||
"rangeRaw": { "from": "now-3h", to: "now" },
|
||||
"annotation": {
|
||||
"datasource": "generic datasource",
|
||||
"enable": true,
|
||||
"name": "annotation name"
|
||||
},
|
||||
"dashboard": DashboardModel
|
||||
}
|
||||
```
|
||||
|
||||
Expected result from datasource.annotationQuery:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"annotation": {
|
||||
"name": "annotation name", //should match the annotation name in grafana
|
||||
"enabled": true,
|
||||
"datasource": "generic datasource"
|
||||
},
|
||||
"title": "Cluster outage",
|
||||
"time": 1457075272576,
|
||||
"text": "Joe causes brain split",
|
||||
"tags": ["joe", "cluster", "failure"]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## QueryCtrl
|
||||
|
||||
A JavaScript class that will be instantiated and treated as an Angular controller when the user edits metrics in a panel. This class has to inherit from the `app/plugins/sdk.QueryCtrl` class.
|
||||
|
||||
Requires a static template or `templateUrl` variable which will be rendered as the view for this controller.
|
||||
|
||||
## ConfigCtrl
|
||||
|
||||
A JavaScript class that will be instantiated and treated as an Angular controller when a user tries to edit or create a new data source of this type.
|
||||
|
||||
Requires a static template or `templateUrl` variable which will be rendered as the view for this controller.
|
||||
|
||||
## AnnotationsQueryCtrl
|
||||
|
||||
A JavaScript class that will be instantiated and treated as an Angular controller when the user chooses this type of data source in the templating menu in the dashboard.
|
||||
|
||||
Requires a static template or `templateUrl` variable which will be rendered as the view for this controller. The fields that are bound to this controller are then sent to the Database objects annotationQuery function.
|
@ -0,0 +1,125 @@
|
||||
+++
|
||||
title = "Legacy defaults and editor mode"
|
||||
type = "docs"
|
||||
aliases = ["/docs/grafana/latest/plugins/developing/defaults-and-editor-mode/"]
|
||||
+++
|
||||
|
||||
# Legacy defaults and editor mode
|
||||
|
||||
Most plugins allow users to customize the behavior by changing settings on an editor tab. These setting fields are saved in the dashboard json.
|
||||
|
||||
## Defaults
|
||||
|
||||
We define fields to be saved in Grafana by creating values on the panel object of the controller. You can see these values for any panel by choosing View JSON from the settings menu in Grafana. Here is an excerpt from the clock panel json (with some fields removed), the panel data is saved in the panels array:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Clock",
|
||||
...
|
||||
"rows": [
|
||||
{
|
||||
...
|
||||
"panels": [
|
||||
{
|
||||
"bgColor": "rgb(132, 151, 130)",
|
||||
"clockType": "24 hour",
|
||||
```
|
||||
|
||||
You can define panel data by first creating a variable with default values for the fields and then setting them on the panel object:
|
||||
|
||||
```javascript
|
||||
const panelDefaults = {
|
||||
clockType: '24 hour',
|
||||
fontSize: '60px',
|
||||
fontWeight: 'normal',
|
||||
bgColor: null
|
||||
};
|
||||
|
||||
constructor($scope, $injector) {
|
||||
super($scope, $injector);
|
||||
_.defaults(this.panel, panelDefaults);
|
||||
|
||||
this.updateClock();
|
||||
}
|
||||
```
|
||||
|
||||
The Lodash function [defaults](https://lodash.com/docs/4.17.4#defaults), which is called in the code above: `_.defaults`, sets a default value only if the value is not already set. This way values that have been changed by the user will not be overwritten.
|
||||
|
||||
These panel fields can be used in the controller or module.html template:
|
||||
|
||||
```html
|
||||
<h2 style="font-size: {{ctrl.panel.fontSize}};">{{ctrl.time}}</h2>
|
||||
```
|
||||
|
||||
If you want your users to be able to change these panel values then you need to expose them in the Grafana editor.
|
||||
|
||||
## Editor Mode
|
||||
|
||||
Editor mode is when a user clicks Edit on a panel. Every panel has a general tab where you change the title and width and some panels have more inbuilt tabs like the Metrics tab or Time Range tab. A panel plugin can add its own tab(s) so that a user can customize the panel.
|
||||
|
||||
Grafana conventions mean all you need to do is to hook up an Angular template with input fields and Grafana will automatically save the values to the dashboard json and load them on dashboard load.
|
||||
|
||||
## Using Events
|
||||
|
||||
To add an editor tab you need to hook into the event model so that the tab is added when the *init-edit-mode* event is triggered. The following code should be added to the constructor of the plugin Ctrl class:
|
||||
|
||||
```javascript
|
||||
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
|
||||
```
|
||||
|
||||
Then you need to create a handler function that is bound to the event. In the example above, the handler is called onInitEditMode. The tab is added by calling the controller function, *addEditorTab*. This function has three parameters; the tab name, the path to a html template for the new editor tab and the tab number. It can be a bit tricky to figure out the path, the path name will be based on the id that is specified in the plugin.json file - for example **grafana-clock-panel**. The code below hooks up an Angular template called editor.html that is located in the `src/partials` directory.
|
||||
|
||||
```javascript
|
||||
onInitEditMode() {
|
||||
this.addEditorTab('Options', 'public/plugins/grafana-clock-panel/editor.html', 2);
|
||||
}
|
||||
```
|
||||
|
||||
## Editor HTML and CSS
|
||||
|
||||
For editor tabs html, it is best to use Grafana css styles rather than custom styles. This is to preserve the look and feel of other tabs in Grafana.
|
||||
|
||||
Most editor tabs should use the [gf-form css class](https://github.com/grafana/grafana/blob/master/public/sass/components/_gf-form.scss) from Grafana. The example below has one row with a couple of columns and each column is wrapped in a div like this:
|
||||
|
||||
```html
|
||||
<div class="section gf-form-group">
|
||||
```
|
||||
|
||||
Then each pair, label and field is wrapped in a div with a gf-form class.
|
||||
|
||||
```html
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-8">Font Size</label>
|
||||
<input type="text" class="gf-form-input width-4" ng-model="ctrl.panel.fontSize" ng-change="ctrl.render()" ng-model-onblur>
|
||||
</div>
|
||||
```
|
||||
|
||||
Note that there are some Angular attributes here. *ng-model* will update the panel data. *ng-change* will render the panel when you change the value. This change will occur on the onblur event due to the *ng-model-onblur* attribute. This means you can see the effect of your changes on the panel while editing.
|
||||
|
||||
{{< imgbox img="/assets/img/blog/clock-panel-editor.png" caption="Panel Editor" >}}
|
||||
|
||||
On the editor tab we use a drop down for 12/24 hour clock, an input field for font size and a color picker for the background color.
|
||||
|
||||
The drop down/select has its own *gf-form-select-wrapper* css class and looks like this:
|
||||
|
||||
```html
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">12 or 24 hour</label>
|
||||
<div class="gf-form-select-wrapper max-width-9">
|
||||
<select class="input-small gf-form-input" ng-model="ctrl.panel.clockType" ng-options="t for t in ['12 hour', '24 hour', 'custom']" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
The color picker (or spectrum picker) is a component that already exists in Grafana. We use it like this for the background color:
|
||||
|
||||
```html
|
||||
<spectrum-picker class="gf-form-input" ng-model="ctrl.panel.bgColor" ng-change="ctrl.render()" ></spectrum-picker>
|
||||
```
|
||||
|
||||
## Editor Tab Finished
|
||||
|
||||
To reiterate, this all ties together quite neatly. We specify properties and panel defaults in the constructor for the panel controller and these can then be changed in the editor. Grafana takes care of saving the changes.
|
||||
|
||||
One thing to be aware of is that panel defaults are used the first time a panel is created to set the initial values of the panel properties. After the panel is saved then the saved value will be used instead. So beware if you update panel defaults they will not automatically update the property in an existing panel. For example, if you set the default font size to 60px first and then in version 2 of the plugin change it to 50px, existing panels will still have 60px and only new panels will get the new 50px value.
|
28
docs/sources/developers/plugins/legacy/panels.md
Normal file
28
docs/sources/developers/plugins/legacy/panels.md
Normal file
@ -0,0 +1,28 @@
|
||||
+++
|
||||
title = "Legacy panel plugins"
|
||||
keywords = ["grafana", "plugins", "panel", "documentation"]
|
||||
type = "docs"
|
||||
aliases = ["/docs/grafana/latest/plugins/developing/panels/"]
|
||||
+++
|
||||
|
||||
# Legacy panel plugins
|
||||
|
||||
Panels are the main building blocks of dashboards.
|
||||
|
||||
## Panel development
|
||||
|
||||
### Scrolling
|
||||
The grafana dashboard framework controls the panel height. To enable a scrollbar within the panel the PanelCtrl needs to set the scrollable static variable:
|
||||
|
||||
```javascript
|
||||
export class MyPanelCtrl extends PanelCtrl {
|
||||
static scrollable = true;
|
||||
...
|
||||
```
|
||||
|
||||
In this case, make sure the template has a single `<div>...</div>` root. The plugin loader will modify that element adding a scrollbar.
|
||||
|
||||
### Examples
|
||||
|
||||
- [clock-panel](https://github.com/grafana/clock-panel)
|
||||
- [singlestat-panel](https://github.com/grafana/grafana/tree/master/public/app/plugins/panel/singlestat)
|
175
docs/sources/developers/plugins/legacy/review-guidelines.md
Normal file
175
docs/sources/developers/plugins/legacy/review-guidelines.md
Normal file
@ -0,0 +1,175 @@
|
||||
+++
|
||||
title = "Legacy review guidelines"
|
||||
type = "docs"
|
||||
aliases = ["/docs/grafana/latest/plugins/developing/plugin-review-guidelines/"]
|
||||
+++
|
||||
|
||||
# Legacy review guidelines
|
||||
|
||||
The Grafana team reviews all plugins that are published on Grafana.com. There are two areas we review, the metadata for the plugin and the plugin functionality.
|
||||
|
||||
## Metadata
|
||||
|
||||
The plugin metadata consists of a `plugin.json` file and the README.md file. The `plugin.json` file is used by Grafana to load the plugin, and the README.md file is shown in the plugins section of Grafana and the plugins section of https://grafana.com.
|
||||
|
||||
### README.md
|
||||
|
||||
The README.md file is shown on the plugins page in Grafana and the plugin page on Grafana.com. There are some differences between the GitHub markdown and the markdown allowed in Grafana/Grafana.com:
|
||||
|
||||
- Cannot contain inline HTML.
|
||||
- Any image links should be absolute links. For example: https://raw.githubusercontent.com/grafana/azure-monitor-datasource/master/dist/img/grafana_cloud_install.png
|
||||
|
||||
The README should:
|
||||
|
||||
- describe the purpose of the plugin.
|
||||
- contain steps on how to get started.
|
||||
|
||||
### Plugin.json
|
||||
|
||||
The `plugin.json` file is the same concept as the `package.json` file for an npm package. When the Grafana server starts it will scan the plugin folders (all folders in the data/plugins subfolder) and load every folder that contains a `plugin.json` file unless the folder contains a subfolder named `dist`. In that case, the Grafana server will load the `dist` folder instead.
|
||||
|
||||
A minimal `plugin.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "panel",
|
||||
"name": "Clock",
|
||||
"id": "yourorg-clock-panel",
|
||||
|
||||
"info": {
|
||||
"description": "Clock panel for grafana",
|
||||
"author": {
|
||||
"name": "Author Name",
|
||||
"url": "http://yourwebsite.com"
|
||||
},
|
||||
"keywords": ["clock", "panel"],
|
||||
"version": "1.0.0",
|
||||
"updated": "2018-03-24"
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"grafanaVersion": "3.x.x",
|
||||
"plugins": [ ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- The convention for the plugin id is **[grafana.com username/org]-[plugin name]-[datasource|app|panel]** and it has to be unique. The org **cannot** be `grafana` unless it is a plugin created by the Grafana core team.
|
||||
|
||||
Examples:
|
||||
|
||||
- raintank-worldping-app
|
||||
- ryantxu-ajax-panel
|
||||
- alexanderzobnin-zabbix-app
|
||||
- hawkular-datasource
|
||||
|
||||
|
||||
- The `type` field should be either `datasource` `app` or `panel`.
|
||||
- The `version` field should be in the form: x.x.x e.g. `1.0.0` or `0.4.1`.
|
||||
|
||||
The full file format for the `plugin.json` file is described [here](http://docs.grafana.org/plugins/developing/plugin.json/).
|
||||
|
||||
## Plugin Language
|
||||
|
||||
JavaScript, TypeScript, ES6 (or any other language) are all fine as long as the contents of the `dist` subdirectory are transpiled to JavaScript (ES5).
|
||||
|
||||
## File and Directory Structure Conventions
|
||||
|
||||
Here is a typical directory structure for a plugin.
|
||||
|
||||
```bash
|
||||
johnnyb-awesome-datasource
|
||||
|-- dist
|
||||
|-- src
|
||||
| |-- img
|
||||
| | |-- logo.svg
|
||||
| |-- partials
|
||||
| | |-- annotations.editor.html
|
||||
| | |-- config.html
|
||||
| | |-- query.editor.html
|
||||
| |-- datasource.js
|
||||
| |-- module.js
|
||||
| |-- plugin.json
|
||||
| |-- query_ctrl.js
|
||||
|-- Gruntfile.js
|
||||
|-- LICENSE
|
||||
|-- package.json
|
||||
|-- README.md
|
||||
```
|
||||
|
||||
Most JavaScript projects have a build step. The generated JavaScript should be placed in the `dist` directory and the source code in the `src` directory. We recommend that the plugin.json file be placed in the src directory and then copied over to the dist directory when building. The `README.md` can be placed in the root or in the dist directory.
|
||||
|
||||
Directories:
|
||||
|
||||
- `src/` contains plugin source files.
|
||||
- `src/partials` contains html templates.
|
||||
- `src/img` contains plugin logos and other images.
|
||||
- `dist/` contains built content.
|
||||
|
||||
## HTML and CSS
|
||||
|
||||
For the HTML on editor tabs, we recommend using the inbuilt Grafana styles rather than defining your own. This makes plugins feel like a more natural part of Grafana. If done correctly, the html will also be responsive and adapt to smaller screens. The `gf-form` css classes should be used for labels and inputs.
|
||||
|
||||
Below is a minimal example of an editor row with one form group and two fields, a dropdown and a text input:
|
||||
|
||||
```html
|
||||
<div class="editor-row">
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">My Plugin Options</h5>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-10">Label1</label>
|
||||
<div class="gf-form-select-wrapper max-width-10">
|
||||
<select class="input-small gf-form-input" ng-model="ctrl.panel.mySelectProperty" ng-options="t for t in ['option1', 'option2', 'option3']" ng-change="ctrl.onSelectChange()"></select>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-10">Label2</label>
|
||||
<input type="text" class="input-small gf-form-input width-10" ng-model="ctrl.panel.myProperty" ng-change="ctrl.onFieldChange()" placeholder="suggestion for user" ng-model-onblur />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Use the `width-x` and `max-width-x` classes to control the width of your labels and input fields. Try to get labels and input fields to line up neatly by having the same width for all the labels in a group and the same width for all inputs in a group if possible.
|
||||
|
||||
## Data Sources
|
||||
|
||||
A basic guide for data sources can be found [here](http://docs.grafana.org/plugins/developing/datasources/).
|
||||
|
||||
### Config Page Guidelines
|
||||
|
||||
- It should be as easy as possible for a user to configure a URL. If the data source is using the `datasource-http-settings` component, it should use the `suggest-url` attribute to suggest the default URL or a URL that is similar to what it should be (especially important if the URL refers to a REST endpoint that is not common knowledge for most users e.g. `https://yourserver:4000/api/custom-endpoint`).
|
||||
|
||||
```html
|
||||
<datasource-http-settings
|
||||
current="ctrl.current"
|
||||
suggest-url="http://localhost:8080">
|
||||
</datasource-http-settings>
|
||||
```
|
||||
|
||||
- The `testDatasource` function should make a query to the data source that will also test that the authentication details are correct. This is so the data source is correctly configured when the user tries to write a query in a new dashboard.
|
||||
|
||||
|
||||
#### Password Security
|
||||
|
||||
If possible, any passwords or secrets should be be saved in the `secureJsonData` blob. To encrypt sensitive data, the Grafana server's proxy feature must be used. The Grafana server has support for token authentication (OAuth) and HTTP Header authentication. If the calls have to be sent directly from the browser to a third-party API, this will not be possible and sensitive data will not be encrypted.
|
||||
|
||||
Read more here about how [authentication for data sources]({{< relref "../authentication.md" >}}) works.
|
||||
|
||||
If using the proxy feature, the Config page should use the `secureJsonData` blob like this:
|
||||
|
||||
- good: `<input type="password" class="gf-form-input" ng-model='ctrl.current.secureJsonData.password' placeholder="password"></input>`
|
||||
- bad: `<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder="password"></input>`
|
||||
|
||||
|
||||
### Query Editor
|
||||
|
||||
Each query editor is unique and can have a unique style. It should be adapted to what the users of the data source are used to.
|
||||
|
||||
- Should use the Grafana CSS `gf-form` classes.
|
||||
- Should be neat and tidy. Labels and fields in columns should be aligned and should be the same width if possible.
|
||||
- The data source should be able to handle when a user toggles a query (by clicking on the eye icon) and not execute the query. This is done by checking the `hide` property - an [example](https://github.com/grafana/grafana/blob/e75840737e81f70b6d169df21eca86a624d4bdc4/public/app/plugins/datasource/postgres/datasource.ts#L73).
|
||||
- Should not execute queries if fields in the Query Editor are empty and the query will throw an exception (defensive programming).
|
||||
- Should handle errors. There are two main ways to do this:
|
||||
- use the notification system in Grafana to show a toaster popup with the error message. Example [here](https://github.com/alexanderzobnin/grafana-zabbix/blob/fdbbba2fb03f5f2a4b3b0715415e09d5a4cf6cde/src/panel-triggers/triggers_panel_ctrl.js#L467-L471).
|
||||
- provide an error notification in the query editor like the MySQL/Postgres data sources do. Example code in the `query_ctrl` [here](https://github.com/grafana/azure-monitor-datasource/blob/b184d077f082a69f962120ef0d1f8296a0d46f03/src/query_ctrl.ts#L36-L51) and in the [html](https://github.com/grafana/azure-monitor-datasource/blob/b184d077f082a69f962120ef0d1f8296a0d46f03/src/partials/query.editor.html#L190-L193).
|
76
docs/sources/developers/plugins/legacy/snapshot-mode.md
Normal file
76
docs/sources/developers/plugins/legacy/snapshot-mode.md
Normal file
@ -0,0 +1,76 @@
|
||||
+++
|
||||
title = "Legacy snapshot mode"
|
||||
type = "docs"
|
||||
aliases = ["/docs/grafana/latest/plugins/developing/snapshot-mode/"]
|
||||
+++
|
||||
|
||||
# Legacy snapshot mode
|
||||
|
||||
{{< imgbox img="/img/docs/Grafana-snapshot-example.png" caption="A dashboard using snapshot data and not live data." >}}
|
||||
|
||||
Grafana has this great feature where you can [save a snapshot of your dashboard]({{< relref "../../../reference/share_dashboard.md" >}}). Instead of sending a screenshot of a dashboard to someone, you can send them a working, interactive Grafana dashboard with the snapshot data embedded inside it. The snapshot can be saved on your Grafana server and is available to all your co-workers. Raintank also hosts a [snapshot server](http://snapshot.raintank.io/) if you want to send the snapshot to someone who does not have access to your Grafana server.
|
||||
|
||||
{{< imgbox img="/img/docs/animated_gifs/snapshots.gif" caption="Selecting a snapshot" >}}
|
||||
|
||||
This all works because Grafana saves a snapshot of the current data in the dashboard json instead of fetching the data from a data source. However, if you are building a custom panel plugin then this will not work straight out of the box. You will need to make some small (and easy!) changes first.
|
||||
|
||||
## Enabling support for loading snapshot data
|
||||
|
||||
Grafana automatically saves data from data sources in the dashboard json when the snapshot is created so we do not have to write any code for that. Enabling snapshot support for reading time series data is very simple. First in the constructor, we need to add an event handler for `data-snapshot-load`. This event is triggered by Grafana when the snapshot data is loaded from the dashboard json.
|
||||
|
||||
```javascript
|
||||
constructor($scope, $injector, contextSrv) {
|
||||
super($scope, $injector);
|
||||
...
|
||||
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
|
||||
this.events.on('data-received', this.onDataReceived.bind(this));
|
||||
this.events.on('panel-teardown', this.onPanelTeardown.bind(this));
|
||||
this.events.on('data-snapshot-load', this.onDataSnapshotLoad.bind(this));
|
||||
```
|
||||
|
||||
Then we need to create a simple event handler that just forwards the data on to our regular `data-received` handler:
|
||||
|
||||
```javascript
|
||||
onDataSnapshotLoad(snapshotData) {
|
||||
this.onDataReceived(snapshotData);
|
||||
}
|
||||
```
|
||||
|
||||
This will cover most use cases for snapshot support. Sometimes you will want to save data that is not time series data from a Grafana data source and then you have to do a bit more work to get snapshot support.
|
||||
|
||||
## Saving custom data for snapshots
|
||||
|
||||
Data that is not time series data from a Grafana data source is not saved automatically by Grafana. Saving custom data for snapshot mode has to be done manually.
|
||||
|
||||
{{< imgbox img="/img/docs/Grafana-save-snapshot.png" caption="Save snapshot" >}}
|
||||
|
||||
Grafana gives us a chance to save data to the dashboard json when it is creating a snapshot. In the 'data-received' event handler, you can check the snapshot flag on the dashboard object. If this is true, then Grafana is creating a snapshot and you can manually save custom data to the panel json. In the example, a new field called snapshotLocationData in the panel json is initialized with a snapshot of the custom data.
|
||||
|
||||
```javascript
|
||||
onDataReceived(dataList) {
|
||||
if (!dataList) return;
|
||||
|
||||
if (this.dashboard.snapshot && this.locations) {
|
||||
this.panel.snapshotLocationData = this.locations;
|
||||
}
|
||||
```
|
||||
|
||||
Now the location data is saved in the dashboard json but we will have to load it manually as well.
|
||||
|
||||
## Loading custom data for snapshots
|
||||
|
||||
The example below shows a function that loads the custom data. The data source for the custom data (an external api in this case) is not available in snapshot mode so a guard check is made to see if there is any snapshot data available first. If there is, then the snapshot data is used instead of trying to load the data from the external api.
|
||||
|
||||
```javascript
|
||||
loadLocationDataFromFile(reload) {
|
||||
if (this.map && !reload) return;
|
||||
|
||||
if (this.panel.snapshotLocationData) {
|
||||
this.locations = this.panel.snapshotLocationData;
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
It is really easy to forget to add this support but it enables a great feature and can be used to demo your panel.
|
||||
|
||||
If there is a panel plugin that you would like to be installed on the Raintank Snapshot server then please contact us via [Slack](https://raintank.slack.com) or [GitHub](https://github.com/grafana/grafana).
|
179
docs/sources/developers/plugins/legacy/style-guide.md
Normal file
179
docs/sources/developers/plugins/legacy/style-guide.md
Normal file
@ -0,0 +1,179 @@
|
||||
+++
|
||||
title = "Legacy code style guide"
|
||||
type = "docs"
|
||||
aliases = ["/docs/grafana/latest/plugins/developing/code-styleguide/"]
|
||||
+++
|
||||
|
||||
# Legacy code style guide
|
||||
|
||||
This guide has two parts. The first part describes the metadata and the second part is a styleguide for HTML/CSS and JavaScript in Grafana plugins and applies if you are using ES6 in your plugin. If using TypeScript then the [Angular TypeScript styleguide](https://angular.io/styleguide) is recommended.
|
||||
|
||||
## Metadata
|
||||
|
||||
The plugin metadata consists of a plugin.json file and the README.md file. These two files are used by Grafana and Grafana.com.
|
||||
|
||||
### Plugin.json (mandatory)
|
||||
|
||||
The plugin.json file is the same concept as the package.json file for an npm package. When Grafana starts it will scan the plugin folders and mount every folder that contains a plugin.json file unless the folder contains a subfolder named `dist`. In that case grafana will mount the `dist` folder instead.
|
||||
|
||||
The most important fields are the first three, especially the id. The convention for the plugin id is **[github username/org]-[plugin name]-[datasource|app|panel]** and it has to be unique.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
raintank-worldping-app
|
||||
grafana-simple-json-datasource
|
||||
grafana-piechart-panel
|
||||
mtanda-histogram-panel
|
||||
```
|
||||
|
||||
The full file format for plugin.json is described [here]({{< relref "metadata.md" >}}).
|
||||
|
||||
Minimal plugin.json:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"type": "panel",
|
||||
"name": "Clock",
|
||||
"id": "yourorg-clock-panel",
|
||||
|
||||
"info": {
|
||||
"description": "Clock panel for grafana",
|
||||
"author": {
|
||||
"name": "Grafana Labs",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"keywords": ["clock", "panel"],
|
||||
"version": "1.0.0",
|
||||
"updated": "2015-03-24"
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"grafanaVersion": "3.x.x",
|
||||
"plugins": [ ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### README.md
|
||||
|
||||
The README.md file is rendered both in the grafana.com plugins page, and within the Grafana application. The only difference from how GitHub renders markdown is that html is not allowed.
|
||||
|
||||
## File and Directory Structure Conventions
|
||||
|
||||
Here is a typical directory structure for a plugin.
|
||||
|
||||
```bash
|
||||
johnnyb-awesome-datasource
|
||||
|-- dist
|
||||
|-- spec
|
||||
| |-- datasource_spec.js
|
||||
| |-- query_ctrl_spec.js
|
||||
| |-- test-main.js
|
||||
|-- src
|
||||
| |-- img
|
||||
| | |-- logo.svg
|
||||
| |-- partials
|
||||
| | |-- annotations.editor.html
|
||||
| | |-- config.html
|
||||
| | |-- query.editor.html
|
||||
| |-- datasource.js
|
||||
| |-- module.js
|
||||
| |-- plugin.json
|
||||
| |-- query_ctrl.js
|
||||
|-- Gruntfile.js
|
||||
|-- LICENSE
|
||||
|-- package.json
|
||||
|-- README.md
|
||||
```
|
||||
|
||||
Most JavaScript projects have a build step and most Grafana plugins are built using Babel and ES6. The generated JavaScript should be placed in the `dist` directory and the source code in the `src` directory. We recommend that the plugin.json file be placed in the src directory and then copied over to the dist directory when building. The `README.md` can be placed in the root or in the dist directory.
|
||||
|
||||
Directories:
|
||||
|
||||
- `src/` contains plugin source files.
|
||||
- `src/partials` contains html templates.
|
||||
- `src/img` contains plugin logos and other images.
|
||||
- `spec/` contains tests (optional).
|
||||
- `dist/` contains built content.
|
||||
|
||||
## HTML and CSS
|
||||
|
||||
For the HTML on editor tabs, we recommend using the inbuilt Grafana styles rather than defining your own. This makes plugins feel like a more natural part of Grafana. If done correctly, the html will also be responsive and adapt to smaller screens. The `gf-form` css classes should be used for labels and inputs.
|
||||
|
||||
Below is a minimal example of an editor row with one form group and two fields, a dropdown and a text input:
|
||||
|
||||
```html
|
||||
<div class="editor-row">
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">My Plugin Options</h5>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-10">Label1</label>
|
||||
<div class="gf-form-select-wrapper max-width-10">
|
||||
<select class="input-small gf-form-input" ng-model="ctrl.panel.mySelectProperty" ng-options="t for t in ['option1', 'option2', 'option3']" ng-change="ctrl.onSelectChange()"></select>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-10">Label2</label>
|
||||
<input type="text" class="input-small gf-form-input width-10" ng-model="ctrl.panel.myProperty" ng-change="ctrl.onFieldChange()" placeholder="suggestion for user" ng-model-onblur />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Use the `width-x` and `max-width-x` classes to control the width of your labels and input fields. Try to get labels and input fields to line up neatly by having the same width for all the labels in a group and the same width for all inputs in a group if possible.
|
||||
|
||||
## Build Scripts
|
||||
|
||||
Our recommendation is to use whatever you usually use - Grunt, Gulp or npm scripts. Most plugins seems to use Grunt so that is probably the easiest to get started with if you do not have a preferred build system. The only requirement is that it supports systemjs which is required by Grafana to load plugins.
|
||||
|
||||
## Linting
|
||||
|
||||
We recommend that you use a linter for your JavaScript. For ES6, the standard linter is [eslint](http://eslint.org/). Rules for linting are described in an .eslintrc that is placed in the root directory. [Here is an example](https://github.com/grafana/worldmap-panel/blob/master/.eslintrc) of linting rules in a plugin.
|
||||
|
||||
### ES6 features
|
||||
|
||||
1. Use `const` if a variable is not going to be reassigned.
|
||||
2. Prefer to use `let` instead `var` ([Exploring ES6](http://exploringjs.com/es6/ch_core-features.html#_from-var-to-letconst))
|
||||
3. Use arrow functions, which don’t shadow `this` ([Exploring ES6](http://exploringjs.com/es6/ch_core-features.html#_from-function-expressions-to-arrow-functions)):
|
||||
|
||||
```js
|
||||
testDatasource() {
|
||||
return this.getServerStatus()
|
||||
.then(status => {
|
||||
return this.doSomething(status);
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
better than
|
||||
|
||||
```js
|
||||
testDatasource() {
|
||||
var self = this;
|
||||
return this.getServerStatus()
|
||||
.then(function(status) {
|
||||
return self.doSomething(status);
|
||||
})
|
||||
}
|
||||
```
|
||||
4. Use native _Promise_ object:
|
||||
|
||||
```js
|
||||
metricFindQuery(query) {
|
||||
if (!query) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
better than
|
||||
|
||||
```js
|
||||
metricFindQuery(query) {
|
||||
if (!query) {
|
||||
return this.$q.when([]);
|
||||
}
|
||||
}
|
||||
```
|
||||
5. If using Lodash, then be consistent and prefer that to the native ES6 array functions.
|
36
docs/sources/developers/plugins/metadata.md
Normal file
36
docs/sources/developers/plugins/metadata.md
Normal file
@ -0,0 +1,36 @@
|
||||
+++
|
||||
title = "plugin.json"
|
||||
keywords = ["grafana", "plugins", "documentation"]
|
||||
type = "docs"
|
||||
aliases = ["/docs/grafana/latest/plugins/developing/plugin.json/"]
|
||||
+++
|
||||
|
||||
# plugin.json
|
||||
|
||||
The plugin.json file is mandatory for all plugins. When Grafana starts it will scan the plugin folders and mount every folder that contains a plugin.json file unless the folder contains a subfolder named `dist`. In that case grafana will mount the `dist` folder instead.
|
||||
|
||||
## Schema
|
||||
|
||||
| Property | Description |
|
||||
|-----------------------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| id | Unique name of the plugin. |
|
||||
| type | One of `app`, `datasource`, `panel` |
|
||||
| name | Human-readable name of the plugin |
|
||||
| dependencies.grafanaVersion | Required Grafana version for this plugin |
|
||||
| dependencies.plugins | An array of required plugins on which this plugin depends |
|
||||
| info.author.name | Author's name |
|
||||
| info.author.url | Link to author's website |
|
||||
| info.description | Description of plugin. Used for search on grafana.com |
|
||||
| info.keywords | Array of plugin keywords. Used for search on grafana.com |
|
||||
| info.links | An array of link objects to be displayed on this plugin's project page in the form `{name: 'foo', url: 'http://example.com'}` |
|
||||
| info.logos.small | Link to the "small" version of the plugin logo, which must be an SVG image. "Large" and "small" logos can be the same image. |
|
||||
| info.logos.large | Link to the "large" version of the plugin logo, which must be an SVG image. "Large" and "small" logos can be the same image. |
|
||||
| info.screenshots | An array of screenshot objects in the form `{name: 'bar', path: 'img/screenshot.png'}` |
|
||||
| info.updated | Date when this plugin was built. Use `%TODAY%` for Grafana to autopopulate this value. |
|
||||
| info.version | Project version of this commit. Use `%VERSION%` for Grafana to autopopulate this value. |
|
||||
|
||||
## Plugin.json Example
|
||||
|
||||
Here's an example of an up-to-date plugin.json file:
|
||||
|
||||
https://github.com/grafana/clock-panel/blob/master/src/plugin.json
|
Reference in New Issue
Block a user