mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
add SavedObject export hooks (#87807)
* initial POC * fix spaces UT * address POC feedback, add tests for applyExportTransforms * add sorting for transforms * add type validation in SOTR * add FTR tests * update documentation * add explicit so type export for client-side * update generated doc * add exporter test * update license headers * update generated doc * fix so import... imports * update generated doc * nits * update generated doc * rename test plugins * adding FTR tests on export failures
This commit is contained in:
parent
b3a9754394
commit
477d0bbe21
53 changed files with 2089 additions and 42 deletions
|
@ -164,6 +164,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [SavedObjectsExportByObjectOptions](./kibana-plugin-core-server.savedobjectsexportbyobjectoptions.md) | Options for the [export by objects API](./kibana-plugin-core-server.savedobjectsexporter.exportbyobjects.md) |
|
||||
| [SavedObjectsExportByTypeOptions](./kibana-plugin-core-server.savedobjectsexportbytypeoptions.md) | Options for the [export by type API](./kibana-plugin-core-server.savedobjectsexporter.exportbytypes.md) |
|
||||
| [SavedObjectsExportResultDetails](./kibana-plugin-core-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry |
|
||||
| [SavedObjectsExportTransformContext](./kibana-plugin-core-server.savedobjectsexporttransformcontext.md) | Context passed down to a [export transform function](./kibana-plugin-core-server.savedobjectsexporttransform.md) |
|
||||
| [SavedObjectsFindOptions](./kibana-plugin-core-server.savedobjectsfindoptions.md) | |
|
||||
| [SavedObjectsFindOptionsReference](./kibana-plugin-core-server.savedobjectsfindoptionsreference.md) | |
|
||||
| [SavedObjectsFindResponse](./kibana-plugin-core-server.savedobjectsfindresponse.md) | Return type of the Saved Objects <code>find()</code> method.<!-- -->\*Note\*: this type is different between the Public and Server Saved Objects clients. |
|
||||
|
@ -299,6 +300,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. |
|
||||
| [SavedObjectsClientFactoryProvider](./kibana-plugin-core-server.savedobjectsclientfactoryprovider.md) | Provider to invoke to retrieve a [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md)<!-- -->. |
|
||||
| [SavedObjectsClientWrapperFactory](./kibana-plugin-core-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. |
|
||||
| [SavedObjectsExportTransform](./kibana-plugin-core-server.savedobjectsexporttransform.md) | Transformation function used to mutate the exported objects of the associated type.<!-- -->A type's export transform function will be executed once per user-initiated export, for all objects of that type. |
|
||||
| [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) | Describe a [saved object type mapping](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) field.<!-- -->Please refer to [elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html) For the mapping documentation |
|
||||
| [SavedObjectsImportHook](./kibana-plugin-core-server.savedobjectsimporthook.md) | A hook associated with a specific saved object type, that will be invoked during the import process. The hook will have access to the objects of the registered type.<!-- -->Currently, the only supported feature for import hooks is to return warnings to be displayed in the UI when the import succeeds. The only interactions the hook can have with the import process is via the hook's response. Mutating the objects inside the hook's code will have no effect. |
|
||||
| [SavedObjectsImportWarning](./kibana-plugin-core-server.savedobjectsimportwarning.md) | Composite type of all the possible types of import warnings.<!-- -->See [SavedObjectsImportSimpleWarning](./kibana-plugin-core-server.savedobjectsimportsimplewarning.md) and [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md) for more details. |
|
||||
|
|
|
@ -18,4 +18,5 @@ export interface SavedObjectExportBaseOptions
|
|||
| [excludeExportDetails](./kibana-plugin-core-server.savedobjectexportbaseoptions.excludeexportdetails.md) | <code>boolean</code> | flag to not append [export details](./kibana-plugin-core-server.savedobjectsexportresultdetails.md) to the end of the export stream. |
|
||||
| [includeReferencesDeep](./kibana-plugin-core-server.savedobjectexportbaseoptions.includereferencesdeep.md) | <code>boolean</code> | flag to also include all related saved objects in the export stream. |
|
||||
| [namespace](./kibana-plugin-core-server.savedobjectexportbaseoptions.namespace.md) | <code>string</code> | optional namespace to override the namespace used by the savedObjectsClient. |
|
||||
| [request](./kibana-plugin-core-server.savedobjectexportbaseoptions.request.md) | <code>KibanaRequest</code> | The http request initiating the export. |
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectExportBaseOptions](./kibana-plugin-core-server.savedobjectexportbaseoptions.md) > [request](./kibana-plugin-core-server.savedobjectexportbaseoptions.request.md)
|
||||
|
||||
## SavedObjectExportBaseOptions.request property
|
||||
|
||||
The http request initiating the export.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
request: KibanaRequest;
|
||||
```
|
|
@ -9,8 +9,9 @@ Constructs a new instance of the `SavedObjectsExporter` class
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
constructor({ savedObjectsClient, exportSizeLimit, }: {
|
||||
constructor({ savedObjectsClient, typeRegistry, exportSizeLimit, }: {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
typeRegistry: ISavedObjectTypeRegistry;
|
||||
exportSizeLimit: number;
|
||||
});
|
||||
```
|
||||
|
@ -19,5 +20,5 @@ constructor({ savedObjectsClient, exportSizeLimit, }: {
|
|||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| { savedObjectsClient, exportSizeLimit, } | <code>{</code><br/><code> savedObjectsClient: SavedObjectsClientContract;</code><br/><code> exportSizeLimit: number;</code><br/><code> }</code> | |
|
||||
| { savedObjectsClient, typeRegistry, exportSizeLimit, } | <code>{</code><br/><code> savedObjectsClient: SavedObjectsClientContract;</code><br/><code> typeRegistry: ISavedObjectTypeRegistry;</code><br/><code> exportSizeLimit: number;</code><br/><code> }</code> | |
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ export declare class SavedObjectsExporter
|
|||
|
||||
| Constructor | Modifiers | Description |
|
||||
| --- | --- | --- |
|
||||
| [(constructor)({ savedObjectsClient, exportSizeLimit, })](./kibana-plugin-core-server.savedobjectsexporter._constructor_.md) | | Constructs a new instance of the <code>SavedObjectsExporter</code> class |
|
||||
| [(constructor)({ savedObjectsClient, typeRegistry, exportSizeLimit, })](./kibana-plugin-core-server.savedobjectsexporter._constructor_.md) | | Constructs a new instance of the <code>SavedObjectsExporter</code> class |
|
||||
|
||||
## Properties
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportError](./kibana-plugin-core-server.savedobjectsexporterror.md) > [invalidTransformError](./kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md)
|
||||
|
||||
## SavedObjectsExportError.invalidTransformError() method
|
||||
|
||||
Error returned when a [export tranform](./kibana-plugin-core-server.savedobjectsexporttransform.md) performed an invalid operation during the transform, such as removing objects from the export, or changing an object's type or id.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
static invalidTransformError(objectKeys: string[]): SavedObjectsExportError;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| objectKeys | <code>string[]</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`SavedObjectsExportError`
|
||||
|
|
@ -29,5 +29,7 @@ export declare class SavedObjectsExportError extends Error
|
|||
| Method | Modifiers | Description |
|
||||
| --- | --- | --- |
|
||||
| [exportSizeExceeded(limit)](./kibana-plugin-core-server.savedobjectsexporterror.exportsizeexceeded.md) | <code>static</code> | |
|
||||
| [invalidTransformError(objectKeys)](./kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md) | <code>static</code> | Error returned when a [export tranform](./kibana-plugin-core-server.savedobjectsexporttransform.md) performed an invalid operation during the transform, such as removing objects from the export, or changing an object's type or id. |
|
||||
| [objectFetchError(objects)](./kibana-plugin-core-server.savedobjectsexporterror.objectfetcherror.md) | <code>static</code> | |
|
||||
| [objectTransformError(objects, cause)](./kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md) | <code>static</code> | Error returned when a [export tranform](./kibana-plugin-core-server.savedobjectsexporttransform.md) threw an error |
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportError](./kibana-plugin-core-server.savedobjectsexporterror.md) > [objectTransformError](./kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md)
|
||||
|
||||
## SavedObjectsExportError.objectTransformError() method
|
||||
|
||||
Error returned when a [export tranform](./kibana-plugin-core-server.savedobjectsexporttransform.md) threw an error
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
static objectTransformError(objects: SavedObject[], cause: Error): SavedObjectsExportError;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| objects | <code>SavedObject[]</code> | |
|
||||
| cause | <code>Error</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`SavedObjectsExportError`
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportTransform](./kibana-plugin-core-server.savedobjectsexporttransform.md)
|
||||
|
||||
## SavedObjectsExportTransform type
|
||||
|
||||
Transformation function used to mutate the exported objects of the associated type.
|
||||
|
||||
A type's export transform function will be executed once per user-initiated export, for all objects of that type.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type SavedObjectsExportTransform = <T = unknown>(context: SavedObjectsExportTransformContext, objects: Array<SavedObject<T>>) => SavedObject[] | Promise<SavedObject[]>;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
Trying to change an object's id or type during the transform will result in a runtime error during the export process.
|
||||
|
||||
## Example 1
|
||||
|
||||
Registering a transform function changing the object's attributes during the export
|
||||
|
||||
```ts
|
||||
// src/plugins/my_plugin/server/plugin.ts
|
||||
import { myType } from './saved_objects';
|
||||
|
||||
export class Plugin() {
|
||||
setup: (core: CoreSetup) => {
|
||||
core.savedObjects.registerType({
|
||||
...myType,
|
||||
management: {
|
||||
...myType.management,
|
||||
onExport: (ctx, objects) => {
|
||||
return objects.map((obj) => ({
|
||||
...obj,
|
||||
attributes: {
|
||||
...obj.attributes,
|
||||
enabled: false,
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Example 2
|
||||
|
||||
Registering a transform function adding additional objects to the export
|
||||
|
||||
```ts
|
||||
// src/plugins/my_plugin/server/plugin.ts
|
||||
import { myType } from './saved_objects';
|
||||
|
||||
export class Plugin() {
|
||||
setup: (core: CoreSetup) => {
|
||||
const savedObjectStartContractPromise = getStartServices().then(
|
||||
([{ savedObjects: savedObjectsStart }]) => savedObjectsStart
|
||||
);
|
||||
|
||||
core.savedObjects.registerType({
|
||||
...myType,
|
||||
management: {
|
||||
...myType.management,
|
||||
onExport: async (ctx, objects) => {
|
||||
const { getScopedClient } = await savedObjectStartContractPromise;
|
||||
const client = getScopedClient(ctx.request);
|
||||
|
||||
const depResponse = await client.find({
|
||||
type: 'my-nested-object',
|
||||
hasReference: objs.map(({ id, type }) => ({ id, type })),
|
||||
});
|
||||
|
||||
return [...objs, ...depResponse.saved_objects];
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportTransformContext](./kibana-plugin-core-server.savedobjectsexporttransformcontext.md)
|
||||
|
||||
## SavedObjectsExportTransformContext interface
|
||||
|
||||
Context passed down to a [export transform function](./kibana-plugin-core-server.savedobjectsexporttransform.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface SavedObjectsExportTransformContext
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [request](./kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md) | <code>KibanaRequest</code> | The request that initiated the export request. Can be used to create scoped services or client inside the [transformation](./kibana-plugin-core-server.savedobjectsexporttransform.md) |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportTransformContext](./kibana-plugin-core-server.savedobjectsexporttransformcontext.md) > [request](./kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md)
|
||||
|
||||
## SavedObjectsExportTransformContext.request property
|
||||
|
||||
The request that initiated the export request. Can be used to create scoped services or client inside the [transformation](./kibana-plugin-core-server.savedobjectsexporttransform.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
request: KibanaRequest;
|
||||
```
|
|
@ -22,5 +22,6 @@ export interface SavedObjectsTypeManagementDefinition
|
|||
| [getTitle](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.gettitle.md) | <code>(savedObject: SavedObject<any>) => string</code> | Function returning the title to display in the management table. If not defined, will use the object's type and id to generate a label. |
|
||||
| [icon](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.icon.md) | <code>string</code> | The eui icon name to display in the management table. If not defined, the default icon will be used. |
|
||||
| [importableAndExportable](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.importableandexportable.md) | <code>boolean</code> | Is the type importable or exportable. Defaults to <code>false</code>. |
|
||||
| [onExport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md) | <code>SavedObjectsExportTransform</code> | An optional export transform function that can be used transform the objects of the registered type during the export process.<!-- -->It can be used to either mutate the exported objects, or add additional objects (of any type) to the export list.<!-- -->See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples. |
|
||||
| [onImport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md) | <code>SavedObjectsImportHook</code> | An optional [import hook](./kibana-plugin-core-server.savedobjectsimporthook.md) to use when importing given type.<!-- -->Import hooks are executed during the savedObjects import process and allow to interact with the imported objects. See the [hook documentation](./kibana-plugin-core-server.savedobjectsimporthook.md) for more info. |
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsTypeManagementDefinition](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.md) > [onExport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md)
|
||||
|
||||
## SavedObjectsTypeManagementDefinition.onExport property
|
||||
|
||||
An optional export transform function that can be used transform the objects of the registered type during the export process.
|
||||
|
||||
It can be used to either mutate the exported objects, or add additional objects (of any type) to the export list.
|
||||
|
||||
See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
onExport?: SavedObjectsExportTransform;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
`importableAndExportable` must be `true` to specify this property.
|
||||
|
|
@ -14,6 +14,10 @@ Import hooks are executed during the savedObjects import process and allow to in
|
|||
onImport?: SavedObjectsImportHook;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
`importableAndExportable` must be `true` to specify this property.
|
||||
|
||||
## Example
|
||||
|
||||
Registering a hook displaying a warning about a specific type of object
|
||||
|
@ -48,5 +52,4 @@ export class Plugin() {
|
|||
}
|
||||
|
||||
```
|
||||
messages returned in the warnings are user facing and must be translated.
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import { ApiResponse } from '@elastic/elasticsearch/lib/Transport';
|
|||
import Boom from '@hapi/boom';
|
||||
import { ConfigDeprecationProvider } from '@kbn/config';
|
||||
import { ConfigPath } from '@kbn/config';
|
||||
import { DetailedPeerCertificate } from 'tls';
|
||||
import { EnvironmentMode } from '@kbn/config';
|
||||
import { EuiBreadcrumb } from '@elastic/eui';
|
||||
import { EuiButtonEmptyProps } from '@elastic/eui';
|
||||
|
@ -18,20 +19,25 @@ import { EuiGlobalToastListToast } from '@elastic/eui';
|
|||
import { History } from 'history';
|
||||
import { Href } from 'history';
|
||||
import { IconType } from '@elastic/eui';
|
||||
import { IncomingHttpHeaders } from 'http';
|
||||
import { KibanaClient } from '@elastic/elasticsearch/api/kibana';
|
||||
import { Location } from 'history';
|
||||
import { LocationDescriptorObject } from 'history';
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { LogMeta } from '@kbn/logging';
|
||||
import { MaybePromise } from '@kbn/utility-types';
|
||||
import { ObjectType } from '@kbn/config-schema';
|
||||
import { Observable } from 'rxjs';
|
||||
import { PackageInfo } from '@kbn/config';
|
||||
import { Path } from 'history';
|
||||
import { PeerCertificate } from 'tls';
|
||||
import { PublicMethodsOf } from '@kbn/utility-types';
|
||||
import { PublicUiSettingsParams as PublicUiSettingsParams_2 } from 'src/core/server/types';
|
||||
import React from 'react';
|
||||
import { RecursiveReadonly } from '@kbn/utility-types';
|
||||
import { Request } from '@hapi/hapi';
|
||||
import * as Rx from 'rxjs';
|
||||
import { SchemaTypeError } from '@kbn/config-schema';
|
||||
import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport';
|
||||
|
@ -39,6 +45,7 @@ import { Type } from '@kbn/config-schema';
|
|||
import { TypeOf } from '@kbn/config-schema';
|
||||
import { UiCounterMetricType } from '@kbn/analytics';
|
||||
import { UnregisterCallback } from 'history';
|
||||
import { URL } from 'url';
|
||||
import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types';
|
||||
|
||||
// @internal (undocumented)
|
||||
|
|
|
@ -320,6 +320,8 @@ export {
|
|||
SavedObjectsExportByObjectOptions,
|
||||
SavedObjectsExportByTypeOptions,
|
||||
SavedObjectsExportError,
|
||||
SavedObjectsExportTransform,
|
||||
SavedObjectsExportTransformContext,
|
||||
SavedObjectsImporter,
|
||||
ISavedObjectsImporter,
|
||||
SavedObjectsImportError,
|
||||
|
|
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* and the Server Side Public License, v 1; you may not use this file except in
|
||||
* compliance with, at your election, the Elastic License or the Server Side
|
||||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedObject } from '../../../types';
|
||||
import { KibanaRequest } from '../../http';
|
||||
import { httpServerMock } from '../../http/http_server.mocks';
|
||||
import { applyExportTransforms } from './apply_export_transforms';
|
||||
import { SavedObjectsExportTransform } from './types';
|
||||
|
||||
const createObj = (
|
||||
type: string,
|
||||
id: string,
|
||||
attributes: Record<string, any> = {}
|
||||
): SavedObject => ({
|
||||
type,
|
||||
id,
|
||||
attributes,
|
||||
references: [],
|
||||
});
|
||||
|
||||
const createTransform = (
|
||||
implementation: SavedObjectsExportTransform = (ctx, objs) => objs
|
||||
): jest.MockedFunction<SavedObjectsExportTransform> => jest.fn(implementation);
|
||||
|
||||
const expectedContext = {
|
||||
request: expect.any(KibanaRequest),
|
||||
};
|
||||
|
||||
describe('applyExportTransforms', () => {
|
||||
let request: ReturnType<typeof httpServerMock.createKibanaRequest>;
|
||||
|
||||
beforeEach(() => {
|
||||
request = httpServerMock.createKibanaRequest();
|
||||
});
|
||||
|
||||
it('calls the transform functions with the correct parameters', async () => {
|
||||
const foo1 = createObj('foo', '1');
|
||||
const foo2 = createObj('foo', '2');
|
||||
const bar1 = createObj('bar', '1');
|
||||
|
||||
const fooTransform = createTransform();
|
||||
const barTransform = createTransform();
|
||||
|
||||
await applyExportTransforms({
|
||||
request,
|
||||
objects: [foo1, bar1, foo2],
|
||||
transforms: {
|
||||
foo: fooTransform,
|
||||
bar: barTransform,
|
||||
},
|
||||
});
|
||||
|
||||
expect(fooTransform).toHaveBeenCalledTimes(1);
|
||||
expect(fooTransform).toHaveBeenCalledWith(expectedContext, [foo1, foo2]);
|
||||
|
||||
expect(barTransform).toHaveBeenCalledTimes(1);
|
||||
expect(barTransform).toHaveBeenCalledWith(expectedContext, [bar1]);
|
||||
});
|
||||
|
||||
it('does not call the transform functions if no objects are present', async () => {
|
||||
const foo1 = createObj('foo', '1');
|
||||
|
||||
const fooTransform = createTransform();
|
||||
const barTransform = createTransform();
|
||||
|
||||
await applyExportTransforms({
|
||||
request,
|
||||
objects: [foo1],
|
||||
transforms: {
|
||||
foo: fooTransform,
|
||||
bar: barTransform,
|
||||
},
|
||||
});
|
||||
|
||||
expect(fooTransform).toHaveBeenCalledTimes(1);
|
||||
expect(fooTransform).toHaveBeenCalledWith(expectedContext, [foo1]);
|
||||
|
||||
expect(barTransform).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('allows to add objects to the export', async () => {
|
||||
const foo1 = createObj('foo', '1');
|
||||
const foo2 = createObj('foo', '2');
|
||||
const bar1 = createObj('bar', '1');
|
||||
const dolly1 = createObj('dolly', '1');
|
||||
const hello1 = createObj('hello', '1');
|
||||
|
||||
const fooTransform = createTransform((ctx, objs) => {
|
||||
return [...objs, dolly1];
|
||||
});
|
||||
const barTransform = createTransform((ctx, objs) => {
|
||||
return [...objs, hello1];
|
||||
});
|
||||
|
||||
const result = await applyExportTransforms({
|
||||
request,
|
||||
objects: [foo1, bar1, foo2],
|
||||
transforms: {
|
||||
foo: fooTransform,
|
||||
bar: barTransform,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toEqual([foo1, foo2, dolly1, bar1, hello1]);
|
||||
});
|
||||
|
||||
it('returns unmutated objects if no transform is defined for the type', async () => {
|
||||
const foo1 = createObj('foo', '1');
|
||||
const foo2 = createObj('foo', '2');
|
||||
const bar1 = createObj('bar', '1');
|
||||
const bar2 = createObj('bar', '2');
|
||||
const dolly1 = createObj('dolly', '1');
|
||||
|
||||
const fooTransform = createTransform((ctx, objs) => {
|
||||
return [...objs, dolly1];
|
||||
});
|
||||
|
||||
const result = await applyExportTransforms({
|
||||
request,
|
||||
objects: [foo1, foo2, bar1, bar2],
|
||||
transforms: {
|
||||
foo: fooTransform,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toEqual([foo1, foo2, dolly1, bar1, bar2]);
|
||||
});
|
||||
|
||||
it('allows to mutate objects', async () => {
|
||||
const foo1 = createObj('foo', '1', { enabled: true });
|
||||
const foo2 = createObj('foo', '2', { enabled: true });
|
||||
|
||||
const disableFoo = (obj: SavedObject<any>) => ({
|
||||
...obj,
|
||||
attributes: {
|
||||
...obj.attributes,
|
||||
enabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
const fooTransform = createTransform((ctx, objs) => {
|
||||
return objs.map(disableFoo);
|
||||
});
|
||||
|
||||
const result = await applyExportTransforms({
|
||||
request,
|
||||
objects: [foo1, foo2],
|
||||
transforms: {
|
||||
foo: fooTransform,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toEqual([foo1, foo2].map(disableFoo));
|
||||
});
|
||||
|
||||
it('supports async transforms', async () => {
|
||||
const foo1 = createObj('foo', '1');
|
||||
const bar1 = createObj('bar', '1');
|
||||
const dolly1 = createObj('dolly', '1');
|
||||
const hello1 = createObj('hello', '1');
|
||||
|
||||
const fooTransform = createTransform((ctx, objs) => {
|
||||
return Promise.resolve([...objs, dolly1]);
|
||||
});
|
||||
|
||||
const barTransform = createTransform((ctx, objs) => {
|
||||
return [...objs, hello1];
|
||||
});
|
||||
|
||||
const result = await applyExportTransforms({
|
||||
request,
|
||||
objects: [foo1, bar1],
|
||||
transforms: {
|
||||
foo: fooTransform,
|
||||
bar: barTransform,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toEqual([foo1, dolly1, bar1, hello1]);
|
||||
});
|
||||
|
||||
it('uses the provided sortFunction when provided', async () => {
|
||||
const foo1 = createObj('foo', 'A');
|
||||
const bar1 = createObj('bar', 'B');
|
||||
const dolly1 = createObj('dolly', 'C');
|
||||
const hello1 = createObj('hello', 'D');
|
||||
|
||||
const fooTransform = createTransform((ctx, objs) => {
|
||||
return [...objs, dolly1];
|
||||
});
|
||||
|
||||
const barTransform = createTransform((ctx, objs) => {
|
||||
return [...objs, hello1];
|
||||
});
|
||||
|
||||
const result = await applyExportTransforms({
|
||||
request,
|
||||
objects: [foo1, bar1],
|
||||
transforms: {
|
||||
foo: fooTransform,
|
||||
bar: barTransform,
|
||||
},
|
||||
sortFunction: (obj1, obj2) => (obj1.id > obj2.id ? 1 : -1),
|
||||
});
|
||||
|
||||
expect(result).toEqual([foo1, bar1, dolly1, hello1]);
|
||||
});
|
||||
|
||||
it('throws when removing objects', async () => {
|
||||
const foo1 = createObj('foo', '1', { enabled: true });
|
||||
const foo2 = createObj('foo', '2', { enabled: true });
|
||||
|
||||
const fooTransform = createTransform((ctx, objs) => {
|
||||
return [objs[0]];
|
||||
});
|
||||
|
||||
await expect(
|
||||
applyExportTransforms({
|
||||
request,
|
||||
objects: [foo1, foo2],
|
||||
transforms: {
|
||||
foo: fooTransform,
|
||||
},
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid transform performed on objects to export"`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when changing the object type', async () => {
|
||||
const foo1 = createObj('foo', '1', { enabled: true });
|
||||
const foo2 = createObj('foo', '2', { enabled: true });
|
||||
|
||||
const fooTransform = createTransform((ctx, objs) => {
|
||||
return objs.map((obj) => ({
|
||||
...obj,
|
||||
type: 'mutated',
|
||||
}));
|
||||
});
|
||||
|
||||
await expect(
|
||||
applyExportTransforms({
|
||||
request,
|
||||
objects: [foo1, foo2],
|
||||
transforms: {
|
||||
foo: fooTransform,
|
||||
},
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid transform performed on objects to export"`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when changing the object id', async () => {
|
||||
const foo1 = createObj('foo', '1', { enabled: true });
|
||||
const foo2 = createObj('foo', '2', { enabled: true });
|
||||
|
||||
const fooTransform = createTransform((ctx, objs) => {
|
||||
return objs.map((obj, idx) => ({
|
||||
...obj,
|
||||
id: `mutated-${idx}`,
|
||||
}));
|
||||
});
|
||||
|
||||
await expect(
|
||||
applyExportTransforms({
|
||||
request,
|
||||
objects: [foo1, foo2],
|
||||
transforms: {
|
||||
foo: fooTransform,
|
||||
},
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid transform performed on objects to export"`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if the transform function throws', async () => {
|
||||
const foo1 = createObj('foo', '1');
|
||||
|
||||
const fooTransform = createTransform(() => {
|
||||
throw new Error('oups.');
|
||||
});
|
||||
|
||||
await expect(
|
||||
applyExportTransforms({
|
||||
request,
|
||||
objects: [foo1],
|
||||
transforms: {
|
||||
foo: fooTransform,
|
||||
},
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`"Error transforming objects to export"`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* and the Server Side Public License, v 1; you may not use this file except in
|
||||
* compliance with, at your election, the Elastic License or the Server Side
|
||||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedObject } from '../../../types';
|
||||
import { KibanaRequest } from '../../http';
|
||||
import { SavedObjectsExportError } from './errors';
|
||||
import { SavedObjectsExportTransform, SavedObjectsExportTransformContext } from './types';
|
||||
import { getObjKey, SavedObjectComparator } from './utils';
|
||||
|
||||
interface ApplyExportTransformsOptions {
|
||||
objects: SavedObject[];
|
||||
request: KibanaRequest;
|
||||
transforms: Record<string, SavedObjectsExportTransform>;
|
||||
sortFunction?: SavedObjectComparator;
|
||||
}
|
||||
|
||||
export const applyExportTransforms = async ({
|
||||
objects,
|
||||
request,
|
||||
transforms,
|
||||
sortFunction,
|
||||
}: ApplyExportTransformsOptions): Promise<SavedObject[]> => {
|
||||
const context = createContext(request);
|
||||
const byType = splitByType(objects);
|
||||
|
||||
let finalObjects: SavedObject[] = [];
|
||||
for (const [type, typeObjs] of Object.entries(byType)) {
|
||||
const typeTransformFn = transforms[type];
|
||||
if (typeTransformFn) {
|
||||
finalObjects = [
|
||||
...finalObjects,
|
||||
...(await applyTransform(typeObjs, typeTransformFn, context)),
|
||||
];
|
||||
} else {
|
||||
finalObjects = [...finalObjects, ...typeObjs];
|
||||
}
|
||||
}
|
||||
|
||||
if (sortFunction) {
|
||||
finalObjects.sort(sortFunction);
|
||||
}
|
||||
|
||||
return finalObjects;
|
||||
};
|
||||
|
||||
const applyTransform = async (
|
||||
objs: SavedObject[],
|
||||
transformFn: SavedObjectsExportTransform,
|
||||
context: SavedObjectsExportTransformContext
|
||||
) => {
|
||||
const objKeys = objs.map(getObjKey);
|
||||
let transformedObjects: SavedObject[];
|
||||
try {
|
||||
transformedObjects = await transformFn(context, objs);
|
||||
} catch (e) {
|
||||
throw SavedObjectsExportError.objectTransformError(objs, e);
|
||||
}
|
||||
assertValidTransform(transformedObjects, objKeys);
|
||||
return transformedObjects;
|
||||
};
|
||||
|
||||
const createContext = (request: KibanaRequest): SavedObjectsExportTransformContext => {
|
||||
return {
|
||||
request,
|
||||
};
|
||||
};
|
||||
|
||||
const splitByType = (objects: SavedObject[]): Record<string, SavedObject[]> => {
|
||||
return objects.reduce((memo, obj) => {
|
||||
memo[obj.type] = [...(memo[obj.type] ?? []), obj];
|
||||
return memo;
|
||||
}, {} as Record<string, SavedObject[]>);
|
||||
};
|
||||
|
||||
const assertValidTransform = (transformedObjects: SavedObject[], initialKeys: string[]) => {
|
||||
const transformedKeys = transformedObjects.map(getObjKey);
|
||||
const missingKeys: string[] = [];
|
||||
initialKeys.forEach((initialKey) => {
|
||||
if (!transformedKeys.includes(initialKey)) {
|
||||
missingKeys.push(initialKey);
|
||||
}
|
||||
});
|
||||
if (missingKeys.length) {
|
||||
throw SavedObjectsExportError.invalidTransformError(missingKeys);
|
||||
}
|
||||
};
|
|
@ -36,4 +36,32 @@ export class SavedObjectsExportError extends Error {
|
|||
objects,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Error returned when a {@link SavedObjectsExportTransform | export tranform} threw an error
|
||||
*/
|
||||
static objectTransformError(objects: SavedObject[], cause: Error) {
|
||||
return new SavedObjectsExportError(
|
||||
'object-transform-error',
|
||||
'Error transforming objects to export',
|
||||
{
|
||||
objects,
|
||||
cause: cause.message,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error returned when a {@link SavedObjectsExportTransform | export tranform} performed an invalid operation
|
||||
* during the transform, such as removing objects from the export, or changing an object's type or id.
|
||||
*/
|
||||
static invalidTransformError(objectKeys: string[]) {
|
||||
return new SavedObjectsExportError(
|
||||
'invalid-transform-error',
|
||||
'Invalid transform performed on objects to export',
|
||||
{
|
||||
objectKeys,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ export {
|
|||
SavedObjectExportBaseOptions,
|
||||
SavedObjectsExportByTypeOptions,
|
||||
SavedObjectsExportResultDetails,
|
||||
SavedObjectsExportTransformContext,
|
||||
SavedObjectsExportTransform,
|
||||
} from './types';
|
||||
export { ISavedObjectsExporter, SavedObjectsExporter } from './saved_objects_exporter';
|
||||
export { SavedObjectsExportError } from './errors';
|
||||
|
|
|
@ -6,24 +6,30 @@
|
|||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { SavedObject } from '../../../types';
|
||||
import { SavedObjectsExporter } from './saved_objects_exporter';
|
||||
import { savedObjectsClientMock } from '../service/saved_objects_client.mock';
|
||||
import { SavedObjectTypeRegistry } from '../saved_objects_type_registry';
|
||||
import { httpServerMock } from '../../http/http_server.mocks';
|
||||
import { Readable } from 'stream';
|
||||
import { createPromiseFromStreams, createConcatStream } from '@kbn/utils';
|
||||
|
||||
async function readStreamToCompletion(stream: Readable) {
|
||||
async function readStreamToCompletion(stream: Readable): Promise<Array<SavedObject<any>>> {
|
||||
return createPromiseFromStreams([stream, createConcatStream([])]);
|
||||
}
|
||||
|
||||
const exportSizeLimit = 500;
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
|
||||
describe('getSortedObjectsForExport()', () => {
|
||||
let savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;
|
||||
let typeRegistry: SavedObjectTypeRegistry;
|
||||
let exporter: SavedObjectsExporter;
|
||||
|
||||
beforeEach(() => {
|
||||
typeRegistry = new SavedObjectTypeRegistry();
|
||||
savedObjectsClient = savedObjectsClientMock.create();
|
||||
exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit });
|
||||
exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit, typeRegistry });
|
||||
});
|
||||
|
||||
describe('#exportByTypes', () => {
|
||||
|
@ -56,6 +62,7 @@ describe('getSortedObjectsForExport()', () => {
|
|||
page: 0,
|
||||
});
|
||||
const exportStream = await exporter.exportByTypes({
|
||||
request,
|
||||
types: ['index-pattern', 'search'],
|
||||
});
|
||||
|
||||
|
@ -115,6 +122,52 @@ describe('getSortedObjectsForExport()', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
test('applies the export transforms', async () => {
|
||||
typeRegistry.registerType({
|
||||
name: 'foo',
|
||||
mappings: { properties: {} },
|
||||
namespaceType: 'single',
|
||||
hidden: false,
|
||||
management: {
|
||||
importableAndExportable: true,
|
||||
onExport: (ctx, objects) => {
|
||||
objects.forEach((obj: SavedObject<any>) => {
|
||||
obj.attributes.foo = 'modified';
|
||||
});
|
||||
return objects;
|
||||
},
|
||||
},
|
||||
});
|
||||
exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit, typeRegistry });
|
||||
|
||||
savedObjectsClient.find.mockResolvedValueOnce({
|
||||
total: 1,
|
||||
saved_objects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'foo',
|
||||
attributes: {
|
||||
foo: 'initial',
|
||||
},
|
||||
score: 0,
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
per_page: 1,
|
||||
page: 0,
|
||||
});
|
||||
const exportStream = await exporter.exportByTypes({
|
||||
request,
|
||||
types: ['foo'],
|
||||
excludeExportDetails: true,
|
||||
});
|
||||
|
||||
const response = await readStreamToCompletion(exportStream);
|
||||
|
||||
expect(response).toHaveLength(1);
|
||||
expect(response[0].attributes.foo).toEqual('modified');
|
||||
});
|
||||
|
||||
test('omits the `namespaces` property from the export', async () => {
|
||||
savedObjectsClient.find.mockResolvedValueOnce({
|
||||
total: 2,
|
||||
|
@ -146,6 +199,7 @@ describe('getSortedObjectsForExport()', () => {
|
|||
page: 0,
|
||||
});
|
||||
const exportStream = await exporter.exportByTypes({
|
||||
request,
|
||||
types: ['index-pattern', 'search'],
|
||||
});
|
||||
|
||||
|
@ -234,6 +288,7 @@ describe('getSortedObjectsForExport()', () => {
|
|||
page: 0,
|
||||
});
|
||||
const exportStream = await exporter.exportByTypes({
|
||||
request,
|
||||
types: ['index-pattern', 'search'],
|
||||
excludeExportDetails: true,
|
||||
});
|
||||
|
@ -293,6 +348,7 @@ describe('getSortedObjectsForExport()', () => {
|
|||
page: 0,
|
||||
});
|
||||
const exportStream = await exporter.exportByTypes({
|
||||
request,
|
||||
types: ['index-pattern', 'search'],
|
||||
search: 'foo',
|
||||
});
|
||||
|
@ -375,6 +431,7 @@ describe('getSortedObjectsForExport()', () => {
|
|||
page: 0,
|
||||
});
|
||||
const exportStream = await exporter.exportByTypes({
|
||||
request,
|
||||
types: ['index-pattern', 'search'],
|
||||
hasReference: [
|
||||
{
|
||||
|
@ -468,6 +525,7 @@ describe('getSortedObjectsForExport()', () => {
|
|||
page: 0,
|
||||
});
|
||||
const exportStream = await exporter.exportByTypes({
|
||||
request,
|
||||
types: ['index-pattern', 'search'],
|
||||
namespace: 'foo',
|
||||
});
|
||||
|
@ -531,7 +589,7 @@ describe('getSortedObjectsForExport()', () => {
|
|||
});
|
||||
|
||||
test('export selected types throws error when exceeding exportSizeLimit', async () => {
|
||||
exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1 });
|
||||
exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1, typeRegistry });
|
||||
|
||||
savedObjectsClient.find.mockResolvedValueOnce({
|
||||
total: 2,
|
||||
|
@ -562,6 +620,7 @@ describe('getSortedObjectsForExport()', () => {
|
|||
});
|
||||
await expect(
|
||||
exporter.exportByTypes({
|
||||
request,
|
||||
types: ['index-pattern', 'search'],
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`"Can't export more than 1 objects"`);
|
||||
|
@ -603,6 +662,7 @@ describe('getSortedObjectsForExport()', () => {
|
|||
],
|
||||
});
|
||||
const exportStream = await exporter.exportByTypes({
|
||||
request,
|
||||
types: ['index-pattern'],
|
||||
});
|
||||
const response = await readStreamToCompletion(exportStream);
|
||||
|
@ -667,6 +727,7 @@ describe('getSortedObjectsForExport()', () => {
|
|||
],
|
||||
});
|
||||
const exportStream = await exporter.exportByObjects({
|
||||
request,
|
||||
objects: [
|
||||
{
|
||||
type: 'index-pattern',
|
||||
|
@ -759,6 +820,7 @@ describe('getSortedObjectsForExport()', () => {
|
|||
});
|
||||
await expect(
|
||||
exporter.exportByObjects({
|
||||
request,
|
||||
objects: [
|
||||
{
|
||||
type: 'index-pattern',
|
||||
|
@ -774,9 +836,10 @@ describe('getSortedObjectsForExport()', () => {
|
|||
});
|
||||
|
||||
test('export selected objects throws error when exceeding exportSizeLimit', async () => {
|
||||
exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1 });
|
||||
exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1, typeRegistry });
|
||||
|
||||
const exportOpts = {
|
||||
request,
|
||||
objects: [
|
||||
{
|
||||
type: 'index-pattern',
|
||||
|
@ -803,6 +866,7 @@ describe('getSortedObjectsForExport()', () => {
|
|||
],
|
||||
});
|
||||
const exportStream = await exporter.exportByObjects({
|
||||
request,
|
||||
objects: [
|
||||
{ type: 'multi', id: '1' },
|
||||
{ type: 'multi', id: '2' },
|
||||
|
@ -846,6 +910,7 @@ describe('getSortedObjectsForExport()', () => {
|
|||
],
|
||||
});
|
||||
const exportStream = await exporter.exportByObjects({
|
||||
request,
|
||||
objects: [
|
||||
{
|
||||
type: 'search',
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import { createListStream } from '@kbn/utils';
|
||||
import { PublicMethodsOf } from '@kbn/utility-types';
|
||||
import { SavedObject, SavedObjectsClientContract } from '../types';
|
||||
import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry';
|
||||
import { fetchNestedDependencies } from './fetch_nested_dependencies';
|
||||
import { sortObjects } from './sort_objects';
|
||||
import {
|
||||
|
@ -16,8 +17,11 @@ import {
|
|||
SavedObjectExportBaseOptions,
|
||||
SavedObjectsExportByObjectOptions,
|
||||
SavedObjectsExportByTypeOptions,
|
||||
SavedObjectsExportTransform,
|
||||
} from './types';
|
||||
import { SavedObjectsExportError } from './errors';
|
||||
import { applyExportTransforms } from './apply_export_transforms';
|
||||
import { byIdAscComparator, getPreservedOrderComparator, SavedObjectComparator } from './utils';
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
@ -29,17 +33,29 @@ export type ISavedObjectsExporter = PublicMethodsOf<SavedObjectsExporter>;
|
|||
*/
|
||||
export class SavedObjectsExporter {
|
||||
readonly #savedObjectsClient: SavedObjectsClientContract;
|
||||
readonly #exportTransforms: Record<string, SavedObjectsExportTransform>;
|
||||
readonly #exportSizeLimit: number;
|
||||
|
||||
constructor({
|
||||
savedObjectsClient,
|
||||
typeRegistry,
|
||||
exportSizeLimit,
|
||||
}: {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
typeRegistry: ISavedObjectTypeRegistry;
|
||||
exportSizeLimit: number;
|
||||
}) {
|
||||
this.#savedObjectsClient = savedObjectsClient;
|
||||
this.#exportSizeLimit = exportSizeLimit;
|
||||
this.#exportTransforms = typeRegistry.getAllTypes().reduce((transforms, type) => {
|
||||
if (type.management?.onExport) {
|
||||
return {
|
||||
...transforms,
|
||||
[type.name]: type.management.onExport,
|
||||
};
|
||||
}
|
||||
return transforms;
|
||||
}, {} as Record<string, SavedObjectsExportTransform>);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,7 +67,8 @@ export class SavedObjectsExporter {
|
|||
*/
|
||||
public async exportByTypes(options: SavedObjectsExportByTypeOptions) {
|
||||
const objects = await this.fetchByTypes(options);
|
||||
return this.processObjects(objects, {
|
||||
return this.processObjects(objects, byIdAscComparator, {
|
||||
request: options.request,
|
||||
includeReferencesDeep: options.includeReferencesDeep,
|
||||
excludeExportDetails: options.excludeExportDetails,
|
||||
namespace: options.namespace,
|
||||
|
@ -70,7 +87,9 @@ export class SavedObjectsExporter {
|
|||
throw SavedObjectsExportError.exportSizeExceeded(this.#exportSizeLimit);
|
||||
}
|
||||
const objects = await this.fetchByObjects(options);
|
||||
return this.processObjects(objects, {
|
||||
const comparator = getPreservedOrderComparator(objects);
|
||||
return this.processObjects(objects, comparator, {
|
||||
request: options.request,
|
||||
includeReferencesDeep: options.includeReferencesDeep,
|
||||
excludeExportDetails: options.excludeExportDetails,
|
||||
namespace: options.namespace,
|
||||
|
@ -79,7 +98,9 @@ export class SavedObjectsExporter {
|
|||
|
||||
private async processObjects(
|
||||
savedObjects: SavedObject[],
|
||||
sortFunction: SavedObjectComparator,
|
||||
{
|
||||
request,
|
||||
excludeExportDetails = false,
|
||||
includeReferencesDeep = false,
|
||||
namespace,
|
||||
|
@ -88,6 +109,13 @@ export class SavedObjectsExporter {
|
|||
let exportedObjects: Array<SavedObject<unknown>>;
|
||||
let missingReferences: SavedObjectsExportResultDetails['missingReferences'] = [];
|
||||
|
||||
savedObjects = await applyExportTransforms({
|
||||
request,
|
||||
objects: savedObjects,
|
||||
transforms: this.#exportTransforms,
|
||||
sortFunction,
|
||||
});
|
||||
|
||||
if (includeReferencesDeep) {
|
||||
const fetchResult = await fetchNestedDependencies(
|
||||
savedObjects,
|
||||
|
@ -145,7 +173,7 @@ export class SavedObjectsExporter {
|
|||
findResponse.saved_objects
|
||||
// exclude the find-specific `score` property from the exported objects
|
||||
.map(({ score, ...obj }) => obj)
|
||||
.sort((a: SavedObject, b: SavedObject) => (a.id > b.id ? 1 : -1))
|
||||
.sort(byIdAscComparator)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,13 @@
|
|||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedObjectsFindOptionsReference } from '../types';
|
||||
import { KibanaRequest } from '../../http';
|
||||
import { SavedObject, SavedObjectsFindOptionsReference } from '../types';
|
||||
|
||||
/** @public */
|
||||
export interface SavedObjectExportBaseOptions {
|
||||
/** The http request initiating the export. */
|
||||
request: KibanaRequest;
|
||||
/** flag to also include all related saved objects in the export stream. */
|
||||
includeReferencesDeep?: boolean;
|
||||
/** flag to not append {@link SavedObjectsExportResultDetails | export details} to the end of the export stream. */
|
||||
|
@ -64,3 +67,92 @@ export interface SavedObjectsExportResultDetails {
|
|||
type: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context passed down to a {@link SavedObjectsExportTransform | export transform function}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface SavedObjectsExportTransformContext {
|
||||
/**
|
||||
* The request that initiated the export request. Can be used to create scoped
|
||||
* services or client inside the {@link SavedObjectsExportTransform | transformation}
|
||||
*/
|
||||
request: KibanaRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformation function used to mutate the exported objects of the associated type.
|
||||
*
|
||||
* A type's export transform function will be executed once per user-initiated export,
|
||||
* for all objects of that type.
|
||||
*
|
||||
* @example
|
||||
* Registering a transform function changing the object's attributes during the export
|
||||
* ```ts
|
||||
* // src/plugins/my_plugin/server/plugin.ts
|
||||
* import { myType } from './saved_objects';
|
||||
*
|
||||
* export class Plugin() {
|
||||
* setup: (core: CoreSetup) => {
|
||||
* core.savedObjects.registerType({
|
||||
* ...myType,
|
||||
* management: {
|
||||
* ...myType.management,
|
||||
* onExport: (ctx, objects) => {
|
||||
* return objects.map((obj) => ({
|
||||
* ...obj,
|
||||
* attributes: {
|
||||
* ...obj.attributes,
|
||||
* enabled: false,
|
||||
* }
|
||||
* })
|
||||
* }
|
||||
* },
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* Registering a transform function adding additional objects to the export
|
||||
* ```ts
|
||||
* // src/plugins/my_plugin/server/plugin.ts
|
||||
* import { myType } from './saved_objects';
|
||||
*
|
||||
* export class Plugin() {
|
||||
* setup: (core: CoreSetup) => {
|
||||
* const savedObjectStartContractPromise = getStartServices().then(
|
||||
* ([{ savedObjects: savedObjectsStart }]) => savedObjectsStart
|
||||
* );
|
||||
*
|
||||
* core.savedObjects.registerType({
|
||||
* ...myType,
|
||||
* management: {
|
||||
* ...myType.management,
|
||||
* onExport: async (ctx, objects) => {
|
||||
* const { getScopedClient } = await savedObjectStartContractPromise;
|
||||
* const client = getScopedClient(ctx.request);
|
||||
*
|
||||
* const depResponse = await client.find({
|
||||
* type: 'my-nested-object',
|
||||
* hasReference: objs.map(({ id, type }) => ({ id, type })),
|
||||
* });
|
||||
*
|
||||
* return [...objs, ...depResponse.saved_objects];
|
||||
* }
|
||||
* },
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @remarks Trying to change an object's id or type during the transform will result in
|
||||
* a runtime error during the export process.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SavedObjectsExportTransform = <T = unknown>(
|
||||
context: SavedObjectsExportTransformContext,
|
||||
objects: Array<SavedObject<T>>
|
||||
) => SavedObject[] | Promise<SavedObject[]>;
|
||||
|
|
57
src/core/server/saved_objects/export/utils.test.ts
Normal file
57
src/core/server/saved_objects/export/utils.test.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* and the Server Side Public License, v 1; you may not use this file except in
|
||||
* compliance with, at your election, the Elastic License or the Server Side
|
||||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
import { byIdAscComparator, getPreservedOrderComparator } from './utils';
|
||||
import { SavedObject } from '../../../types';
|
||||
|
||||
const createObj = (id: string): SavedObject => ({
|
||||
id,
|
||||
type: 'dummy',
|
||||
attributes: {},
|
||||
references: [],
|
||||
});
|
||||
|
||||
describe('byIdAscComparator', () => {
|
||||
it('sorts the objects by id asc', () => {
|
||||
const objs = [createObj('delta'), createObj('alpha'), createObj('beta')];
|
||||
|
||||
objs.sort(byIdAscComparator);
|
||||
|
||||
expect(objs.map((obj) => obj.id)).toEqual(['alpha', 'beta', 'delta']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPreservedOrderComparator', () => {
|
||||
it('sorts objects depending on the order of the provided list', () => {
|
||||
const objA = createObj('A');
|
||||
const objB = createObj('B');
|
||||
const objC = createObj('C');
|
||||
|
||||
const comparator = getPreservedOrderComparator([objA, objB, objC]);
|
||||
|
||||
const objs = [objC, objA, objB];
|
||||
objs.sort(comparator);
|
||||
|
||||
expect(objs.map((obj) => obj.id)).toEqual(['A', 'B', 'C']);
|
||||
});
|
||||
|
||||
it('appends unknown objects at the end of the list and sort them by id', () => {
|
||||
const objA = createObj('A');
|
||||
const objB = createObj('B');
|
||||
const objC = createObj('C');
|
||||
const addedA = createObj('addedA');
|
||||
const addedB = createObj('addedB');
|
||||
|
||||
const comparator = getPreservedOrderComparator([objA, objB, objC]);
|
||||
|
||||
const objs = [addedB, objC, addedA, objA, objB];
|
||||
objs.sort(comparator);
|
||||
|
||||
expect(objs.map((obj) => obj.id)).toEqual(['A', 'B', 'C', 'addedA', 'addedB']);
|
||||
});
|
||||
});
|
46
src/core/server/saved_objects/export/utils.ts
Normal file
46
src/core/server/saved_objects/export/utils.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* and the Server Side Public License, v 1; you may not use this file except in
|
||||
* compliance with, at your election, the Elastic License or the Server Side
|
||||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedObject } from '../../../types';
|
||||
|
||||
export type SavedObjectComparator = (a: SavedObject, b: SavedObject) => number;
|
||||
|
||||
export const getObjKey = (obj: SavedObject) => `${obj.type}|${obj.id}`;
|
||||
|
||||
export const byIdAscComparator: SavedObjectComparator = (a: SavedObject, b: SavedObject) =>
|
||||
a.id > b.id ? 1 : -1;
|
||||
|
||||
/**
|
||||
* Create a comparator that will sort objects depending on their position in the provided array.
|
||||
* Objects not present in the array will be appended at the end of the list, and sorted by id asc.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const comparator = getPreservedOrderComparator([objA, objB, objC]);
|
||||
* const list = [newB, objB, objC, newA, objA]; // with obj.title matching their variable name
|
||||
* list.sort()
|
||||
* // list = [objA, objB, objC, newA, newB]
|
||||
* ```
|
||||
*/
|
||||
export const getPreservedOrderComparator = (objects: SavedObject[]): SavedObjectComparator => {
|
||||
const orderedKeys = objects.map(getObjKey);
|
||||
return (a: SavedObject, b: SavedObject) => {
|
||||
const indexA = orderedKeys.indexOf(getObjKey(a));
|
||||
const indexB = orderedKeys.indexOf(getObjKey(b));
|
||||
if (indexA > -1 && indexB > -1) {
|
||||
return indexA - indexB > 0 ? 1 : -1;
|
||||
}
|
||||
if (indexA > -1) {
|
||||
return -1;
|
||||
}
|
||||
if (indexB > -1) {
|
||||
return 1;
|
||||
}
|
||||
return byIdAscComparator(a, b);
|
||||
};
|
||||
};
|
|
@ -38,6 +38,8 @@ export {
|
|||
SavedObjectsExportByObjectOptions,
|
||||
SavedObjectsExportResultDetails,
|
||||
SavedObjectsExportError,
|
||||
SavedObjectsExportTransformContext,
|
||||
SavedObjectsExportTransform,
|
||||
} from './export';
|
||||
|
||||
export {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { schema } from '@kbn/config-schema';
|
|||
import stringify from 'json-stable-stringify';
|
||||
import { createPromiseFromStreams, createMapStream, createConcatStream } from '@kbn/utils';
|
||||
|
||||
import { IRouter } from '../../http';
|
||||
import { IRouter, KibanaRequest } from '../../http';
|
||||
import { CoreUsageDataSetup } from '../../core_usage_data';
|
||||
import { SavedObjectConfig } from '../saved_objects_config';
|
||||
import {
|
||||
|
@ -78,7 +78,11 @@ const validateOptions = (
|
|||
includeReferencesDeep,
|
||||
search,
|
||||
}: ExportOptions,
|
||||
{ exportSizeLimit, supportedTypes }: { exportSizeLimit: number; supportedTypes: string[] }
|
||||
{
|
||||
exportSizeLimit,
|
||||
supportedTypes,
|
||||
request,
|
||||
}: { exportSizeLimit: number; supportedTypes: string[]; request: KibanaRequest }
|
||||
): EitherExportOptions => {
|
||||
const hasTypes = (types?.length ?? 0) > 0;
|
||||
const hasObjects = (objects?.length ?? 0) > 0;
|
||||
|
@ -106,6 +110,7 @@ const validateOptions = (
|
|||
objects: objects!,
|
||||
excludeExportDetails,
|
||||
includeReferencesDeep,
|
||||
request,
|
||||
};
|
||||
} else {
|
||||
const validationError = validateTypes(types!, supportedTypes);
|
||||
|
@ -118,6 +123,7 @@ const validateOptions = (
|
|||
search,
|
||||
excludeExportDetails,
|
||||
includeReferencesDeep,
|
||||
request,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -165,6 +171,7 @@ export const registerExportRoute = (
|
|||
let options: EitherExportOptions;
|
||||
try {
|
||||
options = validateOptions(cleaned, {
|
||||
request: req,
|
||||
exportSizeLimit: maxImportExportSize,
|
||||
supportedTypes,
|
||||
});
|
||||
|
|
|
@ -457,6 +457,7 @@ export class SavedObjectsService
|
|||
createExporter: (savedObjectsClient) =>
|
||||
new SavedObjectsExporter({
|
||||
savedObjectsClient,
|
||||
typeRegistry: this.typeRegistry,
|
||||
exportSizeLimit: this.config!.maxImportExportSize,
|
||||
}),
|
||||
createImporter: (savedObjectsClient) =>
|
||||
|
|
|
@ -25,25 +25,68 @@ describe('SavedObjectTypeRegistry', () => {
|
|||
registry = new SavedObjectTypeRegistry();
|
||||
});
|
||||
|
||||
it('allows to register types', () => {
|
||||
registry.registerType(createType({ name: 'typeA' }));
|
||||
registry.registerType(createType({ name: 'typeB' }));
|
||||
registry.registerType(createType({ name: 'typeC' }));
|
||||
|
||||
expect(
|
||||
registry
|
||||
.getAllTypes()
|
||||
.map((type) => type.name)
|
||||
.sort()
|
||||
).toEqual(['typeA', 'typeB', 'typeC']);
|
||||
});
|
||||
|
||||
it('throws when trying to register the same type twice', () => {
|
||||
registry.registerType(createType({ name: 'typeA' }));
|
||||
registry.registerType(createType({ name: 'typeB' }));
|
||||
expect(() => {
|
||||
describe('#registerType', () => {
|
||||
it('allows to register types', () => {
|
||||
registry.registerType(createType({ name: 'typeA' }));
|
||||
}).toThrowErrorMatchingInlineSnapshot(`"Type 'typeA' is already registered"`);
|
||||
registry.registerType(createType({ name: 'typeB' }));
|
||||
registry.registerType(createType({ name: 'typeC' }));
|
||||
|
||||
expect(
|
||||
registry
|
||||
.getAllTypes()
|
||||
.map((type) => type.name)
|
||||
.sort()
|
||||
).toEqual(['typeA', 'typeB', 'typeC']);
|
||||
});
|
||||
|
||||
it('throws when trying to register the same type twice', () => {
|
||||
registry.registerType(createType({ name: 'typeA' }));
|
||||
registry.registerType(createType({ name: 'typeB' }));
|
||||
expect(() => {
|
||||
registry.registerType(createType({ name: 'typeA' }));
|
||||
}).toThrowErrorMatchingInlineSnapshot(`"Type 'typeA' is already registered"`);
|
||||
});
|
||||
|
||||
it('throws when `management.onExport` is specified but `management.importableAndExportable` is undefined or false', () => {
|
||||
expect(() => {
|
||||
registry.registerType(
|
||||
createType({
|
||||
name: 'typeA',
|
||||
management: {
|
||||
onExport: (ctx, objs) => objs,
|
||||
},
|
||||
})
|
||||
);
|
||||
}).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Type typeA: 'management.importableAndExportable' must be 'true' when specifying 'management.onExport'"`
|
||||
);
|
||||
expect(() => {
|
||||
registry.registerType(
|
||||
createType({
|
||||
name: 'typeA',
|
||||
management: {
|
||||
importableAndExportable: false,
|
||||
onExport: (ctx, objs) => objs,
|
||||
},
|
||||
})
|
||||
);
|
||||
}).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Type typeA: 'management.importableAndExportable' must be 'true' when specifying 'management.onExport'"`
|
||||
);
|
||||
expect(() => {
|
||||
registry.registerType(
|
||||
createType({
|
||||
name: 'typeA',
|
||||
management: {
|
||||
importableAndExportable: true,
|
||||
onExport: (ctx, objs) => objs,
|
||||
},
|
||||
})
|
||||
);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
// TODO: same test with 'onImport'
|
||||
});
|
||||
|
||||
describe('#getType', () => {
|
||||
|
|
|
@ -32,6 +32,7 @@ export class SavedObjectTypeRegistry {
|
|||
if (this.types.has(type.name)) {
|
||||
throw new Error(`Type '${type.name}' is already registered`);
|
||||
}
|
||||
validateType(type);
|
||||
this.types.set(type.name, deepFreeze(type));
|
||||
}
|
||||
|
||||
|
@ -116,3 +117,13 @@ export class SavedObjectTypeRegistry {
|
|||
return this.types.get(type)?.management?.importableAndExportable ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
const validateType = ({ name, management }: SavedObjectsType) => {
|
||||
if (management) {
|
||||
if (management.onExport && !management.importableAndExportable) {
|
||||
throw new Error(
|
||||
`Type ${name}: 'management.importableAndExportable' must be 'true' when specifying 'management.onExport'`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import { SavedObjectsClient } from './service/saved_objects_client';
|
||||
import { SavedObjectsTypeMappingDefinition } from './mappings';
|
||||
import { SavedObjectMigrationMap } from './migrations';
|
||||
import { SavedObjectsExportTransform } from './export';
|
||||
import { SavedObjectsImportHook } from './import/types';
|
||||
|
||||
export {
|
||||
|
@ -320,6 +321,17 @@ export interface SavedObjectsTypeManagementDefinition {
|
|||
* {@link Capabilities | uiCapabilities} to check if the user has permission to access the object.
|
||||
*/
|
||||
getInAppUrl?: (savedObject: SavedObject<any>) => { path: string; uiCapabilitiesPath: string };
|
||||
/**
|
||||
* An optional export transform function that can be used transform the objects of the registered type during
|
||||
* the export process.
|
||||
*
|
||||
* It can be used to either mutate the exported objects, or add additional objects (of any type) to the export list.
|
||||
*
|
||||
* See {@link SavedObjectsExportTransform | the transform type documentation} for more info and examples.
|
||||
*
|
||||
* @remarks `importableAndExportable` must be `true` to specify this property.
|
||||
*/
|
||||
onExport?: SavedObjectsExportTransform;
|
||||
/**
|
||||
* An optional {@link SavedObjectsImportHook | import hook} to use when importing given type.
|
||||
*
|
||||
|
@ -359,7 +371,8 @@ export interface SavedObjectsTypeManagementDefinition {
|
|||
* }
|
||||
* ```
|
||||
*
|
||||
* @remark messages returned in the warnings are user facing and must be translated.
|
||||
* @remarks messages returned in the warnings are user facing and must be translated.
|
||||
* @remarks `importableAndExportable` must be `true` to specify this property.
|
||||
*/
|
||||
onImport?: SavedObjectsImportHook;
|
||||
}
|
||||
|
|
|
@ -2078,6 +2078,7 @@ export interface SavedObjectExportBaseOptions {
|
|||
excludeExportDetails?: boolean;
|
||||
includeReferencesDeep?: boolean;
|
||||
namespace?: string;
|
||||
request: KibanaRequest;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
@ -2402,8 +2403,9 @@ export interface SavedObjectsExportByTypeOptions extends SavedObjectExportBaseOp
|
|||
export class SavedObjectsExporter {
|
||||
// (undocumented)
|
||||
#private;
|
||||
constructor({ savedObjectsClient, exportSizeLimit, }: {
|
||||
constructor({ savedObjectsClient, typeRegistry, exportSizeLimit, }: {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
typeRegistry: ISavedObjectTypeRegistry;
|
||||
exportSizeLimit: number;
|
||||
});
|
||||
exportByObjects(options: SavedObjectsExportByObjectOptions): Promise<import("stream").Readable>;
|
||||
|
@ -2417,8 +2419,10 @@ export class SavedObjectsExportError extends Error {
|
|||
readonly attributes?: Record<string, any> | undefined;
|
||||
// (undocumented)
|
||||
static exportSizeExceeded(limit: number): SavedObjectsExportError;
|
||||
static invalidTransformError(objectKeys: string[]): SavedObjectsExportError;
|
||||
// (undocumented)
|
||||
static objectFetchError(objects: SavedObject[]): SavedObjectsExportError;
|
||||
static objectTransformError(objects: SavedObject[], cause: Error): SavedObjectsExportError;
|
||||
// (undocumented)
|
||||
readonly type: string;
|
||||
}
|
||||
|
@ -2433,6 +2437,14 @@ export interface SavedObjectsExportResultDetails {
|
|||
}>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type SavedObjectsExportTransform = <T = unknown>(context: SavedObjectsExportTransformContext, objects: Array<SavedObject<T>>) => SavedObject[] | Promise<SavedObject[]>;
|
||||
|
||||
// @public
|
||||
export interface SavedObjectsExportTransformContext {
|
||||
request: KibanaRequest;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type SavedObjectsFieldMapping = SavedObjectsCoreFieldMapping | SavedObjectsComplexFieldMapping;
|
||||
|
||||
|
@ -2851,6 +2863,7 @@ export interface SavedObjectsTypeManagementDefinition {
|
|||
getTitle?: (savedObject: SavedObject<any>) => string;
|
||||
icon?: string;
|
||||
importableAndExportable?: boolean;
|
||||
onExport?: SavedObjectsExportTransform;
|
||||
onImport?: SavedObjectsImportHook;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,34 @@
|
|||
|
||||
/** This module is intended for consumption by public to avoid import issues with server-side code */
|
||||
export { PluginOpaqueId } from './plugins/types';
|
||||
export * from './saved_objects/types';
|
||||
export type {
|
||||
SavedObjectsImportResponse,
|
||||
SavedObjectsImportSuccess,
|
||||
SavedObjectsImportConflictError,
|
||||
SavedObjectsImportAmbiguousConflictError,
|
||||
SavedObjectsImportUnsupportedTypeError,
|
||||
SavedObjectsImportMissingReferencesError,
|
||||
SavedObjectsImportUnknownError,
|
||||
SavedObjectsImportFailure,
|
||||
SavedObjectsImportRetry,
|
||||
SavedObjectsImportWarning,
|
||||
SavedObjectsImportActionRequiredWarning,
|
||||
SavedObjectsImportSimpleWarning,
|
||||
SavedObjectAttributes,
|
||||
SavedObjectAttribute,
|
||||
SavedObjectAttributeSingle,
|
||||
SavedObject,
|
||||
SavedObjectError,
|
||||
SavedObjectReference,
|
||||
SavedObjectsMigrationVersion,
|
||||
SavedObjectStatusMeta,
|
||||
SavedObjectsFindOptionsReference,
|
||||
SavedObjectsFindOptions,
|
||||
SavedObjectsBaseOptions,
|
||||
MutatingOperationRefreshSetting,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsNamespaceType,
|
||||
} from './saved_objects/types';
|
||||
export * from './ui_settings/types';
|
||||
export * from './legacy/types';
|
||||
export type { EnvironmentMode, PackageInfo } from '@kbn/config';
|
||||
|
|
|
@ -24,6 +24,7 @@ import * as CSS from 'csstype';
|
|||
import { Datatable as Datatable_2 } from 'src/plugins/expressions';
|
||||
import { Datatable as Datatable_3 } from 'src/plugins/expressions/common';
|
||||
import { DatatableColumn as DatatableColumn_2 } from 'src/plugins/expressions';
|
||||
import { DetailedPeerCertificate } from 'tls';
|
||||
import { Ensure } from '@kbn/utility-types';
|
||||
import { EnvironmentMode } from '@kbn/config';
|
||||
import { ErrorToastOptions } from 'src/core/public/notifications';
|
||||
|
@ -45,6 +46,7 @@ import { History } from 'history';
|
|||
import { Href } from 'history';
|
||||
import { HttpSetup } from 'kibana/public';
|
||||
import { IconType } from '@elastic/eui';
|
||||
import { IncomingHttpHeaders } from 'http';
|
||||
import { InjectedIntl } from '@kbn/i18n/react';
|
||||
import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public';
|
||||
import { ISearchSource as ISearchSource_2 } from 'src/plugins/data/public';
|
||||
|
@ -60,9 +62,11 @@ import { METRIC_TYPE } from '@kbn/analytics';
|
|||
import { Moment } from 'moment';
|
||||
import moment from 'moment';
|
||||
import { NameList } from 'elasticsearch';
|
||||
import { ObjectType } from '@kbn/config-schema';
|
||||
import { Observable } from 'rxjs';
|
||||
import { PackageInfo } from '@kbn/config';
|
||||
import { Path } from 'history';
|
||||
import { PeerCertificate } from 'tls';
|
||||
import { Plugin as Plugin_2 } from 'src/core/public';
|
||||
import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public';
|
||||
import { PluginInitializerContext as PluginInitializerContext_3 } from 'kibana/public';
|
||||
|
@ -75,6 +79,7 @@ import React from 'react';
|
|||
import * as React_3 from 'react';
|
||||
import { RecursiveReadonly } from '@kbn/utility-types';
|
||||
import { Reporter } from '@kbn/analytics';
|
||||
import { Request as Request_2 } from '@hapi/hapi';
|
||||
import { RequestAdapter } from 'src/plugins/inspector/common';
|
||||
import { RequestStatistics as RequestStatistics_2 } from 'src/plugins/inspector/common';
|
||||
import { Required } from '@kbn/utility-types';
|
||||
|
@ -85,6 +90,7 @@ import { SavedObjectReference } from 'src/core/types';
|
|||
import { SavedObjectsClientContract } from 'src/core/public';
|
||||
import { SavedObjectsFindOptions } from 'kibana/public';
|
||||
import { SavedObjectsFindResponse } from 'kibana/server';
|
||||
import { SchemaTypeError } from '@kbn/config-schema';
|
||||
import { Search } from '@elastic/elasticsearch/api/requestParams';
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common';
|
||||
|
@ -94,12 +100,14 @@ import { ToastsSetup } from 'kibana/public';
|
|||
import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { Type } from '@kbn/config-schema';
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import { UiActionsSetup } from 'src/plugins/ui_actions/public';
|
||||
import { UiActionsStart } from 'src/plugins/ui_actions/public';
|
||||
import { UiCounterMetricType } from '@kbn/analytics';
|
||||
import { Unit } from '@elastic/datemath';
|
||||
import { UnregisterCallback } from 'history';
|
||||
import { URL } from 'url';
|
||||
import { UserProvidedValues } from 'src/core/server/types';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ACTION_GLOBAL_APPLY_FILTER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
|
|
|
@ -12,6 +12,7 @@ import { ApplicationStart as ApplicationStart_2 } from 'kibana/public';
|
|||
import Boom from '@hapi/boom';
|
||||
import { ConfigDeprecationProvider } from '@kbn/config';
|
||||
import * as CSS from 'csstype';
|
||||
import { DetailedPeerCertificate } from 'tls';
|
||||
import { EmbeddableStart as EmbeddableStart_2 } from 'src/plugins/embeddable/public/plugin';
|
||||
import { EnvironmentMode } from '@kbn/config';
|
||||
import { EuiBreadcrumb } from '@elastic/eui';
|
||||
|
@ -25,6 +26,7 @@ import { History } from 'history';
|
|||
import { Href } from 'history';
|
||||
import { I18nStart as I18nStart_2 } from 'src/core/public';
|
||||
import { IconType } from '@elastic/eui';
|
||||
import { IncomingHttpHeaders } from 'http';
|
||||
import { KibanaClient } from '@elastic/elasticsearch/api/kibana';
|
||||
import { Location } from 'history';
|
||||
import { LocationDescriptorObject } from 'history';
|
||||
|
@ -32,30 +34,36 @@ import { Logger } from '@kbn/logging';
|
|||
import { LogMeta } from '@kbn/logging';
|
||||
import { MaybePromise } from '@kbn/utility-types';
|
||||
import { NotificationsStart as NotificationsStart_2 } from 'src/core/public';
|
||||
import { ObjectType } from '@kbn/config-schema';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Optional } from '@kbn/utility-types';
|
||||
import { OverlayRef as OverlayRef_2 } from 'src/core/public';
|
||||
import { OverlayStart as OverlayStart_2 } from 'src/core/public';
|
||||
import { PackageInfo } from '@kbn/config';
|
||||
import { Path } from 'history';
|
||||
import { PeerCertificate } from 'tls';
|
||||
import { PluginInitializerContext } from 'src/core/public';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import { PublicMethodsOf } from '@kbn/utility-types';
|
||||
import { PublicUiSettingsParams } from 'src/core/server/types';
|
||||
import React from 'react';
|
||||
import { RecursiveReadonly } from '@kbn/utility-types';
|
||||
import { Request } from '@hapi/hapi';
|
||||
import * as Rx from 'rxjs';
|
||||
import { SavedObjectAttributes } from 'kibana/server';
|
||||
import { SavedObjectAttributes as SavedObjectAttributes_2 } from 'src/core/public';
|
||||
import { SavedObjectAttributes as SavedObjectAttributes_3 } from 'kibana/public';
|
||||
import { SchemaTypeError } from '@kbn/config-schema';
|
||||
import { SimpleSavedObject as SimpleSavedObject_2 } from 'src/core/public';
|
||||
import { Start as Start_2 } from 'src/plugins/inspector/public';
|
||||
import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { Type } from '@kbn/config-schema';
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import { UiComponent } from 'src/plugins/kibana_utils/public';
|
||||
import { UnregisterCallback } from 'history';
|
||||
import { URL } from 'url';
|
||||
import { UserProvidedValues } from 'src/core/server/types';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ACTION_ADD_PANEL" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"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:type_2-obj_2",
|
||||
"source": {
|
||||
"test-export-add": {
|
||||
"title": "test_2-obj_2"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"index": ".kibana",
|
||||
"type": "doc",
|
||||
"id": "test-export-add-dep:type_dep-obj_2",
|
||||
"source": {
|
||||
"test-export-add-dep": {
|
||||
"title": "type_dep-obj_2"
|
||||
},
|
||||
"type": "test-export-add-dep",
|
||||
"migrationVersion": {},
|
||||
"updated_at": "2018-12-21T00:43:07.096Z",
|
||||
"references": [
|
||||
{
|
||||
"type": "test-export-add",
|
||||
"id": "type_2-obj_2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"index": ".kibana",
|
||||
"type": "doc",
|
||||
"id": "test-export-invalid-transform:type_3-obj_1",
|
||||
"source": {
|
||||
"test-export-invalid-transform": {
|
||||
"title": "test_2-obj_1"
|
||||
},
|
||||
"type": "test-export-invalid-transform",
|
||||
"migrationVersion": {},
|
||||
"updated_at": "2018-12-21T00:43:07.096Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"index": ".kibana",
|
||||
"type": "doc",
|
||||
"id": "test-export-transform-error:type_4-obj_1",
|
||||
"source": {
|
||||
"test-export-transform-error": {
|
||||
"title": "test_2-obj_1"
|
||||
},
|
||||
"type": "test-export-transform-error",
|
||||
"migrationVersion": {},
|
||||
"updated_at": "2018-12-21T00:43:07.096Z"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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": {
|
||||
"type": "geo_shape",
|
||||
"tree": "quadtree"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"id": "savedObjectExportTransforms",
|
||||
"version": "0.0.1",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["saved_object_export_transforms"],
|
||||
"server": true,
|
||||
"ui": false
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "saved_object_export_transforms",
|
||||
"version": "1.0.0",
|
||||
"main": "target/test/plugin_functional/plugins/saved_object_export_transforms",
|
||||
"kibana": {
|
||||
"version": "kibana",
|
||||
"templateVersion": "1.0.0"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"kbn": "node ../../../../scripts/kbn.js",
|
||||
"build": "rm -rf './target' && ../../../../node_modules/.bin/tsc"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* and the Server Side Public License, v 1; you may not use this file except in
|
||||
* compliance with, at your election, the Elastic License or the Server Side
|
||||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedObjectExportTransformsPlugin } from './plugin';
|
||||
|
||||
export const plugin = () => new SavedObjectExportTransformsPlugin();
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* and the Server Side Public License, v 1; you may not use this file except in
|
||||
* compliance with, at your election, the Elastic License or the Server Side
|
||||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Plugin, CoreSetup } from 'kibana/server';
|
||||
|
||||
export class SavedObjectExportTransformsPlugin implements Plugin {
|
||||
public setup({ savedObjects, getStartServices }: CoreSetup, deps: {}) {
|
||||
const savedObjectStartContractPromise = getStartServices().then(
|
||||
([{ savedObjects: savedObjectsStart }]) => savedObjectsStart
|
||||
);
|
||||
|
||||
// example of a SO type that will mutates its properties
|
||||
// during the export transform
|
||||
savedObjects.registerType({
|
||||
name: 'test-export-transform',
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
mappings: {
|
||||
properties: {
|
||||
title: { type: 'text' },
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
management: {
|
||||
defaultSearchField: 'title',
|
||||
importableAndExportable: true,
|
||||
getTitle: (obj) => obj.attributes.title,
|
||||
onExport: (ctx, objs) => {
|
||||
return objs.map((obj) => ({
|
||||
...obj,
|
||||
attributes: {
|
||||
...obj.attributes,
|
||||
enabled: false,
|
||||
},
|
||||
}));
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// example of a SO type that will add additional objects
|
||||
// to the export during the export transform
|
||||
savedObjects.registerType({
|
||||
name: 'test-export-add',
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
mappings: {
|
||||
properties: {
|
||||
title: { type: 'text' },
|
||||
},
|
||||
},
|
||||
management: {
|
||||
defaultSearchField: 'title',
|
||||
importableAndExportable: true,
|
||||
getTitle: (obj) => obj.attributes.title,
|
||||
onExport: async (ctx, objs) => {
|
||||
const { getScopedClient } = await savedObjectStartContractPromise;
|
||||
const client = getScopedClient(ctx.request);
|
||||
const objRefs = objs.map(({ id, type }) => ({ id, type }));
|
||||
const depResponse = await client.find({
|
||||
type: 'test-export-add-dep',
|
||||
hasReference: objRefs,
|
||||
});
|
||||
return [...objs, ...depResponse.saved_objects];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// dependency of `test_export_transform_2` that will be included
|
||||
// when exporting them
|
||||
savedObjects.registerType({
|
||||
name: 'test-export-add-dep',
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
mappings: {
|
||||
properties: {
|
||||
title: { type: 'text' },
|
||||
},
|
||||
},
|
||||
management: {
|
||||
defaultSearchField: 'title',
|
||||
importableAndExportable: true,
|
||||
getTitle: (obj) => obj.attributes.title,
|
||||
},
|
||||
});
|
||||
|
||||
/////////////
|
||||
/////////////
|
||||
// example of a SO type that will throw an object-transform-error
|
||||
savedObjects.registerType({
|
||||
name: 'test-export-transform-error',
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
mappings: {
|
||||
properties: {
|
||||
title: { type: 'text' },
|
||||
},
|
||||
},
|
||||
management: {
|
||||
defaultSearchField: 'title',
|
||||
importableAndExportable: true,
|
||||
getTitle: (obj) => obj.attributes.title,
|
||||
onExport: (ctx, objs) => {
|
||||
throw new Error('Error during transform');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// example of a SO type that will throw an invalid-transform-error
|
||||
savedObjects.registerType({
|
||||
name: 'test-export-invalid-transform',
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
mappings: {
|
||||
properties: {
|
||||
title: { type: 'text' },
|
||||
},
|
||||
},
|
||||
management: {
|
||||
defaultSearchField: 'title',
|
||||
importableAndExportable: true,
|
||||
getTitle: (obj) => obj.attributes.title,
|
||||
onExport: (ctx, objs) => {
|
||||
return objs.map((obj) => ({
|
||||
...obj,
|
||||
id: `${obj.id}-mutated`,
|
||||
}));
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public start() {}
|
||||
public stop() {}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"index.ts",
|
||||
"server/**/*.ts",
|
||||
"../../../../typings/**/*",
|
||||
],
|
||||
"exclude": [],
|
||||
"references": [
|
||||
{ "path": "../../../../src/core/tsconfig.json" }
|
||||
]
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "savedObjectHooks",
|
||||
"id": "savedObjectImportWarnings",
|
||||
"version": "0.0.1",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["saved_object_hooks"],
|
||||
"configPath": ["saved_object_import_warnings"],
|
||||
"server": true,
|
||||
"ui": false
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "saved_object_hooks",
|
||||
"name": "saved_object_import_warnings",
|
||||
"version": "1.0.0",
|
||||
"main": "target/test/plugin_functional/plugins/saved_object_hooks",
|
||||
"main": "target/test/plugin_functional/plugins/saved_object_import_warnings",
|
||||
"kibana": {
|
||||
"version": "kibana",
|
||||
"templateVersion": "1.0.0"
|
|
@ -6,6 +6,6 @@
|
|||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedObjectHooksPlugin } from './plugin';
|
||||
import { SavedObjectImportWarningsPlugin } from './plugin';
|
||||
|
||||
export const plugin = () => new SavedObjectHooksPlugin();
|
||||
export const plugin = () => new SavedObjectImportWarningsPlugin();
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { Plugin, CoreSetup } from 'kibana/server';
|
||||
|
||||
export class SavedObjectHooksPlugin implements Plugin {
|
||||
export class SavedObjectImportWarningsPlugin implements Plugin {
|
||||
public setup({ savedObjects }: CoreSetup, deps: {}) {
|
||||
savedObjects.registerType({
|
||||
name: 'test_import_warning_1',
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* and the Server Side Public License, v 1; you may not use this file except in
|
||||
* compliance with, at your election, the Elastic License or the Server Side
|
||||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import type { SavedObject } from '../../../../src/core/types';
|
||||
import { PluginFunctionalProviderContext } from '../../services';
|
||||
|
||||
function parseNdJson(input: string): Array<SavedObject<any>> {
|
||||
return input.split('\n').map((str) => JSON.parse(str));
|
||||
}
|
||||
|
||||
export default function ({ getService }: PluginFunctionalProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const esArchiver = getService('esArchiver');
|
||||
|
||||
describe('export transforms', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load(
|
||||
'../functional/fixtures/es_archiver/saved_objects_management/export_transform'
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload(
|
||||
'../functional/fixtures/es_archiver/saved_objects_management/export_transform'
|
||||
);
|
||||
});
|
||||
|
||||
it('allows to mutate the objects during an export', async () => {
|
||||
await supertest
|
||||
.post('/api/saved_objects/_export')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
type: ['test-export-transform'],
|
||||
excludeExportDetails: true,
|
||||
})
|
||||
.expect(200)
|
||||
.then((resp) => {
|
||||
const objects = parseNdJson(resp.text);
|
||||
expect(objects.map((obj) => ({ id: obj.id, enabled: obj.attributes.enabled }))).to.eql([
|
||||
{
|
||||
id: 'type_1-obj_1',
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
id: 'type_1-obj_2',
|
||||
enabled: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows to add additional objects to an export', async () => {
|
||||
await supertest
|
||||
.post('/api/saved_objects/_export')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
objects: [
|
||||
{
|
||||
type: 'test-export-add',
|
||||
id: 'type_2-obj_1',
|
||||
},
|
||||
],
|
||||
excludeExportDetails: true,
|
||||
})
|
||||
.expect(200)
|
||||
.then((resp) => {
|
||||
const objects = parseNdJson(resp.text);
|
||||
expect(objects.map((obj) => obj.id)).to.eql(['type_2-obj_1', 'type_dep-obj_1']);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows to add additional objects to an export when exporting by type', async () => {
|
||||
await supertest
|
||||
.post('/api/saved_objects/_export')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
type: ['test-export-add'],
|
||||
excludeExportDetails: true,
|
||||
})
|
||||
.expect(200)
|
||||
.then((resp) => {
|
||||
const objects = parseNdJson(resp.text);
|
||||
expect(objects.map((obj) => obj.id)).to.eql([
|
||||
'type_2-obj_1',
|
||||
'type_2-obj_2',
|
||||
'type_dep-obj_1',
|
||||
'type_dep-obj_2',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a 400 when the type causes a transform error', async () => {
|
||||
await supertest
|
||||
.post('/api/saved_objects/_export')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
type: ['test-export-transform-error'],
|
||||
excludeExportDetails: true,
|
||||
})
|
||||
.expect(400)
|
||||
.then((resp) => {
|
||||
const { attributes, ...error } = resp.body;
|
||||
expect(error).to.eql({
|
||||
error: 'Bad Request',
|
||||
message: 'Error transforming objects to export',
|
||||
statusCode: 400,
|
||||
});
|
||||
expect(attributes.cause).to.eql('Error during transform');
|
||||
expect(attributes.objects.map((obj: any) => obj.id)).to.eql(['type_4-obj_1']);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a 400 when the type causes an invalid transform', async () => {
|
||||
await supertest
|
||||
.post('/api/saved_objects/_export')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
type: ['test-export-invalid-transform'],
|
||||
excludeExportDetails: true,
|
||||
})
|
||||
.expect(400)
|
||||
.then((resp) => {
|
||||
expect(resp.body).to.eql({
|
||||
error: 'Bad Request',
|
||||
message: 'Invalid transform performed on objects to export',
|
||||
statusCode: 400,
|
||||
attributes: {
|
||||
objectKeys: ['test-export-invalid-transform|type_3-obj_1'],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -10,6 +10,7 @@ import { PluginFunctionalProviderContext } from '../../services';
|
|||
|
||||
export default function ({ loadTestFile }: PluginFunctionalProviderContext) {
|
||||
describe('Saved Objects Management', function () {
|
||||
loadTestFile(require.resolve('./export_transform'));
|
||||
loadTestFile(require.resolve('./import_warnings'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@ describe('copySavedObjectsToSpaces', () => {
|
|||
`);
|
||||
|
||||
expect(savedObjectsExporter.exportByObjects).toHaveBeenCalledWith({
|
||||
request: expect.any(Object),
|
||||
excludeExportDetails: true,
|
||||
includeReferencesDeep: true,
|
||||
namespace,
|
||||
|
|
|
@ -29,6 +29,7 @@ export function copySavedObjectsToSpacesFactory(
|
|||
options: Pick<CopyOptions, 'includeReferences' | 'objects'>
|
||||
) => {
|
||||
const objectStream = await savedObjectsExporter.exportByObjects({
|
||||
request,
|
||||
namespace: spaceIdToNamespace(sourceSpaceId),
|
||||
includeReferencesDeep: options.includeReferences,
|
||||
excludeExportDetails: true,
|
||||
|
|
|
@ -174,6 +174,7 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => {
|
|||
`);
|
||||
|
||||
expect(savedObjectsExporter.exportByObjects).toHaveBeenCalledWith({
|
||||
request: expect.any(Object),
|
||||
excludeExportDetails: true,
|
||||
includeReferencesDeep: true,
|
||||
namespace,
|
||||
|
|
|
@ -29,6 +29,7 @@ export function resolveCopySavedObjectsToSpacesConflictsFactory(
|
|||
options: Pick<CopyOptions, 'includeReferences' | 'objects'>
|
||||
) => {
|
||||
const objectStream = await savedObjectsExporter.exportByObjects({
|
||||
request,
|
||||
namespace: spaceIdToNamespace(sourceSpaceId),
|
||||
includeReferencesDeep: options.includeReferences,
|
||||
excludeExportDetails: true,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue