diff --git a/packages/kbn-search-response-warnings/index.ts b/packages/kbn-search-response-warnings/index.ts index 88b61320b99f7..6c3a1ad5487ad 100644 --- a/packages/kbn-search-response-warnings/index.ts +++ b/packages/kbn-search-response-warnings/index.ts @@ -9,11 +9,15 @@ export type { SearchResponseWarning, WarningHandlerCallback } from './src/types'; export { + getWarningsDescription, + getWarningsTitle, SearchResponseWarningsBadge, SearchResponseWarningsBadgePopoverContent, SearchResponseWarningsCallout, SearchResponseWarningsEmptyPrompt, + ViewDetailsPopover, } from './src/components/search_response_warnings'; +export { extractWarnings } from './src/extract_warnings'; export { handleWarnings } from './src/handle_warnings'; export { hasUnsupportedDownsampledAggregationFailure } from './src/has_unsupported_downsampled_aggregation_failure'; diff --git a/packages/kbn-search-response-warnings/src/components/search_response_warnings/index.ts b/packages/kbn-search-response-warnings/src/components/search_response_warnings/index.ts index 06c2b2c18e31a..ff27f996be2a7 100644 --- a/packages/kbn-search-response-warnings/src/components/search_response_warnings/index.ts +++ b/packages/kbn-search-response-warnings/src/components/search_response_warnings/index.ts @@ -6,7 +6,9 @@ * Side Public License, v 1. */ +export { getWarningsDescription, getWarningsTitle } from './i18n_utils'; export { SearchResponseWarningsBadge } from './badge'; export { SearchResponseWarningsBadgePopoverContent } from './badge_popover_content'; export { SearchResponseWarningsCallout } from './callout'; export { SearchResponseWarningsEmptyPrompt } from './empty_prompt'; +export { ViewDetailsPopover } from './view_details_popover'; 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 aa4c30fc1cb40..1977173a0207c 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 @@ -11,6 +11,7 @@ import type { KibanaExecutionContext } from '@kbn/core/public'; import type { Query } from '@kbn/data-plugin/common'; import type { Filter } from '@kbn/es-query'; import type { TimeRange } from '@kbn/es-query'; +import type { SearchResponseWarning } from '@kbn/search-response-warnings'; import { MapExtent } from './map_descriptor'; export type Timeslice = { @@ -91,6 +92,7 @@ export type VectorTileLayerMeta = { export type DataRequestMeta = { // request stop time in milliseconds since epoch requestStopTime?: number; + warnings?: SearchResponseWarning[]; } & Partial< SourceRequestMeta & VectorSourceRequestMeta & diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index d93348dd8ef85..70cdc8ace4cf1 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -10,12 +10,18 @@ import { i18n } from '@kbn/i18n'; import type { Map as MbMap } from '@kbn/mapbox-gl'; import type { Query } from '@kbn/es-query'; +import { + getWarningsTitle, + type SearchResponseWarning, + ViewDetailsPopover, +} from '@kbn/search-response-warnings'; import _ from 'lodash'; import React, { ReactElement, ReactNode } from 'react'; import { EuiIcon } from '@elastic/eui'; import { v4 as uuidv4 } from 'uuid'; import { FeatureCollection } from 'geojson'; import { DataRequest } from '../util/data_request'; +import { hasIncompleteResults } from '../util/tile_meta_feature_utils'; import { LAYER_TYPE, MAX_ZOOM, @@ -41,9 +47,16 @@ import { LICENSED_FEATURES } from '../../licensed_features'; import { IESSource } from '../sources/es_source'; import { TileErrorsList } from './tile_errors_list'; -export interface LayerError { +export const INCOMPLETE_RESULTS_WARNING = i18n.translate( + 'xpack.maps.layer.incompleteResultsWarning', + { + defaultMessage: `Layer had issues returning data and results might be incomplete.`, + } +); + +export interface LayerMessage { title: string; - error: ReactNode; + body: ReactNode; } export interface ILayer { @@ -77,7 +90,9 @@ export interface ILayer { isLayerLoading(zoom: number): boolean; isFilteredByGlobalTime(): Promise; hasErrors(): boolean; - getErrors(): LayerError[]; + getErrors(): LayerMessage[]; + hasWarnings(): boolean; + getWarnings(): LayerMessage[]; /* * ILayer.getMbLayerIds returns a list of all mapbox layers assoicated with this layer. @@ -405,14 +420,14 @@ export class AbstractLayer implements ILayer { }); } - getErrors(): LayerError[] { - const errors: LayerError[] = []; + getErrors(): LayerMessage[] { + const errors: LayerMessage[] = []; const sourceError = this.getSourceDataRequest()?.renderError(); if (sourceError) { errors.push({ title: this._getSourceErrorTitle(), - error: sourceError, + body: sourceError, }); } @@ -421,13 +436,59 @@ export class AbstractLayer implements ILayer { title: i18n.translate('xpack.maps.layer.tileErrorTitle', { defaultMessage: `An error occurred when loading layer tiles`, }), - error: , + body: , }); } return errors; } + hasWarnings(): boolean { + const hasDataRequestWarnings = this._dataRequests.some((dataRequest) => { + const dataRequestMeta = dataRequest.getMeta(); + return dataRequestMeta?.warnings?.length; + }); + + if (hasDataRequestWarnings) { + return true; + } + + return this._isTiled() ? this._getTileMetaFeatures().some(hasIncompleteResults) : false; + } + + getWarnings(): LayerMessage[] { + const warningMessages: LayerMessage[] = []; + + const dataRequestWarnings: SearchResponseWarning[] = []; + this._dataRequests.forEach((dataRequest) => { + const dataRequestMeta = dataRequest.getMeta(); + if (dataRequestMeta?.warnings?.length) { + dataRequestWarnings.push(...dataRequestMeta.warnings); + } + }); + + if (dataRequestWarnings.length) { + warningMessages.push({ + title: getWarningsTitle(dataRequestWarnings), + body: ( + <> + {INCOMPLETE_RESULTS_WARNING}{' '} + + + ), + }); + } + + if (this._isTiled() && this._getTileMetaFeatures().some(hasIncompleteResults)) { + warningMessages.push({ + title: '', + body: INCOMPLETE_RESULTS_WARNING, + }); + } + + return warningMessages; + } + async syncData(syncContext: DataRequestContext) { // no-op by default } diff --git a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx index 7b7ac229f154e..7bdab791f59f9 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx @@ -28,7 +28,7 @@ import { import { ISource, SourceEditorArgs } from '../../sources/source'; import { type DataRequestContext } from '../../../actions'; import { getLayersExtent } from '../../../actions/get_layers_extent'; -import { ILayer, LayerIcon, LayerError } from '../layer'; +import { ILayer, LayerIcon, LayerMessage } from '../layer'; import { IStyle } from '../../styles/style'; import { LICENSED_FEATURES } from '../../../licensed_features'; @@ -299,14 +299,33 @@ export class LayerGroup implements ILayer { }); } - getErrors(): LayerError[] { + getErrors(): LayerMessage[] { return this.hasErrors() ? [ { title: i18n.translate('xpack.maps.layerGroup.childrenErrorMessage', { defaultMessage: `An error occurred when loading nested layers`, }), - error: '', + body: '', + }, + ] + : []; + } + + hasWarnings(): boolean { + return this._children.some((child) => { + return child.hasWarnings(); + }); + } + + getWarnings(): LayerMessage[] { + return this.hasWarnings() + ? [ + { + title: i18n.translate('xpack.maps.layerGroup.incompleteResultsWarning', { + defaultMessage: `Nested layer(s) had issues returning data and results might be incomplete.`, + }), + body: '', }, ] : []; diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.ts index a1f2ed1792e05..fec564c838bd6 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import type { SearchResponseWarning } from '@kbn/search-response-warnings'; import { IVectorLayer } from '../vector_layer'; import { GeoJsonVectorLayer } from '../geojson_vector_layer'; import { IVectorStyle, VectorStyle } from '../../../styles/vector/vector_style'; @@ -183,7 +184,7 @@ export class BlendedVectorLayer extends GeoJsonVectorLayer implements IVectorLay return layerDescriptor; } - private readonly _isClustered: boolean; + private _isClustered: boolean; private readonly _clusterSource: ESGeoGridSource; private readonly _clusterStyle: VectorStyle; private readonly _documentSource: ESSearchSource; @@ -313,11 +314,22 @@ export class BlendedVectorLayer extends GeoJsonVectorLayer implements IVectorLay let isSyncClustered; try { syncContext.startLoading(dataRequestId, requestToken, requestMeta); + const warnings: SearchResponseWarning[] = []; isSyncClustered = !(await this._documentSource.canLoadAllDocuments( + await this.getDisplayName(this._documentSource), requestMeta, - syncContext.registerCancelCallback.bind(null, requestToken) + syncContext.registerCancelCallback.bind(null, requestToken), + syncContext.inspectorAdapters, + (warning) => { + warnings.push(warning); + } )); - syncContext.stopLoading(dataRequestId, requestToken, { isSyncClustered }, requestMeta); + syncContext.stopLoading( + dataRequestId, + requestToken, + { isSyncClustered }, + { ...requestMeta, warnings } + ); } catch (error) { if (!(error instanceof DataRequestAbortError) || !isSearchSourceAbortError(error)) { syncContext.onLoadError(dataRequestId, requestToken, error); @@ -325,9 +337,11 @@ export class BlendedVectorLayer extends GeoJsonVectorLayer implements IVectorLay return; } if (isSyncClustered) { + this._isClustered = true; activeSource = this._clusterSource; activeStyle = this._clusterStyle; } else { + this._isClustered = false; activeSource = this._documentSource; activeStyle = this._documentStyle; } diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx index 4faa32668f7de..30272f5038377 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx @@ -28,7 +28,7 @@ import { DataRequestContext } from '../../../../actions'; import { IVectorStyle, VectorStyle } from '../../../styles/vector/vector_style'; import { ISource } from '../../../sources/source'; import { IVectorSource } from '../../../sources/vector_source'; -import { AbstractLayer, LayerError, LayerIcon } from '../../layer'; +import { AbstractLayer, LayerMessage, LayerIcon } from '../../layer'; import { AbstractVectorLayer, noResultsIcon, @@ -158,7 +158,7 @@ export class GeoJsonVectorLayer extends AbstractVectorLayer { ); } - getErrors(): LayerError[] { + getErrors(): LayerMessage[] { const errors = super.getErrors(); this.getValidJoins().forEach((join) => { @@ -168,7 +168,7 @@ export class GeoJsonVectorLayer extends AbstractVectorLayer { title: i18n.translate('xpack.maps.geojsonVectorLayer.joinErrorTitle', { defaultMessage: `An error occurred when adding join metrics to layer features`, }), - error: joinDescriptor.error, + body: joinDescriptor.error, }); } }); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/perform_inner_joins.test.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/perform_inner_joins.test.ts index 70049304c9d9f..0fceb0729f818 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/perform_inner_joins.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/perform_inner_joins.test.ts @@ -81,8 +81,8 @@ const mockVectorSource = { }, } as unknown as IVectorSource; const innerJoin = new InnerJoin(joinDescriptor, mockVectorSource); -const propertiesMap = new Map>(); -propertiesMap.set('alpha', { [COUNT_PROPERTY_NAME]: 1 }); +const joinMetrics = new Map>(); +joinMetrics.set('alpha', { [COUNT_PROPERTY_NAME]: 1 }); test('should skip join when no state has changed', async () => { const updateSourceData = sinon.spy(); @@ -170,7 +170,7 @@ test('should call updateSourceData with feature collection with updated feature dataHasChanged: false, join: innerJoin, joinIndex: 0, - propertiesMap, + joinMetrics, }, ], updateSourceData, @@ -277,7 +277,7 @@ test('should call updateSourceData when no results returned from terms aggregati dataHasChanged: true, join: innerJoin, joinIndex: 0, - propertiesMap: new Map>(), + joinMetrics: new Map>(), }, ], updateSourceData, @@ -321,8 +321,8 @@ test('should call onJoinError when there are no matching features', async () => const setJoinError = sinon.spy(); // instead of returning military alphabet like "alpha" or "bravo", mismatched key returns numbers, like '1' - const propertiesMapFromMismatchedKey = new Map>(); - propertiesMapFromMismatchedKey.set('1', { [COUNT_PROPERTY_NAME]: 1 }); + const joinMetricsFromMismatchedKey = new Map>(); + joinMetricsFromMismatchedKey.set('1', { [COUNT_PROPERTY_NAME]: 1 }); await performInnerJoins( { @@ -334,7 +334,7 @@ test('should call onJoinError when there are no matching features', async () => dataHasChanged: true, join: innerJoin, joinIndex: 0, - propertiesMap: propertiesMapFromMismatchedKey, + joinMetrics: joinMetricsFromMismatchedKey, }, ], updateSourceData, diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/perform_inner_joins.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/perform_inner_joins.ts index 55401c8edcb72..947aed099c9d9 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/perform_inner_joins.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/perform_inner_joins.ts @@ -59,8 +59,8 @@ export async function performInnerJoins( if (joinKey !== null) { joinStatus.keys.push(joinKey); } - const canJoinOnCurrent = joinState.propertiesMap - ? innerJoin.joinPropertiesToFeature(feature, joinState.propertiesMap) + const canJoinOnCurrent = joinState.joinMetrics + ? innerJoin.joinPropertiesToFeature(feature, joinState.joinMetrics) : false; if (canJoinOnCurrent && !joinStatus.joinedWithAtLeastOneFeature) { joinStatus.joinedWithAtLeastOneFeature = true; @@ -108,8 +108,7 @@ async function getJoinError(joinStatus: { return; } - const hasTerms = - joinStatus.joinState.propertiesMap && joinStatus.joinState.propertiesMap.size > 0; + const hasTerms = joinStatus.joinState.joinMetrics && joinStatus.joinState.joinMetrics.size > 0; if (!hasTerms || joinStatus.joinedWithAtLeastOneFeature) { return; @@ -129,9 +128,7 @@ async function getJoinError(joinStatus: { leftFieldName, leftFieldValues: prettyPrintArray(joinStatus.keys), rightFieldName, - rightFieldValues: prettyPrintArray( - Array.from(joinStatus.joinState.propertiesMap!.keys()) - ), + rightFieldValues: prettyPrintArray(Array.from(joinStatus.joinState.joinMetrics!.keys())), }, }); } diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/types.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/types.ts index 05fc9dd98eacc..930d32fa53552 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/types.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/types.ts @@ -12,5 +12,5 @@ export interface JoinState { dataHasChanged: boolean; join: InnerJoin; joinIndex: number; - propertiesMap?: PropertiesMap; + joinMetrics?: PropertiesMap; } 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 b3974bdc67f47..0d60b2dc1b6bd 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 @@ -50,7 +50,7 @@ import { VectorStyleRequestMeta, } from '../../../../common/descriptor_types'; import { IVectorSource } from '../../sources/vector_source'; -import { LayerIcon, ILayer, LayerError } from '../layer'; +import { LayerIcon, ILayer, LayerMessage } from '../layer'; import { InnerJoin } from '../../joins/inner_join'; import { isSpatialJoin } from '../../joins/is_spatial_join'; import { IField } from '../../fields/field'; @@ -274,7 +274,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer { }); } - getErrors(): LayerError[] { + getErrors(): LayerMessage[] { const errors = super.getErrors(); this.getValidJoins().forEach((join) => { @@ -285,7 +285,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer { title: i18n.translate('xpack.maps.vectorLayer.joinFetchErrorTitle', { defaultMessage: `An error occurred when loading join metrics`, }), - error: joinError, + body: joinError, }); } }); @@ -469,7 +469,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer { startLoading(dataRequestId, requestToken, nextMeta); const layerName = await this.getDisplayName(source); - const styleMeta = await (source as IESSource).loadStylePropsMeta({ + const { styleMeta, warnings } = await (source as IESSource).loadStylePropsMeta({ layerName, style, dynamicStyleProps, @@ -481,7 +481,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer { executionContext: dataFilters.executionContext, }); - stopLoading(dataRequestId, requestToken, styleMeta, nextMeta); + stopLoading(dataRequestId, requestToken, styleMeta, { ...nextMeta, warnings }); } catch (error) { if (!(error instanceof DataRequestAbortError)) { onLoadError(dataRequestId, requestToken, error); @@ -605,27 +605,25 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer { dataHasChanged: false, join, joinIndex, - propertiesMap: prevDataRequest?.getData() as PropertiesMap, + joinMetrics: prevDataRequest?.getData() as PropertiesMap, }; } try { startLoading(sourceDataId, requestToken, joinRequestMeta); - const leftSourceName = await this._source.getDisplayName(); - const propertiesMap = await joinSource.getPropertiesMap( + const { joinMetrics, warnings } = await joinSource.getJoinMetrics( joinRequestMeta, - leftSourceName, - join.getLeftField().getName(), + await this.getDisplayName(), registerCancelCallback.bind(null, requestToken), inspectorAdapters, featureCollection ); - stopLoading(sourceDataId, requestToken, propertiesMap); + stopLoading(sourceDataId, requestToken, joinMetrics, { warnings }); return { dataHasChanged: true, join, joinIndex, - propertiesMap, + joinMetrics, }; } catch (error) { if (!(error instanceof DataRequestAbortError)) { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx index 85cfbd48a2a07..4acb400807ad0 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx @@ -14,6 +14,7 @@ import type { AggregationsCompositeAggregate, SearchResponse, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { SearchResponseWarning } from '@kbn/search-response-warnings'; import type { KibanaExecutionContext } from '@kbn/core/public'; import { ISearchSource } from '@kbn/data-plugin/common/search/search_source'; import { DataView } from '@kbn/data-plugin/common'; @@ -43,7 +44,12 @@ import { DataRequestAbortError } from '../../util/data_request'; import { LICENSED_FEATURES } from '../../../licensed_features'; import { getHttp } from '../../../kibana_services'; -import { GetFeatureActionsArgs, GeoJsonWithMeta, IMvtVectorSource } from '../vector_source'; +import { + GetFeatureActionsArgs, + GeoJsonWithMeta, + IMvtVectorSource, + getLayerFeaturesRequestName, +} from '../vector_source'; import { DataFilters, ESGeoGridSourceDescriptor, @@ -281,6 +287,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo bufferedExtent, inspectorAdapters, executionContext, + onWarning, }: { searchSource: ISearchSource; searchSessionId?: string; @@ -293,6 +300,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo bufferedExtent: MapExtent; inspectorAdapters: Adapters; executionContext: KibanaExecutionContext; + onWarning: (warning: SearchResponseWarning) => void; }) { const gridsPerRequest: number = Math.floor(DEFAULT_MAX_BUCKETS_LIMIT / bucketsPerGrid); const aggs: any = { @@ -352,34 +360,16 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo : this.getId(); const esResponse: SearchResponse = await this._runEsQuery({ requestId, - requestName: i18n.translate('xpack.maps.source.esGrid.compositeInspector.requestName', { - defaultMessage: '{layerName} {bucketsName} composite request ({requestCount})', - values: { - bucketsName: this.getBucketsName(), - layerName, - requestCount, - }, - }), + requestName: getLayerFeaturesRequestName(`${layerName} (${requestCount})`), searchSource, registerCancelCallback, - requestDescription: i18n.translate( - 'xpack.maps.source.esGrid.compositeInspectorDescription', - { - defaultMessage: - 'Get {bucketsName} from data view: {dataViewName}, geospatial field: {geoFieldName}', - values: { - bucketsName: this.getBucketsName(), - dataViewName: indexPattern.getName(), - geoFieldName: this._descriptor.geoField, - }, - } - ), searchSessionId, executionContext: mergeExecutionContext( { description: 'es_geo_grid_source:cluster_composite' }, executionContext ), requestsAdapter: inspectorAdapters.requests, + onWarning, }); features.push(...convertCompositeRespToGeoJson(esResponse, this._descriptor.requestType)); @@ -406,6 +396,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo tooManyBuckets, inspectorAdapters, executionContext, + onWarning, }: { searchSource: ISearchSource; searchSessionId?: string; @@ -417,6 +408,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo tooManyBuckets: boolean; inspectorAdapters: Adapters; executionContext: KibanaExecutionContext; + onWarning: (warning: SearchResponseWarning) => void; }): Promise { const valueAggsDsl = tooManyBuckets ? this.getValueAggsDsl(indexPattern, (metric) => { @@ -446,30 +438,16 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo const esResponse = await this._runEsQuery({ requestId: this.getId(), - requestName: i18n.translate('xpack.maps.source.esGrid.inspector.requestName', { - defaultMessage: '{layerName} {bucketsName} request', - values: { - bucketsName: this.getBucketsName(), - layerName, - }, - }), + requestName: getLayerFeaturesRequestName(layerName), searchSource, registerCancelCallback, - requestDescription: i18n.translate('xpack.maps.source.esGrid.inspector.requestDescription', { - defaultMessage: - 'Get {bucketsName} from data view: {dataViewName}, geospatial field: {geoFieldName}', - values: { - bucketsName: this.getBucketsName(), - dataViewName: indexPattern.getName(), - geoFieldName: this._descriptor.geoField, - }, - }), searchSessionId, executionContext: mergeExecutionContext( { description: 'es_geo_grid_source:cluster' }, executionContext ), requestsAdapter: inspectorAdapters.requests, + onWarning, }); return convertRegularRespToGeoJson(esResponse, this._descriptor.requestType); @@ -516,6 +494,10 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo const supportsCompositeAgg = !(await this._isGeoShape()); const precision = this.getGeoGridPrecision(requestMeta.zoom); + const warnings: SearchResponseWarning[] = []; + const onWarning = (warning: SearchResponseWarning) => { + warnings.push(warning); + }; const features: Feature[] = supportsCompositeAgg && tooManyBuckets ? await this._compositeAggRequest({ @@ -530,6 +512,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo bufferedExtent: requestMeta.buffer, inspectorAdapters, executionContext: requestMeta.executionContext, + onWarning, }) : await this._nonCompositeAggRequest({ searchSource, @@ -542,6 +525,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo tooManyBuckets, inspectorAdapters, executionContext: requestMeta.executionContext, + onWarning, }); return { @@ -551,6 +535,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo }, meta: { areResultsTrimmed: false, + warnings, }, } as GeoJsonWithMeta; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx index 24cafe66de35c..82bb8fec43234 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx @@ -11,6 +11,7 @@ import React from 'react'; import { GeoJsonProperties } from 'geojson'; import { i18n } from '@kbn/i18n'; import { type Filter, buildPhraseFilter } from '@kbn/es-query'; +import type { SearchResponseWarning } from '@kbn/search-response-warnings'; import { Adapters } from '@kbn/inspector-plugin/common/adapters'; import { EMPTY_FEATURE_COLLECTION, @@ -33,7 +34,7 @@ import { ESDocField } from '../../fields/es_doc_field'; import { InlineField } from '../../fields/inline_field'; import { UpdateSourceEditor } from './update_source_editor'; import { ImmutableSourceProperty, SourceEditorArgs } from '../source'; -import { GeoJsonWithMeta } from '../vector_source'; +import { GeoJsonWithMeta, getLayerFeaturesRequestName } from '../vector_source'; import { isValidStringConfig } from '../../util/valid_string_config'; import { IField } from '../../fields/field'; import { ITooltipProperty, TooltipProperty } from '../../tooltips/tooltip_property'; @@ -261,33 +262,21 @@ export class ESGeoLineSource extends AbstractESAggSource { }, }); + const warnings: SearchResponseWarning[] = []; const resp = await this._runEsQuery({ requestId: `${this.getId()}_tracks`, - requestName: i18n.translate('xpack.maps.source.esGeoLine.timeSeriesTrackRequestName', { - defaultMessage: `'{layerName}' tracks request (time series)`, - values: { - layerName, - }, - }), + requestName: getLayerFeaturesRequestName(layerName), searchSource, registerCancelCallback, - requestDescription: i18n.translate( - 'xpack.maps.source.esGeoLine.timeSeriesTrackRequestDescription', - { - defaultMessage: - 'Get tracks from data view: {dataViewName}, geospatial field: {geoFieldName}', - values: { - dataViewName: indexPattern.getName(), - geoFieldName: this._descriptor.geoField, - }, - } - ), searchSessionId: requestMeta.searchSessionId, executionContext: mergeExecutionContext( { description: 'es_geo_line:time_series_tracks' }, requestMeta.executionContext ), requestsAdapter: inspectorAdapters.requests, + onWarning: (warning: SearchResponseWarning) => { + warnings.push(warning); + }, }); const { featureCollection } = convertToGeoJson(resp, TIME_SERIES_ID_FIELD_NAME); @@ -303,7 +292,8 @@ export class ESGeoLineSource extends AbstractESAggSource { entityCount, numTrimmedTracks: 0, // geo_line by time series never truncates tracks and instead simplifies tracks totalEntities: resp?.aggregations?.totalEntities?.value ?? 0, - } as ESGeoLineSourceResponseMeta, + warnings, + }, }; } @@ -333,6 +323,7 @@ export class ESGeoLineSource extends AbstractESAggSource { } const indexPattern = await this.getIndexPattern(); + const warnings: SearchResponseWarning[] = []; // Request is broken into 2 requests // 1) fetch entities: filtered by buffer so that top entities in view are returned @@ -367,27 +358,22 @@ export class ESGeoLineSource extends AbstractESAggSource { const entityResp = await this._runEsQuery({ requestId: `${this.getId()}_entities`, requestName: i18n.translate('xpack.maps.source.esGeoLine.entityRequestName', { - defaultMessage: `'{layerName}' entities request`, + defaultMessage: `load track entities ({layerName})`, values: { layerName, }, }), searchSource: entitySearchSource, registerCancelCallback, - requestDescription: i18n.translate('xpack.maps.source.esGeoLine.entityRequestDescription', { - defaultMessage: - 'Get entities within map buffer from data view: {dataViewName}, entities: {splitFieldName}', - values: { - dataViewName: indexPattern.getName(), - splitFieldName: this._descriptor.splitField, - }, - }), searchSessionId: requestMeta.searchSessionId, executionContext: mergeExecutionContext( { description: 'es_geo_line:entities' }, requestMeta.executionContext ), requestsAdapter: inspectorAdapters.requests, + onWarning: (warning: SearchResponseWarning) => { + warnings.push(warning); + }, }); const entityBuckets: Array<{ key: string; doc_count: number }> = _.get( entityResp, @@ -446,29 +432,18 @@ export class ESGeoLineSource extends AbstractESAggSource { }); const tracksResp = await this._runEsQuery({ requestId: `${this.getId()}_tracks`, - requestName: i18n.translate('xpack.maps.source.esGeoLine.trackRequestName', { - defaultMessage: `'{layerName}' tracks request (terms)`, - values: { - layerName, - }, - }), + requestName: getLayerFeaturesRequestName(layerName), searchSource: tracksSearchSource, registerCancelCallback, - requestDescription: i18n.translate('xpack.maps.source.esGeoLine.trackRequestDescription', { - defaultMessage: - 'Get tracks for {numEntities} entities from data view: {dataViewName}, geospatial field: {geoFieldName}', - values: { - dataViewName: indexPattern.getName(), - geoFieldName: this._descriptor.geoField, - numEntities: entityBuckets.length, - }, - }), searchSessionId: requestMeta.searchSessionId, executionContext: mergeExecutionContext( { description: 'es_geo_line:terms_tracks' }, requestMeta.executionContext ), requestsAdapter: inspectorAdapters.requests, + onWarning: (warning: SearchResponseWarning) => { + warnings.push(warning); + }, }); const { featureCollection, numTrimmedTracks } = convertToGeoJson( tracksResp, @@ -487,7 +462,8 @@ export class ESGeoLineSource extends AbstractESAggSource { entityCount: entityBuckets.length, numTrimmedTracks, totalEntities, - } as ESGeoLineSourceResponseMeta, + warnings, + }, }; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.tsx index eaa6f0c716e19..9540a22bf6a4e 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.tsx @@ -9,6 +9,7 @@ import React from 'react'; import turfBbox from '@turf/bbox'; import { multiPoint } from '@turf/helpers'; import { Adapters } from '@kbn/inspector-plugin/common/adapters'; +import type { SearchResponseWarning } from '@kbn/search-response-warnings'; import { type Filter, buildExistsFilter } from '@kbn/es-query'; import { lastValueFrom } from 'rxjs'; import type { @@ -34,7 +35,7 @@ import { VectorSourceRequestMeta, } from '../../../../common/descriptor_types'; import { isValidStringConfig } from '../../util/valid_string_config'; -import { BoundsRequestMeta, GeoJsonWithMeta } from '../vector_source'; +import { BoundsRequestMeta, GeoJsonWithMeta, getLayerFeaturesRequestName } from '../vector_source'; const MAX_GEOTILE_LEVEL = 29; @@ -188,29 +189,21 @@ export class ESPewPewSource extends AbstractESAggSource { buildExistsFilter({ name: this._descriptor.sourceGeoField, type: 'geo_point' }, indexPattern), ]); + const warnings: SearchResponseWarning[] = []; const esResponse = await this._runEsQuery({ requestId: this.getId(), - requestName: i18n.translate('xpack.maps.pewPew.requestName', { - defaultMessage: '{layerName} paths request', - values: { layerName }, - }), + requestName: getLayerFeaturesRequestName(layerName), searchSource, registerCancelCallback, - requestDescription: i18n.translate('xpack.maps.source.pewPew.inspectorDescription', { - defaultMessage: - 'Get paths from data view: {dataViewName}, source: {sourceFieldName}, destination: {destFieldName}', - values: { - dataViewName: indexPattern.getName(), - destFieldName: this._descriptor.destGeoField, - sourceFieldName: this._descriptor.sourceGeoField, - }, - }), searchSessionId: requestMeta.searchSessionId, executionContext: mergeExecutionContext( { description: 'es_pew_pew_source:connections' }, requestMeta.executionContext ), requestsAdapter: inspectorAdapters.requests, + onWarning: (warning: SearchResponseWarning) => { + warnings.push(warning); + }, }); const { featureCollection } = convertToLines(esResponse); @@ -219,6 +212,7 @@ export class ESPewPewSource extends AbstractESAggSource { data: featureCollection, meta: { areResultsTrimmed: false, + warnings, }, }; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index f56155284232d..77f4b684caf1f 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -9,6 +9,7 @@ import _ from 'lodash'; import React, { ReactElement } from 'react'; import type { QueryDslFieldLookup } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { i18n } from '@kbn/i18n'; +import type { SearchResponseWarning } from '@kbn/search-response-warnings'; import { GeoJsonProperties, Geometry, Position } from 'geojson'; import type { KibanaExecutionContext } from '@kbn/core/public'; import { type Filter, buildPhraseFilter, type TimeRange } from '@kbn/es-query'; @@ -58,6 +59,7 @@ import { import { ImmutableSourceProperty, SourceEditorArgs } from '../source'; import { IField } from '../../fields/field'; import { + getLayerFeaturesRequestName, GetFeatureActionsArgs, GeoJsonWithMeta, IMvtVectorSource, @@ -377,29 +379,21 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource } } + const warnings: SearchResponseWarning[] = []; const resp = await this._runEsQuery({ requestId: this.getId(), - requestName: i18n.translate('xpack.maps.esSearchSource.topHits.requestName', { - defaultMessage: '{layerName} top hits request', - values: { layerName }, - }), + requestName: getLayerFeaturesRequestName(layerName), searchSource, registerCancelCallback, - requestDescription: i18n.translate('xpack.maps.esSearchSource.topHits.requestDescription', { - defaultMessage: - 'Get top hits from data view: {dataViewName}, entities: {entitiesFieldName}, geospatial field: {geoFieldName}', - values: { - dataViewName: indexPattern.getName(), - entitiesFieldName: topHitsGroupByTimeseries ? '_tsid' : topHitsSplitFieldName, - geoFieldName: this._descriptor.geoField, - }, - }), searchSessionId: requestMeta.searchSessionId, executionContext: mergeExecutionContext( { description: 'es_search_source:top_hits' }, requestMeta.executionContext ), requestsAdapter: inspectorAdapters.requests, + onWarning: (warning: SearchResponseWarning) => { + warnings.push(warning); + }, }); const allHits: any[] = []; @@ -424,6 +418,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource areEntitiesTrimmed, entityCount: entityBuckets.length, totalEntities, + warnings, }, }; } @@ -435,6 +430,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource registerCancelCallback: (callback: () => void) => void, inspectorAdapters: Adapters ) { + const warnings: SearchResponseWarning[] = []; const indexPattern = await this.getIndexPattern(); const { docValueFields, sourceOnlyFields } = getDocValueAndSourceFields( @@ -451,7 +447,15 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource delete requestMetaWithoutTimeslice.timeslice; const useRequestMetaWithoutTimeslice = requestMeta.timeslice !== undefined && - (await this.canLoadAllDocuments(requestMetaWithoutTimeslice, registerCancelCallback)); + (await this.canLoadAllDocuments( + layerName, + requestMetaWithoutTimeslice, + registerCancelCallback, + inspectorAdapters, + (warning) => { + warnings.push(warning); + } + )); const maxResultWindow = await this.getMaxResultWindow(); const searchSource = await this.makeSearchSource( @@ -473,26 +477,18 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource const resp = await this._runEsQuery({ requestId: this.getId(), - requestName: i18n.translate('xpack.maps.esSearchSource.requestName', { - defaultMessage: '{layerName} documents request', - values: { layerName }, - }), + requestName: getLayerFeaturesRequestName(layerName), searchSource, registerCancelCallback, - requestDescription: i18n.translate('xpack.maps.esSearchSource.requestDescription', { - defaultMessage: - 'Get documents from data view: {dataViewName}, geospatial field: {geoFieldName}', - values: { - dataViewName: indexPattern.getName(), - geoFieldName: this._descriptor.geoField, - }, - }), searchSessionId: requestMeta.searchSessionId, executionContext: mergeExecutionContext( { description: 'es_search_source:doc_search' }, requestMeta.executionContext ), requestsAdapter: inspectorAdapters.requests, + onWarning: (warning: SearchResponseWarning) => { + warnings.push(warning); + }, }); const isTimeExtentForTimeslice = @@ -506,6 +502,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource ? requestMeta.timeslice : timerangeToTimeextent(requestMeta.timeFilters), isTimeExtentForTimeslice, + warnings, }, }; } @@ -990,25 +987,31 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource } async canLoadAllDocuments( + layerName: string, requestMeta: VectorSourceRequestMeta, - registerCancelCallback: (callback: () => void) => void + registerCancelCallback: (callback: () => void) => void, + inspectorAdapters: Adapters, + onWarning: (warning: SearchResponseWarning) => void ) { - const abortController = new AbortController(); - registerCancelCallback(() => abortController.abort()); const maxResultWindow = await this.getMaxResultWindow(); const searchSource = await this.makeSearchSource(requestMeta, 0); searchSource.setField('trackTotalHits', maxResultWindow + 1); - const { rawResponse: resp } = await lastValueFrom( - searchSource.fetch$({ - abortSignal: abortController.signal, - sessionId: requestMeta.searchSessionId, - legacyHitsTotal: false, - executionContext: mergeExecutionContext( - { description: 'es_search_source:all_doc_counts' }, - requestMeta.executionContext - ), - }) - ); + const resp = await this._runEsQuery({ + requestId: this.getId() + 'features_count', + requestName: i18n.translate('xpack.maps.vectorSource.featuresCountRequestName', { + defaultMessage: 'load features count ({layerName})', + values: { layerName }, + }), + searchSource, + registerCancelCallback, + searchSessionId: requestMeta.searchSessionId, + executionContext: mergeExecutionContext( + { description: 'es_search_source:all_doc_counts' }, + requestMeta.executionContext + ), + requestsAdapter: inspectorAdapters.requests, + onWarning, + }); return !isTotalHitsGreaterThan(resp.hits.total as unknown as TotalHits, maxResultWindow); } 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 cfde9257ca3be..2b5ec413ba6ec 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 @@ -15,13 +15,15 @@ import type { KibanaExecutionContext } from '@kbn/core/public'; import { RequestAdapter } from '@kbn/inspector-plugin/common/adapters/request'; import { lastValueFrom } from 'rxjs'; import type { TimeRange } from '@kbn/es-query'; +import { extractWarnings, type SearchResponseWarning } from '@kbn/search-response-warnings'; import type { IESAggSource } from '../es_agg_source'; import { AbstractVectorSource, BoundsRequestMeta } from '../vector_source'; import { getAutocompleteService, getIndexPatternService, - getTimeFilter, + getInspector, getSearchService, + getTimeFilter, } from '../../../kibana_services'; import { getDataViewNotFoundMessage } from '../../../../common/i18n_getters'; import { createExtentFilter } from '../../../../common/elasticsearch_util'; @@ -34,6 +36,7 @@ import { AbstractSourceDescriptor, DynamicStylePropertyOptions, MapExtent, + StyleMetaData, VectorSourceRequestMeta, } from '../../../../common/descriptor_types'; import { IVectorStyle } from '../../styles/vector/vector_style'; @@ -78,7 +81,7 @@ export interface IESSource extends IVectorSource { searchSessionId?: string; inspectorAdapters: Adapters; executionContext: KibanaExecutionContext; - }): Promise; + }): Promise<{ styleMeta: StyleMetaData; warnings: SearchResponseWarning[] }>; } export class AbstractESSource extends AbstractVectorSource implements IESSource { @@ -157,26 +160,28 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource async _runEsQuery({ registerCancelCallback, - requestDescription, requestId, requestName, searchSessionId, searchSource, executionContext, requestsAdapter, + onWarning, }: { registerCancelCallback: (callback: () => void) => void; - requestDescription: string; requestId: string; requestName: string; searchSessionId?: string; searchSource: ISearchSource; executionContext: KibanaExecutionContext; requestsAdapter: RequestAdapter | undefined; + onWarning?: (warning: SearchResponseWarning) => void; }): Promise { const abortController = new AbortController(); registerCancelCallback(() => abortController.abort()); + const disableWarningToasts = onWarning !== undefined && requestsAdapter !== undefined; + try { const { rawResponse: resp } = await lastValueFrom( searchSource.fetch$({ @@ -187,11 +192,18 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource adapter: requestsAdapter, id: requestId, title: requestName, - description: requestDescription, }, executionContext, + disableWarningToasts, }) ); + + if (disableWarningToasts) { + extractWarnings(resp, getInspector(), requestsAdapter, requestName, requestId).forEach( + onWarning + ); + } + return resp; } catch (error) { if (isSearchSourceAbortError(error)) { @@ -462,7 +474,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource searchSessionId?: string; inspectorAdapters: Adapters; executionContext: KibanaExecutionContext; - }): Promise { + }) { const promises = dynamicStyleProps.map((dynamicStyleProp) => { return dynamicStyleProp.getFieldMetaRequest(); }); @@ -495,30 +507,30 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource } } + const warnings: SearchResponseWarning[] = []; const resp = await this._runEsQuery({ requestId: `${this.getId()}_styleMeta`, requestName: i18n.translate('xpack.maps.source.esSource.stylePropsMetaRequestName', { - defaultMessage: '{layerName} - metadata', + defaultMessage: 'load symbolization ranges ({layerName})', values: { layerName }, }), searchSource, registerCancelCallback, - requestDescription: i18n.translate( - 'xpack.maps.source.esSource.stylePropsMetaRequestDescription', - { - defaultMessage: - 'Elasticsearch request retrieving field metadata used for calculating symbolization bands.', - } - ), searchSessionId, executionContext: mergeExecutionContext( { description: 'es_source:style_meta' }, executionContext ), requestsAdapter: inspectorAdapters.requests, + onWarning: (warning: SearchResponseWarning) => { + warnings.push(warning); + }, }); - return resp.aggregations; + return { + styleMeta: resp.aggregations, + warnings, + }; } getValueSuggestions = async (field: IField, query: string): Promise => { diff --git a/x-pack/plugins/maps/public/classes/sources/join_sources/es_distance_source/es_distance_source.ts b/x-pack/plugins/maps/public/classes/sources/join_sources/es_distance_source/es_distance_source.ts index 8bb3d903d61fe..e3cf6f7a1fda8 100644 --- a/x-pack/plugins/maps/public/classes/sources/join_sources/es_distance_source/es_distance_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/join_sources/es_distance_source/es_distance_source.ts @@ -7,6 +7,7 @@ import { FeatureCollection } from 'geojson'; import { i18n } from '@kbn/i18n'; +import type { SearchResponseWarning } from '@kbn/search-response-warnings'; import type { Query } from '@kbn/es-query'; import { ISearchSource } from '@kbn/data-plugin/public'; import { Adapters } from '@kbn/inspector-plugin/common/adapters'; @@ -19,7 +20,6 @@ import { ESDistanceSourceDescriptor, VectorSourceRequestMeta, } from '../../../../../common/descriptor_types'; -import { PropertiesMap } from '../../../../../common/elasticsearch_util'; import { isValidStringConfig } from '../../../util/valid_string_config'; import { IJoinSource } from '../types'; import type { IESAggSource, ESAggsSourceSyncMeta } from '../../es_agg_source'; @@ -27,6 +27,7 @@ import { IField } from '../../../fields/field'; import { mergeExecutionContext } from '../../execution_context_utils'; import { processDistanceResponse } from './process_distance_response'; import { isSpatialSourceComplete } from '../is_spatial_source_complete'; +import { getJoinMetricsRequestName } from '../i18n_utils'; export const DEFAULT_WITHIN_DISTANCE = 5; @@ -80,14 +81,13 @@ export class ESDistanceSource extends AbstractESAggSource implements IJoinSource }); } - async getPropertiesMap( + async getJoinMetrics( requestMeta: VectorSourceRequestMeta, - leftSourceName: string, - leftFieldName: string, + layerName: string, registerCancelCallback: (callback: () => void) => void, inspectorAdapters: Adapters, featureCollection?: FeatureCollection - ): Promise { + ) { if (featureCollection === undefined) { throw new Error( i18n.translate('xpack.maps.esDistanceSource.noFeatureCollectionMsg', { @@ -97,7 +97,10 @@ export class ESDistanceSource extends AbstractESAggSource implements IJoinSource } if (!this.hasCompleteConfig()) { - return new Map(); + return { + joinMetrics: new Map(), + warnings: [], + }; } const distance = `${this._descriptor.distance}km`; @@ -119,7 +122,10 @@ export class ESDistanceSource extends AbstractESAggSource implements IJoinSource } if (!hasFilters) { - return new Map(); + return { + joinMetrics: new Map(), + warnings: [], + }; } const indexPattern = await this.getIndexPattern(); @@ -133,31 +139,27 @@ export class ESDistanceSource extends AbstractESAggSource implements IJoinSource aggs: this.getValueAggsDsl(indexPattern), }, }); + const warnings: SearchResponseWarning[] = []; const rawEsData = await this._runEsQuery({ requestId: this.getId(), - requestName: i18n.translate('xpack.maps.distanceSource.requestName', { - defaultMessage: '{leftSourceName} within distance join request', - values: { leftSourceName }, - }), + requestName: getJoinMetricsRequestName(layerName), searchSource, registerCancelCallback, - requestDescription: i18n.translate('xpack.maps.distanceSource.requestDescription', { - defaultMessage: - 'Get metrics from data view: {dataViewName}, geospatial field: {geoFieldName}', - values: { - dataViewName: indexPattern.getName(), - geoFieldName: this._descriptor.geoField, - }, - }), searchSessionId: requestMeta.searchSessionId, executionContext: mergeExecutionContext( { description: 'es_distance_source:distance_join_request' }, requestMeta.executionContext ), requestsAdapter: inspectorAdapters.requests, + onWarning: (warning: SearchResponseWarning) => { + warnings.push(warning); + }, }); - return processDistanceResponse(rawEsData, this.getAggKey(AGG_TYPE.COUNT)); + return { + joinMetrics: processDistanceResponse(rawEsData, this.getAggKey(AGG_TYPE.COUNT)), + warnings, + }; } isFilterByMapBounds(): boolean { diff --git a/x-pack/plugins/maps/public/classes/sources/join_sources/es_term_source/es_term_source.ts b/x-pack/plugins/maps/public/classes/sources/join_sources/es_term_source/es_term_source.ts index da477c76baab8..8a1978f6e7d96 100644 --- a/x-pack/plugins/maps/public/classes/sources/join_sources/es_term_source/es_term_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/join_sources/es_term_source/es_term_source.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import type { SearchResponseWarning } from '@kbn/search-response-warnings'; import type { Query } from '@kbn/es-query'; import { ISearchSource } from '@kbn/data-plugin/public'; import { Adapters } from '@kbn/inspector-plugin/common/adapters'; @@ -36,6 +37,7 @@ import type { IESAggSource, ESAggsSourceSyncMeta } from '../../es_agg_source'; import { IField } from '../../../fields/field'; import { mergeExecutionContext } from '../../execution_context_utils'; import { isTermSourceComplete } from './is_term_source_complete'; +import { getJoinMetricsRequestName } from '../i18n_utils'; const TERMS_AGG_NAME = 'join'; const TERMS_BUCKET_KEYS_TO_IGNORE = ['key', 'doc_count']; @@ -125,15 +127,17 @@ export class ESTermSource extends AbstractESAggSource implements ITermJoinSource : super.getAggLabel(aggType, fieldLabel); } - async getPropertiesMap( + async getJoinMetrics( requestMeta: VectorSourceRequestMeta, - leftSourceName: string, - leftFieldName: string, + layerName: string, registerCancelCallback: (callback: () => void) => void, inspectorAdapters: Adapters - ): Promise { + ) { if (!this.hasCompleteConfig()) { - return new Map(); + return { + joinMetrics: new Map(), + warnings: [], + }; } const indexPattern = await this.getIndexPattern(); @@ -150,31 +154,28 @@ export class ESTermSource extends AbstractESAggSource implements ITermJoinSource }, }); + const warnings: SearchResponseWarning[] = []; const rawEsData = await this._runEsQuery({ requestId: this.getId(), - requestName: i18n.translate('xpack.maps.termSource.requestName', { - defaultMessage: '{leftSourceName} term join request', - values: { leftSourceName }, - }), + requestName: getJoinMetricsRequestName(layerName), searchSource, registerCancelCallback, - requestDescription: i18n.translate('xpack.maps.termSource.requestDescription', { - defaultMessage: 'Get metrics from data view: {dataViewName}, term field: {termFieldName}', - values: { - dataViewName: indexPattern.getName(), - termFieldName: this._termField.getName(), - }, - }), searchSessionId: requestMeta.searchSessionId, executionContext: mergeExecutionContext( { description: 'es_term_source:terms' }, requestMeta.executionContext ), requestsAdapter: inspectorAdapters.requests, + onWarning: (warning: SearchResponseWarning) => { + warnings.push(warning); + }, }); const countPropertyName = this.getAggKey(AGG_TYPE.COUNT); - return extractPropertiesMap(rawEsData, countPropertyName); + return { + joinMetrics: extractPropertiesMap(rawEsData, countPropertyName), + warnings, + }; } isFilterByMapBounds(): boolean { diff --git a/x-pack/plugins/maps/public/classes/sources/join_sources/i18n_utils.ts b/x-pack/plugins/maps/public/classes/sources/join_sources/i18n_utils.ts new file mode 100644 index 0000000000000..354fcae53c3ae --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/join_sources/i18n_utils.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const getJoinMetricsRequestName = (layerName: string) => { + return i18n.translate('xpack.maps.joinSource.joinMetricsRequestName', { + defaultMessage: 'load join metrics ({layerName})', + values: { layerName }, + }); +}; diff --git a/x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.test.ts b/x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.test.ts index df9c11a99daa4..95b2b6fae1cd2 100644 --- a/x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.test.ts @@ -17,7 +17,7 @@ describe('TableSource', () => { }); }); - describe('getPropertiesMap', () => { + describe('getJoinMetrics', () => { it('should roll up results', async () => { const tableSource = new TableSource({ term: 'iso', @@ -53,18 +53,17 @@ describe('TableSource', () => { ], }); - const propertiesMap = await tableSource.getPropertiesMap( + const { joinMetrics } = await tableSource.getJoinMetrics( {} as unknown as VectorSourceRequestMeta, - 'a', - 'b', + 'layer1', () => {} ); - expect(propertiesMap.size).toEqual(2); - expect(propertiesMap.get('US')).toEqual({ + expect(joinMetrics.size).toEqual(2); + expect(joinMetrics.get('US')).toEqual({ population: 100, }); - expect(propertiesMap.get('CN')).toEqual({ + expect(joinMetrics.get('CN')).toEqual({ population: 400, }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.ts b/x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.ts index c1321649168a2..0fb24d198bf78 100644 --- a/x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/join_sources/table_source/table_source.ts @@ -54,12 +54,11 @@ export class TableSource extends AbstractVectorSource implements ITermJoinSource return `table source ${uuidv4()}`; } - async getPropertiesMap( + async getJoinMetrics( requestMeta: VectorSourceRequestMeta, - leftSourceName: string, - leftFieldName: string, + layerName: string, registerCancelCallback: (callback: () => void) => void - ): Promise { + ) { const propertiesMap: PropertiesMap = new Map(); const columnNames = this._descriptor.__columns.map((column) => { @@ -86,7 +85,10 @@ export class TableSource extends AbstractVectorSource implements ITermJoinSource } } - return propertiesMap; + return { + joinMetrics: propertiesMap, + warnings: [], + }; } getTermField(): IField { diff --git a/x-pack/plugins/maps/public/classes/sources/join_sources/types.ts b/x-pack/plugins/maps/public/classes/sources/join_sources/types.ts index 06c87f3469ec8..74d124c6b979f 100644 --- a/x-pack/plugins/maps/public/classes/sources/join_sources/types.ts +++ b/x-pack/plugins/maps/public/classes/sources/join_sources/types.ts @@ -6,6 +6,7 @@ */ import { FeatureCollection, GeoJsonProperties } from 'geojson'; +import type { SearchResponseWarning } from '@kbn/search-response-warnings'; import type { KibanaExecutionContext } from '@kbn/core/public'; import { Query } from '@kbn/data-plugin/common/query'; import { Adapters } from '@kbn/inspector-plugin/common/adapters'; @@ -18,14 +19,16 @@ import { ISource } from '../source'; export interface IJoinSource extends ISource { hasCompleteConfig(): boolean; getWhereQuery(): Query | undefined; - getPropertiesMap( + getJoinMetrics( requestMeta: VectorSourceRequestMeta, - leftSourceName: string, - leftFieldName: string, + layerName: string, registerCancelCallback: (callback: () => void) => void, inspectorAdapters: Adapters, featureCollection?: FeatureCollection - ): Promise; + ): Promise<{ + joinMetrics: PropertiesMap; + warnings: SearchResponseWarning[]; + }>; /* * Use getSyncMeta to expose join configurations that require join data re-fetch when changed. diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/index.ts b/x-pack/plugins/maps/public/classes/sources/vector_source/index.ts index f2f834d1d55d0..e33a7c63ad246 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/index.ts +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/index.ts @@ -6,4 +6,12 @@ */ export * from './vector_source'; +import { i18n } from '@kbn/i18n'; export type { IMvtVectorSource } from './mvt_vector_source'; + +export const getLayerFeaturesRequestName = (layerName: string) => { + return i18n.translate('xpack.maps.vectorSource.featuresRequestName', { + defaultMessage: 'load layer features ({layerName})', + values: { layerName }, + }); +}; 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 c4da68816d262..5adaf6ec20c42 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 @@ -29,7 +29,7 @@ import { AbstractSource, ISource } from '../source'; import { IField } from '../../fields/field'; import { DataFilters, - ESSearchSourceResponseMeta, + DataRequestMeta, MapExtent, Timeslice, VectorSourceRequestMeta, @@ -43,11 +43,9 @@ export interface SourceStatus { isDeprecated?: boolean; } -export type GeoJsonFetchMeta = ESSearchSourceResponseMeta; - export interface GeoJsonWithMeta { data: FeatureCollection; - meta?: GeoJsonFetchMeta; + meta?: DataRequestMeta; } export interface BoundsRequestMeta { diff --git a/x-pack/plugins/maps/public/classes/util/tile_meta_feature_utils.test.ts b/x-pack/plugins/maps/public/classes/util/tile_meta_feature_utils.test.ts index c812dfa6b94f2..f8843c22f3893 100644 --- a/x-pack/plugins/maps/public/classes/util/tile_meta_feature_utils.test.ts +++ b/x-pack/plugins/maps/public/classes/util/tile_meta_feature_utils.test.ts @@ -6,7 +6,12 @@ */ import { TileMetaFeature } from '../../../common/descriptor_types'; -import { getAggsMeta, getAggRange, getHitsMeta } from './tile_meta_feature_utils'; +import { + getAggsMeta, + getAggRange, + getHitsMeta, + hasIncompleteResults, +} from './tile_meta_feature_utils'; describe('getAggsMeta', () => { test('should extract doc_count = 0 from meta features when there are no matches', () => { @@ -376,3 +381,73 @@ describe('getHitsMeta', () => { }); }); }); + +describe('hasIncompleteResults', () => { + const metaFeature = { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [0, 0], + }, + properties: { + '_clusters.skipped': 0, + '_clusters.partial': 0, + '_shards.failed': 0, + timed_out: false, + 'hits.total.relation': 'eq', + 'hits.total.value': 28, + }, + } as TileMetaFeature; + + test('should return false when all shards and clusters are successful', () => { + expect(hasIncompleteResults(metaFeature)).toBe(false); + }); + + test('should return true when local cluster has time out', () => { + expect( + hasIncompleteResults({ + ...metaFeature, + properties: { + ...metaFeature.properties, + timed_out: true, + }, + }) + ).toBe(true); + }); + + test('should return true when local cluster has shard failure', () => { + expect( + hasIncompleteResults({ + ...metaFeature, + properties: { + ...metaFeature.properties, + '_shards.failed': 1, + }, + }) + ).toBe(true); + }); + + test('should return true when remote cluster is skipped', () => { + expect( + hasIncompleteResults({ + ...metaFeature, + properties: { + ...metaFeature.properties, + '_clusters.skipped': 1, + }, + }) + ).toBe(true); + }); + + test('should return true when remote cluster has shard failure', () => { + expect( + hasIncompleteResults({ + ...metaFeature, + properties: { + ...metaFeature.properties, + '_clusters.partial': 1, + }, + }) + ).toBe(true); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/util/tile_meta_feature_utils.ts b/x-pack/plugins/maps/public/classes/util/tile_meta_feature_utils.ts index ac12399a2e011..0af0b64b77a53 100644 --- a/x-pack/plugins/maps/public/classes/util/tile_meta_feature_utils.ts +++ b/x-pack/plugins/maps/public/classes/util/tile_meta_feature_utils.ts @@ -68,3 +68,35 @@ export function getAggRange( } : null; } + +export function hasIncompleteResults(tileMetaFeature: TileMetaFeature) { + if ( + typeof tileMetaFeature.properties?.timed_out === 'boolean' && + tileMetaFeature.properties.timed_out + ) { + return true; + } + + if ( + typeof tileMetaFeature.properties?.['_shards.failed'] === 'number' && + tileMetaFeature.properties['_shards.failed'] > 0 + ) { + return true; + } + + if ( + typeof tileMetaFeature.properties?.['_clusters.skipped'] === 'number' && + tileMetaFeature.properties['_clusters.skipped'] > 0 + ) { + return true; + } + + if ( + typeof tileMetaFeature.properties?.['_clusters.partial'] === 'number' && + tileMetaFeature.properties['_clusters.partial'] > 0 + ) { + return true; + } + + return false; +} diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap index 20395d0674511..81e0f8500451c 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap @@ -120,7 +120,7 @@ exports[`LayerControl isLayerTOCOpen Should render expand button 1`] = ` position="left" > @@ -135,7 +135,7 @@ exports[`LayerControl isLayerTOCOpen Should render expand button with error icon position="left" > @@ -150,7 +150,7 @@ exports[`LayerControl isLayerTOCOpen Should render expand button with loading ic position="left" > diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/expand_button.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/expand_button.tsx index f80a84dc6782d..75116d10bbf1a 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/expand_button.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/expand_button.tsx @@ -10,12 +10,12 @@ import { EuiButtonEmpty, EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; interface Props { - hasErrors: boolean; + hasErrorsOrWarnings: boolean; isLoading: boolean; onClick: () => void; } -export function ExpandButton({ hasErrors, isLoading, onClick }: Props) { +export function ExpandButton({ hasErrorsOrWarnings, isLoading, onClick }: Props) { // isLoading indicates at least one layer is loading. // Expand button should never be disabled. // Not using EuiButton* with iconType props because EuiButton* disables button when isLoading prop is true. @@ -34,7 +34,7 @@ export function ExpandButton({ hasErrors, isLoading, onClick }: Props) { ) : ( - + )} ); diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx index c0b0c13ada2d7..ed3235bf7af3b 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx @@ -71,6 +71,9 @@ describe('LayerControl', () => { hasErrors: () => { return false; }, + hasWarnings: () => { + return false; + }, isLayerLoading: () => { return true; }, @@ -86,6 +89,9 @@ describe('LayerControl', () => { hasErrors: () => { return true; }, + hasWarnings: () => { + return false; + }, isLayerLoading: () => { return false; }, diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx index e1c34223483c8..b0fc2a7620bd4 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx @@ -52,8 +52,8 @@ export function LayerControl({ if (isScreenshotMode()) { return null; } - const hasErrors = layerList.some((layer) => { - return layer.hasErrors(); + const hasErrorsOrWarnings = layerList.some((layer) => { + return layer.hasErrors() || layer.hasWarnings(); }); const isLoading = layerList.some((layer) => { return layer.isLayerLoading(zoom); @@ -67,7 +67,11 @@ export function LayerControl({ })} position="left" > - + ); } diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap index 9a7816a6de39c..f770aeed87494 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap @@ -478,9 +478,22 @@ exports[`TOCEntry props should display layer details when isLegendDetailsOpen is className="mapTocEntry__layerDetails" data-test-subj="mapLayerTOCDetailslayer_1" > -
- TOC details mock -
+ { + const mockLayer = { + getErrors: () => { + return [ + { + title: 'simulated error', + body:
, + }, + ]; + }, + getWarnings: () => { + return [ + { + title: 'simulated warning', + body:
, + }, + ]; + }, + renderLegendDetails: () => { + return
; + }, + } as unknown as ILayer; + + test('Should only render errors when layer contains errors', () => { + render(); + screen.getByTestId('layer-error'); + const error = screen.queryByTestId('layer-error'); + expect(error).not.toBeNull(); + const warning = screen.queryByTestId('layer-warning'); + expect(warning).toBeNull(); + const legend = screen.queryByTestId('layer-legend'); + expect(legend).toBeNull(); + }); + + test('Should render warnings and legend when layer contains warnings', () => { + render( + { + return []; + }, + }} + /> + ); + const error = screen.queryByTestId('layer-error'); + expect(error).toBeNull(); + const warning = screen.queryByTestId('layer-warning'); + expect(warning).not.toBeNull(); + const legend = screen.queryByTestId('layer-legend'); + expect(legend).not.toBeNull(); + }); +}); diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/legend_details.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/legend_details.tsx new file mode 100644 index 0000000000000..1561d47aa4945 --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/legend_details.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiCallOut, EuiSpacer } from '@elastic/eui'; +import type { ILayer } from '../../../../../classes/layers/layer'; + +interface Props { + layer: ILayer; +} + +export function LegendDetails({ layer }: Props) { + const errors = layer.getErrors(); + if (errors.length) { + return ( + <> + {errors.map(({ title, body }, index) => ( +
+ + {body} + + +
+ ))} + + ); + } + + const warnings = layer.getWarnings(); + return warnings.length ? ( + <> + {warnings.map(({ title, body }, index) => ( +
+ + {body} + + +
+ ))} + {layer.renderLegendDetails()} + + ) : ( + layer.renderLegendDetails() + ); +} diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx index eb77812d5b62e..488f9a64083db 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx @@ -9,14 +9,7 @@ import React, { Component } from 'react'; import classNames from 'classnames'; import type { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiIcon, - EuiButtonIcon, - EuiCallOut, - EuiConfirmModal, - EuiButtonEmpty, - EuiSpacer, -} from '@elastic/eui'; +import { EuiIcon, EuiButtonIcon, EuiConfirmModal, EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { TOCEntryActionsPopover } from './toc_entry_actions_popover'; import { @@ -25,6 +18,7 @@ import { EDIT_LAYER_SETTINGS_LABEL, FIT_TO_DATA_LABEL, } from './action_labels'; +import { LegendDetails } from './legend_details'; import { ILayer } from '../../../../../classes/layers/layer'; import { isLayerGroup } from '../../../../../classes/layers/layer_group'; @@ -111,7 +105,9 @@ export class TOCEntry extends Component { async _loadHasLegendDetails() { const hasLegendDetails = - (await this.props.layer.hasLegendDetails()) && + ((await this.props.layer.hasLegendDetails()) || + this.props.layer.hasErrors() || + this.props.layer.hasWarnings()) && this.props.layer.isVisible() && this.props.layer.showAtZoomLevel(this.props.zoom); if (this._isMounted && hasLegendDetails !== this.state.hasLegendDetails) { @@ -150,10 +146,6 @@ export class TOCEntry extends Component { this.props.toggleVisible(this.props.layer.getId()); }; - _getLayerErrors = () => { - return isLayerGroup(this.props.layer) ? [] : this.props.layer.getErrors(); - }; - _renderCancelModal() { if (!this.state.shouldShowModal) { return null; @@ -240,8 +232,7 @@ export class TOCEntry extends Component { } _renderDetailsToggle() { - const errors = this._getLayerErrors(); - if (this.props.isDragging || (!this.state.hasLegendDetails && errors.length === 0)) { + if (this.props.isDragging || !this.state.hasLegendDetails) { return null; } @@ -304,32 +295,6 @@ export class TOCEntry extends Component { ); } - _renderLegendDetails = () => { - if (!this.props.isLegendDetailsOpen) { - return null; - } - - const errors = this._getLayerErrors(); - - return this.state.hasLegendDetails || errors.length ? ( -
- {errors.length - ? errors.map(({ title, error }, index) => ( -
- - {error} - - -
- )) - : this.props.layer.renderLegendDetails()} -
- ) : null; - }; - _hightlightAsSelectedLayer() { if (this.props.isCombineLayer) { return false; @@ -365,7 +330,16 @@ export class TOCEntry extends Component { > {this._renderLayerHeader()} - {this._renderLegendDetails()} + {this.props.isLegendDetailsOpen && + this.state.hasLegendDetails && + !isLayerGroup(this.props.layer) ? ( +
+ +
+ ) : null} {this._renderDetailsToggle()} diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx index 71d5a9f708497..ba832f2aa3210 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx @@ -9,7 +9,7 @@ import React, { Component, Fragment, ReactNode } from 'react'; import { EuiButtonEmpty, EuiIcon, EuiToolTip, EuiLoadingSpinner } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ILayer } from '../../../../../../classes/layers/layer'; +import { type ILayer, INCOMPLETE_RESULTS_WARNING } from '../../../../../../classes/layers/layer'; import { IVectorSource } from '../../../../../../classes/sources/vector_source'; import { isLayerGroup } from '../../../../../../classes/layers/layer_group'; @@ -18,12 +18,6 @@ interface Footnote { message?: string | null; } -interface IconAndTooltipContent { - icon?: ReactNode; - tooltipContent?: ReactNode; - footnotes: Footnote[]; -} - export interface ReduxStateProps { isUsingSearch: boolean; zoom: number; @@ -69,22 +63,35 @@ export class TOCEntryButton extends Component { } } - getIconAndTooltipContent(): IconAndTooltipContent { - if (this.props.layer.hasErrors()) { - return { - icon: ( - - ), - tooltipContent: this.props.layer - .getErrors() - .map(({ title }) =>
{title}
), - footnotes: [], - }; + getIconAndTooltipContent(): { + icon?: ReactNode; + tooltipContent?: ReactNode; + footnotes: Footnote[]; + postScript?: string; + } { + const errors = this.props.layer.getErrors(); + if (errors.length) { + const errorIcon = ( + + ); + return isLayerGroup(this.props.layer) + ? { + icon: errorIcon, + footnotes: [], + postScript: errors[0].title, + } + : { + icon: errorIcon, + tooltipContent: this.props.layer + .getErrors() + .map(({ title }) =>
{title}
), + footnotes: [], + }; } if (!this.props.layer.isVisible()) { @@ -118,10 +125,26 @@ export class TOCEntryButton extends Component { }; } - const { icon, tooltipContent } = this.props.layer.getLayerIcon(true); + const { icon: layerIcon, tooltipContent } = this.props.layer.getLayerIcon(true); + const warnings = this.props.layer.getWarnings(); + const icon = warnings.length ? ( + + ) : ( + layerIcon + ); if (isLayerGroup(this.props.layer)) { - return { icon, tooltipContent, footnotes: [] }; + return { + icon, + tooltipContent, + footnotes: [], + postScript: warnings.length ? warnings[0].title : undefined, + }; } const footnotes = []; @@ -158,11 +181,12 @@ export class TOCEntryButton extends Component { icon, tooltipContent, footnotes, + postScript: warnings.length ? INCOMPLETE_RESULTS_WARNING : undefined, }; } render() { - const { icon, tooltipContent, footnotes } = this.getIconAndTooltipContent(); + const { icon, tooltipContent, footnotes, postScript } = this.getIconAndTooltipContent(); const footnoteIcons = footnotes.map((footnote, index) => { return ( @@ -189,6 +213,9 @@ export class TOCEntryButton extends Component { {tooltipContent} {footnoteTooltipContent} + {postScript ? ( +

{postScript}

+ ) : null}
} data-test-subj="layerTocTooltip" diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json index 364a6d24473d6..86955eeea584a 100644 --- a/x-pack/plugins/maps/tsconfig.json +++ b/x-pack/plugins/maps/tsconfig.json @@ -74,6 +74,7 @@ "@kbn/content-management-table-list-view", "@kbn/serverless", "@kbn/logging", + "@kbn/search-response-warnings", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index ee5d9f8b28e68..9e5ca81926535 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -23510,8 +23510,6 @@ "xpack.maps.blendedVectorLayer.clusteredLayerName": "{displayName} en cluster", "xpack.maps.common.esSpatialRelation.clusterFilterLabel": "intersecte le cluster {gridId}", "xpack.maps.deleteLayerConfirmModal.multiLayerWarning": "Le retrait de ce calque retire également {numChildren} {numChildren, plural, one {calque imbriqué} many {les calques} other {les calques}}.", - "xpack.maps.distanceSource.requestDescription": "Obtenir des indicateurs de la vue de données : {dataViewName} ; champ géospatial : {geoFieldName}", - "xpack.maps.distanceSource.requestName": "{leftSourceName} à distance de la requête de liaison", "xpack.maps.embeddable.boundsFilterLabel": "{geoFieldsLabel} dans les limites de la carte", "xpack.maps.es_geo_utils.convert.unsupportedGeometryTypeErrorMessage": "Conversion de la géométrie {geometryType} en geojson impossible ; non pris en charge", "xpack.maps.es_geo_utils.distanceFilterAlias": "dans un rayon de {distanceKm} km de {pointLabel}", @@ -23525,10 +23523,6 @@ "xpack.maps.esSearch.topHitsEntitiesCountMsg": "{entityCount} entités trouvées.", "xpack.maps.esSearch.topHitsResultsTrimmedMsg": "Résultats limités aux {entityCount} premières entités sur environ {totalEntities}.", "xpack.maps.esSearch.topHitsSizeMsg": "Affichage des {topHitsSize} premiers documents par entité.", - "xpack.maps.esSearchSource.requestDescription": "Obtenir des documents de la vue de données : {dataViewName} ; champ géospatial : {geoFieldName}", - "xpack.maps.esSearchSource.requestName": "Requête de documents de {layerName}", - "xpack.maps.esSearchSource.topHits.requestDescription": "Obtenir les premiers résultats de la vue de données : {dataViewName} ; entités : {entitiesFieldName} ; champ géospatial : {geoFieldName}", - "xpack.maps.esSearchSource.topHits.requestName": "Requête des premiers résultats de {layerName}", "xpack.maps.fileUpload.trimmedResultsMsg": "Résultats limités à {numFeatures} fonctionnalités, {previewCoverage} % du fichier.", "xpack.maps.filterByMapExtentMenuItem.displayName": "Filtrer {containerLabel} selon les limites de la carte", "xpack.maps.filterByMapExtentMenuItem.displayNameTooltip": "Quand vous vous déplacez sur la carte ou que vous zoomez, le {containerLabel} se met à jour pour afficher uniquement les données visibles dans les limites de la carte.", @@ -23551,7 +23545,6 @@ "xpack.maps.mask.maskLabel": "Masquer {hideNoun}", "xpack.maps.mask.whenJoinMetric": "{whenLabel} l'indicateur de jonction", "xpack.maps.maskLegend.is": "{aggLabel} est", - "xpack.maps.pewPew.requestName": "Requête de chemins de {layerName}", "xpack.maps.scalingDocs.clustersDetails": "Affichez les clusters lorsque les résultats dépassent {maxResultWindow} documents. Affichez les documents lorsqu'il y a moins de {maxResultWindow} résultats.", "xpack.maps.scalingDocs.limitDetails": "Affichez les fonctionnalités des {maxResultWindow} premiers documents.", "xpack.maps.scalingDocs.maxResultWindow": "Contrainte {maxResultWindow} fournie par le paramètre d'index {link}.", @@ -23567,15 +23560,8 @@ "xpack.maps.source.emsTileSourceDescription": "Service de fond de carte de {host}", "xpack.maps.source.esAggSource.topTermLabel": "{fieldLabel} principal", "xpack.maps.source.esGeoGrid.groupBy.termsDescription": "Créez un suivi pour les {maxTermsTracks} termes principaux. Le suivi est tronqué lorsque le nombre d'éléments dépasse la limite.", - "xpack.maps.source.esGeoLine.entityRequestDescription": "Obtenir des entités au sein de la mémoire tampon de la carte depuis la vue de données  : {dataViewName} ; entités : {splitFieldName}", - "xpack.maps.source.esGeoLine.timeSeriesTrackRequestDescription": "Obtenir des suivis de la vue de données : {dataViewName} ; champ géospatial : {geoFieldName}", - "xpack.maps.source.esGeoLine.trackRequestDescription": "Obtenir des pistes pour des entités {numEntities} depuis la vue de données : {dataViewName} ; champ géospatial : {geoFieldName}", "xpack.maps.source.esGeoLineDisabledReason": "{title} requiert une licence Gold.", - "xpack.maps.source.esGrid.compositeInspector.requestName": "Requête composite de {layerName} {bucketsName} ({requestCount})", - "xpack.maps.source.esGrid.compositeInspectorDescription": "Obtenir {bucketsName} de la vue de données : {dataViewName} ; champ géospatial : {geoFieldName}", "xpack.maps.source.esGrid.compositePaginationErrorMessage": "{layerName} génère trop de requêtes. Réduisez \"Résolution de la grille\" et/ou réduisez le nombre d'indicateurs de premier terme.", - "xpack.maps.source.esGrid.inspector.requestDescription": "Obtenir {bucketsName} de la vue de données : {dataViewName} ; champ géospatial : {geoFieldName}", - "xpack.maps.source.esGrid.inspector.requestName": "Requête de {layerName} {bucketsName}", "xpack.maps.source.esGrid.resolutionParamErrorMessage": "Paramètre de résolution de grille non reconnu : {resolution}", "xpack.maps.source.esJoin.countLabel": "Nombre de {indexPatternLabel}", "xpack.maps.source.esSearch.clusterScalingLabel": "Afficher les clusters lorsque les résultats dépassent {maxResultWindow}", @@ -23586,15 +23572,12 @@ "xpack.maps.source.esSource.noGeoFieldErrorMessage": "La vue de données \"{indexPatternLabel}\" ne contient plus le champ géographique \"{geoField}\"", "xpack.maps.source.esSource.stylePropsMetaRequestName": "{layerName} - Métadonnées", "xpack.maps.source.MVTSingleLayerVectorSourceEditor.urlHelpMessage": "URL du service de cartographie vectoriel .mvt. Par exemple, {url}", - "xpack.maps.source.pewPew.inspectorDescription": "Obtenir des chemins depuis la vue de données : {dataViewName} ; source : {sourceFieldName} ; destination : {destFieldName}", "xpack.maps.spatialJoin.wizardForm.withinExpressionValue": "{distance} {units} de fonctionnalités de calque", "xpack.maps.spatialJoinExpression.noDataViewTitle": "Impossible de charger la vue de données {dataViewId}.", "xpack.maps.spatialJoinExpression.value": "fonctionnalités de {geoField}", "xpack.maps.style.fieldSelect.OriginLabel": "Champs de {fieldOrigin}", "xpack.maps.termJoinExpression.topTerms": "{size} principal", "xpack.maps.termJoinExpression.value": "termes {topTerms} de {term}", - "xpack.maps.termSource.requestDescription": "Obtenir des indicateurs depuis la vue de données : {dataViewName} ; champ de terme : {termFieldName}", - "xpack.maps.termSource.requestName": "Requête de liaison de terme de {leftSourceName}", "xpack.maps.tiles.resultsCompleteMsg": "{countPrefix}{count} documents trouvés.", "xpack.maps.tiles.resultsTrimmedMsg": "Les résultats sont limités à {countPrefix}{count} documents.", "xpack.maps.tooltip.pageNumerText": "{pageNumber} de {total}", @@ -24034,8 +24017,6 @@ "xpack.maps.source.esGeoLine.sortFieldPlaceholder": "Sélectionner le champ de tri", "xpack.maps.source.esGeoLine.splitFieldLabel": "Entité", "xpack.maps.source.esGeoLine.splitFieldPlaceholder": "Sélectionner un champ d'entité", - "xpack.maps.source.esGeoLine.timeSeriesTrackRequestName": "Requêtes de piste \"{layerName}\" (séries temporelles)", - "xpack.maps.source.esGeoLine.trackRequestName": "Requêtes de piste \"{layerName}\" (termes)", "xpack.maps.source.esGeoLine.trackSettingsLabel": "Pistes", "xpack.maps.source.esGeoLineDescription": "Créer des lignes à partir de points", "xpack.maps.source.esGeoLineTitle": "Pistes", @@ -24078,7 +24059,6 @@ "xpack.maps.source.esSearch.useMVTVectorTiles": "Utiliser les tuiles vectorielles", "xpack.maps.source.esSearchDescription": "Points, lignes et polygones d'Elasticsearch", "xpack.maps.source.esSearchTitle": "Documents", - "xpack.maps.source.esSource.stylePropsMetaRequestDescription": "Recherche Elasticsearch récupérant les métadonnées de champ utilisées pour le calcul des champs de symbolisation.", "xpack.maps.source.esTopHitsSearch.sortFieldLabel": "Champ de tri", "xpack.maps.source.esTopHitsSearch.sortOrderLabel": "Ordre de tri", "xpack.maps.source.geofieldLabel": "Champ géospatial", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 35a8bfdededd3..77af8443adc24 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -23524,8 +23524,6 @@ "xpack.maps.blendedVectorLayer.clusteredLayerName": "クラスター化された{displayName}", "xpack.maps.common.esSpatialRelation.clusterFilterLabel": "クラスター{gridId}と交差します", "xpack.maps.deleteLayerConfirmModal.multiLayerWarning": "このレイヤーを削除すと、{numChildren}個のネストされた{numChildren, plural, other {レイヤー}}も削除されます。", - "xpack.maps.distanceSource.requestDescription": "データビューからメトリックを取得します:{dataViewName}、地理空間フィールド:{geoFieldName}", - "xpack.maps.distanceSource.requestName": "距離結合リクエスト内の{leftSourceName}", "xpack.maps.embeddable.boundsFilterLabel": "マップ境界内の{geoFieldsLabel}", "xpack.maps.es_geo_utils.convert.unsupportedGeometryTypeErrorMessage": "{geometryType} ジオメトリから Geojson に変換できません。サポートされていません", "xpack.maps.es_geo_utils.distanceFilterAlias": "{pointLabel}の{distanceKm}km以内", @@ -23539,10 +23537,6 @@ "xpack.maps.esSearch.topHitsEntitiesCountMsg": "{entityCount}エンティティが見つかりました。", "xpack.maps.esSearch.topHitsResultsTrimmedMsg": "結果は~{totalEntities}の{entityCount}エンティティに制限されています。", "xpack.maps.esSearch.topHitsSizeMsg": "エンティティごとに上位の{topHitsSize}ドキュメントを表示しています。", - "xpack.maps.esSearchSource.requestDescription": "データビューからドキュメントを取得します:{dataViewName}、地理空間フィールド:{geoFieldName}", - "xpack.maps.esSearchSource.requestName": "{layerName}ドキュメントリクエスト", - "xpack.maps.esSearchSource.topHits.requestDescription": "データビューから上位の一致を取得します:{dataViewName}、表現:{entitiesFieldName}、地理空間フィールド:{geoFieldName}", - "xpack.maps.esSearchSource.topHits.requestName": "{layerName}の上位の一致リクエスト", "xpack.maps.fileUpload.trimmedResultsMsg": "結果は{numFeatures}機能、ファイルの{previewCoverage}%に制限されています。", "xpack.maps.filterByMapExtentMenuItem.displayName": "マップ境界で{containerLabel}をフィルター", "xpack.maps.filterByMapExtentMenuItem.displayNameTooltip": "マップをズームおよびパンすると、{containerLabel}が更新され、マップ境界に表示されるデータのみが表示されます。", @@ -23565,7 +23559,6 @@ "xpack.maps.mask.maskLabel": "{hideNoun}を非表示", "xpack.maps.mask.whenJoinMetric": "{whenLabel}結合メトリック", "xpack.maps.maskLegend.is": "{aggLabel}は", - "xpack.maps.pewPew.requestName": "{layerName}パスリクエスト", "xpack.maps.scalingDocs.clustersDetails": "結果が{maxResultWindow}ドキュメントを超えたときにクラスターを表示します。結果が{maxResultWindow}未満のときにドキュメントを表示します。", "xpack.maps.scalingDocs.limitDetails": "最初の{maxResultWindow}ドキュメントから特徴量を表示します。", "xpack.maps.scalingDocs.maxResultWindow": "{link}のインデックス設定によって提供される{maxResultWindow}制約。", @@ -23581,15 +23574,8 @@ "xpack.maps.source.emsTileSourceDescription": "{host}からのベースマップサービス", "xpack.maps.source.esAggSource.topTermLabel": "上位{fieldLabel}", "xpack.maps.source.esGeoGrid.groupBy.termsDescription": "上位{maxTermsTracks}語句のトラックを作成します。ポイント数が上限を超えると、トラックは切り捨てられます。", - "xpack.maps.source.esGeoLine.entityRequestDescription": "データビューからマップバッファー内の表現を取得します:{dataViewName}、表現:{splitFieldName}", - "xpack.maps.source.esGeoLine.timeSeriesTrackRequestDescription": "データビューからトラックを取得します:{dataViewName}、地理空間フィールド:{geoFieldName}", - "xpack.maps.source.esGeoLine.trackRequestDescription": "データビューから{numEntities}個の表現のトラックを取得します:{dataViewName}、地理空間フィールド:{geoFieldName}", "xpack.maps.source.esGeoLineDisabledReason": "{title}には Gold ライセンスが必要です。", - "xpack.maps.source.esGrid.compositeInspector.requestName": "{layerName} {bucketsName}複合リクエスト({requestCount})", - "xpack.maps.source.esGrid.compositeInspectorDescription": "データビューから{bucketsName}を取得します:{dataViewName}、地理空間フィールド:{geoFieldName}", "xpack.maps.source.esGrid.compositePaginationErrorMessage": "{layerName}はリクエスト過多の原因になります。「グリッド解像度」を下げるか、またはトップ用語「メトリック」の数を減らしてください。", - "xpack.maps.source.esGrid.inspector.requestDescription": "データビューから{bucketsName}を取得します:{dataViewName}、地理空間フィールド:{geoFieldName}", - "xpack.maps.source.esGrid.inspector.requestName": "{layerName} {bucketsName}リクエスト", "xpack.maps.source.esGrid.resolutionParamErrorMessage": "グリッド解像度パラメーターが認識されません: {resolution}", "xpack.maps.source.esJoin.countLabel": "{indexPatternLabel}のカウント", "xpack.maps.source.esSearch.clusterScalingLabel": "結果が{maxResultWindow}を超えたらクラスターを表示", @@ -23600,15 +23586,12 @@ "xpack.maps.source.esSource.noGeoFieldErrorMessage": "データビュー\"{indexPatternLabel}\"には現在ジオフィールド\"{geoField}\"が含まれていません", "xpack.maps.source.esSource.stylePropsMetaRequestName": "{layerName} - メタデータ", "xpack.maps.source.MVTSingleLayerVectorSourceEditor.urlHelpMessage": ".mvtベクトルタイルサービスのURL。例:{url}", - "xpack.maps.source.pewPew.inspectorDescription": "データビューからパスを取得します:{dataViewName}、ソース:{sourceFieldName}、デスティネーション:{destFieldName}", "xpack.maps.spatialJoin.wizardForm.withinExpressionValue": "レイヤー特徴量の{distance} {units}", "xpack.maps.spatialJoinExpression.noDataViewTitle": "データビュー{dataViewId}を読み込めません。", "xpack.maps.spatialJoinExpression.value": "{geoField}からの特徴量", "xpack.maps.style.fieldSelect.OriginLabel": "{fieldOrigin}からのフィールド", "xpack.maps.termJoinExpression.topTerms": "上位{size}", "xpack.maps.termJoinExpression.value": "{term}からの{topTerms}用語", - "xpack.maps.termSource.requestDescription": "データビューからメトリックを取得します:{dataViewName}、用語フィールド:{termFieldName}", - "xpack.maps.termSource.requestName": "{leftSourceName}用語結合リクエスト", "xpack.maps.tiles.resultsCompleteMsg": "{countPrefix}{count}件のドキュメントが見つかりました。", "xpack.maps.tiles.resultsTrimmedMsg": "結果は{countPrefix}{count}ドキュメントに制限されています。", "xpack.maps.tooltip.pageNumerText": "{pageNumber} / {total}", @@ -24048,8 +24031,6 @@ "xpack.maps.source.esGeoLine.sortFieldPlaceholder": "ソートフィールドを選択", "xpack.maps.source.esGeoLine.splitFieldLabel": "エンティティ", "xpack.maps.source.esGeoLine.splitFieldPlaceholder": "エンティティフィールドを選択", - "xpack.maps.source.esGeoLine.timeSeriesTrackRequestName": "'{layerName}'トラックリクエスト(時系列)", - "xpack.maps.source.esGeoLine.trackRequestName": "'{layerName}'トラックリクエスト(用語)", "xpack.maps.source.esGeoLine.trackSettingsLabel": "追跡", "xpack.maps.source.esGeoLineDescription": "ポイントから線を作成", "xpack.maps.source.esGeoLineTitle": "追跡", @@ -24092,7 +24073,6 @@ "xpack.maps.source.esSearch.useMVTVectorTiles": "ベクトルタイルを使用", "xpack.maps.source.esSearchDescription": "Elasticsearch の点、線、多角形", "xpack.maps.source.esSearchTitle": "ドキュメント", - "xpack.maps.source.esSource.stylePropsMetaRequestDescription": "シンボル化バンドを計算するために使用されるフィールドメタデータを取得するElasticsearchリクエスト。", "xpack.maps.source.esTopHitsSearch.sortFieldLabel": "並べ替えフィールド", "xpack.maps.source.esTopHitsSearch.sortOrderLabel": "並べ替え順", "xpack.maps.source.geofieldLabel": "地理空間フィールド", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 9b4f2125a4034..1f9c3cdc24b55 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -23524,8 +23524,6 @@ "xpack.maps.blendedVectorLayer.clusteredLayerName": "集群 {displayName}", "xpack.maps.common.esSpatialRelation.clusterFilterLabel": "相交集群 {gridId}", "xpack.maps.deleteLayerConfirmModal.multiLayerWarning": "移除此图层还会移除 {numChildren} 个嵌套{numChildren, plural, other {图层}}。", - "xpack.maps.distanceSource.requestDescription": "获取指标的数据视图:{dataViewName}、地理空间字段:{geoFieldName}", - "xpack.maps.distanceSource.requestName": "距离联接请求内的 {leftSourceName}", "xpack.maps.embeddable.boundsFilterLabel": "地图边界内的 {geoFieldsLabel}", "xpack.maps.es_geo_utils.convert.unsupportedGeometryTypeErrorMessage": "无法将 {geometryType} 几何图形转换成 geojson,不支持", "xpack.maps.es_geo_utils.distanceFilterAlias": "{pointLabel} {distanceKm}km 内", @@ -23539,10 +23537,6 @@ "xpack.maps.esSearch.topHitsEntitiesCountMsg": "找到 {entityCount} 个实体。", "xpack.maps.esSearch.topHitsResultsTrimmedMsg": "结果限制为 ~{totalEntities} 个实体中的前 {entityCount} 个。", "xpack.maps.esSearch.topHitsSizeMsg": "显示每个实体排名前 {topHitsSize} 的文档。", - "xpack.maps.esSearchSource.requestDescription": "获取文档的数据视图:{dataViewName}、地理空间字段:{geoFieldName}", - "xpack.maps.esSearchSource.requestName": "{layerName} 文档请求", - "xpack.maps.esSearchSource.topHits.requestDescription": "获取最高命中结果的数据视图:{dataViewName}、实体:{entitiesFieldName}、地理空间字段:{geoFieldName}", - "xpack.maps.esSearchSource.topHits.requestName": "{layerName} 最高命中结果请求", "xpack.maps.fileUpload.trimmedResultsMsg": "结果仅限于 {numFeatures} 个特征、{previewCoverage}% 的文件。", "xpack.maps.filterByMapExtentMenuItem.displayName": "按地图边界筛选 {containerLabel}", "xpack.maps.filterByMapExtentMenuItem.displayNameTooltip": "当您缩放和平移地图时,{containerLabel} 会进行更新以仅显示在地图边界中可见的数据。", @@ -23565,7 +23559,6 @@ "xpack.maps.mask.maskLabel": "隐藏 {hideNoun}", "xpack.maps.mask.whenJoinMetric": "{whenLabel} 联接指标", "xpack.maps.maskLegend.is": "{aggLabel} 为", - "xpack.maps.pewPew.requestName": "{layerName} 路径请求", "xpack.maps.scalingDocs.clustersDetails": "结果超过 {maxResultWindow} 个文档时显示集群。结果数小于 {maxResultWindow} 时显示文档。", "xpack.maps.scalingDocs.limitDetails": "显示前 {maxResultWindow} 个文档中的特征。", "xpack.maps.scalingDocs.maxResultWindow": "{link} 索引设置提供的 {maxResultWindow} 限制。", @@ -23581,15 +23574,8 @@ "xpack.maps.source.emsTileSourceDescription": "来自 {host} 的基础地图服务", "xpack.maps.source.esAggSource.topTermLabel": "排名靠前 {fieldLabel}", "xpack.maps.source.esGeoGrid.groupBy.termsDescription": "为排名前 {maxTermsTracks} 的词创建轨迹。点数超出限制时,会截短轨迹。", - "xpack.maps.source.esGeoLine.entityRequestDescription": "获取地图缓冲内实体的数据视图:{dataViewName}、实体:{splitFieldName}", - "xpack.maps.source.esGeoLine.timeSeriesTrackRequestDescription": "获取轨迹的数据视图:{dataViewName}、地理空间字段:{geoFieldName}", - "xpack.maps.source.esGeoLine.trackRequestDescription": "获取 {numEntities} 个实体的轨迹的数据视图:{dataViewName}、地理空间字段:{geoFieldName}", "xpack.maps.source.esGeoLineDisabledReason": "{title} 需要黄金级许可证。", - "xpack.maps.source.esGrid.compositeInspector.requestName": "{layerName} {bucketsName} 组合请求 ({requestCount})", - "xpack.maps.source.esGrid.compositeInspectorDescription": "获取 {bucketsName} 的数据视图:{dataViewName}、地理空间字段:{geoFieldName}", "xpack.maps.source.esGrid.compositePaginationErrorMessage": "{layerName} 正导致过多的请求。降低“网格分辨率”和/或减少热门词“指标”的数量。", - "xpack.maps.source.esGrid.inspector.requestDescription": "获取 {bucketsName} 的数据视图:{dataViewName}、地理空间字段:{geoFieldName}", - "xpack.maps.source.esGrid.inspector.requestName": "{layerName} {bucketsName} 请求", "xpack.maps.source.esGrid.resolutionParamErrorMessage": "无法识别网格分辨率参数:{resolution}", "xpack.maps.source.esJoin.countLabel": "{indexPatternLabel} 的计数", "xpack.maps.source.esSearch.clusterScalingLabel": "结果超过 {maxResultWindow} 个时显示集群", @@ -23600,15 +23586,12 @@ "xpack.maps.source.esSource.noGeoFieldErrorMessage": "数据视图“{indexPatternLabel}”不再包含地理字段“{geoField}”", "xpack.maps.source.esSource.stylePropsMetaRequestName": "{layerName} - 元数据", "xpack.maps.source.MVTSingleLayerVectorSourceEditor.urlHelpMessage": ".mvt 矢量磁帖服务的 URL。例如 {url}", - "xpack.maps.source.pewPew.inspectorDescription": "获取路径的数据视图:{dataViewName}、源:{sourceFieldName}、目标:{destFieldName}", "xpack.maps.spatialJoin.wizardForm.withinExpressionValue": "图层特征的 {distance} {units}", "xpack.maps.spatialJoinExpression.noDataViewTitle": "无法加载数据视图 {dataViewId}。", "xpack.maps.spatialJoinExpression.value": "{geoField} 中的特征", "xpack.maps.style.fieldSelect.OriginLabel": "来自 {fieldOrigin} 的字段", "xpack.maps.termJoinExpression.topTerms": "排名靠前 {size}", "xpack.maps.termJoinExpression.value": "来自 {term} 的 {topTerms} 词", - "xpack.maps.termSource.requestDescription": "获取指标的数据视图:{dataViewName}、词字段:{termFieldName}", - "xpack.maps.termSource.requestName": "{leftSourceName} 词联接请求", "xpack.maps.tiles.resultsCompleteMsg": "找到 {countPrefix}{count} 个文档。", "xpack.maps.tiles.resultsTrimmedMsg": "结果仅限为 {countPrefix}{count} 个文档。", "xpack.maps.tooltip.pageNumerText": "{total} 的 {pageNumber}", @@ -24048,8 +24031,6 @@ "xpack.maps.source.esGeoLine.sortFieldPlaceholder": "选择排序字段", "xpack.maps.source.esGeoLine.splitFieldLabel": "实体", "xpack.maps.source.esGeoLine.splitFieldPlaceholder": "选择实体字段", - "xpack.maps.source.esGeoLine.timeSeriesTrackRequestName": "“{layerName}”轨迹请求(时间序列)", - "xpack.maps.source.esGeoLine.trackRequestName": "“{layerName}”轨迹请求(词)", "xpack.maps.source.esGeoLine.trackSettingsLabel": "轨迹", "xpack.maps.source.esGeoLineDescription": "从点创建线", "xpack.maps.source.esGeoLineTitle": "轨迹", @@ -24092,7 +24073,6 @@ "xpack.maps.source.esSearch.useMVTVectorTiles": "使用矢量磁贴", "xpack.maps.source.esSearchDescription": "Elasticsearch 的点、线和多边形", "xpack.maps.source.esSearchTitle": "文档", - "xpack.maps.source.esSource.stylePropsMetaRequestDescription": "检索用于计算符号化带的字段元数据的 Elasticsearch 请求。", "xpack.maps.source.esTopHitsSearch.sortFieldLabel": "排序字段", "xpack.maps.source.esTopHitsSearch.sortOrderLabel": "排序顺序", "xpack.maps.source.geofieldLabel": "地理空间字段", diff --git a/x-pack/test/functional/apps/maps/group1/blended_vector_layer.js b/x-pack/test/functional/apps/maps/group1/blended_vector_layer.js index 28a68edf0e75c..d142513dfd4a9 100644 --- a/x-pack/test/functional/apps/maps/group1/blended_vector_layer.js +++ b/x-pack/test/functional/apps/maps/group1/blended_vector_layer.js @@ -13,6 +13,9 @@ export default function ({ getPageObjects, getService }) { const security = getService('security'); describe('blended vector layer', () => { + const LOAD_DOCUMENTS_REQUEST_NAME = 'load layer features (logstash-*)'; + const LOAD_CLUSTERS_REQUEST_NAME = 'load layer features (Clustered logstash-*)'; + before(async () => { await security.testUser.setRoles(['test_logstash_reader', 'global_maps_all']); await PageObjects.maps.loadSavedMap('blended document example'); @@ -27,20 +30,26 @@ export default function ({ getPageObjects, getService }) { }); it('should request documents when zoomed to smaller regions showing less data', async () => { - const { rawResponse: response } = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse( + LOAD_DOCUMENTS_REQUEST_NAME + ); // Allow a range of hits to account for variances in browser window size. expect(response.hits.hits.length).to.be.within(5, 12); }); it('should request clusters when zoomed to larger regions showing lots of data', async () => { await PageObjects.maps.setView(20, -90, 2); - const { rawResponse: response } = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse( + LOAD_CLUSTERS_REQUEST_NAME + ); expect(response.aggregations.gridSplit.buckets.length).to.equal(15); }); it('should request documents when query narrows data', async () => { await PageObjects.maps.setAndSubmitQuery('bytes > 19000'); - const { rawResponse: response } = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse( + LOAD_DOCUMENTS_REQUEST_NAME + ); expect(response.hits.hits.length).to.equal(75); }); }); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/dashboard.js b/x-pack/test/functional/apps/maps/group2/embeddable/dashboard.js index 2750bf3a7f68d..bdc681e06eece 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/dashboard.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/dashboard.js @@ -77,7 +77,7 @@ export default function ({ getPageObjects, getService }) { await retry.try(async () => { const joinExampleRequestNames = await inspector.getRequestNames(); expect(joinExampleRequestNames).to.equal( - 'geo_shapes* documents request,geo_shapes* term join request' + 'load layer features (geo_shapes*),load join metrics (geo_shapes*)' ); }); await inspector.close(); @@ -88,7 +88,7 @@ export default function ({ getPageObjects, getService }) { await inspector.close(); expect(singleExampleRequest).to.be(true); - expect(selectedExampleRequest).to.equal('logstash-* grid request'); + expect(selectedExampleRequest).to.equal('load layer features (logstash-*)'); }); it('should apply container state (time, query, filters) to embeddable when loaded', async () => { @@ -120,7 +120,7 @@ export default function ({ getPageObjects, getService }) { const { rawResponse: joinResponse } = await PageObjects.maps.getResponseFromDashboardPanel( 'join example', - 'geo_shapes* term join request' + 'load join metrics (geo_shapes*)' ); expect(joinResponse.aggregations.join.buckets.length).to.equal(1); }); diff --git a/x-pack/test/functional/apps/maps/group4/joins.js b/x-pack/test/functional/apps/maps/group4/joins.js index bf1304e3f3470..74e68cb12c11a 100644 --- a/x-pack/test/functional/apps/maps/group4/joins.js +++ b/x-pack/test/functional/apps/maps/group4/joins.js @@ -39,7 +39,7 @@ export default function ({ getPageObjects, getService }) { it('should re-fetch join with refresh timer', async () => { async function getRequestTimestamp() { - await PageObjects.maps.openInspectorRequest('geo_shapes* term join request'); + await PageObjects.maps.openInspectorRequest('load join metrics (geo_shapes*)'); const requestStats = await inspector.getTableData(); const requestTimestamp = PageObjects.maps.getInspectorStatRowHit( requestStats, @@ -122,7 +122,7 @@ export default function ({ getPageObjects, getService }) { it('should not apply query to source and apply query to join', async () => { const { rawResponse: joinResponse } = await PageObjects.maps.getResponse( - 'geo_shapes* term join request' + 'load join metrics (geo_shapes*)' ); expect(joinResponse.aggregations.join.buckets.length).to.equal(2); }); @@ -139,7 +139,7 @@ export default function ({ getPageObjects, getService }) { it('should apply query to join request', async () => { const { rawResponse: joinResponse } = await PageObjects.maps.getResponse( - 'geo_shapes* term join request' + 'load join metrics (geo_shapes*)' ); expect(joinResponse.aggregations.join.buckets.length).to.equal(1); });