mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'example_datasource'
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -37,3 +37,4 @@ profile.cov
|
|||||||
.notouch
|
.notouch
|
||||||
/pkg/cmd/grafana-cli/grafana-cli
|
/pkg/cmd/grafana-cli/grafana-cli
|
||||||
/pkg/cmd/grafana-server/grafana-server
|
/pkg/cmd/grafana-server/grafana-server
|
||||||
|
/examples/*/dist
|
||||||
13
examples/datasource-plugin-genericdatasource/.jscs.json
Normal file
13
examples/datasource-plugin-genericdatasource/.jscs.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"disallowImplicitTypeConversion": ["string"],
|
||||||
|
"disallowKeywords": ["with"],
|
||||||
|
"disallowMultipleLineBreaks": true,
|
||||||
|
"disallowMixedSpacesAndTabs": true,
|
||||||
|
"disallowTrailingWhitespace": true,
|
||||||
|
"requireSpacesInFunctionExpression": {
|
||||||
|
"beforeOpeningCurlyBrace": true
|
||||||
|
},
|
||||||
|
"disallowSpacesInsideArrayBrackets": true,
|
||||||
|
"disallowSpacesInsideParentheses": true,
|
||||||
|
"validateIndentation": 2
|
||||||
|
}
|
||||||
36
examples/datasource-plugin-genericdatasource/.jshintrc
Normal file
36
examples/datasource-plugin-genericdatasource/.jshintrc
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
54
examples/datasource-plugin-genericdatasource/Gruntfile.js
Normal file
54
examples/datasource-plugin-genericdatasource/Gruntfile.js
Normal file
@@ -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']);
|
||||||
|
};
|
||||||
75
examples/datasource-plugin-genericdatasource/README.md
Normal file
75
examples/datasource-plugin-genericdatasource/README.md
Normal file
@@ -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
|
||||||
37
examples/datasource-plugin-genericdatasource/package.json
Normal file
37
examples/datasource-plugin-genericdatasource/package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
27
examples/datasource-plugin-genericdatasource/plugin.json
Normal file
27
examples/datasource-plugin-genericdatasource/plugin.json
Normal file
@@ -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": [ ]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.generic-datasource-query-row .query-keyword {
|
||||||
|
width: 75px;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
examples/datasource-plugin-genericdatasource/src/module.js
Normal file
15
examples/datasource-plugin-genericdatasource/src/module.js
Normal file
@@ -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
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<datasource-http-settings current="ctrl.current">
|
||||||
|
</datasource-http-settings>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<query-editor-row ctrl="ctrl" class="generic-datasource-query-row">
|
||||||
|
<ul class="tight-form-list">
|
||||||
|
<li class="tight-form-item query-keyword">Query</li>
|
||||||
|
<li>
|
||||||
|
<metric-segment-model property="ctrl.target.target" get-options="ctrl.getOptions()" on-change="ctrl.onChangeInternal()" css-class="tight-form-item-xxlarge"></metric-segment-model>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</query-editor-row>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<section class="grafana-metric-options" >
|
||||||
|
<div class="gf-form">
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@@ -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';
|
||||||
|
|
||||||
Reference in New Issue
Block a user