diff --git a/latest.json b/latest.json index d8804f98441..8e26289c856 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "5.1.3", - "testing": "5.1.3" + "stable": "5.2.0", + "testing": "5.2.0" } diff --git a/package.json b/package.json index d41b6e1683e..e1af67f5883 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/pkg/api/render.go b/pkg/api/render.go index f6f149980d6..b8ef6cc5cb6 100644 --- a/pkg/api/render.go +++ b/pkg/api/render.go @@ -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 diff --git a/pkg/services/rendering/interface.go b/pkg/services/rendering/interface.go index 9498071f264..85c139cfc04 100644 --- a/pkg/services/rendering/interface.go +++ b/pkg/services/rendering/interface.go @@ -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 diff --git a/pkg/services/rendering/phantomjs.go b/pkg/services/rendering/phantomjs.go index d4ceac0ed43..8e06b5fed9d 100644 --- a/pkg/services/rendering/phantomjs.go +++ b/pkg/services/rendering/phantomjs.go @@ -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() diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index aef43a4760b..19bdf599ce1 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -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()]); }); diff --git a/public/app/features/templating/specs/variable_srv_init_specs.ts b/public/app/features/templating/specs/variable_srv_init_specs.ts index cb98d1d7736..11639c6aa8f 100644 --- a/public/app/features/templating/specs/variable_srv_init_specs.ts +++ b/public/app/features/templating/specs/variable_srv_init_specs.ts @@ -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); + }); + }); }); diff --git a/public/app/features/templating/variable_srv.ts b/public/app/features/templating/variable_srv.ts index fb882516e85..8a096dd9ad2 100644 --- a/public/app/features/templating/variable_srv.ts +++ b/public/app/features/templating/variable_srv.ts @@ -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); }); } diff --git a/public/app/plugins/datasource/influxdb/response_parser.ts b/public/app/plugins/datasource/influxdb/response_parser.ts index 78ce67e7a37..f1a46001d95 100644 --- a/public/app/plugins/datasource/influxdb/response_parser.ts +++ b/public/app/plugins/datasource/influxdb/response_parser.ts @@ -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() }; }); } } diff --git a/public/app/plugins/datasource/influxdb/specs/response_parser.jest.ts b/public/app/plugins/datasource/influxdb/specs/response_parser.jest.ts index 8ddc0fcdaf1..525508b2c1d 100644 --- a/public/app/plugins/datasource/influxdb/specs/response_parser.jest.ts +++ b/public/app/plugins/datasource/influxdb/specs/response_parser.jest.ts @@ -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'); + }); + }); }); }); diff --git a/public/sass/components/_footer.scss b/public/sass/components/_footer.scss index 8b7d64e47fe..4a77ec37605 100644 --- a/public/sass/components/_footer.scss +++ b/public/sass/components/_footer.scss @@ -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; } } diff --git a/public/sass/pages/_login.scss b/public/sass/pages/_login.scss index b81099ff22b..9a4260576b1 100644 --- a/public/sass/pages/_login.scss +++ b/public/sass/pages/_login.scss @@ -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; }