From 15846b27c01498242ae4b8934aafcd49b3e6580c Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 22 Sep 2021 12:47:35 -0600 Subject: [PATCH] [Maps] move joins from LayerDescriptor to VectorLayerDescriptor (#112427) * [Maps] move joins from LayerDescriptor to VectorLayerDescriptor * clean up ISource * export isVectorLayer * tslint * tslint for ml plugin * tslint apm plugin Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../VisitorBreakdownMap/useLayerList.ts | 5 +- .../choropleth_map.tsx | 3 +- .../layer_descriptor_types.ts | 5 +- x-pack/plugins/maps/common/index.ts | 1 + .../common/migrations/add_type_to_termjoin.ts | 11 +- .../maps/common/migrations/references.ts | 58 ++++---- .../blended_vector_layer.ts | 5 +- .../maps/public/classes/layers/layer.test.ts | 115 --------------- .../maps/public/classes/layers/layer.tsx | 68 --------- .../tiled_vector_layer/tiled_vector_layer.tsx | 2 +- .../classes/layers/vector_layer/index.ts | 1 + .../layers/vector_layer/vector_layer.test.tsx | 136 ++++++++++++++++++ .../layers/vector_layer/vector_layer.tsx | 85 ++++++++++- .../mvt_single_layer_vector_source.tsx | 8 ++ .../maps/public/classes/sources/source.ts | 10 -- .../sources/vector_source/vector_source.tsx | 6 + .../edit_layer_panel.test.tsx.snap | 2 + .../edit_layer_panel.test.tsx | 3 + .../edit_layer_panel/edit_layer_panel.tsx | 21 +-- .../edit_layer_panel/index.ts | 9 +- .../join_editor/join_editor.test.tsx | 6 +- .../join_editor/join_editor.tsx | 6 +- .../tooltip_control/tooltip_control.tsx | 6 +- .../maps/server/maps_telemetry/util.ts | 4 +- .../application/explorer/anomalies_map.tsx | 3 +- 25 files changed, 321 insertions(+), 258 deletions(-) create mode 100644 x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts index f1b5b67da21f1..ce42b530b80f5 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts @@ -15,6 +15,7 @@ import { COLOR_MAP_TYPE, FIELD_ORIGIN, LABEL_BORDER_SIZES, + LAYER_TYPE, SOURCE_TYPES, STYLE_TYPE, SYMBOLIZE_AS_TYPES, @@ -154,7 +155,7 @@ export function useLayerList() { maxZoom: 24, alpha: 0.75, visible: true, - type: 'VECTOR', + type: LAYER_TYPE.VECTOR, }; ES_TERM_SOURCE_REGION.whereQuery = getWhereQuery(serviceName!); @@ -178,7 +179,7 @@ export function useLayerList() { maxZoom: 24, alpha: 0.75, visible: true, - type: 'VECTOR', + type: LAYER_TYPE.VECTOR, }; return [ diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx index c2e1e0b7dffa9..318ff655abb21 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx @@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { FIELD_ORIGIN, + LAYER_TYPE, SOURCE_TYPES, STYLE_TYPE, COLOR_MAP_TYPE, @@ -85,7 +86,7 @@ export const getChoroplethTopValuesLayer = ( }, isTimeAware: true, }, - type: 'VECTOR', + type: LAYER_TYPE.VECTOR, }; }; diff --git a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts index 740da8493c53c..244ebc59efd17 100644 --- a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts @@ -23,6 +23,7 @@ import { KBN_IS_TILE_COMPLETE, KBN_METADATA_FEATURE, KBN_VECTOR_SHAPE_TYPE_COUNTS, + LAYER_TYPE, } from '../constants'; export type Attribution = { @@ -56,7 +57,6 @@ export type LayerDescriptor = { alpha?: number; attribution?: Attribution; id: string; - joins?: JoinDescriptor[]; label?: string | null; areLabelsOnTop?: boolean; minZoom?: number; @@ -70,9 +70,12 @@ export type LayerDescriptor = { }; export type VectorLayerDescriptor = LayerDescriptor & { + type: LAYER_TYPE.VECTOR | LAYER_TYPE.TILED_VECTOR | LAYER_TYPE.BLENDED_VECTOR; + joins?: JoinDescriptor[]; style: VectorStyleDescriptor; }; export type HeatmapLayerDescriptor = LayerDescriptor & { + type: LAYER_TYPE.HEATMAP; style: HeatmapStyleDescriptor; }; diff --git a/x-pack/plugins/maps/common/index.ts b/x-pack/plugins/maps/common/index.ts index c1b5d26fca292..8374a4d0dbaa3 100644 --- a/x-pack/plugins/maps/common/index.ts +++ b/x-pack/plugins/maps/common/index.ts @@ -12,6 +12,7 @@ export { FIELD_ORIGIN, INITIAL_LOCATION, LABEL_BORDER_SIZES, + LAYER_TYPE, MAP_SAVED_OBJECT_TYPE, SOURCE_TYPES, STYLE_TYPE, diff --git a/x-pack/plugins/maps/common/migrations/add_type_to_termjoin.ts b/x-pack/plugins/maps/common/migrations/add_type_to_termjoin.ts index d40d85f9b6192..e46bf6a1a6e7f 100644 --- a/x-pack/plugins/maps/common/migrations/add_type_to_termjoin.ts +++ b/x-pack/plugins/maps/common/migrations/add_type_to_termjoin.ts @@ -6,8 +6,8 @@ */ import { MapSavedObjectAttributes } from '../map_saved_object_type'; -import { JoinDescriptor, LayerDescriptor } from '../descriptor_types'; -import { LAYER_TYPE, SOURCE_TYPES } from '../constants'; +import { JoinDescriptor, LayerDescriptor, VectorLayerDescriptor } from '../descriptor_types'; +import { SOURCE_TYPES } from '../constants'; // enforce type property on joins. It's possible older saved-objects do not have this correctly filled in // e.g. sample-data was missing the right.type field. @@ -24,14 +24,15 @@ export function addTypeToTermJoin({ const layerList: LayerDescriptor[] = JSON.parse(attributes.layerListJSON); layerList.forEach((layer: LayerDescriptor) => { - if (layer.type !== LAYER_TYPE.VECTOR) { + if (!('joins' in layer)) { return; } - if (!layer.joins) { + const vectorLayer = layer as VectorLayerDescriptor; + if (!vectorLayer.joins) { return; } - layer.joins.forEach((join: JoinDescriptor) => { + vectorLayer.joins.forEach((join: JoinDescriptor) => { if (!join.right) { return; } diff --git a/x-pack/plugins/maps/common/migrations/references.ts b/x-pack/plugins/maps/common/migrations/references.ts index d48be6bd56fbe..41d9dc063fe47 100644 --- a/x-pack/plugins/maps/common/migrations/references.ts +++ b/x-pack/plugins/maps/common/migrations/references.ts @@ -9,7 +9,7 @@ import { SavedObjectReference } from '../../../../../src/core/types'; import { MapSavedObjectAttributes } from '../map_saved_object_type'; -import { LayerDescriptor } from '../descriptor_types'; +import { LayerDescriptor, VectorLayerDescriptor } from '../descriptor_types'; interface IndexPatternReferenceDescriptor { indexPatternId?: string; @@ -44,21 +44,24 @@ export function extractReferences({ sourceDescriptor.indexPatternRefName = refName; } - // Extract index-pattern references from join - const joins = layer.joins ? layer.joins : []; - joins.forEach((join, joinIndex) => { - if ('indexPatternId' in join.right) { - const sourceDescriptor = join.right as IndexPatternReferenceDescriptor; - const refName = `layer_${layerIndex}_join_${joinIndex}_index_pattern`; - extractedReferences.push({ - name: refName, - type: 'index-pattern', - id: sourceDescriptor.indexPatternId!, - }); - delete sourceDescriptor.indexPatternId; - sourceDescriptor.indexPatternRefName = refName; - } - }); + if ('joins' in layer) { + // Extract index-pattern references from join + const vectorLayer = layer as VectorLayerDescriptor; + const joins = vectorLayer.joins ? vectorLayer.joins : []; + joins.forEach((join, joinIndex) => { + if ('indexPatternId' in join.right) { + const sourceDescriptor = join.right as IndexPatternReferenceDescriptor; + const refName = `layer_${layerIndex}_join_${joinIndex}_index_pattern`; + extractedReferences.push({ + name: refName, + type: 'index-pattern', + id: sourceDescriptor.indexPatternId!, + }); + delete sourceDescriptor.indexPatternId; + sourceDescriptor.indexPatternRefName = refName; + } + }); + } }); return { @@ -99,16 +102,19 @@ export function injectReferences({ delete sourceDescriptor.indexPatternRefName; } - // Inject index-pattern references into join - const joins = layer.joins ? layer.joins : []; - joins.forEach((join) => { - if ('indexPatternRefName' in join.right) { - const sourceDescriptor = join.right as IndexPatternReferenceDescriptor; - const reference = findReference(sourceDescriptor.indexPatternRefName!, references); - sourceDescriptor.indexPatternId = reference.id; - delete sourceDescriptor.indexPatternRefName; - } - }); + if ('joins' in layer) { + // Inject index-pattern references into join + const vectorLayer = layer as VectorLayerDescriptor; + const joins = vectorLayer.joins ? vectorLayer.joins : []; + joins.forEach((join) => { + if ('indexPatternRefName' in join.right) { + const sourceDescriptor = join.right as IndexPatternReferenceDescriptor; + const reference = findReference(sourceDescriptor.indexPatternRefName!, references); + sourceDescriptor.indexPatternId = reference.id; + delete sourceDescriptor.indexPatternRefName; + } + }); + } }); return { diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts index d2734265f3bc3..a158892be9d09 100644 --- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts @@ -33,7 +33,6 @@ import { SizeDynamicOptions, DynamicStylePropertyOptions, StylePropertyOptions, - LayerDescriptor, Timeslice, VectorLayerDescriptor, VectorSourceRequestMeta, @@ -179,7 +178,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { mapColors: string[] ): VectorLayerDescriptor { const layerDescriptor = VectorLayer.createDescriptor(options, mapColors); - layerDescriptor.type = BlendedVectorLayer.type; + layerDescriptor.type = LAYER_TYPE.BLENDED_VECTOR; return layerDescriptor; } @@ -256,7 +255,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { return false; } - async cloneDescriptor(): Promise { + async cloneDescriptor(): Promise { const clonedDescriptor = await super.cloneDescriptor(); // Use super getDisplayName instead of instance getDisplayName to avoid getting 'Clustered Clone of Clustered' diff --git a/x-pack/plugins/maps/public/classes/layers/layer.test.ts b/x-pack/plugins/maps/public/classes/layers/layer.test.ts index 83a936f377c7f..194b41680872c 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/layer.test.ts @@ -9,21 +9,6 @@ import { AbstractLayer } from './layer'; import { ISource } from '../sources/source'; -import { - AGG_TYPE, - FIELD_ORIGIN, - LAYER_STYLE_TYPE, - SOURCE_TYPES, - VECTOR_STYLES, -} from '../../../common/constants'; -import { ESTermSourceDescriptor, VectorStyleDescriptor } from '../../../common/descriptor_types'; -import { getDefaultDynamicProperties } from '../styles/vector/vector_style_defaults'; - -jest.mock('uuid/v4', () => { - return function () { - return '12345'; - }; -}); class MockLayer extends AbstractLayer {} @@ -36,111 +21,11 @@ class MockSource { return {}; } - getDisplayName() { - return 'mySource'; - } - async supportsFitToBounds() { return this._fitToBounds; } } -describe('cloneDescriptor', () => { - describe('with joins', () => { - const styleDescriptor = { - type: LAYER_STYLE_TYPE.VECTOR, - properties: { - ...getDefaultDynamicProperties(), - }, - } as VectorStyleDescriptor; - // @ts-expect-error - styleDescriptor.properties[VECTOR_STYLES.FILL_COLOR].options.field = { - name: '__kbnjoin__count__557d0f15', - origin: FIELD_ORIGIN.JOIN, - }; - // @ts-expect-error - styleDescriptor.properties[VECTOR_STYLES.LINE_COLOR].options.field = { - name: 'bytes', - origin: FIELD_ORIGIN.SOURCE, - }; - // @ts-expect-error - styleDescriptor.properties[VECTOR_STYLES.LABEL_BORDER_COLOR].options.field = { - name: '__kbnjoin__count__6666666666', - origin: FIELD_ORIGIN.JOIN, - }; - - test('Should update data driven styling properties using join fields', async () => { - const layerDescriptor = AbstractLayer.createDescriptor({ - style: styleDescriptor, - joins: [ - { - leftField: 'iso2', - right: { - id: '557d0f15', - indexPatternId: 'myIndexPattern', - indexPatternTitle: 'logs-*', - metrics: [{ type: AGG_TYPE.COUNT }], - term: 'myTermField', - type: SOURCE_TYPES.ES_TERM_SOURCE, - applyGlobalQuery: true, - applyGlobalTime: true, - applyForceRefresh: true, - }, - }, - ], - }); - const layer = new MockLayer({ - layerDescriptor, - source: new MockSource() as unknown as ISource, - }); - const clonedDescriptor = await layer.cloneDescriptor(); - const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties; - // Should update style field belonging to join - // @ts-expect-error - expect(clonedStyleProps[VECTOR_STYLES.FILL_COLOR].options.field.name).toEqual( - '__kbnjoin__count__12345' - ); - // Should not update style field belonging to source - // @ts-expect-error - expect(clonedStyleProps[VECTOR_STYLES.LINE_COLOR].options.field.name).toEqual('bytes'); - // Should not update style feild belonging to different join - // @ts-expect-error - expect(clonedStyleProps[VECTOR_STYLES.LABEL_BORDER_COLOR].options.field.name).toEqual( - '__kbnjoin__count__6666666666' - ); - }); - - test('Should update data driven styling properties using join fields when metrics are not provided', async () => { - const layerDescriptor = AbstractLayer.createDescriptor({ - style: styleDescriptor, - joins: [ - { - leftField: 'iso2', - right: { - id: '557d0f15', - indexPatternId: 'myIndexPattern', - indexPatternTitle: 'logs-*', - term: 'myTermField', - type: 'joinSource', - } as unknown as ESTermSourceDescriptor, - }, - ], - }); - const layer = new MockLayer({ - layerDescriptor, - source: new MockSource() as unknown as ISource, - }); - const clonedDescriptor = await layer.cloneDescriptor(); - const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties; - // Should update style field belonging to join - // @ts-expect-error - expect(clonedStyleProps[VECTOR_STYLES.FILL_COLOR].options.field.name).toEqual( - '__kbnjoin__count__12345' - ); - }); - }); -}); - describe('isFittable', () => { [ { diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index 0700e54a3fe87..e1043a33f28ad 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -16,23 +16,16 @@ import uuid from 'uuid/v4'; import { FeatureCollection } from 'geojson'; import { DataRequest } from '../util/data_request'; import { - AGG_TYPE, - FIELD_ORIGIN, LAYER_TYPE, MAX_ZOOM, MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER, MIN_ZOOM, SOURCE_BOUNDS_DATA_REQUEST_ID, SOURCE_DATA_REQUEST_ID, - SOURCE_TYPES, - STYLE_TYPE, } from '../../../common/constants'; import { copyPersistentState } from '../../reducers/copy_persistent_state'; import { - AggDescriptor, Attribution, - ESTermSourceDescriptor, - JoinDescriptor, LayerDescriptor, MapExtent, StyleDescriptor, @@ -42,7 +35,6 @@ import { import { ImmutableSourceProperty, ISource, SourceEditorArgs } from '../sources/source'; import { DataRequestContext } from '../../actions'; import { IStyle } from '../styles/style'; -import { getJoinAggKey } from '../../../common/get_agg_key'; import { LICENSED_FEATURES } from '../../licensed_features'; import { IESSource } from '../sources/es_source'; @@ -97,8 +89,6 @@ export interface ILayer { isPreviewLayer: () => boolean; areLabelsOnTop: () => boolean; supportsLabelsOnTop: () => boolean; - showJoinEditor(): boolean; - getJoinsDisabledReason(): string | null; isFittable(): Promise; isIncludeInFitToBounds(): boolean; getLicensedFeatures(): Promise; @@ -177,56 +167,6 @@ export class AbstractLayer implements ILayer { const displayName = await this.getDisplayName(); clonedDescriptor.label = `Clone of ${displayName}`; clonedDescriptor.sourceDescriptor = this.getSource().cloneDescriptor(); - - if (clonedDescriptor.joins) { - clonedDescriptor.joins.forEach((joinDescriptor: JoinDescriptor) => { - if (joinDescriptor.right && joinDescriptor.right.type === SOURCE_TYPES.TABLE_SOURCE) { - throw new Error( - 'Cannot clone table-source. Should only be used in MapEmbeddable, not in UX' - ); - } - const termSourceDescriptor: ESTermSourceDescriptor = - joinDescriptor.right as ESTermSourceDescriptor; - - // todo: must tie this to generic thing - const originalJoinId = joinDescriptor.right.id!; - - // right.id is uuid used to track requests in inspector - joinDescriptor.right.id = uuid(); - - // Update all data driven styling properties using join fields - if (clonedDescriptor.style && 'properties' in clonedDescriptor.style) { - const metrics = - termSourceDescriptor.metrics && termSourceDescriptor.metrics.length - ? termSourceDescriptor.metrics - : [{ type: AGG_TYPE.COUNT }]; - metrics.forEach((metricsDescriptor: AggDescriptor) => { - const originalJoinKey = getJoinAggKey({ - aggType: metricsDescriptor.type, - aggFieldName: 'field' in metricsDescriptor ? metricsDescriptor.field : '', - rightSourceId: originalJoinId, - }); - const newJoinKey = getJoinAggKey({ - aggType: metricsDescriptor.type, - aggFieldName: 'field' in metricsDescriptor ? metricsDescriptor.field : '', - rightSourceId: joinDescriptor.right.id!, - }); - - Object.keys(clonedDescriptor.style.properties).forEach((key) => { - const styleProp = clonedDescriptor.style.properties[key]; - if ( - styleProp.type === STYLE_TYPE.DYNAMIC && - styleProp.options.field && - styleProp.options.field.origin === FIELD_ORIGIN.JOIN && - styleProp.options.field.name === originalJoinKey - ) { - styleProp.options.field.name = newJoinKey; - } - }); - }); - } - }); - } return clonedDescriptor; } @@ -234,14 +174,6 @@ export class AbstractLayer implements ILayer { return `${this.getId()}${MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER}${layerNameSuffix}`; } - showJoinEditor(): boolean { - return this.getSource().showJoinEditor(); - } - - getJoinsDisabledReason() { - return this.getSource().getJoinsDisabledReason(); - } - isPreviewLayer(): boolean { return !!this._descriptor.__isPreviewLayer; } diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx index 30ec789cf8106..9b5298685865a 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx @@ -48,7 +48,7 @@ export class TiledVectorLayer extends VectorLayer { mapColors?: string[] ): VectorLayerDescriptor { const layerDescriptor = super.createDescriptor(descriptor, mapColors); - layerDescriptor.type = TiledVectorLayer.type; + layerDescriptor.type = LAYER_TYPE.TILED_VECTOR; if (!layerDescriptor.style) { const styleProperties = VectorStyle.createDefaultStyleProperties(mapColors ? mapColors : []); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts index 3c8449c5aa4ae..cb964f77613da 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts @@ -7,6 +7,7 @@ export { addGeoJsonMbSource, getVectorSourceBounds, syncVectorSource } from './utils'; export { + isVectorLayer, IVectorLayer, VectorLayer, VectorLayerArguments, diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx new file mode 100644 index 0000000000000..618be0b21cd73 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx @@ -0,0 +1,136 @@ +/* + * 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 */ + +jest.mock('../../styles/vector/vector_style', () => ({ + VectorStyle: class MockVectorStyle {}, +})); + +jest.mock('uuid/v4', () => { + return function () { + return '12345'; + }; +}); + +import { + AGG_TYPE, + FIELD_ORIGIN, + LAYER_STYLE_TYPE, + SOURCE_TYPES, + VECTOR_STYLES, +} from '../../../../common/constants'; +import { ESTermSourceDescriptor, VectorStyleDescriptor } from '../../../../common/descriptor_types'; +import { getDefaultDynamicProperties } from '../../styles/vector/vector_style_defaults'; +import { IVectorSource } from '../../sources/vector_source'; +import { VectorLayer } from './vector_layer'; + +class MockSource { + cloneDescriptor() { + return {}; + } + + getDisplayName() { + return 'mySource'; + } +} + +describe('cloneDescriptor', () => { + describe('with joins', () => { + const styleDescriptor = { + type: LAYER_STYLE_TYPE.VECTOR, + properties: { + ...getDefaultDynamicProperties(), + }, + } as VectorStyleDescriptor; + // @ts-expect-error + styleDescriptor.properties[VECTOR_STYLES.FILL_COLOR].options.field = { + name: '__kbnjoin__count__557d0f15', + origin: FIELD_ORIGIN.JOIN, + }; + // @ts-expect-error + styleDescriptor.properties[VECTOR_STYLES.LINE_COLOR].options.field = { + name: 'bytes', + origin: FIELD_ORIGIN.SOURCE, + }; + // @ts-expect-error + styleDescriptor.properties[VECTOR_STYLES.LABEL_BORDER_COLOR].options.field = { + name: '__kbnjoin__count__6666666666', + origin: FIELD_ORIGIN.JOIN, + }; + + test('Should update data driven styling properties using join fields', async () => { + const layerDescriptor = VectorLayer.createDescriptor({ + style: styleDescriptor, + joins: [ + { + leftField: 'iso2', + right: { + id: '557d0f15', + indexPatternId: 'myIndexPattern', + indexPatternTitle: 'logs-*', + metrics: [{ type: AGG_TYPE.COUNT }], + term: 'myTermField', + type: SOURCE_TYPES.ES_TERM_SOURCE, + applyGlobalQuery: true, + applyGlobalTime: true, + applyForceRefresh: true, + }, + }, + ], + }); + const layer = new VectorLayer({ + layerDescriptor, + source: new MockSource() as unknown as IVectorSource, + }); + const clonedDescriptor = await layer.cloneDescriptor(); + const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties; + // Should update style field belonging to join + // @ts-expect-error + expect(clonedStyleProps[VECTOR_STYLES.FILL_COLOR].options.field.name).toEqual( + '__kbnjoin__count__12345' + ); + // Should not update style field belonging to source + // @ts-expect-error + expect(clonedStyleProps[VECTOR_STYLES.LINE_COLOR].options.field.name).toEqual('bytes'); + // Should not update style feild belonging to different join + // @ts-expect-error + expect(clonedStyleProps[VECTOR_STYLES.LABEL_BORDER_COLOR].options.field.name).toEqual( + '__kbnjoin__count__6666666666' + ); + }); + + test('Should update data driven styling properties using join fields when metrics are not provided', async () => { + const layerDescriptor = VectorLayer.createDescriptor({ + style: styleDescriptor, + joins: [ + { + leftField: 'iso2', + right: { + id: '557d0f15', + indexPatternId: 'myIndexPattern', + indexPatternTitle: 'logs-*', + term: 'myTermField', + type: 'joinSource', + } as unknown as ESTermSourceDescriptor, + }, + ], + }); + const layer = new VectorLayer({ + layerDescriptor, + source: new MockSource() as unknown as IVectorSource, + }); + const clonedDescriptor = await layer.cloneDescriptor(); + const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties; + // Should update style field belonging to join + // @ts-expect-error + expect(clonedStyleProps[VECTOR_STYLES.FILL_COLOR].options.field.name).toEqual( + '__kbnjoin__count__12345' + ); + }); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index fb310496ac9ed..3faf92715451c 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import uuid from 'uuid/v4'; import type { Map as MbMap, AnyLayer as MbLayer, @@ -19,6 +20,7 @@ import { i18n } from '@kbn/i18n'; import { AbstractLayer } from '../layer'; import { IVectorStyle, VectorStyle } from '../../styles/vector/vector_style'; import { + AGG_TYPE, FEATURE_ID_PROPERTY_NAME, SOURCE_META_DATA_REQUEST_ID, SOURCE_FORMATTERS_DATA_REQUEST_ID, @@ -29,8 +31,11 @@ import { FIELD_ORIGIN, KBN_TOO_MANY_FEATURES_IMAGE_ID, FieldFormatter, + SOURCE_TYPES, + STYLE_TYPE, SUPPORTS_FEATURE_EDITING_REQUEST_ID, KBN_IS_TILE_COMPLETE, + VECTOR_STYLES, } from '../../../../common/constants'; import { JoinTooltipProperty } from '../../tooltips/join_tooltip_property'; import { DataRequestAbortError } from '../../util/data_request'; @@ -48,8 +53,11 @@ import { TimesliceMaskConfig, } from '../../util/mb_filter_expressions'; import { + AggDescriptor, DynamicStylePropertyOptions, DataFilters, + ESTermSourceDescriptor, + JoinDescriptor, StyleMetaDescriptor, Timeslice, VectorLayerDescriptor, @@ -71,6 +79,11 @@ import { ITermJoinSource } from '../../sources/term_join_source'; import { addGeoJsonMbSource, getVectorSourceBounds, syncVectorSource } from './utils'; import { JoinState, performInnerJoins } from './perform_inner_joins'; import { buildVectorRequestMeta } from '../build_vector_request_meta'; +import { getJoinAggKey } from '../../../../common/get_agg_key'; + +export function isVectorLayer(layer: ILayer) { + return (layer as IVectorLayer).canShowTooltip !== undefined; +} export interface VectorLayerArguments { source: IVectorSource; @@ -83,11 +96,13 @@ export interface IVectorLayer extends ILayer { getFields(): Promise; getStyleEditorFields(): Promise; getJoins(): InnerJoin[]; + getJoinsDisabledReason(): string | null; getValidJoins(): InnerJoin[]; getSource(): IVectorSource; getFeatureById(id: string | number): Feature | null; getPropertiesForTooltip(properties: GeoJsonProperties): Promise; hasJoins(): boolean; + showJoinEditor(): boolean; canShowTooltip(): boolean; supportsFeatureEditing(): boolean; getLeftJoinFields(): Promise; @@ -113,8 +128,8 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { options: Partial, mapColors?: string[] ): VectorLayerDescriptor { - const layerDescriptor = super.createDescriptor(options); - layerDescriptor.type = VectorLayer.type; + const layerDescriptor = super.createDescriptor(options) as VectorLayerDescriptor; + layerDescriptor.type = LAYER_TYPE.VECTOR; if (!options.style) { const styleProperties = VectorStyle.createDefaultStyleProperties(mapColors ? mapColors : []); @@ -125,7 +140,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { layerDescriptor.joins = []; } - return layerDescriptor as VectorLayerDescriptor; + return layerDescriptor; } constructor({ @@ -147,6 +162,62 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { ); } + async cloneDescriptor(): Promise { + const clonedDescriptor = (await super.cloneDescriptor()) as VectorLayerDescriptor; + if (clonedDescriptor.joins) { + clonedDescriptor.joins.forEach((joinDescriptor: JoinDescriptor) => { + if (joinDescriptor.right && joinDescriptor.right.type === SOURCE_TYPES.TABLE_SOURCE) { + throw new Error( + 'Cannot clone table-source. Should only be used in MapEmbeddable, not in UX' + ); + } + const termSourceDescriptor: ESTermSourceDescriptor = + joinDescriptor.right as ESTermSourceDescriptor; + + // todo: must tie this to generic thing + const originalJoinId = joinDescriptor.right.id!; + + // right.id is uuid used to track requests in inspector + joinDescriptor.right.id = uuid(); + + // Update all data driven styling properties using join fields + if (clonedDescriptor.style && 'properties' in clonedDescriptor.style) { + const metrics = + termSourceDescriptor.metrics && termSourceDescriptor.metrics.length + ? termSourceDescriptor.metrics + : [{ type: AGG_TYPE.COUNT }]; + metrics.forEach((metricsDescriptor: AggDescriptor) => { + const originalJoinKey = getJoinAggKey({ + aggType: metricsDescriptor.type, + aggFieldName: 'field' in metricsDescriptor ? metricsDescriptor.field : '', + rightSourceId: originalJoinId, + }); + const newJoinKey = getJoinAggKey({ + aggType: metricsDescriptor.type, + aggFieldName: 'field' in metricsDescriptor ? metricsDescriptor.field : '', + rightSourceId: joinDescriptor.right.id!, + }); + + Object.keys(clonedDescriptor.style.properties).forEach((key) => { + const styleProp = clonedDescriptor.style.properties[key as VECTOR_STYLES]; + if ('type' in styleProp && styleProp.type === STYLE_TYPE.DYNAMIC) { + const options = styleProp.options as DynamicStylePropertyOptions; + if ( + options.field && + options.field.origin === FIELD_ORIGIN.JOIN && + options.field.name === originalJoinKey + ) { + options.field.name = newJoinKey; + } + } + }); + }); + } + }); + } + return clonedDescriptor; + } + getSource(): IVectorSource { return super.getSource() as IVectorSource; } @@ -176,6 +247,10 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { return this._joins.slice(); } + getJoinsDisabledReason() { + return this.getSource().getJoinsDisabledReason(); + } + getValidJoins() { return this.getJoins().filter((join) => { return join.hasCompleteConfig(); @@ -192,6 +267,10 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { return this.getValidJoins().length > 0; } + showJoinEditor(): boolean { + return this.getSource().showJoinEditor(); + } + isInitialDataLoadComplete() { const sourceDataRequest = this.getSourceDataRequest(); if (!sourceDataRequest || !sourceDataRequest.hasData()) { diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx index a955cb336e55e..34a30ae9ec977 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx @@ -243,6 +243,14 @@ export class MVTSingleLayerVectorSource async getDefaultFields(): Promise>> { return {}; } + + showJoinEditor(): boolean { + return false; + } + + getJoinsDisabledReason(): string | null { + return null; + } } registerSource({ diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index 5b2fc16d18b41..4c2cffcf8b070 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -53,8 +53,6 @@ export interface ISource { isESSource(): boolean; renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement | null; supportsFitToBounds(): Promise; - showJoinEditor(): boolean; - getJoinsDisabledReason(): string | null; cloneDescriptor(): AbstractSourceDescriptor; getFieldNames(): string[]; getApplyGlobalQuery(): boolean; @@ -155,14 +153,6 @@ export class AbstractSource implements ISource { return 0; } - showJoinEditor(): boolean { - return false; - } - - getJoinsDisabledReason(): string | null { - return null; - } - isESSource(): boolean { return false; } diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index bc3807a8247b1..3c0adf64216e6 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -59,6 +59,8 @@ export interface IVectorSource extends ISource { getFields(): Promise; getFieldByName(fieldName: string): IField | null; getLeftJoinFields(): Promise; + showJoinEditor(): boolean; + getJoinsDisabledReason(): string | null; /* * Vector layer avoids unnecessarily re-fetching source data. @@ -122,6 +124,10 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc return []; } + getJoinsDisabledReason(): string | null { + return null; + } + async getGeoJsonWithMeta( layerName: string, searchFilters: VectorSourceRequestMeta, diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/__snapshots__/edit_layer_panel.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/__snapshots__/edit_layer_panel.test.tsx.snap index a2ad6fa62dce6..5fb1cc6f72585 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/__snapshots__/edit_layer_panel.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/__snapshots__/edit_layer_panel.test.tsx.snap @@ -93,6 +93,7 @@ exports[`EditLayerPanel is rendered 1`] = ` { return true; }, + canShowTooltip: () => { + return true; + }, supportsElasticsearchFilters: () => { return false; }, diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx index da72969684216..424c4b8e16bec 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx @@ -33,7 +33,7 @@ import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; import { LAYER_TYPE } from '../../../common/constants'; import { getData, getCore } from '../../kibana_services'; import { ILayer } from '../../classes/layers/layer'; -import { IVectorLayer } from '../../classes/layers/vector_layer'; +import { isVectorLayer, IVectorLayer } from '../../classes/layers/vector_layer'; import { ImmutableSourceProperty, OnSourceChangeArgs } from '../../classes/sources/source'; import { IField } from '../../classes/fields/field'; @@ -111,11 +111,12 @@ export class EditLayerPanel extends Component { }; async _loadLeftJoinFields() { - if ( - !this.props.selectedLayer || - !this.props.selectedLayer.showJoinEditor() || - (this.props.selectedLayer as IVectorLayer).getLeftJoinFields === undefined - ) { + if (!this.props.selectedLayer || !isVectorLayer(this.props.selectedLayer)) { + return; + } + + const vectorLayer = this.props.selectedLayer as IVectorLayer; + if (!vectorLayer.showJoinEditor() || vectorLayer.getLeftJoinFields === undefined) { return; } @@ -182,7 +183,11 @@ export class EditLayerPanel extends Component { } _renderJoinSection() { - if (!this.props.selectedLayer || !this.props.selectedLayer.showJoinEditor()) { + if (!this.props.selectedLayer || !isVectorLayer(this.props.selectedLayer)) { + return; + } + const vectorLayer = this.props.selectedLayer as IVectorLayer; + if (!vectorLayer.showJoinEditor()) { return null; } @@ -190,7 +195,7 @@ export class EditLayerPanel extends Component { diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/index.ts b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/index.ts index b78ffb3874e30..84caa45741a62 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/index.ts +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/index.ts @@ -13,11 +13,18 @@ import { LAYER_TYPE } from '../../../common/constants'; import { getSelectedLayer } from '../../selectors/map_selectors'; import { updateSourceProp } from '../../actions'; import { MapStoreState } from '../../reducers/store'; +import { isVectorLayer, IVectorLayer } from '../../classes/layers/vector_layer'; function mapStateToProps(state: MapStoreState) { const selectedLayer = getSelectedLayer(state); + let key = 'none'; + if (selectedLayer) { + key = isVectorLayer(selectedLayer) + ? `${selectedLayer.getId()}${(selectedLayer as IVectorLayer).showJoinEditor()}` + : selectedLayer.getId(); + } return { - key: selectedLayer ? `${selectedLayer.getId()}${selectedLayer.showJoinEditor()}` : '', + key, selectedLayer, }; } diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/join_editor.test.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/join_editor.test.tsx index 27a345cdf2dda..6da05ef0b4092 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/join_editor.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/join_editor.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { ILayer } from '../../../classes/layers/layer'; +import { IVectorLayer } from '../../../classes/layers/vector_layer'; import { JoinEditor } from './join_editor'; import { shallow } from 'enzyme'; import { JoinDescriptor } from '../../../../common/descriptor_types'; @@ -48,7 +48,7 @@ const defaultProps = { test('Should render join editor', () => { const component = shallow( - + ); expect(component).toMatchSnapshot(); }); @@ -57,7 +57,7 @@ test('Should render callout when joins are disabled', () => { const component = shallow( ); expect(component).toMatchSnapshot(); diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/join_editor.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/join_editor.tsx index e0d630994566d..e99ec6a688092 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/join_editor.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/join_editor.tsx @@ -20,7 +20,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { Join } from './resources/join'; -import { ILayer } from '../../../classes/layers/layer'; +import { IVectorLayer } from '../../../classes/layers/vector_layer'; import { JoinDescriptor } from '../../../../common/descriptor_types'; import { SOURCE_TYPES } from '../../../../common/constants'; @@ -31,10 +31,10 @@ export interface JoinField { export interface Props { joins: JoinDescriptor[]; - layer: ILayer; + layer: IVectorLayer; layerDisplayName: string; leftJoinFields: JoinField[]; - onChange: (layer: ILayer, joins: JoinDescriptor[]) => void; + onChange: (layer: IVectorLayer, joins: JoinDescriptor[]) => void; } export function JoinEditor({ joins, layer, onChange, leftJoinFields, layerDisplayName }: Props) { diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.tsx index 3567501455734..c2ad75d9cb335 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.tsx @@ -35,7 +35,7 @@ import { TooltipPopover } from './tooltip_popover'; import { FeatureGeometryFilterForm } from './features_tooltip'; import { EXCLUDE_TOO_MANY_FEATURES_BOX } from '../../../classes/util/mb_filter_expressions'; import { ILayer } from '../../../classes/layers/layer'; -import { IVectorLayer } from '../../../classes/layers/vector_layer'; +import { IVectorLayer, isVectorLayer } from '../../../classes/layers/vector_layer'; import { RenderToolTipContent } from '../../../classes/tooltips/tooltip_property'; function justifyAnchorLocation( @@ -58,10 +58,6 @@ function justifyAnchorLocation( return popupAnchorLocation; } -function isVectorLayer(layer: ILayer) { - return (layer as IVectorLayer).canShowTooltip !== undefined; -} - export interface Props { addFilters: ((filters: Filter[], actionId: string) => Promise) | null; closeOnClickTooltip: (tooltipId: string) => void; diff --git a/x-pack/plugins/maps/server/maps_telemetry/util.ts b/x-pack/plugins/maps/server/maps_telemetry/util.ts index ff9339fca76cb..27190c9b82142 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/util.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/util.ts @@ -10,6 +10,7 @@ import { ESGeoGridSourceDescriptor, ESSearchSourceDescriptor, LayerDescriptor, + VectorLayerDescriptor, } from '../../common/descriptor_types'; import { GRID_RESOLUTION, @@ -265,8 +266,7 @@ export function getTermJoinsPerCluster( ): TELEMETRY_TERM_JOIN_COUNTS_PER_CLUSTER { return getCountsByCluster(layerLists, (layerDescriptor: LayerDescriptor) => { return layerDescriptor.type === LAYER_TYPE.VECTOR && - layerDescriptor.joins && - layerDescriptor.joins.length + (layerDescriptor as VectorLayerDescriptor)?.joins?.length ? TELEMETRY_TERM_JOIN : null; }); diff --git a/x-pack/plugins/ml/public/application/explorer/anomalies_map.tsx b/x-pack/plugins/ml/public/application/explorer/anomalies_map.tsx index 182a8a37fadfc..28f346c0148c6 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomalies_map.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomalies_map.tsx @@ -18,6 +18,7 @@ import { } from '@elastic/eui'; import { FIELD_ORIGIN, + LAYER_TYPE, SOURCE_TYPES, STYLE_TYPE, COLOR_MAP_TYPE, @@ -125,7 +126,7 @@ export const getChoroplethAnomaliesLayer = ( isTimeAware: true, }, visible: false, - type: 'VECTOR', + type: LAYER_TYPE.VECTOR, }; };