From 19e58b45273604ce35030f11f8fec861bf93d006 Mon Sep 17 00:00:00 2001 From: Michael Ihde Date: Wed, 5 May 2021 18:43:02 -0400 Subject: [PATCH] feat: address feature request #99239 This commit addresses #99239 as a proof of concept. The filter list is now constrained only to geo fields that are actually used within the layer sources. Because the ultimate filter created is independent of the index pattern, the UI instead shows the field type and deduplicates when the same field exist in multiple index pattern. This commit leaves room to refactor out some unneeded types and does not yet add unittests. --- .../components/distance_filter_form.tsx | 4 +- .../public/components/geo_field_select.tsx | 79 +++++++++++++++++++ .../public/components/geometry_filter_form.js | 4 +- .../map_container/index.ts | 4 +- .../map_container/map_container.tsx | 60 ++++++++------ .../plugins/maps/public/index_pattern_util.ts | 26 ++++++ .../public/routes/map_page/map_app/index.ts | 4 +- .../routes/map_page/map_app/map_app.tsx | 19 ++--- .../maps/public/selectors/map_selectors.ts | 34 ++++++++ 9 files changed, 192 insertions(+), 42 deletions(-) create mode 100644 x-pack/plugins/maps/public/components/geo_field_select.tsx diff --git a/x-pack/plugins/maps/public/components/distance_filter_form.tsx b/x-pack/plugins/maps/public/components/distance_filter_form.tsx index 14ae6b11b85c8..c0bb0d8366f71 100644 --- a/x-pack/plugins/maps/public/components/distance_filter_form.tsx +++ b/x-pack/plugins/maps/public/components/distance_filter_form.tsx @@ -16,7 +16,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public'; -import { MultiIndexGeoFieldSelect } from './multi_index_geo_field_select'; +import { GeoFieldSelect } from './geo_field_select'; import { GeoFieldWithIndex } from './geo_field_with_index'; import { ActionSelect } from './action_select'; import { ACTION_GLOBAL_APPLY_FILTER } from '../../../../../src/plugins/data/public'; @@ -95,7 +95,7 @@ export class DistanceFilterForm extends Component { /> - void; + selectedField: GeoFieldWithIndex | undefined; +} + +export function GeoFieldSelect({ fields, onChange, selectedField }: Props) { + function onFieldSelect(selectedOptionId: string) { + const { geoFieldType, geoFieldName } = splitOptionId(selectedOptionId); + + const newSelectedField = fields.find((field) => { + return field.geoFieldType === geoFieldType && field.geoFieldName === geoFieldName; + }); + onChange(newSelectedField); + } + + const options = fields.map((geoField: GeoFieldWithIndex) => { + return { + inputDisplay: ( + + + {geoField.geoFieldType} + +
+ {geoField.geoFieldName} +
+ ), + value: createOptionId(geoField), + }; + }); + + return ( + + + + ); +} diff --git a/x-pack/plugins/maps/public/components/geometry_filter_form.js b/x-pack/plugins/maps/public/components/geometry_filter_form.js index ab9964d49d0ef..83102bfd712b4 100644 --- a/x-pack/plugins/maps/public/components/geometry_filter_form.js +++ b/x-pack/plugins/maps/public/components/geometry_filter_form.js @@ -20,7 +20,7 @@ import { import { i18n } from '@kbn/i18n'; import { ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../../common/constants'; import { getEsSpatialRelationLabel } from '../../common/i18n_getters'; -import { MultiIndexGeoFieldSelect } from './multi_index_geo_field_select'; +import { GeoFieldSelect } from './geo_field_select'; import { ActionSelect } from './action_select'; import { ACTION_GLOBAL_APPLY_FILTER } from '../../../../../src/plugins/data/public'; @@ -137,7 +137,7 @@ export class GeometryFilterForm extends Component { /> - ; mapInitError: string | null | undefined; refreshConfig: MapRefreshConfig; renderTooltipContent?: RenderToolTipContent; @@ -66,7 +66,10 @@ interface State { export class MapContainer extends Component { private _isMounted: boolean = false; private _isInitalLoadRenderTimerStarted: boolean = false; - private _prevIndexPatternIds: string[] = []; + private _prevIndexPatternIdsAndFieldNames: Array<{ + indexPatternId: string; + fieldName: string; + }> = []; private _refreshTimerId: number | null = null; private _prevIsPaused: boolean | null = null; private _prevInterval: number | null = null; @@ -91,7 +94,7 @@ export class MapContainer extends Component { } if (!!this.props.addFilters) { - this._loadGeoFields(this.props.indexPatternIds); + this._loadGeoFields(this.props.indexPatternIdsAndFieldNames); } } @@ -116,37 +119,44 @@ export class MapContainer extends Component { } }; - _loadGeoFields = async (nextIndexPatternIds: string[]) => { - if (_.isEqual(nextIndexPatternIds, this._prevIndexPatternIds)) { + _loadGeoFields = async ( + nextIndexPatternIdsAndFieldNames: Array<{ indexPatternId: string; fieldName: string }> + ) => { + if (_.isEqual(nextIndexPatternIdsAndFieldNames, this._prevIndexPatternIdsAndFieldNames)) { // all ready loaded index pattern ids return; } - this._prevIndexPatternIds = nextIndexPatternIds; + this._prevIndexPatternIdsAndFieldNames = nextIndexPatternIdsAndFieldNames; - const geoFields: GeoFieldWithIndex[] = []; - const indexPatterns = await getIndexPatternsFromIds(nextIndexPatternIds); - indexPatterns.forEach((indexPattern) => { - indexPattern.fields.forEach((field) => { - if ( - indexPattern.id && - !indexPatternsUtils.isNestedField(field) && - (field.type === ES_GEO_FIELD_TYPE.GEO_POINT || field.type === ES_GEO_FIELD_TYPE.GEO_SHAPE) - ) { - geoFields.push({ - geoFieldName: field.name, - geoFieldType: field.type, - indexPatternTitle: indexPattern.title, - indexPatternId: indexPattern.id, - }); - } - }); + let geoFields: GeoFieldWithIndex[] = []; + const queryableFields = await getFieldsFromIds(nextIndexPatternIdsAndFieldNames); + queryableFields.forEach(({ indexPattern, field }) => { + if ( + indexPattern.id && + !indexPatternsUtils.isNestedField(field) && + (field.type === ES_GEO_FIELD_TYPE.GEO_POINT || field.type === ES_GEO_FIELD_TYPE.GEO_SHAPE) + ) { + // TODO - the indexPatterns are somewhat silly to include in the various + // filter tools because ultimately the filter that is created simply uses + // the field name and applies to all indexes. Furthermore, it clutters + // the user interface because the same name is repeated mutliple times if + // but the final effect is the same regardless of what you pick. To keep the + // changes small, no refactoring has been done to the GeoFieldWithIndex type + // we simply set the indexPattern fields to null + geoFields.push({ + geoFieldName: field.name, + geoFieldType: field.type, + indexPatternTitle: null, + indexPatternId: null, + }); + } }); + geoFields = _.uniqWith(geoFields, _.isEqual); if (!this._isMounted) { return; } - this.setState({ geoFields }); }; diff --git a/x-pack/plugins/maps/public/index_pattern_util.ts b/x-pack/plugins/maps/public/index_pattern_util.ts index f7894085b15ac..4ed84763474c0 100644 --- a/x-pack/plugins/maps/public/index_pattern_util.ts +++ b/x-pack/plugins/maps/public/index_pattern_util.ts @@ -29,6 +29,32 @@ export function getGeoTileAggNotSupportedReason(field: IFieldType): string | nul return null; } +export async function getFieldFromIndexByName( + indexPatternId: string, + fieldName: string +) { + const indexPattern = await getIndexPatternService().get(indexPatternId); + const field = indexPattern.fields.getByName(fieldName) + return {indexPattern, field}; +} + +export async function getFieldsFromIds( + indexPatternIdsWithFields +) { + const promises: IndexPattern[] = []; + indexPatternIdsWithFields.forEach(async ({indexPatternId, fieldName}) => { + try { + // @ts-ignore + promises.push(getFieldFromIndexByName(indexPatternId, fieldName)); + } catch (error) { + // Unable to load index pattern, better to not throw error so map can render + // Error will be surfaced by layer since it too will be unable to locate the index pattern + return null; + } + }); + return await Promise.all(promises); +} + export async function getIndexPatternsFromIds( indexPatternIds: string[] = [] ): Promise { diff --git a/x-pack/plugins/maps/public/routes/map_page/map_app/index.ts b/x-pack/plugins/maps/public/routes/map_page/map_app/index.ts index 62e645198ba30..968d9afc34369 100644 --- a/x-pack/plugins/maps/public/routes/map_page/map_app/index.ts +++ b/x-pack/plugins/maps/public/routes/map_page/map_app/index.ts @@ -14,7 +14,7 @@ import { getFlyoutDisplay, getIsFullScreen } from '../../../selectors/ui_selecto import { getFilters, getQuery, - getQueryableUniqueIndexPatternIds, + getQueryableUniqueIndexPatternIdsAndFieldNames, getRefreshConfig, getTimeFilters, hasDirtyState, @@ -31,7 +31,7 @@ function mapStateToProps(state: MapStoreState) { isOpenSettingsDisabled: getFlyoutDisplay(state) !== FLYOUT_STATE.NONE, isSaveDisabled: hasDirtyState(state), inspectorAdapters: getInspectorAdapters(state), - nextIndexPatternIds: getQueryableUniqueIndexPatternIds(state), + nextIndexPatternIdsAndFieldNames: getQueryableUniqueIndexPatternIdsAndFieldNames(state), flyoutDisplay: getFlyoutDisplay(state), refreshConfig: getRefreshConfig(state), filters: getFilters(state), diff --git a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx index 53c84e4351cc0..32dc36abaa337 100644 --- a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx @@ -38,7 +38,7 @@ import { QueryState, } from '../../../../../../../src/plugins/data/public'; import { MapContainer } from '../../../connected_components/map_container'; -import { getIndexPatternsFromIds } from '../../../index_pattern_util'; +import { getIndexPatternsFromIds, getQueryableUniqueIndexPatternIdsAndFieldNames } from '../../../index_pattern_util'; import { getTopNavConfig } from '../top_nav_config'; import { MapRefreshConfig, MapQuery } from '../../../../common/descriptor_types'; import { goToSpecifiedPath } from '../../../render_app'; @@ -64,7 +64,7 @@ interface Props { enableFullScreen: () => void; openMapSettings: () => void; inspectorAdapters: Adapters; - nextIndexPatternIds: string[]; + nextIndexPatternIdsAndFieldNames: {indexPatternId:string, fieldName:string}[]; dispatchSetQuery: ({ forceRefresh, filters, @@ -95,7 +95,7 @@ export class MapApp extends React.Component { _globalSyncChangeMonitorSubscription: Subscription | null = null; _appSyncUnsubscribe: (() => void) | null = null; _appStateManager = new AppStateManager(); - _prevIndexPatternIds: string[] | null = null; + _prevIndexPatternIdsAndFieldNames: {indexPatternId:string, fieldName:string}[] | null = null; _isMounted: boolean = false; constructor(props: Props) { @@ -132,7 +132,7 @@ export class MapApp extends React.Component { } componentDidUpdate() { - this._updateIndexPatterns(); + this._updateIndexPatternsAndFieldNames(); } componentWillUnmount() { @@ -167,16 +167,17 @@ export class MapApp extends React.Component { this._onQueryChange({ time: globalState.time }); }; - async _updateIndexPatterns() { - const { nextIndexPatternIds } = this.props; + async _updateIndexPatternsAndFieldNames() { + const { nextIndexPatternIdsAndFieldNames } = this.props; - if (_.isEqual(nextIndexPatternIds, this._prevIndexPatternIds)) { + if (_.isEqual(nextIndexPatternIdsAndFieldNames, this._prevIndexPatternIdsAndFieldNames)) { return; } - this._prevIndexPatternIds = nextIndexPatternIds; + this._prevIndexPatternIdsAndFieldNames = nextIndexPatternIdsAndFieldNames; - const indexPatterns = await getIndexPatternsFromIds(nextIndexPatternIds); + const indexPatternIds = _.map(nextIndexPatternIdsAndFieldNames, 'indexPatternId'); + const indexPatterns = await getIndexPatternsFromIds(indexPatternIds); if (this._isMounted) { this.setState({ indexPatterns }); } diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index d89a5cb76416b..d78a41e75e550 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -23,6 +23,7 @@ import { TiledVectorLayer } from '../classes/layers/tiled_vector_layer/tiled_vec import { DatashaderLayer } from '../datashader/datashader_layer'; import { copyPersistentState, TRACKED_LAYER_DESCRIPTOR } from '../reducers/util'; import { InnerJoin } from '../classes/joins/inner_join'; +import { IESSource } from '../classes/sources/es_source'; import { getSourceByType } from '../classes/sources/source_registry'; import { GeojsonFileSource } from '../classes/sources/geojson_file_source'; import { @@ -395,6 +396,39 @@ export const getQueryableUniqueIndexPatternIds = createSelector( } ); +// Get list of unique index patterns, excluding index patterns from layers that disable applyGlobalQuery +export const getQueryableUniqueIndexPatternIdsAndFieldNames = createSelector( + getLayerList, + getWaitingForMapReadyLayerListRaw, + (layerList, waitingForMapReadyLayerList) => { + const indexPatternIdsAndFieldNames: Array<{ indexPatternId: string; fieldName: string }> = []; + + if (waitingForMapReadyLayerList.length) { + waitingForMapReadyLayerList.forEach((layerDescriptor) => { + const layer = createLayerInstance(layerDescriptor); + const indexPatternIds = layer.getQueryableIndexPatternIds(); + if (layer.getSource().isESSource()) { + const fieldName = (layer.getSource() as IESSource).getGeoFieldName(); + indexPatternIds.forEach((indexPatternId) => { + indexPatternIdsAndFieldNames.push({ indexPatternId, fieldName }); + }); + } + }); + } else { + layerList.forEach((layer) => { + const indexPatternIds = layer.getQueryableIndexPatternIds(); + if (layer.getSource().isESSource()) { + const fieldName = (layer.getSource() as IESSource).getGeoFieldName(); + indexPatternIds.forEach((indexPatternId) => { + indexPatternIdsAndFieldNames.push({ indexPatternId, fieldName }); + }); + } + }); + } + return _.uniq(indexPatternIdsAndFieldNames); + } +); + export const hasDirtyState = createSelector(getLayerListRaw, (layerListRaw) => { return layerListRaw.some((layerDescriptor) => { if (layerDescriptor.__isPreviewLayer) {