diff --git a/src/core_plugins/elasticsearch/index.js b/src/core_plugins/elasticsearch/index.js index e5dc9b05c9037..85decb5b5acf5 100644 --- a/src/core_plugins/elasticsearch/index.js +++ b/src/core_plugins/elasticsearch/index.js @@ -159,8 +159,7 @@ export default function (kibana) { } ); // Set up the health check service and start it. - const mappings = kibana.uiExports.mappings.getCombined(); - const { start, waitUntilReady } = healthCheck(this, server, { mappings }); + const { start, waitUntilReady } = healthCheck(this, server); server.expose('waitUntilReady', waitUntilReady); start(); } diff --git a/src/core_plugins/elasticsearch/lib/__tests__/create_kibana_index.js b/src/core_plugins/elasticsearch/lib/__tests__/create_kibana_index.js index a007fabc207e5..8b243ea962001 100644 --- a/src/core_plugins/elasticsearch/lib/__tests__/create_kibana_index.js +++ b/src/core_plugins/elasticsearch/lib/__tests__/create_kibana_index.js @@ -23,6 +23,7 @@ describe('plugins/elasticsearch', function () { config = function () { return { get: get }; }; _.set(server, 'config', config); + _.set(server, 'getKibanaIndexMappingsDsl', sinon.stub().returns(mappings)); callWithInternalUser = sinon.stub(); cluster = { callWithInternalUser: callWithInternalUser }; @@ -37,14 +38,14 @@ describe('plugins/elasticsearch', function () { }); it('should check cluster.health upon successful index creation', function () { - const fn = createKibanaIndex(server, mappings); + const fn = createKibanaIndex(server); return fn.then(function () { sinon.assert.calledOnce(callWithInternalUser.withArgs('cluster.health', sinon.match.any)); }); }); it('should be created with mappings for config.buildNum', function () { - const fn = createKibanaIndex(server, mappings); + const fn = createKibanaIndex(server); return fn.then(function () { const params = callWithInternalUser.args[0][1]; expect(params) @@ -63,7 +64,7 @@ describe('plugins/elasticsearch', function () { }); it('should be created with 1 shard and default replica', function () { - const fn = createKibanaIndex(server, mappings); + const fn = createKibanaIndex(server); return fn.then(function () { const params = callWithInternalUser.args[0][1]; expect(params) @@ -78,7 +79,7 @@ describe('plugins/elasticsearch', function () { }); it('should be created with index name set in the config', function () { - const fn = createKibanaIndex(server, mappings); + const fn = createKibanaIndex(server); return fn.then(function () { const params = callWithInternalUser.args[0][1]; expect(params) diff --git a/src/core_plugins/elasticsearch/lib/__tests__/ensure_types_exist.js b/src/core_plugins/elasticsearch/lib/__tests__/ensure_types_exist.js deleted file mode 100644 index 8ee4993cb0e49..0000000000000 --- a/src/core_plugins/elasticsearch/lib/__tests__/ensure_types_exist.js +++ /dev/null @@ -1,247 +0,0 @@ -import expect from 'expect.js'; -import sinon from 'sinon'; -import { cloneDeep } from 'lodash'; -import Chance from 'chance'; - -import { ensureTypesExist } from '../ensure_types_exist'; - -const chance = new Chance(); - -function createRandomTypes(n = chance.integer({ min: 10, max: 20 })) { - return chance.n( - () => ({ - name: chance.word(), - mapping: { - type: chance.pickone(['keyword', 'text', 'integer', 'boolean']) - } - }), - n - ); -} - -function typesToMapping(types) { - return types.reduce((acc, type) => ({ - ...acc, - [type.name]: type.mapping - }), {}); -} - -function createV5Index(name, types) { - return { - [name]: { - mappings: typesToMapping(types) - } - }; -} - -function createV6Index(name, types) { - return { - [name]: { - mappings: { - doc: { - properties: typesToMapping(types) - } - } - } - }; -} - -function createCallCluster(index) { - return sinon.spy(async (method, params) => { - switch (method) { - case 'indices.get': - expect(params).to.have.property('index', Object.keys(index)[0]); - return cloneDeep(index); - case 'indices.putMapping': - return { ok: true }; - default: - throw new Error(`stub not expecting callCluster('${method}')`); - } - }); -} - -describe('es/healthCheck/ensureTypesExist()', () => { - describe('general', () => { - it('reads the _mappings feature of the indexName', async () => { - const indexName = chance.word(); - const callCluster = createCallCluster(createV5Index(indexName, [])); - await ensureTypesExist({ - callCluster, - indexName, - types: [], - log: sinon.stub() - }); - - sinon.assert.calledOnce(callCluster); - sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ - feature: '_mappings' - })); - }); - }); - - describe('v5 index', () => { - it('does nothing if mappings match elasticsearch', async () => { - const types = createRandomTypes(); - const indexName = chance.word(); - const callCluster = createCallCluster(createV5Index(indexName, types)); - await ensureTypesExist({ - indexName, - callCluster, - types, - log: sinon.stub() - }); - - sinon.assert.calledOnce(callCluster); - sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName })); - }); - - it('adds types that are not in index', async () => { - const indexTypes = createRandomTypes(); - const missingTypes = indexTypes.splice(-5); - - const indexName = chance.word(); - const callCluster = createCallCluster(createV5Index(indexName, indexTypes)); - await ensureTypesExist({ - indexName, - callCluster, - types: [ - ...indexTypes, - ...missingTypes, - ], - log: sinon.stub() - }); - - sinon.assert.callCount(callCluster, 1 + missingTypes.length); - sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName })); - missingTypes.forEach(type => { - sinon.assert.calledWith(callCluster, 'indices.putMapping', sinon.match({ - index: indexName, - type: type.name, - body: type.mapping - })); - }); - }); - - it('ignores extra types in index', async () => { - const indexTypes = createRandomTypes(); - const missingTypes = indexTypes.splice(-5); - - const indexName = chance.word(); - const callCluster = createCallCluster(createV5Index(indexName, indexTypes)); - await ensureTypesExist({ - indexName, - callCluster, - types: missingTypes, - log: sinon.stub() - }); - - sinon.assert.callCount(callCluster, 1 + missingTypes.length); - sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName })); - missingTypes.forEach(type => { - sinon.assert.calledWith(callCluster, 'indices.putMapping', sinon.match({ - index: indexName, - type: type.name, - body: type.mapping - })); - }); - }); - }); - - describe('v6 index', () => { - it('does nothing if mappings match elasticsearch', async () => { - const types = createRandomTypes(); - const indexName = chance.word(); - const callCluster = createCallCluster(createV6Index(indexName, types)); - await ensureTypesExist({ - indexName, - callCluster, - types, - log: sinon.stub() - }); - - sinon.assert.calledOnce(callCluster); - sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName })); - }); - - it('adds types that are not in index', async () => { - const indexTypes = createRandomTypes(); - const missingTypes = indexTypes.splice(-5); - - const indexName = chance.word(); - const callCluster = createCallCluster(createV6Index(indexName, indexTypes)); - await ensureTypesExist({ - indexName, - callCluster, - types: [ - ...indexTypes, - ...missingTypes, - ], - log: sinon.stub() - }); - - sinon.assert.callCount(callCluster, 1 + missingTypes.length); - sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName })); - missingTypes.forEach(type => { - sinon.assert.calledWith(callCluster, 'indices.putMapping', sinon.match({ - index: indexName, - type: 'doc', - body: { - properties: { - [type.name]: type.mapping, - } - } - })); - }); - }); - - it('ignores extra types in index', async () => { - const indexTypes = createRandomTypes(); - const missingTypes = indexTypes.splice(-5); - - const indexName = chance.word(); - const callCluster = createCallCluster(createV6Index(indexName, indexTypes)); - await ensureTypesExist({ - indexName, - callCluster, - types: missingTypes, - log: sinon.stub() - }); - - sinon.assert.callCount(callCluster, 1 + missingTypes.length); - sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName })); - missingTypes.forEach(type => { - sinon.assert.calledWith(callCluster, 'indices.putMapping', sinon.match({ - index: indexName, - type: 'doc', - body: { - properties: { - [type.name]: type.mapping, - } - } - })); - }); - }); - - it('does not define the _default_ type', async () => { - const indexTypes = []; - const missingTypes = [ - { - name: '_default_', - mapping: {} - } - ]; - - const indexName = chance.word(); - const callCluster = createCallCluster(createV6Index(indexName, indexTypes)); - await ensureTypesExist({ - indexName, - callCluster, - types: missingTypes, - log: sinon.stub() - }); - - sinon.assert.calledOnce(callCluster); - sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName })); - }); - }); -}); diff --git a/src/core_plugins/elasticsearch/lib/__tests__/health_check.js b/src/core_plugins/elasticsearch/lib/__tests__/health_check.js index a0562f05a8c81..e77d730800cfc 100644 --- a/src/core_plugins/elasticsearch/lib/__tests__/health_check.js +++ b/src/core_plugins/elasticsearch/lib/__tests__/health_check.js @@ -9,7 +9,8 @@ import mappings from './fixtures/mappings'; import healthCheck from '../health_check'; import kibanaVersion from '../kibana_version'; import { esTestServerUrlParts } from '../../../../../test/es_test_server_url_parts'; -import * as ensureTypesExistNS from '../ensure_types_exist'; +import * as patchKibanaIndexNS from '../patch_kibana_index'; +import * as migrateConfigNS from '../migrate_config'; const esPort = esTestServerUrlParts.port; const esUrl = url.format(esTestServerUrlParts); @@ -21,13 +22,15 @@ describe('plugins/elasticsearch', () => { let health; let plugin; let cluster; + const sandbox = sinon.sandbox.create(); beforeEach(() => { const COMPATIBLE_VERSION_NUMBER = '5.0.0'; // Stub the Kibana version instead of drawing from package.json. - sinon.stub(kibanaVersion, 'get').returns(COMPATIBLE_VERSION_NUMBER); - sinon.stub(ensureTypesExistNS, 'ensureTypesExist'); + sandbox.stub(kibanaVersion, 'get').returns(COMPATIBLE_VERSION_NUMBER); + sandbox.stub(patchKibanaIndexNS, 'patchKibanaIndex'); + sandbox.stub(migrateConfigNS, 'migrateConfig'); // setup the plugin stub plugin = { @@ -73,19 +76,15 @@ describe('plugins/elasticsearch', () => { getCluster: sinon.stub().returns(cluster) } }, - savedObjectsClientFactory: () => ({ - find: sinon.stub().returns(Promise.resolve({ saved_objects: [] })), - create: sinon.stub().returns(Promise.resolve({ id: 'foo' })), - }) + getKibanaIndexMappingsDsl() { + return mappings; + } }; - health = healthCheck(plugin, server, { mappings }); + health = healthCheck(plugin, server); }); - afterEach(() => { - kibanaVersion.get.restore(); - ensureTypesExistNS.ensureTypesExist.restore(); - }); + afterEach(() => sandbox.restore()); it('should set the cluster green if everything is ready', function () { cluster.callWithInternalUser.withArgs('ping').returns(Promise.resolve()); @@ -101,6 +100,7 @@ describe('plugins/elasticsearch', () => { sinon.assert.calledOnce(cluster.callWithInternalUser.withArgs('ping')); sinon.assert.calledTwice(cluster.callWithInternalUser.withArgs('nodes.info', sinon.match.any)); sinon.assert.calledOnce(cluster.callWithInternalUser.withArgs('cluster.health', sinon.match.any)); + sinon.assert.notCalled(plugin.status.red); sinon.assert.calledOnce(plugin.status.green); expect(plugin.status.green.args[0][0]).to.be('Kibana index ready'); diff --git a/src/core_plugins/elasticsearch/lib/__tests__/patch_kibana_index.js b/src/core_plugins/elasticsearch/lib/__tests__/patch_kibana_index.js new file mode 100644 index 0000000000000..ecddf49a62b17 --- /dev/null +++ b/src/core_plugins/elasticsearch/lib/__tests__/patch_kibana_index.js @@ -0,0 +1,174 @@ +import expect from 'expect.js'; +import sinon from 'sinon'; +import { times, cloneDeep, pick, partition } from 'lodash'; +import Chance from 'chance'; + +import { patchKibanaIndex } from '../patch_kibana_index'; +import { getRootProperties, getRootType } from '../../../../server/mappings'; + +const chance = new Chance(); + +function createRandomMappings(n = chance.integer({ min: 10, max: 20 })) { + return { + [chance.word()]: { + properties: times(n, () => chance.word()) + .reduce((acc, prop) => ({ + ...acc, + [prop]: { + type: chance.pickone(['keyword', 'text', 'integer', 'boolean']) + } + }), {}) + } + }; +} + +function splitMappings(mappings) { + const type = getRootType(mappings); + const allProps = getRootProperties(mappings); + const keyGroups = partition(Object.keys(allProps), (p, i) => i % 2); + return keyGroups.map(keys => ({ + [type]: { + ...mappings[type], + properties: pick(allProps, keys) + } + })); +} + +function createIndex(name, mappings = {}) { + return { + [name]: { + mappings + } + }; +} + +function createCallCluster(index) { + return sinon.spy(async (method, params) => { + switch (method) { + case 'indices.get': + expect(params).to.have.property('index', Object.keys(index)[0]); + return cloneDeep(index); + case 'indices.putMapping': + return { ok: true }; + default: + throw new Error(`stub not expecting callCluster('${method}')`); + } + }); +} + +describe('es/healthCheck/patchKibanaIndex()', () => { + describe('general', () => { + it('reads the _mappings feature of the indexName', async () => { + const indexName = chance.word(); + const mappings = createRandomMappings(); + const callCluster = createCallCluster(createIndex(indexName, mappings)); + await patchKibanaIndex({ + callCluster, + indexName, + kibanaIndexMappingsDsl: mappings, + log: sinon.stub() + }); + + sinon.assert.calledOnce(callCluster); + sinon.assert.calledWithExactly(callCluster, 'indices.get', sinon.match({ + feature: '_mappings' + })); + }); + }); + + describe('multi-type index', () => { + it('rejects', async () => { + try { + const mappings = createRandomMappings(); + const indexName = chance.word(); + const index = createIndex(indexName, { + ...mappings, + ...createRandomMappings(), + ...createRandomMappings(), + ...createRandomMappings(), + }); + const callCluster = createCallCluster(index); + + await patchKibanaIndex({ + indexName, + callCluster, + kibanaIndexMappingsDsl: mappings, + log: sinon.stub() + }); + throw new Error('expected patchKibanaIndex() to throw an error'); + } catch (error) { + expect(error) + .to.have.property('message') + .contain('Your Kibana index is out of date'); + } + }); + }); + + describe('v6 index', () => { + it('does nothing if mappings match elasticsearch', async () => { + const mappings = createRandomMappings(); + const indexName = chance.word(); + const callCluster = createCallCluster(createIndex(indexName, mappings)); + await patchKibanaIndex({ + indexName, + callCluster, + kibanaIndexMappingsDsl: mappings, + log: sinon.stub() + }); + + sinon.assert.calledOnce(callCluster); + sinon.assert.calledWithExactly(callCluster, 'indices.get', sinon.match({ index: indexName })); + }); + + it('adds properties that are not in index', async () => { + const [indexMappings, missingMappings] = splitMappings(createRandomMappings()); + const mappings = { + ...indexMappings, + ...missingMappings, + }; + + const indexName = chance.word(); + const callCluster = createCallCluster(createIndex(indexName, indexMappings)); + await patchKibanaIndex({ + indexName, + callCluster, + kibanaIndexMappingsDsl: mappings, + log: sinon.stub() + }); + + sinon.assert.calledTwice(callCluster); + sinon.assert.calledWithExactly(callCluster, 'indices.get', sinon.match({ index: indexName })); + sinon.assert.calledWithExactly(callCluster, 'indices.putMapping', sinon.match({ + index: indexName, + type: getRootType(mappings), + body: { + properties: getRootProperties(mappings) + } + })); + }); + + it('ignores extra properties in index', async () => { + const [indexMappings, mappings] = splitMappings(createRandomMappings()); + const indexName = chance.word(); + const callCluster = createCallCluster(createIndex(indexName, indexMappings)); + await patchKibanaIndex({ + indexName, + callCluster, + kibanaIndexMappingsDsl: mappings, + log: sinon.stub() + }); + + sinon.assert.calledTwice(callCluster); + sinon.assert.calledWithExactly(callCluster, 'indices.get', sinon.match({ + index: indexName + })); + sinon.assert.calledWithExactly(callCluster, 'indices.putMapping', sinon.match({ + index: indexName, + type: getRootType(mappings), + body: { + properties: getRootProperties(mappings) + } + })); + }); + }); +}); diff --git a/src/core_plugins/elasticsearch/lib/create_kibana_index.js b/src/core_plugins/elasticsearch/lib/create_kibana_index.js index 5a419646ca306..53260dd170d72 100644 --- a/src/core_plugins/elasticsearch/lib/create_kibana_index.js +++ b/src/core_plugins/elasticsearch/lib/create_kibana_index.js @@ -1,15 +1,13 @@ -export default function (server, mappings) { +export default function (server) { const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); const index = server.config().get('kibana.index'); return callWithInternalUser('indices.create', { index: index, body: { settings: { - number_of_shards: 1, - 'index.mapper.dynamic': false, - 'index.mapping.single_type': false + number_of_shards: 1 }, - mappings + mappings: server.getKibanaIndexMappingsDsl() } }) .catch(() => { diff --git a/src/core_plugins/elasticsearch/lib/ensure_types_exist.js b/src/core_plugins/elasticsearch/lib/ensure_types_exist.js deleted file mode 100644 index 34a88f791334b..0000000000000 --- a/src/core_plugins/elasticsearch/lib/ensure_types_exist.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Checks that a kibana index has all of the types specified. Any type - * that is not defined in the existing index will be added via the - * `indicies.putMapping` API. - * - * @param {Object} options - * @property {Function} options.log a method for writing log messages - * @property {string} options.indexName name of the index in elasticsearch - * @property {Function} options.callCluster a function for executing client requests - * @property {Array} options.types an array of objects with `name` and `mapping` properties - * describing the types that should be in the index - * @return {Promise} - */ -export async function ensureTypesExist({ log, indexName, callCluster, types }) { - const index = await callCluster('indices.get', { - index: indexName, - feature: '_mappings' - }); - - // could be different if aliases were resolved by `indices.get` - const resolvedName = Object.keys(index)[0]; - const mappings = index[resolvedName].mappings; - const literalTypes = Object.keys(mappings); - const v6Index = literalTypes.length === 1 && literalTypes[0] === 'doc'; - - // our types aren't really es types, at least not in v6 - const typesDefined = Object.keys( - v6Index - ? mappings.doc.properties - : mappings - ); - - for (const type of types) { - if (v6Index && type.name === '_default_') { - // v6 indices don't get _default_ types - continue; - } - - const defined = typesDefined.includes(type.name); - if (defined) { - continue; - } - - log(['info', 'elasticsearch'], { - tmpl: `Adding mappings to kibana index for SavedObject type "<%= typeName %>"`, - typeName: type.name, - typeMapping: type.mapping - }); - - if (v6Index) { - await callCluster('indices.putMapping', { - index: indexName, - type: 'doc', - body: { - properties: { - [type.name]: type.mapping - } - } - }); - } else { - await callCluster('indices.putMapping', { - index: indexName, - type: type.name, - body: type.mapping - }); - } - } -} diff --git a/src/core_plugins/elasticsearch/lib/health_check.js b/src/core_plugins/elasticsearch/lib/health_check.js index 57971e82cffb0..1874ededd3bb0 100644 --- a/src/core_plugins/elasticsearch/lib/health_check.js +++ b/src/core_plugins/elasticsearch/lib/health_check.js @@ -1,13 +1,13 @@ import _ from 'lodash'; import Promise from 'bluebird'; import elasticsearch from 'elasticsearch'; -import migrateConfig from './migrate_config'; +import { migrateConfig } from './migrate_config'; import createKibanaIndex from './create_kibana_index'; import kibanaVersion from './kibana_version'; import { ensureEsVersion } from './ensure_es_version'; import { ensureNotTribe } from './ensure_not_tribe'; import { ensureAllowExplicitIndex } from './ensure_allow_explicit_index'; -import { ensureTypesExist } from './ensure_types_exist'; +import { patchKibanaIndex } from './patch_kibana_index'; const NoConnections = elasticsearch.errors.NoConnections; import util from 'util'; @@ -17,7 +17,7 @@ const NO_INDEX = 'no_index'; const INITIALIZING = 'initializing'; const READY = 'ready'; -export default function (plugin, server, { mappings }) { +export default function (plugin, server) { const config = server.config(); const callAdminAsKibanaUser = server.plugins.elasticsearch.getCluster('admin').callWithInternalUser; const callDataAsKibanaUser = server.plugins.elasticsearch.getCluster('data').callWithInternalUser; @@ -71,7 +71,7 @@ export default function (plugin, server, { mappings }) { .then(function (health) { if (health === NO_INDEX) { plugin.status.yellow('No existing Kibana index found'); - return createKibanaIndex(server, mappings); + return createKibanaIndex(server); } if (health === INITIALIZING) { @@ -99,11 +99,11 @@ export default function (plugin, server, { mappings }) { .then(() => ensureNotTribe(callAdminAsKibanaUser)) .then(() => ensureAllowExplicitIndex(callAdminAsKibanaUser, config)) .then(waitForShards) - .then(() => ensureTypesExist({ + .then(() => patchKibanaIndex({ callCluster: callAdminAsKibanaUser, log: (...args) => server.log(...args), indexName: config.get('kibana.index'), - types: Object.keys(mappings).map(name => ({ name, mapping: mappings[name] })) + kibanaIndexMappingsDsl: server.getKibanaIndexMappingsDsl() })) .then(_.partial(migrateConfig, server)) .then(() => { diff --git a/src/core_plugins/elasticsearch/lib/migrate_config.js b/src/core_plugins/elasticsearch/lib/migrate_config.js index b46fc1e223926..a5e26fb0800af 100644 --- a/src/core_plugins/elasticsearch/lib/migrate_config.js +++ b/src/core_plugins/elasticsearch/lib/migrate_config.js @@ -1,6 +1,6 @@ import upgrade from './upgrade_config'; -export default async function (server) { +export async function migrateConfig(server) { const savedObjectsClient = server.savedObjectsClientFactory({ callCluster: server.plugins.elasticsearch.getCluster('admin').callWithInternalUser }); diff --git a/src/core_plugins/elasticsearch/lib/patch_kibana_index.js b/src/core_plugins/elasticsearch/lib/patch_kibana_index.js new file mode 100644 index 0000000000000..2b0c6a965f705 --- /dev/null +++ b/src/core_plugins/elasticsearch/lib/patch_kibana_index.js @@ -0,0 +1,101 @@ +import { + getTypes, + getRootType, + getRootProperties +} from '../../../server/mappings'; + +/** + * Checks that the root type in the kibana index has all of the + * root properties specified by the kibanaIndexMappings. + * + * @param {Object} options + * @property {Function} options.log + * @property {string} options.indexName + * @property {Function} options.callCluster + * @property {EsMappingsDsl} options.kibanaIndexMappingsDsl + * @return {Promise} + */ +export async function patchKibanaIndex(options) { + const { + log, + indexName, + callCluster, + kibanaIndexMappingsDsl + } = options; + + const rootEsType = getRootType(kibanaIndexMappingsDsl); + const currentMappingsDsl = await getCurrentMappings(callCluster, indexName, rootEsType); + const missingProperties = await getMissingRootProperties(currentMappingsDsl, kibanaIndexMappingsDsl); + + const missingPropertyNames = Object.keys(missingProperties); + if (!missingPropertyNames.length) { + // all expected properties are in current mapping + return; + } + + // log about new properties + log(['info', 'elasticsearch'], { + tmpl: `Adding mappings to kibana index for SavedObject types "<%= names.join('", "') %>"`, + names: missingPropertyNames + }); + + // add the new properties to the index mapping + await callCluster('indices.putMapping', { + index: indexName, + type: rootEsType, + body: { + properties: missingProperties + } + }); +} + +/** + * Get the mappings dsl for the current Kibana index + * @param {Function} callCluster + * @param {string} indexName + * @param {string} rootEsType + * @return {EsMappingsDsl} + */ +async function getCurrentMappings(callCluster, indexName, rootEsType) { + const index = await callCluster('indices.get', { + index: indexName, + feature: '_mappings' + }); + + // could be different if aliases were resolved by `indices.get` + const resolvedName = Object.keys(index)[0]; + const currentMappingsDsl = index[resolvedName].mappings; + const currentTypes = getTypes(currentMappingsDsl); + + const isV5Index = currentTypes.length > 1 || currentTypes[0] !== rootEsType; + if (isV5Index) { + throw new Error( + 'Your Kibana index is out of date, reset it or use the X-Pack upgrade assistant.' + ); + } + + return currentMappingsDsl; +} + +/** + * Get the properties that are in the expectedMappingsDsl but not the + * currentMappingsDsl. Properties will be an object of properties normally + * found at `[index]mappings[typeName].properties` is es mapping responses + * + * @param {EsMappingsDsl} currentMappingsDsl + * @param {EsMappingsDsl} expectedMappingsDsl + * @return {PropertyMappings} + */ +async function getMissingRootProperties(currentMappingsDsl, expectedMappingsDsl) { + const expectedProps = getRootProperties(expectedMappingsDsl); + const existingProps = getRootProperties(currentMappingsDsl); + + return Object.keys(expectedProps) + .reduce((acc, prop) => { + if (existingProps[prop]) { + return acc; + } else { + return { ...acc, [prop]: expectedProps[prop] }; + } + }, {}); +} diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/edit_index_pattern.js b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/edit_index_pattern.js index 2944c5723c8b2..b8597111c986e 100644 --- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/edit_index_pattern.js +++ b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/edit_index_pattern.js @@ -46,7 +46,7 @@ uiModules.get('apps/management') $scope.kbnUrl = Private(KbnUrlProvider); $scope.indexPattern = $route.current.locals.indexPattern; - docTitle.change($scope.indexPattern.id); + docTitle.change($scope.indexPattern.title); const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => { return pattern.id !== $scope.indexPattern.id; diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/scripted_field_editor/scripted_field_editor.js b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/scripted_field_editor/scripted_field_editor.js index 8fa971ef10f77..1018b40b66952 100644 --- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/scripted_field_editor/scripted_field_editor.js +++ b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/scripted_field_editor/scripted_field_editor.js @@ -9,6 +9,19 @@ uiRoutes .when('/management/kibana/indices/:indexPatternId/create-field/', { mode: 'create' }) .defaults(/management\/kibana\/indices\/[^\/]+\/(field|create-field)(\/|$)/, { template, + mapBreadcrumbs($route, breadcrumbs) { + const { indexPattern } = $route.current.locals; + return breadcrumbs.map(crumb => { + if (crumb.id !== indexPattern.id) { + return crumb; + } + + return { + ...crumb, + display: indexPattern.title + }; + }); + }, resolve: { indexPattern: function ($route, courier) { return courier.indexPatterns.get($route.current.params.indexPatternId) @@ -46,7 +59,7 @@ uiRoutes throw new Error('unknown fieldSettings mode ' + this.mode); } - docTitle.change([this.field.name || 'New Scripted Field', this.indexPattern.id]); + docTitle.change([this.field.name || 'New Scripted Field', this.indexPattern.title]); this.goBack = function () { kbnUrl.changeToRoute(this.indexPattern, 'edit'); }; diff --git a/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js b/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js index 682ef1c64b624..ac3c845500b2c 100644 --- a/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js +++ b/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js @@ -112,17 +112,21 @@ export function decorateMochaUi(lifecycle, context) { switch (property) { case 'describe': case 'describe.only': + case 'describe.skip': case 'xdescribe': case 'context': case 'context.only': + case 'context.skip': case 'xcontext': return wrapSuiteFunction(property, value); case 'it': case 'it.only': + case 'it.skip': case 'xit': case 'specify': case 'specify.only': + case 'specify.skip': case 'xspecify': return wrapTestFunction(property, value); diff --git a/src/server/kbn_server.js b/src/server/kbn_server.js index 5e5a5121cc11e..8ed9760701c65 100644 --- a/src/server/kbn_server.js +++ b/src/server/kbn_server.js @@ -21,6 +21,7 @@ import pluginsInitializeMixin from './plugins/initialize'; import { indexPatternsMixin } from './index_patterns'; import { savedObjectsMixin } from './saved_objects'; import { statsMixin } from './stats'; +import { kibanaIndexMappingsMixin } from './mappings'; import { serverExtensionsMixin } from './server_extensions'; const rootDir = fromRoot('.'); @@ -62,6 +63,9 @@ export default class KbnServer { // tell the config we are done loading plugins configCompleteMixin, + // setup kbnServer.mappings and server.getKibanaIndexMappingsDsl() + kibanaIndexMappingsMixin, + // setup this.uiExports and this.bundles uiMixin, indexPatternsMixin, diff --git a/src/server/mappings/__tests__/index_mappings.js b/src/server/mappings/__tests__/index_mappings.js new file mode 100644 index 0000000000000..999c0eb0d5b3c --- /dev/null +++ b/src/server/mappings/__tests__/index_mappings.js @@ -0,0 +1,128 @@ +import expect from 'expect.js'; +import Chance from 'chance'; + +import { IndexMappings } from '../index_mappings'; +import { getRootType } from '../lib'; + +const chance = new Chance(); + +describe('server/mapping/index_mapping', function () { + describe('constructor', () => { + it('initializes with a default mapping when no args', () => { + const mapping = new IndexMappings(); + const dsl = mapping.getDsl(); + expect(dsl).to.be.an('object'); + expect(getRootType(dsl)).to.be.a('string'); + expect(dsl[getRootType(dsl)]).to.be.an('object'); + }); + + it('accepts a default mapping dsl as the only argument', () => { + const mapping = new IndexMappings({ + foobar: { + dynamic: false, + properties: {} + } + }); + + expect(mapping.getDsl()).to.eql({ + foobar: { + dynamic: false, + properties: {} + } + }); + }); + + it('throws if root type is of type=anything-but-object', () => { + expect(() => { + new IndexMappings({ + root: { + type: chance.pickone(['string', 'keyword', 'geo_point']) + } + }); + }).to.throwException(/non-object/); + }); + + it('throws if root type has no type and no properties', () => { + expect(() => { + new IndexMappings({ + root: {} + }); + }).to.throwException(/non-object/); + }); + + it('initialized root type with properties object if not set', () => { + const mapping = new IndexMappings({ + root: { + type: 'object' + } + }); + + expect(mapping.getDsl()).to.eql({ + root: { + type: 'object', + properties: {} + } + }); + }); + }); + + describe('#getDsl()', () => { + // tests are light because this method is used all over these tests + it('returns mapping as es dsl', function () { + const mapping = new IndexMappings(); + expect(mapping.getDsl()).to.be.an('object'); + }); + }); + + describe('#addRootProperties()', () => { + it('extends the properties of the root type', () => { + const mapping = new IndexMappings({ + x: { properties: {} } + }); + + mapping.addRootProperties({ + y: { + properties: { + z: { + type: 'text' + } + } + } + }); + + expect(mapping.getDsl()).to.eql({ + x: { + properties: { + y: { + properties: { + z: { + type: 'text' + } + } + } + } + } + }); + }); + + it('throws if any property is conflicting', () => { + const props = { foo: 'bar' }; + const mapping = new IndexMappings({ + root: { properties: props } + }); + + expect(() => { + mapping.addRootProperties(props); + }).to.throwException(/foo/); + }); + + it('includes the plugin option in the error message when specified', () => { + const props = { foo: 'bar' }; + const mapping = new IndexMappings({ root: { properties: props } }); + + expect(() => { + mapping.addRootProperties(props, { plugin: 'abc123' }); + }).to.throwException(/plugin abc123/); + }); + }); +}); diff --git a/src/server/mappings/index.js b/src/server/mappings/index.js new file mode 100644 index 0000000000000..2c269e661ce78 --- /dev/null +++ b/src/server/mappings/index.js @@ -0,0 +1,10 @@ +export { + kibanaIndexMappingsMixin +} from './kibana_index_mappings_mixin'; + +export { + getTypes, + getRootType, + getProperty, + getRootProperties, +} from './lib'; diff --git a/src/server/mappings/index_mappings.js b/src/server/mappings/index_mappings.js new file mode 100644 index 0000000000000..26aaa9e7c5ad2 --- /dev/null +++ b/src/server/mappings/index_mappings.js @@ -0,0 +1,61 @@ +import { cloneDeep, isPlainObject } from 'lodash'; + +import { formatListAsProse } from '../../utils'; +import { getRootProperties, getRootType } from './lib'; + +const DEFAULT_INITIAL_DSL = { + rootType: { + type: 'object', + properties: {}, + }, +}; + +export class IndexMappings { + constructor(initialDsl = DEFAULT_INITIAL_DSL) { + this._dsl = cloneDeep(initialDsl); + if (!isPlainObject(this._dsl)) { + throw new TypeError('initial mapping must be an object'); + } + + // ensure that we have a properties object in the dsl + // and that the dsl can be parsed with getRootProperties() and kin + this._setProperties(getRootProperties(this._dsl) || {}); + } + + getDsl() { + return cloneDeep(this._dsl); + } + + addRootProperties(newProperties, options = {}) { + const { plugin } = options; + const rootProperties = getRootProperties(this._dsl); + + + const conflicts = Object.keys(newProperties) + .filter(key => rootProperties.hasOwnProperty(key)); + + if (conflicts.length) { + const props = formatListAsProse(conflicts); + const owner = plugin ? `registered by plugin ${plugin} ` : ''; + throw new Error( + `Mappings for ${props} ${owner}have already been defined` + ); + } + + this._setProperties({ + ...rootProperties, + ...newProperties + }); + } + + _setProperties(newProperties) { + const rootType = getRootType(this._dsl); + this._dsl = { + ...this._dsl, + [rootType]: { + ...this._dsl[rootType], + properties: newProperties + } + }; + } +} diff --git a/src/server/mappings/kibana_index_mappings_mixin.js b/src/server/mappings/kibana_index_mappings_mixin.js new file mode 100644 index 0000000000000..b738100977e6b --- /dev/null +++ b/src/server/mappings/kibana_index_mappings_mixin.js @@ -0,0 +1,60 @@ +import { IndexMappings } from './index_mappings'; + +/** + * The default mappings used for the kibana index. This is + * extended via uiExports type "mappings". See the kibana + * and timelion plugins for examples. + * @type {EsMappingDsl} + */ +const BASE_KIBANA_INDEX_MAPPINGS_DSL = { + doc: { + 'dynamic': 'strict', + properties: { + type: { + type: 'keyword' + }, + + config: { + dynamic: true, + properties: { + buildNum: { + type: 'keyword' + } + } + }, + } + } +}; + +export function kibanaIndexMappingsMixin(kbnServer, server) { + /** + * Stores the current mappings that we expect to find in the Kibana + * index. Using `kbnServer.mappings.addRootProperties()` the UiExports + * class extends these mappings based on `mappings` ui export specs. + * + * Application code should not access this object, and instead should + * use `server.getKibanaIndexMappingsDsl()` from below, mixed with the + * helpers exposed by this module, to interact with the mappings via + * their DSL. + * + * @type {IndexMappings} + */ + kbnServer.mappings = new IndexMappings(BASE_KIBANA_INDEX_MAPPINGS_DSL); + + /** + * Get the mappings dsl that we expect to see in the + * Kibana index. Used by the elasticsearch plugin to create + * and update the kibana index. Also used by the SavedObjectsClient + * to determine the properties defined in the mapping as well as + * things like the "rootType". + * + * See `src/server/mappings/lib/index.js` for helpers useful for reading + * the EsMappingDsl object. + * + * @method server.getKibanaIndexMappingsDsl + * @returns {EsMappingDsl} + */ + server.decorate('server', 'getKibanaIndexMappingsDsl', () => { + return kbnServer.mappings.getDsl(); + }); +} diff --git a/src/server/mappings/lib/__tests__/get_property.js b/src/server/mappings/lib/__tests__/get_property.js new file mode 100644 index 0000000000000..d4d3067f00b36 --- /dev/null +++ b/src/server/mappings/lib/__tests__/get_property.js @@ -0,0 +1,68 @@ +import expect from 'expect.js'; + +import { getProperty } from '../get_property'; + +const MAPPINGS = { + rootType: { + properties: { + foo: { + properties: { + name: { + type: 'text' + }, + description: { + type: 'text' + } + } + }, + bar: { + properties: { + baz: { + type: 'text', + fields: { + box: { + type: 'keyword' + } + } + } + } + } + } + } +}; + +function test(key, mapping) { + expect(typeof key === 'string' || Array.isArray(key)).to.be.ok(); + expect(mapping).to.be.an('object'); + + expect(getProperty(MAPPINGS, key)).to.be(mapping); +} + +describe('getProperty(mappings, path)', () => { + describe('string key', () => { + it('finds root properties', () => { + test('foo', MAPPINGS.rootType.properties.foo); + }); + it('finds nested properties', () => { + test('foo.name', MAPPINGS.rootType.properties.foo.properties.name); + test('foo.description', MAPPINGS.rootType.properties.foo.properties.description); + test('bar.baz', MAPPINGS.rootType.properties.bar.properties.baz); + }); + it('finds nested multi-fields', () => { + test('bar.baz.box', MAPPINGS.rootType.properties.bar.properties.baz.fields.box); + }); + }); + describe('string key', () => { + it('finds root properties', () => { + test(['foo'], MAPPINGS.rootType.properties.foo); + }); + it('finds nested properties', () => { + test(['foo', 'name'], MAPPINGS.rootType.properties.foo.properties.name); + test(['foo', 'description'], MAPPINGS.rootType.properties.foo.properties.description); + test(['bar', 'baz'], MAPPINGS.rootType.properties.bar.properties.baz); + }); + it('finds nested multi-fields', () => { + test(['bar', 'baz', 'box'], MAPPINGS.rootType.properties.bar.properties.baz.fields.box); + }); + }); +}); diff --git a/src/server/mappings/lib/get_property.js b/src/server/mappings/lib/get_property.js new file mode 100644 index 0000000000000..19b8ca1cfe0c8 --- /dev/null +++ b/src/server/mappings/lib/get_property.js @@ -0,0 +1,40 @@ +import toPath from 'lodash/internal/toPath'; + +import { getRootType } from './get_root_type'; + +/** + * Recursively read properties from the mapping object of type "object" + * until the `path` is resolved. + * @param {EsObjectMapping} mapping + * @param {Array} path + * @return {Objects|undefined} + */ +function getPropertyMappingFromObjectMapping(mapping, path) { + const props = mapping && (mapping.properties || mapping.fields); + + if (!props) { + return undefined; + } + + if (path.length > 1) { + return getPropertyMappingFromObjectMapping( + props[path[0]], + path.slice(1) + ); + } else { + return props[path[0]]; + } +} + +/** + * Get the mapping for a specific property within the root type of the EsMappingsDsl. + * @param {EsMappingsDsl} mappings + * @param {string|Array} path + * @return {Object|undefined} + */ +export function getProperty(mappings, path) { + return getPropertyMappingFromObjectMapping( + mappings[getRootType(mappings)], + toPath(path) + ); +} diff --git a/src/server/mappings/lib/get_root_properties.js b/src/server/mappings/lib/get_root_properties.js new file mode 100644 index 0000000000000..b43a54343faf7 --- /dev/null +++ b/src/server/mappings/lib/get_root_properties.js @@ -0,0 +1,29 @@ +import { getRootType } from './get_root_type'; + +/** + * Get the property mappings for the root type in the EsMappingsDsl + * + * If the mappings don't have a root type, or the root type is not + * an object type (it's a keyword or something) this function will + * throw an error. + * + * EsPropertyMappings objects have the root property names as their + * first level keys which map to the mappings object for each property. + * If the property is of type object it too could have a `properties` + * key whose value follows the same format. + * + * This data can be found at `{indexName}.mappings.{typeName}.properties` + * in the es indices.get() response. + * + * @param {EsMappingsDsl} mappings + * @return {EsPropertyMappings} + */ +export function getRootProperties(mappings) { + const mapping = mappings[getRootType(mappings)]; + + if (mapping.type !== 'object' && !mapping.properties) { + throw new TypeError('Unable to get property names non-object root mapping'); + } + + return mapping.properties || {}; +} diff --git a/src/server/mappings/lib/get_root_type.js b/src/server/mappings/lib/get_root_type.js new file mode 100644 index 0000000000000..d028f1564325c --- /dev/null +++ b/src/server/mappings/lib/get_root_type.js @@ -0,0 +1,19 @@ +import { getTypes } from './get_types'; + +/** + * Get the singular root type in the EsMappingsDsl + * object. If there are no types, or there are more + * that one type, this function will throw an error. + * + * @param {EsMappingsDsl} mappings + * @return {string} + */ +export function getRootType(mappings) { + const allTypes = getTypes(mappings); + + if (allTypes.length !== 1) { + throw new TypeError(`Unable to get root type of mappings object with ${allTypes.length} root types.`); + } + + return allTypes[0]; +} diff --git a/src/server/mappings/lib/get_types.js b/src/server/mappings/lib/get_types.js new file mode 100644 index 0000000000000..e3ac283d307de --- /dev/null +++ b/src/server/mappings/lib/get_types.js @@ -0,0 +1,9 @@ +/** + * Get the names of the types defined in the EsMappingsDsl + * + * @param {EsMappingsDsl} mappings + * @return {Array} + */ +export function getTypes(mappings) { + return Object.keys(mappings); +} diff --git a/src/server/mappings/lib/index.js b/src/server/mappings/lib/index.js new file mode 100644 index 0000000000000..7998922732298 --- /dev/null +++ b/src/server/mappings/lib/index.js @@ -0,0 +1,4 @@ +export { getProperty } from './get_property'; +export { getTypes } from './get_types'; +export { getRootType } from './get_root_type'; +export { getRootProperties } from './get_root_properties'; diff --git a/src/server/saved_objects/client/__tests__/saved_objects_client.js b/src/server/saved_objects/client/__tests__/saved_objects_client.js index 24dec0e7848c0..f338c1f5dc561 100644 --- a/src/server/saved_objects/client/__tests__/saved_objects_client.js +++ b/src/server/saved_objects/client/__tests__/saved_objects_client.js @@ -1,71 +1,94 @@ import expect from 'expect.js'; import sinon from 'sinon'; import { SavedObjectsClient } from '../saved_objects_client'; -import { createIdQuery } from '../lib/create_id_query'; +import * as getSearchDslNS from '../lib/search_dsl/search_dsl'; +import { getSearchDsl } from '../lib'; describe('SavedObjectsClient', () => { + const sandbox = sinon.sandbox.create(); + let callAdminCluster; let savedObjectsClient; - const docs = { + const searchResults = { hits: { total: 3, hits: [{ _index: '.kibana', - _type: 'index-pattern', - _id: 'logstash-*', + _type: 'doc', + _id: 'index-pattern:logstash-*', _score: 1, _source: { - title: 'logstash-*', - timeFieldName: '@timestamp', - notExpandable: true + type: 'index-pattern', + 'index-pattern': { + title: 'logstash-*', + timeFieldName: '@timestamp', + notExpandable: true + } } }, { _index: '.kibana', - _type: 'config', - _id: '6.0.0-alpha1', + _type: 'doc', + _id: 'config:6.0.0-alpha1', _score: 1, _source: { - buildNum: 8467, - defaultIndex: 'logstash-*' + type: 'config', + config: { + buildNum: 8467, + defaultIndex: 'logstash-*' + } } }, { _index: '.kibana', - _type: 'index-pattern', - _id: 'stocks-*', + _type: 'doc', + _id: 'index-pattern:stocks-*', _score: 1, _source: { - title: 'stocks-*', - timeFieldName: '@timestamp', - notExpandable: true + type: 'index-pattern', + 'index-pattern': { + title: 'stocks-*', + timeFieldName: '@timestamp', + notExpandable: true + } } }] } }; const mappings = { - 'index-pattern': { + doc: { properties: { - someField: { - type: 'keyword' + 'index-pattern': { + properties: { + someField: { + type: 'keyword' + } + } } } } }; beforeEach(() => { - callAdminCluster = sinon.mock(); + callAdminCluster = sandbox.stub(); savedObjectsClient = new SavedObjectsClient('.kibana-test', mappings, callAdminCluster); + sandbox.stub(getSearchDslNS, 'getSearchDsl').returns({}); }); afterEach(() => { - callAdminCluster.reset(); + sandbox.restore(); }); describe('#create', () => { - it('formats Elasticsearch response', async () => { - callAdminCluster.returns({ _type: 'index-pattern', _id: 'logstash-*', _version: 2 }); + beforeEach(() => { + callAdminCluster.returns(Promise.resolve({ + _type: 'doc', + _id: 'index-pattern:logstash-*', + _version: 2 + })); + }); + it('formats Elasticsearch response', async () => { const response = await savedObjectsClient.create('index-pattern', { title: 'Logstash' }); @@ -81,8 +104,6 @@ describe('SavedObjectsClient', () => { }); it('should use ES index action', async () => { - callAdminCluster.returns({ _type: 'index-pattern', _id: 'logstash-*', _version: 2 }); - await savedObjectsClient.create('index-pattern', { id: 'logstash-*', title: 'Logstash' @@ -95,8 +116,6 @@ describe('SavedObjectsClient', () => { }); it('should use create action if ID defined and overwrite=false', async () => { - callAdminCluster.returns({ _type: 'index-pattern', _id: 'logstash-*', _version: 2 }); - await savedObjectsClient.create('index-pattern', { title: 'Logstash' }, { @@ -110,22 +129,32 @@ describe('SavedObjectsClient', () => { }); it('allows for id to be provided', async () => { - callAdminCluster.returns({ _type: 'index-pattern', _id: 'logstash-*', _version: 2 }); + await savedObjectsClient.create('index-pattern', { + title: 'Logstash' + }, { id: 'logstash-*' }); + + expect(callAdminCluster.calledOnce).to.be(true); + + const args = callAdminCluster.getCall(0).args; + expect(args[1].id).to.be('index-pattern:logstash-*'); + }); + it('self-generates an ID', async () => { await savedObjectsClient.create('index-pattern', { - id: 'logstash-*', title: 'Logstash' - }, { id: 'myId' }); + }); expect(callAdminCluster.calledOnce).to.be(true); const args = callAdminCluster.getCall(0).args; - expect(args[1].id).to.be('myId'); + expect(args[1].id).to.match(/index-pattern:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/); }); }); describe('#bulkCreate', () => { it('formats Elasticsearch request', async () => { + callAdminCluster.returns({ items: [] }); + await savedObjectsClient.bulkCreate([ { type: 'config', id: 'one', attributes: { title: 'Test One' } }, { type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } } @@ -137,30 +166,39 @@ describe('SavedObjectsClient', () => { expect(args[0]).to.be('bulk'); expect(args[1].body).to.eql([ - { create: { _type: 'config', _id: 'one' } }, - { title: 'Test One' }, - { create: { _type: 'index-pattern', _id: 'two' } }, - { title: 'Test Two' } + { create: { _type: 'doc', _id: 'config:one' } }, + { type: 'config', config: { title: 'Test One' } }, + { create: { _type: 'doc', _id: 'index-pattern:two' } }, + { type: 'index-pattern', 'index-pattern': { title: 'Test Two' } } ]); }); it('should overwrite objects if overwrite is truthy', async () => { - await savedObjectsClient.bulkCreate([ - { type: 'config', id: 'one', attributes: { title: 'Test One' } }, - { type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } } - ], { overwrite: true }); + callAdminCluster.returns({ items: [] }); + + await savedObjectsClient.bulkCreate([{ type: 'foo', id: 'bar', attributes: {} }], { overwrite: false }); + sinon.assert.calledOnce(callAdminCluster); + sinon.assert.calledWithExactly(callAdminCluster, 'bulk', sinon.match({ + body: [ + // uses create because overwriting is not allowed + { create: { _type: 'doc', _id: 'foo:bar' } }, + { type: 'foo', 'foo': {} }, + ] + })); - expect(callAdminCluster.calledOnce).to.be(true); + callAdminCluster.reset(); + + await savedObjectsClient.bulkCreate([{ type: 'foo', id: 'bar', attributes: {} }], { overwrite: true }); + sinon.assert.calledOnce(callAdminCluster); + sinon.assert.calledWithExactly(callAdminCluster, 'bulk', sinon.match({ + body: [ + // uses index because overwriting is allowed + { index: { _type: 'doc', _id: 'foo:bar' } }, + { type: 'foo', 'foo': {} }, + ] + })); - const args = callAdminCluster.getCall(0).args; - expect(args[0]).to.be('bulk'); - expect(args[1].body).to.eql([ - { index: { _type: 'config', _id: 'one' } }, - { title: 'Test One' }, - { index: { _type: 'index-pattern', _id: 'two' } }, - { title: 'Test Two' } - ]); }); it('returns document errors', async () => { @@ -168,16 +206,16 @@ describe('SavedObjectsClient', () => { errors: false, items: [{ create: { - _type: 'config', - _id: 'one', + _type: 'doc', + _id: 'config:one', error: { reason: 'type[config] missing' } } }, { create: { - _type: 'index-pattern', - _id: 'two', + _type: 'doc', + _id: 'index-pattern:two', _version: 2 } }] @@ -192,15 +230,12 @@ describe('SavedObjectsClient', () => { { id: 'one', type: 'config', - version: undefined, - attributes: { title: 'Test One' }, error: { message: 'type[config] missing' } }, { id: 'two', type: 'index-pattern', version: 2, attributes: { title: 'Test Two' }, - error: undefined } ]); }); @@ -210,14 +245,14 @@ describe('SavedObjectsClient', () => { errors: false, items: [{ create: { - _type: 'config', - _id: 'one', + _type: 'doc', + _id: 'config:one', _version: 2 } }, { create: { - _type: 'index-pattern', - _id: 'two', + _type: 'doc', + _id: 'index-pattern:two', _version: 2 } }] @@ -234,41 +269,40 @@ describe('SavedObjectsClient', () => { type: 'config', version: 2, attributes: { title: 'Test One' }, - error: undefined }, { id: 'two', type: 'index-pattern', version: 2, attributes: { title: 'Test Two' }, - error: undefined } ]); }); }); describe('#delete', () => { - it('throws notFound when ES is unable to find the document', (done) => { - callAdminCluster.returns(Promise.resolve({ - deleted: 0 - })); + it('throws notFound when ES is unable to find the document', async () => { + callAdminCluster.returns(Promise.resolve({ found: false })); + + try { + await savedObjectsClient.delete('index-pattern', 'logstash-*'); + expect().fail('should throw error'); + } catch(e) { + expect(e.output.statusCode).to.eql(404); + } - savedObjectsClient.delete('index-pattern', 'logstash-*').then(() => { - done('failed'); - }).catch(e => { - expect(e.output.statusCode).to.be(404); - done(); - }); }); it('passes the parameters to callAdminCluster', async () => { + callAdminCluster.returns({}); await savedObjectsClient.delete('index-pattern', 'logstash-*'); expect(callAdminCluster.calledOnce).to.be(true); - const args = callAdminCluster.getCall(0).args; - expect(args[0]).to.be('deleteByQuery'); - expect(args[1]).to.eql({ - body: createIdQuery({ type: 'index-pattern', id: 'logstash-*' }), + const [method, args] = callAdminCluster.getCall(0).args; + expect(method).to.be('delete'); + expect(args).to.eql({ + type: 'doc', + id: 'index-pattern:logstash-*', refresh: 'wait_for', index: '.kibana-test' }); @@ -276,20 +310,68 @@ describe('SavedObjectsClient', () => { }); describe('#find', () => { + beforeEach(() => { + callAdminCluster.returns(searchResults); + }); + + it('requires searchFields be an array if defined', async () => { + try { + await savedObjectsClient.find({ searchFields: 'string' }); + throw new Error('expected find() to reject'); + } catch (error) { + expect(error).to.have.property('message').contain('must be an array'); + } + }); + + it('requires fields be an array if defined', async () => { + try { + await savedObjectsClient.find({ fields: 'string' }); + throw new Error('expected find() to reject'); + } catch (error) { + expect(error).to.have.property('message').contain('must be an array'); + } + }); + + it('passes mappings, search, searchFields, type, sortField, and sortOrder to getSearchDsl', async () => { + const relevantOpts = { + search: 'foo*', + searchFields: ['foo'], + type: 'bar', + sortField: 'name', + sortOrder: 'desc' + }; + + await savedObjectsClient.find(relevantOpts); + sinon.assert.calledOnce(getSearchDsl); + sinon.assert.calledWithExactly(getSearchDsl, mappings, relevantOpts); + }); + + it('merges output of getSearchDsl into es request body', async () => { + getSearchDsl.returns({ query: 1, aggregations: 2 }); + await savedObjectsClient.find(); + sinon.assert.calledOnce(callAdminCluster); + sinon.assert.calledWithExactly(callAdminCluster, 'search', sinon.match({ + body: sinon.match({ + query: 1, + aggregations: 2, + }) + })); + }); + it('formats Elasticsearch response', async () => { - const count = docs.hits.hits.length; + const count = searchResults.hits.hits.length; - callAdminCluster.returns(Promise.resolve(docs)); const response = await savedObjectsClient.find(); expect(response.total).to.be(count); expect(response.saved_objects).to.have.length(count); - docs.hits.hits.forEach((doc, i) => { + + searchResults.hits.hits.forEach((doc, i) => { expect(response.saved_objects[i]).to.eql({ - id: doc._id, - type: doc._type, + id: doc._id.replace(/(index-pattern|config)\:/, ''), + type: doc._source.type, version: doc._version, - attributes: doc._source + attributes: doc._source[doc._source.type] }); }); }); @@ -304,80 +386,8 @@ describe('SavedObjectsClient', () => { expect(options.from).to.be(50); }); - it('accepts type', async () => { - await savedObjectsClient.find({ type: 'index-pattern' }); - - expect(callAdminCluster.calledOnce).to.be(true); - - const options = callAdminCluster.getCall(0).args[1]; - const expectedQuery = { - bool: { - must: [{ match_all: {} }], - filter: [ - { - bool: { - should: [ - { - term: { - _type: 'index-pattern' - } - }, { - term: { - type: 'index-pattern' - } - } - ] - } - } - ] - } - }; - - expect(options.body).to.eql({ - query: expectedQuery, version: true - }); - }); - - it('throws error when providing sortField but no type', (done) => { - savedObjectsClient.find({ - sortField: 'someField' - }).then(() => { - done('failed'); - }).catch(e => { - expect(e).to.be.an(Error); - done(); - }); - }); - - it('accepts sort with type', async () => { - await savedObjectsClient.find({ - type: 'index-pattern', - sortField: 'someField', - sortOrder: 'desc', - }); - - expect(callAdminCluster.calledOnce).to.be(true); - - const options = callAdminCluster.getCall(0).args[1]; - const expectedQuerySort = [ - { - someField: { - order: 'desc', - unmapped_type: 'keyword' - }, - }, { - 'index-pattern.someField': { - order: 'desc', - unmapped_type: 'keyword' - }, - }, - ]; - - expect(options.body.sort).to.eql(expectedQuerySort); - }); - it('can filter by fields', async () => { - await savedObjectsClient.find({ fields: 'title' }); + await savedObjectsClient.find({ fields: ['title'] }); expect(callAdminCluster.calledOnce).to.be(true); @@ -389,22 +399,21 @@ describe('SavedObjectsClient', () => { }); describe('#get', () => { - it('formats Elasticsearch response', async () => { + beforeEach(() => { callAdminCluster.returns(Promise.resolve({ - hits: { - hits: [ - { - _id: 'logstash-*', - _type: 'index-pattern', - _version: 2, - _source: { - title: 'Testing' - } - } - ] + _id: 'index-pattern:logstash-*', + _type: 'doc', + _version: 2, + _source: { + type: 'index-pattern', + 'index-pattern': { + title: 'Testing' + } } })); + }); + it('formats Elasticsearch response', async () => { const response = await savedObjectsClient.get('index-pattern', 'logstash-*'); expect(response).to.eql({ id: 'logstash-*', @@ -415,10 +424,20 @@ describe('SavedObjectsClient', () => { } }); }); + + it('prepends type to the id', async () => { + await savedObjectsClient.get('index-pattern', 'logstash-*'); + + const [, args] = callAdminCluster.getCall(0).args; + expect(args.id).to.eql('index-pattern:logstash-*'); + expect(args.type).to.eql('doc'); + }); }); describe('#bulkGet', () => { - it('accepts an array of mixed type and ids', async () => { + it('accepts a array of mixed type and ids', async () => { + callAdminCluster.returns({ docs: [] }); + await savedObjectsClient.bulkGet([ { id: 'one', type: 'config' }, { id: 'two', type: 'index-pattern' } @@ -427,15 +446,15 @@ describe('SavedObjectsClient', () => { expect(callAdminCluster.calledOnce).to.be(true); const options = callAdminCluster.getCall(0).args[1]; - expect(options.body).to.eql([ - {}, - createIdQuery({ type: 'config', id: 'one' }), - {}, - createIdQuery({ type: 'index-pattern', id: 'two' }) + expect(options.body.docs).to.eql([ + { _type: 'doc', _id: 'config:one' }, + { _type: 'doc', _id: 'index-pattern:two' } ]); }); it('returns early for empty objects argument', async () => { + callAdminCluster.returns({ docs: [] }); + const response = await savedObjectsClient.bulkGet([]); expect(response.saved_objects).to.have.length(0); @@ -444,74 +463,73 @@ describe('SavedObjectsClient', () => { it('reports error on missed objects', async () => { callAdminCluster.returns(Promise.resolve({ - responses: [ - { - hits: { - hits: [ - { - _id: 'good', - _type: 'doc', - _version: 2, - _source: { - type: 'config', - config: { - title: 'Test' - } - } - } - ] - } - } - ] + docs:[{ + _type: 'doc', + _id: 'config:good', + found: true, + _version: 2, + _source: { config: { title: 'Test' } } + }, { + _type: 'doc', + _id: 'config:bad', + found: false + }] })); const { saved_objects: savedObjects } = await savedObjectsClient.bulkGet( [{ id: 'good', type: 'config' }, { id: 'bad', type: 'config' }] ); - expect(savedObjects).to.have.length(1); + expect(savedObjects).to.have.length(2); expect(savedObjects[0]).to.eql({ id: 'good', type: 'config', version: 2, attributes: { title: 'Test' } }); + expect(savedObjects[1]).to.eql({ + id: 'bad', + type: 'config', + error: { statusCode: 404, message: 'Not found' } + }); }); }); describe('#update', () => { - it('returns current ES document version', async () => { - const id = 'logstash-*'; - const type = 'index-pattern'; - const version = 2; - const attributes = { title: 'Testing' }; + const id = 'logstash-*'; + const type = 'index-pattern'; + const newVersion = 2; + const attributes = { title: 'Testing' }; + beforeEach(() => { callAdminCluster.returns(Promise.resolve({ - _id: id, - _type: type, - _version: version, + _id: `${type}:${id}`, + _type: 'doc', + _version: newVersion, result: 'updated' })); + }); + it('returns current ES document version', async () => { const response = await savedObjectsClient.update('index-pattern', 'logstash-*', attributes); expect(response).to.eql({ id, type, - version, + version: newVersion, attributes }); }); it('accepts version', async () => { await savedObjectsClient.update( - 'index-pattern', - 'logstash-*', + type, + id, { title: 'Testing' }, - { version: 1 } + { version: newVersion - 1 } ); const esParams = callAdminCluster.getCall(0).args[1]; - expect(esParams.version).to.be(1); + expect(esParams.version).to.be(newVersion - 1); }); it('passes the parameters to callAdminCluster', async () => { @@ -523,10 +541,10 @@ describe('SavedObjectsClient', () => { expect(args[0]).to.be('update'); expect(args[1]).to.eql({ - type: 'index-pattern', - id: 'logstash-*', + type: 'doc', + id: 'index-pattern:logstash-*', version: undefined, - body: { doc: { title: 'Testing' } }, + body: { doc: { 'index-pattern': { title: 'Testing' } } }, refresh: 'wait_for', index: '.kibana-test' }); diff --git a/src/server/saved_objects/client/__tests__/saved_objects_client_mappings.js b/src/server/saved_objects/client/__tests__/saved_objects_client_mappings.js deleted file mode 100644 index 05b555ec1b346..0000000000000 --- a/src/server/saved_objects/client/__tests__/saved_objects_client_mappings.js +++ /dev/null @@ -1,195 +0,0 @@ -import elasticsearch from 'elasticsearch'; -import expect from 'expect.js'; -import sinon from 'sinon'; - -import { SavedObjectsClient } from '../saved_objects_client'; -const { BadRequest } = elasticsearch.errors; - -describe('SavedObjectsClient', () => { - let callAdminCluster; - let savedObjectsClient; - const illegalArgumentException = { type: 'type_missing_exception' }; - - describe('mapping', () => { - beforeEach(() => { - callAdminCluster = sinon.stub(); - savedObjectsClient = new SavedObjectsClient('.kibana-test', {}, callAdminCluster); - }); - - afterEach(() => { - callAdminCluster.reset(); - }); - - - describe('#create', () => { - it('falls back to single-type mapping', async () => { - const error = new BadRequest('[illegal_argument_exception] Rejecting mapping update to [.kibana-v6]', { - body: { - error: illegalArgumentException - } - }); - - callAdminCluster - .onFirstCall().throws(error) - .onSecondCall().returns(Promise.resolve({ _type: 'index-pattern', _id: 'logstash-*', _version: 2 })); - - const response = await savedObjectsClient.create('index-pattern', { - title: 'Logstash' - }); - - expect(response).to.eql({ - type: 'index-pattern', - id: 'logstash-*', - version: 2, - attributes: { - title: 'Logstash', - } - }); - }); - - it('prepends id for single-type', async () => { - const id = 'foo'; - const error = new BadRequest('[illegal_argument_exception] Rejecting mapping update to [.kibana-v6]', { - body: { - error: illegalArgumentException - } - }); - - callAdminCluster - .onFirstCall().throws(error) - .onSecondCall().returns(Promise.resolve()); - - await savedObjectsClient.create('index-pattern', {}, { id }); - - const [, args] = callAdminCluster.getCall(1).args; - expect(args.id).to.eql('index-pattern:foo'); - }); - }); - - describe('#bulkCreate', () => { - const firstResponse = { - errors: true, - items: [{ - create: { - _type: 'config', - _id: 'one', - _version: 2, - status: 400, - error: illegalArgumentException - } - }, { - create: { - _type: 'index-pattern', - _id: 'two', - _version: 2, - status: 400, - error: illegalArgumentException - } - }] - }; - - const secondResponse = { - errors: false, - items: [{ - create: { - _type: 'config', - _id: 'one', - _version: 2 - } - }, { - create: { - _type: 'index-pattern', - _id: 'two', - _version: 2 - } - }] - }; - - it('falls back to single-type mappings', async () => { - callAdminCluster - .onFirstCall().returns(Promise.resolve(firstResponse)) - .onSecondCall().returns(Promise.resolve(secondResponse)); - - const response = await savedObjectsClient.bulkCreate([ - { type: 'config', id: 'one', attributes: { title: 'Test One' } }, - { type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } } - ]); - - expect(response).to.eql([ - { - id: 'one', - type: 'config', - version: 2, - attributes: { title: 'Test One' }, - error: undefined - }, { - id: 'two', - type: 'index-pattern', - version: 2, - attributes: { title: 'Test Two' }, - error: undefined - } - ]); - }); - - it('prepends id for single-type', async () => { - callAdminCluster - .onFirstCall().returns(Promise.resolve(firstResponse)) - .onSecondCall().returns(Promise.resolve(secondResponse)); - - await savedObjectsClient.bulkCreate([ - { type: 'config', id: 'one', attributes: { title: 'Test One' } }, - { type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } } - ]); - - const [, { body }] = callAdminCluster.getCall(1).args; - expect(body[0].create._id).to.eql('config:one'); - expect(body[2].create._id).to.eql('index-pattern:two'); - // expect(args.id).to.eql('index-pattern:foo'); - }); - }); - - describe('update', () => { - const id = 'logstash-*'; - const type = 'index-pattern'; - const version = 2; - const attributes = { title: 'Testing' }; - const error = new BadRequest('[document_missing_exception] [config][logstash-*]: document missing', { - body: { - error: { - type: 'document_missing_exception' - } - } - }); - - beforeEach(() => { - callAdminCluster - .onFirstCall().throws(error) - .onSecondCall().returns(Promise.resolve({ - _id: id, - _type: type, - _version: version, - result: 'updated' - })); - }); - - - it('falls back to single-type mappings', async () => { - const response = await savedObjectsClient.update('index-pattern', 'logstash-*', attributes); - expect(response).to.eql({ - id, - type, - version, - attributes - }); - }); - - it('prepends id for single-type', async () => { - await savedObjectsClient.update('index-pattern', 'logstash-*', attributes); - - const [, args] = callAdminCluster.getCall(1).args; - expect(args.id).to.eql('index-pattern:logstash-*'); - }); - }); - }); -}); diff --git a/src/server/saved_objects/client/lib/__tests__/compatibility.js b/src/server/saved_objects/client/lib/__tests__/compatibility.js deleted file mode 100644 index 33322e5320460..0000000000000 --- a/src/server/saved_objects/client/lib/__tests__/compatibility.js +++ /dev/null @@ -1,53 +0,0 @@ -import expect from 'expect.js'; -import { v5BulkCreate, v6BulkCreate } from '../compatibility'; - -describe('compatibility', () => { - const testObjects = [ - { type: 'index-pattern', id: 'one', attributes: { title: 'Test Index Pattern' } }, - { type: 'config', id: 'two', attributes: { title: 'Test Config Value' } } - ]; - - describe('v5BulkCreate', () => { - it('handles default options', () => { - const objects = v5BulkCreate(testObjects); - expect(objects).to.eql([ - { create: { _type: 'index-pattern', _id: 'one' } }, - { title: 'Test Index Pattern' }, - { create: { _type: 'config', _id: 'two' } }, - { title: 'Test Config Value' } - ]); - }); - - it('uses index action for options.overwrite=true', () => { - const objects = v5BulkCreate(testObjects, { overwrite: true }); - expect(objects).to.eql([ - { index: { _type: 'index-pattern', _id: 'one' } }, - { title: 'Test Index Pattern' }, - { index: { _type: 'config', _id: 'two' } }, - { title: 'Test Config Value' } - ]); - }); - }); - - describe('v6BulkCreate', () => { - it('handles default options', () => { - const objects = v6BulkCreate(testObjects); - expect(objects).to.eql([ - { create: { _type: 'doc', _id: 'index-pattern:one' } }, - { type: 'index-pattern', 'index-pattern': { title: 'Test Index Pattern' } }, - { create: { _type: 'doc', _id: 'config:two' } }, - { type: 'config', config: { title: 'Test Config Value' } } - ]); - }); - - it('uses index action for options.overwrite=true', () => { - const objects = v6BulkCreate(testObjects, { overwrite: true }); - expect(objects).to.eql([ - { index: { _type: 'doc', _id: 'index-pattern:one' } }, - { type: 'index-pattern', 'index-pattern': { title: 'Test Index Pattern' } }, - { index: { _type: 'doc', _id: 'config:two' } }, - { type: 'config', config: { title: 'Test Config Value' } } - ]); - }); - }); -}); diff --git a/src/server/saved_objects/client/lib/__tests__/create_find_query.js b/src/server/saved_objects/client/lib/__tests__/create_find_query.js deleted file mode 100644 index 60c78a59b446a..0000000000000 --- a/src/server/saved_objects/client/lib/__tests__/create_find_query.js +++ /dev/null @@ -1,96 +0,0 @@ -import expect from 'expect.js'; -import { createFindQuery } from '../create_find_query'; - -const mappings = {}; - -describe('createFindQuery', () => { - it('matches all when there is no type or filter', () => { - const query = createFindQuery(mappings); - expect(query).to.eql({ query: { match_all: {} }, version: true }); - }); - - it('adds bool filter for type', () => { - const query = createFindQuery(mappings, { type: 'index-pattern' }); - expect(query).to.eql({ - query: { - bool: { - filter: [{ - bool: { - should: [ - { - term: { - _type: 'index-pattern' - } - }, - { - term: { - type: 'index-pattern' - } - } - ] - - } - }], - must: [{ - match_all: {} - }] - } - }, - version: true - }); - }); - - it('can search across all fields', () => { - const query = createFindQuery(mappings, { search: 'foo' }); - expect(query).to.eql({ - query: { - bool: { - filter: [], - must: [{ - simple_query_string: { - query: 'foo', - all_fields: true - } - }] - } - }, - version: true - }); - }); - - it('can search a single field', () => { - const query = createFindQuery(mappings, { search: 'foo', searchFields: 'title' }); - expect(query).to.eql({ - query: { - bool: { - filter: [], - must: [{ - simple_query_string: { - query: 'foo', - fields: ['title'] - } - }] - } - }, - version: true - }); - }); - - it('can search across multiple fields', () => { - const query = createFindQuery(mappings, { search: 'foo', searchFields: ['title', 'description'] }); - expect(query).to.eql({ - query: { - bool: { - filter: [], - must: [{ - simple_query_string: { - query: 'foo', - fields: ['title', 'description'] - } - }] - } - }, - version: true - }); - }); -}); diff --git a/src/server/saved_objects/client/lib/__tests__/create_id_query.js b/src/server/saved_objects/client/lib/__tests__/create_id_query.js deleted file mode 100644 index 735e17743baeb..0000000000000 --- a/src/server/saved_objects/client/lib/__tests__/create_id_query.js +++ /dev/null @@ -1,48 +0,0 @@ -import expect from 'expect.js'; -import { createIdQuery } from '../create_id_query'; - -describe('createIdQuery', () => { - it('takes an id and type', () => { - const query = createIdQuery({ id: 'foo', type: 'bar' }); - - const expectedQuery = { - version: true, - size: 1, - query: { - bool: { - should: [ - // v5 document - { - bool: { - must: [ - { term: { _id: 'foo' } }, - { term: { _type: 'bar' } } - ] - } - }, - // migrated v5 document - { - bool: { - must: [ - { term: { _id: 'bar:foo' } }, - { term: { type: 'bar' } } - ] - } - }, - // v6 document - { - bool: { - must: [ - { term: { _id: 'foo' } }, - { term: { type: 'bar' } } - ] - } - }, - ] - } - } - }; - - expect(query).to.eql(expectedQuery); - }); -}); diff --git a/src/server/saved_objects/client/lib/__tests__/normalize_es_doc.js b/src/server/saved_objects/client/lib/__tests__/normalize_es_doc.js deleted file mode 100644 index 51b828d2fd862..0000000000000 --- a/src/server/saved_objects/client/lib/__tests__/normalize_es_doc.js +++ /dev/null @@ -1,105 +0,0 @@ -import expect from 'expect.js'; -import { normalizeEsDoc } from '../normalize_es_doc'; - -describe('normalizeEsDoc', () => { - it('handle legacy doc types', () => { - const doc = { - _id: 'foo', - _type: 'test', - _version: 2, - _source: { title: 'test' } - }; - - expect(normalizeEsDoc(doc)).to.eql({ - id: 'foo', - type: 'test', - version: 2, - attributes: { title: 'test' } - }); - }); - - it('handle migrated single doc type', () => { - const doc = { - _id: 'test:foo', - _type: 'doc', - _version: 2, - _source: { type: 'test', test: { title: 'test' } } - }; - - expect(normalizeEsDoc(doc)).to.eql({ - id: 'foo', - type: 'test', - version: 2, - attributes: { title: 'test' } - }); - }); - - it('handles an overwritten type', () => { - const doc = { - _type: 'doc', - _id: 'test:foo', - _version: 2, - _source: { type: 'test', test: { title: 'test' } } - }; - const overrides = { type: 'test' }; - - expect(normalizeEsDoc(doc, overrides)).to.eql({ - id: 'foo', - type: 'test', - version: 2, - attributes: { title: 'test' } - }); - }); - - it('can add additional keys', () => { - const doc = { - _type: 'doc', - _id: 'test:foo', - _version: 2, - _source: { type: 'test', test: { title: 'test' } } - }; - const overrides = { error: 'An error!' }; - - expect(normalizeEsDoc(doc, overrides)).to.eql({ - id: 'foo', - type: 'test', - version: 2, - attributes: { title: 'test' }, - error: 'An error!' - }); - }); - - it('handles already prefixed ids with the type', () => { - const doc = { - _type: 'doc', - _id: 'test:test:foo', - _version: 2, - _source: { type: 'test', test: { title: 'test' } } - }; - const overrides = { error: 'An error!' }; - - expect(normalizeEsDoc(doc, overrides)).to.eql({ - id: 'test:foo', - type: 'test', - version: 2, - attributes: { title: 'test' }, - error: 'An error!' - }); - }); - - it('handles legacy doc having an attribute the same as type', () => { - const doc = { - _id: 'foo', - _type: 'test', - _version: 2, - _source: { test: 'test' } - }; - - expect(normalizeEsDoc(doc)).to.eql({ - id: 'foo', - type: 'test', - version: 2, - attributes: { test: 'test' } - }); - }); -}); diff --git a/src/server/saved_objects/client/lib/compatibility.js b/src/server/saved_objects/client/lib/compatibility.js deleted file mode 100644 index 27ef4da99bb3c..0000000000000 --- a/src/server/saved_objects/client/lib/compatibility.js +++ /dev/null @@ -1,43 +0,0 @@ -import uuid from 'uuid'; -import { V6_TYPE } from '../saved_objects_client'; - -/** - * @param {array} objects - [{ type, id, attributes }] - * @param {object} [options={}] - * @property {boolean} [options.overwrite=false] - overrides existing documents - * @returns {array} - */ -export function v5BulkCreate(objects, options = {}) { - return objects.reduce((acc, object) => { - const method = object.id && !options.overwrite ? 'create' : 'index'; - - acc.push({ [method]: { _type: object.type, _id: object.id } }); - acc.push(object.attributes); - - return acc; - }, []); -} - -/** - * @param {array} objects - [{ type, id, attributes }] - * @param {object} [options={}] - * @property {boolean} [options.overwrite=false] - overrides existing documents - * @returns {array} - */ -export function v6BulkCreate(objects, options = {}) { - return objects.reduce((acc, object) => { - const method = object.id && !options.overwrite ? 'create' : 'index'; - - acc.push({ [method]: { - _type: V6_TYPE, - _id: `${object.type}:${object.id || uuid.v1()}`, - } }); - - acc.push(Object.assign({}, - { type: object.type }, - { [object.type]: object.attributes } - )); - - return acc; - }, []); -} diff --git a/src/server/saved_objects/client/lib/create_find_query.js b/src/server/saved_objects/client/lib/create_find_query.js deleted file mode 100644 index 8cb6db72216ec..0000000000000 --- a/src/server/saved_objects/client/lib/create_find_query.js +++ /dev/null @@ -1,70 +0,0 @@ -import { get } from 'lodash'; -export function createFindQuery(mappings, options = {}) { - const { type, search, searchFields, sortField, sortOrder } = options; - - if (!type && sortField) { - throw new Error('Cannot sort without knowing the type'); - } - - if (!type && !search) { - return { version: true, query: { match_all: {} } }; - } - - const bool = { must: [], filter: [] }; - - if (type) { - bool.filter.push({ - bool: { - should: [ - { - term: { - _type: type - } - }, - { - term: { - type - } - } - ] - } - }); - } - - if (search) { - const simpleQueryString = { - query: search - }; - - if (!searchFields) { - simpleQueryString.all_fields = true; - } else if (Array.isArray(searchFields)) { - simpleQueryString.fields = searchFields; - } else { - simpleQueryString.fields = [searchFields]; - } - - bool.must.push({ simple_query_string: simpleQueryString }); - } else { - bool.must.push({ - match_all: {} - }); - } - - const query = { version: true, query: { bool } }; - - if (sortField) { - const value = { - order: sortOrder, - unmapped_type: get(mappings, [type, 'properties', sortField, 'type']) - }; - - query.sort = [{ - [sortField]: value - }, { - [`${type}.${sortField}`]: value - }]; - } - - return query; -} diff --git a/src/server/saved_objects/client/lib/create_id_query.js b/src/server/saved_objects/client/lib/create_id_query.js deleted file mode 100644 index 359adb39ccb64..0000000000000 --- a/src/server/saved_objects/client/lib/create_id_query.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Finds a document by either its v5 or v6 format - * - * @param type The documents type - * @param id The documents id or legacy id -**/ -export function createIdQuery({ type, id }) { - return { - version: true, - size: 1, - query: { - bool: { - should: [ - // v5 document - { - bool: { - must: [ - { term: { _id: id } }, - { term: { _type: type } } - ] - } - }, - // migrated v5 document - { - bool: { - must: [ - { term: { _id: `${type}:${id}` } }, - { term: { type: type } } - ] - } - }, - // v6 document - { - bool: { - must: [ - { term: { _id: id } }, - { term: { type: type } } - ] - } - }, - ] - } - } - }; -} diff --git a/src/server/saved_objects/client/lib/index.js b/src/server/saved_objects/client/lib/index.js index 2db84b11d49ec..3410337dfd35b 100644 --- a/src/server/saved_objects/client/lib/index.js +++ b/src/server/saved_objects/client/lib/index.js @@ -1,6 +1,4 @@ -export { createFindQuery } from './create_find_query'; -export { createIdQuery } from './create_id_query'; +export { getSearchDsl } from './search_dsl'; export { handleEsError } from './handle_es_error'; -export { v5BulkCreate, v6BulkCreate } from './compatibility'; -export { normalizeEsDoc } from './normalize_es_doc'; +export { trimIdPrefix } from './trim_id_prefix'; export { includedFields } from './included_fields'; diff --git a/src/server/saved_objects/client/lib/normalize_es_doc.js b/src/server/saved_objects/client/lib/normalize_es_doc.js deleted file mode 100644 index 061efece25158..0000000000000 --- a/src/server/saved_objects/client/lib/normalize_es_doc.js +++ /dev/null @@ -1,29 +0,0 @@ -import { get } from 'lodash'; -import { V6_TYPE } from '../saved_objects_client'; - -export function normalizeEsDoc(doc, overrides = {}) { - if (!doc) return {}; - - let type; - let id = doc._id; - let attributes; - - if (doc._type === V6_TYPE) { - type = overrides.type || get(doc, '_source.type'); - attributes = get(doc, `_source.${type}`); - - // migrated v5 indices and objects created with a specified ID - // have the type prefixed to the id. - id = doc._id.replace(`${type}:`, ''); - } else { - type = overrides.type || doc._type; - attributes = doc._source; - } - - return Object.assign({}, { - id, - type, - version: doc._version, - attributes - }, overrides); -} diff --git a/src/server/saved_objects/client/lib/search_dsl/__tests__/query_params.js b/src/server/saved_objects/client/lib/search_dsl/__tests__/query_params.js new file mode 100644 index 0000000000000..d12be9edfe4aa --- /dev/null +++ b/src/server/saved_objects/client/lib/search_dsl/__tests__/query_params.js @@ -0,0 +1,247 @@ +import expect from 'expect.js'; + +import { getQueryParams } from '../query_params'; + +const MAPPINGS = { + rootType: { + properties: { + type: { + type: 'keyword' + }, + pending: { + properties: { + title: { + type: 'text', + } + } + }, + saved: { + properties: { + title: { + type: 'text', + fields: { + raw: { + type: 'keyword' + } + } + }, + obj: { + properties: { + key1: { + type: 'text' + } + } + } + } + } + } + } +}; + +describe('searchDsl/queryParams', () => { + describe('{}', () => { + it('searches for everything', () => { + expect(getQueryParams(MAPPINGS)) + .to.eql({}); + }); + }); + + describe('{type}', () => { + it('includes just a terms filter', () => { + expect(getQueryParams(MAPPINGS, 'saved')) + .to.eql({ + query: { + bool: { + filter: [ + { + term: { type: 'saved' } + } + ] + } + } + }); + }); + }); + + describe('{search}', () => { + it('includes just a sqs query', () => { + expect(getQueryParams(MAPPINGS, null, 'us*')) + .to.eql({ + query: { + bool: { + must: [ + { + simple_query_string: { + query: 'us*', + all_fields: true + } + } + ] + } + } + }); + }); + }); + + describe('{type,search}', () => { + it('includes bool with sqs query and term filter for type', () => { + expect(getQueryParams(MAPPINGS, 'saved', 'y*')) + .to.eql({ + query: { + bool: { + filter: [ + { term: { type: 'saved' } } + ], + must: [ + { + simple_query_string: { + query: 'y*', + all_fields: true + } + } + ] + } + } + }); + }); + }); + + describe('{search,searchFields}', () => { + it('includes all types for field', () => { + expect(getQueryParams(MAPPINGS, null, 'y*', ['title'])) + .to.eql({ + query: { + bool: { + must: [ + { + simple_query_string: { + query: 'y*', + fields: [ + 'type.title', + 'pending.title', + 'saved.title' + ] + } + } + ] + } + } + }); + }); + it('supports field boosting', () => { + expect(getQueryParams(MAPPINGS, null, 'y*', ['title^3'])) + .to.eql({ + query: { + bool: { + must: [ + { + simple_query_string: { + query: 'y*', + fields: [ + 'type.title^3', + 'pending.title^3', + 'saved.title^3' + ] + } + } + ] + } + } + }); + }); + it('supports field and multi-field', () => { + expect(getQueryParams(MAPPINGS, null, 'y*', ['title', 'title.raw'])) + .to.eql({ + query: { + bool: { + must: [ + { + simple_query_string: { + query: 'y*', + fields: [ + 'type.title', + 'pending.title', + 'saved.title', + 'type.title.raw', + 'pending.title.raw', + 'saved.title.raw', + ] + } + } + ] + } + } + }); + }); + }); + + describe('{type,search,searchFields}', () => { + it('includes bool, and sqs with field list', () => { + expect(getQueryParams(MAPPINGS, 'saved', 'y*', ['title'])) + .to.eql({ + query: { + bool: { + filter: [ + { term: { type: 'saved' } } + ], + must: [ + { + simple_query_string: { + query: 'y*', + fields: [ + 'saved.title' + ] + } + } + ] + } + } + }); + }); + it('supports fields pointing to multi-fields', () => { + expect(getQueryParams(MAPPINGS, 'saved', 'y*', ['title.raw'])) + .to.eql({ + query: { + bool: { + filter: [ + { term: { type: 'saved' } } + ], + must: [ + { + simple_query_string: { + query: 'y*', + fields: [ + 'saved.title.raw' + ] + } + } + ] + } + } + }); + }); + it('supports multiple search fields', () => { + expect(getQueryParams(MAPPINGS, 'saved', 'y*', ['title', 'title.raw'])) + .to.eql({ + query: { + bool: { + filter: [ + { term: { type: 'saved' } } + ], + must: [ + { + simple_query_string: { + query: 'y*', + fields: [ + 'saved.title', + 'saved.title.raw' + ] + } + } + ] + } + } + }); + }); + }); +}); diff --git a/src/server/saved_objects/client/lib/search_dsl/__tests__/search_dsl.js b/src/server/saved_objects/client/lib/search_dsl/__tests__/search_dsl.js new file mode 100644 index 0000000000000..b65e4350ec174 --- /dev/null +++ b/src/server/saved_objects/client/lib/search_dsl/__tests__/search_dsl.js @@ -0,0 +1,77 @@ +import sinon from 'sinon'; +import expect from 'expect.js'; +import { getSearchDsl } from '../search_dsl'; +import * as queryParamsNS from '../query_params'; +import * as sortParamsNS from '../sorting_params'; + +describe('getSearchDsl', () => { + const sandbox = sinon.sandbox.create(); + afterEach(() => sandbox.restore()); + + describe('validation', () => { + it('throws when sortField is passed without type', () => { + expect(() => { + getSearchDsl({}, { + type: undefined, + sortField: 'title' + }); + }).to.throwException(/sort without .+ type/); + }); + it('throws when sortOrder without sortField', () => { + expect(() => { + getSearchDsl({}, { + type: 'foo', + sortOrder: 'desc' + }); + }).to.throwException(/sortOrder requires a sortField/); + }); + }); + + describe('passes control', () => { + it('passes (mappings, type, search, searchFields) to getQueryParams', () => { + const spy = sandbox.spy(queryParamsNS, 'getQueryParams'); + const mappings = { type: { properties: {} } }; + const opts = { + type: 'foo', + search: 'bar', + searchFields: ['baz'] + }; + + getSearchDsl(mappings, opts); + sinon.assert.calledOnce(spy); + sinon.assert.calledWithExactly( + spy, + mappings, + opts.type, + opts.search, + opts.searchFields, + ); + }); + + it('passes (mappings, type, sortField, sortOrder) to getSortingParams', () => { + const spy = sandbox.stub(sortParamsNS, 'getSortingParams').returns({}); + const mappings = { type: { properties: {} } }; + const opts = { + type: 'foo', + sortField: 'bar', + sortOrder: 'baz' + }; + + getSearchDsl(mappings, opts); + sinon.assert.calledOnce(spy); + sinon.assert.calledWithExactly( + spy, + mappings, + opts.type, + opts.sortField, + opts.sortOrder, + ); + }); + + it('returns combination of getQueryParams and getSortingParams', () => { + sandbox.stub(queryParamsNS, 'getQueryParams').returns({ a: 'a' }); + sandbox.stub(sortParamsNS, 'getSortingParams').returns({ b: 'b' }); + expect(getSearchDsl({})).to.eql({ a: 'a', b: 'b' }); + }); + }); +}); diff --git a/src/server/saved_objects/client/lib/search_dsl/__tests__/sorting_params.js b/src/server/saved_objects/client/lib/search_dsl/__tests__/sorting_params.js new file mode 100644 index 0000000000000..dc857ab0b9595 --- /dev/null +++ b/src/server/saved_objects/client/lib/search_dsl/__tests__/sorting_params.js @@ -0,0 +1,125 @@ +import expect from 'expect.js'; + +import { getSortingParams } from '../sorting_params'; + +const MAPPINGS = { + rootType: { + properties: { + pending: { + properties: { + title: { + type: 'text', + } + } + }, + saved: { + properties: { + title: { + type: 'text', + fields: { + raw: { + type: 'keyword' + } + } + }, + obj: { + properties: { + key1: { + type: 'text' + } + } + } + } + } + } + } +}; + +describe('searchDsl/getSortParams', () => { + describe('no sortField, type, or order', () => { + it('returns no params', () => { + expect(getSortingParams(MAPPINGS)) + .to.eql({}); + }); + }); + + describe('type, no sortField', () => { + it('returns no params', () => { + expect(getSortingParams(MAPPINGS, 'pending')) + .to.eql({}); + }); + }); + + describe('type, order, no sortField', () => { + it('returns no params', () => { + expect(getSortingParams(MAPPINGS, 'saved', null, 'desc')) + .to.eql({}); + }); + }); + + describe('search field no direction', () => { + describe('search field is simple property', () => { + it('returns correct params', () => { + expect(getSortingParams(MAPPINGS, 'saved', 'title')) + .to.eql({ + sort: [ + { + 'saved.title': { + order: undefined, + unmapped_type: 'text' + } + } + ] + }); + }); + }); + describe('search field is multi-field', () => { + it('returns correct params', () => { + expect(getSortingParams(MAPPINGS, 'saved', 'title.raw')) + .to.eql({ + sort: [ + { + 'saved.title.raw': { + order: undefined, + unmapped_type: 'keyword' + } + } + ] + }); + }); + }); + }); + + describe('search with direction', () => { + describe('search field is simple property', () => { + it('returns correct params', () => { + expect(getSortingParams(MAPPINGS, 'saved', 'title', 'desc')) + .to.eql({ + sort: [ + { + 'saved.title': { + order: 'desc', + unmapped_type: 'text' + } + } + ] + }); + }); + }); + describe('search field is multi-field', () => { + it('returns correct params', () => { + expect(getSortingParams(MAPPINGS, 'saved', 'title.raw', 'asc')) + .to.eql({ + sort: [ + { + 'saved.title.raw': { + order: 'asc', + unmapped_type: 'keyword' + } + } + ] + }); + }); + }); + }); +}); diff --git a/src/server/saved_objects/client/lib/search_dsl/index.js b/src/server/saved_objects/client/lib/search_dsl/index.js new file mode 100644 index 0000000000000..39d06f61605c1 --- /dev/null +++ b/src/server/saved_objects/client/lib/search_dsl/index.js @@ -0,0 +1 @@ +export { getSearchDsl } from './search_dsl'; diff --git a/src/server/saved_objects/client/lib/search_dsl/query_params.js b/src/server/saved_objects/client/lib/search_dsl/query_params.js new file mode 100644 index 0000000000000..3b29aa41b6d22 --- /dev/null +++ b/src/server/saved_objects/client/lib/search_dsl/query_params.js @@ -0,0 +1,60 @@ +import { getRootProperties } from '../../../../mappings'; + +/** + * Get the field params based on the types and searchFields + * @param {Array} searchFields + * @param {Array} types + * @return {Object} + */ +function getFieldsForTypes(searchFields, types) { + if (!searchFields || !searchFields.length) { + return { + all_fields: true + }; + } + + return { + fields: searchFields.reduce((acc, field) => [ + ...acc, + ...types.map(prefix => `${prefix}.${field}`) + ], []), + }; +} + +/** + * Get the "query" related keys for the search body + * @param {EsMapping} mapping mappings from Ui + * @param {Object} type + * @param {String} search + * @param {Array} searchFields + * @return {Object} + */ +export function getQueryParams(mappings, type, search, searchFields) { + if (!type && !search) { + return {}; + } + + const bool = {}; + + if (type) { + bool.filter = [ + { term: { type } } + ]; + } + + if (search) { + bool.must = [ + { + simple_query_string: { + query: search, + ...getFieldsForTypes( + searchFields, + type ? [type] : Object.keys(getRootProperties(mappings)) + ) + } + } + ]; + } + + return { query: { bool } }; +} diff --git a/src/server/saved_objects/client/lib/search_dsl/search_dsl.js b/src/server/saved_objects/client/lib/search_dsl/search_dsl.js new file mode 100644 index 0000000000000..a76f91eb11b0b --- /dev/null +++ b/src/server/saved_objects/client/lib/search_dsl/search_dsl.js @@ -0,0 +1,27 @@ +import Boom from 'boom'; + +import { getQueryParams } from './query_params'; +import { getSortingParams } from './sorting_params'; + +export function getSearchDsl(mappings, options = {}) { + const { + type, + search, + searchFields, + sortField, + sortOrder + } = options; + + if (!type && sortField) { + throw Boom.notAcceptable('Cannot sort without filtering by type'); + } + + if (sortOrder && !sortField) { + throw Boom.notAcceptable('sortOrder requires a sortField'); + } + + return { + ...getQueryParams(mappings, type, search, searchFields), + ...getSortingParams(mappings, type, sortField, sortOrder), + }; +} diff --git a/src/server/saved_objects/client/lib/search_dsl/sorting_params.js b/src/server/saved_objects/client/lib/search_dsl/sorting_params.js new file mode 100644 index 0000000000000..0ccda5d230fa8 --- /dev/null +++ b/src/server/saved_objects/client/lib/search_dsl/sorting_params.js @@ -0,0 +1,25 @@ +import Boom from 'boom'; + +import { getProperty } from '../../../../mappings'; + +export function getSortingParams(mappings, type, sortField, sortOrder) { + if (!sortField) { + return {}; + } + + const field = getProperty(mappings, `${type}.${sortField}`); + if (!field) { + throw Boom.badRequest(`Unknown sort field ${sortField}`); + } + + return { + sort: [ + { + [`${type}.${sortField}`]: { + order: sortOrder, + unmapped_type: field.type + } + } + ] + }; +} diff --git a/src/server/saved_objects/client/lib/trim_id_prefix.js b/src/server/saved_objects/client/lib/trim_id_prefix.js new file mode 100644 index 0000000000000..0ea137de69c7c --- /dev/null +++ b/src/server/saved_objects/client/lib/trim_id_prefix.js @@ -0,0 +1,25 @@ +function assertNonEmptyString(value, name) { + if (!value || typeof value !== 'string') { + throw new TypeError(`Expected "${value}" to be a ${name}`); + } +} + +/** + * Trim the prefix from the id of a saved object doc + * + * @param {string} id + * @param {string} type + * @return {string} + */ +export function trimIdPrefix(id, type) { + assertNonEmptyString(id, 'document id'); + assertNonEmptyString(type, 'saved object type'); + + const prefix = `${type}:`; + + if (!id.startsWith(prefix)) { + return id; + } + + return id.slice(prefix.length); +} diff --git a/src/server/saved_objects/client/saved_objects_client.js b/src/server/saved_objects/client/saved_objects_client.js index cd69c34fd6d1b..09d41a7cea47a 100644 --- a/src/server/saved_objects/client/saved_objects_client.js +++ b/src/server/saved_objects/client/saved_objects_client.js @@ -1,23 +1,20 @@ import Boom from 'boom'; import uuid from 'uuid'; -import { get } from 'lodash'; + +import { getRootType } from '../../mappings'; import { - createFindQuery, - createIdQuery, + getSearchDsl, handleEsError, - v5BulkCreate, - v6BulkCreate, - normalizeEsDoc, + trimIdPrefix, includedFields } from './lib'; -export const V6_TYPE = 'doc'; - export class SavedObjectsClient { constructor(kibanaIndex, mappings, callAdminCluster) { this._kibanaIndex = kibanaIndex; this._mappings = mappings; + this._type = getRootType(this._mappings); this._callAdminCluster = callAdminCluster; } @@ -32,22 +29,28 @@ export class SavedObjectsClient { * @returns {promise} - { id, type, version, attributes } */ async create(type, attributes = {}, options = {}) { - const method = options.id && !options.overwrite ? 'create' : 'index'; - const response = await this._withKibanaIndexAndMappingFallback(method, { - type, - id: options.id, - body: attributes, - refresh: 'wait_for' - }, { - type: V6_TYPE, - id: `${type}:${options.id || uuid.v1()}`, + const { + id, + overwrite = false + } = options; + + const method = id && !overwrite ? 'create' : 'index'; + const response = await this._withKibanaIndex(method, { + id: this._generateEsId(type, id), + type: this._type, + refresh: 'wait_for', body: { type, [type]: attributes - } + }, }); - return normalizeEsDoc(response, { type, attributes }); + return { + id: trimIdPrefix(response._id, type), + type, + version: response._version, + attributes + }; } /** @@ -55,41 +58,68 @@ export class SavedObjectsClient { * * @param {array} objects - [{ type, id, attributes }] * @param {object} [options={}] - * @property {boolean} [options.force=false] - overrides existing documents - * @property {string} [options.format=v5] + * @property {boolean} [options.overwrite=false] - overwrites existing documents * @returns {promise} - [{ id, type, version, attributes, error: { message } }] */ async bulkCreate(objects, options = {}) { - const { format = 'v5' } = options; - - const bulkCreate = format === 'v5' ? v5BulkCreate : v6BulkCreate; - const response = await this._withKibanaIndex('bulk', { - body: bulkCreate(objects, options), - refresh: 'wait_for' - }); - - const items = get(response, 'items', []); - const missingTypesCount = items.filter(item => { - const method = Object.keys(item)[0]; - return get(item, `${method}.error.type`) === 'type_missing_exception'; - }).length; + const { + overwrite = false + } = options; - const formatFallback = format === 'v5' && items.length > 0 && items.length === missingTypesCount; + const objectToBulkRequest = (object) => { + const method = object.id && !overwrite ? 'create' : 'index'; + + return [ + { + [method]: { + _id: this._generateEsId(object.type, object.id), + _type: this._type, + } + }, + { + type: object.type, + [object.type]: object.attributes + } + ]; + }; - if (formatFallback) { - return this.bulkCreate(objects, Object.assign({}, options, { format: 'v6' })); - } + const { items } = await this._withKibanaIndex('bulk', { + refresh: 'wait_for', + body: objects.reduce((acc, object) => ([ + ...acc, + ...objectToBulkRequest(object) + ]), []), + }); - return get(response, 'items', []).map((resp, i) => { - const method = Object.keys(resp)[0]; - const { type, attributes } = objects[i]; + return items.map((response, i) => { + const { + error, + _id: responseId, + _version: version, + } = Object.values(response)[0]; - return normalizeEsDoc(resp[method], { - id: resp[method]._id, + const { + id = responseId, type, attributes, - error: resp[method].error ? { message: get(resp[method], 'error.reason') } : undefined - }); + } = objects[i]; + + if (error) { + return { + id, + type, + error: { + message: error.reason || JSON.stringify(error) + } + }; + } + + return { + id, + type, + version, + attributes + }; }); } @@ -101,27 +131,28 @@ export class SavedObjectsClient { * @returns {promise} */ async delete(type, id) { - const response = await this._withKibanaIndex('deleteByQuery', { - body: createIdQuery({ type, id }), - refresh: 'wait_for' + const response = await this._withKibanaIndex('delete', { + id: this._generateEsId(type, id), + type: this._type, + refresh: 'wait_for', }); - if (get(response, 'deleted') === 0) { + if (response.found === false) { throw Boom.notFound(); } } /** * @param {object} [options={}] - * @property {string} options.type - * @property {string} options.search - * @property {string} options.searchFields - see Elasticsearch Simple Query String + * @property {string} [options.type] + * @property {string} [options.search] + * @property {Array} [options.searchFields] - see Elasticsearch Simple Query String * Query field argument for more information * @property {integer} [options.page=1] * @property {integer} [options.perPage=20] - * @property {string} options.sortField - * @property {string} options.sortOrder - * @property {array|string} options.fields + * @property {string} [options.sortField] + * @property {string} [options.sortOrder] + * @property {Array} [options.fields] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ async find(options = {}) { @@ -136,23 +167,46 @@ export class SavedObjectsClient { fields, } = options; + if (searchFields && !Array.isArray(searchFields)) { + throw new TypeError('options.searchFields must be an array'); + } + + if (fields && !Array.isArray(fields)) { + throw new TypeError('options.searchFields must be an array'); + } + const esOptions = { - _source: includedFields(type, fields), size: perPage, from: perPage * (page - 1), - body: createFindQuery(this._mappings, { search, searchFields, type, sortField, sortOrder }) + _source: includedFields(type, fields), + body: { + version: true, + ...getSearchDsl(this._mappings, { + search, + searchFields, + type, + sortField, + sortOrder + }) + } }; const response = await this._withKibanaIndex('search', esOptions); return { - saved_objects: get(response, 'hits.hits', []).map(hit => { - return normalizeEsDoc(hit); - }), - total: get(response, 'hits.total', 0), + page, per_page: perPage, - page - + total: response.hits.total, + saved_objects: response.hits.hits.map(hit => { + const type = hit._source.type; + + return { + id: trimIdPrefix(hit._id, type), + type, + version: hit._version, + attributes: hit._source[type], + }; + }), }; } @@ -173,24 +227,33 @@ export class SavedObjectsClient { return { saved_objects: [] }; } - const docs = objects.reduce((acc, { type, id }) => { - return [...acc, {}, createIdQuery({ type, id })]; - }, []); - - const response = await this._withKibanaIndex('msearch', { body: docs }); - const responses = get(response, 'responses', []); + const response = await this._withKibanaIndex('mget', { + body: { + docs: objects.map(object => ({ + _id: this._generateEsId(object.type, object.id), + _type: this._type, + })) + } + }); return { - saved_objects: responses.map((r, i) => { - const [hit] = get(r, 'hits.hits', []); + saved_objects: response.docs.map((doc, i) => { + const { id, type } = objects[i]; - if (!hit) { - return Object.assign({}, objects[i], { + if (doc.found === false) { + return { + id, + type, error: { statusCode: 404, message: 'Not found' } - }); + }; } - return normalizeEsDoc(hit, objects[i]); + return { + id, + type, + version: doc._version, + attributes: doc._source[type] + }; }) }; } @@ -203,14 +266,17 @@ export class SavedObjectsClient { * @returns {promise} - { id, type, version, attributes } */ async get(type, id) { - const response = await this._withKibanaIndex('search', { body: createIdQuery({ type, id }) }); - const [hit] = get(response, 'hits.hits', []); - - if (!hit) { - throw Boom.notFound(); - } + const response = await this._withKibanaIndex('get', { + id: this._generateEsId(type, id), + type: this._type, + }); - return normalizeEsDoc(hit); + return { + id, + type, + version: response._version, + attributes: response._source[type] + }; } /** @@ -223,41 +289,24 @@ export class SavedObjectsClient { * @returns {promise} */ async update(type, id, attributes, options = {}) { - const response = await this._withKibanaIndexAndMappingFallback('update', { - id, - type, + const response = await this._withKibanaIndex('update', { + id: this._generateEsId(type, id), + type: this._type, version: options.version, refresh: 'wait_for', - body: { - doc: attributes - } - }, { - type: V6_TYPE, - id: `${type}:${id}`, body: { doc: { [type]: attributes } - } + }, }); - return normalizeEsDoc(response, { id, type, attributes }); - } - - _withKibanaIndexAndMappingFallback(method, params, fallbackParams) { - const fallbacks = { - 'create': ['type_missing_exception'], - 'index': ['type_missing_exception'], - 'update': ['document_missing_exception'] + return { + id, + type, + version: response._version, + attributes }; - - return this._withKibanaIndex(method, params).catch(err => { - if (get(fallbacks, method, []).includes(get(err, 'data.type'))) { - return this._withKibanaIndex(method, Object.assign({}, params, fallbackParams)); - } - - throw err; - }); } async _withKibanaIndex(method, params) { @@ -270,4 +319,8 @@ export class SavedObjectsClient { throw handleEsError(err); } } + + _generateEsId(type, id) { + return `${type}:${id || uuid.v1()}`; + } } diff --git a/src/server/saved_objects/routes/__tests__/find.js b/src/server/saved_objects/routes/__tests__/find.js index 8b3e4808d10bf..119a47272f4bf 100644 --- a/src/server/saved_objects/routes/__tests__/find.js +++ b/src/server/saved_objects/routes/__tests__/find.js @@ -99,7 +99,7 @@ describe('GET /api/saved_objects/{type?}', () => { expect(savedObjectsClient.find.calledOnce).to.be(true); const options = savedObjectsClient.find.getCall(0).args[0]; - expect(options).to.eql({ perPage: 20, page: 1, searchFields: 'title' }); + expect(options).to.eql({ perPage: 20, page: 1, searchFields: ['title'] }); }); it('accepts the query parameter fields as a string', async () => { @@ -113,7 +113,7 @@ describe('GET /api/saved_objects/{type?}', () => { expect(savedObjectsClient.find.calledOnce).to.be(true); const options = savedObjectsClient.find.getCall(0).args[0]; - expect(options).to.eql({ perPage: 20, page: 1, fields: 'title' }); + expect(options).to.eql({ perPage: 20, page: 1, fields: ['title'] }); }); it('accepts the query parameter fields as an array', async () => { diff --git a/src/server/saved_objects/routes/create.js b/src/server/saved_objects/routes/create.js index 3b46eb220c6a2..e0abb8dd161e4 100644 --- a/src/server/saved_objects/routes/create.js +++ b/src/server/saved_objects/routes/create.js @@ -9,7 +9,7 @@ export const createCreateRoute = (prereqs) => { validate: { query: Joi.object().keys({ overwrite: Joi.boolean().default(false) - }), + }).default(), params: Joi.object().keys({ type: Joi.string().required(), id: Joi.string() diff --git a/src/server/saved_objects/routes/find.js b/src/server/saved_objects/routes/find.js index 1d9720fb0302f..0ef764b6b6451 100644 --- a/src/server/saved_objects/routes/find.js +++ b/src/server/saved_objects/routes/find.js @@ -9,15 +9,15 @@ export const createFindRoute = (prereqs) => ({ validate: { params: Joi.object().keys({ type: Joi.string() - }), + }).default(), query: Joi.object().keys({ per_page: Joi.number().min(0).default(20), page: Joi.number().min(0).default(1), type: Joi.string(), search: Joi.string().allow('').optional(), - search_fields: [Joi.string(), Joi.array().items(Joi.string())], - fields: [Joi.string(), Joi.array().items(Joi.string())] - }) + search_fields: Joi.array().items(Joi.string()).single(), + fields: Joi.array().items(Joi.string()).single() + }).default() }, handler(request, reply) { const options = keysToCamelCaseShallow(request.query); diff --git a/src/server/saved_objects/saved_objects_mixin.js b/src/server/saved_objects/saved_objects_mixin.js index f960229a6001c..0206be644a120 100644 --- a/src/server/saved_objects/saved_objects_mixin.js +++ b/src/server/saved_objects/saved_objects_mixin.js @@ -29,7 +29,7 @@ export function savedObjectsMixin(kbnServer, server) { server.decorate('server', 'savedObjectsClientFactory', ({ callCluster }) => { return new SavedObjectsClient( server.config().get('kibana.index'), - kbnServer.uiExports.mappings.getCombined(), + server.getKibanaIndexMappingsDsl(), callCluster ); }); diff --git a/src/ui/__tests__/ui_mappings.js b/src/ui/__tests__/ui_mappings.js deleted file mode 100644 index 880e988d2412e..0000000000000 --- a/src/ui/__tests__/ui_mappings.js +++ /dev/null @@ -1,40 +0,0 @@ -import expect from 'expect.js'; -import { has } from 'lodash'; -import { MappingsCollection } from '../ui_mappings'; - - -describe('UiExports', function () { - describe('MappingsCollection', function () { - let mappingsCollection; - beforeEach(() => { - mappingsCollection = new MappingsCollection(); - }); - - it('provides default mappings', function () { - expect(mappingsCollection.getCombined()).to.be.an('object'); - }); - - it('registers new mappings', () => { - mappingsCollection.register({ - foo: { - 'properties': { - 'bar': { - 'type': 'text' - } - } - } - }); - - const mappings = mappingsCollection.getCombined(); - expect(has(mappings, 'foo.properties.bar')).to.be(true); - }); - - it('throws and includes the plugin id in the mapping conflict message', () => { - const mappings = { foo: 'bar' }; - const plugin = { plugin: 'abc123' }; - mappingsCollection.register(mappings, plugin); - expect(mappingsCollection.register).withArgs(mappings, plugin).to.throwException(/abc123/); - }); - }); - -}); diff --git a/src/ui/index.js b/src/ui/index.js index aa8517c9e7d0f..5f4bba02d723c 100644 --- a/src/ui/index.js +++ b/src/ui/index.js @@ -14,7 +14,8 @@ import { fieldFormatsMixin } from './field_formats_mixin'; export default async (kbnServer, server, config) => { const uiExports = kbnServer.uiExports = new UiExports({ - urlBasePath: config.get('server.basePath') + urlBasePath: config.get('server.basePath'), + kibanaIndexMappings: kbnServer.mappings, }); await kbnServer.mixin(uiSettingsMixin); diff --git a/src/ui/public/agg_types/controls/field.html b/src/ui/public/agg_types/controls/field.html index 9b5a2797c9f6e..4c52e80cb2294 100644 --- a/src/ui/public/agg_types/controls/field.html +++ b/src/ui/public/agg_types/controls/field.html @@ -27,7 +27,7 @@

