diff --git a/src/plugins/elasticsearch/index.js b/src/plugins/elasticsearch/index.js index 76a44285d9ef8..c60f56f88b6df 100644 --- a/src/plugins/elasticsearch/index.js +++ b/src/plugins/elasticsearch/index.js @@ -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'); diff --git a/src/plugins/elasticsearch/lib/__tests__/routes.js b/src/plugins/elasticsearch/lib/__tests__/routes.js index c4bacca30d553..34a75f6a29814 100644 --- a/src/plugins/elasticsearch/lib/__tests__/routes.js +++ b/src/plugins/elasticsearch/lib/__tests__/routes.js @@ -1,9 +1,8 @@ import expect from 'expect.js'; -import util from 'util'; -import * as kbnTestServer from '../../../../../test/utils/kbn_server'; - -const format = util.format; +import { format } from 'util'; +import * as kbnTestServer from '../../../../../test/utils/kbn_server'; +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()); }); diff --git a/src/plugins/kibana/index.js b/src/plugins/kibana/index.js index 88ab65eb3d38a..f64fbaf4ed80d 100644 --- a/src/plugins/kibana/index.js +++ b/src/plugins/kibana/index.js @@ -34,7 +34,13 @@ module.exports = function (kibana) { kbnDefaultAppId: config.get('kibana.defaultAppId') }; } - } + }, + + injectDefaultVars(server, options) { + return { + kbnIndex: options.index + }; + }, }, init: function (server, options) { diff --git a/src/server/plugins/plugin.js b/src/server/plugins/plugin.js index 2b504142a86ce..18c7786c15720 100644 --- a/src/server/plugins/plugin.js +++ b/src/server/plugins/plugin.js @@ -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; } diff --git a/src/ui/__tests__/fixtures/plugin_async_foo/index.js b/src/ui/__tests__/fixtures/plugin_async_foo/index.js new file mode 100644 index 0000000000000..a0cd8887bbed0 --- /dev/null +++ b/src/ui/__tests__/fixtures/plugin_async_foo/index.js @@ -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 }; + } + } +}); diff --git a/src/ui/__tests__/fixtures/plugin_async_foo/package.json b/src/ui/__tests__/fixtures/plugin_async_foo/package.json new file mode 100644 index 0000000000000..4ad7dda995ca5 --- /dev/null +++ b/src/ui/__tests__/fixtures/plugin_async_foo/package.json @@ -0,0 +1,4 @@ +{ + "name": "plugin_async_foo", + "version": "0.0.0" +} diff --git a/src/ui/__tests__/fixtures/plugin_bar/index.js b/src/ui/__tests__/fixtures/plugin_bar/index.js new file mode 100644 index 0000000000000..59c5556444496 --- /dev/null +++ b/src/ui/__tests__/fixtures/plugin_bar/index.js @@ -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 }; + } + } +}); diff --git a/src/ui/__tests__/fixtures/plugin_bar/package.json b/src/ui/__tests__/fixtures/plugin_bar/package.json new file mode 100644 index 0000000000000..bfc5da0182266 --- /dev/null +++ b/src/ui/__tests__/fixtures/plugin_bar/package.json @@ -0,0 +1,4 @@ +{ + "name": "plugin_bar", + "version": "0.0.0" +} diff --git a/src/ui/__tests__/fixtures/plugin_foo/index.js b/src/ui/__tests__/fixtures/plugin_foo/index.js new file mode 100644 index 0000000000000..59c5556444496 --- /dev/null +++ b/src/ui/__tests__/fixtures/plugin_foo/index.js @@ -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 }; + } + } +}); diff --git a/src/ui/__tests__/fixtures/plugin_foo/package.json b/src/ui/__tests__/fixtures/plugin_foo/package.json new file mode 100644 index 0000000000000..6a73b89110ae9 --- /dev/null +++ b/src/ui/__tests__/fixtures/plugin_foo/package.json @@ -0,0 +1,4 @@ +{ + "name": "plugin_foo", + "version": "0.0.0" +} diff --git a/src/ui/__tests__/ui_exports.js b/src/ui/__tests__/ui_exports.js index 678d88d839835..adf561822e947 100644 --- a/src/ui/__tests__/ui_exports.js +++ b/src/ui/__tests__/ui_exports.js @@ -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' + }); + }); + }); + }); }); diff --git a/src/ui/index.js b/src/ui/index.js index 6dd0407323367..b6a8350f54323 100644 --- a/src/ui/index.js +++ b/src/ui/index.js @@ -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, { diff --git a/src/ui/ui_exports.js b/src/ui/ui_exports.js index da5edef1a8f03..5048d576e989a 100644 --- a/src/ui/ui_exports.js +++ b/src/ui/ui_exports.js @@ -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); } }; @@ -80,6 +87,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)); + }); + }; } } diff --git a/test/utils/kbn_server.js b/test/utils/kbn_server.js index 6f854283e8b0b..52e243142465b 100644 --- a/test/utils/kbn_server.js +++ b/test/utils/kbn_server.js @@ -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 },