diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index a4006732224ce3..6f9c0985f5f4af 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -166,6 +166,7 @@ export enum STYLE_TYPE { export enum LAYER_STYLE_TYPE { VECTOR = 'VECTOR', HEATMAP = 'HEATMAP', + TILE = 'TILE', } export const COLOR_MAP_TYPE = { diff --git a/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts b/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts index f8175b0ed3f109..6980f14d0788ac 100644 --- a/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts +++ b/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts @@ -5,8 +5,9 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ +import { Query } from 'src/plugins/data/public'; import { AGG_TYPE, GRID_RESOLUTION, RENDER_AS, SORT_ORDER, SCALING_TYPES } from '../constants'; -import { VectorStyleDescriptor } from './style_property_descriptor_types'; +import { StyleDescriptor, VectorStyleDescriptor } from './style_property_descriptor_types'; import { DataRequestDescriptor } from './data_request_descriptor_types'; export type AttributionDescriptor = { @@ -17,6 +18,7 @@ export type AttributionDescriptor = { export type AbstractSourceDescriptor = { id?: string; type: string; + applyGlobalQuery?: boolean; }; export type EMSTMSSourceDescriptor = AbstractSourceDescriptor & { @@ -71,17 +73,15 @@ export type ESTermSourceDescriptor = AbstractESAggSourceDescriptor & { term: string; // term field name }; -export type KibanaRegionmapSourceDescriptor = { - type: string; +export type KibanaRegionmapSourceDescriptor = AbstractSourceDescriptor & { name: string; }; -export type KibanaTilemapSourceDescriptor = { - type: string; -}; +// This is for symmetry with other sources only. +// It takes no additional configuration since all params are in the .yml. +export type KibanaTilemapSourceDescriptor = AbstractSourceDescriptor; -export type WMSSourceDescriptor = { - type: string; +export type WMSSourceDescriptor = AbstractSourceDescriptor & { serviceUrl: string; layers: string; styles: string; @@ -111,6 +111,8 @@ export type JoinDescriptor = { right: ESTermSourceDescriptor; }; +// todo : this union type is incompatible with dynamic extensibility of sources. +// Reconsider using SourceDescriptor in type signatures for top-level classes export type SourceDescriptor = | XYZTMSSourceDescriptor | WMSSourceDescriptor @@ -121,7 +123,9 @@ export type SourceDescriptor = | ESGeoGridSourceDescriptor | EMSFileSourceDescriptor | ESPewPewSourceDescriptor - | TiledSingleLayerVectorSourceDescriptor; + | TiledSingleLayerVectorSourceDescriptor + | EMSTMSSourceDescriptor + | EMSFileSourceDescriptor; export type LayerDescriptor = { __dataRequests?: DataRequestDescriptor[]; @@ -129,12 +133,14 @@ export type LayerDescriptor = { __errorMessage?: string; alpha?: number; id: string; - label?: string; + label?: string | null; minZoom?: number; maxZoom?: number; - sourceDescriptor: SourceDescriptor; + sourceDescriptor: SourceDescriptor | null; type?: string; visible?: boolean; + style?: StyleDescriptor | null; + query?: Query; }; export type VectorLayerDescriptor = LayerDescriptor & { diff --git a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.d.ts b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.d.ts index 47e56ff96d6236..381bc5bba01c0f 100644 --- a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.d.ts +++ b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.d.ts @@ -182,7 +182,11 @@ export type VectorStylePropertiesDescriptor = { [VECTOR_STYLES.LABEL_BORDER_SIZE]?: LabelBorderSizeStylePropertyDescriptor; }; -export type VectorStyleDescriptor = { +export type StyleDescriptor = { + type: string; +}; + +export type VectorStyleDescriptor = StyleDescriptor & { type: LAYER_STYLE_TYPE.VECTOR; properties: VectorStylePropertiesDescriptor; }; diff --git a/x-pack/plugins/maps/public/angular/get_initial_layers.test.js b/x-pack/plugins/maps/public/angular/get_initial_layers.test.js index f41ed26b2a05d3..4b5cad8d19260e 100644 --- a/x-pack/plugins/maps/public/angular/get_initial_layers.test.js +++ b/x-pack/plugins/maps/public/angular/get_initial_layers.test.js @@ -52,7 +52,7 @@ describe('kibana.yml configured with map.tilemap.url', () => { sourceDescriptor: { type: 'KIBANA_TILEMAP', }, - style: {}, + style: { type: 'TILE' }, type: 'TILE', visible: true, }, @@ -96,7 +96,7 @@ describe('EMS is enabled', () => { isAutoSelect: true, type: 'EMS_TMS', }, - style: {}, + style: { type: 'TILE' }, type: 'VECTOR_TILE', visible: true, }, diff --git a/x-pack/plugins/maps/public/layers/blended_vector_layer.ts b/x-pack/plugins/maps/public/layers/blended_vector_layer.ts index 9a9ea2968ceebd..1fc3ad203706f0 100644 --- a/x-pack/plugins/maps/public/layers/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/layers/blended_vector_layer.ts @@ -24,7 +24,7 @@ import { } from '../../common/constants'; import { ESGeoGridSource } from './sources/es_geo_grid_source/es_geo_grid_source'; import { canSkipSourceUpdate } from './util/can_skip_fetch'; -import { IVectorLayer, VectorLayerArguments } from './vector_layer'; +import { IVectorLayer } from './vector_layer'; import { IESSource } from './sources/es_source'; import { IESAggSource } from './sources/es_agg_source'; import { ISource } from './sources/source'; @@ -36,6 +36,8 @@ import { DynamicStylePropertyOptions, VectorLayerDescriptor, } from '../../common/descriptor_types'; +import { IStyle } from './styles/style'; +import { IVectorSource } from './sources/vector_source'; const ACTIVE_COUNT_DATA_ID = 'ACTIVE_COUNT_DATA_ID'; @@ -145,6 +147,11 @@ function getClusterStyleDescriptor( return clusterStyleDescriptor; } +export interface BlendedVectorLayerArguments { + source: IVectorSource; + layerDescriptor: VectorLayerDescriptor; +} + export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { static type = LAYER_TYPE.BLENDED_VECTOR; @@ -163,11 +170,14 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { private readonly _documentSource: IESSource; private readonly _documentStyle: IVectorStyle; - constructor(options: VectorLayerArguments) { - super(options); + constructor(options: BlendedVectorLayerArguments) { + super({ + ...options, + joins: [], + }); this._documentSource = this._source as IESSource; // VectorLayer constructor sets _source as document source - this._documentStyle = this._style; // VectorLayer constructor sets _style as document source + this._documentStyle = this._style as IVectorStyle; // VectorLayer constructor sets _style as document source this._clusterSource = getClusterSource(this._documentSource, this._documentStyle); const clusterStyleDescriptor = getClusterStyleDescriptor( @@ -229,11 +239,11 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { return this._documentSource; } - getCurrentStyle() { + getCurrentStyle(): IStyle { return this._isClustered ? this._clusterStyle : this._documentStyle; } - getStyleForEditing() { + getStyleForEditing(): IStyle { return this._documentStyle; } @@ -242,8 +252,8 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { const requestToken = Symbol(`layer-active-count:${this.getId()}`); const searchFilters = this._getSearchFilters( syncContext.dataFilters, - this.getSource(), - this.getCurrentStyle() + this.getSource() as IVectorSource, + this.getCurrentStyle() as IVectorStyle ); const canSkipFetch = await canSkipSourceUpdate({ source: this.getSource(), diff --git a/x-pack/plugins/maps/public/layers/layer.d.ts b/x-pack/plugins/maps/public/layers/layer.d.ts deleted file mode 100644 index e8fc5d473626cb..00000000000000 --- a/x-pack/plugins/maps/public/layers/layer.d.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { LayerDescriptor, MapExtent, MapFilters, MapQuery } from '../../common/descriptor_types'; -import { ISource } from './sources/source'; -import { DataRequest } from './util/data_request'; -import { SyncContext } from '../actions/map_actions'; - -export interface ILayer { - getBounds(mapFilters: MapFilters): Promise; - getDataRequest(id: string): DataRequest | undefined; - getDisplayName(source?: ISource): Promise; - getId(): string; - getSourceDataRequest(): DataRequest | undefined; - getSource(): ISource; - getSourceForEditing(): ISource; - syncData(syncContext: SyncContext): Promise; - isVisible(): boolean; - showAtZoomLevel(zoomLevel: number): boolean; - getMinZoom(): number; - getMaxZoom(): number; - getMinSourceZoom(): number; -} - -export interface ILayerArguments { - layerDescriptor: LayerDescriptor; - source: ISource; -} - -export class AbstractLayer implements ILayer { - static createDescriptor(options: Partial, mapColors?: string[]): LayerDescriptor; - constructor(layerArguments: ILayerArguments); - getBounds(mapFilters: MapFilters): Promise; - getDataRequest(id: string): DataRequest | undefined; - getDisplayName(source?: ISource): Promise; - getId(): string; - getSourceDataRequest(): DataRequest | undefined; - getSource(): ISource; - getSourceForEditing(): ISource; - syncData(syncContext: SyncContext): Promise; - isVisible(): boolean; - showAtZoomLevel(zoomLevel: number): boolean; - getMinZoom(): number; - getMaxZoom(): number; - getMinSourceZoom(): number; - getQuery(): MapQuery; - _removeStaleMbSourcesAndLayers(mbMap: unknown): void; - _requiresPrevSourceCleanup(mbMap: unknown): boolean; -} diff --git a/x-pack/plugins/maps/public/layers/layer.js b/x-pack/plugins/maps/public/layers/layer.js deleted file mode 100644 index 9362ce2c028e6e..00000000000000 --- a/x-pack/plugins/maps/public/layers/layer.js +++ /dev/null @@ -1,373 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import _ from 'lodash'; -import React from 'react'; -import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; -import { DataRequest } from './util/data_request'; -import { - MAX_ZOOM, - MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER, - MIN_ZOOM, - SOURCE_DATA_ID_ORIGIN, -} from '../../common/constants'; -import uuid from 'uuid/v4'; - -import { copyPersistentState } from '../reducers/util.js'; -import { i18n } from '@kbn/i18n'; - -export class AbstractLayer { - constructor({ layerDescriptor, source }) { - this._descriptor = AbstractLayer.createDescriptor(layerDescriptor); - this._source = source; - if (this._descriptor.__dataRequests) { - this._dataRequests = this._descriptor.__dataRequests.map( - dataRequest => new DataRequest(dataRequest) - ); - } else { - this._dataRequests = []; - } - } - - static getBoundDataForSource(mbMap, sourceId) { - const mbStyle = mbMap.getStyle(); - return mbStyle.sources[sourceId].data; - } - - static createDescriptor(options = {}) { - const layerDescriptor = { ...options }; - - layerDescriptor.__dataRequests = _.get(options, '__dataRequests', []); - layerDescriptor.id = _.get(options, 'id', uuid()); - layerDescriptor.label = options.label && options.label.length > 0 ? options.label : null; - layerDescriptor.minZoom = _.get(options, 'minZoom', MIN_ZOOM); - layerDescriptor.maxZoom = _.get(options, 'maxZoom', MAX_ZOOM); - layerDescriptor.alpha = _.get(options, 'alpha', 0.75); - layerDescriptor.visible = _.get(options, 'visible', true); - layerDescriptor.style = _.get(options, 'style', {}); - - return layerDescriptor; - } - - destroy() { - if (this._source) { - this._source.destroy(); - } - } - - async cloneDescriptor() { - const clonedDescriptor = copyPersistentState(this._descriptor); - // layer id is uuid used to track styles/layers in mapbox - clonedDescriptor.id = uuid(); - const displayName = await this.getDisplayName(); - clonedDescriptor.label = `Clone of ${displayName}`; - clonedDescriptor.sourceDescriptor = this.getSource().cloneDescriptor(); - if (clonedDescriptor.joins) { - clonedDescriptor.joins.forEach(joinDescriptor => { - // right.id is uuid used to track requests in inspector - joinDescriptor.right.id = uuid(); - }); - } - return clonedDescriptor; - } - - makeMbLayerId(layerNameSuffix) { - return `${this.getId()}${MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER}${layerNameSuffix}`; - } - - isJoinable() { - return this.getSource().isJoinable(); - } - - supportsElasticsearchFilters() { - return this.getSource().isESSource(); - } - - async supportsFitToBounds() { - return await this.getSource().supportsFitToBounds(); - } - - async getDisplayName(source) { - if (this._descriptor.label) { - return this._descriptor.label; - } - - const sourceDisplayName = source - ? await source.getDisplayName() - : await this.getSource().getDisplayName(); - return sourceDisplayName || `Layer ${this._descriptor.id}`; - } - - async getAttributions() { - if (!this.hasErrors()) { - return await this.getSource().getAttributions(); - } - return []; - } - - getLabel() { - return this._descriptor.label ? this._descriptor.label : ''; - } - - getCustomIconAndTooltipContent() { - return { - icon: , - }; - } - - getIconAndTooltipContent(zoomLevel, isUsingSearch) { - let icon; - let tooltipContent = null; - const footnotes = []; - if (this.hasErrors()) { - icon = ( - - ); - tooltipContent = this.getErrors(); - } else if (this.isLayerLoading()) { - icon = ; - } else if (!this.isVisible()) { - icon = ; - tooltipContent = i18n.translate('xpack.maps.layer.layerHiddenTooltip', { - defaultMessage: `Layer is hidden.`, - }); - } else if (!this.showAtZoomLevel(zoomLevel)) { - const minZoom = this.getMinZoom(); - const maxZoom = this.getMaxZoom(); - icon = ; - tooltipContent = i18n.translate('xpack.maps.layer.zoomFeedbackTooltip', { - defaultMessage: `Layer is visible between zoom levels {minZoom} and {maxZoom}.`, - values: { minZoom, maxZoom }, - }); - } else { - const customIconAndTooltipContent = this.getCustomIconAndTooltipContent(); - if (customIconAndTooltipContent) { - icon = customIconAndTooltipContent.icon; - if (!customIconAndTooltipContent.areResultsTrimmed) { - tooltipContent = customIconAndTooltipContent.tooltipContent; - } else { - footnotes.push({ - icon: , - message: customIconAndTooltipContent.tooltipContent, - }); - } - } - - if (isUsingSearch && this.getQueryableIndexPatternIds().length) { - footnotes.push({ - icon: , - message: i18n.translate('xpack.maps.layer.isUsingSearchMsg', { - defaultMessage: 'Results narrowed by search bar', - }), - }); - } - } - - return { - icon, - tooltipContent, - footnotes, - }; - } - - async hasLegendDetails() { - return false; - } - - renderLegendDetails() { - return null; - } - - getId() { - return this._descriptor.id; - } - - getSource() { - return this._source; - } - - getSourceForEditing() { - return this._source; - } - - isVisible() { - return this._descriptor.visible; - } - - showAtZoomLevel(zoom) { - return zoom >= this.getMinZoom() && zoom <= this.getMaxZoom(); - } - - getMinZoom() { - return this._descriptor.minZoom; - } - - getMaxZoom() { - return this._descriptor.maxZoom; - } - - getMinSourceZoom() { - return this._source.getMinZoom(); - } - - _requiresPrevSourceCleanup() { - return false; - } - - _removeStaleMbSourcesAndLayers(mbMap) { - if (this._requiresPrevSourceCleanup(mbMap)) { - const mbStyle = mbMap.getStyle(); - mbStyle.layers.forEach(mbLayer => { - if (this.ownsMbLayerId(mbLayer.id)) { - mbMap.removeLayer(mbLayer.id); - } - }); - Object.keys(mbStyle.sources).some(mbSourceId => { - if (this.ownsMbSourceId(mbSourceId)) { - mbMap.removeSource(mbSourceId); - } - }); - } - } - - getAlpha() { - return this._descriptor.alpha; - } - - getQuery() { - return this._descriptor.query; - } - - getCurrentStyle() { - return this._style; - } - - getStyleForEditing() { - return this._style; - } - - async getImmutableSourceProperties() { - return this.getSource().getImmutableProperties(); - } - - renderSourceSettingsEditor = ({ onChange }) => { - return this.getSourceForEditing().renderSourceSettingsEditor({ onChange }); - }; - - getPrevRequestToken(dataId) { - const prevDataRequest = this.getDataRequest(dataId); - if (!prevDataRequest) { - return; - } - - return prevDataRequest.getRequestToken(); - } - - getInFlightRequestTokens() { - if (!this._dataRequests) { - return []; - } - - const requestTokens = this._dataRequests.map(dataRequest => dataRequest.getRequestToken()); - return _.compact(requestTokens); - } - - getSourceDataRequest() { - return this.getDataRequest(SOURCE_DATA_ID_ORIGIN); - } - - getDataRequest(id) { - return this._dataRequests.find(dataRequest => dataRequest.getDataId() === id); - } - - isLayerLoading() { - return this._dataRequests.some(dataRequest => dataRequest.isLoading()); - } - - hasErrors() { - return _.get(this._descriptor, '__isInErrorState', false); - } - - getErrors() { - return this.hasErrors() ? this._descriptor.__errorMessage : ''; - } - - toLayerDescriptor() { - return this._descriptor; - } - - async syncData() { - //no-op by default - } - - getMbLayerIds() { - throw new Error('Should implement AbstractLayer#getMbLayerIds'); - } - - ownsMbLayerId() { - throw new Error('Should implement AbstractLayer#ownsMbLayerId'); - } - - ownsMbSourceId() { - throw new Error('Should implement AbstractLayer#ownsMbSourceId'); - } - - canShowTooltip() { - return false; - } - - syncLayerWithMB() { - throw new Error('Should implement AbstractLayer#syncLayerWithMB'); - } - - getLayerTypeIconName() { - throw new Error('should implement Layer#getLayerTypeIconName'); - } - - isDataLoaded() { - const sourceDataRequest = this.getSourceDataRequest(); - return sourceDataRequest && sourceDataRequest.hasData(); - } - - async getBounds(/* mapFilters: MapFilters */) { - return { - minLon: -180, - maxLon: 180, - minLat: -89, - maxLat: 89, - }; - } - - renderStyleEditor({ onStyleDescriptorChange }) { - const style = this.getStyleForEditing(); - if (!style) { - return null; - } - return style.renderEditor({ layer: this, onStyleDescriptorChange }); - } - - getIndexPatternIds() { - return []; - } - - getQueryableIndexPatternIds() { - return []; - } - - syncVisibilityWithMb(mbMap, mbLayerId) { - mbMap.setLayoutProperty(mbLayerId, 'visibility', this.isVisible() ? 'visible' : 'none'); - } - - getType() { - return this._descriptor.type; - } -} diff --git a/x-pack/plugins/maps/public/layers/layer.tsx b/x-pack/plugins/maps/public/layers/layer.tsx new file mode 100644 index 00000000000000..ce48793e1481bd --- /dev/null +++ b/x-pack/plugins/maps/public/layers/layer.tsx @@ -0,0 +1,490 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* eslint-disable @typescript-eslint/consistent-type-definitions */ + +import { Query } from 'src/plugins/data/public'; +import _ from 'lodash'; +import React, { ReactElement } from 'react'; +import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; +import uuid from 'uuid/v4'; +import { i18n } from '@kbn/i18n'; +import { FeatureCollection } from 'geojson'; +import { DataRequest } from './util/data_request'; +import { + MAX_ZOOM, + MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER, + MIN_ZOOM, + SOURCE_DATA_ID_ORIGIN, +} from '../../common/constants'; +// @ts-ignore +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { copyPersistentState } from '../reducers/util.js'; +import { + LayerDescriptor, + MapExtent, + MapFilters, + StyleDescriptor, +} from '../../common/descriptor_types'; +import { Attribution, ImmutableSourceProperty, ISource } from './sources/source'; +import { SyncContext } from '../actions/map_actions'; +import { IStyle } from './styles/style'; + +export interface ILayer { + getBounds(mapFilters: MapFilters): Promise; + getDataRequest(id: string): DataRequest | undefined; + getDisplayName(source?: ISource): Promise; + getId(): string; + getSourceDataRequest(): DataRequest | undefined; + getSource(): ISource; + getSourceForEditing(): ISource; + syncData(syncContext: SyncContext): void; + supportsElasticsearchFilters(): boolean; + supportsFitToBounds(): Promise; + getAttributions(): Promise; + getLabel(): string; + getCustomIconAndTooltipContent(): IconAndTooltipContent; + getIconAndTooltipContent(zoomLevel: number, isUsingSearch: boolean): IconAndTooltipContent; + renderLegendDetails(): ReactElement | null; + showAtZoomLevel(zoom: number): boolean; + getMinZoom(): number; + getMaxZoom(): number; + getMinSourceZoom(): number; + getAlpha(): number; + getQuery(): Query | null; + getStyle(): IStyle; + getStyleForEditing(): IStyle; + getCurrentStyle(): IStyle; + getImmutableSourceProperties(): Promise; + renderSourceSettingsEditor({ onChange }: { onChange: () => void }): ReactElement | null; + isLayerLoading(): boolean; + hasErrors(): boolean; + getErrors(): string; + toLayerDescriptor(): LayerDescriptor; + getMbLayerIds(): string[]; + ownsMbLayerId(mbLayerId: string): boolean; + ownsMbSourceId(mbSourceId: string): boolean; + canShowTooltip(): boolean; + syncLayerWithMB(mbMap: unknown): void; + getLayerTypeIconName(): string; + isDataLoaded(): boolean; + getIndexPatternIds(): string[]; + getQueryableIndexPatternIds(): string[]; + getType(): string | undefined; + isVisible(): boolean; + cloneDescriptor(): Promise; + renderStyleEditor({ + onStyleDescriptorChange, + }: { + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; + }): ReactElement | null; +} +export type Footnote = { + icon: ReactElement; + message?: string | null; +}; +export type IconAndTooltipContent = { + icon?: ReactElement | null; + tooltipContent?: string | null; + footnotes?: Footnote[] | null; + areResultsTrimmed?: boolean; +}; + +export interface ILayerArguments { + layerDescriptor: LayerDescriptor; + source: ISource; + style: IStyle; +} + +export class AbstractLayer implements ILayer { + protected readonly _descriptor: LayerDescriptor; + protected readonly _source: ISource; + protected readonly _style: IStyle; + protected readonly _dataRequests: DataRequest[]; + + static createDescriptor(options: Partial): LayerDescriptor { + return { + ...options, + sourceDescriptor: options.sourceDescriptor ? options.sourceDescriptor : null, + __dataRequests: _.get(options, '__dataRequests', []), + id: _.get(options, 'id', uuid()), + label: options.label && options.label.length > 0 ? options.label : null, + minZoom: _.get(options, 'minZoom', MIN_ZOOM), + maxZoom: _.get(options, 'maxZoom', MAX_ZOOM), + alpha: _.get(options, 'alpha', 0.75), + visible: _.get(options, 'visible', true), + style: _.get(options, 'style', null), + }; + } + + destroy() { + if (this._source) { + this._source.destroy(); + } + } + + constructor({ layerDescriptor, source, style }: ILayerArguments) { + this._descriptor = AbstractLayer.createDescriptor(layerDescriptor); + this._source = source; + this._style = style; + if (this._descriptor.__dataRequests) { + this._dataRequests = this._descriptor.__dataRequests.map( + dataRequest => new DataRequest(dataRequest) + ); + } else { + this._dataRequests = []; + } + } + + static getBoundDataForSource(mbMap: unknown, sourceId: string): FeatureCollection { + // @ts-ignore + const mbStyle = mbMap.getStyle(); + return mbStyle.sources[sourceId].data; + } + + async cloneDescriptor(): Promise { + // @ts-ignore + const clonedDescriptor = copyPersistentState(this._descriptor); + // layer id is uuid used to track styles/layers in mapbox + clonedDescriptor.id = uuid(); + const displayName = await this.getDisplayName(); + clonedDescriptor.label = `Clone of ${displayName}`; + clonedDescriptor.sourceDescriptor = this.getSource().cloneDescriptor(); + + // todo: remove this + // This should not be in AbstractLayer. It relies on knowledge of VectorLayerDescriptor + // @ts-ignore + if (clonedDescriptor.joins) { + // @ts-ignore + clonedDescriptor.joins.forEach(joinDescriptor => { + // right.id is uuid used to track requests in inspector + // @ts-ignore + joinDescriptor.right.id = uuid(); + }); + } + return clonedDescriptor; + } + + makeMbLayerId(layerNameSuffix: string): string { + return `${this.getId()}${MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER}${layerNameSuffix}`; + } + + isJoinable(): boolean { + return this.getSource().isJoinable(); + } + + supportsElasticsearchFilters(): boolean { + return this.getSource().isESSource(); + } + + async supportsFitToBounds(): Promise { + return await this.getSource().supportsFitToBounds(); + } + + async getDisplayName(source?: ISource): Promise { + if (this._descriptor.label) { + return this._descriptor.label; + } + + const sourceDisplayName = source + ? await source.getDisplayName() + : await this.getSource().getDisplayName(); + return sourceDisplayName || `Layer ${this._descriptor.id}`; + } + + async getAttributions(): Promise { + if (!this.hasErrors()) { + return await this.getSource().getAttributions(); + } + return []; + } + + getStyleForEditing(): IStyle { + return this._style; + } + + getStyle() { + return this._style; + } + + getLabel(): string { + return this._descriptor.label ? this._descriptor.label : ''; + } + + getCustomIconAndTooltipContent(): IconAndTooltipContent { + return { + icon: , + }; + } + + getIconAndTooltipContent(zoomLevel: number, isUsingSearch: boolean): IconAndTooltipContent { + let icon; + let tooltipContent = null; + const footnotes = []; + if (this.hasErrors()) { + icon = ( + + ); + tooltipContent = this.getErrors(); + } else if (this.isLayerLoading()) { + icon = ; + } else if (!this.isVisible()) { + icon = ; + tooltipContent = i18n.translate('xpack.maps.layer.layerHiddenTooltip', { + defaultMessage: `Layer is hidden.`, + }); + } else if (!this.showAtZoomLevel(zoomLevel)) { + const minZoom = this.getMinZoom(); + const maxZoom = this.getMaxZoom(); + icon = ; + tooltipContent = i18n.translate('xpack.maps.layer.zoomFeedbackTooltip', { + defaultMessage: `Layer is visible between zoom levels {minZoom} and {maxZoom}.`, + values: { minZoom, maxZoom }, + }); + } else { + const customIconAndTooltipContent = this.getCustomIconAndTooltipContent(); + if (customIconAndTooltipContent) { + icon = customIconAndTooltipContent.icon; + if (!customIconAndTooltipContent.areResultsTrimmed) { + tooltipContent = customIconAndTooltipContent.tooltipContent; + } else { + footnotes.push({ + icon: , + message: customIconAndTooltipContent.tooltipContent, + }); + } + } + + if (isUsingSearch && this.getQueryableIndexPatternIds().length) { + footnotes.push({ + icon: , + message: i18n.translate('xpack.maps.layer.isUsingSearchMsg', { + defaultMessage: 'Results narrowed by search bar', + }), + }); + } + } + + return { + icon, + tooltipContent, + footnotes, + }; + } + + async hasLegendDetails(): Promise { + return false; + } + + renderLegendDetails(): ReactElement | null { + return null; + } + + getId(): string { + return this._descriptor.id; + } + + getSource(): ISource { + return this._source; + } + + getSourceForEditing(): ISource { + return this._source; + } + + isVisible(): boolean { + return !!this._descriptor.visible; + } + + showAtZoomLevel(zoom: number): boolean { + return zoom >= this.getMinZoom() && zoom <= this.getMaxZoom(); + } + + getMinZoom(): number { + return typeof this._descriptor.minZoom === 'number' ? this._descriptor.minZoom : MIN_ZOOM; + } + + getMaxZoom(): number { + return typeof this._descriptor.maxZoom === 'number' ? this._descriptor.maxZoom : MAX_ZOOM; + } + + getMinSourceZoom(): number { + return this._source.getMinZoom(); + } + + _requiresPrevSourceCleanup(mbMap: unknown) { + return false; + } + + _removeStaleMbSourcesAndLayers(mbMap: unknown) { + if (this._requiresPrevSourceCleanup(mbMap)) { + // @ts-ignore + const mbStyle = mbMap.getStyle(); + // @ts-ignore + mbStyle.layers.forEach(mbLayer => { + // @ts-ignore + if (this.ownsMbLayerId(mbLayer.id)) { + // @ts-ignore + mbMap.removeLayer(mbLayer.id); + } + }); + // @ts-ignore + Object.keys(mbStyle.sources).some(mbSourceId => { + // @ts-ignore + if (this.ownsMbSourceId(mbSourceId)) { + // @ts-ignore + mbMap.removeSource(mbSourceId); + } + }); + } + } + + getAlpha(): number { + return typeof this._descriptor.alpha === 'number' ? this._descriptor.alpha : 1; + } + + getQuery(): Query | null { + return this._descriptor.query ? this._descriptor.query : null; + } + + getCurrentStyle(): IStyle { + return this._style; + } + + async getImmutableSourceProperties() { + const source = this.getSource(); + return await source.getImmutableProperties(); + } + + renderSourceSettingsEditor({ onChange }: { onChange: () => void }) { + const source = this.getSourceForEditing(); + return source.renderSourceSettingsEditor({ onChange }); + } + + getPrevRequestToken(dataId: string): symbol | undefined { + const prevDataRequest = this.getDataRequest(dataId); + if (!prevDataRequest) { + return; + } + + return prevDataRequest.getRequestToken(); + } + + getInFlightRequestTokens(): symbol[] { + if (!this._dataRequests) { + return []; + } + + const requestTokens = this._dataRequests.map(dataRequest => dataRequest.getRequestToken()); + + // Compact removes all the undefineds + // @ts-ignore + return _.compact(requestTokens); + } + + getSourceDataRequest(): DataRequest | undefined { + return this.getDataRequest(SOURCE_DATA_ID_ORIGIN); + } + + getDataRequest(id: string): DataRequest | undefined { + return this._dataRequests.find(dataRequest => dataRequest.getDataId() === id); + } + + isLayerLoading(): boolean { + return this._dataRequests.some(dataRequest => dataRequest.isLoading()); + } + + hasErrors(): boolean { + return _.get(this._descriptor, '__isInErrorState', false); + } + + getErrors(): string { + return this.hasErrors() && this._descriptor.__errorMessage + ? this._descriptor.__errorMessage + : ''; + } + + toLayerDescriptor(): LayerDescriptor { + return this._descriptor; + } + + async syncData(syncContext: SyncContext) { + // no-op by default + } + + getMbLayerIds(): string[] { + throw new Error('Should implement AbstractLayer#getMbLayerIds'); + } + + ownsMbLayerId(layerId: string): boolean { + throw new Error('Should implement AbstractLayer#ownsMbLayerId'); + } + + ownsMbSourceId(sourceId: string): boolean { + throw new Error('Should implement AbstractLayer#ownsMbSourceId'); + } + + canShowTooltip() { + return false; + } + + syncLayerWithMB(mbMap: unknown) { + throw new Error('Should implement AbstractLayer#syncLayerWithMB'); + } + + getLayerTypeIconName(): string { + throw new Error('should implement Layer#getLayerTypeIconName'); + } + + isDataLoaded(): boolean { + const sourceDataRequest = this.getSourceDataRequest(); + return sourceDataRequest ? sourceDataRequest.hasData() : false; + } + + async getBounds(mapFilters: MapFilters): Promise { + return { + minLon: -180, + maxLon: 180, + minLat: -89, + maxLat: 89, + }; + } + + renderStyleEditor({ + onStyleDescriptorChange, + }: { + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; + }): ReactElement | null { + const style = this.getStyleForEditing(); + if (!style) { + return null; + } + return style.renderEditor({ layer: this, onStyleDescriptorChange }); + } + + getIndexPatternIds(): string[] { + return []; + } + + getQueryableIndexPatternIds(): string[] { + return []; + } + + syncVisibilityWithMb(mbMap: unknown, mbLayerId: string) { + // @ts-ignore + mbMap.setLayoutProperty(mbLayerId, 'visibility', this.isVisible() ? 'visible' : 'none'); + } + + getType(): string | undefined { + return this._descriptor.type; + } +} diff --git a/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js index 137513ad7c6127..36f898f7237574 100644 --- a/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js +++ b/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js @@ -21,8 +21,6 @@ import { registerSource } from '../source_registry'; export class GeojsonFileSource extends AbstractVectorSource { static type = SOURCE_TYPES.GEOJSON_FILE; - static isIndexingSource = true; - static createDescriptor(geoJson, name) { // Wrap feature as feature collection if needed let featureCollection; @@ -70,7 +68,7 @@ export class GeojsonFileSource extends AbstractVectorSource { } shouldBeIndexed() { - return GeojsonFileSource.isIndexingSource; + return true; } } diff --git a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts index 96347c444dd5b2..51ee15e7ea5af0 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts @@ -6,7 +6,7 @@ import { AbstractESAggSource } from '../es_agg_source'; import { ESGeoGridSourceDescriptor } from '../../../../common/descriptor_types'; -import { GRID_RESOLUTION, RENDER_AS } from '../../../../common/constants'; +import { GRID_RESOLUTION } from '../../../../common/constants'; export class ESGeoGridSource extends AbstractESAggSource { static createDescriptor({ @@ -14,12 +14,7 @@ export class ESGeoGridSource extends AbstractESAggSource { geoField, requestType, resolution, - }: { - indexPatternId: string; - geoField: string; - requestType: RENDER_AS; - resolution?: GRID_RESOLUTION; - }): ESGeoGridSourceDescriptor; + }: Partial): ESGeoGridSourceDescriptor; constructor(sourceDescriptor: ESGeoGridSourceDescriptor, inspectorAdapters: unknown); diff --git a/x-pack/plugins/maps/public/layers/sources/es_source/es_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_source/es_source.d.ts index 092dc3bf0d5a87..3b41ae6bfd86b2 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_source/es_source.d.ts +++ b/x-pack/plugins/maps/public/layers/sources/es_source/es_source.d.ts @@ -8,6 +8,8 @@ import { AbstractVectorSource } from '../vector_source'; import { IVectorSource } from '../vector_source'; import { IndexPattern, SearchSource } from '../../../../../../../src/plugins/data/public'; import { VectorSourceRequestMeta } from '../../../../common/descriptor_types'; +import { VectorStyle } from '../../styles/vector/vector_style'; +import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; export interface IESSource extends IVectorSource { getId(): string; @@ -20,6 +22,13 @@ export interface IESSource extends IVectorSource { limit: number, initialSearchContext?: object ): Promise; + loadStylePropsMeta( + layerName: string, + style: VectorStyle, + dynamicStyleProps: IDynamicStyleProperty[], + registerCancelCallback: (requestToken: symbol, callback: () => void) => void, + searchFilters: VectorSourceRequestMeta + ): Promise; } export class AbstractESSource extends AbstractVectorSource implements IESSource { @@ -33,4 +42,11 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource limit: number, initialSearchContext?: object ): Promise; + loadStylePropsMeta( + layerName: string, + style: VectorStyle, + dynamicStyleProps: IDynamicStyleProperty[], + registerCancelCallback: (requestToken: symbol, callback: () => void) => void, + searchFilters: VectorSourceRequestMeta + ): Promise; } diff --git a/x-pack/plugins/maps/public/layers/sources/es_term_source/es_term_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_term_source/es_term_source.d.ts index 701bd5e2c8b5e6..248ca2b9212b4d 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_term_source/es_term_source.d.ts +++ b/x-pack/plugins/maps/public/layers/sources/es_term_source/es_term_source.d.ts @@ -9,4 +9,5 @@ import { IESAggSource } from '../es_agg_source'; export interface IESTermSource extends IESAggSource { getTermField(): IField; + hasCompleteConfig(): boolean; } diff --git a/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts b/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts index 0bfda6be722034..a73cfbdc0d0435 100644 --- a/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts +++ b/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts @@ -15,9 +15,9 @@ import { IField } from '../../fields/field'; import { registerSource } from '../source_registry'; import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters'; import { - LayerDescriptor, MapExtent, TiledSingleLayerVectorSourceDescriptor, + VectorLayerDescriptor, VectorSourceRequestMeta, VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; @@ -66,12 +66,15 @@ export class MVTSingleLayerVectorSource extends AbstractSource return []; } - createDefaultLayer(options: LayerDescriptor): TiledVectorLayer { - const layerDescriptor = { + createDefaultLayer(options?: Partial): TiledVectorLayer { + const layerDescriptor: Partial = { sourceDescriptor: this._descriptor, ...options, }; - const normalizedLayerDescriptor = TiledVectorLayer.createDescriptor(layerDescriptor, []); + const normalizedLayerDescriptor: VectorLayerDescriptor = TiledVectorLayer.createDescriptor( + layerDescriptor, + [] + ); const vectorLayerArguments: VectorLayerArguments = { layerDescriptor: normalizedLayerDescriptor, source: this, diff --git a/x-pack/plugins/maps/public/layers/sources/source.d.ts b/x-pack/plugins/maps/public/layers/sources/source.d.ts deleted file mode 100644 index 5a01da02adaae4..00000000000000 --- a/x-pack/plugins/maps/public/layers/sources/source.d.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -/* eslint-disable @typescript-eslint/consistent-type-definitions */ - -import { AbstractSourceDescriptor, LayerDescriptor } from '../../../common/descriptor_types'; -import { ILayer } from '../layer'; - -export type ImmutableSourceProperty = { - label: string; - value: string; -}; - -export type Attribution = { - url: string; - label: string; -}; - -export interface ISource { - createDefaultLayer(options?: LayerDescriptor): ILayer; - destroy(): void; - getDisplayName(): Promise; - getInspectorAdapters(): object; - isFieldAware(): boolean; - isFilterByMapBounds(): boolean; - isGeoGridPrecisionAware(): boolean; - isQueryAware(): boolean; - isRefreshTimerAware(): Promise; - isTimeAware(): Promise; - getImmutableProperties(): Promise; - getAttributions(): Promise; - getMinZoom(): number; - getMaxZoom(): number; -} - -export class AbstractSource implements ISource { - readonly _descriptor: AbstractSourceDescriptor; - constructor(sourceDescriptor: AbstractSourceDescriptor, inspectorAdapters?: object); - - destroy(): void; - createDefaultLayer(options?: LayerDescriptor, mapColors?: string[]): ILayer; - getDisplayName(): Promise; - getInspectorAdapters(): object; - isFieldAware(): boolean; - isFilterByMapBounds(): boolean; - isGeoGridPrecisionAware(): boolean; - isQueryAware(): boolean; - isRefreshTimerAware(): Promise; - isTimeAware(): Promise; - getImmutableProperties(): Promise; - getAttributions(): Promise; - getMinZoom(): number; - getMaxZoom(): number; -} diff --git a/x-pack/plugins/maps/public/layers/sources/source.js b/x-pack/plugins/maps/public/layers/sources/source.js deleted file mode 100644 index fd93daf249b265..00000000000000 --- a/x-pack/plugins/maps/public/layers/sources/source.js +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { copyPersistentState } from '../../reducers/util'; -import { MIN_ZOOM, MAX_ZOOM } from '../../../common/constants'; - -export class AbstractSource { - static isIndexingSource = false; - - static renderEditor() { - throw new Error('Must implement Source.renderEditor'); - } - - static createDescriptor() { - throw new Error('Must implement Source.createDescriptor'); - } - - constructor(descriptor, inspectorAdapters) { - this._descriptor = descriptor; - this._inspectorAdapters = inspectorAdapters; - } - - destroy() {} - - cloneDescriptor() { - return copyPersistentState(this._descriptor); - } - - async supportsFitToBounds() { - return true; - } - - /** - * return list of immutable source properties. - * Immutable source properties are properties that can not be edited by the user. - */ - async getImmutableProperties() { - return []; - } - - getInspectorAdapters() { - return this._inspectorAdapters; - } - - _createDefaultLayerDescriptor() { - throw new Error(`Source#createDefaultLayerDescriptor not implemented`); - } - - createDefaultLayer() { - throw new Error(`Source#createDefaultLayer not implemented`); - } - - async getDisplayName() { - console.warn('Source should implement Source#getDisplayName'); - return ''; - } - - /** - * return attribution for this layer as array of objects with url and label property. - * e.g. [{ url: 'example.com', label: 'foobar' }] - * @return {Promise} - */ - async getAttributions() { - return []; - } - - isFieldAware() { - return false; - } - - isRefreshTimerAware() { - return false; - } - - isGeoGridPrecisionAware() { - return false; - } - - async isTimeAware() { - return false; - } - - getFieldNames() { - return []; - } - - hasCompleteConfig() { - throw new Error(`Source#hasCompleteConfig not implemented`); - } - - renderSourceSettingsEditor() { - return null; - } - - getApplyGlobalQuery() { - return !!this._descriptor.applyGlobalQuery; - } - - getIndexPatternIds() { - return []; - } - - getQueryableIndexPatternIds() { - return []; - } - - isFilterByMapBounds() { - return false; - } - - isQueryAware() { - return false; - } - - getGeoGridPrecision() { - return 0; - } - - isJoinable() { - return false; - } - - shouldBeIndexed() { - return AbstractSource.isIndexingSource; - } - - isESSource() { - return false; - } - - // Returns geo_shape indexed_shape context for spatial quering by pre-indexed shapes - async getPreIndexedShape(/* properties */) { - return null; - } - - // Returns function used to format value - async createFieldFormatter(/* field */) { - return null; - } - - async loadStylePropsMeta() { - throw new Error(`Source#loadStylePropsMeta not implemented`); - } - - async getValueSuggestions(/* field, query */) { - return []; - } - - getMinZoom() { - return MIN_ZOOM; - } - - getMaxZoom() { - return MAX_ZOOM; - } -} diff --git a/x-pack/plugins/maps/public/layers/sources/source.ts b/x-pack/plugins/maps/public/layers/sources/source.ts new file mode 100644 index 00000000000000..1cd84010159abf --- /dev/null +++ b/x-pack/plugins/maps/public/layers/sources/source.ts @@ -0,0 +1,195 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/consistent-type-definitions */ + +import { ReactElement } from 'react'; + +import { Adapters } from 'src/plugins/inspector/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +// @ts-ignore +import { copyPersistentState } from '../../reducers/util'; + +import { LayerDescriptor, SourceDescriptor } from '../../../common/descriptor_types'; +import { ILayer } from '../layer'; +import { IField } from '../fields/field'; +import { MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; + +export type ImmutableSourceProperty = { + label: string; + value: string; +}; + +export type Attribution = { + url: string; + label: string; +}; + +export type PreIndexedShape = { + index: string; + id: string | number; + path: string; +}; + +export type FieldFormatter = (value: string | number | null | undefined | boolean) => string; + +export interface ISource { + createDefaultLayer(options?: Partial): ILayer; + destroy(): void; + getDisplayName(): Promise; + getInspectorAdapters(): Adapters | undefined; + isFieldAware(): boolean; + isFilterByMapBounds(): boolean; + isGeoGridPrecisionAware(): boolean; + isQueryAware(): boolean; + isRefreshTimerAware(): boolean; + isTimeAware(): Promise; + getImmutableProperties(): Promise; + getAttributions(): Promise; + isESSource(): boolean; + renderSourceSettingsEditor({ onChange }: { onChange: () => void }): ReactElement | null; + supportsFitToBounds(): Promise; + isJoinable(): boolean; + cloneDescriptor(): SourceDescriptor; + getFieldNames(): string[]; + getApplyGlobalQuery(): boolean; + getIndexPatternIds(): string[]; + getQueryableIndexPatternIds(): string[]; + getGeoGridPrecision(zoom: number): number; + shouldBeIndexed(): boolean; + getPreIndexedShape(): Promise; + createFieldFormatter(field: IField): Promise; + getValueSuggestions(field: IField, query: string): Promise; + getMinZoom(): number; + getMaxZoom(): number; +} + +export class AbstractSource implements ISource { + readonly _descriptor: SourceDescriptor; + readonly _inspectorAdapters?: Adapters | undefined; + + constructor(descriptor: SourceDescriptor, inspectorAdapters?: Adapters) { + this._descriptor = descriptor; + this._inspectorAdapters = inspectorAdapters; + } + + destroy(): void {} + + cloneDescriptor(): SourceDescriptor { + // @ts-ignore + return copyPersistentState(this._descriptor); + } + + async supportsFitToBounds(): Promise { + return true; + } + + /** + * return list of immutable source properties. + * Immutable source properties are properties that can not be edited by the user. + */ + async getImmutableProperties(): Promise { + return []; + } + + getInspectorAdapters(): Adapters | undefined { + return this._inspectorAdapters; + } + + createDefaultLayer(options?: Partial): ILayer { + throw new Error(`Source#createDefaultLayer not implemented`); + } + + async getDisplayName(): Promise { + return ''; + } + + async getAttributions(): Promise { + return []; + } + + isFieldAware(): boolean { + return false; + } + + isRefreshTimerAware(): boolean { + return false; + } + + isGeoGridPrecisionAware(): boolean { + return false; + } + + isQueryAware(): boolean { + return false; + } + + getFieldNames(): string[] { + return []; + } + + renderSourceSettingsEditor() { + return null; + } + + getApplyGlobalQuery(): boolean { + return !!this._descriptor.applyGlobalQuery; + } + + getIndexPatternIds(): string[] { + return []; + } + + getQueryableIndexPatternIds(): string[] { + return []; + } + + getGeoGridPrecision(zoom: number): number { + return 0; + } + + isJoinable(): boolean { + return false; + } + + shouldBeIndexed(): boolean { + return false; + } + + isESSource(): boolean { + return false; + } + + // Returns geo_shape indexed_shape context for spatial quering by pre-indexed shapes + async getPreIndexedShape(/* properties */): Promise { + return null; + } + + // Returns function used to format value + async createFieldFormatter(field: IField): Promise { + return null; + } + + async getValueSuggestions(field: IField, query: string): Promise { + return []; + } + + async isTimeAware(): Promise { + return false; + } + + isFilterByMapBounds(): boolean { + return false; + } + + getMinZoom() { + return MIN_ZOOM; + } + + getMaxZoom() { + return MAX_ZOOM; + } +} diff --git a/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.ts b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.ts index 8b64480f92961a..77f8d88a8c0ab3 100644 --- a/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.ts +++ b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.ts @@ -13,6 +13,7 @@ import { AbstractTMSSource } from '../tms_source'; import { LayerDescriptor, XYZTMSSourceDescriptor } from '../../../../common/descriptor_types'; import { Attribution, ImmutableSourceProperty } from '../source'; import { XYZTMSSourceConfig } from './xyz_tms_editor'; +import { ILayer } from '../../layer'; export const sourceTitle = i18n.translate('xpack.maps.source.ems_xyzTitle', { defaultMessage: 'Tile Map Service', @@ -48,7 +49,7 @@ export class XYZTMSSource extends AbstractTMSSource { ]; } - createDefaultLayer(options?: LayerDescriptor): TileLayer { + createDefaultLayer(options?: LayerDescriptor): ILayer { const layerDescriptor: LayerDescriptor = TileLayer.createDescriptor({ sourceDescriptor: this._descriptor, ...options, diff --git a/x-pack/plugins/maps/public/layers/styles/abstract_style.js b/x-pack/plugins/maps/public/layers/styles/abstract_style.js deleted file mode 100644 index 3e7a3dbf7ed206..00000000000000 --- a/x-pack/plugins/maps/public/layers/styles/abstract_style.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export class AbstractStyle { - getDescriptorWithMissingStylePropsRemoved(/* nextOrdinalFields */) { - return { - hasChanges: false, - }; - } - - async pluckStyleMetaFromSourceDataRequest(/* sourceDataRequest */) { - return {}; - } - - getDescriptor() { - return this._descriptor; - } - - renderEditor(/* { layer, onStyleDescriptorChange } */) { - return null; - } - - getSourceFieldNames() { - return []; - } -} diff --git a/x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js b/x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js index d769fe0da9ec2b..1fa24943c5e516 100644 --- a/x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js +++ b/x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js @@ -5,7 +5,7 @@ */ import React from 'react'; -import { AbstractStyle } from '../abstract_style'; +import { AbstractStyle } from '../style'; import { HeatmapStyleEditor } from './components/heatmap_style_editor'; import { HeatmapLegend } from './components/legend/heatmap_legend'; import { DEFAULT_HEATMAP_COLOR_RAMP_NAME } from './components/heatmap_constants'; diff --git a/x-pack/plugins/maps/public/layers/styles/style.ts b/x-pack/plugins/maps/public/layers/styles/style.ts new file mode 100644 index 00000000000000..38fdc36904412a --- /dev/null +++ b/x-pack/plugins/maps/public/layers/styles/style.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ReactElement } from 'react'; +import { StyleDescriptor, StyleMetaDescriptor } from '../../../common/descriptor_types'; +import { ILayer } from '../layer'; +import { IField } from '../fields/field'; +import { DataRequest } from '../util/data_request'; + +export interface IStyle { + getDescriptor(): StyleDescriptor | null; + getDescriptorWithMissingStylePropsRemoved( + nextFields: IField[] + ): { hasChanges: boolean; nextStyleDescriptor?: StyleDescriptor }; + pluckStyleMetaFromSourceDataRequest(sourceDataRequest: DataRequest): StyleMetaDescriptor; + renderEditor({ + layer, + onStyleDescriptorChange, + }: { + layer: ILayer; + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; + }): ReactElement | null; + getSourceFieldNames(): string[]; +} + +export class AbstractStyle implements IStyle { + readonly _descriptor: StyleDescriptor | null; + + constructor(descriptor: StyleDescriptor | null) { + this._descriptor = descriptor; + } + + getDescriptorWithMissingStylePropsRemoved( + nextFields: IField[] + ): { hasChanges: boolean; nextStyleDescriptor?: StyleDescriptor } { + return { + hasChanges: false, + }; + } + + pluckStyleMetaFromSourceDataRequest(sourceDataRequest: DataRequest): StyleMetaDescriptor { + return { fieldMeta: {} }; + } + + getDescriptor(): StyleDescriptor | null { + return this._descriptor; + } + + renderEditor(/* { layer, onStyleDescriptorChange } */) { + return null; + } + + getSourceFieldNames(): string[] { + return []; + } +} diff --git a/x-pack/plugins/maps/public/layers/styles/tile/tile_style.ts b/x-pack/plugins/maps/public/layers/styles/tile/tile_style.ts new file mode 100644 index 00000000000000..f658d0821edf2c --- /dev/null +++ b/x-pack/plugins/maps/public/layers/styles/tile/tile_style.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AbstractStyle } from '../style'; +import { LAYER_STYLE_TYPE } from '../../../../common/constants'; + +export class TileStyle extends AbstractStyle { + constructor() { + super({ + type: LAYER_STYLE_TYPE.TILE, + }); + } +} diff --git a/x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts index e010d5ac7d7a31..762322b8e09f9a 100644 --- a/x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts +++ b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts @@ -7,24 +7,23 @@ import { IStyleProperty } from './properties/style_property'; import { IDynamicStyleProperty } from './properties/dynamic_style_property'; import { IVectorLayer } from '../../vector_layer'; import { IVectorSource } from '../../sources/vector_source'; +import { AbstractStyle, IStyle } from '../style'; import { VectorStyleDescriptor, VectorStylePropertiesDescriptor, } from '../../../../common/descriptor_types'; -export interface IVectorStyle { +export interface IVectorStyle extends IStyle { getAllStyleProperties(): IStyleProperty[]; - getDescriptor(): VectorStyleDescriptor; getDynamicPropertiesArray(): IDynamicStyleProperty[]; getSourceFieldNames(): string[]; } -export class VectorStyle implements IVectorStyle { +export class VectorStyle extends AbstractStyle implements IVectorStyle { static createDescriptor(properties: VectorStylePropertiesDescriptor): VectorStyleDescriptor; static createDefaultStyleProperties(mapColors: string[]): VectorStylePropertiesDescriptor; constructor(descriptor: VectorStyleDescriptor, source: IVectorSource, layer: IVectorLayer); getSourceFieldNames(): string[]; getAllStyleProperties(): IStyleProperty[]; - getDescriptor(): VectorStyleDescriptor; getDynamicPropertiesArray(): IDynamicStyleProperty[]; } diff --git a/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js index b044c98d44d417..5a4edd9c93a058 100644 --- a/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js @@ -8,7 +8,7 @@ import _ from 'lodash'; import React from 'react'; import { VectorStyleEditor } from './components/vector_style_editor'; import { getDefaultProperties, LINE_STYLES, POLYGON_STYLES } from './vector_style_defaults'; -import { AbstractStyle } from '../abstract_style'; +import { AbstractStyle } from '../style'; import { GEO_JSON_TYPE, FIELD_ORIGIN, @@ -60,6 +60,7 @@ export class VectorStyle extends AbstractStyle { constructor(descriptor = {}, source, layer) { super(); + descriptor = descriptor === null ? {} : descriptor; this._source = source; this._layer = layer; this._descriptor = { diff --git a/x-pack/plugins/maps/public/layers/tile_layer.d.ts b/x-pack/plugins/maps/public/layers/tile_layer.d.ts index 53e8c388ee4c2e..8a1ef0f172717d 100644 --- a/x-pack/plugins/maps/public/layers/tile_layer.d.ts +++ b/x-pack/plugins/maps/public/layers/tile_layer.d.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AbstractLayer, ILayerArguments } from './layer'; +import { AbstractLayer } from './layer'; import { ITMSSource } from './sources/tms_source'; import { LayerDescriptor } from '../../common/descriptor_types'; -interface ITileLayerArguments extends ILayerArguments { +interface ITileLayerArguments { source: ITMSSource; layerDescriptor: LayerDescriptor; } diff --git a/x-pack/plugins/maps/public/layers/tile_layer.js b/x-pack/plugins/maps/public/layers/tile_layer.js index 2ac60e12d137a7..baded3c287637c 100644 --- a/x-pack/plugins/maps/public/layers/tile_layer.js +++ b/x-pack/plugins/maps/public/layers/tile_layer.js @@ -6,7 +6,8 @@ import { AbstractLayer } from './layer'; import _ from 'lodash'; -import { SOURCE_DATA_ID_ORIGIN, LAYER_TYPE } from '../../common/constants'; +import { SOURCE_DATA_ID_ORIGIN, LAYER_TYPE, LAYER_STYLE_TYPE } from '../../common/constants'; +import { TileStyle } from './styles/tile/tile_style'; export class TileLayer extends AbstractLayer { static type = LAYER_TYPE.TILE; @@ -15,9 +16,14 @@ export class TileLayer extends AbstractLayer { const tileLayerDescriptor = super.createDescriptor(options, mapColors); tileLayerDescriptor.type = TileLayer.type; tileLayerDescriptor.alpha = _.get(options, 'alpha', 1); + tileLayerDescriptor.style = { type: LAYER_STYLE_TYPE.TILE }; return tileLayerDescriptor; } + constructor({ source, layerDescriptor }) { + super({ source, layerDescriptor, style: new TileStyle() }); + } + async syncData({ startLoading, stopLoading, onLoadError, dataFilters }) { if (!this.isVisible() || !this.showAtZoomLevel(dataFilters.zoom)) { return; diff --git a/x-pack/plugins/maps/public/layers/tile_layer.test.ts b/x-pack/plugins/maps/public/layers/tile_layer.test.ts index f8c2fd9db60fa5..a7e8be9fc4b460 100644 --- a/x-pack/plugins/maps/public/layers/tile_layer.test.ts +++ b/x-pack/plugins/maps/public/layers/tile_layer.test.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TileLayer } from './tile_layer'; +// eslint-disable-next-line max-classes-per-file +import { ITileLayerArguments, TileLayer } from './tile_layer'; import { SOURCE_TYPES } from '../../common/constants'; import { XYZTMSSourceDescriptor } from '../../common/descriptor_types'; import { ITMSSource, AbstractTMSSource } from './sources/tms_source'; @@ -38,10 +39,13 @@ class MockTileSource extends AbstractTMSSource implements ITMSSource { describe('TileLayer', () => { it('should use display-label from source', async () => { const source = new MockTileSource(sourceDescriptor); - const layer: ILayer = new TileLayer({ + + const args: ITileLayerArguments = { source, layerDescriptor: { id: 'layerid', sourceDescriptor }, - }); + }; + + const layer: ILayer = new TileLayer(args); expect(await source.getDisplayName()).toEqual(await layer.getDisplayName()); }); diff --git a/x-pack/plugins/maps/public/layers/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/layers/tiled_vector_layer.tsx index c47cae5641e563..06c5ef579b2212 100644 --- a/x-pack/plugins/maps/public/layers/tiled_vector_layer.tsx +++ b/x-pack/plugins/maps/public/layers/tiled_vector_layer.tsx @@ -20,7 +20,7 @@ export class TiledVectorLayer extends VectorLayer { static type = LAYER_TYPE.TILED_VECTOR; static createDescriptor( - descriptor: VectorLayerDescriptor, + descriptor: Partial, mapColors: string[] ): VectorLayerDescriptor { const layerDescriptor = super.createDescriptor(descriptor, mapColors); diff --git a/x-pack/plugins/maps/public/layers/vector_layer.d.ts b/x-pack/plugins/maps/public/layers/vector_layer.d.ts index 3d5b8054ff3fd8..efc1f3011c687e 100644 --- a/x-pack/plugins/maps/public/layers/vector_layer.d.ts +++ b/x-pack/plugins/maps/public/layers/vector_layer.d.ts @@ -19,7 +19,7 @@ import { IVectorStyle } from './styles/vector/vector_style'; import { IField } from './fields/field'; import { SyncContext } from '../actions/map_actions'; -type VectorLayerArguments = { +export type VectorLayerArguments = { source: IVectorSource; joins?: IJoin[]; layerDescriptor: VectorLayerDescriptor; @@ -33,14 +33,12 @@ export interface IVectorLayer extends ILayer { } export class VectorLayer extends AbstractLayer implements IVectorLayer { + protected readonly _style: IVectorStyle; static createDescriptor( options: Partial, mapColors?: string[] ): VectorLayerDescriptor; - protected readonly _source: IVectorSource; - protected readonly _style: IVectorStyle; - constructor(options: VectorLayerArguments); getLayerTypeIconName(): string; getFields(): Promise; diff --git a/x-pack/plugins/maps/public/layers/vector_layer.js b/x-pack/plugins/maps/public/layers/vector_layer.js index c5947a63587ea8..17b7f8152d76da 100644 --- a/x-pack/plugins/maps/public/layers/vector_layer.js +++ b/x-pack/plugins/maps/public/layers/vector_layer.js @@ -484,6 +484,8 @@ export class VectorLayer extends AbstractLayer { try { startLoading(dataRequestId, requestToken, nextMeta); const layerName = await this.getDisplayName(source); + + //todo: cast source to ESSource when migrating to TS const styleMeta = await source.loadStylePropsMeta( layerName, style, diff --git a/x-pack/plugins/maps/public/layers/vector_tile_layer.js b/x-pack/plugins/maps/public/layers/vector_tile_layer.js index c620ec6c56dc3a..fc7812a2c86c70 100644 --- a/x-pack/plugins/maps/public/layers/vector_tile_layer.js +++ b/x-pack/plugins/maps/public/layers/vector_tile_layer.js @@ -6,7 +6,7 @@ import { TileLayer } from './tile_layer'; import _ from 'lodash'; -import { SOURCE_DATA_ID_ORIGIN, LAYER_TYPE } from '../../common/constants'; +import { SOURCE_DATA_ID_ORIGIN, LAYER_TYPE, LAYER_STYLE_TYPE } from '../../common/constants'; import { isRetina } from '../meta'; import { addSpriteSheetToMapFromImageData, @@ -28,6 +28,7 @@ export class VectorTileLayer extends TileLayer { const tileLayerDescriptor = super.createDescriptor(options); tileLayerDescriptor.type = VectorTileLayer.type; tileLayerDescriptor.alpha = _.get(options, 'alpha', 1); + tileLayerDescriptor.style = { type: LAYER_STYLE_TYPE.TILE }; return tileLayerDescriptor; }