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 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:

  1. raintank-worldping-app
  2. grafana-simple-json-datasource
  3. grafana-piechart-panel
  4. mtanda-histogram-panel

For more information about the file format for plugin.json file, refer to metadata.

Minimal plugin.json:

  1. {
  2. "type": "panel",
  3. "name": "Clock",
  4. "id": "yourorg-clock-panel",
  5. "info": {
  6. "description": "Clock panel for grafana",
  7. "author": {
  8. "name": "Grafana Labs",
  9. "url": "https://grafana.com"
  10. },
  11. "keywords": ["clock", "panel"],
  12. "version": "1.0.0",
  13. "updated": "2015-03-24"
  14. },
  15. "dependencies": {
  16. "grafanaVersion": "3.x.x",
  17. "plugins": [ ]
  18. }
  19. }

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.

  1. johnnyb-awesome-datasource
  2. |-- dist
  3. |-- spec
  4. | |-- datasource_spec.js
  5. | |-- query_ctrl_spec.js
  6. | |-- test-main.js
  7. |-- src
  8. | |-- img
  9. | | |-- logo.svg
  10. | |-- partials
  11. | | |-- annotations.editor.html
  12. | | |-- config.html
  13. | | |-- query.editor.html
  14. | |-- datasource.js
  15. | |-- module.js
  16. | |-- plugin.json
  17. | |-- query_ctrl.js
  18. |-- Gruntfile.js
  19. |-- LICENSE
  20. |-- package.json
  21. |-- 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:

  1. <div class="editor-row">
  2. <div class="section gf-form-group">
  3. <h5 class="section-heading">My Plugin Options</h5>
  4. <div class="gf-form">
  5. <label class="gf-form-label width-10">Label1</label>
  6. <div class="gf-form-select-wrapper max-width-10">
  7. <select
  8. class="input-small gf-form-input"
  9. ng-model="ctrl.panel.mySelectProperty"
  10. ng-options="t for t in ['option1', 'option2', 'option3']"
  11. ng-change="ctrl.onSelectChange()"
  12. ></select>
  13. </div>
  14. <div class="gf-form">
  15. <label class="gf-form-label width-10">Label2</label>
  16. <input
  17. type="text"
  18. class="input-small gf-form-input width-10"
  19. ng-model="ctrl.panel.myProperty"
  20. ng-change="ctrl.onFieldChange()"
  21. placeholder="suggestion for user"
  22. ng-model-onblur
  23. />
  24. </div>
  25. </div>
  26. </div>
  27. </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. Rules for linting are described in an .eslintrc that is placed in the root directory. For an example of linting rules in a plugin, refer to .eslintrc.

ES6 features

  1. Use const if a variable is not going to be reassigned.

  2. Prefer to use let instead var (Exploring ES6)

  3. Use arrow functions, which don’t shadow this (Exploring ES6):

    1. testDatasource() {
    2. return this.getServerStatus()
    3. .then(status => {
    4. return this.doSomething(status);
    5. })
    6. }

    better than

    1. testDatasource() {
    2. var self = this;
    3. return this.getServerStatus()
    4. .then(function(status) {
    5. return self.doSomething(status);
    6. })
    7. }
  4. Use native Promise object:

    1. metricFindQuery(query) {
    2. if (!query) {
    3. return Promise.resolve([]);
    4. }
    5. }

    better than

    1. metricFindQuery(query) {
    2. if (!query) {
    3. return this.$q.when([]);
    4. }
    5. }
  5. If using Lodash, then be consistent and prefer that to the native ES6 array functions.