Restructure plugin docs (#24381)

This commit is contained in:
Marcus Olsson
2020-05-07 18:45:52 +02:00
committed by GitHub
parent f88ec875a6
commit fc0c717bfb
20 changed files with 257 additions and 323 deletions

View 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" >}})

View 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)

View 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.

View File

@ -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.

View 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)

View 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).

View 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).

View 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 dont 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.