[maps] remove IVectorSource.getFieldNames (#159747)

`IVectorSource` interface has many similar sounding methods that are
used for different purposes. These lead to confusion and an unclear API
* getFieldNames
* getFields
* getFieldByName

Although `getFieldNames` sounds similar to `getFields`, the 2 are used
for very different purposes.
* getFieldNames returns a string array that is used to trigger source
re-fetch
* getFields returns an array of fields to drive UI such as field
selection for data driven styling

`getFieldNames` overlaps 100% in functionality with `getSyncMeta` and is
not needed. Combining output of `getFieldNames` into `getSyncMeta`
simplifies the `IVectorSource` API and removes some confusion.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2023-06-27 11:36:43 -06:00 committed by GitHub
parent 8bbfeef0a3
commit 2fb4d5fff8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 113 additions and 118 deletions

View file

@ -106,10 +106,6 @@ export class CustomRasterSource implements IRasterSource {
return false;
}
getFieldNames(): string[] {
return [];
}
renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement<any> | null {
return null;
}

View file

@ -46,6 +46,9 @@ export type SourceRequestMeta = DataFilters & {
};
export type VectorSourceRequestMeta = SourceRequestMeta & {
/*
* List of feature property keys used in client for data driven styling, join keys, and feature masking
*/
fieldNames: string[];
timesliceMaskField?: string;
sourceMeta: object | null;

View file

@ -100,7 +100,7 @@ export class HeatmapLayer extends AbstractLayer {
prevDataRequest: this.getSourceDataRequest(),
requestMeta: buildVectorRequestMeta(
this.getSource(),
this.getSource().getFieldNames(),
[], // fieldNames is empty because heatmap layer only support metrics
syncContext.dataFilters,
this.getQuery(),
syncContext.isForceRefresh,

View file

@ -51,6 +51,7 @@ import {
import { IVectorSource } from '../../sources/vector_source';
import { LayerIcon, ILayer } from '../layer';
import { InnerJoin } from '../../joins/inner_join';
import { isSpatialJoin } from '../../joins/is_spatial_join';
import { IField } from '../../fields/field';
import { DataRequestContext } from '../../../actions';
import { ITooltipProperty } from '../../tooltips/tooltip_property';
@ -354,9 +355,12 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer {
isFeatureEditorOpenForLayer: boolean
): Promise<VectorSourceRequestMeta> {
const fieldNames = [
...source.getFieldNames(),
...style.getSourceFieldNames(),
...this.getValidJoins().map((join) => join.getLeftField().getName()),
...this.getValidJoins()
.filter((join) => {
return !isSpatialJoin(join.toDescriptor());
})
.map((join) => join.getLeftField().getName()),
];
const timesliceMaskFieldName = await source.getTimesliceMaskFieldName();
@ -550,7 +554,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer {
const joinRequestMeta = buildVectorRequestMeta(
joinSource,
joinSource.getFieldNames(),
[], // fieldNames is empty because join sources only support metrics
dataFilters,
joinSource.getWhereQuery(),
isForceRefresh,

View file

@ -8,12 +8,16 @@
import { i18n } from '@kbn/i18n';
import { GeoJsonProperties } from 'geojson';
import { DataView } from '@kbn/data-plugin/common';
import type { IESAggSource } from './types';
import type { IESAggSource, ESAggsSourceSyncMeta } from './types';
import { AbstractESSource } from '../es_source';
import { esAggFieldsFactory, IESAggField } from '../../fields/agg';
import { AGG_TYPE, FIELD_ORIGIN } from '../../../../common/constants';
import { getSourceAggKey } from '../../../../common/get_agg_key';
import { AbstractESAggSourceDescriptor, AggDescriptor } from '../../../../common/descriptor_types';
import {
AbstractESAggSourceDescriptor,
AggDescriptor,
DataFilters,
} from '../../../../common/descriptor_types';
import { IField } from '../../fields/field';
import { ITooltipProperty } from '../../tooltips/tooltip_property';
import { getAggDisplayName } from './get_agg_display_name';
@ -142,6 +146,23 @@ export abstract class AbstractESAggSource extends AbstractESSource implements IE
return false;
}
/*
* Changes in requestMeta.fieldNames does not require re-fetch.
* It is not possible to filter metrics from responses so all metrics are always returned in all responses.
*/
isFieldAware(): boolean {
return false;
}
/*
* Force re-fetch when requested metrics change.
*/
getSyncMeta(dataFilters: DataFilters): ESAggsSourceSyncMeta {
return {
metrics: this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName()),
};
}
getGeoGridPrecision(zoom: number): number {
return 0;
}

View file

@ -5,6 +5,6 @@
* 2.0.
*/
export type { IESAggSource } from './types';
export type { IESAggSource, ESAggsSourceSyncMeta } from './types';
export { AbstractESAggSource, DEFAULT_METRIC } from './es_agg_source';
export { getAggDisplayName } from './get_agg_display_name';

View file

@ -25,3 +25,7 @@ export interface IESAggSource extends IESSource {
isGeoGridPrecisionAware(): boolean;
getGeoGridPrecision(zoom: number): number;
}
export interface ESAggsSourceSyncMeta {
metrics: string[];
}

View file

@ -35,7 +35,7 @@ import {
} from '../../../../common/constants';
import { getDataSourceLabel, getDataViewLabel } from '../../../../common/i18n_getters';
import { buildGeoGridFilter } from '../../../../common/elasticsearch_util';
import { AbstractESAggSource } from '../es_agg_source';
import { AbstractESAggSource, ESAggsSourceSyncMeta } from '../es_agg_source';
import { DataRequestAbortError } from '../../util/data_request';
import { LICENSED_FEATURES } from '../../../licensed_features';
@ -56,11 +56,10 @@ import { isMvt } from './is_mvt';
import { VectorStyle } from '../../styles/vector/vector_style';
import { getIconSize } from './get_icon_size';
interface ESGeoGridSourceSyncMeta {
geogridPrecision: number;
requestType: RENDER_AS;
resolution: GRID_RESOLUTION;
}
type ESGeoGridSourceSyncMeta = ESAggsSourceSyncMeta &
Pick<ESGeoGridSourceDescriptor, 'requestType' | 'resolution'> & {
geogridPrecision: number;
};
const MAX_GEOTILE_LEVEL = 29;
@ -160,6 +159,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo
getSyncMeta(dataFilters: DataFilters): ESGeoGridSourceSyncMeta {
return {
...super.getSyncMeta(dataFilters),
geogridPrecision: this.getGeoGridPrecision(dataFilters.zoom),
requestType: this._descriptor.requestType,
resolution: this._descriptor.resolution,
@ -189,10 +189,6 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo
return isMvt(this._descriptor.requestType, this._descriptor.resolution);
}
getFieldNames() {
return this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName());
}
isGeoGridPrecisionAware(): boolean {
if (this.isMvt()) {
// MVT gridded data should not bootstrap each time the precision changes

View file

@ -20,12 +20,13 @@ import {
} from '../../../../common/constants';
import { getField, addFieldToDSL } from '../../../../common/elasticsearch_util';
import {
DataFilters,
ESGeoLineSourceDescriptor,
ESGeoLineSourceResponseMeta,
VectorSourceRequestMeta,
} from '../../../../common/descriptor_types';
import { getDataSourceLabel, getDataViewLabel } from '../../../../common/i18n_getters';
import { AbstractESAggSource } from '../es_agg_source';
import { AbstractESAggSource, ESAggsSourceSyncMeta } from '../es_agg_source';
import { DataRequest } from '../../util/data_request';
import { convertToGeoJson } from './convert_to_geojson';
import { ESDocField } from '../../fields/es_doc_field';
@ -39,7 +40,8 @@ import { getIsGoldPlus } from '../../../licensed_features';
import { LICENSED_FEATURES } from '../../../licensed_features';
import { mergeExecutionContext } from '../execution_context_utils';
type ESGeoLineSourceSyncMeta = Pick<ESGeoLineSourceDescriptor, 'splitField' | 'sortField'>;
type ESGeoLineSourceSyncMeta = ESAggsSourceSyncMeta &
Pick<ESGeoLineSourceDescriptor, 'splitField' | 'sortField'>;
const MAX_TRACKS = 250;
@ -107,8 +109,9 @@ export class ESGeoLineSource extends AbstractESAggSource {
);
}
getSyncMeta(): ESGeoLineSourceSyncMeta {
getSyncMeta(dataFilters: DataFilters): ESGeoLineSourceSyncMeta {
return {
...super.getSyncMeta(dataFilters),
splitField: this._descriptor.splitField,
sortField: this._descriptor.sortField,
};
@ -141,14 +144,6 @@ export class ESGeoLineSource extends AbstractESAggSource {
});
}
getFieldNames() {
return [
...this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName()),
this._descriptor.splitField,
this._descriptor.sortField,
];
}
async getFields(): Promise<IField[]> {
return [...this.getMetricFields(), this._createSplitField()];
}

View file

@ -93,6 +93,7 @@ export class ESPewPewSource extends AbstractESAggSource {
getSyncMeta(dataFilters: DataFilters) {
return {
...super.getSyncMeta(dataFilters),
geogridPrecision: this.getGeoGridPrecision(dataFilters.zoom),
};
}

View file

@ -78,6 +78,7 @@ import { FeatureGeometryFilterForm } from '../../../connected_components/mb_map/
type ESSearchSourceSyncMeta = Pick<
ESSearchSourceDescriptor,
| 'geoField'
| 'filterByMapBounds'
| 'sortField'
| 'sortOrder'
@ -211,10 +212,6 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource
}
}
getFieldNames(): string[] {
return [this._descriptor.geoField];
}
isMvt() {
return this._descriptor.scalingType === SCALING_TYPES.MVT;
}
@ -282,12 +279,9 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource
const indexPattern: DataView = await this.getIndexPattern();
const fieldNames = requestMeta.fieldNames.filter(
(fieldName) => fieldName !== this._descriptor.geoField
);
const { docValueFields, sourceOnlyFields, scriptFields } = getDocValueAndSourceFields(
indexPattern,
fieldNames,
requestMeta.fieldNames,
'epoch_millis'
);
const topHits: {
@ -398,7 +392,6 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource
};
}
// requestMeta.fieldNames contains geo field and any fields needed for styling features
// Performs Elasticsearch search request being careful to pull back only required fields to minimize response size
async _getSearchHits(
layerName: string,
@ -408,12 +401,9 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource
) {
const indexPattern = await this.getIndexPattern();
const fieldNames = requestMeta.fieldNames.filter(
(fieldName) => fieldName !== this._descriptor.geoField
);
const { docValueFields, sourceOnlyFields } = getDocValueAndSourceFields(
indexPattern,
fieldNames,
requestMeta.fieldNames,
'epoch_millis'
);
@ -537,6 +527,15 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource
return indexSettings.maxResultWindow;
}
/*
* Changes in requestMeta.fieldNames requires re-fetch.
* requestMeta.fieldNames are used to acheive smallest response possible.
* Response only includes fields required for client usage.
*/
isFieldAware(): boolean {
return true;
}
async getGeoJsonWithMeta(
layerName: string,
requestMeta: VectorSourceRequestMeta,
@ -790,6 +789,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource
getSyncMeta(): ESSearchSourceSyncMeta {
return {
geoField: this._descriptor.geoField,
filterByMapBounds: this._descriptor.filterByMapBounds,
sortField: this._descriptor.sortField,
sortOrder: this._descriptor.sortOrder,
@ -879,19 +879,15 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource
// use fields API
searchSource.setField(
'fields',
requestMeta.fieldNames
.filter((fieldName) => {
return fieldName !== this._descriptor.geoField;
})
.map((fieldName) => {
const field = dataView.fields.getByName(fieldName);
return field && field.type === 'date'
? {
field: fieldName,
format: 'epoch_millis',
}
: fieldName;
})
requestMeta.fieldNames.map((fieldName) => {
const field = dataView.fields.getByName(fieldName);
return field && field.type === 'date'
? {
field: fieldName,
format: 'epoch_millis',
}
: fieldName;
})
);
const mvtUrlServicePath = getHttp().basePath.prepend(`${MVT_GETTILE_API_PATH}/{z}/{x}/{y}.pbf`);

View file

@ -129,10 +129,6 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource
return this._descriptor.applyForceRefresh;
}
isFieldAware(): boolean {
return true;
}
isQueryAware(): boolean {
return true;
}

View file

@ -15,13 +15,14 @@ import { getJoinAggKey } from '../../../../../common/get_agg_key';
import { AbstractESAggSource } from '../../es_agg_source';
import type { BucketProperties } from '../../../../../common/elasticsearch_util';
import {
DataFilters,
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 } from '../../es_agg_source';
import type { IESAggSource, ESAggsSourceSyncMeta } from '../../es_agg_source';
import { IField } from '../../../fields/field';
import { mergeExecutionContext } from '../../execution_context_utils';
import { processDistanceResponse } from './process_distance_response';
@ -29,7 +30,8 @@ import { isSpatialSourceComplete } from '../is_spatial_source_complete';
export const DEFAULT_WITHIN_DISTANCE = 5;
type ESDistanceSourceSyncMeta = Pick<ESDistanceSourceDescriptor, 'distance' | 'geoField'>;
type ESDistanceSourceSyncMeta = ESAggsSourceSyncMeta &
Pick<ESDistanceSourceDescriptor, 'distance' | 'geoField'>;
export class ESDistanceSource extends AbstractESAggSource implements IJoinSource, IESAggSource {
static type = SOURCE_TYPES.ES_DISTANCE_SOURCE;
@ -162,12 +164,9 @@ export class ESDistanceSource extends AbstractESAggSource implements IJoinSource
return false;
}
getFieldNames(): string[] {
return this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName());
}
getSyncMeta(): ESDistanceSourceSyncMeta {
getSyncMeta(dataFilters: DataFilters): ESDistanceSourceSyncMeta {
return {
...super.getSyncMeta(dataFilters),
distance: this._descriptor.distance,
geoField: this._descriptor.geoField,
};

View file

@ -6,6 +6,7 @@
*/
import { AGG_TYPE } from '../../../../../common/constants';
import { DataFilters } from '../../../../../common/descriptor_types';
import type { BucketProperties, PropertiesMap } from '../../../../../common/elasticsearch_util';
import { ESTermSource, extractPropertiesMap } from './es_term_source';
@ -117,8 +118,9 @@ describe('getSyncMeta', () => {
indexPatternId: 'foobar',
size: 10,
});
expect(source.getSyncMeta()).toEqual({
expect(source.getSyncMeta({} as unknown as DataFilters)).toEqual({
indexPatternId: 'foobar',
metrics: ['__kbnjoin__count__1234'],
size: 10,
term: 'myTermField',
});

View file

@ -25,13 +25,14 @@ import {
BucketProperties,
} from '../../../../../common/elasticsearch_util';
import {
DataFilters,
ESTermSourceDescriptor,
VectorSourceRequestMeta,
} from '../../../../../common/descriptor_types';
import { PropertiesMap } from '../../../../../common/elasticsearch_util';
import { isValidStringConfig } from '../../../util/valid_string_config';
import { ITermJoinSource } from '../types';
import type { IESAggSource } from '../../es_agg_source';
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';
@ -39,7 +40,8 @@ import { isTermSourceComplete } from './is_term_source_complete';
const TERMS_AGG_NAME = 'join';
const TERMS_BUCKET_KEYS_TO_IGNORE = ['key', 'doc_count'];
type ESTermSourceSyncMeta = Pick<ESTermSourceDescriptor, 'indexPatternId' | 'size' | 'term'>;
type ESTermSourceSyncMeta = ESAggsSourceSyncMeta &
Pick<ESTermSourceDescriptor, 'indexPatternId' | 'size' | 'term'>;
export function extractPropertiesMap(rawEsData: any, countPropertyName: string): PropertiesMap {
const propertiesMap: PropertiesMap = new Map<string, BucketProperties>();
@ -184,12 +186,9 @@ export class ESTermSource extends AbstractESAggSource implements ITermJoinSource
return `es_table ${this.getIndexPatternId()}`;
}
getFieldNames(): string[] {
return this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName());
}
getSyncMeta(): ESTermSourceSyncMeta {
getSyncMeta(dataFilters: DataFilters): ESTermSourceSyncMeta {
return {
...super.getSyncMeta(dataFilters),
indexPatternId: this._descriptor.indexPatternId,
size: this._descriptor.size,
term: this._descriptor.term,

View file

@ -54,10 +54,6 @@ export class TableSource extends AbstractVectorSource implements ITermJoinSource
return `table source ${uuidv4()}`;
}
getSyncMeta(): null {
return null;
}
async getPropertiesMap(
requestMeta: VectorSourceRequestMeta,
leftSourceName: string,
@ -66,7 +62,9 @@ export class TableSource extends AbstractVectorSource implements ITermJoinSource
): Promise<PropertiesMap> {
const propertiesMap: PropertiesMap = new Map<string, BucketProperties>();
const fieldNames = await this.getFieldNames();
const columnNames = this._descriptor.__columns.map((column) => {
return column.name;
});
for (let i = 0; i < this._descriptor.__rows.length; i++) {
const row: { [key: string]: string | number } = this._descriptor.__rows[i];
@ -77,7 +75,7 @@ export class TableSource extends AbstractVectorSource implements ITermJoinSource
if (key === this._descriptor.term && row[key]) {
propKey = row[key];
}
if (fieldNames.indexOf(key) >= 0 && key !== this._descriptor.term) {
if (columnNames.indexOf(key) >= 0 && key !== this._descriptor.term) {
props[key] = row[key];
}
}
@ -137,12 +135,6 @@ export class TableSource extends AbstractVectorSource implements ITermJoinSource
});
}
getFieldNames(): string[] {
return this._descriptor.__columns.map((column) => {
return column.name;
});
}
hasTooltipProperties(): boolean {
return false;
}

View file

@ -10,7 +10,7 @@ import type { KibanaExecutionContext } from '@kbn/core/public';
import { Query } from '@kbn/data-plugin/common/query';
import { Adapters } from '@kbn/inspector-plugin/common/adapters';
import { IField } from '../../fields/field';
import { VectorSourceRequestMeta } from '../../../../common/descriptor_types';
import { DataFilters, VectorSourceRequestMeta } from '../../../../common/descriptor_types';
import { PropertiesMap } from '../../../../common/elasticsearch_util';
import { ITooltipProperty } from '../../tooltips/tooltip_property';
import { ISource } from '../source';
@ -28,10 +28,9 @@ export interface IJoinSource extends ISource {
): Promise<PropertiesMap>;
/*
* Vector layer avoids unnecessarily re-fetching join data.
* Use getSyncMeta to expose fields that require join data re-fetch when changed.
* Use getSyncMeta to expose join configurations that require join data re-fetch when changed.
*/
getSyncMeta(): object | null;
getSyncMeta(dataFilters: DataFilters): object | null;
getId(): string;
getRightFields(): IField[];

View file

@ -92,12 +92,6 @@ export class MVTSingleLayerVectorSource extends AbstractSource implements IMvtVe
);
}
getFieldNames(): string[] {
return this._descriptor.fields.map((field: MVTFieldDescriptor) => {
return field.name;
});
}
addFeature(geometry: Geometry | Position[]): Promise<void> {
throw new Error('Does not implement addFeature');
}
@ -191,8 +185,12 @@ export class MVTSingleLayerVectorSource extends AbstractSource implements IMvtVe
return null;
}
getSyncMeta(): null {
return null;
getSyncMeta() {
return {
mvtFields: this._descriptor.fields.map((field: MVTFieldDescriptor) => {
return field.name;
}),
};
}
isBoundsAware() {

View file

@ -46,6 +46,13 @@ export type ImmutableSourceProperty = {
export interface ISource {
getDisplayName(): Promise<string>;
getType(): string;
/*
* Re-fetch flag. When function returns true, source will re-fetch on requestMeta.fieldNames changes.
* Example uses of fieldNames change requiring re-fetch:
* 1) Data driven styling
* 2) Term join
* 3) Feature masking.
*/
isFieldAware(): boolean;
isFilterByMapBounds(): boolean;
isQueryAware(): boolean;
@ -56,7 +63,6 @@ export interface ISource {
renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement<any> | null;
supportsFitToBounds(): Promise<boolean>;
cloneDescriptor(): AbstractSourceDescriptor;
getFieldNames(): string[];
getApplyGlobalQuery(): boolean;
getApplyGlobalTime(): boolean;
getApplyForceRefresh(): boolean;
@ -113,10 +119,6 @@ export class AbstractSource implements ISource {
return false;
}
getFieldNames(): string[] {
return [];
}
renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement<any> | null {
return null;
}

View file

@ -109,12 +109,10 @@ export interface IVectorSource extends ISource {
supportsJoins(): boolean;
/*
* Vector layer avoids unnecessarily re-fetching source data.
* Use getSyncMeta to expose fields that require source data re-fetch when changed.
* Use getSyncMeta to expose source configuration changes that require source data re-fetch when changed.
*/
getSyncMeta(dataFilters: DataFilters): object | null;
getFieldNames(): string[];
createField({ fieldName }: { fieldName: string }): IField;
hasTooltipProperties(): boolean;
getSupportedShapeTypes(): Promise<VECTOR_SHAPE_TYPE[]>;
@ -141,10 +139,6 @@ export interface IVectorSource extends ISource {
}
export class AbstractVectorSource extends AbstractSource implements IVectorSource {
getFieldNames(): string[] {
return [];
}
isMvt() {
return false;
}

View file

@ -185,12 +185,10 @@ export class AnomalySource implements IVectorSource {
return false;
}
getFieldNames(): string[] {
return Object.keys(ANOMALY_SOURCE_FIELDS);
}
async getFields(): Promise<IField[]> {
return this.getFieldNames().map((field) => new AnomalySourceField({ source: this, field }));
return Object.keys(ANOMALY_SOURCE_FIELDS).map(
(field) => new AnomalySourceField({ source: this, field })
);
}
getGeoGridPrecision(zoom: number): number {