Merge branch 'master' into implement/betterErrorReading

This commit is contained in:
spalger 2015-12-09 11:11:12 -07:00
commit 43ee9e8d22
37 changed files with 439 additions and 593 deletions

View file

@ -21,5 +21,4 @@ if [ ! -x "$NODE" ]; then
exit 1
fi
exec "${NODE}" "${DIR}/src/cli" ${@}
exec "${NODE}" $NODE_OPTIONS "${DIR}/src/cli" ${@}

View file

@ -18,7 +18,7 @@ If Not Exist "%NODE%" (
)
TITLE Kibana Server
"%NODE%" "%DIR%\src\cli" %*
"%NODE%" %NODE_OPTIONS% "%DIR%\src\cli" %*
:finally

View file

@ -24,7 +24,7 @@ module.exports = function (kibana) {
key: Joi.string()
}).default(),
apiVersion: Joi.string().default('2.0'),
minimumVersion: Joi.string().default('2.1.0')
engineVersion: Joi.string().valid('^2.1.0').default('^2.1.0')
}).default();
},

View file

@ -10,7 +10,7 @@ describe('plugins/elasticsearch', function () {
var plugin;
beforeEach(function () {
var get = sinon.stub().withArgs('elasticserach.minimumVersion').returns('1.4.3');
var get = sinon.stub().withArgs('elasticsearch.engineVersion').returns('^1.4.3');
var config = function () { return { get: get }; };
server = {
log: _.noop,

View file

@ -55,7 +55,7 @@ describe('plugins/elasticsearch', function () {
});
it('should set the cluster green if everything is ready', function () {
get.withArgs('elasticsearch.minimumVersion').returns('1.4.4');
get.withArgs('elasticsearch.engineVersion').returns('^1.4.4');
get.withArgs('kibana.index').returns('.my-kibana');
client.ping.returns(Promise.resolve());
client.cluster.health.returns(Promise.resolve({ timed_out: false, status: 'green' }));
@ -74,7 +74,7 @@ describe('plugins/elasticsearch', function () {
it('should set the cluster red if the ping fails, then to green', function () {
get.withArgs('elasticsearch.url').returns('http://localhost:9210');
get.withArgs('elasticsearch.minimumVersion').returns('1.4.4');
get.withArgs('elasticsearch.engineVersion').returns('^1.4.4');
get.withArgs('kibana.index').returns('.my-kibana');
client.ping.onCall(0).returns(Promise.reject(new NoConnections()));
client.ping.onCall(1).returns(Promise.resolve());
@ -98,7 +98,7 @@ describe('plugins/elasticsearch', function () {
it('should set the cluster red if the health check status is red, then to green', function () {
get.withArgs('elasticsearch.url').returns('http://localhost:9210');
get.withArgs('elasticsearch.minimumVersion').returns('1.4.4');
get.withArgs('elasticsearch.engineVersion').returns('^1.4.4');
get.withArgs('kibana.index').returns('.my-kibana');
client.ping.returns(Promise.resolve());
client.cluster.health.onCall(0).returns(Promise.resolve({ timed_out: false, status: 'red' }));
@ -121,7 +121,7 @@ describe('plugins/elasticsearch', function () {
it('should set the cluster yellow if the health check timed_out and create index', function () {
get.withArgs('elasticsearch.url').returns('http://localhost:9210');
get.withArgs('elasticsearch.minimumVersion').returns('1.4.4');
get.withArgs('elasticsearch.engineVersion').returns('^1.4.4');
get.withArgs('kibana.index').returns('.my-kibana');
client.ping.returns(Promise.resolve());
client.cluster.health.onCall(0).returns(Promise.resolve({ timed_out: true, status: 'red' }));

View file

@ -1,134 +0,0 @@
var _ = require('lodash');
var versionMath = require('../version_math');
var expect = require('expect.js');
var versions = [
'1.1.12',
'1.1.12',
'1.1.12',
'1.1.12',
'0.90.0',
'0.90.1',
'1.0.0',
'1.0',
'1.2.3',
'2.0.0',
'2.0.1',
'2.3.1'
];
describe('plugins/elasticsearch', function () {
describe('lib/version_math', function () {
describe('version math (0.90.0 - 2.3.1)', function () {
var methods = 'max,min,eq,is,lt,lte,gt,gte'.split(',');
describe('methods', function () {
it('should have ' + methods.join(', ') + ' methods', function () {
_.each(methods, function (method) {
expect(versionMath[method]).to.be.a(Function);
});
});
});
describe('min & max', function () {
it('has a max of 2.3.1', function () {
expect(versionMath.max(versions)).to.be('2.3.1');
});
it('has a min of 0.90.0', function () {
expect(versionMath.min(versions)).to.be('0.90.0');
});
});
describe('eq / lowest version', function () {
it('should be true for 0.90.0', function () {
expect(versionMath.eq('0.90.0', versions)).to.be(true);
});
it('should be false for 1.0', function () {
expect(versionMath.eq('1.0', versions)).to.be(false);
});
});
describe('gt / lowest version', function () {
it('is > 0.20.3', function () {
expect(versionMath.gt('0.20.3', versions)).to.be(true);
});
it('is not > 0.90.0', function () {
expect(versionMath.gt('0.90.0', versions)).to.be(false);
});
it('is not > 1.0.0', function () {
expect(versionMath.gt('1.0.0', versions)).to.be(false);
});
});
describe('gte / lowest version', function () {
it('is >= 0.20.3', function () {
expect(versionMath.gte('0.20.3', versions)).to.be(true);
});
it('is >= 0.90.0', function () {
expect(versionMath.gte('0.90.0', versions)).to.be(true);
});
it('is not >= 1.0.0', function () {
expect(versionMath.gte('1.0.0', versions)).to.be(false);
});
});
describe('lt / highest version', function () {
it('is not < 0.20.3', function () {
expect(versionMath.lt('0.20.3', versions)).to.be(false);
});
it('is not < 2.3.1', function () {
expect(versionMath.lt('2.3.1', versions)).to.be(false);
});
it('is < 2.5', function () {
expect(versionMath.lt('2.5', versions)).to.be(true);
});
});
describe('lte / highest version', function () {
it('is not =< 0.20.3', function () {
expect(versionMath.lte('0.20.3', versions)).to.be(false);
});
it('is =< 2.3.1', function () {
expect(versionMath.lte('2.3.1', versions)).to.be(true);
});
it('is =< 2.5', function () {
expect(versionMath.lte('2.5', versions)).to.be(true);
});
});
describe('is', function () {
it('exactly, <, <=, >, >=', function () {
expect(versionMath.is('0.90.0', versions)).to.be(true);
expect(versionMath.is('0.20.0', versions)).to.be(false);
expect(versionMath.is('>0.20.0', versions)).to.be(true);
expect(versionMath.is('>0.90.0', versions)).to.be(false);
expect(versionMath.is('>0.90.1', versions)).to.be(false);
expect(versionMath.is('>=0.20.0', versions)).to.be(true);
expect(versionMath.is('>=0.90.0', versions)).to.be(true);
expect(versionMath.is('>=0.90.1', versions)).to.be(false);
expect(versionMath.is('<2.5', versions)).to.be(true);
expect(versionMath.is('<2.3.1', versions)).to.be(false);
expect(versionMath.is('<0.90.1', versions)).to.be(false);
expect(versionMath.is('<=2.5', versions)).to.be(true);
expect(versionMath.is('<=2.3.1', versions)).to.be(true);
expect(versionMath.is('<=0.90.1', versions)).to.be(false);
});
});
});
});
});

View file

@ -0,0 +1,45 @@
var versionSatisfies = require('../version_satisfies');
var expect = require('expect.js');
var versionChecks = [
// order is: ['actual version', 'match expression', satisfied (true/false)]
['0.90.0', '>=0.90.0', true],
['1.2.0', '>=1.2.1 <2.0.0', false],
['1.2.1', '>=1.2.1 <2.0.0', true],
['1.4.4', '>=1.2.1 <2.0.0', true],
['1.7.4', '>=1.3.1 <2.0.0', true],
['2.0.0', '>=1.3.1 <2.0.0', false],
['1.4.3', '^1.4.3', true],
['1.4.3-Beta1', '^1.4.3', true],
['1.4.4', '^1.4.3', true],
['1.1.12', '^1.0.0', true],
['1.1.12', '~1.0.0', false],
['1.6.1-SNAPSHOT', '1.6.1', true],
['1.6.1-SNAPSHOT', '1.6.2', false],
['1.7.1-SNAPSHOT', '^1.3.1', true],
['1.3.4', '^1.4.0', false],
['2.0.1', '^2.0.0', true],
['2.1.1', '^2.1.0', true],
['2.2.0', '^2.1.0', true],
['3.0.0-snapshot', '^2.1.0', false],
['3.0.0', '^2.1.0', false],
['2.10.20-snapshot', '^2.10.20', true],
['2.10.999', '^2.10.20', true],
];
describe('plugins/elasticsearch', function () {
describe('lib/version_satisfies', function () {
versionChecks.forEach(function (spec) {
var actual = spec[0];
var match = spec[1];
var satisfied = spec[2];
var desc = actual + ' satisfies ' + match;
describe(desc, function () {
it('should be ' + satisfied, function () {
expect(versionSatisfies(actual, match)).to.be(satisfied);
});
});
});
});
});

View file

@ -1,13 +1,13 @@
var _ = require('lodash');
var esBool = require('./es_bool');
var versionMath = require('./version_math');
var versionSatisfies = require('./version_satisfies');
var SetupError = require('./setup_error');
module.exports = function (server) {
server.log(['plugin', 'debug'], 'Checking Elasticsearch version');
var client = server.plugins.elasticsearch.client;
var minimumElasticsearchVersion = server.config().get('elasticsearch.minimumVersion');
var engineVersion = server.config().get('elasticsearch.engineVersion');
return client.nodes.info()
.then(function (info) {
@ -18,9 +18,8 @@ module.exports = function (server) {
return false;
}
// remove nodes that are gte the min version
var v = node.version.split('-')[0];
return !versionMath.gte(minimumElasticsearchVersion, v);
// remove nodes that satify required engine version
return !versionSatisfies(node.version, engineVersion);
});
if (!badNodes.length) return true;
@ -30,7 +29,7 @@ module.exports = function (server) {
});
var message = `This version of Kibana requires Elasticsearch ` +
`${minimumElasticsearchVersion} or higher on all nodes. I found ` +
`${engineVersion} on all nodes. I found ` +
`the following incompatible nodes in your cluster: ${badNodeNames.join(',')}`;
throw new SetupError(server, message);

View file

@ -1,139 +0,0 @@
var _ = require('lodash');
function VersionMathException(message) {
this.message = message;
this.name = 'VersionMathException';
}
// Determine if a specific version meets the minimum requirement
var compare = function (required, installed) {
if (_.isUndefined(installed)) {
return;
}
if (!required || !installed) {
return undefined;
}
var a = installed.split('.');
var b = required.split('.');
var i;
// leave suffixes as is ("RC1 or -SNAPSHOT")
for (i = 0; i < Math.min(a.length, 3); ++i) {
a[i] = Number(a[i]);
}
for (i = 0; i < Math.min(b.length, 3); ++i) {
b[i] = Number(b[i]);
}
if (a.length === 2) {
a[2] = 0;
}
if (a[0] > b[0]) { return true; }
if (a[0] < b[0]) { return false; }
if (a[1] > b[1]) { return true; }
if (a[1] < b[1]) { return false; }
if (a[2] > b[2]) { return true; }
if (a[2] < b[2]) { return false; }
if (a.length > 3) {
// rc/beta suffix
if (b.length <= 3) {
return false;
} // no suffix on b -> a<b
return a[3] >= b[3];
}
if (b.length > 3) {
// b has a suffix but a not -> a>b
return true;
}
return true;
};
// Sort versions from lowest to highest
var sortVersions = function (versions) {
if (!_.isArray(versions)) versions = [versions];
return _.uniq(versions).sort(function (a, b) {
return compare(a, b) ? -1 : 1;
});
};
// Get the max version in this cluster
var max = function (versions) {
return sortVersions(versions).pop();
};
// Return the lowest version in the cluster
var min = function (versions) {
return sortVersions(versions).shift();
};
// Check if the lowest version in the cluster is >= to `version`
var gte = function (version, versions) {
var _versions = sortVersions(versions);
return compare(version, min(_versions));
};
// Check if the highest version in the cluster is <= to `version`
var lte = function (version, versions) {
var _versions = sortVersions(versions);
return compare(max(_versions), version);
};
// check if lowest version in cluster = `version`
var eq = function (version, versions) {
var _versions = sortVersions(versions);
return version === min(_versions) ? true : false;
};
// version > lowest version in cluster?
var gt = function (version, versions) {
var _versions = sortVersions(versions);
return version === min(_versions) ? false : gte(version, _versions);
};
// version < highest version in cluster?
var lt = function (version, versions) {
var _versions = sortVersions(versions);
return version === max(_versions) ? false : lte(version, _versions);
};
/*
Takes a version string with one of the following optional comparison prefixes: >,>=,<.<=
and evaluates if the cluster meets the requirement. If the prefix is omitted exact match
is assumed
*/
var is = function (equation, versions) {
var _versions = sortVersions(versions);
var _v = equation;
var _cf;
if (_v.charAt(0) === '>') {
_cf = _v.charAt(1) === '=' ? gte(_v.slice(2), _versions) : gt(_v.slice(1), _versions);
} else if (_v.charAt(0) === '<') {
_cf = _v.charAt(1) === '=' ? lte(_v.slice(2), _versions) : lt(_v.slice(1), _versions);
} else {
_cf = eq(_v, _versions);
}
return _cf;
};
module.exports = {
min: min,
max: max,
is: is,
eq: eq,
gt: gt,
gte: gte,
lt: lt,
lte: lte
};

View file

@ -0,0 +1,16 @@
var semver = require('semver');
module.exports = function (actual, expected) {
try {
var ver = cleanVersion(actual);
return semver.satisfies(ver, expected);
} catch (err) {
return false;
}
function cleanVersion(version) {
var match = version.match(/\d+\.\d+\.\d+/);
if (!match) return version;
return match[0];
}
};

View file

@ -1,9 +1,9 @@
<div class="app-container">
<nav class="navbar navbar-default navbar-static-top subnav">
<nav class="navbar navbar-default navbar-static-top subnav" data-test-subj="settingsNav">
<div class="container-fluid">
<ul class="nav navbar-nav">
<li ng-repeat="section in sections" ng-class="section.class">
<a class="navbar-link" kbn-href="{{section.url}}">{{section.display}}</a>
<a class="navbar-link" kbn-href="{{section.url}}" data-test-subj="{{section.name}}">{{section.display}}</a>
</li>
</ul>
</div>

View file

@ -1,4 +1,4 @@
<tr ng-class="conf.value === undefined ? 'default' : 'custom'">
<tr ng-class="conf.value === undefined ? 'default' : 'custom'" data-test-subj="advancedSetting {{conf.name}}" >
<td class="name">
<b>{{conf.name}}</b>
<span class="smaller" ng-show="!conf.isCustom && conf.value !== undefined">
@ -59,12 +59,13 @@
name="conf.name"
ng-model="conf.unsavedValue"
ng-options="option as option for option in conf.options"
class="form-control">
class="form-control"
data-test-subj="selectInput">
</select>
</form>
<!-- Setting display formats -->
<span ng-if="!conf.editting">
<span ng-if="!conf.editting" data-test-subj="currentValue">
<span ng-show="(conf.normal || conf.json || conf.select)">{{conf.value || conf.defVal}}</span>
<span ng-show="conf.array">{{(conf.value || conf.defVal).join(', ')}}</span>
<span ng-show="conf.bool">{{conf.value === undefined ? conf.defVal : conf.value}}</span>
@ -77,7 +78,8 @@
ng-click="edit(conf)"
class="btn btn-default"
ng-disabled="conf.tooComplex"
aria-label="Edit">
aria-label="Edit"
data-test-subj="editButton">
<span class="sr-only">Edit</span>
<i aria-hidden="true" class="fa fa-pencil"></i>
</button>
@ -87,7 +89,8 @@
ng-click="save(conf)"
class="btn btn-success"
ng-disabled="conf.loading || conf.tooComplex || forms.configEdit.$invalid"
aria-label="Save">
aria-label="Save"
data-test-subj="saveButton">
<span class="sr-only">Save</span>
<i aria-hidden="true" ng-if="!conf.loading" class="fa fa-save"></i>
<i aria-hidden="true" ng-if="conf.loading" class="fa fa-spinner"></i>
@ -98,7 +101,8 @@
ng-click="clear(conf)"
ng-hide="conf.value === undefined"
class="btn btn-danger"
aria-label="Clear">
aria-label="Clear"
data-test-subj="clearButton">
<span class="sr-only">Clear</span>
<i aria-hidden="true" class="fa fa-trash-o"></i>
</button>
@ -107,7 +111,8 @@
ng-if="conf.editting"
ng-click="cancelEdit(conf)"
class="btn btn-default"
aria-label="Cancel edit">
aria-label="Cancel edit"
data-test-subj="cancelButton">
<span class="sr-only">Cancel Edit</span>
<i aria-hidden="true" class="fa fa-times"></i>
</button>

View file

@ -4,7 +4,9 @@ import KbnServer from '../../KbnServer';
describe('cookie validation', function () {
let kbnServer;
beforeEach(function () {
kbnServer = new KbnServer();
kbnServer = new KbnServer({
server: { autoListen: false }
});
return kbnServer.ready();
});
afterEach(function () {

View file

@ -93,7 +93,12 @@ define(function (require) {
ignore_unavailable: true,
preference: sessionId,
body: body
}));
}))
.catch(function (err) {
return strategy.handleResponseError
? strategy.handleResponseError(executable, err)
: Promise.reject(err);
});
})
.then(function (clientResp) {
return strategy.getResponses(clientResp);

View file

@ -31,6 +31,57 @@ describe('ui/courier/fetch/strategy/search', () => {
});
});
describe('#handleResponseError()', () => {
let error;
beforeEach(() => {
error = { status: 404, body: { error: { index: '[-*]' } } };
});
it('recovers 404 for index -* with empty response', () => {
let resp;
search.handleResponseError(reqsFetchParams, error).then(val => resp = val);
$rootScope.$apply();
expect(resp.responses).not.to.be(undefined);
});
it('mocks all of the bundled searches', () => {
let resp;
reqsFetchParams.push({});
search.handleResponseError(reqsFetchParams, error).then(val => resp = val);
$rootScope.$apply();
expect(Array.isArray(resp.responses)).to.be(true);
expect(resp.responses.length).to.be(2);
resp.responses.forEach(res => {
expect(res.hits.total).to.be(0);
expect(res.hits.hits.length).to.be(0);
});
});
context('when not a 404', () => {
it('rejects with the original response', () => {
error.status = 403;
let err;
search.handleResponseError(reqsFetchParams, error).catch(val => err = val);
$rootScope.$apply();
expect(err).to.be(error);
});
});
context('when not for -* index', () => {
it('rejects with the original response', () => {
error.body.error.index = '[foo-*]';
let err;
search.handleResponseError(reqsFetchParams, error).catch(val => err = val);
$rootScope.$apply();
expect(err).to.be(error);
});
});
});
describe('#reqsFetchParamsToBody()', () => {
it('filters out any body properties that begin with $', () => {
let value;

View file

@ -4,9 +4,32 @@ define(function (require) {
var angular = require('angular');
var toJson = require('ui/utils/aggressive_parse').toJson;
function emptyResponse() {
return { hits: { total: 0, hits: [] } };
};
return {
clientMethod: 'msearch',
/**
* Recover from a 404 when searching against no indexes
*
* If we get a 404 while intentionally searching for no indexes, we can
* simply mock an empty result since that is ultimately what kibana cares
* about.
*
* @param {object} response - the client response from elasticsearch
* @return {Promise} - fulfilled by mock or rejected with original error
*/
handleResponseError: function (requests, response) {
var is404 = _.get(response, 'status') === 404;
var isEmptyIndexList = _.get(response, 'body.error.index') === '[-*]';
return is404 && isEmptyIndexList
? Promise.resolve({ responses: requests.map(emptyResponse) })
: Promise.reject(response);
},
/**
* Flatten a series of requests into as ES request body
*

View file

@ -0,0 +1,33 @@
import ngMock from 'ngMock';
import expect from 'expect.js';
import sinon from 'auto-release-sinon';
describe('IndexPatterns service', function () {
let indexPatterns;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
const IndexPattern = Private(require('../_index_pattern'));
indexPatterns = Private(require('../index_patterns'));
// prevent IndexPattern initialization from doing anything
Private.stub(
require('../_index_pattern'),
function (...args) {
const indexPattern = new IndexPattern(...args);
sinon.stub(indexPattern, 'init', function () {
return new Promise();
});
return indexPattern;
}
);
}));
it('does not cache gets without an id', function () {
expect(indexPatterns.get()).to.not.be(indexPatterns.get());
});
it('does cache gets for the same id', function () {
expect(indexPatterns.get(1)).to.be(indexPatterns.get(1));
});
});

View file

@ -16,6 +16,7 @@ define(function (require) {
var flattenHit = Private(require('ui/index_patterns/_flatten_hit'));
var formatHit = require('ui/index_patterns/_format_hit');
var calculateIndices = Private(require('ui/index_patterns/_calculate_indices'));
var patternCache = Private(require('ui/index_patterns/_pattern_cache'));
var type = 'index-pattern';
@ -236,7 +237,14 @@ define(function (require) {
return safeConfirm(confirmMessage).then(
function () {
return docSource.doIndex(body).then(setId);
return Promise.try(function () {
const cached = patternCache.get(self.id);
if (cached) {
return cached.then(pattern => pattern.destroy());
}
})
.then(() => docSource.doIndex(body))
.then(setId);
},
_.constant(false) // if the user doesn't overwrite, resolve with false
);
@ -275,6 +283,11 @@ define(function (require) {
return '' + self.toJSON();
};
self.destroy = function () {
patternCache.clear(self.id);
docSource.destroy();
};
self.metaFields = config.get('metaFields');
self.getComputedFields = getComputedFields.bind(self);

View file

@ -2,7 +2,7 @@ define(function (require) {
var module = require('ui/modules').get('kibana/index_patterns');
require('ui/filters/short_dots');
module.service('indexPatterns', function (es, Notifier, Private, Promise, kbnIndex) {
function IndexPatternsProvider(es, Notifier, Private, Promise, kbnIndex) {
var self = this;
var _ = require('lodash');
var errors = require('ui/errors');
@ -25,7 +25,8 @@ define(function (require) {
self.delete = function (pattern) {
self.getIds.clearCache();
patternCache.delete(pattern.id);
pattern.destroy();
return es.delete({
index: kbnIndex,
type: 'index-pattern',
@ -44,5 +45,8 @@ define(function (require) {
self.patternToWildcard = Private(require('ui/index_patterns/_pattern_to_wildcard'));
self.fieldFormats = Private(require('ui/registry/field_formats'));
self.IndexPattern = IndexPattern;
});
}
module.service('indexPatterns', Private => Private(IndexPatternsProvider));
return IndexPatternsProvider;
});

View file

@ -0,0 +1,7 @@
require('!!file?name=[path][name].[ext]!ui/stringify/icons/go.png');
require('!!file?name=[path][name].[ext]!ui/stringify/icons/stop.png');
require('!!file?name=[path][name].[ext]!ui/stringify/icons/de.png');
require('!!file?name=[path][name].[ext]!ui/stringify/icons/ne.png');
require('!!file?name=[path][name].[ext]!ui/stringify/icons/us.png');
require('!!file?name=[path][name].[ext]!ui/stringify/icons/ni.png');
require('!!file?name=[path][name].[ext]!ui/stringify/icons/cv.png');

View file

@ -3,7 +3,9 @@ define(function (require) {
var _ = require('lodash');
var FieldFormat = Private(require('ui/index_patterns/_field_format/FieldFormat'));
require('ui/field_format_editor/pattern/pattern');
require('ui/stringify/icons');
_.class(Url).inherits(FieldFormat);
function Url(params) {
@ -28,7 +30,7 @@ define(function (require) {
template: require('ui/stringify/editors/url.html'),
controllerAs: 'url',
controller: function ($scope) {
var iconPattern = 'ui/stringify/icons/{{value}}.png';
var iconPattern = '/bundles/src/ui/public/stringify/icons/{{value}}.png';
this.samples = {
a: [ 'john', '/some/pathname/asset.png', 1234 ],

View file

@ -0,0 +1,52 @@
var angular = require('angular');
var expect = require('expect.js');
var ngMock = require('ngMock');
var moment = require('moment-timezone');
var sinon = require('sinon');
var $ = require('jquery');
require('ui/timepicker/offset_timezone');
describe('Offset timezone', function () {
let $compile;
let $rootScope;
let element;
let getTimezoneOffset;
const html = '<div offset-timezone ng-model="value"/>{{value}}</div>';
const timezoneOffset = 60;
const mockDate = '2015-11-23T15:01:18-01:00';
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
getTimezoneOffset = sinon.stub(Date.prototype, 'getTimezoneOffset', function () {
return timezoneOffset;
});
}));
beforeEach(function () {
element = $compile(html)($rootScope);
});
it('should offset the timezone so the day is not changed when converting from moment to the Date constructor', function () {
var mockDate = $rootScope.value = moment(mockDate).tz('UTC');
$rootScope.$digest();
var offsetDate = moment(element.controller('ngModel').$modelValue);
expect(offsetDate.diff(mockDate, 'minutes')).to.be(timezoneOffset);
});
it('should keep the date the same when reading from the DOM', function () {
var mockDate = $rootScope.value = moment(mockDate).tz('UTC');
$rootScope.$digest();
var domDate = moment($(element).text().replace(/"/g, ''));
expect(domDate.diff(mockDate, 'minutes')).to.be(0);
});
afterEach(function () {
getTimezoneOffset.restore();
});
});

View file

@ -0,0 +1,55 @@
define(function (require) {
var _ = require('lodash');
var angular = require('angular');
var moment = require('moment');
/**
* moment objects can have an associated timezone, and when converting to a Date the
* timezone is changed to browser time. This can cause issues, such as a day picker
* showing the wrong day.
* When a moment date is passed in, offset the timezone so that after converting to a Date object
* the day does not appear changed. When reading back, convert to moment and remove the offset.
*/
require('ui/modules')
.get('kibana')
.directive('offsetTimezone', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, $el, attrs, ngModelCntrl) {
ngModelCntrl.$formatters.unshift(toDatePicker);
ngModelCntrl.$parsers.unshift(fromDatePicker);
// State for whether the last change was internal or external
// Internal changes(i.e selecting a new day multiple times) should not
// continue to offset the date.
var offsetDate = false;
//Going from Date object to moment
function fromDatePicker(value) {
if (!value) return;
var date = moment(value);
if (offsetDate) {
var offset = value.getTimezoneOffset() + date.utcOffset();
offsetDate = false;
date.minutes(date.minutes() - offset);
}
return date;
}
//Going from moment to Date object
function toDatePicker(value) {
if (!value) return;
var date = new Date(value.format('YYYY-MM-DDTHH:mm:ss.SSSZ'));
var offset = date.getTimezoneOffset() + value.utcOffset();
date.setMinutes(date.getMinutes() + offset);
offsetDate = true;
ngModelCntrl.$modelValue = date;
}
}
};
});
});

View file

@ -124,7 +124,11 @@
<input type="text" required class="form-control" input-datetime="{{format}}" ng-model="absolute.from">
</div>
<div>
<datepicker ng-model="absolute.from" max-date="absolute.to" show-weeks="false"></datepicker>
<datepicker
offset-timezone
ng-model="absolute.from"
max-date="absolute.to"
show-weeks="false"></datepicker>
</div>
</div>
@ -137,7 +141,11 @@
<input type="text" required class="form-control" input-datetime="{{format}}" ng-model="absolute.to">
</div>
<div>
<datepicker ng-model="absolute.to" min-date="absolute.from" show-weeks="false"></datepicker>
<datepicker
offset-timezone
ng-model="absolute.to"
min-date="absolute.from"
show-weeks="false"></datepicker>
</div>
</div>

View file

@ -10,6 +10,7 @@ define(function (require) {
require('ui/timepicker/quick_ranges');
require('ui/timepicker/refresh_intervals');
require('ui/timepicker/time_units');
require('ui/timepicker/offset_timezone');
module.directive('kbnTimepicker', function (quickRanges, timeUnits, refreshIntervals) {
return {
@ -60,14 +61,6 @@ define(function (require) {
{text: 'Years ago', value: 'y'},
];
$scope.$watch('absolute.from', function (date) {
if (_.isDate(date)) $scope.absolute.from = moment(date);
});
$scope.$watch('absolute.to', function (date) {
if (_.isDate(date)) $scope.absolute.to = moment(date);
});
$scope.setMode = function (thisMode) {
switch (thisMode) {
case 'quick':

View file

@ -1,128 +0,0 @@
var _ = require('lodash');
var versionmath = require('ui/utils/versionmath');
var expect = require('expect.js');
var versions = [
'1.1.12',
'1.1.12',
'1.1.12',
'1.1.12',
'0.90.0',
'0.90.1',
'1.0.0',
'1.0',
'1.2.3',
'2.0.0',
'2.0.1',
'2.3.1'
];
describe('version math (0.90.0 - 2.3.1)', function () {
var methods = 'max,min,eq,is,lt,lte,gt,gte'.split(',');
describe('methods', function () {
it('should have ' + methods.join(', ') + ' methods', function () {
_.each(methods, function (method) {
expect(versionmath[method]).to.be.a(Function);
});
});
});
describe('min & max', function () {
it('has a max of 2.3.1', function () {
expect(versionmath.max(versions)).to.be('2.3.1');
});
it('has a min of 0.90.0', function () {
expect(versionmath.min(versions)).to.be('0.90.0');
});
});
describe('eq / lowest version', function () {
it('should be true for 0.90.0', function () {
expect(versionmath.eq('0.90.0', versions)).to.be(true);
});
it('should be false for 1.0', function () {
expect(versionmath.eq('1.0', versions)).to.be(false);
});
});
describe('gt / lowest version', function () {
it('is > 0.20.3', function () {
expect(versionmath.gt('0.20.3', versions)).to.be(true);
});
it('is not > 0.90.0', function () {
expect(versionmath.gt('0.90.0', versions)).to.be(false);
});
it('is not > 1.0.0', function () {
expect(versionmath.gt('1.0.0', versions)).to.be(false);
});
});
describe('gte / lowest version', function () {
it('is >= 0.20.3', function () {
expect(versionmath.gte('0.20.3', versions)).to.be(true);
});
it('is >= 0.90.0', function () {
expect(versionmath.gte('0.90.0', versions)).to.be(true);
});
it('is not >= 1.0.0', function () {
expect(versionmath.gte('1.0.0', versions)).to.be(false);
});
});
describe('lt / highest version', function () {
it('is not < 0.20.3', function () {
expect(versionmath.lt('0.20.3', versions)).to.be(false);
});
it('is not < 2.3.1', function () {
expect(versionmath.lt('2.3.1', versions)).to.be(false);
});
it('is < 2.5', function () {
expect(versionmath.lt('2.5', versions)).to.be(true);
});
});
describe('lte / highest version', function () {
it('is not =< 0.20.3', function () {
expect(versionmath.lte('0.20.3', versions)).to.be(false);
});
it('is =< 2.3.1', function () {
expect(versionmath.lte('2.3.1', versions)).to.be(true);
});
it('is =< 2.5', function () {
expect(versionmath.lte('2.5', versions)).to.be(true);
});
});
describe('is', function () {
it('exactly, <, <=, >, >=', function () {
expect(versionmath.is('0.90.0', versions)).to.be(true);
expect(versionmath.is('0.20.0', versions)).to.be(false);
expect(versionmath.is('>0.20.0', versions)).to.be(true);
expect(versionmath.is('>0.90.0', versions)).to.be(false);
expect(versionmath.is('>0.90.1', versions)).to.be(false);
expect(versionmath.is('>=0.20.0', versions)).to.be(true);
expect(versionmath.is('>=0.90.0', versions)).to.be(true);
expect(versionmath.is('>=0.90.1', versions)).to.be(false);
expect(versionmath.is('<2.5', versions)).to.be(true);
expect(versionmath.is('<2.3.1', versions)).to.be(false);
expect(versionmath.is('<0.90.1', versions)).to.be(false);
expect(versionmath.is('<=2.5', versions)).to.be(true);
expect(versionmath.is('<=2.3.1', versions)).to.be(true);
expect(versionmath.is('<=0.90.1', versions)).to.be(false);
});
});
});

View file

@ -1,139 +0,0 @@
define(function (require) {
var _ = require('lodash');
function VersionMathException(message) {
this.message = message;
this.name = 'VersionMathException';
}
// Get the max version in this cluster
function max(versions) {
return sortVersions(versions).pop();
}
// Return the lowest version in the cluster
function min(versions) {
return sortVersions(versions).shift();
}
// Sort versions from lowest to highest
function sortVersions(versions) {
if (!_.isArray(versions)) versions = [versions];
return _.uniq(versions).sort(function (a, b) {
return compare(a, b) ? -1 : 1;
});
}
/*
Takes a version string with one of the following optional comparison prefixes: >,>=,<.<=
and evaluates if the cluster meets the requirement. If the prefix is omitted exact match
is assumed
*/
function is(equation, versions) {
var _versions = sortVersions(versions);
var _v = equation;
var _cf;
if (_v.charAt(0) === '>') {
_cf = _v.charAt(1) === '=' ? gte(_v.slice(2), _versions) : gt(_v.slice(1), _versions);
} else if (_v.charAt(0) === '<') {
_cf = _v.charAt(1) === '=' ? lte(_v.slice(2), _versions) : lt(_v.slice(1), _versions);
} else {
_cf = eq(_v, _versions);
}
return _cf;
}
// check if lowest version in cluster = `version`
function eq(version, versions) {
var _versions = sortVersions(versions);
return version === min(_versions) ? true : false;
}
// version > lowest version in cluster?
function gt(version, versions) {
var _versions = sortVersions(versions);
return version === min(_versions) ? false : gte(version, _versions);
}
// version < highest version in cluster?
function lt(version, versions) {
var _versions = sortVersions(versions);
return version === max(_versions) ? false : lte(version, _versions);
}
// Check if the lowest version in the cluster is >= to `version`
function gte(version, versions) {
var _versions = sortVersions(versions);
return compare(version, min(_versions));
}
// Check if the highest version in the cluster is <= to `version`
function lte(version, versions) {
var _versions = sortVersions(versions);
return compare(max(_versions), version);
}
// Determine if a specific version meets the minimum requirement
function compare(required, installed) {
if (_.isUndefined(installed)) {
return;
}
if (!required || !installed) {
return undefined;
}
var a = installed.split('.');
var b = required.split('.');
var i;
// leave suffixes as is ("RC1 or -SNAPSHOT")
for (i = 0; i < Math.min(a.length, 3); ++i) {
a[i] = Number(a[i]);
}
for (i = 0; i < Math.min(b.length, 3); ++i) {
b[i] = Number(b[i]);
}
if (a.length === 2) {
a[2] = 0;
}
if (a[0] > b[0]) { return true; }
if (a[0] < b[0]) { return false; }
if (a[1] > b[1]) { return true; }
if (a[1] < b[1]) { return false; }
if (a[2] > b[2]) { return true; }
if (a[2] < b[2]) { return false; }
if (a.length > 3) {
// rc/beta suffix
if (b.length <= 3) {
return false;
} // no suffix on b -> a<b
return a[3] >= b[3];
}
if (b.length > 3) {
// b has a suffix but a not -> a>b
return true;
}
return true;
}
return {
min: min,
max: max,
is: is,
eq: eq,
gt: gt,
gte: gte,
lt: lt,
lte: lte
};
});

View file

@ -1,7 +1,7 @@
define(function (require) {
var Common = require('../../../support/pages/Common');
var HeaderPage = require('../../../support/pages/HeaderPage');
var SettingsPage = require('../../../support/pages/SettingsPage');
var SettingsPage = require('../../../support/pages/settings_page');
var DiscoverPage = require('../../../support/pages/DiscoverPage');
var expect = require('intern/dojo/node!expect.js');

View file

@ -0,0 +1,45 @@
define(function (require) {
var Common = require('../../../support/pages/Common');
var SettingsPage = require('../../../support/pages/settings_page');
var expect = require('intern/dojo/node!expect.js');
var Promise = require('bluebird');
return function (bdd, scenarioManager) {
bdd.describe('creating and deleting default index', function describeIndexTests() {
var common;
var settingsPage;
bdd.before(function () {
common = new Common(this.remote);
settingsPage = new SettingsPage(this.remote);
return scenarioManager.reload('emptyKibana')
.then(function () {
return settingsPage.navigateTo();
});
});
bdd.describe('index pattern creation', function indexPatternCreation() {
bdd.before(function () {
return settingsPage.createIndexPattern();
});
bdd.it('should allow setting advanced settings', function () {
return settingsPage.clickAdvancedTab()
.then(function TestCallSetAdvancedSettingsForTimezone() {
common.log('calling setAdvancedSetting');
return settingsPage.setAdvancedSettings('dateFormat:tz', 'America/Phoenix');
})
.then(function GetAdvancedSetting() {
return settingsPage.getAdvancedSettings('dateFormat:tz');
})
.then(function (advancedSetting) {
expect(advancedSetting).to.be('America/Phoenix');
})
.catch(common.handleError(this));
});
});
});
};
});

View file

@ -1,6 +1,6 @@
define(function (require) {
var Common = require('../../../support/pages/Common');
var SettingsPage = require('../../../support/pages/SettingsPage');
var SettingsPage = require('../../../support/pages/settings_page');
var expect = require('intern/dojo/node!expect.js');
return function (bdd, scenarioManager) {

View file

@ -1,6 +1,6 @@
define(function (require) {
var Common = require('../../../support/pages/Common');
var SettingsPage = require('../../../support/pages/SettingsPage');
var SettingsPage = require('../../../support/pages/settings_page');
var expect = require('intern/dojo/node!expect.js');
var Promise = require('bluebird');

View file

@ -1,6 +1,6 @@
define(function (require) {
var Common = require('../../../support/pages/Common');
var SettingsPage = require('../../../support/pages/SettingsPage');
var SettingsPage = require('../../../support/pages/settings_page');
var expect = require('intern/dojo/node!expect.js');
//var Promise = require('bluebird');

View file

@ -1,7 +1,7 @@
define(function (require) {
var config = require('intern').config;
var Common = require('../../../support/pages/Common');
var SettingsPage = require('../../../support/pages/SettingsPage');
var SettingsPage = require('../../../support/pages/settings_page');
var expect = require('intern/dojo/node!expect.js');
var Promise = require('bluebird');

View file

@ -1,7 +1,7 @@
define(function (require) {
var expect = require('intern/dojo/node!expect.js');
var Common = require('../../../support/pages/Common');
var SettingsPage = require('../../../support/pages/SettingsPage');
var SettingsPage = require('../../../support/pages/settings_page');
return function (bdd, scenarioManager) {
bdd.describe('initial state', function () {

View file

@ -9,6 +9,7 @@ define(function (require) {
var indexPatternCreateDeleteTest = require('./_index_pattern_create_delete');
var indexPatternResultsSortTest = require('./_index_pattern_results_sort');
var indexPatternPopularityTest = require('./_index_pattern_popularity');
var advancedSettingsTest = require('./_advanced_settings');
bdd.describe('settings app', function () {
var scenarioManager = new ScenarioManager(url.format(config.servers.elasticsearch));
@ -30,6 +31,7 @@ define(function (require) {
});
});
advancedSettingsTest(bdd, scenarioManager);
initialStateTest(bdd, scenarioManager);
creationChangesTest(bdd, scenarioManager);
indexPatternCreateDeleteTest(bdd, scenarioManager);

View file

@ -202,7 +202,8 @@ define(function (require) {
});
},
findTestSubject: function (selector) {
findTestSubject: function findTestSubject(selector) {
this.debug('in findTestSubject: ' + selector);
return this.remote.findByCssSelector(testSubjSelector(selector));
}
};

View file

@ -1,4 +1,4 @@
// in test/support/pages/SettingsPage.js
// in test/support/pages/settings_page.js
define(function (require) {
var config = require('intern').config;
var Promise = require('bluebird');
@ -7,13 +7,39 @@ define(function (require) {
var defaultTimeout = config.timeouts.default;
var common;
function SettingsPage(remote) {
function settingsPage(remote) {
this.remote = remote;
common = new Common(this.remote);
}
SettingsPage.prototype = {
constructor: SettingsPage,
settingsPage.prototype = {
constructor: settingsPage,
clickAdvancedTab: function () {
common.debug('in clickAdvancedTab');
return common.findTestSubject('settingsNav advanced').click();
},
setAdvancedSettings: function setAdvancedSettings(propertyName, propertyValue) {
var self = this;
return common.findTestSubject('advancedSetting&' + propertyName + ' editButton')
.click()
.then(function setAdvancedSettingsClickPropertyValue(selectList) {
return self.remote.findByCssSelector('option[label="' + propertyValue + '"]')
.click();
})
.then(function setAdvancedSettingsClickSaveButton() {
return common.findTestSubject('advancedSetting&' + propertyName + ' saveButton')
.click();
});
},
getAdvancedSettings: function getAdvancedSettings(propertyName) {
common.debug('in setAdvancedSettings');
return common.findTestSubject('advancedSetting&' + propertyName + ' currentValue')
.getVisibleText();
},
navigateTo: function () {
return common.navigateToApp('settings');
@ -295,5 +321,5 @@ define(function (require) {
}
};
return SettingsPage;
return settingsPage;
});