From 6282389a9fc31717e4f8f46d48e5081d65a981c6 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 10 Nov 2020 10:15:35 -0700 Subject: [PATCH] [Maps] show icon when layer is filtered by time and allow layers to ignore global time range (#83006) (#83067) * [Maps] show icon when layer is filtered by time and allow layers to ignore global time range * show icon if layer is narrowed by time fitler * tslint * apply global time to source check box * apply global time to join check box * tslint and jest expect updates * one more tslint fix * tslint, fix apm jest test, update time filter icon when disabling applyGlobalTime Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__tests__/__mocks__/regions_layer.mock.ts | 6 +- .../VisitorBreakdownMap/useLayerList.ts | 4 +- .../data_request_descriptor_types.ts | 10 +- .../source_descriptor_types.ts | 3 +- .../create_choropleth_layer_descriptor.ts | 2 + .../create_region_map_layer_descriptor.ts | 2 + .../maps/public/classes/layers/layer.test.ts | 2 + .../maps/public/classes/layers/layer.tsx | 83 +----- .../create_layer_descriptor.test.ts | 4 + .../observability/create_layer_descriptor.ts | 2 + .../security/create_layer_descriptors.test.ts | 6 + .../layers/vector_layer/vector_layer.tsx | 19 ++ .../es_agg_source/es_agg_source.test.ts | 12 +- .../es_geo_grid_source.test.ts | 1 + .../es_search_source/es_search_source.test.ts | 1 + .../classes/sources/es_source/es_source.ts | 22 +- .../sources/es_term_source/es_term_source.ts | 18 +- .../kibana_regionmap_source.ts | 4 - .../mvt_single_layer_vector_source.tsx | 4 - .../maps/public/classes/sources/source.ts | 7 +- .../sources/vector_source/vector_source.tsx | 2 +- .../public/classes/util/can_skip_fetch.ts | 7 +- .../components/global_time_checkbox.tsx | 32 ++ .../filter_editor/filter_editor.js | 29 +- .../layer_panel/join_editor/join_editor.tsx | 1 + .../layer_panel/join_editor/resources/join.js | 71 +++-- .../toc_entry_actions_popover.test.tsx.snap | 281 +++++++----------- .../toc_entry_actions_popover/index.ts | 10 +- .../toc_entry_actions_popover.test.tsx | 15 - .../toc_entry_actions_popover.tsx | 65 +--- .../toc_entry/toc_entry_button/index.ts | 20 ++ .../toc_entry_button/toc_entry_button.tsx | 184 ++++++++++++ 32 files changed, 553 insertions(+), 376 deletions(-) create mode 100644 x-pack/plugins/maps/public/components/global_time_checkbox.tsx create mode 100644 x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/index.ts create mode 100644 x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__mocks__/regions_layer.mock.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__mocks__/regions_layer.mock.ts index e5643325833759..6d259a5a2e48c7 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__mocks__/regions_layer.mock.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__mocks__/regions_layer.mock.ts @@ -21,6 +21,8 @@ export const mockLayerList = [ { leftField: 'iso2', right: { + applyGlobalQuery: true, + applyGlobalTime: true, type: 'ES_TERM_SOURCE', id: '3657625d-17b0-41ef-99ba-3a2b2938655c', indexPatternTitle: 'apm-*', @@ -38,7 +40,6 @@ export const mockLayerList = [ }, ], indexPatternId: 'apm_static_index_pattern_id', - applyGlobalQuery: true, }, }, ], @@ -46,7 +47,6 @@ export const mockLayerList = [ type: 'EMS_FILE', id: 'world_countries', tooltipProperties: ['name'], - applyGlobalQuery: true, }, style: { type: 'VECTOR', @@ -96,6 +96,8 @@ export const mockLayerList = [ { leftField: 'region_iso_code', right: { + applyGlobalQuery: true, + applyGlobalTime: true, type: 'ES_TERM_SOURCE', id: 'e62a1b9c-d7ff-4fd4-a0f6-0fdc44bb9e41', indexPatternTitle: 'apm-*', 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 bc45d58329f496..a1cdf7bb646e5d 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 @@ -43,6 +43,7 @@ const ES_TERM_SOURCE_COUNTRY: ESTermSourceDescriptor = { ], indexPatternId: APM_STATIC_INDEX_PATTERN_ID, applyGlobalQuery: true, + applyGlobalTime: true, }; const ES_TERM_SOURCE_REGION: ESTermSourceDescriptor = { @@ -56,6 +57,8 @@ const ES_TERM_SOURCE_REGION: ESTermSourceDescriptor = { language: 'kuery', }, indexPatternId: APM_STATIC_INDEX_PATTERN_ID, + applyGlobalQuery: true, + applyGlobalTime: true, }; const getWhereQuery = (serviceName: string) => { @@ -158,7 +161,6 @@ export function useLayerList() { type: 'EMS_FILE', id: 'world_countries', tooltipProperties: [COUNTRY_NAME], - applyGlobalQuery: true, }, style: getLayerStyle(TRANSACTION_DURATION_COUNTRY), id: 'e8d1d974-eed8-462f-be2c-f0004b7619b2', diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts index fc691f339f34a6..68fc784182a775 100644 --- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts @@ -38,17 +38,17 @@ export type VectorSourceSyncMeta = ESSearchSourceSyncMeta | ESGeoGridSourceSyncM export type VectorSourceRequestMeta = MapFilters & { applyGlobalQuery: boolean; + applyGlobalTime: boolean; fieldNames: string[]; geogridPrecision?: number; sourceQuery?: MapQuery; sourceMeta: VectorSourceSyncMeta; }; -export type VectorJoinSourceRequestMeta = MapFilters & { - applyGlobalQuery: boolean; - fieldNames: string[]; - sourceQuery?: Query; -}; +export type VectorJoinSourceRequestMeta = Omit< + VectorSourceRequestMeta, + 'geogridPrecision' | 'sourceMeta' +> & { sourceQuery?: Query }; export type VectorStyleRequestMeta = MapFilters & { dynamicStyleFields: string[]; diff --git a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts index 3dc90a12513fdd..a6afbe4d55f9b1 100644 --- a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts @@ -18,7 +18,6 @@ export type AttributionDescriptor = { export type AbstractSourceDescriptor = { id?: string; type: string; - applyGlobalQuery?: boolean; }; export type EMSTMSSourceDescriptor = AbstractSourceDescriptor & { @@ -37,6 +36,8 @@ export type AbstractESSourceDescriptor = AbstractSourceDescriptor & { id: string; indexPatternId: string; geoField?: string; + applyGlobalQuery: boolean; + applyGlobalTime: boolean; }; export type AggDescriptor = { diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts index 61fb6ef54c2073..43d1d39c170c01 100644 --- a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts @@ -61,6 +61,8 @@ function createChoroplethLayerDescriptor({ indexPatternTitle: rightIndexPatternTitle, term: rightTermField, metrics: [metricsDescriptor], + applyGlobalQuery: true, + applyGlobalTime: true, }, }, ], diff --git a/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts index 8bf078806cfbc1..8830831b8b6566 100644 --- a/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts @@ -87,6 +87,8 @@ export function createRegionMapLayerDescriptor({ indexPatternTitle: indexPatternTitle ? indexPatternTitle : indexPatternId, term: termsFieldName, metrics: [metricsDescriptor], + applyGlobalQuery: true, + applyGlobalTime: true, }, }, ], 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 76df7c2af840a7..e669ddf13e5acd 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/layer.test.ts @@ -74,6 +74,8 @@ describe('cloneDescriptor', () => { metrics: [{ type: AGG_TYPE.COUNT }], term: 'myTermField', type: 'joinSource', + applyGlobalQuery: true, + applyGlobalTime: true, }, }, ], diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index f75dae84b1723f..7c76df7f6e877c 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -8,9 +8,8 @@ import { Query } from 'src/plugins/data/public'; import _ from 'lodash'; import React, { ReactElement } from 'react'; -import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; import uuid from 'uuid/v4'; -import { i18n } from '@kbn/i18n'; import { FeatureCollection } from 'geojson'; import { DataRequest } from '../util/data_request'; import { @@ -49,8 +48,6 @@ export interface ILayer { supportsFitToBounds(): Promise; getAttributions(): Promise; getLabel(): string; - getCustomIconAndTooltipContent(): CustomIconAndTooltipContent; - getIconAndTooltipContent(zoomLevel: number, isUsingSearch: boolean): IconAndTooltipContent; renderLegendDetails(): ReactElement | null; showAtZoomLevel(zoom: number): boolean; getMinZoom(): number; @@ -64,6 +61,7 @@ export interface ILayer { getImmutableSourceProperties(): Promise; renderSourceSettingsEditor({ onChange }: SourceEditorArgs): ReactElement | null; isLayerLoading(): boolean; + isFilteredByGlobalTime(): Promise; hasErrors(): boolean; getErrors(): string; getMbLayerIds(): string[]; @@ -93,16 +91,9 @@ export interface ILayer { getJoinsDisabledReason(): string | null; isFittable(): Promise; getLicensedFeatures(): Promise; + getCustomIconAndTooltipContent(): CustomIconAndTooltipContent; } -export type Footnote = { - icon: ReactElement; - message?: string | null; -}; -export type IconAndTooltipContent = { - icon?: ReactElement | null; - tooltipContent?: string | null; - footnotes: Footnote[]; -}; + export type CustomIconAndTooltipContent = { icon: ReactElement | null; tooltipContent?: string | null; @@ -237,6 +228,10 @@ export class AbstractLayer implements ILayer { return (await this.supportsFitToBounds()) && this.isVisible(); } + async isFilteredByGlobalTime(): Promise { + return false; + } + async getDisplayName(source?: ISource): Promise { if (this._descriptor.label) { return this._descriptor.label; @@ -277,68 +272,6 @@ export class AbstractLayer implements ILayer { }; } - 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; } diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts index 66eba3a5398010..e2678ee218b4bd 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts @@ -39,6 +39,8 @@ describe('createLayerDescriptor', () => { { leftField: 'iso2', right: { + applyGlobalQuery: true, + applyGlobalTime: true, id: '12345', indexPatternId: 'apm_static_index_pattern_id', indexPatternTitle: 'apm-*', @@ -176,6 +178,7 @@ describe('createLayerDescriptor', () => { }, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, geoField: 'client.geo.location', id: '12345', indexPatternId: 'apm_static_index_pattern_id', @@ -218,6 +221,7 @@ describe('createLayerDescriptor', () => { }, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, geoField: 'client.geo.location', id: '12345', indexPatternId: 'apm_static_index_pattern_id', diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts index bdd86d78b53000..5dbf07ed2a535f 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts @@ -177,6 +177,8 @@ export function createLayerDescriptor({ term: 'client.geo.country_iso_code', metrics: [metricsDescriptor], whereQuery: apmSourceQuery, + applyGlobalQuery: true, + applyGlobalTime: true, }, }, ], diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts index 22456527491ebb..fe04678deacae6 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts @@ -33,6 +33,7 @@ describe('createLayerDescriptor', () => { minZoom: 0, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, filterByMapBounds: true, geoField: 'client.geo.location', id: '12345', @@ -140,6 +141,7 @@ describe('createLayerDescriptor', () => { minZoom: 0, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, filterByMapBounds: true, geoField: 'server.geo.location', id: '12345', @@ -247,6 +249,7 @@ describe('createLayerDescriptor', () => { minZoom: 0, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, destGeoField: 'server.geo.location', id: '12345', indexPatternId: 'id', @@ -366,6 +369,7 @@ describe('createLayerDescriptor', () => { minZoom: 0, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, filterByMapBounds: true, geoField: 'source.geo.location', id: '12345', @@ -473,6 +477,7 @@ describe('createLayerDescriptor', () => { minZoom: 0, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, filterByMapBounds: true, geoField: 'destination.geo.location', id: '12345', @@ -580,6 +585,7 @@ describe('createLayerDescriptor', () => { minZoom: 0, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, destGeoField: 'destination.geo.location', id: '12345', indexPatternId: 'id', 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 b9d78348962458..b4c0098bb13389 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 @@ -254,6 +254,7 @@ export class VectorLayer extends AbstractLayer { timeFilters: searchFilters.timeFilters, filters: searchFilters.filters, applyGlobalQuery: searchFilters.applyGlobalQuery, + applyGlobalTime: searchFilters.applyGlobalTime, }; let bounds = null; @@ -315,6 +316,22 @@ export class VectorLayer extends AbstractLayer { return indexPatternIds; } + async isFilteredByGlobalTime(): Promise { + if (this.getSource().getApplyGlobalTime() && (await this.getSource().isTimeAware())) { + return true; + } + + const joinPromises = this.getValidJoins().map(async (join) => { + return ( + join.getRightJoinSource().getApplyGlobalTime() && + (await join.getRightJoinSource().isTimeAware()) + ); + }); + return (await Promise.all(joinPromises)).some((isJoinTimeAware: boolean) => { + return isJoinTimeAware; + }); + } + async _syncJoin({ join, startLoading, @@ -331,6 +348,7 @@ export class VectorLayer extends AbstractLayer { fieldNames: joinSource.getFieldNames(), sourceQuery: joinSource.getWhereQuery(), applyGlobalQuery: joinSource.getApplyGlobalQuery(), + applyGlobalTime: joinSource.getApplyGlobalTime(), }; const prevDataRequest = this.getDataRequest(sourceDataId); @@ -403,6 +421,7 @@ export class VectorLayer extends AbstractLayer { geogridPrecision: source.getGeoGridPrecision(dataFilters.zoom), sourceQuery: sourceQuery ? sourceQuery : undefined, applyGlobalQuery: source.getApplyGlobalQuery(), + applyGlobalTime: source.getApplyGlobalTime(), sourceMeta: source.getSyncMeta(), }; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts index cf0170ab7f1bdd..d31e8366e4ef4d 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts @@ -31,7 +31,17 @@ const metricExamples = [ class TestESAggSource extends AbstractESAggSource { constructor(metrics: AggDescriptor[]) { - super({ type: 'test', id: 'foobar', indexPatternId: 'foobarid', metrics }, []); + super( + { + type: 'test', + id: 'foobar', + indexPatternId: 'foobarid', + metrics, + applyGlobalQuery: true, + applyGlobalTime: true, + }, + [] + ); } } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts index 3b1cf3293c0d3d..8ac014c820ace3 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts @@ -151,6 +151,7 @@ describe('ESGeoGridSource', () => { }, extent, applyGlobalQuery: true, + applyGlobalTime: true, fieldNames: [], buffer: extent, sourceQuery: { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts index e7099115ffe5ed..ec14a80ae761e9 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts @@ -103,6 +103,7 @@ describe('ESSearchSource', () => { }, sourceMeta: null, applyGlobalQuery: true, + applyGlobalTime: true, }; it('Should only include required props', async () => { diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts index 68b6b131978ea5..bef0b8c6ea7aff 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts @@ -82,8 +82,9 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource type: isValidStringConfig(descriptor.type) ? descriptor.type! : '', indexPatternId: descriptor.indexPatternId!, applyGlobalQuery: - // backfill old _.get usage - typeof descriptor.applyGlobalQuery !== 'undefined' ? !!descriptor.applyGlobalQuery : true, + typeof descriptor.applyGlobalQuery !== 'undefined' ? descriptor.applyGlobalQuery : true, + applyGlobalTime: + typeof descriptor.applyGlobalTime !== 'undefined' ? descriptor.applyGlobalTime : true, }; } @@ -96,6 +97,14 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource return this._descriptor.id; } + getApplyGlobalQuery(): boolean { + return this._descriptor.applyGlobalQuery; + } + + getApplyGlobalTime(): boolean { + return this._descriptor.applyGlobalTime; + } + isFieldAware(): boolean { return true; } @@ -203,10 +212,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource initialSearchContext?: object ): Promise { const indexPattern = await this.getIndexPattern(); - const isTimeAware = await this.isTimeAware(); - const applyGlobalQuery = - typeof searchFilters.applyGlobalQuery === 'boolean' ? searchFilters.applyGlobalQuery : true; - const globalFilters: Filter[] = applyGlobalQuery ? searchFilters.filters : []; + const globalFilters: Filter[] = searchFilters.applyGlobalQuery ? searchFilters.filters : []; const allFilters: Filter[] = [...globalFilters]; if (this.isFilterByMapBounds() && 'buffer' in searchFilters && searchFilters.buffer) { // buffer can be empty @@ -226,7 +232,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource // @ts-expect-error allFilters.push(extentFilter); } - if (isTimeAware) { + if (searchFilters.applyGlobalTime && (await this.isTimeAware())) { const filter = getTimeFilter().createFilter(indexPattern, searchFilters.timeFilters); if (filter) { allFilters.push(filter); @@ -238,7 +244,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource searchSource.setField('index', indexPattern); searchSource.setField('size', limit); searchSource.setField('filter', allFilters); - if (applyGlobalQuery) { + if (searchFilters.applyGlobalQuery) { searchSource.setField('query', searchFilters.query); } diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts index 3220253436168f..8ef50a1cb7a1c9 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts @@ -28,6 +28,7 @@ import { } from '../../../../common/descriptor_types'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; import { PropertiesMap } from '../../../../common/elasticsearch_util'; +import { isValidStringConfig } from '../../util/valid_string_config'; const TERMS_AGG_NAME = 'join'; const TERMS_BUCKET_KEYS_TO_IGNORE = ['key', 'doc_count']; @@ -48,12 +49,25 @@ export function extractPropertiesMap(rawEsData: any, countPropertyName: string): export class ESTermSource extends AbstractESAggSource { static type = SOURCE_TYPES.ES_TERM_SOURCE; + static createDescriptor(descriptor: Partial): ESTermSourceDescriptor { + const normalizedDescriptor = AbstractESAggSource.createDescriptor(descriptor); + if (!isValidStringConfig(descriptor.term)) { + throw new Error('Cannot create an ESTermSource without a term'); + } + return { + ...normalizedDescriptor, + term: descriptor.term!, + type: SOURCE_TYPES.ES_TERM_SOURCE, + }; + } + private readonly _termField: ESDocField; readonly _descriptor: ESTermSourceDescriptor; constructor(descriptor: ESTermSourceDescriptor, inspectorAdapters: Adapters) { - super(AbstractESAggSource.createDescriptor(descriptor), inspectorAdapters); - this._descriptor = descriptor; + const sourceDescriptor = ESTermSource.createDescriptor(descriptor); + super(sourceDescriptor, inspectorAdapters); + this._descriptor = sourceDescriptor; this._termField = new ESDocField({ fieldName: this._descriptor.term, source: this, diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts index bf39d78a4784f4..e6db127e45116d 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts @@ -102,10 +102,6 @@ export class KibanaRegionmapSource extends AbstractVectorSource { return this._descriptor.name; } - async isTimeAware() { - return false; - } - canFormatFeatureProperties() { return true; } 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 6390626b006b44..adc478c1f45d3a 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 @@ -189,10 +189,6 @@ export class MVTSingleLayerVectorSource return null; } - getApplyGlobalQuery(): boolean { - return false; - } - isBoundsAware() { return false; } diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index f24ec012836b60..7a52c4e6fde20a 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -60,6 +60,7 @@ export interface ISource { cloneDescriptor(): AbstractSourceDescriptor; getFieldNames(): string[]; getApplyGlobalQuery(): boolean; + getApplyGlobalTime(): boolean; getIndexPatternIds(): string[]; getQueryableIndexPatternIds(): string[]; getGeoGridPrecision(zoom: number): number; @@ -135,7 +136,11 @@ export class AbstractSource implements ISource { } getApplyGlobalQuery(): boolean { - return !!this._descriptor.applyGlobalQuery; + return false; + } + + getApplyGlobalTime(): boolean { + return false; } getIndexPatternIds(): string[] { 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 38ff3b49a87f47..32db97708e3976 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 @@ -37,6 +37,7 @@ export interface GeoJsonWithMeta { export interface BoundsFilters { applyGlobalQuery: boolean; + applyGlobalTime: boolean; filters: Filter[]; query?: MapQuery; sourceQuery?: MapQuery; @@ -61,7 +62,6 @@ export interface IVectorSource extends ISource { getLeftJoinFields(): Promise; getSyncMeta(): VectorSourceSyncMeta | null; getFieldNames(): string[]; - getApplyGlobalQuery(): boolean; createField({ fieldName }: { fieldName: string }): IField; canFormatFeatureProperties(): boolean; getSupportedShapeTypes(): Promise; diff --git a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts index 147870dbef371f..a7919ad058e4b5 100644 --- a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts +++ b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts @@ -84,9 +84,13 @@ export async function canSkipSourceUpdate({ return false; } + let updateDueToApplyGlobalTime = false; let updateDueToTime = false; if (timeAware) { - updateDueToTime = !_.isEqual(prevMeta.timeFilters, nextMeta.timeFilters); + updateDueToApplyGlobalTime = prevMeta.applyGlobalTime !== nextMeta.applyGlobalTime; + if (nextMeta.applyGlobalTime) { + updateDueToTime = !_.isEqual(prevMeta.timeFilters, nextMeta.timeFilters); + } } let updateDueToRefreshTimer = false; @@ -132,6 +136,7 @@ export async function canSkipSourceUpdate({ const updateDueToSourceMetaChange = !_.isEqual(prevMeta.sourceMeta, nextMeta.sourceMeta); return ( + !updateDueToApplyGlobalTime && !updateDueToTime && !updateDueToRefreshTimer && !updateDueToExtentChange && diff --git a/x-pack/plugins/maps/public/components/global_time_checkbox.tsx b/x-pack/plugins/maps/public/components/global_time_checkbox.tsx new file mode 100644 index 00000000000000..b224ad64747452 --- /dev/null +++ b/x-pack/plugins/maps/public/components/global_time_checkbox.tsx @@ -0,0 +1,32 @@ +/* + * 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 React from 'react'; +import { EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; + +interface Props { + applyGlobalTime: boolean; + label: string; + setApplyGlobalTime: (applyGlobalTime: boolean) => void; +} + +export function GlobalTimeCheckbox({ applyGlobalTime, label, setApplyGlobalTime }: Props) { + const onApplyGlobalTimeChange = (event: EuiSwitchEvent) => { + setApplyGlobalTime(event.target.checked); + }; + + return ( + + + + ); +} diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js b/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js index d2652fac5bd2c1..70a04ec37860da 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js @@ -22,23 +22,26 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { getIndexPatternService, getData } from '../../../kibana_services'; import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox'; +import { GlobalTimeCheckbox } from '../../../components/global_time_checkbox'; export class FilterEditor extends Component { state = { isPopoverOpen: false, indexPatterns: [], + isSourceTimeAware: false, }; componentDidMount() { this._isMounted = true; this._loadIndexPatterns(); + this._loadSourceTimeAware(); } componentWillUnmount() { this._isMounted = false; } - _loadIndexPatterns = async () => { + async _loadIndexPatterns() { // Filter only effects source so only load source indices. const indexPatternIds = this.props.layer.getSource().getIndexPatternIds(); const indexPatterns = []; @@ -58,7 +61,14 @@ export class FilterEditor extends Component { } this.setState({ indexPatterns }); - }; + } + + async _loadSourceTimeAware() { + const isSourceTimeAware = await this.props.layer.getSource().isTimeAware(); + if (this._isMounted) { + this.setState({ isSourceTimeAware }); + } + } _toggle = () => { this.setState((prevState) => ({ @@ -79,6 +89,10 @@ export class FilterEditor extends Component { this.props.updateSourceProp(this.props.layer.getId(), 'applyGlobalQuery', applyGlobalQuery); }; + _onApplyGlobalTimeChange = (applyGlobalTime) => { + this.props.updateSourceProp(this.props.layer.getId(), 'applyGlobalTime', applyGlobalTime); + }; + _renderQueryPopover() { const layerQuery = this.props.layer.getQuery(); const { SearchBar } = getData().ui; @@ -165,6 +179,15 @@ export class FilterEditor extends Component { } render() { + const globalTimeCheckbox = this.state.isSourceTimeAware ? ( + + ) : null; return ( @@ -191,6 +214,8 @@ export class FilterEditor extends Component { applyGlobalQuery={this.props.layer.getSource().getApplyGlobalQuery()} setApplyGlobalQuery={this._onApplyGlobalQueryChange} /> + + {globalTimeCheckbox} ); } diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx index 2065668858e22b..2d14ba20047bc2 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx @@ -66,6 +66,7 @@ export function JoinEditor({ joins, layer, onChange, leftJoinFields, layerDispla right: { id: uuid(), applyGlobalQuery: true, + applyGlobalTime: true, }, } as JoinDescriptor, ]); diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js index 5b44801eb780d9..54c2b908d15363 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js @@ -12,6 +12,7 @@ import { JoinExpression } from './join_expression'; import { MetricsExpression } from './metrics_expression'; import { WhereExpression } from './where_expression'; import { GlobalFilterCheckbox } from '../../../../components/global_filter_checkbox'; +import { GlobalTimeCheckbox } from '../../../../components/global_time_checkbox'; import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; @@ -126,6 +127,16 @@ export class Join extends Component { }); }; + _onApplyGlobalTimeChange = (applyGlobalTime) => { + this.props.onChange({ + leftField: this.props.join.leftField, + right: { + ...this.props.join.right, + applyGlobalTime, + }, + }); + }; + render() { const { join, onRemove, leftFields, leftSourceName } = this.props; const { rightFields, indexPattern } = this.state; @@ -137,6 +148,7 @@ export class Join extends Component { let metricsExpression; let globalFilterCheckbox; + let globalTimeCheckbox; if (isJoinConfigComplete) { metricsExpression = ( @@ -148,16 +160,27 @@ export class Join extends Component { ); globalFilterCheckbox = ( - - + ); + if (this.state.indexPattern && this.state.indexPattern.timeFieldName) { + globalTimeCheckbox = ( + - - ); + ); + } } let whereExpression; @@ -194,22 +217,24 @@ export class Join extends Component { {metricsExpression} {whereExpression} - - {globalFilterCheckbox} - - + + {globalFilterCheckbox} + + {globalTimeCheckbox} + + ); } diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap index 456414889c7328..ea37e76bc8494c 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap @@ -5,49 +5,35 @@ exports[`TOCEntryActionsPopover is rendered 1`] = ` anchorClassName="mapLayTocActions__popoverAnchor" anchorPosition="leftUp" button={ - - simulated tooltip content at zoom: 0 -
- - mockFootnoteIcon - - - simulated footnote at isUsingSearch: true -
- + - - - - mockIcon - - - layer 1 - - - - - mockFootnoteIcon - - - -
+ onClick={[Function]} + /> } className="mapLayTocActions" closePopover={[Function]} @@ -132,49 +118,35 @@ exports[`TOCEntryActionsPopover should disable fit to data when supportsFitToBou anchorClassName="mapLayTocActions__popoverAnchor" anchorPosition="leftUp" button={ - - simulated tooltip content at zoom: 0 -
- - mockFootnoteIcon - - - simulated footnote at isUsingSearch: true -
- + - - - - mockIcon - - - layer 1 - - - - - mockFootnoteIcon - - - -
+ onClick={[Function]} + /> } className="mapLayTocActions" closePopover={[Function]} @@ -259,49 +231,36 @@ exports[`TOCEntryActionsPopover should have "show layer" action when layer is no anchorClassName="mapLayTocActions__popoverAnchor" anchorPosition="leftUp" button={ - - simulated tooltip content at zoom: 0 -
- - mockFootnoteIcon - - - simulated footnote at isUsingSearch: true -
- + - - - - mockIcon - - - layer 1 - - - - - mockFootnoteIcon - - - -
+ onClick={[Function]} + /> } className="mapLayTocActions" closePopover={[Function]} @@ -386,49 +345,35 @@ exports[`TOCEntryActionsPopover should not show edit actions in read only mode 1 anchorClassName="mapLayTocActions__popoverAnchor" anchorPosition="leftUp" button={ - - simulated tooltip content at zoom: 0 -
- - mockFootnoteIcon - - - simulated footnote at isUsingSearch: true -
- + - - - - mockIcon - - - layer 1 - - - - - mockFootnoteIcon - - - -
+ onClick={[Function]} + /> } className="mapLayTocActions" closePopover={[Function]} diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts index d8d43e1e1b27a0..673bebc43582e0 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts @@ -14,15 +14,12 @@ import { cloneLayer, removeLayer, } from '../../../../../../actions'; -import { getMapZoom, isUsingSearch } from '../../../../../../selectors/map_selectors'; import { getIsReadOnly } from '../../../../../../selectors/ui_selectors'; import { TOCEntryActionsPopover } from './toc_entry_actions_popover'; function mapStateToProps(state: MapStoreState) { return { isReadOnly: getIsReadOnly(state), - isUsingSearch: isUsingSearch(state), - zoom: getMapZoom(state), }; } @@ -43,8 +40,5 @@ function mapDispatchToProps(dispatch: ThunkDispatchmockIcon, - tooltipContent: `simulated tooltip content at zoom: ${zoom}`, - footnotes: [ - { - icon: mockFootnoteIcon, - message: `simulated footnote at isUsingSearch: ${isUsingSearch}`, - }, - ], - }; - } } const defaultProps = { @@ -59,11 +46,9 @@ const defaultProps = { fitToBounds: () => {}, isEditButtonDisabled: false, isReadOnly: false, - isUsingSearch: true, layer: new LayerMock(), removeLayer: () => {}, toggleVisible: () => {}, - zoom: 0, }; describe('TOCEntryActionsPopover', () => { diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx index b46de57e6a789c..4d669dfbe235e5 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; -import { EuiButtonEmpty, EuiPopover, EuiContextMenu, EuiIcon, EuiToolTip } from '@elastic/eui'; +import { EuiPopover, EuiContextMenu, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ILayer } from '../../../../../../classes/layers/layer'; +import { TOCEntryButton } from '../toc_entry_button'; interface Props { cloneLayer: (layerId: string) => void; @@ -18,11 +19,9 @@ interface Props { fitToBounds: (layerId: string) => void; isEditButtonDisabled: boolean; isReadOnly: boolean; - isUsingSearch: boolean; layer: ILayer; removeLayer: (layerId: string) => void; toggleVisible: (layerId: string) => void; - zoom: number; } interface State { @@ -82,55 +81,6 @@ export class TOCEntryActionsPopover extends Component { this.props.toggleVisible(this.props.layer.getId()); } - _renderPopoverToggleButton() { - const { icon, tooltipContent, footnotes } = this.props.layer.getIconAndTooltipContent( - this.props.zoom, - this.props.isUsingSearch - ); - - const footnoteIcons = footnotes.map((footnote, index) => { - return ( - - {''} - {footnote.icon} - - ); - }); - const footnoteTooltipContent = footnotes.map((footnote, index) => { - return ( -
- {footnote.icon} {footnote.message} -
- ); - }); - - return ( - - {tooltipContent} - {footnoteTooltipContent} - - } - > - - {icon} - {this.props.displayName} {footnoteIcons} - - - ); - } - _getActionsPanel() { const actionItems = [ { @@ -222,7 +172,14 @@ export class TOCEntryActionsPopover extends Component { + } isOpen={this.state.isPopoverOpen} closePopover={this._closePopover} panelPaddingSize="none" diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/index.ts b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/index.ts new file mode 100644 index 00000000000000..d83c158d0385f4 --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { connect } from 'react-redux'; +import { MapStoreState } from '../../../../../../reducers/store'; +import { getMapZoom, isUsingSearch } from '../../../../../../selectors/map_selectors'; +import { TOCEntryButton, StateProps, OwnProps } from './toc_entry_button'; + +function mapStateToProps(state: MapStoreState, ownProps: OwnProps): StateProps { + return { + isUsingSearch: isUsingSearch(state), + zoom: getMapZoom(state), + }; +} + +const connected = connect(mapStateToProps)(TOCEntryButton); +export { connected as TOCEntryButton }; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx new file mode 100644 index 00000000000000..87c60278b361a9 --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx @@ -0,0 +1,184 @@ +/* + * 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 React, { Component, Fragment, ReactElement } from 'react'; + +import { EuiButtonEmpty, EuiIcon, EuiToolTip, EuiLoadingSpinner } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ILayer } from '../../../../../../classes/layers/layer'; + +interface Footnote { + icon: ReactElement; + message?: string | null; +} + +interface IconAndTooltipContent { + icon?: ReactElement | null; + tooltipContent?: string | null; + footnotes: Footnote[]; +} + +export interface StateProps { + isUsingSearch: boolean; + zoom: number; +} + +export interface OwnProps { + displayName: string; + escapedDisplayName: string; + layer: ILayer; + onClick: () => void; +} + +type Props = StateProps & OwnProps; + +interface State { + isFilteredByGlobalTime: boolean; +} + +export class TOCEntryButton extends Component { + private _isMounted: boolean = false; + + state: State = { + isFilteredByGlobalTime: false, + }; + + componentDidMount() { + this._isMounted = true; + this._loadIsFilteredByGlobalTime(); + } + + componentDidUpdate() { + this._loadIsFilteredByGlobalTime(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + async _loadIsFilteredByGlobalTime() { + const isFilteredByGlobalTime = await this.props.layer.isFilteredByGlobalTime(); + if (this._isMounted && isFilteredByGlobalTime !== this.state.isFilteredByGlobalTime) { + this.setState({ isFilteredByGlobalTime }); + } + } + + getIconAndTooltipContent(): IconAndTooltipContent { + let icon; + let tooltipContent = null; + const footnotes = []; + if (this.props.layer.hasErrors()) { + icon = ( + + ); + tooltipContent = this.props.layer.getErrors(); + } else if (this.props.layer.isLayerLoading()) { + icon = ; + } else if (!this.props.layer.isVisible()) { + icon = ; + tooltipContent = i18n.translate('xpack.maps.layer.layerHiddenTooltip', { + defaultMessage: `Layer is hidden.`, + }); + } else if (!this.props.layer.showAtZoomLevel(this.props.zoom)) { + const minZoom = this.props.layer.getMinZoom(); + const maxZoom = this.props.layer.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.props.layer.getCustomIconAndTooltipContent(); + if (customIconAndTooltipContent) { + icon = customIconAndTooltipContent.icon; + if (!customIconAndTooltipContent.areResultsTrimmed) { + tooltipContent = customIconAndTooltipContent.tooltipContent; + } else { + footnotes.push({ + icon: , + message: customIconAndTooltipContent.tooltipContent, + }); + } + } + + if (this.props.isUsingSearch && this.props.layer.getQueryableIndexPatternIds().length) { + footnotes.push({ + icon: , + message: i18n.translate('xpack.maps.layer.isUsingSearchMsg', { + defaultMessage: 'Results narrowed by search bar', + }), + }); + } + if (this.state.isFilteredByGlobalTime) { + footnotes.push({ + icon: , + message: i18n.translate('xpack.maps.layer.isUsingTimeFilter', { + defaultMessage: 'Results narrowed by time filter', + }), + }); + } + } + + return { + icon, + tooltipContent, + footnotes, + }; + } + + render() { + const { icon, tooltipContent, footnotes } = this.getIconAndTooltipContent(); + + const footnoteIcons = footnotes.map((footnote, index) => { + return ( + + {''} + {footnote.icon} + + ); + }); + const footnoteTooltipContent = footnotes.map((footnote, index) => { + return ( +
+ {footnote.icon} {footnote.message} +
+ ); + }); + + return ( + + {tooltipContent} + {footnoteTooltipContent} + + } + > + + {icon} + {this.props.displayName} {footnoteIcons} + + + ); + } +}