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';
+