mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Lens] Expose vis registration (#122348)
* expose vis registration * add example app * remove file * fix and stabilize * tsconfig fix * fix type problems * handle migrations * fix problems * fix tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
d9d3230b94
commit
2f869baf18
36 changed files with 1023 additions and 99 deletions
|
@ -13,11 +13,11 @@ import { GetMigrationFunctionObjectFn } from 'src/plugins/kibana_utils/common';
|
|||
|
||||
describe('embeddable migrations', () => {
|
||||
test('should have all saved object migrations versions (>7.13.0)', () => {
|
||||
const savedObjectMigrationVersions = Object.keys(getAllMigrations({})).filter((version) => {
|
||||
const savedObjectMigrationVersions = Object.keys(getAllMigrations({}, {})).filter((version) => {
|
||||
return semverGte(version, '7.13.1');
|
||||
});
|
||||
const embeddableMigrationVersions = (
|
||||
makeLensEmbeddableFactory(() => ({}))()?.migrations as GetMigrationFunctionObjectFn
|
||||
makeLensEmbeddableFactory(() => ({}), {})()?.migrations as GetMigrationFunctionObjectFn
|
||||
)();
|
||||
if (embeddableMigrationVersions) {
|
||||
expect(savedObjectMigrationVersions.sort()).toEqual(
|
||||
|
@ -47,14 +47,17 @@ describe('embeddable migrations', () => {
|
|||
};
|
||||
|
||||
const migrations = (
|
||||
makeLensEmbeddableFactory(() => ({
|
||||
[migrationVersion]: (filters: Filter[]) => {
|
||||
return filters.map((filterState) => ({
|
||||
...filterState,
|
||||
migrated: true,
|
||||
}));
|
||||
},
|
||||
}))()?.migrations as GetMigrationFunctionObjectFn
|
||||
makeLensEmbeddableFactory(
|
||||
() => ({
|
||||
[migrationVersion]: (filters: Filter[]) => {
|
||||
return filters.map((filterState) => ({
|
||||
...filterState,
|
||||
migrated: true,
|
||||
}));
|
||||
},
|
||||
}),
|
||||
{}
|
||||
)()?.migrations as GetMigrationFunctionObjectFn
|
||||
)();
|
||||
|
||||
const migratedLensDoc = migrations[migrationVersion](lensVisualizationDoc);
|
||||
|
@ -76,4 +79,57 @@ describe('embeddable migrations', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should properly apply a custom visualization migration', () => {
|
||||
const migrationVersion = 'some-version';
|
||||
|
||||
const lensVisualizationDoc = {
|
||||
attributes: {
|
||||
visualizationType: 'abc',
|
||||
state: {
|
||||
visualization: { oldState: true },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const migrationFn = jest.fn((oldState: { oldState: boolean }) => ({
|
||||
newState: oldState.oldState,
|
||||
}));
|
||||
|
||||
const embeddableMigrationVersions = (
|
||||
makeLensEmbeddableFactory(() => ({}), {
|
||||
abc: () => ({
|
||||
[migrationVersion]: migrationFn,
|
||||
}),
|
||||
})()?.migrations as GetMigrationFunctionObjectFn
|
||||
)();
|
||||
|
||||
const migratedLensDoc = embeddableMigrationVersions?.[migrationVersion](lensVisualizationDoc);
|
||||
const otherLensDoc = embeddableMigrationVersions?.[migrationVersion]({
|
||||
...lensVisualizationDoc,
|
||||
attributes: {
|
||||
...lensVisualizationDoc.attributes,
|
||||
visualizationType: 'def',
|
||||
},
|
||||
});
|
||||
|
||||
expect(migrationFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(migratedLensDoc).toEqual({
|
||||
attributes: {
|
||||
visualizationType: 'abc',
|
||||
state: {
|
||||
visualization: { newState: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(otherLensDoc).toEqual({
|
||||
attributes: {
|
||||
visualizationType: 'def',
|
||||
state: {
|
||||
visualization: { oldState: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,9 +19,11 @@ import {
|
|||
commonRenameOperationsForFormula,
|
||||
commonRenameRecordsField,
|
||||
commonUpdateVisLayerType,
|
||||
getLensCustomVisualizationMigrations,
|
||||
getLensFilterMigrations,
|
||||
} from '../migrations/common_migrations';
|
||||
import {
|
||||
CustomVisualizationMigrations,
|
||||
LensDocShape713,
|
||||
LensDocShape715,
|
||||
LensDocShapePre712,
|
||||
|
@ -31,55 +33,64 @@ import {
|
|||
import { extract, inject } from '../../common/embeddable_factory';
|
||||
|
||||
export const makeLensEmbeddableFactory =
|
||||
(getFilterMigrations: () => MigrateFunctionsObject) => (): EmbeddableRegistryDefinition => {
|
||||
(
|
||||
getFilterMigrations: () => MigrateFunctionsObject,
|
||||
customVisualizationMigrations: CustomVisualizationMigrations
|
||||
) =>
|
||||
(): EmbeddableRegistryDefinition => {
|
||||
return {
|
||||
id: DOC_TYPE,
|
||||
migrations: () =>
|
||||
mergeMigrationFunctionMaps(getLensFilterMigrations(getFilterMigrations()), {
|
||||
// This migration is run in 7.13.1 for `by value` panels because the 7.13 release window was missed.
|
||||
'7.13.1': (state) => {
|
||||
const lensState = state as unknown as { attributes: LensDocShapePre712 };
|
||||
const migratedLensState = commonRenameOperationsForFormula(lensState.attributes);
|
||||
return {
|
||||
...lensState,
|
||||
attributes: migratedLensState,
|
||||
} as unknown as SerializableRecord;
|
||||
},
|
||||
'7.14.0': (state) => {
|
||||
const lensState = state as unknown as { attributes: LensDocShape713 };
|
||||
const migratedLensState = commonRemoveTimezoneDateHistogramParam(lensState.attributes);
|
||||
return {
|
||||
...lensState,
|
||||
attributes: migratedLensState,
|
||||
} as unknown as SerializableRecord;
|
||||
},
|
||||
'7.15.0': (state) => {
|
||||
const lensState = state as unknown as { attributes: LensDocShape715<VisStatePre715> };
|
||||
const migratedLensState = commonUpdateVisLayerType(lensState.attributes);
|
||||
return {
|
||||
...lensState,
|
||||
attributes: migratedLensState,
|
||||
} as unknown as SerializableRecord;
|
||||
},
|
||||
'7.16.0': (state) => {
|
||||
const lensState = state as unknown as { attributes: LensDocShape715<VisState716> };
|
||||
const migratedLensState = commonMakeReversePaletteAsCustom(lensState.attributes);
|
||||
return {
|
||||
...lensState,
|
||||
attributes: migratedLensState,
|
||||
} as unknown as SerializableRecord;
|
||||
},
|
||||
'8.1.0': (state) => {
|
||||
const lensState = state as unknown as { attributes: LensDocShape715 };
|
||||
const migratedLensState = commonRenameRecordsField(
|
||||
commonRenameFilterReferences(lensState.attributes)
|
||||
);
|
||||
return {
|
||||
...lensState,
|
||||
attributes: migratedLensState,
|
||||
} as unknown as SerializableRecord;
|
||||
},
|
||||
}),
|
||||
mergeMigrationFunctionMaps(
|
||||
mergeMigrationFunctionMaps(getLensFilterMigrations(getFilterMigrations()), {
|
||||
// This migration is run in 7.13.1 for `by value` panels because the 7.13 release window was missed.
|
||||
'7.13.1': (state) => {
|
||||
const lensState = state as unknown as { attributes: LensDocShapePre712 };
|
||||
const migratedLensState = commonRenameOperationsForFormula(lensState.attributes);
|
||||
return {
|
||||
...lensState,
|
||||
attributes: migratedLensState,
|
||||
} as unknown as SerializableRecord;
|
||||
},
|
||||
'7.14.0': (state) => {
|
||||
const lensState = state as unknown as { attributes: LensDocShape713 };
|
||||
const migratedLensState = commonRemoveTimezoneDateHistogramParam(
|
||||
lensState.attributes
|
||||
);
|
||||
return {
|
||||
...lensState,
|
||||
attributes: migratedLensState,
|
||||
} as unknown as SerializableRecord;
|
||||
},
|
||||
'7.15.0': (state) => {
|
||||
const lensState = state as unknown as { attributes: LensDocShape715<VisStatePre715> };
|
||||
const migratedLensState = commonUpdateVisLayerType(lensState.attributes);
|
||||
return {
|
||||
...lensState,
|
||||
attributes: migratedLensState,
|
||||
} as unknown as SerializableRecord;
|
||||
},
|
||||
'7.16.0': (state) => {
|
||||
const lensState = state as unknown as { attributes: LensDocShape715<VisState716> };
|
||||
const migratedLensState = commonMakeReversePaletteAsCustom(lensState.attributes);
|
||||
return {
|
||||
...lensState,
|
||||
attributes: migratedLensState,
|
||||
} as unknown as SerializableRecord;
|
||||
},
|
||||
'8.1.0': (state) => {
|
||||
const lensState = state as unknown as { attributes: LensDocShape715 };
|
||||
const migratedLensState = commonRenameRecordsField(
|
||||
commonRenameFilterReferences(lensState.attributes)
|
||||
);
|
||||
return {
|
||||
...lensState,
|
||||
attributes: migratedLensState,
|
||||
} as unknown as SerializableRecord;
|
||||
},
|
||||
}),
|
||||
getLensCustomVisualizationMigrations(customVisualizationMigrations)
|
||||
),
|
||||
extract,
|
||||
inject,
|
||||
};
|
||||
|
|
|
@ -7,7 +7,12 @@
|
|||
|
||||
import { cloneDeep, mapValues } from 'lodash';
|
||||
import { PaletteOutput } from 'src/plugins/charts/common';
|
||||
import { MigrateFunctionsObject } from '../../../../../src/plugins/kibana_utils/common';
|
||||
import { SerializableRecord } from '@kbn/utility-types';
|
||||
import {
|
||||
mergeMigrationFunctionMaps,
|
||||
MigrateFunction,
|
||||
MigrateFunctionsObject,
|
||||
} from '../../../../../src/plugins/kibana_utils/common';
|
||||
import {
|
||||
LensDocShapePre712,
|
||||
OperationTypePre712,
|
||||
|
@ -18,6 +23,7 @@ import {
|
|||
VisStatePost715,
|
||||
VisStatePre715,
|
||||
VisState716,
|
||||
CustomVisualizationMigrations,
|
||||
LensDocShape810,
|
||||
} from './types';
|
||||
import { CustomPaletteParams, DOCUMENT_FIELD_NAME, layerTypes } from '../../common';
|
||||
|
@ -186,6 +192,51 @@ export const commonRenameFilterReferences = (attributes: LensDocShape715): LensD
|
|||
return newAttributes as LensDocShape810;
|
||||
};
|
||||
|
||||
const getApplyCustomVisualizationMigrationToLens = (id: string, migration: MigrateFunction) => {
|
||||
return (savedObject: { attributes: LensDocShape }) => {
|
||||
if (savedObject.attributes.visualizationType !== id) return savedObject;
|
||||
return {
|
||||
...savedObject,
|
||||
attributes: {
|
||||
...savedObject.attributes,
|
||||
state: {
|
||||
...savedObject.attributes.state,
|
||||
visualization: migration(
|
||||
savedObject.attributes.state.visualization as SerializableRecord
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* This creates a migration map that applies custom visualization migrations
|
||||
*/
|
||||
export const getLensCustomVisualizationMigrations = (
|
||||
customVisualizationMigrations: CustomVisualizationMigrations
|
||||
) => {
|
||||
return Object.entries(customVisualizationMigrations)
|
||||
.map(([id, migrationGetter]) => {
|
||||
const migrationMap: MigrateFunctionsObject = {};
|
||||
const currentMigrations = migrationGetter();
|
||||
for (const version in currentMigrations) {
|
||||
if (currentMigrations.hasOwnProperty(version)) {
|
||||
migrationMap[version] = getApplyCustomVisualizationMigrationToLens(
|
||||
id,
|
||||
currentMigrations[version]
|
||||
);
|
||||
}
|
||||
}
|
||||
return migrationMap;
|
||||
})
|
||||
.reduce(
|
||||
(fullMigrationMap, currentVisualizationTypeMigrationMap) =>
|
||||
mergeMigrationFunctionMaps(fullMigrationMap, currentVisualizationTypeMigrationMap),
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* This creates a migration map that applies filter migrations to Lens visualizations
|
||||
*/
|
||||
|
|
|
@ -24,7 +24,7 @@ import { PaletteOutput } from 'src/plugins/charts/common';
|
|||
import { Filter } from '@kbn/es-query';
|
||||
|
||||
describe('Lens migrations', () => {
|
||||
const migrations = getAllMigrations({});
|
||||
const migrations = getAllMigrations({}, {});
|
||||
describe('7.7.0 missing dimensions in XY', () => {
|
||||
const context = {} as SavedObjectMigrationContext;
|
||||
|
||||
|
@ -1611,14 +1611,17 @@ describe('Lens migrations', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const migrationFunctionsObject = getAllMigrations({
|
||||
[migrationVersion]: (filters: Filter[]) => {
|
||||
return filters.map((filterState) => ({
|
||||
...filterState,
|
||||
migrated: true,
|
||||
}));
|
||||
const migrationFunctionsObject = getAllMigrations(
|
||||
{
|
||||
[migrationVersion]: (filters: Filter[]) => {
|
||||
return filters.map((filterState) => ({
|
||||
...filterState,
|
||||
migrated: true,
|
||||
}));
|
||||
},
|
||||
},
|
||||
});
|
||||
{}
|
||||
);
|
||||
|
||||
const migratedLensDoc = migrationFunctionsObject[migrationVersion](
|
||||
lensVisualizationDoc as SavedObjectUnsanitizedDoc,
|
||||
|
@ -1642,4 +1645,63 @@ describe('Lens migrations', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should properly apply a custom visualization migration', () => {
|
||||
const migrationVersion = 'some-version';
|
||||
|
||||
const lensVisualizationDoc = {
|
||||
attributes: {
|
||||
visualizationType: 'abc',
|
||||
state: {
|
||||
visualization: { oldState: true },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const migrationFn = jest.fn((oldState: { oldState: boolean }) => ({
|
||||
newState: oldState.oldState,
|
||||
}));
|
||||
|
||||
const migrationFunctionsObject = getAllMigrations(
|
||||
{},
|
||||
{
|
||||
abc: () => ({
|
||||
[migrationVersion]: migrationFn,
|
||||
}),
|
||||
}
|
||||
);
|
||||
const migratedLensDoc = migrationFunctionsObject[migrationVersion](
|
||||
lensVisualizationDoc as SavedObjectUnsanitizedDoc,
|
||||
{} as SavedObjectMigrationContext
|
||||
);
|
||||
const otherLensDoc = migrationFunctionsObject[migrationVersion](
|
||||
{
|
||||
...lensVisualizationDoc,
|
||||
attributes: {
|
||||
...lensVisualizationDoc.attributes,
|
||||
visualizationType: 'def',
|
||||
},
|
||||
} as SavedObjectUnsanitizedDoc,
|
||||
{} as SavedObjectMigrationContext
|
||||
);
|
||||
|
||||
expect(migrationFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(migratedLensDoc).toEqual({
|
||||
attributes: {
|
||||
visualizationType: 'abc',
|
||||
state: {
|
||||
visualization: { newState: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(otherLensDoc).toEqual({
|
||||
attributes: {
|
||||
visualizationType: 'def',
|
||||
state: {
|
||||
visualization: { oldState: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -27,6 +27,7 @@ import {
|
|||
VisStatePost715,
|
||||
VisStatePre715,
|
||||
VisState716,
|
||||
CustomVisualizationMigrations,
|
||||
LensDocShape810,
|
||||
} from './types';
|
||||
import {
|
||||
|
@ -36,6 +37,7 @@ import {
|
|||
commonMakeReversePaletteAsCustom,
|
||||
commonRenameFilterReferences,
|
||||
getLensFilterMigrations,
|
||||
getLensCustomVisualizationMigrations,
|
||||
commonRenameRecordsField,
|
||||
} from './common_migrations';
|
||||
|
||||
|
@ -473,9 +475,13 @@ const lensMigrations: SavedObjectMigrationMap = {
|
|||
};
|
||||
|
||||
export const getAllMigrations = (
|
||||
filterMigrations: MigrateFunctionsObject
|
||||
filterMigrations: MigrateFunctionsObject,
|
||||
customVisualizationMigrations: CustomVisualizationMigrations
|
||||
): SavedObjectMigrationMap =>
|
||||
mergeSavedObjectMigrationMaps(
|
||||
lensMigrations,
|
||||
getLensFilterMigrations(filterMigrations) as unknown as SavedObjectMigrationMap
|
||||
mergeSavedObjectMigrationMaps(
|
||||
lensMigrations,
|
||||
getLensFilterMigrations(filterMigrations) as unknown as SavedObjectMigrationMap
|
||||
),
|
||||
getLensCustomVisualizationMigrations(customVisualizationMigrations)
|
||||
);
|
||||
|
|
|
@ -8,8 +8,11 @@
|
|||
import type { PaletteOutput } from 'src/plugins/charts/common';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { Query } from 'src/plugins/data/public';
|
||||
import type { MigrateFunctionsObject } from 'src/plugins/kibana_utils/common';
|
||||
import type { CustomPaletteParams, LayerType, PersistableFilter } from '../../common';
|
||||
|
||||
export type CustomVisualizationMigrations = Record<string, () => MigrateFunctionsObject>;
|
||||
|
||||
export type OperationTypePre712 =
|
||||
| 'avg'
|
||||
| 'cardinality'
|
||||
|
|
|
@ -14,6 +14,8 @@ import {
|
|||
} from 'src/plugins/data/server';
|
||||
import { ExpressionsServerSetup } from 'src/plugins/expressions/server';
|
||||
import { FieldFormatsStart } from 'src/plugins/field_formats/server';
|
||||
import type { MigrateFunctionsObject } from 'src/plugins/kibana_utils/common';
|
||||
|
||||
import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server';
|
||||
import { setupRoutes } from './routes';
|
||||
import { getUiSettings } from './ui_settings';
|
||||
|
@ -26,6 +28,7 @@ import { setupSavedObjects } from './saved_objects';
|
|||
import { EmbeddableSetup } from '../../../../src/plugins/embeddable/server';
|
||||
import { setupExpressions } from './expressions';
|
||||
import { makeLensEmbeddableFactory } from './embeddable/make_lens_embeddable_factory';
|
||||
import type { CustomVisualizationMigrations } from './migrations/types';
|
||||
|
||||
export interface PluginSetupContract {
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
|
@ -43,11 +46,22 @@ export interface PluginStartContract {
|
|||
}
|
||||
|
||||
export interface LensServerPluginSetup {
|
||||
/**
|
||||
* Server side embeddable definition which provides migrations to run if Lens state is embedded into another saved object somewhere
|
||||
*/
|
||||
lensEmbeddableFactory: ReturnType<typeof makeLensEmbeddableFactory>;
|
||||
/**
|
||||
* Register custom migration functions for custom third party Lens visualizations
|
||||
*/
|
||||
registerVisualizationMigration: (
|
||||
id: string,
|
||||
migrationsGetter: () => MigrateFunctionsObject
|
||||
) => void;
|
||||
}
|
||||
|
||||
export class LensServerPlugin implements Plugin<LensServerPluginSetup, {}, {}, {}> {
|
||||
private readonly telemetryLogger: Logger;
|
||||
private customVisualizationMigrations: CustomVisualizationMigrations = {};
|
||||
|
||||
constructor(private initializerContext: PluginInitializerContext) {
|
||||
this.telemetryLogger = initializerContext.logger.get('usage');
|
||||
|
@ -57,7 +71,7 @@ export class LensServerPlugin implements Plugin<LensServerPluginSetup, {}, {}, {
|
|||
const getFilterMigrations = plugins.data.query.filterManager.getAllMigrations.bind(
|
||||
plugins.data.query.filterManager
|
||||
);
|
||||
setupSavedObjects(core, getFilterMigrations);
|
||||
setupSavedObjects(core, getFilterMigrations, this.customVisualizationMigrations);
|
||||
setupRoutes(core, this.initializerContext.logger.get());
|
||||
setupExpressions(core, plugins.expressions);
|
||||
core.uiSettings.register(getUiSettings());
|
||||
|
@ -72,10 +86,22 @@ export class LensServerPlugin implements Plugin<LensServerPluginSetup, {}, {}, {
|
|||
initializeLensTelemetry(this.telemetryLogger, core, plugins.taskManager);
|
||||
}
|
||||
|
||||
const lensEmbeddableFactory = makeLensEmbeddableFactory(getFilterMigrations);
|
||||
const lensEmbeddableFactory = makeLensEmbeddableFactory(
|
||||
getFilterMigrations,
|
||||
this.customVisualizationMigrations
|
||||
);
|
||||
plugins.embeddable.registerEmbeddableFactory(lensEmbeddableFactory());
|
||||
return {
|
||||
lensEmbeddableFactory,
|
||||
registerVisualizationMigration: (
|
||||
id: string,
|
||||
migrationsGetter: () => MigrateFunctionsObject
|
||||
) => {
|
||||
if (this.customVisualizationMigrations[id]) {
|
||||
throw new Error(`Migrations object for visualization ${id} registered already`);
|
||||
}
|
||||
this.customVisualizationMigrations[id] = migrationsGetter;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -9,10 +9,12 @@ import { CoreSetup } from 'kibana/server';
|
|||
import { MigrateFunctionsObject } from '../../../../src/plugins/kibana_utils/common';
|
||||
import { getEditPath } from '../common';
|
||||
import { getAllMigrations } from './migrations/saved_object_migrations';
|
||||
import { CustomVisualizationMigrations } from './migrations/types';
|
||||
|
||||
export function setupSavedObjects(
|
||||
core: CoreSetup,
|
||||
getFilterMigrations: () => MigrateFunctionsObject
|
||||
getFilterMigrations: () => MigrateFunctionsObject,
|
||||
customVisualizationMigrations: CustomVisualizationMigrations
|
||||
) {
|
||||
core.savedObjects.registerType({
|
||||
name: 'lens',
|
||||
|
@ -29,7 +31,7 @@ export function setupSavedObjects(
|
|||
uiCapabilitiesPath: 'visualize.show',
|
||||
}),
|
||||
},
|
||||
migrations: () => getAllMigrations(getFilterMigrations()),
|
||||
migrations: () => getAllMigrations(getFilterMigrations(), customVisualizationMigrations),
|
||||
mappings: {
|
||||
properties: {
|
||||
title: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue