Merge pull request #12415 from grafana/cp-5.2.0
Cherry-picks for v5.2.0 stable
This commit is contained in:
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"stable": "5.1.3",
|
||||
"testing": "5.1.3"
|
||||
"stable": "5.2.0",
|
||||
"testing": "5.2.0"
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"company": "Grafana Labs"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "5.2.0-beta3",
|
||||
"version": "5.2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/grafana/grafana.git"
|
||||
|
||||
@@ -3,7 +3,9 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
@@ -55,6 +57,15 @@ func (hs *HTTPServer) RenderToPng(c *m.ReqContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil && err == rendering.ErrPhantomJSNotInstalled {
|
||||
if strings.HasPrefix(runtime.GOARCH, "arm") {
|
||||
c.Handle(500, "Rendering failed - PhantomJS isn't included in arm build per default", err)
|
||||
} else {
|
||||
c.Handle(500, "Rendering failed - PhantomJS isn't installed correctly", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.Handle(500, "Rendering failed.", err)
|
||||
return
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
var ErrTimeout = errors.New("Timeout error. You can set timeout in seconds with &timeout url parameter")
|
||||
var ErrNoRenderer = errors.New("No renderer plugin found nor is an external render server configured")
|
||||
var ErrPhantomJSNotInstalled = errors.New("PhantomJS executable not found")
|
||||
|
||||
type Opts struct {
|
||||
Width int
|
||||
|
||||
@@ -24,6 +24,11 @@ func (rs *RenderingService) renderViaPhantomJS(ctx context.Context, opts Opts) (
|
||||
|
||||
url := rs.getURL(opts.Path)
|
||||
binPath, _ := filepath.Abs(filepath.Join(rs.Cfg.PhantomDir, executable))
|
||||
if _, err := os.Stat(binPath); os.IsNotExist(err) {
|
||||
rs.log.Error("executable not found", "executable", binPath)
|
||||
return nil, ErrPhantomJSNotInstalled
|
||||
}
|
||||
|
||||
scriptPath, _ := filepath.Abs(filepath.Join(rs.Cfg.PhantomDir, "render.js"))
|
||||
pngPath := rs.getFilePathForNewImage()
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export class DatasourceSrv {
|
||||
datasources: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $q, private $injector, $rootScope, private templateSrv) {
|
||||
constructor(private $q, private $injector, private $rootScope, private templateSrv) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export class DatasourceSrv {
|
||||
this.datasources[name] = instance;
|
||||
deferred.resolve(instance);
|
||||
})
|
||||
.catch(function(err) {
|
||||
.catch(err => {
|
||||
this.$rootScope.appEvent('alert-error', [dsConfig.name + ' plugin failed', err.toString()]);
|
||||
});
|
||||
|
||||
|
||||
@@ -179,4 +179,38 @@ describe('VariableSrv init', function() {
|
||||
expect(variable.options[2].selected).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describeInitScenario('when template variable is present in url multiple times using key/values', scenario => {
|
||||
scenario.setup(() => {
|
||||
scenario.variables = [
|
||||
{
|
||||
name: 'apps',
|
||||
type: 'query',
|
||||
multi: true,
|
||||
current: { text: 'Val1', value: 'val1' },
|
||||
options: [
|
||||
{ text: 'Val1', value: 'val1' },
|
||||
{ text: 'Val2', value: 'val2' },
|
||||
{ text: 'Val3', value: 'val3', selected: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
scenario.urlParams['var-apps'] = ['val2', 'val1'];
|
||||
});
|
||||
|
||||
it('should update current value', function() {
|
||||
var variable = ctx.variableSrv.variables[0];
|
||||
expect(variable.current.value.length).to.be(2);
|
||||
expect(variable.current.value[0]).to.be('val2');
|
||||
expect(variable.current.value[1]).to.be('val1');
|
||||
expect(variable.current.text).to.be('Val2 + Val1');
|
||||
expect(variable.options[0].selected).to.be(true);
|
||||
expect(variable.options[1].selected).to.be(true);
|
||||
});
|
||||
|
||||
it('should set options that are not in value to selected false', function() {
|
||||
var variable = ctx.variableSrv.variables[0];
|
||||
expect(variable.options[2].selected).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -209,7 +209,24 @@ export class VariableSrv {
|
||||
return op.text === urlValue || op.value === urlValue;
|
||||
});
|
||||
|
||||
option = option || { text: urlValue, value: urlValue };
|
||||
let defaultText = urlValue;
|
||||
let defaultValue = urlValue;
|
||||
|
||||
if (!option && _.isArray(urlValue)) {
|
||||
defaultText = [];
|
||||
|
||||
for (let n = 0; n < urlValue.length; n++) {
|
||||
let t = _.find(variable.options, op => {
|
||||
return op.value === urlValue[n];
|
||||
});
|
||||
|
||||
if (t) {
|
||||
defaultText.push(t.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
option = option || { text: defaultText, value: defaultValue };
|
||||
return variable.setValue(option);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,14 +11,30 @@ export default class ResponseParser {
|
||||
return [];
|
||||
}
|
||||
|
||||
var influxdb11format = query.toLowerCase().indexOf('show tag values') >= 0;
|
||||
var normalizedQuery = query.toLowerCase();
|
||||
var isValueFirst =
|
||||
normalizedQuery.indexOf('show field keys') >= 0 || normalizedQuery.indexOf('show retention policies') >= 0;
|
||||
|
||||
var res = {};
|
||||
_.each(influxResults.series, serie => {
|
||||
_.each(serie.values, value => {
|
||||
if (_.isArray(value)) {
|
||||
if (influxdb11format) {
|
||||
addUnique(res, value[1] || value[0]);
|
||||
// In general, there are 2 possible shapes for the returned value.
|
||||
// The first one is a two-element array,
|
||||
// where the first element is somewhat a metadata value:
|
||||
// the tag name for SHOW TAG VALUES queries,
|
||||
// the time field for SELECT queries, etc.
|
||||
// The second shape is an one-element array,
|
||||
// that is containing an immediate value.
|
||||
// For example, SHOW FIELD KEYS queries return such shape.
|
||||
// Note, pre-0.11 versions return
|
||||
// the second shape for SHOW TAG VALUES queries
|
||||
// (while the newer versions—first).
|
||||
|
||||
if (isValueFirst) {
|
||||
addUnique(res, value[0]);
|
||||
} else if (value[1] !== undefined) {
|
||||
addUnique(res, value[1]);
|
||||
} else {
|
||||
addUnique(res, value[0]);
|
||||
}
|
||||
@@ -29,7 +45,7 @@ export default class ResponseParser {
|
||||
});
|
||||
|
||||
return _.map(res, value => {
|
||||
return { text: value };
|
||||
return { text: value.toString() };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,30 +85,36 @@ describe('influxdb response parser', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('SELECT response', () => {
|
||||
var query = 'SELECT "usage_iowait" FROM "cpu" LIMIT 10';
|
||||
var response = {
|
||||
results: [
|
||||
{
|
||||
series: [
|
||||
{
|
||||
name: 'cpu',
|
||||
columns: ['time', 'usage_iowait'],
|
||||
values: [[1488465190006040638, 0.0], [1488465190006040638, 15.0], [1488465190006040638, 20.2]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
var result = parser.parse(query, response);
|
||||
|
||||
it('should return second column', () => {
|
||||
expect(_.size(result)).toBe(3);
|
||||
expect(result[0].text).toBe('0');
|
||||
expect(result[1].text).toBe('15');
|
||||
expect(result[2].text).toBe('20.2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SHOW FIELD response', () => {
|
||||
var query = 'SHOW FIELD KEYS FROM "cpu"';
|
||||
describe('response from 0.10.0', () => {
|
||||
var response = {
|
||||
results: [
|
||||
{
|
||||
series: [
|
||||
{
|
||||
name: 'measurements',
|
||||
columns: ['name'],
|
||||
values: [['cpu'], ['derivative'], ['logins.count'], ['logs'], ['payment.ended'], ['payment.started']],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
var result = parser.parse(query, response);
|
||||
it('should get two responses', () => {
|
||||
expect(_.size(result)).toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('response from 0.11.0', () => {
|
||||
describe('response from pre-1.0', () => {
|
||||
var response = {
|
||||
results: [
|
||||
{
|
||||
@@ -129,5 +135,28 @@ describe('influxdb response parser', () => {
|
||||
expect(_.size(result)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('response from 1.0', () => {
|
||||
var response = {
|
||||
results: [
|
||||
{
|
||||
series: [
|
||||
{
|
||||
name: 'cpu',
|
||||
columns: ['fieldKey', 'fieldType'],
|
||||
values: [['time', 'float']],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
var result = parser.parse(query, response);
|
||||
|
||||
it('should return first column', () => {
|
||||
expect(_.size(result)).toBe(1);
|
||||
expect(result[0].text).toBe('time');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
display: inline-block;
|
||||
padding-right: 2px;
|
||||
&::after {
|
||||
content: " | ";
|
||||
content: ' | ';
|
||||
padding-left: 2px;
|
||||
}
|
||||
}
|
||||
@@ -33,14 +33,21 @@
|
||||
li:last-child {
|
||||
&::after {
|
||||
padding-left: 0;
|
||||
content: "";
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-page {
|
||||
.footer {
|
||||
padding: 1rem 0 1rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.footer {
|
||||
bottom: $spacer;
|
||||
position: absolute;
|
||||
bottom: $spacer;
|
||||
padding: 5rem 0 1rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
$login-border: #8daac5;
|
||||
|
||||
.login {
|
||||
background-position: center;
|
||||
min-height: 85vh;
|
||||
height: 80vh;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
min-width: 100%;
|
||||
margin-left: 0;
|
||||
@@ -95,7 +94,7 @@ select:-webkit-autofill:focus {
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
height: 320px;
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
.login-branding {
|
||||
@@ -106,6 +105,7 @@ select:-webkit-autofill:focus {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-grow: 0;
|
||||
padding-top: 2rem;
|
||||
|
||||
.logo-icon {
|
||||
width: 70px;
|
||||
@@ -127,7 +127,7 @@ select:-webkit-autofill:focus {
|
||||
|
||||
.login-inner-box {
|
||||
text-align: center;
|
||||
padding: 2rem 4rem;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -243,7 +243,7 @@ select:-webkit-autofill:focus {
|
||||
justify-content: space-between;
|
||||
|
||||
.login-divider-line {
|
||||
width: 110px;
|
||||
width: 100px;
|
||||
height: 10px;
|
||||
border-bottom: 1px solid $login-border;
|
||||
|
||||
@@ -323,7 +323,10 @@ select:-webkit-autofill:focus {
|
||||
width: 35%;
|
||||
padding: 4rem 2rem;
|
||||
border-right: 1px solid $login-border;
|
||||
justify-content: flex-start;
|
||||
|
||||
.logo-icon {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-inner-box {
|
||||
@@ -331,14 +334,18 @@ select:-webkit-autofill:focus {
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
|
||||
.login-branding {
|
||||
.logo-icon {
|
||||
width: 80px;
|
||||
.login-divider {
|
||||
.login-divider-line {
|
||||
width: 110px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.login {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.login-content {
|
||||
flex: 1 0 100%;
|
||||
}
|
||||
@@ -373,10 +380,6 @@ select:-webkit-autofill:focus {
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
.login {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.login-form-input {
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user