From da65b8e82826eadf464a3c19d9b77fa45560a26f Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Tue, 7 Apr 2020 18:45:19 -0400 Subject: [PATCH 1/2] [Lens] [7.7] Migration for 7.7 --- x-pack/legacy/plugins/lens/index.ts | 2 + x-pack/legacy/plugins/lens/migrations.test.ts | 157 ++++++++++++++++++ x-pack/legacy/plugins/lens/migrations.ts | 67 ++++++++ 3 files changed, 226 insertions(+) create mode 100644 x-pack/legacy/plugins/lens/migrations.test.ts create mode 100644 x-pack/legacy/plugins/lens/migrations.ts diff --git a/x-pack/legacy/plugins/lens/index.ts b/x-pack/legacy/plugins/lens/index.ts index b1c67fb81ba07..153c576dda642 100644 --- a/x-pack/legacy/plugins/lens/index.ts +++ b/x-pack/legacy/plugins/lens/index.ts @@ -13,6 +13,7 @@ import { getEditPath, NOT_INTERNATIONALIZED_PRODUCT_NAME, } from '../../../plugins/lens/common'; +import { migrations } from './migrations'; export const lens: LegacyPluginInitializer = kibana => { return new kibana.Plugin({ @@ -33,6 +34,7 @@ export const lens: LegacyPluginInitializer = kibana => { embeddableFactories: [`plugins/${PLUGIN_ID}/legacy`], styleSheetPaths: resolve(__dirname, 'public/index.scss'), mappings, + migrations: { lens: migrations }, savedObjectsManagement: { lens: { defaultSearchField: 'title', diff --git a/x-pack/legacy/plugins/lens/migrations.test.ts b/x-pack/legacy/plugins/lens/migrations.test.ts new file mode 100644 index 0000000000000..90d1cac10416c --- /dev/null +++ b/x-pack/legacy/plugins/lens/migrations.test.ts @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { migrations } from './migrations'; + +describe('Lens migrations', () => { + describe('7.7.0 missing dimensions in XY', () => { + const migrate = (doc: unknown) => migrations['7.7.0'](doc); + + const example = { + type: 'lens', + attributes: { + expression: + 'kibana\n| kibana_context query="{\\"language\\":\\"kuery\\",\\"query\\":\\"\\"}" \n| lens_merge_tables layerIds="c61a8afb-a185-4fae-a064-fb3846f6c451" \n tables={esaggs index="logstash-*" metricsAtAllLevels=false partialRows=false includeFormatHints=true aggConfigs="[{\\"id\\":\\"2cd09808-3915-49f4-b3b0-82767eba23f7\\",\\"enabled\\":true,\\"type\\":\\"max\\",\\"schema\\":\\"metric\\",\\"params\\":{\\"field\\":\\"bytes\\"}}]" | lens_rename_columns idMap="{\\"col-0-2cd09808-3915-49f4-b3b0-82767eba23f7\\":\\"2cd09808-3915-49f4-b3b0-82767eba23f7\\"}"}\n| lens_metric_chart title="Maximum of bytes" accessor="2cd09808-3915-49f4-b3b0-82767eba23f7"', + state: { + datasourceMetaData: { + filterableIndexPatterns: [ + { + id: 'logstash-*', + title: 'logstash-*', + }, + ], + }, + datasourceStates: { + indexpattern: { + currentIndexPatternId: 'logstash-*', + layers: { + 'c61a8afb-a185-4fae-a064-fb3846f6c451': { + columnOrder: ['2cd09808-3915-49f4-b3b0-82767eba23f7'], + columns: { + '2cd09808-3915-49f4-b3b0-82767eba23f7': { + dataType: 'number', + isBucketed: false, + label: 'Maximum of bytes', + operationType: 'max', + scale: 'ratio', + sourceField: 'bytes', + }, + 'd3e62a7a-c259-4fff-a2fc-eebf20b7008a': { + dataType: 'number', + isBucketed: false, + label: 'Minimum of bytes', + operationType: 'min', + scale: 'ratio', + sourceField: 'bytes', + }, + 'd6e40cea-6299-43b4-9c9d-b4ee305a2ce8': { + dataType: 'date', + isBucketed: true, + label: 'Date Histogram of @timestamp', + operationType: 'date_histogram', + params: { + interval: 'auto', + }, + scale: 'interval', + sourceField: '@timestamp', + }, + }, + indexPatternId: 'logstash-*', + }, + }, + }, + }, + filters: [], + query: { + language: 'kuery', + query: '', + }, + visualization: { + accessor: '2cd09808-3915-49f4-b3b0-82767eba23f7', + isHorizontal: false, + layerId: 'c61a8afb-a185-4fae-a064-fb3846f6c451', + layers: [ + { + accessors: [ + 'd3e62a7a-c259-4fff-a2fc-eebf20b7008a', + '26ef70a9-c837-444c-886e-6bd905ee7335', + ], + layerId: 'c61a8afb-a185-4fae-a064-fb3846f6c451', + seriesType: 'area', + splitAccessor: '54cd64ed-2a44-4591-af84-b2624504569a', + xAccessor: 'd6e40cea-6299-43b4-9c9d-b4ee305a2ce8', + }, + ], + legend: { + isVisible: true, + position: 'right', + }, + preferredSeriesType: 'area', + }, + }, + title: 'Artistpreviouslyknownaslens', + visualizationType: 'lnsXY', + }, + }; + + it('should not change anything by XY visualizations', () => { + const target = { + ...example, + attributes: { + ...example.attributes, + visualizationType: 'lnsMetric', + }, + }; + const result = migrate(target); + expect(result).toEqual(target); + }); + + it('should handle missing layers', () => { + const result = migrate({ + ...example, + attributes: { + ...example.attributes, + state: { + ...example.attributes.state, + datasourceStates: { + indexpattern: { + layers: [], + }, + }, + }, + }, + }); + + expect(result.attributes.state.visualization.layers).toEqual([ + { + layerId: 'c61a8afb-a185-4fae-a064-fb3846f6c451', + seriesType: 'area', + // Removed split accessor + splitAccessor: undefined, + xAccessor: undefined, + // Removed a yAcccessor + accessors: [], + }, + ]); + }); + + it('should remove only missing accessors', () => { + const result = migrate(example); + + expect(result.attributes.state.visualization.layers).toEqual([ + { + layerId: 'c61a8afb-a185-4fae-a064-fb3846f6c451', + seriesType: 'area', + xAccessor: 'd6e40cea-6299-43b4-9c9d-b4ee305a2ce8', + // Removed split accessor + splitAccessor: undefined, + // Removed a yAcccessor + accessors: ['d3e62a7a-c259-4fff-a2fc-eebf20b7008a'], + }, + ]); + }); + }); +}); diff --git a/x-pack/legacy/plugins/lens/migrations.ts b/x-pack/legacy/plugins/lens/migrations.ts new file mode 100644 index 0000000000000..30eced3074eba --- /dev/null +++ b/x-pack/legacy/plugins/lens/migrations.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { cloneDeep } from 'lodash'; +import { SimpleSavedObject } from 'src/core/public'; + +interface RawLensSavedXYObject770 { + type: 'lens'; + attributes: { + visualizationType: string; + state: { + datasourceStates?: { + indexpattern?: { + layers: Record }>; + }; + }; + visualization: { + layers: Array<{ + layerId: string; + accessors: string[]; + xAccessor: string; + splitAccessor: string; + }>; + }; + }; + }; +} + +type LensSavedXYObjectPost770 = RawLensSavedXYObject770; + +function isLensSavedXY770( + doc: SimpleSavedObject | RawLensSavedXYObject770 +): doc is RawLensSavedXYObject770 { + return ( + doc.type === 'lens' && + doc.attributes && + (doc.attributes as Record).visualizationType === 'lnsXY' + ); +} + +export const migrations = { + '7.7.0': ( + doc: SimpleSavedObject | RawLensSavedXYObject770 + ): SimpleSavedObject | LensSavedXYObjectPost770 => { + const newDoc = cloneDeep(doc); + if (!isLensSavedXY770(newDoc)) { + return newDoc; + } + const datasourceState = newDoc.attributes.state?.datasourceStates?.indexpattern; + const datasourceLayers = datasourceState?.layers ?? {}; + const xyState = newDoc.attributes.state?.visualization; + newDoc.attributes.state.visualization.layers = xyState.layers.map(layer => { + const layerId = layer.layerId; + const datasource = datasourceLayers[layerId]; + return { + ...layer, + xAccessor: datasource?.columns[layer.xAccessor] ? layer.xAccessor : undefined, + splitAccessor: datasource?.columns[layer.splitAccessor] ? layer.splitAccessor : undefined, + accessors: layer.accessors.filter(accessor => !!datasource?.columns[accessor]), + }; + }) as typeof xyState.layers; + return newDoc; + }, +}; From 7ccd6e1b6a5e4724011e7d7bb88b16aa1cc20176 Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Tue, 7 Apr 2020 18:54:06 -0400 Subject: [PATCH 2/2] Fix type issues --- x-pack/legacy/plugins/lens/migrations.test.ts | 13 +++++---- x-pack/legacy/plugins/lens/migrations.ts | 28 ++++++++++--------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/x-pack/legacy/plugins/lens/migrations.test.ts b/x-pack/legacy/plugins/lens/migrations.test.ts index 90d1cac10416c..7e9dd36bca1f5 100644 --- a/x-pack/legacy/plugins/lens/migrations.test.ts +++ b/x-pack/legacy/plugins/lens/migrations.test.ts @@ -4,13 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { migrations } from './migrations'; +import { migrations, RawLensSavedXYObject770 } from './migrations'; +import { SimpleSavedObject } from 'src/core/public'; describe('Lens migrations', () => { describe('7.7.0 missing dimensions in XY', () => { - const migrate = (doc: unknown) => migrations['7.7.0'](doc); + const migrate = (doc: SimpleSavedObject | RawLensSavedXYObject770) => migrations['7.7.0'](doc); - const example = { + const example: RawLensSavedXYObject770 = { type: 'lens', attributes: { expression: @@ -105,7 +106,7 @@ describe('Lens migrations', () => { visualizationType: 'lnsMetric', }, }; - const result = migrate(target); + const result = migrate(target as SimpleSavedObject); expect(result).toEqual(target); }); @@ -123,7 +124,7 @@ describe('Lens migrations', () => { }, }, }, - }); + } as SimpleSavedObject) as RawLensSavedXYObject770; expect(result.attributes.state.visualization.layers).toEqual([ { @@ -139,7 +140,7 @@ describe('Lens migrations', () => { }); it('should remove only missing accessors', () => { - const result = migrate(example); + const result = migrate(example) as RawLensSavedXYObject770; expect(result.attributes.state.visualization.layers).toEqual([ { diff --git a/x-pack/legacy/plugins/lens/migrations.ts b/x-pack/legacy/plugins/lens/migrations.ts index 30eced3074eba..14b41b3af537c 100644 --- a/x-pack/legacy/plugins/lens/migrations.ts +++ b/x-pack/legacy/plugins/lens/migrations.ts @@ -7,23 +7,25 @@ import { cloneDeep } from 'lodash'; import { SimpleSavedObject } from 'src/core/public'; -interface RawLensSavedXYObject770 { +export interface RawLensSavedXYObject770 { type: 'lens'; - attributes: { + attributes: Record & { visualizationType: string; - state: { - datasourceStates?: { - indexpattern?: { - layers: Record }>; + state: Record & { + datasourceStates?: Record & { + indexpattern?: Record & { + layers: Record & { columns: Record }>; }; }; - visualization: { - layers: Array<{ - layerId: string; - accessors: string[]; - xAccessor: string; - splitAccessor: string; - }>; + visualization: Record & { + layers: Array< + Record & { + layerId: string; + accessors: string[]; + xAccessor: string; + splitAccessor: string; + } + >; }; }; };