Browse Source

feat(plugins): major improvement in plugins golang code

Torkel Ödegaard 10 years ago
parent
commit
1ffcea1952

+ 38 - 0
pkg/plugins/app_plugin.go

@@ -0,0 +1,38 @@
+package plugins
+
+import (
+	"encoding/json"
+
+	"github.com/grafana/grafana/pkg/models"
+)
+
+type AppPluginPage struct {
+	Text    string          `json:"text"`
+	Icon    string          `json:"icon"`
+	Url     string          `json:"url"`
+	ReqRole models.RoleType `json:"reqRole"`
+}
+
+type AppPluginCss struct {
+	Light string `json:"light"`
+	Dark  string `json:"dark"`
+}
+
+type AppPlugin struct {
+	FrontendPluginBase
+	Enabled bool           `json:"enabled"`
+	Pinned  bool           `json:"pinned"`
+	Css     *AppPluginCss  `json:"css"`
+	Page    *AppPluginPage `json:"page"`
+}
+
+func (p *AppPlugin) Load(decoder *json.Decoder, pluginDir string) error {
+	if err := decoder.Decode(&p); err != nil {
+		return err
+	}
+
+	p.PluginDir = pluginDir
+	p.initFrontendPlugin()
+	Apps[p.Id] = p
+	return nil
+}

+ 25 - 0
pkg/plugins/datasource_plugin.go

@@ -0,0 +1,25 @@
+package plugins
+
+import "encoding/json"
+
+type DataSourcePlugin struct {
+	FrontendPluginBase
+	DefaultMatchFormat string `json:"defaultMatchFormat"`
+	Annotations        bool   `json:"annotations"`
+	Metrics            bool   `json:"metrics"`
+	BuiltIn            bool   `json:"builtIn"`
+	Mixed              bool   `json:"mixed"`
+	App                string `json:"app"`
+}
+
+func (p *DataSourcePlugin) Load(decoder *json.Decoder, pluginDir string) error {
+	if err := decoder.Decode(&p); err != nil {
+		return err
+	}
+
+	p.PluginDir = pluginDir
+	p.initFrontendPlugin()
+	DataSources[p.Id] = p
+
+	return nil
+}

+ 47 - 0
pkg/plugins/frontend_plugin.go

@@ -0,0 +1,47 @@
+package plugins
+
+import (
+	"net/url"
+	"path"
+)
+
+type FrontendPluginBase struct {
+	PluginBase
+	Module     string `json:"module"`
+	StaticRoot string `json:"staticRoot"`
+}
+
+func (fp *FrontendPluginBase) initFrontendPlugin() {
+	if fp.StaticRoot != "" {
+		StaticRoutes = append(StaticRoutes, &PluginStaticRoute{
+			Directory: fp.StaticRoot,
+			PluginId:  fp.Id,
+		})
+	}
+
+	fp.Info.Logos.Small = evalRelativePluginUrlPath(fp.Info.Logos.Small, fp.Id)
+	fp.Info.Logos.Large = evalRelativePluginUrlPath(fp.Info.Logos.Large, fp.Id)
+
+	fp.handleModuleDefaults()
+}
+
+func (fp *FrontendPluginBase) handleModuleDefaults() {
+	if fp.Module != "" {
+		return
+	}
+
+	if fp.StaticRoot != "" {
+		fp.Module = path.Join("plugins", fp.Type, fp.Id, "module")
+		return
+	}
+
+	fp.Module = path.Join("app/plugins", fp.Type, fp.Id, "module")
+}
+
+func evalRelativePluginUrlPath(pathStr string, pluginId string) string {
+	u, _ := url.Parse(pathStr)
+	if u.IsAbs() {
+		return pathStr
+	}
+	return path.Join("public/plugins", pluginId, pathStr)
+}

+ 14 - 49
pkg/plugins/models.go

@@ -1,15 +1,22 @@
 package plugins
 
 import (
+	"encoding/json"
+
 	"github.com/grafana/grafana/pkg/models"
 )
 
-type PluginCommon struct {
-	Type       string     `json:"type"`
-	Name       string     `json:"name"`
-	Id         string     `json:"id"`
-	StaticRoot string     `json:"staticRoot"`
-	Info       PluginInfo `json:"info"`
+type PluginLoader interface {
+	Load(decoder *json.Decoder, pluginDir string) error
+}
+
+type PluginBase struct {
+	Type      string     `json:"type"`
+	Name      string     `json:"name"`
+	Id        string     `json:"id"`
+	App       string     `json:"app"`
+	Info      PluginInfo `json:"info"`
+	PluginDir string     `json:"-"`
 }
 
 type PluginInfo struct {
@@ -29,30 +36,11 @@ type PluginLogos struct {
 	Large string `json:"large"`
 }
 
-type DataSourcePlugin struct {
-	PluginCommon
-	Module             string                 `json:"module"`
-	ServiceName        string                 `json:"serviceName"`
-	Partials           map[string]interface{} `json:"partials"`
-	DefaultMatchFormat string                 `json:"defaultMatchFormat"`
-	Annotations        bool                   `json:"annotations"`
-	Metrics            bool                   `json:"metrics"`
-	BuiltIn            bool                   `json:"builtIn"`
-	Mixed              bool                   `json:"mixed"`
-	App                string                 `json:"app"`
-}
-
 type PluginStaticRoute struct {
 	Directory string
 	PluginId  string
 }
 
-type PanelPlugin struct {
-	PluginCommon
-	Module string `json:"module"`
-	App    string `json:"app"`
-}
-
 type ApiPluginRoute struct {
 	Path            string          `json:"path"`
 	Method          string          `json:"method"`
@@ -60,34 +48,11 @@ type ApiPluginRoute struct {
 	ReqGrafanaAdmin bool            `json:"reqGrafanaAdmin"`
 	ReqRole         models.RoleType `json:"reqRole"`
 	Url             string          `json:"url"`
-	App             string          `json:"app"`
-}
-
-type AppPluginPage struct {
-	Text    string          `json:"text"`
-	Icon    string          `json:"icon"`
-	Url     string          `json:"url"`
-	ReqRole models.RoleType `json:"reqRole"`
-}
-
-type AppPluginCss struct {
-	Light string `json:"light"`
-	Dark  string `json:"dark"`
 }
 
 type ApiPlugin struct {
-	PluginCommon
+	PluginBase
 	Routes []*ApiPluginRoute `json:"routes"`
-	App    string            `json:"app"`
-}
-
-type AppPlugin struct {
-	PluginCommon
-	Enabled bool           `json:"enabled"`
-	Pinned  bool           `json:"pinned"`
-	Module  string         `json:"module"`
-	Css     *AppPluginCss  `json:"css"`
-	Page    *AppPluginPage `json:"page"`
 }
 
 type EnabledPlugins struct {

+ 19 - 0
pkg/plugins/panel_plugin.go

@@ -0,0 +1,19 @@
+package plugins
+
+import "encoding/json"
+
+type PanelPlugin struct {
+	FrontendPluginBase
+}
+
+func (p *PanelPlugin) Load(decoder *json.Decoder, pluginDir string) error {
+	if err := decoder.Decode(&p); err != nil {
+		return err
+	}
+
+	p.PluginDir = pluginDir
+	p.initFrontendPlugin()
+	Panels[p.Id] = p
+
+	return nil
+}

+ 15 - 59
pkg/plugins/plugins.go

@@ -5,10 +5,10 @@ import (
 	"encoding/json"
 	"errors"
 	"io"
-	"net/url"
 	"os"
 	"path"
 	"path/filepath"
+	"reflect"
 	"strings"
 	"text/template"
 
@@ -24,6 +24,7 @@ var (
 	ApiPlugins   map[string]*ApiPlugin
 	StaticRoutes []*PluginStaticRoute
 	Apps         map[string]*AppPlugin
+	PluginTypes  map[string]interface{}
 )
 
 type PluginScanner struct {
@@ -37,6 +38,12 @@ func Init() error {
 	StaticRoutes = make([]*PluginStaticRoute, 0)
 	Panels = make(map[string]*PanelPlugin)
 	Apps = make(map[string]*AppPlugin)
+	PluginTypes = map[string]interface{}{
+		"panel":      PanelPlugin{},
+		"datasource": DataSourcePlugin{},
+		"api":        ApiPlugin{},
+		"app":        AppPlugin{},
+	}
 
 	scan(path.Join(setting.StaticRootPath, "app/plugins"))
 	checkPluginPaths()
@@ -115,27 +122,7 @@ func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err erro
 	return nil
 }
 
-func evalRelativePluginUrlPath(pathStr string, pluginId string) string {
-	u, _ := url.Parse(pathStr)
-	if u.IsAbs() {
-		return pathStr
-	}
-	return path.Join("public/plugins", pluginId, pathStr)
-}
-
-func addPublicContent(plugin *PluginCommon, currentDir string) {
-	if plugin.StaticRoot != "" {
-		StaticRoutes = append(StaticRoutes, &PluginStaticRoute{
-			Directory: path.Join(currentDir, plugin.StaticRoot),
-			PluginId:  plugin.Id,
-		})
-	}
-
-	plugin.Info.Logos.Small = evalRelativePluginUrlPath(plugin.Info.Logos.Small, plugin.Id)
-	plugin.Info.Logos.Large = evalRelativePluginUrlPath(plugin.Info.Logos.Large, plugin.Id)
-}
-
-func interpolatePluginJson(reader io.Reader, pluginCommon *PluginCommon) (io.Reader, error) {
+func interpolatePluginJson(reader io.Reader, pluginCommon *PluginBase) (io.Reader, error) {
 	buf := new(bytes.Buffer)
 	buf.ReadFrom(reader)
 	jsonStr := buf.String() //
@@ -167,7 +154,7 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
 	defer reader.Close()
 
 	jsonParser := json.NewDecoder(reader)
-	pluginCommon := PluginCommon{}
+	pluginCommon := PluginBase{}
 	if err := jsonParser.Decode(&pluginCommon); err != nil {
 		return err
 	}
@@ -177,52 +164,21 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
 	}
 
 	reader.Seek(0, 0)
-
 	if newReader, err := interpolatePluginJson(reader, &pluginCommon); err != nil {
 		return err
 	} else {
 		jsonParser = json.NewDecoder(newReader)
 	}
 
-	switch pluginCommon.Type {
-	case "datasource":
-		p := DataSourcePlugin{}
-		if err := jsonParser.Decode(&p); err != nil {
-			return err
-		}
-
-		DataSources[p.Id] = &p
-		addPublicContent(&p.PluginCommon, currentDir)
-
-	case "panel":
-		p := PanelPlugin{}
-		reader.Seek(0, 0)
-		if err := jsonParser.Decode(&p); err != nil {
-			return err
-		}
+	var loader PluginLoader
 
-		Panels[p.Id] = &p
-		addPublicContent(&p.PluginCommon, currentDir)
-	case "api":
-		p := ApiPlugin{}
-		reader.Seek(0, 0)
-		if err := jsonParser.Decode(&p); err != nil {
-			return err
-		}
-		ApiPlugins[p.Id] = &p
-	case "app":
-		p := AppPlugin{}
-		reader.Seek(0, 0)
-		if err := jsonParser.Decode(&p); err != nil {
-			return err
-		}
-		Apps[p.Id] = &p
-		addPublicContent(&p.PluginCommon, currentDir)
-	default:
+	if pluginGoType, exists := PluginTypes[pluginCommon.Type]; !exists {
 		return errors.New("Unkown plugin type " + pluginCommon.Type)
+	} else {
+		loader = reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader)
 	}
 
-	return nil
+	return loader.Load(jsonParser, currentDir)
 }
 
 func GetEnabledPlugins(orgApps []*models.AppPlugin) EnabledPlugins {

+ 4 - 0
pkg/plugins/plugins_test.go

@@ -19,6 +19,10 @@ func TestPluginScans(t *testing.T) {
 		So(err, ShouldBeNil)
 		So(len(DataSources), ShouldBeGreaterThan, 1)
 		So(len(Panels), ShouldBeGreaterThan, 1)
+
+		Convey("Should set module automatically", func() {
+			So(DataSources["graphite"].Module, ShouldEqual, "app/plugins/datasource/graphite/module")
+		})
 	})
 
 	Convey("When reading app plugin definition", t, func() {

+ 0 - 7
public/app/plugins/datasource/cloudwatch/plugin.json

@@ -3,13 +3,6 @@
   "name": "CloudWatch",
   "id": "cloudwatch",
 
-  "module": "app/plugins/datasource/cloudwatch/module",
-
-  "partials": {
-    "config": "app/plugins/datasource/cloudwatch/partials/config.html",
-    "query": "app/plugins/datasource/cloudwatch/partials/query.editor.html"
-  },
-
   "metrics": true,
   "annotations": true
 }

+ 0 - 2
public/app/plugins/datasource/elasticsearch/plugin.json

@@ -3,8 +3,6 @@
   "name": "Elasticsearch",
   "id": "elasticsearch",
 
-  "module": "app/plugins/datasource/elasticsearch/module",
-
   "defaultMatchFormat": "lucene",
   "annotations": true,
   "metrics": true

+ 0 - 2
public/app/plugins/datasource/grafana/plugin.json

@@ -3,8 +3,6 @@
   "name": "Grafana",
   "id": "grafana",
 
-  "module": "app/plugins/datasource/grafana/module",
-
   "builtIn": true,
   "metrics": true
 }

+ 4 - 0
public/app/plugins/datasource/graphite/module.js

@@ -19,6 +19,10 @@ function (angular, GraphiteDatasource) {
     return {templateUrl: 'app/plugins/datasource/graphite/partials/annotations.editor.html'};
   });
 
+  module.directive('datasourceCustomSettingsViewGraphite', function() {
+    return {templateUrl: 'app/plugins/datasource/graphite/partials/config.html'};
+  });
+
   return {
     Datasource: GraphiteDatasource,
   };

+ 0 - 2
public/app/plugins/datasource/graphite/plugin.json

@@ -3,8 +3,6 @@
   "type": "datasource",
   "id": "graphite",
 
-  "module": "app/plugins/datasource/graphite/module",
-
   "defaultMatchFormat": "glob",
   "metrics": true,
   "annotations": true

+ 0 - 2
public/app/plugins/datasource/influxdb/plugin.json

@@ -3,8 +3,6 @@
   "name": "InfluxDB 0.9.x",
   "id": "influxdb",
 
-  "module": "app/plugins/datasource/influxdb/module",
-
   "defaultMatchFormat": "regex values",
   "metrics": true,
   "annotations": true

+ 0 - 4
public/app/plugins/datasource/mixed/plugin.json

@@ -5,9 +5,5 @@
 
   "builtIn": true,
   "mixed": true,
-
-  "serviceName": "MixedDatasource",
-
-  "module": "app/plugins/datasource/mixed/datasource",
   "metrics": true
 }

+ 0 - 2
public/app/plugins/datasource/opentsdb/plugin.json

@@ -3,8 +3,6 @@
   "name": "OpenTSDB",
   "id": "opentsdb",
 
-  "module": "app/plugins/datasource/opentsdb/module",
-
   "metrics": true,
   "defaultMatchFormat": "pipe"
 }

+ 3 - 0
public/app/plugins/datasource/prometheus/datasource.d.ts

@@ -0,0 +1,3 @@
+declare var Datasource: any;
+export default Datasource;
+

+ 34 - 41
public/app/plugins/datasource/prometheus/datasource.js

@@ -3,31 +3,25 @@ define([
   'lodash',
   'moment',
   'app/core/utils/datemath',
-  './directives',
   './query_ctrl',
 ],
 function (angular, _, moment, dateMath) {
   'use strict';
 
-  var module = angular.module('grafana.services');
-
   var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/;
 
-  module.factory('PrometheusDatasource', function($q, backendSrv, templateSrv) {
-
-    function PrometheusDatasource(datasource) {
-      this.type = 'prometheus';
-      this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
-      this.name = datasource.name;
-      this.supportMetrics = true;
-      this.url = datasource.url;
-      this.directUrl = datasource.directUrl;
-      this.basicAuth = datasource.basicAuth;
-      this.withCredentials = datasource.withCredentials;
-      this.lastErrors = {};
-    }
-
-    PrometheusDatasource.prototype._request = function(method, url) {
+  function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv) {
+    this.type = 'prometheus';
+    this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
+    this.name = instanceSettings.name;
+    this.supportMetrics = true;
+    this.url = instanceSettings.url;
+    this.directUrl = instanceSettings.directUrl;
+    this.basicAuth = instanceSettings.basicAuth;
+    this.withCredentials = instanceSettings.withCredentials;
+    this.lastErrors = {};
+
+    this._request = function(method, url) {
       var options = {
         url: this.url + url,
         method: method
@@ -46,7 +40,7 @@ function (angular, _, moment, dateMath) {
     };
 
     // Called once per panel (graph)
-    PrometheusDatasource.prototype.query = function(options) {
+    this.query = function(options) {
       var start = getPrometheusTime(options.range.from, false);
       var end = getPrometheusTime(options.range.to, true);
 
@@ -86,31 +80,31 @@ function (angular, _, moment, dateMath) {
 
       var self = this;
       return $q.all(allQueryPromise)
-        .then(function(allResponse) {
-          var result = [];
-
-          _.each(allResponse, function(response, index) {
-            if (response.status === 'error') {
-              self.lastErrors.query = response.error;
-              throw response.error;
-            }
-            delete self.lastErrors.query;
-
-            _.each(response.data.data.result, function(metricData) {
-              result.push(transformMetricData(metricData, options.targets[index]));
-            });
+      .then(function(allResponse) {
+        var result = [];
+
+        _.each(allResponse, function(response, index) {
+          if (response.status === 'error') {
+            self.lastErrors.query = response.error;
+            throw response.error;
+          }
+          delete self.lastErrors.query;
+
+          _.each(response.data.data.result, function(metricData) {
+            result.push(transformMetricData(metricData, options.targets[index]));
           });
-
-          return { data: result };
         });
+
+        return { data: result };
+      });
     };
 
-    PrometheusDatasource.prototype.performTimeSeriesQuery = function(query, start, end) {
+    this.performTimeSeriesQuery = function(query, start, end) {
       var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end + '&step=' + query.step;
       return this._request('GET', url);
     };
 
-    PrometheusDatasource.prototype.performSuggestQuery = function(query) {
+    this.performSuggestQuery = function(query) {
       var url = '/api/v1/label/__name__/values';
 
       return this._request('GET', url).then(function(result) {
@@ -120,7 +114,7 @@ function (angular, _, moment, dateMath) {
       });
     };
 
-    PrometheusDatasource.prototype.metricFindQuery = function(query) {
+    this.metricFindQuery = function(query) {
       if (!query) { return $q.when([]); }
 
       var interpolated;
@@ -196,7 +190,7 @@ function (angular, _, moment, dateMath) {
       }
     };
 
-    PrometheusDatasource.prototype.testDatasource = function() {
+    this.testDatasource = function() {
       return this.metricFindQuery('metrics(.*)').then(function() {
         return { status: 'success', message: 'Data source is working', title: 'Success' };
       });
@@ -276,8 +270,7 @@ function (angular, _, moment, dateMath) {
       }
       return (date.valueOf() / 1000).toFixed(0);
     }
+  }
 
-    return PrometheusDatasource;
-  });
-
+  return PrometheusDatasource;
 });

+ 9 - 1
public/app/plugins/datasource/prometheus/directives.js → public/app/plugins/datasource/prometheus/module.js

@@ -1,7 +1,8 @@
 define([
   'angular',
+  './datasource',
 ],
-function (angular) {
+function (angular, PromDatasource) {
   'use strict';
 
   var module = angular.module('grafana.directives');
@@ -10,4 +11,11 @@ function (angular) {
     return {controller: 'PrometheusQueryCtrl', templateUrl: 'app/plugins/datasource/prometheus/partials/query.editor.html'};
   });
 
+  module.directive('datasourceCustomSettingsViewPrometheus', function() {
+    return {templateUrl: 'app/plugins/datasource/prometheus/partials/config.html'};
+  });
+
+  return {
+    Datasource: PromDatasource
+  };
 });

+ 0 - 8
public/app/plugins/datasource/prometheus/plugin.json

@@ -3,13 +3,5 @@
   "name": "Prometheus",
   "id": "prometheus",
 
-  "serviceName": "PrometheusDatasource",
-
-  "module": "app/plugins/datasource/prometheus/datasource",
-
-  "partials": {
-    "config": "app/plugins/datasource/prometheus/partials/config.html"
-  },
-
   "metrics": true
 }

+ 9 - 6
public/app/plugins/datasource/prometheus/specs/datasource_specs.ts

@@ -1,17 +1,20 @@
-import '../datasource';
 import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
 import moment from 'moment';
 import helpers from 'test/specs/helpers';
+import Datasource from '../datasource';
 
 describe('PrometheusDatasource', function() {
-
   var ctx = new helpers.ServiceTestContext();
+  var instanceSettings = {url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp' };
+
   beforeEach(angularMocks.module('grafana.core'));
   beforeEach(angularMocks.module('grafana.services'));
-  beforeEach(ctx.createService('PrometheusDatasource'));
-  beforeEach(function() {
-    ctx.ds = new ctx.service({ url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp' });
-  });
+  beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
+    ctx.$q = $q;
+    ctx.$httpBackend =  $httpBackend;
+    ctx.$rootScope = $rootScope;
+    ctx.ds = $injector.instantiate(Datasource, {instanceSettings: instanceSettings});
+  }));
 
   describe('When querying prometheus with one target using query editor target spec', function() {
     var results;