[maps] fix geojson layer with joins and no left source matches stuck in loading state (#160222)

closes https://github.com/elastic/kibana/issues/156630

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2023-06-26 09:27:28 -06:00 committed by GitHub
parent a23f0b27d1
commit c9474270d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 194 additions and 9 deletions

View file

@ -0,0 +1,166 @@
/*
* 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 { SOURCE_DATA_REQUEST_ID } from '../../../../../common/constants';
import { VectorLayerDescriptor } from '../../../../../common/descriptor_types';
import { InnerJoin } from '../../../joins/inner_join';
import { IJoinSource } from '../../../sources/join_sources';
import { IVectorSource } from '../../../sources/vector_source';
import { GeoJsonVectorLayer } from './geojson_vector_layer';
const joinDataRequestId = 'join_source_a0b0da65-5e1a-4967-9dbe-74f24391afe2';
const mockJoin = {
hasCompleteConfig: () => {
return true;
},
getSourceDataRequestId: () => {
return joinDataRequestId;
},
getRightJoinSource: () => {
return {} as unknown as IJoinSource;
},
} as unknown as InnerJoin;
describe('isLayerLoading', () => {
test('should return true when source loading has not started', () => {
const layer = new GeoJsonVectorLayer({
customIcons: [],
layerDescriptor: {},
source: {} as unknown as IVectorSource,
});
expect(layer.isLayerLoading(1)).toBe(true);
});
test('Should return true when source data request is pending', () => {
const layer = new GeoJsonVectorLayer({
customIcons: [],
layerDescriptor: {
__dataRequests: [
{
data: {},
dataId: SOURCE_DATA_REQUEST_ID,
dataRequestMetaAtStart: {},
dataRequestToken: Symbol(),
},
],
},
source: {} as unknown as IVectorSource,
});
expect(layer.isLayerLoading(1)).toBe(true);
});
describe('no joins', () => {
test('Should return false when source data request is finished', () => {
const layer = new GeoJsonVectorLayer({
customIcons: [],
layerDescriptor: {
__dataRequests: [
{
data: {},
dataId: SOURCE_DATA_REQUEST_ID,
dataRequestMeta: {},
dataRequestMetaAtStart: undefined,
dataRequestToken: undefined,
},
],
},
source: {} as unknown as IVectorSource,
});
expect(layer.isLayerLoading(1)).toBe(false);
});
});
describe('joins', () => {
describe('source data loaded with no features', () => {
test('should return false when join loading has not started', () => {
const layer = new GeoJsonVectorLayer({
customIcons: [],
joins: [mockJoin],
layerDescriptor: {
__dataRequests: [
{
data: {
features: [],
},
dataId: SOURCE_DATA_REQUEST_ID,
dataRequestMeta: {},
dataRequestMetaAtStart: undefined,
dataRequestToken: undefined,
},
],
} as unknown as VectorLayerDescriptor,
source: {} as unknown as IVectorSource,
});
expect(layer.isLayerLoading(1)).toBe(false);
});
});
describe('source data loaded with features', () => {
const sourceDataRequestDescriptorWithFeatures = {
data: {
features: [{}],
},
dataId: SOURCE_DATA_REQUEST_ID,
dataRequestMeta: {},
dataRequestMetaAtStart: undefined,
dataRequestToken: undefined,
};
test('should return true when join loading has not started', () => {
const layer = new GeoJsonVectorLayer({
customIcons: [],
joins: [mockJoin],
layerDescriptor: {
__dataRequests: [sourceDataRequestDescriptorWithFeatures],
} as unknown as VectorLayerDescriptor,
source: {} as unknown as IVectorSource,
});
expect(layer.isLayerLoading(1)).toBe(true);
});
test('should return true when join data request is pending', () => {
const layer = new GeoJsonVectorLayer({
customIcons: [],
joins: [mockJoin],
layerDescriptor: {
__dataRequests: [
sourceDataRequestDescriptorWithFeatures,
{
dataId: joinDataRequestId,
dataRequestMetaAtStart: {},
dataRequestToken: Symbol(),
},
],
} as unknown as VectorLayerDescriptor,
source: {} as unknown as IVectorSource,
});
expect(layer.isLayerLoading(1)).toBe(true);
});
test('should return false when join data request is finished', () => {
const layer = new GeoJsonVectorLayer({
customIcons: [],
joins: [mockJoin],
layerDescriptor: {
__dataRequests: [
sourceDataRequestDescriptorWithFeatures,
{
data: {},
dataId: joinDataRequestId,
dataRequestMeta: {},
dataRequestMetaAtStart: undefined,
dataRequestToken: undefined,
},
],
} as unknown as VectorLayerDescriptor,
source: {} as unknown as IVectorSource,
});
expect(layer.isLayerLoading(1)).toBe(false);
});
});
});
});

View file

@ -60,6 +60,23 @@ export class GeoJsonVectorLayer extends AbstractVectorLayer {
return layerDescriptor;
}
isLayerLoading(zoom: number) {
const isSourceLoading = super.isLayerLoading(zoom);
if (isSourceLoading) {
return true;
}
// Do not check join loading status when there are no source features. Why?
// syncMeta short circuits join loading when there are no source features
// because there is no reason to fetch join results when there is nothing to join with
const featureCollection = this._getSourceFeatureCollection();
if (!featureCollection || featureCollection?.features?.length === 0) {
return false;
}
return this._isLoadingJoins();
}
_isTiled(): boolean {
// Uses untiled maplibre source 'geojson'
return false;

View file

@ -74,6 +74,11 @@ export class MvtVectorLayer extends AbstractVectorLayer {
this._source = args.source as IMvtVectorSource;
}
isLayerLoading(zoom: number) {
const isSourceLoading = super.isLayerLoading(zoom);
return isSourceLoading ? true : this._isLoadingJoins();
}
_isTiled(): boolean {
// Uses tiled maplibre source 'vector'
return true;

View file

@ -259,12 +259,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer {
return this.getValidJoins().length > 0;
}
isLayerLoading(zoom: number) {
const isSourceLoading = super.isLayerLoading(zoom);
if (isSourceLoading) {
return true;
}
_isLoadingJoins() {
return this.getValidJoins().some((join) => {
const joinDataRequest = this.getDataRequest(join.getSourceDataRequestId());
return !joinDataRequest || joinDataRequest.isLoading();

View file

@ -106,7 +106,8 @@ export function GeoIndexPatternSelect(props: Props) {
}
const IndexPatternSelect = getIndexPatternSelectComponent();
const error = !hasGeoFields
const isDataViewInvalid = !props.dataView ? false : !hasGeoFields;
const error = isDataViewInvalid
? i18n.translate('xpack.maps.noGeoFieldInIndexPattern.message', {
defaultMessage: 'Data view does not contain any geospatial fields',
})
@ -116,9 +117,9 @@ export function GeoIndexPatternSelect(props: Props) {
<>
{_renderNoIndexPatternWarning()}
<EuiFormRow label={getDataViewLabel()} isInvalid={!hasGeoFields} error={error}>
<EuiFormRow label={getDataViewLabel()} isInvalid={isDataViewInvalid} error={error}>
<IndexPatternSelect
isInvalid={!hasGeoFields}
isInvalid={isDataViewInvalid}
isDisabled={noDataViews}
indexPatternId={props.dataView?.id ? props.dataView.id : ''}
onChange={_onIndexPatternSelect}

View file

@ -118,6 +118,7 @@ export class AddLayerPanel extends Component<Props, State> {
disabled={isDisabled || isLoading}
isLoading={isLoading}
onClick={addLayersAndClose}
iconSide="right"
>
{ADD_LAYER_STEP_SECONDARY_ACTION_BUTTON_LABEL}
</EuiButton>