diff --git a/pkg/api/api.go b/pkg/api/api.go index d4d23b17ef4..4024655775e 100644 --- a/pkg/api/api.go +++ b/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)) diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index fe7122f8557..042c03f9832 100644 --- a/pkg/api/plugins.go +++ b/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) } diff --git a/pkg/plugins/datasource_plugin.go b/pkg/plugins/datasource_plugin.go index 8cb0748936b..e46c2709649 100644 --- a/pkg/plugins/datasource_plugin.go +++ b/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 } diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index fd04852f2f4..541b37c8a8a 100644 --- a/pkg/plugins/models.go +++ b/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 { diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index b6c3639cbbf..885bd5c9e03 100644 --- a/pkg/plugins/plugins.go +++ b/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 } } diff --git a/public/app/features/panel/metrics_tab.ts b/public/app/features/panel/metrics_tab.ts index 82fa3be99e7..25957ac35ae 100644 --- a/public/app/features/panel/metrics_tab.ts +++ b/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; } diff --git a/public/app/features/panel/partials/metrics_tab.html b/public/app/features/panel/partials/metrics_tab.html index 8ceee90e7ff..53033d0e241 100644 --- a/public/app/features/panel/partials/metrics_tab.html +++ b/public/app/features/panel/partials/metrics_tab.html @@ -11,41 +11,90 @@ on-change="ctrl.datasourceChanged($option)"> -
- - +
+ + A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example 1m if your data is written every minute. Access auto interval via variable $__interval for time range string and $__interval_ms for numeric variable that can be used in math expressions. -
+
+
+ + + + If your time series store has a query cache this option can override the default + cache timeout. Specify a numeric value in seconds. + +
+
+ + + + The maximum data points the query should return. For graphs this + is automatically set to one data point per pixel. + +
+
-
- +
+
- -
-
+ + + +
+
+ + + +
+
-
- - - - +
+ + + +
@@ -56,16 +105,16 @@ {{ctrl.panelCtrl.nextRefId}} - + - -
-
+ +
+ diff --git a/public/app/features/plugins/plugin_edit_ctrl.ts b/public/app/features/plugins/plugin_edit_ctrl.ts index 4910ee95490..50a9138e7f8 100644 --- a/public/app/features/plugins/plugin_edit_ctrl.ts +++ b/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)); }); } diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index 59ba485814b..61d97b38650 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/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), diff --git a/public/app/plugins/datasource/graphite/help.md b/public/app/plugins/datasource/graphite/help.md new file mode 100644 index 00000000000..c57d89985d3 --- /dev/null +++ b/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) diff --git a/public/app/plugins/datasource/graphite/plugin.json b/public/app/plugins/datasource/graphite/plugin.json index 85009cfd294..6802366691e 100644 --- a/public/app/plugins/datasource/graphite/plugin.json +++ b/public/app/plugins/datasource/graphite/plugin.json @@ -10,6 +10,8 @@ "metrics": true, "alerting": true, "annotations": true, + "maxDataPoints": true, + "cacheTimeout": true, "info": { "author": { diff --git a/public/app/plugins/datasource/influxdb/plugin.json b/public/app/plugins/datasource/influxdb/plugin.json index 56587f91c78..097b6594536 100644 --- a/public/app/plugins/datasource/influxdb/plugin.json +++ b/public/app/plugins/datasource/influxdb/plugin.json @@ -7,6 +7,7 @@ "metrics": true, "annotations": true, "alerting": true, + "minInterval": true, "info": { "author": { diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 9ce6b2fd93f..131e52f3f02 100644 --- a/public/sass/_variables.dark.scss +++ b/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; diff --git a/public/sass/components/_gf-form.scss b/public/sass/components/_gf-form.scss index a330cc779a0..a15f91e9ed3 100644 --- a/public/sass/components/_gf-form.scss +++ b/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 { diff --git a/public/sass/components/_infobox.scss b/public/sass/components/_infobox.scss index cb80187f655..a7d762c602d 100644 --- a/public/sass/components/_infobox.scss +++ b/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; +}