mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
Saved object export: apply export hooks to referenced / nested objects (#100769)
* execute export transform for nested references * fix sort * fix duplicate references * add FTR test
This commit is contained in:
parent
d62bb452dd
commit
aa8aa7f23d
9 changed files with 1423 additions and 786 deletions
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const applyExportTransformsMock = jest.fn();
|
||||||
|
jest.doMock('./apply_export_transforms', () => ({
|
||||||
|
applyExportTransforms: applyExportTransformsMock,
|
||||||
|
}));
|
|
@ -0,0 +1,528 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { applyExportTransformsMock } from './collect_exported_objects.test.mocks';
|
||||||
|
import { savedObjectsClientMock } from '../../mocks';
|
||||||
|
import { httpServerMock } from '../../http/http_server.mocks';
|
||||||
|
import { SavedObject, SavedObjectError } from '../../../types';
|
||||||
|
import type { SavedObjectsExportTransform } from './types';
|
||||||
|
import { collectExportedObjects } from './collect_exported_objects';
|
||||||
|
|
||||||
|
const createObject = (parts: Partial<SavedObject>): SavedObject => ({
|
||||||
|
id: 'id',
|
||||||
|
type: 'type',
|
||||||
|
references: [],
|
||||||
|
attributes: {},
|
||||||
|
...parts,
|
||||||
|
});
|
||||||
|
|
||||||
|
const createError = (parts: Partial<SavedObjectError> = {}): SavedObjectError => ({
|
||||||
|
error: 'error',
|
||||||
|
message: 'message',
|
||||||
|
statusCode: 404,
|
||||||
|
...parts,
|
||||||
|
});
|
||||||
|
|
||||||
|
const toIdTuple = (obj: SavedObject) => ({ type: obj.type, id: obj.id });
|
||||||
|
|
||||||
|
describe('collectExportedObjects', () => {
|
||||||
|
let savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;
|
||||||
|
let request: ReturnType<typeof httpServerMock.createKibanaRequest>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
savedObjectsClient = savedObjectsClientMock.create();
|
||||||
|
request = httpServerMock.createKibanaRequest();
|
||||||
|
applyExportTransformsMock.mockImplementation(({ objects }) => objects);
|
||||||
|
savedObjectsClient.bulkGet.mockResolvedValue({ saved_objects: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
applyExportTransformsMock.mockReset();
|
||||||
|
savedObjectsClient.bulkGet.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when `includeReferences` is `true`', () => {
|
||||||
|
it('calls `applyExportTransforms` with the correct parameters', async () => {
|
||||||
|
const obj1 = createObject({
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
});
|
||||||
|
const obj2 = createObject({
|
||||||
|
type: 'foo',
|
||||||
|
id: '2',
|
||||||
|
});
|
||||||
|
|
||||||
|
const fooTransform: SavedObjectsExportTransform = jest.fn();
|
||||||
|
|
||||||
|
await collectExportedObjects({
|
||||||
|
objects: [obj1, obj2],
|
||||||
|
savedObjectsClient,
|
||||||
|
request,
|
||||||
|
exportTransforms: { foo: fooTransform },
|
||||||
|
includeReferences: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(applyExportTransformsMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(applyExportTransformsMock).toHaveBeenCalledWith({
|
||||||
|
objects: [obj1, obj2],
|
||||||
|
transforms: { foo: fooTransform },
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the collected objects', async () => {
|
||||||
|
const foo1 = createObject({
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
id: '2',
|
||||||
|
name: 'bar-2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const bar2 = createObject({
|
||||||
|
type: 'bar',
|
||||||
|
id: '2',
|
||||||
|
});
|
||||||
|
const dolly3 = createObject({
|
||||||
|
type: 'dolly',
|
||||||
|
id: '3',
|
||||||
|
});
|
||||||
|
|
||||||
|
applyExportTransformsMock.mockImplementationOnce(({ objects }) => [...objects, dolly3]);
|
||||||
|
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||||
|
saved_objects: [bar2],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { objects, missingRefs } = await collectExportedObjects({
|
||||||
|
objects: [foo1],
|
||||||
|
savedObjectsClient,
|
||||||
|
request,
|
||||||
|
exportTransforms: {},
|
||||||
|
includeReferences: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(missingRefs).toHaveLength(0);
|
||||||
|
expect(objects.map(toIdTuple)).toEqual([foo1, dolly3, bar2].map(toIdTuple));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the missing references', async () => {
|
||||||
|
const foo1 = createObject({
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
id: '2',
|
||||||
|
name: 'bar-2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'missing',
|
||||||
|
id: '1',
|
||||||
|
name: 'missing-1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const bar2 = createObject({
|
||||||
|
type: 'bar',
|
||||||
|
id: '2',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'missing',
|
||||||
|
id: '2',
|
||||||
|
name: 'missing-2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const missing1 = createObject({
|
||||||
|
type: 'missing',
|
||||||
|
id: '1',
|
||||||
|
error: createError(),
|
||||||
|
});
|
||||||
|
const missing2 = createObject({
|
||||||
|
type: 'missing',
|
||||||
|
id: '2',
|
||||||
|
error: createError(),
|
||||||
|
});
|
||||||
|
|
||||||
|
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||||
|
saved_objects: [bar2, missing1],
|
||||||
|
});
|
||||||
|
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||||
|
saved_objects: [missing2],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { objects, missingRefs } = await collectExportedObjects({
|
||||||
|
objects: [foo1],
|
||||||
|
savedObjectsClient,
|
||||||
|
request,
|
||||||
|
exportTransforms: {},
|
||||||
|
includeReferences: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(missingRefs).toEqual([missing1, missing2].map(toIdTuple));
|
||||||
|
expect(objects.map(toIdTuple)).toEqual([foo1, bar2].map(toIdTuple));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not call `client.bulkGet` when no objects have references', async () => {
|
||||||
|
const obj1 = createObject({
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
});
|
||||||
|
const obj2 = createObject({
|
||||||
|
type: 'foo',
|
||||||
|
id: '2',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { objects, missingRefs } = await collectExportedObjects({
|
||||||
|
objects: [obj1, obj2],
|
||||||
|
savedObjectsClient,
|
||||||
|
request,
|
||||||
|
exportTransforms: {},
|
||||||
|
includeReferences: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(missingRefs).toHaveLength(0);
|
||||||
|
expect(objects.map(toIdTuple)).toEqual([
|
||||||
|
{
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'foo',
|
||||||
|
id: '2',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(savedObjectsClient.bulkGet).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls `applyExportTransforms` for each iteration', async () => {
|
||||||
|
const foo1 = createObject({
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
id: '2',
|
||||||
|
name: 'bar-2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const bar2 = createObject({
|
||||||
|
type: 'bar',
|
||||||
|
id: '2',
|
||||||
|
});
|
||||||
|
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||||
|
saved_objects: [bar2],
|
||||||
|
});
|
||||||
|
|
||||||
|
await collectExportedObjects({
|
||||||
|
objects: [foo1],
|
||||||
|
savedObjectsClient,
|
||||||
|
request,
|
||||||
|
exportTransforms: {},
|
||||||
|
includeReferences: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(1);
|
||||||
|
expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith(
|
||||||
|
[toIdTuple(bar2)],
|
||||||
|
expect.any(Object)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(applyExportTransformsMock).toHaveBeenCalledTimes(2);
|
||||||
|
expect(applyExportTransformsMock).toHaveBeenCalledWith({
|
||||||
|
objects: [foo1],
|
||||||
|
transforms: {},
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
expect(applyExportTransformsMock).toHaveBeenCalledWith({
|
||||||
|
objects: [bar2],
|
||||||
|
transforms: {},
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores references that are already included in the export', async () => {
|
||||||
|
const foo1 = createObject({
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
id: '2',
|
||||||
|
name: 'bar-2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const bar2 = createObject({
|
||||||
|
type: 'bar',
|
||||||
|
id: '2',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
name: 'foo-1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'dolly',
|
||||||
|
id: '3',
|
||||||
|
name: 'dolly-3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const dolly3 = createObject({
|
||||||
|
type: 'dolly',
|
||||||
|
id: '3',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
name: 'foo-1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||||
|
saved_objects: [bar2],
|
||||||
|
});
|
||||||
|
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||||
|
saved_objects: [foo1, dolly3],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { objects } = await collectExportedObjects({
|
||||||
|
objects: [foo1],
|
||||||
|
savedObjectsClient,
|
||||||
|
request,
|
||||||
|
exportTransforms: {},
|
||||||
|
includeReferences: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(2);
|
||||||
|
expect(savedObjectsClient.bulkGet).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
[toIdTuple(bar2)],
|
||||||
|
expect.any(Object)
|
||||||
|
);
|
||||||
|
expect(savedObjectsClient.bulkGet).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
[toIdTuple(dolly3)],
|
||||||
|
expect.any(Object)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(objects.map(toIdTuple)).toEqual([foo1, bar2, dolly3].map(toIdTuple));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not fetch duplicates of references', async () => {
|
||||||
|
const foo1 = createObject({
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'dolly',
|
||||||
|
id: '3',
|
||||||
|
name: 'dolly-3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'baz',
|
||||||
|
id: '4',
|
||||||
|
name: 'baz-4',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const bar2 = createObject({
|
||||||
|
type: 'bar',
|
||||||
|
id: '2',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'dolly',
|
||||||
|
id: '3',
|
||||||
|
name: 'dolly-3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const dolly3 = createObject({
|
||||||
|
type: 'dolly',
|
||||||
|
id: '3',
|
||||||
|
});
|
||||||
|
const baz4 = createObject({
|
||||||
|
type: 'baz',
|
||||||
|
id: '4',
|
||||||
|
});
|
||||||
|
|
||||||
|
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||||
|
saved_objects: [dolly3, baz4],
|
||||||
|
});
|
||||||
|
|
||||||
|
await collectExportedObjects({
|
||||||
|
objects: [foo1, bar2],
|
||||||
|
savedObjectsClient,
|
||||||
|
request,
|
||||||
|
exportTransforms: {},
|
||||||
|
includeReferences: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(1);
|
||||||
|
expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith(
|
||||||
|
[dolly3, baz4].map(toIdTuple),
|
||||||
|
expect.any(Object)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetch references for additional objects returned by the export transform', async () => {
|
||||||
|
const foo1 = createObject({
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'baz',
|
||||||
|
id: '4',
|
||||||
|
name: 'baz-4',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const bar2 = createObject({
|
||||||
|
type: 'bar',
|
||||||
|
id: '2',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'dolly',
|
||||||
|
id: '3',
|
||||||
|
name: 'dolly-3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
applyExportTransformsMock.mockImplementationOnce(({ objects }) => [...objects, bar2]);
|
||||||
|
|
||||||
|
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||||
|
saved_objects: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await collectExportedObjects({
|
||||||
|
objects: [foo1],
|
||||||
|
savedObjectsClient,
|
||||||
|
request,
|
||||||
|
exportTransforms: {},
|
||||||
|
includeReferences: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(1);
|
||||||
|
expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith(
|
||||||
|
[
|
||||||
|
{ type: 'baz', id: '4' },
|
||||||
|
{ type: 'dolly', id: '3' },
|
||||||
|
],
|
||||||
|
expect.any(Object)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetch references for additional objects returned by the export transform of nested references', async () => {
|
||||||
|
const foo1 = createObject({
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
id: '2',
|
||||||
|
name: 'bar-2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const bar2 = createObject({
|
||||||
|
type: 'bar',
|
||||||
|
id: '2',
|
||||||
|
references: [],
|
||||||
|
});
|
||||||
|
const dolly3 = createObject({
|
||||||
|
type: 'dolly',
|
||||||
|
id: '3',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
type: 'baz',
|
||||||
|
id: '4',
|
||||||
|
name: 'baz-4',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const baz4 = createObject({
|
||||||
|
type: 'baz',
|
||||||
|
id: '4',
|
||||||
|
});
|
||||||
|
|
||||||
|
// first call for foo-1
|
||||||
|
applyExportTransformsMock.mockImplementationOnce(({ objects }) => [...objects]);
|
||||||
|
// second call for bar-2
|
||||||
|
applyExportTransformsMock.mockImplementationOnce(({ objects }) => [...objects, dolly3]);
|
||||||
|
|
||||||
|
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||||
|
saved_objects: [bar2],
|
||||||
|
});
|
||||||
|
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||||
|
saved_objects: [baz4],
|
||||||
|
});
|
||||||
|
|
||||||
|
await collectExportedObjects({
|
||||||
|
objects: [foo1],
|
||||||
|
savedObjectsClient,
|
||||||
|
request,
|
||||||
|
exportTransforms: {},
|
||||||
|
includeReferences: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(2);
|
||||||
|
expect(savedObjectsClient.bulkGet).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
[toIdTuple(bar2)],
|
||||||
|
expect.any(Object)
|
||||||
|
);
|
||||||
|
expect(savedObjectsClient.bulkGet).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
[toIdTuple(baz4)],
|
||||||
|
expect.any(Object)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when `includeReferences` is `false`', () => {
|
||||||
|
it('does not fetch the object references', async () => {
|
||||||
|
const obj1 = createObject({
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: 'bar',
|
||||||
|
name: 'bar-2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { objects, missingRefs } = await collectExportedObjects({
|
||||||
|
objects: [obj1],
|
||||||
|
savedObjectsClient,
|
||||||
|
request,
|
||||||
|
exportTransforms: {},
|
||||||
|
includeReferences: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(missingRefs).toHaveLength(0);
|
||||||
|
expect(objects.map(toIdTuple)).toEqual([
|
||||||
|
{
|
||||||
|
type: 'foo',
|
||||||
|
id: '1',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(savedObjectsClient.bulkGet).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
128
src/core/server/saved_objects/export/collect_exported_objects.ts
Normal file
128
src/core/server/saved_objects/export/collect_exported_objects.ts
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { SavedObject } from '../../../types';
|
||||||
|
import type { KibanaRequest } from '../../http';
|
||||||
|
import { SavedObjectsClientContract } from '../types';
|
||||||
|
import type { SavedObjectsExportTransform } from './types';
|
||||||
|
import { applyExportTransforms } from './apply_export_transforms';
|
||||||
|
|
||||||
|
interface CollectExportedObjectOptions {
|
||||||
|
savedObjectsClient: SavedObjectsClientContract;
|
||||||
|
objects: SavedObject[];
|
||||||
|
/** flag to also include all related saved objects in the export stream. */
|
||||||
|
includeReferences?: boolean;
|
||||||
|
/** optional namespace to override the namespace used by the savedObjectsClient. */
|
||||||
|
namespace?: string;
|
||||||
|
/** The http request initiating the export. */
|
||||||
|
request: KibanaRequest;
|
||||||
|
/** export transform per type */
|
||||||
|
exportTransforms: Record<string, SavedObjectsExportTransform>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CollectExportedObjectResult {
|
||||||
|
objects: SavedObject[];
|
||||||
|
missingRefs: CollectedReference[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const collectExportedObjects = async ({
|
||||||
|
objects,
|
||||||
|
includeReferences = true,
|
||||||
|
namespace,
|
||||||
|
request,
|
||||||
|
exportTransforms,
|
||||||
|
savedObjectsClient,
|
||||||
|
}: CollectExportedObjectOptions): Promise<CollectExportedObjectResult> => {
|
||||||
|
const collectedObjects: SavedObject[] = [];
|
||||||
|
const collectedMissingRefs: CollectedReference[] = [];
|
||||||
|
const alreadyProcessed: Set<string> = new Set();
|
||||||
|
|
||||||
|
let currentObjects = objects;
|
||||||
|
do {
|
||||||
|
const transformed = (
|
||||||
|
await applyExportTransforms({
|
||||||
|
request,
|
||||||
|
objects: currentObjects,
|
||||||
|
transforms: exportTransforms,
|
||||||
|
})
|
||||||
|
).filter((object) => !alreadyProcessed.has(objKey(object)));
|
||||||
|
|
||||||
|
transformed.forEach((obj) => alreadyProcessed.add(objKey(obj)));
|
||||||
|
collectedObjects.push(...transformed);
|
||||||
|
|
||||||
|
if (includeReferences) {
|
||||||
|
const references = collectReferences(transformed, alreadyProcessed);
|
||||||
|
if (references.length) {
|
||||||
|
const { objects: fetchedObjects, missingRefs } = await fetchReferences({
|
||||||
|
references,
|
||||||
|
namespace,
|
||||||
|
client: savedObjectsClient,
|
||||||
|
});
|
||||||
|
collectedMissingRefs.push(...missingRefs);
|
||||||
|
currentObjects = fetchedObjects;
|
||||||
|
} else {
|
||||||
|
currentObjects = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentObjects = [];
|
||||||
|
}
|
||||||
|
} while (includeReferences && currentObjects.length);
|
||||||
|
|
||||||
|
return {
|
||||||
|
objects: collectedObjects,
|
||||||
|
missingRefs: collectedMissingRefs,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const objKey = (obj: { type: string; id: string }) => `${obj.type}:${obj.id}`;
|
||||||
|
|
||||||
|
type ObjectKey = string;
|
||||||
|
|
||||||
|
interface CollectedReference {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectReferences = (
|
||||||
|
objects: SavedObject[],
|
||||||
|
alreadyProcessed: Set<ObjectKey>
|
||||||
|
): CollectedReference[] => {
|
||||||
|
const references: Map<string, CollectedReference> = new Map();
|
||||||
|
objects.forEach((obj) => {
|
||||||
|
obj.references?.forEach((ref) => {
|
||||||
|
const refKey = objKey(ref);
|
||||||
|
if (!alreadyProcessed.has(refKey)) {
|
||||||
|
references.set(refKey, { type: ref.type, id: ref.id });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return [...references.values()];
|
||||||
|
};
|
||||||
|
|
||||||
|
interface FetchReferencesResult {
|
||||||
|
objects: SavedObject[];
|
||||||
|
missingRefs: CollectedReference[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchReferences = async ({
|
||||||
|
references,
|
||||||
|
client,
|
||||||
|
namespace,
|
||||||
|
}: {
|
||||||
|
references: CollectedReference[];
|
||||||
|
client: SavedObjectsClientContract;
|
||||||
|
namespace?: string;
|
||||||
|
}): Promise<FetchReferencesResult> => {
|
||||||
|
const { saved_objects: savedObjects } = await client.bulkGet(references, { namespace });
|
||||||
|
return {
|
||||||
|
objects: savedObjects.filter((obj) => !obj.error),
|
||||||
|
missingRefs: savedObjects
|
||||||
|
.filter((obj) => obj.error)
|
||||||
|
.map((obj) => ({ type: obj.type, id: obj.id })),
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,606 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { SavedObject } from '../types';
|
|
||||||
import { savedObjectsClientMock } from '../../mocks';
|
|
||||||
import { getObjectReferencesToFetch, fetchNestedDependencies } from './fetch_nested_dependencies';
|
|
||||||
import { SavedObjectsErrorHelpers } from '..';
|
|
||||||
|
|
||||||
describe('getObjectReferencesToFetch()', () => {
|
|
||||||
test('works with no saved objects', () => {
|
|
||||||
const map = new Map<string, SavedObject>();
|
|
||||||
const result = getObjectReferencesToFetch(map);
|
|
||||||
expect(result).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('excludes already fetched objects', () => {
|
|
||||||
const map = new Map<string, SavedObject>();
|
|
||||||
map.set('index-pattern:1', {
|
|
||||||
id: '1',
|
|
||||||
type: 'index-pattern',
|
|
||||||
attributes: {},
|
|
||||||
references: [],
|
|
||||||
});
|
|
||||||
map.set('visualization:2', {
|
|
||||||
id: '2',
|
|
||||||
type: 'visualization',
|
|
||||||
attributes: {},
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
name: 'ref_0',
|
|
||||||
type: 'index-pattern',
|
|
||||||
id: '1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const result = getObjectReferencesToFetch(map);
|
|
||||||
expect(result).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns objects that are missing', () => {
|
|
||||||
const map = new Map<string, SavedObject>();
|
|
||||||
map.set('visualization:2', {
|
|
||||||
id: '2',
|
|
||||||
type: 'visualization',
|
|
||||||
attributes: {},
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
name: 'ref_0',
|
|
||||||
type: 'index-pattern',
|
|
||||||
id: '1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const result = getObjectReferencesToFetch(map);
|
|
||||||
expect(result).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"id": "1",
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('does not fail on circular dependencies', () => {
|
|
||||||
const map = new Map<string, SavedObject>();
|
|
||||||
map.set('index-pattern:1', {
|
|
||||||
id: '1',
|
|
||||||
type: 'index-pattern',
|
|
||||||
attributes: {},
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
name: 'ref_0',
|
|
||||||
type: 'visualization',
|
|
||||||
id: '2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
map.set('visualization:2', {
|
|
||||||
id: '2',
|
|
||||||
type: 'visualization',
|
|
||||||
attributes: {},
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
name: 'ref_0',
|
|
||||||
type: 'index-pattern',
|
|
||||||
id: '1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const result = getObjectReferencesToFetch(map);
|
|
||||||
expect(result).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('injectNestedDependencies', () => {
|
|
||||||
const savedObjectsClient = savedObjectsClientMock.create();
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
test(`doesn't fetch when no dependencies are missing`, async () => {
|
|
||||||
const savedObjects = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
type: 'index-pattern',
|
|
||||||
attributes: {},
|
|
||||||
references: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const result = await fetchNestedDependencies(savedObjects, savedObjectsClient);
|
|
||||||
expect(result).toMatchInlineSnapshot(`
|
|
||||||
Object {
|
|
||||||
"missingRefs": Array [],
|
|
||||||
"objects": Array [
|
|
||||||
Object {
|
|
||||||
"attributes": Object {},
|
|
||||||
"id": "1",
|
|
||||||
"references": Array [],
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test(`doesn't fetch references that are already fetched`, async () => {
|
|
||||||
const savedObjects = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
type: 'index-pattern',
|
|
||||||
attributes: {},
|
|
||||||
references: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
type: 'search',
|
|
||||||
attributes: {},
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
name: 'ref_0',
|
|
||||||
type: 'index-pattern',
|
|
||||||
id: '1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const result = await fetchNestedDependencies(savedObjects, savedObjectsClient);
|
|
||||||
expect(result).toMatchInlineSnapshot(`
|
|
||||||
Object {
|
|
||||||
"missingRefs": Array [],
|
|
||||||
"objects": 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",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetches dependencies at least one level deep', async () => {
|
|
||||||
const savedObjects = [
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
type: 'search',
|
|
||||||
attributes: {},
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
name: 'ref_0',
|
|
||||||
type: 'index-pattern',
|
|
||||||
id: '1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
|
||||||
saved_objects: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
type: 'index-pattern',
|
|
||||||
attributes: {},
|
|
||||||
references: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const result = await fetchNestedDependencies(savedObjects, savedObjectsClient);
|
|
||||||
expect(result).toMatchInlineSnapshot(`
|
|
||||||
Object {
|
|
||||||
"missingRefs": Array [],
|
|
||||||
"objects": Array [
|
|
||||||
Object {
|
|
||||||
"attributes": Object {},
|
|
||||||
"id": "2",
|
|
||||||
"references": Array [
|
|
||||||
Object {
|
|
||||||
"id": "1",
|
|
||||||
"name": "ref_0",
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": "search",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"attributes": Object {},
|
|
||||||
"id": "1",
|
|
||||||
"references": Array [],
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
expect(savedObjectsClient.bulkGet).toMatchInlineSnapshot(`
|
|
||||||
[MockFunction] {
|
|
||||||
"calls": Array [
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"id": "1",
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
Object {
|
|
||||||
"namespace": undefined,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"results": Array [
|
|
||||||
Object {
|
|
||||||
"type": "return",
|
|
||||||
"value": Promise {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetches dependencies multiple levels deep', async () => {
|
|
||||||
const savedObjects = [
|
|
||||||
{
|
|
||||||
id: '5',
|
|
||||||
type: 'dashboard',
|
|
||||||
attributes: {},
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
name: 'panel_0',
|
|
||||||
type: 'visualization',
|
|
||||||
id: '4',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'panel_1',
|
|
||||||
type: 'visualization',
|
|
||||||
id: '3',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
|
||||||
saved_objects: [
|
|
||||||
{
|
|
||||||
id: '4',
|
|
||||||
type: 'visualization',
|
|
||||||
attributes: {},
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
name: 'ref_0',
|
|
||||||
type: 'search',
|
|
||||||
id: '2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
type: 'visualization',
|
|
||||||
attributes: {},
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
name: 'ref_0',
|
|
||||||
type: 'index-pattern',
|
|
||||||
id: '1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
|
||||||
saved_objects: [
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
type: 'search',
|
|
||||||
attributes: {},
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
name: 'ref_0',
|
|
||||||
type: 'index-pattern',
|
|
||||||
id: '1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
type: 'index-pattern',
|
|
||||||
attributes: {},
|
|
||||||
references: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const result = await fetchNestedDependencies(savedObjects, savedObjectsClient);
|
|
||||||
expect(result).toMatchInlineSnapshot(`
|
|
||||||
Object {
|
|
||||||
"missingRefs": Array [],
|
|
||||||
"objects": Array [
|
|
||||||
Object {
|
|
||||||
"attributes": Object {},
|
|
||||||
"id": "5",
|
|
||||||
"references": Array [
|
|
||||||
Object {
|
|
||||||
"id": "4",
|
|
||||||
"name": "panel_0",
|
|
||||||
"type": "visualization",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "3",
|
|
||||||
"name": "panel_1",
|
|
||||||
"type": "visualization",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": "dashboard",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"attributes": Object {},
|
|
||||||
"id": "4",
|
|
||||||
"references": Array [
|
|
||||||
Object {
|
|
||||||
"id": "2",
|
|
||||||
"name": "ref_0",
|
|
||||||
"type": "search",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": "visualization",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"attributes": Object {},
|
|
||||||
"id": "3",
|
|
||||||
"references": Array [
|
|
||||||
Object {
|
|
||||||
"id": "1",
|
|
||||||
"name": "ref_0",
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": "visualization",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"attributes": Object {},
|
|
||||||
"id": "2",
|
|
||||||
"references": Array [
|
|
||||||
Object {
|
|
||||||
"id": "1",
|
|
||||||
"name": "ref_0",
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": "search",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"attributes": Object {},
|
|
||||||
"id": "1",
|
|
||||||
"references": Array [],
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
expect(savedObjectsClient.bulkGet).toMatchInlineSnapshot(`
|
|
||||||
[MockFunction] {
|
|
||||||
"calls": Array [
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"id": "4",
|
|
||||||
"type": "visualization",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "3",
|
|
||||||
"type": "visualization",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
Object {
|
|
||||||
"namespace": undefined,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"id": "2",
|
|
||||||
"type": "search",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "1",
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
Object {
|
|
||||||
"namespace": undefined,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"results": Array [
|
|
||||||
Object {
|
|
||||||
"type": "return",
|
|
||||||
"value": Promise {},
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"type": "return",
|
|
||||||
"value": Promise {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns list of missing references', async () => {
|
|
||||||
const savedObjects = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
type: 'search',
|
|
||||||
attributes: {},
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
name: 'ref_0',
|
|
||||||
type: 'index-pattern',
|
|
||||||
id: '1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ref_1',
|
|
||||||
type: 'index-pattern',
|
|
||||||
id: '2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
|
||||||
saved_objects: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
type: 'index-pattern',
|
|
||||||
error: SavedObjectsErrorHelpers.createGenericNotFoundError('index-pattern', '1').output
|
|
||||||
.payload,
|
|
||||||
attributes: {},
|
|
||||||
references: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
type: 'index-pattern',
|
|
||||||
attributes: {},
|
|
||||||
references: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const result = await fetchNestedDependencies(savedObjects, savedObjectsClient);
|
|
||||||
expect(result).toMatchInlineSnapshot(`
|
|
||||||
Object {
|
|
||||||
"missingRefs": Array [
|
|
||||||
Object {
|
|
||||||
"id": "1",
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"objects": Array [
|
|
||||||
Object {
|
|
||||||
"attributes": Object {},
|
|
||||||
"id": "1",
|
|
||||||
"references": Array [
|
|
||||||
Object {
|
|
||||||
"id": "1",
|
|
||||||
"name": "ref_0",
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "2",
|
|
||||||
"name": "ref_1",
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": "search",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"attributes": Object {},
|
|
||||||
"id": "2",
|
|
||||||
"references": Array [],
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('does not fail on circular dependencies', async () => {
|
|
||||||
const savedObjects = [
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
type: 'search',
|
|
||||||
attributes: {},
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
name: 'ref_0',
|
|
||||||
type: 'index-pattern',
|
|
||||||
id: '1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
|
||||||
saved_objects: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
type: 'index-pattern',
|
|
||||||
attributes: {},
|
|
||||||
references: [
|
|
||||||
{
|
|
||||||
name: 'ref_0',
|
|
||||||
type: 'search',
|
|
||||||
id: '2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const result = await fetchNestedDependencies(savedObjects, savedObjectsClient);
|
|
||||||
expect(result).toMatchInlineSnapshot(`
|
|
||||||
Object {
|
|
||||||
"missingRefs": Array [],
|
|
||||||
"objects": Array [
|
|
||||||
Object {
|
|
||||||
"attributes": Object {},
|
|
||||||
"id": "2",
|
|
||||||
"references": Array [
|
|
||||||
Object {
|
|
||||||
"id": "1",
|
|
||||||
"name": "ref_0",
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": "search",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"attributes": Object {},
|
|
||||||
"id": "1",
|
|
||||||
"references": Array [
|
|
||||||
Object {
|
|
||||||
"id": "2",
|
|
||||||
"name": "ref_0",
|
|
||||||
"type": "search",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
expect(savedObjectsClient.bulkGet).toMatchInlineSnapshot(`
|
|
||||||
[MockFunction] {
|
|
||||||
"calls": Array [
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"id": "1",
|
|
||||||
"type": "index-pattern",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
Object {
|
|
||||||
"namespace": undefined,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"results": Array [
|
|
||||||
Object {
|
|
||||||
"type": "return",
|
|
||||||
"value": Promise {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,50 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { SavedObject, SavedObjectsClientContract } from '../types';
|
|
||||||
|
|
||||||
export function getObjectReferencesToFetch(savedObjectsMap: Map<string, SavedObject>) {
|
|
||||||
const objectsToFetch = new Map<string, { type: string; id: string }>();
|
|
||||||
for (const savedObject of savedObjectsMap.values()) {
|
|
||||||
for (const ref of savedObject.references || []) {
|
|
||||||
if (!savedObjectsMap.has(objKey(ref))) {
|
|
||||||
objectsToFetch.set(objKey(ref), { type: ref.type, id: ref.id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [...objectsToFetch.values()];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchNestedDependencies(
|
|
||||||
savedObjects: SavedObject[],
|
|
||||||
savedObjectsClient: SavedObjectsClientContract,
|
|
||||||
namespace?: string
|
|
||||||
) {
|
|
||||||
const savedObjectsMap = new Map<string, SavedObject>();
|
|
||||||
for (const savedObject of savedObjects) {
|
|
||||||
savedObjectsMap.set(objKey(savedObject), savedObject);
|
|
||||||
}
|
|
||||||
let objectsToFetch = getObjectReferencesToFetch(savedObjectsMap);
|
|
||||||
while (objectsToFetch.length > 0) {
|
|
||||||
const bulkGetResponse = await savedObjectsClient.bulkGet(objectsToFetch, { namespace });
|
|
||||||
// Push to array result
|
|
||||||
for (const savedObject of bulkGetResponse.saved_objects) {
|
|
||||||
savedObjectsMap.set(objKey(savedObject), savedObject);
|
|
||||||
}
|
|
||||||
objectsToFetch = getObjectReferencesToFetch(savedObjectsMap);
|
|
||||||
}
|
|
||||||
const allObjects = [...savedObjectsMap.values()];
|
|
||||||
return {
|
|
||||||
objects: allObjects.filter((obj) => !obj.error),
|
|
||||||
missingRefs: allObjects
|
|
||||||
.filter((obj) => !!obj.error)
|
|
||||||
.map((obj) => ({ type: obj.type, id: obj.id })),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const objKey = (obj: { type: string; id: string }) => `${obj.type}:${obj.id}`;
|
|
|
@ -12,7 +12,6 @@ import { Logger } from '../../logging';
|
||||||
import { SavedObject, SavedObjectsClientContract } from '../types';
|
import { SavedObject, SavedObjectsClientContract } from '../types';
|
||||||
import { SavedObjectsFindResult } from '../service';
|
import { SavedObjectsFindResult } from '../service';
|
||||||
import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry';
|
import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry';
|
||||||
import { fetchNestedDependencies } from './fetch_nested_dependencies';
|
|
||||||
import { sortObjects } from './sort_objects';
|
import { sortObjects } from './sort_objects';
|
||||||
import {
|
import {
|
||||||
SavedObjectsExportResultDetails,
|
SavedObjectsExportResultDetails,
|
||||||
|
@ -22,7 +21,7 @@ import {
|
||||||
SavedObjectsExportTransform,
|
SavedObjectsExportTransform,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { SavedObjectsExportError } from './errors';
|
import { SavedObjectsExportError } from './errors';
|
||||||
import { applyExportTransforms } from './apply_export_transforms';
|
import { collectExportedObjects } from './collect_exported_objects';
|
||||||
import { byIdAscComparator, getPreservedOrderComparator, SavedObjectComparator } from './utils';
|
import { byIdAscComparator, getPreservedOrderComparator, SavedObjectComparator } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,28 +117,21 @@ export class SavedObjectsExporter {
|
||||||
}: SavedObjectExportBaseOptions
|
}: SavedObjectExportBaseOptions
|
||||||
) {
|
) {
|
||||||
this.#log.debug(`Processing [${savedObjects.length}] saved objects.`);
|
this.#log.debug(`Processing [${savedObjects.length}] saved objects.`);
|
||||||
let exportedObjects: Array<SavedObject<unknown>>;
|
|
||||||
let missingReferences: SavedObjectsExportResultDetails['missingReferences'] = [];
|
|
||||||
|
|
||||||
savedObjects = await applyExportTransforms({
|
const {
|
||||||
request,
|
objects: collectedObjects,
|
||||||
|
missingRefs: missingReferences,
|
||||||
|
} = await collectExportedObjects({
|
||||||
objects: savedObjects,
|
objects: savedObjects,
|
||||||
transforms: this.#exportTransforms,
|
includeReferences: includeReferencesDeep,
|
||||||
sortFunction,
|
namespace,
|
||||||
|
request,
|
||||||
|
exportTransforms: this.#exportTransforms,
|
||||||
|
savedObjectsClient: this.#savedObjectsClient,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (includeReferencesDeep) {
|
// sort with the provided sort function then with the default export sorting
|
||||||
this.#log.debug(`Fetching saved objects references.`);
|
const exportedObjects = sortObjects(collectedObjects.sort(sortFunction));
|
||||||
const fetchResult = await fetchNestedDependencies(
|
|
||||||
savedObjects,
|
|
||||||
this.#savedObjectsClient,
|
|
||||||
namespace
|
|
||||||
);
|
|
||||||
exportedObjects = sortObjects(fetchResult.objects);
|
|
||||||
missingReferences = fetchResult.missingRefs;
|
|
||||||
} else {
|
|
||||||
exportedObjects = sortObjects(savedObjects);
|
|
||||||
}
|
|
||||||
|
|
||||||
// redact attributes that should not be exported
|
// redact attributes that should not be exported
|
||||||
const redactedObjects = includeNamespaces
|
const redactedObjects = includeNamespaces
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
{
|
||||||
|
"type": "doc",
|
||||||
|
"value": {
|
||||||
|
"index": ".kibana",
|
||||||
|
"type": "doc",
|
||||||
|
"id": "test-export-transform:type_1-obj_1",
|
||||||
|
"source": {
|
||||||
|
"test-export-transform": {
|
||||||
|
"title": "test_1-obj_1",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"type": "test-export-transform",
|
||||||
|
"migrationVersion": {},
|
||||||
|
"updated_at": "2018-12-21T00:43:07.096Z",
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"type": "test-export-transform",
|
||||||
|
"id": "type_1-obj_2",
|
||||||
|
"name": "ref-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test-export-add",
|
||||||
|
"id": "type_2-obj_1",
|
||||||
|
"name": "ref-2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "doc",
|
||||||
|
"value": {
|
||||||
|
"index": ".kibana",
|
||||||
|
"type": "doc",
|
||||||
|
"id": "test-export-transform:type_1-obj_2",
|
||||||
|
"source": {
|
||||||
|
"test-export-transform": {
|
||||||
|
"title": "test_1-obj_2",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"type": "test-export-transform",
|
||||||
|
"migrationVersion": {},
|
||||||
|
"updated_at": "2018-12-21T00:43:07.096Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "doc",
|
||||||
|
"value": {
|
||||||
|
"index": ".kibana",
|
||||||
|
"type": "doc",
|
||||||
|
"id": "test-export-add:type_2-obj_1",
|
||||||
|
"source": {
|
||||||
|
"test-export-add": {
|
||||||
|
"title": "test_2-obj_1"
|
||||||
|
},
|
||||||
|
"type": "test-export-add",
|
||||||
|
"migrationVersion": {},
|
||||||
|
"updated_at": "2018-12-21T00:43:07.096Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "doc",
|
||||||
|
"value": {
|
||||||
|
"index": ".kibana",
|
||||||
|
"type": "doc",
|
||||||
|
"id": "test-export-add-dep:type_dep-obj_1",
|
||||||
|
"source": {
|
||||||
|
"test-export-add-dep": {
|
||||||
|
"title": "type_dep-obj_1"
|
||||||
|
},
|
||||||
|
"type": "test-export-add-dep",
|
||||||
|
"migrationVersion": {},
|
||||||
|
"updated_at": "2018-12-21T00:43:07.096Z",
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"type": "test-export-add",
|
||||||
|
"id": "type_2-obj_1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,499 @@
|
||||||
|
{
|
||||||
|
"type": "index",
|
||||||
|
"value": {
|
||||||
|
"index": ".kibana",
|
||||||
|
"settings": {
|
||||||
|
"index": {
|
||||||
|
"number_of_shards": "1",
|
||||||
|
"auto_expand_replicas": "0-1",
|
||||||
|
"number_of_replicas": "0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": {
|
||||||
|
"dynamic": "strict",
|
||||||
|
"properties": {
|
||||||
|
"test-export-transform": {
|
||||||
|
"properties": {
|
||||||
|
"title": { "type": "text" },
|
||||||
|
"enabled": { "type": "boolean" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test-export-add": {
|
||||||
|
"properties": {
|
||||||
|
"title": { "type": "text" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test-export-add-dep": {
|
||||||
|
"properties": {
|
||||||
|
"title": { "type": "text" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test-export-transform-error": {
|
||||||
|
"properties": {
|
||||||
|
"title": { "type": "text" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test-export-invalid-transform": {
|
||||||
|
"properties": {
|
||||||
|
"title": { "type": "text" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apm-telemetry": {
|
||||||
|
"properties": {
|
||||||
|
"has_any_services": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"services_per_agent": {
|
||||||
|
"properties": {
|
||||||
|
"go": {
|
||||||
|
"type": "long",
|
||||||
|
"null_value": 0
|
||||||
|
},
|
||||||
|
"java": {
|
||||||
|
"type": "long",
|
||||||
|
"null_value": 0
|
||||||
|
},
|
||||||
|
"js-base": {
|
||||||
|
"type": "long",
|
||||||
|
"null_value": 0
|
||||||
|
},
|
||||||
|
"nodejs": {
|
||||||
|
"type": "long",
|
||||||
|
"null_value": 0
|
||||||
|
},
|
||||||
|
"python": {
|
||||||
|
"type": "long",
|
||||||
|
"null_value": 0
|
||||||
|
},
|
||||||
|
"ruby": {
|
||||||
|
"type": "long",
|
||||||
|
"null_value": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"canvas-workpad": {
|
||||||
|
"dynamic": "false",
|
||||||
|
"properties": {
|
||||||
|
"@created": {
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
"@timestamp": {
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "text",
|
||||||
|
"index": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "text",
|
||||||
|
"fields": {
|
||||||
|
"keyword": {
|
||||||
|
"type": "keyword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"dynamic": "true",
|
||||||
|
"properties": {
|
||||||
|
"accessibility:disableAnimations": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"buildNum": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"dateFormat:tz": {
|
||||||
|
"type": "text",
|
||||||
|
"fields": {
|
||||||
|
"keyword": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 256
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultIndex": {
|
||||||
|
"type": "text",
|
||||||
|
"fields": {
|
||||||
|
"keyword": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 256
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"telemetry:optIn": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map": {
|
||||||
|
"properties": {
|
||||||
|
"bounds": {
|
||||||
|
"dynamic": false,
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"layerListJSON": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"mapStateJSON": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"uiStateJSON": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"graph-workspace": {
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"kibanaSavedObjectMeta": {
|
||||||
|
"properties": {
|
||||||
|
"searchSourceJSON": {
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"numLinks": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"numVertices": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"wsState": {
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"index-pattern": {
|
||||||
|
"properties": {
|
||||||
|
"fieldFormatMap": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"intervalName": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"notExpandable": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"sourceFilters": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"timeFieldName": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"typeMeta": {
|
||||||
|
"type": "keyword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"kql-telemetry": {
|
||||||
|
"properties": {
|
||||||
|
"optInCount": {
|
||||||
|
"type": "long"
|
||||||
|
},
|
||||||
|
"optOutCount": {
|
||||||
|
"type": "long"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"migrationVersion": {
|
||||||
|
"dynamic": "true",
|
||||||
|
"properties": {
|
||||||
|
"index-pattern": {
|
||||||
|
"type": "text",
|
||||||
|
"fields": {
|
||||||
|
"keyword": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 256
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"space": {
|
||||||
|
"type": "text",
|
||||||
|
"fields": {
|
||||||
|
"keyword": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 256
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"namespace": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"space": {
|
||||||
|
"properties": {
|
||||||
|
"_reserved": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"disabledFeatures": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"initials": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "text",
|
||||||
|
"fields": {
|
||||||
|
"keyword": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 2048
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spaceId": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"telemetry": {
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"references": {
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "keyword"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "nested"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
||||||
const esArchiver = getService('esArchiver');
|
const esArchiver = getService('esArchiver');
|
||||||
|
|
||||||
describe('export transforms', () => {
|
describe('export transforms', () => {
|
||||||
|
describe('root objects export transforms', () => {
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await esArchiver.load(
|
await esArchiver.load(
|
||||||
'../functional/fixtures/es_archiver/saved_objects_management/export_transform'
|
'../functional/fixtures/es_archiver/saved_objects_management/export_transform'
|
||||||
|
@ -137,4 +138,50 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('FOO nested export transforms', () => {
|
||||||
|
before(async () => {
|
||||||
|
await esArchiver.load(
|
||||||
|
'../functional/fixtures/es_archiver/saved_objects_management/nested_export_transform'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await esArchiver.unload(
|
||||||
|
'../functional/fixtures/es_archiver/saved_objects_management/nested_export_transform'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('execute export transforms for reference objects', async () => {
|
||||||
|
await supertest
|
||||||
|
.post('/api/saved_objects/_export')
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send({
|
||||||
|
objects: [
|
||||||
|
{
|
||||||
|
type: 'test-export-transform',
|
||||||
|
id: 'type_1-obj_1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
includeReferencesDeep: true,
|
||||||
|
excludeExportDetails: true,
|
||||||
|
})
|
||||||
|
.expect(200)
|
||||||
|
.then((resp) => {
|
||||||
|
const objects = parseNdJson(resp.text).sort((obj1, obj2) =>
|
||||||
|
obj1.id.localeCompare(obj2.id)
|
||||||
|
);
|
||||||
|
expect(objects.map((obj) => obj.id)).to.eql([
|
||||||
|
'type_1-obj_1',
|
||||||
|
'type_1-obj_2',
|
||||||
|
'type_2-obj_1',
|
||||||
|
'type_dep-obj_1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(objects[0].attributes.enabled).to.eql(false);
|
||||||
|
expect(objects[1].attributes.enabled).to.eql(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue