mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
APEX-54 Stricter type checking for unsafe_transform functions (#222973)
Address https://github.com/elastic/kibana/issues/216061 Adds an indirection layer in the definition of the `transformFn:`, which forces devs to explicitly define the types of the documents being transformed. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
6aafb4f7f4
commit
852f416a01
18 changed files with 471 additions and 181 deletions
|
@ -350,12 +350,24 @@ Used to execute an arbitrary transformation function.
|
|||
*Usage example:*
|
||||
|
||||
```ts
|
||||
let change: SavedObjectsModelUnsafeTransformChange = {
|
||||
// Please define your transform function on a separate const.
|
||||
// Use explicit types for the generic arguments, as shown below.
|
||||
// This will reduce the chances of introducing bugs.
|
||||
const transformFn: SavedObjectModelUnsafeTransformFn<BeforeType, AfterType> = (
|
||||
doc: SavedObjectModelTransformationDoc<BeforeType>
|
||||
) => {
|
||||
const attributes: AfterType = {
|
||||
...doc.attributes,
|
||||
someAddedField: 'defaultValue',
|
||||
};
|
||||
|
||||
return { document: { ...doc, attributes } };
|
||||
};
|
||||
|
||||
// this is how you would specify a change in the changes: []
|
||||
const change: SavedObjectsModelUnsafeTransformChange = {
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
document.attributes.someAddedField = 'defaultValue';
|
||||
return { document };
|
||||
},
|
||||
transformFn: (typeSafeGuard) => typeSafeGuard(transformFn),
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -1111,7 +1123,3 @@ Which is why, when using this option, the API consumer needs to make sure that *
|
|||
#### Using `bulkUpdate` for fields with large `json` blobs [_using_bulkupdate_for_fields_with_large_json_blobs]
|
||||
|
||||
The savedObjects `bulkUpdate` API will update documents client-side and then reindex the updated documents. These update operations are done in-memory, and cause memory constraint issues when updating many objects with large `json` blobs stored in some fields. As such, we recommend against using `bulkUpdate` for savedObjects that: - use arrays (as these tend to be large objects) - store large `json` blobs in some fields
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -104,9 +104,10 @@ export class EsoModelVersionExample
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((document) => {
|
||||
return { document };
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
schemas: {
|
||||
|
|
|
@ -185,7 +185,7 @@ describe('buildModelVersionTransformFn', () => {
|
|||
const changes: SavedObjectsModelChange[] = [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn,
|
||||
transformFn: (typeSafeGuard) => typeSafeGuard(transformFn),
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -203,11 +203,20 @@ describe('buildModelVersionTransformFn', () => {
|
|||
const changes: SavedObjectsModelChange[] = [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document, ctx) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard(
|
||||
(
|
||||
document: SavedObjectModelTransformationDoc<{
|
||||
oldProp?: string;
|
||||
newProp: string;
|
||||
}>,
|
||||
ctx
|
||||
) => {
|
||||
delete document.attributes.oldProp;
|
||||
document.attributes.newProp = 'newValue';
|
||||
return { document };
|
||||
},
|
||||
}
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -305,10 +314,17 @@ describe('buildModelVersionTransformFn', () => {
|
|||
},
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard(
|
||||
(
|
||||
document: SavedObjectModelTransformationDoc<{
|
||||
unsafeNewProp: string;
|
||||
}>
|
||||
) => {
|
||||
document.attributes.unsafeNewProp = 'unsafeNewValue';
|
||||
return { document };
|
||||
},
|
||||
}
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -14,6 +14,10 @@ import type {
|
|||
SavedObjectsModelUnsafeTransformChange,
|
||||
SavedObjectsModelDataBackfillChange,
|
||||
SavedObjectsModelDataRemovalChange,
|
||||
SavedObjectModelUnsafeTransformFn,
|
||||
SavedObjectModelTransformationDoc,
|
||||
SavedObjectModelTransformationContext,
|
||||
SavedObjectModelTransformationResult,
|
||||
} from '@kbn/core-saved-objects-server';
|
||||
|
||||
/**
|
||||
|
@ -59,11 +63,25 @@ export const dataBackfillChangeToTransformFn = (
|
|||
};
|
||||
};
|
||||
|
||||
// we must force 'any' type on the generic arguments of the received function
|
||||
// otherwise they are 'unknown' and they cannot be cast to the PreviousAttributes and NewAttributes
|
||||
// generic arguments needed by the typeSafeGuard functions
|
||||
type TypeSafeGuardUnsafeTransformFn = (
|
||||
fn: SavedObjectModelUnsafeTransformFn<any, any>
|
||||
) => SavedObjectModelUnsafeTransformFn;
|
||||
|
||||
const typeSafeGuard: TypeSafeGuardUnsafeTransformFn = (
|
||||
fn: SavedObjectModelUnsafeTransformFn
|
||||
): SavedObjectModelTransformationFn => {
|
||||
return (
|
||||
document: SavedObjectModelTransformationDoc,
|
||||
context: SavedObjectModelTransformationContext
|
||||
): SavedObjectModelTransformationResult => fn(document, context);
|
||||
};
|
||||
|
||||
export const unsafeTransformChangeToTransformFn = (
|
||||
change: SavedObjectsModelUnsafeTransformChange
|
||||
): SavedObjectModelTransformationFn => {
|
||||
return change.transformFn;
|
||||
};
|
||||
): SavedObjectModelTransformationFn => change.transformFn(typeSafeGuard);
|
||||
|
||||
const mergeTransformFunctions = (
|
||||
transformFns: SavedObjectModelTransformationFn[]
|
||||
|
|
|
@ -276,12 +276,24 @@ Used to execute an arbitrary transformation function.
|
|||
*Usage example:*
|
||||
|
||||
```ts
|
||||
let change: SavedObjectsModelUnsafeTransformChange = {
|
||||
// Please define your transform function on a separate const.
|
||||
// Use explicit types for the generic arguments, as shown below.
|
||||
// This will reduce the chances of introducing bugs.
|
||||
const transformFn: SavedObjectModelUnsafeTransformFn<BeforeType, AfterType> = (
|
||||
doc: SavedObjectModelTransformationDoc<BeforeType>
|
||||
) => {
|
||||
const attributes: AfterType = {
|
||||
...doc.attributes,
|
||||
someAddedField: 'defaultValue',
|
||||
};
|
||||
|
||||
return { document: { ...doc, attributes } };
|
||||
};
|
||||
|
||||
// this is how you would specify a change in the changes: []
|
||||
const change: SavedObjectsModelUnsafeTransformChange = {
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
document.attributes.someAddedField = 'defaultValue';
|
||||
return { document };
|
||||
},
|
||||
transformFn: (typeSafeGuard) => typeSafeGuard(transformFn),
|
||||
};
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { SavedObjectDoc } from '../serialization';
|
||||
import { SavedObjectsModelUnsafeTransformChange } from './model_change';
|
||||
import {
|
||||
SavedObjectModelTransformationContext,
|
||||
SavedObjectModelTransformationDoc,
|
||||
SavedObjectModelUnsafeTransformFn,
|
||||
} from './transformations';
|
||||
|
||||
interface BeforeType {
|
||||
a: boolean;
|
||||
}
|
||||
|
||||
interface AfterType extends BeforeType {
|
||||
aString: string;
|
||||
}
|
||||
|
||||
describe('test', () => {
|
||||
const testDoc: SavedObjectDoc<BeforeType> = {
|
||||
id: 'someType:docId',
|
||||
type: 'someType',
|
||||
attributes: {
|
||||
a: false,
|
||||
},
|
||||
};
|
||||
const testContext: SavedObjectModelTransformationContext = {
|
||||
log: {
|
||||
debug: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
modelVersion: 1,
|
||||
namespaceType: 'agnostic',
|
||||
};
|
||||
|
||||
it('TS fails if users try to define untyped transform functions', () => {
|
||||
const untypedTransformFn: SavedObjectModelUnsafeTransformFn = (doc) => {
|
||||
const attributes: AfterType = {
|
||||
// @ts-expect-error
|
||||
...doc.attributes,
|
||||
// @ts-expect-error
|
||||
aString: doc.attributes.a ? 'true' : 'false',
|
||||
};
|
||||
|
||||
return { document: { ...doc, attributes } };
|
||||
};
|
||||
|
||||
expect(untypedTransformFn(testDoc, testContext)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"document": Object {
|
||||
"attributes": Object {
|
||||
"a": false,
|
||||
"aString": "false",
|
||||
},
|
||||
"id": "someType:docId",
|
||||
"type": "someType",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('allows defining transform changes', () => {
|
||||
const transformFn: SavedObjectModelUnsafeTransformFn<BeforeType, AfterType> = (
|
||||
doc: SavedObjectModelTransformationDoc<BeforeType>
|
||||
) => {
|
||||
const attributes: AfterType = {
|
||||
...doc.attributes,
|
||||
aString: doc.attributes.a ? 'true' : 'false',
|
||||
};
|
||||
|
||||
return { document: { ...doc, attributes } };
|
||||
};
|
||||
|
||||
// this is how you would specify a change in the changes: []
|
||||
const change: SavedObjectsModelUnsafeTransformChange = {
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (typeSafeGuard) => typeSafeGuard(transformFn),
|
||||
};
|
||||
|
||||
expect(change).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"transformFn": [Function],
|
||||
"type": "unsafe_transform",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -10,6 +10,7 @@
|
|||
import type { SavedObjectsMappingProperties } from '../mapping_definition';
|
||||
import type {
|
||||
SavedObjectModelDataBackfillFn,
|
||||
SavedObjectModelTransformationFn,
|
||||
SavedObjectModelUnsafeTransformFn,
|
||||
} from './transformations';
|
||||
|
||||
|
@ -143,15 +144,26 @@ export interface SavedObjectsModelDataRemovalChange {
|
|||
|
||||
/**
|
||||
* A {@link SavedObjectsModelChange | model change} executing an arbitrary transformation function.
|
||||
*
|
||||
* Please define your transform function on a separate const.
|
||||
* Use explicit types for the generic arguments, as shown below.
|
||||
* This will reduce the chances of introducing bugs.
|
||||
* @example
|
||||
* ```ts
|
||||
* let change: SavedObjectsModelUnsafeTransformChange = {
|
||||
* const transformFn: SavedObjectModelUnsafeTransformFn<BeforeType, AfterType> = (
|
||||
* doc: SavedObjectModelTransformationDoc<BeforeType>
|
||||
* ) => {
|
||||
* const attributes: AfterType = {
|
||||
* ...doc.attributes,
|
||||
* someAddedField: 'defaultValue',
|
||||
* };
|
||||
*
|
||||
* return { document: { ...doc, attributes } };
|
||||
* };
|
||||
*
|
||||
* // this is how you would specify a change in the changes: []
|
||||
* const change: SavedObjectsModelUnsafeTransformChange = {
|
||||
* type: 'unsafe_transform',
|
||||
* transformFn: (document) => {
|
||||
* document.attributes.someAddedField = 'defaultValue';
|
||||
* return { document };
|
||||
* },
|
||||
* transformFn: (typeSafeGuard) => typeSafeGuard(transformFn),
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
|
@ -160,13 +172,14 @@ export interface SavedObjectsModelDataRemovalChange {
|
|||
* Those should only be used when there's no other way to cover one's migration needs.
|
||||
* Please reach out to the Core team if you think you need to use this, as you theoretically shouldn't.
|
||||
*/
|
||||
export interface SavedObjectsModelUnsafeTransformChange<
|
||||
PreviousAttributes = any,
|
||||
NewAttributes = any
|
||||
> {
|
||||
export interface SavedObjectsModelUnsafeTransformChange {
|
||||
type: 'unsafe_transform';
|
||||
/**
|
||||
* The transform function to execute.
|
||||
*/
|
||||
transformFn: SavedObjectModelUnsafeTransformFn<PreviousAttributes, NewAttributes>;
|
||||
transformFn: (
|
||||
typeSafeGuard: <PreviousAttributes, NewAttributes>(
|
||||
fn: SavedObjectModelUnsafeTransformFn<PreviousAttributes, NewAttributes>
|
||||
) => SavedObjectModelTransformationFn
|
||||
) => SavedObjectModelTransformationFn;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,10 @@
|
|||
*/
|
||||
|
||||
import type { SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server';
|
||||
import type { SavedObjectsType } from '@kbn/core-saved-objects-server';
|
||||
import type {
|
||||
SavedObjectModelUnsafeTransformFn,
|
||||
SavedObjectsType,
|
||||
} from '@kbn/core-saved-objects-server';
|
||||
import type { IndexTypesMap } from '@kbn/core-saved-objects-base-server-internal';
|
||||
import type { ElasticsearchClientWrapperFactory } from './elasticsearch_client_wrapper';
|
||||
import {
|
||||
|
@ -45,6 +48,18 @@ const defaultType: SavedObjectsType<any> = {
|
|||
|
||||
export const REMOVED_TYPES = ['deprecated', 'server'];
|
||||
|
||||
interface ComplexTypeV0 {
|
||||
name: string;
|
||||
value: number;
|
||||
firstHalf: boolean;
|
||||
}
|
||||
|
||||
interface ComplexTypeV1 {
|
||||
name: string;
|
||||
value: number;
|
||||
firstHalf: boolean;
|
||||
}
|
||||
|
||||
export const baselineTypes: Array<SavedObjectsType<any>> = [
|
||||
{
|
||||
...defaultType,
|
||||
|
@ -123,8 +138,18 @@ export const getCompatibleBaselineTypes = (removedTypes: string[]) =>
|
|||
}
|
||||
});
|
||||
|
||||
export const getReindexingBaselineTypes = (removedTypes: string[]) =>
|
||||
getUpToDateBaselineTypes(removedTypes).map<SavedObjectsType>((type) => {
|
||||
export const getReindexingBaselineTypes = (removedTypes: string[]) => {
|
||||
const transformComplex: SavedObjectModelUnsafeTransformFn<ComplexTypeV0, ComplexTypeV1> = (
|
||||
doc
|
||||
) => {
|
||||
if (doc.attributes.value % 100 === 0) {
|
||||
throw new Error(
|
||||
`Cannot convert 'complex' objects with values that are multiple of 100 ${doc.id}`
|
||||
);
|
||||
}
|
||||
return { document: doc };
|
||||
};
|
||||
return getUpToDateBaselineTypes(removedTypes).map<SavedObjectsType>((type) => {
|
||||
// introduce an incompatible change
|
||||
if (type.name === 'complex') {
|
||||
return {
|
||||
|
@ -152,14 +177,7 @@ export const getReindexingBaselineTypes = (removedTypes: string[]) =>
|
|||
},
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (doc) => {
|
||||
if (doc.attributes.value % 100 === 0) {
|
||||
throw new Error(
|
||||
`Cannot convert 'complex' objects with values that are multiple of 100 ${doc.id}`
|
||||
);
|
||||
}
|
||||
return { document: doc };
|
||||
},
|
||||
transformFn: (typeSafeGuard) => typeSafeGuard(transformComplex),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -192,6 +210,7 @@ export const getReindexingBaselineTypes = (removedTypes: string[]) =>
|
|||
return type;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export interface GetBaselineDocumentsParams {
|
||||
documentsPerType?: number;
|
||||
|
|
|
@ -107,9 +107,10 @@ describe('ZDT upgrades - encountering conversion failures', () => {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (doc) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((doc) => {
|
||||
throw new Error(`error from ${doc.id}`);
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -122,12 +123,13 @@ describe('ZDT upgrades - encountering conversion failures', () => {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (doc) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((doc) => {
|
||||
if (doc.id === 'b-0') {
|
||||
throw new Error(`error from ${doc.id}`);
|
||||
}
|
||||
return { document: doc };
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { SavedObjectsType, SavedObject } from '@kbn/core-saved-objects-server';
|
||||
import type {
|
||||
SavedObjectsType,
|
||||
SavedObject,
|
||||
SavedObjectModelUnsafeTransformFn,
|
||||
} from '@kbn/core-saved-objects-server';
|
||||
import { createModelVersionTestMigrator } from './model_version_tester';
|
||||
|
||||
const createObject = (parts: Partial<SavedObject>): SavedObject => {
|
||||
|
@ -22,6 +26,26 @@ const createObject = (parts: Partial<SavedObject>): SavedObject => {
|
|||
};
|
||||
|
||||
describe('modelVersionTester', () => {
|
||||
interface V3 {
|
||||
someExistingField: string;
|
||||
}
|
||||
|
||||
interface V4 extends V3 {
|
||||
fieldUnsafelyAddedInV4: string;
|
||||
}
|
||||
|
||||
const testTypeUnsafeTransform: SavedObjectModelUnsafeTransformFn<V3, V4> = (doc) => {
|
||||
const transformedDoc = {
|
||||
...doc,
|
||||
attributes: {
|
||||
...doc.attributes,
|
||||
fieldUnsafelyAddedInV4: '4',
|
||||
},
|
||||
};
|
||||
|
||||
return { document: transformedDoc };
|
||||
};
|
||||
|
||||
const testType: SavedObjectsType = {
|
||||
name: 'test-type',
|
||||
hidden: false,
|
||||
|
@ -90,14 +114,7 @@ describe('modelVersionTester', () => {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (doc) => {
|
||||
doc.attributes = {
|
||||
...doc.attributes,
|
||||
fieldUnsafelyAddedInV4: '4',
|
||||
};
|
||||
|
||||
return { document: doc };
|
||||
},
|
||||
transformFn: (typeSafeGuard) => typeSafeGuard(testTypeUnsafeTransform),
|
||||
},
|
||||
],
|
||||
schemas: {
|
||||
|
|
|
@ -9,6 +9,10 @@ import { logger } from 'elastic-apm-node';
|
|||
|
||||
import type {
|
||||
SavedObjectModelTransformationContext,
|
||||
SavedObjectModelTransformationDoc,
|
||||
SavedObjectModelTransformationFn,
|
||||
SavedObjectModelTransformationResult,
|
||||
SavedObjectModelUnsafeTransformFn,
|
||||
SavedObjectsModelUnsafeTransformChange,
|
||||
} from '@kbn/core-saved-objects-server';
|
||||
|
||||
|
@ -17,6 +21,15 @@ import type { EncryptedSavedObjectTypeRegistration } from './crypto';
|
|||
import { EncryptionError, EncryptionErrorOperation } from './crypto';
|
||||
import { encryptedSavedObjectsServiceMock } from './crypto/index.mock';
|
||||
|
||||
const dummyTypeSafeGuard = (
|
||||
fn: SavedObjectModelUnsafeTransformFn<any, any>
|
||||
): SavedObjectModelTransformationFn => {
|
||||
return (
|
||||
document: SavedObjectModelTransformationDoc,
|
||||
ctx: SavedObjectModelTransformationContext
|
||||
): SavedObjectModelTransformationResult => fn(document, ctx);
|
||||
};
|
||||
|
||||
describe('create ESO model version', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -47,9 +60,10 @@ describe('create ESO model version', () => {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((document) => {
|
||||
return { document };
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -99,10 +113,11 @@ describe('create ESO model version', () => {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((document: SavedObjectModelTransformationDoc<{ three: string }>) => {
|
||||
document.attributes.three = '3';
|
||||
return { document };
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: 'data_removal',
|
||||
|
@ -110,10 +125,11 @@ describe('create ESO model version', () => {
|
|||
},
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((document: SavedObjectModelTransformationDoc<{ two: string }>) => {
|
||||
document.attributes.two = '2';
|
||||
return { document: { ...document, new_prop_1: 'new prop 1' } };
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: 'data_backfill',
|
||||
|
@ -123,10 +139,11 @@ describe('create ESO model version', () => {
|
|||
},
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((document: SavedObjectModelTransformationDoc<{ four: string }>) => {
|
||||
document.attributes.four = '4';
|
||||
return { document: { ...document, new_prop_2: 'new prop 2' } };
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -157,7 +174,7 @@ describe('create ESO model version', () => {
|
|||
) as SavedObjectsModelUnsafeTransformChange[];
|
||||
expect(unsafeTransforms.length === 1);
|
||||
|
||||
const result = unsafeTransforms[0].transformFn(
|
||||
const result = unsafeTransforms[0].transformFn(dummyTypeSafeGuard)(
|
||||
{
|
||||
id: '123',
|
||||
type: 'known-type-1',
|
||||
|
@ -204,9 +221,10 @@ describe('create ESO model version', () => {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((document) => {
|
||||
return { document };
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -228,7 +246,7 @@ describe('create ESO model version', () => {
|
|||
) as SavedObjectsModelUnsafeTransformChange[];
|
||||
expect(unsafeTransforms.length === 1);
|
||||
expect(() => {
|
||||
unsafeTransforms[0].transformFn(
|
||||
unsafeTransforms[0].transformFn(dummyTypeSafeGuard)(
|
||||
{
|
||||
id: '123',
|
||||
type: 'known-type-1',
|
||||
|
@ -265,9 +283,10 @@ describe('create ESO model version', () => {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((document) => {
|
||||
return { document };
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -289,7 +308,7 @@ describe('create ESO model version', () => {
|
|||
) as SavedObjectsModelUnsafeTransformChange[];
|
||||
expect(unsafeTransforms.length === 1);
|
||||
expect(() => {
|
||||
unsafeTransforms[0].transformFn(
|
||||
unsafeTransforms[0].transformFn(dummyTypeSafeGuard)(
|
||||
{
|
||||
id: '123',
|
||||
type: 'known-type-1',
|
||||
|
@ -327,9 +346,10 @@ describe('create ESO model version', () => {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((document) => {
|
||||
return { document };
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -363,7 +383,7 @@ describe('create ESO model version', () => {
|
|||
(change) => change.type === 'unsafe_transform'
|
||||
) as SavedObjectsModelUnsafeTransformChange[];
|
||||
expect(unsafeTransforms.length === 1);
|
||||
unsafeTransforms[0].transformFn(
|
||||
unsafeTransforms[0].transformFn(dummyTypeSafeGuard)(
|
||||
{
|
||||
id: '123',
|
||||
type: 'known-type-1',
|
||||
|
@ -406,9 +426,10 @@ describe('create ESO model version', () => {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((document) => {
|
||||
throw new Error('transform failed!');
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -427,7 +448,7 @@ describe('create ESO model version', () => {
|
|||
) as SavedObjectsModelUnsafeTransformChange[];
|
||||
expect(unsafeTransforms.length === 1);
|
||||
expect(() => {
|
||||
unsafeTransforms[0].transformFn(
|
||||
unsafeTransforms[0].transformFn(dummyTypeSafeGuard)(
|
||||
{
|
||||
id: '123',
|
||||
type: 'known-type-1',
|
||||
|
@ -464,9 +485,10 @@ describe('create ESO model version', () => {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((document) => {
|
||||
throw new Error('transform failed!');
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -486,7 +508,7 @@ describe('create ESO model version', () => {
|
|||
) as SavedObjectsModelUnsafeTransformChange[];
|
||||
expect(unsafeTransforms.length === 1);
|
||||
expect(() => {
|
||||
unsafeTransforms[0].transformFn(
|
||||
unsafeTransforms[0].transformFn(dummyTypeSafeGuard)(
|
||||
{
|
||||
id: '123',
|
||||
type: 'known-type-1',
|
||||
|
@ -523,9 +545,10 @@ describe('create ESO model version', () => {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((document) => {
|
||||
return { document };
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -547,7 +570,7 @@ describe('create ESO model version', () => {
|
|||
) as SavedObjectsModelUnsafeTransformChange[];
|
||||
expect(unsafeTransforms.length === 1);
|
||||
expect(() => {
|
||||
unsafeTransforms[0].transformFn(
|
||||
unsafeTransforms[0].transformFn(dummyTypeSafeGuard)(
|
||||
{
|
||||
id: '123',
|
||||
type: 'known-type-1',
|
||||
|
@ -591,9 +614,10 @@ describe('create ESO model version', () => {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard((document) => {
|
||||
return { document };
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -616,7 +640,7 @@ describe('create ESO model version', () => {
|
|||
) as SavedObjectsModelUnsafeTransformChange[];
|
||||
expect(unsafeTransforms.length === 1);
|
||||
expect(() => {
|
||||
unsafeTransforms[0].transformFn(
|
||||
unsafeTransforms[0].transformFn(dummyTypeSafeGuard)(
|
||||
{
|
||||
id: '123',
|
||||
type: 'known-type-1',
|
||||
|
@ -667,7 +691,15 @@ describe('create ESO model version', () => {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard(
|
||||
(
|
||||
document: SavedObjectModelTransformationDoc<{
|
||||
firstAttr: string;
|
||||
nonEncryptedAttr?: string;
|
||||
encryptedAttr: string;
|
||||
}>
|
||||
) => {
|
||||
// modify an encrypted field
|
||||
document.attributes.firstAttr = `~~${document.attributes.firstAttr}~~`;
|
||||
// encrypt a non encrypted field if it's there
|
||||
|
@ -676,7 +708,8 @@ describe('create ESO model version', () => {
|
|||
delete document.attributes.nonEncryptedAttr;
|
||||
}
|
||||
return { document };
|
||||
},
|
||||
}
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -703,7 +736,7 @@ describe('create ESO model version', () => {
|
|||
(change) => change.type === 'unsafe_transform'
|
||||
) as SavedObjectsModelUnsafeTransformChange[];
|
||||
expect(unsafeTransforms.length === 1);
|
||||
const result = unsafeTransforms[0].transformFn(
|
||||
const result = unsafeTransforms[0].transformFn(dummyTypeSafeGuard)(
|
||||
{
|
||||
id: '123',
|
||||
type: 'known-type-1',
|
||||
|
|
|
@ -73,7 +73,12 @@ export const getCreateEsoModelVersion =
|
|||
incomingChanges
|
||||
);
|
||||
|
||||
return { ...modelVersion, changes: [{ type: 'unsafe_transform', transformFn }] };
|
||||
return {
|
||||
...modelVersion,
|
||||
changes: [
|
||||
{ type: 'unsafe_transform', transformFn: (typeSafeGuard) => typeSafeGuard(transformFn) },
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
function createMergedTransformFn(
|
||||
|
|
|
@ -119,7 +119,7 @@ export const entityDefinition: SavedObjectsType = {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: removeOptionalIdentityFields,
|
||||
transformFn: (typeSafeGuard) => typeSafeGuard(removeOptionalIdentityFields),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -13,6 +13,7 @@ import type {
|
|||
SavedObjectsNamespaceType,
|
||||
SavedObjectUnsanitizedDoc,
|
||||
} from '@kbn/core/server';
|
||||
import type { SavedObjectModelTransformationDoc } from '@kbn/core-saved-objects-server';
|
||||
import type {
|
||||
EncryptedSavedObjectsPluginSetup,
|
||||
EncryptedSavedObjectsPluginStart,
|
||||
|
@ -337,13 +338,22 @@ function defineModelVersionWithMigration(core: CoreSetup<PluginsStart>, deps: Pl
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard(
|
||||
// ideally, we should use generic types for the whole function, defining it on a separate const
|
||||
(
|
||||
document: SavedObjectModelTransformationDoc<{
|
||||
additionalEncryptedAttribute: string;
|
||||
nonEncryptedAttribute: string;
|
||||
}>
|
||||
) => {
|
||||
const {
|
||||
attributes: { nonEncryptedAttribute },
|
||||
} = document;
|
||||
document.attributes.nonEncryptedAttribute = `${nonEncryptedAttribute}-migrated`;
|
||||
return { document };
|
||||
},
|
||||
}
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -356,11 +366,20 @@ function defineModelVersionWithMigration(core: CoreSetup<PluginsStart>, deps: Pl
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
transformFn: (typeSafeGuard) =>
|
||||
typeSafeGuard(
|
||||
// ideally, we should use generic types for the whole function, defining it on a separate const
|
||||
(
|
||||
document: SavedObjectModelTransformationDoc<{
|
||||
additionalEncryptedAttribute: string;
|
||||
nonEncryptedAttribute: string;
|
||||
}>
|
||||
) => {
|
||||
// clone and modify the non encrypted field
|
||||
document.attributes.additionalEncryptedAttribute = `${document.attributes.nonEncryptedAttribute}-encrypted`;
|
||||
return { document };
|
||||
},
|
||||
}
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -16,5 +16,6 @@
|
|||
"@kbn/std",
|
||||
"@kbn/encrypted-saved-objects-plugin",
|
||||
"@kbn/spaces-plugin",
|
||||
"@kbn/core-saved-objects-server",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,10 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { TypeOf } from '@kbn/config-schema';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { fold } from 'fp-ts/Either';
|
||||
import { pipe } from 'fp-ts/pipeable';
|
||||
import type { SavedObject, SavedObjectsType } from '@kbn/core/server';
|
||||
import type {
|
||||
SavedObjectModelTransformationDoc,
|
||||
SavedObjectModelUnsafeTransformFn,
|
||||
} from '@kbn/core-saved-objects-server';
|
||||
import { inventoryViewSavedObjectRT } from './types';
|
||||
|
||||
export const inventoryViewSavedObjectName = 'inventory-view';
|
||||
|
@ -32,6 +37,37 @@ const schemaV2 = schema.object(
|
|||
{ unknowns: 'allow' }
|
||||
);
|
||||
|
||||
type V1 = TypeOf<typeof schemaV1>;
|
||||
type V2 = TypeOf<typeof schemaV2>;
|
||||
|
||||
const inventoryV2Transform: SavedObjectModelUnsafeTransformFn<V1, V2> = (doc) => {
|
||||
// steps property did exist, even though it wasn't present in the schema
|
||||
const asV2 = doc as SavedObjectModelTransformationDoc<V2>;
|
||||
|
||||
if (typeof asV2.attributes.legend?.steps === 'undefined') {
|
||||
return { document: asV2 };
|
||||
} else {
|
||||
let steps = asV2.attributes.legend?.steps;
|
||||
if (steps > 18) {
|
||||
steps = 18;
|
||||
} else if (steps < 2) {
|
||||
steps = 2;
|
||||
}
|
||||
|
||||
const document: SavedObjectModelTransformationDoc<V2> = {
|
||||
...asV2,
|
||||
attributes: {
|
||||
...asV2.attributes,
|
||||
legend: {
|
||||
...asV2.attributes.legend,
|
||||
steps,
|
||||
},
|
||||
},
|
||||
};
|
||||
return { document };
|
||||
}
|
||||
};
|
||||
|
||||
export const inventoryViewSavedObjectType: SavedObjectsType = {
|
||||
name: inventoryViewSavedObjectName,
|
||||
hidden: false,
|
||||
|
@ -58,14 +94,7 @@ export const inventoryViewSavedObjectType: SavedObjectsType = {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: (document) => {
|
||||
if (document.attributes.legend?.steps > 18) {
|
||||
document.attributes.legend.steps = 18;
|
||||
} else if (document.attributes.legend?.steps < 2) {
|
||||
document.attributes.legend.steps = 2;
|
||||
}
|
||||
return { document };
|
||||
},
|
||||
transformFn: (typeSafeGuard) => typeSafeGuard(inventoryV2Transform),
|
||||
},
|
||||
],
|
||||
schemas: {
|
||||
|
|
|
@ -119,7 +119,8 @@
|
|||
"@kbn/core-chrome-browser",
|
||||
"@kbn/presentation-containers",
|
||||
"@kbn/object-utils",
|
||||
"@kbn/coloring"
|
||||
"@kbn/coloring",
|
||||
"@kbn/core-saved-objects-server"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ export const modelVersion1: SavedObjectsModelVersion = {
|
|||
changes: [
|
||||
{
|
||||
type: 'unsafe_transform',
|
||||
transformFn: transformGeoProperty,
|
||||
transformFn: (typeSafeGuard) => typeSafeGuard(transformGeoProperty),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue