[Maps] Convert ES-sources to typescript (#81951)

This commit is contained in:
Thomas Neirynck 2020-11-05 08:56:11 -05:00 committed by GitHub
parent f4386fc5b0
commit 051ed13858
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 1066 additions and 879 deletions

View file

@ -19,28 +19,29 @@
import { schema, TypeOf } from '@kbn/config-schema'; import { schema, TypeOf } from '@kbn/config-schema';
export const configSchema = schema.object({ const layerConfigSchema = schema.object({
includeElasticMapsService: schema.boolean({ defaultValue: true }), url: schema.string(),
layers: schema.arrayOf( format: schema.object({
type: schema.string({ defaultValue: 'geojson' }),
}),
meta: schema.object({
feature_collection_path: schema.string({ defaultValue: 'data' }),
}),
attribution: schema.string(),
name: schema.string(),
fields: schema.arrayOf(
schema.object({ schema.object({
url: schema.string(),
format: schema.object({
type: schema.string({ defaultValue: 'geojson' }),
}),
meta: schema.object({
feature_collection_path: schema.string({ defaultValue: 'data' }),
}),
attribution: schema.string(),
name: schema.string(), name: schema.string(),
fields: schema.arrayOf( description: schema.string(),
schema.object({ })
name: schema.string(),
description: schema.string(),
})
),
}),
{ defaultValue: [] }
), ),
}); });
export const configSchema = schema.object({
includeElasticMapsService: schema.boolean({ defaultValue: true }),
layers: schema.arrayOf(layerConfigSchema, { defaultValue: [] }),
});
export type LayerConfig = TypeOf<typeof layerConfigSchema>;
export type ConfigSchema = TypeOf<typeof configSchema>; export type ConfigSchema = TypeOf<typeof configSchema>;

View file

@ -60,11 +60,6 @@ export enum LAYER_TYPE {
TILED_VECTOR = 'TILED_VECTOR', // similar to a regular vector-layer, but it consumes the data as .mvt tilea iso GeoJson. It supports similar ad-hoc configurations like a regular vector layer (E.g. using IVectorStyle), although there is some loss of functionality e.g. does not support term joining TILED_VECTOR = 'TILED_VECTOR', // similar to a regular vector-layer, but it consumes the data as .mvt tilea iso GeoJson. It supports similar ad-hoc configurations like a regular vector layer (E.g. using IVectorStyle), although there is some loss of functionality e.g. does not support term joining
} }
export enum SORT_ORDER {
ASC = 'asc',
DESC = 'desc',
}
export enum SOURCE_TYPES { export enum SOURCE_TYPES {
EMS_TMS = 'EMS_TMS', EMS_TMS = 'EMS_TMS',
EMS_FILE = 'EMS_FILE', EMS_FILE = 'EMS_FILE',
@ -237,6 +232,11 @@ export enum SCALING_TYPES {
MVT = 'MVT', MVT = 'MVT',
} }
export enum FORMAT_TYPE {
GEOJSON = 'geojson',
TOPOJSON = 'topojson',
}
export enum MVT_FIELD_TYPE { export enum MVT_FIELD_TYPE {
STRING = 'String', STRING = 'String',
NUMBER = 'Number', NUMBER = 'Number',

View file

@ -5,7 +5,9 @@
*/ */
/* eslint-disable @typescript-eslint/consistent-type-definitions */ /* eslint-disable @typescript-eslint/consistent-type-definitions */
import { RENDER_AS, SORT_ORDER, SCALING_TYPES } from '../constants'; import { Query } from 'src/plugins/data/public';
import { SortDirection } from 'src/plugins/data/common/search';
import { RENDER_AS, SCALING_TYPES } from '../constants';
import { MapExtent, MapQuery } from './map_descriptor'; import { MapExtent, MapQuery } from './map_descriptor';
import { Filter, TimeRange } from '../../../../../src/plugins/data/common'; import { Filter, TimeRange } from '../../../../../src/plugins/data/common';
@ -22,7 +24,7 @@ export type MapFilters = {
type ESSearchSourceSyncMeta = { type ESSearchSourceSyncMeta = {
sortField: string; sortField: string;
sortOrder: SORT_ORDER; sortOrder: SortDirection;
scalingType: SCALING_TYPES; scalingType: SCALING_TYPES;
topHitsSplitField: string; topHitsSplitField: string;
topHitsSize: number; topHitsSize: number;
@ -45,7 +47,7 @@ export type VectorSourceRequestMeta = MapFilters & {
export type VectorJoinSourceRequestMeta = MapFilters & { export type VectorJoinSourceRequestMeta = MapFilters & {
applyGlobalQuery: boolean; applyGlobalQuery: boolean;
fieldNames: string[]; fieldNames: string[];
sourceQuery: MapQuery; sourceQuery?: Query;
}; };
export type VectorStyleRequestMeta = MapFilters & { export type VectorStyleRequestMeta = MapFilters & {

View file

@ -7,14 +7,8 @@
import { FeatureCollection } from 'geojson'; import { FeatureCollection } from 'geojson';
import { Query } from 'src/plugins/data/public'; import { Query } from 'src/plugins/data/public';
import { import { SortDirection } from 'src/plugins/data/common/search';
AGG_TYPE, import { AGG_TYPE, GRID_RESOLUTION, RENDER_AS, SCALING_TYPES, MVT_FIELD_TYPE } from '../constants';
GRID_RESOLUTION,
RENDER_AS,
SORT_ORDER,
SCALING_TYPES,
MVT_FIELD_TYPE,
} from '../constants';
export type AttributionDescriptor = { export type AttributionDescriptor = {
attributionText?: string; attributionText?: string;
@ -40,6 +34,7 @@ export type EMSFileSourceDescriptor = AbstractSourceDescriptor & {
export type AbstractESSourceDescriptor = AbstractSourceDescriptor & { export type AbstractESSourceDescriptor = AbstractSourceDescriptor & {
// id: UUID // id: UUID
id: string;
indexPatternId: string; indexPatternId: string;
geoField?: string; geoField?: string;
}; };
@ -55,18 +50,20 @@ export type AbstractESAggSourceDescriptor = AbstractESSourceDescriptor & {
}; };
export type ESGeoGridSourceDescriptor = AbstractESAggSourceDescriptor & { export type ESGeoGridSourceDescriptor = AbstractESAggSourceDescriptor & {
requestType?: RENDER_AS; geoField: string;
resolution?: GRID_RESOLUTION; requestType: RENDER_AS;
resolution: GRID_RESOLUTION;
}; };
export type ESSearchSourceDescriptor = AbstractESSourceDescriptor & { export type ESSearchSourceDescriptor = AbstractESSourceDescriptor & {
geoField: string;
filterByMapBounds?: boolean; filterByMapBounds?: boolean;
tooltipProperties?: string[]; tooltipProperties?: string[];
sortField?: string; sortField: string;
sortOrder?: SORT_ORDER; sortOrder: SortDirection;
scalingType: SCALING_TYPES; scalingType: SCALING_TYPES;
topHitsSplitField?: string; topHitsSplitField: string;
topHitsSize?: number; topHitsSize: number;
}; };
export type ESPewPewSourceDescriptor = AbstractESAggSourceDescriptor & { export type ESPewPewSourceDescriptor = AbstractESAggSourceDescriptor & {
@ -76,7 +73,7 @@ export type ESPewPewSourceDescriptor = AbstractESAggSourceDescriptor & {
export type ESTermSourceDescriptor = AbstractESAggSourceDescriptor & { export type ESTermSourceDescriptor = AbstractESAggSourceDescriptor & {
indexPatternTitle?: string; indexPatternTitle?: string;
term?: string; // term field name term: string; // term field name
whereQuery?: Query; whereQuery?: Query;
}; };

View file

@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { FeatureCollection, GeoJsonProperties } from 'geojson'; import { FeatureCollection, GeoJsonProperties, Polygon } from 'geojson';
import { MapExtent } from '../descriptor_types'; import { MapExtent } from '../descriptor_types';
import { ES_GEO_FIELD_TYPE } from '../constants'; import { ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../constants';
export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent; export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent;
@ -23,3 +23,31 @@ export function hitsToGeoJson(
geoFieldType: ES_GEO_FIELD_TYPE, geoFieldType: ES_GEO_FIELD_TYPE,
epochMillisFields: string[] epochMillisFields: string[]
): FeatureCollection; ): FeatureCollection;
export interface ESBBox {
top_left: number[];
bottom_right: number[];
}
export interface ESGeoBoundingBoxFilter {
geo_bounding_box: {
[geoFieldName: string]: ESBBox;
};
}
export interface ESPolygonFilter {
geo_shape: {
[geoFieldName: string]: {
shape: Polygon;
relation: ES_SPATIAL_RELATIONS.INTERSECTS;
};
};
}
export function createExtentFilter(
mapExtent: MapExtent,
geoFieldName: string,
geoFieldType: ES_GEO_FIELD_TYPE
): ESPolygonFilter | ESGeoBoundingBoxFilter;
export function makeESBbox({ maxLat, maxLon, minLat, minLon }: MapExtent): ESBBox;

View file

@ -8,7 +8,10 @@ import _ from 'lodash';
import { IndexPattern, IFieldType } from '../../../../../src/plugins/data/common'; import { IndexPattern, IFieldType } from '../../../../../src/plugins/data/common';
import { TOP_TERM_PERCENTAGE_SUFFIX } from '../constants'; import { TOP_TERM_PERCENTAGE_SUFFIX } from '../constants';
export function getField(indexPattern: IndexPattern, fieldName: string) { export type BucketProperties = Record<string | number, unknown>;
export type PropertiesMap = Map<string, BucketProperties>;
export function getField(indexPattern: IndexPattern, fieldName: string): IFieldType {
const field = indexPattern.fields.getByName(fieldName); const field = indexPattern.fields.getByName(fieldName);
if (!field) { if (!field) {
throw new Error( throw new Error(
@ -33,9 +36,10 @@ export function addFieldToDSL(dsl: object, field: IFieldType) {
}; };
} }
export type BucketProperties = Record<string | number, unknown>; export function extractPropertiesFromBucket(
bucket: any,
export function extractPropertiesFromBucket(bucket: any, ignoreKeys: string[] = []) { ignoreKeys: string[] = []
): BucketProperties {
const properties: BucketProperties = {}; const properties: BucketProperties = {};
for (const key in bucket) { for (const key in bucket) {
if (ignoreKeys.includes(key) || !bucket.hasOwnProperty(key)) { if (ignoreKeys.includes(key) || !bucket.hasOwnProperty(key)) {

View file

@ -5,7 +5,8 @@
*/ */
import _ from 'lodash'; import _ from 'lodash';
import { SOURCE_TYPES, SORT_ORDER } from '../constants'; import { SOURCE_TYPES } from '../constants';
import { SortDirection } from '../../../../../src/plugins/data/common/search';
function isEsDocumentSource(layerDescriptor) { function isEsDocumentSource(layerDescriptor) {
const sourceType = _.get(layerDescriptor, 'sourceDescriptor.type'); const sourceType = _.get(layerDescriptor, 'sourceDescriptor.type');
@ -23,7 +24,7 @@ export function topHitsTimeToSort({ attributes }) {
if (_.has(layerDescriptor, 'sourceDescriptor.topHitsTimeField')) { if (_.has(layerDescriptor, 'sourceDescriptor.topHitsTimeField')) {
layerDescriptor.sourceDescriptor.sortField = layerDescriptor.sourceDescriptor.sortField =
layerDescriptor.sourceDescriptor.topHitsTimeField; layerDescriptor.sourceDescriptor.topHitsTimeField;
layerDescriptor.sourceDescriptor.sortOrder = SORT_ORDER.DESC; layerDescriptor.sourceDescriptor.sortOrder = SortDirection.desc;
delete layerDescriptor.sourceDescriptor.topHitsTimeField; delete layerDescriptor.sourceDescriptor.topHitsTimeField;
} }
} }

View file

@ -5,12 +5,12 @@
*/ */
import { IField, AbstractField } from './field'; import { IField, AbstractField } from './field';
import { IKibanaRegionSource } from '../sources/kibana_regionmap_source/kibana_regionmap_source'; import { KibanaRegionmapSource } from '../sources/kibana_regionmap_source/kibana_regionmap_source';
import { FIELD_ORIGIN } from '../../../common/constants'; import { FIELD_ORIGIN } from '../../../common/constants';
import { IVectorSource } from '../sources/vector_source'; import { IVectorSource } from '../sources/vector_source';
export class KibanaRegionField extends AbstractField implements IField { export class KibanaRegionField extends AbstractField implements IField {
private readonly _source: IKibanaRegionSource; private readonly _source: KibanaRegionmapSource;
constructor({ constructor({
fieldName, fieldName,
@ -18,7 +18,7 @@ export class KibanaRegionField extends AbstractField implements IField {
origin, origin,
}: { }: {
fieldName: string; fieldName: string;
source: IKibanaRegionSource; source: KibanaRegionmapSource;
origin: FIELD_ORIGIN; origin: FIELD_ORIGIN;
}) { }) {
super({ fieldName, origin }); super({ fieldName, origin });

View file

@ -5,19 +5,20 @@
*/ */
import { Feature, GeoJsonProperties } from 'geojson'; import { Feature, GeoJsonProperties } from 'geojson';
import { IESTermSource } from '../sources/es_term_source'; import { ESTermSource } from '../sources/es_term_source';
import { IJoin, PropertiesMap } from './join'; import { IJoin } from './join';
import { JoinDescriptor } from '../../../common/descriptor_types'; import { JoinDescriptor } from '../../../common/descriptor_types';
import { ISource } from '../sources/source'; import { ISource } from '../sources/source';
import { ITooltipProperty } from '../tooltips/tooltip_property'; import { ITooltipProperty } from '../tooltips/tooltip_property';
import { IField } from '../fields/field'; import { IField } from '../fields/field';
import { PropertiesMap } from '../../../common/elasticsearch_util';
export class InnerJoin implements IJoin { export class InnerJoin implements IJoin {
constructor(joinDescriptor: JoinDescriptor, leftSource: ISource); constructor(joinDescriptor: JoinDescriptor, leftSource: ISource);
destroy: () => void; destroy: () => void;
getRightJoinSource(): IESTermSource; getRightJoinSource(): ESTermSource;
toDescriptor(): JoinDescriptor; toDescriptor(): JoinDescriptor;

View file

@ -5,18 +5,16 @@
*/ */
import { Feature, GeoJsonProperties } from 'geojson'; import { Feature, GeoJsonProperties } from 'geojson';
import { IESTermSource } from '../sources/es_term_source'; import { ESTermSource } from '../sources/es_term_source';
import { JoinDescriptor } from '../../../common/descriptor_types'; import { JoinDescriptor } from '../../../common/descriptor_types';
import { ITooltipProperty } from '../tooltips/tooltip_property'; import { ITooltipProperty } from '../tooltips/tooltip_property';
import { IField } from '../fields/field'; import { IField } from '../fields/field';
import { BucketProperties } from '../../../common/elasticsearch_util'; import { PropertiesMap } from '../../../common/elasticsearch_util';
export type PropertiesMap = Map<string, BucketProperties>;
export interface IJoin { export interface IJoin {
destroy: () => void; destroy: () => void;
getRightJoinSource: () => IESTermSource; getRightJoinSource: () => ESTermSource;
toDescriptor: () => JoinDescriptor; toDescriptor: () => JoinDescriptor;

View file

@ -25,7 +25,6 @@ import { ESGeoGridSource } from '../../sources/es_geo_grid_source/es_geo_grid_so
import { canSkipSourceUpdate } from '../../util/can_skip_fetch'; import { canSkipSourceUpdate } from '../../util/can_skip_fetch';
import { IVectorLayer } from '../vector_layer/vector_layer'; import { IVectorLayer } from '../vector_layer/vector_layer';
import { IESSource } from '../../sources/es_source'; import { IESSource } from '../../sources/es_source';
import { IESAggSource } from '../../sources/es_agg_source';
import { ISource } from '../../sources/source'; import { ISource } from '../../sources/source';
import { DataRequestContext } from '../../../actions'; import { DataRequestContext } from '../../../actions';
import { DataRequestAbortError } from '../../util/data_request'; import { DataRequestAbortError } from '../../util/data_request';
@ -36,9 +35,11 @@ import {
StylePropertyOptions, StylePropertyOptions,
LayerDescriptor, LayerDescriptor,
VectorLayerDescriptor, VectorLayerDescriptor,
VectorSourceRequestMeta,
} from '../../../../common/descriptor_types'; } from '../../../../common/descriptor_types';
import { IVectorSource } from '../../sources/vector_source'; import { IVectorSource } from '../../sources/vector_source';
import { LICENSED_FEATURES } from '../../../licensed_features'; import { LICENSED_FEATURES } from '../../../licensed_features';
import { ESSearchSource } from '../../sources/es_search_source/es_search_source';
const ACTIVE_COUNT_DATA_ID = 'ACTIVE_COUNT_DATA_ID'; const ACTIVE_COUNT_DATA_ID = 'ACTIVE_COUNT_DATA_ID';
@ -50,7 +51,7 @@ function getAggType(dynamicProperty: IDynamicStyleProperty<DynamicStylePropertyO
return dynamicProperty.isOrdinal() ? AGG_TYPE.AVG : AGG_TYPE.TERMS; return dynamicProperty.isOrdinal() ? AGG_TYPE.AVG : AGG_TYPE.TERMS;
} }
function getClusterSource(documentSource: IESSource, documentStyle: IVectorStyle): IESAggSource { function getClusterSource(documentSource: IESSource, documentStyle: IVectorStyle): ESGeoGridSource {
const clusterSourceDescriptor = ESGeoGridSource.createDescriptor({ const clusterSourceDescriptor = ESGeoGridSource.createDescriptor({
indexPatternId: documentSource.getIndexPatternId(), indexPatternId: documentSource.getIndexPatternId(),
geoField: documentSource.getGeoFieldName(), geoField: documentSource.getGeoFieldName(),
@ -75,7 +76,7 @@ function getClusterSource(documentSource: IESSource, documentStyle: IVectorStyle
function getClusterStyleDescriptor( function getClusterStyleDescriptor(
documentStyle: IVectorStyle, documentStyle: IVectorStyle,
clusterSource: IESAggSource clusterSource: ESGeoGridSource
): VectorStyleDescriptor { ): VectorStyleDescriptor {
const defaultDynamicProperties = getDefaultDynamicProperties(); const defaultDynamicProperties = getDefaultDynamicProperties();
const clusterStyleDescriptor: VectorStyleDescriptor = { const clusterStyleDescriptor: VectorStyleDescriptor = {
@ -177,9 +178,9 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {
} }
private readonly _isClustered: boolean; private readonly _isClustered: boolean;
private readonly _clusterSource: IESAggSource; private readonly _clusterSource: ESGeoGridSource;
private readonly _clusterStyle: IVectorStyle; private readonly _clusterStyle: IVectorStyle;
private readonly _documentSource: IESSource; private readonly _documentSource: ESSearchSource;
private readonly _documentStyle: IVectorStyle; private readonly _documentStyle: IVectorStyle;
constructor(options: BlendedVectorLayerArguments) { constructor(options: BlendedVectorLayerArguments) {
@ -188,7 +189,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {
joins: [], joins: [],
}); });
this._documentSource = this._source as IESSource; // VectorLayer constructor sets _source as document source this._documentSource = this._source as ESSearchSource; // VectorLayer constructor sets _source as document source
this._documentStyle = this._style as IVectorStyle; // VectorLayer constructor sets _style as document source this._documentStyle = this._style as IVectorStyle; // VectorLayer constructor sets _style as document source
this._clusterSource = getClusterSource(this._documentSource, this._documentStyle); this._clusterSource = getClusterSource(this._documentSource, this._documentStyle);
@ -279,7 +280,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {
async syncData(syncContext: DataRequestContext) { async syncData(syncContext: DataRequestContext) {
const dataRequestId = ACTIVE_COUNT_DATA_ID; const dataRequestId = ACTIVE_COUNT_DATA_ID;
const requestToken = Symbol(`layer-active-count:${this.getId()}`); const requestToken = Symbol(`layer-active-count:${this.getId()}`);
const searchFilters = this._getSearchFilters( const searchFilters: VectorSourceRequestMeta = this._getSearchFilters(
syncContext.dataFilters, syncContext.dataFilters,
this.getSource(), this.getSource(),
this.getCurrentStyle() this.getCurrentStyle()

View file

@ -415,7 +415,7 @@ export class AbstractLayer implements ILayer {
return this._descriptor.query ? this._descriptor.query : null; return this._descriptor.query ? this._descriptor.query : null;
} }
async getImmutableSourceProperties() { async getImmutableSourceProperties(): Promise<ImmutableSourceProperty[]> {
const source = this.getSource(); const source = this.getSource();
return await source.getImmutableProperties(); return await source.getImmutableProperties();
} }

View file

@ -175,6 +175,7 @@ describe('createLayerDescriptor', () => {
query: 'processor.event:"transaction"', query: 'processor.event:"transaction"',
}, },
sourceDescriptor: { sourceDescriptor: {
applyGlobalQuery: true,
geoField: 'client.geo.location', geoField: 'client.geo.location',
id: '12345', id: '12345',
indexPatternId: 'apm_static_index_pattern_id', indexPatternId: 'apm_static_index_pattern_id',
@ -216,6 +217,7 @@ describe('createLayerDescriptor', () => {
query: 'processor.event:"transaction"', query: 'processor.event:"transaction"',
}, },
sourceDescriptor: { sourceDescriptor: {
applyGlobalQuery: true,
geoField: 'client.geo.location', geoField: 'client.geo.location',
id: '12345', id: '12345',
indexPatternId: 'apm_static_index_pattern_id', indexPatternId: 'apm_static_index_pattern_id',

View file

@ -21,7 +21,7 @@ jest.mock('uuid/v4', () => {
import { createSecurityLayerDescriptors } from './create_layer_descriptors'; import { createSecurityLayerDescriptors } from './create_layer_descriptors';
describe('createLayerDescriptor', () => { describe('createLayerDescriptor', () => {
test('amp index', () => { test('apm index', () => {
expect(createSecurityLayerDescriptors('id', 'apm-*-transaction*')).toEqual([ expect(createSecurityLayerDescriptors('id', 'apm-*-transaction*')).toEqual([
{ {
__dataRequests: [], __dataRequests: [],
@ -32,6 +32,7 @@ describe('createLayerDescriptor', () => {
maxZoom: 24, maxZoom: 24,
minZoom: 0, minZoom: 0,
sourceDescriptor: { sourceDescriptor: {
applyGlobalQuery: true,
filterByMapBounds: true, filterByMapBounds: true,
geoField: 'client.geo.location', geoField: 'client.geo.location',
id: '12345', id: '12345',
@ -138,6 +139,7 @@ describe('createLayerDescriptor', () => {
maxZoom: 24, maxZoom: 24,
minZoom: 0, minZoom: 0,
sourceDescriptor: { sourceDescriptor: {
applyGlobalQuery: true,
filterByMapBounds: true, filterByMapBounds: true,
geoField: 'server.geo.location', geoField: 'server.geo.location',
id: '12345', id: '12345',
@ -244,6 +246,7 @@ describe('createLayerDescriptor', () => {
maxZoom: 24, maxZoom: 24,
minZoom: 0, minZoom: 0,
sourceDescriptor: { sourceDescriptor: {
applyGlobalQuery: true,
destGeoField: 'server.geo.location', destGeoField: 'server.geo.location',
id: '12345', id: '12345',
indexPatternId: 'id', indexPatternId: 'id',
@ -362,6 +365,7 @@ describe('createLayerDescriptor', () => {
maxZoom: 24, maxZoom: 24,
minZoom: 0, minZoom: 0,
sourceDescriptor: { sourceDescriptor: {
applyGlobalQuery: true,
filterByMapBounds: true, filterByMapBounds: true,
geoField: 'source.geo.location', geoField: 'source.geo.location',
id: '12345', id: '12345',
@ -468,6 +472,7 @@ describe('createLayerDescriptor', () => {
maxZoom: 24, maxZoom: 24,
minZoom: 0, minZoom: 0,
sourceDescriptor: { sourceDescriptor: {
applyGlobalQuery: true,
filterByMapBounds: true, filterByMapBounds: true,
geoField: 'destination.geo.location', geoField: 'destination.geo.location',
id: '12345', id: '12345',
@ -574,6 +579,7 @@ describe('createLayerDescriptor', () => {
maxZoom: 24, maxZoom: 24,
minZoom: 0, minZoom: 0,
sourceDescriptor: { sourceDescriptor: {
applyGlobalQuery: true,
destGeoField: 'destination.geo.location', destGeoField: 'destination.geo.location',
id: '12345', id: '12345',
indexPatternId: 'id', indexPatternId: 'id',

View file

@ -46,18 +46,20 @@ import {
DynamicStylePropertyOptions, DynamicStylePropertyOptions,
MapFilters, MapFilters,
MapQuery, MapQuery,
VectorJoinSourceRequestMeta,
VectorLayerDescriptor, VectorLayerDescriptor,
VectorSourceRequestMeta, VectorSourceRequestMeta,
VectorStyleRequestMeta, VectorStyleRequestMeta,
} from '../../../../common/descriptor_types'; } from '../../../../common/descriptor_types';
import { IVectorSource } from '../../sources/vector_source'; import { IVectorSource } from '../../sources/vector_source';
import { CustomIconAndTooltipContent, ILayer } from '../layer'; import { CustomIconAndTooltipContent, ILayer } from '../layer';
import { IJoin, PropertiesMap } from '../../joins/join'; import { IJoin } from '../../joins/join';
import { IField } from '../../fields/field'; import { IField } from '../../fields/field';
import { DataRequestContext } from '../../../actions'; import { DataRequestContext } from '../../../actions';
import { ITooltipProperty } from '../../tooltips/tooltip_property'; import { ITooltipProperty } from '../../tooltips/tooltip_property';
import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property';
import { IESSource } from '../../sources/es_source'; import { IESSource } from '../../sources/es_source';
import { PropertiesMap } from '../../../../common/elasticsearch_util';
interface SourceResult { interface SourceResult {
refreshed: boolean; refreshed: boolean;
@ -239,7 +241,7 @@ export class VectorLayer extends AbstractLayer {
} }
const requestToken = Symbol(`${SOURCE_BOUNDS_DATA_REQUEST_ID}-${this.getId()}`); const requestToken = Symbol(`${SOURCE_BOUNDS_DATA_REQUEST_ID}-${this.getId()}`);
const searchFilters = this._getSearchFilters( const searchFilters: VectorSourceRequestMeta = this._getSearchFilters(
dataFilters, dataFilters,
this.getSource(), this.getSource(),
this.getCurrentStyle() this.getCurrentStyle()
@ -324,7 +326,7 @@ export class VectorLayer extends AbstractLayer {
const joinSource = join.getRightJoinSource(); const joinSource = join.getRightJoinSource();
const sourceDataId = join.getSourceDataRequestId(); const sourceDataId = join.getSourceDataRequestId();
const requestToken = Symbol(`layer-join-refresh:${this.getId()} - ${sourceDataId}`); const requestToken = Symbol(`layer-join-refresh:${this.getId()} - ${sourceDataId}`);
const searchFilters = { const searchFilters: VectorJoinSourceRequestMeta = {
...dataFilters, ...dataFilters,
fieldNames: joinSource.getFieldNames(), fieldNames: joinSource.getFieldNames(),
sourceQuery: joinSource.getWhereQuery(), sourceQuery: joinSource.getWhereQuery(),
@ -386,9 +388,11 @@ export class VectorLayer extends AbstractLayer {
source: IVectorSource, source: IVectorSource,
style: IVectorStyle style: IVectorStyle
): VectorSourceRequestMeta { ): VectorSourceRequestMeta {
const styleFieldNames =
style.getType() === LAYER_STYLE_TYPE.VECTOR ? style.getSourceFieldNames() : [];
const fieldNames = [ const fieldNames = [
...source.getFieldNames(), ...source.getFieldNames(),
...(style.getType() === LAYER_STYLE_TYPE.VECTOR ? style.getSourceFieldNames() : []), ...styleFieldNames,
...this.getValidJoins().map((join) => join.getLeftField().getName()), ...this.getValidJoins().map((join) => join.getLeftField().getName()),
]; ];
@ -464,7 +468,11 @@ export class VectorLayer extends AbstractLayer {
} = syncContext; } = syncContext;
const dataRequestId = SOURCE_DATA_REQUEST_ID; const dataRequestId = SOURCE_DATA_REQUEST_ID;
const requestToken = Symbol(`layer-${this.getId()}-${dataRequestId}`); const requestToken = Symbol(`layer-${this.getId()}-${dataRequestId}`);
const searchFilters = this._getSearchFilters(dataFilters, source, style); const searchFilters: VectorSourceRequestMeta = this._getSearchFilters(
dataFilters,
source,
style
);
const prevDataRequest = this.getSourceDataRequest(); const prevDataRequest = this.getSourceDataRequest();
const canSkipFetch = await canSkipSourceUpdate({ const canSkipFetch = await canSkipSourceUpdate({
source, source,

View file

@ -11,7 +11,12 @@ import { Adapters } from 'src/plugins/inspector/public';
import { FileLayer } from '@elastic/ems-client'; import { FileLayer } from '@elastic/ems-client';
import { Attribution, ImmutableSourceProperty, SourceEditorArgs } from '../source'; import { Attribution, ImmutableSourceProperty, SourceEditorArgs } from '../source';
import { AbstractVectorSource, GeoJsonWithMeta, IVectorSource } from '../vector_source'; import { AbstractVectorSource, GeoJsonWithMeta, IVectorSource } from '../vector_source';
import { SOURCE_TYPES, FIELD_ORIGIN, VECTOR_SHAPE_TYPE } from '../../../../common/constants'; import {
SOURCE_TYPES,
FIELD_ORIGIN,
VECTOR_SHAPE_TYPE,
FORMAT_TYPE,
} from '../../../../common/constants';
import { getEmsFileLayers } from '../../../meta'; import { getEmsFileLayers } from '../../../meta';
import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { UpdateSourceEditor } from './update_source_editor'; import { UpdateSourceEditor } from './update_source_editor';
@ -30,11 +35,9 @@ export const sourceTitle = i18n.translate('xpack.maps.source.emsFileTitle', {
}); });
export class EMSFileSource extends AbstractVectorSource implements IEmsFileSource { export class EMSFileSource extends AbstractVectorSource implements IEmsFileSource {
static type = SOURCE_TYPES.EMS_FILE;
static createDescriptor({ id, tooltipProperties = [] }: Partial<EMSFileSourceDescriptor>) { static createDescriptor({ id, tooltipProperties = [] }: Partial<EMSFileSourceDescriptor>) {
return { return {
type: EMSFileSource.type, type: SOURCE_TYPES.EMS_FILE,
id: id!, id: id!,
tooltipProperties, tooltipProperties,
}; };
@ -99,7 +102,7 @@ export class EMSFileSource extends AbstractVectorSource implements IEmsFileSourc
const emsFileLayer = await this.getEMSFileLayer(); const emsFileLayer = await this.getEMSFileLayer();
// @ts-ignore // @ts-ignore
const featureCollection = await AbstractVectorSource.getGeoJson({ const featureCollection = await AbstractVectorSource.getGeoJson({
format: emsFileLayer.getDefaultFormatType(), format: emsFileLayer.getDefaultFormatType() as FORMAT_TYPE,
featureCollectionPath: 'data', featureCollectionPath: 'data',
fetchUrl: emsFileLayer.getDefaultFormatUrl(), fetchUrl: emsFileLayer.getDefaultFormatUrl(),
}); });

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import _ from 'lodash';
import React from 'react'; import React from 'react';
import { AbstractTMSSource } from '../tms_source'; import { AbstractTMSSource } from '../tms_source';
import { getEmsTmsServices } from '../../../meta'; import { getEmsTmsServices } from '../../../meta';
@ -20,25 +19,18 @@ export const sourceTitle = i18n.translate('xpack.maps.source.emsTileTitle', {
}); });
export class EMSTMSSource extends AbstractTMSSource { export class EMSTMSSource extends AbstractTMSSource {
static type = SOURCE_TYPES.EMS_TMS; static createDescriptor(descriptor) {
static createDescriptor(sourceConfig) {
return { return {
type: EMSTMSSource.type, type: SOURCE_TYPES.EMS_TMS,
id: sourceConfig.id, id: descriptor.id,
isAutoSelect: sourceConfig.isAutoSelect, isAutoSelect:
typeof descriptor.isAutoSelect !== 'undefined' ? !!descriptor.isAutoSelect : false,
}; };
} }
constructor(descriptor, inspectorAdapters) { constructor(descriptor, inspectorAdapters) {
super( descriptor = EMSTMSSource.createDescriptor(descriptor);
{ super(descriptor, inspectorAdapters);
id: descriptor.id,
type: EMSTMSSource.type,
isAutoSelect: _.get(descriptor, 'isAutoSelect', false),
},
inspectorAdapters
);
} }
renderSourceSettingsEditor({ onChange }) { renderSourceSettingsEditor({ onChange }) {

View file

@ -33,9 +33,21 @@ export abstract class AbstractESAggSource extends AbstractESSource {
private readonly _metricFields: IESAggField[]; private readonly _metricFields: IESAggField[];
private readonly _canReadFromGeoJson: boolean; private readonly _canReadFromGeoJson: boolean;
static createDescriptor(
descriptor: Partial<AbstractESAggSourceDescriptor>
): AbstractESAggSourceDescriptor {
const normalizedDescriptor = AbstractESSource.createDescriptor(descriptor);
return {
...normalizedDescriptor,
type: descriptor.type ? descriptor.type : '',
metrics:
descriptor.metrics && descriptor.metrics.length > 0 ? descriptor.metrics : [DEFAULT_METRIC],
};
}
constructor( constructor(
descriptor: AbstractESAggSourceDescriptor, descriptor: AbstractESAggSourceDescriptor,
inspectorAdapters: Adapters, inspectorAdapters?: Adapters,
canReadFromGeoJson = true canReadFromGeoJson = true
) { ) {
super(descriptor, inspectorAdapters); super(descriptor, inspectorAdapters);
@ -55,7 +67,7 @@ export abstract class AbstractESAggSource extends AbstractESSource {
} }
} }
getFieldByName(fieldName: string) { getFieldByName(fieldName: string): IField | null {
return this.getMetricFieldForName(fieldName); return this.getMetricFieldForName(fieldName);
} }
@ -113,7 +125,7 @@ export abstract class AbstractESAggSource extends AbstractESSource {
} }
} }
async getFields() { async getFields(): Promise<IField[]> {
return this.getMetricFields(); return this.getMetricFields();
} }
@ -128,7 +140,7 @@ export abstract class AbstractESAggSource extends AbstractESSource {
return valueAggsDsl; return valueAggsDsl;
} }
async getTooltipProperties(properties: GeoJsonProperties) { async getTooltipProperties(properties: GeoJsonProperties): Promise<ITooltipProperty[]> {
const metricFields = await this.getFields(); const metricFields = await this.getFields();
const promises: Array<Promise<ITooltipProperty>> = []; const promises: Array<Promise<ITooltipProperty>> = [];
metricFields.forEach((metricField) => { metricFields.forEach((metricField) => {

View file

@ -1,46 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { AbstractESAggSource } from '../es_agg_source';
import {
ESGeoGridSourceDescriptor,
MapFilters,
MapQuery,
VectorSourceSyncMeta,
VectorSourceRequestMeta,
} from '../../../../common/descriptor_types';
import { GRID_RESOLUTION } from '../../../../common/constants';
import { IField } from '../../fields/field';
import { ITiledSingleLayerVectorSource } from '../vector_source';
export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingleLayerVectorSource {
static createDescriptor({
indexPatternId,
geoField,
requestType,
resolution,
}: Partial<ESGeoGridSourceDescriptor>): ESGeoGridSourceDescriptor;
constructor(sourceDescriptor: ESGeoGridSourceDescriptor, inspectorAdapters: unknown);
readonly _descriptor: ESGeoGridSourceDescriptor;
getFieldNames(): string[];
getGridResolution(): GRID_RESOLUTION;
getGeoGridPrecision(zoom: number): number;
createField({ fieldName }: { fieldName: string }): IField;
getLayerName(): string;
getUrlTemplateWithMeta(
searchFilters: VectorSourceRequestMeta
): Promise<{
layerName: string;
urlTemplate: string;
minSourceZoom: number;
maxSourceZoom: number;
}>;
}

View file

@ -4,37 +4,51 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import React from 'react'; import React, { ReactElement } from 'react';
import uuid from 'uuid/v4';
import { i18n } from '@kbn/i18n';
import rison from 'rison-node';
import { Feature } from 'geojson';
import { SearchResponse } from 'elasticsearch';
import { import {
convertCompositeRespToGeoJson, convertCompositeRespToGeoJson,
convertRegularRespToGeoJson, convertRegularRespToGeoJson,
makeESBbox, makeESBbox,
} from '../../../../common/elasticsearch_util'; } from '../../../../common/elasticsearch_util';
// @ts-expect-error
import { UpdateSourceEditor } from './update_source_editor'; import { UpdateSourceEditor } from './update_source_editor';
import { import {
SOURCE_TYPES,
DEFAULT_MAX_BUCKETS_LIMIT, DEFAULT_MAX_BUCKETS_LIMIT,
RENDER_AS,
GRID_RESOLUTION,
VECTOR_SHAPE_TYPE,
MVT_SOURCE_LAYER_NAME,
GIS_API_PATH,
MVT_GETGRIDTILE_API_PATH,
GEOTILE_GRID_AGG_NAME,
GEOCENTROID_AGG_NAME,
ES_GEO_FIELD_TYPE, ES_GEO_FIELD_TYPE,
GEOCENTROID_AGG_NAME,
GEOTILE_GRID_AGG_NAME,
GIS_API_PATH,
GRID_RESOLUTION,
MVT_GETGRIDTILE_API_PATH,
MVT_SOURCE_LAYER_NAME,
RENDER_AS,
SOURCE_TYPES,
VECTOR_SHAPE_TYPE,
} from '../../../../common/constants'; } from '../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { AbstractESAggSource, DEFAULT_METRIC } from '../es_agg_source'; import { AbstractESAggSource } from '../es_agg_source';
import { DataRequestAbortError } from '../../util/data_request'; import { DataRequestAbortError } from '../../util/data_request';
import { registerSource } from '../source_registry'; import { registerSource } from '../source_registry';
import { LICENSED_FEATURES } from '../../../licensed_features'; import { LICENSED_FEATURES } from '../../../licensed_features';
import rison from 'rison-node';
import { getHttp } from '../../../kibana_services'; import { getHttp } from '../../../kibana_services';
import { GeoJsonWithMeta, ITiledSingleLayerVectorSource } from '../vector_source';
import {
ESGeoGridSourceDescriptor,
MapExtent,
VectorSourceRequestMeta,
VectorSourceSyncMeta,
} from '../../../../common/descriptor_types';
import { ImmutableSourceProperty, SourceEditorArgs } from '../source';
import { ISearchSource } from '../../../../../../../src/plugins/data/common/search/search_source';
import { IndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns/index_patterns';
import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters';
import { isValidStringConfig } from '../../util/valid_string_config';
export const MAX_GEOTILE_LEVEL = 29; export const MAX_GEOTILE_LEVEL = 29;
@ -46,31 +60,41 @@ export const heatmapTitle = i18n.translate('xpack.maps.source.esGridHeatmapTitle
defaultMessage: 'Heat map', defaultMessage: 'Heat map',
}); });
export class ESGeoGridSource extends AbstractESAggSource { export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingleLayerVectorSource {
static type = SOURCE_TYPES.ES_GEO_GRID; static createDescriptor(
descriptor: Partial<ESGeoGridSourceDescriptor>
static createDescriptor({ indexPatternId, geoField, metrics, requestType, resolution }) { ): ESGeoGridSourceDescriptor {
const normalizedDescriptor = AbstractESAggSource.createDescriptor(descriptor);
if (!isValidStringConfig(normalizedDescriptor.geoField)) {
throw new Error('Cannot create an ESGeoGridSourceDescriptor without a geoField');
}
return { return {
type: ESGeoGridSource.type, ...normalizedDescriptor,
id: uuid(), type: SOURCE_TYPES.ES_GEO_GRID,
indexPatternId, geoField: normalizedDescriptor.geoField!,
geoField, requestType: descriptor.requestType || RENDER_AS.POINT,
metrics: metrics ? metrics : [DEFAULT_METRIC], resolution: descriptor.resolution ? descriptor.resolution : GRID_RESOLUTION.COARSE,
requestType,
resolution: resolution ? resolution : GRID_RESOLUTION.COARSE,
}; };
} }
constructor(descriptor, inspectorAdapters) { readonly _descriptor: ESGeoGridSourceDescriptor;
super(descriptor, inspectorAdapters, descriptor.resolution !== GRID_RESOLUTION.SUPER_FINE);
constructor(descriptor: Partial<ESGeoGridSourceDescriptor>, inspectorAdapters?: Adapters) {
const sourceDescriptor = ESGeoGridSource.createDescriptor(descriptor);
super(
sourceDescriptor,
inspectorAdapters,
descriptor.resolution !== GRID_RESOLUTION.SUPER_FINE
);
this._descriptor = sourceDescriptor;
} }
renderSourceSettingsEditor({ onChange, currentLayerType }) { renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement<any> {
return ( return (
<UpdateSourceEditor <UpdateSourceEditor
currentLayerType={currentLayerType} currentLayerType={sourceEditorArgs.currentLayerType}
indexPatternId={this.getIndexPatternId()} indexPatternId={this.getIndexPatternId()}
onChange={onChange} onChange={sourceEditorArgs.onChange}
metrics={this._descriptor.metrics} metrics={this._descriptor.metrics}
renderAs={this._descriptor.requestType} renderAs={this._descriptor.requestType}
resolution={this._descriptor.resolution} resolution={this._descriptor.resolution}
@ -78,17 +102,17 @@ export class ESGeoGridSource extends AbstractESAggSource {
); );
} }
getSyncMeta() { getSyncMeta(): VectorSourceSyncMeta {
return { return {
requestType: this._descriptor.requestType, requestType: this._descriptor.requestType,
}; };
} }
async getImmutableProperties() { async getImmutableProperties(): Promise<ImmutableSourceProperty[]> {
let indexPatternTitle = this.getIndexPatternId(); let indexPatternName = this.getIndexPatternId();
try { try {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
indexPatternTitle = indexPattern.title; indexPatternName = indexPattern.title;
} catch (error) { } catch (error) {
// ignore error, title will just default to id // ignore error, title will just default to id
} }
@ -102,7 +126,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
label: i18n.translate('xpack.maps.source.esGrid.indexPatternLabel', { label: i18n.translate('xpack.maps.source.esGrid.indexPatternLabel', {
defaultMessage: 'Index pattern', defaultMessage: 'Index pattern',
}), }),
value: indexPatternTitle, value: indexPatternName,
}, },
{ {
label: i18n.translate('xpack.maps.source.esGrid.geospatialFieldLabel', { label: i18n.translate('xpack.maps.source.esGrid.geospatialFieldLabel', {
@ -117,7 +141,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
return this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName()); return this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName());
} }
isGeoGridPrecisionAware() { isGeoGridPrecisionAware(): boolean {
if (this._descriptor.resolution === GRID_RESOLUTION.SUPER_FINE) { if (this._descriptor.resolution === GRID_RESOLUTION.SUPER_FINE) {
// MVT gridded data should not bootstrap each time the precision changes // MVT gridded data should not bootstrap each time the precision changes
// mapbox-gl needs to handle this // mapbox-gl needs to handle this
@ -128,15 +152,15 @@ export class ESGeoGridSource extends AbstractESAggSource {
} }
} }
showJoinEditor() { showJoinEditor(): boolean {
return false; return false;
} }
getGridResolution() { getGridResolution(): GRID_RESOLUTION {
return this._descriptor.resolution; return this._descriptor.resolution;
} }
getGeoGridPrecision(zoom) { getGeoGridPrecision(zoom: number): number {
if (this._descriptor.resolution === GRID_RESOLUTION.SUPER_FINE) { if (this._descriptor.resolution === GRID_RESOLUTION.SUPER_FINE) {
// The target-precision needs to be determined server side. // The target-precision needs to be determined server side.
return NaN; return NaN;
@ -178,9 +202,18 @@ export class ESGeoGridSource extends AbstractESAggSource {
bucketsPerGrid, bucketsPerGrid,
isRequestStillActive, isRequestStillActive,
bufferedExtent, bufferedExtent,
}: {
searchSource: ISearchSource;
indexPattern: IndexPattern;
precision: number;
layerName: string;
registerCancelCallback: (callback: () => void) => void;
bucketsPerGrid: number;
isRequestStillActive: () => boolean;
bufferedExtent: MapExtent;
}) { }) {
const gridsPerRequest = Math.floor(DEFAULT_MAX_BUCKETS_LIMIT / bucketsPerGrid); const gridsPerRequest: number = Math.floor(DEFAULT_MAX_BUCKETS_LIMIT / bucketsPerGrid);
const aggs = { const aggs: any = {
compositeSplit: { compositeSplit: {
composite: { composite: {
size: gridsPerRequest, size: gridsPerRequest,
@ -232,8 +265,10 @@ export class ESGeoGridSource extends AbstractESAggSource {
aggs.compositeSplit.composite.after = afterKey; aggs.compositeSplit.composite.after = afterKey;
} }
searchSource.setField('aggs', aggs); searchSource.setField('aggs', aggs);
const requestId = afterKey ? `${this.getId()} afterKey ${afterKey.geoSplit}` : this.getId(); const requestId: string = afterKey
const esResponse = await this._runEsQuery({ ? `${this.getId()} afterKey ${afterKey.geoSplit}`
: this.getId();
const esResponse: SearchResponse<unknown> = await this._runEsQuery({
requestId, requestId,
requestName: `${layerName} (${requestCount})`, requestName: `${layerName} (${requestCount})`,
searchSource, searchSource,
@ -259,7 +294,12 @@ export class ESGeoGridSource extends AbstractESAggSource {
return features; return features;
} }
_addNonCompositeAggsToSearchSource(searchSource, indexPattern, precision, bufferedExtent) { _addNonCompositeAggsToSearchSource(
searchSource: ISearchSource,
indexPattern: IndexPattern,
precision: number | null,
bufferedExtent?: MapExtent | null
) {
searchSource.setField('aggs', { searchSource.setField('aggs', {
[GEOTILE_GRID_AGG_NAME]: { [GEOTILE_GRID_AGG_NAME]: {
geotile_grid: { geotile_grid: {
@ -290,7 +330,14 @@ export class ESGeoGridSource extends AbstractESAggSource {
layerName, layerName,
registerCancelCallback, registerCancelCallback,
bufferedExtent, bufferedExtent,
}) { }: {
searchSource: ISearchSource;
indexPattern: IndexPattern;
precision: number;
layerName: string;
registerCancelCallback: (callback: () => void) => void;
bufferedExtent?: MapExtent;
}): Promise<Feature[]> {
this._addNonCompositeAggsToSearchSource(searchSource, indexPattern, precision, bufferedExtent); this._addNonCompositeAggsToSearchSource(searchSource, indexPattern, precision, bufferedExtent);
const esResponse = await this._runEsQuery({ const esResponse = await this._runEsQuery({
@ -306,52 +353,69 @@ export class ESGeoGridSource extends AbstractESAggSource {
return convertRegularRespToGeoJson(esResponse, this._descriptor.requestType); return convertRegularRespToGeoJson(esResponse, this._descriptor.requestType);
} }
async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback, isRequestStillActive) { async getGeoJsonWithMeta(
const indexPattern = await this.getIndexPattern(); layerName: string,
const searchSource = await this.makeSearchSource(searchFilters, 0); searchFilters: VectorSourceRequestMeta,
registerCancelCallback: (callback: () => void) => void,
isRequestStillActive: () => boolean
): Promise<GeoJsonWithMeta> {
const indexPattern: IndexPattern = await this.getIndexPattern();
const searchSource: ISearchSource = await this.makeSearchSource(searchFilters, 0);
let bucketsPerGrid = 1; let bucketsPerGrid = 1;
this.getMetricFields().forEach((metricField) => { this.getMetricFields().forEach((metricField) => {
bucketsPerGrid += metricField.getBucketCount(); bucketsPerGrid += metricField.getBucketCount();
}); });
const features = let features: Feature[];
bucketsPerGrid === 1 if (searchFilters.buffer) {
? await this._nonCompositeAggRequest({ features =
searchSource, bucketsPerGrid === 1
indexPattern, ? await this._nonCompositeAggRequest({
precision: searchFilters.geogridPrecision, searchSource,
layerName, indexPattern,
registerCancelCallback, precision: searchFilters.geogridPrecision || 0,
bufferedExtent: searchFilters.buffer, layerName,
}) registerCancelCallback,
: await this._compositeAggRequest({ bufferedExtent: searchFilters.buffer,
searchSource, })
indexPattern, : await this._compositeAggRequest({
precision: searchFilters.geogridPrecision, searchSource,
layerName, indexPattern,
registerCancelCallback, precision: searchFilters.geogridPrecision || 0,
bucketsPerGrid, layerName,
isRequestStillActive, registerCancelCallback,
bufferedExtent: searchFilters.buffer, bucketsPerGrid,
}); isRequestStillActive,
bufferedExtent: searchFilters.buffer,
});
} else {
throw new Error('Cannot get GeoJson without searchFilter.buffer');
}
return { return {
data: { data: {
type: 'FeatureCollection', type: 'FeatureCollection',
features: features, features,
}, },
meta: { meta: {
areResultsTrimmed: false, areResultsTrimmed: false,
}, },
}; } as GeoJsonWithMeta;
} }
getLayerName() { getLayerName(): string {
return MVT_SOURCE_LAYER_NAME; return MVT_SOURCE_LAYER_NAME;
} }
async getUrlTemplateWithMeta(searchFilters) { async getUrlTemplateWithMeta(
searchFilters: VectorSourceRequestMeta
): Promise<{
layerName: string;
urlTemplate: string;
minSourceZoom: number;
maxSourceZoom: number;
}> {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
const searchSource = await this.makeSearchSource(searchFilters, 0); const searchSource = await this.makeSearchSource(searchFilters, 0);
@ -376,25 +440,25 @@ export class ESGeoGridSource extends AbstractESAggSource {
layerName: this.getLayerName(), layerName: this.getLayerName(),
minSourceZoom: this.getMinZoom(), minSourceZoom: this.getMinZoom(),
maxSourceZoom: this.getMaxZoom(), maxSourceZoom: this.getMaxZoom(),
urlTemplate: urlTemplate, urlTemplate,
}; };
} }
isFilterByMapBounds() { isFilterByMapBounds(): boolean {
if (this._descriptor.resolution === GRID_RESOLUTION.SUPER_FINE) { if (this._descriptor.resolution === GRID_RESOLUTION.SUPER_FINE) {
//MVT gridded data. Should exclude bounds-filter from ES-DSL // MVT gridded data. Should exclude bounds-filter from ES-DSL
return false; return false;
} else { } else {
//Should include bounds-filter from ES-DSL // Should include bounds-filter from ES-DSL
return true; return true;
} }
} }
canFormatFeatureProperties() { canFormatFeatureProperties(): boolean {
return true; return true;
} }
async getSupportedShapeTypes() { async getSupportedShapeTypes(): Promise<VECTOR_SHAPE_TYPE[]> {
if (this._descriptor.requestType === RENDER_AS.GRID) { if (this._descriptor.requestType === RENDER_AS.GRID) {
return [VECTOR_SHAPE_TYPE.POLYGON]; return [VECTOR_SHAPE_TYPE.POLYGON];
} }
@ -402,7 +466,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
return [VECTOR_SHAPE_TYPE.POINT]; return [VECTOR_SHAPE_TYPE.POINT];
} }
async getLicensedFeatures() { async getLicensedFeatures(): Promise<LICENSED_FEATURES[]> {
const geoField = await this._getGeoField(); const geoField = await this._getGeoField();
return geoField.type === ES_GEO_FIELD_TYPE.GEO_SHAPE return geoField.type === ES_GEO_FIELD_TYPE.GEO_SHAPE
? [LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE] ? [LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE]

View file

@ -5,7 +5,6 @@
*/ */
import React from 'react'; import React from 'react';
import uuid from 'uuid/v4';
import turfBbox from '@turf/bbox'; import turfBbox from '@turf/bbox';
import { multiPoint } from '@turf/helpers'; import { multiPoint } from '@turf/helpers';
@ -14,7 +13,7 @@ import { i18n } from '@kbn/i18n';
import { SOURCE_TYPES, VECTOR_SHAPE_TYPE } from '../../../../common/constants'; import { SOURCE_TYPES, VECTOR_SHAPE_TYPE } from '../../../../common/constants';
import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { convertToLines } from './convert_to_lines'; import { convertToLines } from './convert_to_lines';
import { AbstractESAggSource, DEFAULT_METRIC } from '../es_agg_source'; import { AbstractESAggSource } from '../es_agg_source';
import { registerSource } from '../source_registry'; import { registerSource } from '../source_registry';
import { turfBboxToBounds } from '../../../../common/elasticsearch_util'; import { turfBboxToBounds } from '../../../../common/elasticsearch_util';
import { DataRequestAbortError } from '../../util/data_request'; import { DataRequestAbortError } from '../../util/data_request';
@ -28,14 +27,14 @@ export const sourceTitle = i18n.translate('xpack.maps.source.pewPewTitle', {
export class ESPewPewSource extends AbstractESAggSource { export class ESPewPewSource extends AbstractESAggSource {
static type = SOURCE_TYPES.ES_PEW_PEW; static type = SOURCE_TYPES.ES_PEW_PEW;
static createDescriptor({ indexPatternId, sourceGeoField, destGeoField, metrics }) { static createDescriptor(descriptor) {
const normalizedDescriptor = AbstractESAggSource.createDescriptor(descriptor);
return { return {
...normalizedDescriptor,
type: ESPewPewSource.type, type: ESPewPewSource.type,
id: uuid(), indexPatternId: descriptor.indexPatternId,
indexPatternId: indexPatternId, sourceGeoField: descriptor.sourceGeoField,
sourceGeoField, destGeoField: descriptor.destGeoField,
destGeoField,
metrics: metrics ? metrics : [DEFAULT_METRIC],
}; };
} }

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
export const DEFAULT_FILTER_BY_MAP_BOUNDS = true; export const DEFAULT_FILTER_BY_MAP_BOUNDS: boolean = true;

View file

@ -16,8 +16,15 @@ import { VectorLayer } from '../../layers/vector_layer/vector_layer';
import { LAYER_WIZARD_CATEGORY, SCALING_TYPES } from '../../../../common/constants'; import { LAYER_WIZARD_CATEGORY, SCALING_TYPES } from '../../../../common/constants';
import { TiledVectorLayer } from '../../layers/tiled_vector_layer/tiled_vector_layer'; import { TiledVectorLayer } from '../../layers/tiled_vector_layer/tiled_vector_layer';
import { EsDocumentsLayerIcon } from './es_documents_layer_icon'; import { EsDocumentsLayerIcon } from './es_documents_layer_icon';
import {
ESSearchSourceDescriptor,
VectorLayerDescriptor,
} from '../../../../common/descriptor_types';
export function createDefaultLayerDescriptor(sourceConfig: unknown, mapColors: string[]) { export function createDefaultLayerDescriptor(
sourceConfig: Partial<ESSearchSourceDescriptor>,
mapColors: string[]
): VectorLayerDescriptor {
const sourceDescriptor = ESSearchSource.createDescriptor(sourceConfig); const sourceDescriptor = ESSearchSource.createDescriptor(sourceConfig);
if (sourceDescriptor.scalingType === SCALING_TYPES.CLUSTERS) { if (sourceDescriptor.scalingType === SCALING_TYPES.CLUSTERS) {
@ -36,7 +43,7 @@ export const esDocumentsLayerWizardConfig: LayerWizard = {
}), }),
icon: EsDocumentsLayerIcon, icon: EsDocumentsLayerIcon,
renderWizard: ({ previewLayers, mapColors }: RenderWizardArguments) => { renderWizard: ({ previewLayers, mapColors }: RenderWizardArguments) => {
const onSourceConfigChange = (sourceConfig: unknown) => { const onSourceConfigChange = (sourceConfig: Partial<ESSearchSourceDescriptor>) => {
if (!sourceConfig) { if (!sourceConfig) {
previewLayers([]); previewLayers([]);
return; return;

View file

@ -1,26 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { AbstractESSource } from '../es_source';
import { ESSearchSourceDescriptor, MapFilters } from '../../../../common/descriptor_types';
import { ITiledSingleLayerVectorSource } from '../vector_source';
export class ESSearchSource extends AbstractESSource implements ITiledSingleLayerVectorSource {
static createDescriptor(sourceConfig: unknown): ESSearchSourceDescriptor;
constructor(sourceDescriptor: Partial<ESSearchSourceDescriptor>, inspectorAdapters: unknown);
getFieldNames(): string[];
getUrlTemplateWithMeta(
searchFilters: MapFilters
): Promise<{
layerName: string;
urlTemplate: string;
minSourceZoom: number;
maxSourceZoom: number;
}>;
getLayerName(): string;
}

View file

@ -11,21 +11,22 @@ jest.mock('./load_index_settings');
import { getIndexPatternService, getSearchService, getHttp } from '../../../kibana_services'; import { getIndexPatternService, getSearchService, getHttp } from '../../../kibana_services';
import { SearchSource } from 'src/plugins/data/public'; import { SearchSource } from 'src/plugins/data/public';
// @ts-expect-error
import { loadIndexSettings } from './load_index_settings'; import { loadIndexSettings } from './load_index_settings';
import { ESSearchSource } from './es_search_source'; import { ESSearchSource } from './es_search_source';
import { VectorSourceRequestMeta } from '../../../../common/descriptor_types'; import { VectorSourceRequestMeta } from '../../../../common/descriptor_types';
const mockDescriptor = { indexPatternId: 'foo', geoField: 'bar' };
describe('ESSearchSource', () => { describe('ESSearchSource', () => {
it('constructor', () => { it('constructor', () => {
const esSearchSource = new ESSearchSource({}, null); const esSearchSource = new ESSearchSource(mockDescriptor);
expect(esSearchSource instanceof ESSearchSource).toBe(true); expect(esSearchSource instanceof ESSearchSource).toBe(true);
}); });
describe('ITiledSingleLayerVectorSource', () => { describe('ITiledSingleLayerVectorSource', () => {
it('mb-source params', () => { it('mb-source params', () => {
const esSearchSource = new ESSearchSource({}, null); const esSearchSource = new ESSearchSource(mockDescriptor);
expect(esSearchSource.getMinZoom()).toBe(0); expect(esSearchSource.getMinZoom()).toBe(0);
expect(esSearchSource.getMaxZoom()).toBe(24); expect(esSearchSource.getMaxZoom()).toBe(24);
expect(esSearchSource.getLayerName()).toBe('source_layer'); expect(esSearchSource.getLayerName()).toBe('source_layer');
@ -72,6 +73,7 @@ describe('ESSearchSource', () => {
getIndexPatternService.mockReturnValue(mockIndexPatternService); getIndexPatternService.mockReturnValue(mockIndexPatternService);
// @ts-expect-error // @ts-expect-error
getSearchService.mockReturnValue(mockSearchService); getSearchService.mockReturnValue(mockSearchService);
// @ts-expect-error
loadIndexSettings.mockReturnValue({ loadIndexSettings.mockReturnValue({
maxResultWindow: 1000, maxResultWindow: 1000,
}); });
@ -104,10 +106,10 @@ describe('ESSearchSource', () => {
}; };
it('Should only include required props', async () => { it('Should only include required props', async () => {
const esSearchSource = new ESSearchSource( const esSearchSource = new ESSearchSource({
{ geoField: geoFieldName, indexPatternId: 'ipId' }, geoField: geoFieldName,
null indexPatternId: 'ipId',
); });
const urlTemplateWithMeta = await esSearchSource.getUrlTemplateWithMeta(searchFilters); const urlTemplateWithMeta = await esSearchSource.getUrlTemplateWithMeta(searchFilters);
expect(urlTemplateWithMeta.urlTemplate).toBe( expect(urlTemplateWithMeta.urlTemplate).toBe(
`rootdir/api/maps/mvt/getTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':fields,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))&geoFieldType=geo_shape` `rootdir/api/maps/mvt/getTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':fields,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))&geoFieldType=geo_shape`
@ -118,22 +120,28 @@ describe('ESSearchSource', () => {
describe('isFilterByMapBounds', () => { describe('isFilterByMapBounds', () => {
it('default', () => { it('default', () => {
const esSearchSource = new ESSearchSource({}, null); const esSearchSource = new ESSearchSource(mockDescriptor);
expect(esSearchSource.isFilterByMapBounds()).toBe(true); expect(esSearchSource.isFilterByMapBounds()).toBe(true);
}); });
it('mvt', () => { it('mvt', () => {
const esSearchSource = new ESSearchSource({ scalingType: SCALING_TYPES.MVT }, null); const esSearchSource = new ESSearchSource({
...mockDescriptor,
scalingType: SCALING_TYPES.MVT,
});
expect(esSearchSource.isFilterByMapBounds()).toBe(false); expect(esSearchSource.isFilterByMapBounds()).toBe(false);
}); });
}); });
describe('getJoinsDisabledReason', () => { describe('getJoinsDisabledReason', () => {
it('default', () => { it('default', () => {
const esSearchSource = new ESSearchSource({}, null); const esSearchSource = new ESSearchSource(mockDescriptor);
expect(esSearchSource.getJoinsDisabledReason()).toBe(null); expect(esSearchSource.getJoinsDisabledReason()).toBe(null);
}); });
it('mvt', () => { it('mvt', () => {
const esSearchSource = new ESSearchSource({ scalingType: SCALING_TYPES.MVT }, null); const esSearchSource = new ESSearchSource({
...mockDescriptor,
scalingType: SCALING_TYPES.MVT,
});
expect(esSearchSource.getJoinsDisabledReason()).toBe( expect(esSearchSource.getJoinsDisabledReason()).toBe(
'Joins are not supported when scaling by mvt vector tiles' 'Joins are not supported when scaling by mvt vector tiles'
); );
@ -142,12 +150,15 @@ describe('ESSearchSource', () => {
describe('getFields', () => { describe('getFields', () => {
it('default', () => { it('default', () => {
const esSearchSource = new ESSearchSource({}, null); const esSearchSource = new ESSearchSource(mockDescriptor);
const docField = esSearchSource.createField({ fieldName: 'prop1' }); const docField = esSearchSource.createField({ fieldName: 'prop1' });
expect(docField.canReadFromGeoJson()).toBe(true); expect(docField.canReadFromGeoJson()).toBe(true);
}); });
it('mvt', () => { it('mvt', () => {
const esSearchSource = new ESSearchSource({ scalingType: SCALING_TYPES.MVT }, null); const esSearchSource = new ESSearchSource({
...mockDescriptor,
scalingType: SCALING_TYPES.MVT,
});
const docField = esSearchSource.createField({ fieldName: 'prop1' }); const docField = esSearchSource.createField({ fieldName: 'prop1' });
expect(docField.canReadFromGeoJson()).toBe(false); expect(docField.canReadFromGeoJson()).toBe(false);
}); });

View file

@ -5,50 +5,82 @@
*/ */
import _ from 'lodash'; import _ from 'lodash';
import React from 'react'; import React, { ReactElement } from 'react';
import rison from 'rison-node'; import rison from 'rison-node';
import { i18n } from '@kbn/i18n';
import { IFieldType, IndexPattern } from 'src/plugins/data/public';
import { FeatureCollection, GeoJsonProperties } from 'geojson';
import { AbstractESSource } from '../es_source'; import { AbstractESSource } from '../es_source';
import { getSearchService, getHttp } from '../../../kibana_services'; import { getHttp, getSearchService } from '../../../kibana_services';
import { hitsToGeoJson, getField, addFieldToDSL } from '../../../../common/elasticsearch_util'; import { addFieldToDSL, getField, hitsToGeoJson } from '../../../../common/elasticsearch_util';
// @ts-expect-error
import { UpdateSourceEditor } from './update_source_editor'; import { UpdateSourceEditor } from './update_source_editor';
import { import {
SOURCE_TYPES,
ES_GEO_FIELD_TYPE,
DEFAULT_MAX_BUCKETS_LIMIT, DEFAULT_MAX_BUCKETS_LIMIT,
SORT_ORDER, ES_GEO_FIELD_TYPE,
SCALING_TYPES, FIELD_ORIGIN,
VECTOR_SHAPE_TYPE,
MVT_SOURCE_LAYER_NAME,
GIS_API_PATH, GIS_API_PATH,
MVT_GETTILE_API_PATH, MVT_GETTILE_API_PATH,
MVT_SOURCE_LAYER_NAME,
SCALING_TYPES,
SOURCE_TYPES,
VECTOR_SHAPE_TYPE,
} from '../../../../common/constants'; } from '../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { getSourceFields } from '../../../index_pattern_util'; import { getSourceFields } from '../../../index_pattern_util';
import { loadIndexSettings } from './load_index_settings'; import { loadIndexSettings } from './load_index_settings';
import uuid from 'uuid/v4';
import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants';
import { ESDocField } from '../../fields/es_doc_field'; import { ESDocField } from '../../fields/es_doc_field';
import { registerSource } from '../source_registry'; import { registerSource } from '../source_registry';
import {
ESSearchSourceDescriptor,
VectorSourceRequestMeta,
VectorSourceSyncMeta,
} from '../../../../common/descriptor_types';
import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters';
import { ImmutableSourceProperty, PreIndexedShape, SourceEditorArgs } from '../source';
import { IField } from '../../fields/field';
import {
GeoJsonWithMeta,
ITiledSingleLayerVectorSource,
SourceTooltipConfig,
} from '../vector_source';
import { ITooltipProperty } from '../../tooltips/tooltip_property';
import { DataRequest } from '../../util/data_request';
import { SortDirection, SortDirectionNumeric } from '../../../../../../../src/plugins/data/common';
import { isValidStringConfig } from '../../util/valid_string_config';
export const sourceTitle = i18n.translate('xpack.maps.source.esSearchTitle', { export const sourceTitle = i18n.translate('xpack.maps.source.esSearchTitle', {
defaultMessage: 'Documents', defaultMessage: 'Documents',
}); });
function getDocValueAndSourceFields(indexPattern, fieldNames) { export interface ScriptField {
const docValueFields = []; source: string;
const sourceOnlyFields = []; lang: string;
const scriptFields = {}; }
function getDocValueAndSourceFields(
indexPattern: IndexPattern,
fieldNames: string[]
): {
docValueFields: Array<string | { format: string; field: string }>;
sourceOnlyFields: string[];
scriptFields: Record<string, { script: ScriptField }>;
} {
const docValueFields: Array<string | { format: string; field: string }> = [];
const sourceOnlyFields: string[] = [];
const scriptFields: Record<string, { script: ScriptField }> = {};
fieldNames.forEach((fieldName) => { fieldNames.forEach((fieldName) => {
const field = getField(indexPattern, fieldName); const field = getField(indexPattern, fieldName);
if (field.scripted) { if (field.scripted) {
scriptFields[field.name] = { scriptFields[field.name] = {
script: { script: {
source: field.script, source: field.script || '',
lang: field.lang, lang: field.lang || '',
}, },
}; };
} else if (field.readFromDocValues) { } else if (field.readFromDocValues) {
@ -68,43 +100,64 @@ function getDocValueAndSourceFields(indexPattern, fieldNames) {
return { docValueFields, sourceOnlyFields, scriptFields }; return { docValueFields, sourceOnlyFields, scriptFields };
} }
export class ESSearchSource extends AbstractESSource { export class ESSearchSource extends AbstractESSource implements ITiledSingleLayerVectorSource {
static type = SOURCE_TYPES.ES_SEARCH; readonly _descriptor: ESSearchSourceDescriptor;
protected readonly _tooltipFields: ESDocField[];
static createDescriptor(descriptor) { static createDescriptor(descriptor: Partial<ESSearchSourceDescriptor>): ESSearchSourceDescriptor {
const normalizedDescriptor = AbstractESSource.createDescriptor(descriptor);
if (!isValidStringConfig(normalizedDescriptor.geoField)) {
throw new Error('Cannot create an ESSearchSourceDescriptor without a geoField');
}
return { return {
...descriptor, ...normalizedDescriptor,
id: descriptor.id ? descriptor.id : uuid(), type: SOURCE_TYPES.ES_SEARCH,
type: ESSearchSource.type, geoField: normalizedDescriptor.geoField!,
indexPatternId: descriptor.indexPatternId, filterByMapBounds:
geoField: descriptor.geoField, typeof descriptor.filterByMapBounds === 'boolean'
filterByMapBounds: _.get(descriptor, 'filterByMapBounds', DEFAULT_FILTER_BY_MAP_BOUNDS), ? descriptor.filterByMapBounds
tooltipProperties: _.get(descriptor, 'tooltipProperties', []), : DEFAULT_FILTER_BY_MAP_BOUNDS,
sortField: _.get(descriptor, 'sortField', ''), tooltipProperties: Array.isArray(descriptor.tooltipProperties)
sortOrder: _.get(descriptor, 'sortOrder', SORT_ORDER.DESC), ? descriptor.tooltipProperties
scalingType: _.get(descriptor, 'scalingType', SCALING_TYPES.LIMIT), : [],
topHitsSplitField: descriptor.topHitsSplitField, sortField: isValidStringConfig(descriptor.sortField) ? (descriptor.sortField as string) : '',
topHitsSize: _.get(descriptor, 'topHitsSize', 1), sortOrder: isValidStringConfig(descriptor.sortOrder)
? descriptor.sortOrder!
: SortDirection.desc,
scalingType: isValidStringConfig(descriptor.scalingType)
? descriptor.scalingType!
: SCALING_TYPES.LIMIT,
topHitsSplitField: isValidStringConfig(descriptor.topHitsSplitField)
? descriptor.topHitsSplitField!
: '',
topHitsSize:
typeof descriptor.topHitsSize === 'number' && descriptor.topHitsSize > 0
? descriptor.topHitsSize
: 1,
}; };
} }
constructor(descriptor, inspectorAdapters) { constructor(descriptor: Partial<ESSearchSourceDescriptor>, inspectorAdapters?: Adapters) {
super(ESSearchSource.createDescriptor(descriptor), inspectorAdapters); const sourceDescriptor = ESSearchSource.createDescriptor(descriptor);
super(sourceDescriptor, inspectorAdapters);
this._tooltipFields = this._descriptor.tooltipProperties.map((property) => this._descriptor = sourceDescriptor;
this.createField({ fieldName: property }) this._tooltipFields = this._descriptor.tooltipProperties
); ? this._descriptor.tooltipProperties.map((property) => {
return this.createField({ fieldName: property });
})
: [];
} }
createField({ fieldName }) { createField({ fieldName }: { fieldName: string }): ESDocField {
return new ESDocField({ return new ESDocField({
fieldName, fieldName,
source: this, source: this,
origin: FIELD_ORIGIN.SOURCE,
canReadFromGeoJson: this._descriptor.scalingType !== SCALING_TYPES.MVT, canReadFromGeoJson: this._descriptor.scalingType !== SCALING_TYPES.MVT,
}); });
} }
renderSourceSettingsEditor({ onChange }) { renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement<any> | null {
const getGeoField = () => { const getGeoField = () => {
return this._getGeoField(); return this._getGeoField();
}; };
@ -113,7 +166,7 @@ export class ESSearchSource extends AbstractESSource {
source={this} source={this}
indexPatternId={this.getIndexPatternId()} indexPatternId={this.getIndexPatternId()}
getGeoField={getGeoField} getGeoField={getGeoField}
onChange={onChange} onChange={sourceEditorArgs.onChange}
tooltipFields={this._tooltipFields} tooltipFields={this._tooltipFields}
sortField={this._descriptor.sortField} sortField={this._descriptor.sortField}
sortOrder={this._descriptor.sortOrder} sortOrder={this._descriptor.sortOrder}
@ -125,34 +178,36 @@ export class ESSearchSource extends AbstractESSource {
); );
} }
async getFields() { async getFields(): Promise<IField[]> {
try { try {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
return indexPattern.fields const fields: IFieldType[] = indexPattern.fields.filter((field) => {
.filter((field) => { // Ensure fielddata is enabled for field.
// Ensure fielddata is enabled for field. // Search does not request _source
// Search does not request _source return field.aggregatable;
return field.aggregatable; });
})
.map((field) => { return fields.map(
(field): IField => {
return this.createField({ fieldName: field.name }); return this.createField({ fieldName: field.name });
}); }
);
} catch (error) { } catch (error) {
// failed index-pattern retrieval will show up as error-message in the layer-toc-entry // failed index-pattern retrieval will show up as error-message in the layer-toc-entry
return []; return [];
} }
} }
getFieldNames() { getFieldNames(): string[] {
return [this._descriptor.geoField]; return [this._descriptor.geoField];
} }
async getImmutableProperties() { async getImmutableProperties(): Promise<ImmutableSourceProperty[]> {
let indexPatternTitle = this.getIndexPatternId(); let indexPatternName = this.getIndexPatternId();
let geoFieldType = ''; let geoFieldType = '';
try { try {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
indexPatternTitle = indexPattern.title; indexPatternName = indexPattern.title;
const geoField = await this._getGeoField(); const geoField = await this._getGeoField();
geoFieldType = geoField.type; geoFieldType = geoField.type;
} catch (error) { } catch (error) {
@ -168,7 +223,7 @@ export class ESSearchSource extends AbstractESSource {
label: i18n.translate('xpack.maps.source.esSearch.indexPatternLabel', { label: i18n.translate('xpack.maps.source.esSearch.indexPatternLabel', {
defaultMessage: `Index pattern`, defaultMessage: `Index pattern`,
}), }),
value: indexPatternTitle, value: indexPatternName,
}, },
{ {
label: i18n.translate('xpack.maps.source.esSearch.geoFieldLabel', { label: i18n.translate('xpack.maps.source.esSearch.geoFieldLabel', {
@ -186,8 +241,12 @@ export class ESSearchSource extends AbstractESSource {
} }
// Returns sort content for an Elasticsearch search body // Returns sort content for an Elasticsearch search body
_buildEsSort() { _buildEsSort(): Array<Record<string, SortDirectionNumeric>> {
const { sortField, sortOrder } = this._descriptor; const { sortField, sortOrder } = this._descriptor;
if (!sortField) {
throw new Error('Cannot build sort');
}
return [ return [
{ {
[sortField]: { [sortField]: {
@ -197,16 +256,30 @@ export class ESSearchSource extends AbstractESSource {
]; ];
} }
async _getTopHits(layerName, searchFilters, registerCancelCallback) { async _getTopHits(
layerName: string,
searchFilters: VectorSourceRequestMeta,
registerCancelCallback: (callback: () => void) => void
) {
const { topHitsSplitField: topHitsSplitFieldName, topHitsSize } = this._descriptor; const { topHitsSplitField: topHitsSplitFieldName, topHitsSize } = this._descriptor;
const indexPattern = await this.getIndexPattern(); if (!topHitsSplitFieldName) {
throw new Error('Cannot _getTopHits without topHitsSplitField');
}
const indexPattern: IndexPattern = await this.getIndexPattern();
const { docValueFields, sourceOnlyFields, scriptFields } = getDocValueAndSourceFields( const { docValueFields, sourceOnlyFields, scriptFields } = getDocValueAndSourceFields(
indexPattern, indexPattern,
searchFilters.fieldNames searchFilters.fieldNames
); );
const topHits = { const topHits: {
size: number;
script_fields: Record<string, { script: ScriptField }>;
docvalue_fields: Array<string | { format: string; field: string }>;
_source?: boolean | { includes: string[] };
sort?: Array<Record<string, SortDirectionNumeric>>;
} = {
size: topHitsSize, size: topHitsSize,
script_fields: scriptFields, script_fields: scriptFields,
docvalue_fields: docValueFields, docvalue_fields: docValueFields,
@ -215,6 +288,7 @@ export class ESSearchSource extends AbstractESSource {
if (this._hasSort()) { if (this._hasSort()) {
topHits.sort = this._buildEsSort(); topHits.sort = this._buildEsSort();
} }
if (sourceOnlyFields.length === 0) { if (sourceOnlyFields.length === 0) {
topHits._source = false; topHits._source = false;
} else { } else {
@ -223,7 +297,7 @@ export class ESSearchSource extends AbstractESSource {
}; };
} }
const topHitsSplitField = getField(indexPattern, topHitsSplitFieldName); const topHitsSplitField: IFieldType = getField(indexPattern, topHitsSplitFieldName);
const cardinalityAgg = { precision_threshold: 1 }; const cardinalityAgg = { precision_threshold: 1 };
const termsAgg = { const termsAgg = {
size: DEFAULT_MAX_BUCKETS_LIMIT, size: DEFAULT_MAX_BUCKETS_LIMIT,
@ -253,13 +327,13 @@ export class ESSearchSource extends AbstractESSource {
requestDescription: 'Elasticsearch document top hits request', requestDescription: 'Elasticsearch document top hits request',
}); });
const allHits = []; const allHits: any[] = [];
const entityBuckets = _.get(resp, 'aggregations.entitySplit.buckets', []); const entityBuckets = _.get(resp, 'aggregations.entitySplit.buckets', []);
const totalEntities = _.get(resp, 'aggregations.totalEntities.value', 0); const totalEntities = _.get(resp, 'aggregations.totalEntities.value', 0);
// can not compare entityBuckets.length to totalEntities because totalEntities is an approximate // can not compare entityBuckets.length to totalEntities because totalEntities is an approximate
const areEntitiesTrimmed = entityBuckets.length >= DEFAULT_MAX_BUCKETS_LIMIT; const areEntitiesTrimmed = entityBuckets.length >= DEFAULT_MAX_BUCKETS_LIMIT;
let areTopHitsTrimmed = false; let areTopHitsTrimmed = false;
entityBuckets.forEach((entityBucket) => { entityBuckets.forEach((entityBucket: any) => {
const total = _.get(entityBucket, 'entityHits.hits.total', 0); const total = _.get(entityBucket, 'entityHits.hits.total', 0);
const hits = _.get(entityBucket, 'entityHits.hits.hits', []); const hits = _.get(entityBucket, 'entityHits.hits.hits', []);
// Reverse hits list so top documents by sort are drawn on top // Reverse hits list so top documents by sort are drawn on top
@ -282,7 +356,12 @@ export class ESSearchSource extends AbstractESSource {
// searchFilters.fieldNames contains geo field and any fields needed for styling features // searchFilters.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 // Performs Elasticsearch search request being careful to pull back only required fields to minimize response size
async _getSearchHits(layerName, searchFilters, maxResultWindow, registerCancelCallback) { async _getSearchHits(
layerName: string,
searchFilters: VectorSourceRequestMeta,
maxResultWindow: number,
registerCancelCallback: (callback: () => void) => void
) {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
const { docValueFields, sourceOnlyFields } = getDocValueAndSourceFields( const { docValueFields, sourceOnlyFields } = getDocValueAndSourceFields(
@ -322,23 +401,28 @@ export class ESSearchSource extends AbstractESSource {
}; };
} }
_isTopHits() { _isTopHits(): boolean {
const { scalingType, topHitsSplitField } = this._descriptor; const { scalingType, topHitsSplitField } = this._descriptor;
return !!(scalingType === SCALING_TYPES.TOP_HITS && topHitsSplitField); return !!(scalingType === SCALING_TYPES.TOP_HITS && topHitsSplitField);
} }
_hasSort() { _hasSort(): boolean {
const { sortField, sortOrder } = this._descriptor; const { sortField, sortOrder } = this._descriptor;
return !!sortField && !!sortOrder; return !!sortField && !!sortOrder;
} }
async getMaxResultWindow() { async getMaxResultWindow(): Promise<number> {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
const indexSettings = await loadIndexSettings(indexPattern.title); const indexSettings = await loadIndexSettings(indexPattern.title);
return indexSettings.maxResultWindow; return indexSettings.maxResultWindow;
} }
async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback) { async getGeoJsonWithMeta(
layerName: string,
searchFilters: VectorSourceRequestMeta,
registerCancelCallback: (callback: () => void) => void,
isRequestStillActive: () => boolean
): Promise<GeoJsonWithMeta> {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
const indexSettings = await loadIndexSettings(indexPattern.title); const indexSettings = await loadIndexSettings(indexPattern.title);
@ -355,7 +439,7 @@ export class ESSearchSource extends AbstractESSource {
const unusedMetaFields = indexPattern.metaFields.filter((metaField) => { const unusedMetaFields = indexPattern.metaFields.filter((metaField) => {
return !['_id', '_index'].includes(metaField); return !['_id', '_index'].includes(metaField);
}); });
const flattenHit = (hit) => { const flattenHit = (hit: Record<string, any>) => {
const properties = indexPattern.flattenHit(hit); const properties = indexPattern.flattenHit(hit);
// remove metaFields // remove metaFields
unusedMetaFields.forEach((metaField) => { unusedMetaFields.forEach((metaField) => {
@ -375,7 +459,7 @@ export class ESSearchSource extends AbstractESSource {
hits, hits,
flattenHit, flattenHit,
geoField.name, geoField.name,
geoField.type, geoField.type as ES_GEO_FIELD_TYPE,
epochMillisFields epochMillisFields
); );
} catch (error) { } catch (error) {
@ -394,11 +478,11 @@ export class ESSearchSource extends AbstractESSource {
}; };
} }
canFormatFeatureProperties() { canFormatFeatureProperties(): boolean {
return this._tooltipFields.length > 0; return this._tooltipFields.length > 0;
} }
async _loadTooltipProperties(docId, index, indexPattern) { async _loadTooltipProperties(docId: string | number, index: string, indexPattern: IndexPattern) {
if (this._tooltipFields.length === 0) { if (this._tooltipFields.length === 0) {
return {}; return {};
} }
@ -430,7 +514,7 @@ export class ESSearchSource extends AbstractESSource {
} }
const properties = indexPattern.flattenHit(hit); const properties = indexPattern.flattenHit(hit);
indexPattern.metaFields.forEach((metaField) => { indexPattern.metaFields.forEach((metaField: string) => {
if (!this._getTooltipPropertyNames().includes(metaField)) { if (!this._getTooltipPropertyNames().includes(metaField)) {
delete properties[metaField]; delete properties[metaField];
} }
@ -438,7 +522,14 @@ export class ESSearchSource extends AbstractESSource {
return properties; return properties;
} }
async getTooltipProperties(properties) { _getTooltipPropertyNames(): string[] {
return this._tooltipFields.map((field: IField) => field.getName());
}
async getTooltipProperties(properties: GeoJsonProperties): Promise<ITooltipProperty[]> {
if (properties === null) {
throw new Error('properties cannot be null');
}
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
const propertyValues = await this._loadTooltipProperties( const propertyValues = await this._loadTooltipProperties(
properties._id, properties._id,
@ -452,25 +543,27 @@ export class ESSearchSource extends AbstractESSource {
return Promise.all(tooltipProperties); return Promise.all(tooltipProperties);
} }
isFilterByMapBounds() { isFilterByMapBounds(): boolean {
if (this._descriptor.scalingType === SCALING_TYPES.CLUSTER) { if (this._descriptor.scalingType === SCALING_TYPES.CLUSTERS) {
return true; return true;
} else if (this._descriptor.scalingType === SCALING_TYPES.MVT) { } else if (this._descriptor.scalingType === SCALING_TYPES.MVT) {
return false; return false;
} else { } else {
return this._descriptor.filterByMapBounds; return !!this._descriptor.filterByMapBounds;
} }
} }
async getLeftJoinFields() { async getLeftJoinFields(): Promise<IField[]> {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
// Left fields are retrieved from _source. // Left fields are retrieved from _source.
return getSourceFields(indexPattern.fields).map((field) => return getSourceFields(indexPattern.fields).map(
this.createField({ fieldName: field.name }) (field): IField => {
return this.createField({ fieldName: field.name });
}
); );
} }
async getSupportedShapeTypes() { async getSupportedShapeTypes(): Promise<VECTOR_SHAPE_TYPE[]> {
let geoFieldType; let geoFieldType;
try { try {
const geoField = await this._getGeoField(); const geoField = await this._getGeoField();
@ -486,8 +579,10 @@ export class ESSearchSource extends AbstractESSource {
return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON]; return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON];
} }
getSourceTooltipContent(sourceDataRequest) { getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig {
const featureCollection = sourceDataRequest ? sourceDataRequest.getData() : null; const featureCollection: FeatureCollection | null = sourceDataRequest
? (sourceDataRequest.getData() as FeatureCollection)
: null;
const meta = sourceDataRequest ? sourceDataRequest.getMeta() : null; const meta = sourceDataRequest ? sourceDataRequest.getMeta() : null;
if (!featureCollection || !meta) { if (!featureCollection || !meta) {
// no tooltip content needed when there is no feature collection or meta // no tooltip content needed when there is no feature collection or meta
@ -519,7 +614,7 @@ export class ESSearchSource extends AbstractESSource {
tooltipContent: `${entitiesFoundMsg} ${docsPerEntityMsg}`, tooltipContent: `${entitiesFoundMsg} ${docsPerEntityMsg}`,
// Used to show trimmed icon in legend // Used to show trimmed icon in legend
// user only needs to be notified of trimmed results when entities are trimmed // user only needs to be notified of trimmed results when entities are trimmed
areResultsTrimmed: meta.areEntitiesTrimmed, areResultsTrimmed: !!meta.areEntitiesTrimmed,
}; };
} }
@ -542,7 +637,7 @@ export class ESSearchSource extends AbstractESSource {
}; };
} }
getSyncMeta() { getSyncMeta(): VectorSourceSyncMeta | null {
return { return {
sortField: this._descriptor.sortField, sortField: this._descriptor.sortField,
sortOrder: this._descriptor.sortOrder, sortOrder: this._descriptor.sortOrder,
@ -552,7 +647,10 @@ export class ESSearchSource extends AbstractESSource {
}; };
} }
async getPreIndexedShape(properties) { async getPreIndexedShape(properties: GeoJsonProperties): Promise<PreIndexedShape | null> {
if (properties === null) {
return null;
}
const geoField = await this._getGeoField(); const geoField = await this._getGeoField();
return { return {
index: properties._index, // Can not use index pattern title because it may reference many indices index: properties._index, // Can not use index pattern title because it may reference many indices
@ -561,7 +659,7 @@ export class ESSearchSource extends AbstractESSource {
}; };
} }
getJoinsDisabledReason() { getJoinsDisabledReason(): string | null {
let reason; let reason;
if (this._descriptor.scalingType === SCALING_TYPES.CLUSTERS) { if (this._descriptor.scalingType === SCALING_TYPES.CLUSTERS) {
reason = i18n.translate('xpack.maps.source.esSearch.joinsDisabledReason', { reason = i18n.translate('xpack.maps.source.esSearch.joinsDisabledReason', {
@ -577,11 +675,18 @@ export class ESSearchSource extends AbstractESSource {
return reason; return reason;
} }
getLayerName() { getLayerName(): string {
return MVT_SOURCE_LAYER_NAME; return MVT_SOURCE_LAYER_NAME;
} }
async getUrlTemplateWithMeta(searchFilters) { async getUrlTemplateWithMeta(
searchFilters: VectorSourceRequestMeta
): Promise<{
layerName: string;
urlTemplate: string;
minSourceZoom: number;
maxSourceZoom: number;
}> {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
const indexSettings = await loadIndexSettings(indexPattern.title); const indexSettings = await loadIndexSettings(indexPattern.title);
@ -621,7 +726,7 @@ export class ESSearchSource extends AbstractESSource {
layerName: this.getLayerName(), layerName: this.getLayerName(),
minSourceZoom: this.getMinZoom(), minSourceZoom: this.getMinZoom(),
maxSourceZoom: this.getMaxZoom(), maxSourceZoom: this.getMaxZoom(),
urlTemplate: urlTemplate, urlTemplate,
}; };
} }
} }

View file

@ -4,20 +4,25 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { i18n } from '@kbn/i18n';
import { import {
DEFAULT_MAX_RESULT_WINDOW, DEFAULT_MAX_RESULT_WINDOW,
DEFAULT_MAX_INNER_RESULT_WINDOW, DEFAULT_MAX_INNER_RESULT_WINDOW,
INDEX_SETTINGS_API_PATH, INDEX_SETTINGS_API_PATH,
} from '../../../../common/constants'; } from '../../../../common/constants';
import { getHttp, getToasts } from '../../../kibana_services'; import { getHttp, getToasts } from '../../../kibana_services';
import { i18n } from '@kbn/i18n';
let toastDisplayed = false; let toastDisplayed = false;
const indexSettings = new Map(); const indexSettings = new Map<string, Promise<INDEX_SETTINGS>>();
export async function loadIndexSettings(indexPatternTitle) { export interface INDEX_SETTINGS {
maxResultWindow: number;
maxInnerResultWindow: number;
}
export async function loadIndexSettings(indexPatternTitle: string): Promise<INDEX_SETTINGS> {
if (indexSettings.has(indexPatternTitle)) { if (indexSettings.has(indexPatternTitle)) {
return indexSettings.get(indexPatternTitle); return indexSettings.get(indexPatternTitle)!;
} }
const fetchPromise = fetchIndexSettings(indexPatternTitle); const fetchPromise = fetchIndexSettings(indexPatternTitle);
@ -25,7 +30,7 @@ export async function loadIndexSettings(indexPatternTitle) {
return fetchPromise; return fetchPromise;
} }
async function fetchIndexSettings(indexPatternTitle) { async function fetchIndexSettings(indexPatternTitle: string): Promise<INDEX_SETTINGS> {
const http = getHttp(); const http = getHttp();
const toasts = getToasts(); const toasts = getToasts();
try { try {
@ -50,6 +55,7 @@ async function fetchIndexSettings(indexPatternTitle) {
toastDisplayed = true; toastDisplayed = true;
toasts.addWarning(warningMsg); toasts.addWarning(warningMsg);
} }
// eslint-disable-next-line no-console
console.warn(warningMsg); console.warn(warningMsg);
return { return {
maxResultWindow: DEFAULT_MAX_RESULT_WINDOW, maxResultWindow: DEFAULT_MAX_RESULT_WINDOW,

View file

@ -18,10 +18,10 @@ import {
getSourceFields, getSourceFields,
supportsGeoTileAgg, supportsGeoTileAgg,
} from '../../../index_pattern_util'; } from '../../../index_pattern_util';
import { SORT_ORDER } from '../../../../common/constants'; import { SortDirection, indexPatterns } from '../../../../../../../src/plugins/data/public';
import { ESDocField } from '../../fields/es_doc_field'; import { ESDocField } from '../../fields/es_doc_field';
import { FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { indexPatterns } from '../../../../../../../src/plugins/data/public';
import { ScalingForm } from './scaling_form'; import { ScalingForm } from './scaling_form';
export class UpdateSourceEditor extends Component { export class UpdateSourceEditor extends Component {
@ -183,13 +183,13 @@ export class UpdateSourceEditor extends Component {
text: i18n.translate('xpack.maps.source.esSearch.ascendingLabel', { text: i18n.translate('xpack.maps.source.esSearch.ascendingLabel', {
defaultMessage: 'ascending', defaultMessage: 'ascending',
}), }),
value: SORT_ORDER.ASC, value: SortDirection.asc,
}, },
{ {
text: i18n.translate('xpack.maps.source.esSearch.descendingLabel', { text: i18n.translate('xpack.maps.source.esSearch.descendingLabel', {
defaultMessage: 'descending', defaultMessage: 'descending',
}), }),
value: SORT_ORDER.DESC, value: SortDirection.desc,
}, },
]} ]}
value={this.props.sortOrder} value={this.props.sortOrder}

View file

@ -1,86 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { AbstractVectorSource } from '../vector_source';
import { IVectorSource } from '../vector_source';
import { TimeRange } from '../../../../../../../src/plugins/data/common';
import { IndexPattern, ISearchSource } from '../../../../../../../src/plugins/data/public';
import {
DynamicStylePropertyOptions,
MapQuery,
VectorSourceRequestMeta,
} from '../../../../common/descriptor_types';
import { IVectorStyle } from '../../styles/vector/vector_style';
import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property';
export interface IESSource extends IVectorSource {
getId(): string;
getIndexPattern(): Promise<IndexPattern>;
getIndexPatternId(): string;
getGeoFieldName(): string;
getMaxResultWindow(): Promise<number>;
makeSearchSource(
searchFilters: VectorSourceRequestMeta,
limit: number,
initialSearchContext?: object
): Promise<ISearchSource>;
loadStylePropsMeta({
layerName,
style,
dynamicStyleProps,
registerCancelCallback,
sourceQuery,
timeFilters,
}: {
layerName: string;
style: IVectorStyle;
dynamicStyleProps: Array<IDynamicStyleProperty<DynamicStylePropertyOptions>>;
registerCancelCallback: (callback: () => void) => void;
sourceQuery?: MapQuery;
timeFilters: TimeRange;
}): Promise<object>;
}
export class AbstractESSource extends AbstractVectorSource implements IESSource {
getId(): string;
getIndexPattern(): Promise<IndexPattern>;
getIndexPatternId(): string;
getGeoFieldName(): string;
getMaxResultWindow(): Promise<number>;
makeSearchSource(
searchFilters: VectorSourceRequestMeta,
limit: number,
initialSearchContext?: object
): Promise<ISearchSource>;
loadStylePropsMeta({
layerName,
style,
dynamicStyleProps,
registerCancelCallback,
sourceQuery,
timeFilters,
}: {
layerName: string;
style: IVectorStyle;
dynamicStyleProps: Array<IDynamicStyleProperty<DynamicStylePropertyOptions>>;
registerCancelCallback: (callback: () => void) => void;
sourceQuery?: MapQuery;
timeFilters: TimeRange;
}): Promise<object>;
_runEsQuery: ({
requestId,
requestName,
requestDescription,
searchSource,
registerCancelCallback,
}: {
requestId: string;
requestName: string;
requestDescription: string;
searchSource: ISearchSource;
registerCancelCallback: () => void;
}) => Promise<unknown>;
}

View file

@ -4,7 +4,10 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { AbstractVectorSource } from '../vector_source'; import { i18n } from '@kbn/i18n';
import uuid from 'uuid/v4';
import { Filter, IFieldType, IndexPattern, ISearchSource } from 'src/plugins/data/public';
import { AbstractVectorSource, BoundsFilters } from '../vector_source';
import { import {
getAutocompleteService, getAutocompleteService,
getIndexPatternService, getIndexPatternService,
@ -12,62 +15,122 @@ import {
getSearchService, getSearchService,
} from '../../../kibana_services'; } from '../../../kibana_services';
import { createExtentFilter } from '../../../../common/elasticsearch_util'; import { createExtentFilter } from '../../../../common/elasticsearch_util';
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import uuid from 'uuid/v4';
import { copyPersistentState } from '../../../reducers/util'; import { copyPersistentState } from '../../../reducers/util';
import { DataRequestAbortError } from '../../util/data_request'; import { DataRequestAbortError } from '../../util/data_request';
import { expandToTileBoundaries } from '../../../../common/geo_tile_utils'; import { expandToTileBoundaries } from '../../../../common/geo_tile_utils';
import { search } from '../../../../../../../src/plugins/data/public'; import { search } from '../../../../../../../src/plugins/data/public';
import { IVectorSource } from '../vector_source';
import { TimeRange } from '../../../../../../../src/plugins/data/common';
import {
AbstractESSourceDescriptor,
AbstractSourceDescriptor,
DynamicStylePropertyOptions,
MapExtent,
MapQuery,
VectorJoinSourceRequestMeta,
VectorSourceRequestMeta,
} from '../../../../common/descriptor_types';
import { IVectorStyle } from '../../styles/vector/vector_style';
import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property';
import { IField } from '../../fields/field';
import { ES_GEO_FIELD_TYPE, FieldFormatter } from '../../../../common/constants';
import {
Adapters,
RequestResponder,
} from '../../../../../../../src/plugins/inspector/common/adapters';
import { isValidStringConfig } from '../../util/valid_string_config';
export class AbstractESSource extends AbstractVectorSource { export interface IESSource extends IVectorSource {
constructor(descriptor, inspectorAdapters) { isESSource(): true;
super( getId(): string;
{ getIndexPattern(): Promise<IndexPattern>;
...descriptor, getIndexPatternId(): string;
applyGlobalQuery: _.get(descriptor, 'applyGlobalQuery', true), getGeoFieldName(): string;
}, loadStylePropsMeta({
inspectorAdapters layerName,
); style,
dynamicStyleProps,
registerCancelCallback,
sourceQuery,
timeFilters,
}: {
layerName: string;
style: IVectorStyle;
dynamicStyleProps: Array<IDynamicStyleProperty<DynamicStylePropertyOptions>>;
registerCancelCallback: (callback: () => void) => void;
sourceQuery?: MapQuery;
timeFilters: TimeRange;
}): Promise<object>;
}
export class AbstractESSource extends AbstractVectorSource implements IESSource {
indexPattern?: IndexPattern;
readonly _descriptor: AbstractESSourceDescriptor;
static createDescriptor(
descriptor: Partial<AbstractESSourceDescriptor>
): AbstractESSourceDescriptor {
if (!isValidStringConfig(descriptor.indexPatternId)) {
throw new Error(
'Cannot create AbstractESSourceDescriptor when indexPatternId is not provided'
);
}
return {
...descriptor,
id: isValidStringConfig(descriptor.id) ? descriptor.id! : uuid(),
type: isValidStringConfig(descriptor.type) ? descriptor.type! : '',
indexPatternId: descriptor.indexPatternId!,
applyGlobalQuery:
// backfill old _.get usage
typeof descriptor.applyGlobalQuery !== 'undefined' ? !!descriptor.applyGlobalQuery : true,
};
} }
getId() { constructor(descriptor: AbstractESSourceDescriptor, inspectorAdapters?: Adapters) {
super(AbstractESSource.createDescriptor(descriptor), inspectorAdapters);
this._descriptor = descriptor;
}
getId(): string {
return this._descriptor.id; return this._descriptor.id;
} }
isFieldAware() { isFieldAware(): boolean {
return true; return true;
} }
isRefreshTimerAware() { isRefreshTimerAware(): boolean {
return true; return true;
} }
isQueryAware() { isQueryAware(): boolean {
return true; return true;
} }
getIndexPatternIds() { getIndexPatternIds(): string[] {
return [this.getIndexPatternId()]; return [this.getIndexPatternId()];
} }
getQueryableIndexPatternIds() { getQueryableIndexPatternIds(): string[] {
if (this.getApplyGlobalQuery()) { if (this.getApplyGlobalQuery()) {
return [this.getIndexPatternId()]; return [this.getIndexPatternId()];
} }
return []; return [];
} }
isESSource() { isESSource(): true {
return true; return true;
} }
destroy() { destroy() {
this._inspectorAdapters.requests.resetRequest(this.getId()); const inspectorAdapters = this.getInspectorAdapters();
if (inspectorAdapters) {
inspectorAdapters.requests.resetRequest(this.getId());
}
} }
cloneDescriptor() { cloneDescriptor(): AbstractSourceDescriptor {
const clonedDescriptor = copyPersistentState(this._descriptor); const clonedDescriptor = copyPersistentState(this._descriptor);
// id used as uuid to track requests in inspector // id used as uuid to track requests in inspector
clonedDescriptor.id = uuid(); clonedDescriptor.id = uuid();
@ -80,26 +143,45 @@ export class AbstractESSource extends AbstractVectorSource {
requestDescription, requestDescription,
searchSource, searchSource,
registerCancelCallback, registerCancelCallback,
}) { }: {
requestId: string;
requestName: string;
requestDescription: string;
searchSource: ISearchSource;
registerCancelCallback: (callback: () => void) => void;
}): Promise<any> {
const abortController = new AbortController(); const abortController = new AbortController();
registerCancelCallback(() => abortController.abort()); registerCancelCallback(() => abortController.abort());
const inspectorRequest = this._inspectorAdapters.requests.start(requestName, { const inspectorAdapters = this.getInspectorAdapters();
id: requestId, let inspectorRequest: RequestResponder | undefined;
description: requestDescription, if (inspectorAdapters) {
}); inspectorRequest = inspectorAdapters.requests.start(requestName, {
id: requestId,
description: requestDescription,
});
}
let resp; let resp;
try { try {
inspectorRequest.stats(search.getRequestInspectorStats(searchSource)); if (inspectorRequest) {
searchSource.getSearchRequestBody().then((body) => { const requestStats = search.getRequestInspectorStats(searchSource);
inspectorRequest.json(body); inspectorRequest.stats(requestStats);
}); searchSource.getSearchRequestBody().then((body) => {
if (inspectorRequest) {
inspectorRequest.json(body);
}
});
}
resp = await searchSource.fetch({ abortSignal: abortController.signal }); resp = await searchSource.fetch({ abortSignal: abortController.signal });
inspectorRequest if (inspectorRequest) {
.stats(search.getResponseInspectorStats(resp, searchSource)) const responseStats = search.getResponseInspectorStats(resp, searchSource);
.ok({ json: resp }); inspectorRequest.stats(responseStats).ok({ json: resp });
}
} catch (error) { } catch (error) {
inspectorRequest.error({ error }); if (inspectorRequest) {
inspectorRequest.error(error);
}
if (error.name === 'AbortError') { if (error.name === 'AbortError') {
throw new DataRequestAbortError(); throw new DataRequestAbortError();
} }
@ -115,22 +197,40 @@ export class AbstractESSource extends AbstractVectorSource {
return resp; return resp;
} }
async makeSearchSource(searchFilters, limit, initialSearchContext) { async makeSearchSource(
searchFilters: VectorSourceRequestMeta | VectorJoinSourceRequestMeta | BoundsFilters,
limit: number,
initialSearchContext?: object
): Promise<ISearchSource> {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
const isTimeAware = await this.isTimeAware(); const isTimeAware = await this.isTimeAware();
const applyGlobalQuery = _.get(searchFilters, 'applyGlobalQuery', true); const applyGlobalQuery =
const globalFilters = applyGlobalQuery ? searchFilters.filters : []; typeof searchFilters.applyGlobalQuery === 'boolean' ? searchFilters.applyGlobalQuery : true;
const allFilters = [...globalFilters]; const globalFilters: Filter[] = applyGlobalQuery ? searchFilters.filters : [];
if (this.isFilterByMapBounds() && searchFilters.buffer) { const allFilters: Filter[] = [...globalFilters];
//buffer can be empty if (this.isFilterByMapBounds() && 'buffer' in searchFilters && searchFilters.buffer) {
// buffer can be empty
const geoField = await this._getGeoField(); const geoField = await this._getGeoField();
const buffer = this.isGeoGridPrecisionAware() const buffer: MapExtent =
? expandToTileBoundaries(searchFilters.buffer, searchFilters.geogridPrecision) this.isGeoGridPrecisionAware() &&
: searchFilters.buffer; 'geogridPrecision' in searchFilters &&
allFilters.push(createExtentFilter(buffer, geoField.name, geoField.type)); typeof searchFilters.geogridPrecision === 'number'
? expandToTileBoundaries(searchFilters.buffer, searchFilters.geogridPrecision)
: searchFilters.buffer;
const extentFilter = createExtentFilter(
buffer,
geoField.name,
geoField.type as ES_GEO_FIELD_TYPE
);
// @ts-expect-error
allFilters.push(extentFilter);
} }
if (isTimeAware) { if (isTimeAware) {
allFilters.push(getTimeFilter().createFilter(indexPattern, searchFilters.timeFilters)); const filter = getTimeFilter().createFilter(indexPattern, searchFilters.timeFilters);
if (filter) {
allFilters.push(filter);
}
} }
const searchService = getSearchService(); const searchService = getSearchService();
const searchSource = await searchService.searchSource.create(initialSearchContext); const searchSource = await searchService.searchSource.create(initialSearchContext);
@ -153,7 +253,10 @@ export class AbstractESSource extends AbstractVectorSource {
return searchSource; return searchSource;
} }
async getBoundsForFilters(boundsFilters, registerCancelCallback) { async getBoundsForFilters(
boundsFilters: BoundsFilters,
registerCancelCallback: (callback: () => void) => void
): Promise<MapExtent | null> {
const searchSource = await this.makeSearchSource(boundsFilters, 0); const searchSource = await this.makeSearchSource(boundsFilters, 0);
searchSource.setField('aggs', { searchSource.setField('aggs', {
fitToBounds: { fitToBounds: {
@ -184,14 +287,14 @@ export class AbstractESSource extends AbstractVectorSource {
const minLon = esBounds.top_left.lon; const minLon = esBounds.top_left.lon;
const maxLon = esBounds.bottom_right.lon; const maxLon = esBounds.bottom_right.lon;
return { return {
minLon: minLon > maxLon ? minLon - 360 : minLon, //fixes an ES bbox to straddle dateline minLon: minLon > maxLon ? minLon - 360 : minLon, // fixes an ES bbox to straddle dateline
maxLon, maxLon,
minLat: esBounds.bottom_right.lat, minLat: esBounds.bottom_right.lat,
maxLat: esBounds.top_left.lat, maxLat: esBounds.top_left.lat,
}; };
} }
async isTimeAware() { async isTimeAware(): Promise<boolean> {
try { try {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
const timeField = indexPattern.timeFieldName; const timeField = indexPattern.timeFieldName;
@ -201,15 +304,19 @@ export class AbstractESSource extends AbstractVectorSource {
} }
} }
getIndexPatternId() { getIndexPatternId(): string {
return this._descriptor.indexPatternId; return this._descriptor.indexPatternId;
} }
getGeoFieldName() { getGeoFieldName(): string {
if (!this._descriptor.geoField) {
throw new Error('Should not call');
}
return this._descriptor.geoField; return this._descriptor.geoField;
} }
async getIndexPattern() { async getIndexPattern(): Promise<IndexPattern> {
// Do we need this cache? Doesn't the IndexPatternService take care of this?
if (this.indexPattern) { if (this.indexPattern) {
return this.indexPattern; return this.indexPattern;
} }
@ -227,16 +334,16 @@ export class AbstractESSource extends AbstractVectorSource {
} }
} }
async supportsFitToBounds() { async supportsFitToBounds(): Promise<boolean> {
try { try {
const geoField = await this._getGeoField(); const geoField = await this._getGeoField();
return geoField.aggregatable; return !!geoField.aggregatable;
} catch (error) { } catch (error) {
return false; return false;
} }
} }
async _getGeoField() { async _getGeoField(): Promise<IFieldType> {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
const geoField = indexPattern.fields.getByName(this.getGeoFieldName()); const geoField = indexPattern.fields.getByName(this.getGeoFieldName());
if (!geoField) { if (!geoField) {
@ -250,7 +357,7 @@ export class AbstractESSource extends AbstractVectorSource {
return geoField; return geoField;
} }
async getDisplayName() { async getDisplayName(): Promise<string> {
try { try {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
return indexPattern.title; return indexPattern.title;
@ -260,15 +367,11 @@ export class AbstractESSource extends AbstractVectorSource {
} }
} }
isBoundsAware() { isBoundsAware(): boolean {
return true; return true;
} }
getId() { async createFieldFormatter(field: IField): Promise<FieldFormatter | null> {
return this._descriptor.id;
}
async createFieldFormatter(field) {
let indexPattern; let indexPattern;
try { try {
indexPattern = await this.getIndexPattern(); indexPattern = await this.getIndexPattern();
@ -291,15 +394,25 @@ export class AbstractESSource extends AbstractVectorSource {
registerCancelCallback, registerCancelCallback,
sourceQuery, sourceQuery,
timeFilters, timeFilters,
}) { }: {
layerName: string;
style: IVectorStyle;
dynamicStyleProps: Array<IDynamicStyleProperty<DynamicStylePropertyOptions>>;
registerCancelCallback: (callback: () => void) => void;
sourceQuery?: MapQuery;
timeFilters: TimeRange;
}): Promise<object> {
const promises = dynamicStyleProps.map((dynamicStyleProp) => { const promises = dynamicStyleProps.map((dynamicStyleProp) => {
return dynamicStyleProp.getFieldMetaRequest(); return dynamicStyleProp.getFieldMetaRequest();
}); });
const fieldAggRequests = await Promise.all(promises); const fieldAggRequests = await Promise.all(promises);
const aggs = fieldAggRequests.reduce((aggs, fieldAggRequest) => { const allAggs: Record<string, any> = fieldAggRequests.reduce(
return fieldAggRequest ? { ...aggs, ...fieldAggRequest } : aggs; (aggs: Record<string, any>, fieldAggRequest: unknown | null) => {
}, {}); return fieldAggRequest ? { ...aggs, ...(fieldAggRequest as Record<string, any>) } : aggs;
},
{}
);
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
const searchService = getSearchService(); const searchService = getSearchService();
@ -307,12 +420,15 @@ export class AbstractESSource extends AbstractVectorSource {
searchSource.setField('index', indexPattern); searchSource.setField('index', indexPattern);
searchSource.setField('size', 0); searchSource.setField('size', 0);
searchSource.setField('aggs', aggs); searchSource.setField('aggs', allAggs);
if (sourceQuery) { if (sourceQuery) {
searchSource.setField('query', sourceQuery); searchSource.setField('query', sourceQuery);
} }
if (style.isTimeAware() && (await this.isTimeAware())) { if (style.isTimeAware() && (await this.isTimeAware())) {
searchSource.setField('filter', [getTimeFilter().createFilter(indexPattern, timeFilters)]); const timeFilter = getTimeFilter().createFilter(indexPattern, timeFilters);
if (timeFilter) {
searchSource.setField('filter', [timeFilter]);
}
} }
const resp = await this._runEsQuery({ const resp = await this._runEsQuery({
@ -335,15 +451,17 @@ export class AbstractESSource extends AbstractVectorSource {
return resp.aggregations; return resp.aggregations;
} }
getValueSuggestions = async (field, query) => { getValueSuggestions = async (field: IField, query: string): Promise<string[]> => {
try { try {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
const indexPatternField = indexPattern.fields.getByName(field.getRootName())!;
return await getAutocompleteService().getValueSuggestions({ return await getAutocompleteService().getValueSuggestions({
indexPattern, indexPattern,
field: indexPattern.fields.getByName(field.getRootName()), field: indexPatternField,
query, query,
}); });
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console
console.warn( console.warn(
`Unable to fetch suggestions for field: ${field.getRootName()}, query: ${query}, error: ${ `Unable to fetch suggestions for field: ${field.getRootName()}, query: ${query}, error: ${
error.message error.message

View file

@ -1,22 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { MapQuery, VectorJoinSourceRequestMeta } from '../../../../common/descriptor_types';
import { IField } from '../../fields/field';
import { IESAggSource } from '../es_agg_source';
import { PropertiesMap } from '../../joins/join';
export interface IESTermSource extends IESAggSource {
getTermField: () => IField;
hasCompleteConfig: () => boolean;
getWhereQuery: () => MapQuery;
getPropertiesMap: (
searchFilters: VectorJoinSourceRequestMeta,
leftSourceName: string,
leftFieldName: string,
registerCancelCallback: (callback: () => void) => void
) => PropertiesMap;
}

View file

@ -34,6 +34,7 @@ describe('getMetricFields', () => {
id: '1234', id: '1234',
indexPatternTitle: indexPatternTitle, indexPatternTitle: indexPatternTitle,
term: termFieldName, term: termFieldName,
indexPatternId: 'foobar',
}); });
const metrics = source.getMetricFields(); const metrics = source.getMetricFields();
expect(metrics[0].getName()).toEqual('__kbnjoin__count__1234'); expect(metrics[0].getName()).toEqual('__kbnjoin__count__1234');
@ -46,6 +47,7 @@ describe('getMetricFields', () => {
indexPatternTitle: indexPatternTitle, indexPatternTitle: indexPatternTitle,
term: termFieldName, term: termFieldName,
metrics: metricExamples, metrics: metricExamples,
indexPatternId: 'foobar',
}); });
const metrics = source.getMetricFields(); const metrics = source.getMetricFields();
expect(metrics[0].getName()).toEqual('__kbnjoin__sum_of_myFieldGettingSummed__1234'); expect(metrics[0].getName()).toEqual('__kbnjoin__sum_of_myFieldGettingSummed__1234');

View file

@ -5,8 +5,8 @@
*/ */
import _ from 'lodash'; import _ from 'lodash';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { ISearchSource, Query } from 'src/plugins/data/public';
import { import {
AGG_TYPE, AGG_TYPE,
DEFAULT_MAX_BUCKETS_LIMIT, DEFAULT_MAX_BUCKETS_LIMIT,
@ -20,15 +20,22 @@ import {
getField, getField,
addFieldToDSL, addFieldToDSL,
extractPropertiesFromBucket, extractPropertiesFromBucket,
BucketProperties,
} from '../../../../common/elasticsearch_util'; } from '../../../../common/elasticsearch_util';
import {
ESTermSourceDescriptor,
VectorJoinSourceRequestMeta,
} from '../../../../common/descriptor_types';
import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters';
import { PropertiesMap } from '../../../../common/elasticsearch_util';
const TERMS_AGG_NAME = 'join'; const TERMS_AGG_NAME = 'join';
const TERMS_BUCKET_KEYS_TO_IGNORE = ['key', 'doc_count']; const TERMS_BUCKET_KEYS_TO_IGNORE = ['key', 'doc_count'];
export function extractPropertiesMap(rawEsData, countPropertyName) { export function extractPropertiesMap(rawEsData: any, countPropertyName: string): PropertiesMap {
const propertiesMap = new Map(); const propertiesMap: PropertiesMap = new Map<string, BucketProperties>();
_.get(rawEsData, ['aggregations', TERMS_AGG_NAME, 'buckets'], []).forEach((termBucket) => { const buckets: any[] = _.get(rawEsData, ['aggregations', TERMS_AGG_NAME, 'buckets'], []);
buckets.forEach((termBucket: any) => {
const properties = extractPropertiesFromBucket(termBucket, TERMS_BUCKET_KEYS_TO_IGNORE); const properties = extractPropertiesFromBucket(termBucket, TERMS_BUCKET_KEYS_TO_IGNORE);
if (countPropertyName) { if (countPropertyName) {
properties[countPropertyName] = termBucket.doc_count; properties[countPropertyName] = termBucket.doc_count;
@ -41,37 +48,36 @@ export function extractPropertiesMap(rawEsData, countPropertyName) {
export class ESTermSource extends AbstractESAggSource { export class ESTermSource extends AbstractESAggSource {
static type = SOURCE_TYPES.ES_TERM_SOURCE; static type = SOURCE_TYPES.ES_TERM_SOURCE;
constructor(descriptor, inspectorAdapters) { private readonly _termField: ESDocField;
super(descriptor, inspectorAdapters); readonly _descriptor: ESTermSourceDescriptor;
constructor(descriptor: ESTermSourceDescriptor, inspectorAdapters: Adapters) {
super(AbstractESAggSource.createDescriptor(descriptor), inspectorAdapters);
this._descriptor = descriptor;
this._termField = new ESDocField({ this._termField = new ESDocField({
fieldName: descriptor.term, fieldName: this._descriptor.term,
source: this, source: this,
origin: this.getOriginForField(), origin: this.getOriginForField(),
}); });
} }
static renderEditor({}) {
//no need to localize. this editor is never rendered.
return `<div>editor details</div>`;
}
hasCompleteConfig() { hasCompleteConfig() {
return _.has(this._descriptor, 'indexPatternId') && _.has(this._descriptor, 'term'); return _.has(this._descriptor, 'indexPatternId') && _.has(this._descriptor, 'term');
} }
getTermField() { getTermField(): ESDocField {
return this._termField; return this._termField;
} }
getOriginForField() { getOriginForField(): FIELD_ORIGIN {
return FIELD_ORIGIN.JOIN; return FIELD_ORIGIN.JOIN;
} }
getWhereQuery() { getWhereQuery(): Query | undefined {
return this._descriptor.whereQuery; return this._descriptor.whereQuery;
} }
getAggKey(aggType, fieldName) { getAggKey(aggType: AGG_TYPE, fieldName?: string): string {
return getJoinAggKey({ return getJoinAggKey({
aggType, aggType,
aggFieldName: fieldName, aggFieldName: fieldName,
@ -79,7 +85,7 @@ export class ESTermSource extends AbstractESAggSource {
}); });
} }
getAggLabel(aggType, fieldName) { getAggLabel(aggType: AGG_TYPE, fieldName: string) {
return aggType === AGG_TYPE.COUNT return aggType === AGG_TYPE.COUNT
? i18n.translate('xpack.maps.source.esJoin.countLabel', { ? i18n.translate('xpack.maps.source.esJoin.countLabel', {
defaultMessage: `Count of {indexPatternTitle}`, defaultMessage: `Count of {indexPatternTitle}`,
@ -88,13 +94,18 @@ export class ESTermSource extends AbstractESAggSource {
: super.getAggLabel(aggType, fieldName); : super.getAggLabel(aggType, fieldName);
} }
async getPropertiesMap(searchFilters, leftSourceName, leftFieldName, registerCancelCallback) { async getPropertiesMap(
searchFilters: VectorJoinSourceRequestMeta,
leftSourceName: string,
leftFieldName: string,
registerCancelCallback: (callback: () => void) => void
): Promise<PropertiesMap> {
if (!this.hasCompleteConfig()) { if (!this.hasCompleteConfig()) {
return []; return new Map<string, BucketProperties>();
} }
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
const searchSource = await this.makeSearchSource(searchFilters, 0); const searchSource: ISearchSource = await this.makeSearchSource(searchFilters, 0);
const termsField = getField(indexPattern, this._termField.getName()); const termsField = getField(indexPattern, this._termField.getName());
const termsAgg = { size: DEFAULT_MAX_BUCKETS_LIMIT }; const termsAgg = { size: DEFAULT_MAX_BUCKETS_LIMIT };
searchSource.setField('aggs', { searchSource.setField('aggs', {
@ -122,16 +133,16 @@ export class ESTermSource extends AbstractESAggSource {
return extractPropertiesMap(rawEsData, countPropertyName); return extractPropertiesMap(rawEsData, countPropertyName);
} }
isFilterByMapBounds() { isFilterByMapBounds(): boolean {
return false; return false;
} }
async getDisplayName() { async getDisplayName(): Promise<string> {
//no need to localize. this is never rendered. // no need to localize. this is never rendered.
return `es_table ${this.getIndexPatternId()}`; return `es_table ${this.getIndexPatternId()}`;
} }
getFieldNames() { getFieldNames(): string[] {
return this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName()); return this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName());
} }
} }

View file

@ -5,10 +5,11 @@
*/ */
import { Feature, FeatureCollection } from 'geojson'; import { Feature, FeatureCollection } from 'geojson';
import { AbstractVectorSource } from '../vector_source'; import { AbstractVectorSource, GeoJsonWithMeta } from '../vector_source';
import { EMPTY_FEATURE_COLLECTION, SOURCE_TYPES } from '../../../../common/constants'; import { EMPTY_FEATURE_COLLECTION, SOURCE_TYPES } from '../../../../common/constants';
import { GeojsonFileSourceDescriptor } from '../../../../common/descriptor_types'; import { GeojsonFileSourceDescriptor } from '../../../../common/descriptor_types';
import { registerSource } from '../source_registry'; import { registerSource } from '../source_registry';
import { IField } from '../../fields/field';
function getFeatureCollection(geoJson: Feature | FeatureCollection | null): FeatureCollection { function getFeatureCollection(geoJson: Feature | FeatureCollection | null): FeatureCollection {
if (!geoJson) { if (!geoJson) {
@ -30,26 +31,28 @@ function getFeatureCollection(geoJson: Feature | FeatureCollection | null): Feat
} }
export class GeojsonFileSource extends AbstractVectorSource { export class GeojsonFileSource extends AbstractVectorSource {
static type = SOURCE_TYPES.GEOJSON_FILE;
static createDescriptor( static createDescriptor(
geoJson: Feature | FeatureCollection | null, geoJson: Feature | FeatureCollection | null,
name: string name: string
): GeojsonFileSourceDescriptor { ): GeojsonFileSourceDescriptor {
return { return {
type: GeojsonFileSource.type, type: SOURCE_TYPES.GEOJSON_FILE,
__featureCollection: getFeatureCollection(geoJson), __featureCollection: getFeatureCollection(geoJson),
name, name,
}; };
} }
async getGeoJsonWithMeta() { async getGeoJsonWithMeta(): Promise<GeoJsonWithMeta> {
return { return {
data: (this._descriptor as GeojsonFileSourceDescriptor).__featureCollection, data: (this._descriptor as GeojsonFileSourceDescriptor).__featureCollection,
meta: {}, meta: {},
}; };
} }
createField({ fieldName }: { fieldName: string }): IField {
throw new Error('Not implemented');
}
async getDisplayName() { async getDisplayName() {
return (this._descriptor as GeojsonFileSourceDescriptor).name; return (this._descriptor as GeojsonFileSourceDescriptor).name;
} }

View file

@ -26,7 +26,7 @@ export const kibanaRegionMapLayerWizardConfig: LayerWizard = {
}), }),
icon: 'logoKibana', icon: 'logoKibana',
renderWizard: ({ previewLayers, mapColors }: RenderWizardArguments) => { renderWizard: ({ previewLayers, mapColors }: RenderWizardArguments) => {
const onSourceConfigChange = (sourceConfig: unknown) => { const onSourceConfigChange = (sourceConfig: { name: string }) => {
const sourceDescriptor = KibanaRegionmapSource.createDescriptor(sourceConfig); const sourceDescriptor = KibanaRegionmapSource.createDescriptor(sourceConfig);
const layerDescriptor = VectorLayer.createDescriptor({ sourceDescriptor }, mapColors); const layerDescriptor = VectorLayer.createDescriptor({ sourceDescriptor }, mapColors);
previewLayers([layerDescriptor]); previewLayers([layerDescriptor]);

View file

@ -1,15 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { AbstractVectorSource, IVectorSource } from '../vector_source';
export interface IKibanaRegionSource extends IVectorSource {
getVectorFileMeta(): Promise<unknown>;
}
export class KibanaRegionSource extends AbstractVectorSource implements IKibanaRegionSource {
getVectorFileMeta(): Promise<unknown>;
}

View file

@ -4,29 +4,38 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { AbstractVectorSource } from '../vector_source';
import { getKibanaRegionList } from '../../../meta';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { AbstractVectorSource, GeoJsonWithMeta } from '../vector_source';
import { getKibanaRegionList } from '../../../meta';
import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { FIELD_ORIGIN, SOURCE_TYPES } from '../../../../common/constants'; import { FIELD_ORIGIN, FORMAT_TYPE, SOURCE_TYPES } from '../../../../common/constants';
import { KibanaRegionField } from '../../fields/kibana_region_field'; import { KibanaRegionField } from '../../fields/kibana_region_field';
import { registerSource } from '../source_registry'; import { registerSource } from '../source_registry';
import { KibanaRegionmapSourceDescriptor } from '../../../../common/descriptor_types/source_descriptor_types';
import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters';
import { IField } from '../../fields/field';
import { LayerConfig } from '../../../../../../../src/plugins/region_map/config';
export const sourceTitle = i18n.translate('xpack.maps.source.kbnRegionMapTitle', { export const sourceTitle = i18n.translate('xpack.maps.source.kbnRegionMapTitle', {
defaultMessage: 'Configured GeoJSON', defaultMessage: 'Configured GeoJSON',
}); });
export class KibanaRegionmapSource extends AbstractVectorSource { export class KibanaRegionmapSource extends AbstractVectorSource {
static type = SOURCE_TYPES.REGIONMAP_FILE; readonly _descriptor: KibanaRegionmapSourceDescriptor;
static createDescriptor({ name }) { static createDescriptor({ name }: { name: string }): KibanaRegionmapSourceDescriptor {
return { return {
type: KibanaRegionmapSource.type, type: SOURCE_TYPES.REGIONMAP_FILE,
name: name, name,
}; };
} }
createField({ fieldName }) { constructor(descriptor: KibanaRegionmapSourceDescriptor, inspectorAdapters?: Adapters) {
super(descriptor, inspectorAdapters);
this._descriptor = descriptor;
}
createField({ fieldName }: { fieldName: string }): KibanaRegionField {
return new KibanaRegionField({ return new KibanaRegionField({
fieldName, fieldName,
source: this, source: this,
@ -49,10 +58,12 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
]; ];
} }
async getVectorFileMeta() { async getVectorFileMeta(): Promise<LayerConfig> {
const regionList = getKibanaRegionList(); const regionList: LayerConfig[] = getKibanaRegionList();
const meta = regionList.find((source) => source.name === this._descriptor.name); const layerConfig: LayerConfig | undefined = regionList.find(
if (!meta) { (regionConfig: LayerConfig) => regionConfig.name === this._descriptor.name
);
if (!layerConfig) {
throw new Error( throw new Error(
i18n.translate('xpack.maps.source.kbnRegionMap.noConfigErrorMessage', { i18n.translate('xpack.maps.source.kbnRegionMap.noConfigErrorMessage', {
defaultMessage: `Unable to find map.regionmap configuration for {name}`, defaultMessage: `Unable to find map.regionmap configuration for {name}`,
@ -62,13 +73,13 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
}) })
); );
} }
return meta; return layerConfig;
} }
async getGeoJsonWithMeta() { async getGeoJsonWithMeta(): Promise<GeoJsonWithMeta> {
const vectorFileMeta = await this.getVectorFileMeta(); const vectorFileMeta = await this.getVectorFileMeta();
const featureCollection = await AbstractVectorSource.getGeoJson({ const featureCollection = await AbstractVectorSource.getGeoJson({
format: vectorFileMeta.format.type, format: vectorFileMeta.format.type as FORMAT_TYPE,
featureCollectionPath: vectorFileMeta.meta.feature_collection_path, featureCollectionPath: vectorFileMeta.meta.feature_collection_path,
fetchUrl: vectorFileMeta.url, fetchUrl: vectorFileMeta.url,
}); });
@ -78,12 +89,16 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
}; };
} }
async getLeftJoinFields() { async getLeftJoinFields(): Promise<IField[]> {
const vectorFileMeta = await this.getVectorFileMeta(); const vectorFileMeta: LayerConfig = await this.getVectorFileMeta();
return vectorFileMeta.fields.map((f) => this.createField({ fieldName: f.name })); return vectorFileMeta.fields.map(
(field): KibanaRegionField => {
return this.createField({ fieldName: field.name });
}
);
} }
async getDisplayName() { async getDisplayName(): Promise<string> {
return this._descriptor.name; return this._descriptor.name;
} }

View file

@ -28,6 +28,7 @@ import {
import { MVTField } from '../../fields/mvt_field'; import { MVTField } from '../../fields/mvt_field';
import { UpdateSourceEditor } from './update_source_editor'; import { UpdateSourceEditor } from './update_source_editor';
import { ITooltipProperty, TooltipProperty } from '../../tooltips/tooltip_property'; import { ITooltipProperty, TooltipProperty } from '../../tooltips/tooltip_property';
import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters';
export const sourceTitle = i18n.translate( export const sourceTitle = i18n.translate(
'xpack.maps.source.MVTSingleLayerVectorSource.sourceTitle', 'xpack.maps.source.MVTSingleLayerVectorSource.sourceTitle',
@ -66,7 +67,7 @@ export class MVTSingleLayerVectorSource
constructor( constructor(
sourceDescriptor: TiledSingleLayerVectorSourceDescriptor, sourceDescriptor: TiledSingleLayerVectorSourceDescriptor,
inspectorAdapters?: object inspectorAdapters?: Adapters
) { ) {
super(sourceDescriptor, inspectorAdapters); super(sourceDescriptor, inspectorAdapters);
this._descriptor = MVTSingleLayerVectorSource.createDescriptor(sourceDescriptor); this._descriptor = MVTSingleLayerVectorSource.createDescriptor(sourceDescriptor);
@ -165,22 +166,22 @@ export class MVTSingleLayerVectorSource
return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON]; return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON];
} }
canFormatFeatureProperties() { canFormatFeatureProperties(): boolean {
return !!this._tooltipFields.length; return !!this._tooltipFields.length;
} }
getMinZoom() { getMinZoom(): number {
return this._descriptor.minSourceZoom; return this._descriptor.minSourceZoom;
} }
getMaxZoom() { getMaxZoom(): number {
return this._descriptor.maxSourceZoom; return this._descriptor.maxSourceZoom;
} }
getBoundsForFilters( async getBoundsForFilters(
boundsFilters: BoundsFilters, boundsFilters: BoundsFilters,
registerCancelCallback: (callback: () => void) => void registerCancelCallback: (callback: () => void) => void
): MapExtent | null { ): Promise<MapExtent | null> {
return null; return null;
} }

View file

@ -9,6 +9,7 @@
import { ReactElement } from 'react'; import { ReactElement } from 'react';
import { Adapters } from 'src/plugins/inspector/public'; import { Adapters } from 'src/plugins/inspector/public';
import { GeoJsonProperties } from 'geojson';
import { copyPersistentState } from '../../reducers/util'; import { copyPersistentState } from '../../reducers/util';
import { IField } from '../fields/field'; import { IField } from '../fields/field';
@ -62,7 +63,7 @@ export interface ISource {
getIndexPatternIds(): string[]; getIndexPatternIds(): string[];
getQueryableIndexPatternIds(): string[]; getQueryableIndexPatternIds(): string[];
getGeoGridPrecision(zoom: number): number; getGeoGridPrecision(zoom: number): number;
getPreIndexedShape(): Promise<PreIndexedShape | null>; getPreIndexedShape(properties: GeoJsonProperties): Promise<PreIndexedShape | null>;
createFieldFormatter(field: IField): Promise<FieldFormatter | null>; createFieldFormatter(field: IField): Promise<FieldFormatter | null>;
getValueSuggestions(field: IField, query: string): Promise<string[]>; getValueSuggestions(field: IField, query: string): Promise<string[]>;
getMinZoom(): number; getMinZoom(): number;
@ -72,7 +73,7 @@ export interface ISource {
export class AbstractSource implements ISource { export class AbstractSource implements ISource {
readonly _descriptor: AbstractSourceDescriptor; readonly _descriptor: AbstractSourceDescriptor;
readonly _inspectorAdapters?: Adapters | undefined; private readonly _inspectorAdapters?: Adapters;
constructor(descriptor: AbstractSourceDescriptor, inspectorAdapters?: Adapters) { constructor(descriptor: AbstractSourceDescriptor, inspectorAdapters?: Adapters) {
this._descriptor = descriptor; this._descriptor = descriptor;
@ -153,7 +154,7 @@ export class AbstractSource implements ISource {
return false; return false;
} }
getJoinsDisabledReason() { getJoinsDisabledReason(): string | null {
return null; return null;
} }
@ -162,7 +163,7 @@ export class AbstractSource implements ISource {
} }
// Returns geo_shape indexed_shape context for spatial quering by pre-indexed shapes // Returns geo_shape indexed_shape context for spatial quering by pre-indexed shapes
async getPreIndexedShape(/* properties */): Promise<PreIndexedShape | null> { async getPreIndexedShape(properties: GeoJsonProperties): Promise<PreIndexedShape | null> {
return null; return null;
} }
@ -183,11 +184,11 @@ export class AbstractSource implements ISource {
return false; return false;
} }
getMinZoom() { getMinZoom(): number {
return MIN_ZOOM; return MIN_ZOOM;
} }
getMaxZoom() { getMaxZoom(): number {
return MAX_ZOOM; return MAX_ZOOM;
} }

View file

@ -6,11 +6,12 @@
/* eslint-disable @typescript-eslint/consistent-type-definitions */ /* eslint-disable @typescript-eslint/consistent-type-definitions */
import { ISource } from './source'; import { ISource } from './source';
import { Adapters } from '../../../../../../src/plugins/inspector/common/adapters';
export type SourceRegistryEntry = { export type SourceRegistryEntry = {
ConstructorFunction: new ( ConstructorFunction: new (
sourceDescriptor: any, // this is the source-descriptor that corresponds specifically to the particular ISource instance sourceDescriptor: any, // this is the source-descriptor that corresponds specifically to the particular ISource instance
inspectorAdapters?: object inspectorAdapters?: Adapters
) => ISource; ) => ISource;
type: string; type: string;
}; };

View file

@ -1,108 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable @typescript-eslint/consistent-type-definitions */
import { FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
import { Filter, TimeRange } from 'src/plugins/data/public';
import { AbstractSource, ISource } from '../source';
import { IField } from '../../fields/field';
import {
ESSearchSourceResponseMeta,
MapExtent,
MapFilters,
MapQuery,
VectorSourceRequestMeta,
VectorSourceSyncMeta,
} from '../../../../common/descriptor_types';
import { VECTOR_SHAPE_TYPE } from '../../../../common/constants';
import { ITooltipProperty } from '../../tooltips/tooltip_property';
import { DataRequest } from '../../util/data_request';
export interface SourceTooltipConfig {
tooltipContent: string | null;
areResultsTrimmed: boolean;
}
export type GeoJsonFetchMeta = ESSearchSourceResponseMeta;
export type GeoJsonWithMeta = {
data: FeatureCollection;
meta?: GeoJsonFetchMeta;
};
export type BoundsFilters = {
applyGlobalQuery: boolean;
filters: Filter[];
query?: MapQuery;
sourceQuery?: MapQuery;
timeFilters: TimeRange;
};
export interface IVectorSource extends ISource {
getTooltipProperties(properties: GeoJsonProperties): Promise<ITooltipProperty[]>;
getBoundsForFilters(
boundsFilters: BoundsFilters,
registerCancelCallback: (callback: () => void) => void
): MapExtent | null;
getGeoJsonWithMeta(
layerName: string,
searchFilters: MapFilters,
registerCancelCallback: (callback: () => void) => void,
isRequestStillActive: () => boolean
): Promise<GeoJsonWithMeta>;
getFields(): Promise<IField[]>;
getFieldByName(fieldName: string): IField | null;
getLeftJoinFields(): Promise<IField[]>;
getSyncMeta(): VectorSourceSyncMeta;
getFieldNames(): string[];
getApplyGlobalQuery(): boolean;
createField({ fieldName }: { fieldName: string }): IField;
canFormatFeatureProperties(): boolean;
getSupportedShapeTypes(): Promise<VECTOR_SHAPE_TYPE[]>;
isBoundsAware(): boolean;
getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig;
}
export class AbstractVectorSource extends AbstractSource implements IVectorSource {
getTooltipProperties(properties: GeoJsonProperties): Promise<ITooltipProperty[]>;
getBoundsForFilters(
boundsFilters: BoundsFilters,
registerCancelCallback: (callback: () => void) => void
): MapExtent | null;
getGeoJsonWithMeta(
layerName: string,
searchFilters: VectorSourceRequestMeta,
registerCancelCallback: (callback: () => void) => void,
isRequestStillActive: () => boolean
): Promise<GeoJsonWithMeta>;
getFields(): Promise<IField[]>;
getFieldByName(fieldName: string): IField | null;
getLeftJoinFields(): Promise<IField[]>;
getSyncMeta(): VectorSourceSyncMeta;
getSupportedShapeTypes(): Promise<VECTOR_SHAPE_TYPE[]>;
canFormatFeatureProperties(): boolean;
getApplyGlobalQuery(): boolean;
getFieldNames(): string[];
createField({ fieldName }: { fieldName: string }): IField;
isBoundsAware(): boolean;
getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig;
}
export interface ITiledSingleLayerVectorSource extends IVectorSource {
getUrlTemplateWithMeta(
searchFilters: VectorSourceRequestMeta
): Promise<{
layerName: string;
urlTemplate: string;
minSourceZoom: number;
maxSourceZoom: number;
}>;
getMinZoom(): number;
getMaxZoom(): number;
getLayerName(): string;
}

View file

@ -1,140 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { TooltipProperty } from '../../tooltips/tooltip_property';
import { AbstractSource } from './../source';
import * as topojson from 'topojson-client';
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { VECTOR_SHAPE_TYPE } from '../../../../common/constants';
export class AbstractVectorSource extends AbstractSource {
static async getGeoJson({ format, featureCollectionPath, fetchUrl }) {
let fetchedJson;
try {
// TODO proxy map.regionmap url requests through kibana server and then use kfetch
// Can not use kfetch because fetchUrl may point to external URL. (map.regionmap)
const response = await fetch(fetchUrl);
if (!response.ok) {
throw new Error('Request failed');
}
fetchedJson = await response.json();
} catch (e) {
throw new Error(
i18n.translate('xpack.maps.source.vetorSource.requestFailedErrorMessage', {
defaultMessage: `Unable to fetch vector shapes from url: {fetchUrl}`,
values: { fetchUrl },
})
);
}
if (format === 'geojson') {
return fetchedJson;
}
if (format === 'topojson') {
const features = _.get(fetchedJson, `objects.${featureCollectionPath}`);
return topojson.feature(fetchedJson, features);
}
throw new Error(
i18n.translate('xpack.maps.source.vetorSource.formatErrorMessage', {
defaultMessage: `Unable to fetch vector shapes from url: {format}`,
values: { format },
})
);
}
/**
* factory function creating a new field-instance
* @param fieldName
* @param label
* @returns {IField}
*/
createField() {
throw new Error(`Should implemement ${this.constructor.type} ${this}`);
}
getFieldNames() {
return [];
}
/**
* Retrieves a field. This may be an existing instance.
* @param fieldName
* @param label
* @returns {IField}
*/
getFieldByName(name) {
return this.createField({ fieldName: name });
}
_getTooltipPropertyNames() {
return this._tooltipFields.map((field) => field.getName());
}
isFilterByMapBounds() {
return false;
}
isBoundsAware() {
return false;
}
async getBoundsForFilters() {
console.warn('Should implement AbstractVectorSource#getBoundsForFilters');
return null;
}
async getFields() {
return [];
}
async getLeftJoinFields() {
return [];
}
async getGeoJsonWithMeta() {
throw new Error('Should implement VectorSource#getGeoJson');
}
canFormatFeatureProperties() {
return false;
}
// Allow source to filter and format feature properties before displaying to user
async getTooltipProperties(properties) {
const tooltipProperties = [];
for (const key in properties) {
if (key.startsWith('__kbn')) {
//these are system properties and should be ignored
continue;
}
tooltipProperties.push(new TooltipProperty(key, key, properties[key]));
}
return tooltipProperties;
}
async isTimeAware() {
return false;
}
showJoinEditor() {
return true;
}
async getSupportedShapeTypes() {
return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON];
}
getSourceTooltipContent(/* sourceDataRequest */) {
return { tooltipContent: null, areResultsTrimmed: false };
}
getSyncMeta() {
return {};
}
}

View file

@ -0,0 +1,209 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
// @ts-expect-error
import * as topojson from 'topojson-client';
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { FeatureCollection, GeoJsonProperties } from 'geojson';
import { Filter, TimeRange } from 'src/plugins/data/public';
import { FORMAT_TYPE, VECTOR_SHAPE_TYPE } from '../../../../common/constants';
import { TooltipProperty, ITooltipProperty } from '../../tooltips/tooltip_property';
import { AbstractSource, ISource } from '../source';
import { IField } from '../../fields/field';
import {
ESSearchSourceResponseMeta,
MapExtent,
MapQuery,
VectorSourceRequestMeta,
VectorSourceSyncMeta,
} from '../../../../common/descriptor_types';
import { DataRequest } from '../../util/data_request';
export interface SourceTooltipConfig {
tooltipContent: string | null;
areResultsTrimmed: boolean;
}
export type GeoJsonFetchMeta = ESSearchSourceResponseMeta;
export interface GeoJsonWithMeta {
data: FeatureCollection;
meta?: GeoJsonFetchMeta;
}
export interface BoundsFilters {
applyGlobalQuery: boolean;
filters: Filter[];
query?: MapQuery;
sourceQuery?: MapQuery;
timeFilters: TimeRange;
}
export interface IVectorSource extends ISource {
getTooltipProperties(properties: GeoJsonProperties): Promise<ITooltipProperty[]>;
getBoundsForFilters(
boundsFilters: BoundsFilters,
registerCancelCallback: (callback: () => void) => void
): Promise<MapExtent | null>;
getGeoJsonWithMeta(
layerName: string,
searchFilters: VectorSourceRequestMeta,
registerCancelCallback: (callback: () => void) => void,
isRequestStillActive: () => boolean
): Promise<GeoJsonWithMeta>;
getFields(): Promise<IField[]>;
getFieldByName(fieldName: string): IField | null;
getLeftJoinFields(): Promise<IField[]>;
getSyncMeta(): VectorSourceSyncMeta | null;
getFieldNames(): string[];
getApplyGlobalQuery(): boolean;
createField({ fieldName }: { fieldName: string }): IField;
canFormatFeatureProperties(): boolean;
getSupportedShapeTypes(): Promise<VECTOR_SHAPE_TYPE[]>;
isBoundsAware(): boolean;
getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig;
}
export interface ITiledSingleLayerVectorSource extends IVectorSource {
getUrlTemplateWithMeta(
searchFilters: VectorSourceRequestMeta
): Promise<{
layerName: string;
urlTemplate: string;
minSourceZoom: number;
maxSourceZoom: number;
}>;
getMinZoom(): number;
getMaxZoom(): number;
getLayerName(): string;
}
export class AbstractVectorSource extends AbstractSource implements IVectorSource {
static async getGeoJson({
format,
featureCollectionPath,
fetchUrl,
}: {
format: FORMAT_TYPE;
featureCollectionPath: string;
fetchUrl: string;
}) {
let fetchedJson;
try {
const response = await fetch(fetchUrl);
if (!response.ok) {
throw new Error('Request failed');
}
fetchedJson = await response.json();
} catch (e) {
throw new Error(
i18n.translate('xpack.maps.source.vetorSource.requestFailedErrorMessage', {
defaultMessage: `Unable to fetch vector shapes from url: {fetchUrl}`,
values: { fetchUrl },
})
);
}
if (format === FORMAT_TYPE.GEOJSON) {
return fetchedJson;
}
if (format === FORMAT_TYPE.TOPOJSON) {
const features = _.get(fetchedJson, `objects.${featureCollectionPath}`);
return topojson.feature(fetchedJson, features);
}
throw new Error(
i18n.translate('xpack.maps.source.vetorSource.formatErrorMessage', {
defaultMessage: `Unable to fetch vector shapes from url: {format}`,
values: { format },
})
);
}
getFieldNames(): string[] {
return [];
}
createField({ fieldName }: { fieldName: string }): IField {
throw new Error('Not implemented');
}
getFieldByName(fieldName: string): IField | null {
return this.createField({ fieldName });
}
isFilterByMapBounds() {
return false;
}
isBoundsAware(): boolean {
return false;
}
async getBoundsForFilters(
boundsFilters: BoundsFilters,
registerCancelCallback: (callback: () => void) => void
): Promise<MapExtent | null> {
return null;
}
async getFields(): Promise<IField[]> {
return [];
}
async getLeftJoinFields(): Promise<IField[]> {
return [];
}
async getGeoJsonWithMeta(
layerName: string,
searchFilters: VectorSourceRequestMeta,
registerCancelCallback: (callback: () => void) => void,
isRequestStillActive: () => boolean
): Promise<GeoJsonWithMeta> {
throw new Error('Should implement VectorSource#getGeoJson');
}
canFormatFeatureProperties() {
return false;
}
// Allow source to filter and format feature properties before displaying to user
async getTooltipProperties(properties: GeoJsonProperties): Promise<ITooltipProperty[]> {
const tooltipProperties: ITooltipProperty[] = [];
for (const key in properties) {
if (key.startsWith('__kbn')) {
// these are system properties and should be ignored
continue;
}
tooltipProperties.push(new TooltipProperty(key, key, properties[key]));
}
return tooltipProperties;
}
async isTimeAware() {
return false;
}
showJoinEditor() {
return true;
}
async getSupportedShapeTypes() {
return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON];
}
getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig {
return { tooltipContent: null, areResultsTrimmed: false };
}
getSyncMeta(): VectorSourceSyncMeta | null {
return null;
}
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/**
* Validate user-generated data (e.g. descriptors). Possibly dirty or of wrong type.
* @param value
*/
export function isValidStringConfig(value: any): boolean {
return typeof value === 'string' && value !== '';
}

View file

@ -29,8 +29,9 @@ import {
getKibanaVersion, getKibanaVersion,
} from './kibana_services'; } from './kibana_services';
import { getLicenseId } from './licensed_features'; import { getLicenseId } from './licensed_features';
import { LayerConfig } from '../../../../src/plugins/region_map/config';
export function getKibanaRegionList(): unknown[] { export function getKibanaRegionList(): LayerConfig[] {
return getRegionmapLayers(); return getRegionmapLayers();
} }

View file

@ -34,6 +34,7 @@ import {
import { extractFeaturesFromFilters } from '../../common/elasticsearch_util'; import { extractFeaturesFromFilters } from '../../common/elasticsearch_util';
import { MapStoreState } from '../reducers/store'; import { MapStoreState } from '../reducers/store';
import { import {
AbstractSourceDescriptor,
DataRequestDescriptor, DataRequestDescriptor,
DrawState, DrawState,
Goto, Goto,
@ -94,7 +95,13 @@ export function createLayerInstance(
} }
} }
function createSourceInstance(sourceDescriptor: any, inspectorAdapters?: Adapters): ISource { function createSourceInstance(
sourceDescriptor: AbstractSourceDescriptor | null,
inspectorAdapters?: Adapters
): ISource {
if (sourceDescriptor === null) {
throw new Error('Source-descriptor should be initialized');
}
const source = getSourceByType(sourceDescriptor.type); const source = getSourceByType(sourceDescriptor.type);
if (!source) { if (!source) {
throw new Error(`Unrecognized sourceType ${sourceDescriptor.type}`); throw new Error(`Unrecognized sourceType ${sourceDescriptor.type}`);