[8.0] [maps] fix Dashboards with by-value maps are broken when copied to new space (#125599) (#125887)

* merge with main

* update expect
This commit is contained in:
Nathan Reese 2022-02-16 20:46:05 -07:00 committed by GitHub
parent dd1ed799e4
commit 2c775e32a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 226 additions and 17 deletions

View file

@ -0,0 +1,49 @@
/*
* 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 { extract } from './extract';
test('Should return original state and empty references with by-reference embeddable state', () => {
const mapByReferenceInput = {
id: '2192e502-0ec7-4316-82fb-c9bbf78525c4',
type: 'map',
};
expect(extract!(mapByReferenceInput)).toEqual({
state: mapByReferenceInput,
references: [],
});
});
test('Should update state with refNames with by-value embeddable state', () => {
const mapByValueInput = {
id: '8d62c3f0-c61f-4c09-ac24-9b8ee4320e20',
attributes: {
layerListJSON:
'[{"sourceDescriptor":{"indexPatternId":"90943e30-9a47-11e8-b64d-95841ca0b247"}}]',
},
type: 'map',
};
expect(extract!(mapByValueInput)).toEqual({
references: [
{
id: '90943e30-9a47-11e8-b64d-95841ca0b247',
name: 'layer_0_source_index_pattern',
type: 'index-pattern',
},
],
state: {
id: '8d62c3f0-c61f-4c09-ac24-9b8ee4320e20',
attributes: {
layerListJSON:
'[{"sourceDescriptor":{"indexPatternRefName":"layer_0_source_index_pattern"}}]',
},
type: 'map',
},
});
});

View 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EmbeddableRegistryDefinition } from 'src/plugins/embeddable/server';
import { MapEmbeddablePersistableState } from './types';
import { MapSavedObjectAttributes } from '../map_saved_object_type';
import { extractReferences } from '../migrations/references';
export const extract: EmbeddableRegistryDefinition['extract'] = (state) => {
const typedState = state as MapEmbeddablePersistableState;
// by-reference embeddable
if (!('attributes' in typedState) || typedState.attributes === undefined) {
// No references to extract for by-reference embeddable since all references are stored with by-reference saved object
return { state, references: [] };
}
// by-value embeddable
const { attributes, references } = extractReferences({
attributes: typedState.attributes as MapSavedObjectAttributes,
});
return {
state: {
...state,
attributes,
},
references,
};
};

View file

@ -0,0 +1,9 @@
/*
* 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 { extract } from './extract';
export { inject } from './inject';

View file

@ -0,0 +1,51 @@
/*
* 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 { inject } from './inject';
test('Should return original state with by-reference embeddable state', () => {
const mapByReferenceInput = {
id: '2192e502-0ec7-4316-82fb-c9bbf78525c4',
type: 'map',
};
const refernces = [
{
name: 'panel_2192e502-0ec7-4316-82fb-c9bbf78525c4',
type: 'map',
id: '7f92d7d0-8e5f-11ec-9477-312c8a6de896',
},
];
expect(inject!(mapByReferenceInput, refernces)).toEqual(mapByReferenceInput);
});
test('Should inject refNames with by-value embeddable state', () => {
const mapByValueInput = {
id: '8d62c3f0-c61f-4c09-ac24-9b8ee4320e20',
attributes: {
layerListJSON:
'[{"sourceDescriptor":{"indexPatternRefName":"layer_0_source_index_pattern"}}]',
},
type: 'map',
};
const refernces = [
{
name: 'layer_0_source_index_pattern',
type: 'index-pattern',
id: 'changed_index_pattern_id',
},
];
expect(inject!(mapByValueInput, refernces)).toEqual({
id: '8d62c3f0-c61f-4c09-ac24-9b8ee4320e20',
attributes: {
layerListJSON: '[{"sourceDescriptor":{"indexPatternId":"changed_index_pattern_id"}}]',
},
type: 'map',
});
});

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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EmbeddableRegistryDefinition } from 'src/plugins/embeddable/server';
import { MapEmbeddablePersistableState } from './types';
import { MapSavedObjectAttributes } from '../map_saved_object_type';
import { extractReferences, injectReferences } from '../migrations/references';
export const inject: EmbeddableRegistryDefinition['inject'] = (state, references) => {
const typedState = state as MapEmbeddablePersistableState;
// by-reference embeddable
if (!('attributes' in typedState) || typedState.attributes === undefined) {
return typedState;
}
// by-value embeddable
try {
// run embeddable state through extract logic to ensure any state with hard coded ids is replace with refNames
// refName generation will produce consistent values allowing inject logic to then replace refNames with current ids.
const { attributes: attributesWithNoHardCodedIds } = extractReferences({
attributes: typedState.attributes as MapSavedObjectAttributes,
});
const { attributes: attributesWithInjectedIds } = injectReferences({
attributes: attributesWithNoHardCodedIds,
references,
});
return {
...typedState,
attributes: attributesWithInjectedIds,
};
} catch (error) {
// inject exception prevents entire dashboard from display
// Instead of throwing, swallow error and let dashboard display
// Errors will surface in map panel. Any layer that failed injection will surface the error in the legend
// Users can then manually edit map to resolve any problems.
return typedState;
}
};

View file

@ -0,0 +1,13 @@
/*
* 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 { SerializableRecord } from '@kbn/utility-types';
import { EmbeddableStateWithType } from 'src/plugins/embeddable/common';
export type MapEmbeddablePersistableState = EmbeddableStateWithType & {
attributes: SerializableRecord;
};

View file

@ -6,16 +6,15 @@
*/
import { i18n } from '@kbn/i18n';
import { EmbeddableStateWithType } from 'src/plugins/embeddable/common';
import {
EmbeddableFactoryDefinition,
IContainer,
} from '../../../../../src/plugins/embeddable/public';
import { MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants';
import { getMapEmbeddableDisplayName } from '../../common/i18n_getters';
import { MapByReferenceInput, MapEmbeddableInput, MapByValueInput } from './types';
import { extract, inject } from '../../common/embeddable';
import { MapByReferenceInput, MapEmbeddableInput } from './types';
import { lazyLoadMapModules } from '../lazy_load_bundle';
import { extractReferences } from '../../common/migrations/references';
export class MapEmbeddableFactory implements EmbeddableFactoryDefinition {
type = MAP_SAVED_OBJECT_TYPE;
@ -63,17 +62,7 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition {
);
};
extract(state: EmbeddableStateWithType) {
const maybeMapByValueInput = state as EmbeddableStateWithType | MapByValueInput;
inject = inject;
if ((maybeMapByValueInput as MapByValueInput).attributes !== undefined) {
const { references } = extractReferences({
attributes: (maybeMapByValueInput as MapByValueInput).attributes,
});
return { state, references };
}
return { state, references: [] };
}
extract = extract;
}

View file

@ -15,7 +15,10 @@ describe('saved object migrations and embeddable migrations', () => {
const savedObjectMigrationVersions = Object.keys(savedObjectMigrations).filter((version) => {
return semverGte(version, '7.13.0');
});
const embeddableMigrationVersions = Object.keys(embeddableMigrations);
const embeddableMigrationVersions = Object.keys(embeddableMigrations).filter((key) => {
// filter out embeddable only migration keys
return !['8.0.1'].includes(key);
});
expect(savedObjectMigrationVersions.sort()).toEqual(embeddableMigrationVersions.sort());
});
});

View file

@ -9,6 +9,7 @@ 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 { extractReferences } from '../common/migrations/references';
/*
* Embeddables such as Maps, Lens, and Visualize can be embedded by value or by reference on a dashboard.
@ -42,4 +43,17 @@ export const embeddableMigrations = {
return state;
}
},
'8.0.1': (state: SerializableRecord) => {
try {
const { attributes } = extractReferences(state as { attributes: MapSavedObjectAttributes });
return {
...state,
attributes,
} as SerializableRecord;
} catch (e) {
// Do not fail migration
// Maps application can display error when viewed
return state;
}
},
};

View file

@ -23,6 +23,7 @@ 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';
// @ts-ignore
@ -225,6 +226,8 @@ export class MapsPlugin implements Plugin {
plugins.embeddable.registerEmbeddableFactory({
id: MAP_SAVED_OBJECT_TYPE,
migrations: embeddableMigrations,
inject,
extract,
});
return {

View file

@ -90,7 +90,7 @@ export default function ({ getService }) {
}
expect(panels.length).to.be(1);
expect(panels[0].type).to.be('map');
expect(panels[0].version).to.be('8.0.0');
expect(panels[0].version).to.be('8.0.1');
});
});
});