mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Add a method for plugins to add injected vars to every app (#9071)
* [uiExports] add replaceInjectedVars() export type * [ui] do not assume es plugin is always there * [server/status] fix typo * [ui] add errror handling to /app/{id} endpoint * [ui] add tests for replaceInjectedVars() * [npm] swap out jsdom with cheerio * [server/ui] continue extender => replacer rename
This commit is contained in:
parent
f1717660a9
commit
dd46f75b6e
8 changed files with 171 additions and 11 deletions
|
@ -168,6 +168,7 @@
|
|||
"auto-release-sinon": "1.0.3",
|
||||
"babel-eslint": "4.1.8",
|
||||
"chai": "3.5.0",
|
||||
"cheerio": "0.22.0",
|
||||
"chokidar": "1.6.0",
|
||||
"chromedriver": "2.24.1",
|
||||
"elasticdump": "2.1.1",
|
||||
|
|
|
@ -45,7 +45,7 @@ exports.all = [
|
|||
severity: -1,
|
||||
icon: 'toggle-off',
|
||||
nicknames: [
|
||||
'I\'m I even a thing?'
|
||||
'Am I even a thing?'
|
||||
]
|
||||
}
|
||||
];
|
||||
|
|
13
src/ui/__tests__/fixtures/test_app/index.js
Normal file
13
src/ui/__tests__/fixtures/test_app/index.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
module.exports = kibana => new kibana.Plugin({
|
||||
uiExports: {
|
||||
app: {
|
||||
name: 'test_app',
|
||||
main: 'plugins/test_app/index.js',
|
||||
injectVars() {
|
||||
return {
|
||||
from_test_app: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
4
src/ui/__tests__/fixtures/test_app/package.json
Normal file
4
src/ui/__tests__/fixtures/test_app/package.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "test_app",
|
||||
"version": "kibana"
|
||||
}
|
0
src/ui/__tests__/fixtures/test_app/public/index.js
Normal file
0
src/ui/__tests__/fixtures/test_app/public/index.js
Normal file
125
src/ui/__tests__/ui_exports_replace_injected_vars.js
Normal file
125
src/ui/__tests__/ui_exports_replace_injected_vars.js
Normal file
|
@ -0,0 +1,125 @@
|
|||
import { resolve } from 'path';
|
||||
|
||||
import { delay } from 'bluebird';
|
||||
import expect from 'expect.js';
|
||||
import sinon from 'sinon';
|
||||
import cheerio from 'cheerio';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
import KbnServer from '../../server/kbn_server';
|
||||
|
||||
const getInjectedVarsFromResponse = (resp) => {
|
||||
const $ = cheerio.load(resp.payload);
|
||||
const data = $('kbn-initial-state').attr('data');
|
||||
return JSON.parse(data).vars;
|
||||
};
|
||||
|
||||
const injectReplacer = (kbnServer, replacer) => {
|
||||
// normally the replacer would be defined in a plugin's uiExports,
|
||||
// but that requires stubbing out an entire plugin directory for
|
||||
// each test, so we fake it and jam the replacer into uiExports
|
||||
kbnServer.uiExports.injectedVarsReplacers.push(replacer);
|
||||
};
|
||||
|
||||
describe('UiExports', function () {
|
||||
describe('#replaceInjectedVars', function () {
|
||||
this.slow(2000);
|
||||
this.timeout(10000);
|
||||
|
||||
let kbnServer;
|
||||
beforeEach(async () => {
|
||||
kbnServer = new KbnServer({
|
||||
server: { port: 0 }, // pick a random open port
|
||||
logging: { silent: true }, // no logs
|
||||
optimize: { enabled: false },
|
||||
uiSettings: { enabled: false },
|
||||
plugins: {
|
||||
paths: [resolve(__dirname, './fixtures/test_app')] // inject an app so we can hit /app/{id}
|
||||
},
|
||||
});
|
||||
|
||||
await kbnServer.ready();
|
||||
kbnServer.status.get('ui settings').state = 'green';
|
||||
kbnServer.server.decorate('server', 'uiSettings', () => {
|
||||
return { getDefaults: noop };
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await kbnServer.close();
|
||||
kbnServer = null;
|
||||
});
|
||||
|
||||
it('allows sync replacing of injected vars', async () => {
|
||||
injectReplacer(kbnServer, () => ({ a: 1 }));
|
||||
|
||||
const resp = await kbnServer.inject('/app/test_app');
|
||||
const injectedVars = getInjectedVarsFromResponse(resp);
|
||||
|
||||
expect(injectedVars).to.eql({ a: 1 });
|
||||
});
|
||||
|
||||
it('allows async replacing of injected vars', async () => {
|
||||
const asyncThing = () => delay(100).return('world');
|
||||
|
||||
injectReplacer(kbnServer, async () => {
|
||||
return {
|
||||
hello: await asyncThing()
|
||||
};
|
||||
});
|
||||
|
||||
const resp = await kbnServer.inject('/app/test_app');
|
||||
const injectedVars = getInjectedVarsFromResponse(resp);
|
||||
|
||||
expect(injectedVars).to.eql({
|
||||
hello: 'world'
|
||||
});
|
||||
});
|
||||
|
||||
it('passes originalInjectedVars, request, and server to replacer', async () => {
|
||||
const stub = sinon.stub();
|
||||
injectReplacer(kbnServer, () => ({ foo: 'bar' }));
|
||||
injectReplacer(kbnServer, stub);
|
||||
|
||||
await kbnServer.inject('/app/test_app');
|
||||
|
||||
sinon.assert.calledOnce(stub);
|
||||
expect(stub.firstCall.args[0]).to.eql({ foo: 'bar' }); // originalInjectedVars
|
||||
expect(stub.firstCall.args[1]).to.have.property('path', '/app/test_app'); // request
|
||||
expect(stub.firstCall.args[1]).to.have.property('server', kbnServer.server); // request
|
||||
expect(stub.firstCall.args[2]).to.be(kbnServer.server);
|
||||
});
|
||||
|
||||
it('calls the methods sequentially', async () => {
|
||||
injectReplacer(kbnServer, () => ({ name: '' }));
|
||||
injectReplacer(kbnServer, orig => ({ name: orig.name + 's' }));
|
||||
injectReplacer(kbnServer, orig => ({ name: orig.name + 'a' }));
|
||||
injectReplacer(kbnServer, orig => ({ name: orig.name + 'm' }));
|
||||
|
||||
const resp = await kbnServer.inject('/app/test_app');
|
||||
const injectedVars = getInjectedVarsFromResponse(resp);
|
||||
|
||||
expect(injectedVars).to.eql({ name: 'sam' });
|
||||
});
|
||||
|
||||
it('propogates errors thrown in replacers', async () => {
|
||||
injectReplacer(kbnServer, async () => {
|
||||
await delay(100);
|
||||
throw new Error('replacer failed');
|
||||
});
|
||||
|
||||
const resp = await kbnServer.inject('/app/test_app');
|
||||
expect(resp).to.have.property('statusCode', 500);
|
||||
});
|
||||
|
||||
it('starts off with the injected vars for the app merged with the default injected vars', async () => {
|
||||
const stub = sinon.stub();
|
||||
injectReplacer(kbnServer, stub);
|
||||
kbnServer.uiExports.defaultInjectedVars.from_defaults = true;
|
||||
|
||||
const resp = await kbnServer.inject('/app/test_app');
|
||||
sinon.assert.calledOnce(stub);
|
||||
expect(stub.firstCall.args[0]).to.eql({ from_defaults: true, from_test_app: true });
|
||||
});
|
||||
});
|
||||
});
|
|
@ -2,6 +2,7 @@ import { format as formatUrl } from 'url';
|
|||
import { readFileSync as readFile } from 'fs';
|
||||
import { defaults } from 'lodash';
|
||||
import Boom from 'boom';
|
||||
import { reduce as reduceAsync } from 'bluebird';
|
||||
import { resolve } from 'path';
|
||||
import fromRoot from '../utils/from_root';
|
||||
import UiExports from './ui_exports';
|
||||
|
@ -43,15 +44,19 @@ export default async (kbnServer, server, config) => {
|
|||
server.route({
|
||||
path: '/app/{id}',
|
||||
method: 'GET',
|
||||
handler: function (req, reply) {
|
||||
async handler(req, reply) {
|
||||
const id = req.params.id;
|
||||
const app = uiExports.apps.byId[id];
|
||||
if (!app) return reply(Boom.notFound('Unknown app ' + id));
|
||||
|
||||
if (kbnServer.status.isGreen()) {
|
||||
return reply.renderApp(app);
|
||||
} else {
|
||||
return reply.renderStatusPage();
|
||||
try {
|
||||
if (kbnServer.status.isGreen()) {
|
||||
await reply.renderApp(app);
|
||||
} else {
|
||||
await reply.renderStatusPage();
|
||||
}
|
||||
} catch (err) {
|
||||
reply(Boom.wrap(err));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -70,7 +75,11 @@ export default async (kbnServer, server, config) => {
|
|||
defaults: await server.uiSettings().getDefaults(),
|
||||
user: {}
|
||||
},
|
||||
vars: defaults(app.getInjectedVars() || {}, uiExports.defaultInjectedVars),
|
||||
vars: await reduceAsync(
|
||||
uiExports.injectedVarsReplacers,
|
||||
async (acc, replacer) => await replacer(acc, this.request, server),
|
||||
defaults(await app.getInjectedVars() || {}, uiExports.defaultInjectedVars)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -83,16 +92,18 @@ export default async (kbnServer, server, config) => {
|
|||
}
|
||||
|
||||
async function renderApp(app) {
|
||||
const isElasticsearchPluginRed = server.plugins.elasticsearch.status.state === 'red';
|
||||
const payload = await getPayload(app);
|
||||
if (!isElasticsearchPluginRed) {
|
||||
const payload = await getPayload.call(this, app);
|
||||
|
||||
const esStatus = kbnServer.status.getForPluginId('elasticsearch');
|
||||
if (esStatus && esStatus.state !== 'red') {
|
||||
payload.uiSettings.user = await server.uiSettings().getUserProvided();
|
||||
}
|
||||
|
||||
return viewAppWithPayload.call(this, app, payload);
|
||||
}
|
||||
|
||||
async function renderAppWithDefaultConfig(app) {
|
||||
const payload = await getPayload(app);
|
||||
const payload = await getPayload.call(this, app);
|
||||
return viewAppWithPayload.call(this, app, payload);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ class UiExports {
|
|||
this.consumers = [];
|
||||
this.bundleProviders = [];
|
||||
this.defaultInjectedVars = {};
|
||||
this.injectedVarsReplacers = [];
|
||||
}
|
||||
|
||||
consumePlugin(plugin) {
|
||||
|
@ -107,6 +108,11 @@ class UiExports {
|
|||
_.merge(this.defaultInjectedVars, await injector.call(plugin, server, options));
|
||||
});
|
||||
};
|
||||
|
||||
case 'replaceInjectedVars':
|
||||
return (plugin, replacer) => {
|
||||
this.injectedVarsReplacers.push(replacer);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue