The Grafana Datasource plug-in development practice 1 describes the basic things you need to know to develop a Datasource. This article introduces specific development practices in a project.

The Datasource module

To interact with the rest of Grafana, the plug-in file needs to export the following five modules:

Datasource  // Required
QueryCtrl  // Required
ConfigCtrl  // Required
QueryOptionsCtrl  // 
AnnotationsQueryCtrl  //
Copy the code

Constructor function

The datasource plug-in communicates with the datasource and converts the data into a time series. The data source has the following functions:

query(options)  // For panel query data
testDatasource()  // a page for custom data sources to test that the currently configured data source is available
annotationQuery(options) // Used for dashboard to get comment information
metricFindQuery(options) // used by query editor to get metric suggestions.
Copy the code

The constructor function takes the following arguments:

constructor(instanceSettings, $q, backendSrv, templateSrv) {}
// instanceSettings
{
  id: 5.jsonData: {
    keepCookies: [].tlsAuth: false.tlsAuthWithCACert: false.tlsSkipVerify: false,},meta: {
    alerting: false.annotations: true.baseUrl: "public/plugins/grafana-server-datasource".dependencies: {
      grafanaVersion: "3.x.x".plugins: []},id: "grafana-server-datasource".includes: null.info: {
      author: {
        name:"liuchunhui".url:"https://grafana.com",},description: "Proxy server as data source".links: [{name: "Github".url: ""},
        {name: "MIT License".url: ""}].logos: {
        large:"public/plugins/grafana-server-datasource/img/server-logo.png".small:"public/plugins/grafana-server-datasource/img/server-logo.png"
      },
     screenshots:null
     updated:"2018-04-23"
     version:"1.0.0"
    },
    metrics: true.module: "plugins/grafana-server-datasource/module".name: "Proxy server".routes: null.type: "datasource",
  }
  name:"Proxy server data source".type:"grafana-server-datasource".url:"/api/datasources/proxy/5",}// $q is a function
// backendSrv objects are:
{
  $http: ƒ p (e),$qM: ƒ (t),$timeout: ƒ (o o, s, u),HTTP_REQUEST_CANCELLED: - 1.alertSrv: {
   $rootScope: object,
   $timeoutƒ o(O, S,u) list: []} contextSrv: {isEditor: true.isGrafanaAdmin: true.isSignedIn: true.sidemenu: true.sidemenuSmallBreakpoint: false.user: {  // Information about the currently logged in user
      email:"admin@localhost".gravatarUrl:"/avatar/46d229b033af06a191ff2267bca9ae56".helpFlags1:0.id:1.isGrafanaAdmin:true.isSignedIn:true.lightTheme:true.locale:"zh-CN".login:"admin".name:"admin".orgCount:1.orgId:1.orgName:"Main Org.".orgRole:"Admin".timezone:"browser",},version:"5.0.1",},inFlightRequests: {},noBackendCache:true,}// templateSrv objects are:
{
  builtIns: {
    __interval: {text: "1s".value: "1s"},
    __interval_ms: {text: "100".value: "100"}},grafanaVariables: {},
  index: {},regex:/\$(\w+)|\[\[([\s\S]+?)(?::(\w+))?\]\]|\${(\w+)(? ::(\w+))? }/g
}
Copy the code

The query function

The function to call to actually query the data. The official data sources have two different results, Time Series and table. Currently, all grafana official data sources and panels support time Series format, while table format is only supported by the InfluxDB data source and table panel. The data format returned by the datasource is the same as that of the Grafana panel. Datasource. query time series

[{
   "target":"upper_75"."datapoints":[
      [622.1450754160000],
      [365.1450754220000]]}, {"target":"upper_90"."datapoints":[
      [861.1450754160000],
      [767.1450754220000]]}]Copy the code

Datasource. query table

[{
   "columns": [{
      "text": "Time"."type": "time"."sort": true."desc": true}, {"text": "mean"}, {"text": "sum",}],"rows": [[
      1457425380000.null.null
   ], [
      1457425370000.1002.76215352.1002.76215352
   ]],
    "type": "table"
}]
Copy the code

The request object passed to the datasource.query function:

{
   "range": {
      "from": moment,  // Start date of global time filtering
      "raw": {from: "now-7d".to: "now"},
      "to": moment,  // Global time filter end date
   },
    "rangeRaw": {
      "from": "now-7d"."to": "now",},"interval": "15m"."intervalMs": 900000."targets": [{  // Define query conditions
      "refId": "A"."target": "upper_75" 
   }, {
      "refId": "B"."target": "upper_90"}]."format": "json"."maxDataPoints": 2495.//decided by the panel
    "cacheTimeout": undefined."panelId": 2."timezone": "browser"
}
Copy the code

Example of the query function:

query(options) {  // The panel plugin listens for 'data-received'
    
    const params = {  // Encapsulate HTTP request parameters
        from: options.range.from.format('x'),
        to: options.range.to.format('x'),
        targets: options.queries,
    };

    return this.doRequest({  // Initiate an HTTP request and return the result
        url: this.url + '/card-data'.// Make a common interface specification
        method: 'POST'.data: params
    });
}


doRequest(options) {
    options.withCredentials = this.withCredentials;
    options.headers = this.headers;
    return this.backendSrv.datasourceRequest(options);  DatasourceRequest () is the request datasource function provided by Grafana
}
Copy the code

TestDatasource function

When the user clicks the Save&Test button when adding a new data source, the details are first saved to the database, and testDatasource calls the functions defined in the data source plug-in. This function issues queries to the data source, verifies that the data source configuration is available correctly, and ensures that the data source is configured correctly when the user writes a query in the new dashboard. Function examples:

Return {status: "", message: "", title: ""} */
testDatasource() {
    return this.doRequest({
        url: this.url + '/'.method: 'GET',
    }).then(response= > {
        if (response.status === 200) {
            return { status: "success".message: "Data source is working".title: "Success"}; }}); }Copy the code

AnnotationQuery function

Annotation query, passed to the datasource. AnnotationQuery function request object:

{
   "range": { 
      "from": "The 2016-03-04 T04:07:55. 144 z"."to": "The 2016-03-04 T07:07:55. 144 z" },
   "rangeRaw": { 
      "from": "now-3h"."to": "now" 
   },
   "annotation": {
      "datasource": "generic datasource"."enable": true."name": "annotation name"}}Copy the code

The datasource. AnnotationQuery expected results:

[{
   "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"
}]
Copy the code

You can access ‘comments’ using the Grafana platform’s database

MetricFindQuery function

ConfigCtrl module

When a user edits or creates a new data source of this type, the class is instantiated and treated as an Angular controller. This class requires a static template or templateUrl variable that will be rendered to the view of this controller. The contents of this object in the ConfigCtrl class are:

