Merge branch 'master' into alerting_definitions

This commit is contained in:
Torkel Ödegaard
2016-05-16 14:12:56 +02:00
20 changed files with 220 additions and 62 deletions

3
.floo Normal file
View File

@@ -0,0 +1,3 @@
{
"url": "https://floobits.com/raintank/grafana"
}

13
.flooignore Normal file
View File

@@ -0,0 +1,13 @@
#*
*.o
*.pyc
*.pyo
*~
extern/
node_modules/
tmp/
data/
vendor/
public_gen/
dist/

View File

@@ -1,4 +1,11 @@
# 3.0.2 Stable (unreleased)
# 3.1.0
### Enhancements
* **Singlestat**: Add support for range to text mappings, closes [#1319](https://github.com/grafana/grafana/issues/1319)
* **Graph**: Adds sort order options for graph tooltip, closes [#1189](https://github.com/grafana/grafana/issues/1189)
* **Theme**: Add default theme to config file [#5011](https://github.com/grafana/grafana/pull/5011)
# 3.0.2 Stable (2016-05-16)
* **Templating**: Fixed issue mixing row repeat and panel repeats, fixes [#4988](https://github.com/grafana/grafana/issues/4988)
* **Templating**: Fixed issue detecting dependencies in nested variables, fixes [#4987](https://github.com/grafana/grafana/issues/4987), fixes [#4986](https://github.com/grafana/grafana/issues/4986)
@@ -9,6 +16,7 @@
# 3.0.1 Stable (2016-05-11)
### Bug fixes
* **Templating**: Fixed issue with new data source variable not persisting current selected value, fixes [#4934](https://github.com/grafana/grafana/issues/4934)
# 3.0.0-beta7 (2016-05-02)

View File

@@ -172,6 +172,9 @@ verify_email_enabled = false
# Background text for the user field on the login page
login_hint = email or username
# Default UI theme ("dark" or "light")
default_theme = dark
#################################### Anonymous Auth ##########################
[auth.anonymous]
# enable anonymous access

View File

@@ -155,6 +155,9 @@ check_for_updates = true
# Background text for the user field on the login page
;login_hint = email or username
# Default UI theme ("dark" or "light")
;default_theme = dark
#################################### Anonymous Auth ##########################
[auth.anonymous]
# enable anonymous access

View File

@@ -8,3 +8,10 @@ graphite:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
fake-data-gen:
image: grafana/fake-data-gen
net: bridge
environment:
FD_DATASOURCE: graphite
FD_PORT: 2003

View File

@@ -2,4 +2,10 @@ opentsdb:
image: opower/opentsdb:latest
ports:
- "4242:4242"
fake-data-gen:
image: grafana/fake-data-gen
net: bridge
environment:
FD_DATASOURCE: opentsdb

View File

@@ -10,13 +10,13 @@ page_keywords: grafana, installation, debian, ubuntu, guide
Description | Download
------------ | -------------
Stable .deb for Debian-based Linux | [grafana_3.0.1_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.1_amd64.deb)
Stable .deb for Debian-based Linux | [grafana_3.0.2-1463383025_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.2-1463383025_amd64.deb)
## Install Stable
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.1_amd64.deb
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.2-1463383025_amd64.deb
$ sudo apt-get install -y adduser libfontconfig
$ sudo dpkg -i grafana_3.0.1_amd64.deb
$ sudo dpkg -i grafana_3.0.2-1463383025_amd64.deb
## APT Repository

View File

@@ -10,24 +10,24 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
Description | Download
------------ | -------------
Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-3.0.1-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.1-1.x86_64.rpm)
Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-3.0.2-1463383025.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.2-1463383025.x86_64.rpm)
## Install Stable Release from package file
You can install Grafana using Yum directly.
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.1-1.x86_64.rpm
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.2-1463383025.x86_64.rpm
Or install manually using `rpm`.
#### On CentOS / Fedora / Redhat:
$ sudo yum install initscripts fontconfig
$ sudo rpm -Uvh grafana-3.0.1-1.x86_64.rpm
$ sudo rpm -Uvh grafana-3.0.2-1463383025.x86_64.rpm
#### On OpenSuse:
$ sudo rpm -i --nodeps grafana-3.0.1-1.x86_64.rpm
$ sudo rpm -i --nodeps grafana-3.0.2-1463383025.x86_64.rpm
## Install via YUM Repository

View File

@@ -10,7 +10,7 @@ page_keywords: grafana, installation, windows guide
Description | Download
------------ | -------------
Stable Zip package for Windows | [grafana.2.6.0.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.5.0.windows-x64.zip)
Stable Zip package for Windows | [grafana.3.0.2.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-3.0.2.windows-x64.zip)
## Configure

View File

@@ -4,7 +4,7 @@
"company": "Coding Instinct AB"
},
"name": "grafana",
"version": "3.0.2",
"version": "3.1.0",
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git"

View File

@@ -5,6 +5,8 @@ import (
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
func init() {
@@ -26,7 +28,7 @@ func GetPreferencesWithDefaults(query *m.GetPreferencesWithDefaultsQuery) error
}
res := &m.Preferences{
Theme: "dark",
Theme: setting.DefaultTheme,
Timezone: "browser",
HomeDashboardId: 0,
}

View File

@@ -88,6 +88,7 @@ var (
AutoAssignOrgRole string
VerifyEmailEnabled bool
LoginHint string
DefaultTheme string
// Http auth
AdminUser string
@@ -457,6 +458,7 @@ func NewConfigContext(args *CommandLineArgs) error {
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"})
VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
LoginHint = users.Key("login_hint").String()
DefaultTheme = users.Key("default_theme").String()
// anonymous access
AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)

View File

@@ -81,9 +81,9 @@ function ($) {
// Stacked series can increase its length on each new stacked serie if null points found,
// to speed the index search we begin always on the last found hoverIndex.
var newhoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex);
results.push({ value: value, hoverIndex: newhoverIndex });
results.push({ value: value, hoverIndex: newhoverIndex, color: series.color, label: series.label });
} else {
results.push({ value: value, hoverIndex: hoverIndex });
results.push({ value: value, hoverIndex: hoverIndex, color: series.color, label: series.label });
}
}
@@ -133,6 +133,18 @@ function ($) {
absoluteTime = dashboard.formatDate(seriesHoverInfo.time, tooltipFormat);
// Dynamically reorder the hovercard for the current time point if the
// option is enabled.
if (panel.tooltip.ordering === 'decreasing') {
seriesHoverInfo.sort(function(a, b) {
return parseFloat(b.value) - parseFloat(a.value);
});
} else if (panel.tooltip.ordering === 'increasing') {
seriesHoverInfo.sort(function(a, b) {
return parseFloat(a.value) - parseFloat(b.value);
});
}
for (i = 0; i < seriesHoverInfo.length; i++) {
hoverInfo = seriesHoverInfo[i];
@@ -150,7 +162,7 @@ function ($) {
value = series.formatValue(hoverInfo.value);
seriesHtml += '<div class="graph-tooltip-list-item ' + highlightClass + '"><div class="graph-tooltip-series-name">';
seriesHtml += '<i class="fa fa-minus" style="color:' + series.color +';"></i> ' + series.label + ':</div>';
seriesHtml += '<i class="fa fa-minus" style="color:' + hoverInfo.color +';"></i> ' + hoverInfo.label + ':</div>';
seriesHtml += '<div class="graph-tooltip-value">' + value + '</div></div>';
plot.highlight(i, hoverInfo.hoverIndex);
}

View File

@@ -94,6 +94,7 @@ class GraphCtrl extends MetricsPanelCtrl {
tooltip : {
value_type: 'cumulative',
shared: true,
ordering: 'alphabetical',
msResolution: false,
},
// time overrides

View File

@@ -42,23 +42,29 @@
<div class="section gf-form-group">
<h5 class="section-heading">Misc options</h5>
<div class="gf-form">
<label class="gf-form-label width-7">Null value</label>
<label class="gf-form-label width-9">Null value</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input max-width-8" ng-model="ctrl.panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="ctrl.render()"></select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-7">Renderer</label>
<label class="gf-form-label width-9">Renderer</label>
<div class="gf-form-select-wrapper max-width-8">
<select class="gf-form-input" ng-model="ctrl.panel.renderer" ng-options="f for f in ['flot', 'png']" ng-change="ctrl.render()"></select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-7">Tooltip mode</label>
<label class="gf-form-label width-9">Tooltip mode</label>
<div class="gf-form-select-wrapper max-width-8">
<select class="gf-form-input" ng-model="ctrl.panel.tooltip.shared" ng-options="f.value as f.text for f in [{text: 'All series', value: true}, {text: 'Single', value: false}]" ng-change="ctrl.render()"></select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-9">Tooltip ordering<tip>The ordering from top to bottom</tip></label>
<div class="gf-form-select-wrapper max-width-8">
<select class="gf-form-input" ng-model="ctrl.panel.tooltip.ordering" ng-options="f.value as f.text for f in [{text: 'Alphabetical', value: 'alphabetical'}, {text: 'Increasing', value: 'increasing'}, {text: 'Decreasing', value: 'decreasing'}]" ng-change="ctrl.render()"></select>
</div>
</div>
</div>
<div class="section gf-form-group">

View File

@@ -204,35 +204,3 @@
</div>
</div>
</div>
<div class="editor-row">
<div class="section" style="margin-bottom: 20px">
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item">
<strong>Value to text mapping</strong>
</li>
<li class="tight-form-item" ng-repeat-start="map in ctrl.panel.valueMaps">
<i class="fa fa-remove pointer" ng-click="ctrl.removeValueMap(map)"></i>
</li>
<li>
<input type="text" ng-model="map.value" placeholder="value" class="input-mini tight-form-input" ng-blur="ctrl.render()">
</li>
<li class="tight-form-item">
<i class="fa fa-arrow-right"></i>
</li>
<li ng-repeat-end>
<input type="text" placeholder="text" ng-model="map.text" class="input-mini tight-form-input" ng-blur="ctrl.render()">
</li>
<li>
<a class="pointer tight-form-item last" ng-click="ctrl.addValueMap();">
<i class="fa fa-plus"></i>
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>

View File

@@ -0,0 +1,58 @@
<div class="editor-row">
<div class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label">
Type
</span>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="ctrl.panel.mappingType"
ng-options="f.value as f.name for f in ctrl.panel.mappingTypes" ng-change="ctrl.render()"></select>
</div>
</div>
</div>
</div>
<div class="editor-row" ng-if="ctrl.panel.mappingType==1">
<h5 class="page-heading">Set valuea mappings</h5>
<div class="gf-form-group">
<div class="gf-form" ng-repeat="map in ctrl.panel.valueMaps">
<span class="gf-form-label">
<i class="fa fa-remove pointer" ng-click="ctrl.removeValueMap(map)"></i>
</span>
<input type="text" ng-model="map.value" placeholder="value" class="gf-form-input max-width-6" ng-blur="ctrl.render()">
<span class="gf-form-label">
<i class="fa fa-arrow-right"></i>
</span>
<input type="text" placeholder="text" ng-model="map.text" class="gf-form-input max-width-8" ng-blur="ctrl.render()">
</div>
<div class="gf-form-button-row">
<button class="btn btn-inverse" ng-click="ctrl.addValueMap();">
<i class="fa fa-plus"></i>
Add a value mapping
</button>
</div>
</div>
</div>
<div class="editor-row" ng-if="ctrl.panel.mappingType==2">
<h5 class="page-heading">Set range mappings</h5>
<div class="gf-form-group">
<div class="gf-form" ng-repeat="rangeMap in ctrl.panel.rangeMaps">
<span class="gf-form-label">
<i class="fa fa-remove pointer" ng-click="ctrl.removeRangeMap(rangeMap)"></i>
</span>
<span class="gf-form-label">From</span>
<input type="text" ng-model="rangeMap.from" class="gf-form-input max-width-6" ng-blur="ctrl.render()">
<span class="gf-form-label">To</span>
<input type="text" ng-model="rangeMap.to" class="gf-form-input max-width-6" ng-blur="ctrl.render()">
<span class="gf-form-label">Text</span>
<input type="text" ng-model="rangeMap.text" class="gf-form-input max-width-8" ng-blur="ctrl.render()">
</div>
<div class="gf-form-button-row">
<button class="btn btn-inverse" ng-click="ctrl.addRangeMap()">
<i class="fa fa-plus"></i>
Add a range mapping
</button>
</div>
</div>
</div>

View File

@@ -35,6 +35,14 @@ class SingleStatCtrl extends MetricsPanelCtrl {
valueMaps: [
{ value: 'null', op: '=', text: 'N/A' }
],
mappingTypes: [
{name: 'value to text', value: 1},
{name: 'range to text', value: 2},
],
rangeMaps: [
{ from: 'null', to: 'null', text: 'N/A' }
],
mappingType: 1,
nullPointMode: 'connected',
valueName: 'avg',
prefixFontSize: '50%',
@@ -73,6 +81,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
onInitEditMode() {
this.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
this.addEditorTab('Options', 'public/app/plugins/panel/singlestat/editor.html', 2);
this.addEditorTab('Value Mappings', 'public/app/plugins/panel/singlestat/mappings.html', 3);
this.unitFormats = kbn.getUnitFormats();
}
@@ -192,23 +201,45 @@ class SingleStatCtrl extends MetricsPanelCtrl {
}
}
// check value to text mappings
for (var i = 0; i < this.panel.valueMaps.length; i++) {
var map = this.panel.valueMaps[i];
// special null case
if (map.value === 'null') {
if (data.value === null || data.value === void 0) {
// check value to text mappings if its enabled
if (this.panel.mappingType === 1) {
for (var i = 0; i < this.panel.valueMaps.length; i++) {
var map = this.panel.valueMaps[i];
// special null case
if (map.value === 'null') {
if (data.value === null || data.value === void 0) {
data.valueFormated = map.text;
return;
}
continue;
}
// value/number to text mapping
var value = parseFloat(map.value);
if (value === data.valueRounded) {
data.valueFormated = map.text;
return;
}
continue;
}
} else if (this.panel.mappingType === 2) {
for (var i = 0; i < this.panel.rangeMaps.length; i++) {
var map = this.panel.rangeMaps[i];
// special null case
if (map.from === 'null' && map.to === 'null') {
if (data.value === null || data.value === void 0) {
data.valueFormated = map.text;
return;
}
continue;
}
// value/number to text mapping
var value = parseFloat(map.value);
if (value === data.valueRounded) {
data.valueFormated = map.text;
return;
// value/number to range mapping
var from = parseFloat(map.from);
var to = parseFloat(map.to);
if (to >= data.valueRounded && from <= data.valueRounded) {
data.valueFormated = map.text;
return;
}
}
}
@@ -227,6 +258,16 @@ class SingleStatCtrl extends MetricsPanelCtrl {
this.panel.valueMaps.push({value: '', op: '=', text: '' });
}
removeRangeMap(rangeMap) {
var index = _.indexOf(this.panel.rangeMaps, rangeMap);
this.panel.rangeMaps.splice(index, 1);
this.render();
};
addRangeMap() {
this.panel.rangeMaps.push({from: '', to: '', text: ''});
}
link(scope, elem, attrs, ctrl) {
var $location = this.$location;
var linkSrv = this.linkSrv;

View File

@@ -84,4 +84,29 @@ describe('SingleStatCtrl', function() {
expect(ctx.data.valueFormated).to.be('OK');
});
});
singleStatScenario('When range to text mapping is specifiedfor first range', function(ctx) {
ctx.setup(function() {
ctx.datapoints = [[41,50]];
ctx.ctrl.panel.mappingType = 2;
ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}];
});
it('Should replace value with text OK', function() {
expect(ctx.data.valueFormated).to.be('OK');
});
});
singleStatScenario('When range to text mapping is specified for other ranges', function(ctx) {
ctx.setup(function() {
ctx.datapoints = [[65,75]];
ctx.ctrl.panel.mappingType = 2;
ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}];
});
it('Should replace value with text NOT OK', function() {
expect(ctx.data.valueFormated).to.be('NOT OK');
});
});
});