Merge branch 'master' into test-coverage

This commit is contained in:
Joe Fleming 2015-08-24 16:17:10 -07:00
commit 3018f82cf2
45 changed files with 333 additions and 293 deletions

3
.gitignore vendored
View file

@ -10,8 +10,9 @@ target
.idea
*.iml
*.log
esvm
/esvm
.htpasswd
installedPlugins
disabledPlugins
webpackstats.json
config/kibana.dev.yml

View file

@ -59,15 +59,35 @@ Here are some hints for getting eslint setup in your favorite editor:
To ensure that your changes will not break other functionality, please run the test suite and build process before submitting your pull request.
Before running the tests you will need to install the projects dependencies as described below.
Before running the tests you will need to install the projects dependencies as described above.
Once that is complete just run:
```sh
./node_modules/.bin/grunt test build
npm run test && npm run build
```
Distributable, built packages can be found in `target/` after the build completes.
Distributable packages can be found in `target/` after the build completes.
#### Debugging test failures
The standard `npm run test` task runs several sub tasks and can take several minutes to complete, making debugging failures pretty painful. In order to ease the pain specialized tasks provide alternate methods for running the tests.
<dl>
<dt><code>npm run test:quick</code></dt>
<dd>Runs both server and browser tests, but skips linting</dd>
<dt><code>npm run test:server</code> or <code>npm run test:browser</code></dt>
<dd>Runs the tests for just the server or browser</dd>
<dt><code>npm run test:dev</code></dt>
<dd>
Initializes an environment for debugging the browser tests. Includes an dedicated instance of the kibana server for building the test bundle, and a karma server. When running this task the build is optimized for the first time and then a karma-owned instance of the browser is opened. Click the "debug" button to open a new tab that executes the unit tests.
<br>
<img src="http://i.imgur.com/DwHxgfq.png">
</dd>
</dl>
### Submit a pull request

View file

@ -16,6 +16,10 @@ module.exports = function (grunt) {
configFile: __dirname + '/src/config/kibana.yml',
karmaBrowser: (function () {
if (grunt.option('browser')) {
return grunt.option('browser');
}
switch (require('os').platform()) {
case 'win32':
return 'IE';

View file

@ -81,7 +81,7 @@ If you are using a self-signed certificate for Elasticsearch, set the `ca` prope
[source,text]
----
# If you need to provide a CA certificate for your Elasticsarech instance, put
# If you need to provide a CA certificate for your Elasticsearch instance, put
# the path of the pem file here.
ca: /path/to/your/ca/cacert.pem
----

View file

@ -1,72 +0,0 @@
// Karma configuration
// Generated on Mon Jul 27 2015 04:03:51 GMT-0700 (MST)
module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
captureTimeout: 30000,
browserNoActivityTimeout: 120000,
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha'],
// list of files / patterns to load in the browser
files: [
'http://localhost:5601/bundles/commons.bundle.js',
'http://localhost:5601/bundles/tests.bundle.js',
'http://localhost:5601/bundles/commons.style.css',
'http://localhost:5601/bundles/tests.style.css'
],
proxies: {
'/tests/': 'http://localhost:5601/tests/',
'/bundles/': 'http://localhost:5601/bundles/'
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'growl'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: [
require('os').platform() === 'win32' ? 'IE' : 'Chrome'
],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: false,
client: {
mocha: {
reporter: 'html', // change Karma's debug.html to the mocha web reporter
timeout: 10000,
slow: 5000
}
}
});
};

View file

@ -34,7 +34,12 @@
],
"scripts": {
"test": "grunt test",
"start": "node ./src/server/cli/index.js --dev",
"test:dev": "grunt test:dev",
"test:quick": "grunt test:quick",
"test:browser": "grunt test:browser",
"test:server": "grunt test:server",
"build": "grunt build",
"start": "./bin/kibana --dev",
"precommit": "grunt lintStagedFiles",
"karma": "karma start"
},
@ -92,6 +97,7 @@
"jquery": "^2.1.4",
"js-yaml": "^3.2.5",
"json-stringify-safe": "^5.0.1",
"jstimezonedetect": "^1.0.5",
"leaflet": "^0.7.3",
"less": "^2.5.1",
"less-loader": "^2.2.0",
@ -131,7 +137,7 @@
"grunt-cli": "0.1.13",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-copy": "^0.8.0",
"grunt-esvm": "^1.1.3",
"grunt-esvm": "^1.1.5",
"grunt-karma": "^0.12.0",
"grunt-run": "spalger/grunt-run#master",
"grunt-s3": "^0.2.0-alpha.3",
@ -145,6 +151,7 @@
"karma-growl-reporter": "^0.1.1",
"karma-ie-launcher": "^0.2.0",
"karma-mocha": "^0.2.0",
"karma-safari-launcher": "^0.1.1",
"libesvm": "^1.0.1",
"license-checker": "^3.1.0",
"load-grunt-config": "^0.7.0",

View file

@ -1,6 +1,6 @@
let cluster = require('cluster');
let { join } = require('path');
let { startsWith, debounce, compact, invoke, bindAll, once } = require('lodash');
let { debounce, compact, invoke, bindAll, once } = require('lodash');
let Log = require('../Log');
let Worker = require('./Worker');
@ -64,7 +64,7 @@ module.exports = class ClusterManager {
'installedPlugins'
], {
cwd: fromRoot('.'),
ignored: /[\\\/](node_modules|bower_components|public)[\\\/]/,
ignored: /[\\\/](\..*|node_modules|bower_components|public|__tests__)[\\\/]/
});
this.watcher.on('add', this.onWatcherAdd);
@ -81,25 +81,32 @@ module.exports = class ClusterManager {
}
setupManualRestart() {
let input = '';
let clear = () => input = '';
let clearSoon = debounce(clear, 1250);
let readline = require('readline');
let rl = readline.createInterface(process.stdin, process.stdout);
process.stdin.on('data', chunk => {
input += chunk.toString('utf8');
let nls = 0;
let clear = () => nls = 0;
let clearSoon = debounce(clear, 2000);
if (input === '\n') {
// wait for final \n
rl.setPrompt('');
rl.prompt();
rl.on('line', line => {
nls = line.trim() ? 0 : nls + 1;
if (nls >= 2) {
clearSoon.cancel();
clear();
this.server.start();
} else {
clearSoon();
}
else if (startsWith(input, '\n\n')) {
clearSoon.cancel();
this.server.start();
clear();
}
else {
clear();
}
rl.prompt();
});
rl.on('SIGINT', () => {
rl.pause();
process.kill(process.pid, 'SIGINT');
});
}

View file

@ -1,6 +1,8 @@
var _ = require('lodash');
var zlib = require('zlib');
var Promise = require('bluebird');
var url = require('url');
var fs = require('fs');
var request = require('request');
var tar = require('tar');
var progressReporter = require('./progressReporter');
@ -17,7 +19,7 @@ module.exports = function (settings, logger) {
throw new Error('Not a valid url.');
}
logger.log('attempting to download ' + sourceUrl);
logger.log('Attempting to extract from ' + sourceUrl);
return Promise.try(function () {
return downloadSingle(sourceUrl, settings.workingPath, settings.timeout, logger)
@ -26,7 +28,7 @@ module.exports = function (settings, logger) {
return tryNext();
}
if (err.message === 'EEXTRACT') {
throw (new Error('Error extracting the plugin archive'));
throw (new Error('Error extracting the plugin archive... is this a valid tar.gz file?'));
}
throw (err);
});
@ -54,10 +56,10 @@ module.exports = function (settings, logger) {
}
return wrappedRequest(requestOptions)
.then(function (req) {
var reporter = progressReporter(logger, req);
.then(function (fileStream) {
var reporter = progressReporter(logger, fileStream);
req
fileStream
.on('response', reporter.handleResponse)
.on('data', reporter.handleData)
.on('error', _.partial(reporter.handleError, 'ENOTFOUND'))
@ -73,7 +75,12 @@ module.exports = function (settings, logger) {
function wrappedRequest(requestOptions) {
return Promise.try(function () {
return request.get(requestOptions);
let urlInfo = url.parse(requestOptions.url);
if (/^file/.test(urlInfo.protocol)) {
return fs.createReadStream(urlInfo.path);
} else {
return request.get(requestOptions);
}
})
.catch(function (err) {
if (err.message.match(/invalid uri/i)) {

View file

@ -7,7 +7,7 @@ module.exports = {
};
function install(settings, logger) {
logger.log(`installing ${settings.package}`);
logger.log(`Installing ${settings.package}`);
try {
fs.statSync(settings.pluginPath);

View file

@ -1,9 +1,9 @@
var Promise = require('bluebird');
/*
Responsible for reporting the progress of the file request stream
Responsible for reporting the progress of the file stream
*/
module.exports = function (logger, request) {
module.exports = function (logger, stream) {
var oldDotCount = 0;
var runningTotal = 0;
var totalSize = 0;
@ -22,7 +22,7 @@ module.exports = function (logger, request) {
if (err) logger.error(err);
hasError = true;
request.abort();
if (stream.abort) stream.abort();
_reject(new Error(errorMessage));
}
@ -56,7 +56,7 @@ module.exports = function (logger, request) {
function handleEnd() {
if (hasError) return;
logger.log('Download Complete');
logger.log('Extraction complete');
_resolve();
}

View file

@ -86,7 +86,11 @@ module.exports = function (program) {
let set = _.partial(_.set, settings);
let get = _.partial(_.get, settings);
if (opts.dev) set('env', 'development');
if (opts.dev) {
set('env', 'development');
set('optimize.lazy', true);
}
if (opts.elasticsearch) set('elasticsearch.url', opts.elasticsearch);
if (opts.port) set('server.port', opts.port);
if (opts.host) set('server.host', opts.host);

View file

@ -1,20 +0,0 @@
/**
* Optimized application entry file
*
* This is programatically created and updated, do not modify
*
* built using: <%= optimizerTagline %>
* includes code from:
<%
entry.deps.sort().forEach(function (plugin) {
print(` * - ${plugin}\n`);
})
%> *
*/
require('ui/testHarness');
<%
entry.modules.slice(0).reverse().forEach(function (id) {
print(`require('${id.replace(/\\/g, '\\\\')}');\n`);
});
%>require('ui/testHarness').bootstrap();

View file

@ -23,7 +23,8 @@ module.exports = function (kibana) {
cert: Joi.string(),
key: Joi.string()
}).default(),
minimumVerison: Joi.string().default('1.4.4')
apiVersion: Joi.string().default('master'),
minimumVerison: Joi.string().default('2.0.0')
}).default();
},

View file

@ -13,6 +13,7 @@ describe('plugins/elasticsearch', function () {
var get = sinon.stub().withArgs('elasticserach.minimumVerison').returns('1.4.3');
var config = function () { return { get: get }; };
server = {
log: _.noop,
config: config,
plugins: {
elasticsearch: {

View file

@ -4,6 +4,8 @@ var versionMath = require('./version_math');
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.minimumVerison');
@ -31,7 +33,6 @@ module.exports = function (server) {
`${minimumElasticsearchVersion} or higher on all nodes. I found ` +
`the following incompatible nodes in your cluster: ${badNodeNames.join(',')}`;
server.plugins.elasticsearch.status.red(message);
throw new SetupError(server, message);
});
};

View file

@ -13,6 +13,7 @@ module.exports = function (server) {
var clientCrt = config.get('elasticsearch.ssl.cert');
var clientKey = config.get('elasticsearch.ssl.key');
var ca = config.get('elasticsearch.ssl.ca');
var apiVersion = config.get('elasticsearch.apiVersion');
if (username && password) {
uri.auth = util.format('%s:%s', username, password);
@ -30,7 +31,7 @@ module.exports = function (server) {
var client = new elasticsearch.Client({
host: url.format(uri),
ssl: ssl,
apiVersion: '1.4',
apiVersion: apiVersion,
log: function () {
this.error = function (err) {
server.log(['error', 'elasticsearch'], err);

View file

@ -15,7 +15,6 @@ module.exports = function (plugin, server) {
plugin.status.yellow('Waiting for Elasticsearch');
function waitForPong() {
return client.ping({ requestTimeout: 1500 }).catch(function (err) {
if (!(err instanceof NoConnections)) throw err;
@ -54,13 +53,12 @@ module.exports = function (plugin, server) {
function check() {
return waitForPong()
.then(_.partial(checkEsVersion, server, plugin))
.then(_.partial(checkEsVersion, server))
.then(waitForShards)
.then(_.partial(migrateConfig, server))
.catch(_.bindKey(server, 'log', 'error'));
.catch(err => plugin.status.red(err));
}
var timeoutId = null;
function scheduleCheck(ms) {

View file

@ -19,7 +19,7 @@
<i aria-hidden="true" class="fa fa-chain-broken"></i> Unlinked!
</div>
<form ng-if="vis.type.requiresSearch && $state.linked" class="inline-form" name="queryInput">
<form ng-if="vis.type.requiresSearch && $state.linked" class="inline-form fill" name="queryInput">
<div class="typeahead">
<div class="input-group">
<input
@ -39,7 +39,7 @@
<form
ng-if="vis.type.requiresSearch && !$state.linked"
ng-submit="fetch()"
class="inline-form"
class="inline-form fill"
name="queryInput">
<div class="typeahead" kbn-typeahead="visualize">
<div class="input-group"

View file

@ -1,6 +1,4 @@
module.exports = (kibana) => {
if (!kibana.config.get('optimize.tests')) return;
let { union } = require('lodash');
let utils = require('requirefrom')('src/utils');

View file

@ -79,20 +79,12 @@ module.exports = Joi.object({
optimize: Joi.object({
enabled: Joi.boolean().default(true),
bundleFilter: Joi.string().when('tests', {
is: true,
then: Joi.default('tests'),
otherwise: Joi.default('*')
}),
bundleFilter: Joi.string().default('!tests'),
bundleDir: Joi.string().default(fromRoot('optimize/bundles')),
viewCaching: Joi.boolean().default(Joi.ref('$prod')),
lazy: Joi.boolean().when('$dev', {
is: true,
then: Joi.default(true),
otherwise: Joi.default(false)
}),
lazy: Joi.boolean().default(false),
lazyPort: Joi.number().default(5602),
lazyHost: Joi.string().hostname().default('0.0.0.0'),
lazyHost: Joi.string().hostname().default('localhost'),
lazyPrebuild: Joi.boolean().default(false),
lazyProxyTimeout: Joi.number().default(5 * 60000),
unsafeCache: Joi
@ -109,8 +101,7 @@ module.exports = Joi.object({
Joi.boolean()
)
.default(Joi.ref('$dev')),
profile: Joi.boolean().default(false),
tests: Joi.boolean().default(false),
profile: Joi.boolean().default(false)
}).default()
}).default();

View file

@ -13,7 +13,10 @@ class Status extends EventEmitter {
this.on('change', function (previous, previousMsg) {
this.since = new Date();
server.log(['status', name, 'info'], {
var tags = ['status', name];
tags.push(this.state === 'red' ? 'error' : 'info');
server.log(tags, {
tmpl: 'Status changed from <%= prevState %> to <%= state %><% message && print(` - ${message}`) %>',
name: name,
state: this.state,
@ -42,8 +45,14 @@ states.all.forEach(function (state) {
let previous = this.state;
let previousMsg = this.message;
this.state = state.id;
this.error = null;
this.message = message || state.title;
this.state = state.id;
if (message instanceof Error) {
this.error = message;
this.message = message.message;
}
if (previous === this.state && previousMsg === this.message) {
// noop

View file

@ -0,0 +1,25 @@
module.exports = function () {
const id = Symbol('id');
class RangeKey {
constructor(bucket) {
this.gte = bucket.from == null ? -Infinity : bucket.from;
this.lt = bucket.to == null ? +Infinity : bucket.to;
this[id] = RangeKey.idBucket(bucket);
}
static idBucket(bucket) {
return `from:${bucket.from},to:${bucket.to}`;
}
toString() {
return this[id];
}
}
return RangeKey;
};

View file

@ -2,7 +2,12 @@ define(function (require) {
var buildRangeFilter = require('ui/filter_manager/lib/range');
return function createRangeFilterProvider(Private) {
return function (aggConfig, key) {
return buildRangeFilter(aggConfig.params.field, key, aggConfig.vis.indexPattern);
return buildRangeFilter(
aggConfig.params.field,
key,
aggConfig.vis.indexPattern,
aggConfig.fieldFormatter()(key)
);
};
};
});

View file

@ -2,11 +2,12 @@ define(function (require) {
return function DateHistogramAggType(timefilter, config, Private) {
var _ = require('lodash');
var moment = require('moment');
var tzDetect = require('jstimezonedetect').jstz;
var BucketAggType = Private(require('ui/agg_types/buckets/_bucket_agg_type'));
var TimeBuckets = Private(require('ui/time_buckets'));
var createFilter = Private(require('ui/agg_types/buckets/create_filter/date_histogram'));
var intervalOptions = Private(require('ui/agg_types/buckets/_interval_options'));
var timeZone = tzDetect.determine().name();
var tzOffset = moment().format('Z');
function getInterval(agg) {
@ -93,7 +94,7 @@ define(function (require) {
var interval = agg.buckets.getInterval();
output.bucketInterval = interval;
output.params.interval = interval.expression;
output.params.time_zone = tzOffset;
output.params.time_zone = timeZone || tzOffset;
var scaleMetrics = interval.scaled && interval.scale < 1;
if (scaleMetrics) {

View file

@ -4,7 +4,10 @@ define(function (require) {
var BucketAggType = Private(require('ui/agg_types/buckets/_bucket_agg_type'));
var createFilter = Private(require('ui/agg_types/buckets/create_filter/range'));
var FieldFormat = Private(require('ui/index_patterns/_field_format/FieldFormat'));
var RangeKey = Private(require('./RangeKey'));
var keyCaches = new WeakMap();
var formats = new WeakMap();
return new BucketAggType({
name: 'range',
@ -14,22 +17,36 @@ define(function (require) {
return aggConfig.params.field.displayName + ' ranges';
},
getKey: function (bucket, key, agg) {
let range = { gte: bucket.from, lt: bucket.to };
var keys = keyCaches.get(agg);
if (range.gte == null) range.gte = -Infinity;
if (range.lt == null) range.lt = +Infinity;
if (!keys) {
keys = new Map();
keyCaches.set(agg, keys);
}
return range;
var id = RangeKey.idBucket(bucket);
var key = keys.get(id);
if (!key) {
key = new RangeKey(bucket);
keys.set(id, key);
}
return key;
},
getFormat: function (agg) {
if (agg.$$rangeAggTypeFormat) return agg.$$rangeAggTypeFormat;
let format = formats.get(agg);
if (format) return format;
var RangeFormat = FieldFormat.from(function (range) {
var format = agg.fieldOwnFormatter();
let RangeFormat = FieldFormat.from(function (range) {
let format = agg.fieldOwnFormatter();
return `${format(range.gte)} to ${format(range.lt)}`;
});
return (this.$$rangeAggTypeFormat = new RangeFormat());
format = new RangeFormat();
formats.set(agg, format);
return format;
},
params: [
{

View file

@ -22,7 +22,7 @@ module.exports = function (chrome, internals) {
}()))
.directive('kbnChrome', function ($rootScope) {
return {
compile: function ($el) {
template: function ($el) {
var $content = $(require('ui/chrome/chrome.html'));
var $app = $content.find('.application');
@ -35,7 +35,7 @@ module.exports = function (chrome, internals) {
$app.html(internals.rootTemplate);
}
$el.html($content);
return $content;
},
controllerAs: 'chrome',
controller: function ($scope, $rootScope, $location, $http) {

View file

@ -1,3 +1,5 @@
require('babel/polyfill');
var _ = require('lodash');
var $ = require('jquery');
var angular = require('angular');

View file

@ -1,14 +1,16 @@
<th width="1%"></th>
<th ng-if="indexPattern.timeFieldName">
<span>Time <i ng-class="headerClass(indexPattern.timeFieldName)" ng-click="sort(indexPattern.timeFieldName)" tooltip="Sort by time"></i></span>
</th>
<th ng-repeat="name in columns">
<span class="table-header-name">
{{name | shortDots}} <i ng-class="headerClass(name)" ng-click="sort(name)" tooltip="{{tooltip(name)}}" tooltip-append-to-body="1"></i>
</span>
<span class="table-header-move">
<i ng-click="toggleColumn(name)" ng-show="canRemove(name)" class="fa fa-remove" tooltip="Remove column" tooltip-append-to-body="1"></i>
<i ng-click="moveLeft(name)" class="fa fa-angle-double-left" ng-show="!$first" tooltip="Move column to the left" tooltip-append-to-body="1"></i>
<i ng-click="moveRight(name)" class="fa fa-angle-double-right" ng-show="!$last" tooltip="Move column to the right" tooltip-append-to-body="1"></i>
</span>
</th>
<tr>
<th width="1%"></th>
<th ng-if="indexPattern.timeFieldName">
<span>Time <i ng-class="headerClass(indexPattern.timeFieldName)" ng-click="sort(indexPattern.timeFieldName)" tooltip="Sort by time"></i></span>
</th>
<th ng-repeat="name in columns">
<span class="table-header-name">
{{name | shortDots}} <i ng-class="headerClass(name)" ng-click="sort(name)" tooltip="{{tooltip(name)}}" tooltip-append-to-body="1"></i>
</span>
<span class="table-header-move">
<i ng-click="toggleColumn(name)" ng-show="canRemove(name)" class="fa fa-remove" tooltip="Remove column" tooltip-append-to-body="1"></i>
<i ng-click="moveLeft(name)" class="fa fa-angle-double-left" ng-show="!$first" tooltip="Move column to the left" tooltip-append-to-body="1"></i>
<i ng-click="moveRight(name)" class="fa fa-angle-double-right" ng-show="!$last" tooltip="Move column to the right" tooltip-append-to-body="1"></i>
</span>
</th>
</tr>

View file

@ -2,7 +2,6 @@
doc-table {
overflow: auto;
margin: 5px;
flex: 1 1 100%;
.discover-table-datafield {

View file

@ -1,7 +1,8 @@
define(function (require) {
var _ = require('lodash');
var Notifier = require('ui/notify/Notifier');
return function EventsProvider(Private, Promise, Notifier) {
return function EventsProvider(Private, Promise) {
var SimpleEmitter = require('ui/utils/SimpleEmitter');
var notify = new Notifier({ location: 'EventEmitter' });

View file

@ -10,8 +10,14 @@ define(function () {
.get(filter.meta.index).then(function (indexPattern) {
key = filter.meta.field;
field = indexPattern.fields.byName[key];
value = filter.script.params.value;
value = field.format.convert(value);
if (filter.meta.formattedValue) {
value = filter.meta.formattedValue;
} else {
value = filter.script.params.value;
value = field.format.convert(value);
}
return { key: key, value: value };
});
}

View file

@ -1,7 +1,10 @@
define(function (require) {
var _ = require('lodash');
return function buildRangeFilter(field, params, indexPattern) {
var filter = { meta: { index: indexPattern.id} };
return function buildRangeFilter(field, params, indexPattern, formattedValue) {
var filter = { meta: { index: indexPattern.id } };
if (formattedValue) filter.meta.formattedValue = formattedValue;
params = _.clone(params);
if (params.gte && params.gt) throw new Error('gte and gt are mutually exclusive');
if (params.lte && params.lt) throw new Error('lte and lt are mutually exclusive');

View file

@ -0,0 +1,20 @@
describe('Date Format', function () {
var fieldFormats;
var expect = require('expect.js');
var ngMock = require('ngMock');
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
fieldFormats = Private(require('ui/registry/field_formats'));
}));
it('decoding an undefined or null date should return an empty string', function () {
var DateFormat = fieldFormats.getType('date');
var date = new DateFormat({
pattern: 'dd-MM-yyyy'
});
expect(date.convert(null)).to.be('');
expect(date.convert(undefined)).to.be('');
});
});

View file

@ -4,4 +4,5 @@ describe('Stringify Component', function () {
require('./_source');
require('./_string');
require('./_url');
require('./_date');
});

View file

@ -45,6 +45,9 @@ define(function (require) {
if (this._memoizedPattern !== pattern) {
this._memoizedPattern = pattern;
this._memoizedConverter = _.memoize(function converter(val) {
if (val === null || val === undefined) {
return '';
}
return moment(val).format(pattern);
});
}

View file

@ -23,11 +23,6 @@
}
}
// the element should take up an even share of available space
> .fill {
flex: 1 1 1%;
}
button {
padding: @padding-base-vertical @padding-base-horizontal;
font-size: @font-size-base;
@ -79,7 +74,7 @@
}
.inline-form {
.flex-parent();
.flex-parent(0, 0, auto);
display: flex;
> .typeahead {
@ -102,6 +97,11 @@
}
}
}
// the element should take up an even share of available space
> .fill {
flex: 1 1 1%;
}
}
.nav-controls .column {

View file

@ -16,12 +16,13 @@ define(function (require) {
throw new Error('OrderedXKeysUtilService expects an object');
}
var objKeys = getUniqKeys(obj);
var uniqKeys = getUniqKeys(obj);
var uniqKeysPairs = [...uniqKeys.entries()];
var interval = _.get(obj, 'ordered.interval');
var dateInterval = moment.isDuration(interval) ? interval : false;
return _(objKeys)
.pairs()
return _(uniqKeysPairs)
.sortBy(function (d) {
if (d[1].isDate || d[1].isOrdered) {
return +d[0];

View file

@ -17,7 +17,7 @@ define(function (require) {
}
var flattenedData = flattenDataArray(obj);
var uniqueXValues = {};
var uniqueXValues = new Map();
var charts;
if (!obj.series) {
@ -36,7 +36,7 @@ define(function (require) {
flattenedData.forEach(function (d, i) {
var key = d.x;
var prev = uniqueXValues[key];
var prev = uniqueXValues.get(key);
if (d.xi != null) {
i = d.xi;
@ -46,12 +46,12 @@ define(function (require) {
i = Math.min(i, prev.index);
}
uniqueXValues[key] = {
uniqueXValues.set(key, {
index: i,
isDate: isDate,
isOrdered: isOrdered,
isNumber: _.isNumber(key)
};
});
});
return uniqueXValues;

View file

@ -14,6 +14,7 @@ module.exports = function (grunt) {
'_build:readme',
'_build:installNpmDeps',
'clean:testsFromModules',
'clean:deepModuleBins',
'clean:deepModules',
'run:optimizeBuild',
'stop:optimizeBuild',

View file

@ -4,6 +4,7 @@ module.exports = function (grunt) {
build: 'build',
target: 'target',
testsFromModules: 'build/kibana/node_modules/**/*test*/**',
deepModules: 'build/kibana/node_modules/*/node_modules/**/{' + modules.join(',') + '}/**'
deepModuleBins: 'build/kibana/node_modules/*/node_modules/**/.bin/{' + modules.join(',') + '}',
deepModules: 'build/kibana/node_modules/*/node_modules/**/{' + modules.join(',') + '}/',
};
};

View file

@ -1,12 +1,44 @@
module.exports = function (grunt) {
return {
unit: {
configFile: 'karma.conf.js',
singleRun: true,
reporters: 'dots',
browsers: [
'<%= karmaBrowser %>'
]
}
options: {
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
captureTimeout: 30000,
browserNoActivityTimeout: 120000,
frameworks: ['mocha'],
port: 9876,
colors: true,
logLevel: grunt.option('debug') || grunt.option('verbose') ? 'DEBUG' : 'INFO',
autoWatch: false,
browsers: ['<%= karmaBrowser %>'],
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'growl'],
// list of files / patterns to load in the browser
files: [
'http://localhost:5610/bundles/commons.bundle.js',
'http://localhost:5610/bundles/tests.bundle.js',
'http://localhost:5610/bundles/commons.style.css',
'http://localhost:5610/bundles/tests.style.css'
],
proxies: {
'/tests/': 'http://localhost:5610/tests/',
'/bundles/': 'http://localhost:5610/bundles/'
},
client: {
mocha: {
reporter: 'html', // change Karma's debug.html to the mocha web reporter
timeout: 10000,
slow: 5000
}
}
},
dev: { singleRun: false },
unit: { singleRun: true },
};
};

View file

@ -12,10 +12,31 @@ module.exports = function (grunt) {
},
cmd: './bin/kibana',
args: [
'--server.port=5610',
'--env.name=development',
'--logging.json=false',
'--optimize.tests=true',
'--optimize.lazy=false'
'--optimize.bundleFilter=tests',
'--plugins.initialize=false'
]
},
devTestServer: {
options: {
wait: false,
ready: /Server running/,
quiet: false,
failOnError: false
},
cmd: './bin/kibana',
args: [
'--dev',
'--no-watch',
'--server.port=5610',
'--optimize.lazyPort=5611',
'--optimize.lazyPrebuild=true',
'--logging.json=false',
'--optimize.bundleFilter=tests',
'--plugins.initialize=false'
]
},

View file

@ -1,8 +1,10 @@
module.exports = function (grunt) {
let { compact } = require('lodash');
grunt.registerTask('jenkins', 'Jenkins build script', [
grunt.registerTask('jenkins', 'Jenkins build script', compact([
'esvm:dev',
'test'
]);
'test',
process.env.JOB_NAME === 'kibana_core' ? 'build' : null
]));
};

View file

@ -1,58 +0,0 @@
module.exports = function (grunt) {
var maybeStartServer = function (options) {
return function () {
var http = require('http');
var opts = {
method: 'HEAD',
path: '/api/status',
host: 'localhost',
port: options.port
};
grunt.log.debug('checking for server', JSON.stringify(opts));
var req = http.request(opts);
var done = (function (cb) {
return function (res) {
req.removeListener('error', onError);
req.removeListener('response', onResponse);
if (res) res.socket.destroy();
cb();
};
}(this.async()));
function onResponse(res) {
grunt.log.debug('Server responded with', res.statusCode);
if (res.statusCode === 200 && res.headers['x-app-name'] === 'kibana') {
grunt.log.ok('Kibana server already started on port', options.port);
} else {
grunt.log.error('Another server is already running on port', options.port);
process.exit(1); // eslint-disable-line no-process-exit
}
done(res);
}
function onError(err) {
if (err.code !== 'ECONNREFUSED') {
grunt.log.error('Kibana server check failed', err);
}
grunt.config.set(options.name, true);
grunt.task.run(options.tasks);
done();
}
req.on('error', onError);
req.on('response', onResponse);
req.end();
};
};
grunt.registerTask('maybeStartTestServer', maybeStartServer({
name: 'kibana-server',
port: grunt.option('port') || 5601,
tasks: ['run:testServer']
}));
};

View file

@ -1,29 +1,26 @@
var _ = require('lodash');
module.exports = function (grunt) {
grunt.registerTask('test', function () {
if (grunt.option('quick')) {
grunt.task.run('quick-test');
return;
}
grunt.registerTask('test:server', [ 'simplemocha:all' ]);
grunt.registerTask('test:browser', [ 'run:testServer', 'karma:unit' ]);
grunt.registerTask('test:quick', [
'test:server',
'test:browser'
]);
grunt.registerTask('test:dev', [
'run:devTestServer',
'karma:dev'
]);
grunt.registerTask('test', function (subTask) {
if (subTask) grunt.fail.fatal(`invalid task "test:${subTask}"`);
grunt.task.run(_.compact([
'eslint:source',
'simplemocha:all',
'maybeStartTestServer',
'karma:unit'
!grunt.option('quick') && 'eslint:source',
'test:quick'
]));
});
grunt.registerTask('quick-test', function () {
grunt.task.run([
'simplemocha:all',
'maybeStartTestServer',
'karma:unit'
]);
});
grunt.registerTask('test:watch', [
'maybeStartTestServer',
'watch:test'
]);
grunt.registerTask('quick-test', ['test:quick']); // historical alias
};