[uiExports] add uiExport type "injectDefaultVars"

In order for plugins to inject default injected vars, they should define an injectVars uiExport that will be called once in order to build the default injected vars. This function should return an object with the injectedVars it wishes to add. Plugins are run in a non-deterministic order, so they should not be written to override each other. injectedVars should all have unique names. The injectedVars for the app will override any values provided by default injectedVars functions.

[server/plugins/init] allow extendRegister() fns to be async

[server/plugins/init] convert async promise to bluebird

[uiExports] execute default injectedVars functions early

[server/plugin] do not leak server.register() implemenation detail

[test/utils/kbnServer] opt-in to plugin loading

[uiExports] test injectDefaultVars()
This commit is contained in:
Joe Fleming 2016-06-02 14:15:47 -07:00
parent f5094b9f0a
commit ea603d033e
15 changed files with 205 additions and 36 deletions

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,10 +1,7 @@
const expect = require('expect.js');
const util = require('util');
const requireFromTest = require('requirefrom')('test');
const kbnTestServer = requireFromTest('utils/kbn_server');
const format = util.format;
const { format } = require('util');
const kbnTestServer = require('../../../../../test/utils/kbn_server');
const fromRoot = require('../../../../utils/fromRoot');
describe('plugins/elasticsearch', function () {
describe('routes', function () {
@ -14,7 +11,13 @@ describe('plugins/elasticsearch', function () {
before(function () {
this.timeout(15000); // 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

@ -41,7 +41,13 @@ module.exports = function (kibana) {
kbnDefaultAppId: config.get('kibana.defaultAppId')
};
}
}
},
injectDefaultVars(server, options) {
return {
kbnIndex: options.index
};
},
}
});

View file

@ -16,6 +16,7 @@ define(function (require) {
// This is the only thing that gets injected into controllers
module.service('savedDashboards', function (Promise, SavedDashboard, kbnIndex, es, kbnUrl) {
console.log('savedDashboards', kbnIndex);
const scanner = new Scanner(es, {
index: kbnIndex,
type: 'dashboard'

View file

@ -1,9 +1,11 @@
let _ = require('lodash');
let Joi = require('joi');
let { attempt, fromNode } = require('bluebird');
let Bluebird = require('bluebird');
let { resolve } = require('path');
let { inherits } = require('util');
const extendInitFns = Symbol('extend plugin initialization');
const defaultConfigSchema = Joi.object({
enabled: Joi.boolean().default(true)
}).default();
@ -23,6 +25,7 @@ module.exports = class Plugin {
this.externalInit = opts.init || _.noop;
this.getConfigSchema = opts.config || _.noop;
this.init = _.once(this.init);
this[extendInitFns] = [];
}
static scoped(kbnServer, path, pkg) {
@ -51,14 +54,14 @@ 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, next) => {
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.toString() %>',
@ -72,12 +75,16 @@ module.exports = class Plugin {
this.status = kbnServer.status.create(this);
server.expose('status', this.status);
attempt(this.externalInit, [server, options], this).nodeify(next);
return await Bluebird.attempt(this.externalInit, [server, options], this);
};
const register = (server, options, next) => {
Bluebird.resolve(asyncRegister(server, options)).nodeify(next);
};
register.attributes = { name: id, version: version };
await fromNode(cb => {
await Bluebird.fromNode(cb => {
kbnServer.server.register({
register: register,
options: config.has(id) ? config.get(id) : null
@ -91,6 +98,10 @@ module.exports = class Plugin {
}
}
extendInit(fn) {
this[extendInitFns].push(fn);
}
toJSON() {
return this.pkg;
}

View file

@ -11,6 +11,7 @@ class UiExports {
this.exportConsumer = _.memoize(this.exportConsumer);
this.consumers = [];
this.bundleProviders = [];
this.defaultInjectedVars = [];
}
consumePlugin(plugin) {
@ -53,6 +54,12 @@ class UiExports {
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);
}
};
@ -76,6 +83,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

@ -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,11 +1,13 @@
import expect from 'expect.js';
import { resolve } from 'path';
import UiExports from '../UiExports';
import UiExports from '../ui_exports';
import * as kbnTestServer from '../../../test/utils/kbn_server';
describe('UiExports', function () {
describe('#find()', function () {
it('finds exports based on the passed export names', function () {
let uiExports = new UiExports({});
var uiExports = new UiExports({});
uiExports.aliases.foo = ['a', 'b', 'c'];
uiExports.aliases.bar = ['d', 'e', 'f'];
@ -15,7 +17,7 @@ describe('UiExports', function () {
});
it('allows query types that match nothing', function () {
let uiExports = new UiExports({});
var uiExports = new UiExports({});
uiExports.aliases.foo = ['a', 'b', 'c'];
expect(uiExports.find(['foo'])).to.eql(['a', 'b', 'c']);
@ -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

@ -5,7 +5,6 @@ import { kibanaUser, kibanaServer } from '../shield';
const src = requirefrom('src');
const KbnServer = src('server/KbnServer');
const fromRoot = src('utils/fromRoot');
const SERVER_DEFAULTS = {
server: {
@ -17,11 +16,7 @@ const SERVER_DEFAULTS = {
logging: {
quiet: true
},
plugins: {
scanDirs: [
fromRoot('src/plugins')
]
},
plugins: {},
optimize: {
enabled: false
},