diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.exportsizelimit.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.exportsizelimit.md index d8ff7b4c9e2ed..36eba99273997 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.exportsizelimit.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.exportsizelimit.md @@ -4,6 +4,8 @@ ## SavedObjectsExportOptions.exportSizeLimit property +the maximum number of objects to export. + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.includereferencesdeep.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.includereferencesdeep.md index 1972cc6634b75..d721fc260eaf8 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.includereferencesdeep.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.includereferencesdeep.md @@ -4,6 +4,8 @@ ## SavedObjectsExportOptions.includeReferencesDeep property +flag to also include all related saved objects in the export response. + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.md index 66f501a0f1433..0f1bd94d01552 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.md @@ -16,10 +16,11 @@ export interface SavedObjectsExportOptions | Property | Type | Description | | --- | --- | --- | -| [exportSizeLimit](./kibana-plugin-server.savedobjectsexportoptions.exportsizelimit.md) | number | | -| [includeReferencesDeep](./kibana-plugin-server.savedobjectsexportoptions.includereferencesdeep.md) | boolean | | -| [namespace](./kibana-plugin-server.savedobjectsexportoptions.namespace.md) | string | | -| [objects](./kibana-plugin-server.savedobjectsexportoptions.objects.md) | Array<{
id: string;
type: string;
}> | | -| [savedObjectsClient](./kibana-plugin-server.savedobjectsexportoptions.savedobjectsclient.md) | SavedObjectsClientContract | | -| [types](./kibana-plugin-server.savedobjectsexportoptions.types.md) | string[] | | +| [exportSizeLimit](./kibana-plugin-server.savedobjectsexportoptions.exportsizelimit.md) | number | the maximum number of objects to export. | +| [includeReferencesDeep](./kibana-plugin-server.savedobjectsexportoptions.includereferencesdeep.md) | boolean | flag to also include all related saved objects in the export response. | +| [namespace](./kibana-plugin-server.savedobjectsexportoptions.namespace.md) | string | optional namespace to override the namespace used by the savedObjectsClient. | +| [objects](./kibana-plugin-server.savedobjectsexportoptions.objects.md) | Array<{
id: string;
type: string;
}> | optional array of objects to export. | +| [savedObjectsClient](./kibana-plugin-server.savedobjectsexportoptions.savedobjectsclient.md) | SavedObjectsClientContract | an instance of the SavedObjectsClient. | +| [search](./kibana-plugin-server.savedobjectsexportoptions.search.md) | string | optional query string to filter exported objects. | +| [types](./kibana-plugin-server.savedobjectsexportoptions.types.md) | string[] | optional array of saved object types. | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.namespace.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.namespace.md index b5abfba7f6910..1a28cc92e6e7e 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.namespace.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.namespace.md @@ -4,6 +4,8 @@ ## SavedObjectsExportOptions.namespace property +optional namespace to override the namespace used by the savedObjectsClient. + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.objects.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.objects.md index 46cb62841d46c..cd32f66c0f81e 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.objects.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.objects.md @@ -4,6 +4,8 @@ ## SavedObjectsExportOptions.objects property +optional array of objects to export. + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.savedobjectsclient.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.savedobjectsclient.md index fc206d0f7e877..1e0dd6c6f164f 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.savedobjectsclient.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.savedobjectsclient.md @@ -4,6 +4,8 @@ ## SavedObjectsExportOptions.savedObjectsClient property +an instance of the SavedObjectsClient. + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.search.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.search.md new file mode 100644 index 0000000000000..5e44486ee65e0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.search.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsExportOptions](./kibana-plugin-server.savedobjectsexportoptions.md) > [search](./kibana-plugin-server.savedobjectsexportoptions.search.md) + +## SavedObjectsExportOptions.search property + +optional query string to filter exported objects. + +Signature: + +```typescript +search?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.types.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.types.md index 204402fe355e3..cf1eb676f7ab8 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.types.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.types.md @@ -4,6 +4,8 @@ ## SavedObjectsExportOptions.types property +optional array of saved object types. + Signature: ```typescript diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts index ad2a0e469ddd8..df3bbe7c455e4 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts @@ -96,29 +96,114 @@ describe('getSortedObjectsForExport()', () => { ] `); expect(savedObjectsClient.find).toMatchInlineSnapshot(` - [MockFunction] { - "calls": Array [ - Array [ + [MockFunction] { + "calls": Array [ + Array [ + Object { + "namespace": undefined, + "perPage": 500, + "search": undefined, + "sortField": "_id", + "sortOrder": "asc", + "type": Array [ + "index-pattern", + "search", + ], + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Promise {}, + }, + ], + } + `); + }); + + test('exports selected types with search string when present', async () => { + savedObjectsClient.find.mockResolvedValueOnce({ + total: 2, + saved_objects: [ + { + id: '2', + type: 'search', + attributes: {}, + references: [ + { + name: 'name', + type: 'index-pattern', + id: '1', + }, + ], + }, + { + id: '1', + type: 'index-pattern', + attributes: {}, + references: [], + }, + ], + per_page: 1, + page: 0, + }); + const exportStream = await getSortedObjectsForExport({ + savedObjectsClient, + exportSizeLimit: 500, + types: ['index-pattern', 'search'], + search: 'foo', + }); + + const response = await readStreamToCompletion(exportStream); + + expect(response).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ Object { - "namespace": undefined, - "perPage": 500, - "sortField": "_id", - "sortOrder": "asc", - "type": Array [ - "index-pattern", - "search", - ], + "id": "1", + "name": "name", + "type": "index-pattern", }, ], - ], - "results": Array [ - Object { - "type": "return", - "value": Promise {}, - }, - ], - } + "type": "search", + }, + ] `); + expect(savedObjectsClient.find).toMatchInlineSnapshot(` + [MockFunction] { + "calls": Array [ + Array [ + Object { + "namespace": undefined, + "perPage": 500, + "search": "foo", + "sortField": "_id", + "sortOrder": "asc", + "type": Array [ + "index-pattern", + "search", + ], + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Promise {}, + }, + ], + } + `); }); test('exports from the provided namespace when present', async () => { @@ -179,29 +264,30 @@ describe('getSortedObjectsForExport()', () => { ] `); expect(savedObjectsClient.find).toMatchInlineSnapshot(` - [MockFunction] { - "calls": Array [ - Array [ - Object { - "namespace": "foo", - "perPage": 500, - "sortField": "_id", - "sortOrder": "asc", - "type": Array [ - "index-pattern", - "search", - ], - }, - ], - ], - "results": Array [ - Object { - "type": "return", - "value": Promise {}, - }, + [MockFunction] { + "calls": Array [ + Array [ + Object { + "namespace": "foo", + "perPage": 500, + "search": undefined, + "sortField": "_id", + "sortOrder": "asc", + "type": Array [ + "index-pattern", + "search", ], - } - `); + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Promise {}, + }, + ], + } + `); }); test('export selected types throws error when exceeding exportSizeLimit', async () => { @@ -464,4 +550,17 @@ describe('getSortedObjectsForExport()', () => { `"Either \`type\` or \`objects\` are required."` ); }); + + test('rejects when both objects and search are passed in', () => { + const exportOpts = { + exportSizeLimit: 1, + savedObjectsClient, + objects: [{ type: 'index-pattern', id: '1' }], + search: 'foo', + }; + + expect(getSortedObjectsForExport(exportOpts)).rejects.toThrowErrorMatchingInlineSnapshot( + `"Can't specify both \\"search\\" and \\"objects\\" properties when exporting"` + ); + }); }); diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts index b4e7c2887fd3a..eca8fc0405300 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts @@ -28,26 +28,38 @@ import { sortObjects } from './sort_objects'; * @public */ export interface SavedObjectsExportOptions { + /** optional array of saved object types. */ types?: string[]; + /** optional array of objects to export. */ objects?: Array<{ + /** the saved object id. */ id: string; + /** the saved object type. */ type: string; }>; + /** optional query string to filter exported objects. */ + search?: string; + /** an instance of the SavedObjectsClient. */ savedObjectsClient: SavedObjectsClientContract; + /** the maximum number of objects to export. */ exportSizeLimit: number; + /** flag to also include all related saved objects in the export response. */ includeReferencesDeep?: boolean; + /** optional namespace to override the namespace used by the savedObjectsClient. */ namespace?: string; } async function fetchObjectsToExport({ objects, types, + search, exportSizeLimit, savedObjectsClient, namespace, }: { objects?: SavedObjectsExportOptions['objects']; types?: string[]; + search?: string; exportSizeLimit: number; savedObjectsClient: SavedObjectsClientContract; namespace?: string; @@ -56,6 +68,9 @@ async function fetchObjectsToExport({ if (objects.length > exportSizeLimit) { throw Boom.badRequest(`Can't export more than ${exportSizeLimit} objects`); } + if (typeof search === 'string') { + throw Boom.badRequest(`Can't specify both "search" and "objects" properties when exporting`); + } const bulkGetResult = await savedObjectsClient.bulkGet(objects, { namespace }); const erroredObjects = bulkGetResult.saved_objects.filter(obj => !!obj.error); if (erroredObjects.length) { @@ -69,6 +84,7 @@ async function fetchObjectsToExport({ } else if (types && types.length > 0) { const findResponse = await savedObjectsClient.find({ type: types, + search, sortField: '_id', sortOrder: 'asc', perPage: exportSizeLimit, @@ -86,6 +102,7 @@ async function fetchObjectsToExport({ export async function getSortedObjectsForExport({ types, objects, + search, savedObjectsClient, exportSizeLimit, includeReferencesDeep = false, @@ -94,6 +111,7 @@ export async function getSortedObjectsForExport({ const objectsToExport = await fetchObjectsToExport({ types, objects, + search, savedObjectsClient, exportSizeLimit, namespace, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 6451e2b9b7153..79728ecc8fb98 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -820,20 +820,15 @@ export class SavedObjectsErrorHelpers { // @public export interface SavedObjectsExportOptions { - // (undocumented) exportSizeLimit: number; - // (undocumented) includeReferencesDeep?: boolean; - // (undocumented) namespace?: string; - // (undocumented) objects?: Array<{ id: string; type: string; }>; - // (undocumented) savedObjectsClient: SavedObjectsClientContract; - // (undocumented) + search?: string; types?: string[]; } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js index f7b7f06c2f502..8a7597421600f 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js @@ -19,6 +19,7 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { Query } from '@elastic/eui'; import { ObjectsTable, POSSIBLE_TYPES } from '../objects_table'; import { Flyout } from '../components/flyout/'; @@ -44,8 +45,8 @@ jest.mock('../../../lib/fetch_export_objects', () => ({ fetchExportObjects: jest.fn(), })); -jest.mock('../../../lib/fetch_export_by_type', () => ({ - fetchExportByType: jest.fn(), +jest.mock('../../../lib/fetch_export_by_type_and_search', () => ({ + fetchExportByTypeAndSearch: jest.fn(), })); jest.mock('../../../lib/get_saved_object_counts', () => ({ @@ -305,7 +306,7 @@ describe('ObjectsTable', () => { }); it('should export all', async () => { - const { fetchExportByType } = require('../../../lib/fetch_export_by_type'); + const { fetchExportByTypeAndSearch } = require('../../../lib/fetch_export_by_type_and_search'); const { saveAs } = require('@elastic/filesaver'); const component = shallowWithIntl( { // Set up mocks const blob = new Blob([JSON.stringify(allSavedObjects)], { type: 'application/ndjson' }); - fetchExportByType.mockImplementation(() => blob); + fetchExportByTypeAndSearch.mockImplementation(() => blob); await component.instance().onExportAll(); - expect(fetchExportByType).toHaveBeenCalledWith(POSSIBLE_TYPES, true); + expect(fetchExportByTypeAndSearch).toHaveBeenCalledWith(POSSIBLE_TYPES, undefined, true); + expect(saveAs).toHaveBeenCalledWith(blob, 'export.ndjson'); + expect(addSuccessMock).toHaveBeenCalledWith({ title: 'Your file is downloading in the background' }); + }); + + it('should export all, accounting for the current search criteria', async () => { + const { fetchExportByTypeAndSearch } = require('../../../lib/fetch_export_by_type_and_search'); + const { saveAs } = require('@elastic/filesaver'); + const component = shallowWithIntl( + + ); + + component.instance().onQueryChange({ + query: Query.parse('test') + }); + + // Ensure all promises resolve + await new Promise(resolve => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + // Set up mocks + const blob = new Blob([JSON.stringify(allSavedObjects)], { type: 'application/ndjson' }); + fetchExportByTypeAndSearch.mockImplementation(() => blob); + + await component.instance().onExportAll(); + + expect(fetchExportByTypeAndSearch).toHaveBeenCalledWith(POSSIBLE_TYPES, 'test*', true); expect(saveAs).toHaveBeenCalledWith(blob, 'export.ndjson'); expect(addSuccessMock).toHaveBeenCalledWith({ title: 'Your file is downloading in the background' }); }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/relationships.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/relationships.test.js index d0c45f8a7ee08..3670028726f10 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/relationships.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/relationships.test.js @@ -26,8 +26,8 @@ jest.mock('ui/chrome', () => ({ addBasePath: () => '' })); -jest.mock('../../../../../lib/fetch_export_by_type', () => ({ - fetchExportByType: jest.fn(), +jest.mock('../../../../../lib/fetch_export_by_type_and_search', () => ({ + fetchExportByTypeAndSearch: jest.fn(), })); jest.mock('../../../../../lib/fetch_export_objects', () => ({ diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js index 326c577ebe220..3e7e84d75bfe1 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js @@ -58,7 +58,7 @@ import { getRelationships, getSavedObjectLabel, fetchExportObjects, - fetchExportByType, + fetchExportByTypeAndSearch, findObjects, } from '../../lib'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; @@ -303,7 +303,8 @@ class ObjectsTableUI extends Component { onExportAll = async () => { const { intl } = this.props; - const { exportAllSelectedOptions, isIncludeReferencesDeepChecked } = this.state; + const { exportAllSelectedOptions, isIncludeReferencesDeepChecked, activeQuery } = this.state; + const { queryText } = parseQuery(activeQuery); const exportTypes = Object.entries(exportAllSelectedOptions).reduce( (accum, [id, selected]) => { if (selected) { @@ -316,7 +317,7 @@ class ObjectsTableUI extends Component { let blob; try { - blob = await fetchExportByType(exportTypes, isIncludeReferencesDeepChecked); + blob = await fetchExportByTypeAndSearch(exportTypes, queryText ? `${queryText}*` : undefined, isIncludeReferencesDeepChecked); } catch (e) { toastNotifications.addDanger({ title: intl.formatMessage({ diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/fetch_export_by_type.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/fetch_export_by_type_and_search.js similarity index 90% rename from src/legacy/core_plugins/kibana/public/management/sections/objects/lib/fetch_export_by_type.js rename to src/legacy/core_plugins/kibana/public/management/sections/objects/lib/fetch_export_by_type_and_search.js index 71c022b9d3998..788a4635d8dac 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/fetch_export_by_type.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/fetch_export_by_type_and_search.js @@ -19,12 +19,13 @@ import { kfetch } from 'ui/kfetch'; -export async function fetchExportByType(types, includeReferencesDeep = false) { +export async function fetchExportByTypeAndSearch(types, search, includeReferencesDeep = false) { return await kfetch({ method: 'POST', pathname: '/api/saved_objects/_export', body: JSON.stringify({ type: types, + search, includeReferencesDeep, }), }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/index.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/index.js index 2818f0d8a6cb4..245812867f1de 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/index.js @@ -17,7 +17,7 @@ * under the License. */ -export * from './fetch_export_by_type'; +export * from './fetch_export_by_type_and_search'; export * from './fetch_export_objects'; export * from './in_app_url'; export * from './get_relationships'; diff --git a/src/legacy/server/saved_objects/routes/export.test.ts b/src/legacy/server/saved_objects/routes/export.test.ts index 6b6e6ac90a48c..491e3a9067611 100644 --- a/src/legacy/server/saved_objects/routes/export.test.ts +++ b/src/legacy/server/saved_objects/routes/export.test.ts @@ -62,12 +62,42 @@ describe('POST /api/saved_objects/_export', () => { jest.resetAllMocks(); }); + test('does not allow both "search" and "objects" to be specified', async () => { + const request = { + method: 'POST', + url: '/api/saved_objects/_export', + payload: { + search: 'search', + objects: [{ type: 'search', id: 'bar' }], + includeReferencesDeep: true, + }, + }; + + const { payload, statusCode } = await server.inject(request); + + expect(statusCode).toEqual(400); + expect(JSON.parse(payload)).toMatchInlineSnapshot(` + Object { + "error": "Bad Request", + "message": "\\"search\\" must not exist simultaneously with [objects]", + "statusCode": 400, + "validation": Object { + "keys": Array [ + "value", + ], + "source": "payload", + }, + } + `); + }); + test('formats successful response', async () => { const request = { method: 'POST', url: '/api/saved_objects/_export', payload: { type: 'search', + search: 'my search string', includeReferencesDeep: true, }, }; @@ -101,58 +131,59 @@ describe('POST /api/saved_objects/_export', () => { expect(headers).toHaveProperty('content-disposition', 'attachment; filename="export.ndjson"'); expect(headers).toHaveProperty('content-type', 'application/ndjson'); expect(objects).toMatchInlineSnapshot(` -Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "ref_0", - "type": "index-pattern", - }, - ], - "type": "search", - }, -] -`); - expect(getSortedObjectsForExport).toMatchInlineSnapshot(` -[MockFunction] { - "calls": Array [ - Array [ - Object { - "exportSizeLimit": 10000, - "includeReferencesDeep": true, - "objects": undefined, - "savedObjectsClient": Object { - "bulkCreate": [MockFunction], - "bulkGet": [MockFunction], - "create": [MockFunction], - "delete": [MockFunction], - "errors": Object {}, - "find": [MockFunction], - "get": [MockFunction], - "update": [MockFunction], + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", }, - "types": Array [ - "search", + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "ref_0", + "type": "index-pattern", + }, + ], + "type": "search", + }, + ] + `); + expect(getSortedObjectsForExport).toMatchInlineSnapshot(` + [MockFunction] { + "calls": Array [ + Array [ + Object { + "exportSizeLimit": 10000, + "includeReferencesDeep": true, + "objects": undefined, + "savedObjectsClient": Object { + "bulkCreate": [MockFunction], + "bulkGet": [MockFunction], + "create": [MockFunction], + "delete": [MockFunction], + "errors": Object {}, + "find": [MockFunction], + "get": [MockFunction], + "update": [MockFunction], + }, + "search": "my search string", + "types": Array [ + "search", + ], + }, + ], ], - }, - ], - ], - "results": Array [ - Object { - "type": "return", - "value": Promise {}, - }, - ], -} -`); + "results": Array [ + Object { + "type": "return", + "value": Promise {}, + }, + ], + } + `); }); }); diff --git a/src/legacy/server/saved_objects/routes/export.ts b/src/legacy/server/saved_objects/routes/export.ts index 32d655531ec74..fc120030a873c 100644 --- a/src/legacy/server/saved_objects/routes/export.ts +++ b/src/legacy/server/saved_objects/routes/export.ts @@ -41,6 +41,7 @@ interface ExportRequest extends Hapi.Request { type: string; id: string; }>; + search?: string; includeReferencesDeep: boolean; }; } @@ -70,9 +71,11 @@ export const createExportRoute = ( }) .max(server.config().get('savedObjects.maxImportExportSize')) .optional(), + search: Joi.string().optional(), includeReferencesDeep: Joi.boolean().default(false), }) .xor('type', 'objects') + .nand('search', 'objects') .default(), }, async handler(request: ExportRequest, h: Hapi.ResponseToolkit) { @@ -80,6 +83,7 @@ export const createExportRoute = ( const exportStream = await getSortedObjectsForExport({ savedObjectsClient, types: request.payload.type, + search: request.payload.search, objects: request.payload.objects, exportSizeLimit: server.config().get('savedObjects.maxImportExportSize'), includeReferencesDeep: request.payload.includeReferencesDeep, diff --git a/test/api_integration/apis/saved_objects/export.js b/test/api_integration/apis/saved_objects/export.js index 0564c095faa88..e39749aa48159 100644 --- a/test/api_integration/apis/saved_objects/export.js +++ b/test/api_integration/apis/saved_objects/export.js @@ -94,6 +94,27 @@ export default function ({ getService }) { }); }); + it('should support including dependencies when exporting by type and search', async () => { + await supertest + .post('/api/saved_objects/_export') + .send({ + includeReferencesDeep: true, + type: ['dashboard'], + search: 'Requests*' + }) + .expect(200) + .then((resp) => { + const objects = resp.text.split('\n').map(JSON.parse); + expect(objects).to.have.length(3); + expect(objects[0]).to.have.property('id', '91200a00-9efd-11e7-acb3-3dab96693fab'); + expect(objects[0]).to.have.property('type', 'index-pattern'); + expect(objects[1]).to.have.property('id', 'dd7caf20-9efd-11e7-acb3-3dab96693fab'); + expect(objects[1]).to.have.property('type', 'visualization'); + expect(objects[2]).to.have.property('id', 'be3733a0-9efe-11e7-acb3-3dab96693fab'); + expect(objects[2]).to.have.property('type', 'dashboard'); + }); + }); + it(`should throw error when object doesn't exist`, async () => { await supertest .post('/api/saved_objects/_export')