Merge branch 'master' into feature/design

This commit is contained in:
Rashid Khan 2016-03-21 12:03:06 -07:00
commit cf838fff96
22 changed files with 265 additions and 51 deletions

View file

@ -128,7 +128,7 @@ Runs both server and browser tests, but skips linting
Run only the server tests
`npm run test:browser`
Run only the browser tests
Run only the browser tests. Coverage reports are available for browser tests by running `npm run test:coverage`. You can find the results under the `coverage/` directory that will be created upon completion.
`npm run test:dev`
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.

View file

@ -156,7 +156,7 @@
"grunt-cli": "0.1.13",
"grunt-contrib-clean": "0.6.0",
"grunt-contrib-copy": "0.8.1",
"grunt-esvm": "3.0.3",
"grunt-esvm": "3.0.4",
"grunt-karma": "0.12.0",
"grunt-run": "0.5.0",
"grunt-s3": "0.2.0-alpha.3",

View file

@ -33,6 +33,16 @@ module.exports = function ({ Plugin }) {
}).default();
},
uiExports: {
injectDefaultVars(server, options) {
return {
esRequestTimeout: options.requestTimeout,
esShardTimeout: options.shardTimeout,
esApiVersion: options.apiVersion,
};
}
},
init(server, options) {
const kibanaIndex = server.config().get('kibana.index');

View file

@ -1,9 +1,8 @@
import expect from 'expect.js';
import util from 'util';
import { format } from 'util';
import * as kbnTestServer from '../../../../../test/utils/kbn_server';
const format = util.format;
import fromRoot from '../../../../utils/from_root';
describe('plugins/elasticsearch', function () {
describe('routes', function () {
@ -13,7 +12,13 @@ describe('plugins/elasticsearch', function () {
before(function () {
this.timeout(60000); // sometimes waiting for server takes longer than 10
kbnServer = kbnTestServer.createServer();
kbnServer = kbnTestServer.createServer({
plugins: {
scanDirs: [
fromRoot('src/plugins')
]
}
});
return kbnServer.ready()
.then(() => kbnServer.server.plugins.elasticsearch.waitUntilReady());
});

View file

@ -67,7 +67,12 @@ module.exports = function (kibana) {
description: 'define index patterns, change config, and more',
icon: 'plugins/kibana/assets/settings.svg',
}
]
],
injectDefaultVars(server, options) {
return {
kbnIndex: options.index
};
},
},
init: function (server, options) {

View file

@ -39,21 +39,21 @@ describe('createMappingsFromPatternFields', function () {
let mappings = createMappingsFromPatternFields(testFields);
_.forEach(mappings, function (mapping) {
if (mapping.type !== 'string') {
if (mapping.type !== 'text') {
expect(_.isEqual(mapping, {
type: mapping.type,
index: 'not_analyzed',
index: true,
doc_values: true
})).to.be.ok();
}
});
});
it('should give strings a multi-field mapping', function () {
it('should give strings a multi-field mapping with a "text" base type', function () {
let mappings = createMappingsFromPatternFields(testFields);
_.forEach(mappings, function (mapping) {
if (mapping.type === 'string') {
if (mapping.type === 'text') {
expect(mapping).to.have.property('fields');
}
});
@ -68,7 +68,7 @@ describe('createMappingsFromPatternFields', function () {
expect(mappings.geo.properties).to.have.property('coordinates');
expect(_.isEqual(mappings.geo.properties.coordinates, {
type: 'geo_point',
index: 'not_analyzed',
index: true,
doc_values: true
})).to.be.ok();
});

View file

@ -13,10 +13,9 @@ module.exports = function createMappingsFromPatternFields(fields) {
if (field.type === 'string') {
mapping = {
type: 'string',
index: 'analyzed',
type: 'text',
fields: {
raw: {type: 'string', index: 'not_analyzed', doc_values: true, ignore_above: 256}
raw: {type: 'keyword', ignore_above: 256}
}
};
}
@ -24,7 +23,7 @@ module.exports = function createMappingsFromPatternFields(fields) {
const fieldType = field.type === 'number' ? 'double' : field.type;
mapping = {
type: fieldType,
index: 'not_analyzed',
index: true,
doc_values: true
};
}

View file

@ -58,10 +58,9 @@ module.exports = function registerPost(server) {
match: '*',
match_mapping_type: 'string',
mapping: {
type: 'string',
index: 'analyzed',
type: 'text',
fields: {
raw: {type: 'string', index: 'not_analyzed', doc_values: true, ignore_above: 256}
raw: {type: 'keyword', ignore_above: 256}
}
}
}

View file

@ -1,9 +1,11 @@
import _ from 'lodash';
import Joi from 'joi';
import { attempt, fromNode } from 'bluebird';
import Bluebird, { attempt, fromNode } from 'bluebird';
import { basename, resolve } from 'path';
import { inherits } from 'util';
const extendInitFns = Symbol('extend plugin initialization');
const defaultConfigSchema = Joi.object({
enabled: Joi.boolean().default(true)
}).default();
@ -57,6 +59,7 @@ module.exports = class Plugin {
this.externalInit = opts.init || _.noop;
this.getConfigSchema = opts.config || _.noop;
this.init = _.once(this.init);
this[extendInitFns] = [];
if (opts.publicDir === false) {
this.publicDir = null;
@ -98,14 +101,12 @@ module.exports = class Plugin {
let { config } = kbnServer;
// setup the hapi register function and get on with it
let register = (server, options, next) => {
const asyncRegister = async (server, options) => {
this.server = server;
// bind the server and options to all
// apps created by this plugin
for (let app of this.apps) {
app.getInjectedVars = _.partial(app.getInjectedVars, server, options);
}
await Promise.all(this[extendInitFns].map(async fn => {
await fn.call(this, server, options);
}));
server.log(['plugins', 'debug'], {
tmpl: 'Initializing plugin <%= plugin.id %>',
@ -119,7 +120,11 @@ module.exports = class Plugin {
this.status = kbnServer.status.create(`plugin:${this.id}`);
server.expose('status', this.status);
attempt(this.externalInit, [server, options], this).nodeify(next);
return await attempt(this.externalInit, [server, options], this);
};
const register = (server, options, next) => {
Bluebird.resolve(asyncRegister(server, options)).nodeify(next);
};
register.attributes = { name: id, version: version };
@ -138,6 +143,10 @@ module.exports = class Plugin {
}
}
extendInit(fn) {
this[extendInitFns].push(fn);
}
toJSON() {
return this.pkg;
}

View file

@ -0,0 +1,18 @@
import Bluebird from 'bluebird';
export default kibana => new kibana.Plugin({
config(Joi) {
return Joi.object().keys({
enabled: Joi.boolean().default(true),
delay: Joi.number().required(),
shared: Joi.string(),
}).default();
},
uiExports: {
async injectDefaultVars(server, options) {
await Bluebird.delay(options.delay);
return { shared: options.shared };
}
}
});

View file

@ -0,0 +1,4 @@
{
"name": "plugin_async_foo",
"version": "0.0.0"
}

View file

@ -0,0 +1,14 @@
export default kibana => new kibana.Plugin({
config(Joi) {
return Joi.object().keys({
enabled: Joi.boolean().default(true),
shared: Joi.string()
}).default();
},
uiExports: {
injectDefaultVars(server, options) {
return { shared: options.shared };
}
}
});

View file

@ -0,0 +1,4 @@
{
"name": "plugin_bar",
"version": "0.0.0"
}

View file

@ -0,0 +1,14 @@
export default kibana => new kibana.Plugin({
config(Joi) {
return Joi.object().keys({
enabled: Joi.boolean().default(true),
shared: Joi.string()
}).default();
},
uiExports: {
injectDefaultVars(server, options) {
return { shared: options.shared };
}
}
});

View file

@ -0,0 +1,4 @@
{
"name": "plugin_foo",
"version": "0.0.0"
}

View file

@ -1,6 +1,8 @@
import expect from 'expect.js';
import { resolve } from 'path';
import UiExports from '../ui_exports';
import * as kbnTestServer from '../../../test/utils/kbn_server';
describe('UiExports', function () {
describe('#find()', function () {
@ -23,4 +25,83 @@ describe('UiExports', function () {
expect(uiExports.find(['foo', 'bar'])).to.eql(['a', 'b', 'c']);
});
});
//
describe('#defaultInjectedVars', function () {
context('two plugins, two sync', function () {
this.slow(10000);
this.timeout(60000);
let kbnServer;
before(async function () {
kbnServer = kbnTestServer.createServer({
plugins: {
paths: [
resolve(__dirname, 'fixtures/plugin_bar'),
resolve(__dirname, 'fixtures/plugin_foo')
]
},
plugin_foo: {
shared: 'foo'
},
plugin_bar: {
shared: 'bar'
}
});
await kbnServer.ready();
});
after(async function () {
await kbnServer.close();
});
it('merges the two plugins in the order they are loaded', function () {
expect(kbnServer.uiExports.defaultInjectedVars).to.eql({
shared: 'foo'
});
});
});
context('two plugins, one async', function () {
this.slow(10000);
this.timeout(60000);
let kbnServer;
before(async function () {
kbnServer = kbnTestServer.createServer({
plugins: {
scanDirs: [],
paths: [
resolve(__dirname, 'fixtures/plugin_async_foo'),
resolve(__dirname, 'fixtures/plugin_foo')
]
},
plugin_async_foo: {
delay: 500,
shared: 'foo'
},
plugin_bar: {
shared: 'bar'
}
});
await kbnServer.ready();
});
after(async function () {
await kbnServer.close();
});
it('merges the two plugins in the order they are loaded', function () {
// even though plugin_async_foo loads 500ms later, it is still "first" to merge
expect(kbnServer.uiExports.defaultInjectedVars).to.eql({
shared: 'foo'
});
});
});
});
});

View file

@ -59,16 +59,6 @@ module.exports = async (kbnServer, server, config) => {
}
});
const defaultInjectedVars = {};
if (config.has('kibana')) {
defaultInjectedVars.kbnIndex = config.get('kibana.index');
}
if (config.has('elasticsearch')) {
defaultInjectedVars.esRequestTimeout = config.get('elasticsearch.requestTimeout');
defaultInjectedVars.esShardTimeout = config.get('elasticsearch.shardTimeout');
defaultInjectedVars.esApiVersion = config.get('elasticsearch.apiVersion');
}
server.decorate('reply', 'renderApp', function (app) {
const payload = {
app: app,
@ -77,7 +67,7 @@ module.exports = async (kbnServer, server, config) => {
buildNum: config.get('pkg.buildNum'),
buildSha: config.get('pkg.buildSha'),
basePath: config.get('server.basePath'),
vars: defaults(app.getInjectedVars() || {}, defaultInjectedVars),
vars: defaults(app.getInjectedVars() || {}, uiExports.defaultInjectedVars),
};
return this.view(app.templateName, {

View file

@ -17,4 +17,14 @@ describe('String Format', function () {
expect(string.convert('Zm9vYmFy')).to.be('foobar');
});
it('convert a string to title case', function () {
var StringFormat = fieldFormats.getType('string');
var string = new StringFormat({
transform: 'title'
});
expect(string.convert('PLEASE DO NOT SHOUT')).to.be('Please Do Not Shout');
expect(string.convert('Mean, variance and standard_deviation.')).to.be('Mean, Variance And Standard_deviation.');
expect(string.convert('Stay CALM!')).to.be('Stay Calm!');
});
});

View file

@ -36,12 +36,14 @@ export default function StringFormatProvider(Private) {
{ id: false, name: '- none -' },
{ id: 'lower', name: 'Lower Case' },
{ id: 'upper', name: 'Upper Case' },
{ id: 'title', name: 'Title Case' },
{ id: 'short', name: 'Short Dots' },
{ id: 'base64', name: 'Base64 Decode'}
];
_String.sampleInputs = [
'A Quick Brown Fox.',
'STAY CALM!',
'com.organizations.project.ClassName',
'hostname.net',
'SGVsbG8gd29ybGQ='
@ -55,10 +57,15 @@ export default function StringFormatProvider(Private) {
}
};
_String.prototype._toTitleCase = function (val) {
return val.replace(/\w\S*/g, txt => { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); });
};
_String.prototype._convert = function (val) {
switch (this.param('transform')) {
case 'lower': return String(val).toLowerCase();
case 'upper': return String(val).toUpperCase();
case 'title': return this._toTitleCase(val);
case 'short': return _.shortenDottedString(val);
case 'base64': return this._base64Decode(val);
default: return _.asPrettyString(val);

View file

@ -13,6 +13,7 @@ class UiExports {
this.exportConsumer = _.memoize(this.exportConsumer);
this.consumers = [];
this.bundleProviders = [];
this.defaultInjectedVars = {};
}
consumePlugin(plugin) {
@ -52,7 +53,17 @@ class UiExports {
return (plugin, specs) => {
const id = plugin.id;
for (let spec of [].concat(specs || [])) {
const app = this.apps.new({ id, ...spec });
let app = this.apps.new(_.defaults({}, spec, {
id: plugin.id,
urlBasePath: this.urlBasePath
}));
plugin.extendInit((server, options) => { // eslint-disable-line no-loop-func
const wrapped = app.getInjectedVars;
app.getInjectedVars = () => wrapped.call(plugin, server, options);
});
plugin.apps.add(app);
}
};
@ -88,6 +99,13 @@ class UiExports {
this.aliases[adhocType] = _.union(this.aliases[adhocType] || [], spec);
});
};
case 'injectDefaultVars':
return (plugin, injector) => {
plugin.extendInit(async (server, options) => {
_.merge(this.defaultInjectedVars, await injector.call(plugin, server, options));
});
};
}
}

View file

@ -12,7 +12,12 @@ define(function (require) {
});
bdd.afterEach(function () {
return request.del('/kibana/ingest/logstash-*');
return request.del('/kibana/ingest/logstash-*')
.then(function () {
return scenarioManager.client.indices.delete({
index: 'logstash-*'
});
});
});
bdd.it('should return 400 for an invalid payload', function invalidPayload() {
@ -57,6 +62,29 @@ define(function (require) {
});
});
bdd.it('should successfully create new indices based on the template', function newIndices() {
return request.post('/kibana/ingest')
.send(createTestData())
.expect(204)
.then(function () {
return scenarioManager.client.create({
index: 'logstash-1',
type: 'foo',
id: '1',
body: {
ip: '192.168.1.1',
'@timestamp': '2015-09-20T10:28:22.684Z',
agent: 'Jack',
bytes: 9001,
geo: {coordinates: {lat: 43.07260861, lon: -92.61077833}}
}
})
.then(function (response) {
expect(response.created).to.be.ok();
});
});
});
bdd.it('should provide defaults for field properties', function createTemplate() {
return request.post('/kibana/ingest')
.send(createTestData())
@ -92,15 +120,15 @@ define(function (require) {
.then(function (template) {
var mappings = template['kibana-logstash-*'].mappings._default_.properties;
expect(mappings).to.be.ok();
expect(_.isEqual(mappings.ip, {index: 'not_analyzed', type: 'ip', doc_values: true})).to.be.ok();
expect(_.isEqual(mappings['@timestamp'], {index: 'not_analyzed', type: 'date', doc_values: true})).to.be.ok();
expect(_.isEqual(mappings.bytes, {index: 'not_analyzed', type: 'double', doc_values: true})).to.be.ok();
expect(_.isEqual(mappings.ip, {index: true, type: 'ip', doc_values: true})).to.be.ok();
expect(_.isEqual(mappings['@timestamp'], {index: true, type: 'date', doc_values: true})).to.be.ok();
expect(_.isEqual(mappings.bytes, {index: true, type: 'double', doc_values: true})).to.be.ok();
// object fields are mapped as such, with individual mappings for each of their properties
expect(_.isEqual(mappings.geo, {
properties: {
coordinates: {
index: 'not_analyzed',
index: true,
type: 'geo_point',
doc_values: true
}

View file

@ -3,7 +3,6 @@ import { defaultsDeep, set } from 'lodash';
import { header as basicAuthHeader } from './base_auth';
import { kibanaUser, kibanaServer } from '../shield';
import KbnServer from '../../src/server/kbn_server';
import fromRoot from '../../src/utils/from_root';
import serverConfig from '../server_config';
const SERVER_DEFAULTS = {
@ -16,11 +15,7 @@ const SERVER_DEFAULTS = {
logging: {
quiet: true
},
plugins: {
scanDirs: [
fromRoot('src/plugins')
]
},
plugins: {},
optimize: {
enabled: false
},