[maps] give data plugin control of filter migrations (#124729)

* convert saved_object_migrations to TS

* wire up filter migrations for saved objects

* cleanup

* implement filter migration

* update embeddable migration

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2022-02-24 11:25:54 -07:00 committed by GitHub
parent 68ab355dc3
commit 6f2e49d82b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 286 additions and 103 deletions

View file

@ -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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Filter } from '@kbn/es-query';
import { migrateDataPersistedState } from './migrate_data_persisted_state';
const attributes = {
title: 'My map',
mapStateJSON:
'{"filters":[{"meta":{"index":"90943e30-9a47-11e8-b64d-95841ca0b247","params":{"lt":10000,"gte":2000},"field":"bytes","alias":null,"negate":false,"disabled":false,"type":"range","key":"bytes"},"query":{"range":{"bytes":{"lt":10000,"gte":2000}}},"$state":{"store":"appState"}}]}',
};
const filterMigrationMock = (filters: Filter[]): Filter[] => {
return filters.map((filter) => {
return {
...filter,
alias: 'filter_has_been_migrated',
};
});
};
test('should apply data migrations to data peristed data', () => {
const { mapStateJSON } = migrateDataPersistedState({ attributes }, filterMigrationMock);
const mapState = JSON.parse(mapStateJSON!);
expect(mapState.filters[0].alias).toEqual('filter_has_been_migrated');
});

View file

@ -0,0 +1,35 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Filter } from '@kbn/es-query';
import { MapSavedObjectAttributes } from '../map_saved_object_type';
import { MigrateFunction } from '../../../../../src/plugins/kibana_utils/common';
export function migrateDataPersistedState(
{
attributes,
}: {
attributes: MapSavedObjectAttributes;
},
filterMigration: MigrateFunction<Filter[], Filter[]>
): MapSavedObjectAttributes {
let mapState: { filters: Filter[] } = { filters: [] };
if (attributes.mapStateJSON) {
try {
mapState = JSON.parse(attributes.mapStateJSON);
} catch (e) {
throw new Error('Unable to parse attribute mapStateJSON');
}
mapState.filters = filterMigration(mapState.filters);
}
return {
...attributes,
mapStateJSON: JSON.stringify(mapState),
};
}

View file

@ -7,8 +7,7 @@
import semverGte from 'semver/functions/gte';
import { embeddableMigrations } from './embeddable_migrations';
// @ts-ignore
import { savedObjectMigrations } from './saved_objects/saved_object_migrations';
import { savedObjectMigrations } from '../saved_objects/saved_object_migrations';
describe('saved object migrations and embeddable migrations', () => {
test('should have same versions registered (>7.12)', () => {

View file

@ -6,11 +6,11 @@
*/
import type { SerializableRecord } from '@kbn/utility-types';
import { MapSavedObjectAttributes } from '../common/map_saved_object_type';
import { moveAttribution } from '../common/migrations/move_attribution';
import { setEmsTmsDefaultModes } from '../common/migrations/set_ems_tms_default_modes';
import { renameLayerTypes } from '../common/migrations/rename_layer_types';
import { extractReferences } from '../common/migrations/references';
import { MapSavedObjectAttributes } from '../../common/map_saved_object_type';
import { moveAttribution } from '../../common/migrations/move_attribution';
import { setEmsTmsDefaultModes } from '../../common/migrations/set_ems_tms_default_modes';
import { renameLayerTypes } from '../../common/migrations/rename_layer_types';
import { extractReferences } from '../../common/migrations/references';
/*
* Embeddables such as Maps, Lens, and Visualize can be embedded by value or by reference on a dashboard.

View file

@ -0,0 +1,8 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { setupEmbeddable } from './setup_embeddable';

View file

@ -0,0 +1,33 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EmbeddableSetup } from '../../../../../src/plugins/embeddable/server';
import {
mergeMigrationFunctionMaps,
MigrateFunctionsObject,
} from '../../../../../src/plugins/kibana_utils/common';
import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants';
import { extract, inject } from '../../common/embeddable';
import { embeddableMigrations } from './embeddable_migrations';
import { getPersistedStateMigrations } from '../saved_objects';
export function setupEmbeddable(
embeddable: EmbeddableSetup,
getFilterMigrations: () => MigrateFunctionsObject
) {
embeddable.registerEmbeddableFactory({
id: MAP_SAVED_OBJECT_TYPE,
migrations: () => {
return mergeMigrationFunctionMaps(
embeddableMigrations,
getPersistedStateMigrations(getFilterMigrations())
);
},
inject,
extract,
});
}

View file

@ -22,15 +22,14 @@ import { getFlightsSavedObjects } from './sample_data/flights_saved_objects.js';
import { getWebLogsSavedObjects } from './sample_data/web_logs_saved_objects.js';
import { registerMapsUsageCollector } from './maps_telemetry/collectors/register';
import { APP_ID, APP_ICON, MAP_SAVED_OBJECT_TYPE, getFullPath } from '../common/constants';
import { extract, inject } from '../common/embeddable';
import { mapSavedObjects, mapsTelemetrySavedObjects } from './saved_objects';
import { MapsXPackConfig } from '../config';
import { setStartServices } from './kibana_server_services';
import { emsBoundariesSpecProvider } from './tutorials/ems';
import { initRoutes } from './routes';
import { HomeServerPluginSetup } from '../../../../src/plugins/home/server';
import type { EMSSettings } from '../../../../src/plugins/maps_ems/server';
import { embeddableMigrations } from './embeddable_migrations';
import { setupEmbeddable } from './embeddable';
import { setupSavedObjects } from './saved_objects';
import { registerIntegrations } from './register_integrations';
import { StartDeps, SetupDeps } from './types';
@ -146,6 +145,10 @@ export class MapsPlugin implements Plugin {
}
setup(core: CoreSetup, plugins: SetupDeps) {
const getFilterMigrations = plugins.data.query.filterManager.getAllMigrations.bind(
plugins.data.query.filterManager
);
const { usageCollection, home, features, customIntegrations } = plugins;
const config$ = this._initializerContext.config.create();
@ -192,16 +195,10 @@ export class MapsPlugin implements Plugin {
},
});
core.savedObjects.registerType(mapsTelemetrySavedObjects);
core.savedObjects.registerType(mapSavedObjects);
setupSavedObjects(core, getFilterMigrations);
registerMapsUsageCollector(usageCollection);
plugins.embeddable.registerEmbeddableFactory({
id: MAP_SAVED_OBJECT_TYPE,
migrations: embeddableMigrations,
inject,
extract,
});
setupEmbeddable(plugins.embeddable, getFilterMigrations);
return {
config: config$,

View file

@ -5,5 +5,4 @@
* 2.0.
*/
export { mapsTelemetrySavedObjects } from './maps_telemetry';
export { mapSavedObjects } from './map';
export { getPersistedStateMigrations, setupSavedObjects } from './setup_saved_objects';

View file

@ -1,44 +0,0 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SavedObjectsType } from 'src/core/server';
import { APP_ICON, getFullPath } from '../../common/constants';
// @ts-ignore
import { savedObjectMigrations } from './saved_object_migrations';
export const mapSavedObjects: SavedObjectsType = {
name: 'map',
hidden: false,
namespaceType: 'multiple-isolated',
convertToMultiNamespaceTypeVersion: '8.0.0',
mappings: {
properties: {
description: { type: 'text' },
title: { type: 'text' },
version: { type: 'integer' },
mapStateJSON: { type: 'text' },
layerListJSON: { type: 'text' },
uiStateJSON: { type: 'text' },
bounds: { dynamic: false, properties: {} }, // Disable removed field
},
},
management: {
icon: APP_ICON,
defaultSearchField: 'title',
importableAndExportable: true,
getTitle(obj) {
return obj.attributes.title;
},
getInAppUrl(obj) {
return {
path: getFullPath(obj.id),
uiCapabilitiesPath: 'maps.show',
};
},
},
migrations: savedObjectMigrations,
};

View file

@ -1,25 +0,0 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SavedObjectsType } from 'src/core/server';
/*
* The maps-telemetry saved object type isn't used, but in order to remove these fields from
* the mappings we register this type with `type: 'object', enabled: true` to remove all
* previous fields from the mappings until https://github.com/elastic/kibana/issues/67086 is
* solved.
*/
export const mapsTelemetrySavedObjects: SavedObjectsType = {
name: 'maps-telemetry',
hidden: false,
namespaceType: 'agnostic',
mappings: {
// @ts-ignore Core types don't support this since it's only really valid when removing a previously registered type
type: 'object',
enabled: false,
},
};

View file

@ -5,11 +5,17 @@
* 2.0.
*/
import type { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from 'kibana/server';
import { extractReferences } from '../../common/migrations/references';
// @ts-expect-error
import { emsRasterTileToEmsVectorTile } from '../../common/migrations/ems_raster_tile_to_ems_vector_tile';
// @ts-expect-error
import { topHitsTimeToSort } from '../../common/migrations/top_hits_time_to_sort';
// @ts-expect-error
import { moveApplyGlobalQueryToSources } from '../../common/migrations/move_apply_global_query';
// @ts-expect-error
import { addFieldMetaOptions } from '../../common/migrations/add_field_meta_options';
// @ts-expect-error
import { migrateSymbolStyleDescriptor } from '../../common/migrations/migrate_symbol_style_descriptor';
import { migrateUseTopHitsToScalingType } from '../../common/migrations/scaling_type';
import { migrateJoinAggKey } from '../../common/migrations/join_agg_key';
@ -19,8 +25,13 @@ import { addTypeToTermJoin } from '../../common/migrations/add_type_to_termjoin'
import { moveAttribution } from '../../common/migrations/move_attribution';
import { setEmsTmsDefaultModes } from '../../common/migrations/set_ems_tms_default_modes';
import { renameLayerTypes } from '../../common/migrations/rename_layer_types';
import type { MapSavedObjectAttributes } from '../../common/map_saved_object_type';
function logMigrationWarning(context, errorMsg, doc) {
function logMigrationWarning(
context: SavedObjectMigrationContext,
errorMsg: string,
doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>
) {
context.log.warning(
`map migration failed (${context.migrationVersion}). ${errorMsg}. attributes: ${JSON.stringify(
doc
@ -36,7 +47,10 @@ function logMigrationWarning(context, errorMsg, doc) {
* This is the saved object migration registry.
*/
export const savedObjectMigrations = {
'7.2.0': (doc, context) => {
'7.2.0': (
doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>,
context: SavedObjectMigrationContext
) => {
try {
const { attributes, references } = extractReferences(doc);
@ -50,7 +64,10 @@ export const savedObjectMigrations = {
return doc;
}
},
'7.4.0': (doc, context) => {
'7.4.0': (
doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>,
context: SavedObjectMigrationContext
) => {
try {
const attributes = emsRasterTileToEmsVectorTile(doc);
@ -63,7 +80,10 @@ export const savedObjectMigrations = {
return doc;
}
},
'7.5.0': (doc, context) => {
'7.5.0': (
doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>,
context: SavedObjectMigrationContext
) => {
try {
const attributes = topHitsTimeToSort(doc);
@ -76,7 +96,10 @@ export const savedObjectMigrations = {
return doc;
}
},
'7.6.0': (doc, context) => {
'7.6.0': (
doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>,
context: SavedObjectMigrationContext
) => {
try {
const attributesPhase1 = moveApplyGlobalQueryToSources(doc);
const attributesPhase2 = addFieldMetaOptions({ attributes: attributesPhase1 });
@ -90,7 +113,10 @@ export const savedObjectMigrations = {
return doc;
}
},
'7.7.0': (doc, context) => {
'7.7.0': (
doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>,
context: SavedObjectMigrationContext
) => {
try {
const attributesPhase1 = migrateSymbolStyleDescriptor(doc);
const attributesPhase2 = migrateUseTopHitsToScalingType({ attributes: attributesPhase1 });
@ -104,7 +130,10 @@ export const savedObjectMigrations = {
return doc;
}
},
'7.8.0': (doc, context) => {
'7.8.0': (
doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>,
context: SavedObjectMigrationContext
) => {
try {
const attributes = migrateJoinAggKey(doc);
@ -117,7 +146,10 @@ export const savedObjectMigrations = {
return doc;
}
},
'7.9.0': (doc, context) => {
'7.9.0': (
doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>,
context: SavedObjectMigrationContext
) => {
try {
const attributes = removeBoundsFromSavedObject(doc);
@ -130,7 +162,10 @@ export const savedObjectMigrations = {
return doc;
}
},
'7.10.0': (doc, context) => {
'7.10.0': (
doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>,
context: SavedObjectMigrationContext
) => {
try {
const attributes = setDefaultAutoFitToBounds(doc);
@ -143,7 +178,10 @@ export const savedObjectMigrations = {
return doc;
}
},
'7.12.0': (doc, context) => {
'7.12.0': (
doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>,
context: SavedObjectMigrationContext
) => {
try {
const attributes = addTypeToTermJoin(doc);
@ -156,7 +194,10 @@ export const savedObjectMigrations = {
return doc;
}
},
'7.14.0': (doc, context) => {
'7.14.0': (
doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>,
context: SavedObjectMigrationContext
) => {
try {
const attributes = moveAttribution(doc);
@ -169,7 +210,10 @@ export const savedObjectMigrations = {
return doc;
}
},
'8.0.0': (doc, context) => {
'8.0.0': (
doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>,
context: SavedObjectMigrationContext
) => {
try {
const attributes = setEmsTmsDefaultModes(doc);
@ -182,7 +226,10 @@ export const savedObjectMigrations = {
return doc;
}
},
'8.1.0': (doc, context) => {
'8.1.0': (
doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>,
context: SavedObjectMigrationContext
) => {
try {
const attributes = renameLayerTypes(doc);

View file

@ -0,0 +1,100 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { mapValues } from 'lodash';
import type { CoreSetup, SavedObjectUnsanitizedDoc } from 'kibana/server';
import type { SavedObjectMigrationMap } from 'src/core/server';
import { MigrateFunctionsObject } from '../../../../../src/plugins/kibana_utils/common';
import { mergeSavedObjectMigrationMaps } from '../../../../../src/core/server';
import { APP_ICON, getFullPath } from '../../common/constants';
import { migrateDataPersistedState } from '../../common/migrations/migrate_data_persisted_state';
import type { MapSavedObjectAttributes } from '../../common/map_saved_object_type';
import { savedObjectMigrations } from './saved_object_migrations';
export function setupSavedObjects(
core: CoreSetup,
getFilterMigrations: () => MigrateFunctionsObject
) {
core.savedObjects.registerType({
name: 'map',
hidden: false,
namespaceType: 'multiple-isolated',
convertToMultiNamespaceTypeVersion: '8.0.0',
mappings: {
properties: {
description: { type: 'text' },
title: { type: 'text' },
version: { type: 'integer' },
mapStateJSON: { type: 'text' },
layerListJSON: { type: 'text' },
uiStateJSON: { type: 'text' },
bounds: { dynamic: false, properties: {} }, // Disable removed field
},
},
management: {
icon: APP_ICON,
defaultSearchField: 'title',
importableAndExportable: true,
getTitle(obj) {
return obj.attributes.title;
},
getInAppUrl(obj) {
return {
path: getFullPath(obj.id),
uiCapabilitiesPath: 'maps.show',
};
},
},
migrations: () => {
return mergeSavedObjectMigrationMaps(
savedObjectMigrations,
getPersistedStateMigrations(getFilterMigrations()) as unknown as SavedObjectMigrationMap
);
},
});
/*
* The maps-telemetry saved object type isn't used, but in order to remove these fields from
* the mappings we register this type with `type: 'object', enabled: true` to remove all
* previous fields from the mappings until https://github.com/elastic/kibana/issues/67086 is
* solved.
*/
core.savedObjects.registerType({
name: 'maps-telemetry',
hidden: false,
namespaceType: 'agnostic',
mappings: {
// @ts-ignore Core types don't support this since it's only really valid when removing a previously registered type
type: 'object',
enabled: false,
},
});
}
/**
* This creates a migration map that applies external plugin migrations to persisted state stored in Maps
*/
export const getPersistedStateMigrations = (
filterMigrations: MigrateFunctionsObject
): MigrateFunctionsObject =>
mapValues(
filterMigrations,
(filterMigration) => (doc: SavedObjectUnsanitizedDoc<MapSavedObjectAttributes>) => {
try {
const attributes = migrateDataPersistedState(doc, filterMigration);
return {
...doc,
attributes,
};
} catch (e) {
// Do not fail migration
// Maps application can display error when saved object is viewed
return doc;
}
}
);

View file

@ -11,10 +11,14 @@ import { HomeServerPluginSetup } from '../../../../src/plugins/home/server';
import { LicensingPluginSetup } from '../../licensing/server';
import { MapsEmsPluginServerSetup } from '../../../../src/plugins/maps_ems/server';
import { EmbeddableSetup } from '../../../../src/plugins/embeddable/server';
import { PluginStart as DataPluginStart } from '../../../../src/plugins/data/server';
import {
PluginSetup as DataPluginSetup,
PluginStart as DataPluginStart,
} from '../../../../src/plugins/data/server';
import { CustomIntegrationsPluginSetup } from '../../../../src/plugins/custom_integrations/server';
export interface SetupDeps {
data: DataPluginSetup;
features: FeaturesPluginSetupContract;
usageCollection?: UsageCollectionSetup;
home?: HomeServerPluginSetup;