[maps] fix map application is broken if you open a map with layers or sources that do not exist (#176419)

Fixes https://github.com/elastic/kibana/issues/176318

PR adds try/catch blocks around all usages of `createInstanceError`.

### Test steps
1) start kibana with `yarn start --run-examples`
2) create new map, add new layer `Weather data provided by NOAA`
3) save map
4) re-start kibana with `yarn start`
5) open map saved in step 3. Map should open and layer shoould display
error in legend
<img width="500" alt="Screenshot 2024-02-07 at 8 13 01 PM"
src="a70e7ee9-fe30-499a-add5-8686fb591ce6">

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2024-02-08 10:58:55 -07:00 committed by GitHub
parent 2d15f36edf
commit d404ea46e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 164 additions and 56 deletions

View file

@ -0,0 +1,89 @@
/*
* 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.
*/
/* eslint-disable max-classes-per-file */
import { i18n } from '@kbn/i18n';
import { LayerDescriptor } from '../../../common/descriptor_types';
import { AbstractLayer } from './layer';
import { AbstractSource } from '../sources/source';
import { IStyle } from '../styles/style';
class InvalidSource extends AbstractSource {
constructor(id?: string) {
super({
id,
type: 'INVALID',
});
}
}
export class InvalidLayer extends AbstractLayer {
private readonly _error: Error;
private readonly _style: IStyle;
constructor(layerDescriptor: LayerDescriptor, error: Error) {
super({
layerDescriptor,
source: new InvalidSource(layerDescriptor.sourceDescriptor?.id),
});
this._error = error;
this._style = {
getType() {
return 'INVALID';
},
renderEditor() {
return null;
},
};
}
hasErrors() {
return true;
}
getErrors() {
return [
{
title: i18n.translate('xpack.maps.invalidLayer.errorTitle', {
defaultMessage: `Unable to create layer`,
}),
body: this._error.message,
},
];
}
getStyleForEditing() {
return this._style;
}
getStyle() {
return this._style;
}
getCurrentStyle() {
return this._style;
}
getMbLayerIds() {
return [];
}
ownsMbLayerId() {
return false;
}
ownsMbSourceId() {
return false;
}
syncLayerWithMB() {}
getLayerTypeIconName() {
return 'error';
}
}

View file

@ -245,7 +245,7 @@ export class AbstractLayer implements ILayer {
const sourceDisplayName = source
? await source.getDisplayName()
: await this.getSource().getDisplayName();
return sourceDisplayName || `Layer ${this._descriptor.id}`;
return sourceDisplayName || this._descriptor.id;
}
async getAttributions(): Promise<Attribution[]> {

View file

@ -23,6 +23,7 @@ import {
import { VectorStyle } from '../classes/styles/vector/vector_style';
import { isLayerGroup, LayerGroup } from '../classes/layers/layer_group';
import { HeatmapLayer } from '../classes/layers/heatmap_layer';
import { InvalidLayer } from '../classes/layers/invalid_layer';
import { getTimeFilter } from '../kibana_services';
import { getChartsPaletteServiceGetColor } from '../reducers/non_serializable_instances';
import { copyPersistentState, TRACKED_LAYER_DESCRIPTOR } from '../reducers/copy_persistent_state';
@ -76,54 +77,58 @@ export function createLayerInstance(
customIcons: CustomIcon[],
chartsPaletteServiceGetColor?: (value: string) => string | null
): ILayer {
if (layerDescriptor.type === LAYER_TYPE.LAYER_GROUP) {
return new LayerGroup({ layerDescriptor: layerDescriptor as LayerGroupDescriptor });
}
try {
if (layerDescriptor.type === LAYER_TYPE.LAYER_GROUP) {
return new LayerGroup({ layerDescriptor: layerDescriptor as LayerGroupDescriptor });
}
const source: ISource = createSourceInstance(layerDescriptor.sourceDescriptor);
switch (layerDescriptor.type) {
case LAYER_TYPE.RASTER_TILE:
return new RasterTileLayer({ layerDescriptor, source: source as IRasterSource });
case LAYER_TYPE.EMS_VECTOR_TILE:
return new EmsVectorTileLayer({
layerDescriptor: layerDescriptor as EMSVectorTileLayerDescriptor,
source: source as EMSTMSSource,
});
case LAYER_TYPE.HEATMAP:
return new HeatmapLayer({
layerDescriptor: layerDescriptor as HeatmapLayerDescriptor,
source: source as ESGeoGridSource,
});
case LAYER_TYPE.GEOJSON_VECTOR:
return new GeoJsonVectorLayer({
layerDescriptor: layerDescriptor as VectorLayerDescriptor,
source: source as IVectorSource,
joins: createJoinInstances(
layerDescriptor as VectorLayerDescriptor,
source as IVectorSource
),
customIcons,
chartsPaletteServiceGetColor,
});
case LAYER_TYPE.BLENDED_VECTOR:
return new BlendedVectorLayer({
layerDescriptor: layerDescriptor as VectorLayerDescriptor,
source: source as IVectorSource,
customIcons,
chartsPaletteServiceGetColor,
});
case LAYER_TYPE.MVT_VECTOR:
return new MvtVectorLayer({
layerDescriptor: layerDescriptor as VectorLayerDescriptor,
source: source as IVectorSource,
joins: createJoinInstances(
layerDescriptor as VectorLayerDescriptor,
source as IVectorSource
),
customIcons,
});
default:
throw new Error(`Unrecognized layerType ${layerDescriptor.type}`);
const source: ISource = createSourceInstance(layerDescriptor.sourceDescriptor);
switch (layerDescriptor.type) {
case LAYER_TYPE.RASTER_TILE:
return new RasterTileLayer({ layerDescriptor, source: source as IRasterSource });
case LAYER_TYPE.EMS_VECTOR_TILE:
return new EmsVectorTileLayer({
layerDescriptor: layerDescriptor as EMSVectorTileLayerDescriptor,
source: source as EMSTMSSource,
});
case LAYER_TYPE.HEATMAP:
return new HeatmapLayer({
layerDescriptor: layerDescriptor as HeatmapLayerDescriptor,
source: source as ESGeoGridSource,
});
case LAYER_TYPE.GEOJSON_VECTOR:
return new GeoJsonVectorLayer({
layerDescriptor: layerDescriptor as VectorLayerDescriptor,
source: source as IVectorSource,
joins: createJoinInstances(
layerDescriptor as VectorLayerDescriptor,
source as IVectorSource
),
customIcons,
chartsPaletteServiceGetColor,
});
case LAYER_TYPE.BLENDED_VECTOR:
return new BlendedVectorLayer({
layerDescriptor: layerDescriptor as VectorLayerDescriptor,
source: source as IVectorSource,
customIcons,
chartsPaletteServiceGetColor,
});
case LAYER_TYPE.MVT_VECTOR:
return new MvtVectorLayer({
layerDescriptor: layerDescriptor as VectorLayerDescriptor,
source: source as IVectorSource,
joins: createJoinInstances(
layerDescriptor as VectorLayerDescriptor,
source as IVectorSource
),
customIcons,
});
default:
throw new Error(`Unrecognized layerType ${layerDescriptor.type}`);
}
} catch (error) {
return new InvalidLayer(layerDescriptor, error);
}
}

View file

@ -60,7 +60,7 @@ export default function ({ getService }: FtrProviderContext) {
es_point_to_point: { min: 1, max: 1, total: 1, avg: 0.03571428571428571 },
es_top_hits: { min: 1, max: 1, total: 2, avg: 0.07142857142857142 },
es_agg_heatmap: { min: 1, max: 1, total: 1, avg: 0.03571428571428571 },
esql: { min: 1, max: 1, total: 1, avg: 0.03571428571428571 },
esql: { min: 1, max: 1, total: 2, avg: 0.07142857142857142 },
kbn_tms_raster: { min: 1, max: 1, total: 1, avg: 0.03571428571428571 },
ems_basemap: { min: 1, max: 1, total: 1, avg: 0.03571428571428571 },
ems_region: { min: 1, max: 1, total: 1, avg: 0.03571428571428571 },
@ -81,8 +81,8 @@ export default function ({ getService }: FtrProviderContext) {
min: 0,
},
dataSourcesCount: {
avg: 1.1785714285714286,
max: 6,
avg: 1.2142857142857142,
max: 7,
min: 1,
},
emsVectorLayersCount: {
@ -104,8 +104,8 @@ export default function ({ getService }: FtrProviderContext) {
min: 1,
},
GEOJSON_VECTOR: {
avg: 0.8214285714285714,
max: 5,
avg: 0.8571428571428571,
max: 6,
min: 1,
},
HEATMAP: {
@ -125,8 +125,8 @@ export default function ({ getService }: FtrProviderContext) {
},
},
layersCount: {
avg: 1.2142857142857142,
max: 7,
avg: 1.25,
max: 8,
min: 1,
},
},

View file

@ -18,6 +18,20 @@ export default function ({ getPageObjects, getService }) {
await PageObjects.maps.loadSavedMap('layer with errors');
});
describe('Layer with invalid descriptor', () => {
const INVALID_LAYER_NAME = 'fff76ebb-57a6-4067-a373-1d191b9bd1a3';
it('should diplay error icon in legend', async () => {
await PageObjects.maps.hasErrorIconExistsOrFail(INVALID_LAYER_NAME);
});
it('should allow deletion of layer', async () => {
await PageObjects.maps.removeLayer(INVALID_LAYER_NAME);
const exists = await PageObjects.maps.doesLayerExist(INVALID_LAYER_NAME);
expect(exists).to.be(false);
});
});
describe('Layer with EsError', () => {
after(async () => {
await inspector.close();

File diff suppressed because one or more lines are too long