mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
parent
cba02df513
commit
026a7b4956
26 changed files with 470 additions and 103 deletions
|
@ -1,11 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-embeddable-server](./kibana-plugin-plugins-embeddable-server.md) > [EmbeddableSetup](./kibana-plugin-plugins-embeddable-server.embeddablesetup.md) > [getMigrationVersions](./kibana-plugin-plugins-embeddable-server.embeddablesetup.getmigrationversions.md)
|
||||
[Home](./index.md) > [kibana-plugin-plugins-embeddable-server](./kibana-plugin-plugins-embeddable-server.md) > [EmbeddableSetup](./kibana-plugin-plugins-embeddable-server.embeddablesetup.md) > [getAllMigrations](./kibana-plugin-plugins-embeddable-server.embeddablesetup.getallmigrations.md)
|
||||
|
||||
## EmbeddableSetup.getMigrationVersions property
|
||||
## EmbeddableSetup.getAllMigrations property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
getMigrationVersions: () => string[];
|
||||
getAllMigrations: () => MigrateFunctionsObject;
|
||||
```
|
|
@ -14,7 +14,7 @@ export interface EmbeddableSetup extends PersistableStateService<EmbeddableState
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [getMigrationVersions](./kibana-plugin-plugins-embeddable-server.embeddablesetup.getmigrationversions.md) | <code>() => string[]</code> | |
|
||||
| [getAllMigrations](./kibana-plugin-plugins-embeddable-server.embeddablesetup.getallmigrations.md) | <code>() => MigrateFunctionsObject</code> | |
|
||||
| [registerEmbeddableFactory](./kibana-plugin-plugins-embeddable-server.embeddablesetup.registerembeddablefactory.md) | <code>(factory: EmbeddableRegistryDefinition) => void</code> | |
|
||||
| [registerEnhancement](./kibana-plugin-plugins-embeddable-server.embeddablesetup.registerenhancement.md) | <code>(enhancement: EnhancementRegistryDefinition) => void</code> | |
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ export { TODO_EMBEDDABLE, TodoEmbeddableFactory } from './todo';
|
|||
|
||||
export { BOOK_EMBEDDABLE } from './book';
|
||||
|
||||
export { SIMPLE_EMBEDDABLE } from './migrations';
|
||||
|
||||
import { EmbeddableExamplesPlugin } from './plugin';
|
||||
|
||||
export {
|
||||
|
|
10
examples/embeddable_examples/public/migrations/index.ts
Normal file
10
examples/embeddable_examples/public/migrations/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './migrations_embeddable';
|
||||
export * from './migrations_embeddable_factory';
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { MigrateFunction } from '../../../../src/plugins/kibana_utils/common/persistable_state';
|
||||
import { SimpleEmbeddableInput } from './migrations_embeddable_factory';
|
||||
import { EmbeddableInput } from '../../../../src/plugins/embeddable/common';
|
||||
|
||||
// before 7.3.0 this embeddable received a very simple input with a variable named `number`
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
type SimpleEmbeddableInput_pre7_3_0 = EmbeddableInput & {
|
||||
number: number;
|
||||
};
|
||||
|
||||
type SimpleEmbeddable730MigrateFn = MigrateFunction<
|
||||
SimpleEmbeddableInput_pre7_3_0,
|
||||
SimpleEmbeddableInput
|
||||
>;
|
||||
|
||||
// when migrating old state we'll need to set a default title, or we should make title optional in the new state
|
||||
const defaultTitle = 'no title';
|
||||
|
||||
export const migration730: SimpleEmbeddable730MigrateFn = (state) => {
|
||||
const newState: SimpleEmbeddableInput = { ...state, title: defaultTitle, value: state.number };
|
||||
return newState;
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SIMPLE_EMBEDDABLE, SimpleEmbeddableInput } from '.';
|
||||
import { Embeddable, IContainer } from '../../../../src/plugins/embeddable/public';
|
||||
|
||||
export class SimpleEmbeddable extends Embeddable<SimpleEmbeddableInput> {
|
||||
// The type of this embeddable. This will be used to find the appropriate factory
|
||||
// to instantiate this kind of embeddable.
|
||||
public readonly type = SIMPLE_EMBEDDABLE;
|
||||
|
||||
constructor(initialInput: SimpleEmbeddableInput, parent?: IContainer) {
|
||||
super(
|
||||
// Input state is irrelevant to this embeddable, just pass it along.
|
||||
initialInput,
|
||||
// Initial output state - this embeddable does not do anything with output, so just
|
||||
// pass along an empty object.
|
||||
{},
|
||||
// Optional parent component, this embeddable can optionally be rendered inside a container.
|
||||
parent
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render yourself at the dom node using whatever framework you like, angular, react, or just plain
|
||||
* vanilla js.
|
||||
* @param node
|
||||
*/
|
||||
public render(node: HTMLElement) {
|
||||
const input = this.getInput();
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
node.innerHTML = `<div data-test-subj="simpleEmbeddable">${input.title} ${input.value}</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is mostly relevant for time based embeddables which need to update data
|
||||
* even if EmbeddableInput has not changed at all.
|
||||
*/
|
||||
public reload() {}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
IContainer,
|
||||
EmbeddableInput,
|
||||
EmbeddableFactoryDefinition,
|
||||
EmbeddableFactory,
|
||||
} from '../../../../src/plugins/embeddable/public';
|
||||
import { SimpleEmbeddable } from './migrations_embeddable';
|
||||
import { migration730 } from './migration.7.3.0';
|
||||
|
||||
export const SIMPLE_EMBEDDABLE = 'SIMPLE_EMBEDDABLE';
|
||||
|
||||
// in 7.3.0 we added `title` to the input and renamed the `number` variable to `value`
|
||||
export type SimpleEmbeddableInput = EmbeddableInput & {
|
||||
title: string;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export type SimpleEmbeddableFactory = EmbeddableFactory;
|
||||
export class SimpleEmbeddableFactoryDefinition
|
||||
implements EmbeddableFactoryDefinition<SimpleEmbeddableInput> {
|
||||
public readonly type = SIMPLE_EMBEDDABLE;
|
||||
|
||||
// we need to provide migration function every time we change the interface of our state
|
||||
public readonly migrations = {
|
||||
'7.3.0': migration730,
|
||||
};
|
||||
|
||||
/**
|
||||
* In our simple example, we let everyone have permissions to edit this. Most
|
||||
* embeddables should check the UI Capabilities service to be sure of
|
||||
* the right permissions.
|
||||
*/
|
||||
public async isEditable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async create(initialInput: SimpleEmbeddableInput, parent?: IContainer) {
|
||||
return new SimpleEmbeddable(initialInput, parent);
|
||||
}
|
||||
|
||||
public getDisplayName() {
|
||||
return i18n.translate('embeddableExamples.migrations.displayName', {
|
||||
defaultMessage: 'hello world',
|
||||
});
|
||||
}
|
||||
}
|
|
@ -49,6 +49,11 @@ import {
|
|||
import { UiActionsStart } from '../../../src/plugins/ui_actions/public';
|
||||
import { createAddBookToLibraryAction } from './book/add_book_to_library_action';
|
||||
import { createUnlinkBookFromLibraryAction } from './book/unlink_book_from_library_action';
|
||||
import {
|
||||
SIMPLE_EMBEDDABLE,
|
||||
SimpleEmbeddableFactory,
|
||||
SimpleEmbeddableFactoryDefinition,
|
||||
} from './migrations';
|
||||
|
||||
export interface EmbeddableExamplesSetupDependencies {
|
||||
embeddable: EmbeddableSetup;
|
||||
|
@ -68,6 +73,7 @@ interface ExampleEmbeddableFactories {
|
|||
getTodoEmbeddableFactory: () => TodoEmbeddableFactory;
|
||||
getTodoRefEmbeddableFactory: () => TodoRefEmbeddableFactory;
|
||||
getBookEmbeddableFactory: () => BookEmbeddableFactory;
|
||||
getMigrationsEmbeddableFactory: () => SimpleEmbeddableFactory;
|
||||
}
|
||||
|
||||
export interface EmbeddableExamplesStart {
|
||||
|
@ -94,6 +100,11 @@ export class EmbeddableExamplesPlugin
|
|||
new HelloWorldEmbeddableFactoryDefinition()
|
||||
);
|
||||
|
||||
this.exampleEmbeddableFactories.getMigrationsEmbeddableFactory = deps.embeddable.registerEmbeddableFactory(
|
||||
SIMPLE_EMBEDDABLE,
|
||||
new SimpleEmbeddableFactoryDefinition()
|
||||
);
|
||||
|
||||
this.exampleEmbeddableFactories.getMultiTaskTodoEmbeddableFactory = deps.embeddable.registerEmbeddableFactory(
|
||||
MULTI_TASK_TODO_EMBEDDABLE,
|
||||
new MultiTaskTodoEmbeddableFactoryDefinition()
|
||||
|
|
|
@ -9,11 +9,19 @@
|
|||
import { Plugin, CoreSetup, CoreStart } from 'kibana/server';
|
||||
import { todoSavedObject } from './todo_saved_object';
|
||||
import { bookSavedObject } from './book_saved_object';
|
||||
import { searchableListSavedObject } from './searchable_list_saved_object';
|
||||
import { EmbeddableSetup } from '../../../src/plugins/embeddable/server';
|
||||
|
||||
export class EmbeddableExamplesPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
export interface EmbeddableExamplesSetupDependencies {
|
||||
embeddable: EmbeddableSetup;
|
||||
}
|
||||
|
||||
export class EmbeddableExamplesPlugin
|
||||
implements Plugin<void, void, EmbeddableExamplesSetupDependencies> {
|
||||
public setup(core: CoreSetup, { embeddable }: EmbeddableExamplesSetupDependencies) {
|
||||
core.savedObjects.registerType(todoSavedObject);
|
||||
core.savedObjects.registerType(bookSavedObject);
|
||||
core.savedObjects.registerType(searchableListSavedObject(embeddable));
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { mapValues } from 'lodash';
|
||||
import { SavedObjectsType, SavedObjectUnsanitizedDoc } from 'kibana/server';
|
||||
import { EmbeddableSetup } from '../../../src/plugins/embeddable/server';
|
||||
|
||||
export const searchableListSavedObject = (embeddable: EmbeddableSetup) => {
|
||||
return {
|
||||
name: 'searchableList',
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
management: {
|
||||
icon: 'visualizeApp',
|
||||
defaultSearchField: 'title',
|
||||
importableAndExportable: true,
|
||||
getTitle(obj: any) {
|
||||
return obj.attributes.title;
|
||||
},
|
||||
},
|
||||
mappings: {
|
||||
properties: {
|
||||
title: { type: 'text' },
|
||||
version: { type: 'integer' },
|
||||
},
|
||||
},
|
||||
migrations: () => {
|
||||
// we assume all the migration will be done by embeddables service and that saved object holds no extra state besides that of searchable list embeddable input\
|
||||
// if saved object would hold additional information we would need to merge the response from embeddables.getAllMigrations with our custom migrations.
|
||||
return mapValues(embeddable.getAllMigrations(), (migrate) => {
|
||||
return (state: SavedObjectUnsanitizedDoc) => ({
|
||||
...state,
|
||||
attributes: migrate(state.attributes),
|
||||
});
|
||||
});
|
||||
},
|
||||
} as SavedObjectsType;
|
||||
};
|
|
@ -39,7 +39,7 @@ const injectImplementation = (
|
|||
};
|
||||
embeddableSetupMock.extract.mockImplementation(extractImplementation);
|
||||
embeddableSetupMock.inject.mockImplementation(injectImplementation);
|
||||
embeddableSetupMock.getMigrationVersions.mockImplementation(() => []);
|
||||
embeddableSetupMock.getAllMigrations.mockImplementation(() => ({}));
|
||||
|
||||
const migrations = createDashboardSavedObjectTypeMigrations({
|
||||
embeddable: embeddableSetupMock,
|
||||
|
@ -586,28 +586,14 @@ describe('dashboard', () => {
|
|||
type: 'dashboard',
|
||||
};
|
||||
|
||||
it('should add all embeddable migrations for versions above 7.12.0 to dashboard saved object migrations', () => {
|
||||
const newEmbeddableSetupMock = createEmbeddableSetupMock();
|
||||
newEmbeddableSetupMock.getMigrationVersions.mockImplementation(() => [
|
||||
'7.10.100',
|
||||
'7.13.0',
|
||||
'8.0.0',
|
||||
]);
|
||||
const migrationsList = createDashboardSavedObjectTypeMigrations({
|
||||
embeddable: newEmbeddableSetupMock,
|
||||
});
|
||||
expect(Object.keys(migrationsList).indexOf('8.0.0')).not.toBe(-1);
|
||||
expect(Object.keys(migrationsList).indexOf('7.13.0')).not.toBe(-1);
|
||||
expect(Object.keys(migrationsList).indexOf('7.10.100')).toBe(-1);
|
||||
});
|
||||
|
||||
it('runs migrations on by value panels only', () => {
|
||||
const newEmbeddableSetupMock = createEmbeddableSetupMock();
|
||||
newEmbeddableSetupMock.getMigrationVersions.mockImplementation(() => ['7.13.0']);
|
||||
newEmbeddableSetupMock.migrate.mockImplementation((state: SerializableState) => {
|
||||
state.superCoolKey = 'ONLY 4 BY VALUE EMBEDDABLES THANK YOU VERY MUCH';
|
||||
return state;
|
||||
});
|
||||
newEmbeddableSetupMock.getAllMigrations.mockImplementation(() => ({
|
||||
'7.13.0': (state: SerializableState) => {
|
||||
state.superCoolKey = 'ONLY 4 BY VALUE EMBEDDABLES THANK YOU VERY MUCH';
|
||||
return state;
|
||||
},
|
||||
}));
|
||||
const migrationsList = createDashboardSavedObjectTypeMigrations({
|
||||
embeddable: newEmbeddableSetupMock,
|
||||
});
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import semver from 'semver';
|
||||
import { get, flow, identity } from 'lodash';
|
||||
import { get, flow, mapValues } from 'lodash';
|
||||
import {
|
||||
SavedObjectAttributes,
|
||||
SavedObjectMigrationFn,
|
||||
|
@ -26,7 +25,12 @@ import {
|
|||
} from '../../common/embeddable/embeddable_saved_object_converters';
|
||||
import { SavedObjectEmbeddableInput } from '../../../embeddable/common';
|
||||
import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../../data/common';
|
||||
import { SerializableValue } from '../../../kibana_utils/common';
|
||||
import {
|
||||
mergeMigrationFunctionMaps,
|
||||
MigrateFunction,
|
||||
MigrateFunctionsObject,
|
||||
SerializableValue,
|
||||
} from '../../../kibana_utils/common';
|
||||
import { replaceIndexPatternReference } from './replace_index_pattern_reference';
|
||||
|
||||
function migrateIndexPattern(doc: DashboardDoc700To720) {
|
||||
|
@ -156,7 +160,7 @@ type ValueOrReferenceInput = SavedObjectEmbeddableInput & {
|
|||
|
||||
// Runs the embeddable migrations on each panel
|
||||
const migrateByValuePanels = (
|
||||
deps: DashboardSavedObjectTypeMigrationsDeps,
|
||||
migrate: MigrateFunction,
|
||||
version: string
|
||||
): SavedObjectMigrationFn => (doc: any) => {
|
||||
const { attributes } = doc;
|
||||
|
@ -179,13 +183,10 @@ const migrateByValuePanels = (
|
|||
// saved vis is used to store by value input for Visualize. This should eventually be renamed to `attributes` to align with Lens and Maps
|
||||
if (originalPanelState.explicitInput.attributes || originalPanelState.explicitInput.savedVis) {
|
||||
// If this panel is by value, migrate the state using embeddable migrations
|
||||
const migratedInput = deps.embeddable.migrate(
|
||||
{
|
||||
...originalPanelState.explicitInput,
|
||||
type: originalPanelState.type,
|
||||
},
|
||||
version
|
||||
);
|
||||
const migratedInput = migrate({
|
||||
...originalPanelState.explicitInput,
|
||||
type: originalPanelState.type,
|
||||
});
|
||||
// Convert the embeddable state back into the panel shape
|
||||
newPanels.push(
|
||||
convertPanelStateToSavedDashboardPanel(
|
||||
|
@ -216,16 +217,12 @@ export interface DashboardSavedObjectTypeMigrationsDeps {
|
|||
export const createDashboardSavedObjectTypeMigrations = (
|
||||
deps: DashboardSavedObjectTypeMigrationsDeps
|
||||
): SavedObjectMigrationMap => {
|
||||
const embeddableMigrations = Object.fromEntries(
|
||||
deps.embeddable
|
||||
.getMigrationVersions()
|
||||
.filter((version) => semver.gt(version, '7.12.0'))
|
||||
.map((version): [string, SavedObjectMigrationFn] => {
|
||||
return [version, migrateByValuePanels(deps, version)];
|
||||
})
|
||||
);
|
||||
const embeddableMigrations = mapValues<MigrateFunctionsObject, SavedObjectMigrationFn>(
|
||||
deps.embeddable.getAllMigrations(),
|
||||
migrateByValuePanels
|
||||
) as MigrateFunctionsObject;
|
||||
|
||||
return {
|
||||
const dashboardMigrations = {
|
||||
/**
|
||||
* We need to have this migration twice, once with a version prior to 7.0.0 once with a version
|
||||
* after it. The reason for that is, that this migration has been introduced once 7.0.0 was already
|
||||
|
@ -242,14 +239,14 @@ export const createDashboardSavedObjectTypeMigrations = (
|
|||
'7.9.3': flow(migrateMatchAllQuery),
|
||||
'7.11.0': flow(createExtractPanelReferencesMigration(deps)),
|
||||
|
||||
...embeddableMigrations,
|
||||
|
||||
/**
|
||||
* Any dashboard saved object migrations that come after this point will have to be wary of
|
||||
* potentially overwriting embeddable migrations. An example of how to mitigate this follows:
|
||||
*/
|
||||
// '7.x': flow(yourNewMigrationFunction, embeddableMigrations['7.x'] ?? identity),
|
||||
|
||||
'7.14.0': flow(replaceIndexPatternReference, embeddableMigrations['7.14.0'] ?? identity),
|
||||
'7.14.0': flow(replaceIndexPatternReference),
|
||||
};
|
||||
|
||||
return mergeMigrationFunctionMaps(dashboardMigrations, embeddableMigrations);
|
||||
};
|
||||
|
|
34
src/plugins/embeddable/common/lib/get_all_migrations.test.ts
Normal file
34
src/plugins/embeddable/common/lib/get_all_migrations.test.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getAllMigrations } from './get_all_migrations';
|
||||
|
||||
describe('embeddable getAllMigratons', () => {
|
||||
const factories = [{ migrations: { '7.11.0': (state: any) => state } }];
|
||||
const enhacements = [{ migrations: { '7.12.0': (state: any) => state } }];
|
||||
const migrateFn = jest.fn();
|
||||
|
||||
test('returns base migrations', () => {
|
||||
expect(getAllMigrations([], [], migrateFn)).toEqual({});
|
||||
});
|
||||
|
||||
test('returns embeddable factory migrations', () => {
|
||||
expect(getAllMigrations(factories as any, [], migrateFn)).toHaveProperty(['7.11.0']);
|
||||
});
|
||||
|
||||
test('returns enhancement migrations', () => {
|
||||
const migrations = getAllMigrations([], enhacements as any, migrateFn);
|
||||
expect(migrations).toHaveProperty(['7.12.0']);
|
||||
});
|
||||
|
||||
test('returns all migrations', () => {
|
||||
const migrations = getAllMigrations(factories as any, enhacements as any, migrateFn);
|
||||
expect(migrations).toHaveProperty(['7.11.0']);
|
||||
expect(migrations).toHaveProperty(['7.12.0']);
|
||||
});
|
||||
});
|
44
src/plugins/embeddable/common/lib/get_all_migrations.ts
Normal file
44
src/plugins/embeddable/common/lib/get_all_migrations.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { baseEmbeddableMigrations } from './migrate_base_input';
|
||||
import {
|
||||
MigrateFunctionsObject,
|
||||
PersistableState,
|
||||
PersistableStateMigrateFn,
|
||||
} from '../../../kibana_utils/common/persistable_state';
|
||||
|
||||
export const getAllMigrations = (
|
||||
factories: unknown[],
|
||||
enhancements: unknown[],
|
||||
migrateFn: PersistableStateMigrateFn
|
||||
) => {
|
||||
const uniqueVersions = new Set<string>();
|
||||
for (const baseMigrationVersion of Object.keys(baseEmbeddableMigrations)) {
|
||||
uniqueVersions.add(baseMigrationVersion);
|
||||
}
|
||||
for (const factory of factories) {
|
||||
Object.keys((factory as PersistableState).migrations).forEach((version) =>
|
||||
uniqueVersions.add(version)
|
||||
);
|
||||
}
|
||||
for (const enhancement of enhancements) {
|
||||
Object.keys((enhancement as PersistableState).migrations).forEach((version) =>
|
||||
uniqueVersions.add(version)
|
||||
);
|
||||
}
|
||||
|
||||
const migrations: MigrateFunctionsObject = {};
|
||||
uniqueVersions.forEach((version) => {
|
||||
migrations[version] = (state) => ({
|
||||
...migrateFn(state, version),
|
||||
});
|
||||
});
|
||||
|
||||
return migrations;
|
||||
};
|
|
@ -10,8 +10,10 @@ import { CommonEmbeddableStartContract } from '../types';
|
|||
import { baseEmbeddableMigrations } from './migrate_base_input';
|
||||
import { SerializableState } from '../../../kibana_utils/common/persistable_state';
|
||||
|
||||
export type MigrateFunction = (state: SerializableState, version: string) => SerializableState;
|
||||
|
||||
export const getMigrateFunction = (embeddables: CommonEmbeddableStartContract) => {
|
||||
return (state: SerializableState, version: string) => {
|
||||
const migrateFn: MigrateFunction = (state: SerializableState, version: string) => {
|
||||
const enhancements = (state.enhancements as SerializableState) || {};
|
||||
const factory = embeddables.getEmbeddableFactory(state.type as string);
|
||||
|
||||
|
@ -19,10 +21,16 @@ export const getMigrateFunction = (embeddables: CommonEmbeddableStartContract) =
|
|||
? baseEmbeddableMigrations[version](state)
|
||||
: state;
|
||||
|
||||
if (factory && factory.migrations[version]) {
|
||||
if (factory?.migrations[version]) {
|
||||
updatedInput = factory.migrations[version](updatedInput);
|
||||
}
|
||||
|
||||
if (factory?.isContainerType) {
|
||||
updatedInput.panels = ((state.panels as SerializableState[]) || []).map((panel) => {
|
||||
return migrateFn(panel, version);
|
||||
});
|
||||
}
|
||||
|
||||
updatedInput.enhancements = {};
|
||||
Object.keys(enhancements).forEach((key) => {
|
||||
if (!enhancements[key]) return;
|
||||
|
@ -35,4 +43,6 @@ export const getMigrateFunction = (embeddables: CommonEmbeddableStartContract) =
|
|||
|
||||
return updatedInput;
|
||||
};
|
||||
|
||||
return migrateFn;
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ export const createEmbeddablePersistableStateServiceMock = (): jest.Mocked<Embed
|
|||
return {
|
||||
inject: jest.fn((state, references) => state),
|
||||
extract: jest.fn((state) => ({ state, references: [] })),
|
||||
migrate: jest.fn((state, version) => state),
|
||||
getAllMigrations: jest.fn(() => ({})),
|
||||
telemetry: jest.fn((state, collector) => ({})),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -113,7 +113,7 @@ const createStartContract = (): Start => {
|
|||
telemetry: jest.fn(),
|
||||
extract: jest.fn(),
|
||||
inject: jest.fn(),
|
||||
migrate: jest.fn(),
|
||||
getAllMigrations: jest.fn(),
|
||||
EmbeddablePanel: jest.fn(),
|
||||
getStateTransfer: jest.fn(() => createEmbeddableStateTransferMock() as EmbeddableStateTransfer),
|
||||
getAttributeService: jest.fn(),
|
||||
|
|
|
@ -106,8 +106,34 @@ describe('embeddable factory', () => {
|
|||
my: 'state',
|
||||
} as any;
|
||||
|
||||
const containerEmbeddableFactoryId = 'CONTAINER';
|
||||
const containerEmbeddableFactory = {
|
||||
type: containerEmbeddableFactoryId,
|
||||
create: jest.fn(),
|
||||
getDisplayName: () => 'Container',
|
||||
isContainer: true,
|
||||
isEditable: () => Promise.resolve(true),
|
||||
extract: jest.fn().mockImplementation((state) => ({ state, references: [] })),
|
||||
inject: jest.fn().mockImplementation((state) => state),
|
||||
telemetry: jest.fn().mockResolvedValue({}),
|
||||
migrations: { '7.12.0': jest.fn().mockImplementation((state) => state) },
|
||||
};
|
||||
|
||||
const containerState = {
|
||||
id: containerEmbeddableFactoryId,
|
||||
type: containerEmbeddableFactoryId,
|
||||
some: 'state',
|
||||
panels: [
|
||||
{
|
||||
...embeddableState,
|
||||
},
|
||||
],
|
||||
} as any;
|
||||
|
||||
setup.registerEmbeddableFactory(embeddableFactoryId, embeddableFactory);
|
||||
setup.registerEmbeddableFactory(containerEmbeddableFactoryId, containerEmbeddableFactory);
|
||||
|
||||
test('cannot register embeddable factory with the same ID', async () => {
|
||||
setup.registerEmbeddableFactory(embeddableFactoryId, embeddableFactory);
|
||||
expect(() =>
|
||||
setup.registerEmbeddableFactory(embeddableFactoryId, embeddableFactory)
|
||||
).toThrowError(
|
||||
|
@ -131,7 +157,12 @@ describe('embeddable factory', () => {
|
|||
});
|
||||
|
||||
test('embeddableFactory migrate function gets called when calling embeddable migrate', () => {
|
||||
start.migrate(embeddableState, '7.11.0');
|
||||
start.getAllMigrations!()['7.11.0']!(embeddableState);
|
||||
expect(embeddableFactory.migrations['7.11.0']).toBeCalledWith(embeddableState);
|
||||
});
|
||||
|
||||
test('panels inside container get automatically migrated when migrating conta1iner', () => {
|
||||
start.getAllMigrations!()['7.11.0']!(containerState);
|
||||
expect(embeddableFactory.migrations['7.11.0']).toBeCalledWith(embeddableState);
|
||||
});
|
||||
});
|
||||
|
@ -156,8 +187,9 @@ describe('embeddable enhancements', () => {
|
|||
},
|
||||
} as any;
|
||||
|
||||
setup.registerEnhancement(embeddableEnhancement);
|
||||
|
||||
test('cannot register embeddable enhancement with the same ID', async () => {
|
||||
setup.registerEnhancement(embeddableEnhancement);
|
||||
expect(() => setup.registerEnhancement(embeddableEnhancement)).toThrowError(
|
||||
'enhancement with id test already exists in the registry'
|
||||
);
|
||||
|
@ -179,7 +211,7 @@ describe('embeddable enhancements', () => {
|
|||
});
|
||||
|
||||
test('enhancement migrate function gets called when calling embeddable migrate', () => {
|
||||
start.migrate(embeddableState, '7.11.0');
|
||||
start.getAllMigrations!()['7.11.0']!(embeddableState);
|
||||
expect(embeddableEnhancement.migrations['7.11.0']).toBeCalledWith(
|
||||
embeddableState.enhancements.test
|
||||
);
|
||||
|
@ -187,9 +219,9 @@ describe('embeddable enhancements', () => {
|
|||
|
||||
test('doesnt fail if there is no migration function registered for specific version', () => {
|
||||
expect(() => {
|
||||
start.migrate(embeddableState, '7.10.0');
|
||||
start.getAllMigrations!()['7.11.0']!(embeddableState);
|
||||
}).not.toThrow();
|
||||
|
||||
expect(start.migrate(embeddableState, '7.10.0')).toEqual(embeddableState);
|
||||
expect(start.getAllMigrations!()['7.11.0']!(embeddableState)).toEqual(embeddableState);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -49,6 +49,7 @@ import {
|
|||
getMigrateFunction,
|
||||
getTelemetryFunction,
|
||||
} from '../common/lib';
|
||||
import { getAllMigrations } from '../common/lib/get_all_migrations';
|
||||
|
||||
export interface EmbeddableSetupDependencies {
|
||||
uiActions: UiActionsSetup;
|
||||
|
@ -205,7 +206,12 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
telemetry: getTelemetryFunction(commonContract),
|
||||
extract: getExtractFunction(commonContract),
|
||||
inject: getInjectFunction(commonContract),
|
||||
migrate: getMigrateFunction(commonContract),
|
||||
getAllMigrations: () =>
|
||||
getAllMigrations(
|
||||
Array.from(this.embeddableFactories.values()),
|
||||
Array.from(this.enhancements.values()),
|
||||
getMigrateFunction(commonContract)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import { EmbeddableSetup, EmbeddableStart } from './plugin';
|
|||
export const createEmbeddableSetupMock = (): jest.Mocked<EmbeddableSetup> => ({
|
||||
...createEmbeddablePersistableStateServiceMock(),
|
||||
registerEmbeddableFactory: jest.fn(),
|
||||
getMigrationVersions: jest.fn().mockReturnValue([]),
|
||||
getAllMigrations: jest.fn().mockReturnValue({}),
|
||||
registerEnhancement: jest.fn(),
|
||||
});
|
||||
|
||||
|
|
|
@ -16,19 +16,24 @@ import {
|
|||
EmbeddableRegistryDefinition,
|
||||
} from './types';
|
||||
import {
|
||||
baseEmbeddableMigrations,
|
||||
getExtractFunction,
|
||||
getInjectFunction,
|
||||
getMigrateFunction,
|
||||
getTelemetryFunction,
|
||||
} from '../common/lib';
|
||||
import { PersistableStateService, SerializableState } from '../../kibana_utils/common';
|
||||
import {
|
||||
PersistableStateService,
|
||||
SerializableState,
|
||||
PersistableStateMigrateFn,
|
||||
MigrateFunctionsObject,
|
||||
} from '../../kibana_utils/common';
|
||||
import { EmbeddableStateWithType } from '../common/types';
|
||||
import { getAllMigrations } from '../common/lib/get_all_migrations';
|
||||
|
||||
export interface EmbeddableSetup extends PersistableStateService<EmbeddableStateWithType> {
|
||||
registerEmbeddableFactory: (factory: EmbeddableRegistryDefinition) => void;
|
||||
registerEnhancement: (enhancement: EnhancementRegistryDefinition) => void;
|
||||
getMigrationVersions: () => string[];
|
||||
getAllMigrations: () => MigrateFunctionsObject;
|
||||
}
|
||||
|
||||
export type EmbeddableStart = PersistableStateService<EmbeddableStateWithType>;
|
||||
|
@ -36,20 +41,27 @@ export type EmbeddableStart = PersistableStateService<EmbeddableStateWithType>;
|
|||
export class EmbeddableServerPlugin implements Plugin<EmbeddableSetup, EmbeddableStart> {
|
||||
private readonly embeddableFactories: EmbeddableFactoryRegistry = new Map();
|
||||
private readonly enhancements: EnhancementsRegistry = new Map();
|
||||
private migrateFn: PersistableStateMigrateFn | undefined;
|
||||
|
||||
public setup(core: CoreSetup) {
|
||||
const commonContract = {
|
||||
getEmbeddableFactory: this.getEmbeddableFactory,
|
||||
getEnhancement: this.getEnhancement,
|
||||
};
|
||||
|
||||
this.migrateFn = getMigrateFunction(commonContract);
|
||||
return {
|
||||
getMigrationVersions: this.getMigrationVersions,
|
||||
registerEmbeddableFactory: this.registerEmbeddableFactory,
|
||||
registerEnhancement: this.registerEnhancement,
|
||||
telemetry: getTelemetryFunction(commonContract),
|
||||
extract: getExtractFunction(commonContract),
|
||||
inject: getInjectFunction(commonContract),
|
||||
migrate: getMigrateFunction(commonContract),
|
||||
getAllMigrations: () =>
|
||||
getAllMigrations(
|
||||
Array.from(this.embeddableFactories.values()),
|
||||
Array.from(this.enhancements.values()),
|
||||
this.migrateFn!
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -63,7 +75,12 @@ export class EmbeddableServerPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
telemetry: getTelemetryFunction(commonContract),
|
||||
extract: getExtractFunction(commonContract),
|
||||
inject: getInjectFunction(commonContract),
|
||||
migrate: getMigrateFunction(commonContract),
|
||||
getAllMigrations: () =>
|
||||
getAllMigrations(
|
||||
Array.from(this.embeddableFactories.values()),
|
||||
Array.from(this.enhancements.values()),
|
||||
this.migrateFn!
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -128,20 +145,4 @@ export class EmbeddableServerPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
}
|
||||
);
|
||||
};
|
||||
|
||||
private getMigrationVersions = () => {
|
||||
const uniqueVersions = new Set<string>();
|
||||
for (const baseMigrationVersion of Object.keys(baseEmbeddableMigrations)) {
|
||||
uniqueVersions.add(baseMigrationVersion);
|
||||
}
|
||||
const factories = this.embeddableFactories.values();
|
||||
for (const factory of factories) {
|
||||
Object.keys(factory.migrations).forEach((version) => uniqueVersions.add(version));
|
||||
}
|
||||
const enhancements = this.enhancements.values();
|
||||
for (const enhancement of enhancements) {
|
||||
Object.keys(enhancement.migrations).forEach((version) => uniqueVersions.add(version));
|
||||
}
|
||||
return Array.from(uniqueVersions);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -23,8 +23,10 @@ export interface EmbeddableRegistryDefinition<P extends EmbeddableStateWithType
|
|||
//
|
||||
// @public (undocumented)
|
||||
export interface EmbeddableSetup extends PersistableStateService<EmbeddableStateWithType> {
|
||||
// Warning: (ae-forgotten-export) The symbol "MigrateFunctionsObject" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
getMigrationVersions: () => string[];
|
||||
getAllMigrations: () => MigrateFunctionsObject;
|
||||
// (undocumented)
|
||||
registerEmbeddableFactory: (factory: EmbeddableRegistryDefinition) => void;
|
||||
// (undocumented)
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
|
||||
export * from './types';
|
||||
export { migrateToLatest } from './migrate_to_latest';
|
||||
export { mergeMigrationFunctionMaps } from './merge_migration_function_map';
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { mergeMigrationFunctionMaps } from './merge_migration_function_map';
|
||||
|
||||
describe('mergeSavedObjectMigrationMaps', () => {
|
||||
const obj1 = {
|
||||
'7.12.1': (state: number) => state + 1,
|
||||
'7.12.2': (state: number) => state + 2,
|
||||
};
|
||||
|
||||
const obj2 = {
|
||||
'7.12.0': (state: number) => state - 2,
|
||||
'7.12.2': (state: number) => state + 2,
|
||||
};
|
||||
|
||||
test('correctly merges two saved object migration maps', () => {
|
||||
const result = mergeMigrationFunctionMaps(obj1, obj2);
|
||||
expect(result['7.12.0'](5)).toEqual(3);
|
||||
expect(result['7.12.1'](5)).toEqual(6);
|
||||
expect(result['7.12.2'](5)).toEqual(9);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { mergeWith } from 'lodash';
|
||||
import { MigrateFunctionsObject, MigrateFunction, SerializableState } from './types';
|
||||
|
||||
export const mergeMigrationFunctionMaps = (
|
||||
obj1: MigrateFunctionsObject,
|
||||
obj2: MigrateFunctionsObject
|
||||
) => {
|
||||
const customizer = (objValue: MigrateFunction, srcValue: MigrateFunction) => {
|
||||
if (!srcValue || !objValue) {
|
||||
return srcValue || objValue;
|
||||
}
|
||||
return (state: SerializableState) => objValue(srcValue(state));
|
||||
};
|
||||
|
||||
return mergeWith({ ...obj1 }, obj2, customizer);
|
||||
};
|
|
@ -99,12 +99,22 @@ export interface PersistableState<P extends SerializableState = SerializableStat
|
|||
* accumulated over time. Migration functions are keyed using semver version
|
||||
* of Kibana releases.
|
||||
*/
|
||||
export type MigrateFunctionsObject = { [semver: string]: MigrateFunction };
|
||||
export type MigrateFunctionsObject = { [semver: string]: MigrateFunction<any, any> };
|
||||
export type MigrateFunction<
|
||||
FromVersion extends SerializableState = SerializableState,
|
||||
ToVersion extends SerializableState = SerializableState
|
||||
> = (state: FromVersion) => ToVersion;
|
||||
|
||||
/**
|
||||
* migrate function runs the specified migration
|
||||
* @param state
|
||||
* @param version
|
||||
*/
|
||||
export type PersistableStateMigrateFn = (
|
||||
state: SerializableState,
|
||||
version: string
|
||||
) => SerializableState;
|
||||
|
||||
/**
|
||||
* @todo Shall we remove this?
|
||||
*/
|
||||
|
@ -150,23 +160,6 @@ export interface PersistableStateService<P extends SerializableState = Serializa
|
|||
*/
|
||||
extract(state: P): { state: P; references: SavedObjectReference[] };
|
||||
|
||||
/**
|
||||
* Migrate function runs a specified migration of a {@link PersistableState}
|
||||
* item.
|
||||
*
|
||||
* When using this method it is up to consumer to make sure that the
|
||||
* migration function are executed in the right semver order. To avoid such
|
||||
* potentially error prone complexity, prefer using `migrateToLatest` method
|
||||
* instead.
|
||||
*
|
||||
* @param state The old persistable state serializable state object, which
|
||||
* needs a migration.
|
||||
* @param version Semver version of the migration to execute.
|
||||
* @returns Persistable state object updated with the specified migration
|
||||
* applied to it.
|
||||
*/
|
||||
migrate(state: SerializableState, version: string): SerializableState;
|
||||
|
||||
/**
|
||||
* A function which receives the state of an older object and version and
|
||||
* should migrate the state of the object to the latest possible version using
|
||||
|
@ -177,4 +170,9 @@ export interface PersistableStateService<P extends SerializableState = Serializa
|
|||
* @returns A serializable state object migrated to the latest state.
|
||||
*/
|
||||
migrateToLatest?: (state: VersionedState) => VersionedState<P>;
|
||||
|
||||
/**
|
||||
* returns all registered migrations
|
||||
*/
|
||||
getAllMigrations?: () => MigrateFunctionsObject;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue