Disclaimer
Due to the author’s reluctance to go into the study of the outdated AngularJS used by Grafana for the interface, and the almost complete lack of documentation for plug-in development, this article may contain incorrect statements, traces of peanuts and other nuts.
/dist ... // . Grafana . /src /img logo.svg // , /partials // config.html // . , http. query.editor.html // . . datasource.js // module.js // plugin.json // - query_ctrl.js // , html- README.md // , Grafana gruntfile.js // LICENSE.txt // package.json // - Node.js , README.md // Node.js
{ "name": "-", "version": "0.1.0", "description": "--", "repository": { "type": "git", "url": "git+https://-github-" }, "author": "-", "license": "MIT", "devDependencies": { "babel": "~6.5.1", "grunt": "~0.4.5", "grunt-babel": "~6.0.0", "grunt-contrib-clean": "~0.6.0", "grunt-contrib-copy": "~0.8.2", "grunt-contrib-uglify": "~0.11.0", "grunt-contrib-watch": "^0.6.1", "grunt-execute": "~0.2.2", "grunt-sass": "^1.1.0", "grunt-systemjs-builder": "^0.2.5", "load-grunt-tasks": "~3.2.0", "babel-plugin-transform-es2015-for-of": "^6.6.0", "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", "babel-preset-es2015": "^6.24.1" }, "dependencies": {}, "homepage": "https://--" }
module.exports = function(grunt) { require('load-grunt-tasks')(grunt); grunt.loadNpmTasks('grunt-execute'); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-build-number'); grunt.initConfig({ clean: ["dist"], copy: { src_to_dist: { cwd: 'src', expand: true, src: [ '**/*', '!*.js', '!module.js', '!**/*.scss' ], dest: 'dist/' }, pluginDef: { expand: true, src: ['plugin.json'], dest: 'dist/', } }, watch: { rebuild_all: { files: ['src/**/*', 'plugin.json'], tasks: ['default'], options: {spawn: false} }, }, babel: { options: { sourceMap: true, presets: ["es2015"], plugins: ['transform-es2015-modules-systemjs', "transform-es2015-for-of"], }, dist: { files: [{ cwd: 'src', expand: true, src: [ '*.js', 'module.js', ], dest: 'dist/' }] }, }, sass: { options: { sourceMap: true }, dist: { files: { } } } }); grunt.registerTask('default', [ 'clean', 'copy:src_to_dist', 'copy:pluginDef', 'babel', 'sass' ]); }
npm install --only=dev npm install grunt -g
grunt
command will be available to build the distributive into the dist folder.id
as --datasource
, as well as what information it will provide by setting the values ​​of the metrics
, alerting
and annotations
variables. Read more about plugin.json here . { "name": "-", "id": "--", "type": "datasource", "metrics": true, "alerting": false, "annotations": false, "info": { "description": "-", "author": { "name": "-", "url": "-" }, "logos": { "small": "img/logo.svg", "large": "img/logo.svg" }, "links": [ { "name": "GitHub", "url": "https://-github-" }, { "name": "", "url": "https://---" } ], "version": "0.1.0", "updated": "2018-05-10" }, "dependencies": { "grafanaVersion": "5.x", "plugins": [] } }
<datasource-http-settings current="ctrl.current"></datasource-http-settings>
ctrl.target.myprop
looks like this <select ng-model="ctrl.target.myprop" ng-options="v.value as v.name for v in ctrl.myprops"> </select>
ctrl.myprops
needs to be loaded asynchronously, then it will be necessary to create a controller . Grafana already has a component with the desired implementation. <gf-form-dropdown model="ctrl.target.myprop" class = "max-width-12" lookup-text="true" allow-custom = "false" get-options = "ctrl.getMyProps()" on-change = "ctrl.panelCtrl.refresh()" > </gf-form-dropdown>
ctrl
is an object of the class implemented in query_ctrl.js , associated with the current metric.ctrl.target
contains metric properties that will be sent to the source in the request.ctrl.panelCtrl.refresh()
causes the panel to request data again.lookup-text
specifies whether a hint is available for the field by a drop-down list.allow-custom
specifies that it is permissible to select items not from the drop-down list.get-options
method for getting the elements of the dropdown list. The result of the method, returned as a value or promise, must be an array of elements of the form {text: "", value: ""}
.model
, get-options
and on-change
are different from the original ng-model
, ng-options
and ng-change
.gf-form-dropdown
, there is also a metric-segment-model
. Its use can be seen here . There are no documentation on components, so the list and capabilities can be found only by studying the source code . <query-editor-row query-ctrl="ctrl" class="mydatasource-datasource-query-row"> <div class="gf-form-inline"> <div class="gf-form max-width-12"> <gf-form-dropdown model="ctrl.target.myprop" class = "max-width-12" lookup-text="true" custom = "false" get-options="ctrl.getMyProps()" on-change = "ctrl.updateMyParams()" > </gf-form-dropdown> </div> <div class="gf-form" ng-if = "ctrl.panel.type == 'graph'"> <label class="gf-form-label width-5">Name</label> <input type="text" ng-model="ctrl.target.label" class="gf-form-input width-12" spellcheck="false" > </div> <div class="gf-form" ng-if = "ctrl.target.myparams.length > 0"> <label class="gf-form-label width-5">Params</label> <input type="text" ng-repeat = "param in ctrl.target.myparams" ng-model="ctrl.target.myparams[param]" class="gf-form-input width-12" spellcheck="false" placeholder = "{{param}}" ng-change = "ctrl.panelCtrl.refresh();" > </div> <div class="gf-form gf-form--grow"> <div class="gf-form-label gf-form-label--grow"></div> </div> </div> </query-editor-row>
gf-form--grow
needed to fill the unoccupied part of the line with the background.ng-if = "ctrl.panel.type == 'graph'"
.testDatasource()
and query(options)
. The first is used to test the connection to the source during its registration (the “Save and Test” button), the second is called each time the panel requests data. I will dwell on it in more detail. { "timezone":"browser", "panelId":6, "dashboardId":1, "range":{ "from":"2018-05-10T23:30:42.318Z", "to":"2018-05-10T23:47:11.566Z", "raw":{ "from":"2018-05-10T23:30:42.318Z", "to":"2018-05-10T23:47:11.566Z" } }, "rangeRaw":{ "from":"2018-05-10T23:30:42.318Z", "to":"2018-05-10T23:47:11.566Z" }, "interval":"2s", "intervalMs":2000, "targets":[ { "myprop":"value1", "myparams":{ "column":"val", "table":"t" }, "refId":"A", "$$hashKey":"object:174" }, { "refId":"B", "$$hashKey":"object:185", "myprop":"value2", "myparams":{ "column":"val2", "table":"t2" }, "datatype":"table" } ], "maxDataPoints":320, "scopedVars":{ "__interval":{ "text":"2s", "value":"2s" }, "__interval_ms":{ "text":2000, "value":2000 } } }
range
containing the period for which information is required, and targets
is a list of metrics, each of which corresponds to the target
property of the class object defined in query_ctrl
.targets
must be filtered by the hide
property in order not to request the results of “hidden” metrics, as well as to remove obviously “wrong” metrics, for example, with undefined parameters. Then, according to the received list, data is requested for each metric and the resulting one must be converted into a format supported by Grafana .query
is different for different types of panels, so if the data is requested for a graph, the result needs to be converted to the form {target: -, datapoints: --[, ]}
, and for a table, then {columns: --{text: -, type: --}, rows: }
.type
attribute to the target
based on this.panel.type
and convert the result based on it. It is somewhat strange that in options
the panel type is not transmitted.query
method should be a promise that returns {data: -}
.backendSrv.datasourceRequest(options)
method, which, depending on the type of data source selected, either forwards the data to Grafana or executes the request directly by the browser.Promise.all
var requests = this.targets.map((target) => ... ); var scope = requests.map((req) => this.backendSrv.datasourceRequest(req)); return Promise.all(scope).then(function (results) { // results data, ... return Promise.resolve({data}); })
metricFindQuery(options)
method, which returns an array (possibly through a promise) with elements of the form {text: "", value: ""}
. In addition, the query
will need to go through the options.targets
and for each element of this array for all its properties, where a variable can be substituted, perform the conversion target.myprop = this.templateSrv.replace(target.myprop, options.scopedVars, 'regex');
annotationQuery(options)
.Source: https://habr.com/ru/post/358404/
All Articles