diff --git a/src/core_plugins/kibana/server/routes/api/ingest/register_post.js b/src/core_plugins/kibana/server/routes/api/ingest/register_post.js index a1b0c5976b54f..caf4d23f05795 100644 --- a/src/core_plugins/kibana/server/routes/api/ingest/register_post.js +++ b/src/core_plugins/kibana/server/routes/api/ingest/register_post.js @@ -64,7 +64,8 @@ export function registerPost(server) { } }, handler: async function (req, reply) { - const config = await server.uiSettings().getAll(); + const uiSettings = server.uiSettings(); + const metaFields = await uiSettings.get('metaFields'); const boundCallWithRequest = _.partial(server.plugins.elasticsearch.callWithRequest, req); const requestDocument = _.cloneDeep(req.payload); const indexPattern = keysToCamelCaseShallow(requestDocument.index_pattern); @@ -74,8 +75,6 @@ export function registerPost(server) { delete indexPattern.id; const mappings = createMappingsFromPatternFields(indexPattern.fields); - - const metaFields = _.get(config, 'metaFields.userValue', config.metaFields.value); const indexPatternMetaFields = _.map(metaFields, name => ({name})); indexPattern.fields = initDefaultFieldProps(indexPattern.fields.concat(indexPatternMetaFields)); diff --git a/src/ui/settings/__tests__/index.js b/src/ui/settings/__tests__/index.js new file mode 100644 index 0000000000000..2e09f4c00a4e9 --- /dev/null +++ b/src/ui/settings/__tests__/index.js @@ -0,0 +1,308 @@ +import { isEqual } from 'lodash'; +import sinon from 'sinon'; +import expect from 'expect.js'; +import init from '..'; +import defaultsProvider from '../defaults'; + +describe('ui settings', function () { + describe('overview', function () { + it('has expected api surface', function () { + const { uiSettings } = instantiate(); + expect(typeof uiSettings.get).to.equal('function'); + expect(typeof uiSettings.getAll).to.equal('function'); + expect(typeof uiSettings.getDefaults).to.equal('function'); + expect(typeof uiSettings.getRaw).to.equal('function'); + expect(typeof uiSettings.getUserProvided).to.equal('function'); + expect(typeof uiSettings.remove).to.equal('function'); + expect(typeof uiSettings.removeMany).to.equal('function'); + expect(typeof uiSettings.set).to.equal('function'); + expect(typeof uiSettings.setMany).to.equal('function'); + }); + }); + + describe('#setMany()', function () { + it('returns a promise', () => { + const { uiSettings } = instantiate(); + const result = uiSettings.setMany({ a: 'b' }); + expect(result).to.be.a(Promise); + }); + + it('updates a single value in one operation', function () { + const { server, uiSettings, configGet } = instantiate(); + const result = uiSettings.setMany({ one: 'value' }); + expectElasticsearchUpdateQuery(server, configGet, { + one: 'value' + }); + }); + + it('updates several values in one operation', function () { + const { server, uiSettings, configGet } = instantiate(); + const result = uiSettings.setMany({ one: 'value', another: 'val' }); + expectElasticsearchUpdateQuery(server, configGet, { + one: 'value', another: 'val' + }); + }); + }); + + describe('#set()', function () { + it('returns a promise', () => { + const { uiSettings } = instantiate(); + const result = uiSettings.set('a', 'b'); + expect(result).to.be.a(Promise); + }); + + it('updates single values by (key, value)', function () { + const { server, uiSettings, configGet } = instantiate(); + const result = uiSettings.set('one', 'value'); + expectElasticsearchUpdateQuery(server, configGet, { + one: 'value' + }); + }); + }); + + describe('#remove()', function () { + it('returns a promise', () => { + const { uiSettings } = instantiate(); + const result = uiSettings.remove('one'); + expect(result).to.be.a(Promise); + }); + + it('removes single values by key', function () { + const { server, uiSettings, configGet } = instantiate(); + const result = uiSettings.remove('one'); + expectElasticsearchUpdateQuery(server, configGet, { + one: null + }); + }); + }); + + describe('#removeMany()', function () { + it('returns a promise', () => { + const { uiSettings } = instantiate(); + const result = uiSettings.removeMany(['one']); + expect(result).to.be.a(Promise); + }); + + it('removes a single value', function () { + const { server, uiSettings, configGet } = instantiate(); + const result = uiSettings.removeMany(['one']); + expectElasticsearchUpdateQuery(server, configGet, { + one: null + }); + }); + + it('updates several values in one operation', function () { + const { server, uiSettings, configGet } = instantiate(); + const result = uiSettings.removeMany(['one', 'two', 'three']); + expectElasticsearchUpdateQuery(server, configGet, { + one: null, two: null, three: null + }); + }); + }); + + describe('#getDefaults()', function () { + it('is promised the default values', async function () { + const { server, uiSettings, configGet } = instantiate(); + const defaults = await uiSettings.getDefaults(); + expect(isEqual(defaults, defaultsProvider())).to.equal(true); + }); + }); + + describe('#getUserProvided()', function () { + it('pulls user configuration from ES', async function () { + const getResult = { user: 'customized' }; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.getUserProvided(); + expectElasticsearchGetQuery(server, configGet); + }); + + it('returns user configuration', async function () { + const getResult = { user: 'customized' }; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.getUserProvided(); + expect(isEqual(result, { + user: { userValue: 'customized' } + })).to.equal(true); + }); + + it('ignores null user configuration (because default values)', async function () { + const getResult = { user: 'customized', usingDefault: null, something: 'else' }; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.getUserProvided(); + expect(isEqual(result, { + user: { userValue: 'customized' }, something: { userValue: 'else' } + })).to.equal(true); + }); + }); + + describe('#getRaw()', function () { + it('pulls user configuration from ES', async function () { + const getResult = {}; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.getRaw(); + expectElasticsearchGetQuery(server, configGet); + }); + + it(`without user configuration it's equal to the defaults`, async function () { + const getResult = {}; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.getRaw(); + expect(isEqual(result, defaultsProvider())).to.equal(true); + }); + + it(`user configuration gets merged with defaults`, async function () { + const getResult = { foo: 'bar' }; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.getRaw(); + const merged = defaultsProvider(); + merged.foo = { userValue: 'bar' }; + expect(isEqual(result, merged)).to.equal(true); + }); + + it(`user configuration gets merged into defaults`, async function () { + const getResult = { dateFormat: 'YYYY-MM-DD' }; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.getRaw(); + const merged = defaultsProvider(); + merged.dateFormat.userValue = 'YYYY-MM-DD'; + expect(isEqual(result, merged)).to.equal(true); + }); + }); + + describe('#getAll()', function () { + it('pulls user configuration from ES', async function () { + const getResult = {}; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.getAll(); + expectElasticsearchGetQuery(server, configGet); + }); + + it(`returns key value pairs`, async function () { + const getResult = {}; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.getAll(); + const defaults = defaultsProvider(); + const expectation = {}; + Object.keys(defaults).forEach(key => { + expectation[key] = defaults[key].value; + }); + expect(isEqual(result, expectation)).to.equal(true); + }); + + it(`returns key value pairs including user configuration`, async function () { + const getResult = { something: 'user-provided' }; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.getAll(); + const defaults = defaultsProvider(); + const expectation = {}; + Object.keys(defaults).forEach(key => { + expectation[key] = defaults[key].value; + }); + expectation.something = 'user-provided'; + expect(isEqual(result, expectation)).to.equal(true); + }); + + it(`returns key value pairs including user configuration for existing settings`, async function () { + const getResult = { dateFormat: 'YYYY-MM-DD' }; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.getAll(); + const defaults = defaultsProvider(); + const expectation = {}; + Object.keys(defaults).forEach(key => { + expectation[key] = defaults[key].value; + }); + expectation.dateFormat = 'YYYY-MM-DD'; + expect(isEqual(result, expectation)).to.equal(true); + }); + }); + + describe('#get()', function () { + it('pulls user configuration from ES', async function () { + const getResult = {}; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.get(); + expectElasticsearchGetQuery(server, configGet); + }); + + it(`returns the promised value for a key`, async function () { + const getResult = {}; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.get('dateFormat'); + const defaults = defaultsProvider(); + expect(result).to.equal(defaults.dateFormat.value); + }); + + it(`returns the user-configured value for a custom key`, async function () { + const getResult = { custom: 'value' }; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.get('custom'); + expect(result).to.equal('value'); + }); + + it(`returns the user-configured value for a modified key`, async function () { + const getResult = { dateFormat: 'YYYY-MM-DD' }; + const { server, uiSettings, configGet } = instantiate({ getResult }); + const result = await uiSettings.get('dateFormat'); + expect(result).to.equal('YYYY-MM-DD'); + }); + }); +}); + +function expectElasticsearchGetQuery(server, configGet) { + expect(server.plugins.elasticsearch.client.get.callCount).to.equal(1); + expect(isEqual(server.plugins.elasticsearch.client.get.firstCall.args, [{ + index: configGet('kibana.index'), + id: configGet('pkg.version'), + type: 'config' + }])).to.equal(true); +} + +function expectElasticsearchUpdateQuery(server, configGet, doc) { + expect(server.plugins.elasticsearch.client.update.callCount).to.equal(1); + expect(isEqual(server.plugins.elasticsearch.client.update.firstCall.args, [{ + index: configGet('kibana.index'), + id: configGet('pkg.version'), + type: 'config', + body: { doc } + }])).to.equal(true); +} + +function instantiate({ getResult } = {}) { + const esStatus = { + state: 'green', + on: sinon.spy() + }; + const settingsStatus = { + state: 'green', + red: sinon.spy(), + yellow: sinon.spy(), + green: sinon.spy() + }; + const kbnServer = { + status: { + create: sinon.stub().withArgs('ui settings').returns(settingsStatus), + getForPluginId: sinon.stub().withArgs('elasticsearch').returns(esStatus) + }, + ready: sinon.stub().returns(Promise.resolve()) + }; + const server = { + decorate: (_, key, value) => server[key] = value, + plugins: { + elasticsearch: { + client: { + get: sinon.stub().returns(Promise.resolve({ _source: getResult })), + update: sinon.stub().returns(Promise.resolve()) + } + } + } + }; + const configGet = sinon.stub(); + configGet.withArgs('kibana.index').returns('.kibana'); + configGet.withArgs('pkg.version').returns('1.2.3-test'); + const config = { + get: configGet + }; + const setupSettings = init(kbnServer, server, config); + const uiSettings = server.uiSettings(); + return { server, uiSettings, configGet }; +} diff --git a/src/ui/settings/index.js b/src/ui/settings/index.js index fbc17c2075af3..3709e48c8124b 100644 --- a/src/ui/settings/index.js +++ b/src/ui/settings/index.js @@ -4,18 +4,47 @@ import defaultsProvider from './defaults'; export default function setupSettings(kbnServer, server, config) { const status = kbnServer.status.create('ui settings'); const uiSettings = { + // returns a Promise for the value of the requested setting + get, + // returns a Promise for a hash of setting key/value pairs getAll, - getDefaults, - getUserProvided, + // .set(key, value), returns a Promise for persisting the new value to ES set, + // takes a key/value hash, returns a Promise for persisting the new values to ES setMany, - remove + // returns a Promise for removing the provided key from user-specific settings + remove, + // takes an array, returns a Promise for removing every provided key from user-specific settings + removeMany, + + // returns a Promise for the default settings, follows metadata format (see ./defaults) + getDefaults, + // returns a Promise for user-specific settings stored in ES, follows metadata format + getUserProvided, + // returns a Promise merging results of getDefaults & getUserProvided, follows metadata format + getRaw }; server.decorate('server', 'uiSettings', () => uiSettings); kbnServer.ready().then(mirrorEsStatus); + function get(key) { + return getAll().then(all => all[key]); + } + function getAll() { + return getRaw() + .then(raw => Object.keys(raw) + .reduce((all, key) => { + const item = raw[key]; + const hasUserValue = 'userValue' in item; + all[key] = hasUserValue ? item.userValue : item.value; + return all; + }, {}) + ); + } + + function getRaw() { return Promise .all([getDefaults(), getUserProvided()]) .then(([defaults, user]) => defaultsDeep(user, defaults)); @@ -59,6 +88,14 @@ export default function setupSettings(kbnServer, server, config) { return set(key, null); } + function removeMany(keys) { + const changes = {}; + keys.forEach(key => { + changes[key] = null; + }); + return setMany(changes); + } + function mirrorEsStatus() { const esStatus = kbnServer.status.getForPluginId('elasticsearch'); diff --git a/tasks/config/simplemocha.js b/tasks/config/simplemocha.js index 2dddcfbe1bd41..95213d40e2f97 100644 --- a/tasks/config/simplemocha.js +++ b/tasks/config/simplemocha.js @@ -13,8 +13,7 @@ module.exports = { 'test/**/__tests__/**/*.js', 'src/**/__tests__/**/*.js', 'test/fixtures/__tests__/*.js', - '!src/**/public/**', - '!src/ui/**' + '!src/**/public/**' ] } };