{
  current: {
    name: "Proxy server data source".// Data source name
    isDefault: true.// Is the default
    type: "grafana-server-datasource".// The id of the data source plug-in
    url: "http://localhost:3001".// HTTP: the url of the data source
    access: "proxy".// HTTP: indicates the connection data source type, which can be 'direct' or 'proxy'
    basicAuth: true.// Auth: Basic Auth option
    basicAuthUser: "basic auth user".// Basic Auth Details: User option, valid when basicAuth is true
    basicAuthPassword:"basic auth password".// Basic Auth Details: Password option, valid when basicAuth is true
    withCredentials: true.// Auth: With Credentials option
    jsonData: {
     tlsAuth: true.// Auth: TLS Client Auth option
      tlsAuthWithCACert: true.// Auth: With CA Cert option
      tlsSkipVerify: true.// Auth: Skip TLS Verification (Insecure)
      keepCookies: ["Advanced Cookoe"].// Advanced HTTP Settings: Whitelist of cookies
    },
    secureJsonData: {
      tlsCACert: "TLS Auth CA Cert".// TLS Auth Details: the CACert option, valid when the tlsAuthWithCACert value under jsonData is true
      tlsClientCert: "TLS Auth Client Cert".// TLS Auth Details: Client Cert option, valid when the tlsAuth value under jsonData is true
      tlsClientKey: "TLS Auth Client Key".// TLS Auth Details: Client Key option, valid when the tlsAuth value under jsonData is true
    },
    secureJsonFields: {},},meta: {
    baseUrl: "public/plugins/grafana-server-datasource".defaultNavUrl: "".dependencies: {
      grafanaVersion: "3.x.x".plugins: []},enabled: false.hasUpdate: false.id: "grafana-server-datasource".includes: null.info: {  // plugin.json configuration information
     author: {
        name: "liuchunhui".url: "https://grafana.com"
      },
      description: "Proxy server as data source".links: [{name: "Github".url: ""},
        {name: "MIT License".url: ""}].logos: {
      large:"public/plugins/grafana-server-datasource/img/server-logo.png".small:"public/plugins/grafana-server-datasource/img/server-logo.png"
      },
      screenshots:null.updated:"2018-04-23".version:"1.0.0"
    },
    jsonData: null.latestVersion: "".module: "plugins/grafana-server-datasource/module".name: "Proxy server".pinned: false
    state: "".type: "datasource",}Copy the code

The angular component grafana encapsulates in the template page, passing this current object to implement the HTTP and Auth module definitions:

<datasource-http-settings current="ctrl.current"></datasource-http-settings>
Copy the code

QueryCtrl module

A JavaScript class that is instantiated and handled as an Angular controller when the user edits metrics in the palette. The class must be from the app/plugins/SDK. QueryCtrl class inheritance. This class requires a static template or templateUrl variable that will be rendered to the view of this controller. This class is initialized when the user switches back to the Metrics module under panel. So we can do our own thing in the constructor. For example, obtain a list of indicator dimensions as a display condition for user filtering.

import { QueryCtrl } from 'app/plugins/sdk';
export default class GenericQueryCtrl extends QueryCtrl {
    constructor($scope, $injector) {
        super($scope, $injector);

        // Get the parameter list request
        this.requestParams().then(response= > {
            const targets = response.data.target;
            this.options = response.data.options;
            this.text = response.data.text;
            this.keys = Object.keys(targets);

            for (let key in targets) {
                this.target[key] = this.target[key] || targets[key]; }}); } requestParams() {// Request a list of parameters
        const params = {
            header: {
                'Content-Type': 'application/json'
            },
            method: 'GET'.retry: 0.url: this.datasource.url + '/param-list'
        };
        return this.datasource.backendSrv.datasourceRequest(params);  // Use the HTTP request function provided by Grafana
    }
    onChangeInternal() {  // Refresh the panel
        this.panelCtrl.refresh(); Grafana comes with a method to update the panel
    }

    toggleEditorMode() {  // Whether to enable editing mode
        this.target.rawQuery = !this.target.rawQuery;
    }
}

GenericQueryCtrl.templateUrl = './page/query.html';

Copy the code

QueryCtrl controller query.html template:

<query-editor-row query-ctrl="ctrl" has-text-edit-mode="true">
    <div class="gf-form"
         ng-if=! "" ctrl.target.rawQuery"
         ng-repeat="key in ctrl.keys">
        <span class="gf-form-label width-7">
            {{ctrl.text[key]}}
        </span>
        <select class="gf-form-input width-25"
                ng-model="ctrl.target[key]"
                ng-change="ctrl.onChangeInternal()">
            <option ng-repeat="option in ctrl.options[key]"
                    value="{{option.name}}">
                {{option.desc}}
            </option>
        </select>
    </div>
    <div ng-if="ctrl.target.rawQuery">
        <textarea class="gf-form-input" rows="5" spellcheck="false" ng-blur="ctrl.onChangeInternal()" />
    </div>
</query-editor-row>
Copy the code


The contents of the tag are added to the Add Query template. The hash-text-edit-mode =”true” attribute of the tag enables Toggle Edit mode. The target.rawQuery parameter in the QueryCtrl controller marks the switch between two editing modes, but these two modes need to be defined in code.

AnnotationsQueryCtrl module

A JavaScript class that is instantiated and processed as an Angular controller when the user selects this type of datasource from the datasource template menu. This class requires a static template or templateUrl variable that will be rendered to the view of this controller. Fields bound to this controller are then sent to the database object annotationQuery function. You can customize the dashboard’s Built in Query criteria when developing the plug-in. AnnotationQueryCtrl code:

export default class GenericAnnotationsQueryCtrl {}
GenericAnnotationsQueryCtrl.templateUrl = './page/annotationsQuery.html';
Copy the code

AnnotationsQuery. HTML code:

<h5 class="section-heading">Annotated query criteria Settings</h5>
<div class="gf-form-group">
    <div class="gf-form">
        <input type="text" class="gf-form-input" ng-model='ctrl.annotation.query' placeholder="" />
    </div>
</div>
Copy the code

QueryOptionsCtrl module

A JavaScript class that is instantiated and handled as an Angular controller when the user edits metrics in the palette. This controller handles panel scope Settings for the data source, such as interval, rate, and aggregation (if required). This class requires a static template or templateUrl variable that will be rendered to the view of this controller.

QueryOptionsCtrl code:

export default class GenericQueryOptionsCtrl {}
GenericQueryOptionsCtrl.templateUrl = './page/queryOptions.html';
Copy the code

QueryOptions. HTML code:

<section class="grafana-metric-options" >
    <div class="gf-form"></div>
</section>
Copy the code