import { i18n } from '@kbn/i18n' ; import { IVectorLayer } from '../vector_layer' ; import { GeoJsonVectorLayer } from '../geojson_vector_layer' ; import { IVectorStyle , VectorStyle } from '../../../styles/vector/vector_style' ; import { getDefaultDynamicProperties } from '../../../styles/vector/vector_style_defaults' ; import { IDynamicStyleProperty } from '../../../styles/vector/properties/dynamic_style_property' ; import { IStyleProperty } from '../../../styles/vector/properties/style_property' ; import { COUNT_PROP_LABEL , COUNT_PROP_NAME , LAYER_TYPE , AGG_TYPE , RENDER_AS , STYLE_TYPE , VECTOR_STYLES , LAYER_STYLE_TYPE , FIELD_ORIGIN , } from '../../../../../common/constants' ; import { ESGeoGridSource } from '../../../sources/es_geo_grid_source/es_geo_grid_source' ; import { canSkipSourceUpdate } from '../../../util/can_skip_fetch' ; import { IESSource } from '../../../sources/es_source' ; import { ISource } from '../../../sources/source' ; import { DataRequestContext } from '../../../../actions' ; import { DataRequestAbortError } from '../../../util/data_request' ; import { VectorStyleDescriptor , SizeDynamicOptions , DynamicStylePropertyOptions , StylePropertyOptions , Timeslice , VectorLayerDescriptor , VectorSourceRequestMeta , VectorStylePropertiesDescriptor , } from '../../../../../common/descriptor_types' ; import { IVectorSource } from '../../../sources/vector_source' ; import { LICENSED_FEATURES } from '../../../../licensed_features' ; import { ESSearchSource } from '../../../sources/es_search_source/es_search_source' ; import { isSearchSourceAbortError } from '../../../sources/es_source/es_source' ; const ACTIVE_COUNT_DATA_ID = 'ACTIVE_COUNT_DATA_ID' ; function getAggType ( dynamicProperty ) { return dynamicProperty . isOrdinal ( ) ? AGG_TYPE . AVG : AGG_TYPE . TERMS ; } function getClusterSource ( documentSource , documentStyle ) { const clusterSourceDescriptor = ESGeoGridSource . createDescriptor ( { indexPatternId : documentSource . getIndexPatternId ( ) , geoField : documentSource . getGeoFieldName ( ) , requestType : RENDER_AS . POINT , } ) ; clusterSourceDescriptor . applyGlobalQuery = documentSource . getApplyGlobalQuery ( ) ; clusterSourceDescriptor . applyGlobalTime = documentSource . getApplyGlobalTime ( ) ; clusterSourceDescriptor . applyForceRefresh = documentSource . getApplyForceRefresh ( ) ; clusterSourceDescriptor . metrics = [ { type : AGG_TYPE . COUNT , label : COUNT_PROP_LABEL , } , ... documentStyle . getDynamicPropertiesArray ( ) . map ( ( dynamicProperty ) => { return { type : getAggType ( dynamicProperty ) , field : dynamicProperty . getFieldName ( ) , } ; } ) , ] ; clusterSourceDescriptor . id = documentSource . getId ( ) ; return new ESGeoGridSource ( clusterSourceDescriptor , documentSource . getInspectorAdapters ( ) ) ; } function getClusterStyleDescriptor ( documentStyle , clusterSource ) { const defaultDynamicProperties = getDefaultDynamicProperties ( ) ; const clusterStyleDescriptor = { type : LAYER_STYLE_TYPE . VECTOR , properties : { [ VECTOR_STYLES . LABEL_TEXT ] : { type : STYLE_TYPE . DYNAMIC , options : { ... defaultDynamicProperties [ VECTOR_STYLES . LABEL_TEXT ] . options , field : { name : COUNT_PROP_NAME , origin : FIELD_ORIGIN . SOURCE , } , } , } , [ VECTOR_STYLES . ICON_SIZE ] : { type : STYLE_TYPE . DYNAMIC , options : { ... ( defaultDynamicProperties [ VECTOR_STYLES . ICON_SIZE ] . options as SizeDynamicOptions ) , field : { name : COUNT_PROP_NAME , origin : FIELD_ORIGIN . SOURCE , } , } , } , } , isTimeAware : true , } ; documentStyle . getAllStyleProperties ( ) . forEach ( ( styleProperty ) => { const styleName = styleProperty . getStyleName ( ) ; if ( [ VECTOR_STYLES . LABEL_TEXT , VECTOR_STYLES . ICON_SIZE ] . includes ( styleName ) && ( ! styleProperty . isDynamic ( ) || ! styleProperty . isComplete ( ) ) ) { return ; } if ( styleName === VECTOR_STYLES . SYMBOLIZE_AS || styleName === VECTOR_STYLES . LABEL_BORDER_SIZE ) { clusterStyleDescriptor . properties [ styleName ] = { options : { ... styleProperty . getOptions ( ) } , } ; } else if ( styleProperty . isDynamic ( ) ) { const options = styleProperty . getOptions ( ) as DynamicStylePropertyOptions ; const field = options && options . field && options . field . name ? { ... options . field , name : clusterSource . getAggKey ( getAggType ( styleProperty as IDynamicStyleProperty < DynamicStylePropertyOptions > ) , options . field . name ) , } : undefined ; clusterStyleDescriptor . properties [ styleName ] = { type : STYLE_TYPE . DYNAMIC , options : { ... options , field , } , } ; } else { clusterStyleDescriptor . properties [ styleName ] = { type : STYLE_TYPE . STATIC , options : { ... styleProperty . getOptions ( ) } , } ; } } ) ; return clusterStyleDescriptor as VectorStyleDescriptor ; } export interface BlendedVectorLayerArguments { chartsPaletteServiceGetColor ? : ( value : string ) => string | null ; source : IVectorSource ; layerDescriptor : VectorLayerDescriptor ; } export class BlendedVectorLayer extends GeoJsonVectorLayer implements IVectorLayer { static createDescriptor ( options , mapColors ) { const layerDescriptor = GeoJsonVectorLayer . createDescriptor ( options , mapColors ) ; layerDescriptor . type = LAYER_TYPE . BLENDED_VECTOR ; return layerDescriptor ; } private readonly _isClustered : boolean ; private readonly _clusterSource : ESGeoGridSource ; private readonly _clusterStyle : VectorStyle ; private readonly _documentSource : ESSearchSource ; private readonly _documentStyle : VectorStyle ; constructor ( options ) { super ( { ... options , joins : [ ] , } ) ; this . _documentSource = this . _source as ESSearchSource ; this . _documentStyle = this . _style ; this . _clusterSource = getClusterSource ( this . _documentSource , this . _documentStyle ) ; const clusterStyleDescriptor = getClusterStyleDescriptor ( this . _documentStyle , this . _clusterSource ) ; this . _clusterStyle = new VectorStyle ( clusterStyleDescriptor , this . _clusterSource , this , options . chartsPaletteServiceGetColor ) ; let isClustered = false ; const countDataRequest = this . getDataRequest ( ACTIVE_COUNT_DATA_ID ) ; if ( countDataRequest ) { const requestData = countDataRequest . getData ( ) as { isSyncClustered : boolean ; } ; if ( requestData && requestData . isSyncClustered ) { isClustered = true ; } } this . _isClustered = isClustered ; } destroy ( ) { if ( this . _documentSource ) { this . _documentSource . destroy ( ) ; } if ( this . _clusterSource ) { this . _clusterSource . destroy ( ) ; } } async getDisplayName ( source ? ) { const displayName = await super . getDisplayName ( source ) ; return this . _isClustered ? i18n . translate ( 'xpack.maps.blendedVectorLayer.clusteredLayerName' , { defaultMessage : 'Clustered {displayName}' , values : { displayName } , } ) : displayName ; } showJoinEditor ( ) { return true ; } getJoinsDisabledReason ( ) { return this . _documentSource . getJoinsDisabledReason ( ) ; } getJoins ( ) { return [ ] ; } hasJoins ( ) { return false ; } async cloneDescriptor ( ) { const clonedDescriptor = await super . cloneDescriptor ( ) ; const displayName = await super . getDisplayName ( ) ; clonedDescriptor . label = `Clone of ${ displayName }` ; clonedDescriptor . sourceDescriptor = this . _documentSource . cloneDescriptor ( ) ; return clonedDescriptor ; } getSource ( ) { return this . _isClustered ? this . _clusterSource : this . _documentSource ; } getSourceForEditing ( ) { return this . _documentSource ; } getCurrentStyle ( ) { return this . _isClustered ? this . _clusterStyle : this . _documentStyle ; } getStyleForEditing ( ) { return this . _documentStyle ; } async syncData ( syncContext ) { const dataRequestId = ACTIVE_COUNT_DATA_ID ; const requestToken = Symbol ( `layer-active-count:${ this . getId ( ) }` ) ; const requestMeta = await this . _getVectorSourceRequestMeta ( syncContext . isForceRefresh , syncContext . dataFilters , this . getSource ( ) , this . getCurrentStyle ( ) ) ; const source = this . getSource ( ) ; const canSkipSourceFetch = await canSkipSourceUpdate ( { source , prevDataRequest : this . getDataRequest ( dataRequestId ) , nextRequestMeta : requestMeta , extentAware : source . isFilterByMapBounds ( ) , getUpdateDueToTimeslice : ( timeslice ? ) => { return this . _getUpdateDueToTimesliceFromSourceRequestMeta ( source , timeslice ) ; } , } ) ; let activeSource ; let activeStyle ; if ( canSkipSourceFetch ) { if ( this . _isClustered ) { activeSource = this . _clusterSource ; activeStyle = this . _clusterStyle ; } else { activeSource = this . _documentSource ; activeStyle = this . _documentStyle ; } } else { let isSyncClustered ; try { syncContext . startLoading ( dataRequestId , requestToken , requestMeta ) ; isSyncClustered = ! ( await this . _documentSource . canLoadAllDocuments ( requestMeta , syncContext . registerCancelCallback . bind ( null , requestToken ) ) ) ; syncContext . stopLoading ( dataRequestId , requestToken , { isSyncClustered } , requestMeta ) ; } catch ( error ) { if ( ! ( error instanceof DataRequestAbortError ) || ! isSearchSourceAbortError ( error ) ) { syncContext . onLoadError ( dataRequestId , requestToken , error . message ) ; } return ; } if ( isSyncClustered ) { activeSource = this . _clusterSource ; activeStyle = this . _clusterStyle ; } else { activeSource = this . _documentSource ; activeStyle = this . _documentStyle ; } } super . _syncData ( syncContext , activeSource , activeStyle ) ; } async getLicensedFeatures ( ) { return [ ... ( await this . _clusterSource . getLicensedFeatures ( ) ) , ... ( await this . _documentSource . getLicensedFeatures ( ) ) , ] ; } }