[Lens] use data plugin migrations for search sources in legacy visualizations (#123005)

This commit is contained in:
Andrew Tate 2022-02-01 08:01:41 -06:00 committed by GitHub
parent 021285e4b3
commit 62c76b9700
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 521 additions and 226 deletions

View file

@ -244,6 +244,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| --- | --- |
| [APP\_WRAPPER\_CLASS](./kibana-plugin-core-server.app_wrapper_class.md) | The class name for top level \*and\* nested application wrappers to ensure proper layout |
| [kibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) | Set of helpers used to create <code>KibanaResponse</code> to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-core-server.requesthandler.md) execution. |
| [mergeSavedObjectMigrationMaps](./kibana-plugin-core-server.mergesavedobjectmigrationmaps.md) | Merges two saved object migration maps. |
| [pollEsNodesVersion](./kibana-plugin-core-server.pollesnodesversion.md) | |
| [ServiceStatusLevels](./kibana-plugin-core-server.servicestatuslevels.md) | The current "level" of availability of a service. |
| [validBodyOutput](./kibana-plugin-core-server.validbodyoutput.md) | The set of valid body.output |

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [mergeSavedObjectMigrationMaps](./kibana-plugin-core-server.mergesavedobjectmigrationmaps.md)
## mergeSavedObjectMigrationMaps variable
Merges two saved object migration maps.
<b>Signature:</b>
```typescript
mergeSavedObjectMigrationMaps: (map1: SavedObjectMigrationMap, map2: SavedObjectMigrationMap) => SavedObjectMigrationMap
```

View file

@ -276,6 +276,7 @@ export {
SavedObjectsSerializer,
SavedObjectTypeRegistry,
SavedObjectsUtils,
mergeSavedObjectMigrationMaps,
} from './saved_objects';
export type {

View file

@ -84,6 +84,7 @@ export type {
SavedObjectMigrationFn,
SavedObjectMigrationContext,
} from './migrations';
export { mergeSavedObjectMigrationMaps } from './migrations';
export type {
SavedObjectsNamespaceType,

View file

@ -14,3 +14,4 @@ export type {
SavedObjectMigrationMap,
SavedObjectMigrationContext,
} from './types';
export { mergeSavedObjectMigrationMaps } from './utils';

View file

@ -0,0 +1,70 @@
/*
* 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 { SavedObjectMigrationContext, SavedObjectMigrationMap } from '.';
import { mergeSavedObjectMigrationMaps } from './utils';
import { SavedObjectUnsanitizedDoc } from '..';
describe('mergeSavedObjectMigrationMaps', () => {
const obj1: SavedObjectMigrationMap = {
'7.12.1': (doc, context) => {
context.log.info('');
return {
...doc,
attributes: { ...doc.attributes, counter: doc.attributes.counter + 1 },
};
},
'7.12.2': (doc, context) => {
context.log.info('');
return {
...doc,
attributes: { ...doc.attributes, counter: doc.attributes.counter + 2 },
};
},
};
const obj2: SavedObjectMigrationMap = {
'7.12.0': (doc, context) => {
context.log.info('');
return {
...doc,
attributes: { ...doc.attributes, counter: doc.attributes.counter - 2 },
};
},
'7.12.2': (doc, context) => {
context.log.info('');
return {
...doc,
attributes: { ...doc.attributes, counter: doc.attributes.counter + 2 },
};
},
};
test('correctly merges two saved object migration maps', () => {
const context = { log: { info: jest.fn() } } as unknown as SavedObjectMigrationContext;
const result = mergeSavedObjectMigrationMaps(obj1, obj2);
expect(
result['7.12.0']({ attributes: { counter: 5 } } as SavedObjectUnsanitizedDoc, context)
.attributes.counter
).toEqual(3);
expect(context.log.info).toHaveBeenCalledTimes(1);
expect(
result['7.12.1']({ attributes: { counter: 5 } } as SavedObjectUnsanitizedDoc, context)
.attributes.counter
).toEqual(6);
expect(context.log.info).toHaveBeenCalledTimes(2);
expect(
result['7.12.2']({ attributes: { counter: 5 } } as SavedObjectUnsanitizedDoc, context)
.attributes.counter
).toEqual(9);
expect(context.log.info).toHaveBeenCalledTimes(4);
});
});

View 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 { mergeWith } from 'lodash';
import {
SavedObjectMigrationContext,
SavedObjectMigrationFn,
SavedObjectMigrationMap,
SavedObjectUnsanitizedDoc,
} from '../..';
/**
* Merges two saved object migration maps.
*
* If there is a migration for a given version on only one of the maps,
* that migration function will be used:
*
* mergeSavedObjectMigrationMaps({ '1.2.3': f }, { '4.5.6': g }) -> { '1.2.3': f, '4.5.6': g }
*
* If there is a migration for a given version on both maps, the migrations will be composed:
*
* mergeSavedObjectMigrationMaps({ '1.2.3': f }, { '1.2.3': g }) -> { '1.2.3': (doc, context) => f(g(doc, context), context) }
*
* @public
*/
export const mergeSavedObjectMigrationMaps = (
map1: SavedObjectMigrationMap,
map2: SavedObjectMigrationMap
): SavedObjectMigrationMap => {
const customizer = (objValue: SavedObjectMigrationFn, srcValue: SavedObjectMigrationFn) => {
if (!srcValue || !objValue) {
return srcValue || objValue;
}
return (state: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext) =>
objValue(srcValue(state, context), context);
};
return mergeWith({ ...map1 }, map2, customizer);
};

View file

@ -1467,6 +1467,9 @@ export type MakeUsageFromSchema<T> = {
[Key in keyof T]?: T[Key] extends Maybe<object[]> ? false : T[Key] extends Maybe<any[]> ? boolean : T[Key] extends Maybe<object> ? MakeUsageFromSchema<T[Key]> | boolean : boolean;
};
// @public
export const mergeSavedObjectMigrationMaps: (map1: SavedObjectMigrationMap, map2: SavedObjectMigrationMap) => SavedObjectMigrationMap;
// @public
export interface MetricsServiceSetup {
readonly collectionInterval: number;

View file

@ -16,11 +16,31 @@ import {
SerializedSearchSourceFields,
} from './';
import { IndexPatternsContract } from '../..';
import { mergeMigrationFunctionMaps } from '../../../../kibana_utils/common';
import {
mergeMigrationFunctionMaps,
MigrateFunctionsObject,
} from '../../../../kibana_utils/common';
import { getAllMigrations as filtersGetAllMigrations } from '../../query/persistable_state';
const getAllMigrations = (): MigrateFunctionsObject => {
const searchSourceMigrations = {};
// we don't know if embeddables have any migrations defined so we need to fetch them and map the received functions so we pass
// them the correct input and that we correctly map the response
const filterMigrations = mapValues(filtersGetAllMigrations(), (migrate) => {
return (state: SerializedSearchSourceFields) => ({
...state,
filter: migrate(state.filter),
});
});
return mergeMigrationFunctionMaps(searchSourceMigrations, filterMigrations);
};
export class SearchSourceService {
public setup() {}
public setup() {
return { getAllMigrations };
}
public start(indexPatterns: IndexPatternsContract, dependencies: SearchSourceDependencies) {
return {
@ -39,20 +59,7 @@ export class SearchSourceService {
return { state: newState, references };
},
inject: injectReferences,
getAllMigrations: () => {
const searchSourceMigrations = {};
// we don't know if embeddables have any migrations defined so we need to fetch them and map the received functions so we pass
// them the correct input and that we correctly map the response
const filterMigrations = mapValues(filtersGetAllMigrations(), (migrate) => {
return (state: SerializedSearchSourceFields) => ({
...state,
filter: migrate(state.filter),
});
});
return mergeMigrationFunctionMaps(searchSourceMigrations, filterMigrations);
},
getAllMigrations,
telemetry: () => {
return {};
},

View file

@ -17,6 +17,7 @@ export function createSearchSetupMock(): jest.Mocked<ISearchSetup> {
aggs: searchAggsSetupMock(),
registerSearchStrategy: jest.fn(),
__enhance: jest.fn(),
searchSource: searchSourceMock.createSetupContract(),
};
}

View file

@ -236,6 +236,7 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
aggs,
registerSearchStrategy: this.registerSearchStrategy,
usage,
searchSource: this.searchSourceService.setup(),
};
}

View file

@ -10,7 +10,13 @@ import type { MockedKeys } from '@kbn/utility-types/jest';
import { KibanaRequest } from 'src/core/server';
import { searchSourceCommonMock } from '../../../common/search/search_source/mocks';
import type { ISearchStart } from '../types';
import type { ISearchStart, ISearchSetup } from '../types';
function createSetupContract(): MockedKeys<ISearchSetup['searchSource']> {
return {
getAllMigrations: jest.fn(),
};
}
function createStartContract(): MockedKeys<ISearchStart['searchSource']> {
return {
@ -21,5 +27,6 @@ function createStartContract(): MockedKeys<ISearchStart['searchSource']> {
}
export const searchSourceMock = {
createSetupContract,
createStartContract,
};

View file

@ -23,6 +23,7 @@ import {
ISearchClient,
IEsSearchResponse,
IEsSearchRequest,
SearchSourceService,
} from '../../common/search';
import { AggsSetup, AggsStart } from './aggs';
import { SearchUsage } from './collectors';
@ -63,6 +64,8 @@ export interface ISearchSetup {
* @internal
*/
__enhance: (enhancements: SearchEnhancements) => void;
searchSource: ReturnType<SearchSourceService['setup']>;
}
/**

View file

@ -8,7 +8,7 @@
import { SavedObjectAttributes } from 'kibana/server';
import type { SerializableRecord } from '@kbn/utility-types';
import { AggConfigSerialized } from 'src/plugins/data/common';
import { AggConfigSerialized, SerializedSearchSourceFields } from 'src/plugins/data/common';
export interface VisParams {
[key: string]: any;
@ -32,3 +32,20 @@ export interface VisualizationSavedObjectAttributes extends SavedObjectAttribute
visState: string;
uiStateJSON: string;
}
export interface SerializedVisData {
expression?: string;
aggs: AggConfigSerialized[];
searchSource: SerializedSearchSourceFields;
savedSearchId?: string;
}
export interface SerializedVis<T = VisParams> {
id?: string;
title: string;
description?: string;
type: string;
params: T;
uiState?: any;
data: SerializedVisData;
}

View file

@ -16,9 +16,9 @@ import {
import type { ISearchSource } from '../../data/common';
import { ExpressionAstExpression } from '../../expressions/public';
import type { SerializedVis, Vis } from './vis';
import type { Vis } from './vis';
import type { PersistedState } from './persisted_state';
import type { VisParams } from '../common';
import type { VisParams, SerializedVis } from '../common';
export type { Vis, SerializedVis, VisParams };
export interface SavedVisState {

View file

@ -22,34 +22,13 @@ import { i18n } from '@kbn/i18n';
import { PersistedState } from './persisted_state';
import { getTypes, getAggs, getSearch, getSavedObjects, getSpaces } from './services';
import {
IAggConfigs,
IndexPattern,
ISearchSource,
AggConfigSerialized,
SerializedSearchSourceFields,
} from '../../data/public';
import { IAggConfigs, IndexPattern, ISearchSource, AggConfigSerialized } from '../../data/public';
import { BaseVisType } from './vis_types';
import { VisParams } from '../common/types';
import { SerializedVis, SerializedVisData, VisParams } from '../common/types';
import { getSavedSearch, throwErrorOnSavedSearchUrlConflict } from '../../discover/public';
export interface SerializedVisData {
expression?: string;
aggs: AggConfigSerialized[];
searchSource: SerializedSearchSourceFields;
savedSearchId?: string;
}
export interface SerializedVis<T = VisParams> {
id?: string;
title: string;
description?: string;
type: string;
params: T;
uiState?: any;
data: SerializedVisData;
}
export type { SerializedVis, SerializedVisData };
export interface VisData {
ast?: string;

View file

@ -0,0 +1,69 @@
/*
* 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 semverGte from 'semver/functions/gte';
import { makeVisualizeEmbeddableFactory } from './make_visualize_embeddable_factory';
import { getAllMigrations } from '../migrations/visualization_saved_object_migrations';
import { SerializedSearchSourceFields } from 'src/plugins/data/public';
import { GetMigrationFunctionObjectFn } from 'src/plugins/kibana_utils/common';
describe('embeddable migrations', () => {
test('should have same versions registered as saved object migrations versions (>7.13.0)', () => {
const savedObjectMigrationVersions = Object.keys(getAllMigrations({})).filter((version) => {
return semverGte(version, '7.13.1');
});
const embeddableMigrationVersions = (
makeVisualizeEmbeddableFactory(() => ({}))()?.migrations as GetMigrationFunctionObjectFn
)();
if (embeddableMigrationVersions) {
expect(savedObjectMigrationVersions.sort()).toEqual(
Object.keys(embeddableMigrationVersions).sort()
);
}
});
test('should properly apply a filter migration within a legacy visualization', () => {
const migrationVersion = 'some-version';
const embeddedVisualizationDoc = {
savedVis: {
data: {
searchSource: {
type: 'some-type',
migrated: false,
},
},
},
};
const embeddableMigrationVersions = (
makeVisualizeEmbeddableFactory(() => ({
[migrationVersion]: (searchSource: SerializedSearchSourceFields) => {
return {
...searchSource,
migrated: true,
};
},
}))()?.migrations as GetMigrationFunctionObjectFn
)();
const migratedVisualizationDoc =
embeddableMigrationVersions?.[migrationVersion](embeddedVisualizationDoc);
expect(migratedVisualizationDoc).toEqual({
savedVis: {
data: {
searchSource: {
type: 'some-type',
migrated: true,
},
},
},
});
});
});

View file

@ -6,9 +6,15 @@
* Side Public License, v 1.
*/
import { flow } from 'lodash';
import { flow, mapValues } from 'lodash';
import { EmbeddableRegistryDefinition } from 'src/plugins/embeddable/server';
import type { SerializableRecord } from '@kbn/utility-types';
import { SerializedSearchSourceFields } from 'src/plugins/data/public';
import {
mergeMigrationFunctionMaps,
MigrateFunctionsObject,
MigrateFunction,
} from '../../../kibana_utils/common';
import {
commonAddSupportOfDualIndexSelectionModeInTSVB,
commonHideTSVBLastValueIndicator,
@ -20,6 +26,7 @@ import {
commonAddDropLastBucketIntoTSVBModel714Above,
commonRemoveMarkdownLessFromTSVB,
} from '../migrations/visualization_common_migrations';
import { SerializedVis } from '../../common';
const byValueAddSupportOfDualIndexSelectionModeInTSVB = (state: SerializableRecord) => {
return {
@ -84,26 +91,53 @@ const byValueRemoveMarkdownLessFromTSVB = (state: SerializableRecord) => {
};
};
export const visualizeEmbeddableFactory = (): EmbeddableRegistryDefinition => {
return {
id: 'visualization',
migrations: {
// These migrations are run in 7.13.1 for `by value` panels because the 7.13 release window was missed.
'7.13.1': (state) =>
flow(
byValueAddSupportOfDualIndexSelectionModeInTSVB,
byValueHideTSVBLastValueIndicator,
byValueRemoveDefaultIndexPatternAndTimeFieldFromTSVBModel
)(state),
'7.14.0': (state) =>
flow(
byValueAddEmptyValueColorRule,
byValueMigrateVislibPie,
byValueMigrateTagcloud,
byValueAddDropLastBucketIntoTSVBModel
)(state),
'7.17.0': (state) => flow(byValueAddDropLastBucketIntoTSVBModel714Above)(state),
'8.0.0': (state) => flow(byValueRemoveMarkdownLessFromTSVB)(state),
},
const getEmbeddedVisualizationSearchSourceMigrations = (
searchSourceMigrations: MigrateFunctionsObject
) =>
mapValues<MigrateFunctionsObject, MigrateFunction>(
searchSourceMigrations,
(migrate: MigrateFunction<SerializedSearchSourceFields>): MigrateFunction =>
(state) => {
const _state = state as unknown as { savedVis: SerializedVis };
return {
..._state,
savedVis: {
..._state.savedVis,
data: {
..._state.savedVis.data,
searchSource: migrate(_state.savedVis.data.searchSource),
},
},
};
}
);
export const makeVisualizeEmbeddableFactory =
(getSearchSourceMigrations: () => MigrateFunctionsObject) => (): EmbeddableRegistryDefinition => {
return {
id: 'visualization',
// migrations set up as a callable so that getSearchSourceMigrations doesn't get invoked till after plugin setup steps
migrations: () =>
mergeMigrationFunctionMaps(
getEmbeddedVisualizationSearchSourceMigrations(getSearchSourceMigrations()),
{
// These migrations are run in 7.13.1 for `by value` panels because the 7.13 release window was missed.
'7.13.1': (state) =>
flow(
byValueAddSupportOfDualIndexSelectionModeInTSVB,
byValueHideTSVBLastValueIndicator,
byValueRemoveDefaultIndexPatternAndTimeFieldFromTSVBModel
)(state),
'7.14.0': (state) =>
flow(
byValueAddEmptyValueColorRule,
byValueMigrateVislibPie,
byValueMigrateTagcloud,
byValueAddDropLastBucketIntoTSVBModel
)(state),
'7.17.0': (state) => flow(byValueAddDropLastBucketIntoTSVBModel714Above)(state),
'8.0.0': (state) => flow(byValueRemoveMarkdownLessFromTSVB)(state),
}
),
};
};
};

View file

@ -1,27 +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 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 semverGte from 'semver/functions/gte';
import { visualizeEmbeddableFactory } from './visualize_embeddable_factory';
import { visualizationSavedObjectTypeMigrations } from '../migrations/visualization_saved_object_migrations';
describe('saved object migrations and embeddable migrations', () => {
test('should have same versions registered (>7.13.0)', () => {
const savedObjectMigrationVersions = Object.keys(visualizationSavedObjectTypeMigrations).filter(
(version) => {
return semverGte(version, '7.13.1');
}
);
const embeddableMigrationVersions = visualizeEmbeddableFactory()?.migrations;
if (embeddableMigrationVersions) {
expect(savedObjectMigrationVersions.sort()).toEqual(
Object.keys(embeddableMigrationVersions).sort()
);
}
});
});

View file

@ -6,8 +6,12 @@
* Side Public License, v 1.
*/
import { visualizationSavedObjectTypeMigrations } from './visualization_saved_object_migrations';
import { SavedObjectMigrationContext, SavedObjectMigrationFn } from 'kibana/server';
import { getAllMigrations } from './visualization_saved_object_migrations';
import {
SavedObjectMigrationContext,
SavedObjectMigrationFn,
SavedObjectUnsanitizedDoc,
} from 'kibana/server';
const savedObjectMigrationContext = null as unknown as SavedObjectMigrationContext;
@ -56,6 +60,8 @@ const testMigrateMatchAllQuery = (migrate: Function) => {
};
describe('migration visualization', () => {
const visualizationSavedObjectTypeMigrations = getAllMigrations({});
describe('6.7.2', () => {
const migrate = (doc: any) =>
visualizationSavedObjectTypeMigrations['6.7.2'](
@ -2432,4 +2438,35 @@ describe('migration visualization', () => {
`);
});
});
it('should apply search source migrations within visualization', () => {
const visualizationDoc = {
attributes: {
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
some: 'prop',
migrated: false,
}),
},
},
} as SavedObjectUnsanitizedDoc;
const versionToTest = '1.2.3';
const visMigrations = getAllMigrations({
[versionToTest]: (state) => ({ ...state, migrated: true }),
});
expect(
visMigrations[versionToTest](visualizationDoc, {} as SavedObjectMigrationContext)
).toEqual({
attributes: {
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
some: 'prop',
migrated: true,
}),
},
},
});
});
});

View file

@ -6,11 +6,16 @@
* Side Public License, v 1.
*/
import { cloneDeep, get, omit, has, flow, forOwn } from 'lodash';
import { cloneDeep, get, omit, has, flow, forOwn, mapValues } from 'lodash';
import type { SavedObjectMigrationFn, SavedObjectMigrationMap } from 'kibana/server';
import { mergeSavedObjectMigrationMaps } from '../../../../core/server';
import { MigrateFunctionsObject, MigrateFunction } from '../../../kibana_utils/common';
import type { SavedObjectMigrationFn } from 'kibana/server';
import { DEFAULT_QUERY_LANGUAGE, INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../../data/common';
import {
DEFAULT_QUERY_LANGUAGE,
INDEX_PATTERN_SAVED_OBJECT_TYPE,
SerializedSearchSourceFields,
} from '../../../data/common';
import {
commonAddSupportOfDualIndexSelectionModeInTSVB,
commonHideTSVBLastValueIndicator,
@ -22,6 +27,7 @@ import {
commonAddDropLastBucketIntoTSVBModel714Above,
commonRemoveMarkdownLessFromTSVB,
} from './visualization_common_migrations';
import { VisualizationSavedObjectAttributes } from '../../common';
const migrateIndexPattern: SavedObjectMigrationFn<any, any> = (doc) => {
const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON');
@ -1126,7 +1132,7 @@ export const removeMarkdownLessFromTSVB: SavedObjectMigrationFn<any, any> = (doc
return doc;
};
export const visualizationSavedObjectTypeMigrations = {
const visualizationSavedObjectTypeMigrations = {
/**
* 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
@ -1182,3 +1188,40 @@ export const visualizationSavedObjectTypeMigrations = {
'7.17.0': flow(addDropLastBucketIntoTSVBModel714Above),
'8.0.0': flow(removeMarkdownLessFromTSVB),
};
/**
* This creates a migration map that applies search source migrations to legacy visualization SOs
*/
const getVisualizationSearchSourceMigrations = (searchSourceMigrations: MigrateFunctionsObject) =>
mapValues<MigrateFunctionsObject, MigrateFunction>(
searchSourceMigrations,
(migrate: MigrateFunction<SerializedSearchSourceFields>): MigrateFunction =>
(state) => {
const _state = state as unknown as { attributes: VisualizationSavedObjectAttributes };
const parsedSearchSourceJSON = _state.attributes.kibanaSavedObjectMeta.searchSourceJSON;
if (!parsedSearchSourceJSON) return _state;
return {
..._state,
attributes: {
..._state.attributes,
kibanaSavedObjectMeta: {
..._state.attributes.kibanaSavedObjectMeta,
searchSourceJSON: JSON.stringify(migrate(JSON.parse(parsedSearchSourceJSON))),
},
},
};
}
);
export const getAllMigrations = (
searchSourceMigrations: MigrateFunctionsObject
): SavedObjectMigrationMap =>
mergeSavedObjectMigrationMaps(
visualizationSavedObjectTypeMigrations,
getVisualizationSearchSourceMigrations(
searchSourceMigrations
) as unknown as SavedObjectMigrationMap
);

View file

@ -9,8 +9,8 @@
import { i18n } from '@kbn/i18n';
import { schema } from '@kbn/config-schema';
import { PluginSetup as DataPluginSetup } from 'src/plugins/data/server';
import { VISUALIZE_ENABLE_LABS_SETTING } from '../common/constants';
import { visualizationSavedObjectType } from './saved_objects';
import { registerVisualizationsCollector } from './usage_collector';
import { capabilitiesProvider } from './capabilities_provider';
@ -24,7 +24,8 @@ import type {
} from '../../../core/server';
import type { UsageCollectionSetup } from '../../usage_collection/server';
import type { EmbeddableSetup } from '../../embeddable/server';
import { visualizeEmbeddableFactory } from './embeddable/visualize_embeddable_factory';
import { makeVisualizeEmbeddableFactory } from './embeddable/make_visualize_embeddable_factory';
import { getVisualizationSavedObjectType } from './saved_objects';
export class VisualizationsPlugin
implements Plugin<VisualizationsPluginSetup, VisualizationsPluginStart>
@ -37,11 +38,18 @@ export class VisualizationsPlugin
public setup(
core: CoreSetup,
plugins: { usageCollection?: UsageCollectionSetup; embeddable: EmbeddableSetup }
plugins: {
usageCollection?: UsageCollectionSetup;
embeddable: EmbeddableSetup;
data: DataPluginSetup;
}
) {
this.logger.debug('visualizations: Setup');
core.savedObjects.registerType(visualizationSavedObjectType);
const getSearchSourceMigrations = plugins.data.search.searchSource.getAllMigrations.bind(
plugins.data.search.searchSource
);
core.savedObjects.registerType(getVisualizationSavedObjectType(getSearchSourceMigrations));
core.capabilities.registerProvider(capabilitiesProvider);
core.uiSettings.register({
@ -63,7 +71,9 @@ export class VisualizationsPlugin
registerVisualizationsCollector(plugins.usageCollection);
}
plugins.embeddable.registerEmbeddableFactory(visualizeEmbeddableFactory());
plugins.embeddable.registerEmbeddableFactory(
makeVisualizeEmbeddableFactory(getSearchSourceMigrations)()
);
return {};
}

View file

@ -6,4 +6,4 @@
* Side Public License, v 1.
*/
export { visualizationSavedObjectType } from './visualization';
export { getVisualizationSavedObjectType } from './visualization';

View file

@ -7,9 +7,12 @@
*/
import { SavedObjectsType } from 'kibana/server';
import { visualizationSavedObjectTypeMigrations } from '../migrations/visualization_saved_object_migrations';
import { MigrateFunctionsObject } from 'src/plugins/kibana_utils/common';
import { getAllMigrations } from '../migrations/visualization_saved_object_migrations';
export const visualizationSavedObjectType: SavedObjectsType = {
export const getVisualizationSavedObjectType = (
getSearchSourceMigrations: () => MigrateFunctionsObject
): SavedObjectsType => ({
name: 'visualization',
hidden: false,
namespaceType: 'multiple-isolated',
@ -41,5 +44,5 @@ export const visualizationSavedObjectType: SavedObjectsType = {
visState: { type: 'text', index: false },
},
},
migrations: visualizationSavedObjectTypeMigrations,
};
migrations: () => getAllMigrations(getSearchSourceMigrations()),
});

View file

@ -822,7 +822,7 @@ describe('common utils', () => {
].join('\n\n');
const extractedReferences = extractLensReferencesFromCommentString(
makeLensEmbeddableFactory({}),
makeLensEmbeddableFactory(() => ({})),
commentString
);
@ -921,7 +921,7 @@ describe('common utils', () => {
].join('\n\n');
const updatedReferences = getOrUpdateLensReferences(
makeLensEmbeddableFactory({}),
makeLensEmbeddableFactory(() => ({})),
newCommentString,
{
references: currentCommentReferences,

View file

@ -36,7 +36,7 @@ import { GENERATED_ALERT, SUB_CASE_SAVED_OBJECT } from './constants';
describe('comments migrations', () => {
const migrations = createCommentsMigrations({
lensEmbeddableFactory: makeLensEmbeddableFactory({}),
lensEmbeddableFactory: makeLensEmbeddableFactory(() => ({})),
});
const contextMock = savedObjectsServiceMock.createMigrationContext();

View file

@ -9,13 +9,16 @@ import semverGte from 'semver/functions/gte';
import { makeLensEmbeddableFactory } from './make_lens_embeddable_factory';
import { getAllMigrations } from '../migrations/saved_object_migrations';
import { Filter } from '@kbn/es-query';
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) => {
return semverGte(version, '7.13.1');
});
const embeddableMigrationVersions = makeLensEmbeddableFactory({})()?.migrations;
const embeddableMigrationVersions = (
makeLensEmbeddableFactory(() => ({}))()?.migrations as GetMigrationFunctionObjectFn
)();
if (embeddableMigrationVersions) {
expect(savedObjectMigrationVersions.sort()).toEqual(
Object.keys(embeddableMigrationVersions).sort()
@ -43,19 +46,17 @@ describe('embeddable migrations', () => {
},
};
const embeddableMigrationVersions = makeLensEmbeddableFactory({
[migrationVersion]: (filters: Filter[]) => {
return filters.map((filterState) => ({
...filterState,
migrated: true,
}));
},
})()?.migrations;
const migrations = (
makeLensEmbeddableFactory(() => ({
[migrationVersion]: (filters: Filter[]) => {
return filters.map((filterState) => ({
...filterState,
migrated: true,
}));
},
}))()?.migrations as GetMigrationFunctionObjectFn
)();
const migrations =
typeof embeddableMigrationVersions === 'function'
? embeddableMigrationVersions()
: embeddableMigrationVersions || {};
const migratedLensDoc = migrations[migrationVersion](lensVisualizationDoc);
expect(migratedLensDoc).toEqual({

View file

@ -31,54 +31,55 @@ import {
import { extract, inject } from '../../common/embeddable_factory';
export const makeLensEmbeddableFactory =
(filterMigrations: MigrateFunctionsObject) => (): EmbeddableRegistryDefinition => {
(getFilterMigrations: () => MigrateFunctionsObject) => (): EmbeddableRegistryDefinition => {
return {
id: DOC_TYPE,
migrations: mergeMigrationFunctionMaps(getLensFilterMigrations(filterMigrations), {
// 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;
},
}),
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;
},
}),
extract,
inject,
};

View file

@ -5,13 +5,9 @@
* 2.0.
*/
import { cloneDeep } from 'lodash';
import { cloneDeep, mapValues } from 'lodash';
import { PaletteOutput } from 'src/plugins/charts/common';
import { Filter } from '@kbn/es-query';
import {
MigrateFunction,
MigrateFunctionsObject,
} from '../../../../../src/plugins/kibana_utils/common';
import { MigrateFunctionsObject } from '../../../../../src/plugins/kibana_utils/common';
import {
LensDocShapePre712,
OperationTypePre712,
@ -190,30 +186,16 @@ export const commonRenameFilterReferences = (attributes: LensDocShape715): LensD
return newAttributes as LensDocShape810;
};
const getApplyFilterMigrationToLens = (filterMigration: MigrateFunction<Filter[]>) => {
return (savedObject: { attributes: LensDocShape }) => {
return {
...savedObject,
attributes: {
...savedObject.attributes,
state: {
...savedObject.attributes.state,
filters: filterMigration(savedObject.attributes.state.filters as unknown as Filter[]),
},
},
};
};
};
/**
* This creates a migration map that applies filter migrations to Lens visualizations
*/
export const getLensFilterMigrations = (filterMigrations: MigrateFunctionsObject) => {
const migrationMap: MigrateFunctionsObject = {};
for (const version in filterMigrations) {
if (filterMigrations.hasOwnProperty(version)) {
migrationMap[version] = getApplyFilterMigrationToLens(filterMigrations[version]);
}
}
return migrationMap;
};
export const getLensFilterMigrations = (
filterMigrations: MigrateFunctionsObject
): MigrateFunctionsObject =>
mapValues(filterMigrations, (migrate) => (lensDoc: { attributes: LensDocShape }) => ({
...lensDoc,
attributes: {
...lensDoc.attributes,
state: { ...lensDoc.attributes.state, filters: migrate(lensDoc.attributes.state.filters) },
},
}));

View file

@ -5,17 +5,17 @@
* 2.0.
*/
import { cloneDeep, flow, mergeWith } from 'lodash';
import { cloneDeep, flow } from 'lodash';
import { fromExpression, toExpression, Ast, AstFunction } from '@kbn/interpreter';
import {
SavedObjectMigrationMap,
SavedObjectMigrationFn,
SavedObjectReference,
SavedObjectUnsanitizedDoc,
SavedObjectMigrationContext,
} from 'src/core/server';
import { Filter } from '@kbn/es-query';
import { Query } from 'src/plugins/data/public';
import { mergeSavedObjectMigrationMaps } from '../../../../../src/core/server';
import { MigrateFunctionsObject } from '../../../../../src/plugins/kibana_utils/common';
import { PersistableFilter } from '../../common';
import {
@ -472,22 +472,10 @@ const lensMigrations: SavedObjectMigrationMap = {
'8.1.0': flow(renameFilterReferences, renameRecordsField),
};
export const mergeSavedObjectMigrationMaps = (
obj1: SavedObjectMigrationMap,
obj2: SavedObjectMigrationMap
): SavedObjectMigrationMap => {
const customizer = (objValue: SavedObjectMigrationFn, srcValue: SavedObjectMigrationFn) => {
if (!srcValue || !objValue) {
return srcValue || objValue;
}
return (state: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext) =>
objValue(srcValue(state, context), context);
};
return mergeWith({ ...obj1 }, obj2, customizer);
};
export const getAllMigrations = (
filterMigrations: MigrateFunctionsObject
): SavedObjectMigrationMap =>
mergeSavedObjectMigrationMaps(lensMigrations, getLensFilterMigrations(filterMigrations));
mergeSavedObjectMigrationMaps(
lensMigrations,
getLensFilterMigrations(filterMigrations) as unknown as SavedObjectMigrationMap
);

View file

@ -54,8 +54,10 @@ export class LensServerPlugin implements Plugin<LensServerPluginSetup, {}, {}, {
}
setup(core: CoreSetup<PluginStartContract>, plugins: PluginSetupContract) {
const filterMigrations = plugins.data.query.filterManager.getAllMigrations();
setupSavedObjects(core, filterMigrations);
const getFilterMigrations = plugins.data.query.filterManager.getAllMigrations.bind(
plugins.data.query.filterManager
);
setupSavedObjects(core, getFilterMigrations);
setupRoutes(core, this.initializerContext.logger.get());
setupExpressions(core, plugins.expressions);
core.uiSettings.register(getUiSettings());
@ -70,7 +72,7 @@ export class LensServerPlugin implements Plugin<LensServerPluginSetup, {}, {}, {
initializeLensTelemetry(this.telemetryLogger, core, plugins.taskManager);
}
const lensEmbeddableFactory = makeLensEmbeddableFactory(filterMigrations);
const lensEmbeddableFactory = makeLensEmbeddableFactory(getFilterMigrations);
plugins.embeddable.registerEmbeddableFactory(lensEmbeddableFactory());
return {
lensEmbeddableFactory,

View file

@ -10,7 +10,10 @@ import { MigrateFunctionsObject } from '../../../../src/plugins/kibana_utils/com
import { getEditPath } from '../common';
import { getAllMigrations } from './migrations/saved_object_migrations';
export function setupSavedObjects(core: CoreSetup, filterMigrations: MigrateFunctionsObject) {
export function setupSavedObjects(
core: CoreSetup,
getFilterMigrations: () => MigrateFunctionsObject
) {
core.savedObjects.registerType({
name: 'lens',
hidden: false,
@ -26,7 +29,7 @@ export function setupSavedObjects(core: CoreSetup, filterMigrations: MigrateFunc
uiCapabilitiesPath: 'visualize.show',
}),
},
migrations: getAllMigrations(filterMigrations),
migrations: () => getAllMigrations(getFilterMigrations()),
mappings: {
properties: {
title: {