From 3435df8b9c715072510addc5408dfc64f18bba11 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 2 Mar 2016 08:39:18 +0100 Subject: [PATCH] feat(examples): add datasource plugin example --- .../.jscs.json | 13 ++++ .../.jshintrc | 36 +++++++++ .../Gruntfile.js | 54 +++++++++++++ .../README.md | 75 +++++++++++++++++++ .../package.json | 37 +++++++++ .../plugin.json | 27 +++++++ .../src/css/query-editor.css | 3 + .../src/datasource.js | 65 ++++++++++++++++ .../src/module.js | 15 ++++ .../src/partials/config.html | 2 + .../src/partials/query.editor.html | 8 ++ .../src/partials/query.options.html | 4 + .../src/query_ctrl.js | 26 +++++++ 13 files changed, 365 insertions(+) create mode 100644 examples/datasource-plugin-genericdatasource/.jscs.json create mode 100644 examples/datasource-plugin-genericdatasource/.jshintrc create mode 100644 examples/datasource-plugin-genericdatasource/Gruntfile.js create mode 100644 examples/datasource-plugin-genericdatasource/README.md create mode 100644 examples/datasource-plugin-genericdatasource/package.json create mode 100644 examples/datasource-plugin-genericdatasource/plugin.json create mode 100644 examples/datasource-plugin-genericdatasource/src/css/query-editor.css create mode 100644 examples/datasource-plugin-genericdatasource/src/datasource.js create mode 100644 examples/datasource-plugin-genericdatasource/src/module.js create mode 100644 examples/datasource-plugin-genericdatasource/src/partials/config.html create mode 100644 examples/datasource-plugin-genericdatasource/src/partials/query.editor.html create mode 100644 examples/datasource-plugin-genericdatasource/src/partials/query.options.html create mode 100644 examples/datasource-plugin-genericdatasource/src/query_ctrl.js diff --git a/examples/datasource-plugin-genericdatasource/.jscs.json b/examples/datasource-plugin-genericdatasource/.jscs.json new file mode 100644 index 00000000000..dcf694dcc63 --- /dev/null +++ b/examples/datasource-plugin-genericdatasource/.jscs.json @@ -0,0 +1,13 @@ +{ + "disallowImplicitTypeConversion": ["string"], + "disallowKeywords": ["with"], + "disallowMultipleLineBreaks": true, + "disallowMixedSpacesAndTabs": true, + "disallowTrailingWhitespace": true, + "requireSpacesInFunctionExpression": { + "beforeOpeningCurlyBrace": true + }, + "disallowSpacesInsideArrayBrackets": true, + "disallowSpacesInsideParentheses": true, + "validateIndentation": 2 +} \ No newline at end of file diff --git a/examples/datasource-plugin-genericdatasource/.jshintrc b/examples/datasource-plugin-genericdatasource/.jshintrc new file mode 100644 index 00000000000..3725af83afc --- /dev/null +++ b/examples/datasource-plugin-genericdatasource/.jshintrc @@ -0,0 +1,36 @@ +{ + "browser": true, + "esnext": true, + + "bitwise":false, + "curly": true, + "eqnull": true, + "devel": true, + "eqeqeq": true, + "forin": false, + "immed": true, + "supernew": true, + "expr": true, + "indent": 2, + "latedef": true, + "newcap": true, + "noarg": true, + "noempty": true, + "undef": true, + "boss": true, + "trailing": true, + "laxbreak": true, + "laxcomma": true, + "sub": true, + "unused": true, + "maxdepth": 6, + "maxlen": 140, + + "globals": { + "System": true, + "define": true, + "require": true, + "Chromath": false, + "setImmediate": true + } +} diff --git a/examples/datasource-plugin-genericdatasource/Gruntfile.js b/examples/datasource-plugin-genericdatasource/Gruntfile.js new file mode 100644 index 00000000000..d36a4716f31 --- /dev/null +++ b/examples/datasource-plugin-genericdatasource/Gruntfile.js @@ -0,0 +1,54 @@ +module.exports = function(grunt) { + + require('load-grunt-tasks')(grunt); + + grunt.loadNpmTasks('grunt-execute'); + grunt.loadNpmTasks('grunt-contrib-clean'); + + grunt.initConfig({ + + clean: ["dist"], + + copy: { + src_to_dist: { + cwd: 'src', + expand: true, + src: ['**/*', '!**/*.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'], + dest: 'dist', + ext:'.js' + }] + }, + }, + + }); + + grunt.registerTask('default', ['clean', 'copy:src_to_dist', 'copy:pluginDef', 'babel']); +}; diff --git a/examples/datasource-plugin-genericdatasource/README.md b/examples/datasource-plugin-genericdatasource/README.md new file mode 100644 index 00000000000..67a9f1fb7ec --- /dev/null +++ b/examples/datasource-plugin-genericdatasource/README.md @@ -0,0 +1,75 @@ +#Generic backend datasource# + +This is a very minimalistic datasource that forwards http requests in a defined format. The idea is that anybody should be able to build an api and retrieve data from any datasource without built-in support in grafana. + +Its also serves as an living example implementation of a datasource. + +A guide for installing plugins can be found at [placeholder for links]. + +Your backend need implement 3 urls + * "/" Should return 200 ok. Used for "Test connection" on the datasource config page. + * "/search" Used by the find metric options on the query tab in panels + * "/query" Should return metrics based on input + +## Metric discovery ## + +### Request ### +``` +{ refId: 'F', target: 'select metric' } +``` +### Expected Response ### + +An array of options based on the target input + +####Example#### +``` +["upper_25","upper_50","upper_75","upper_90","upper_95"] +``` + +## Metric query ## + +### Request ### +``` +{ + 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 +} +``` +### Expected response ### + +An array of +``` +{ + "target":"target_name", + "datapoints":[ + [intvalue, timestamp in epoch], + [intvalue, timestamp in epoch] + ] +} +``` +###Example### +``` +[ + { + "target":"upper_75", + "datapoints":[ + [622,1450754160000], + [365,1450754220000] + ] + }, + { + "target":"upper_90", + "datapoints":[ + [861,1450754160000], + [767,1450754220000] + ] + } +] +``` +## Example backend implementation ## +https://gist.github.com/bergquist/bc4aa5baface3cffa109 \ No newline at end of file diff --git a/examples/datasource-plugin-genericdatasource/package.json b/examples/datasource-plugin-genericdatasource/package.json new file mode 100644 index 00000000000..91c53734ec8 --- /dev/null +++ b/examples/datasource-plugin-genericdatasource/package.json @@ -0,0 +1,37 @@ +{ + "name": "kentik-app", + "private": true, + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/raintank/kentik-app-poc.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/raintank/kentik-app-poc/issues" + }, + "devDependencies": { + "grunt": "~0.4.5", + "babel": "~6.5.1", + "grunt-babel": "~6.0.0", + "grunt-contrib-copy": "~0.8.2", + "grunt-contrib-watch": "^0.6.1", + "grunt-contrib-uglify": "~0.11.0", + "grunt-systemjs-builder": "^0.2.5", + "load-grunt-tasks": "~3.2.0", + "grunt-execute": "~0.2.2", + "grunt-contrib-clean": "~0.6.0" + }, + "dependencies": { + "babel-plugin-transform-es2015-modules-systemjs": "^6.5.0", + "babel-preset-es2015": "^6.5.0", + "lodash": "~4.0.0" + }, + "homepage": "https://github.com/raintank/kentik-app-poc#readme" +} diff --git a/examples/datasource-plugin-genericdatasource/plugin.json b/examples/datasource-plugin-genericdatasource/plugin.json new file mode 100644 index 00000000000..4c3a9accd05 --- /dev/null +++ b/examples/datasource-plugin-genericdatasource/plugin.json @@ -0,0 +1,27 @@ +{ + "name": "GenericDatasource", + "id": "datasource-plugin-genericdatasource", + "type": "datasource", + + "module": "plugins/genericdatasource/datasource", + + "staticRoot": ".", + + "metrics": true, + "annotations": false, + + "info": { + "description": "generic datsource plugin", + "author": { + "name": "Raintank Inc.", + "url": "http://raintank.io" + }, + "version": "0.9.0", + "updated": "2016-02-10" + }, + + "dependencies": { + "grafanaVersion": "2.6.x", + "plugins": [ ] + } +} diff --git a/examples/datasource-plugin-genericdatasource/src/css/query-editor.css b/examples/datasource-plugin-genericdatasource/src/css/query-editor.css new file mode 100644 index 00000000000..3b678b9f368 --- /dev/null +++ b/examples/datasource-plugin-genericdatasource/src/css/query-editor.css @@ -0,0 +1,3 @@ +.generic-datasource-query-row .query-keyword { + width: 75px; +} \ No newline at end of file diff --git a/examples/datasource-plugin-genericdatasource/src/datasource.js b/examples/datasource-plugin-genericdatasource/src/datasource.js new file mode 100644 index 00000000000..1b68cf61fb1 --- /dev/null +++ b/examples/datasource-plugin-genericdatasource/src/datasource.js @@ -0,0 +1,65 @@ +export class GenericDatasource { + + constructor(instanceSettings, $q, backendSrv) { + this.type = instanceSettings.type; + this.url = instanceSettings.url; + this.name = instanceSettings.name; + this.q = $q; + this.backendSrv = backendSrv; + } + + // Called once per panel (graph) + query(options) { + var query = this.buildQueryParameters(options); + + if (query.targets.length <= 0) { + return this.q.when([]); + } + + return this.backendSrv.datasourceRequest({ + url: this.url + '/query', + data: query, + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }); + } + + // Required + // Used for testing datasource in datasource configuration pange + testDatasource() { + return this.backendSrv.datasourceRequest({ + url: this.url + '/', + method: 'GET' + }).then(response => { + if (response.status === 200) { + return { status: "success", message: "Data source is working", title: "Success" }; + } + }); + } + + // Optional + // Required for templating + metricFindQuery(options) { + return this.backendSrv.datasourceRequest({ + url: this.url + '/search', + data: options, + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }).then(this.mapToTextValue); + } + + mapToTextValue(result) { + return _.map(result.data, (d, i) => { + return { text: d, value: i}; + }); + } + + buildQueryParameters(options) { + //remove placeholder targets + options.targets = _.filter(options.targets, target => { + return target.target !== 'select metric'; + }); + + return options; + } +} diff --git a/examples/datasource-plugin-genericdatasource/src/module.js b/examples/datasource-plugin-genericdatasource/src/module.js new file mode 100644 index 00000000000..5dc2f9c89d8 --- /dev/null +++ b/examples/datasource-plugin-genericdatasource/src/module.js @@ -0,0 +1,15 @@ +import {GenericDatasource} from './datasource'; +import {GenericDatasourceQueryCtrl} from './query_ctrl'; + +class GenericConfigCtrl {} +GenericConfigCtrl.templateUrl = 'partials/config.html'; + +class GenericQueryOptionsCtrl {} +GenericQueryOptionsCtrl.templateUrl = 'partials/query.options.html'; + +export { + GenericDatasource as Datasource, + GenericDatasourceQueryCtrl as QueryCtrl, + GenericConfigCtrl as ConfigCtrl, + GenericQueryOptionsCtrl as QueryOptionsCtrl +}; diff --git a/examples/datasource-plugin-genericdatasource/src/partials/config.html b/examples/datasource-plugin-genericdatasource/src/partials/config.html new file mode 100644 index 00000000000..30470039554 --- /dev/null +++ b/examples/datasource-plugin-genericdatasource/src/partials/config.html @@ -0,0 +1,2 @@ + + diff --git a/examples/datasource-plugin-genericdatasource/src/partials/query.editor.html b/examples/datasource-plugin-genericdatasource/src/partials/query.editor.html new file mode 100644 index 00000000000..b24cb0cb884 --- /dev/null +++ b/examples/datasource-plugin-genericdatasource/src/partials/query.editor.html @@ -0,0 +1,8 @@ + + + diff --git a/examples/datasource-plugin-genericdatasource/src/partials/query.options.html b/examples/datasource-plugin-genericdatasource/src/partials/query.options.html new file mode 100644 index 00000000000..b4600118683 --- /dev/null +++ b/examples/datasource-plugin-genericdatasource/src/partials/query.options.html @@ -0,0 +1,4 @@ +
+
+
+
diff --git a/examples/datasource-plugin-genericdatasource/src/query_ctrl.js b/examples/datasource-plugin-genericdatasource/src/query_ctrl.js new file mode 100644 index 00000000000..30c572b6892 --- /dev/null +++ b/examples/datasource-plugin-genericdatasource/src/query_ctrl.js @@ -0,0 +1,26 @@ +import {QueryCtrl} from 'app/plugins/sdk'; +import './css/query-editor.css!' + +export class GenericDatasourceQueryCtrl extends QueryCtrl { + + constructor($scope, $injector, uiSegmentSrv) { + super($scope, $injector); + + this.scope = $scope; + this.uiSegmentSrv = uiSegmentSrv; + this.target.target = this.target.target || 'select metric'; + } + + getOptions() { + return this.datasource.metricFindQuery(this.target) + .then(this.uiSegmentSrv.transformToSegments(false)); + // Options have to be transformed by uiSegmentSrv to be usable by metric-segment-model directive + } + + onChangeInternal() { + this.panelCtrl.refresh(); // Asks the panel to refresh data. + } +} + +GenericDatasourceQueryCtrl.templateUrl = 'partials/query.editor.html'; +