- No Compatible Fields: The "{{ vis.indexPattern.id }}" index pattern does not contain any of the following field types: {{ agg.type.params.byName.field.filterFieldTypes | commaList:false }} + No Compatible Fields: The "{{ vis.indexPattern.title }}" index pattern does not contain any of the following field types: {{ agg.type.params.byName.field.filterFieldTypes | commaList:false }}

diff --git a/src/ui/public/chrome/api/angular.js b/src/ui/public/chrome/api/angular.js index 6e7180c16e0bd..11e6a1b52d9f2 100644 --- a/src/ui/public/chrome/api/angular.js +++ b/src/ui/public/chrome/api/angular.js @@ -11,7 +11,6 @@ const URL_LIMIT_WARN_WITHIN = 1000; export function initAngularApi(chrome, internals) { chrome.getFirstPathSegment = _.noop; - chrome.getBreadcrumbs = _.noop; chrome.setupAngular = function () { const kibana = uiModules.get('kibana'); @@ -49,19 +48,6 @@ export function initAngularApi(chrome, internals) { return $location.path().split('/')[1]; }; - chrome.getBreadcrumbs = () => { - const path = $location.path(); - let length = path.length - 1; - - // trim trailing slash - if (path.charAt(length) === '/') { - length--; - } - - return path.substr(1, length) - .split('/'); - }; - const notify = new Notifier(); const urlOverflow = Private(UrlOverflowServiceProvider); const check = () => { diff --git a/src/ui/public/courier/saved_object/saved_object_loader.js b/src/ui/public/courier/saved_object/saved_object_loader.js index 9488b85dcd7b3..cfdf39552bcd4 100644 --- a/src/ui/public/courier/saved_object/saved_object_loader.js +++ b/src/ui/public/courier/saved_object/saved_object_loader.js @@ -89,7 +89,7 @@ export class SavedObjectLoader { * @param size * @returns {Promise} */ - find(search, size = 100) { + find(search = '', size = 100) { return this.savedObjectsClient.find( { type: this.lowercaseType, diff --git a/src/ui/public/index_patterns/__tests__/_index_pattern.js b/src/ui/public/index_patterns/__tests__/_index_pattern.js index 34604718ff7e5..19f15496bd527 100644 --- a/src/ui/public/index_patterns/__tests__/_index_pattern.js +++ b/src/ui/public/index_patterns/__tests__/_index_pattern.js @@ -458,7 +458,7 @@ describe('index pattern', function () { expect(notif.content).to.match(MARKDOWN_LINK_RE); const [,text,url] = notif.content.match(MARKDOWN_LINK_RE); - expect(text).to.contain(indexPattern.id); + expect(text).to.contain(indexPattern.title); expect(url).to.contain(indexPattern.id); expect(url).to.contain('management/kibana/indices'); }); diff --git a/src/ui/public/index_patterns/_field.js b/src/ui/public/index_patterns/_field.js index 58a6849d8f814..d2d2632851414 100644 --- a/src/ui/public/index_patterns/_field.js +++ b/src/ui/public/index_patterns/_field.js @@ -26,7 +26,7 @@ export function IndexPatternsFieldProvider(Private, shortDotsFilter, $rootScope, notify.error( 'Unknown field type "' + spec.type + '"' + ' for field "' + spec.name + '"' + - ' in indexPattern "' + indexPattern.id + '"' + ' in indexPattern "' + indexPattern.title + '"' ); } diff --git a/src/ui/public/index_patterns/_index_pattern.js b/src/ui/public/index_patterns/_index_pattern.js index ae598b2f11b3b..3d59d195e463d 100644 --- a/src/ui/public/index_patterns/_index_pattern.js +++ b/src/ui/public/index_patterns/_index_pattern.js @@ -93,11 +93,15 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise, // give index pattern all of the values in _source _.assign(indexPattern, response._source); + if (!indexPattern.title) { + indexPattern.title = indexPattern.id; + } + if (indexPattern.isUnsupportedTimePattern()) { if (!isUserAwareOfUnsupportedTimePattern(indexPattern)) { const warning = ( 'Support for time-intervals has been removed. ' + - `View the ["${indexPattern.id}" index pattern in management](` + + `View the ["${indexPattern.title}" index pattern in management](` + kbnUrl.getRouteHref(indexPattern, 'edit') + ') for more information.' ); @@ -403,7 +407,7 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise, }); } - async save() { + save() { return savedObjectsClient.update(type, this.id, this.prepBody()) .then(({ id }) => setId(this, id)); } diff --git a/src/ui/public/kbn_top_nav/__tests__/bread_crumb_urls.js b/src/ui/public/kbn_top_nav/__tests__/bread_crumb_urls.js deleted file mode 100644 index 8db81c0ac8b58..0000000000000 --- a/src/ui/public/kbn_top_nav/__tests__/bread_crumb_urls.js +++ /dev/null @@ -1,61 +0,0 @@ -import expect from 'expect.js'; - -import { getBreadCrumbUrls } from '../bread_crumbs/bread_crumb_urls'; - -describe('getBreadCrumbUrls', function () { - - it('returns urls for the breadcrumbs', function () { - const breadCrumbUrls = getBreadCrumbUrls( - ['path1', 'path2', 'a', 'longlonglonglong'], - 'http://test.com/path1/path2/a/longlonglonglong'); - expect(breadCrumbUrls.length).to.equal(4); - expect(breadCrumbUrls[0].url).to.equal('http://test.com/path1'); - expect(breadCrumbUrls[0].title).to.equal('Path 1'); - - expect(breadCrumbUrls[1].url).to.equal('http://test.com/path1/path2'); - expect(breadCrumbUrls[1].title).to.equal('Path 2'); - - expect(breadCrumbUrls[2].url).to.equal('http://test.com/path1/path2/a'); - expect(breadCrumbUrls[2].title).to.equal('A'); - - expect(breadCrumbUrls[3].url).to.equal('http://test.com/path1/path2/a/longlonglonglong'); - expect(breadCrumbUrls[3].title).to.equal('Longlonglonglong'); - }); - - it('is case insensitive', function () { - const breadCrumbUrls = getBreadCrumbUrls(['paTh1', 'path2'], 'http://TEST.com/paTh1/path2'); - expect(breadCrumbUrls.length).to.equal(2); - expect(breadCrumbUrls[0].url).to.equal('http://TEST.com/paTh1'); - expect(breadCrumbUrls[0].path).to.equal('paTh1'); - expect(breadCrumbUrls[0].title).to.equal('Pa Th 1'); - - expect(breadCrumbUrls[1].url).to.equal('http://TEST.com/paTh1/path2'); - expect(breadCrumbUrls[1].title).to.equal('Path 2'); - }); - - it('handles no breadcrumbs case', function () { - const breadCrumbUrls = getBreadCrumbUrls([], 'http://test.com'); - expect(breadCrumbUrls.length).to.equal(0); - }); - - it('handles spaces in breadcrumbs', function () { - const breadCrumbUrls = getBreadCrumbUrls( - ['something', 'somethingElse', 'snake_case', 'longLongLongLong'], - 'http://test.com/something/somethingElse/snake_case/longLongLongLong'); - expect(breadCrumbUrls.length).to.equal(4); - expect(breadCrumbUrls[0].url).to.equal('http://test.com/something'); - expect(breadCrumbUrls[0].title).to.equal('Something'); - - expect(breadCrumbUrls[1].url).to.equal('http://test.com/something/somethingElse'); - expect(breadCrumbUrls[1].path).to.equal('somethingElse'); - expect(breadCrumbUrls[1].title).to.equal('Something Else'); - - expect(breadCrumbUrls[2].url).to.equal('http://test.com/something/somethingElse/snake_case'); - expect(breadCrumbUrls[2].path).to.equal('snake_case'); - expect(breadCrumbUrls[2].title).to.equal('Snake Case'); - - expect(breadCrumbUrls[3].url).to.equal('http://test.com/something/somethingElse/snake_case/longLongLongLong'); - expect(breadCrumbUrls[3].title).to.equal('Long Long Long Long'); - }); - -}); diff --git a/src/ui/public/kbn_top_nav/bread_crumbs/bread_crumb_urls.js b/src/ui/public/kbn_top_nav/bread_crumbs/bread_crumb_urls.js deleted file mode 100644 index fc5146a613642..0000000000000 --- a/src/ui/public/kbn_top_nav/bread_crumbs/bread_crumb_urls.js +++ /dev/null @@ -1,27 +0,0 @@ -import _ from 'lodash'; -/** - * @typedef BreadCrumbUrl {Object} - * @property title {String} the display title for the breadcrumb - * @property path {String} the subdirectory for this particular breadcrumb - * @property url {String} a url for the breadcrumb - */ - -/** - * - * @param {Array.} breadcrumbs An array of breadcrumbs for the given url. - * @param {String} url The current url that the breadcrumbs have been generated for - * @returns {Array. An array comprised of objects that - * will contain both the url for the given breadcrumb, as well as the breadcrumb the url - * was generated for. - */ -export function getBreadCrumbUrls(breadcrumbs, url) { - // the url should not have a slash on the end or else the route will not be properly built - const urlBase = url.replace(/\/+$/, '').replace(breadcrumbs.join('/'), ''); - return breadcrumbs.map((path, index) => { - return { - path: path, - title: _.startCase(path), - url: urlBase + breadcrumbs.slice(0, index + 1).join('/') - }; - }); -} diff --git a/src/ui/public/kbn_top_nav/bread_crumbs/bread_crumbs.html b/src/ui/public/kbn_top_nav/bread_crumbs/bread_crumbs.html index e48b704f30a8a..9a98f763cb85e 100644 --- a/src/ui/public/kbn_top_nav/bread_crumbs/bread_crumbs.html +++ b/src/ui/public/kbn_top_nav/bread_crumbs/bread_crumbs.html @@ -1,23 +1,23 @@
- {{ breadcrumb.title }} + {{ breadcrumb.display }}
({ - path: path, - title: _.startCase(path) - })); + function omitPagesFilter(crumb) { + return ( + !$scope.omitPages || + !$scope.omitPages.includes(crumb.id) + ); } - if ($scope.omitCurrentPage === true) { - $scope.breadcrumbs.pop(); + + function omitCurrentPageFilter(crumb) { + return !($scope.omitCurrentPage && crumb.current); } + + $scope.$watchMulti([ + '[]omitPages', + 'omitCurrentPage' + ], function getBreadcrumbs() { + $scope.breadcrumbs = ( + uiRouter + .getBreadcrumbs() + .filter(omitPagesFilter) + .filter(omitCurrentPageFilter) + ); + }); } }; }); diff --git a/src/ui/public/routes/breadcrumbs.js b/src/ui/public/routes/breadcrumbs.js new file mode 100644 index 0000000000000..c9ffd284639e5 --- /dev/null +++ b/src/ui/public/routes/breadcrumbs.js @@ -0,0 +1,22 @@ +import { trim, startCase } from 'lodash'; + +/** + * Take a path (from $location.path() usually) and parse + * it's segments into a list of breadcrumbs + * + * @param {string} path + * @return {Array} + */ +export function parsePathToBreadcrumbs(path) { + return trim(path, '/') + .split('/') + .reduce((acc, id, i, parts) => [ + ...acc, + { + id, + display: startCase(id), + href: i === 0 ? `#/${id}` : `${acc[i - 1].href}/${id}`, + current: i === (parts.length - 1) + } + ], []); +} diff --git a/src/ui/public/routes/route_manager.js b/src/ui/public/routes/route_manager.js index 09a1b2a57ed12..356525aabb4fb 100644 --- a/src/ui/public/routes/route_manager.js +++ b/src/ui/public/routes/route_manager.js @@ -1,7 +1,8 @@ -import _ from 'lodash'; +import { defaultsDeep, wrap } from 'lodash'; import { wrapRouteWithPrep } from './wrap_route_with_prep'; import { RouteSetupManager } from './route_setup_manager'; +import { parsePathToBreadcrumbs } from './breadcrumbs'; // eslint-disable-next-line kibana-custom/no-default-export export default function RouteManager() { @@ -16,18 +17,17 @@ export default function RouteManager() { const path = args[0]; const route = args[1] || {}; - // merge in any defaults - defaults.forEach(function (args) { - if (args[0].test(path)) { - _.merge(route, args[1]); + defaults.forEach(def => { + if (def.regex.test(path)) { + defaultsDeep(route, def.value); } }); - if (route.reloadOnSearch === void 0) { + if (route.reloadOnSearch == null) { route.reloadOnSearch = false; } - if (route.requireDefaultIndex === void 0) { + if (route.requireDefaultIndex == null) { route.requireDefaultIndex = false; } @@ -41,14 +41,22 @@ export default function RouteManager() { } }; + self.run = function ($location, $route, $injector) { + self.getBreadcrumbs = () => { + const breadcrumbs = parsePathToBreadcrumbs($location.path()); + const map = $route.current.mapBreadcrumbs; + return map ? $injector.invoke(map, null, { breadcrumbs }) : breadcrumbs; + }; + }; + const wrapSetupAndChain = (fn, ...args) => { fn.apply(setup, args); return this; }; - this.addSetupWork = _.wrap(setup.addSetupWork, wrapSetupAndChain); - this.afterSetupWork = _.wrap(setup.afterSetupWork, wrapSetupAndChain); - this.afterWork = _.wrap(setup.afterWork, wrapSetupAndChain); + this.addSetupWork = wrap(setup.addSetupWork, wrapSetupAndChain); + this.afterSetupWork = wrap(setup.afterSetupWork, wrapSetupAndChain); + this.afterWork = wrap(setup.afterWork, wrapSetupAndChain); self.when = function (path, route) { when.push([path, route]); @@ -57,8 +65,8 @@ export default function RouteManager() { // before attaching the routes to the routeProvider, test the RE // against the .when() path and add/override the resolves if there is a match - self.defaults = function (RE, def) { - defaults.push([RE, def]); + self.defaults = function (regex, value) { + defaults.push({ regex, value }); return self; }; @@ -67,5 +75,10 @@ export default function RouteManager() { return self; }; + self.getBreadcrumbs = function () { + // overwritten in self.run(); + return []; + }; + self.RouteManager = RouteManager; } diff --git a/src/ui/public/routes/routes.js b/src/ui/public/routes/routes.js index 1d82af2ed68b5..241587b649f12 100644 --- a/src/ui/public/routes/routes.js +++ b/src/ui/public/routes/routes.js @@ -5,12 +5,17 @@ import { WAIT_FOR_URL_CHANGE_TOKEN } from './route_setup_manager'; const defaultRouteManager = new RouteManager(); // eslint-disable-next-line kibana-custom/no-default-export -export default { - ...defaultRouteManager, - WAIT_FOR_URL_CHANGE_TOKEN, - enable() { - uiModules - .get('kibana', ['ngRoute']) - .config(defaultRouteManager.config); +export default Object.create(defaultRouteManager, { + WAIT_FOR_URL_CHANGE_TOKEN: { + value: WAIT_FOR_URL_CHANGE_TOKEN + }, + + enable: { + value() { + uiModules + .get('kibana', ['ngRoute']) + .config(defaultRouteManager.config) + .run(defaultRouteManager.run); + } } -}; +}); diff --git a/src/ui/public/saved_objects/__tests__/saved_objects_client.js b/src/ui/public/saved_objects/__tests__/saved_objects_client.js index 855dbabfa4669..742cf40afdd2f 100644 --- a/src/ui/public/saved_objects/__tests__/saved_objects_client.js +++ b/src/ui/public/saved_objects/__tests__/saved_objects_client.js @@ -298,7 +298,7 @@ describe('SavedObjectsClient', () => { expect($http.calledOnce).to.be(true); const options = $http.getCall(0).args[0]; - expect(options.url).to.eql(`${basePath}/api/saved_objects/index-pattern?type=index-pattern&invalid=true`); + expect(options.url).to.eql(`${basePath}/api/saved_objects/?type=index-pattern&invalid=true`); }); it('accepts fields', () => { diff --git a/src/ui/public/saved_objects/find_object_by_title.js b/src/ui/public/saved_objects/find_object_by_title.js index 0059c40d7f435..7e9bbb0d42fb8 100644 --- a/src/ui/public/saved_objects/find_object_by_title.js +++ b/src/ui/public/saved_objects/find_object_by_title.js @@ -17,7 +17,7 @@ export function findObjectByTitle(savedObjectsClient, type, title) { type, perPage: 10, search: `"${title}"`, - searchFields: 'title', + searchFields: ['title'], fields: ['title'] }).then(response => { const match = find(response.savedObjects, (obj) => { diff --git a/src/ui/public/saved_objects/saved_objects_client.js b/src/ui/public/saved_objects/saved_objects_client.js index 71180bc3175d4..c0f34c3c100cb 100644 --- a/src/ui/public/saved_objects/saved_objects_client.js +++ b/src/ui/public/saved_objects/saved_objects_client.js @@ -74,7 +74,7 @@ export class SavedObjectsClient { * @returns {promise} - { savedObjects: [ SavedObject({ id, type, version, attributes }) ]} */ find(options = {}) { - const url = this._getUrl([options.type], keysToSnakeCaseShallow(options)); + const url = this._getUrl([], keysToSnakeCaseShallow(options)); return this._request('GET', url).then(resp => { resp.saved_objects = resp.saved_objects.map(d => this.createSavedObject(d)); diff --git a/src/ui/ui_exports.js b/src/ui/ui_exports.js index 02007b74d0b8d..70606553717ca 100644 --- a/src/ui/ui_exports.js +++ b/src/ui/ui_exports.js @@ -3,10 +3,9 @@ import minimatch from 'minimatch'; import UiAppCollection from './ui_app_collection'; import UiNavLinkCollection from './ui_nav_link_collection'; -import { MappingsCollection } from './ui_mappings'; export default class UiExports { - constructor({ urlBasePath }) { + constructor({ urlBasePath, kibanaIndexMappings }) { this.navLinks = new UiNavLinkCollection(this); this.apps = new UiAppCollection(this); this.aliases = { @@ -29,7 +28,7 @@ export default class UiExports { this.bundleProviders = []; this.defaultInjectedVars = {}; this.injectedVarsReplacers = []; - this.mappings = new MappingsCollection(); + this.kibanaIndexMappings = kibanaIndexMappings; } consumePlugin(plugin) { @@ -146,7 +145,7 @@ export default class UiExports { case 'mappings': return (plugin, mappings) => { - this.mappings.register(mappings, { plugin: plugin.id }); + this.kibanaIndexMappings.addRootProperties(mappings, { plugin: plugin.id }); }; case 'replaceInjectedVars': diff --git a/src/ui/ui_mappings.js b/src/ui/ui_mappings.js deleted file mode 100644 index 812770ff9f01e..0000000000000 --- a/src/ui/ui_mappings.js +++ /dev/null @@ -1,37 +0,0 @@ -import _ from 'lodash'; - -class MappingsCollection { - constructor() { - this._defaultMappings = { - '_default_': { - 'dynamic': 'strict' - }, - config: { - dynamic: true, - properties: { - buildNum: { - type: 'keyword' - } - } - }, - }; - this._currentMappings = _.cloneDeep(this._defaultMappings); - } - - getCombined = () => { - return this._currentMappings; - } - - register = (newMappings, options = {}) => { - Object.keys(this._currentMappings).forEach(key => { - if (newMappings.hasOwnProperty(key)) { - const pluginPartial = options.plugin ? `registered by plugin ${options.plugin} ` : ''; - throw new Error(`Mappings for ${key} ${pluginPartial}have already been defined`); - return; - } - }); - Object.assign(this._currentMappings, newMappings); - } -} - -export { MappingsCollection }; diff --git a/tasks/config/run.js b/tasks/config/run.js index 72e98674b6350..f10bc0d5d7560 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -74,7 +74,6 @@ module.exports = function (grunt) { ...stdDevArgs, '--dev', '--no-base-path', - '--no-ssl', '--optimize.enabled=false', '--elasticsearch.url=' + format(esTestServerUrlParts), '--server.port=' + kibanaTestServerUrlParts.port, diff --git a/test/api_integration/config.js b/test/api_integration/config.js index 0077c67816ff2..4adac456114eb 100644 --- a/test/api_integration/config.js +++ b/test/api_integration/config.js @@ -13,6 +13,8 @@ export default async function ({ readConfigFile }) { services: { es: commonConfig.get('services.es'), esArchiver: commonConfig.get('services.esArchiver'), + kibanaIndex: commonConfig.get('services.kibanaIndex'), + retry: commonConfig.get('services.retry'), supertest: SupertestProvider, chance: ChanceProvider, }, diff --git a/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/data.json.gz b/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/data.json.gz index 57080f6a2a59d..51f3d08846d37 100644 Binary files a/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/data.json.gz and b/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/data.json.gz differ diff --git a/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/mappings.json b/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/mappings.json index b47c0a003a42e..5bbc3978c3c36 100644 --- a/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/mappings.json +++ b/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/mappings.json @@ -30,4 +30,4 @@ } } } -} +} \ No newline at end of file diff --git a/test/api_integration/fixtures/es_archiver/index_patterns/daily_index/data.json.gz b/test/api_integration/fixtures/es_archiver/index_patterns/daily_index/data.json.gz index 76668fdeb56f0..2e807e5c1fc31 100644 Binary files a/test/api_integration/fixtures/es_archiver/index_patterns/daily_index/data.json.gz and b/test/api_integration/fixtures/es_archiver/index_patterns/daily_index/data.json.gz differ diff --git a/test/api_integration/fixtures/es_archiver/index_patterns/daily_index/mappings.json b/test/api_integration/fixtures/es_archiver/index_patterns/daily_index/mappings.json index 077315f44903d..20a55d612dff8 100644 --- a/test/api_integration/fixtures/es_archiver/index_patterns/daily_index/mappings.json +++ b/test/api_integration/fixtures/es_archiver/index_patterns/daily_index/mappings.json @@ -46,4 +46,4 @@ } } } -} +} \ No newline at end of file diff --git a/test/api_integration/fixtures/es_archiver/index_patterns/time_based_indices/mappings.json b/test/api_integration/fixtures/es_archiver/index_patterns/time_based_indices/mappings.json index 87fdbfd488337..054e9a7c851be 100644 --- a/test/api_integration/fixtures/es_archiver/index_patterns/time_based_indices/mappings.json +++ b/test/api_integration/fixtures/es_archiver/index_patterns/time_based_indices/mappings.json @@ -121,4 +121,4 @@ } } } -} +} \ No newline at end of file diff --git a/test/api_integration/fixtures/es_archiver/search/count/data.json.gz b/test/api_integration/fixtures/es_archiver/search/count/data.json.gz index 39fb66e4d9392..e6fc50ad4f203 100644 Binary files a/test/api_integration/fixtures/es_archiver/search/count/data.json.gz and b/test/api_integration/fixtures/es_archiver/search/count/data.json.gz differ diff --git a/test/api_integration/fixtures/es_archiver/search/count/mappings.json b/test/api_integration/fixtures/es_archiver/search/count/mappings.json index f1d60cc32b156..011430cf843ce 100644 --- a/test/api_integration/fixtures/es_archiver/search/count/mappings.json +++ b/test/api_integration/fixtures/es_archiver/search/count/mappings.json @@ -4,242 +4,241 @@ "index": ".kibana", "settings": { "index": { - "mapping": { - "single_type": "false" - }, "number_of_shards": "1", - "mapper": { - "dynamic": "false" - }, + "mapper.dynamic": false, "number_of_replicas": "1" } }, "mappings": { - "url": { - "dynamic": "strict", + "doc": { "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" + "type": { + "type": "keyword" }, "url": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 2048 + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } } } - } - } - }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" }, - "kibanaSavedObjectMeta": { + "timelion-sheet": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { "type": "text" + }, + "version": { + "type": "integer" } } }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "_default_": { - "dynamic": "strict" - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { + "visualization": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { "type": "text" } } }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - }, - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + } + } }, - "kibanaSavedObjectMeta": { + "search": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { "type": "text" + }, + "version": { + "type": "integer" } } }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "index-pattern": { - "dynamic": "strict", - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "index-pattern": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { "type": "text" } } }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { + "dashboard": { + "dynamic": "strict", "properties": { - "display": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { "type": "keyword" }, - "pause": { + "timeRestore": { "type": "boolean" }, - "section": { - "type": "integer" + "timeTo": { + "type": "keyword" }, - "value": { + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { "type": "integer" } } }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } } } } diff --git a/test/common/config.js b/test/common/config.js index 6fd6bcf061405..a2ec8badd7f16 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -1,7 +1,9 @@ import { KibanaServerProvider, + KibanaIndexProvider, EsProvider, EsArchiverProvider, + RetryProvider, } from './services'; import { esTestServerUrlParts } from '../es_test_server_url_parts'; @@ -15,6 +17,8 @@ export default function () { }, services: { kibanaServer: KibanaServerProvider, + kibanaIndex: KibanaIndexProvider, + retry: RetryProvider, es: EsProvider, esArchiver: EsArchiverProvider, } diff --git a/test/common/services/index.js b/test/common/services/index.js index d0debb30687e0..941e1e5df2de7 100644 --- a/test/common/services/index.js +++ b/test/common/services/index.js @@ -1,3 +1,5 @@ export { KibanaServerProvider } from './kibana_server'; +export { KibanaIndexProvider } from './kibana_index'; export { EsProvider } from './es'; export { EsArchiverProvider } from './es_archiver'; +export { RetryProvider } from './retry'; diff --git a/test/common/services/kibana_index.js b/test/common/services/kibana_index.js new file mode 100644 index 0000000000000..8f7061bc8eac9 --- /dev/null +++ b/test/common/services/kibana_index.js @@ -0,0 +1,21 @@ +export async function KibanaIndexProvider({ getService }) { + const retry = getService('retry'); + const es = getService('es'); + + const KIBANA_INDEX_NAME = '.kibana'; + const esIndex = await retry.try(async () => { + return await es.indices.get({ + index: KIBANA_INDEX_NAME + }); + }); + + return new class KibanaIndex { + getName() { + return KIBANA_INDEX_NAME; + } + + getMappingsDsl() { + return Object.values(esIndex)[0].mappings; + } + }; +} diff --git a/test/common/services/kibana_server/kibana_server.js b/test/common/services/kibana_server/kibana_server.js index dae3201e01918..18e4c827d333b 100644 --- a/test/common/services/kibana_server/kibana_server.js +++ b/test/common/services/kibana_server/kibana_server.js @@ -6,18 +6,19 @@ import { KibanaServerStatus } from './status'; import { KibanaServerUiSettings } from './ui_settings'; import { KibanaServerVersion } from './version'; -export function KibanaServerProvider({ getService }) { +export async function KibanaServerProvider({ getService }) { const log = getService('log'); const config = getService('config'); const lifecycle = getService('lifecycle'); const es = getService('es'); + const kibanaIndex = await getService('kibanaIndex').init(); - class KibanaServer { + return new class KibanaServer { constructor() { const url = formatUrl(config.get('servers.kibana')); this.status = new KibanaServerStatus(url); this.version = new KibanaServerVersion(this.status); - this.uiSettings = new KibanaServerUiSettings(log, es, this.version); + this.uiSettings = new KibanaServerUiSettings(log, es, kibanaIndex, this.version); lifecycle.on('beforeEachTest', async () => { await this.waitForStabilization(); @@ -63,7 +64,5 @@ export function KibanaServerProvider({ getService }) { const docState = exists ? 'exists' : `doesn't exist`; throw new Error(`Kibana never stabilized: config doc ${docState} and status is ${state}`); } - } - - return new KibanaServer(); + }; } diff --git a/test/common/services/kibana_server/ui_settings.js b/test/common/services/kibana_server/ui_settings.js index 7976b6c441ec4..565248c6c7a15 100644 --- a/test/common/services/kibana_server/ui_settings.js +++ b/test/common/services/kibana_server/ui_settings.js @@ -1,46 +1,49 @@ import { get } from 'lodash'; +import toPath from 'lodash/internal/toPath'; + +import { SavedObjectsClient } from '../../../../src/server/saved_objects'; + +function createCallCluster(es) { + return function callCluster(method, params) { + const path = toPath(method); + const contextPath = path.slice(0, -1); + + const action = get(es, path); + const context = contextPath.length ? get(es, contextPath) : es; + + return action.call(context, params); + }; +} export class KibanaServerUiSettings { - constructor(log, es, kibanaVersion) { - this.es = es; - this.log = log; - this.kibanaVersion = kibanaVersion; + constructor(log, es, kibanaIndex, kibanaVersion) { + this._log = log; + this._kibanaVersion = kibanaVersion; + this._savedObjectsClient = new SavedObjectsClient( + kibanaIndex.getName(), + kibanaIndex.getMappingsDsl(), + createCallCluster(es) + ); } - async _docParams() { - const { kibanaVersion } = this; - return { - index: '.kibana', - type: 'config', - id: await kibanaVersion.get() - }; + async _id() { + return await this._kibanaVersion.get(); } async existInEs() { - const { es } = this; - return await es.exists(await this._docParams()); - } - - async _read() { - const { log, es } = this; - try { - const doc = await es.get(await this._docParams()); - log.verbose('Fetched kibana config doc', doc); - return doc; - } catch (err) { - log.debug('Failed to fetch kibana config doc', err.message); - return; - } + return !!(await this._read()); } /* ** Gets defaultIndex from the config doc. */ async getDefaultIndex() { - const { log } = this; const doc = await this._read(); - const defaultIndex = get(doc, ['_source', 'defaultIndex']); - log.verbose('uiSettings.defaultIndex: %j', defaultIndex); + if (!doc) { + throw new TypeError('Failed to fetch kibana config doc'); + } + const defaultIndex = doc.attributes.defaultIndex; + this._log.verbose('uiSettings.defaultIndex: %j', defaultIndex); return defaultIndex; } @@ -62,12 +65,10 @@ export class KibanaServerUiSettings { } async replace(doc) { - const { log, es } = this; - log.debug('updating kibana config doc: %j', doc); - await es.index({ - ...(await this._docParams()), - refresh: 'wait_for', - body: doc, + this._log.debug('replacing kibana config doc: %j', doc); + await this._savedObjectsClient.create('config', { doc }, { + id: await this._id(), + overwrite: true, }); } @@ -75,13 +76,19 @@ export class KibanaServerUiSettings { * Add fields to the config doc (like setting timezone and defaultIndex) * @return {Promise} A promise that is resolved when elasticsearch has a response */ - async update(doc) { - const { log, es } = this; - log.debug('updating kibana config doc: %j', doc); - await es.update({ - ...(await this._docParams()), - refresh: 'wait_for', - body: { doc, upsert: doc }, - }); + async update(updates) { + this._log.debug('applying update to kibana config: %j', updates); + await this._savedObjectsClient.update('config', await this._id(), updates); + } + + async _read() { + try { + const doc = await this._savedObjectsClient.get('config', await this._id()); + this._log.verbose('Fetched kibana config doc', doc); + return doc; + } catch (err) { + this._log.debug('Failed to fetch kibana config doc', err.message); + return; + } } } diff --git a/test/functional/services/retry.js b/test/common/services/retry.js similarity index 100% rename from test/functional/services/retry.js rename to test/common/services/retry.js diff --git a/test/functional/config.js b/test/functional/config.js index 4774bcd23cb23..e48a07642df11 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -16,7 +16,6 @@ import { RemoteProvider, FilterBarProvider, FindProvider, - RetryProvider, TestSubjectsProvider, DocTableProvider, ScreenshotsProvider, @@ -53,10 +52,11 @@ export default async function ({ readConfigFile }) { es: commonConfig.get('services.es'), esArchiver: commonConfig.get('services.esArchiver'), kibanaServer: commonConfig.get('services.kibanaServer'), + kibanaIndex: commonConfig.get('services.kibanaIndex'), + retry: commonConfig.get('services.retry'), remote: RemoteProvider, filterBar: FilterBarProvider, find: FindProvider, - retry: RetryProvider, testSubjects: TestSubjectsProvider, docTable: DocTableProvider, screenshots: ScreenshotsProvider, diff --git a/test/functional/fixtures/es_archiver/dashboard/data.json.gz b/test/functional/fixtures/es_archiver/dashboard/data.json.gz index 686be73b74c0b..583406fa4da4f 100644 Binary files a/test/functional/fixtures/es_archiver/dashboard/data.json.gz and b/test/functional/fixtures/es_archiver/dashboard/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/dashboard/mappings.json b/test/functional/fixtures/es_archiver/dashboard/mappings.json index 7a58ea71d5165..b8be6b1d280a5 100644 --- a/test/functional/fixtures/es_archiver/dashboard/mappings.json +++ b/test/functional/fixtures/es_archiver/dashboard/mappings.json @@ -4,244 +4,243 @@ "index": ".kibana", "settings": { "index": { - "mapping": { - "single_type": "false" - }, "number_of_shards": "1", - "mapper": { - "dynamic": "false" - }, + "mapper.dynamic": false, "number_of_replicas": "1" } }, "mappings": { - "index-pattern": { - "dynamic": "strict", + "doc": { "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { + "type": { "type": "keyword" }, - "title": { - "type": "text" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "index-pattern": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { "type": "text" } } }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "search": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "columns": { + "type": "keyword" + }, + "description": { "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" } } }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "timelion-sheet": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { "type": "text" + }, + "version": { + "type": "integer" } } }, - "optionsJSON": { - "type": "text" + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } }, - "panelsJSON": { - "type": "text" + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + } + } }, - "refreshInterval": { + "dashboard": { + "dynamic": "strict", "properties": { - "display": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { "type": "keyword" }, - "pause": { + "timeRestore": { "type": "boolean" }, - "section": { - "type": "integer" + "timeTo": { + "type": "keyword" }, - "value": { + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { "type": "integer" } } }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { + "visualization": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { "type": "text" } } }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - }, - "url": { - "dynamic": "strict", - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, "url": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 2048 + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } } } } } - }, - "_default_": { - "dynamic": "strict" } } } diff --git a/test/functional/fixtures/es_archiver/discover/data.json.gz b/test/functional/fixtures/es_archiver/discover/data.json.gz index 662d73c9261b1..020ca814620a8 100644 Binary files a/test/functional/fixtures/es_archiver/discover/data.json.gz and b/test/functional/fixtures/es_archiver/discover/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/discover/mappings.json b/test/functional/fixtures/es_archiver/discover/mappings.json index 2710bf90b8d57..560178d1bc509 100644 --- a/test/functional/fixtures/es_archiver/discover/mappings.json +++ b/test/functional/fixtures/es_archiver/discover/mappings.json @@ -4,242 +4,241 @@ "index": ".kibana", "settings": { "index": { - "mapping": { - "single_type": "false" - }, "number_of_shards": "1", - "mapper": { - "dynamic": "false" - }, + "mapper.dynamic": false, "number_of_replicas": "1" } }, "mappings": { - "url": { - "dynamic": "strict", + "doc": { "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" + "type": { + "type": "keyword" }, "url": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 2048 + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } } } - } - } - }, - "index-pattern": { - "dynamic": "strict", - "properties": { - "fieldFormatMap": { - "type": "text" }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "index-pattern": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { "type": "text" } } }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } }, - "refreshInterval": { + "dashboard": { + "dynamic": "strict", "properties": { - "display": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { "type": "keyword" }, - "pause": { + "timeRestore": { "type": "boolean" }, - "section": { - "type": "integer" + "timeTo": { + "type": "keyword" }, - "value": { + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { "type": "integer" } } }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + } + } }, - "kibanaSavedObjectMeta": { + "visualization": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { "type": "text" } } }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - }, - "_default_": { - "dynamic": "strict" - }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "timelion-sheet": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" } } }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "search": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { "type": "text" + }, + "version": { + "type": "integer" } } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" } } } diff --git a/test/functional/fixtures/es_archiver/empty_kibana/data.json.gz b/test/functional/fixtures/es_archiver/empty_kibana/data.json.gz index d2eadfa1d04dd..bffef555b9bf3 100644 Binary files a/test/functional/fixtures/es_archiver/empty_kibana/data.json.gz and b/test/functional/fixtures/es_archiver/empty_kibana/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/empty_kibana/mappings.json b/test/functional/fixtures/es_archiver/empty_kibana/mappings.json index 4279c41b5a5cf..e100065d7d155 100644 --- a/test/functional/fixtures/es_archiver/empty_kibana/mappings.json +++ b/test/functional/fixtures/es_archiver/empty_kibana/mappings.json @@ -4,249 +4,248 @@ "index": ".kibana", "settings": { "index": { - "mapping": { - "single_type": "false" - }, "number_of_shards": "1", - "mapper": { - "dynamic": "false" - }, + "mapper.dynamic": false, "number_of_replicas": "1" } }, "mappings": { - "timelion-sheet": { - "dynamic": "strict", + "doc": { "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" + "type": { + "type": "keyword" }, - "kibanaSavedObjectMeta": { + "timelion-sheet": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { "type": "text" + }, + "version": { + "type": "integer" } } }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { + "visualization": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { "type": "text" } } }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "search": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { "type": "text" + }, + "version": { + "type": "integer" } } }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "url": { - "dynamic": "strict", - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, "url": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 2048 + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } } } - } - } - }, - "_default_": { - "dynamic": "strict" - }, - "index-pattern": { - "dynamic": "strict", - "properties": { - "fieldFormatMap": { - "type": "text" }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "index-pattern": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { "type": "text" } } }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { + "dashboard": { + "dynamic": "strict", "properties": { - "display": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { "type": "keyword" }, - "pause": { + "timeRestore": { "type": "boolean" }, - "section": { - "type": "integer" + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" }, - "value": { + "version": { "type": "integer" } } }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } }, - "dateFormat:tz": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 256 + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } } } } diff --git a/test/functional/fixtures/es_archiver/logstash_functional/data.json.gz b/test/functional/fixtures/es_archiver/logstash_functional/data.json.gz index 44ce898062a99..075f0394f84e2 100644 Binary files a/test/functional/fixtures/es_archiver/logstash_functional/data.json.gz and b/test/functional/fixtures/es_archiver/logstash_functional/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/makelogs/data.json.gz b/test/functional/fixtures/es_archiver/makelogs/data.json.gz index 98409da09cb46..fd00aa63d23f1 100644 Binary files a/test/functional/fixtures/es_archiver/makelogs/data.json.gz and b/test/functional/fixtures/es_archiver/makelogs/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/visualize/data.json.gz b/test/functional/fixtures/es_archiver/visualize/data.json.gz index 3e1c28feee965..94b6c2a37efb9 100644 Binary files a/test/functional/fixtures/es_archiver/visualize/data.json.gz and b/test/functional/fixtures/es_archiver/visualize/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/visualize/mappings.json b/test/functional/fixtures/es_archiver/visualize/mappings.json index 1d12de0d45aaa..37ba6e6a3b135 100644 --- a/test/functional/fixtures/es_archiver/visualize/mappings.json +++ b/test/functional/fixtures/es_archiver/visualize/mappings.json @@ -4,242 +4,241 @@ "index": ".kibana", "settings": { "index": { - "mapping": { - "single_type": "false" - }, "number_of_shards": "1", - "mapper": { - "dynamic": "false" - }, + "mapper.dynamic": false, "number_of_replicas": "1" } }, "mappings": { - "server": { - "dynamic": "strict", + "doc": { "properties": { - "uuid": { + "type": { "type": "keyword" - } - } - }, - "url": { - "dynamic": "strict", - "properties": { - "accessCount": { - "type": "long" }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } }, "url": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 2048 + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } } } - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "search": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "columns": { + "type": "keyword" + }, + "description": { "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" } } }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { + "visualization": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { "type": "text" } } }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - }, - "index-pattern": { - "dynamic": "strict", - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "_default_": { - "dynamic": "strict" - }, - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "index-pattern": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { "type": "text" } } }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + } + } }, - "refreshInterval": { + "dashboard": { + "dynamic": "strict", "properties": { - "display": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { "type": "keyword" }, - "pause": { + "timeRestore": { "type": "boolean" }, - "section": { - "type": "integer" + "timeTo": { + "type": "keyword" }, - "value": { + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { "type": "integer" } } }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "timelion-sheet": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" } } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" } } } diff --git a/test/functional/fixtures/es_archiver/visualize_source-filters/data.json.gz b/test/functional/fixtures/es_archiver/visualize_source-filters/data.json.gz index ec79629572ea5..084cccf7330ae 100644 Binary files a/test/functional/fixtures/es_archiver/visualize_source-filters/data.json.gz and b/test/functional/fixtures/es_archiver/visualize_source-filters/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json b/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json index e8cbf9d2a3771..cd9f29b7d8e3d 100644 --- a/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json +++ b/test/functional/fixtures/es_archiver/visualize_source-filters/mappings.json @@ -4,240 +4,239 @@ "index": ".kibana", "settings": { "index": { - "mapping": { - "single_type": "false" - }, "number_of_shards": "1", - "mapper": { - "dynamic": "false" - }, + "mapper.dynamic": false, "number_of_replicas": "1" } }, "mappings": { - "visualization": { - "dynamic": "strict", + "doc": { "properties": { - "description": { - "type": "text" + "type": { + "type": "keyword" }, - "kibanaSavedObjectMeta": { + "visualization": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { "type": "text" } } }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "timelion-sheet": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" } } }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "config": { + "dynamic": "true", "properties": { - "searchSourceJSON": { - "type": "text" + "buildNum": { + "type": "keyword" } } }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { + "dashboard": { + "dynamic": "strict", "properties": { - "display": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { "type": "keyword" }, - "pause": { + "timeRestore": { "type": "boolean" }, - "section": { - "type": "integer" + "timeTo": { + "type": "keyword" }, - "value": { + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { "type": "integer" } } }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "index-pattern": { - "dynamic": "strict", - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "_default_": { - "dynamic": "strict" - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "index-pattern": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { "type": "text" } } }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "url": { - "dynamic": "strict", - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } }, - "createDate": { - "type": "date" + "search": { + "dynamic": "strict", + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } }, "url": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 2048 + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } } } } diff --git a/test/functional/fixtures/es_archiver/visualize_source_filters/data.json.gz b/test/functional/fixtures/es_archiver/visualize_source_filters/data.json.gz index 62f6303cc780f..925e8b15d89d0 100644 Binary files a/test/functional/fixtures/es_archiver/visualize_source_filters/data.json.gz and b/test/functional/fixtures/es_archiver/visualize_source_filters/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json b/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json index ce9378ac3c808..f57afd6f8a089 100644 --- a/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json +++ b/test/functional/fixtures/es_archiver/visualize_source_filters/mappings.json @@ -4,260 +4,259 @@ "index": ".kibana", "settings": { "index": { - "mapping": { - "single_type": "false" - }, "number_of_shards": "1", - "mapper": { - "dynamic": "false" - }, + "mapper.dynamic": false, "number_of_replicas": "1" } }, "mappings": { - "index-pattern": { - "dynamic": "strict", + "doc": { "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { + "type": { "type": "keyword" }, - "title": { - "type": "text" - } - } - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "index-pattern": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { "type": "text" } } }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } }, - "refreshInterval": { + "dashboard": { + "dynamic": "strict", "properties": { - "display": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { "type": "keyword" }, - "pause": { + "timeRestore": { "type": "boolean" }, - "section": { - "type": "integer" + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" }, - "value": { + "version": { "type": "integer" } } }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "search": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { "type": "text" + }, + "version": { + "type": "integer" } } }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "_default_": { - "dynamic": "strict" - }, - "url": { - "dynamic": "strict", - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, "url": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 2048 - } - } - } - } - }, - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - }, - "dateFormat:tz": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 256 + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } } } }, - "defaultIndex": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 256 + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } } } - } - } - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" }, - "kibanaSavedObjectMeta": { + "visualization": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { "type": "text" } } }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { + "timelion-sheet": { + "dynamic": "strict", "properties": { - "searchSourceJSON": { + "description": { "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" } } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" } } } diff --git a/test/functional/services/index.js b/test/functional/services/index.js index 0ec496ceadbc3..9cd9c86167d12 100644 --- a/test/functional/services/index.js +++ b/test/functional/services/index.js @@ -1,4 +1,3 @@ -export { RetryProvider } from './retry'; export { FilterBarProvider } from './filter_bar'; export { FindProvider } from './find'; export { TestSubjectsProvider } from './test_subjects';