mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Restructure SavedObject types internal representation (#56378)
* adapt types and tests to prepare for new NP api * rename and export public types * update generated doc * first implementation of registerMappings * adapt es archiver to convert legacy mappings * update generated doc * fix more tests * add unit tests * add legacy-compat unit test * add documentation and examples * Introduce SavedObjectTypeRegistry and SavedObjectType types * add and fix tests * expose createSerializer API and fix usages * remove registerMappings API, add internal registerType * revert changes to migration guide * adapt ES-archiver migrator creation * export serializer-related types * update generated doc * add and use convertTypesToLegacySchema * remove / move to internal some mapping types * fix forEach closure context * add missing docs * fix core path * some nits * fix so_mixin tests * fix integration tests * fix integration tests for real * add documentation on serializer + restructure files and types * nit * add and use the ISavedObjectTypeRegistry interface * Add documentation, deprecates migrationLogger#warning * better typing for SavedObjectsRawDoc._source * nits * update generated doc * remove exposition of SavedObjectsTypeMappingDefinitions, update doc * creates so internal contracts mocks * improve documentation
This commit is contained in:
parent
595c869780
commit
ea7c78d10e
104 changed files with 3062 additions and 951 deletions
|
@ -25,6 +25,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | |
|
||||
| [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | |
|
||||
| [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | |
|
||||
| [SavedObjectsSerializer](./kibana-plugin-server.savedobjectsserializer.md) | A serializer that can be used to manually convert [raw](./kibana-plugin-server.savedobjectsrawdoc.md) or [sanitized](./kibana-plugin-server.savedobjectsanitizeddoc.md) documents to the other kind. |
|
||||
| [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" <code>ClusterClient</code> but exposes additional <code>callAsCurrentUser</code> method that doesn't use credentials of the Kibana internal user (as <code>callAsInternalUser</code> does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.<!-- -->See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md)<!-- -->. |
|
||||
|
||||
## Enumerations
|
||||
|
@ -106,6 +107,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [RouteValidatorOptions](./kibana-plugin-server.routevalidatoroptions.md) | Additional options for the RouteValidator class to modify its default behaviour. |
|
||||
| [SavedObject](./kibana-plugin-server.savedobject.md) | |
|
||||
| [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the <code>attributes</code> property. |
|
||||
| [SavedObjectMigrationMap](./kibana-plugin-server.savedobjectmigrationmap.md) | A map of [migration functions](./kibana-plugin-server.savedobjectmigrationfn.md) to be used for a given type. The map's keys must be valid semver versions.<!-- -->For a given document, only migrations with a higher version number than that of the document will be applied. Migrations are executed in order, starting from the lowest version and ending with the highest one. |
|
||||
| [SavedObjectReference](./kibana-plugin-server.savedobjectreference.md) | A reference to another saved object. |
|
||||
| [SavedObjectsBaseOptions](./kibana-plugin-server.savedobjectsbaseoptions.md) | |
|
||||
| [SavedObjectsBulkCreateObject](./kibana-plugin-server.savedobjectsbulkcreateobject.md) | |
|
||||
|
@ -116,6 +118,8 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [SavedObjectsBulkUpdateResponse](./kibana-plugin-server.savedobjectsbulkupdateresponse.md) | |
|
||||
| [SavedObjectsClientProviderOptions](./kibana-plugin-server.savedobjectsclientprovideroptions.md) | Options to control the creation of the Saved Objects Client. |
|
||||
| [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) | Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. |
|
||||
| [SavedObjectsComplexFieldMapping](./kibana-plugin-server.savedobjectscomplexfieldmapping.md) | See [SavedObjectsFieldMapping](./kibana-plugin-server.savedobjectsfieldmapping.md) for documentation. |
|
||||
| [SavedObjectsCoreFieldMapping](./kibana-plugin-server.savedobjectscorefieldmapping.md) | See [SavedObjectsFieldMapping](./kibana-plugin-server.savedobjectsfieldmapping.md) for documentation. |
|
||||
| [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) | |
|
||||
| [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) | |
|
||||
| [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) | |
|
||||
|
@ -132,6 +136,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [SavedObjectsImportUnknownError](./kibana-plugin-server.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. |
|
||||
| [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-server.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. |
|
||||
| [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) | |
|
||||
| [SavedObjectsMappingProperties](./kibana-plugin-server.savedobjectsmappingproperties.md) | Describe the fields of a [saved object type](./kibana-plugin-server.savedobjectstypemappingdefinition.md)<!-- -->. |
|
||||
| [SavedObjectsMigrationLogger](./kibana-plugin-server.savedobjectsmigrationlogger.md) | |
|
||||
| [SavedObjectsMigrationVersion](./kibana-plugin-server.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. |
|
||||
| [SavedObjectsRawDoc](./kibana-plugin-server.savedobjectsrawdoc.md) | A raw document as represented directly in the saved object index. |
|
||||
|
@ -139,6 +144,8 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. |
|
||||
| [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persistence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. |
|
||||
| [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. |
|
||||
| [SavedObjectsType](./kibana-plugin-server.savedobjectstype.md) | |
|
||||
| [SavedObjectsTypeMappingDefinition](./kibana-plugin-server.savedobjectstypemappingdefinition.md) | Describe a saved object type mapping. |
|
||||
| [SavedObjectsUpdateOptions](./kibana-plugin-server.savedobjectsupdateoptions.md) | |
|
||||
| [SavedObjectsUpdateResponse](./kibana-plugin-server.savedobjectsupdateresponse.md) | |
|
||||
| [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) | Return type from a function to validate cookie contents. |
|
||||
|
@ -218,10 +225,13 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [RouteValidatorFullConfig](./kibana-plugin-server.routevalidatorfullconfig.md) | Route validations config and options merged into one object |
|
||||
| [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value |
|
||||
| [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) |
|
||||
| [SavedObjectMigrationFn](./kibana-plugin-server.savedobjectmigrationfn.md) | A migration function defined for a [saved objects type](./kibana-plugin-server.savedobjectstype.md) used to migrate it's |
|
||||
| [SavedObjectSanitizedDoc](./kibana-plugin-server.savedobjectsanitizeddoc.md) | |
|
||||
| [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.<!-- -->\#\# SavedObjectsClient errors<!-- -->Since the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:<!-- -->1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)<!-- -->Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the <code>isXYZError()</code> helpers exposed at <code>SavedObjectsErrorHelpers</code> should be used to understand and manage error responses from the <code>SavedObjectsClient</code>.<!-- -->Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for <code>error.body.error.type</code> or doing substring checks on <code>error.body.error.reason</code>, just use the helpers to understand the meaning of the error:<!-- -->\`\`\`<!-- -->js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }<!-- -->if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }<!-- -->// always rethrow the error unless you handle it throw error; \`\`\`<!-- -->\#\#\# 404s from missing index<!-- -->From the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.<!-- -->At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.<!-- -->From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.<!-- -->\#\#\# 503s from missing index<!-- -->Unlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's <code>action.auto_create_index</code> setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.<!-- -->See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) |
|
||||
| [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. |
|
||||
| [SavedObjectsClientFactoryProvider](./kibana-plugin-server.savedobjectsclientfactoryprovider.md) | Provider to invoke to retrieve a [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md)<!-- -->. |
|
||||
| [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. |
|
||||
| [SavedObjectsFieldMapping](./kibana-plugin-server.savedobjectsfieldmapping.md) | Describe a [saved object type mapping](./kibana-plugin-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 |
|
||||
| [ScopeableRequest](./kibana-plugin-server.scopeablerequest.md) | A user credentials container. It accommodates the necessary auth credentials to impersonate the current user.<!-- -->See [KibanaRequest](./kibana-plugin-server.kibanarequest.md)<!-- -->. |
|
||||
| [SharedGlobalConfig](./kibana-plugin-server.sharedglobalconfig.md) | |
|
||||
| [StringValidation](./kibana-plugin-server.stringvalidation.md) | Allows regex objects or a regex string |
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectMigrationFn](./kibana-plugin-server.savedobjectmigrationfn.md)
|
||||
|
||||
## SavedObjectMigrationFn type
|
||||
|
||||
A migration function defined for a [saved objects type](./kibana-plugin-server.savedobjectstype.md) used to migrate it's
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type SavedObjectMigrationFn = (doc: SavedObjectUnsanitizedDoc, log: SavedObjectsMigrationLogger) => SavedObjectUnsanitizedDoc;
|
||||
```
|
|
@ -0,0 +1,27 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectMigrationMap](./kibana-plugin-server.savedobjectmigrationmap.md)
|
||||
|
||||
## SavedObjectMigrationMap interface
|
||||
|
||||
A map of [migration functions](./kibana-plugin-server.savedobjectmigrationfn.md) to be used for a given type. The map's keys must be valid semver versions.
|
||||
|
||||
For a given document, only migrations with a higher version number than that of the document will be applied. Migrations are executed in order, starting from the lowest version and ending with the highest one.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface SavedObjectMigrationMap
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
```typescript
|
||||
const migrations: SavedObjectMigrationMap = {
|
||||
'1.0.0': migrateToV1,
|
||||
'2.1.0': migrateToV21
|
||||
}
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectSanitizedDoc](./kibana-plugin-server.savedobjectsanitizeddoc.md)
|
||||
|
||||
## SavedObjectSanitizedDoc type
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type SavedObjectSanitizedDoc = SavedObjectDoc & Referencable;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsComplexFieldMapping](./kibana-plugin-server.savedobjectscomplexfieldmapping.md) > [dynamic](./kibana-plugin-server.savedobjectscomplexfieldmapping.dynamic.md)
|
||||
|
||||
## SavedObjectsComplexFieldMapping.dynamic property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
dynamic?: string;
|
||||
```
|
|
@ -0,0 +1,22 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsComplexFieldMapping](./kibana-plugin-server.savedobjectscomplexfieldmapping.md)
|
||||
|
||||
## SavedObjectsComplexFieldMapping interface
|
||||
|
||||
See [SavedObjectsFieldMapping](./kibana-plugin-server.savedobjectsfieldmapping.md) for documentation.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface SavedObjectsComplexFieldMapping
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [dynamic](./kibana-plugin-server.savedobjectscomplexfieldmapping.dynamic.md) | <code>string</code> | |
|
||||
| [properties](./kibana-plugin-server.savedobjectscomplexfieldmapping.properties.md) | <code>SavedObjectsMappingProperties</code> | |
|
||||
| [type](./kibana-plugin-server.savedobjectscomplexfieldmapping.type.md) | <code>string</code> | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsComplexFieldMapping](./kibana-plugin-server.savedobjectscomplexfieldmapping.md) > [properties](./kibana-plugin-server.savedobjectscomplexfieldmapping.properties.md)
|
||||
|
||||
## SavedObjectsComplexFieldMapping.properties property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
properties: SavedObjectsMappingProperties;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsComplexFieldMapping](./kibana-plugin-server.savedobjectscomplexfieldmapping.md) > [type](./kibana-plugin-server.savedobjectscomplexfieldmapping.type.md)
|
||||
|
||||
## SavedObjectsComplexFieldMapping.type property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
type?: string;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsCoreFieldMapping](./kibana-plugin-server.savedobjectscorefieldmapping.md) > [enabled](./kibana-plugin-server.savedobjectscorefieldmapping.enabled.md)
|
||||
|
||||
## SavedObjectsCoreFieldMapping.enabled property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
enabled?: boolean;
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsCoreFieldMapping](./kibana-plugin-server.savedobjectscorefieldmapping.md) > [fields](./kibana-plugin-server.savedobjectscorefieldmapping.fields.md)
|
||||
|
||||
## SavedObjectsCoreFieldMapping.fields property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
fields?: {
|
||||
[subfield: string]: {
|
||||
type: string;
|
||||
};
|
||||
};
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsCoreFieldMapping](./kibana-plugin-server.savedobjectscorefieldmapping.md) > [index](./kibana-plugin-server.savedobjectscorefieldmapping.index.md)
|
||||
|
||||
## SavedObjectsCoreFieldMapping.index property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
index?: boolean;
|
||||
```
|
|
@ -0,0 +1,23 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsCoreFieldMapping](./kibana-plugin-server.savedobjectscorefieldmapping.md)
|
||||
|
||||
## SavedObjectsCoreFieldMapping interface
|
||||
|
||||
See [SavedObjectsFieldMapping](./kibana-plugin-server.savedobjectsfieldmapping.md) for documentation.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface SavedObjectsCoreFieldMapping
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [enabled](./kibana-plugin-server.savedobjectscorefieldmapping.enabled.md) | <code>boolean</code> | |
|
||||
| [fields](./kibana-plugin-server.savedobjectscorefieldmapping.fields.md) | <code>{</code><br/><code> [subfield: string]: {</code><br/><code> type: string;</code><br/><code> };</code><br/><code> }</code> | |
|
||||
| [index](./kibana-plugin-server.savedobjectscorefieldmapping.index.md) | <code>boolean</code> | |
|
||||
| [type](./kibana-plugin-server.savedobjectscorefieldmapping.type.md) | <code>string</code> | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsCoreFieldMapping](./kibana-plugin-server.savedobjectscorefieldmapping.md) > [type](./kibana-plugin-server.savedobjectscorefieldmapping.type.md)
|
||||
|
||||
## SavedObjectsCoreFieldMapping.type property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
type: string;
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsFieldMapping](./kibana-plugin-server.savedobjectsfieldmapping.md)
|
||||
|
||||
## SavedObjectsFieldMapping type
|
||||
|
||||
Describe a [saved object type mapping](./kibana-plugin-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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type SavedObjectsFieldMapping = SavedObjectsCoreFieldMapping | SavedObjectsComplexFieldMapping;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsMappingProperties](./kibana-plugin-server.savedobjectsmappingproperties.md)
|
||||
|
||||
## SavedObjectsMappingProperties interface
|
||||
|
||||
Describe the fields of a [saved object type](./kibana-plugin-server.savedobjectstypemappingdefinition.md)<!-- -->.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface SavedObjectsMappingProperties
|
||||
```
|
|
@ -17,5 +17,6 @@ export interface SavedObjectsMigrationLogger
|
|||
| --- | --- | --- |
|
||||
| [debug](./kibana-plugin-server.savedobjectsmigrationlogger.debug.md) | <code>(msg: string) => void</code> | |
|
||||
| [info](./kibana-plugin-server.savedobjectsmigrationlogger.info.md) | <code>(msg: string) => void</code> | |
|
||||
| [warn](./kibana-plugin-server.savedobjectsmigrationlogger.warn.md) | <code>(msg: string) => void</code> | |
|
||||
| [warning](./kibana-plugin-server.savedobjectsmigrationlogger.warning.md) | <code>(msg: string) => void</code> | |
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsMigrationLogger](./kibana-plugin-server.savedobjectsmigrationlogger.md) > [warn](./kibana-plugin-server.savedobjectsmigrationlogger.warn.md)
|
||||
|
||||
## SavedObjectsMigrationLogger.warn property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
warn: (msg: string) => void;
|
||||
```
|
|
@ -4,6 +4,11 @@
|
|||
|
||||
## SavedObjectsMigrationLogger.warning property
|
||||
|
||||
> Warning: This API is now obsolete.
|
||||
>
|
||||
> Use `warn` instead.
|
||||
>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
_source: any;
|
||||
_source: SavedObjectsRawDocSource;
|
||||
```
|
||||
|
|
|
@ -9,7 +9,7 @@ A raw document as represented directly in the saved object index.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface RawDoc
|
||||
export interface SavedObjectsRawDoc
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
@ -19,6 +19,6 @@ export interface RawDoc
|
|||
| [\_id](./kibana-plugin-server.savedobjectsrawdoc._id.md) | <code>string</code> | |
|
||||
| [\_primary\_term](./kibana-plugin-server.savedobjectsrawdoc._primary_term.md) | <code>number</code> | |
|
||||
| [\_seq\_no](./kibana-plugin-server.savedobjectsrawdoc._seq_no.md) | <code>number</code> | |
|
||||
| [\_source](./kibana-plugin-server.savedobjectsrawdoc._source.md) | <code>any</code> | |
|
||||
| [\_source](./kibana-plugin-server.savedobjectsrawdoc._source.md) | <code>SavedObjectsRawDocSource</code> | |
|
||||
| [\_type](./kibana-plugin-server.savedobjectsrawdoc._type.md) | <code>string</code> | |
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsSerializer](./kibana-plugin-server.savedobjectsserializer.md) > [generateRawId](./kibana-plugin-server.savedobjectsserializer.generaterawid.md)
|
||||
|
||||
## SavedObjectsSerializer.generateRawId() method
|
||||
|
||||
Given a saved object type and id, generates the compound id that is stored in the raw document.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
generateRawId(namespace: string | undefined, type: string, id?: string): string;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| namespace | <code>string | undefined</code> | |
|
||||
| type | <code>string</code> | |
|
||||
| id | <code>string</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`string`
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsSerializer](./kibana-plugin-server.savedobjectsserializer.md) > [isRawSavedObject](./kibana-plugin-server.savedobjectsserializer.israwsavedobject.md)
|
||||
|
||||
## SavedObjectsSerializer.isRawSavedObject() method
|
||||
|
||||
Determines whether or not the raw document can be converted to a saved object.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
isRawSavedObject(rawDoc: SavedObjectsRawDoc): boolean;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| rawDoc | <code>SavedObjectsRawDoc</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`boolean`
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsSerializer](./kibana-plugin-server.savedobjectsserializer.md)
|
||||
|
||||
## SavedObjectsSerializer class
|
||||
|
||||
A serializer that can be used to manually convert [raw](./kibana-plugin-server.savedobjectsrawdoc.md) or [sanitized](./kibana-plugin-server.savedobjectsanitizeddoc.md) documents to the other kind.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare class SavedObjectsSerializer
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
Serializer instances should only be created and accessed by calling [SavedObjectsServiceStart.createSerializer](./kibana-plugin-server.savedobjectsservicestart.createserializer.md)
|
||||
|
||||
The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsSerializer` class.
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Modifiers | Description |
|
||||
| --- | --- | --- |
|
||||
| [generateRawId(namespace, type, id)](./kibana-plugin-server.savedobjectsserializer.generaterawid.md) | | Given a saved object type and id, generates the compound id that is stored in the raw document. |
|
||||
| [isRawSavedObject(rawDoc)](./kibana-plugin-server.savedobjectsserializer.israwsavedobject.md) | | Determines whether or not the raw document can be converted to a saved object. |
|
||||
| [rawToSavedObject(doc)](./kibana-plugin-server.savedobjectsserializer.rawtosavedobject.md) | | Converts a document from the format that is stored in elasticsearch to the saved object client format. |
|
||||
| [savedObjectToRaw(savedObj)](./kibana-plugin-server.savedobjectsserializer.savedobjecttoraw.md) | | Converts a document from the saved object client format to the format that is stored in elasticsearch. |
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsSerializer](./kibana-plugin-server.savedobjectsserializer.md) > [rawToSavedObject](./kibana-plugin-server.savedobjectsserializer.rawtosavedobject.md)
|
||||
|
||||
## SavedObjectsSerializer.rawToSavedObject() method
|
||||
|
||||
Converts a document from the format that is stored in elasticsearch to the saved object client format.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
rawToSavedObject(doc: SavedObjectsRawDoc): SavedObjectSanitizedDoc;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| doc | <code>SavedObjectsRawDoc</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`SavedObjectSanitizedDoc`
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsSerializer](./kibana-plugin-server.savedobjectsserializer.md) > [savedObjectToRaw](./kibana-plugin-server.savedobjectsserializer.savedobjecttoraw.md)
|
||||
|
||||
## SavedObjectsSerializer.savedObjectToRaw() method
|
||||
|
||||
Converts a document from the saved object client format to the format that is stored in elasticsearch.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
savedObjectToRaw(savedObj: SavedObjectSanitizedDoc): SavedObjectsRawDoc;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| savedObj | <code>SavedObjectSanitizedDoc</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`SavedObjectsRawDoc`
|
||||
|
|
@ -20,9 +20,19 @@ When plugins access the Saved Objects client, a new client is created using the
|
|||
|
||||
## Example
|
||||
|
||||
|
||||
```ts
|
||||
import { SavedObjectsClient, CoreSetup } from 'src/core/server';
|
||||
|
||||
export class Plugin() { setup: (core: CoreSetup) =<!-- -->> { core.savedObjects.setClientFactory((<!-- -->{ request: KibanaRequest }<!-- -->) =<!-- -->> { return new SavedObjectsClient(core.savedObjects.scopedRepository(request)); }<!-- -->) } }
|
||||
export class Plugin() {
|
||||
setup: (core: CoreSetup) => {
|
||||
core.savedObjects.setClientFactory(({ request: KibanaRequest }) => {
|
||||
return new SavedObjectsClient(core.savedObjects.scopedRepository(request));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) > [createSerializer](./kibana-plugin-server.savedobjectsservicestart.createserializer.md)
|
||||
|
||||
## SavedObjectsServiceStart.createSerializer property
|
||||
|
||||
Creates a [serializer](./kibana-plugin-server.savedobjectsserializer.md) that is aware of all registered types.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
createSerializer: () => SavedObjectsSerializer;
|
||||
```
|
|
@ -18,5 +18,6 @@ export interface SavedObjectsServiceStart
|
|||
| --- | --- | --- |
|
||||
| [createInternalRepository](./kibana-plugin-server.savedobjectsservicestart.createinternalrepository.md) | <code>(extraTypes?: string[]) => ISavedObjectsRepository</code> | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. |
|
||||
| [createScopedRepository](./kibana-plugin-server.savedobjectsservicestart.createscopedrepository.md) | <code>(req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository</code> | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. |
|
||||
| [createSerializer](./kibana-plugin-server.savedobjectsservicestart.createserializer.md) | <code>() => SavedObjectsSerializer</code> | Creates a [serializer](./kibana-plugin-server.savedobjectsserializer.md) that is aware of all registered types. |
|
||||
| [getScopedClient](./kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) | <code>(req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract</code> | Creates a [Saved Objects client](./kibana-plugin-server.savedobjectsclientcontract.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. If other plugins have registered Saved Objects client wrappers, these will be applied to extend the functionality of the client.<!-- -->A client that is already scoped to the incoming request is also exposed from the route handler context see [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md)<!-- -->. |
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsType](./kibana-plugin-server.savedobjectstype.md) > [convertToAliasScript](./kibana-plugin-server.savedobjectstype.converttoaliasscript.md)
|
||||
|
||||
## SavedObjectsType.convertToAliasScript property
|
||||
|
||||
If defined, will be used to convert the type to an alias.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
convertToAliasScript?: string;
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsType](./kibana-plugin-server.savedobjectstype.md) > [hidden](./kibana-plugin-server.savedobjectstype.hidden.md)
|
||||
|
||||
## SavedObjectsType.hidden property
|
||||
|
||||
Is the type hidden by default. If true, repositories will not have access to this type unless explicitly declared as an `extraType` when creating the repository.
|
||||
|
||||
See [createInternalRepository](./kibana-plugin-server.savedobjectsservicestart.createinternalrepository.md)<!-- -->.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
hidden: boolean;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsType](./kibana-plugin-server.savedobjectstype.md) > [indexPattern](./kibana-plugin-server.savedobjectstype.indexpattern.md)
|
||||
|
||||
## SavedObjectsType.indexPattern property
|
||||
|
||||
If defined, the type instances will be stored in the given index instead of the default one.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
indexPattern?: string;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsType](./kibana-plugin-server.savedobjectstype.md) > [mappings](./kibana-plugin-server.savedobjectstype.mappings.md)
|
||||
|
||||
## SavedObjectsType.mappings property
|
||||
|
||||
The [mapping definition](./kibana-plugin-server.savedobjectstypemappingdefinition.md) for the type.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
mappings: SavedObjectsTypeMappingDefinition;
|
||||
```
|
|
@ -0,0 +1,28 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsType](./kibana-plugin-server.savedobjectstype.md)
|
||||
|
||||
## SavedObjectsType interface
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface SavedObjectsType
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
This is only internal for now, and will only be public when we expose the registerType API
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [convertToAliasScript](./kibana-plugin-server.savedobjectstype.converttoaliasscript.md) | <code>string</code> | If defined, will be used to convert the type to an alias. |
|
||||
| [hidden](./kibana-plugin-server.savedobjectstype.hidden.md) | <code>boolean</code> | Is the type hidden by default. If true, repositories will not have access to this type unless explicitly declared as an <code>extraType</code> when creating the repository.<!-- -->See [createInternalRepository](./kibana-plugin-server.savedobjectsservicestart.createinternalrepository.md)<!-- -->. |
|
||||
| [indexPattern](./kibana-plugin-server.savedobjectstype.indexpattern.md) | <code>string</code> | If defined, the type instances will be stored in the given index instead of the default one. |
|
||||
| [mappings](./kibana-plugin-server.savedobjectstype.mappings.md) | <code>SavedObjectsTypeMappingDefinition</code> | The [mapping definition](./kibana-plugin-server.savedobjectstypemappingdefinition.md) for the type. |
|
||||
| [migrations](./kibana-plugin-server.savedobjectstype.migrations.md) | <code>SavedObjectMigrationMap</code> | An optional map of [migrations](./kibana-plugin-server.savedobjectmigrationfn.md) to be used to migrate the type. |
|
||||
| [name](./kibana-plugin-server.savedobjectstype.name.md) | <code>string</code> | The name of the type, which is also used as the internal id. |
|
||||
| [namespaceAgnostic](./kibana-plugin-server.savedobjectstype.namespaceagnostic.md) | <code>boolean</code> | Is the type global (true), or namespaced (false). |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsType](./kibana-plugin-server.savedobjectstype.md) > [migrations](./kibana-plugin-server.savedobjectstype.migrations.md)
|
||||
|
||||
## SavedObjectsType.migrations property
|
||||
|
||||
An optional map of [migrations](./kibana-plugin-server.savedobjectmigrationfn.md) to be used to migrate the type.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
migrations?: SavedObjectMigrationMap;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsType](./kibana-plugin-server.savedobjectstype.md) > [name](./kibana-plugin-server.savedobjectstype.name.md)
|
||||
|
||||
## SavedObjectsType.name property
|
||||
|
||||
The name of the type, which is also used as the internal id.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
name: string;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsType](./kibana-plugin-server.savedobjectstype.md) > [namespaceAgnostic](./kibana-plugin-server.savedobjectstype.namespaceagnostic.md)
|
||||
|
||||
## SavedObjectsType.namespaceAgnostic property
|
||||
|
||||
Is the type global (true), or namespaced (false).
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
namespaceAgnostic: boolean;
|
||||
```
|
|
@ -0,0 +1,45 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsTypeMappingDefinition](./kibana-plugin-server.savedobjectstypemappingdefinition.md)
|
||||
|
||||
## SavedObjectsTypeMappingDefinition interface
|
||||
|
||||
Describe a saved object type mapping.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface SavedObjectsTypeMappingDefinition
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
```ts
|
||||
const typeDefinition: SavedObjectsTypeMappingDefinition = {
|
||||
properties: {
|
||||
enabled: {
|
||||
type: "boolean"
|
||||
},
|
||||
sendUsageFrom: {
|
||||
ignore_above: 256,
|
||||
type: "keyword"
|
||||
},
|
||||
lastReported: {
|
||||
type: "date"
|
||||
},
|
||||
lastVersionChecked: {
|
||||
ignore_above: 256,
|
||||
type: "keyword"
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [properties](./kibana-plugin-server.savedobjectstypemappingdefinition.properties.md) | <code>SavedObjectsMappingProperties</code> | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsTypeMappingDefinition](./kibana-plugin-server.savedobjectstypemappingdefinition.md) > [properties](./kibana-plugin-server.savedobjectstypemappingdefinition.properties.md)
|
||||
|
||||
## SavedObjectsTypeMappingDefinition.properties property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
properties: SavedObjectsMappingProperties;
|
||||
```
|
|
@ -197,6 +197,7 @@ export {
|
|||
SavedObjectsImportUnsupportedTypeError,
|
||||
SavedObjectsMigrationLogger,
|
||||
SavedObjectsRawDoc,
|
||||
SavedObjectSanitizedDoc,
|
||||
SavedObjectsRepositoryFactory,
|
||||
SavedObjectsResolveImportErrorsOptions,
|
||||
SavedObjectsSchema,
|
||||
|
@ -211,6 +212,15 @@ export {
|
|||
SavedObjectsRepository,
|
||||
SavedObjectsDeleteByNamespaceOptions,
|
||||
SavedObjectsIncrementCounterOptions,
|
||||
SavedObjectsComplexFieldMapping,
|
||||
SavedObjectsCoreFieldMapping,
|
||||
SavedObjectsFieldMapping,
|
||||
SavedObjectsTypeMappingDefinition,
|
||||
SavedObjectsMappingProperties,
|
||||
SavedObjectTypeRegistry,
|
||||
SavedObjectsType,
|
||||
SavedObjectMigrationMap,
|
||||
SavedObjectMigrationFn,
|
||||
} from './saved_objects';
|
||||
|
||||
export {
|
||||
|
|
|
@ -18,13 +18,18 @@
|
|||
*/
|
||||
|
||||
import { LegacyService } from './legacy_service';
|
||||
import { LegacyServiceDiscoverPlugins, LegacyServiceSetupDeps } from './types';
|
||||
import { LegacyConfig, LegacyServiceDiscoverPlugins, LegacyServiceSetupDeps } from './types';
|
||||
|
||||
type LegacyServiceMock = jest.Mocked<PublicMethodsOf<LegacyService> & { legacyId: symbol }>;
|
||||
|
||||
const createDiscoverPluginsMock = (): LegacyServiceDiscoverPlugins => ({
|
||||
pluginSpecs: [],
|
||||
uiExports: {} as any,
|
||||
uiExports: {
|
||||
savedObjectSchemas: {},
|
||||
savedObjectMappings: [],
|
||||
savedObjectMigrations: {},
|
||||
savedObjectValidations: {},
|
||||
},
|
||||
navLinks: [],
|
||||
pluginExtendedConfig: {
|
||||
get: jest.fn(),
|
||||
|
@ -34,6 +39,7 @@ const createDiscoverPluginsMock = (): LegacyServiceDiscoverPlugins => ({
|
|||
disabledPluginSpecs: [],
|
||||
settings: {},
|
||||
});
|
||||
|
||||
const createLegacyServiceMock = (): LegacyServiceMock => ({
|
||||
legacyId: Symbol(),
|
||||
discoverPlugins: jest.fn().mockResolvedValue(createDiscoverPluginsMock()),
|
||||
|
@ -42,8 +48,15 @@ const createLegacyServiceMock = (): LegacyServiceMock => ({
|
|||
stop: jest.fn(),
|
||||
});
|
||||
|
||||
const createLegacyConfigMock = (): jest.Mocked<LegacyConfig> => ({
|
||||
get: jest.fn(),
|
||||
has: jest.fn(),
|
||||
set: jest.fn(),
|
||||
});
|
||||
|
||||
export const legacyServiceMock = {
|
||||
create: createLegacyServiceMock,
|
||||
createSetupContract: (deps: LegacyServiceSetupDeps) => createLegacyServiceMock().setup(deps),
|
||||
createDiscoverPlugins: createDiscoverPluginsMock,
|
||||
createLegacyConfig: createLegacyConfigMock,
|
||||
};
|
||||
|
|
|
@ -83,7 +83,7 @@ beforeEach(() => {
|
|||
getAuthHeaders: () => undefined,
|
||||
} as any,
|
||||
},
|
||||
savedObjects: savedObjectsServiceMock.createSetupContract(),
|
||||
savedObjects: savedObjectsServiceMock.createInternalSetupContract(),
|
||||
plugins: {
|
||||
contracts: new Map([['plugin-id', 'plugin-value']]),
|
||||
uiPlugins: {
|
||||
|
@ -101,7 +101,7 @@ beforeEach(() => {
|
|||
startDeps = {
|
||||
core: {
|
||||
capabilities: capabilitiesServiceMock.createStartContract(),
|
||||
savedObjects: savedObjectsServiceMock.createStartContract(),
|
||||
savedObjects: savedObjectsServiceMock.createInternalStartContract(),
|
||||
uiSettings: uiSettingsServiceMock.createStartContract(),
|
||||
plugins: { contracts: new Map() },
|
||||
},
|
||||
|
|
|
@ -262,6 +262,7 @@ export class LegacyService implements CoreService {
|
|||
getScopedClient: startDeps.core.savedObjects.getScopedClient,
|
||||
createScopedRepository: startDeps.core.savedObjects.createScopedRepository,
|
||||
createInternalRepository: startDeps.core.savedObjects.createInternalRepository,
|
||||
createSerializer: startDeps.core.savedObjects.createSerializer,
|
||||
},
|
||||
uiSettings: { asScopedToClient: startDeps.core.uiSettings.asScopedToClient },
|
||||
};
|
||||
|
@ -328,6 +329,7 @@ export class LegacyService implements CoreService {
|
|||
__internals: {
|
||||
hapiServer: setupDeps.core.http.server,
|
||||
kibanaMigrator: startDeps.core.savedObjects.migrator,
|
||||
typeRegistry: startDeps.core.savedObjects.typeRegistry,
|
||||
uiPlugins: setupDeps.core.plugins.uiPlugins,
|
||||
elasticsearch: setupDeps.core.elasticsearch,
|
||||
rendering: setupDeps.core.rendering,
|
||||
|
|
|
@ -38,6 +38,7 @@ export { httpServiceMock } from './http/http_service.mock';
|
|||
export { loggingServiceMock } from './logging/logging_service.mock';
|
||||
export { savedObjectsClientMock } from './saved_objects/service/saved_objects_client.mock';
|
||||
export { savedObjectsRepositoryMock } from './saved_objects/service/lib/repository.mock';
|
||||
export { typeRegistryMock as savedObjectsTypeRegistryMock } from './saved_objects/saved_objects_type_registry.mock';
|
||||
export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
|
||||
import { uuidServiceMock } from './uuid/uuid_service.mock';
|
||||
|
||||
|
@ -112,12 +113,19 @@ function createCoreSetupMock() {
|
|||
const uiSettingsMock = {
|
||||
register: uiSettingsServiceMock.createSetupContract().register,
|
||||
};
|
||||
|
||||
const savedObjectsService = savedObjectsServiceMock.createSetupContract();
|
||||
const savedObjectMock: jest.Mocked<CoreSetup['savedObjects']> = {
|
||||
addClientWrapper: savedObjectsService.addClientWrapper,
|
||||
setClientFactoryProvider: savedObjectsService.setClientFactoryProvider,
|
||||
};
|
||||
|
||||
const mock: CoreSetupMockType = {
|
||||
capabilities: capabilitiesServiceMock.createSetupContract(),
|
||||
context: contextServiceMock.createSetupContract(),
|
||||
elasticsearch: elasticsearchServiceMock.createSetup(),
|
||||
http: httpMock,
|
||||
savedObjects: savedObjectsServiceMock.createSetupContract(),
|
||||
savedObjects: savedObjectMock,
|
||||
uiSettings: uiSettingsMock,
|
||||
uuid: uuidServiceMock.createSetupContract(),
|
||||
getStartServices: jest
|
||||
|
@ -145,7 +153,7 @@ function createInternalCoreSetupMock() {
|
|||
elasticsearch: elasticsearchServiceMock.createInternalSetup(),
|
||||
http: httpServiceMock.createSetupContract(),
|
||||
uiSettings: uiSettingsServiceMock.createSetupContract(),
|
||||
savedObjects: savedObjectsServiceMock.createSetupContract(),
|
||||
savedObjects: savedObjectsServiceMock.createInternalSetupContract(),
|
||||
uuid: uuidServiceMock.createSetupContract(),
|
||||
};
|
||||
return setupDeps;
|
||||
|
@ -154,7 +162,7 @@ function createInternalCoreSetupMock() {
|
|||
function createInternalCoreStartMock() {
|
||||
const startDeps: InternalCoreStart = {
|
||||
capabilities: capabilitiesServiceMock.createStartContract(),
|
||||
savedObjects: savedObjectsServiceMock.createStartContract(),
|
||||
savedObjects: savedObjectsServiceMock.createInternalStartContract(),
|
||||
uiSettings: uiSettingsServiceMock.createStartContract(),
|
||||
};
|
||||
return startDeps;
|
||||
|
|
|
@ -205,6 +205,7 @@ export function createPluginStartContext<TPlugin, TPluginDependencies>(
|
|||
getScopedClient: deps.savedObjects.getScopedClient,
|
||||
createInternalRepository: deps.savedObjects.createInternalRepository,
|
||||
createScopedRepository: deps.savedObjects.createScopedRepository,
|
||||
createSerializer: deps.savedObjects.createSerializer,
|
||||
},
|
||||
uiSettings: {
|
||||
asScopedToClient: deps.uiSettings.asScopedToClient,
|
||||
|
|
144
src/core/server/saved_objects/__snapshots__/utils.test.ts.snap
generated
Normal file
144
src/core/server/saved_objects/__snapshots__/utils.test.ts.snap
generated
Normal file
|
@ -0,0 +1,144 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`convertLegacyTypes converts the legacy mappings using default values if no schemas are specified 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"convertToAliasScript": undefined,
|
||||
"hidden": false,
|
||||
"indexPattern": undefined,
|
||||
"mappings": Object {
|
||||
"properties": Object {
|
||||
"fieldA": Object {
|
||||
"type": "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
"migrations": Object {},
|
||||
"name": "typeA",
|
||||
"namespaceAgnostic": false,
|
||||
},
|
||||
Object {
|
||||
"convertToAliasScript": undefined,
|
||||
"hidden": false,
|
||||
"indexPattern": undefined,
|
||||
"mappings": Object {
|
||||
"properties": Object {
|
||||
"fieldB": Object {
|
||||
"type": "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
"migrations": Object {},
|
||||
"name": "typeB",
|
||||
"namespaceAgnostic": false,
|
||||
},
|
||||
Object {
|
||||
"convertToAliasScript": undefined,
|
||||
"hidden": false,
|
||||
"indexPattern": undefined,
|
||||
"mappings": Object {
|
||||
"properties": Object {
|
||||
"fieldC": Object {
|
||||
"type": "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
"migrations": Object {},
|
||||
"name": "typeC",
|
||||
"namespaceAgnostic": false,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`convertLegacyTypes merges everything when all are present 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"convertToAliasScript": undefined,
|
||||
"hidden": true,
|
||||
"indexPattern": "myIndex",
|
||||
"mappings": Object {
|
||||
"properties": Object {
|
||||
"fieldA": Object {
|
||||
"type": "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
"migrations": Object {
|
||||
"1.0.0": [MockFunction],
|
||||
"2.0.4": [MockFunction],
|
||||
},
|
||||
"name": "typeA",
|
||||
"namespaceAgnostic": true,
|
||||
},
|
||||
Object {
|
||||
"convertToAliasScript": "some alias script",
|
||||
"hidden": false,
|
||||
"indexPattern": undefined,
|
||||
"mappings": Object {
|
||||
"properties": Object {
|
||||
"anotherFieldB": Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
"fieldB": Object {
|
||||
"type": "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
"migrations": Object {},
|
||||
"name": "typeB",
|
||||
"namespaceAgnostic": false,
|
||||
},
|
||||
Object {
|
||||
"convertToAliasScript": undefined,
|
||||
"hidden": false,
|
||||
"indexPattern": undefined,
|
||||
"mappings": Object {
|
||||
"properties": Object {
|
||||
"fieldC": Object {
|
||||
"type": "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
"migrations": Object {
|
||||
"1.5.3": [MockFunction],
|
||||
},
|
||||
"name": "typeC",
|
||||
"namespaceAgnostic": false,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`convertLegacyTypes merges the mappings and the schema to create the type when schema exists for the type 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"convertToAliasScript": undefined,
|
||||
"hidden": true,
|
||||
"indexPattern": "fooBar",
|
||||
"mappings": Object {
|
||||
"properties": Object {
|
||||
"fieldA": Object {
|
||||
"type": "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
"migrations": Object {},
|
||||
"name": "typeA",
|
||||
"namespaceAgnostic": true,
|
||||
},
|
||||
Object {
|
||||
"convertToAliasScript": undefined,
|
||||
"hidden": false,
|
||||
"indexPattern": undefined,
|
||||
"mappings": Object {
|
||||
"properties": Object {
|
||||
"fieldC": Object {
|
||||
"type": "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
"migrations": Object {},
|
||||
"name": "typeC",
|
||||
"namespaceAgnostic": false,
|
||||
},
|
||||
]
|
||||
`;
|
|
@ -31,7 +31,12 @@ export {
|
|||
SavedObjectsExportResultDetails,
|
||||
} from './export';
|
||||
|
||||
export { SavedObjectsSerializer, RawDoc as SavedObjectsRawDoc } from './serialization';
|
||||
export {
|
||||
SavedObjectsSerializer,
|
||||
SavedObjectsRawDoc,
|
||||
SavedObjectSanitizedDoc,
|
||||
SavedObjectUnsanitizedDoc,
|
||||
} from './serialization';
|
||||
|
||||
export { SavedObjectsMigrationLogger } from './migrations/core/migration_logger';
|
||||
|
||||
|
@ -50,4 +55,18 @@ export {
|
|||
SavedObjectsDeleteByNamespaceOptions,
|
||||
} from './service/lib/repository';
|
||||
|
||||
export {
|
||||
SavedObjectsCoreFieldMapping,
|
||||
SavedObjectsComplexFieldMapping,
|
||||
SavedObjectsFieldMapping,
|
||||
SavedObjectsMappingProperties,
|
||||
SavedObjectsTypeMappingDefinition,
|
||||
SavedObjectsTypeMappingDefinitions,
|
||||
} from './mappings';
|
||||
|
||||
export { SavedObjectMigrationMap, SavedObjectMigrationFn } from './migrations';
|
||||
|
||||
export { SavedObjectsType } from './types';
|
||||
|
||||
export { config } from './saved_objects_config';
|
||||
export { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objects_type_registry';
|
||||
|
|
|
@ -18,9 +18,12 @@
|
|||
*/
|
||||
export { getTypes, getProperty, getRootProperties, getRootPropertiesObjects } from './lib';
|
||||
export {
|
||||
FieldMapping,
|
||||
MappingMeta,
|
||||
MappingProperties,
|
||||
SavedObjectsComplexFieldMapping,
|
||||
SavedObjectsCoreFieldMapping,
|
||||
SavedObjectsTypeMappingDefinition,
|
||||
SavedObjectsTypeMappingDefinitions,
|
||||
SavedObjectsMappingProperties,
|
||||
SavedObjectsFieldMapping,
|
||||
IndexMappingMeta,
|
||||
IndexMapping,
|
||||
SavedObjectsMapping,
|
||||
} from './types';
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { FieldMapping, IndexMapping } from '../types';
|
||||
import { SavedObjectsFieldMapping, IndexMapping } from '../types';
|
||||
import { getProperty } from './get_property';
|
||||
|
||||
const MAPPINGS = {
|
||||
|
@ -47,7 +47,7 @@ const MAPPINGS = {
|
|||
},
|
||||
};
|
||||
|
||||
function runTest(key: string | string[], mapping: IndexMapping | FieldMapping) {
|
||||
function runTest(key: string | string[], mapping: IndexMapping | SavedObjectsFieldMapping) {
|
||||
expect(typeof key === 'string' || Array.isArray(key)).toBeTruthy();
|
||||
expect(typeof mapping).toBe('object');
|
||||
|
||||
|
|
|
@ -18,15 +18,15 @@
|
|||
*/
|
||||
|
||||
import toPath from 'lodash/internal/toPath';
|
||||
import { CoreFieldMapping, FieldMapping, IndexMapping } from '../types';
|
||||
import { SavedObjectsCoreFieldMapping, SavedObjectsFieldMapping, IndexMapping } from '../types';
|
||||
|
||||
function getPropertyMappingFromObjectMapping(
|
||||
mapping: IndexMapping | FieldMapping,
|
||||
mapping: IndexMapping | SavedObjectsFieldMapping,
|
||||
path: string[]
|
||||
): FieldMapping | undefined {
|
||||
): SavedObjectsFieldMapping | undefined {
|
||||
const props =
|
||||
(mapping && (mapping as IndexMapping).properties) ||
|
||||
(mapping && (mapping as CoreFieldMapping).fields);
|
||||
(mapping && (mapping as SavedObjectsCoreFieldMapping).fields);
|
||||
|
||||
if (!props) {
|
||||
return undefined;
|
||||
|
@ -39,6 +39,9 @@ function getPropertyMappingFromObjectMapping(
|
|||
}
|
||||
}
|
||||
|
||||
export function getProperty(mappings: IndexMapping | FieldMapping, path: string | string[]) {
|
||||
export function getProperty(
|
||||
mappings: IndexMapping | SavedObjectsFieldMapping,
|
||||
path: string | string[]
|
||||
) {
|
||||
return getPropertyMappingFromObjectMapping(mappings, toPath(path));
|
||||
}
|
||||
|
|
|
@ -17,7 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ComplexFieldMapping, IndexMapping, MappingProperties } from '../types';
|
||||
import {
|
||||
SavedObjectsComplexFieldMapping,
|
||||
IndexMapping,
|
||||
SavedObjectsMappingProperties,
|
||||
} from '../types';
|
||||
import { getRootProperties } from './get_root_properties';
|
||||
|
||||
/**
|
||||
|
@ -43,10 +47,10 @@ export function getRootPropertiesObjects(mappings: IndexMapping) {
|
|||
// we consider the existence of the properties or type of object to designate that this is an object datatype
|
||||
if (
|
||||
!blacklist.includes(key) &&
|
||||
((value as ComplexFieldMapping).properties || value.type === 'object')
|
||||
((value as SavedObjectsComplexFieldMapping).properties || value.type === 'object')
|
||||
) {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {} as MappingProperties);
|
||||
}, {} as SavedObjectsMappingProperties);
|
||||
}
|
||||
|
|
|
@ -17,10 +17,119 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
// FieldMapping isn't 1:1 with the options available,
|
||||
// modify as needed.
|
||||
export interface CoreFieldMapping {
|
||||
/**
|
||||
* Describe a saved object type mapping.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const typeDefinition: SavedObjectsTypeMappingDefinition = {
|
||||
* properties: {
|
||||
* enabled: {
|
||||
* type: "boolean"
|
||||
* },
|
||||
* sendUsageFrom: {
|
||||
* ignore_above: 256,
|
||||
* type: "keyword"
|
||||
* },
|
||||
* lastReported: {
|
||||
* type: "date"
|
||||
* },
|
||||
* lastVersionChecked: {
|
||||
* ignore_above: 256,
|
||||
* type: "keyword"
|
||||
* },
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface SavedObjectsTypeMappingDefinition {
|
||||
properties: SavedObjectsMappingProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of {@link SavedObjectsTypeMappingDefinition | saved object type mappings}
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const mappings: SavedObjectsTypeMappingDefinitions = {
|
||||
* someType: {
|
||||
* properties: {
|
||||
* enabled: {
|
||||
* type: "boolean"
|
||||
* },
|
||||
* field: {
|
||||
* type: "keyword"
|
||||
* },
|
||||
* },
|
||||
* },
|
||||
* anotherType: {
|
||||
* properties: {
|
||||
* enabled: {
|
||||
* type: "boolean"
|
||||
* },
|
||||
* lastReported: {
|
||||
* type: "date"
|
||||
* },
|
||||
* },
|
||||
* },
|
||||
|
||||
* }
|
||||
* ```
|
||||
* @remark This is the format for the legacy `mappings.json` savedObject mapping file.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export interface SavedObjectsTypeMappingDefinitions {
|
||||
[type: string]: SavedObjectsTypeMappingDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the fields of a {@link SavedObjectsTypeMappingDefinition | saved object type}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface SavedObjectsMappingProperties {
|
||||
[field: string]: SavedObjectsFieldMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe a {@link SavedObjectsTypeMappingDefinition | saved object type mapping} field.
|
||||
*
|
||||
* Please refer to {@link https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html | elasticsearch documentation}
|
||||
* For the mapping documentation
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SavedObjectsFieldMapping =
|
||||
| SavedObjectsCoreFieldMapping
|
||||
| SavedObjectsComplexFieldMapping;
|
||||
|
||||
/** @internal */
|
||||
export interface IndexMapping {
|
||||
dynamic?: string;
|
||||
properties: SavedObjectsMappingProperties;
|
||||
_meta?: IndexMappingMeta;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface IndexMappingMeta {
|
||||
// A dictionary of key -> md5 hash (e.g. 'dashboard': '24234qdfa3aefa3wa')
|
||||
// with each key being a root-level mapping property, and each value being
|
||||
// the md5 hash of that mapping's value when the index was created.
|
||||
migrationMappingPropertyHashes?: { [k: string]: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link SavedObjectsFieldMapping} for documentation.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface SavedObjectsCoreFieldMapping {
|
||||
type: string;
|
||||
index?: boolean;
|
||||
enabled?: boolean;
|
||||
fields?: {
|
||||
[subfield: string]: {
|
||||
type: string;
|
||||
|
@ -28,36 +137,13 @@ export interface CoreFieldMapping {
|
|||
};
|
||||
}
|
||||
|
||||
// FieldMapping isn't 1:1 with the options available,
|
||||
// modify as needed.
|
||||
export interface ComplexFieldMapping {
|
||||
/**
|
||||
* See {@link SavedObjectsFieldMapping} for documentation.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface SavedObjectsComplexFieldMapping {
|
||||
dynamic?: string;
|
||||
type?: string;
|
||||
properties: MappingProperties;
|
||||
}
|
||||
|
||||
export type FieldMapping = CoreFieldMapping | ComplexFieldMapping;
|
||||
|
||||
export interface MappingProperties {
|
||||
[field: string]: FieldMapping;
|
||||
}
|
||||
|
||||
export interface SavedObjectsMapping {
|
||||
pluginId: string;
|
||||
properties: MappingProperties;
|
||||
}
|
||||
|
||||
export interface MappingMeta {
|
||||
// A dictionary of key -> md5 hash (e.g. 'dashboard': '24234qdfa3aefa3wa')
|
||||
// with each key being a root-level mapping property, and each value being
|
||||
// the md5 hash of that mapping's value when the index was created.
|
||||
migrationMappingPropertyHashes?: { [k: string]: string };
|
||||
}
|
||||
|
||||
// IndexMapping isn't 1:1 with the options available,
|
||||
// modify as needed.
|
||||
export interface IndexMapping {
|
||||
dynamic?: string;
|
||||
properties: MappingProperties;
|
||||
_meta?: MappingMeta;
|
||||
properties: SavedObjectsMappingProperties;
|
||||
}
|
||||
|
|
|
@ -27,21 +27,19 @@ describe('buildActiveMappings', () => {
|
|||
bbb: { type: 'long' },
|
||||
};
|
||||
|
||||
expect(buildActiveMappings({ properties })).toMatchSnapshot();
|
||||
expect(buildActiveMappings(properties)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('disallows duplicate mappings', () => {
|
||||
const properties = { type: { type: 'long' } };
|
||||
|
||||
expect(() => buildActiveMappings({ properties })).toThrow(
|
||||
/Cannot redefine core mapping \"type\"/
|
||||
);
|
||||
expect(() => buildActiveMappings(properties)).toThrow(/Cannot redefine core mapping \"type\"/);
|
||||
});
|
||||
|
||||
test('disallows mappings with leading underscore', () => {
|
||||
const properties = { _hm: { type: 'keyword' } };
|
||||
|
||||
expect(() => buildActiveMappings({ properties })).toThrow(
|
||||
expect(() => buildActiveMappings(properties)).toThrow(
|
||||
/Invalid mapping \"_hm\"\. Mappings cannot start with _/
|
||||
);
|
||||
});
|
||||
|
@ -53,7 +51,7 @@ describe('buildActiveMappings', () => {
|
|||
ccc: { fields: { b: { type: 'text' }, a: { type: 'text' } }, type: 'keyword' },
|
||||
};
|
||||
|
||||
const mappings = buildActiveMappings({ properties });
|
||||
const mappings = buildActiveMappings(properties);
|
||||
const hashes = mappings._meta!.migrationMappingPropertyHashes!;
|
||||
|
||||
expect(hashes.aaa).toBeDefined();
|
||||
|
|
|
@ -22,31 +22,31 @@
|
|||
*/
|
||||
|
||||
import crypto from 'crypto';
|
||||
import _ from 'lodash';
|
||||
import { IndexMapping, MappingProperties } from './../../mappings';
|
||||
import { cloneDeep, mapValues } from 'lodash';
|
||||
import {
|
||||
IndexMapping,
|
||||
SavedObjectsMappingProperties,
|
||||
SavedObjectsTypeMappingDefinitions,
|
||||
} from './../../mappings';
|
||||
|
||||
/**
|
||||
* Creates an index mapping with the core properties required by saved object
|
||||
* indices, as well as the specified additional properties.
|
||||
*
|
||||
* @param {Opts} opts
|
||||
* @prop {MappingDefinition} properties - The mapping's properties
|
||||
* @returns {IndexMapping}
|
||||
* @param typeDefinitions - the type definitions to build mapping from.
|
||||
*/
|
||||
export function buildActiveMappings({
|
||||
properties,
|
||||
}: {
|
||||
properties: MappingProperties;
|
||||
}): IndexMapping {
|
||||
export function buildActiveMappings(
|
||||
typeDefinitions: SavedObjectsTypeMappingDefinitions | SavedObjectsMappingProperties
|
||||
): IndexMapping {
|
||||
const mapping = defaultMapping();
|
||||
|
||||
properties = validateAndMerge(mapping.properties, properties);
|
||||
const mergedProperties = validateAndMerge(mapping.properties, typeDefinitions);
|
||||
|
||||
return _.cloneDeep({
|
||||
return cloneDeep({
|
||||
...mapping,
|
||||
properties,
|
||||
properties: mergedProperties,
|
||||
_meta: {
|
||||
migrationMappingPropertyHashes: md5Values(properties),
|
||||
migrationMappingPropertyHashes: md5Values(mergedProperties),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ function canonicalStringify(obj: any): string {
|
|||
|
||||
// Convert an object's values to md5 hash strings
|
||||
function md5Values(obj: any) {
|
||||
return _.mapValues(obj, md5Object);
|
||||
return mapValues(obj, md5Object);
|
||||
}
|
||||
|
||||
// If something exists in actual, but is missing in expected, we don't
|
||||
|
@ -171,12 +171,14 @@ function defaultMapping(): IndexMapping {
|
|||
};
|
||||
}
|
||||
|
||||
function validateAndMerge(dest: MappingProperties, source: MappingProperties) {
|
||||
function validateAndMerge(
|
||||
dest: SavedObjectsMappingProperties,
|
||||
source: SavedObjectsTypeMappingDefinitions | SavedObjectsMappingProperties
|
||||
) {
|
||||
Object.keys(source).forEach(k => {
|
||||
if (k.startsWith('_')) {
|
||||
throw new Error(`Invalid mapping "${k}". Mappings cannot start with _.`);
|
||||
}
|
||||
|
||||
if (dest.hasOwnProperty(k)) {
|
||||
throw new Error(`Cannot redefine core mapping "${k}".`);
|
||||
}
|
||||
|
|
|
@ -18,20 +18,30 @@
|
|||
*/
|
||||
|
||||
import { createIndexMap } from './build_index_map';
|
||||
import { ObjectToConfigAdapter } from '../../../config';
|
||||
import { SavedObjectsSchema } from '../../schema';
|
||||
import { LegacyConfig } from '../../../legacy';
|
||||
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
|
||||
import { SavedObjectsType } from '../../types';
|
||||
|
||||
const config = (new ObjectToConfigAdapter({}) as unknown) as LegacyConfig;
|
||||
const createRegistry = (...types: Array<Partial<SavedObjectsType>>) => {
|
||||
const registry = new SavedObjectTypeRegistry();
|
||||
types.forEach(type =>
|
||||
registry.registerType({
|
||||
name: 'unknown',
|
||||
namespaceAgnostic: false,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
migrations: {},
|
||||
...type,
|
||||
})
|
||||
);
|
||||
return registry;
|
||||
};
|
||||
|
||||
test('mappings without index pattern goes to default index', () => {
|
||||
const result = createIndexMap({
|
||||
config,
|
||||
kibanaIndexName: '.kibana',
|
||||
schema: new SavedObjectsSchema({
|
||||
type1: {
|
||||
isNamespaceAgnostic: false,
|
||||
},
|
||||
registry: createRegistry({
|
||||
name: 'type1',
|
||||
namespaceAgnostic: false,
|
||||
}),
|
||||
indexMap: {
|
||||
type1: {
|
||||
|
@ -60,13 +70,11 @@ test('mappings without index pattern goes to default index', () => {
|
|||
|
||||
test(`mappings with custom index pattern doesn't go to default index`, () => {
|
||||
const result = createIndexMap({
|
||||
config,
|
||||
kibanaIndexName: '.kibana',
|
||||
schema: new SavedObjectsSchema({
|
||||
type1: {
|
||||
isNamespaceAgnostic: false,
|
||||
indexPattern: '.other_kibana',
|
||||
},
|
||||
registry: createRegistry({
|
||||
name: 'type1',
|
||||
namespaceAgnostic: false,
|
||||
indexPattern: '.other_kibana',
|
||||
}),
|
||||
indexMap: {
|
||||
type1: {
|
||||
|
@ -95,14 +103,12 @@ test(`mappings with custom index pattern doesn't go to default index`, () => {
|
|||
|
||||
test('creating a script gets added to the index pattern', () => {
|
||||
const result = createIndexMap({
|
||||
config,
|
||||
kibanaIndexName: '.kibana',
|
||||
schema: new SavedObjectsSchema({
|
||||
type1: {
|
||||
isNamespaceAgnostic: false,
|
||||
indexPattern: '.other_kibana',
|
||||
convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id`,
|
||||
},
|
||||
registry: createRegistry({
|
||||
name: 'type1',
|
||||
namespaceAgnostic: false,
|
||||
indexPattern: '.other_kibana',
|
||||
convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id`,
|
||||
}),
|
||||
indexMap: {
|
||||
type1: {
|
||||
|
@ -132,16 +138,19 @@ test('creating a script gets added to the index pattern', () => {
|
|||
|
||||
test('throws when two scripts are defined for an index pattern', () => {
|
||||
const defaultIndex = '.kibana';
|
||||
const schema = new SavedObjectsSchema({
|
||||
type1: {
|
||||
isNamespaceAgnostic: false,
|
||||
const registry = createRegistry(
|
||||
{
|
||||
name: 'type1',
|
||||
namespaceAgnostic: false,
|
||||
convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id`,
|
||||
},
|
||||
type2: {
|
||||
isNamespaceAgnostic: false,
|
||||
{
|
||||
name: 'type2',
|
||||
namespaceAgnostic: false,
|
||||
convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id`,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const indexMap = {
|
||||
type1: {
|
||||
properties: {
|
||||
|
@ -160,9 +169,8 @@ test('throws when two scripts are defined for an index pattern', () => {
|
|||
};
|
||||
expect(() =>
|
||||
createIndexMap({
|
||||
config,
|
||||
kibanaIndexName: defaultIndex,
|
||||
schema,
|
||||
registry,
|
||||
indexMap,
|
||||
})
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
|
|
|
@ -17,39 +17,32 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { MappingProperties } from '../../mappings';
|
||||
import { SavedObjectsSchema } from '../../schema';
|
||||
import { LegacyConfig } from '../../../legacy';
|
||||
import { SavedObjectsTypeMappingDefinitions } from '../../mappings';
|
||||
import { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry';
|
||||
|
||||
export interface CreateIndexMapOptions {
|
||||
config: LegacyConfig;
|
||||
kibanaIndexName: string;
|
||||
schema: SavedObjectsSchema;
|
||||
indexMap: MappingProperties;
|
||||
registry: ISavedObjectTypeRegistry;
|
||||
indexMap: SavedObjectsTypeMappingDefinitions;
|
||||
}
|
||||
|
||||
export interface IndexMap {
|
||||
[index: string]: {
|
||||
typeMappings: MappingProperties;
|
||||
typeMappings: SavedObjectsTypeMappingDefinitions;
|
||||
script?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* This file contains logic to convert savedObjectSchemas into a dictonary of indexes and documents
|
||||
* This file contains logic to convert savedObjectSchemas into a dictionary of indexes and documents
|
||||
*/
|
||||
export function createIndexMap({
|
||||
/** @deprecated Remove once savedObjectsSchemas are exposed from Core */
|
||||
config,
|
||||
kibanaIndexName,
|
||||
schema,
|
||||
indexMap,
|
||||
}: CreateIndexMapOptions) {
|
||||
export function createIndexMap({ kibanaIndexName, registry, indexMap }: CreateIndexMapOptions) {
|
||||
const map: IndexMap = {};
|
||||
Object.keys(indexMap).forEach(type => {
|
||||
const script = schema.getConvertToAliasScript(type);
|
||||
const typeDef = registry.getType(type);
|
||||
const script = typeDef?.convertToAliasScript;
|
||||
// Defaults to kibanaIndexName if indexPattern isn't defined
|
||||
const indexPattern = schema.getIndexForType(config, type) || kibanaIndexName;
|
||||
const indexPattern = typeDef?.indexPattern || kibanaIndexName;
|
||||
if (!map.hasOwnProperty(indexPattern as string)) {
|
||||
map[indexPattern] = { typeMappings: {} };
|
||||
}
|
||||
|
|
|
@ -18,41 +18,49 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { RawSavedObjectDoc } from '../../serialization';
|
||||
import { SavedObjectUnsanitizedDoc } from '../../serialization';
|
||||
import { DocumentMigrator } from './document_migrator';
|
||||
import { loggingServiceMock } from '../../../logging/logging_service.mock';
|
||||
import { SavedObjectsType } from '../../types';
|
||||
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
|
||||
|
||||
const mockLoggerFactory = loggingServiceMock.create();
|
||||
const mockLogger = mockLoggerFactory.get('mock logger');
|
||||
|
||||
const createRegistry = (...types: Array<Partial<SavedObjectsType>>) => {
|
||||
const registry = new SavedObjectTypeRegistry();
|
||||
types.forEach(type =>
|
||||
registry.registerType({
|
||||
name: 'unknown',
|
||||
namespaceAgnostic: false,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
migrations: {},
|
||||
...type,
|
||||
})
|
||||
);
|
||||
return registry;
|
||||
};
|
||||
|
||||
describe('DocumentMigrator', () => {
|
||||
function testOpts() {
|
||||
return {
|
||||
kibanaVersion: '25.2.3',
|
||||
migrations: {},
|
||||
typeRegistry: createRegistry(),
|
||||
validateDoc: _.noop,
|
||||
log: mockLogger,
|
||||
};
|
||||
}
|
||||
|
||||
it('validates the migration definition', () => {
|
||||
const invalidDefinition: any = {
|
||||
kibanaVersion: '3.2.3',
|
||||
migrations: 'hello',
|
||||
validateDoc: _.noop,
|
||||
};
|
||||
expect(() => new DocumentMigrator(invalidDefinition)).toThrow(
|
||||
/Migration definition should be an object/i
|
||||
);
|
||||
});
|
||||
|
||||
it('validates individual migration definitions', () => {
|
||||
const invalidDefinition: any = {
|
||||
const invalidDefinition = {
|
||||
kibanaVersion: '3.2.3',
|
||||
migrations: {
|
||||
foo: _.noop,
|
||||
},
|
||||
typeRegistry: createRegistry({
|
||||
name: 'foo',
|
||||
migrations: _.noop as any,
|
||||
}),
|
||||
validateDoc: _.noop,
|
||||
log: mockLogger,
|
||||
};
|
||||
expect(() => new DocumentMigrator(invalidDefinition)).toThrow(
|
||||
/Migration for type foo should be an object/i
|
||||
|
@ -60,14 +68,16 @@ describe('DocumentMigrator', () => {
|
|||
});
|
||||
|
||||
it('validates individual migration semvers', () => {
|
||||
const invalidDefinition: any = {
|
||||
const invalidDefinition = {
|
||||
kibanaVersion: '3.2.3',
|
||||
migrations: {
|
||||
foo: {
|
||||
bar: _.noop,
|
||||
typeRegistry: createRegistry({
|
||||
name: 'foo',
|
||||
migrations: {
|
||||
bar: doc => doc,
|
||||
},
|
||||
},
|
||||
}),
|
||||
validateDoc: _.noop,
|
||||
log: mockLogger,
|
||||
};
|
||||
expect(() => new DocumentMigrator(invalidDefinition)).toThrow(
|
||||
/Expected all properties to be semvers/i
|
||||
|
@ -75,14 +85,16 @@ describe('DocumentMigrator', () => {
|
|||
});
|
||||
|
||||
it('validates the migration function', () => {
|
||||
const invalidDefinition: any = {
|
||||
const invalidDefinition = {
|
||||
kibanaVersion: '3.2.3',
|
||||
migrations: {
|
||||
foo: {
|
||||
'1.2.3': 23,
|
||||
typeRegistry: createRegistry({
|
||||
name: 'foo',
|
||||
migrations: {
|
||||
'1.2.3': 23 as any,
|
||||
},
|
||||
},
|
||||
}),
|
||||
validateDoc: _.noop,
|
||||
log: mockLogger,
|
||||
};
|
||||
expect(() => new DocumentMigrator(invalidDefinition)).toThrow(
|
||||
/expected a function, but got 23/i
|
||||
|
@ -92,11 +104,12 @@ describe('DocumentMigrator', () => {
|
|||
it('migrates type and attributes', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
user: {
|
||||
typeRegistry: createRegistry({
|
||||
name: 'user',
|
||||
migrations: {
|
||||
'1.2.3': setAttr('attributes.name', 'Chris'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const actual = migrator.migrate({
|
||||
id: 'me',
|
||||
|
@ -115,14 +128,15 @@ describe('DocumentMigrator', () => {
|
|||
it(`doesn't mutate the original document`, () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
user: {
|
||||
'1.2.3': (doc: RawSavedObjectDoc) => {
|
||||
typeRegistry: createRegistry({
|
||||
name: 'user',
|
||||
migrations: {
|
||||
'1.2.3': doc => {
|
||||
_.set(doc, 'attributes.name', 'Mike');
|
||||
return doc;
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const originalDoc = {
|
||||
id: 'me',
|
||||
|
@ -138,11 +152,12 @@ describe('DocumentMigrator', () => {
|
|||
it('migrates meta properties', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
acl: {
|
||||
typeRegistry: createRegistry({
|
||||
name: 'acl',
|
||||
migrations: {
|
||||
'2.3.5': setAttr('acl', 'admins-only,sucka!'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const actual = migrator.migrate({
|
||||
id: 'me',
|
||||
|
@ -163,11 +178,26 @@ describe('DocumentMigrator', () => {
|
|||
it('does not apply migrations to unrelated docs', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
aaa: { '1.0.0': setAttr('aaa', 'A') },
|
||||
bbb: { '1.0.0': setAttr('bbb', 'B') },
|
||||
ccc: { '1.0.0': setAttr('ccc', 'C') },
|
||||
},
|
||||
typeRegistry: createRegistry(
|
||||
{
|
||||
name: 'aaa',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('aaa', 'A'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bbb',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('bbb', 'B'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ccc',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('ccc', 'C'),
|
||||
},
|
||||
}
|
||||
),
|
||||
});
|
||||
const actual = migrator.migrate({
|
||||
id: 'me',
|
||||
|
@ -185,11 +215,26 @@ describe('DocumentMigrator', () => {
|
|||
it('assumes documents w/ undefined migrationVersion are up to date', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
user: { '1.0.0': setAttr('aaa', 'A') },
|
||||
bbb: { '2.3.4': setAttr('bbb', 'B') },
|
||||
ccc: { '1.0.0': setAttr('ccc', 'C') },
|
||||
},
|
||||
typeRegistry: createRegistry(
|
||||
{
|
||||
name: 'user',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('aaa', 'A'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bbb',
|
||||
migrations: {
|
||||
'2.3.4': setAttr('bbb', 'B'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ccc',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('ccc', 'C'),
|
||||
},
|
||||
}
|
||||
),
|
||||
});
|
||||
const actual = migrator.migrate({
|
||||
id: 'me',
|
||||
|
@ -212,13 +257,14 @@ describe('DocumentMigrator', () => {
|
|||
it('only applies migrations that are more recent than the doc', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
dog: {
|
||||
typeRegistry: createRegistry({
|
||||
name: 'dog',
|
||||
migrations: {
|
||||
'1.2.3': setAttr('attributes.a', 'A'),
|
||||
'1.2.4': setAttr('attributes.b', 'B'),
|
||||
'2.0.1': setAttr('attributes.c', 'C'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
|
@ -254,11 +300,12 @@ describe('DocumentMigrator', () => {
|
|||
it('rejects docs that belong to a newer plugin', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
dawg: {
|
||||
typeRegistry: createRegistry({
|
||||
name: 'dawg',
|
||||
migrations: {
|
||||
'1.2.3': setAttr('attributes.a', 'A'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect(() =>
|
||||
migrator.migrate({
|
||||
|
@ -276,13 +323,14 @@ describe('DocumentMigrator', () => {
|
|||
let count = 0;
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
dog: {
|
||||
typeRegistry: createRegistry({
|
||||
name: 'dog',
|
||||
migrations: {
|
||||
'2.2.4': setAttr('attributes.b', () => ++count),
|
||||
'10.0.1': setAttr('attributes.c', () => ++count),
|
||||
'1.2.3': setAttr('attributes.a', () => ++count),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
|
@ -301,14 +349,20 @@ describe('DocumentMigrator', () => {
|
|||
it('allows props to be added', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
animal: {
|
||||
'1.0.0': setAttr('animal', (name: string) => `Animal: ${name}`),
|
||||
typeRegistry: createRegistry(
|
||||
{
|
||||
name: 'animal',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('animal', (name: string) => `Animal: ${name}`),
|
||||
},
|
||||
},
|
||||
dog: {
|
||||
'2.2.4': setAttr('animal', 'Doggie'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dog',
|
||||
migrations: {
|
||||
'2.2.4': setAttr('animal', 'Doggie'),
|
||||
},
|
||||
}
|
||||
),
|
||||
});
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
|
@ -328,16 +382,22 @@ describe('DocumentMigrator', () => {
|
|||
it('allows props to be renamed', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
animal: {
|
||||
'1.0.0': setAttr('animal', (name: string) => `Animal: ${name}`),
|
||||
'3.2.1': renameAttr('animal', 'dawg'),
|
||||
typeRegistry: createRegistry(
|
||||
{
|
||||
name: 'animal',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('animal', (name: string) => `Animal: ${name}`),
|
||||
'3.2.1': renameAttr('animal', 'dawg'),
|
||||
},
|
||||
},
|
||||
dawg: {
|
||||
'2.2.4': renameAttr('dawg', 'animal'),
|
||||
'3.2.0': setAttr('dawg', (name: string) => `Dawg3.x: ${name}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dawg',
|
||||
migrations: {
|
||||
'2.2.4': renameAttr('dawg', 'animal'),
|
||||
'3.2.0': setAttr('dawg', (name: string) => `Dawg3.x: ${name}`),
|
||||
},
|
||||
}
|
||||
),
|
||||
});
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
|
@ -358,14 +418,20 @@ describe('DocumentMigrator', () => {
|
|||
it('allows changing type', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
cat: {
|
||||
'1.0.0': setAttr('attributes.name', (name: string) => `Kitty ${name}`),
|
||||
typeRegistry: createRegistry(
|
||||
{
|
||||
name: 'cat',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('attributes.name', (name: string) => `Kitty ${name}`),
|
||||
},
|
||||
},
|
||||
dog: {
|
||||
'2.2.4': setAttr('type', 'cat'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dog',
|
||||
migrations: {
|
||||
'2.2.4': setAttr('type', 'cat'),
|
||||
},
|
||||
}
|
||||
),
|
||||
});
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
|
@ -384,11 +450,12 @@ describe('DocumentMigrator', () => {
|
|||
it('disallows updating a migrationVersion prop to a lower version', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
cat: {
|
||||
typeRegistry: createRegistry({
|
||||
name: 'cat',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('migrationVersion.foo', '3.2.1'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
|
@ -406,11 +473,12 @@ describe('DocumentMigrator', () => {
|
|||
it('disallows removing a migrationVersion prop', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
cat: {
|
||||
typeRegistry: createRegistry({
|
||||
name: 'cat',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('migrationVersion', {}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect(() =>
|
||||
migrator.migrate({
|
||||
|
@ -427,8 +495,9 @@ describe('DocumentMigrator', () => {
|
|||
it('allows updating a migrationVersion prop to a later version', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
cat: {
|
||||
typeRegistry: createRegistry({
|
||||
name: 'cat',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('migrationVersion.cat', '2.9.1'),
|
||||
'2.0.0': () => {
|
||||
throw new Error('POW!');
|
||||
|
@ -438,7 +507,7 @@ describe('DocumentMigrator', () => {
|
|||
},
|
||||
'3.0.0': setAttr('attributes.name', 'Shiny'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
|
@ -457,11 +526,12 @@ describe('DocumentMigrator', () => {
|
|||
it('allows adding props to migrationVersion', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
cat: {
|
||||
typeRegistry: createRegistry({
|
||||
name: 'cat',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('migrationVersion.foo', '5.6.7'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
|
@ -481,13 +551,14 @@ describe('DocumentMigrator', () => {
|
|||
const log = mockLogger;
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
dog: {
|
||||
typeRegistry: createRegistry({
|
||||
name: 'dog',
|
||||
migrations: {
|
||||
'1.2.3': () => {
|
||||
throw new Error('Dang diggity!');
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
log,
|
||||
});
|
||||
const failedDoc = {
|
||||
|
@ -511,15 +582,16 @@ describe('DocumentMigrator', () => {
|
|||
const logTestMsg = '...said the joker to the thief';
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
dog: {
|
||||
typeRegistry: createRegistry({
|
||||
name: 'dog',
|
||||
migrations: {
|
||||
'1.2.3': (doc, log) => {
|
||||
log.info(logTestMsg);
|
||||
log.warning(logTestMsg);
|
||||
return doc;
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
log: mockLogger,
|
||||
});
|
||||
const doc = {
|
||||
|
@ -536,17 +608,23 @@ describe('DocumentMigrator', () => {
|
|||
test('extracts the latest migration version info', () => {
|
||||
const { migrationVersion } = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
aaa: {
|
||||
'1.2.3': (doc: RawSavedObjectDoc) => doc,
|
||||
'10.4.0': (doc: RawSavedObjectDoc) => doc,
|
||||
'2.2.1': (doc: RawSavedObjectDoc) => doc,
|
||||
typeRegistry: createRegistry(
|
||||
{
|
||||
name: 'aaa',
|
||||
migrations: {
|
||||
'1.2.3': (doc: SavedObjectUnsanitizedDoc) => doc,
|
||||
'10.4.0': (doc: SavedObjectUnsanitizedDoc) => doc,
|
||||
'2.2.1': (doc: SavedObjectUnsanitizedDoc) => doc,
|
||||
},
|
||||
},
|
||||
bbb: {
|
||||
'3.2.3': (doc: RawSavedObjectDoc) => doc,
|
||||
'2.0.0': (doc: RawSavedObjectDoc) => doc,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bbb',
|
||||
migrations: {
|
||||
'3.2.3': (doc: SavedObjectUnsanitizedDoc) => doc,
|
||||
'2.0.0': (doc: SavedObjectUnsanitizedDoc) => doc,
|
||||
},
|
||||
}
|
||||
),
|
||||
});
|
||||
|
||||
expect(migrationVersion).toEqual({
|
||||
|
@ -558,11 +636,12 @@ describe('DocumentMigrator', () => {
|
|||
test('fails if the validate doc throws', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
migrations: {
|
||||
aaa: {
|
||||
typeRegistry: createRegistry({
|
||||
name: 'aaa',
|
||||
migrations: {
|
||||
'2.3.4': d => _.set(d, 'attributes.counter', 42),
|
||||
},
|
||||
},
|
||||
}),
|
||||
validateDoc: d => {
|
||||
if ((d.attributes as any).counter === 42) {
|
||||
throw new Error('Meaningful!');
|
||||
|
@ -577,11 +656,15 @@ describe('DocumentMigrator', () => {
|
|||
});
|
||||
|
||||
function renameAttr(path: string, newPath: string) {
|
||||
return (doc: RawSavedObjectDoc) =>
|
||||
_.omit(_.set(doc, newPath, _.get(doc, path)) as {}, path) as RawSavedObjectDoc;
|
||||
return (doc: SavedObjectUnsanitizedDoc) =>
|
||||
_.omit(_.set(doc, newPath, _.get(doc, path)) as {}, path) as SavedObjectUnsanitizedDoc;
|
||||
}
|
||||
|
||||
function setAttr(path: string, value: any) {
|
||||
return (doc: RawSavedObjectDoc) =>
|
||||
_.set(doc, path, _.isFunction(value) ? value(_.get(doc, path)) : value) as RawSavedObjectDoc;
|
||||
return (doc: SavedObjectUnsanitizedDoc) =>
|
||||
_.set(
|
||||
doc,
|
||||
path,
|
||||
_.isFunction(value) ? value(_.get(doc, path)) : value
|
||||
) as SavedObjectUnsanitizedDoc;
|
||||
}
|
||||
|
|
|
@ -65,23 +65,19 @@ import _ from 'lodash';
|
|||
import cloneDeep from 'lodash.clonedeep';
|
||||
import Semver from 'semver';
|
||||
import { Logger } from '../../../logging';
|
||||
import { RawSavedObjectDoc } from '../../serialization';
|
||||
import { SavedObjectUnsanitizedDoc } from '../../serialization';
|
||||
import { SavedObjectsMigrationVersion } from '../../types';
|
||||
import { MigrationLogger, SavedObjectsMigrationLogger } from './migration_logger';
|
||||
import { MigrationLogger } from './migration_logger';
|
||||
import { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry';
|
||||
import { SavedObjectMigrationFn } from '../types';
|
||||
|
||||
export type TransformFn = (doc: RawSavedObjectDoc) => RawSavedObjectDoc;
|
||||
export type TransformFn = (doc: SavedObjectUnsanitizedDoc) => SavedObjectUnsanitizedDoc;
|
||||
|
||||
type MigrationFn = (doc: RawSavedObjectDoc, log: SavedObjectsMigrationLogger) => RawSavedObjectDoc;
|
||||
type ValidateDoc = (doc: SavedObjectUnsanitizedDoc) => void;
|
||||
|
||||
type ValidateDoc = (doc: RawSavedObjectDoc) => void;
|
||||
|
||||
export interface MigrationDefinition {
|
||||
[type: string]: { [version: string]: MigrationFn };
|
||||
}
|
||||
|
||||
interface Opts {
|
||||
interface DocumentMigratorOptions {
|
||||
kibanaVersion: string;
|
||||
migrations: MigrationDefinition;
|
||||
typeRegistry: ISavedObjectTypeRegistry;
|
||||
validateDoc: ValidateDoc;
|
||||
log: Logger;
|
||||
}
|
||||
|
@ -114,22 +110,22 @@ export class DocumentMigrator implements VersionedTransformer {
|
|||
/**
|
||||
* Creates an instance of DocumentMigrator.
|
||||
*
|
||||
* @param {Opts} opts
|
||||
* @param {DocumentMigratorOptions} opts
|
||||
* @prop {string} kibanaVersion - The current version of Kibana
|
||||
* @prop {MigrationDefinition} migrations - The migrations that will be used to migrate documents
|
||||
* @prop {SavedObjectTypeRegistry} typeRegistry - The type registry to get type migrations from
|
||||
* @prop {ValidateDoc} validateDoc - A function which, given a document throws an error if it is
|
||||
* not up to date. This is used to ensure we don't let unmigrated documents slip through.
|
||||
* @prop {Logger} log - The migration logger
|
||||
* @memberof DocumentMigrator
|
||||
*/
|
||||
constructor(opts: Opts) {
|
||||
validateMigrationDefinition(opts.migrations);
|
||||
constructor({ typeRegistry, kibanaVersion, log, validateDoc }: DocumentMigratorOptions) {
|
||||
validateMigrationDefinition(typeRegistry);
|
||||
|
||||
this.migrations = buildActiveMigrations(opts.migrations, opts.log);
|
||||
this.migrations = buildActiveMigrations(typeRegistry, log);
|
||||
this.transformDoc = buildDocumentTransform({
|
||||
kibanaVersion: opts.kibanaVersion,
|
||||
kibanaVersion,
|
||||
migrations: this.migrations,
|
||||
validateDoc: opts.validateDoc,
|
||||
validateDoc,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -147,11 +143,11 @@ export class DocumentMigrator implements VersionedTransformer {
|
|||
/**
|
||||
* Migrates a document to the latest version.
|
||||
*
|
||||
* @param {RawSavedObjectDoc} doc
|
||||
* @returns {RawSavedObjectDoc}
|
||||
* @param {SavedObjectUnsanitizedDoc} doc
|
||||
* @returns {SavedObjectUnsanitizedDoc}
|
||||
* @memberof DocumentMigrator
|
||||
*/
|
||||
public migrate = (doc: RawSavedObjectDoc): RawSavedObjectDoc => {
|
||||
public migrate = (doc: SavedObjectUnsanitizedDoc): SavedObjectUnsanitizedDoc => {
|
||||
// Clone the document to prevent accidental mutations on the original data
|
||||
// Ex: Importing sample data that is cached at import level, migrations would
|
||||
// execute on mutated data the second time.
|
||||
|
@ -166,7 +162,7 @@ export class DocumentMigrator implements VersionedTransformer {
|
|||
* language. So, this is just to provide a little developer-friendly error messaging. Joi was
|
||||
* giving weird errors, so we're just doing manual validation.
|
||||
*/
|
||||
function validateMigrationDefinition(migrations: MigrationDefinition) {
|
||||
function validateMigrationDefinition(registry: ISavedObjectTypeRegistry) {
|
||||
function assertObject(obj: any, prefix: string) {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
throw new Error(`${prefix} Got ${obj}.`);
|
||||
|
@ -187,17 +183,17 @@ function validateMigrationDefinition(migrations: MigrationDefinition) {
|
|||
}
|
||||
}
|
||||
|
||||
assertObject(migrations, 'Migration definition should be an object.');
|
||||
|
||||
Object.entries(migrations).forEach(([type, versions]: any) => {
|
||||
assertObject(
|
||||
versions,
|
||||
`Migration for type ${type} should be an object like { '2.0.0': (doc) => doc }.`
|
||||
);
|
||||
Object.entries(versions).forEach(([version, fn]) => {
|
||||
assertValidSemver(version, type);
|
||||
assertValidTransform(fn, version, type);
|
||||
});
|
||||
registry.getAllTypes().forEach(type => {
|
||||
if (type.migrations) {
|
||||
assertObject(
|
||||
type.migrations,
|
||||
`Migration for type ${type.name} should be an object like { '2.0.0': (doc) => doc }.`
|
||||
);
|
||||
Object.entries(type.migrations).forEach(([version, fn]) => {
|
||||
assertValidSemver(version, type.name);
|
||||
assertValidTransform(fn, version, type.name);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -207,19 +203,28 @@ function validateMigrationDefinition(migrations: MigrationDefinition) {
|
|||
* From: { type: { version: fn } }
|
||||
* To: { type: { latestVersion: string, transforms: [{ version: string, transform: fn }] } }
|
||||
*/
|
||||
function buildActiveMigrations(migrations: MigrationDefinition, log: Logger): ActiveMigrations {
|
||||
return _.mapValues(migrations, (versions, prop) => {
|
||||
const transforms = Object.entries(versions)
|
||||
.map(([version, transform]) => ({
|
||||
version,
|
||||
transform: wrapWithTry(version, prop!, transform, log),
|
||||
}))
|
||||
.sort((a, b) => Semver.compare(a.version, b.version));
|
||||
return {
|
||||
latestVersion: _.last(transforms).version,
|
||||
transforms,
|
||||
};
|
||||
});
|
||||
function buildActiveMigrations(
|
||||
typeRegistry: ISavedObjectTypeRegistry,
|
||||
log: Logger
|
||||
): ActiveMigrations {
|
||||
return typeRegistry
|
||||
.getAllTypes()
|
||||
.filter(type => type.migrations && Object.keys(type.migrations).length > 0)
|
||||
.reduce((migrations, type) => {
|
||||
const transforms = Object.entries(type.migrations!)
|
||||
.map(([version, transform]) => ({
|
||||
version,
|
||||
transform: wrapWithTry(version, type.name, transform, log),
|
||||
}))
|
||||
.sort((a, b) => Semver.compare(a.version, b.version));
|
||||
return {
|
||||
...migrations,
|
||||
[type.name]: {
|
||||
latestVersion: _.last(transforms).version,
|
||||
transforms,
|
||||
},
|
||||
};
|
||||
}, {} as ActiveMigrations);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -234,7 +239,7 @@ function buildDocumentTransform({
|
|||
migrations: ActiveMigrations;
|
||||
validateDoc: ValidateDoc;
|
||||
}): TransformFn {
|
||||
return function transformAndValidate(doc: RawSavedObjectDoc) {
|
||||
return function transformAndValidate(doc: SavedObjectUnsanitizedDoc) {
|
||||
const result = doc.migrationVersion
|
||||
? applyMigrations(doc, migrations)
|
||||
: markAsUpToDate(doc, migrations);
|
||||
|
@ -252,7 +257,7 @@ function buildDocumentTransform({
|
|||
};
|
||||
}
|
||||
|
||||
function applyMigrations(doc: RawSavedObjectDoc, migrations: ActiveMigrations) {
|
||||
function applyMigrations(doc: SavedObjectUnsanitizedDoc, migrations: ActiveMigrations) {
|
||||
while (true) {
|
||||
const prop = nextUnmigratedProp(doc, migrations);
|
||||
if (!prop) {
|
||||
|
@ -265,14 +270,14 @@ function applyMigrations(doc: RawSavedObjectDoc, migrations: ActiveMigrations) {
|
|||
/**
|
||||
* Gets the doc's props, handling the special case of "type".
|
||||
*/
|
||||
function props(doc: RawSavedObjectDoc) {
|
||||
function props(doc: SavedObjectUnsanitizedDoc) {
|
||||
return Object.keys(doc).concat(doc.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the prop version in a saved object document or in our latest migrations.
|
||||
*/
|
||||
function propVersion(doc: RawSavedObjectDoc | ActiveMigrations, prop: string) {
|
||||
function propVersion(doc: SavedObjectUnsanitizedDoc | ActiveMigrations, prop: string) {
|
||||
return (
|
||||
(doc[prop] && doc[prop].latestVersion) ||
|
||||
(doc.migrationVersion && (doc as any).migrationVersion[prop])
|
||||
|
@ -282,7 +287,7 @@ function propVersion(doc: RawSavedObjectDoc | ActiveMigrations, prop: string) {
|
|||
/**
|
||||
* Sets the doc's migrationVersion to be the most recent version
|
||||
*/
|
||||
function markAsUpToDate(doc: RawSavedObjectDoc, migrations: ActiveMigrations) {
|
||||
function markAsUpToDate(doc: SavedObjectUnsanitizedDoc, migrations: ActiveMigrations) {
|
||||
return {
|
||||
...doc,
|
||||
migrationVersion: props(doc).reduce((acc, prop) => {
|
||||
|
@ -296,20 +301,25 @@ function markAsUpToDate(doc: RawSavedObjectDoc, migrations: ActiveMigrations) {
|
|||
* If a specific transform function fails, this tacks on a bit of information
|
||||
* about the document and transform that caused the failure.
|
||||
*/
|
||||
function wrapWithTry(version: string, prop: string, transform: MigrationFn, log: Logger) {
|
||||
return function tryTransformDoc(doc: RawSavedObjectDoc) {
|
||||
function wrapWithTry(
|
||||
version: string,
|
||||
type: string,
|
||||
migrationFn: SavedObjectMigrationFn,
|
||||
log: Logger
|
||||
) {
|
||||
return function tryTransformDoc(doc: SavedObjectUnsanitizedDoc) {
|
||||
try {
|
||||
const result = transform(doc, new MigrationLogger(log));
|
||||
const result = migrationFn(doc, new MigrationLogger(log));
|
||||
|
||||
// A basic sanity check to help migration authors detect basic errors
|
||||
// (e.g. forgetting to return the transformed doc)
|
||||
if (!result || !result.type) {
|
||||
throw new Error(`Invalid saved object returned from migration ${prop}:${version}.`);
|
||||
throw new Error(`Invalid saved object returned from migration ${type}:${version}.`);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const failedTransform = `${prop}:${version}`;
|
||||
const failedTransform = `${type}:${version}`;
|
||||
const failedDoc = JSON.stringify(doc);
|
||||
log.warn(
|
||||
`Failed to transform document ${doc}. Transform: ${failedTransform}\nDoc: ${failedDoc}`
|
||||
|
@ -322,7 +332,7 @@ function wrapWithTry(version: string, prop: string, transform: MigrationFn, log:
|
|||
/**
|
||||
* Finds the first unmigrated property in the specified document.
|
||||
*/
|
||||
function nextUnmigratedProp(doc: RawSavedObjectDoc, migrations: ActiveMigrations) {
|
||||
function nextUnmigratedProp(doc: SavedObjectUnsanitizedDoc, migrations: ActiveMigrations) {
|
||||
return props(doc).find(p => {
|
||||
const latestVersion = propVersion(migrations, p);
|
||||
const docVersion = propVersion(doc, p);
|
||||
|
@ -352,10 +362,10 @@ function nextUnmigratedProp(doc: RawSavedObjectDoc, migrations: ActiveMigrations
|
|||
* Applies any relevent migrations to the document for the specified property.
|
||||
*/
|
||||
function migrateProp(
|
||||
doc: RawSavedObjectDoc,
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
prop: string,
|
||||
migrations: ActiveMigrations
|
||||
): RawSavedObjectDoc {
|
||||
): SavedObjectUnsanitizedDoc {
|
||||
const originalType = doc.type;
|
||||
let migrationVersion = _.clone(doc.migrationVersion) || {};
|
||||
const typeChanged = () => !doc.hasOwnProperty(prop) || doc.type !== originalType;
|
||||
|
@ -376,7 +386,11 @@ function migrateProp(
|
|||
/**
|
||||
* Retrieves any prop transforms that have not been applied to doc.
|
||||
*/
|
||||
function applicableTransforms(migrations: ActiveMigrations, doc: RawSavedObjectDoc, prop: string) {
|
||||
function applicableTransforms(
|
||||
migrations: ActiveMigrations,
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
prop: string
|
||||
) {
|
||||
const minVersion = propVersion(doc, prop);
|
||||
const { transforms } = migrations[prop];
|
||||
return minVersion
|
||||
|
@ -389,7 +403,7 @@ function applicableTransforms(migrations: ActiveMigrations, doc: RawSavedObjectD
|
|||
* has not mutated migrationVersion in an unsupported way.
|
||||
*/
|
||||
function updateMigrationVersion(
|
||||
doc: RawSavedObjectDoc,
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
migrationVersion: SavedObjectsMigrationVersion,
|
||||
prop: string,
|
||||
version: string
|
||||
|
@ -405,7 +419,7 @@ function updateMigrationVersion(
|
|||
* as this could get us into an infinite loop. So, we explicitly check for that here.
|
||||
*/
|
||||
function assertNoDowngrades(
|
||||
doc: RawSavedObjectDoc,
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
migrationVersion: SavedObjectsMigrationVersion,
|
||||
prop: string,
|
||||
version: string
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { SavedObjectsSchema } from '../../schema';
|
||||
import { RawSavedObjectDoc, SavedObjectsSerializer } from '../../serialization';
|
||||
import { SavedObjectUnsanitizedDoc, SavedObjectsSerializer } from '../../serialization';
|
||||
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
|
||||
import { IndexMigrator } from './index_migrator';
|
||||
import { loggingServiceMock } from '../../../logging/logging_service.mock';
|
||||
|
||||
|
@ -39,7 +39,7 @@ describe('IndexMigrator', () => {
|
|||
migrationVersion: {},
|
||||
migrate: _.identity,
|
||||
},
|
||||
serializer: new SavedObjectsSerializer(new SavedObjectsSchema()),
|
||||
serializer: new SavedObjectsSerializer(new SavedObjectTypeRegistry()),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -254,7 +254,7 @@ describe('IndexMigrator', () => {
|
|||
test('transforms all docs from the original index', async () => {
|
||||
let count = 0;
|
||||
const { callCluster } = testOpts;
|
||||
const migrateDoc = jest.fn((doc: RawSavedObjectDoc) => {
|
||||
const migrateDoc = jest.fn((doc: SavedObjectUnsanitizedDoc) => {
|
||||
return {
|
||||
...doc,
|
||||
attributes: { name: ++count },
|
||||
|
|
|
@ -18,17 +18,21 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { SavedObjectsSchema } from '../../schema';
|
||||
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
|
||||
import { SavedObjectsSerializer } from '../../serialization';
|
||||
import { migrateRawDocs } from './migrate_raw_docs';
|
||||
|
||||
describe('migrateRawDocs', () => {
|
||||
test('converts raw docs to saved objects', async () => {
|
||||
const transform = jest.fn<any, any>((doc: any) => _.set(doc, 'attributes.name', 'HOI!'));
|
||||
const result = migrateRawDocs(new SavedObjectsSerializer(new SavedObjectsSchema()), transform, [
|
||||
{ _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } },
|
||||
{ _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } },
|
||||
]);
|
||||
const result = migrateRawDocs(
|
||||
new SavedObjectsSerializer(new SavedObjectTypeRegistry()),
|
||||
transform,
|
||||
[
|
||||
{ _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } },
|
||||
{ _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } },
|
||||
]
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
|
@ -48,10 +52,14 @@ describe('migrateRawDocs', () => {
|
|||
const transform = jest.fn<any, any>((doc: any) =>
|
||||
_.set(_.cloneDeep(doc), 'attributes.name', 'TADA')
|
||||
);
|
||||
const result = migrateRawDocs(new SavedObjectsSerializer(new SavedObjectsSchema()), transform, [
|
||||
{ _id: 'foo:b', _source: { type: 'a', a: { name: 'AAA' } } },
|
||||
{ _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } },
|
||||
]);
|
||||
const result = migrateRawDocs(
|
||||
new SavedObjectsSerializer(new SavedObjectTypeRegistry()),
|
||||
transform,
|
||||
[
|
||||
{ _id: 'foo:b', _source: { type: 'a', a: { name: 'AAA' } } },
|
||||
{ _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } },
|
||||
]
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ _id: 'foo:b', _source: { type: 'a', a: { name: 'AAA' } } },
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
* This file provides logic for migrating raw documents.
|
||||
*/
|
||||
|
||||
import { RawDoc, SavedObjectsSerializer } from '../../serialization';
|
||||
import { SavedObjectsRawDoc, SavedObjectsSerializer } from '../../serialization';
|
||||
import { TransformFn } from './document_migrator';
|
||||
|
||||
/**
|
||||
|
@ -29,14 +29,14 @@ import { TransformFn } from './document_migrator';
|
|||
* of raw docs. Any raw docs that are not valid saved objects will simply be passed through.
|
||||
*
|
||||
* @param {TransformFn} migrateDoc
|
||||
* @param {RawDoc[]} rawDocs
|
||||
* @returns {RawDoc[]}
|
||||
* @param {SavedObjectsRawDoc[]} rawDocs
|
||||
* @returns {SavedObjectsRawDoc[]}
|
||||
*/
|
||||
export function migrateRawDocs(
|
||||
serializer: SavedObjectsSerializer,
|
||||
migrateDoc: TransformFn,
|
||||
rawDocs: RawDoc[]
|
||||
): RawDoc[] {
|
||||
rawDocs: SavedObjectsRawDoc[]
|
||||
): SavedObjectsRawDoc[] {
|
||||
return rawDocs.map(raw => {
|
||||
if (serializer.isRawSavedObject(raw)) {
|
||||
const savedObject = serializer.rawToSavedObject(raw);
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
import { Logger } from 'src/core/server/logging';
|
||||
import { SavedObjectsSerializer } from '../../serialization';
|
||||
import { MappingProperties } from '../../mappings';
|
||||
import { SavedObjectsTypeMappingDefinitions } from '../../mappings';
|
||||
import { buildActiveMappings } from './build_active_mappings';
|
||||
import { CallCluster } from './call_cluster';
|
||||
import { VersionedTransformer } from './document_migrator';
|
||||
|
@ -40,7 +40,7 @@ export interface MigrationOpts {
|
|||
callCluster: CallCluster;
|
||||
index: string;
|
||||
log: Logger;
|
||||
mappingProperties: MappingProperties;
|
||||
mappingProperties: SavedObjectsTypeMappingDefinitions;
|
||||
documentMigrator: VersionedTransformer;
|
||||
serializer: SavedObjectsSerializer;
|
||||
convertToAliasScript?: string;
|
||||
|
@ -107,9 +107,9 @@ function createSourceContext(source: FullIndexInfo, alias: string) {
|
|||
function createDestContext(
|
||||
source: FullIndexInfo,
|
||||
alias: string,
|
||||
mappingProperties: MappingProperties
|
||||
mappingProperties: SavedObjectsTypeMappingDefinitions
|
||||
): FullIndexInfo {
|
||||
const activeMappings = buildActiveMappings({ properties: mappingProperties });
|
||||
const activeMappings = buildActiveMappings(mappingProperties);
|
||||
|
||||
return {
|
||||
aliases: {},
|
||||
|
@ -133,6 +133,5 @@ function createDestContext(
|
|||
function nextIndexName(indexName: string, alias: string) {
|
||||
const indexSuffix = (indexName.match(/[\d]+$/) || [])[0];
|
||||
const indexNum = parseInt(indexSuffix, 10) || 0;
|
||||
|
||||
return `${alias}_${indexNum + 1}`;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ describe('coordinateMigration', () => {
|
|||
const log = {
|
||||
debug: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
info: jest.fn(),
|
||||
};
|
||||
|
||||
|
|
|
@ -30,7 +30,11 @@ export type LogFn = (path: string[], message: string) => void;
|
|||
export interface SavedObjectsMigrationLogger {
|
||||
debug: (msg: string) => void;
|
||||
info: (msg: string) => void;
|
||||
/**
|
||||
* @deprecated Use `warn` instead.
|
||||
*/
|
||||
warning: (msg: string) => void;
|
||||
warn: (msg: string) => void;
|
||||
}
|
||||
|
||||
export class MigrationLogger implements SavedObjectsMigrationLogger {
|
||||
|
@ -43,4 +47,5 @@ export class MigrationLogger implements SavedObjectsMigrationLogger {
|
|||
public info = (msg: string) => this.logger.info(msg);
|
||||
public debug = (msg: string) => this.logger.debug(msg);
|
||||
public warning = (msg: string) => this.logger.warn(msg);
|
||||
public warn = (msg: string) => this.logger.warn(msg);
|
||||
}
|
||||
|
|
|
@ -18,3 +18,4 @@
|
|||
*/
|
||||
|
||||
export { KibanaMigrator, IKibanaMigrator } from './kibana';
|
||||
export { SavedObjectMigrationFn, SavedObjectMigrationMap } from './types';
|
||||
|
|
|
@ -4,8 +4,8 @@ exports[`KibanaMigrator getActiveMappings returns full index mappings w/ core pr
|
|||
Object {
|
||||
"_meta": Object {
|
||||
"migrationMappingPropertyHashes": Object {
|
||||
"amap": "625b32086eb1d1203564cf85062dd22e",
|
||||
"bmap": "625b32086eb1d1203564cf85062dd22e",
|
||||
"amap": "510f1f0adb69830cf8a1c5ce2923ed82",
|
||||
"bmap": "510f1f0adb69830cf8a1c5ce2923ed82",
|
||||
"config": "87aca8fdb053154f11383fce3dbf3edf",
|
||||
"migrationVersion": "4a1746014a75ade3a714e1db5763276f",
|
||||
"namespace": "2f4316de49999235636386fe51dc06c1",
|
||||
|
@ -17,10 +17,18 @@ Object {
|
|||
"dynamic": "strict",
|
||||
"properties": Object {
|
||||
"amap": Object {
|
||||
"type": "text",
|
||||
"properties": Object {
|
||||
"field": Object {
|
||||
"type": "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
"bmap": Object {
|
||||
"type": "text",
|
||||
"properties": Object {
|
||||
"field": Object {
|
||||
"type": "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
"config": Object {
|
||||
"dynamic": "true",
|
||||
|
|
|
@ -19,28 +19,29 @@
|
|||
|
||||
import { KibanaMigrator } from './kibana_migrator';
|
||||
import { buildActiveMappings } from '../core';
|
||||
import { SavedObjectsMapping } from '../../mappings';
|
||||
const { mergeProperties } = jest.requireActual('./kibana_migrator');
|
||||
const { mergeTypes } = jest.requireActual('./kibana_migrator');
|
||||
import { SavedObjectsType } from '../../types';
|
||||
|
||||
const defaultSavedObjectMappings = [
|
||||
const defaultSavedObjectTypes: SavedObjectsType[] = [
|
||||
{
|
||||
pluginId: 'testplugin',
|
||||
properties: {
|
||||
testtype: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
},
|
||||
name: 'testtype',
|
||||
hidden: false,
|
||||
namespaceAgnostic: false,
|
||||
mappings: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
migrations: {},
|
||||
},
|
||||
];
|
||||
|
||||
const createMigrator = (
|
||||
{
|
||||
savedObjectMappings,
|
||||
types,
|
||||
}: {
|
||||
savedObjectMappings: SavedObjectsMapping[];
|
||||
} = { savedObjectMappings: defaultSavedObjectMappings }
|
||||
types: SavedObjectsType[];
|
||||
} = { types: defaultSavedObjectTypes }
|
||||
) => {
|
||||
const mockMigrator: jest.Mocked<PublicMethodsOf<KibanaMigrator>> = {
|
||||
runMigrations: jest.fn(),
|
||||
|
@ -48,9 +49,7 @@ const createMigrator = (
|
|||
migrateDocument: jest.fn(),
|
||||
};
|
||||
|
||||
mockMigrator.getActiveMappings.mockReturnValue(
|
||||
buildActiveMappings({ properties: mergeProperties(savedObjectMappings) })
|
||||
);
|
||||
mockMigrator.getActiveMappings.mockReturnValue(buildActiveMappings(mergeTypes(types)));
|
||||
mockMigrator.migrateDocument.mockImplementation(doc => doc);
|
||||
return mockMigrator;
|
||||
};
|
||||
|
|
|
@ -17,45 +17,49 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { KibanaMigratorOptions, KibanaMigrator } from './kibana_migrator';
|
||||
import { loggingServiceMock } from '../../../logging/logging_service.mock';
|
||||
import { SavedObjectsSchema } from '../../schema';
|
||||
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
|
||||
import { SavedObjectsType } from '../../types';
|
||||
|
||||
const createRegistry = (types: Array<Partial<SavedObjectsType>>) => {
|
||||
const registry = new SavedObjectTypeRegistry();
|
||||
types.forEach(type =>
|
||||
registry.registerType({
|
||||
name: 'unknown',
|
||||
namespaceAgnostic: false,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
migrations: {},
|
||||
...type,
|
||||
})
|
||||
);
|
||||
return registry;
|
||||
};
|
||||
|
||||
describe('KibanaMigrator', () => {
|
||||
describe('getActiveMappings', () => {
|
||||
it('returns full index mappings w/ core properties', () => {
|
||||
const options = mockOptions();
|
||||
options.savedObjectMappings = [
|
||||
options.typeRegistry = createRegistry([
|
||||
{
|
||||
pluginId: 'aaa',
|
||||
properties: { amap: { type: 'text' } },
|
||||
name: 'amap',
|
||||
mappings: {
|
||||
properties: { field: { type: 'text' } },
|
||||
},
|
||||
},
|
||||
{
|
||||
pluginId: 'bbb',
|
||||
properties: { bmap: { type: 'text' } },
|
||||
name: 'bmap',
|
||||
indexPattern: 'other-index',
|
||||
mappings: {
|
||||
properties: { field: { type: 'text' } },
|
||||
},
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
const mappings = new KibanaMigrator(options).getActiveMappings();
|
||||
expect(mappings).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('Fails if duplicate mappings are defined', () => {
|
||||
const options = mockOptions();
|
||||
options.savedObjectMappings = [
|
||||
{
|
||||
pluginId: 'aaa',
|
||||
properties: { amap: { type: 'text' } },
|
||||
},
|
||||
{
|
||||
pluginId: 'bbb',
|
||||
properties: { amap: { type: 'long' } },
|
||||
},
|
||||
];
|
||||
expect(() => new KibanaMigrator(options).getActiveMappings()).toThrow(
|
||||
/Plugin bbb is attempting to redefine mapping "amap"/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('runMigrations', () => {
|
||||
|
@ -78,41 +82,37 @@ describe('KibanaMigrator', () => {
|
|||
});
|
||||
});
|
||||
|
||||
function mockOptions({ configValues }: { configValues?: any } = {}): KibanaMigratorOptions {
|
||||
function mockOptions(): KibanaMigratorOptions {
|
||||
const callCluster = jest.fn();
|
||||
return {
|
||||
logger: loggingServiceMock.create().get(),
|
||||
kibanaVersion: '8.2.3',
|
||||
savedObjectValidations: {},
|
||||
savedObjectMigrations: {},
|
||||
savedObjectMappings: [
|
||||
typeRegistry: createRegistry([
|
||||
{
|
||||
pluginId: 'testtype',
|
||||
properties: {
|
||||
testtype: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
},
|
||||
name: 'testtype',
|
||||
hidden: false,
|
||||
namespaceAgnostic: false,
|
||||
mappings: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
migrations: {},
|
||||
},
|
||||
{
|
||||
pluginId: 'testtype2',
|
||||
properties: {
|
||||
testtype2: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
savedObjectSchemas: new SavedObjectsSchema({
|
||||
testtype2: {
|
||||
isNamespaceAgnostic: false,
|
||||
name: 'testtype2',
|
||||
hidden: false,
|
||||
namespaceAgnostic: false,
|
||||
indexPattern: 'other-index',
|
||||
mappings: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
migrations: {},
|
||||
},
|
||||
}),
|
||||
]),
|
||||
kibanaConfig: {
|
||||
enabled: true,
|
||||
index: '.my-index',
|
||||
|
@ -123,15 +123,6 @@ function mockOptions({ configValues }: { configValues?: any } = {}): KibanaMigra
|
|||
scrollDuration: '10m',
|
||||
skip: false,
|
||||
},
|
||||
config: {
|
||||
get: (name: string) => {
|
||||
if (configValues && configValues[name]) {
|
||||
return configValues[name];
|
||||
} else {
|
||||
throw new Error(`Unexpected config ${name}`);
|
||||
}
|
||||
},
|
||||
} as KibanaMigratorOptions['config'],
|
||||
callCluster,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,30 +24,23 @@
|
|||
|
||||
import { Logger } from 'src/core/server/logging';
|
||||
import { KibanaConfigType } from 'src/core/server/kibana_config';
|
||||
import { MappingProperties, SavedObjectsMapping, IndexMapping } from '../../mappings';
|
||||
import { SavedObjectsSchema } from '../../schema';
|
||||
import { RawSavedObjectDoc, SavedObjectsSerializer } from '../../serialization';
|
||||
import { IndexMapping, SavedObjectsTypeMappingDefinitions } from '../../mappings';
|
||||
import { SavedObjectUnsanitizedDoc, SavedObjectsSerializer } from '../../serialization';
|
||||
import { docValidator, PropertyValidators } from '../../validation';
|
||||
import { buildActiveMappings, CallCluster, IndexMigrator } from '../core';
|
||||
import {
|
||||
DocumentMigrator,
|
||||
VersionedTransformer,
|
||||
MigrationDefinition,
|
||||
} from '../core/document_migrator';
|
||||
import { DocumentMigrator, VersionedTransformer } from '../core/document_migrator';
|
||||
import { createIndexMap } from '../core/build_index_map';
|
||||
import { SavedObjectsConfigType } from '../../saved_objects_config';
|
||||
import { LegacyConfig } from '../../../legacy';
|
||||
import { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry';
|
||||
import { SavedObjectsType } from '../../types';
|
||||
|
||||
export interface KibanaMigratorOptions {
|
||||
callCluster: CallCluster;
|
||||
config: LegacyConfig;
|
||||
typeRegistry: ISavedObjectTypeRegistry;
|
||||
savedObjectsConfig: SavedObjectsConfigType;
|
||||
kibanaConfig: KibanaConfigType;
|
||||
kibanaVersion: string;
|
||||
logger: Logger;
|
||||
savedObjectMappings: SavedObjectsMapping[];
|
||||
savedObjectMigrations: MigrationDefinition;
|
||||
savedObjectSchemas: SavedObjectsSchema;
|
||||
savedObjectValidations: PropertyValidators;
|
||||
}
|
||||
|
||||
|
@ -58,13 +51,12 @@ export type IKibanaMigrator = Pick<KibanaMigrator, keyof KibanaMigrator>;
|
|||
*/
|
||||
export class KibanaMigrator {
|
||||
private readonly callCluster: CallCluster;
|
||||
private readonly config: LegacyConfig;
|
||||
private readonly savedObjectsConfig: SavedObjectsConfigType;
|
||||
private readonly documentMigrator: VersionedTransformer;
|
||||
private readonly kibanaConfig: KibanaConfigType;
|
||||
private readonly log: Logger;
|
||||
private readonly mappingProperties: MappingProperties;
|
||||
private readonly schema: SavedObjectsSchema;
|
||||
private readonly mappingProperties: SavedObjectsTypeMappingDefinitions;
|
||||
private readonly typeRegistry: ISavedObjectTypeRegistry;
|
||||
private readonly serializer: SavedObjectsSerializer;
|
||||
private migrationResult?: Promise<Array<{ status: string }>>;
|
||||
|
||||
|
@ -73,27 +65,23 @@ export class KibanaMigrator {
|
|||
*/
|
||||
constructor({
|
||||
callCluster,
|
||||
config,
|
||||
typeRegistry,
|
||||
kibanaConfig,
|
||||
savedObjectsConfig,
|
||||
savedObjectValidations,
|
||||
kibanaVersion,
|
||||
logger,
|
||||
savedObjectMappings,
|
||||
savedObjectMigrations,
|
||||
savedObjectSchemas,
|
||||
savedObjectValidations,
|
||||
}: KibanaMigratorOptions) {
|
||||
this.config = config;
|
||||
this.callCluster = callCluster;
|
||||
this.kibanaConfig = kibanaConfig;
|
||||
this.savedObjectsConfig = savedObjectsConfig;
|
||||
this.schema = savedObjectSchemas;
|
||||
this.serializer = new SavedObjectsSerializer(this.schema);
|
||||
this.mappingProperties = mergeProperties(savedObjectMappings || []);
|
||||
this.typeRegistry = typeRegistry;
|
||||
this.serializer = new SavedObjectsSerializer(this.typeRegistry);
|
||||
this.mappingProperties = mergeTypes(this.typeRegistry.getAllTypes());
|
||||
this.log = logger;
|
||||
this.documentMigrator = new DocumentMigrator({
|
||||
kibanaVersion,
|
||||
migrations: savedObjectMigrations || {},
|
||||
typeRegistry,
|
||||
validateDoc: docValidator(savedObjectValidations || {}),
|
||||
log: this.log,
|
||||
});
|
||||
|
@ -118,10 +106,9 @@ export class KibanaMigrator {
|
|||
private runMigrationsInternal() {
|
||||
const kibanaIndexName = this.kibanaConfig.index;
|
||||
const indexMap = createIndexMap({
|
||||
config: this.config,
|
||||
kibanaIndexName,
|
||||
indexMap: this.mappingProperties,
|
||||
schema: this.schema,
|
||||
registry: this.typeRegistry,
|
||||
});
|
||||
|
||||
const migrators = Object.keys(indexMap).map(index => {
|
||||
|
@ -150,7 +137,7 @@ export class KibanaMigrator {
|
|||
*
|
||||
*/
|
||||
public getActiveMappings(): IndexMapping {
|
||||
return buildActiveMappings({ properties: this.mappingProperties });
|
||||
return buildActiveMappings(this.mappingProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -159,7 +146,7 @@ export class KibanaMigrator {
|
|||
* @param doc - The saved object to migrate
|
||||
* @returns `doc` with all registered migrations applied.
|
||||
*/
|
||||
public migrateDocument(doc: RawSavedObjectDoc): RawSavedObjectDoc {
|
||||
public migrateDocument(doc: SavedObjectUnsanitizedDoc): SavedObjectUnsanitizedDoc {
|
||||
return this.documentMigrator.migrate(doc);
|
||||
}
|
||||
}
|
||||
|
@ -168,12 +155,15 @@ export class KibanaMigrator {
|
|||
* Merges savedObjectMappings properties into a single object, verifying that
|
||||
* no mappings are redefined.
|
||||
*/
|
||||
export function mergeProperties(mappings: SavedObjectsMapping[]): MappingProperties {
|
||||
return mappings.reduce((acc, { pluginId, properties }) => {
|
||||
const duplicate = Object.keys(properties).find(k => acc.hasOwnProperty(k));
|
||||
export function mergeTypes(types: SavedObjectsType[]): SavedObjectsTypeMappingDefinitions {
|
||||
return types.reduce((acc, { name: type, mappings }) => {
|
||||
const duplicate = acc.hasOwnProperty(type);
|
||||
if (duplicate) {
|
||||
throw new Error(`Plugin ${pluginId} is attempting to redefine mapping "${duplicate}".`);
|
||||
throw new Error(`Type ${type} is already defined.`);
|
||||
}
|
||||
return Object.assign(acc, properties);
|
||||
return {
|
||||
...acc,
|
||||
[type]: mappings,
|
||||
};
|
||||
}, {});
|
||||
}
|
||||
|
|
51
src/core/server/saved_objects/migrations/types.ts
Normal file
51
src/core/server/saved_objects/migrations/types.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { SavedObjectUnsanitizedDoc } from '../serialization';
|
||||
import { SavedObjectsMigrationLogger } from './core/migration_logger';
|
||||
|
||||
/**
|
||||
* A migration function defined for a {@link SavedObjectsType | saved objects type}
|
||||
* used to migrate it's {@link SavedObjectUnsanitizedDoc | documents}
|
||||
*/
|
||||
export type SavedObjectMigrationFn = (
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
log: SavedObjectsMigrationLogger
|
||||
) => SavedObjectUnsanitizedDoc;
|
||||
|
||||
/**
|
||||
* A map of {@link SavedObjectMigrationFn | migration functions} to be used for a given type.
|
||||
* The map's keys must be valid semver versions.
|
||||
*
|
||||
* For a given document, only migrations with a higher version number than that of the document will be applied.
|
||||
* Migrations are executed in order, starting from the lowest version and ending with the highest one.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const migrations: SavedObjectMigrationMap = {
|
||||
* '1.0.0': migrateToV1,
|
||||
* '2.1.0': migrateToV21
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface SavedObjectMigrationMap {
|
||||
[version: string]: SavedObjectMigrationFn;
|
||||
}
|
|
@ -21,38 +21,60 @@ import {
|
|||
SavedObjectsService,
|
||||
InternalSavedObjectsServiceSetup,
|
||||
InternalSavedObjectsServiceStart,
|
||||
SavedObjectsServiceSetup,
|
||||
SavedObjectsServiceStart,
|
||||
} from './saved_objects_service';
|
||||
import { mockKibanaMigrator } from './migrations/kibana/kibana_migrator.mock';
|
||||
import { savedObjectsClientProviderMock } from './service/lib/scoped_client_provider.mock';
|
||||
import { savedObjectsRepositoryMock } from './service/lib/repository.mock';
|
||||
import { savedObjectsClientMock } from './service/saved_objects_client.mock';
|
||||
import { typeRegistryMock } from './saved_objects_type_registry.mock';
|
||||
|
||||
type SavedObjectsServiceContract = PublicMethodsOf<SavedObjectsService>;
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<InternalSavedObjectsServiceStart> = {
|
||||
clientProvider: savedObjectsClientProviderMock.create(),
|
||||
const startContrat: jest.Mocked<SavedObjectsServiceStart> = {
|
||||
getScopedClient: jest.fn(),
|
||||
createInternalRepository: jest.fn(),
|
||||
createScopedRepository: jest.fn(),
|
||||
migrator: mockKibanaMigrator.create(),
|
||||
createSerializer: jest.fn(),
|
||||
};
|
||||
|
||||
startContract.getScopedClient.mockReturnValue(savedObjectsClientMock.create());
|
||||
startContract.createInternalRepository.mockReturnValue(savedObjectsRepositoryMock.create());
|
||||
startContract.createScopedRepository.mockReturnValue(savedObjectsRepositoryMock.create());
|
||||
startContrat.getScopedClient.mockReturnValue(savedObjectsClientMock.create());
|
||||
startContrat.createInternalRepository.mockReturnValue(savedObjectsRepositoryMock.create());
|
||||
startContrat.createScopedRepository.mockReturnValue(savedObjectsRepositoryMock.create());
|
||||
|
||||
return startContract;
|
||||
return startContrat;
|
||||
};
|
||||
|
||||
const createInternalStartContractMock = () => {
|
||||
const internalStartContract: jest.Mocked<InternalSavedObjectsServiceStart> = {
|
||||
...createStartContractMock(),
|
||||
clientProvider: savedObjectsClientProviderMock.create(),
|
||||
migrator: mockKibanaMigrator.create(),
|
||||
typeRegistry: typeRegistryMock.create(),
|
||||
};
|
||||
|
||||
return internalStartContract;
|
||||
};
|
||||
|
||||
const createSetupContractMock = () => {
|
||||
const setupContract: jest.Mocked<InternalSavedObjectsServiceSetup> = {
|
||||
const setupContract: jest.Mocked<SavedObjectsServiceSetup> = {
|
||||
setClientFactoryProvider: jest.fn(),
|
||||
addClientWrapper: jest.fn(),
|
||||
};
|
||||
|
||||
return setupContract;
|
||||
};
|
||||
|
||||
const createInternalSetupContractMock = () => {
|
||||
const internalSetupContract: jest.Mocked<InternalSavedObjectsServiceSetup> = {
|
||||
...createSetupContractMock(),
|
||||
registerType: jest.fn(),
|
||||
};
|
||||
return internalSetupContract;
|
||||
};
|
||||
|
||||
const createSavedObjectsServiceMock = () => {
|
||||
const mocked: jest.Mocked<SavedObjectsServiceContract> = {
|
||||
setup: jest.fn(),
|
||||
|
@ -60,14 +82,16 @@ const createSavedObjectsServiceMock = () => {
|
|||
stop: jest.fn(),
|
||||
};
|
||||
|
||||
mocked.setup.mockResolvedValue(createSetupContractMock());
|
||||
mocked.start.mockResolvedValue(createStartContractMock());
|
||||
mocked.setup.mockResolvedValue(createInternalSetupContractMock());
|
||||
mocked.start.mockResolvedValue(createInternalStartContractMock());
|
||||
mocked.stop.mockResolvedValue();
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const savedObjectsServiceMock = {
|
||||
create: createSavedObjectsServiceMock,
|
||||
createInternalSetupContract: createInternalSetupContractMock,
|
||||
createSetupContract: createSetupContractMock,
|
||||
createInternalStartContract: createInternalStartContractMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import { mockKibanaMigrator } from './migrations/kibana/kibana_migrator.mock';
|
||||
import { savedObjectsClientProviderMock } from './service/lib/scoped_client_provider.mock';
|
||||
import { typeRegistryMock } from './saved_objects_type_registry.mock';
|
||||
|
||||
export const migratorInstanceMock = mockKibanaMigrator.create();
|
||||
export const KibanaMigratorMock = jest.fn().mockImplementation(() => migratorInstanceMock);
|
||||
|
@ -30,3 +31,8 @@ export const clientProviderInstanceMock = savedObjectsClientProviderMock.create(
|
|||
jest.doMock('./service/lib/scoped_client_provider', () => ({
|
||||
SavedObjectsClientProvider: jest.fn().mockImplementation(() => clientProviderInstanceMock),
|
||||
}));
|
||||
|
||||
export const typeRegistryInstanceMock = typeRegistryMock.create();
|
||||
jest.doMock('./saved_objects_type_registry', () => ({
|
||||
SavedObjectTypeRegistry: jest.fn().mockImplementation(() => typeRegistryInstanceMock),
|
||||
}));
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
KibanaMigratorMock,
|
||||
migratorInstanceMock,
|
||||
clientProviderInstanceMock,
|
||||
typeRegistryInstanceMock,
|
||||
} from './saved_objects_service.test.mocks';
|
||||
|
||||
import { SavedObjectsService } from './saved_objects_service';
|
||||
|
@ -108,6 +109,25 @@ describe('SavedObjectsService', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registerType', () => {
|
||||
it('registers the type to the internal typeRegistry', async () => {
|
||||
const coreContext = mockCoreContext.create();
|
||||
const soService = new SavedObjectsService(coreContext);
|
||||
const setup = await soService.setup(createSetupDeps());
|
||||
|
||||
const type = {
|
||||
name: 'someType',
|
||||
hidden: false,
|
||||
namespaceAgnostic: false,
|
||||
mappings: { properties: {} },
|
||||
};
|
||||
setup.registerType(type);
|
||||
|
||||
expect(typeRegistryInstanceMock.registerType).toHaveBeenCalledTimes(1);
|
||||
expect(typeRegistryInstanceMock.registerType).toHaveBeenCalledWith(type);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#start()', () => {
|
||||
|
|
|
@ -21,7 +21,6 @@ import { CoreService } from 'src/core/types';
|
|||
import { first, filter, take } from 'rxjs/operators';
|
||||
import {
|
||||
SavedObjectsClient,
|
||||
SavedObjectsSchema,
|
||||
SavedObjectsClientProvider,
|
||||
ISavedObjectsClientProvider,
|
||||
SavedObjectsClientProviderOptions,
|
||||
|
@ -34,17 +33,17 @@ import { KibanaConfigType } from '../kibana_config';
|
|||
import { migrationsRetryCallCluster } from '../elasticsearch/retry_call_cluster';
|
||||
import { SavedObjectsConfigType } from './saved_objects_config';
|
||||
import { KibanaRequest } from '../http';
|
||||
import { SavedObjectsClientContract } from './types';
|
||||
import { SavedObjectsClientContract, SavedObjectsType } from './types';
|
||||
import { ISavedObjectsRepository, SavedObjectsRepository } from './service/lib/repository';
|
||||
import {
|
||||
SavedObjectsClientFactoryProvider,
|
||||
SavedObjectsClientWrapperFactory,
|
||||
} from './service/lib/scoped_client_provider';
|
||||
import { Logger } from '../logging';
|
||||
import { SavedObjectsMapping } from './mappings';
|
||||
import { MigrationDefinition } from './migrations/core/document_migrator';
|
||||
import { SavedObjectsSchemaDefinition } from './schema';
|
||||
import { convertLegacyTypes } from './utils';
|
||||
import { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objects_type_registry';
|
||||
import { PropertyValidators } from './validation';
|
||||
import { SavedObjectsSerializer } from './serialization';
|
||||
|
||||
/**
|
||||
* Saved Objects is Kibana's data persistence mechanism allowing plugins to
|
||||
|
@ -70,6 +69,7 @@ import { PropertyValidators } from './validation';
|
|||
* constructor.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { SavedObjectsClient, CoreSetup } from 'src/core/server';
|
||||
*
|
||||
* export class Plugin() {
|
||||
|
@ -79,6 +79,7 @@ import { PropertyValidators } from './validation';
|
|||
* })
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
|
@ -102,7 +103,9 @@ export interface SavedObjectsServiceSetup {
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type InternalSavedObjectsServiceSetup = SavedObjectsServiceSetup;
|
||||
export interface InternalSavedObjectsServiceSetup extends SavedObjectsServiceSetup {
|
||||
registerType: (type: SavedObjectsType) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saved Objects is Kibana's data persisentence mechanism allowing plugins to
|
||||
|
@ -146,6 +149,10 @@ export interface SavedObjectsServiceStart {
|
|||
* @param extraTypes - A list of additional hidden types the repository should have access to.
|
||||
*/
|
||||
createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository;
|
||||
/**
|
||||
* Creates a {@link SavedObjectsSerializer | serializer} that is aware of all registered types.
|
||||
*/
|
||||
createSerializer: () => SavedObjectsSerializer;
|
||||
}
|
||||
|
||||
export interface InternalSavedObjectsServiceStart extends SavedObjectsServiceStart {
|
||||
|
@ -157,6 +164,10 @@ export interface InternalSavedObjectsServiceStart extends SavedObjectsServiceSta
|
|||
* @deprecated Exposed only for injecting into Legacy
|
||||
*/
|
||||
clientProvider: ISavedObjectsClientProvider;
|
||||
/**
|
||||
* @deprecated Exposed only for injecting into Legacy
|
||||
*/
|
||||
typeRegistry: ISavedObjectTypeRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -207,9 +218,7 @@ export class SavedObjectsService
|
|||
private clientFactoryProvider?: SavedObjectsClientFactoryProvider;
|
||||
private clientFactoryWrappers: WrappedClientFactoryWrapper[] = [];
|
||||
|
||||
private mappings: SavedObjectsMapping[] = [];
|
||||
private migrations: MigrationDefinition = {};
|
||||
private schemas: SavedObjectsSchemaDefinition = {};
|
||||
private typeRegistry = new SavedObjectTypeRegistry();
|
||||
private validations: PropertyValidators = {};
|
||||
|
||||
constructor(private readonly coreContext: CoreContext) {
|
||||
|
@ -221,16 +230,12 @@ export class SavedObjectsService
|
|||
|
||||
this.setupDeps = setupDeps;
|
||||
|
||||
const {
|
||||
savedObjectSchemas: savedObjectsSchemasDefinition,
|
||||
savedObjectMappings,
|
||||
savedObjectMigrations,
|
||||
savedObjectValidations,
|
||||
} = setupDeps.legacyPlugins.uiExports;
|
||||
this.mappings = savedObjectMappings;
|
||||
this.migrations = savedObjectMigrations;
|
||||
this.schemas = savedObjectsSchemasDefinition;
|
||||
this.validations = savedObjectValidations;
|
||||
const legacyTypes = convertLegacyTypes(
|
||||
setupDeps.legacyPlugins.uiExports,
|
||||
setupDeps.legacyPlugins.pluginExtendedConfig
|
||||
);
|
||||
legacyTypes.forEach(type => this.typeRegistry.registerType(type));
|
||||
this.validations = setupDeps.legacyPlugins.uiExports.savedObjectValidations || {};
|
||||
|
||||
return {
|
||||
setClientFactoryProvider: provider => {
|
||||
|
@ -246,6 +251,9 @@ export class SavedObjectsService
|
|||
factory,
|
||||
});
|
||||
},
|
||||
registerType: type => {
|
||||
this.typeRegistry.registerType(type);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -303,8 +311,7 @@ export class SavedObjectsService
|
|||
const createRepository = (callCluster: APICaller, extraTypes: string[] = []) => {
|
||||
return SavedObjectsRepository.createRepository(
|
||||
migrator,
|
||||
new SavedObjectsSchema(this.schemas),
|
||||
this.setupDeps!.legacyPlugins.pluginExtendedConfig,
|
||||
this.typeRegistry,
|
||||
kibanaConfig.index,
|
||||
callCluster,
|
||||
extraTypes
|
||||
|
@ -335,9 +342,11 @@ export class SavedObjectsService
|
|||
return {
|
||||
migrator,
|
||||
clientProvider,
|
||||
typeRegistry: this.typeRegistry,
|
||||
getScopedClient: clientProvider.getClient.bind(clientProvider),
|
||||
createScopedRepository: repositoryFactory.createScopedRepository,
|
||||
createInternalRepository: repositoryFactory.createInternalRepository,
|
||||
createSerializer: () => new SavedObjectsSerializer(this.typeRegistry),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -348,18 +357,14 @@ export class SavedObjectsService
|
|||
savedObjectsConfig: SavedObjectsConfigType,
|
||||
migrationsRetryDelay?: number
|
||||
): KibanaMigrator {
|
||||
const savedObjectSchemas = new SavedObjectsSchema(this.schemas);
|
||||
const adminClient = this.setupDeps!.elasticsearch.adminClient;
|
||||
|
||||
return new KibanaMigrator({
|
||||
savedObjectSchemas,
|
||||
savedObjectMappings: this.mappings,
|
||||
savedObjectMigrations: this.migrations,
|
||||
savedObjectValidations: this.validations,
|
||||
typeRegistry: this.typeRegistry,
|
||||
logger: this.logger,
|
||||
kibanaVersion: this.coreContext.env.packageInfo.version,
|
||||
config: this.setupDeps!.legacyPlugins.pluginExtendedConfig,
|
||||
savedObjectsConfig,
|
||||
savedObjectValidations: this.validations,
|
||||
kibanaConfig,
|
||||
callCluster: migrationsRetryCallCluster(
|
||||
adminClient.callAsInternalUser,
|
||||
|
|
|
@ -17,19 +17,25 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsSchema } from './schema';
|
||||
import { ISavedObjectTypeRegistry } from './saved_objects_type_registry';
|
||||
|
||||
type Schema = PublicMethodsOf<SavedObjectsSchema>;
|
||||
const createSchemaMock = () => {
|
||||
const mocked: jest.Mocked<Schema> = {
|
||||
getIndexForType: jest.fn().mockReturnValue('.kibana-test'),
|
||||
isHiddenType: jest.fn().mockReturnValue(false),
|
||||
isNamespaceAgnostic: jest.fn((type: string) => type === 'global'),
|
||||
getConvertToAliasScript: jest.fn().mockReturnValue(undefined),
|
||||
const createRegistryMock = (): jest.Mocked<ISavedObjectTypeRegistry> => {
|
||||
const mock = {
|
||||
registerType: jest.fn(),
|
||||
getType: jest.fn(),
|
||||
getAllTypes: jest.fn(),
|
||||
isNamespaceAgnostic: jest.fn(),
|
||||
isHidden: jest.fn(),
|
||||
getIndex: jest.fn(),
|
||||
};
|
||||
return mocked;
|
||||
|
||||
mock.getIndex.mockReturnValue('.kibana-test');
|
||||
mock.isHidden.mockReturnValue(false);
|
||||
mock.isNamespaceAgnostic.mockImplementation((type: string) => type === 'global');
|
||||
|
||||
return mock;
|
||||
};
|
||||
|
||||
export const schemaMock = {
|
||||
create: createSchemaMock,
|
||||
export const typeRegistryMock = {
|
||||
create: createRegistryMock,
|
||||
};
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { SavedObjectTypeRegistry } from './saved_objects_type_registry';
|
||||
import { SavedObjectsType } from './types';
|
||||
|
||||
const createType = (type: Partial<SavedObjectsType>): SavedObjectsType => ({
|
||||
name: 'unknown',
|
||||
hidden: false,
|
||||
namespaceAgnostic: false,
|
||||
mappings: { properties: {} },
|
||||
migrations: {},
|
||||
...type,
|
||||
});
|
||||
|
||||
describe('SavedObjectTypeRegistry', () => {
|
||||
let registry: SavedObjectTypeRegistry;
|
||||
|
||||
beforeEach(() => {
|
||||
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(() => {
|
||||
registry.registerType(createType({ name: 'typeA' }));
|
||||
}).toThrowErrorMatchingInlineSnapshot(`"Type 'typeA' is already registered"`);
|
||||
});
|
||||
|
||||
describe('#getType', () => {
|
||||
it(`retrieve a type by it's name`, () => {
|
||||
const typeA = createType({ name: 'typeA' });
|
||||
const typeB = createType({ name: 'typeB' });
|
||||
registry.registerType(typeA);
|
||||
registry.registerType(typeB);
|
||||
registry.registerType(createType({ name: 'typeC' }));
|
||||
|
||||
expect(registry.getType('typeA')).toEqual(typeA);
|
||||
expect(registry.getType('typeB')).toEqual(typeB);
|
||||
expect(registry.getType('unknownType')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('forbids to mutate the registered types', () => {
|
||||
registry.registerType(
|
||||
createType({
|
||||
name: 'typeA',
|
||||
mappings: {
|
||||
properties: {
|
||||
someField: { type: 'text' },
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const typeA = registry.getType('typeA')!;
|
||||
|
||||
expect(() => {
|
||||
typeA.migrations = {};
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
typeA.name = 'foo';
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
typeA.mappings.properties = {};
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
typeA.indexPattern = '.overrided';
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getAllTypes', () => {
|
||||
it('returns all registered types', () => {
|
||||
const typeA = createType({ name: 'typeA' });
|
||||
const typeB = createType({ name: 'typeB' });
|
||||
const typeC = createType({ name: 'typeC' });
|
||||
registry.registerType(typeA);
|
||||
registry.registerType(typeB);
|
||||
|
||||
const registered = registry.getAllTypes();
|
||||
expect(registered.length).toEqual(2);
|
||||
expect(registered).toContainEqual(typeA);
|
||||
expect(registered).toContainEqual(typeB);
|
||||
expect(registered).not.toContainEqual(typeC);
|
||||
});
|
||||
|
||||
it('forbids to mutate the registered types', () => {
|
||||
registry.registerType(
|
||||
createType({
|
||||
name: 'typeA',
|
||||
mappings: {
|
||||
properties: {
|
||||
someField: { type: 'text' },
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
registry.registerType(
|
||||
createType({
|
||||
name: 'typeB',
|
||||
migrations: {
|
||||
'1.0.0': jest.fn(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const typeA = registry.getType('typeA')!;
|
||||
const typeB = registry.getType('typeB')!;
|
||||
|
||||
expect(() => {
|
||||
typeA.migrations = {};
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
typeA.name = 'foo';
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
typeB.mappings.properties = {};
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
typeB.indexPattern = '.overrided';
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('does not mutate the registered types when altering the list', () => {
|
||||
registry.registerType(createType({ name: 'typeA' }));
|
||||
registry.registerType(createType({ name: 'typeB' }));
|
||||
registry.registerType(createType({ name: 'typeC' }));
|
||||
|
||||
const types = registry.getAllTypes();
|
||||
types.splice(0, 3);
|
||||
|
||||
expect(registry.getAllTypes().length).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isNamespaceAgnostic', () => {
|
||||
it('returns correct value for the type', () => {
|
||||
registry.registerType(createType({ name: 'typeA', namespaceAgnostic: true }));
|
||||
registry.registerType(createType({ name: 'typeB', namespaceAgnostic: false }));
|
||||
|
||||
expect(registry.isNamespaceAgnostic('typeA')).toEqual(true);
|
||||
expect(registry.isNamespaceAgnostic('typeB')).toEqual(false);
|
||||
});
|
||||
it('returns false when the type is not registered', () => {
|
||||
registry.registerType(createType({ name: 'typeA', namespaceAgnostic: true }));
|
||||
registry.registerType(createType({ name: 'typeB', namespaceAgnostic: false }));
|
||||
|
||||
expect(registry.isNamespaceAgnostic('unknownType')).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isHidden', () => {
|
||||
it('returns correct value for the type', () => {
|
||||
registry.registerType(createType({ name: 'typeA', hidden: true }));
|
||||
registry.registerType(createType({ name: 'typeB', hidden: false }));
|
||||
|
||||
expect(registry.isHidden('typeA')).toEqual(true);
|
||||
expect(registry.isHidden('typeB')).toEqual(false);
|
||||
});
|
||||
it('returns false when the type is not registered', () => {
|
||||
registry.registerType(createType({ name: 'typeA', hidden: true }));
|
||||
registry.registerType(createType({ name: 'typeB', hidden: false }));
|
||||
|
||||
expect(registry.isHidden('unknownType')).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getIndex', () => {
|
||||
it('returns correct value for the type', () => {
|
||||
registry.registerType(createType({ name: 'typeA', indexPattern: '.custom-index' }));
|
||||
registry.registerType(createType({ name: 'typeB', indexPattern: '.another-index' }));
|
||||
registry.registerType(createType({ name: 'typeWithNoIndex' }));
|
||||
|
||||
expect(registry.getIndex('typeA')).toEqual('.custom-index');
|
||||
expect(registry.getIndex('typeB')).toEqual('.another-index');
|
||||
expect(registry.getIndex('typeWithNoIndex')).toBeUndefined();
|
||||
});
|
||||
it('returns undefined when the type is not registered', () => {
|
||||
registry.registerType(createType({ name: 'typeA', namespaceAgnostic: true }));
|
||||
registry.registerType(createType({ name: 'typeB', namespaceAgnostic: false }));
|
||||
|
||||
expect(registry.getIndex('unknownType')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
86
src/core/server/saved_objects/saved_objects_type_registry.ts
Normal file
86
src/core/server/saved_objects/saved_objects_type_registry.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { deepFreeze } from '../../utils';
|
||||
import { SavedObjectsType } from './types';
|
||||
|
||||
/**
|
||||
* See {@link SavedObjectTypeRegistry} for documentation.
|
||||
*
|
||||
* @internal
|
||||
* */
|
||||
export type ISavedObjectTypeRegistry = PublicMethodsOf<SavedObjectTypeRegistry>;
|
||||
|
||||
/**
|
||||
* Registry holding information about all the registered {@link SavedObjectsType | savedObject types}.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export class SavedObjectTypeRegistry {
|
||||
private readonly types = new Map<string, SavedObjectsType>();
|
||||
|
||||
/**
|
||||
* Register a {@link SavedObjectsType | type} inside the registry.
|
||||
* A type can only be registered once. subsequent calls with the same type name will throw an error.
|
||||
*/
|
||||
public registerType(type: SavedObjectsType) {
|
||||
if (this.types.has(type.name)) {
|
||||
throw new Error(`Type '${type.name}' is already registered`);
|
||||
}
|
||||
this.types.set(type.name, deepFreeze(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link SavedObjectsType | type} definition for given type name.
|
||||
*/
|
||||
public getType(type: string) {
|
||||
return this.types.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all {@link SavedObjectsType | types} currently registered.
|
||||
*/
|
||||
public getAllTypes() {
|
||||
return [...this.types.values()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `namespaceAgnostic` property for given type, or `false` if
|
||||
* the type is not registered.
|
||||
*/
|
||||
public isNamespaceAgnostic(type: string) {
|
||||
return this.types.get(type)?.namespaceAgnostic ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `hidden` property for given type, or `false` if
|
||||
* the type is not registered.
|
||||
*/
|
||||
public isHidden(type: string) {
|
||||
return this.types.get(type)?.hidden ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `indexPattern` property for given type, or `undefined` if
|
||||
* the type is not registered.
|
||||
*/
|
||||
public getIndex(type: string) {
|
||||
return this.types.get(type)?.indexPattern;
|
||||
}
|
||||
}
|
|
@ -19,6 +19,10 @@
|
|||
|
||||
import { LegacyConfig } from '../../legacy';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @internal
|
||||
**/
|
||||
interface SavedObjectsSchemaTypeDefinition {
|
||||
isNamespaceAgnostic: boolean;
|
||||
hidden?: boolean;
|
||||
|
@ -26,12 +30,18 @@ interface SavedObjectsSchemaTypeDefinition {
|
|||
convertToAliasScript?: string;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
/**
|
||||
* @deprecated
|
||||
* @internal
|
||||
**/
|
||||
export interface SavedObjectsSchemaDefinition {
|
||||
[key: string]: SavedObjectsSchemaTypeDefinition;
|
||||
[type: string]: SavedObjectsSchemaTypeDefinition;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
/**
|
||||
* @deprecated This is only used by the {@link SavedObjectsLegacyService | legacy savedObjects service}
|
||||
* @internal
|
||||
**/
|
||||
export class SavedObjectsSchema {
|
||||
private readonly definition?: SavedObjectsSchemaDefinition;
|
||||
constructor(schemaDefinition?: SavedObjectsSchemaDefinition) {
|
||||
|
@ -46,7 +56,6 @@ export class SavedObjectsSchema {
|
|||
return false;
|
||||
}
|
||||
|
||||
// TODO: Remove dependency on config when we move SavedObjectsSchema to NP
|
||||
public getIndexForType(config: LegacyConfig, type: string): string | undefined {
|
||||
if (this.definition != null && this.definition.hasOwnProperty(type)) {
|
||||
const { indexPattern } = this.definition[type];
|
||||
|
|
|
@ -22,167 +22,5 @@
|
|||
* the raw document format as stored in ElasticSearch.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
|
||||
import uuid from 'uuid';
|
||||
import { SavedObjectsSchema } from '../schema';
|
||||
import { decodeVersion, encodeVersion } from '../version';
|
||||
import { SavedObjectsMigrationVersion, SavedObjectReference } from '../types';
|
||||
|
||||
/**
|
||||
* A raw document as represented directly in the saved object index.
|
||||
*/
|
||||
export interface RawDoc {
|
||||
_id: string;
|
||||
_source: any;
|
||||
_type?: string;
|
||||
_seq_no?: number;
|
||||
_primary_term?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A saved object type definition that allows for miscellaneous, unknown
|
||||
* properties, as current discussions around security, ACLs, etc indicate
|
||||
* that future props are likely to be added. Migrations support this
|
||||
* scenario out of the box.
|
||||
*/
|
||||
interface SavedObjectDoc {
|
||||
attributes: object;
|
||||
id?: string; // NOTE: SavedObjectDoc is used for uncreated objects where `id` is optional
|
||||
type: string;
|
||||
namespace?: string;
|
||||
migrationVersion?: SavedObjectsMigrationVersion;
|
||||
version?: string;
|
||||
updated_at?: string;
|
||||
|
||||
[rootProp: string]: any;
|
||||
}
|
||||
|
||||
interface Referencable {
|
||||
references: SavedObjectReference[];
|
||||
}
|
||||
|
||||
/**
|
||||
* We want to have two types, one that guarantees a "references" attribute
|
||||
* will exist and one that allows it to be null. Since we're not migrating
|
||||
* all the saved objects to have a "references" array, we need to support
|
||||
* the scenarios where it may be missing (ex migrations).
|
||||
*/
|
||||
export type RawSavedObjectDoc = SavedObjectDoc & Partial<Referencable>;
|
||||
export type SanitizedSavedObjectDoc = SavedObjectDoc & Referencable;
|
||||
|
||||
function assertNonEmptyString(value: string, name: string) {
|
||||
if (!value || typeof value !== 'string') {
|
||||
throw new TypeError(`Expected "${value}" to be a ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export class SavedObjectsSerializer {
|
||||
private readonly schema: SavedObjectsSchema;
|
||||
|
||||
constructor(schema: SavedObjectsSchema) {
|
||||
this.schema = schema;
|
||||
}
|
||||
/**
|
||||
* Determines whether or not the raw document can be converted to a saved object.
|
||||
*
|
||||
* @param {RawDoc} rawDoc - The raw ES document to be tested
|
||||
*/
|
||||
public isRawSavedObject(rawDoc: RawDoc) {
|
||||
const { type, namespace } = rawDoc._source;
|
||||
const namespacePrefix =
|
||||
namespace && !this.schema.isNamespaceAgnostic(type) ? `${namespace}:` : '';
|
||||
return (
|
||||
type &&
|
||||
rawDoc._id.startsWith(`${namespacePrefix}${type}:`) &&
|
||||
rawDoc._source.hasOwnProperty(type)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a document from the format that is stored in elasticsearch to the saved object client format.
|
||||
*
|
||||
* @param {RawDoc} rawDoc - The raw ES document to be converted to saved object format.
|
||||
*/
|
||||
public rawToSavedObject(doc: RawDoc): SanitizedSavedObjectDoc {
|
||||
const { _id, _source, _seq_no, _primary_term } = doc;
|
||||
const { type, namespace } = _source;
|
||||
|
||||
const version =
|
||||
_seq_no != null || _primary_term != null
|
||||
? encodeVersion(_seq_no!, _primary_term!)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
type,
|
||||
id: this.trimIdPrefix(namespace, type, _id),
|
||||
...(namespace && !this.schema.isNamespaceAgnostic(type) && { namespace }),
|
||||
attributes: _source[type],
|
||||
references: _source.references || [],
|
||||
...(_source.migrationVersion && { migrationVersion: _source.migrationVersion }),
|
||||
...(_source.updated_at && { updated_at: _source.updated_at }),
|
||||
...(version && { version }),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a document from the saved object client format to the format that is stored in elasticsearch.
|
||||
*
|
||||
* @param {SanitizedSavedObjectDoc} savedObj - The saved object to be converted to raw ES format.
|
||||
*/
|
||||
public savedObjectToRaw(savedObj: SanitizedSavedObjectDoc): RawDoc {
|
||||
const {
|
||||
id,
|
||||
type,
|
||||
namespace,
|
||||
attributes,
|
||||
migrationVersion,
|
||||
updated_at,
|
||||
version,
|
||||
references,
|
||||
} = savedObj;
|
||||
const source = {
|
||||
[type]: attributes,
|
||||
type,
|
||||
references,
|
||||
...(namespace && !this.schema.isNamespaceAgnostic(type) && { namespace }),
|
||||
...(migrationVersion && { migrationVersion }),
|
||||
...(updated_at && { updated_at }),
|
||||
};
|
||||
|
||||
return {
|
||||
_id: this.generateRawId(namespace, type, id),
|
||||
_source: source,
|
||||
...(version != null && decodeVersion(version)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a saved object type and id, generates the compound id that is stored in the raw document.
|
||||
*
|
||||
* @param {string} namespace - The namespace of the saved object
|
||||
* @param {string} type - The saved object type
|
||||
* @param {string} id - The id of the saved object
|
||||
*/
|
||||
public generateRawId(namespace: string | undefined, type: string, id?: string) {
|
||||
const namespacePrefix =
|
||||
namespace && !this.schema.isNamespaceAgnostic(type) ? `${namespace}:` : '';
|
||||
return `${namespacePrefix}${type}:${id || uuid.v1()}`;
|
||||
}
|
||||
|
||||
private trimIdPrefix(namespace: string | undefined, type: string, id: string) {
|
||||
assertNonEmptyString(id, 'document id');
|
||||
assertNonEmptyString(type, 'saved object type');
|
||||
|
||||
const namespacePrefix =
|
||||
namespace && !this.schema.isNamespaceAgnostic(type) ? `${namespace}:` : '';
|
||||
const prefix = `${namespacePrefix}${type}:`;
|
||||
|
||||
if (!id.startsWith(prefix)) {
|
||||
return id;
|
||||
}
|
||||
|
||||
return id.slice(prefix.length);
|
||||
}
|
||||
}
|
||||
export { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc, SavedObjectsRawDoc } from './types';
|
||||
export { SavedObjectsSerializer } from './serializer';
|
||||
|
|
|
@ -18,14 +18,21 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { SavedObjectsSerializer } from '.';
|
||||
import { SavedObjectsSchema } from '../schema';
|
||||
import { SavedObjectsSerializer } from './serializer';
|
||||
import { typeRegistryMock } from '../saved_objects_type_registry.mock';
|
||||
import { encodeVersion } from '../version';
|
||||
|
||||
describe('saved object conversion', () => {
|
||||
let typeRegistry: ReturnType<typeof typeRegistryMock.create>;
|
||||
|
||||
beforeEach(() => {
|
||||
typeRegistry = typeRegistryMock.create();
|
||||
typeRegistry.isNamespaceAgnostic.mockReturnValue(false);
|
||||
});
|
||||
|
||||
describe('#rawToSavedObject', () => {
|
||||
test('it copies the _source.type property to type', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_source: {
|
||||
|
@ -36,7 +43,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('it copies the _source.references property to references', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_source: {
|
||||
|
@ -54,7 +61,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('if specified it copies the _source.migrationVersion property to migrationVersion', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_source: {
|
||||
|
@ -72,7 +79,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if _source.migrationVersion is unspecified it doesn't set migrationVersion`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_source: {
|
||||
|
@ -83,8 +90,8 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('it converts the id and type properties, and retains migrationVersion', () => {
|
||||
const now = new Date();
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const now = String(new Date());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'hello:world',
|
||||
_seq_no: 3,
|
||||
|
@ -121,7 +128,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if version is unspecified it doesn't set version`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_source: {
|
||||
|
@ -133,7 +140,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if specified it encodes _seq_no and _primary_term to version`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_seq_no: 4,
|
||||
|
@ -147,7 +154,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if only _seq_no is specified it throws`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(() =>
|
||||
serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
|
@ -161,7 +168,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if only _primary_term is throws`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(() =>
|
||||
serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
|
@ -175,7 +182,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('if specified it copies the _source.updated_at property to updated_at', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const now = Date();
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
|
@ -188,7 +195,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if _source.updated_at is unspecified it doesn't set updated_at`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_source: {
|
||||
|
@ -199,7 +206,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('it does not pass unknown properties through', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'universe',
|
||||
_source: {
|
||||
|
@ -221,7 +228,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('it does not create attributes if [type] is missing', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'universe',
|
||||
_source: {
|
||||
|
@ -236,7 +243,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('it fails for documents which do not specify a type', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(() =>
|
||||
serializer.rawToSavedObject({
|
||||
_id: 'universe',
|
||||
|
@ -244,13 +251,13 @@ describe('saved object conversion', () => {
|
|||
hello: {
|
||||
world: 'earth',
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
})
|
||||
).toThrow(/Expected "undefined" to be a saved object type/);
|
||||
});
|
||||
|
||||
test('it is complimentary with savedObjectToRaw', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const raw = {
|
||||
_id: 'foo-namespace:foo:bar',
|
||||
_primary_term: 24,
|
||||
|
@ -266,7 +273,7 @@ describe('saved object conversion', () => {
|
|||
bar: '9.8.7',
|
||||
},
|
||||
namespace: 'foo-namespace',
|
||||
updated_at: new Date(),
|
||||
updated_at: String(new Date()),
|
||||
references: [],
|
||||
},
|
||||
};
|
||||
|
@ -277,7 +284,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('it handles unprefixed ids', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'universe',
|
||||
_source: {
|
||||
|
@ -290,7 +297,7 @@ describe('saved object conversion', () => {
|
|||
|
||||
describe('namespaced type without a namespace', () => {
|
||||
test(`removes type prefix from _id`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_source: {
|
||||
|
@ -302,7 +309,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if prefixed by random prefix and type it copies _id to id`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'random:foo:bar',
|
||||
_source: {
|
||||
|
@ -314,7 +321,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`doesn't specify namespace`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_source: {
|
||||
|
@ -328,7 +335,7 @@ describe('saved object conversion', () => {
|
|||
|
||||
describe('namespaced type with a namespace', () => {
|
||||
test(`removes type and namespace prefix from _id`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'baz:foo:bar',
|
||||
_source: {
|
||||
|
@ -341,7 +348,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if prefixed by only type it copies _id to id`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_source: {
|
||||
|
@ -354,7 +361,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if prefixed by random prefix and type it copies _id to id`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'random:foo:bar',
|
||||
_source: {
|
||||
|
@ -367,7 +374,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`copies _source.namespace to namespace`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'baz:foo:bar',
|
||||
_source: {
|
||||
|
@ -381,10 +388,12 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
describe('namespace agnostic type with a namespace', () => {
|
||||
beforeEach(() => {
|
||||
typeRegistry.isNamespaceAgnostic.mockReturnValue(true);
|
||||
});
|
||||
|
||||
test(`removes type prefix from _id`, () => {
|
||||
const serializer = new SavedObjectsSerializer(
|
||||
new SavedObjectsSchema({ foo: { isNamespaceAgnostic: true } })
|
||||
);
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_source: {
|
||||
|
@ -397,9 +406,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if prefixed by namespace and type it copies _id to id`, () => {
|
||||
const serializer = new SavedObjectsSerializer(
|
||||
new SavedObjectsSchema({ foo: { isNamespaceAgnostic: true } })
|
||||
);
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'baz:foo:bar',
|
||||
_source: {
|
||||
|
@ -412,9 +419,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`doesn't copy _source.namespace to namespace`, () => {
|
||||
const serializer = new SavedObjectsSerializer(
|
||||
new SavedObjectsSchema({ foo: { isNamespaceAgnostic: true } })
|
||||
);
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'baz:foo:bar',
|
||||
_source: {
|
||||
|
@ -430,7 +435,7 @@ describe('saved object conversion', () => {
|
|||
|
||||
describe('#savedObjectToRaw', () => {
|
||||
test('it copies the type property to _source.type and uses the ROOT_TYPE as _type', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
type: 'foo',
|
||||
attributes: {},
|
||||
|
@ -440,7 +445,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('it copies the references property to _source.references', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
id: '1',
|
||||
type: 'foo',
|
||||
|
@ -457,7 +462,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('if specified it copies the updated_at property to _source.updated_at', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const now = new Date();
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
type: '',
|
||||
|
@ -469,7 +474,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if unspecified it doesn't add updated_at property to _source`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
type: '',
|
||||
attributes: {},
|
||||
|
@ -479,7 +484,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('it copies the migrationVersion property to _source.migrationVersion', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
type: '',
|
||||
attributes: {},
|
||||
|
@ -496,7 +501,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if unspecified it doesn't add migrationVersion property to _source`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
type: '',
|
||||
attributes: {},
|
||||
|
@ -506,7 +511,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('it decodes the version property to _seq_no and _primary_term', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
type: '',
|
||||
attributes: {},
|
||||
|
@ -518,7 +523,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if unspecified it doesn't add _seq_no or _primary_term properties`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
type: '',
|
||||
attributes: {},
|
||||
|
@ -529,7 +534,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`if version invalid it throws`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(() =>
|
||||
serializer.savedObjectToRaw({
|
||||
type: '',
|
||||
|
@ -540,7 +545,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('it copies attributes to _source[type]', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
type: 'foo',
|
||||
attributes: {
|
||||
|
@ -557,7 +562,7 @@ describe('saved object conversion', () => {
|
|||
|
||||
describe('namespaced type without a namespace', () => {
|
||||
test('generates an id prefixed with type, if no id is specified', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const v1 = serializer.savedObjectToRaw({
|
||||
type: 'foo',
|
||||
attributes: {
|
||||
|
@ -577,7 +582,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`doesn't specify _source.namespace`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
type: '',
|
||||
attributes: {},
|
||||
|
@ -589,7 +594,7 @@ describe('saved object conversion', () => {
|
|||
|
||||
describe('namespaced type with a namespace', () => {
|
||||
test('generates an id prefixed with namespace and type, if no id is specified', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const v1 = serializer.savedObjectToRaw({
|
||||
type: 'foo',
|
||||
namespace: 'bar',
|
||||
|
@ -611,7 +616,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`it copies namespace to _source.namespace`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
type: 'foo',
|
||||
attributes: {},
|
||||
|
@ -623,10 +628,12 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
describe('namespace agnostic type with a namespace', () => {
|
||||
beforeEach(() => {
|
||||
typeRegistry.isNamespaceAgnostic.mockReturnValue(true);
|
||||
});
|
||||
|
||||
test('generates an id prefixed with type, if no id is specified', () => {
|
||||
const serializer = new SavedObjectsSerializer(
|
||||
new SavedObjectsSchema({ foo: { isNamespaceAgnostic: true } })
|
||||
);
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const v1 = serializer.savedObjectToRaw({
|
||||
type: 'foo',
|
||||
namespace: 'bar',
|
||||
|
@ -648,9 +655,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`doesn't specify _source.namespace`, () => {
|
||||
const serializer = new SavedObjectsSerializer(
|
||||
new SavedObjectsSchema({ foo: { isNamespaceAgnostic: true } })
|
||||
);
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
type: 'foo',
|
||||
namespace: 'bar',
|
||||
|
@ -665,7 +670,7 @@ describe('saved object conversion', () => {
|
|||
describe('#isRawSavedObject', () => {
|
||||
describe('namespaced type without a namespace', () => {
|
||||
test('is true if the id is prefixed and the type matches', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'hello:world',
|
||||
|
@ -678,7 +683,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if the id is not prefixed', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'world',
|
||||
|
@ -691,19 +696,19 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if the type attribute is missing', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'hello:world',
|
||||
_source: {
|
||||
hello: {},
|
||||
},
|
||||
} as any,
|
||||
})
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test(`is false if the type prefix omits the :`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'helloworld',
|
||||
|
@ -716,7 +721,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if the type attribute does not match the id', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'hello:world',
|
||||
|
@ -730,7 +735,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if there is no [type] attribute', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'hello:world',
|
||||
|
@ -745,7 +750,7 @@ describe('saved object conversion', () => {
|
|||
|
||||
describe('namespaced type with a namespace', () => {
|
||||
test('is true if the id is prefixed with type and namespace and the type matches', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'foo:hello:world',
|
||||
|
@ -759,7 +764,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if the id is not prefixed by anything', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'world',
|
||||
|
@ -773,7 +778,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if the id is prefixed only with type and the type matches', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'hello:world',
|
||||
|
@ -787,7 +792,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if the id is prefixed only with namespace and the namespace matches', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'foo:world',
|
||||
|
@ -801,7 +806,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`is false if the id prefix omits the trailing :`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'foo:helloworld',
|
||||
|
@ -815,20 +820,20 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if the type attribute is missing', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'foo:hello:world',
|
||||
_source: {
|
||||
hello: {},
|
||||
namespace: 'foo',
|
||||
},
|
||||
} as any,
|
||||
})
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('is false if the type attribute does not match the id', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'foo:hello:world',
|
||||
|
@ -843,7 +848,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if the namespace attribute does not match the id', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'bar:jam:world',
|
||||
|
@ -858,7 +863,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if there is no [type] attribute', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'hello:world',
|
||||
|
@ -872,11 +877,13 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('namespace agonstic type with a namespace', () => {
|
||||
describe('namespace agnostic type with a namespace', () => {
|
||||
beforeEach(() => {
|
||||
typeRegistry.isNamespaceAgnostic.mockReturnValue(true);
|
||||
});
|
||||
|
||||
test('is true if the id is prefixed with type and the type matches', () => {
|
||||
const serializer = new SavedObjectsSerializer(
|
||||
new SavedObjectsSchema({ hello: { isNamespaceAgnostic: true } })
|
||||
);
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'hello:world',
|
||||
|
@ -890,9 +897,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if the id is not prefixed', () => {
|
||||
const serializer = new SavedObjectsSerializer(
|
||||
new SavedObjectsSchema({ hello: { isNamespaceAgnostic: true } })
|
||||
);
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'world',
|
||||
|
@ -906,9 +911,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if the id is prefixed with type and namespace', () => {
|
||||
const serializer = new SavedObjectsSerializer(
|
||||
new SavedObjectsSchema({ hello: { isNamespaceAgnostic: true } })
|
||||
);
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'foo:hello:world',
|
||||
|
@ -922,9 +925,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test(`is false if the type prefix omits the :`, () => {
|
||||
const serializer = new SavedObjectsSerializer(
|
||||
new SavedObjectsSchema({ hello: { isNamespaceAgnostic: true } })
|
||||
);
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'helloworld',
|
||||
|
@ -938,24 +939,20 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if the type attribute is missing', () => {
|
||||
const serializer = new SavedObjectsSerializer(
|
||||
new SavedObjectsSchema({ hello: { isNamespaceAgnostic: true } })
|
||||
);
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'hello:world',
|
||||
_source: {
|
||||
hello: {},
|
||||
namespace: 'foo',
|
||||
},
|
||||
} as any,
|
||||
})
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('is false if the type attribute does not match the id', () => {
|
||||
const serializer = new SavedObjectsSerializer(
|
||||
new SavedObjectsSchema({ hello: { isNamespaceAgnostic: true } })
|
||||
);
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'hello:world',
|
||||
|
@ -970,9 +967,7 @@ describe('saved object conversion', () => {
|
|||
});
|
||||
|
||||
test('is false if there is no [type] attribute', () => {
|
||||
const serializer = new SavedObjectsSerializer(
|
||||
new SavedObjectsSchema({ hello: { isNamespaceAgnostic: true } })
|
||||
);
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
expect(
|
||||
serializer.isRawSavedObject({
|
||||
_id: 'hello:world',
|
||||
|
@ -990,13 +985,13 @@ describe('saved object conversion', () => {
|
|||
describe('#generateRawId', () => {
|
||||
describe('namespaced type without a namespace', () => {
|
||||
test('generates an id if none is specified', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const id = serializer.generateRawId('', 'goodbye');
|
||||
expect(id).toMatch(/goodbye\:[\w-]+$/);
|
||||
});
|
||||
|
||||
test('uses the id that is specified', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const id = serializer.generateRawId('', 'hello', 'world');
|
||||
expect(id).toMatch('hello:world');
|
||||
});
|
||||
|
@ -1004,13 +999,13 @@ describe('saved object conversion', () => {
|
|||
|
||||
describe('namespaced type with a namespace', () => {
|
||||
test('generates an id if none is specified and prefixes namespace', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const id = serializer.generateRawId('foo', 'goodbye');
|
||||
expect(id).toMatch(/foo:goodbye\:[\w-]+$/);
|
||||
});
|
||||
|
||||
test('uses the id that is specified and prefixes the namespace', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const id = serializer.generateRawId('foo', 'hello', 'world');
|
||||
expect(id).toMatch('foo:hello:world');
|
||||
});
|
||||
|
@ -1018,15 +1013,15 @@ describe('saved object conversion', () => {
|
|||
|
||||
describe('namespace agnostic type with a namespace', () => {
|
||||
test(`generates an id if none is specified and doesn't prefix namespace`, () => {
|
||||
const serializer = new SavedObjectsSerializer(
|
||||
new SavedObjectsSchema({ foo: { isNamespaceAgnostic: true } })
|
||||
);
|
||||
typeRegistry.isNamespaceAgnostic.mockReturnValue(true);
|
||||
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const id = serializer.generateRawId('foo', 'goodbye');
|
||||
expect(id).toMatch(/goodbye\:[\w-]+$/);
|
||||
});
|
||||
|
||||
test(`uses the id that is specified and doesn't prefix the namespace`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const id = serializer.generateRawId('foo', 'hello', 'world');
|
||||
expect(id).toMatch('hello:world');
|
||||
});
|
151
src/core/server/saved_objects/serialization/serializer.ts
Normal file
151
src/core/server/saved_objects/serialization/serializer.ts
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
|
||||
import uuid from 'uuid';
|
||||
import { decodeVersion, encodeVersion } from '../version';
|
||||
import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry';
|
||||
import { SavedObjectsRawDoc, SavedObjectSanitizedDoc } from './types';
|
||||
|
||||
/**
|
||||
* A serializer that can be used to manually convert {@link SavedObjectsRawDoc | raw} or
|
||||
* {@link SavedObjectSanitizedDoc | sanitized} documents to the other kind.
|
||||
*
|
||||
* @remarks Serializer instances should only be created and accessed by calling {@link SavedObjectsServiceStart.createSerializer}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class SavedObjectsSerializer {
|
||||
private readonly registry: ISavedObjectTypeRegistry;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
constructor(registry: ISavedObjectTypeRegistry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
/**
|
||||
* Determines whether or not the raw document can be converted to a saved object.
|
||||
*
|
||||
* @param {SavedObjectsRawDoc} rawDoc - The raw ES document to be tested
|
||||
*/
|
||||
public isRawSavedObject(rawDoc: SavedObjectsRawDoc) {
|
||||
const { type, namespace } = rawDoc._source;
|
||||
const namespacePrefix =
|
||||
namespace && !this.registry.isNamespaceAgnostic(type) ? `${namespace}:` : '';
|
||||
return Boolean(
|
||||
type &&
|
||||
rawDoc._id.startsWith(`${namespacePrefix}${type}:`) &&
|
||||
rawDoc._source.hasOwnProperty(type)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a document from the format that is stored in elasticsearch to the saved object client format.
|
||||
*
|
||||
* @param {SavedObjectsRawDoc} doc - The raw ES document to be converted to saved object format.
|
||||
*/
|
||||
public rawToSavedObject(doc: SavedObjectsRawDoc): SavedObjectSanitizedDoc {
|
||||
const { _id, _source, _seq_no, _primary_term } = doc;
|
||||
const { type, namespace } = _source;
|
||||
|
||||
const version =
|
||||
_seq_no != null || _primary_term != null
|
||||
? encodeVersion(_seq_no!, _primary_term!)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
type,
|
||||
id: this.trimIdPrefix(namespace, type, _id),
|
||||
...(namespace && !this.registry.isNamespaceAgnostic(type) && { namespace }),
|
||||
attributes: _source[type],
|
||||
references: _source.references || [],
|
||||
...(_source.migrationVersion && { migrationVersion: _source.migrationVersion }),
|
||||
...(_source.updated_at && { updated_at: _source.updated_at }),
|
||||
...(version && { version }),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a document from the saved object client format to the format that is stored in elasticsearch.
|
||||
*
|
||||
* @param {SavedObjectSanitizedDoc} savedObj - The saved object to be converted to raw ES format.
|
||||
*/
|
||||
public savedObjectToRaw(savedObj: SavedObjectSanitizedDoc): SavedObjectsRawDoc {
|
||||
const {
|
||||
id,
|
||||
type,
|
||||
namespace,
|
||||
attributes,
|
||||
migrationVersion,
|
||||
updated_at,
|
||||
version,
|
||||
references,
|
||||
} = savedObj;
|
||||
const source = {
|
||||
[type]: attributes,
|
||||
type,
|
||||
references,
|
||||
...(namespace && !this.registry.isNamespaceAgnostic(type) && { namespace }),
|
||||
...(migrationVersion && { migrationVersion }),
|
||||
...(updated_at && { updated_at }),
|
||||
};
|
||||
|
||||
return {
|
||||
_id: this.generateRawId(namespace, type, id),
|
||||
_source: source,
|
||||
...(version != null && decodeVersion(version)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a saved object type and id, generates the compound id that is stored in the raw document.
|
||||
*
|
||||
* @param {string} namespace - The namespace of the saved object
|
||||
* @param {string} type - The saved object type
|
||||
* @param {string} id - The id of the saved object
|
||||
*/
|
||||
public generateRawId(namespace: string | undefined, type: string, id?: string) {
|
||||
const namespacePrefix =
|
||||
namespace && !this.registry.isNamespaceAgnostic(type) ? `${namespace}:` : '';
|
||||
return `${namespacePrefix}${type}:${id || uuid.v1()}`;
|
||||
}
|
||||
|
||||
private trimIdPrefix(namespace: string | undefined, type: string, id: string) {
|
||||
assertNonEmptyString(id, 'document id');
|
||||
assertNonEmptyString(type, 'saved object type');
|
||||
|
||||
const namespacePrefix =
|
||||
namespace && !this.registry.isNamespaceAgnostic(type) ? `${namespace}:` : '';
|
||||
const prefix = `${namespacePrefix}${type}:`;
|
||||
|
||||
if (!id.startsWith(prefix)) {
|
||||
return id;
|
||||
}
|
||||
|
||||
return id.slice(prefix.length);
|
||||
}
|
||||
}
|
||||
|
||||
function assertNonEmptyString(value: string, name: string) {
|
||||
if (!value || typeof value !== 'string') {
|
||||
throw new TypeError(`Expected "${value}" to be a ${name}`);
|
||||
}
|
||||
}
|
79
src/core/server/saved_objects/serialization/types.ts
Normal file
79
src/core/server/saved_objects/serialization/types.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsMigrationVersion, SavedObjectReference } from '../types';
|
||||
|
||||
/**
|
||||
* A raw document as represented directly in the saved object index.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface SavedObjectsRawDoc {
|
||||
_id: string;
|
||||
_source: SavedObjectsRawDocSource;
|
||||
_type?: string;
|
||||
_seq_no?: number;
|
||||
_primary_term?: number;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface SavedObjectsRawDocSource {
|
||||
type: string;
|
||||
namespace?: string;
|
||||
migrationVersion?: SavedObjectsMigrationVersion;
|
||||
updated_at?: string;
|
||||
references?: SavedObjectReference[];
|
||||
|
||||
[typeMapping: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* A saved object type definition that allows for miscellaneous, unknown
|
||||
* properties, as current discussions around security, ACLs, etc indicate
|
||||
* that future props are likely to be added. Migrations support this
|
||||
* scenario out of the box.
|
||||
*/
|
||||
interface SavedObjectDoc {
|
||||
attributes: object;
|
||||
id?: string; // NOTE: SavedObjectDoc is used for uncreated objects where `id` is optional
|
||||
type: string;
|
||||
namespace?: string;
|
||||
migrationVersion?: SavedObjectsMigrationVersion;
|
||||
version?: string;
|
||||
updated_at?: string;
|
||||
|
||||
[rootProp: string]: any;
|
||||
}
|
||||
|
||||
interface Referencable {
|
||||
references: SavedObjectReference[];
|
||||
}
|
||||
|
||||
/**
|
||||
* We want to have two types, one that guarantees a "references" attribute
|
||||
* will exist and one that allows it to be null. Since we're not migrating
|
||||
* all the saved objects to have a "references" array, we need to support
|
||||
* the scenarios where it may be missing (ex migrations).
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SavedObjectUnsanitizedDoc = SavedObjectDoc & Partial<Referencable>;
|
||||
|
||||
/** @public */
|
||||
export type SavedObjectSanitizedDoc = SavedObjectDoc & Referencable;
|
|
@ -21,10 +21,9 @@ import _ from 'lodash';
|
|||
import { SavedObjectsRepository } from './repository';
|
||||
import * as getSearchDslNS from './search_dsl/search_dsl';
|
||||
import { SavedObjectsErrorHelpers } from './errors';
|
||||
import { SavedObjectsSchema } from '../../schema';
|
||||
import { SavedObjectsSerializer } from '../../serialization';
|
||||
import { getRootPropertiesObjects } from '../../mappings/lib/get_root_properties_objects';
|
||||
import { encodeHitVersion } from '../../version';
|
||||
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
|
||||
|
||||
jest.mock('./search_dsl/search_dsl', () => ({ getSearchDsl: jest.fn() }));
|
||||
|
||||
|
@ -35,6 +34,7 @@ describe('SavedObjectsRepository', () => {
|
|||
let callAdminCluster;
|
||||
let savedObjectsRepository;
|
||||
let migrator;
|
||||
|
||||
const mockTimestamp = '2017-08-14T15:49:14.886Z';
|
||||
const mockTimestampFields = { updated_at: mockTimestamp };
|
||||
const mockVersionProps = { _seq_no: 1, _primary_term: 1 };
|
||||
|
@ -240,12 +240,95 @@ describe('SavedObjectsRepository', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const schema = new SavedObjectsSchema({
|
||||
globaltype: { isNamespaceAgnostic: true },
|
||||
foo: { isNamespaceAgnostic: true },
|
||||
bar: { isNamespaceAgnostic: true },
|
||||
baz: { indexPattern: 'beats' },
|
||||
hiddenType: { isNamespaceAgnostic: true, hidden: true },
|
||||
const typeRegistry = new SavedObjectTypeRegistry();
|
||||
typeRegistry.registerType({
|
||||
name: 'config',
|
||||
hidden: false,
|
||||
namespaceAgnostic: false,
|
||||
mappings: {
|
||||
properties: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
});
|
||||
typeRegistry.registerType({
|
||||
name: 'index-pattern',
|
||||
hidden: false,
|
||||
namespaceAgnostic: false,
|
||||
mappings: {
|
||||
properties: {
|
||||
someField: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
typeRegistry.registerType({
|
||||
name: 'dashboard',
|
||||
hidden: false,
|
||||
namespaceAgnostic: false,
|
||||
mappings: {
|
||||
properties: {
|
||||
otherField: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
typeRegistry.registerType({
|
||||
name: 'globaltype',
|
||||
hidden: false,
|
||||
namespaceAgnostic: true,
|
||||
mappings: {
|
||||
properties: {
|
||||
yetAnotherField: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
typeRegistry.registerType({
|
||||
name: 'foo',
|
||||
hidden: false,
|
||||
namespaceAgnostic: true,
|
||||
mappings: {
|
||||
properties: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
});
|
||||
typeRegistry.registerType({
|
||||
name: 'bar',
|
||||
hidden: false,
|
||||
namespaceAgnostic: true,
|
||||
mappings: {
|
||||
properties: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
});
|
||||
typeRegistry.registerType({
|
||||
name: 'baz',
|
||||
hidden: false,
|
||||
namespaceAgnostic: false,
|
||||
indexPattern: 'beats',
|
||||
mappings: {
|
||||
properties: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
});
|
||||
typeRegistry.registerType({
|
||||
name: 'hiddenType',
|
||||
hidden: true,
|
||||
namespaceAgnostic: true,
|
||||
mappings: {
|
||||
properties: {
|
||||
someField: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -255,16 +338,16 @@ describe('SavedObjectsRepository', () => {
|
|||
runMigrations: async () => ({ status: 'skipped' }),
|
||||
};
|
||||
|
||||
const serializer = new SavedObjectsSerializer(schema);
|
||||
const allTypes = Object.keys(getRootPropertiesObjects(mappings));
|
||||
const allowedTypes = [...new Set(allTypes.filter(type => !schema.isHiddenType(type)))];
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const allTypes = typeRegistry.getAllTypes().map(type => type.name);
|
||||
const allowedTypes = [...new Set(allTypes.filter(type => !typeRegistry.isHidden(type)))];
|
||||
|
||||
savedObjectsRepository = new SavedObjectsRepository({
|
||||
index: '.kibana-test',
|
||||
mappings,
|
||||
callCluster: callAdminCluster,
|
||||
migrator,
|
||||
schema,
|
||||
typeRegistry,
|
||||
serializer,
|
||||
allowedTypes,
|
||||
});
|
||||
|
@ -1171,7 +1254,7 @@ describe('SavedObjectsRepository', () => {
|
|||
expect(result).toEqual(deleteByQueryResults);
|
||||
expect(callAdminCluster).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, schema, {
|
||||
expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, typeRegistry, {
|
||||
namespace: 'my-namespace',
|
||||
type: ['config', 'baz', 'index-pattern', 'dashboard'],
|
||||
});
|
||||
|
@ -1261,7 +1344,11 @@ describe('SavedObjectsRepository', () => {
|
|||
|
||||
await savedObjectsRepository.find(relevantOpts);
|
||||
expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledTimes(1);
|
||||
expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, schema, relevantOpts);
|
||||
expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(
|
||||
mappings,
|
||||
typeRegistry,
|
||||
relevantOpts
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts KQL filter and passes keuryNode to getSearchDsl', async () => {
|
||||
|
|
|
@ -27,9 +27,12 @@ import { includedFields } from './included_fields';
|
|||
import { decorateEsError } from './decorate_es_error';
|
||||
import { SavedObjectsErrorHelpers } from './errors';
|
||||
import { decodeRequestVersion, encodeVersion, encodeHitVersion } from '../../version';
|
||||
import { SavedObjectsSchema } from '../../schema';
|
||||
import { KibanaMigrator } from '../../migrations';
|
||||
import { SavedObjectsSerializer, SanitizedSavedObjectDoc, RawDoc } from '../../serialization';
|
||||
import {
|
||||
SavedObjectsSerializer,
|
||||
SavedObjectSanitizedDoc,
|
||||
SavedObjectsRawDoc,
|
||||
} from '../../serialization';
|
||||
import {
|
||||
SavedObjectsBulkCreateObject,
|
||||
SavedObjectsBulkGetObject,
|
||||
|
@ -51,8 +54,8 @@ import {
|
|||
SavedObjectsMigrationVersion,
|
||||
MutatingOperationRefreshSetting,
|
||||
} from '../../types';
|
||||
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
|
||||
import { validateConvertFilterToKueryNode } from './filter_utils';
|
||||
import { LegacyConfig } from '../../../legacy';
|
||||
|
||||
// BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository
|
||||
// so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient.
|
||||
|
@ -75,11 +78,9 @@ const isLeft = <L, R>(either: Either<L, R>): either is Left<L> => {
|
|||
|
||||
export interface SavedObjectsRepositoryOptions {
|
||||
index: string;
|
||||
/** @deprecated Will be removed once SavedObjectsSchema is exposed from Core */
|
||||
config: LegacyConfig;
|
||||
mappings: IndexMapping;
|
||||
callCluster: APICaller;
|
||||
schema: SavedObjectsSchema;
|
||||
typeRegistry: SavedObjectTypeRegistry;
|
||||
serializer: SavedObjectsSerializer;
|
||||
migrator: KibanaMigrator;
|
||||
allowedTypes: string[];
|
||||
|
@ -118,9 +119,8 @@ export type ISavedObjectsRepository = Pick<SavedObjectsRepository, keyof SavedOb
|
|||
export class SavedObjectsRepository {
|
||||
private _migrator: KibanaMigrator;
|
||||
private _index: string;
|
||||
private _config: LegacyConfig;
|
||||
private _mappings: IndexMapping;
|
||||
private _schema: SavedObjectsSchema;
|
||||
private _registry: SavedObjectTypeRegistry;
|
||||
private _allowedTypes: string[];
|
||||
private _unwrappedCallCluster: APICaller;
|
||||
private _serializer: SavedObjectsSerializer;
|
||||
|
@ -135,8 +135,7 @@ export class SavedObjectsRepository {
|
|||
*/
|
||||
public static createRepository(
|
||||
migrator: KibanaMigrator,
|
||||
schema: SavedObjectsSchema,
|
||||
config: LegacyConfig,
|
||||
typeRegistry: SavedObjectTypeRegistry,
|
||||
indexName: string,
|
||||
callCluster: APICaller,
|
||||
extraTypes: string[] = [],
|
||||
|
@ -144,8 +143,8 @@ export class SavedObjectsRepository {
|
|||
): ISavedObjectsRepository {
|
||||
const mappings = migrator.getActiveMappings();
|
||||
const allTypes = Object.keys(getRootPropertiesObjects(mappings));
|
||||
const serializer = new SavedObjectsSerializer(schema);
|
||||
const visibleTypes = allTypes.filter(type => !schema.isHiddenType(type));
|
||||
const serializer = new SavedObjectsSerializer(typeRegistry);
|
||||
const visibleTypes = allTypes.filter(type => !typeRegistry.isHidden(type));
|
||||
|
||||
const missingTypeMappings = extraTypes.filter(type => !allTypes.includes(type));
|
||||
if (missingTypeMappings.length > 0) {
|
||||
|
@ -158,10 +157,9 @@ export class SavedObjectsRepository {
|
|||
|
||||
return new injectedConstructor({
|
||||
index: indexName,
|
||||
config,
|
||||
migrator,
|
||||
mappings,
|
||||
schema,
|
||||
typeRegistry,
|
||||
serializer,
|
||||
allowedTypes,
|
||||
callCluster: retryCallCluster(callCluster),
|
||||
|
@ -171,10 +169,9 @@ export class SavedObjectsRepository {
|
|||
private constructor(options: SavedObjectsRepositoryOptions) {
|
||||
const {
|
||||
index,
|
||||
config,
|
||||
mappings,
|
||||
callCluster,
|
||||
schema,
|
||||
typeRegistry,
|
||||
serializer,
|
||||
migrator,
|
||||
allowedTypes = [],
|
||||
|
@ -189,9 +186,8 @@ export class SavedObjectsRepository {
|
|||
// to returning them.
|
||||
this._migrator = migrator;
|
||||
this._index = index;
|
||||
this._config = config;
|
||||
this._mappings = mappings;
|
||||
this._schema = schema;
|
||||
this._registry = typeRegistry;
|
||||
if (allowedTypes.length === 0) {
|
||||
throw new Error('Empty or missing types for saved object repository!');
|
||||
}
|
||||
|
@ -201,7 +197,6 @@ export class SavedObjectsRepository {
|
|||
await migrator.runMigrations();
|
||||
return callCluster(...args);
|
||||
};
|
||||
this._schema = schema;
|
||||
this._serializer = serializer;
|
||||
}
|
||||
|
||||
|
@ -250,7 +245,7 @@ export class SavedObjectsRepository {
|
|||
references,
|
||||
});
|
||||
|
||||
const raw = this._serializer.savedObjectToRaw(migrated as SanitizedSavedObjectDoc);
|
||||
const raw = this._serializer.savedObjectToRaw(migrated as SavedObjectSanitizedDoc);
|
||||
|
||||
const response = await this._writeToCluster(method, {
|
||||
id: raw._id,
|
||||
|
@ -316,7 +311,7 @@ export class SavedObjectsRepository {
|
|||
namespace,
|
||||
updated_at: time,
|
||||
references: object.references || [],
|
||||
}) as SanitizedSavedObjectDoc
|
||||
}) as SavedObjectSanitizedDoc
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -435,7 +430,7 @@ export class SavedObjectsRepository {
|
|||
|
||||
const allTypes = Object.keys(getRootPropertiesObjects(this._mappings));
|
||||
|
||||
const typesToDelete = allTypes.filter(type => !this._schema.isNamespaceAgnostic(type));
|
||||
const typesToDelete = allTypes.filter(type => !this._registry.isNamespaceAgnostic(type));
|
||||
|
||||
const esOptions = {
|
||||
index: this.getIndicesForTypes(typesToDelete),
|
||||
|
@ -443,7 +438,7 @@ export class SavedObjectsRepository {
|
|||
refresh,
|
||||
body: {
|
||||
conflicts: 'proceed',
|
||||
...getSearchDsl(this._mappings, this._schema, {
|
||||
...getSearchDsl(this._mappings, this._registry, {
|
||||
namespace,
|
||||
type: typesToDelete,
|
||||
}),
|
||||
|
@ -531,7 +526,7 @@ export class SavedObjectsRepository {
|
|||
rest_total_hits_as_int: true,
|
||||
body: {
|
||||
seq_no_primary_term: true,
|
||||
...getSearchDsl(this._mappings, this._schema, {
|
||||
...getSearchDsl(this._mappings, this._registry, {
|
||||
search,
|
||||
defaultSearchOperator,
|
||||
searchFields,
|
||||
|
@ -562,7 +557,9 @@ export class SavedObjectsRepository {
|
|||
page,
|
||||
per_page: perPage,
|
||||
total: response.hits.total,
|
||||
saved_objects: response.hits.hits.map((hit: RawDoc) => this._rawToSavedObject(hit)),
|
||||
saved_objects: response.hits.hits.map((hit: SavedObjectsRawDoc) =>
|
||||
this._rawToSavedObject(hit)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -890,7 +887,7 @@ export class SavedObjectsRepository {
|
|||
updated_at: time,
|
||||
});
|
||||
|
||||
const raw = this._serializer.savedObjectToRaw(migrated as SanitizedSavedObjectDoc);
|
||||
const raw = this._serializer.savedObjectToRaw(migrated as SavedObjectSanitizedDoc);
|
||||
|
||||
const response = await this._writeToCluster('update', {
|
||||
id: this._serializer.generateRawId(namespace, type, id),
|
||||
|
@ -952,7 +949,7 @@ export class SavedObjectsRepository {
|
|||
* @param type - the type
|
||||
*/
|
||||
private getIndexForType(type: string) {
|
||||
return this._schema.getIndexForType(this._config, type) || this._index;
|
||||
return this._registry.getIndex(type) || this._index;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -964,7 +961,7 @@ export class SavedObjectsRepository {
|
|||
*/
|
||||
private getIndicesForTypes(types: string[]) {
|
||||
const unique = (array: string[]) => [...new Set(array)];
|
||||
return unique(types.map(t => this._schema.getIndexForType(this._config, t) || this._index));
|
||||
return unique(types.map(t => this.getIndexForType(t)));
|
||||
}
|
||||
|
||||
private _getCurrentTime() {
|
||||
|
@ -975,7 +972,7 @@ export class SavedObjectsRepository {
|
|||
// includes the namespace, and we use this for migrating documents. However, we don't
|
||||
// want the namespace to be returned from the repository, as the repository scopes each
|
||||
// method transparently to the specified namespace.
|
||||
private _rawToSavedObject(raw: RawDoc): SavedObject {
|
||||
private _rawToSavedObject(raw: SavedObjectsRawDoc): SavedObject {
|
||||
const savedObject = this._serializer.rawToSavedObject(raw);
|
||||
return omit(savedObject, 'namespace');
|
||||
}
|
||||
|
|
|
@ -18,43 +18,54 @@
|
|||
*/
|
||||
import { SavedObjectsRepository } from './repository';
|
||||
import { mockKibanaMigrator } from '../../migrations/kibana/kibana_migrator.mock';
|
||||
import { SavedObjectsSchema } from '../../schema';
|
||||
import { KibanaMigrator } from '../../migrations';
|
||||
import { LegacyConfig } from '../../../legacy';
|
||||
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
|
||||
|
||||
jest.mock('./repository');
|
||||
|
||||
const { SavedObjectsRepository: originalRepository } = jest.requireActual('./repository');
|
||||
|
||||
describe('SavedObjectsRepository#createRepository', () => {
|
||||
const callAdminCluster = jest.fn();
|
||||
const schema = new SavedObjectsSchema({
|
||||
nsAgnosticType: { isNamespaceAgnostic: true },
|
||||
nsType: { indexPattern: 'beats', isNamespaceAgnostic: false },
|
||||
hiddenType: { isNamespaceAgnostic: true, hidden: true },
|
||||
});
|
||||
const mappings = [
|
||||
{
|
||||
pluginId: 'testplugin',
|
||||
|
||||
const typeRegistry = new SavedObjectTypeRegistry();
|
||||
typeRegistry.registerType({
|
||||
name: 'nsAgnosticType',
|
||||
hidden: false,
|
||||
namespaceAgnostic: true,
|
||||
mappings: {
|
||||
properties: {
|
||||
nsAgnosticType: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
nsType: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
hiddenType: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
name: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
];
|
||||
const migrator = mockKibanaMigrator.create({ savedObjectMappings: mappings });
|
||||
migrations: {},
|
||||
});
|
||||
|
||||
typeRegistry.registerType({
|
||||
name: 'nsType',
|
||||
hidden: false,
|
||||
namespaceAgnostic: false,
|
||||
indexPattern: 'beats',
|
||||
mappings: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
migrations: {},
|
||||
});
|
||||
typeRegistry.registerType({
|
||||
name: 'hiddenType',
|
||||
hidden: true,
|
||||
namespaceAgnostic: true,
|
||||
mappings: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
migrations: {},
|
||||
});
|
||||
|
||||
const migrator = mockKibanaMigrator.create({ types: typeRegistry.getAllTypes() });
|
||||
const RepositoryConstructor = (SavedObjectsRepository as unknown) as jest.Mock<
|
||||
SavedObjectsRepository
|
||||
>;
|
||||
|
@ -67,8 +78,7 @@ describe('SavedObjectsRepository#createRepository', () => {
|
|||
try {
|
||||
originalRepository.createRepository(
|
||||
(migrator as unknown) as KibanaMigrator,
|
||||
schema,
|
||||
{} as LegacyConfig,
|
||||
typeRegistry,
|
||||
'.kibana-test',
|
||||
callAdminCluster,
|
||||
['unMappedType1', 'unmappedType2']
|
||||
|
@ -83,8 +93,7 @@ describe('SavedObjectsRepository#createRepository', () => {
|
|||
it('should create a repository without hidden types', () => {
|
||||
const repository = originalRepository.createRepository(
|
||||
(migrator as unknown) as KibanaMigrator,
|
||||
schema,
|
||||
{} as LegacyConfig,
|
||||
typeRegistry,
|
||||
'.kibana-test',
|
||||
callAdminCluster,
|
||||
[],
|
||||
|
@ -103,8 +112,7 @@ describe('SavedObjectsRepository#createRepository', () => {
|
|||
it('should create a repository with a unique list of hidden types', () => {
|
||||
const repository = originalRepository.createRepository(
|
||||
(migrator as unknown) as KibanaMigrator,
|
||||
schema,
|
||||
{} as LegacyConfig,
|
||||
typeRegistry,
|
||||
'.kibana-test',
|
||||
callAdminCluster,
|
||||
['hiddenType', 'hiddenType', 'hiddenType'],
|
||||
|
|
|
@ -17,10 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { schemaMock } from '../../../schema/schema.mock';
|
||||
import { typeRegistryMock } from '../../../saved_objects_type_registry.mock';
|
||||
import { getQueryParams } from './query_params';
|
||||
|
||||
const SCHEMA = schemaMock.create();
|
||||
const registry = typeRegistryMock.create();
|
||||
|
||||
const MAPPINGS = {
|
||||
properties: {
|
||||
type: {
|
||||
|
@ -85,7 +86,7 @@ const createTypeClause = (type: string, namespace?: string) => {
|
|||
describe('searchDsl/queryParams', () => {
|
||||
describe('no parameters', () => {
|
||||
it('searches for all known types without a namespace specified', () => {
|
||||
expect(getQueryParams({ mappings: MAPPINGS, schema: SCHEMA })).toEqual({
|
||||
expect(getQueryParams({ mappings: MAPPINGS, registry })).toEqual({
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -108,9 +109,7 @@ describe('searchDsl/queryParams', () => {
|
|||
|
||||
describe('namespace', () => {
|
||||
it('filters namespaced types for namespace, and ensures namespace agnostic types have no namespace', () => {
|
||||
expect(
|
||||
getQueryParams({ mappings: MAPPINGS, schema: SCHEMA, namespace: 'foo-namespace' })
|
||||
).toEqual({
|
||||
expect(getQueryParams({ mappings: MAPPINGS, registry, namespace: 'foo-namespace' })).toEqual({
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
|
@ -134,7 +133,7 @@ describe('searchDsl/queryParams', () => {
|
|||
describe('type (singular, namespaced)', () => {
|
||||
it('includes a terms filter for type and namespace not being specified', () => {
|
||||
expect(
|
||||
getQueryParams({ mappings: MAPPINGS, schema: SCHEMA, namespace: undefined, type: 'saved' })
|
||||
getQueryParams({ mappings: MAPPINGS, registry, namespace: undefined, type: 'saved' })
|
||||
).toEqual({
|
||||
query: {
|
||||
bool: {
|
||||
|
@ -155,7 +154,7 @@ describe('searchDsl/queryParams', () => {
|
|||
describe('type (singular, global)', () => {
|
||||
it('includes a terms filter for type and namespace not being specified', () => {
|
||||
expect(
|
||||
getQueryParams({ mappings: MAPPINGS, schema: SCHEMA, namespace: undefined, type: 'global' })
|
||||
getQueryParams({ mappings: MAPPINGS, registry, namespace: undefined, type: 'global' })
|
||||
).toEqual({
|
||||
query: {
|
||||
bool: {
|
||||
|
@ -178,7 +177,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: undefined,
|
||||
type: ['saved', 'global'],
|
||||
})
|
||||
|
@ -204,7 +203,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
type: ['saved', 'global'],
|
||||
})
|
||||
|
@ -230,7 +229,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: undefined,
|
||||
type: undefined,
|
||||
search: 'us*',
|
||||
|
@ -270,7 +269,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
type: undefined,
|
||||
search: 'us*',
|
||||
|
@ -310,7 +309,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: undefined,
|
||||
type: ['saved', 'global'],
|
||||
search: 'us*',
|
||||
|
@ -346,7 +345,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
type: ['saved', 'global'],
|
||||
search: 'us*',
|
||||
|
@ -382,7 +381,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: undefined,
|
||||
type: undefined,
|
||||
search: 'y*',
|
||||
|
@ -419,7 +418,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: undefined,
|
||||
type: undefined,
|
||||
search: 'y*',
|
||||
|
@ -456,7 +455,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: undefined,
|
||||
type: undefined,
|
||||
search: 'y*',
|
||||
|
@ -503,7 +502,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
type: undefined,
|
||||
search: 'y*',
|
||||
|
@ -540,7 +539,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
type: undefined,
|
||||
search: 'y*',
|
||||
|
@ -577,7 +576,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
type: undefined,
|
||||
search: 'y*',
|
||||
|
@ -624,7 +623,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: undefined,
|
||||
type: ['saved', 'global'],
|
||||
search: 'y*',
|
||||
|
@ -657,7 +656,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: undefined,
|
||||
type: ['saved', 'global'],
|
||||
search: 'y*',
|
||||
|
@ -690,7 +689,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: undefined,
|
||||
type: ['saved', 'global'],
|
||||
search: 'y*',
|
||||
|
@ -726,7 +725,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
type: ['saved', 'global'],
|
||||
search: 'y*',
|
||||
|
@ -759,7 +758,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
type: ['saved', 'global'],
|
||||
search: 'y*',
|
||||
|
@ -792,7 +791,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
type: ['saved', 'global'],
|
||||
search: 'y*',
|
||||
|
@ -828,7 +827,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
type: ['saved', 'global'],
|
||||
search: 'foo',
|
||||
|
@ -902,7 +901,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
type: ['saved', 'global'],
|
||||
search: undefined,
|
||||
|
@ -958,7 +957,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
kueryNode: {
|
||||
type: 'function',
|
||||
|
@ -1052,7 +1051,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
kueryNode: {
|
||||
type: 'function',
|
||||
|
@ -1189,7 +1188,7 @@ describe('searchDsl/queryParams', () => {
|
|||
expect(
|
||||
getQueryParams({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
registry,
|
||||
namespace: 'foo-namespace',
|
||||
search: 'y*',
|
||||
searchFields: ['title'],
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
import { esKuery } from '../../../../../../plugins/data/server';
|
||||
|
||||
import { getRootPropertiesObjects, IndexMapping } from '../../../mappings';
|
||||
import { SavedObjectsSchema } from '../../../schema';
|
||||
import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry';
|
||||
|
||||
/**
|
||||
* Gets the types based on the type. Uses mappings to support
|
||||
|
@ -61,8 +61,12 @@ function getFieldsForTypes(types: string[], searchFields?: string[]) {
|
|||
* Gets the clause that will filter for the type in the namespace.
|
||||
* Some types are namespace agnostic, so they must be treated differently.
|
||||
*/
|
||||
function getClauseForType(schema: SavedObjectsSchema, namespace: string | undefined, type: string) {
|
||||
if (namespace && !schema.isNamespaceAgnostic(type)) {
|
||||
function getClauseForType(
|
||||
registry: ISavedObjectTypeRegistry,
|
||||
namespace: string | undefined,
|
||||
type: string
|
||||
) {
|
||||
if (namespace && !registry.isNamespaceAgnostic(type)) {
|
||||
return {
|
||||
bool: {
|
||||
must: [{ term: { type } }, { term: { namespace } }],
|
||||
|
@ -85,7 +89,7 @@ interface HasReferenceQueryParams {
|
|||
|
||||
interface QueryParams {
|
||||
mappings: IndexMapping;
|
||||
schema: SavedObjectsSchema;
|
||||
registry: ISavedObjectTypeRegistry;
|
||||
namespace?: string;
|
||||
type?: string | string[];
|
||||
search?: string;
|
||||
|
@ -100,7 +104,7 @@ interface QueryParams {
|
|||
*/
|
||||
export function getQueryParams({
|
||||
mappings,
|
||||
schema,
|
||||
registry,
|
||||
namespace,
|
||||
type,
|
||||
search,
|
||||
|
@ -140,7 +144,7 @@ export function getQueryParams({
|
|||
},
|
||||
]
|
||||
: undefined,
|
||||
should: types.map(shouldType => getClauseForType(schema, namespace, shouldType)),
|
||||
should: types.map(shouldType => getClauseForType(registry, namespace, shouldType)),
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
jest.mock('./query_params');
|
||||
jest.mock('./sorting_params');
|
||||
|
||||
import { schemaMock } from '../../../schema/schema.mock';
|
||||
import { typeRegistryMock } from '../../../saved_objects_type_registry.mock';
|
||||
import * as queryParamsNS from './query_params';
|
||||
import { getSearchDsl } from './search_dsl';
|
||||
import * as sortParamsNS from './sorting_params';
|
||||
|
@ -28,8 +28,8 @@ import * as sortParamsNS from './sorting_params';
|
|||
const getQueryParams = queryParamsNS.getQueryParams as jest.Mock;
|
||||
const getSortingParams = sortParamsNS.getSortingParams as jest.Mock;
|
||||
|
||||
const SCHEMA = schemaMock.create();
|
||||
const MAPPINGS = { properties: {} };
|
||||
const registry = typeRegistryMock.create();
|
||||
const mappings = { properties: {} };
|
||||
|
||||
describe('getSearchDsl', () => {
|
||||
afterEach(() => {
|
||||
|
@ -40,7 +40,7 @@ describe('getSearchDsl', () => {
|
|||
describe('validation', () => {
|
||||
it('throws when type is not specified', () => {
|
||||
expect(() => {
|
||||
getSearchDsl(MAPPINGS, SCHEMA, {
|
||||
getSearchDsl(mappings, registry, {
|
||||
type: undefined as any,
|
||||
sortField: 'title',
|
||||
});
|
||||
|
@ -48,7 +48,7 @@ describe('getSearchDsl', () => {
|
|||
});
|
||||
it('throws when sortOrder without sortField', () => {
|
||||
expect(() => {
|
||||
getSearchDsl(MAPPINGS, SCHEMA, {
|
||||
getSearchDsl(mappings, registry, {
|
||||
type: 'foo',
|
||||
sortOrder: 'desc',
|
||||
});
|
||||
|
@ -70,11 +70,11 @@ describe('getSearchDsl', () => {
|
|||
},
|
||||
};
|
||||
|
||||
getSearchDsl(MAPPINGS, SCHEMA, opts);
|
||||
getSearchDsl(mappings, registry, opts);
|
||||
expect(getQueryParams).toHaveBeenCalledTimes(1);
|
||||
expect(getQueryParams).toHaveBeenCalledWith({
|
||||
mappings: MAPPINGS,
|
||||
schema: SCHEMA,
|
||||
mappings,
|
||||
registry,
|
||||
namespace: opts.namespace,
|
||||
type: opts.type,
|
||||
search: opts.search,
|
||||
|
@ -92,10 +92,10 @@ describe('getSearchDsl', () => {
|
|||
sortOrder: 'baz',
|
||||
};
|
||||
|
||||
getSearchDsl(MAPPINGS, SCHEMA, opts);
|
||||
getSearchDsl(mappings, registry, opts);
|
||||
expect(getSortingParams).toHaveBeenCalledTimes(1);
|
||||
expect(getSortingParams).toHaveBeenCalledWith(
|
||||
MAPPINGS,
|
||||
mappings,
|
||||
opts.type,
|
||||
opts.sortField,
|
||||
opts.sortOrder
|
||||
|
@ -105,7 +105,7 @@ describe('getSearchDsl', () => {
|
|||
it('returns combination of getQueryParams and getSortingParams', () => {
|
||||
getQueryParams.mockReturnValue({ a: 'a' });
|
||||
getSortingParams.mockReturnValue({ b: 'b' });
|
||||
expect(getSearchDsl(MAPPINGS, SCHEMA, { type: 'foo' })).toEqual({ a: 'a', b: 'b' });
|
||||
expect(getSearchDsl(mappings, registry, { type: 'foo' })).toEqual({ a: 'a', b: 'b' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
import Boom from 'boom';
|
||||
|
||||
import { IndexMapping } from '../../../mappings';
|
||||
import { SavedObjectsSchema } from '../../../schema';
|
||||
import { getQueryParams } from './query_params';
|
||||
import { getSortingParams } from './sorting_params';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { esKuery } from '../../../../../../plugins/data/server';
|
||||
import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry';
|
||||
|
||||
interface GetSearchDslOptions {
|
||||
type: string | string[];
|
||||
|
@ -43,7 +43,7 @@ interface GetSearchDslOptions {
|
|||
|
||||
export function getSearchDsl(
|
||||
mappings: IndexMapping,
|
||||
schema: SavedObjectsSchema,
|
||||
registry: ISavedObjectTypeRegistry,
|
||||
options: GetSearchDslOptions
|
||||
) {
|
||||
const {
|
||||
|
@ -69,7 +69,7 @@ export function getSearchDsl(
|
|||
return {
|
||||
...getQueryParams({
|
||||
mappings,
|
||||
schema,
|
||||
registry,
|
||||
namespace,
|
||||
type,
|
||||
search,
|
||||
|
|
|
@ -18,9 +18,8 @@
|
|||
*/
|
||||
|
||||
import { SavedObjectsClient } from './service/saved_objects_client';
|
||||
import { SavedObjectsMapping } from './mappings';
|
||||
import { MigrationDefinition } from './migrations/core/document_migrator';
|
||||
import { SavedObjectsSchemaDefinition } from './schema';
|
||||
import { SavedObjectsTypeMappingDefinition, SavedObjectsTypeMappingDefinitions } from './mappings';
|
||||
import { SavedObjectMigrationMap } from './migrations';
|
||||
import { PropertyValidators } from './validation';
|
||||
|
||||
export {
|
||||
|
@ -34,6 +33,7 @@ export {
|
|||
} from './import/types';
|
||||
|
||||
import { SavedObjectAttributes } from '../../types';
|
||||
import { LegacyConfig } from '../legacy';
|
||||
export {
|
||||
SavedObjectAttributes,
|
||||
SavedObjectAttribute,
|
||||
|
@ -208,13 +208,88 @@ export type MutatingOperationRefreshSetting = boolean | 'wait_for';
|
|||
*/
|
||||
export type SavedObjectsClientContract = Pick<SavedObjectsClient, keyof SavedObjectsClient>;
|
||||
|
||||
/**
|
||||
* @remarks This is only internal for now, and will only be public when we expose the registerType API
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface SavedObjectsType {
|
||||
/**
|
||||
* The name of the type, which is also used as the internal id.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Is the type hidden by default. If true, repositories will not have access to this type unless explicitly
|
||||
* declared as an `extraType` when creating the repository.
|
||||
*
|
||||
* See {@link SavedObjectsServiceStart.createInternalRepository | createInternalRepository}.
|
||||
*/
|
||||
hidden: boolean;
|
||||
/**
|
||||
* Is the type global (true), or namespaced (false).
|
||||
*/
|
||||
namespaceAgnostic: boolean;
|
||||
/**
|
||||
* If defined, the type instances will be stored in the given index instead of the default one.
|
||||
*/
|
||||
indexPattern?: string;
|
||||
/**
|
||||
* If defined, will be used to convert the type to an alias.
|
||||
*/
|
||||
convertToAliasScript?: string;
|
||||
/**
|
||||
* The {@link SavedObjectsTypeMappingDefinition | mapping definition} for the type.
|
||||
*/
|
||||
mappings: SavedObjectsTypeMappingDefinition;
|
||||
/**
|
||||
* An optional map of {@link SavedObjectMigrationFn | migrations} to be used to migrate the type.
|
||||
*/
|
||||
migrations?: SavedObjectMigrationMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated
|
||||
*/
|
||||
export interface SavedObjectsLegacyUiExports {
|
||||
savedObjectMappings: SavedObjectsMapping[];
|
||||
savedObjectMigrations: MigrationDefinition;
|
||||
savedObjectSchemas: SavedObjectsSchemaDefinition;
|
||||
savedObjectMappings: SavedObjectsLegacyMapping[];
|
||||
savedObjectMigrations: SavedObjectsLegacyMigrationDefinitions;
|
||||
savedObjectSchemas: SavedObjectsLegacySchemaDefinitions;
|
||||
savedObjectValidations: PropertyValidators;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated
|
||||
*/
|
||||
export interface SavedObjectsLegacyMapping {
|
||||
pluginId: string;
|
||||
properties: SavedObjectsTypeMappingDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated
|
||||
*/
|
||||
export interface SavedObjectsLegacyMigrationDefinitions {
|
||||
[type: string]: SavedObjectMigrationMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated
|
||||
*/
|
||||
interface SavedObjectsLegacyTypeSchema {
|
||||
isNamespaceAgnostic?: boolean;
|
||||
hidden?: boolean;
|
||||
indexPattern?: ((config: LegacyConfig) => string) | string;
|
||||
convertToAliasScript?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated
|
||||
*/
|
||||
export interface SavedObjectsLegacySchemaDefinitions {
|
||||
[type: string]: SavedObjectsLegacyTypeSchema;
|
||||
}
|
||||
|
|
285
src/core/server/saved_objects/utils.test.ts
Normal file
285
src/core/server/saved_objects/utils.test.ts
Normal file
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { legacyServiceMock } from '../legacy/legacy_service.mock';
|
||||
import { convertLegacyTypes, convertTypesToLegacySchema } from './utils';
|
||||
import { SavedObjectsLegacyUiExports, SavedObjectsType } from './types';
|
||||
import { LegacyConfig } from 'kibana/server';
|
||||
|
||||
describe('convertLegacyTypes', () => {
|
||||
let legacyConfig: ReturnType<typeof legacyServiceMock.createLegacyConfig>;
|
||||
|
||||
beforeEach(() => {
|
||||
legacyConfig = legacyServiceMock.createLegacyConfig();
|
||||
});
|
||||
|
||||
it('converts the legacy mappings using default values if no schemas are specified', () => {
|
||||
const uiExports: SavedObjectsLegacyUiExports = {
|
||||
savedObjectMappings: [
|
||||
{
|
||||
pluginId: 'pluginA',
|
||||
properties: {
|
||||
typeA: {
|
||||
properties: {
|
||||
fieldA: { type: 'text' },
|
||||
},
|
||||
},
|
||||
typeB: {
|
||||
properties: {
|
||||
fieldB: { type: 'text' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
pluginId: 'pluginB',
|
||||
properties: {
|
||||
typeC: {
|
||||
properties: {
|
||||
fieldC: { type: 'text' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
savedObjectMigrations: {},
|
||||
savedObjectSchemas: {},
|
||||
savedObjectValidations: {},
|
||||
};
|
||||
|
||||
const converted = convertLegacyTypes(uiExports, legacyConfig);
|
||||
expect(converted).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('merges the mappings and the schema to create the type when schema exists for the type', () => {
|
||||
const uiExports: SavedObjectsLegacyUiExports = {
|
||||
savedObjectMappings: [
|
||||
{
|
||||
pluginId: 'pluginA',
|
||||
properties: {
|
||||
typeA: {
|
||||
properties: {
|
||||
fieldA: { type: 'text' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
pluginId: 'pluginB',
|
||||
properties: {
|
||||
typeC: {
|
||||
properties: {
|
||||
fieldC: { type: 'text' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
savedObjectMigrations: {},
|
||||
savedObjectSchemas: {
|
||||
typeA: {
|
||||
indexPattern: 'fooBar',
|
||||
hidden: true,
|
||||
isNamespaceAgnostic: true,
|
||||
},
|
||||
},
|
||||
savedObjectValidations: {},
|
||||
};
|
||||
|
||||
const converted = convertLegacyTypes(uiExports, legacyConfig);
|
||||
expect(converted).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('invokes indexPattern to retrieve the index when it is a function', () => {
|
||||
const indexPatternAccessor: (config: LegacyConfig) => string = jest.fn(config => {
|
||||
config.get('foo.bar');
|
||||
return 'myIndex';
|
||||
});
|
||||
|
||||
const uiExports: SavedObjectsLegacyUiExports = {
|
||||
savedObjectMappings: [
|
||||
{
|
||||
pluginId: 'pluginA',
|
||||
properties: {
|
||||
typeA: {
|
||||
properties: {
|
||||
fieldA: { type: 'text' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
savedObjectMigrations: {},
|
||||
savedObjectSchemas: {
|
||||
typeA: {
|
||||
indexPattern: indexPatternAccessor,
|
||||
hidden: true,
|
||||
isNamespaceAgnostic: true,
|
||||
},
|
||||
},
|
||||
savedObjectValidations: {},
|
||||
};
|
||||
|
||||
const converted = convertLegacyTypes(uiExports, legacyConfig);
|
||||
|
||||
expect(indexPatternAccessor).toHaveBeenCalledWith(legacyConfig);
|
||||
expect(legacyConfig.get).toHaveBeenCalledWith('foo.bar');
|
||||
expect(converted.length).toEqual(1);
|
||||
expect(converted[0].indexPattern).toEqual('myIndex');
|
||||
});
|
||||
|
||||
it('import migrations from the uiExports', () => {
|
||||
const migrationsA = {
|
||||
'1.0.0': jest.fn(),
|
||||
'2.0.4': jest.fn(),
|
||||
};
|
||||
const migrationsB = {
|
||||
'1.5.3': jest.fn(),
|
||||
};
|
||||
|
||||
const uiExports: SavedObjectsLegacyUiExports = {
|
||||
savedObjectMappings: [
|
||||
{
|
||||
pluginId: 'pluginA',
|
||||
properties: {
|
||||
typeA: {
|
||||
properties: {
|
||||
fieldA: { type: 'text' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
pluginId: 'pluginB',
|
||||
properties: {
|
||||
typeB: {
|
||||
properties: {
|
||||
fieldC: { type: 'text' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
savedObjectMigrations: {
|
||||
typeA: migrationsA,
|
||||
typeB: migrationsB,
|
||||
},
|
||||
savedObjectSchemas: {},
|
||||
savedObjectValidations: {},
|
||||
};
|
||||
|
||||
const converted = convertLegacyTypes(uiExports, legacyConfig);
|
||||
expect(converted.length).toEqual(2);
|
||||
expect(converted[0].migrations).toEqual(migrationsA);
|
||||
expect(converted[1].migrations).toEqual(migrationsB);
|
||||
});
|
||||
|
||||
it('merges everything when all are present', () => {
|
||||
const uiExports: SavedObjectsLegacyUiExports = {
|
||||
savedObjectMappings: [
|
||||
{
|
||||
pluginId: 'pluginA',
|
||||
properties: {
|
||||
typeA: {
|
||||
properties: {
|
||||
fieldA: { type: 'text' },
|
||||
},
|
||||
},
|
||||
typeB: {
|
||||
properties: {
|
||||
fieldB: { type: 'text' },
|
||||
anotherFieldB: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
pluginId: 'pluginB',
|
||||
properties: {
|
||||
typeC: {
|
||||
properties: {
|
||||
fieldC: { type: 'text' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
savedObjectMigrations: {
|
||||
typeA: {
|
||||
'1.0.0': jest.fn(),
|
||||
'2.0.4': jest.fn(),
|
||||
},
|
||||
typeC: {
|
||||
'1.5.3': jest.fn(),
|
||||
},
|
||||
},
|
||||
savedObjectSchemas: {
|
||||
typeA: {
|
||||
indexPattern: jest.fn(config => {
|
||||
config.get('foo.bar');
|
||||
return 'myIndex';
|
||||
}),
|
||||
hidden: true,
|
||||
isNamespaceAgnostic: true,
|
||||
},
|
||||
typeB: {
|
||||
convertToAliasScript: 'some alias script',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
savedObjectValidations: {},
|
||||
};
|
||||
|
||||
const converted = convertLegacyTypes(uiExports, legacyConfig);
|
||||
expect(converted).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertTypesToLegacySchema', () => {
|
||||
it('converts types to the legacy schema format', () => {
|
||||
const types: SavedObjectsType[] = [
|
||||
{
|
||||
name: 'typeA',
|
||||
hidden: false,
|
||||
namespaceAgnostic: true,
|
||||
mappings: { properties: {} },
|
||||
convertToAliasScript: 'some script',
|
||||
},
|
||||
{
|
||||
name: 'typeB',
|
||||
hidden: true,
|
||||
namespaceAgnostic: false,
|
||||
indexPattern: 'myIndex',
|
||||
mappings: { properties: {} },
|
||||
},
|
||||
];
|
||||
expect(convertTypesToLegacySchema(types)).toEqual({
|
||||
typeA: {
|
||||
hidden: false,
|
||||
isNamespaceAgnostic: true,
|
||||
convertToAliasScript: 'some script',
|
||||
},
|
||||
typeB: {
|
||||
hidden: true,
|
||||
isNamespaceAgnostic: false,
|
||||
indexPattern: 'myIndex',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
76
src/core/server/saved_objects/utils.ts
Normal file
76
src/core/server/saved_objects/utils.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { LegacyConfig } from '../legacy';
|
||||
import { SavedObjectsType, SavedObjectsLegacyUiExports } from './types';
|
||||
import { SavedObjectsSchemaDefinition } from './schema';
|
||||
|
||||
/**
|
||||
* Converts the legacy savedObjects mappings, schema, and migrations
|
||||
* to actual {@link SavedObjectsType | saved object types}
|
||||
*/
|
||||
export const convertLegacyTypes = (
|
||||
{
|
||||
savedObjectMappings = [],
|
||||
savedObjectMigrations = {},
|
||||
savedObjectSchemas = {},
|
||||
}: SavedObjectsLegacyUiExports,
|
||||
legacyConfig: LegacyConfig
|
||||
): SavedObjectsType[] => {
|
||||
return savedObjectMappings.reduce((types, { pluginId, properties }) => {
|
||||
return [
|
||||
...types,
|
||||
...Object.entries(properties).map(([type, mappings]) => {
|
||||
const schema = savedObjectSchemas[type];
|
||||
const migrations = savedObjectMigrations[type];
|
||||
return {
|
||||
name: type,
|
||||
hidden: schema?.hidden ?? false,
|
||||
namespaceAgnostic: schema?.isNamespaceAgnostic ?? false,
|
||||
mappings,
|
||||
indexPattern:
|
||||
typeof schema?.indexPattern === 'function'
|
||||
? schema.indexPattern(legacyConfig)
|
||||
: schema?.indexPattern,
|
||||
convertToAliasScript: schema?.convertToAliasScript,
|
||||
migrations: migrations ?? {},
|
||||
};
|
||||
}),
|
||||
];
|
||||
}, [] as SavedObjectsType[]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert {@link SavedObjectsType | saved object types} to the legacy {@link SavedObjectsSchemaDefinition | schema} format
|
||||
*/
|
||||
export const convertTypesToLegacySchema = (
|
||||
types: SavedObjectsType[]
|
||||
): SavedObjectsSchemaDefinition => {
|
||||
return types.reduce((schema, type) => {
|
||||
return {
|
||||
...schema,
|
||||
[type.name]: {
|
||||
isNamespaceAgnostic: type.namespaceAgnostic,
|
||||
hidden: type.hidden,
|
||||
indexPattern: type.indexPattern,
|
||||
convertToAliasScript: type.convertToAliasScript,
|
||||
},
|
||||
};
|
||||
}, {} as SavedObjectsSchemaDefinition);
|
||||
};
|
|
@ -1421,6 +1421,19 @@ export interface SavedObjectAttributes {
|
|||
// @public
|
||||
export type SavedObjectAttributeSingle = string | number | boolean | null | undefined | SavedObjectAttributes;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "SavedObjectUnsanitizedDoc" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "SavedObjectMigrationFn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "SavedObjectUnsanitizedDoc"
|
||||
//
|
||||
// @public
|
||||
export type SavedObjectMigrationFn = (doc: SavedObjectUnsanitizedDoc, log: SavedObjectsMigrationLogger) => SavedObjectUnsanitizedDoc;
|
||||
|
||||
// @public
|
||||
export interface SavedObjectMigrationMap {
|
||||
// (undocumented)
|
||||
[version: string]: SavedObjectMigrationFn;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface SavedObjectReference {
|
||||
// (undocumented)
|
||||
|
@ -1431,6 +1444,12 @@ export interface SavedObjectReference {
|
|||
type: string;
|
||||
}
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "SavedObjectDoc" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-forgotten-export) The symbol "Referencable" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type SavedObjectSanitizedDoc = SavedObjectDoc & Referencable;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SavedObjectsBaseOptions {
|
||||
namespace?: string;
|
||||
|
@ -1534,6 +1553,32 @@ export interface SavedObjectsClientWrapperOptions {
|
|||
request: KibanaRequest;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface SavedObjectsComplexFieldMapping {
|
||||
// (undocumented)
|
||||
dynamic?: string;
|
||||
// (undocumented)
|
||||
properties: SavedObjectsMappingProperties;
|
||||
// (undocumented)
|
||||
type?: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface SavedObjectsCoreFieldMapping {
|
||||
// (undocumented)
|
||||
enabled?: boolean;
|
||||
// (undocumented)
|
||||
fields?: {
|
||||
[subfield: string]: {
|
||||
type: string;
|
||||
};
|
||||
};
|
||||
// (undocumented)
|
||||
index?: boolean;
|
||||
// (undocumented)
|
||||
type: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions {
|
||||
id?: string;
|
||||
|
@ -1629,6 +1674,9 @@ export interface SavedObjectsExportResultDetails {
|
|||
}>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type SavedObjectsFieldMapping = SavedObjectsCoreFieldMapping | SavedObjectsComplexFieldMapping;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions {
|
||||
// (undocumented)
|
||||
|
@ -1793,6 +1841,12 @@ export interface SavedObjectsLegacyService {
|
|||
types: string[];
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface SavedObjectsMappingProperties {
|
||||
// (undocumented)
|
||||
[field: string]: SavedObjectsFieldMapping;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SavedObjectsMigrationLogger {
|
||||
// (undocumented)
|
||||
|
@ -1800,6 +1854,8 @@ export interface SavedObjectsMigrationLogger {
|
|||
// (undocumented)
|
||||
info: (msg: string) => void;
|
||||
// (undocumented)
|
||||
warn: (msg: string) => void;
|
||||
// @deprecated (undocumented)
|
||||
warning: (msg: string) => void;
|
||||
}
|
||||
|
||||
|
@ -1809,8 +1865,6 @@ export interface SavedObjectsMigrationVersion {
|
|||
[pluginName: string]: string;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RawDoc" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export interface SavedObjectsRawDoc {
|
||||
// (undocumented)
|
||||
|
@ -1819,8 +1873,10 @@ export interface SavedObjectsRawDoc {
|
|||
_primary_term?: number;
|
||||
// (undocumented)
|
||||
_seq_no?: number;
|
||||
// Warning: (ae-forgotten-export) The symbol "SavedObjectsRawDocSource" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
_source: any;
|
||||
_source: SavedObjectsRawDocSource;
|
||||
// (undocumented)
|
||||
_type?: string;
|
||||
}
|
||||
|
@ -1834,7 +1890,7 @@ export class SavedObjectsRepository {
|
|||
// Warning: (ae-forgotten-export) The symbol "KibanaMigrator" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @internal
|
||||
static createRepository(migrator: KibanaMigrator, schema: SavedObjectsSchema, config: LegacyConfig, indexName: string, callCluster: APICaller, extraTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository;
|
||||
static createRepository(migrator: KibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, callCluster: APICaller, extraTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository;
|
||||
delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>;
|
||||
deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise<any>;
|
||||
// (undocumented)
|
||||
|
@ -1873,7 +1929,7 @@ export interface SavedObjectsResolveImportErrorsOptions {
|
|||
supportedTypes: string[];
|
||||
}
|
||||
|
||||
// @internal (undocumented)
|
||||
// @internal @deprecated (undocumented)
|
||||
export class SavedObjectsSchema {
|
||||
// Warning: (ae-forgotten-export) The symbol "SavedObjectsSchemaDefinition" needs to be exported by the entry point index.d.ts
|
||||
constructor(schemaDefinition?: SavedObjectsSchemaDefinition);
|
||||
|
@ -1887,14 +1943,16 @@ export class SavedObjectsSchema {
|
|||
isNamespaceAgnostic(type: string): boolean;
|
||||
}
|
||||
|
||||
// @internal (undocumented)
|
||||
// @public
|
||||
export class SavedObjectsSerializer {
|
||||
constructor(schema: SavedObjectsSchema);
|
||||
// Warning: (ae-forgotten-export) The symbol "ISavedObjectTypeRegistry" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @internal
|
||||
constructor(registry: ISavedObjectTypeRegistry);
|
||||
generateRawId(namespace: string | undefined, type: string, id?: string): string;
|
||||
isRawSavedObject(rawDoc: SavedObjectsRawDoc): any;
|
||||
// Warning: (ae-forgotten-export) The symbol "SanitizedSavedObjectDoc" needs to be exported by the entry point index.d.ts
|
||||
rawToSavedObject(doc: SavedObjectsRawDoc): SanitizedSavedObjectDoc;
|
||||
savedObjectToRaw(savedObj: SanitizedSavedObjectDoc): SavedObjectsRawDoc;
|
||||
isRawSavedObject(rawDoc: SavedObjectsRawDoc): boolean;
|
||||
rawToSavedObject(doc: SavedObjectsRawDoc): SavedObjectSanitizedDoc;
|
||||
savedObjectToRaw(savedObj: SavedObjectSanitizedDoc): SavedObjectsRawDoc;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
@ -1907,9 +1965,27 @@ export interface SavedObjectsServiceSetup {
|
|||
export interface SavedObjectsServiceStart {
|
||||
createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository;
|
||||
createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository;
|
||||
createSerializer: () => SavedObjectsSerializer;
|
||||
getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SavedObjectsType {
|
||||
convertToAliasScript?: string;
|
||||
hidden: boolean;
|
||||
indexPattern?: string;
|
||||
mappings: SavedObjectsTypeMappingDefinition;
|
||||
migrations?: SavedObjectMigrationMap;
|
||||
name: string;
|
||||
namespaceAgnostic: boolean;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface SavedObjectsTypeMappingDefinition {
|
||||
// (undocumented)
|
||||
properties: SavedObjectsMappingProperties;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions {
|
||||
references?: SavedObjectReference[];
|
||||
|
@ -1925,6 +2001,16 @@ export interface SavedObjectsUpdateResponse<T extends SavedObjectAttributes = an
|
|||
references: SavedObjectReference[] | undefined;
|
||||
}
|
||||
|
||||
// @internal
|
||||
export class SavedObjectTypeRegistry {
|
||||
getAllTypes(): SavedObjectsType[];
|
||||
getIndex(type: string): string | undefined;
|
||||
getType(type: string): SavedObjectsType | undefined;
|
||||
isHidden(type: string): boolean;
|
||||
isNamespaceAgnostic(type: string): boolean;
|
||||
registerType(type: SavedObjectsType): void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type ScopeableRequest = KibanaRequest | LegacyRequest | FakeRequest;
|
||||
|
||||
|
|
|
@ -28,7 +28,9 @@ import { ToolingLog } from '@kbn/dev-utils';
|
|||
import { Stats } from '../stats';
|
||||
import { deleteIndex } from './delete_index';
|
||||
import { KibanaMigrator } from '../../../core/server/saved_objects/migrations';
|
||||
import { SavedObjectsSchema } from '../../../core/server/saved_objects';
|
||||
import { LegacyConfig } from '../../../core/server';
|
||||
import { convertLegacyTypes } from '../../../core/server/saved_objects/utils';
|
||||
import { SavedObjectTypeRegistry } from '../../../core/server/saved_objects';
|
||||
// @ts-ignore
|
||||
import { collectUiExports } from '../../../legacy/ui/ui_exports';
|
||||
// @ts-ignore
|
||||
|
@ -101,9 +103,14 @@ export async function migrateKibanaIndex({
|
|||
const uiExports = await getUiExports(kibanaPluginIds);
|
||||
const kibanaVersion = await loadKibanaVersion();
|
||||
|
||||
const config: Record<string, string> = {
|
||||
const configKeys: Record<string, string> = {
|
||||
'xpack.task_manager.index': '.kibana_task_manager',
|
||||
};
|
||||
const config = { get: (path: string) => configKeys[path] };
|
||||
|
||||
const savedObjectTypes = convertLegacyTypes(uiExports, config as LegacyConfig);
|
||||
const typeRegistry = new SavedObjectTypeRegistry();
|
||||
savedObjectTypes.forEach(type => typeRegistry.registerType(type));
|
||||
|
||||
const logger = {
|
||||
trace: log.verbose.bind(log),
|
||||
|
@ -117,7 +124,6 @@ export async function migrateKibanaIndex({
|
|||
};
|
||||
|
||||
const migratorOptions = {
|
||||
config: { get: (path: string) => config[path] } as any,
|
||||
savedObjectsConfig: {
|
||||
scrollDuration: '5m',
|
||||
batchSize: 100,
|
||||
|
@ -129,9 +135,7 @@ export async function migrateKibanaIndex({
|
|||
} as any,
|
||||
logger,
|
||||
kibanaVersion,
|
||||
savedObjectSchemas: new SavedObjectsSchema(uiExports.savedObjectSchemas),
|
||||
savedObjectMappings: uiExports.savedObjectMappings,
|
||||
savedObjectMigrations: uiExports.savedObjectMigrations,
|
||||
typeRegistry,
|
||||
savedObjectValidations: uiExports.savedObjectValidations,
|
||||
callCluster: (path: string, ...args: any[]) =>
|
||||
(get(client, path) as Function).call(client, ...args),
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
|
||||
const mockLogger = {
|
||||
warning: () => {},
|
||||
warn: () => {},
|
||||
debug: () => {},
|
||||
info: () => {},
|
||||
};
|
||||
|
|
1
src/legacy/server/kbn_server.d.ts
vendored
1
src/legacy/server/kbn_server.d.ts
vendored
|
@ -114,6 +114,7 @@ export interface KibanaCore {
|
|||
elasticsearch: LegacyServiceSetupDeps['core']['elasticsearch'];
|
||||
hapiServer: LegacyServiceSetupDeps['core']['http']['server'];
|
||||
kibanaMigrator: LegacyServiceStartDeps['core']['savedObjects']['migrator'];
|
||||
typeRegistry: LegacyServiceStartDeps['core']['savedObjects']['typeRegistry'];
|
||||
legacy: ILegacyInternals;
|
||||
rendering: LegacyServiceSetupDeps['core']['rendering'];
|
||||
uiPlugins: LegacyServiceSetupDeps['core']['plugins']['uiPlugins'];
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
// Disable lint errors for imports from src/core/server/saved_objects until SavedObjects migration is complete
|
||||
/* eslint-disable @kbn/eslint/no-restricted-paths */
|
||||
import { SavedObjectsSchema } from '../../../core/server/saved_objects/schema';
|
||||
import { SavedObjectsSerializer } from '../../../core/server/saved_objects/serialization';
|
||||
import {
|
||||
SavedObjectsClient,
|
||||
SavedObjectsRepository,
|
||||
|
@ -29,6 +28,7 @@ import {
|
|||
resolveImportErrors,
|
||||
} from '../../../core/server/saved_objects';
|
||||
import { getRootPropertiesObjects } from '../../../core/server/saved_objects/mappings';
|
||||
import { convertTypesToLegacySchema } from '../../../core/server/saved_objects/utils';
|
||||
import { SavedObjectsManagement } from '../../../core/server/saved_objects/management';
|
||||
|
||||
import {
|
||||
|
@ -57,9 +57,10 @@ function getImportableAndExportableTypes({ kbnServer, visibleTypes }) {
|
|||
|
||||
export function savedObjectsMixin(kbnServer, server) {
|
||||
const migrator = kbnServer.newPlatform.__internals.kibanaMigrator;
|
||||
const typeRegistry = kbnServer.newPlatform.__internals.typeRegistry;
|
||||
const mappings = migrator.getActiveMappings();
|
||||
const allTypes = Object.keys(getRootPropertiesObjects(mappings));
|
||||
const schema = new SavedObjectsSchema(kbnServer.uiExports.savedObjectSchemas);
|
||||
const schema = new SavedObjectsSchema(convertTypesToLegacySchema(typeRegistry.getAllTypes()));
|
||||
const visibleTypes = allTypes.filter(type => !schema.isHiddenType(type));
|
||||
const importableAndExportableTypes = getImportableAndExportableTypes({ kbnServer, visibleTypes });
|
||||
|
||||
|
@ -99,7 +100,7 @@ export function savedObjectsMixin(kbnServer, server) {
|
|||
server.route(createResolveImportErrorsRoute(prereqs, server, importableAndExportableTypes));
|
||||
server.route(createLogLegacyImportRoute());
|
||||
|
||||
const serializer = new SavedObjectsSerializer(schema);
|
||||
const serializer = kbnServer.newPlatform.start.core.savedObjects.createSerializer();
|
||||
|
||||
const createRepository = (callCluster, extraTypes = []) => {
|
||||
if (typeof callCluster !== 'function') {
|
||||
|
@ -118,10 +119,9 @@ export function savedObjectsMixin(kbnServer, server) {
|
|||
|
||||
return new SavedObjectsRepository({
|
||||
index: config.get('kibana.index'),
|
||||
config,
|
||||
migrator,
|
||||
mappings,
|
||||
schema,
|
||||
typeRegistry,
|
||||
serializer,
|
||||
allowedTypes,
|
||||
callCluster,
|
||||
|
|
|
@ -22,6 +22,14 @@ import { savedObjectsMixin } from './saved_objects_mixin';
|
|||
import { mockKibanaMigrator } from '../../../core/server/saved_objects/migrations/kibana/kibana_migrator.mock';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { savedObjectsClientProviderMock } from '../../../core/server/saved_objects/service/lib/scoped_client_provider.mock';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { convertLegacyTypes } from '../../../core/server/saved_objects/utils';
|
||||
import { SavedObjectTypeRegistry } from '../../../core/server';
|
||||
import { coreMock } from '../../../core/server/mocks';
|
||||
|
||||
const mockConfig = {
|
||||
get: jest.fn().mockReturnValue('anything'),
|
||||
};
|
||||
|
||||
const savedObjectMappings = [
|
||||
{
|
||||
|
@ -61,7 +69,30 @@ const savedObjectMappings = [
|
|||
},
|
||||
];
|
||||
|
||||
const migrator = mockKibanaMigrator.create({ savedObjectMappings });
|
||||
const savedObjectSchemas = {
|
||||
hiddentype: {
|
||||
hidden: true,
|
||||
},
|
||||
doc1: {
|
||||
indexPattern: 'other-index',
|
||||
},
|
||||
};
|
||||
|
||||
const savedObjectTypes = convertLegacyTypes(
|
||||
{
|
||||
savedObjectMappings,
|
||||
savedObjectSchemas,
|
||||
savedObjectMigrations: {},
|
||||
},
|
||||
mockConfig
|
||||
);
|
||||
|
||||
const typeRegistry = new SavedObjectTypeRegistry();
|
||||
savedObjectTypes.forEach(type => typeRegistry.registerType(type));
|
||||
|
||||
const migrator = mockKibanaMigrator.create({
|
||||
types: savedObjectTypes,
|
||||
});
|
||||
|
||||
describe('Saved Objects Mixin', () => {
|
||||
let mockKbnServer;
|
||||
|
@ -113,7 +144,17 @@ describe('Saved Objects Mixin', () => {
|
|||
};
|
||||
mockKbnServer = {
|
||||
newPlatform: {
|
||||
__internals: { kibanaMigrator: migrator, savedObjectsClientProvider: clientProvider },
|
||||
__internals: {
|
||||
kibanaMigrator: migrator,
|
||||
savedObjectsClientProvider: clientProvider,
|
||||
typeRegistry,
|
||||
},
|
||||
setup: {
|
||||
core: coreMock.createSetup(),
|
||||
},
|
||||
start: {
|
||||
core: coreMock.createStart(),
|
||||
},
|
||||
},
|
||||
server: mockServer,
|
||||
ready: () => {},
|
||||
|
@ -124,14 +165,7 @@ describe('Saved Objects Mixin', () => {
|
|||
},
|
||||
uiExports: {
|
||||
savedObjectMappings,
|
||||
savedObjectSchemas: {
|
||||
hiddentype: {
|
||||
hidden: true,
|
||||
},
|
||||
doc1: {
|
||||
indexPattern: 'other-index',
|
||||
},
|
||||
},
|
||||
savedObjectSchemas,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -27,8 +27,10 @@ import {
|
|||
DocumentMigrator,
|
||||
IndexMigrator,
|
||||
} from '../../../../src/core/server/saved_objects/migrations/core';
|
||||
import { SavedObjectsSerializer } from '../../../../src/core/server/saved_objects/serialization';
|
||||
import { SavedObjectsSchema } from '../../../../src/core/server/saved_objects/schema';
|
||||
import {
|
||||
SavedObjectsSerializer,
|
||||
SavedObjectTypeRegistry,
|
||||
} from '../../../../src/core/server/saved_objects';
|
||||
|
||||
export default ({ getService }) => {
|
||||
const es = getService('legacyEs');
|
||||
|
@ -350,10 +352,15 @@ async function migrateIndex({
|
|||
validateDoc,
|
||||
obsoleteIndexTemplatePattern,
|
||||
}) {
|
||||
const typeRegistry = new SavedObjectTypeRegistry();
|
||||
const types = migrationsToTypes(migrations);
|
||||
types.forEach(type => typeRegistry.registerType(type));
|
||||
|
||||
const documentMigrator = new DocumentMigrator({
|
||||
kibanaVersion: '99.9.9',
|
||||
migrations,
|
||||
typeRegistry,
|
||||
validateDoc: validateDoc || _.noop,
|
||||
log: { info: _.noop, debug: _.noop, warn: _.noop },
|
||||
});
|
||||
|
||||
const migrator = new IndexMigrator({
|
||||
|
@ -366,12 +373,22 @@ async function migrateIndex({
|
|||
log: { info: _.noop, debug: _.noop, warn: _.noop },
|
||||
pollInterval: 50,
|
||||
scrollDuration: '5m',
|
||||
serializer: new SavedObjectsSerializer(new SavedObjectsSchema()),
|
||||
serializer: new SavedObjectsSerializer(typeRegistry),
|
||||
});
|
||||
|
||||
return await migrator.migrate();
|
||||
}
|
||||
|
||||
function migrationsToTypes(migrations) {
|
||||
return Object.entries(migrations).map(([type, migrations]) => ({
|
||||
name: type,
|
||||
hidden: false,
|
||||
namespaceAgnostic: false,
|
||||
mappings: { properties: {} },
|
||||
migrations: { ...migrations },
|
||||
}));
|
||||
}
|
||||
|
||||
async function fetchDocs({ callCluster, index }) {
|
||||
const {
|
||||
hits: { hits },
|
||||
|
|
|
@ -51,9 +51,7 @@ export function taskManager(kibana: any) {
|
|||
server.expose(
|
||||
createLegacyApi(
|
||||
getTaskManagerSetup(server)!
|
||||
.registerLegacyAPI({
|
||||
savedObjectSchemas,
|
||||
})
|
||||
.registerLegacyAPI({})
|
||||
.then((taskManagerPlugin: TaskManager) => {
|
||||
// we can't tell the Kibana Platform Task Manager plugin to
|
||||
// to wait to `start` as that happens before legacy plugins
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
|
||||
import { createTaskManager, LegacyDeps } from './create_task_manager';
|
||||
import { mockLogger } from './test_utils';
|
||||
import { CoreSetup, UuidServiceSetup } from 'kibana/server';
|
||||
import { savedObjectsRepositoryMock } from '../../../../src/core/server/mocks';
|
||||
import { CoreSetup, SavedObjectsSerializer, UuidServiceSetup } from '../../../../src/core/server';
|
||||
import {
|
||||
savedObjectsRepositoryMock,
|
||||
savedObjectsTypeRegistryMock,
|
||||
} from '../../../../src/core/server/mocks';
|
||||
|
||||
jest.mock('./task_manager');
|
||||
|
||||
|
@ -23,7 +26,7 @@ describe('createTaskManager', () => {
|
|||
|
||||
const getMockLegacyDeps = (): LegacyDeps => ({
|
||||
config: {},
|
||||
savedObjectSchemas: {},
|
||||
savedObjectsSerializer: new SavedObjectsSerializer(savedObjectsTypeRegistryMock.create()),
|
||||
elasticsearch: {
|
||||
callAsInternalUser: jest.fn(),
|
||||
},
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue