ソースを参照

plugin change: make interval, cache timeout & max data points options in plugin.json, remove query.options component feature, add help markdown feature and toggle for data sources

Torkel Ödegaard 8 年 前
コミット
84d4958a3c

+ 1 - 1
pkg/api/api.go

@@ -209,7 +209,7 @@ func (hs *HttpServer) registerRoutes() {
 
 		r.Get("/plugins", wrap(GetPluginList))
 		r.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))
-		r.Get("/plugins/:pluginId/readme", wrap(GetPluginReadme))
+		r.Get("/plugins/:pluginId/markdown/:name", wrap(GetPluginMarkdown))
 
 		r.Group("/plugins", func() {
 			r.Get("/:pluginId/dashboards/", wrap(GetPluginDashboards))

+ 4 - 3
pkg/api/plugins.go

@@ -147,15 +147,16 @@ func GetPluginDashboards(c *middleware.Context) Response {
 	}
 }
 
-func GetPluginReadme(c *middleware.Context) Response {
+func GetPluginMarkdown(c *middleware.Context) Response {
 	pluginId := c.Params(":pluginId")
+	name := c.Params(":name")
 
-	if content, err := plugins.GetPluginReadme(pluginId); err != nil {
+	if content, err := plugins.GetPluginMarkdown(pluginId, name); err != nil {
 		if notfound, ok := err.(plugins.PluginNotFoundError); ok {
 			return ApiError(404, notfound.Error(), nil)
 		}
 
-		return ApiError(500, "Could not get readme", err)
+		return ApiError(500, "Could not get markdown file", err)
 	} else {
 		return Respond(200, content)
 	}

+ 25 - 7
pkg/plugins/datasource_plugin.go

@@ -1,15 +1,24 @@
 package plugins
 
-import "encoding/json"
+import (
+	"encoding/json"
+	"os"
+	"path/filepath"
+)
 
 type DataSourcePlugin struct {
 	FrontendPluginBase
-	Annotations bool              `json:"annotations"`
-	Metrics     bool              `json:"metrics"`
-	Alerting    bool              `json:"alerting"`
-	BuiltIn     bool              `json:"builtIn"`
-	Mixed       bool              `json:"mixed"`
-	Routes      []*AppPluginRoute `json:"routes"`
+	Annotations   bool `json:"annotations"`
+	Metrics       bool `json:"metrics"`
+	Alerting      bool `json:"alerting"`
+	MinInterval   bool `json:"minInterval,omitempty"`
+	CacheTimeout  bool `json:"cacheTimeout,omitempty"`
+	MaxDataPoints bool `json:"maxDataPoints,omitempty"`
+	BuiltIn       bool `json:"builtIn,omitempty"`
+	Mixed         bool `json:"mixed,omitempty"`
+	HasHelp       bool `json:"hasHelp,omitempty"`
+
+	Routes []*AppPluginRoute `json:"-"`
 }
 
 func (p *DataSourcePlugin) Load(decoder *json.Decoder, pluginDir string) error {
@@ -21,6 +30,15 @@ func (p *DataSourcePlugin) Load(decoder *json.Decoder, pluginDir string) error {
 		return err
 	}
 
+	// look for help markdown
+	helpPath := filepath.Join(p.PluginDir, "HELP.md")
+	if _, err := os.Stat(helpPath); os.IsNotExist(err) {
+		helpPath = filepath.Join(p.PluginDir, "help.md")
+	}
+	if _, err := os.Stat(helpPath); err == nil {
+		p.HasHelp = true
+	}
+
 	DataSources[p.Id] = p
 	return nil
 }

+ 2 - 5
pkg/plugins/models.go

@@ -38,8 +38,8 @@ type PluginBase struct {
 	Includes     []*PluginInclude   `json:"includes"`
 	Module       string             `json:"module"`
 	BaseUrl      string             `json:"baseUrl"`
-	HideFromList bool               `json:"hideFromList"`
-	State        string             `json:"state"`
+	HideFromList bool               `json:"hideFromList,omitempty"`
+	State        string             `json:"state,omitempty"`
 
 	IncludedInAppId string `json:"-"`
 	PluginDir       string `json:"-"`
@@ -48,9 +48,6 @@ type PluginBase struct {
 
 	GrafanaNetVersion   string `json:"-"`
 	GrafanaNetHasUpdate bool   `json:"-"`
-
-	// cache for readme file contents
-	Readme []byte `json:"-"`
 }
 
 func (pb *PluginBase) registerPlugin(pluginDir string) error {

+ 9 - 14
pkg/plugins/plugins.go

@@ -3,6 +3,7 @@ package plugins
 import (
 	"encoding/json"
 	"errors"
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path"
@@ -166,30 +167,24 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
 	return loader.Load(jsonParser, currentDir)
 }
 
-func GetPluginReadme(pluginId string) ([]byte, error) {
+func GetPluginMarkdown(pluginId string, name string) ([]byte, error) {
 	plug, exists := Plugins[pluginId]
 	if !exists {
 		return nil, PluginNotFoundError{pluginId}
 	}
 
-	if plug.Readme != nil {
-		return plug.Readme, nil
+	path := filepath.Join(plug.PluginDir, fmt.Sprintf("%s.md", strings.ToUpper(name)))
+	if _, err := os.Stat(path); os.IsNotExist(err) {
+		path = filepath.Join(plug.PluginDir, fmt.Sprintf("%s.md", strings.ToLower(name)))
 	}
 
-	readmePath := filepath.Join(plug.PluginDir, "README.md")
-	if _, err := os.Stat(readmePath); os.IsNotExist(err) {
-		readmePath = filepath.Join(plug.PluginDir, "readme.md")
+	if _, err := os.Stat(path); os.IsNotExist(err) {
+		return make([]byte, 0), nil
 	}
 
-	if _, err := os.Stat(readmePath); os.IsNotExist(err) {
-		plug.Readme = make([]byte, 0)
-		return plug.Readme, nil
-	}
-
-	if readmeBytes, err := ioutil.ReadFile(readmePath); err != nil {
+	if data, err := ioutil.ReadFile(path); err != nil {
 		return nil, err
 	} else {
-		plug.Readme = readmeBytes
-		return plug.Readme, nil
+		return data, nil
 	}
 }

+ 31 - 1
public/app/features/panel/metrics_tab.ts

@@ -2,6 +2,7 @@
 
 import _ from 'lodash';
 import {DashboardModel} from '../dashboard/model';
+import Remarkable from 'remarkable';
 
 export class MetricsTabCtrl {
   dsName: string;
@@ -14,9 +15,16 @@ export class MetricsTabCtrl {
   panelDsValue: any;
   addQueryDropdown: any;
   queryTroubleshooterOpen: boolean;
+  helpOpen: boolean;
+  hasHelp: boolean;
+  helpHtml: string;
+  hasMinInterval: boolean;
+  hasCacheTimeout: boolean;
+  hasMaxDataPoints: boolean;
+  animateStart: boolean;
 
   /** @ngInject */
-  constructor($scope, private uiSegmentSrv, private datasourceSrv) {
+  constructor($scope, private $sce, private datasourceSrv, private backendSrv, private $timeout) {
     this.panelCtrl = $scope.ctrl;
     $scope.ctrl = this;
 
@@ -34,6 +42,14 @@ export class MetricsTabCtrl {
     this.addQueryDropdown = {text: 'Add Query', value: null, fake: true};
     // update next ref id
     this.panelCtrl.nextRefId = this.dashboard.getNextQueryLetter(this.panel);
+    this.updateDatasourceOptions();
+  }
+
+  updateDatasourceOptions() {
+    this.hasHelp = this.current.meta.hasHelp;
+    this.hasMinInterval = this.current.meta.minInterval === true;
+    this.hasCacheTimeout = this.current.meta.cacheTimeout === true;
+    this.hasMaxDataPoints = this.current.meta.maxDataPoints === true;
   }
 
   getOptions(includeBuiltin) {
@@ -51,6 +67,7 @@ export class MetricsTabCtrl {
 
     this.current = option.datasource;
     this.panelCtrl.setDatasource(option.datasource);
+    this.updateDatasourceOptions();
   }
 
   addMixedQuery(option) {
@@ -67,6 +84,19 @@ export class MetricsTabCtrl {
     this.panelCtrl.addQuery({isNew: true});
   }
 
+  toggleHelp() {
+    this.animateStart = false;
+    this.helpOpen = !this.helpOpen;
+    this.backendSrv.get(`/api/plugins/${this.current.meta.id}/markdown/help`).then(res => {
+      var md = new Remarkable();
+      this.helpHtml = this.$sce.trustAsHtml(md.render(res));
+
+      this.$timeout(() => {
+        this.animateStart = true;
+      }, 1);
+    });
+  }
+
   toggleQueryTroubleshooter() {
     this.queryTroubleshooterOpen = !this.queryTroubleshooterOpen;
   }

+ 77 - 28
public/app/features/panel/partials/metrics_tab.html

@@ -11,41 +11,90 @@
                         on-change="ctrl.datasourceChanged($option)">
       </gf-form-dropdown>
 		</div>
-		<div class="gf-form">
-			<label class="gf-form-label">Min auto interval</label>
-			<input type="text" class="gf-form-input width-7" placeholder="1s" />
+		<div class="gf-form" ng-if="ctrl.hasMinInterval">
+			<label class="gf-form-label">
+				Min auto interval
+			</label>
+			<input type="text"
+						class="gf-form-input width-6"
+						placeholder="1s"
+						ng-model="ctrl.panel.interval"
+						spellcheck="false"
+						ng-model-onblur ng-change="ctrl.panelCtrl.refresh()"
+						/>
 			<info-popover mode="right-absolute">
 				A lower limit for the auto group by time interval. Recommended to be set to write frequency,
 				for example <code>1m</code> if your data is written every minute. Access auto interval via variable <code>$__interval</code> for time range
 				string and <code>$__interval_ms</code> for numeric variable that can be used in math expressions.
 			</info-popover>
-    </div>
-		<div class="gf-form gf-form--grow">
-			<label class="gf-form-label gf-form-label--grow"></label>
 		</div>
-		<div class="gf-form">
+		<div class="gf-form" ng-if="ctrl.hasCacheTimeout">
+			<label class="gf-form-label">
+				Cache timeout
+			</label>
+			<input  type="text"
+							class="gf-form-input width-6"
+							placeholder="60"
+							ng-model="ctrl.panel.cacheTimeout"
+						  ng-model-onblur ng-change="ctrl.panelCtrl.refresh()"
+							spellcheck="false"
+							/>
+			<info-popover mode="right-absolute">
+					If your time series store has a query cache this option can override the default
+					cache timeout. Specify a numeric value in seconds.
+			</info-popover>
+		</div>
+		<div class="gf-form" ng-if="ctrl.hasMaxDataPoints">
 			<label class="gf-form-label">
-				<i class="fa fa-question-circle"></i>
-				<a href="http://google.com">Help &amp; Docs</a>
+				Max data points
 			</label>
+			<input type="text"
+					 	 class="gf-form-input width-6"
+						 placeholder="auto"
+					   ng-model-onblur ng-change="ctrl.panelCtrl.refresh()"
+						 ng-model="ctrl.panel.maxDataPoints"
+						 spellcheck="false"  />
+			<info-popover mode="right-absolute">
+					The maximum data points the query should return. For graphs this
+					is automatically set to one data point per pixel.
+			</info-popover>
+		</div>
+
+		<div class="gf-form gf-form--grow">
+			<label class="gf-form-label gf-form-label--grow"></label>
+		</div>
+		<div class="gf-form" ng-if="ctrl.hasHelp">
+			<button class="btn btn-secondary gf-form-btn" ng-click="ctrl.toggleHelp()">
+				<i class="fa fa-chevron-right" ng-hide="ctrl.helpOpen"></i>
+				<i class="fa fa-chevron-down" ng-show="ctrl.helpOpen"></i>
+				Help
+			</button>
 		</div>
 		<div class="gf-form">
-			<button class="btn btn-secondary gf-form-btn" ng-click="ctrl.toggleQueryTroubleshooter()">
+			<button class="btn btn-secondary gf-form-btn" ng-click="ctrl.toggleQueryTroubleshooter()" bs-tooltip="'Display data query request & response'">
 				<i class="fa fa-chevron-right" ng-hide="ctrl.queryTroubleshooterOpen"></i>
 				<i class="fa fa-chevron-down" ng-show="ctrl.queryTroubleshooterOpen"></i>
 				Query Inspector
 			</button>
-    </div>
-  </div>
+		</div>
+	</div>
+
+	<div class="grafana-info-box grafana-info-box--animate" ng-if="ctrl.helpOpen" ng-class="{'grafana-info-box--animate-open': ctrl.animateStart}">
+		<div class="markdown-html" ng-bind-html="ctrl.helpHtml"></div>
+		<a class="grafana-info-box__close" ng-click="ctrl.toggleHelp()">
+			<i class="fa fa-chevron-up"></i>
+		</a>
+	</div>
+
 	<query-troubleshooter panel-ctrl="ctrl.panelCtrl" is-open="ctrl.queryTroubleshooterOpen"></query-troubleshooter>
 </div>
 
 <div class="query-editor-rows gf-form-group">
-  <div ng-repeat="target in ctrl.panel.targets" ng-class="{'gf-form-disabled': target.hide}">
-    <rebuild-on-change property="ctrl.panel.datasource || target.datasource" show-null="true">
-      <plugin-component type="query-ctrl">
-      </plugin-component>
-    </rebuild-on-change>
+	<div ng-repeat="target in ctrl.panel.targets" ng-class="{'gf-form-disabled': target.hide}">
+		<rebuild-on-change property="ctrl.panel.datasource || target.datasource" show-null="true">
+			<plugin-component type="query-ctrl">
+			</plugin-component>
+		</rebuild-on-change>
 	</div>
 
 	<div class="gf-form-query">
@@ -56,16 +105,16 @@
 				</span>
 				<span class="gf-form-query-letter-cell-letter">{{ctrl.panelCtrl.nextRefId}}</span>
 			</label>
-      <button class="btn btn-secondary gf-form-btn" ng-click="ctrl.addQuery()" ng-hide="ctrl.current.meta.mixed">
-        Add Query
-      </button>
+			<button class="btn btn-secondary gf-form-btn" ng-click="ctrl.addQuery()" ng-hide="ctrl.current.meta.mixed">
+				Add Query
+			</button>
 
-      <div class="dropdown" ng-if="ctrl.current.meta.mixed">
-        <gf-form-dropdown model="ctrl.addQueryDropdown"
-                          get-options="ctrl.getOptions(false)"
-                          on-change="ctrl.addMixedQuery($option)">
-        </gf-form-dropdown>
-      </div>
-    </div>
-  </div>
+			<div class="dropdown" ng-if="ctrl.current.meta.mixed">
+				<gf-form-dropdown model="ctrl.addQueryDropdown"
+													get-options="ctrl.getOptions(false)"
+													on-change="ctrl.addMixedQuery($option)">
+				</gf-form-dropdown>
+			</div>
+		</div>
+	</div>
 </div>

+ 4 - 5
public/app/features/plugins/plugin_edit_ctrl.ts

@@ -3,6 +3,7 @@
 import angular from 'angular';
 import _ from 'lodash';
 import appEvents from 'app/core/app_events';
+import Remarkable from 'remarkable';
 
 export class PluginEditCtrl {
   model: any;
@@ -67,11 +68,9 @@ export class PluginEditCtrl {
   }
 
   initReadme() {
-    return this.backendSrv.get(`/api/plugins/${this.pluginId}/readme`).then(res => {
-      return System.import('remarkable').then(Remarkable => {
-        var md = new Remarkable();
-        this.readmeHtml = this.$sce.trustAsHtml(md.render(res));
-      });
+    return this.backendSrv.get(`/api/plugins/${this.pluginId}/markdown/readme`).then(res => {
+      var md = new Remarkable();
+      this.readmeHtml = this.$sce.trustAsHtml(md.render(res));
     });
   }
 

+ 13 - 0
public/app/plugins/datasource/graphite/datasource.ts

@@ -16,6 +16,19 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv
   this.withCredentials = instanceSettings.withCredentials;
   this.render_method = instanceSettings.render_method || 'POST';
 
+  this.getQueryOptionsInfo = function() {
+    return {
+      "maxDataPoints": true,
+      "cacheTimeout": true,
+      "links": [
+        {
+          text: "Help",
+          url: "http://docs.grafana.org/features/datasources/graphite/#using-graphite-in-grafana"
+        }
+      ]
+    };
+  };
+
   this.query = function(options) {
     var graphOptions = {
       from: this.translateTime(options.rangeRaw.from, false),

+ 32 - 0
public/app/plugins/datasource/graphite/help.md

@@ -0,0 +1,32 @@
+#### Get Shorter legend names
+
+- alias() function to specify a custom series name<
+- aliasByNode(2) to alias by a specific part of your metric path
+- aliasByNode(2, -1) you can add multiple segment paths, and use negative index
+- groupByNode(2, 'sum') is useful if you have 2 wildcards in your metric path and want to sumSeries and group by
+
+#### Series as parameter
+
+- Some graphite functions allow you to have many series arguments
+- Use #[A-Z] to use a graphite query as parameter to a function
+- Examples:
+  - asPercent(#A, #B)
+  - prod.srv-01.counters.count - asPercent(#A) : percentage of count in comparison with A query
+  - prod.srv-01.counters.count - sumSeries(#A) : sum count and series A
+  - divideSeries(#A, #B)
+
+If a query is added only to be used as a parameter, hide it from the graph with the eye icon
+
+#### Max data points
+- Every graphite request is issued with a maxDataPoints parameter
+- Graphite uses this parameter to consolidate the real number of values down to this number
+- If there are more real values, then by default they will be consolidated using averages
+- This could hide real peaks and max values in your series
+- You can change how point consolidation is made using the consolidateBy graphite function
+- Point consolidation will effect series legend values (min,max,total,current)
+- if you override maxDataPoint and set a high value performance can be severely effected
+
+#### Documentation links:
+
+- [Grafana's Graphite Documentation](http://docs.grafana.org/features/datasources/graphite)
+- [Official Graphite Documentation](https://graphite.readthedocs.io)

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

@@ -10,6 +10,8 @@
   "metrics": true,
   "alerting": true,
   "annotations": true,
+  "maxDataPoints": true,
+  "cacheTimeout": true,
 
   "info": {
     "author": {

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

@@ -7,6 +7,7 @@
   "metrics": true,
   "annotations": true,
   "alerting": true,
+  "minInterval": true,
 
   "info": {
     "author": {

+ 1 - 1
public/sass/_variables.dark.scss

@@ -276,7 +276,7 @@ $card-background-hover: linear-gradient(135deg, #343434, #262626);
 $card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .3);
 
 // info box
-$info-box-background: linear-gradient(120deg, #142749, #0e203e);
+$info-box-background: linear-gradient(177deg, #006e95, #412078);
 
 // footer
 $footer-link-color:   $gray-1;

+ 1 - 1
public/sass/components/_gf-form.scss

@@ -277,7 +277,7 @@ $gf-form-margin: 0.25rem;
   &--right-absolute {
     position: absolute;
     right: $spacer;
-    top: 8px;
+    top: 10px;
   }
 
   &--right-normal {

+ 28 - 9
public/sass/components/_infobox.scss

@@ -1,12 +1,12 @@
-.grafana-info-box::before {
-  content: "\f05a";
-  font-family:'FontAwesome';
-  position: absolute;
-  top: -13px;
-  left: -8px;
-  font-size: 20px;
-  color: $text-color;
-}
+// .grafana-info-box::before {
+//   content: "\f05a";
+//   font-family:'FontAwesome';
+//   position: absolute;
+//   top: -13px;
+//   left: -8px;
+//   font-size: 20px;
+//   color: $text-color;
+// }
 
 .grafana-info-box {
   position: relative;
@@ -15,6 +15,7 @@
   padding: 1rem;
   border-radius: 4px;
   margin-bottom: $spacer;
+  margin-right: $gf-form-margin;
 
   h5 {
     margin-bottom: $spacer;
@@ -26,5 +27,23 @@
   a {
     @extend .external-link;
   }
+
+  &--animate {
+    max-height: 0;
+    overflow: hidden;
+  }
+
+  &--animate-open {
+    max-height: 1000px;
+    transition: max-height 250ms ease-in-out;
+  }
 }
 
+.grafana-info-box__close {
+  text-align: center;
+  display: block;
+  color: $link-color !important;
+  height: 0;
+  position: relative;
+  top: -9px;
+}