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 992e54e83ff6e..5b0a7ee457dbc 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 @@ -8,7 +8,7 @@ describe('importDashboards(req)', () => { let requestStub; beforeEach(() => { requestStub = sinon.stub().returns(Promise.resolve({ - responses: [] + items: [] })); req = { @@ -47,10 +47,10 @@ describe('importDashboards(req)', () => { 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: '{}' } + { create: { _type: 'doc', _id: 'dashboard-01' } }, + { dashboard: { panelJSON: '{}' }, type: 'dashboard' }, + { create: { _type: 'doc', _id: 'panel-01' } }, + { visualization: { visState: '{}' }, type: 'visualization' } ], index: '.kibana' }); @@ -64,10 +64,10 @@ describe('importDashboards(req)', () => { 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: { _type: 'doc', _id: 'dashboard-01' } }, + { dashboard: { panelJSON: '{}' }, type: 'dashboard' }, + { index: { _type: 'doc', _id: 'panel-01' } }, + { visualization: { visState: '{}' }, type: 'visualization' } ], index: '.kibana' }); @@ -81,8 +81,8 @@ describe('importDashboards(req)', () => { 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: 'doc', _id: 'dashboard-01' } }, + { dashboard: { panelJSON: '{}' }, type: 'dashboard' } ], index: '.kibana' }); 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 47a161090b8c7..a874992530b83 100644 --- a/src/server/saved_objects/client/__tests__/saved_objects_client.js +++ b/src/server/saved_objects/client/__tests__/saved_objects_client.js @@ -97,10 +97,10 @@ 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: 'one' } }, + { config: { title: 'Test One' }, type: 'config' }, + { create: { _type: 'doc', _id: 'two' } }, + { 'index-pattern': { title: 'Test Two' }, type: 'index-pattern' } ]); }); @@ -116,10 +116,10 @@ describe('SavedObjectsClient', () => { 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' } + { index: { _type: 'doc', _id: 'one' } }, + { config: { title: 'Test One' }, type: 'config' }, + { index: { _type: 'doc', _id: 'two' } }, + { 'index-pattern': { title: 'Test Two' }, type: 'index-pattern' } ]); }); @@ -208,7 +208,9 @@ describe('SavedObjectsClient', () => { describe('#delete', () => { it('throws notFound when ES is unable to find the document', (done) => { - callAdminCluster.returns(Promise.resolve({ found: false })); + callAdminCluster.returns(Promise.resolve({ + deleted: 0 + })); savedObjectsClient.delete('index-pattern', 'logstash-*').then(() => { done('failed'); @@ -224,12 +226,43 @@ describe('SavedObjectsClient', () => { expect(callAdminCluster.calledOnce).to.be(true); const args = callAdminCluster.getCall(0).args; - expect(args[0]).to.be('delete'); + expect(args[0]).to.be('deleteByQuery'); expect(args[1]).to.eql({ - type: 'index-pattern', - id: 'logstash-*', refresh: 'wait_for', - index: '.kibana-test' + index: '.kibana-test', + body: { + version: true, + query: { + bool: { + should: [ + { + ids: { + type: 'index-pattern', + values: 'logstash-*' + } + }, + { + bool: { + must: [ + { + term: { + id: { + value: 'logstash-*' + } + } + }, + { + type: { + value: 'doc' + } + } + ] + } + } + ] + } + } + } }); }); }); @@ -272,7 +305,23 @@ describe('SavedObjectsClient', () => { const expectedQuery = { bool: { must: [{ match_all: {} }], - filter: [{ term: { _type: 'index-pattern' } }] + filter: [ + { + bool: { + should: [ + { + term: { + _type: 'index-pattern' + } + }, { + term: { + type: 'index-pattern' + } + } + ] + } + } + ] } }; @@ -294,11 +343,17 @@ describe('SavedObjectsClient', () => { describe('#get', () => { it('formats Elasticsearch response', async () => { callAdminCluster.returns(Promise.resolve({ - _id: 'logstash-*', - _type: 'index-pattern', - _version: 2, - _source: { - title: 'Testing' + hits: { + hits: [ + { + _id: 'logstash-*', + _type: 'index-pattern', + _version: 2, + _source: { + title: 'Testing' + } + } + ] } })); @@ -325,10 +380,13 @@ describe('SavedObjectsClient', () => { expect(callAdminCluster.calledOnce).to.be(true); const options = callAdminCluster.getCall(0).args[1]; - expect(options.body.docs).to.eql([ - { _type: 'config', _id: 'one' }, - { _type: 'index-pattern', _id: 'two' }, - { _type: undefined, _id: 'three' } + expect(options.body).to.eql([ + {}, + { version: true, query: { bool: { should: [{ ids: { values: 'one', type: 'config' } }, { bool: { must: [{ term: { id: { value: 'one' } } }, { type: { value: 'doc' } }] } }] } } }, //eslint-disable-line max-len + {}, + { version: true, query: { bool: { should: [{ ids: { values: 'two', type: 'index-pattern' } }, { bool: { must: [{ term: { id: { value: 'two' } } }, { type: { value: 'doc' } }] } }] } } }, //eslint-disable-line max-len + {}, + { version: true, query: { bool: { should: [{ ids: { values: 'three' } }, { bool: { must: [{ term: { id: { value: 'three' } } }, { type: { value: 'doc' } }] } }] } } } //eslint-disable-line max-len ]); }); @@ -341,17 +399,25 @@ describe('SavedObjectsClient', () => { it('omits missed objects', async () => { callAdminCluster.returns(Promise.resolve({ - docs:[{ - _type: 'config', - _id: 'bad', - found: false - }, { - _type: 'config', - _id: 'good', - found: true, - _version: 2, - _source: { title: 'Test' } - }] + responses: [ + { + hits: { + hits: [ + { + _id: 'good', + _type: 'doc', + _version: 2, + _source: { + type: 'config', + config: { + title: 'Test' + } + } + } + ] + } + } + ] })); const { saved_objects: savedObjects } = await savedObjectsClient.bulkGet( @@ -412,10 +478,10 @@ describe('SavedObjectsClient', () => { expect(args[0]).to.be('update'); expect(args[1]).to.eql({ - type: 'index-pattern', + type: 'doc', id: '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/lib/__tests__/create_find_query.js b/src/server/saved_objects/client/lib/__tests__/create_find_query.js index e682c5ddbda6f..932256e4b882d 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 @@ -13,8 +13,20 @@ describe('createFindQuery', () => { query: { bool: { filter: [{ - term: { - _type: 'index-pattern' + bool: { + should: [ + { + term: { + _type: 'index-pattern' + } + }, + { + term: { + type: 'index-pattern' + } + } + ] + } }], must: [{ 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 new file mode 100644 index 0000000000000..0dcab4c22a20c --- /dev/null +++ b/src/server/saved_objects/client/lib/__tests__/create_id_query.js @@ -0,0 +1,47 @@ +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, + query: { + bool: { + should: [ + { + ids: { + values: 'foo', + type: 'bar' + } + }, + { + bool: { + must: [ + { + term: { + id: { + value: 'foo' + } + } + }, + { + type: { + value: 'doc' + } + } + ] + } + } + ] + } + } + }; + + expect(query).to.eql(expectedQuery); + }); + +}); diff --git a/src/server/saved_objects/client/lib/__tests__/get_doc_type.js b/src/server/saved_objects/client/lib/__tests__/get_doc_type.js new file mode 100644 index 0000000000000..1a8917bd97461 --- /dev/null +++ b/src/server/saved_objects/client/lib/__tests__/get_doc_type.js @@ -0,0 +1,37 @@ +import expect from 'expect.js'; +import { getDocType } from '../get_doc_type'; + +describe('getDocType', () => { + it('gets the doc type from a legacy index', () => { + const legacyResponse = { + _index: '.kibana', + _type: 'config', + _id: '6.0.0-alpha2', + _score: 1, + _source: { + buildNum: 8467, + defaultIndex: '.kibana' + } + }; + + expect(getDocType(legacyResponse)).to.eql('config'); + }); + + it('gets the doc type from a new index', () => { + const newResponse = { + _index: '.kibana', + _type: 'doc', + _id: 'AVzBOLGoIr2L3MkvUTRP', + _score: 1, + _source: { + type: 'config', + config: { + buildNum: 8467, + defaultIndex: '.kibana' + } + } + }; + + expect(getDocType(newResponse)).to.eql('config'); + }); +}); 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 fc5993e21b8a5..818c29a612541 100644 --- a/src/server/saved_objects/client/lib/create_find_query.js +++ b/src/server/saved_objects/client/lib/create_find_query.js @@ -9,8 +9,19 @@ export function createFindQuery(options = {}) { if (type) { bool.filter.push({ - term: { - _type: type + bool: { + should: [ + { + term: { + _type: type + } + }, + { + term: { + type + } + } + ] } }); } diff --git a/src/server/saved_objects/client/lib/create_id_query.js b/src/server/saved_objects/client/lib/create_id_query.js new file mode 100644 index 0000000000000..e8422df4eafff --- /dev/null +++ b/src/server/saved_objects/client/lib/create_id_query.js @@ -0,0 +1,42 @@ +/** + * Finds a document by either its v6 or v5 format + * @param type The documents type + * @param id The documents id or legacy id +**/ +export function createIdQuery({ type, id }) { + const ids = { + values: id + }; + + if (type) ids.type = type; + return { + version: true, + query: { + bool: { + should: [ + { + ids + }, + { + bool: { + must: [ + { + term: { + id: { + value: id + } + } + }, + { + type: { + value: 'doc' + } + } + ] + } + } + ] + } + } + }; +} diff --git a/src/server/saved_objects/client/lib/get_doc_type.js b/src/server/saved_objects/client/lib/get_doc_type.js new file mode 100644 index 0000000000000..eb883e4741aca --- /dev/null +++ b/src/server/saved_objects/client/lib/get_doc_type.js @@ -0,0 +1,5 @@ +import { get } from 'lodash'; + +export function getDocType(doc) { + return get(doc, '_source.type') || doc._type; +} diff --git a/src/server/saved_objects/client/lib/index.js b/src/server/saved_objects/client/lib/index.js index b0e3483b16b31..95f74bacc9c93 100644 --- a/src/server/saved_objects/client/lib/index.js +++ b/src/server/saved_objects/client/lib/index.js @@ -1,2 +1,4 @@ export { createFindQuery } from './create_find_query'; +export { createIdQuery } from './create_id_query'; +export { getDocType } from './get_doc_type'; export { handleEsError } from './handle_es_error'; diff --git a/src/server/saved_objects/client/saved_objects_client.js b/src/server/saved_objects/client/saved_objects_client.js index 1ecbd1ebf9ec0..2635d6174e79c 100644 --- a/src/server/saved_objects/client/saved_objects_client.js +++ b/src/server/saved_objects/client/saved_objects_client.js @@ -3,9 +3,21 @@ import { get } from 'lodash'; import { createFindQuery, + createIdQuery, handleEsError, + getDocType } from './lib'; +const V6_TYPE = 'doc'; +const TYPE_MISSING_EXCEPTION = 'type_missing_exception'; + +function isTypeMissing(item, action) { + return get(item, `${action}.error.type`) === TYPE_MISSING_EXCEPTION; +} +function boomIsNotFound(err) { + return err.isBoom && err.output.statusCode === 404; +} + export class SavedObjectsClient { constructor(kibanaIndex, callAdminCluster) { this._kibanaIndex = kibanaIndex; @@ -13,7 +25,23 @@ export class SavedObjectsClient { } async create(type, body = {}) { - const response = await this._withKibanaIndex('index', { type, body }); + let response; + try { + response = await this._withKibanaIndex('index', { + type: V6_TYPE, + body: { + [type]: body + }, + refresh: 'wait_for' + }); + } catch(err) { + if (!boomIsNotFound(err)) throw err; + response = await this._withKibanaIndex('index', { + type, + body, + refresh: 'wait_for' + }); + } return { id: response._id, @@ -33,34 +61,47 @@ export class SavedObjectsClient { */ async bulkCreate(objects, options = {}) { const action = options.force === true ? 'index' : 'create'; - - const body = objects.reduce((acc, object) => { - acc.push({ [action]: { _type: object.type, _id: object.id } }); - acc.push(object.attributes); - + let response; + const v6Body = objects.reduce((acc, object) => { + acc.push({ [action]: { _type: 'doc', _id: object.id } }); + acc.push(Object.assign({}, { + type: object.type + }, { [object.type]: object.attributes })); return acc; }, []); + response = await this._withKibanaIndex('bulk', { body: v6Body }); + + const items = get(response, 'items', []); + const missingErrors = items.filter(item => isTypeMissing(item, action)).length; + const usesV5Index = items.length && items.length === missingErrors; + + if (usesV5Index) { + const v5Body = objects.reduce((acc, object) => { + acc.push({ [action]: { _type: object.type, _id: object.id } }); + acc.push(object.attributes); + return acc; + }, []); + response = await this._withKibanaIndex('bulk', { body: v5Body }); + } - return await this._withKibanaIndex('bulk', { body }) - .then(resp => get(resp, 'items', []).map((resp, i) => { - return { - id: resp[action]._id, - type: resp[action]._type, - version: resp[action]._version, - attributes: objects[i].attributes, - error: resp[action].error ? { message: get(resp[action], 'error.reason') } : undefined - }; - })); + return get(response, 'items', []).map((resp, i) => { + return { + id: resp[action]._id, + type: resp[action]._type, + version: resp[action]._version, + attributes: objects[i].attributes, + error: resp[action].error ? { message: get(resp[action], 'error.reason') } : undefined + }; + }); } async delete(type, id) { - const response = await this._withKibanaIndex('delete', { - type, - id, + const response = await this._withKibanaIndex('deleteByQuery', { + body: createIdQuery({ type, id }), refresh: 'wait_for' }); - if (get(response, 'found') === false) { + if (get(response, 'deleted') === 0) { throw Boom.notFound(); } } @@ -76,7 +117,6 @@ export class SavedObjectsClient { } = options; const esOptions = { - type, _source: fields, size: perPage, from: perPage * (page - 1), @@ -87,11 +127,12 @@ export class SavedObjectsClient { return { saved_objects: get(response, 'hits.hits', []).map(r => { + const docType = getDocType(r); return { id: r._id, - type: r._type, + type: docType, version: r._version, - attributes: r._source + attributes: get(r, `_source.${docType}`) || r._source }; }), total: get(response, 'hits.total', 0), @@ -118,49 +159,79 @@ export class SavedObjectsClient { return { saved_objects: [] }; } - const docs = objects.map(doc => { - return { _type: get(doc, 'type'), _id: get(doc, 'id') }; - }); + const docs = objects.reduce((acc, { type, id }) => { + acc.push({}, createIdQuery({ type, id })); + return acc; + }, []); - const response = await this._withKibanaIndex('mget', { body: { docs } }) - .then(resp => get(resp, 'docs', []).filter(resp => resp.found)); + + const response = await this._withKibanaIndex('msearch', { body: docs }) + .then(resp => { + let results = []; + const responses = get(resp, 'responses', []); + responses.forEach(r => { + results = results.concat(get(r, 'hits.hits')); + }); + return results; + }); return { saved_objects: response.map(r => { + const docType = getDocType(r); + return { id: r._id, - type: r._type, + type: docType, version: r._version, - attributes: r._source + attributes: get(r, `_source.${docType}`) || r._source }; }) }; } async get(type, id) { - const response = await this._withKibanaIndex('get', { - type, - id, - }); + const response = await this._withKibanaIndex('search', { body: createIdQuery({ type, id }) }); + const hit = get(response, 'hits.hits.0'); + if (!hit) throw Boom.notFound(); + + const attributes = get(hit, `_source.${type}`) || get(hit, '_source'); return { - id: response._id, - type: response._type, - version: response._version, - attributes: response._source + id: hit._id, + type: hit._type, + version: hit._version, + attributes }; } async update(type, id, attributes, options = {}) { - const response = await this._withKibanaIndex('update', { - type, + const baseParams = { id, version: get(options, 'version'), - body: { - doc: attributes - }, refresh: 'wait_for' - }); + }; + + let response; + try { + const v6Params = Object.assign({}, baseParams, { + type: V6_TYPE, + body: { + doc: { + [type]: attributes + } + }, + }); + response = await this._withKibanaIndex('update', v6Params); + } catch (err) { + if (!boomIsNotFound(err)) throw err; + const v5Params = Object.assign({}, baseParams, { + type, + body: { + doc: attributes + } + }); + response = await this._withKibanaIndex('update', v5Params); + } return { id: id, diff --git a/test/api_integration/apis/index.js b/test/api_integration/apis/index.js index 34fc5d2de59d5..b494ef7ed4fe5 100644 --- a/test/api_integration/apis/index.js +++ b/test/api_integration/apis/index.js @@ -3,5 +3,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./index_patterns')); loadTestFile(require.resolve('./scripts')); loadTestFile(require.resolve('./search')); + loadTestFile(require.resolve('./saved_objects')); }); } diff --git a/test/api_integration/apis/saved_objects/bulk_get.js b/test/api_integration/apis/saved_objects/bulk_get.js new file mode 100644 index 0000000000000..e9c2e67a8140c --- /dev/null +++ b/test/api_integration/apis/saved_objects/bulk_get.js @@ -0,0 +1,82 @@ +import expect from 'expect.js'; +import { get } from 'lodash'; + +export default function ({ getService }) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('Bulk get API', () => { + + describe(('multiple _types'), () => { + before(() => esArchiver.load('saved_objects/multiple_types')); + after(() => esArchiver.unload('saved_objects/multiple_types')); + runTests(); + }); + + describe(('single _type'), () => { + before(() => esArchiver.load('saved_objects/single_type')); + after(() => esArchiver.unload('saved_objects/single_type')); + runTests(); + }); + + function runTests() { + + it('should be able to get objects', () => { + return supertest + .post('/api/saved_objects/bulk_get') + .send([ + { + id: '6.0.0-alpha3', + type: 'config' + } + ]) + .expect(200) + .then((response) => { + expect(get(response, 'body.saved_objects.0.attributes')).to.eql({ + buildNum: 8467, + defaultIndex: '.kibana', + 'doc_table:highlight': false + }); + }); + }); + + it('should not return objects that don\'t exist', () => { + return supertest + .post('/api/saved_objects/bulk_get') + .send([ + { + id: 'foo', + type: 'config' + } + ]) + .expect(200) + .then((response) => { + expect(get(response, 'body.saved_objects').length).to.be(0); + }); + }); + + it('should should error type is missing', () => { + return supertest + .post('/api/saved_objects/bulk_get') + .send([ + { + type: 'config' + } + ]) + .expect(400); + }); + + it('should should error id is missing', () => { + return supertest + .post('/api/saved_objects/bulk_get') + .send([ + { + id: 'foo' + } + ]) + .expect(400); + }); + + } + }); +} diff --git a/test/api_integration/apis/saved_objects/create.js b/test/api_integration/apis/saved_objects/create.js new file mode 100644 index 0000000000000..6772ff7ec4e9c --- /dev/null +++ b/test/api_integration/apis/saved_objects/create.js @@ -0,0 +1,34 @@ +import expect from 'expect.js'; +import { get } from 'lodash'; + +export default function ({ getService }) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('Create API', () => { + + describe(('multiple _types'), () => { + before(() => esArchiver.load('saved_objects/multiple_types')); + after(() => esArchiver.unload('saved_objects/multiple_types')); + runTests(); + }); + + describe(('single _type'), () => { + before(() => esArchiver.load('saved_objects/single_type')); + after(() => esArchiver.unload('saved_objects/single_type')); + runTests(); + }); + + function runTests() { + it('should be able to create objects', () => { + return supertest + .post('/api/saved_objects/index-pattern') + .send({ attributes: { title: 'Test pattern' } }) + .expect(200) + .then((response) => { + expect(get(response, 'body.attributes.title')).to.be('Test pattern'); + }); + }); + } + }); +} diff --git a/test/api_integration/apis/saved_objects/delete.js b/test/api_integration/apis/saved_objects/delete.js new file mode 100644 index 0000000000000..d2e10f35962f6 --- /dev/null +++ b/test/api_integration/apis/saved_objects/delete.js @@ -0,0 +1,34 @@ + +export default function ({ getService }) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('Create API', () => { + + describe(('multiple _types'), () => { + before(() => esArchiver.load('saved_objects/multiple_types')); + after(() => esArchiver.unload('saved_objects/multiple_types')); + runTests(); + }); + + describe(('single _type'), () => { + before(() => esArchiver.load('saved_objects/single_type')); + after(() => esArchiver.unload('saved_objects/single_type')); + runTests(); + }); + + function runTests() { + it('should be able to delete objects', () => { + return supertest + .delete('/api/saved_objects/index-pattern/.kibana') + .expect(200); + }); + + it('should 404 if the object does not exist', () => { + return supertest + .delete('/api/saved_objects/index-pattern/hello') + .expect(404); + }); + } + }); +} diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js new file mode 100644 index 0000000000000..c35e77f682ef2 --- /dev/null +++ b/test/api_integration/apis/saved_objects/find.js @@ -0,0 +1,38 @@ +import expect from 'expect.js'; +import { get } from 'lodash'; + +export default function ({ getService }) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('Find API', () => { + + describe(('multiple _types'), () => { + before(() => esArchiver.load('saved_objects/multiple_types')); + after(() => esArchiver.unload('saved_objects/multiple_types')); + runTests(); + }); + + describe(('single _type'), () => { + before(() => esArchiver.load('saved_objects/single_type')); + after(() => esArchiver.unload('saved_objects/single_type')); + runTests(); + }); + + function runTests() { + it('should be able to search', async () => { + const visualization = await supertest + .post('/api/saved_objects/visualization') + .send({ attributes: { title: 'Test visualization' } }); + + const response = await supertest + .get('/api/saved_objects') + .query({ + search: 'test' + }); + expect(get(response, 'body.saved_objects.0.id')).to.be(get(visualization, 'body.id')); + expect(get(response, 'body.total')).to.be(1); + }); + } + }); +} diff --git a/test/api_integration/apis/saved_objects/get.js b/test/api_integration/apis/saved_objects/get.js new file mode 100644 index 0000000000000..42b37f04bf4dd --- /dev/null +++ b/test/api_integration/apis/saved_objects/get.js @@ -0,0 +1,40 @@ +import expect from 'expect.js'; +import { get } from 'lodash'; + +export default function ({ getService }) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('Get API', () => { + + describe(('multiple _types'), () => { + before(() => esArchiver.load('saved_objects/multiple_types')); + after(() => esArchiver.unload('saved_objects/multiple_types')); + runTests(); + }); + + describe(('single _type'), () => { + before(() => esArchiver.load('saved_objects/single_type')); + after(() => esArchiver.unload('saved_objects/single_type')); + runTests(); + }); + + function runTests() { + it('should return a saved object by id', () => { + return supertest + .get('/api/saved_objects/index-pattern/.kibana') + .expect(200) + .then((response) => { + expect(get(response, 'body.attributes.title')).to.be('.kibana'); + }); + }); + + it('should 404 if no saved objects are found', () => { + return supertest + .get('/api/saved_objects/index-pattern/does_not_exist') + .expect(404); + }); + } + + }); +} diff --git a/test/api_integration/apis/saved_objects/index.js b/test/api_integration/apis/saved_objects/index.js new file mode 100644 index 0000000000000..65caec43421f2 --- /dev/null +++ b/test/api_integration/apis/saved_objects/index.js @@ -0,0 +1,10 @@ +export default function ({ loadTestFile }) { + describe('scripts', () => { + loadTestFile(require.resolve('./get')); + loadTestFile(require.resolve('./bulk_get')); + loadTestFile(require.resolve('./create')); + loadTestFile(require.resolve('./delete')); + loadTestFile(require.resolve('./update')); + loadTestFile(require.resolve('./find')); + }); +} diff --git a/test/api_integration/apis/saved_objects/update.js b/test/api_integration/apis/saved_objects/update.js new file mode 100644 index 0000000000000..b37fb4d184a03 --- /dev/null +++ b/test/api_integration/apis/saved_objects/update.js @@ -0,0 +1,41 @@ +import expect from 'expect.js'; +import { get } from 'lodash'; + +export default function ({ getService }) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('Update API', () => { + + describe(('multiple _types'), () => { + before(() => esArchiver.load('saved_objects/multiple_types')); + after(() => esArchiver.unload('saved_objects/multiple_types')); + runTests(); + }); + + describe(('single _type'), () => { + before(() => esArchiver.load('saved_objects/single_type')); + after(() => esArchiver.unload('saved_objects/single_type')); + runTests(); + }); + + function runTests() { + it('should be able to update objects', () => { + return supertest + .post('/api/saved_objects/index-pattern') + .send({ attributes: { title: 'Test title' } }) + .expect(200) + .then((response) => { + const id = get(response, 'body.id'); + return supertest + .put(`/api/saved_objects/index-pattern/${id}`) + .send({ attributes: { title: 'Updated title' } }) + .expect(200); + }) + .then((response) => { + expect(get(response, 'body.attributes.title')).to.be('Updated title'); + }); + }); + } + }); +} diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/multiple_types/data.json.gz b/test/api_integration/fixtures/es_archiver/saved_objects/multiple_types/data.json.gz new file mode 100644 index 0000000000000..5e94c1c67d682 Binary files /dev/null and b/test/api_integration/fixtures/es_archiver/saved_objects/multiple_types/data.json.gz differ diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/multiple_types/mappings.json b/test/api_integration/fixtures/es_archiver/saved_objects/multiple_types/mappings.json new file mode 100644 index 0000000000000..1d6af568071c7 --- /dev/null +++ b/test/api_integration/fixtures/es_archiver/saved_objects/multiple_types/mappings.json @@ -0,0 +1,260 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "mapping": { + "single_type": "false" + }, + "number_of_shards": "1", + "mapper": { + "dynamic": "false" + }, + "number_of_replicas": "1" + } + }, + "mappings": { + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "visualization": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "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": { + "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" + } + } + }, + "dashboard": { + "dynamic": "strict", + "properties": { + "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" + }, + "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" + } + } + }, + "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" + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "doc_table:highlight": { + "type": "boolean" + } + } + }, + "url": { + "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/api_integration/fixtures/es_archiver/saved_objects/single_type/data.json.gz b/test/api_integration/fixtures/es_archiver/saved_objects/single_type/data.json.gz new file mode 100644 index 0000000000000..4739b06ead6ff Binary files /dev/null and b/test/api_integration/fixtures/es_archiver/saved_objects/single_type/data.json.gz differ diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/single_type/mappings.json b/test/api_integration/fixtures/es_archiver/saved_objects/single_type/mappings.json new file mode 100644 index 0000000000000..2232a56f623b9 --- /dev/null +++ b/test/api_integration/fixtures/es_archiver/saved_objects/single_type/mappings.json @@ -0,0 +1,260 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "mapping": { + "single_type": "false" + }, + "number_of_shards": "1", + "number_of_replicas": "1" + } + }, + "mappings": { + "doc": { + "dynamic": "strict", + "properties": { + "config": { + "properties": { + "buildNum": { + "type": "keyword" + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "doc_table:highlight": { + "type": "boolean" + } + } + }, + "dashboard": { + "properties": { + "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" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "id": { + "type": "keyword" + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "timelion-sheet": { + "properties": { + "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" + } + } + }, + "type": { + "type": "keyword" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "_default_": { + "dynamic": "strict" + } + } + } +}