From e43f645825111807c0f97007a5c28fdbc0eeba06 Mon Sep 17 00:00:00 2001 From: Brandon Kobel Date: Mon, 10 Jul 2017 15:15:04 -0400 Subject: [PATCH] ES Healthcheck v6 mapping compatibility (#12714) * Beginning to update the healthcheck to use the SavedObjectsClient Some tests are still broken The sort method on the SavedObjectsClient isn't there yet * Adding sort to create_find_query * Fixing the tests * Fixing upgrade_config tests * Making the SavedObjectsClient be dependant on the mappings to enable sorting * Fixing disabled tests * Fiixng test wording * Passing the savedObjectsClient to the stats route handler * Passing the savedObjectsClient to upgradeConfig from migratConfig * Using array of keys with _.get instead of manual string concatenation --- .../lib/__tests__/health_check.js | 2 + .../lib/__tests__/is_upgradeable.js | 32 +++-- .../lib/__tests__/upgrade_config.js | 114 +++++++++--------- .../elasticsearch/lib/is_upgradeable.js | 14 +-- .../elasticsearch/lib/migrate_config.js | 30 ++--- .../elasticsearch/lib/upgrade_config.js | 33 +++-- .../lib/export/__tests__/export_dashboards.js | 3 + .../server/lib/export/export_dashboards.js | 5 +- .../lib/import/__tests__/import_dashboards.js | 70 ++++------- .../server/lib/import/import_dashboards.js | 6 +- .../client/__tests__/saved_objects_client.js | 50 +++++++- .../client/lib/__tests__/create_find_query.js | 12 +- .../client/lib/create_find_query.js | 26 +++- .../client/saved_objects_client.js | 10 +- .../saved_objects/saved_objects_mixin.js | 27 +++-- 15 files changed, 236 insertions(+), 198 deletions(-) diff --git a/src/core_plugins/elasticsearch/lib/__tests__/health_check.js b/src/core_plugins/elasticsearch/lib/__tests__/health_check.js index 84bf94a639ae67..d781c80701f15a 100644 --- a/src/core_plugins/elasticsearch/lib/__tests__/health_check.js +++ b/src/core_plugins/elasticsearch/lib/__tests__/health_check.js @@ -42,6 +42,7 @@ describe('plugins/elasticsearch', () => { cluster = { callWithInternalUser: sinon.stub() }; cluster.callWithInternalUser.withArgs('index', sinon.match.any).returns(Promise.resolve()); + cluster.callWithInternalUser.withArgs('create', sinon.match.any).returns(Promise.resolve({ _id: 1, _version: 1 })); cluster.callWithInternalUser.withArgs('mget', sinon.match.any).returns(Promise.resolve({ ok: true })); cluster.callWithInternalUser.withArgs('get', sinon.match.any).returns(Promise.resolve({ found: false })); cluster.callWithInternalUser.withArgs('search', sinon.match.any).returns(Promise.resolve({ hits: { hits: [] } })); @@ -61,6 +62,7 @@ describe('plugins/elasticsearch', () => { const get = sinon.stub(); get.withArgs('elasticsearch.url').returns(esUrl); get.withArgs('kibana.index').returns('.my-kibana'); + get.withArgs('pkg.version').returns('1.0.0'); const set = sinon.stub(); diff --git a/src/core_plugins/elasticsearch/lib/__tests__/is_upgradeable.js b/src/core_plugins/elasticsearch/lib/__tests__/is_upgradeable.js index 36b5329bf38353..729ab19d58fadc 100644 --- a/src/core_plugins/elasticsearch/lib/__tests__/is_upgradeable.js +++ b/src/core_plugins/elasticsearch/lib/__tests__/is_upgradeable.js @@ -18,12 +18,12 @@ describe('plugins/elasticsearch', function () { }) }; - function upgradeDoc(_id, _version, bool) { + function upgradeDoc(id, _version, bool) { describe('', function () { before(function () { version = _version; }); - it(`should return ${bool} for ${_id} <= ${version}`, function () { - expect(isUpgradeable(server, { _id: _id })).to.be(bool); + it(`should return ${bool} for ${id} <= ${version}`, function () { + expect(isUpgradeable(server, { id: id })).to.be(bool); }); after(function () { version = pkg.version; }); @@ -42,32 +42,28 @@ describe('plugins/elasticsearch', function () { upgradeDoc('4.1.0-rc1-SNAPSHOT', '4.1.0-rc1', false); upgradeDoc('5.0.0-alpha1', '5.0.0', false); - it('should handle missing _id field', function () { - const doc = { - '_index': '.kibana', - '_type': 'config', - '_score': 1, - '_source': { + it('should handle missing id field', function () { + const configSavedObject = { + 'type': 'config', + 'attributes': { 'buildNum': 1.7976931348623157e+308, 'defaultIndex': '[logstash-]YYYY.MM.DD' } }; - expect(isUpgradeable(server, doc)).to.be(false); + expect(isUpgradeable(server, configSavedObject)).to.be(false); }); - it('should handle _id of @@version', function () { - const doc = { - '_index': '.kibana', - '_type': 'config', - '_id': '@@version', - '_score': 1, - '_source': { + it('should handle id of @@version', function () { + const configSavedObject = { + 'type': 'config', + 'id': '@@version', + 'attributes': { 'buildNum': 1.7976931348623157e+308, 'defaultIndex': '[logstash-]YYYY.MM.DD' } }; - expect(isUpgradeable(server, doc)).to.be(false); + expect(isUpgradeable(server, configSavedObject)).to.be(false); }); }); diff --git a/src/core_plugins/elasticsearch/lib/__tests__/upgrade_config.js b/src/core_plugins/elasticsearch/lib/__tests__/upgrade_config.js index 7f90ea66a3a74a..a757bc1178d2a5 100644 --- a/src/core_plugins/elasticsearch/lib/__tests__/upgrade_config.js +++ b/src/core_plugins/elasticsearch/lib/__tests__/upgrade_config.js @@ -8,7 +8,7 @@ describe('plugins/elasticsearch', function () { describe('lib/upgrade_config', function () { let get; let server; - let callWithInternalUser; + let savedObjectsClient; let upgrade; beforeEach(function () { @@ -17,7 +17,9 @@ describe('plugins/elasticsearch', function () { get.withArgs('pkg.version').returns('4.0.1'); get.withArgs('pkg.buildNum').returns(Math.random()); - callWithInternalUser = sinon.stub(); + savedObjectsClient = { + create: sinon.stub() + }; server = { log: sinon.stub(), @@ -26,22 +28,15 @@ describe('plugins/elasticsearch', function () { get: get }; }, - plugins: { - elasticsearch: { - getCluster: sinon.stub().withArgs('admin').returns({ - callWithInternalUser: callWithInternalUser - }) - } - } }; - upgrade = upgradeConfig(server); + upgrade = upgradeConfig(server, savedObjectsClient); }); describe('nothing is found', function () { - const response = { hits: { hits:[] } }; + const configSavedObjects = { hits: { hits:[] } }; beforeEach(function () { - callWithInternalUser.withArgs('create', sinon.match.any).returns(Promise.resolve()); + savedObjectsClient.create.returns(Promise.resolve({ id: 1, version: 1 })); }); describe('production', function () { @@ -52,17 +47,17 @@ describe('plugins/elasticsearch', function () { }); it('should resolve buildNum to pkg.buildNum config', function () { - return upgrade(response).then(function () { - sinon.assert.calledOnce(callWithInternalUser); - const params = callWithInternalUser.args[0][1]; - expect(params.body).to.have.property('buildNum', get('pkg.buildNum')); + return upgrade(configSavedObjects).then(function () { + sinon.assert.calledOnce(savedObjectsClient.create); + const attributes = savedObjectsClient.create.args[0][1]; + expect(attributes).to.have.property('buildNum', get('pkg.buildNum')); }); }); it('should resolve version to pkg.version config', function () { - return upgrade(response).then(function () { - const params = callWithInternalUser.args[0][1]; - expect(params).to.have.property('id', get('pkg.version')); + return upgrade(configSavedObjects).then(function () { + const options = savedObjectsClient.create.args[0][2]; + expect(options).to.have.property('id', get('pkg.version')); }); }); }); @@ -75,68 +70,68 @@ describe('plugins/elasticsearch', function () { }); it('should resolve buildNum to pkg.buildNum config', function () { - return upgrade(response).then(function () { - const params = callWithInternalUser.args[0][1]; - expect(params.body).to.have.property('buildNum', get('pkg.buildNum')); + return upgrade(configSavedObjects).then(function () { + const attributes = savedObjectsClient.create.args[0][1]; + expect(attributes).to.have.property('buildNum', get('pkg.buildNum')); }); }); it('should resolve version to pkg.version config', function () { - return upgrade(response).then(function () { - const params = callWithInternalUser.args[0][1]; - expect(params).to.have.property('id', get('pkg.version')); + return upgrade(configSavedObjects).then(function () { + const options = savedObjectsClient.create.args[0][2]; + expect(options).to.have.property('id', get('pkg.version')); }); }); }); }); it('should resolve with undefined if the current version is found', function () { - const response = { hits: { hits: [ { _id: '4.0.1' } ] } }; - return upgrade(response).then(function (resp) { + const configSavedObjects = [ { id: '4.0.1' } ]; + return upgrade(configSavedObjects).then(function (resp) { expect(resp).to.be(undefined); }); }); it('should create new config if the nothing is upgradeable', function () { get.withArgs('pkg.buildNum').returns(9833); - callWithInternalUser.withArgs('create', sinon.match.any).returns(Promise.resolve()); - - const response = { hits: { hits: [ { _id: '4.0.1-alpha3' }, { _id: '4.0.1-beta1' }, { _id: '4.0.0-SNAPSHOT1' } ] } }; - return upgrade(response).then(function () { - sinon.assert.calledOnce(callWithInternalUser); - const params = callWithInternalUser.args[0][1]; - expect(params).to.have.property('body'); - expect(params.body).to.have.property('buildNum', 9833); - expect(params).to.have.property('index', '.my-kibana'); - expect(params).to.have.property('type', 'config'); - expect(params).to.have.property('id', '4.0.1'); + savedObjectsClient.create.returns(Promise.resolve({ id: 1, version: 1 })); + + const configSavedObjects = [ { id: '4.0.1-alpha3' }, { id: '4.0.1-beta1' }, { id: '4.0.0-SNAPSHOT1' } ]; + return upgrade(configSavedObjects).then(function () { + sinon.assert.calledOnce(savedObjectsClient.create); + const savedObjectType = savedObjectsClient.create.args[0][0]; + expect(savedObjectType).to.eql('config'); + const attributes = savedObjectsClient.create.args[0][1]; + expect(attributes).to.have.property('buildNum', 9833); + const options = savedObjectsClient.create.args[0][2]; + expect(options).to.have.property('id', '4.0.1'); }); }); it('should update the build number on the new config', function () { get.withArgs('pkg.buildNum').returns(5801); - callWithInternalUser.withArgs('create', sinon.match.any).returns(Promise.resolve()); - - const response = { hits: { hits: [ { _id: '4.0.0', _source: { buildNum: 1 } } ] } }; - - return upgrade(response).then(function () { - sinon.assert.calledOnce(callWithInternalUser); - const params = callWithInternalUser.args[0][1]; - expect(params).to.have.property('body'); - expect(params.body).to.have.property('buildNum', 5801); - expect(params).to.have.property('index', '.my-kibana'); - expect(params).to.have.property('type', 'config'); - expect(params).to.have.property('id', '4.0.1'); + savedObjectsClient.create.returns(Promise.resolve({ id: 1, version: 1 })); + + const configSavedObjects = [ { id: '4.0.0', attributes: { buildNum: 1 } } ]; + + return upgrade(configSavedObjects).then(function () { + sinon.assert.calledOnce(savedObjectsClient.create); + const attributes = savedObjectsClient.create.args[0][1]; + expect(attributes).to.have.property('buildNum', 5801); + const savedObjectType = savedObjectsClient.create.args[0][0]; + expect(savedObjectType).to.eql('config'); + const options = savedObjectsClient.create.args[0][2]; + expect(options).to.have.property('id', '4.0.1'); }); }); it('should log a message for upgrades', function () { get.withArgs('pkg.buildNum').returns(5801); - callWithInternalUser.withArgs('create', sinon.match.any).returns(Promise.resolve()); + savedObjectsClient.create.returns(Promise.resolve({ id: 1, version: 1 })); - const response = { hits: { hits: [ { _id: '4.0.0', _source: { buildNum: 1 } } ] } }; + const configSavedObjects = [ { id: '4.0.0', attributes: { buildNum: 1 } } ]; - return upgrade(response).then(function () { + return upgrade(configSavedObjects).then(function () { sinon.assert.calledOnce(server.log); expect(server.log.args[0][0]).to.eql(['plugin', 'elasticsearch']); const msg = server.log.args[0][1]; @@ -148,15 +143,14 @@ describe('plugins/elasticsearch', function () { it('should copy attributes from old config', function () { get.withArgs('pkg.buildNum').returns(5801); - callWithInternalUser.withArgs('create', sinon.match.any).returns(Promise.resolve()); + savedObjectsClient.create.returns(Promise.resolve({ id: 1, version: 1 })); - const response = { hits: { hits: [ { _id: '4.0.0', _source: { buildNum: 1, defaultIndex: 'logstash-*' } } ] } }; + const configSavedObjects = [ { id: '4.0.0', attributes: { buildNum: 1, defaultIndex: 'logstash-*' } } ]; - return upgrade(response).then(function () { - sinon.assert.calledOnce(callWithInternalUser); - const params = callWithInternalUser.args[0][1]; - expect(params).to.have.property('body'); - expect(params.body).to.have.property('defaultIndex', 'logstash-*'); + return upgrade(configSavedObjects).then(function () { + sinon.assert.calledOnce(savedObjectsClient.create); + const attributes = savedObjectsClient.create.args[0][1]; + expect(attributes).to.have.property('defaultIndex', 'logstash-*'); }); }); }); diff --git a/src/core_plugins/elasticsearch/lib/is_upgradeable.js b/src/core_plugins/elasticsearch/lib/is_upgradeable.js index 2da4012d01e5aa..7b9de5632c02a8 100644 --- a/src/core_plugins/elasticsearch/lib/is_upgradeable.js +++ b/src/core_plugins/elasticsearch/lib/is_upgradeable.js @@ -1,17 +1,17 @@ import semver from 'semver'; const rcVersionRegex = /(\d+\.\d+\.\d+)\-rc(\d+)/i; -module.exports = function (server, doc) { +export default function (server, configSavedObject) { const config = server.config(); - if (/alpha|beta|snapshot/i.test(doc._id)) return false; - if (!doc._id) return false; - if (doc._id === config.get('pkg.version')) return false; + if (/alpha|beta|snapshot/i.test(configSavedObject.id)) return false; + if (!configSavedObject.id) return false; + if (configSavedObject.id === config.get('pkg.version')) return false; let packageRcRelease = Infinity; let rcRelease = Infinity; let packageVersion = config.get('pkg.version'); - let version = doc._id; - const matches = doc._id.match(rcVersionRegex); + let version = configSavedObject.id; + const matches = configSavedObject.id.match(rcVersionRegex); const packageMatches = config.get('pkg.version').match(rcVersionRegex); if (matches) { @@ -30,4 +30,4 @@ module.exports = function (server, doc) { return false; } return true; -}; +} diff --git a/src/core_plugins/elasticsearch/lib/migrate_config.js b/src/core_plugins/elasticsearch/lib/migrate_config.js index 5a114639d7b6e5..a7cd31ce5d8622 100644 --- a/src/core_plugins/elasticsearch/lib/migrate_config.js +++ b/src/core_plugins/elasticsearch/lib/migrate_config.js @@ -1,24 +1,18 @@ -import { get } from 'lodash'; import upgrade from './upgrade_config'; +import { SavedObjectsClient } from '../../../server/saved_objects'; -module.exports = function (server, { mappings }) { +export default async function (server, { mappings }) { const config = server.config(); const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - const options = { - index: config.get('kibana.index'), + + const savedObjectsClient = new SavedObjectsClient(config.get('kibana.index'), mappings, callWithInternalUser); + const { saved_objects: configSavedObjects } = await savedObjectsClient.find({ type: 'config', - body: { - size: 1000, - sort: [ - { - buildNum: { - order: 'desc', - unmapped_type: get(mappings, 'config.properties.buildNum.type') || 'keyword' - } - } - ] - } - }; + page: 1, + perPage: 1000, + sortField: 'buildNum', + sortOrder: 'desc' + }); - return callWithInternalUser('search', options).then(upgrade(server)); -}; + return await upgrade(server, savedObjectsClient)(configSavedObjects); +} diff --git a/src/core_plugins/elasticsearch/lib/upgrade_config.js b/src/core_plugins/elasticsearch/lib/upgrade_config.js index 2ac78615b13c42..f66ee323319f0f 100644 --- a/src/core_plugins/elasticsearch/lib/upgrade_config.js +++ b/src/core_plugins/elasticsearch/lib/upgrade_config.js @@ -2,28 +2,26 @@ import Promise from 'bluebird'; import isUpgradeable from './is_upgradeable'; import _ from 'lodash'; -module.exports = function (server) { - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); +export default function (server, savedObjectsClient) { const config = server.config(); function createNewConfig() { - return callWithInternalUser('create', { - index: config.get('kibana.index'), - type: 'config', - body: { buildNum: config.get('pkg.buildNum') }, + return savedObjectsClient.create('config', { + buildNum: config.get('pkg.buildNum') + }, { id: config.get('pkg.version') }); } - return function (response) { + return function (configSavedObjects) { // Check to see if there are any doc. If not then we set the build number and id - if (response.hits.hits.length === 0) { + if (configSavedObjects.length === 0) { return createNewConfig(); } // if we already have a the current version in the index then we need to stop - const devConfig = _.find(response.hits.hits, function currentVersion(hit) { - return hit._id !== '@@version' && hit._id === config.get('pkg.version'); + const devConfig = _.find(configSavedObjects, function currentVersion(configSavedObject) { + return configSavedObject.id !== '@@version' && configSavedObject.id === config.get('pkg.version'); }); if (devConfig) { @@ -32,26 +30,23 @@ module.exports = function (server) { // Look for upgradeable configs. If none of them are upgradeable // then create a new one. - const body = _.find(response.hits.hits, isUpgradeable.bind(null, server)); - if (!body) { + const configSavedObject = _.find(configSavedObjects, isUpgradeable.bind(null, server)); + if (!configSavedObject) { return createNewConfig(); } // if the build number is still the template string (which it wil be in development) // then we need to set it to the max interger. Otherwise we will set it to the build num - body._source.buildNum = config.get('pkg.buildNum'); + configSavedObject.attributes.buildNum = config.get('pkg.buildNum'); server.log(['plugin', 'elasticsearch'], { tmpl: 'Upgrade config from <%= prevVersion %> to <%= newVersion %>', - prevVersion: body._id, + prevVersion: configSavedObject.id, newVersion: config.get('pkg.version') }); - return callWithInternalUser('create', { - index: config.get('kibana.index'), - type: 'config', - body: body._source, + return savedObjectsClient.create('config', configSavedObject.attributes, { id: config.get('pkg.version') }); }; -}; +} diff --git a/src/core_plugins/kibana/server/lib/export/__tests__/export_dashboards.js b/src/core_plugins/kibana/server/lib/export/__tests__/export_dashboards.js index b21acdfc792483..0b7990cebd2c96 100644 --- a/src/core_plugins/kibana/server/lib/export/__tests__/export_dashboards.js +++ b/src/core_plugins/kibana/server/lib/export/__tests__/export_dashboards.js @@ -18,6 +18,9 @@ describe('exportDashboards(req)', () => { getCluster: () => ({ callWithRequest: sinon.stub() }) } }, + }, + getSavedObjectsClient() { + return null; } }; diff --git a/src/core_plugins/kibana/server/lib/export/export_dashboards.js b/src/core_plugins/kibana/server/lib/export/export_dashboards.js index 8a266ceb54ab5d..2a2d22d43f98b1 100644 --- a/src/core_plugins/kibana/server/lib/export/export_dashboards.js +++ b/src/core_plugins/kibana/server/lib/export/export_dashboards.js @@ -1,15 +1,12 @@ import _ from 'lodash'; import { collectDashboards } from './collect_dashboards'; -import { SavedObjectsClient } from '../../../../../server/saved_objects'; export async function exportDashboards(req) { const ids = _.flatten([req.query.dashboard]); const config = req.server.config(); - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin'); - const callAdminCluster = (...args) => callWithRequest(req, ...args); - const savedObjectsClient = new SavedObjectsClient(config.get('kibana.index'), callAdminCluster); + const savedObjectsClient = req.getSavedObjectsClient(); const objects = await collectDashboards(savedObjectsClient, ids); return { diff --git a/src/core_plugins/kibana/server/lib/import/__tests__/import_dashboards.js b/src/core_plugins/kibana/server/lib/import/__tests__/import_dashboards.js index 7262611e1399eb..1d1a139181db23 100644 --- a/src/core_plugins/kibana/server/lib/import/__tests__/import_dashboards.js +++ b/src/core_plugins/kibana/server/lib/import/__tests__/import_dashboards.js @@ -5,19 +5,16 @@ import { expect } from 'chai'; describe('importDashboards(req)', () => { let req; - let requestStub; + let bulkCreateStub; beforeEach(() => { - requestStub = sinon.stub().returns(Promise.resolve({ - responses: [] - })); - + bulkCreateStub = sinon.stub().returns(Promise.resolve()); req = { query: {}, payload: { version: '6.0.0', objects: [ { id: 'dashboard-01', type: 'dashboard', attributes: { panelJSON: '{}' } }, - { id: 'panel-01', type: 'visualization', attributes: { visState: '{}' } } + { id: 'panel-01', type: 'visualization', attributes: { visState: '{}' } }, ] }, server: { @@ -31,12 +28,12 @@ describe('importDashboards(req)', () => { throw new Error(`${id} is not available`); } } }), - plugins: { - elasticsearch: { - getCluster: () => ({ callWithRequest: requestStub }) - } - } - } + }, + getSavedObjectsClient() { + return { + bulkCreate: bulkCreateStub + }; + }, }; }); @@ -48,54 +45,31 @@ describe('importDashboards(req)', () => { }); }); - it('should make a bulk request to create each asset', () => { + it('should call bulkCreate with each asset', () => { return importDashboards(req).then(() => { - expect(requestStub.calledOnce).to.equal(true); - expect(requestStub.args[0][1]).to.equal('bulk'); - expect(requestStub.args[0][2]).to.eql({ - body: [ - { create: { _type: 'dashboard', _id: 'dashboard-01' } }, - { panelJSON: '{}' }, - { create: { _type: 'visualization', _id: 'panel-01' } }, - { visState: '{}' } - ], - index: '.kibana', - refresh: 'wait_for' - }); + expect(bulkCreateStub.calledOnce).to.equal(true); + expect(bulkCreateStub.args[0][0]).to.eql([ + { id: 'dashboard-01', type: 'dashboard', attributes: { panelJSON: '{}' } }, + { id: 'panel-01', type: 'visualization', attributes: { visState: '{}' } }, + ]); }); }); - it('should make a bulk request index each asset if force is truthy', () => { + it('should call bulkCreate with overwrite true if force is truthy', () => { req.query = { force: 'true' }; return importDashboards(req).then(() => { - expect(requestStub.calledOnce).to.equal(true); - expect(requestStub.args[0][1]).to.equal('bulk'); - expect(requestStub.args[0][2]).to.eql({ - body: [ - { index: { _type: 'dashboard', _id: 'dashboard-01' } }, - { panelJSON: '{}' }, - { index: { _type: 'visualization', _id: 'panel-01' } }, - { visState: '{}' } - ], - index: '.kibana', - refresh: 'wait_for' - }); + expect(bulkCreateStub.calledOnce).to.equal(true); + expect(bulkCreateStub.args[0][1]).to.eql({ overwrite: true }); }); }); it('should exclude types based on exclude argument', () => { req.query = { exclude: 'visualization' }; return importDashboards(req).then(() => { - expect(requestStub.calledOnce).to.equal(true); - expect(requestStub.args[0][1]).to.equal('bulk'); - expect(requestStub.args[0][2]).to.eql({ - body: [ - { create: { _type: 'dashboard', _id: 'dashboard-01' } }, - { panelJSON: '{}' } - ], - index: '.kibana', - refresh: 'wait_for' - }); + expect(bulkCreateStub.calledOnce).to.equal(true); + expect(bulkCreateStub.args[0][0]).to.eql([ + { id: 'dashboard-01', type: 'dashboard', attributes: { panelJSON: '{}' } }, + ]); }); }); diff --git a/src/core_plugins/kibana/server/lib/import/import_dashboards.js b/src/core_plugins/kibana/server/lib/import/import_dashboards.js index fd78c8ad486808..51cb227611b121 100644 --- a/src/core_plugins/kibana/server/lib/import/import_dashboards.js +++ b/src/core_plugins/kibana/server/lib/import/import_dashboards.js @@ -1,5 +1,4 @@ import { flatten } from 'lodash'; -import { SavedObjectsClient } from '../../../../../server/saved_objects'; export async function importDashboards(req) { const { payload } = req; @@ -7,10 +6,7 @@ export async function importDashboards(req) { const overwrite = 'force' in req.query && req.query.force !== false; const exclude = flatten([req.query.exclude]); - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin'); - const callAdminCluster = (...args) => callWithRequest(req, ...args); - const savedObjectsClient = new SavedObjectsClient(config.get('kibana.index'), callAdminCluster); - + const savedObjectsClient = req.getSavedObjectsClient(); if (payload.version !== config.get('pkg.version')) { throw new Error(`Version ${payload.version} does not match ${config.get('pkg.version')}.`); 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 37391fa8e48b32..95d25f89925d3a 100644 --- a/src/server/saved_objects/client/__tests__/saved_objects_client.js +++ b/src/server/saved_objects/client/__tests__/saved_objects_client.js @@ -41,9 +41,19 @@ describe('SavedObjectsClient', () => { } }; + const mappings = { + 'index-pattern': { + properties: { + someField: { + type: 'keyword' + } + } + } + }; + beforeEach(() => { callAdminCluster = sinon.mock(); - savedObjectsClient = new SavedObjectsClient('.kibana-test', callAdminCluster); + savedObjectsClient = new SavedObjectsClient('.kibana-test', mappings, callAdminCluster); }); afterEach(() => { @@ -310,6 +320,44 @@ describe('SavedObjectsClient', () => { }); }); + 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' }); 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 index e682c5ddbda6f6..b97b6b3c3c8a7a 100644 --- a/src/server/saved_objects/client/lib/__tests__/create_find_query.js +++ b/src/server/saved_objects/client/lib/__tests__/create_find_query.js @@ -1,14 +1,16 @@ 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(); + const query = createFindQuery(mappings); expect(query).to.eql({ query: { match_all: {} }, version: true }); }); it('adds bool filter for type', () => { - const query = createFindQuery({ type: 'index-pattern' }); + const query = createFindQuery(mappings, { type: 'index-pattern' }); expect(query).to.eql({ query: { bool: { @@ -27,7 +29,7 @@ describe('createFindQuery', () => { }); it('can search across all fields', () => { - const query = createFindQuery({ search: 'foo' }); + const query = createFindQuery(mappings, { search: 'foo' }); expect(query).to.eql({ query: { bool: { @@ -45,7 +47,7 @@ describe('createFindQuery', () => { }); it('can search a single field', () => { - const query = createFindQuery({ search: 'foo', searchFields: 'title' }); + const query = createFindQuery(mappings, { search: 'foo', searchFields: 'title' }); expect(query).to.eql({ query: { bool: { @@ -63,7 +65,7 @@ describe('createFindQuery', () => { }); it('can search across multiple fields', () => { - const query = createFindQuery({ search: 'foo', searchFields: ['title', 'description'] }); + const query = createFindQuery(mappings, { search: 'foo', searchFields: ['title', 'description'] }); expect(query).to.eql({ query: { bool: { diff --git a/src/server/saved_objects/client/lib/create_find_query.js b/src/server/saved_objects/client/lib/create_find_query.js index fc5993e21b8a5c..c088323c6a0f7c 100644 --- a/src/server/saved_objects/client/lib/create_find_query.js +++ b/src/server/saved_objects/client/lib/create_find_query.js @@ -1,5 +1,10 @@ -export function createFindQuery(options = {}) { - const { type, search, searchFields } = options; +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: {} } }; @@ -35,5 +40,20 @@ export function createFindQuery(options = {}) { }); } - return { version: true, query: { bool } }; + 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/saved_objects_client.js b/src/server/saved_objects/client/saved_objects_client.js index 32faf98d0f86a9..dd80381ef74373 100644 --- a/src/server/saved_objects/client/saved_objects_client.js +++ b/src/server/saved_objects/client/saved_objects_client.js @@ -7,8 +7,9 @@ import { } from './lib'; export class SavedObjectsClient { - constructor(kibanaIndex, callAdminCluster) { + constructor(kibanaIndex, mappings, callAdminCluster) { this._kibanaIndex = kibanaIndex; + this._mappings = mappings; this._callAdminCluster = callAdminCluster; } @@ -98,6 +99,7 @@ export class SavedObjectsClient { * Query field argument for more information * @property {integer} [options.page=1] * @property {integer} [options.perPage=20] + * @property {array} options.sort * @property {array} options.fields * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ @@ -108,7 +110,9 @@ export class SavedObjectsClient { searchFields, page = 1, perPage = 20, - fields + sortField, + sortOrder, + fields, } = options; const esOptions = { @@ -116,7 +120,7 @@ export class SavedObjectsClient { _source: fields, size: perPage, from: perPage * (page - 1), - body: createFindQuery({ search, searchFields, type }) + body: createFindQuery(this._mappings, { search, searchFields, type, sortField, sortOrder }) }; const response = await this._withKibanaIndex('search', esOptions); diff --git a/src/server/saved_objects/saved_objects_mixin.js b/src/server/saved_objects/saved_objects_mixin.js index 8eca0b22ef0b59..f699cadfe78aec 100644 --- a/src/server/saved_objects/saved_objects_mixin.js +++ b/src/server/saved_objects/saved_objects_mixin.js @@ -14,13 +14,7 @@ export function savedObjectsMixin(kbnServer, server) { getSavedObjectsClient: { assign: 'savedObjectsClient', method(req, reply) { - const adminCluster = req.server.plugins.elasticsearch.getCluster('admin'); - const callAdminCluster = (...args) => adminCluster.callWithRequest(req, ...args); - - reply(new SavedObjectsClient( - server.config().get('kibana.index'), - callAdminCluster - )); + reply(req.getSavedObjectsClient()); } }, }; @@ -31,4 +25,23 @@ export function savedObjectsMixin(kbnServer, server) { server.route(createFindRoute(prereqs)); server.route(createGetRoute(prereqs)); server.route(createUpdateRoute(prereqs)); + + const savedObjectsClientCache = new WeakMap(); + server.decorate('request', 'getSavedObjectsClient', function () { + const request = this; + + if (savedObjectsClientCache.has(request)) { + return savedObjectsClientCache.get(request); + } + + const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); + const callAdminCluster = (...args) => callWithRequest(request, ...args); + const savedObjectsClient = new SavedObjectsClient( + server.config().get('kibana.index'), + kbnServer.uiExports.mappings.getCombined(), + callAdminCluster + ); + savedObjectsClientCache.set(request, savedObjectsClient); + return savedObjectsClient; + }); }