[Maps] Convert ES-sources to typescript (#81951)
This commit is contained in:
parent
f4386fc5b0
commit
051ed13858
|
@ -19,28 +19,29 @@
|
|||
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
|
||||
export const configSchema = schema.object({
|
||||
includeElasticMapsService: schema.boolean({ defaultValue: true }),
|
||||
layers: schema.arrayOf(
|
||||
const layerConfigSchema = 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(),
|
||||
fields: schema.arrayOf(
|
||||
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(),
|
||||
fields: schema.arrayOf(
|
||||
schema.object({
|
||||
name: schema.string(),
|
||||
description: schema.string(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
{ defaultValue: [] }
|
||||
description: schema.string(),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
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>;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
export enum SORT_ORDER {
|
||||
ASC = 'asc',
|
||||
DESC = 'desc',
|
||||
}
|
||||
|
||||
export enum SOURCE_TYPES {
|
||||
EMS_TMS = 'EMS_TMS',
|
||||
EMS_FILE = 'EMS_FILE',
|
||||
|
@ -237,6 +232,11 @@ export enum SCALING_TYPES {
|
|||
MVT = 'MVT',
|
||||
}
|
||||
|
||||
export enum FORMAT_TYPE {
|
||||
GEOJSON = 'geojson',
|
||||
TOPOJSON = 'topojson',
|
||||
}
|
||||
|
||||
export enum MVT_FIELD_TYPE {
|
||||
STRING = 'String',
|
||||
NUMBER = 'Number',
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
*/
|
||||
/* 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 { Filter, TimeRange } from '../../../../../src/plugins/data/common';
|
||||
|
||||
|
@ -22,7 +24,7 @@ export type MapFilters = {
|
|||
|
||||
type ESSearchSourceSyncMeta = {
|
||||
sortField: string;
|
||||
sortOrder: SORT_ORDER;
|
||||
sortOrder: SortDirection;
|
||||
scalingType: SCALING_TYPES;
|
||||
topHitsSplitField: string;
|
||||
topHitsSize: number;
|
||||
|
@ -45,7 +47,7 @@ export type VectorSourceRequestMeta = MapFilters & {
|
|||
export type VectorJoinSourceRequestMeta = MapFilters & {
|
||||
applyGlobalQuery: boolean;
|
||||
fieldNames: string[];
|
||||
sourceQuery: MapQuery;
|
||||
sourceQuery?: Query;
|
||||
};
|
||||
|
||||
export type VectorStyleRequestMeta = MapFilters & {
|
||||
|
|
|
@ -7,14 +7,8 @@
|
|||
|
||||
import { FeatureCollection } from 'geojson';
|
||||
import { Query } from 'src/plugins/data/public';
|
||||
import {
|
||||
AGG_TYPE,
|
||||
GRID_RESOLUTION,
|
||||
RENDER_AS,
|
||||
SORT_ORDER,
|
||||
SCALING_TYPES,
|
||||
MVT_FIELD_TYPE,
|
||||
} from '../constants';
|
||||
import { SortDirection } from 'src/plugins/data/common/search';
|
||||
import { AGG_TYPE, GRID_RESOLUTION, RENDER_AS, SCALING_TYPES, MVT_FIELD_TYPE } from '../constants';
|
||||
|
||||
export type AttributionDescriptor = {
|
||||
attributionText?: string;
|
||||
|
@ -40,6 +34,7 @@ export type EMSFileSourceDescriptor = AbstractSourceDescriptor & {
|
|||
|
||||
export type AbstractESSourceDescriptor = AbstractSourceDescriptor & {
|
||||
// id: UUID
|
||||
id: string;
|
||||
indexPatternId: string;
|
||||
geoField?: string;
|
||||
};
|
||||
|
@ -55,18 +50,20 @@ export type AbstractESAggSourceDescriptor = AbstractESSourceDescriptor & {
|
|||
};
|
||||
|
||||
export type ESGeoGridSourceDescriptor = AbstractESAggSourceDescriptor & {
|
||||
requestType?: RENDER_AS;
|
||||
resolution?: GRID_RESOLUTION;
|
||||
geoField: string;
|
||||
requestType: RENDER_AS;
|
||||
resolution: GRID_RESOLUTION;
|
||||
};
|
||||
|
||||
export type ESSearchSourceDescriptor = AbstractESSourceDescriptor & {
|
||||
geoField: string;
|
||||
filterByMapBounds?: boolean;
|
||||
tooltipProperties?: string[];
|
||||
sortField?: string;
|
||||
sortOrder?: SORT_ORDER;
|
||||
sortField: string;
|
||||
sortOrder: SortDirection;
|
||||
scalingType: SCALING_TYPES;
|
||||
topHitsSplitField?: string;
|
||||
topHitsSize?: number;
|
||||
topHitsSplitField: string;
|
||||
topHitsSize: number;
|
||||
};
|
||||
|
||||
export type ESPewPewSourceDescriptor = AbstractESAggSourceDescriptor & {
|
||||
|
@ -76,7 +73,7 @@ export type ESPewPewSourceDescriptor = AbstractESAggSourceDescriptor & {
|
|||
|
||||
export type ESTermSourceDescriptor = AbstractESAggSourceDescriptor & {
|
||||
indexPatternTitle?: string;
|
||||
term?: string; // term field name
|
||||
term: string; // term field name
|
||||
whereQuery?: Query;
|
||||
};
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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 { ES_GEO_FIELD_TYPE } from '../constants';
|
||||
import { ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../constants';
|
||||
|
||||
export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent;
|
||||
|
||||
|
@ -23,3 +23,31 @@ export function hitsToGeoJson(
|
|||
geoFieldType: ES_GEO_FIELD_TYPE,
|
||||
epochMillisFields: string[]
|
||||
): 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;
|
||||
|
|
|
@ -8,7 +8,10 @@ import _ from 'lodash';
|
|||
import { IndexPattern, IFieldType } from '../../../../../src/plugins/data/common';
|
||||
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);
|
||||
if (!field) {
|
||||
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, ignoreKeys: string[] = []) {
|
||||
export function extractPropertiesFromBucket(
|
||||
bucket: any,
|
||||
ignoreKeys: string[] = []
|
||||
): BucketProperties {
|
||||
const properties: BucketProperties = {};
|
||||
for (const key in bucket) {
|
||||
if (ignoreKeys.includes(key) || !bucket.hasOwnProperty(key)) {
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
*/
|
||||
|
||||
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) {
|
||||
const sourceType = _.get(layerDescriptor, 'sourceDescriptor.type');
|
||||
|
@ -23,7 +24,7 @@ export function topHitsTimeToSort({ attributes }) {
|
|||
if (_.has(layerDescriptor, 'sourceDescriptor.topHitsTimeField')) {
|
||||
layerDescriptor.sourceDescriptor.sortField =
|
||||
layerDescriptor.sourceDescriptor.topHitsTimeField;
|
||||
layerDescriptor.sourceDescriptor.sortOrder = SORT_ORDER.DESC;
|
||||
layerDescriptor.sourceDescriptor.sortOrder = SortDirection.desc;
|
||||
delete layerDescriptor.sourceDescriptor.topHitsTimeField;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
*/
|
||||
|
||||
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 { IVectorSource } from '../sources/vector_source';
|
||||
|
||||
export class KibanaRegionField extends AbstractField implements IField {
|
||||
private readonly _source: IKibanaRegionSource;
|
||||
private readonly _source: KibanaRegionmapSource;
|
||||
|
||||
constructor({
|
||||
fieldName,
|
||||
|
@ -18,7 +18,7 @@ export class KibanaRegionField extends AbstractField implements IField {
|
|||
origin,
|
||||
}: {
|
||||
fieldName: string;
|
||||
source: IKibanaRegionSource;
|
||||
source: KibanaRegionmapSource;
|
||||
origin: FIELD_ORIGIN;
|
||||
}) {
|
||||
super({ fieldName, origin });
|
||||
|
|
|
@ -5,19 +5,20 @@
|
|||
*/
|
||||
|
||||
import { Feature, GeoJsonProperties } from 'geojson';
|
||||
import { IESTermSource } from '../sources/es_term_source';
|
||||
import { IJoin, PropertiesMap } from './join';
|
||||
import { ESTermSource } from '../sources/es_term_source';
|
||||
import { IJoin } from './join';
|
||||
import { JoinDescriptor } from '../../../common/descriptor_types';
|
||||
import { ISource } from '../sources/source';
|
||||
import { ITooltipProperty } from '../tooltips/tooltip_property';
|
||||
import { IField } from '../fields/field';
|
||||
import { PropertiesMap } from '../../../common/elasticsearch_util';
|
||||
|
||||
export class InnerJoin implements IJoin {
|
||||
constructor(joinDescriptor: JoinDescriptor, leftSource: ISource);
|
||||
|
||||
destroy: () => void;
|
||||
|
||||
getRightJoinSource(): IESTermSource;
|
||||
getRightJoinSource(): ESTermSource;
|
||||
|
||||
toDescriptor(): JoinDescriptor;
|
||||
|
||||
|
|
|
@ -5,18 +5,16 @@
|
|||
*/
|
||||
|
||||
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 { ITooltipProperty } from '../tooltips/tooltip_property';
|
||||
import { IField } from '../fields/field';
|
||||
import { BucketProperties } from '../../../common/elasticsearch_util';
|
||||
|
||||
export type PropertiesMap = Map<string, BucketProperties>;
|
||||
import { PropertiesMap } from '../../../common/elasticsearch_util';
|
||||
|
||||
export interface IJoin {
|
||||
destroy: () => void;
|
||||
|
||||
getRightJoinSource: () => IESTermSource;
|
||||
getRightJoinSource: () => ESTermSource;
|
||||
|
||||
toDescriptor: () => JoinDescriptor;
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ import { ESGeoGridSource } from '../../sources/es_geo_grid_source/es_geo_grid_so
|
|||
import { canSkipSourceUpdate } from '../../util/can_skip_fetch';
|
||||
import { IVectorLayer } from '../vector_layer/vector_layer';
|
||||
import { IESSource } from '../../sources/es_source';
|
||||
import { IESAggSource } from '../../sources/es_agg_source';
|
||||
import { ISource } from '../../sources/source';
|
||||
import { DataRequestContext } from '../../../actions';
|
||||
import { DataRequestAbortError } from '../../util/data_request';
|
||||
|
@ -36,9 +35,11 @@ import {
|
|||
StylePropertyOptions,
|
||||
LayerDescriptor,
|
||||
VectorLayerDescriptor,
|
||||
VectorSourceRequestMeta,
|
||||
} from '../../../../common/descriptor_types';
|
||||
import { IVectorSource } from '../../sources/vector_source';
|
||||
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';
|
||||
|
||||
|
@ -50,7 +51,7 @@ function getAggType(dynamicProperty: IDynamicStyleProperty<DynamicStylePropertyO
|
|||
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({
|
||||
indexPatternId: documentSource.getIndexPatternId(),
|
||||
geoField: documentSource.getGeoFieldName(),
|
||||
|
@ -75,7 +76,7 @@ function getClusterSource(documentSource: IESSource, documentStyle: IVectorStyle
|
|||
|
||||
function getClusterStyleDescriptor(
|
||||
documentStyle: IVectorStyle,
|
||||
clusterSource: IESAggSource
|
||||
clusterSource: ESGeoGridSource
|
||||
): VectorStyleDescriptor {
|
||||
const defaultDynamicProperties = getDefaultDynamicProperties();
|
||||
const clusterStyleDescriptor: VectorStyleDescriptor = {
|
||||
|
@ -177,9 +178,9 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {
|
|||
}
|
||||
|
||||
private readonly _isClustered: boolean;
|
||||
private readonly _clusterSource: IESAggSource;
|
||||
private readonly _clusterSource: ESGeoGridSource;
|
||||
private readonly _clusterStyle: IVectorStyle;
|
||||
private readonly _documentSource: IESSource;
|
||||
private readonly _documentSource: ESSearchSource;
|
||||
private readonly _documentStyle: IVectorStyle;
|
||||
|
||||
constructor(options: BlendedVectorLayerArguments) {
|
||||
|
@ -188,7 +189,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {
|
|||
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._clusterSource = getClusterSource(this._documentSource, this._documentStyle);
|
||||
|
@ -279,7 +280,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {
|
|||
async syncData(syncContext: DataRequestContext) {
|
||||
const dataRequestId = ACTIVE_COUNT_DATA_ID;
|
||||
const requestToken = Symbol(`layer-active-count:${this.getId()}`);
|
||||
const searchFilters = this._getSearchFilters(
|
||||
const searchFilters: VectorSourceRequestMeta = this._getSearchFilters(
|
||||
syncContext.dataFilters,
|
||||
this.getSource(),
|
||||
this.getCurrentStyle()
|
||||
|
|
|
@ -415,7 +415,7 @@ export class AbstractLayer implements ILayer {
|
|||
return this._descriptor.query ? this._descriptor.query : null;
|
||||
}
|
||||
|
||||
async getImmutableSourceProperties() {
|
||||
async getImmutableSourceProperties(): Promise<ImmutableSourceProperty[]> {
|
||||
const source = this.getSource();
|
||||
return await source.getImmutableProperties();
|
||||
}
|
||||
|
|
|
@ -175,6 +175,7 @@ describe('createLayerDescriptor', () => {
|
|||
query: 'processor.event:"transaction"',
|
||||
},
|
||||
sourceDescriptor: {
|
||||
applyGlobalQuery: true,
|
||||
geoField: 'client.geo.location',
|
||||
id: '12345',
|
||||
indexPatternId: 'apm_static_index_pattern_id',
|
||||
|
@ -216,6 +217,7 @@ describe('createLayerDescriptor', () => {
|
|||
query: 'processor.event:"transaction"',
|
||||
},
|
||||
sourceDescriptor: {
|
||||
applyGlobalQuery: true,
|
||||
geoField: 'client.geo.location',
|
||||
id: '12345',
|
||||
indexPatternId: 'apm_static_index_pattern_id',
|
||||
|
|
|
@ -21,7 +21,7 @@ jest.mock('uuid/v4', () => {
|
|||
import { createSecurityLayerDescriptors } from './create_layer_descriptors';
|
||||
|
||||
describe('createLayerDescriptor', () => {
|
||||
test('amp index', () => {
|
||||
test('apm index', () => {
|
||||
expect(createSecurityLayerDescriptors('id', 'apm-*-transaction*')).toEqual([
|
||||
{
|
||||
__dataRequests: [],
|
||||
|
@ -32,6 +32,7 @@ describe('createLayerDescriptor', () => {
|
|||
maxZoom: 24,
|
||||
minZoom: 0,
|
||||
sourceDescriptor: {
|
||||
applyGlobalQuery: true,
|
||||
filterByMapBounds: true,
|
||||
geoField: 'client.geo.location',
|
||||
id: '12345',
|
||||
|
@ -138,6 +139,7 @@ describe('createLayerDescriptor', () => {
|
|||
maxZoom: 24,
|
||||
minZoom: 0,
|
||||
sourceDescriptor: {
|
||||
applyGlobalQuery: true,
|
||||
filterByMapBounds: true,
|
||||
geoField: 'server.geo.location',
|
||||
id: '12345',
|
||||
|
@ -244,6 +246,7 @@ describe('createLayerDescriptor', () => {
|
|||
maxZoom: 24,
|
||||
minZoom: 0,
|
||||
sourceDescriptor: {
|
||||
applyGlobalQuery: true,
|
||||
destGeoField: 'server.geo.location',
|
||||
id: '12345',
|
||||
indexPatternId: 'id',
|
||||
|
@ -362,6 +365,7 @@ describe('createLayerDescriptor', () => {
|
|||
maxZoom: 24,
|
||||
minZoom: 0,
|
||||
sourceDescriptor: {
|
||||
applyGlobalQuery: true,
|
||||
filterByMapBounds: true,
|
||||
geoField: 'source.geo.location',
|
||||
id: '12345',
|
||||
|
@ -468,6 +472,7 @@ describe('createLayerDescriptor', () => {
|
|||
maxZoom: 24,
|
||||
minZoom: 0,
|
||||
sourceDescriptor: {
|
||||
applyGlobalQuery: true,
|
||||
filterByMapBounds: true,
|
||||
geoField: 'destination.geo.location',
|
||||
id: '12345',
|
||||
|
@ -574,6 +579,7 @@ describe('createLayerDescriptor', () => {
|
|||
maxZoom: 24,
|
||||
minZoom: 0,
|
||||
sourceDescriptor: {
|
||||
applyGlobalQuery: true,
|
||||
destGeoField: 'destination.geo.location',
|
||||
id: '12345',
|
||||
indexPatternId: 'id',
|
||||
|
|
|
@ -46,18 +46,20 @@ import {
|
|||
DynamicStylePropertyOptions,
|
||||
MapFilters,
|
||||
MapQuery,
|
||||
VectorJoinSourceRequestMeta,
|
||||
VectorLayerDescriptor,
|
||||
VectorSourceRequestMeta,
|
||||
VectorStyleRequestMeta,
|
||||
} from '../../../../common/descriptor_types';
|
||||
import { IVectorSource } from '../../sources/vector_source';
|
||||
import { CustomIconAndTooltipContent, ILayer } from '../layer';
|
||||
import { IJoin, PropertiesMap } from '../../joins/join';
|
||||
import { IJoin } from '../../joins/join';
|
||||
import { IField } from '../../fields/field';
|
||||
import { DataRequestContext } from '../../../actions';
|
||||
import { ITooltipProperty } from '../../tooltips/tooltip_property';
|
||||
import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property';
|
||||
import { IESSource } from '../../sources/es_source';
|
||||
import { PropertiesMap } from '../../../../common/elasticsearch_util';
|
||||
|
||||
interface SourceResult {
|
||||
refreshed: boolean;
|
||||
|
@ -239,7 +241,7 @@ export class VectorLayer extends AbstractLayer {
|
|||
}
|
||||
|
||||
const requestToken = Symbol(`${SOURCE_BOUNDS_DATA_REQUEST_ID}-${this.getId()}`);
|
||||
const searchFilters = this._getSearchFilters(
|
||||
const searchFilters: VectorSourceRequestMeta = this._getSearchFilters(
|
||||
dataFilters,
|
||||
this.getSource(),
|
||||
this.getCurrentStyle()
|
||||
|
@ -324,7 +326,7 @@ export class VectorLayer extends AbstractLayer {
|
|||
const joinSource = join.getRightJoinSource();
|
||||
const sourceDataId = join.getSourceDataRequestId();
|
||||
const requestToken = Symbol(`layer-join-refresh:${this.getId()} - ${sourceDataId}`);
|
||||
const searchFilters = {
|
||||
const searchFilters: VectorJoinSourceRequestMeta = {
|
||||
...dataFilters,
|
||||
fieldNames: joinSource.getFieldNames(),
|
||||
sourceQuery: joinSource.getWhereQuery(),
|
||||
|
@ -386,9 +388,11 @@ export class VectorLayer extends AbstractLayer {
|
|||
source: IVectorSource,
|
||||
style: IVectorStyle
|
||||
): VectorSourceRequestMeta {
|
||||
const styleFieldNames =
|
||||
style.getType() === LAYER_STYLE_TYPE.VECTOR ? style.getSourceFieldNames() : [];
|
||||
const fieldNames = [
|
||||
...source.getFieldNames(),
|
||||
...(style.getType() === LAYER_STYLE_TYPE.VECTOR ? style.getSourceFieldNames() : []),
|
||||
...styleFieldNames,
|
||||
...this.getValidJoins().map((join) => join.getLeftField().getName()),
|
||||
];
|
||||
|
||||
|
@ -464,7 +468,11 @@ export class VectorLayer extends AbstractLayer {
|
|||
} = syncContext;
|
||||
const dataRequestId = SOURCE_DATA_REQUEST_ID;
|
||||
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 canSkipFetch = await canSkipSourceUpdate({
|
||||
source,
|
||||
|
|
|
@ -11,7 +11,12 @@ import { Adapters } from 'src/plugins/inspector/public';
|
|||
import { FileLayer } from '@elastic/ems-client';
|
||||
import { Attribution, ImmutableSourceProperty, SourceEditorArgs } from '../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 { getDataSourceLabel } from '../../../../common/i18n_getters';
|
||||
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 {
|
||||
static type = SOURCE_TYPES.EMS_FILE;
|
||||
|
||||
static createDescriptor({ id, tooltipProperties = [] }: Partial<EMSFileSourceDescriptor>) {
|
||||
return {
|
||||
type: EMSFileSource.type,
|
||||
type: SOURCE_TYPES.EMS_FILE,
|
||||
id: id!,
|
||||
tooltipProperties,
|
||||
};
|
||||
|
@ -99,7 +102,7 @@ export class EMSFileSource extends AbstractVectorSource implements IEmsFileSourc
|
|||
const emsFileLayer = await this.getEMSFileLayer();
|
||||
// @ts-ignore
|
||||
const featureCollection = await AbstractVectorSource.getGeoJson({
|
||||
format: emsFileLayer.getDefaultFormatType(),
|
||||
format: emsFileLayer.getDefaultFormatType() as FORMAT_TYPE,
|
||||
featureCollectionPath: 'data',
|
||||
fetchUrl: emsFileLayer.getDefaultFormatUrl(),
|
||||
});
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { AbstractTMSSource } from '../tms_source';
|
||||
import { getEmsTmsServices } from '../../../meta';
|
||||
|
@ -20,25 +19,18 @@ export const sourceTitle = i18n.translate('xpack.maps.source.emsTileTitle', {
|
|||
});
|
||||
|
||||
export class EMSTMSSource extends AbstractTMSSource {
|
||||
static type = SOURCE_TYPES.EMS_TMS;
|
||||
|
||||
static createDescriptor(sourceConfig) {
|
||||
static createDescriptor(descriptor) {
|
||||
return {
|
||||
type: EMSTMSSource.type,
|
||||
id: sourceConfig.id,
|
||||
isAutoSelect: sourceConfig.isAutoSelect,
|
||||
type: SOURCE_TYPES.EMS_TMS,
|
||||
id: descriptor.id,
|
||||
isAutoSelect:
|
||||
typeof descriptor.isAutoSelect !== 'undefined' ? !!descriptor.isAutoSelect : false,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(descriptor, inspectorAdapters) {
|
||||
super(
|
||||
{
|
||||
id: descriptor.id,
|
||||
type: EMSTMSSource.type,
|
||||
isAutoSelect: _.get(descriptor, 'isAutoSelect', false),
|
||||
},
|
||||
inspectorAdapters
|
||||
);
|
||||
descriptor = EMSTMSSource.createDescriptor(descriptor);
|
||||
super(descriptor, inspectorAdapters);
|
||||
}
|
||||
|
||||
renderSourceSettingsEditor({ onChange }) {
|
||||
|
|
|
@ -33,9 +33,21 @@ export abstract class AbstractESAggSource extends AbstractESSource {
|
|||
private readonly _metricFields: IESAggField[];
|
||||
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(
|
||||
descriptor: AbstractESAggSourceDescriptor,
|
||||
inspectorAdapters: Adapters,
|
||||
inspectorAdapters?: Adapters,
|
||||
canReadFromGeoJson = true
|
||||
) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -113,7 +125,7 @@ export abstract class AbstractESAggSource extends AbstractESSource {
|
|||
}
|
||||
}
|
||||
|
||||
async getFields() {
|
||||
async getFields(): Promise<IField[]> {
|
||||
return this.getMetricFields();
|
||||
}
|
||||
|
||||
|
@ -128,7 +140,7 @@ export abstract class AbstractESAggSource extends AbstractESSource {
|
|||
return valueAggsDsl;
|
||||
}
|
||||
|
||||
async getTooltipProperties(properties: GeoJsonProperties) {
|
||||
async getTooltipProperties(properties: GeoJsonProperties): Promise<ITooltipProperty[]> {
|
||||
const metricFields = await this.getFields();
|
||||
const promises: Array<Promise<ITooltipProperty>> = [];
|
||||
metricFields.forEach((metricField) => {
|
||||
|
|
|
@ -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;
|
||||
}>;
|
||||
}
|
|
@ -4,37 +4,51 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import uuid from 'uuid/v4';
|
||||
import React, { ReactElement } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import rison from 'rison-node';
|
||||
import { Feature } from 'geojson';
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import {
|
||||
convertCompositeRespToGeoJson,
|
||||
convertRegularRespToGeoJson,
|
||||
makeESBbox,
|
||||
} from '../../../../common/elasticsearch_util';
|
||||
// @ts-expect-error
|
||||
import { UpdateSourceEditor } from './update_source_editor';
|
||||
import {
|
||||
SOURCE_TYPES,
|
||||
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,
|
||||
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';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
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 { registerSource } from '../source_registry';
|
||||
import { LICENSED_FEATURES } from '../../../licensed_features';
|
||||
|
||||
import rison from 'rison-node';
|
||||
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;
|
||||
|
||||
|
@ -46,31 +60,41 @@ export const heatmapTitle = i18n.translate('xpack.maps.source.esGridHeatmapTitle
|
|||
defaultMessage: 'Heat map',
|
||||
});
|
||||
|
||||
export class ESGeoGridSource extends AbstractESAggSource {
|
||||
static type = SOURCE_TYPES.ES_GEO_GRID;
|
||||
|
||||
static createDescriptor({ indexPatternId, geoField, metrics, requestType, resolution }) {
|
||||
export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingleLayerVectorSource {
|
||||
static createDescriptor(
|
||||
descriptor: Partial<ESGeoGridSourceDescriptor>
|
||||
): ESGeoGridSourceDescriptor {
|
||||
const normalizedDescriptor = AbstractESAggSource.createDescriptor(descriptor);
|
||||
if (!isValidStringConfig(normalizedDescriptor.geoField)) {
|
||||
throw new Error('Cannot create an ESGeoGridSourceDescriptor without a geoField');
|
||||
}
|
||||
return {
|
||||
type: ESGeoGridSource.type,
|
||||
id: uuid(),
|
||||
indexPatternId,
|
||||
geoField,
|
||||
metrics: metrics ? metrics : [DEFAULT_METRIC],
|
||||
requestType,
|
||||
resolution: resolution ? resolution : GRID_RESOLUTION.COARSE,
|
||||
...normalizedDescriptor,
|
||||
type: SOURCE_TYPES.ES_GEO_GRID,
|
||||
geoField: normalizedDescriptor.geoField!,
|
||||
requestType: descriptor.requestType || RENDER_AS.POINT,
|
||||
resolution: descriptor.resolution ? descriptor.resolution : GRID_RESOLUTION.COARSE,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(descriptor, inspectorAdapters) {
|
||||
super(descriptor, inspectorAdapters, descriptor.resolution !== GRID_RESOLUTION.SUPER_FINE);
|
||||
readonly _descriptor: ESGeoGridSourceDescriptor;
|
||||
|
||||
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 (
|
||||
<UpdateSourceEditor
|
||||
currentLayerType={currentLayerType}
|
||||
currentLayerType={sourceEditorArgs.currentLayerType}
|
||||
indexPatternId={this.getIndexPatternId()}
|
||||
onChange={onChange}
|
||||
onChange={sourceEditorArgs.onChange}
|
||||
metrics={this._descriptor.metrics}
|
||||
renderAs={this._descriptor.requestType}
|
||||
resolution={this._descriptor.resolution}
|
||||
|
@ -78,17 +102,17 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
);
|
||||
}
|
||||
|
||||
getSyncMeta() {
|
||||
getSyncMeta(): VectorSourceSyncMeta {
|
||||
return {
|
||||
requestType: this._descriptor.requestType,
|
||||
};
|
||||
}
|
||||
|
||||
async getImmutableProperties() {
|
||||
let indexPatternTitle = this.getIndexPatternId();
|
||||
async getImmutableProperties(): Promise<ImmutableSourceProperty[]> {
|
||||
let indexPatternName = this.getIndexPatternId();
|
||||
try {
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
indexPatternTitle = indexPattern.title;
|
||||
indexPatternName = indexPattern.title;
|
||||
} catch (error) {
|
||||
// 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', {
|
||||
defaultMessage: 'Index pattern',
|
||||
}),
|
||||
value: indexPatternTitle,
|
||||
value: indexPatternName,
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.maps.source.esGrid.geospatialFieldLabel', {
|
||||
|
@ -117,7 +141,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
return this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName());
|
||||
}
|
||||
|
||||
isGeoGridPrecisionAware() {
|
||||
isGeoGridPrecisionAware(): boolean {
|
||||
if (this._descriptor.resolution === GRID_RESOLUTION.SUPER_FINE) {
|
||||
// MVT gridded data should not bootstrap each time the precision changes
|
||||
// mapbox-gl needs to handle this
|
||||
|
@ -128,15 +152,15 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
}
|
||||
}
|
||||
|
||||
showJoinEditor() {
|
||||
showJoinEditor(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getGridResolution() {
|
||||
getGridResolution(): GRID_RESOLUTION {
|
||||
return this._descriptor.resolution;
|
||||
}
|
||||
|
||||
getGeoGridPrecision(zoom) {
|
||||
getGeoGridPrecision(zoom: number): number {
|
||||
if (this._descriptor.resolution === GRID_RESOLUTION.SUPER_FINE) {
|
||||
// The target-precision needs to be determined server side.
|
||||
return NaN;
|
||||
|
@ -178,9 +202,18 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
bucketsPerGrid,
|
||||
isRequestStillActive,
|
||||
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 aggs = {
|
||||
const gridsPerRequest: number = Math.floor(DEFAULT_MAX_BUCKETS_LIMIT / bucketsPerGrid);
|
||||
const aggs: any = {
|
||||
compositeSplit: {
|
||||
composite: {
|
||||
size: gridsPerRequest,
|
||||
|
@ -232,8 +265,10 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
aggs.compositeSplit.composite.after = afterKey;
|
||||
}
|
||||
searchSource.setField('aggs', aggs);
|
||||
const requestId = afterKey ? `${this.getId()} afterKey ${afterKey.geoSplit}` : this.getId();
|
||||
const esResponse = await this._runEsQuery({
|
||||
const requestId: string = afterKey
|
||||
? `${this.getId()} afterKey ${afterKey.geoSplit}`
|
||||
: this.getId();
|
||||
const esResponse: SearchResponse<unknown> = await this._runEsQuery({
|
||||
requestId,
|
||||
requestName: `${layerName} (${requestCount})`,
|
||||
searchSource,
|
||||
|
@ -259,7 +294,12 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
return features;
|
||||
}
|
||||
|
||||
_addNonCompositeAggsToSearchSource(searchSource, indexPattern, precision, bufferedExtent) {
|
||||
_addNonCompositeAggsToSearchSource(
|
||||
searchSource: ISearchSource,
|
||||
indexPattern: IndexPattern,
|
||||
precision: number | null,
|
||||
bufferedExtent?: MapExtent | null
|
||||
) {
|
||||
searchSource.setField('aggs', {
|
||||
[GEOTILE_GRID_AGG_NAME]: {
|
||||
geotile_grid: {
|
||||
|
@ -290,7 +330,14 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
layerName,
|
||||
registerCancelCallback,
|
||||
bufferedExtent,
|
||||
}) {
|
||||
}: {
|
||||
searchSource: ISearchSource;
|
||||
indexPattern: IndexPattern;
|
||||
precision: number;
|
||||
layerName: string;
|
||||
registerCancelCallback: (callback: () => void) => void;
|
||||
bufferedExtent?: MapExtent;
|
||||
}): Promise<Feature[]> {
|
||||
this._addNonCompositeAggsToSearchSource(searchSource, indexPattern, precision, bufferedExtent);
|
||||
|
||||
const esResponse = await this._runEsQuery({
|
||||
|
@ -306,52 +353,69 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
return convertRegularRespToGeoJson(esResponse, this._descriptor.requestType);
|
||||
}
|
||||
|
||||
async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback, isRequestStillActive) {
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const searchSource = await this.makeSearchSource(searchFilters, 0);
|
||||
async getGeoJsonWithMeta(
|
||||
layerName: string,
|
||||
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;
|
||||
this.getMetricFields().forEach((metricField) => {
|
||||
bucketsPerGrid += metricField.getBucketCount();
|
||||
});
|
||||
|
||||
const features =
|
||||
bucketsPerGrid === 1
|
||||
? await this._nonCompositeAggRequest({
|
||||
searchSource,
|
||||
indexPattern,
|
||||
precision: searchFilters.geogridPrecision,
|
||||
layerName,
|
||||
registerCancelCallback,
|
||||
bufferedExtent: searchFilters.buffer,
|
||||
})
|
||||
: await this._compositeAggRequest({
|
||||
searchSource,
|
||||
indexPattern,
|
||||
precision: searchFilters.geogridPrecision,
|
||||
layerName,
|
||||
registerCancelCallback,
|
||||
bucketsPerGrid,
|
||||
isRequestStillActive,
|
||||
bufferedExtent: searchFilters.buffer,
|
||||
});
|
||||
let features: Feature[];
|
||||
if (searchFilters.buffer) {
|
||||
features =
|
||||
bucketsPerGrid === 1
|
||||
? await this._nonCompositeAggRequest({
|
||||
searchSource,
|
||||
indexPattern,
|
||||
precision: searchFilters.geogridPrecision || 0,
|
||||
layerName,
|
||||
registerCancelCallback,
|
||||
bufferedExtent: searchFilters.buffer,
|
||||
})
|
||||
: await this._compositeAggRequest({
|
||||
searchSource,
|
||||
indexPattern,
|
||||
precision: searchFilters.geogridPrecision || 0,
|
||||
layerName,
|
||||
registerCancelCallback,
|
||||
bucketsPerGrid,
|
||||
isRequestStillActive,
|
||||
bufferedExtent: searchFilters.buffer,
|
||||
});
|
||||
} else {
|
||||
throw new Error('Cannot get GeoJson without searchFilter.buffer');
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
type: 'FeatureCollection',
|
||||
features: features,
|
||||
features,
|
||||
},
|
||||
meta: {
|
||||
areResultsTrimmed: false,
|
||||
},
|
||||
};
|
||||
} as GeoJsonWithMeta;
|
||||
}
|
||||
|
||||
getLayerName() {
|
||||
getLayerName(): string {
|
||||
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 searchSource = await this.makeSearchSource(searchFilters, 0);
|
||||
|
||||
|
@ -376,25 +440,25 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
layerName: this.getLayerName(),
|
||||
minSourceZoom: this.getMinZoom(),
|
||||
maxSourceZoom: this.getMaxZoom(),
|
||||
urlTemplate: urlTemplate,
|
||||
urlTemplate,
|
||||
};
|
||||
}
|
||||
|
||||
isFilterByMapBounds() {
|
||||
isFilterByMapBounds(): boolean {
|
||||
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;
|
||||
} else {
|
||||
//Should include bounds-filter from ES-DSL
|
||||
// Should include bounds-filter from ES-DSL
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
canFormatFeatureProperties() {
|
||||
canFormatFeatureProperties(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getSupportedShapeTypes() {
|
||||
async getSupportedShapeTypes(): Promise<VECTOR_SHAPE_TYPE[]> {
|
||||
if (this._descriptor.requestType === RENDER_AS.GRID) {
|
||||
return [VECTOR_SHAPE_TYPE.POLYGON];
|
||||
}
|
||||
|
@ -402,7 +466,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
return [VECTOR_SHAPE_TYPE.POINT];
|
||||
}
|
||||
|
||||
async getLicensedFeatures() {
|
||||
async getLicensedFeatures(): Promise<LICENSED_FEATURES[]> {
|
||||
const geoField = await this._getGeoField();
|
||||
return geoField.type === ES_GEO_FIELD_TYPE.GEO_SHAPE
|
||||
? [LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE]
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import uuid from 'uuid/v4';
|
||||
import turfBbox from '@turf/bbox';
|
||||
import { multiPoint } from '@turf/helpers';
|
||||
|
||||
|
@ -14,7 +13,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { SOURCE_TYPES, VECTOR_SHAPE_TYPE } from '../../../../common/constants';
|
||||
import { getDataSourceLabel } from '../../../../common/i18n_getters';
|
||||
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 { turfBboxToBounds } from '../../../../common/elasticsearch_util';
|
||||
import { DataRequestAbortError } from '../../util/data_request';
|
||||
|
@ -28,14 +27,14 @@ export const sourceTitle = i18n.translate('xpack.maps.source.pewPewTitle', {
|
|||
export class ESPewPewSource extends AbstractESAggSource {
|
||||
static type = SOURCE_TYPES.ES_PEW_PEW;
|
||||
|
||||
static createDescriptor({ indexPatternId, sourceGeoField, destGeoField, metrics }) {
|
||||
static createDescriptor(descriptor) {
|
||||
const normalizedDescriptor = AbstractESAggSource.createDescriptor(descriptor);
|
||||
return {
|
||||
...normalizedDescriptor,
|
||||
type: ESPewPewSource.type,
|
||||
id: uuid(),
|
||||
indexPatternId: indexPatternId,
|
||||
sourceGeoField,
|
||||
destGeoField,
|
||||
metrics: metrics ? metrics : [DEFAULT_METRIC],
|
||||
indexPatternId: descriptor.indexPatternId,
|
||||
sourceGeoField: descriptor.sourceGeoField,
|
||||
destGeoField: descriptor.destGeoField,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -4,4 +4,4 @@
|
|||
* 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;
|
|
@ -16,8 +16,15 @@ import { VectorLayer } from '../../layers/vector_layer/vector_layer';
|
|||
import { LAYER_WIZARD_CATEGORY, SCALING_TYPES } from '../../../../common/constants';
|
||||
import { TiledVectorLayer } from '../../layers/tiled_vector_layer/tiled_vector_layer';
|
||||
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);
|
||||
|
||||
if (sourceDescriptor.scalingType === SCALING_TYPES.CLUSTERS) {
|
||||
|
@ -36,7 +43,7 @@ export const esDocumentsLayerWizardConfig: LayerWizard = {
|
|||
}),
|
||||
icon: EsDocumentsLayerIcon,
|
||||
renderWizard: ({ previewLayers, mapColors }: RenderWizardArguments) => {
|
||||
const onSourceConfigChange = (sourceConfig: unknown) => {
|
||||
const onSourceConfigChange = (sourceConfig: Partial<ESSearchSourceDescriptor>) => {
|
||||
if (!sourceConfig) {
|
||||
previewLayers([]);
|
||||
return;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -11,21 +11,22 @@ jest.mock('./load_index_settings');
|
|||
import { getIndexPatternService, getSearchService, getHttp } from '../../../kibana_services';
|
||||
import { SearchSource } from 'src/plugins/data/public';
|
||||
|
||||
// @ts-expect-error
|
||||
import { loadIndexSettings } from './load_index_settings';
|
||||
|
||||
import { ESSearchSource } from './es_search_source';
|
||||
import { VectorSourceRequestMeta } from '../../../../common/descriptor_types';
|
||||
|
||||
const mockDescriptor = { indexPatternId: 'foo', geoField: 'bar' };
|
||||
|
||||
describe('ESSearchSource', () => {
|
||||
it('constructor', () => {
|
||||
const esSearchSource = new ESSearchSource({}, null);
|
||||
const esSearchSource = new ESSearchSource(mockDescriptor);
|
||||
expect(esSearchSource instanceof ESSearchSource).toBe(true);
|
||||
});
|
||||
|
||||
describe('ITiledSingleLayerVectorSource', () => {
|
||||
it('mb-source params', () => {
|
||||
const esSearchSource = new ESSearchSource({}, null);
|
||||
const esSearchSource = new ESSearchSource(mockDescriptor);
|
||||
expect(esSearchSource.getMinZoom()).toBe(0);
|
||||
expect(esSearchSource.getMaxZoom()).toBe(24);
|
||||
expect(esSearchSource.getLayerName()).toBe('source_layer');
|
||||
|
@ -72,6 +73,7 @@ describe('ESSearchSource', () => {
|
|||
getIndexPatternService.mockReturnValue(mockIndexPatternService);
|
||||
// @ts-expect-error
|
||||
getSearchService.mockReturnValue(mockSearchService);
|
||||
// @ts-expect-error
|
||||
loadIndexSettings.mockReturnValue({
|
||||
maxResultWindow: 1000,
|
||||
});
|
||||
|
@ -104,10 +106,10 @@ describe('ESSearchSource', () => {
|
|||
};
|
||||
|
||||
it('Should only include required props', async () => {
|
||||
const esSearchSource = new ESSearchSource(
|
||||
{ geoField: geoFieldName, indexPatternId: 'ipId' },
|
||||
null
|
||||
);
|
||||
const esSearchSource = new ESSearchSource({
|
||||
geoField: geoFieldName,
|
||||
indexPatternId: 'ipId',
|
||||
});
|
||||
const urlTemplateWithMeta = await esSearchSource.getUrlTemplateWithMeta(searchFilters);
|
||||
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`
|
||||
|
@ -118,22 +120,28 @@ describe('ESSearchSource', () => {
|
|||
|
||||
describe('isFilterByMapBounds', () => {
|
||||
it('default', () => {
|
||||
const esSearchSource = new ESSearchSource({}, null);
|
||||
const esSearchSource = new ESSearchSource(mockDescriptor);
|
||||
expect(esSearchSource.isFilterByMapBounds()).toBe(true);
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getJoinsDisabledReason', () => {
|
||||
it('default', () => {
|
||||
const esSearchSource = new ESSearchSource({}, null);
|
||||
const esSearchSource = new ESSearchSource(mockDescriptor);
|
||||
expect(esSearchSource.getJoinsDisabledReason()).toBe(null);
|
||||
});
|
||||
it('mvt', () => {
|
||||
const esSearchSource = new ESSearchSource({ scalingType: SCALING_TYPES.MVT }, null);
|
||||
const esSearchSource = new ESSearchSource({
|
||||
...mockDescriptor,
|
||||
scalingType: SCALING_TYPES.MVT,
|
||||
});
|
||||
expect(esSearchSource.getJoinsDisabledReason()).toBe(
|
||||
'Joins are not supported when scaling by mvt vector tiles'
|
||||
);
|
||||
|
@ -142,12 +150,15 @@ describe('ESSearchSource', () => {
|
|||
|
||||
describe('getFields', () => {
|
||||
it('default', () => {
|
||||
const esSearchSource = new ESSearchSource({}, null);
|
||||
const esSearchSource = new ESSearchSource(mockDescriptor);
|
||||
const docField = esSearchSource.createField({ fieldName: 'prop1' });
|
||||
expect(docField.canReadFromGeoJson()).toBe(true);
|
||||
});
|
||||
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' });
|
||||
expect(docField.canReadFromGeoJson()).toBe(false);
|
||||
});
|
||||
|
|
|
@ -5,50 +5,82 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import React, { ReactElement } from 'react';
|
||||
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 { getSearchService, getHttp } from '../../../kibana_services';
|
||||
import { hitsToGeoJson, getField, addFieldToDSL } from '../../../../common/elasticsearch_util';
|
||||
import { getHttp, getSearchService } from '../../../kibana_services';
|
||||
import { addFieldToDSL, getField, hitsToGeoJson } from '../../../../common/elasticsearch_util';
|
||||
// @ts-expect-error
|
||||
import { UpdateSourceEditor } from './update_source_editor';
|
||||
|
||||
import {
|
||||
SOURCE_TYPES,
|
||||
ES_GEO_FIELD_TYPE,
|
||||
DEFAULT_MAX_BUCKETS_LIMIT,
|
||||
SORT_ORDER,
|
||||
SCALING_TYPES,
|
||||
VECTOR_SHAPE_TYPE,
|
||||
MVT_SOURCE_LAYER_NAME,
|
||||
ES_GEO_FIELD_TYPE,
|
||||
FIELD_ORIGIN,
|
||||
GIS_API_PATH,
|
||||
MVT_GETTILE_API_PATH,
|
||||
MVT_SOURCE_LAYER_NAME,
|
||||
SCALING_TYPES,
|
||||
SOURCE_TYPES,
|
||||
VECTOR_SHAPE_TYPE,
|
||||
} from '../../../../common/constants';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getDataSourceLabel } from '../../../../common/i18n_getters';
|
||||
import { getSourceFields } from '../../../index_pattern_util';
|
||||
import { loadIndexSettings } from './load_index_settings';
|
||||
import uuid from 'uuid/v4';
|
||||
|
||||
import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants';
|
||||
import { ESDocField } from '../../fields/es_doc_field';
|
||||
|
||||
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', {
|
||||
defaultMessage: 'Documents',
|
||||
});
|
||||
|
||||
function getDocValueAndSourceFields(indexPattern, fieldNames) {
|
||||
const docValueFields = [];
|
||||
const sourceOnlyFields = [];
|
||||
const scriptFields = {};
|
||||
export interface ScriptField {
|
||||
source: string;
|
||||
lang: string;
|
||||
}
|
||||
|
||||
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) => {
|
||||
const field = getField(indexPattern, fieldName);
|
||||
if (field.scripted) {
|
||||
scriptFields[field.name] = {
|
||||
script: {
|
||||
source: field.script,
|
||||
lang: field.lang,
|
||||
source: field.script || '',
|
||||
lang: field.lang || '',
|
||||
},
|
||||
};
|
||||
} else if (field.readFromDocValues) {
|
||||
|
@ -68,43 +100,64 @@ function getDocValueAndSourceFields(indexPattern, fieldNames) {
|
|||
return { docValueFields, sourceOnlyFields, scriptFields };
|
||||
}
|
||||
|
||||
export class ESSearchSource extends AbstractESSource {
|
||||
static type = SOURCE_TYPES.ES_SEARCH;
|
||||
export class ESSearchSource extends AbstractESSource implements ITiledSingleLayerVectorSource {
|
||||
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 {
|
||||
...descriptor,
|
||||
id: descriptor.id ? descriptor.id : uuid(),
|
||||
type: ESSearchSource.type,
|
||||
indexPatternId: descriptor.indexPatternId,
|
||||
geoField: descriptor.geoField,
|
||||
filterByMapBounds: _.get(descriptor, 'filterByMapBounds', DEFAULT_FILTER_BY_MAP_BOUNDS),
|
||||
tooltipProperties: _.get(descriptor, 'tooltipProperties', []),
|
||||
sortField: _.get(descriptor, 'sortField', ''),
|
||||
sortOrder: _.get(descriptor, 'sortOrder', SORT_ORDER.DESC),
|
||||
scalingType: _.get(descriptor, 'scalingType', SCALING_TYPES.LIMIT),
|
||||
topHitsSplitField: descriptor.topHitsSplitField,
|
||||
topHitsSize: _.get(descriptor, 'topHitsSize', 1),
|
||||
...normalizedDescriptor,
|
||||
type: SOURCE_TYPES.ES_SEARCH,
|
||||
geoField: normalizedDescriptor.geoField!,
|
||||
filterByMapBounds:
|
||||
typeof descriptor.filterByMapBounds === 'boolean'
|
||||
? descriptor.filterByMapBounds
|
||||
: DEFAULT_FILTER_BY_MAP_BOUNDS,
|
||||
tooltipProperties: Array.isArray(descriptor.tooltipProperties)
|
||||
? descriptor.tooltipProperties
|
||||
: [],
|
||||
sortField: isValidStringConfig(descriptor.sortField) ? (descriptor.sortField as string) : '',
|
||||
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) {
|
||||
super(ESSearchSource.createDescriptor(descriptor), inspectorAdapters);
|
||||
|
||||
this._tooltipFields = this._descriptor.tooltipProperties.map((property) =>
|
||||
this.createField({ fieldName: property })
|
||||
);
|
||||
constructor(descriptor: Partial<ESSearchSourceDescriptor>, inspectorAdapters?: Adapters) {
|
||||
const sourceDescriptor = ESSearchSource.createDescriptor(descriptor);
|
||||
super(sourceDescriptor, inspectorAdapters);
|
||||
this._descriptor = sourceDescriptor;
|
||||
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({
|
||||
fieldName,
|
||||
source: this,
|
||||
origin: FIELD_ORIGIN.SOURCE,
|
||||
canReadFromGeoJson: this._descriptor.scalingType !== SCALING_TYPES.MVT,
|
||||
});
|
||||
}
|
||||
|
||||
renderSourceSettingsEditor({ onChange }) {
|
||||
renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement<any> | null {
|
||||
const getGeoField = () => {
|
||||
return this._getGeoField();
|
||||
};
|
||||
|
@ -113,7 +166,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
source={this}
|
||||
indexPatternId={this.getIndexPatternId()}
|
||||
getGeoField={getGeoField}
|
||||
onChange={onChange}
|
||||
onChange={sourceEditorArgs.onChange}
|
||||
tooltipFields={this._tooltipFields}
|
||||
sortField={this._descriptor.sortField}
|
||||
sortOrder={this._descriptor.sortOrder}
|
||||
|
@ -125,34 +178,36 @@ export class ESSearchSource extends AbstractESSource {
|
|||
);
|
||||
}
|
||||
|
||||
async getFields() {
|
||||
async getFields(): Promise<IField[]> {
|
||||
try {
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
return indexPattern.fields
|
||||
.filter((field) => {
|
||||
// Ensure fielddata is enabled for field.
|
||||
// Search does not request _source
|
||||
return field.aggregatable;
|
||||
})
|
||||
.map((field) => {
|
||||
const fields: IFieldType[] = indexPattern.fields.filter((field) => {
|
||||
// Ensure fielddata is enabled for field.
|
||||
// Search does not request _source
|
||||
return field.aggregatable;
|
||||
});
|
||||
|
||||
return fields.map(
|
||||
(field): IField => {
|
||||
return this.createField({ fieldName: field.name });
|
||||
});
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
// failed index-pattern retrieval will show up as error-message in the layer-toc-entry
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
getFieldNames() {
|
||||
getFieldNames(): string[] {
|
||||
return [this._descriptor.geoField];
|
||||
}
|
||||
|
||||
async getImmutableProperties() {
|
||||
let indexPatternTitle = this.getIndexPatternId();
|
||||
async getImmutableProperties(): Promise<ImmutableSourceProperty[]> {
|
||||
let indexPatternName = this.getIndexPatternId();
|
||||
let geoFieldType = '';
|
||||
try {
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
indexPatternTitle = indexPattern.title;
|
||||
indexPatternName = indexPattern.title;
|
||||
const geoField = await this._getGeoField();
|
||||
geoFieldType = geoField.type;
|
||||
} catch (error) {
|
||||
|
@ -168,7 +223,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
label: i18n.translate('xpack.maps.source.esSearch.indexPatternLabel', {
|
||||
defaultMessage: `Index pattern`,
|
||||
}),
|
||||
value: indexPatternTitle,
|
||||
value: indexPatternName,
|
||||
},
|
||||
{
|
||||
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
|
||||
_buildEsSort() {
|
||||
_buildEsSort(): Array<Record<string, SortDirectionNumeric>> {
|
||||
const { sortField, sortOrder } = this._descriptor;
|
||||
|
||||
if (!sortField) {
|
||||
throw new Error('Cannot build sort');
|
||||
}
|
||||
return [
|
||||
{
|
||||
[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 indexPattern = await this.getIndexPattern();
|
||||
if (!topHitsSplitFieldName) {
|
||||
throw new Error('Cannot _getTopHits without topHitsSplitField');
|
||||
}
|
||||
|
||||
const indexPattern: IndexPattern = await this.getIndexPattern();
|
||||
|
||||
const { docValueFields, sourceOnlyFields, scriptFields } = getDocValueAndSourceFields(
|
||||
indexPattern,
|
||||
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,
|
||||
script_fields: scriptFields,
|
||||
docvalue_fields: docValueFields,
|
||||
|
@ -215,6 +288,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
if (this._hasSort()) {
|
||||
topHits.sort = this._buildEsSort();
|
||||
}
|
||||
|
||||
if (sourceOnlyFields.length === 0) {
|
||||
topHits._source = false;
|
||||
} 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 termsAgg = {
|
||||
size: DEFAULT_MAX_BUCKETS_LIMIT,
|
||||
|
@ -253,13 +327,13 @@ export class ESSearchSource extends AbstractESSource {
|
|||
requestDescription: 'Elasticsearch document top hits request',
|
||||
});
|
||||
|
||||
const allHits = [];
|
||||
const allHits: any[] = [];
|
||||
const entityBuckets = _.get(resp, 'aggregations.entitySplit.buckets', []);
|
||||
const totalEntities = _.get(resp, 'aggregations.totalEntities.value', 0);
|
||||
// can not compare entityBuckets.length to totalEntities because totalEntities is an approximate
|
||||
const areEntitiesTrimmed = entityBuckets.length >= DEFAULT_MAX_BUCKETS_LIMIT;
|
||||
let areTopHitsTrimmed = false;
|
||||
entityBuckets.forEach((entityBucket) => {
|
||||
entityBuckets.forEach((entityBucket: any) => {
|
||||
const total = _.get(entityBucket, 'entityHits.hits.total', 0);
|
||||
const hits = _.get(entityBucket, 'entityHits.hits.hits', []);
|
||||
// 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
|
||||
// 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 { docValueFields, sourceOnlyFields } = getDocValueAndSourceFields(
|
||||
|
@ -322,23 +401,28 @@ export class ESSearchSource extends AbstractESSource {
|
|||
};
|
||||
}
|
||||
|
||||
_isTopHits() {
|
||||
_isTopHits(): boolean {
|
||||
const { scalingType, topHitsSplitField } = this._descriptor;
|
||||
return !!(scalingType === SCALING_TYPES.TOP_HITS && topHitsSplitField);
|
||||
}
|
||||
|
||||
_hasSort() {
|
||||
_hasSort(): boolean {
|
||||
const { sortField, sortOrder } = this._descriptor;
|
||||
return !!sortField && !!sortOrder;
|
||||
}
|
||||
|
||||
async getMaxResultWindow() {
|
||||
async getMaxResultWindow(): Promise<number> {
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const indexSettings = await loadIndexSettings(indexPattern.title);
|
||||
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 indexSettings = await loadIndexSettings(indexPattern.title);
|
||||
|
@ -355,7 +439,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
const unusedMetaFields = indexPattern.metaFields.filter((metaField) => {
|
||||
return !['_id', '_index'].includes(metaField);
|
||||
});
|
||||
const flattenHit = (hit) => {
|
||||
const flattenHit = (hit: Record<string, any>) => {
|
||||
const properties = indexPattern.flattenHit(hit);
|
||||
// remove metaFields
|
||||
unusedMetaFields.forEach((metaField) => {
|
||||
|
@ -375,7 +459,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
hits,
|
||||
flattenHit,
|
||||
geoField.name,
|
||||
geoField.type,
|
||||
geoField.type as ES_GEO_FIELD_TYPE,
|
||||
epochMillisFields
|
||||
);
|
||||
} catch (error) {
|
||||
|
@ -394,11 +478,11 @@ export class ESSearchSource extends AbstractESSource {
|
|||
};
|
||||
}
|
||||
|
||||
canFormatFeatureProperties() {
|
||||
canFormatFeatureProperties(): boolean {
|
||||
return this._tooltipFields.length > 0;
|
||||
}
|
||||
|
||||
async _loadTooltipProperties(docId, index, indexPattern) {
|
||||
async _loadTooltipProperties(docId: string | number, index: string, indexPattern: IndexPattern) {
|
||||
if (this._tooltipFields.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
@ -430,7 +514,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
}
|
||||
|
||||
const properties = indexPattern.flattenHit(hit);
|
||||
indexPattern.metaFields.forEach((metaField) => {
|
||||
indexPattern.metaFields.forEach((metaField: string) => {
|
||||
if (!this._getTooltipPropertyNames().includes(metaField)) {
|
||||
delete properties[metaField];
|
||||
}
|
||||
|
@ -438,7 +522,14 @@ export class ESSearchSource extends AbstractESSource {
|
|||
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 propertyValues = await this._loadTooltipProperties(
|
||||
properties._id,
|
||||
|
@ -452,25 +543,27 @@ export class ESSearchSource extends AbstractESSource {
|
|||
return Promise.all(tooltipProperties);
|
||||
}
|
||||
|
||||
isFilterByMapBounds() {
|
||||
if (this._descriptor.scalingType === SCALING_TYPES.CLUSTER) {
|
||||
isFilterByMapBounds(): boolean {
|
||||
if (this._descriptor.scalingType === SCALING_TYPES.CLUSTERS) {
|
||||
return true;
|
||||
} else if (this._descriptor.scalingType === SCALING_TYPES.MVT) {
|
||||
return false;
|
||||
} else {
|
||||
return this._descriptor.filterByMapBounds;
|
||||
return !!this._descriptor.filterByMapBounds;
|
||||
}
|
||||
}
|
||||
|
||||
async getLeftJoinFields() {
|
||||
async getLeftJoinFields(): Promise<IField[]> {
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
// Left fields are retrieved from _source.
|
||||
return getSourceFields(indexPattern.fields).map((field) =>
|
||||
this.createField({ fieldName: field.name })
|
||||
return getSourceFields(indexPattern.fields).map(
|
||||
(field): IField => {
|
||||
return this.createField({ fieldName: field.name });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async getSupportedShapeTypes() {
|
||||
async getSupportedShapeTypes(): Promise<VECTOR_SHAPE_TYPE[]> {
|
||||
let geoFieldType;
|
||||
try {
|
||||
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];
|
||||
}
|
||||
|
||||
getSourceTooltipContent(sourceDataRequest) {
|
||||
const featureCollection = sourceDataRequest ? sourceDataRequest.getData() : null;
|
||||
getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig {
|
||||
const featureCollection: FeatureCollection | null = sourceDataRequest
|
||||
? (sourceDataRequest.getData() as FeatureCollection)
|
||||
: null;
|
||||
const meta = sourceDataRequest ? sourceDataRequest.getMeta() : null;
|
||||
if (!featureCollection || !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}`,
|
||||
// Used to show trimmed icon in legend
|
||||
// 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 {
|
||||
sortField: this._descriptor.sortField,
|
||||
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();
|
||||
return {
|
||||
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;
|
||||
if (this._descriptor.scalingType === SCALING_TYPES.CLUSTERS) {
|
||||
reason = i18n.translate('xpack.maps.source.esSearch.joinsDisabledReason', {
|
||||
|
@ -577,11 +675,18 @@ export class ESSearchSource extends AbstractESSource {
|
|||
return reason;
|
||||
}
|
||||
|
||||
getLayerName() {
|
||||
getLayerName(): string {
|
||||
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 indexSettings = await loadIndexSettings(indexPattern.title);
|
||||
|
||||
|
@ -621,7 +726,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
layerName: this.getLayerName(),
|
||||
minSourceZoom: this.getMinZoom(),
|
||||
maxSourceZoom: this.getMaxZoom(),
|
||||
urlTemplate: urlTemplate,
|
||||
urlTemplate,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -4,20 +4,25 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
DEFAULT_MAX_RESULT_WINDOW,
|
||||
DEFAULT_MAX_INNER_RESULT_WINDOW,
|
||||
INDEX_SETTINGS_API_PATH,
|
||||
} from '../../../../common/constants';
|
||||
import { getHttp, getToasts } from '../../../kibana_services';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
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)) {
|
||||
return indexSettings.get(indexPatternTitle);
|
||||
return indexSettings.get(indexPatternTitle)!;
|
||||
}
|
||||
|
||||
const fetchPromise = fetchIndexSettings(indexPatternTitle);
|
||||
|
@ -25,7 +30,7 @@ export async function loadIndexSettings(indexPatternTitle) {
|
|||
return fetchPromise;
|
||||
}
|
||||
|
||||
async function fetchIndexSettings(indexPatternTitle) {
|
||||
async function fetchIndexSettings(indexPatternTitle: string): Promise<INDEX_SETTINGS> {
|
||||
const http = getHttp();
|
||||
const toasts = getToasts();
|
||||
try {
|
||||
|
@ -50,6 +55,7 @@ async function fetchIndexSettings(indexPatternTitle) {
|
|||
toastDisplayed = true;
|
||||
toasts.addWarning(warningMsg);
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(warningMsg);
|
||||
return {
|
||||
maxResultWindow: DEFAULT_MAX_RESULT_WINDOW,
|
|
@ -18,10 +18,10 @@ import {
|
|||
getSourceFields,
|
||||
supportsGeoTileAgg,
|
||||
} 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 { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { indexPatterns } from '../../../../../../../src/plugins/data/public';
|
||||
|
||||
import { ScalingForm } from './scaling_form';
|
||||
|
||||
export class UpdateSourceEditor extends Component {
|
||||
|
@ -183,13 +183,13 @@ export class UpdateSourceEditor extends Component {
|
|||
text: i18n.translate('xpack.maps.source.esSearch.ascendingLabel', {
|
||||
defaultMessage: 'ascending',
|
||||
}),
|
||||
value: SORT_ORDER.ASC,
|
||||
value: SortDirection.asc,
|
||||
},
|
||||
{
|
||||
text: i18n.translate('xpack.maps.source.esSearch.descendingLabel', {
|
||||
defaultMessage: 'descending',
|
||||
}),
|
||||
value: SORT_ORDER.DESC,
|
||||
value: SortDirection.desc,
|
||||
},
|
||||
]}
|
||||
value={this.props.sortOrder}
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -4,7 +4,10 @@
|
|||
* 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 {
|
||||
getAutocompleteService,
|
||||
getIndexPatternService,
|
||||
|
@ -12,62 +15,122 @@ import {
|
|||
getSearchService,
|
||||
} from '../../../kibana_services';
|
||||
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 { DataRequestAbortError } from '../../util/data_request';
|
||||
import { expandToTileBoundaries } from '../../../../common/geo_tile_utils';
|
||||
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 {
|
||||
constructor(descriptor, inspectorAdapters) {
|
||||
super(
|
||||
{
|
||||
...descriptor,
|
||||
applyGlobalQuery: _.get(descriptor, 'applyGlobalQuery', true),
|
||||
},
|
||||
inspectorAdapters
|
||||
);
|
||||
export interface IESSource extends IVectorSource {
|
||||
isESSource(): true;
|
||||
getId(): string;
|
||||
getIndexPattern(): Promise<IndexPattern>;
|
||||
getIndexPatternId(): string;
|
||||
getGeoFieldName(): string;
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
isFieldAware() {
|
||||
isFieldAware(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
isRefreshTimerAware() {
|
||||
isRefreshTimerAware(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
isQueryAware() {
|
||||
isQueryAware(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
getIndexPatternIds() {
|
||||
getIndexPatternIds(): string[] {
|
||||
return [this.getIndexPatternId()];
|
||||
}
|
||||
|
||||
getQueryableIndexPatternIds() {
|
||||
getQueryableIndexPatternIds(): string[] {
|
||||
if (this.getApplyGlobalQuery()) {
|
||||
return [this.getIndexPatternId()];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
isESSource() {
|
||||
isESSource(): true {
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
// id used as uuid to track requests in inspector
|
||||
clonedDescriptor.id = uuid();
|
||||
|
@ -80,26 +143,45 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
requestDescription,
|
||||
searchSource,
|
||||
registerCancelCallback,
|
||||
}) {
|
||||
}: {
|
||||
requestId: string;
|
||||
requestName: string;
|
||||
requestDescription: string;
|
||||
searchSource: ISearchSource;
|
||||
registerCancelCallback: (callback: () => void) => void;
|
||||
}): Promise<any> {
|
||||
const abortController = new AbortController();
|
||||
registerCancelCallback(() => abortController.abort());
|
||||
|
||||
const inspectorRequest = this._inspectorAdapters.requests.start(requestName, {
|
||||
id: requestId,
|
||||
description: requestDescription,
|
||||
});
|
||||
const inspectorAdapters = this.getInspectorAdapters();
|
||||
let inspectorRequest: RequestResponder | undefined;
|
||||
if (inspectorAdapters) {
|
||||
inspectorRequest = inspectorAdapters.requests.start(requestName, {
|
||||
id: requestId,
|
||||
description: requestDescription,
|
||||
});
|
||||
}
|
||||
|
||||
let resp;
|
||||
try {
|
||||
inspectorRequest.stats(search.getRequestInspectorStats(searchSource));
|
||||
searchSource.getSearchRequestBody().then((body) => {
|
||||
inspectorRequest.json(body);
|
||||
});
|
||||
if (inspectorRequest) {
|
||||
const requestStats = search.getRequestInspectorStats(searchSource);
|
||||
inspectorRequest.stats(requestStats);
|
||||
searchSource.getSearchRequestBody().then((body) => {
|
||||
if (inspectorRequest) {
|
||||
inspectorRequest.json(body);
|
||||
}
|
||||
});
|
||||
}
|
||||
resp = await searchSource.fetch({ abortSignal: abortController.signal });
|
||||
inspectorRequest
|
||||
.stats(search.getResponseInspectorStats(resp, searchSource))
|
||||
.ok({ json: resp });
|
||||
if (inspectorRequest) {
|
||||
const responseStats = search.getResponseInspectorStats(resp, searchSource);
|
||||
inspectorRequest.stats(responseStats).ok({ json: resp });
|
||||
}
|
||||
} catch (error) {
|
||||
inspectorRequest.error({ error });
|
||||
if (inspectorRequest) {
|
||||
inspectorRequest.error(error);
|
||||
}
|
||||
if (error.name === 'AbortError') {
|
||||
throw new DataRequestAbortError();
|
||||
}
|
||||
|
@ -115,22 +197,40 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
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 isTimeAware = await this.isTimeAware();
|
||||
const applyGlobalQuery = _.get(searchFilters, 'applyGlobalQuery', true);
|
||||
const globalFilters = applyGlobalQuery ? searchFilters.filters : [];
|
||||
const allFilters = [...globalFilters];
|
||||
if (this.isFilterByMapBounds() && searchFilters.buffer) {
|
||||
//buffer can be empty
|
||||
const applyGlobalQuery =
|
||||
typeof searchFilters.applyGlobalQuery === 'boolean' ? searchFilters.applyGlobalQuery : true;
|
||||
const globalFilters: Filter[] = applyGlobalQuery ? searchFilters.filters : [];
|
||||
const allFilters: Filter[] = [...globalFilters];
|
||||
if (this.isFilterByMapBounds() && 'buffer' in searchFilters && searchFilters.buffer) {
|
||||
// buffer can be empty
|
||||
const geoField = await this._getGeoField();
|
||||
const buffer = this.isGeoGridPrecisionAware()
|
||||
? expandToTileBoundaries(searchFilters.buffer, searchFilters.geogridPrecision)
|
||||
: searchFilters.buffer;
|
||||
allFilters.push(createExtentFilter(buffer, geoField.name, geoField.type));
|
||||
const buffer: MapExtent =
|
||||
this.isGeoGridPrecisionAware() &&
|
||||
'geogridPrecision' in searchFilters &&
|
||||
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) {
|
||||
allFilters.push(getTimeFilter().createFilter(indexPattern, searchFilters.timeFilters));
|
||||
const filter = getTimeFilter().createFilter(indexPattern, searchFilters.timeFilters);
|
||||
if (filter) {
|
||||
allFilters.push(filter);
|
||||
}
|
||||
}
|
||||
const searchService = getSearchService();
|
||||
const searchSource = await searchService.searchSource.create(initialSearchContext);
|
||||
|
@ -153,7 +253,10 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
return searchSource;
|
||||
}
|
||||
|
||||
async getBoundsForFilters(boundsFilters, registerCancelCallback) {
|
||||
async getBoundsForFilters(
|
||||
boundsFilters: BoundsFilters,
|
||||
registerCancelCallback: (callback: () => void) => void
|
||||
): Promise<MapExtent | null> {
|
||||
const searchSource = await this.makeSearchSource(boundsFilters, 0);
|
||||
searchSource.setField('aggs', {
|
||||
fitToBounds: {
|
||||
|
@ -184,14 +287,14 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
const minLon = esBounds.top_left.lon;
|
||||
const maxLon = esBounds.bottom_right.lon;
|
||||
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,
|
||||
minLat: esBounds.bottom_right.lat,
|
||||
maxLat: esBounds.top_left.lat,
|
||||
};
|
||||
}
|
||||
|
||||
async isTimeAware() {
|
||||
async isTimeAware(): Promise<boolean> {
|
||||
try {
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const timeField = indexPattern.timeFieldName;
|
||||
|
@ -201,15 +304,19 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
}
|
||||
}
|
||||
|
||||
getIndexPatternId() {
|
||||
getIndexPatternId(): string {
|
||||
return this._descriptor.indexPatternId;
|
||||
}
|
||||
|
||||
getGeoFieldName() {
|
||||
getGeoFieldName(): string {
|
||||
if (!this._descriptor.geoField) {
|
||||
throw new Error('Should not call');
|
||||
}
|
||||
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) {
|
||||
return this.indexPattern;
|
||||
}
|
||||
|
@ -227,16 +334,16 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
}
|
||||
}
|
||||
|
||||
async supportsFitToBounds() {
|
||||
async supportsFitToBounds(): Promise<boolean> {
|
||||
try {
|
||||
const geoField = await this._getGeoField();
|
||||
return geoField.aggregatable;
|
||||
return !!geoField.aggregatable;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async _getGeoField() {
|
||||
async _getGeoField(): Promise<IFieldType> {
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const geoField = indexPattern.fields.getByName(this.getGeoFieldName());
|
||||
if (!geoField) {
|
||||
|
@ -250,7 +357,7 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
return geoField;
|
||||
}
|
||||
|
||||
async getDisplayName() {
|
||||
async getDisplayName(): Promise<string> {
|
||||
try {
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
return indexPattern.title;
|
||||
|
@ -260,15 +367,11 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
}
|
||||
}
|
||||
|
||||
isBoundsAware() {
|
||||
isBoundsAware(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this._descriptor.id;
|
||||
}
|
||||
|
||||
async createFieldFormatter(field) {
|
||||
async createFieldFormatter(field: IField): Promise<FieldFormatter | null> {
|
||||
let indexPattern;
|
||||
try {
|
||||
indexPattern = await this.getIndexPattern();
|
||||
|
@ -291,15 +394,25 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
registerCancelCallback,
|
||||
sourceQuery,
|
||||
timeFilters,
|
||||
}) {
|
||||
}: {
|
||||
layerName: string;
|
||||
style: IVectorStyle;
|
||||
dynamicStyleProps: Array<IDynamicStyleProperty<DynamicStylePropertyOptions>>;
|
||||
registerCancelCallback: (callback: () => void) => void;
|
||||
sourceQuery?: MapQuery;
|
||||
timeFilters: TimeRange;
|
||||
}): Promise<object> {
|
||||
const promises = dynamicStyleProps.map((dynamicStyleProp) => {
|
||||
return dynamicStyleProp.getFieldMetaRequest();
|
||||
});
|
||||
|
||||
const fieldAggRequests = await Promise.all(promises);
|
||||
const aggs = fieldAggRequests.reduce((aggs, fieldAggRequest) => {
|
||||
return fieldAggRequest ? { ...aggs, ...fieldAggRequest } : aggs;
|
||||
}, {});
|
||||
const allAggs: Record<string, any> = fieldAggRequests.reduce(
|
||||
(aggs: Record<string, any>, fieldAggRequest: unknown | null) => {
|
||||
return fieldAggRequest ? { ...aggs, ...(fieldAggRequest as Record<string, any>) } : aggs;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const searchService = getSearchService();
|
||||
|
@ -307,12 +420,15 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
|
||||
searchSource.setField('index', indexPattern);
|
||||
searchSource.setField('size', 0);
|
||||
searchSource.setField('aggs', aggs);
|
||||
searchSource.setField('aggs', allAggs);
|
||||
if (sourceQuery) {
|
||||
searchSource.setField('query', sourceQuery);
|
||||
}
|
||||
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({
|
||||
|
@ -335,15 +451,17 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
return resp.aggregations;
|
||||
}
|
||||
|
||||
getValueSuggestions = async (field, query) => {
|
||||
getValueSuggestions = async (field: IField, query: string): Promise<string[]> => {
|
||||
try {
|
||||
const indexPattern = await this.getIndexPattern();
|
||||
const indexPatternField = indexPattern.fields.getByName(field.getRootName())!;
|
||||
return await getAutocompleteService().getValueSuggestions({
|
||||
indexPattern,
|
||||
field: indexPattern.fields.getByName(field.getRootName()),
|
||||
field: indexPatternField,
|
||||
query,
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`Unable to fetch suggestions for field: ${field.getRootName()}, query: ${query}, error: ${
|
||||
error.message
|
|
@ -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;
|
||||
}
|
|
@ -34,6 +34,7 @@ describe('getMetricFields', () => {
|
|||
id: '1234',
|
||||
indexPatternTitle: indexPatternTitle,
|
||||
term: termFieldName,
|
||||
indexPatternId: 'foobar',
|
||||
});
|
||||
const metrics = source.getMetricFields();
|
||||
expect(metrics[0].getName()).toEqual('__kbnjoin__count__1234');
|
||||
|
@ -46,6 +47,7 @@ describe('getMetricFields', () => {
|
|||
indexPatternTitle: indexPatternTitle,
|
||||
term: termFieldName,
|
||||
metrics: metricExamples,
|
||||
indexPatternId: 'foobar',
|
||||
});
|
||||
const metrics = source.getMetricFields();
|
||||
expect(metrics[0].getName()).toEqual('__kbnjoin__sum_of_myFieldGettingSummed__1234');
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ISearchSource, Query } from 'src/plugins/data/public';
|
||||
import {
|
||||
AGG_TYPE,
|
||||
DEFAULT_MAX_BUCKETS_LIMIT,
|
||||
|
@ -20,15 +20,22 @@ import {
|
|||
getField,
|
||||
addFieldToDSL,
|
||||
extractPropertiesFromBucket,
|
||||
BucketProperties,
|
||||
} 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_BUCKET_KEYS_TO_IGNORE = ['key', 'doc_count'];
|
||||
|
||||
export function extractPropertiesMap(rawEsData, countPropertyName) {
|
||||
const propertiesMap = new Map();
|
||||
_.get(rawEsData, ['aggregations', TERMS_AGG_NAME, 'buckets'], []).forEach((termBucket) => {
|
||||
export function extractPropertiesMap(rawEsData: any, countPropertyName: string): PropertiesMap {
|
||||
const propertiesMap: PropertiesMap = new Map<string, BucketProperties>();
|
||||
const buckets: any[] = _.get(rawEsData, ['aggregations', TERMS_AGG_NAME, 'buckets'], []);
|
||||
buckets.forEach((termBucket: any) => {
|
||||
const properties = extractPropertiesFromBucket(termBucket, TERMS_BUCKET_KEYS_TO_IGNORE);
|
||||
if (countPropertyName) {
|
||||
properties[countPropertyName] = termBucket.doc_count;
|
||||
|
@ -41,37 +48,36 @@ export function extractPropertiesMap(rawEsData, countPropertyName) {
|
|||
export class ESTermSource extends AbstractESAggSource {
|
||||
static type = SOURCE_TYPES.ES_TERM_SOURCE;
|
||||
|
||||
constructor(descriptor, inspectorAdapters) {
|
||||
super(descriptor, inspectorAdapters);
|
||||
private readonly _termField: ESDocField;
|
||||
readonly _descriptor: ESTermSourceDescriptor;
|
||||
|
||||
constructor(descriptor: ESTermSourceDescriptor, inspectorAdapters: Adapters) {
|
||||
super(AbstractESAggSource.createDescriptor(descriptor), inspectorAdapters);
|
||||
this._descriptor = descriptor;
|
||||
this._termField = new ESDocField({
|
||||
fieldName: descriptor.term,
|
||||
fieldName: this._descriptor.term,
|
||||
source: this,
|
||||
origin: this.getOriginForField(),
|
||||
});
|
||||
}
|
||||
|
||||
static renderEditor({}) {
|
||||
//no need to localize. this editor is never rendered.
|
||||
return `<div>editor details</div>`;
|
||||
}
|
||||
|
||||
hasCompleteConfig() {
|
||||
return _.has(this._descriptor, 'indexPatternId') && _.has(this._descriptor, 'term');
|
||||
}
|
||||
|
||||
getTermField() {
|
||||
getTermField(): ESDocField {
|
||||
return this._termField;
|
||||
}
|
||||
|
||||
getOriginForField() {
|
||||
getOriginForField(): FIELD_ORIGIN {
|
||||
return FIELD_ORIGIN.JOIN;
|
||||
}
|
||||
|
||||
getWhereQuery() {
|
||||
getWhereQuery(): Query | undefined {
|
||||
return this._descriptor.whereQuery;
|
||||
}
|
||||
|
||||
getAggKey(aggType, fieldName) {
|
||||
getAggKey(aggType: AGG_TYPE, fieldName?: string): string {
|
||||
return getJoinAggKey({
|
||||
aggType,
|
||||
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
|
||||
? i18n.translate('xpack.maps.source.esJoin.countLabel', {
|
||||
defaultMessage: `Count of {indexPatternTitle}`,
|
||||
|
@ -88,13 +94,18 @@ export class ESTermSource extends AbstractESAggSource {
|
|||
: 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()) {
|
||||
return [];
|
||||
return new Map<string, BucketProperties>();
|
||||
}
|
||||
|
||||
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 termsAgg = { size: DEFAULT_MAX_BUCKETS_LIMIT };
|
||||
searchSource.setField('aggs', {
|
||||
|
@ -122,16 +133,16 @@ export class ESTermSource extends AbstractESAggSource {
|
|||
return extractPropertiesMap(rawEsData, countPropertyName);
|
||||
}
|
||||
|
||||
isFilterByMapBounds() {
|
||||
isFilterByMapBounds(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
async getDisplayName() {
|
||||
//no need to localize. this is never rendered.
|
||||
async getDisplayName(): Promise<string> {
|
||||
// no need to localize. this is never rendered.
|
||||
return `es_table ${this.getIndexPatternId()}`;
|
||||
}
|
||||
|
||||
getFieldNames() {
|
||||
getFieldNames(): string[] {
|
||||
return this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName());
|
||||
}
|
||||
}
|
|
@ -5,10 +5,11 @@
|
|||
*/
|
||||
|
||||
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 { GeojsonFileSourceDescriptor } from '../../../../common/descriptor_types';
|
||||
import { registerSource } from '../source_registry';
|
||||
import { IField } from '../../fields/field';
|
||||
|
||||
function getFeatureCollection(geoJson: Feature | FeatureCollection | null): FeatureCollection {
|
||||
if (!geoJson) {
|
||||
|
@ -30,26 +31,28 @@ function getFeatureCollection(geoJson: Feature | FeatureCollection | null): Feat
|
|||
}
|
||||
|
||||
export class GeojsonFileSource extends AbstractVectorSource {
|
||||
static type = SOURCE_TYPES.GEOJSON_FILE;
|
||||
|
||||
static createDescriptor(
|
||||
geoJson: Feature | FeatureCollection | null,
|
||||
name: string
|
||||
): GeojsonFileSourceDescriptor {
|
||||
return {
|
||||
type: GeojsonFileSource.type,
|
||||
type: SOURCE_TYPES.GEOJSON_FILE,
|
||||
__featureCollection: getFeatureCollection(geoJson),
|
||||
name,
|
||||
};
|
||||
}
|
||||
|
||||
async getGeoJsonWithMeta() {
|
||||
async getGeoJsonWithMeta(): Promise<GeoJsonWithMeta> {
|
||||
return {
|
||||
data: (this._descriptor as GeojsonFileSourceDescriptor).__featureCollection,
|
||||
meta: {},
|
||||
};
|
||||
}
|
||||
|
||||
createField({ fieldName }: { fieldName: string }): IField {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
async getDisplayName() {
|
||||
return (this._descriptor as GeojsonFileSourceDescriptor).name;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ export const kibanaRegionMapLayerWizardConfig: LayerWizard = {
|
|||
}),
|
||||
icon: 'logoKibana',
|
||||
renderWizard: ({ previewLayers, mapColors }: RenderWizardArguments) => {
|
||||
const onSourceConfigChange = (sourceConfig: unknown) => {
|
||||
const onSourceConfigChange = (sourceConfig: { name: string }) => {
|
||||
const sourceDescriptor = KibanaRegionmapSource.createDescriptor(sourceConfig);
|
||||
const layerDescriptor = VectorLayer.createDescriptor({ sourceDescriptor }, mapColors);
|
||||
previewLayers([layerDescriptor]);
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -4,29 +4,38 @@
|
|||
* 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 { AbstractVectorSource, GeoJsonWithMeta } from '../vector_source';
|
||||
import { getKibanaRegionList } from '../../../meta';
|
||||
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 { 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', {
|
||||
defaultMessage: 'Configured GeoJSON',
|
||||
});
|
||||
|
||||
export class KibanaRegionmapSource extends AbstractVectorSource {
|
||||
static type = SOURCE_TYPES.REGIONMAP_FILE;
|
||||
readonly _descriptor: KibanaRegionmapSourceDescriptor;
|
||||
|
||||
static createDescriptor({ name }) {
|
||||
static createDescriptor({ name }: { name: string }): KibanaRegionmapSourceDescriptor {
|
||||
return {
|
||||
type: KibanaRegionmapSource.type,
|
||||
name: name,
|
||||
type: SOURCE_TYPES.REGIONMAP_FILE,
|
||||
name,
|
||||
};
|
||||
}
|
||||
|
||||
createField({ fieldName }) {
|
||||
constructor(descriptor: KibanaRegionmapSourceDescriptor, inspectorAdapters?: Adapters) {
|
||||
super(descriptor, inspectorAdapters);
|
||||
this._descriptor = descriptor;
|
||||
}
|
||||
|
||||
createField({ fieldName }: { fieldName: string }): KibanaRegionField {
|
||||
return new KibanaRegionField({
|
||||
fieldName,
|
||||
source: this,
|
||||
|
@ -49,10 +58,12 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
|
|||
];
|
||||
}
|
||||
|
||||
async getVectorFileMeta() {
|
||||
const regionList = getKibanaRegionList();
|
||||
const meta = regionList.find((source) => source.name === this._descriptor.name);
|
||||
if (!meta) {
|
||||
async getVectorFileMeta(): Promise<LayerConfig> {
|
||||
const regionList: LayerConfig[] = getKibanaRegionList();
|
||||
const layerConfig: LayerConfig | undefined = regionList.find(
|
||||
(regionConfig: LayerConfig) => regionConfig.name === this._descriptor.name
|
||||
);
|
||||
if (!layerConfig) {
|
||||
throw new Error(
|
||||
i18n.translate('xpack.maps.source.kbnRegionMap.noConfigErrorMessage', {
|
||||
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 featureCollection = await AbstractVectorSource.getGeoJson({
|
||||
format: vectorFileMeta.format.type,
|
||||
format: vectorFileMeta.format.type as FORMAT_TYPE,
|
||||
featureCollectionPath: vectorFileMeta.meta.feature_collection_path,
|
||||
fetchUrl: vectorFileMeta.url,
|
||||
});
|
||||
|
@ -78,12 +89,16 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
|
|||
};
|
||||
}
|
||||
|
||||
async getLeftJoinFields() {
|
||||
const vectorFileMeta = await this.getVectorFileMeta();
|
||||
return vectorFileMeta.fields.map((f) => this.createField({ fieldName: f.name }));
|
||||
async getLeftJoinFields(): Promise<IField[]> {
|
||||
const vectorFileMeta: LayerConfig = await this.getVectorFileMeta();
|
||||
return vectorFileMeta.fields.map(
|
||||
(field): KibanaRegionField => {
|
||||
return this.createField({ fieldName: field.name });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async getDisplayName() {
|
||||
async getDisplayName(): Promise<string> {
|
||||
return this._descriptor.name;
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ import {
|
|||
import { MVTField } from '../../fields/mvt_field';
|
||||
import { UpdateSourceEditor } from './update_source_editor';
|
||||
import { ITooltipProperty, TooltipProperty } from '../../tooltips/tooltip_property';
|
||||
import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters';
|
||||
|
||||
export const sourceTitle = i18n.translate(
|
||||
'xpack.maps.source.MVTSingleLayerVectorSource.sourceTitle',
|
||||
|
@ -66,7 +67,7 @@ export class MVTSingleLayerVectorSource
|
|||
|
||||
constructor(
|
||||
sourceDescriptor: TiledSingleLayerVectorSourceDescriptor,
|
||||
inspectorAdapters?: object
|
||||
inspectorAdapters?: Adapters
|
||||
) {
|
||||
super(sourceDescriptor, inspectorAdapters);
|
||||
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];
|
||||
}
|
||||
|
||||
canFormatFeatureProperties() {
|
||||
canFormatFeatureProperties(): boolean {
|
||||
return !!this._tooltipFields.length;
|
||||
}
|
||||
|
||||
getMinZoom() {
|
||||
getMinZoom(): number {
|
||||
return this._descriptor.minSourceZoom;
|
||||
}
|
||||
|
||||
getMaxZoom() {
|
||||
getMaxZoom(): number {
|
||||
return this._descriptor.maxSourceZoom;
|
||||
}
|
||||
|
||||
getBoundsForFilters(
|
||||
async getBoundsForFilters(
|
||||
boundsFilters: BoundsFilters,
|
||||
registerCancelCallback: (callback: () => void) => void
|
||||
): MapExtent | null {
|
||||
): Promise<MapExtent | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import { ReactElement } from 'react';
|
||||
|
||||
import { Adapters } from 'src/plugins/inspector/public';
|
||||
import { GeoJsonProperties } from 'geojson';
|
||||
import { copyPersistentState } from '../../reducers/util';
|
||||
|
||||
import { IField } from '../fields/field';
|
||||
|
@ -62,7 +63,7 @@ export interface ISource {
|
|||
getIndexPatternIds(): string[];
|
||||
getQueryableIndexPatternIds(): string[];
|
||||
getGeoGridPrecision(zoom: number): number;
|
||||
getPreIndexedShape(): Promise<PreIndexedShape | null>;
|
||||
getPreIndexedShape(properties: GeoJsonProperties): Promise<PreIndexedShape | null>;
|
||||
createFieldFormatter(field: IField): Promise<FieldFormatter | null>;
|
||||
getValueSuggestions(field: IField, query: string): Promise<string[]>;
|
||||
getMinZoom(): number;
|
||||
|
@ -72,7 +73,7 @@ export interface ISource {
|
|||
|
||||
export class AbstractSource implements ISource {
|
||||
readonly _descriptor: AbstractSourceDescriptor;
|
||||
readonly _inspectorAdapters?: Adapters | undefined;
|
||||
private readonly _inspectorAdapters?: Adapters;
|
||||
|
||||
constructor(descriptor: AbstractSourceDescriptor, inspectorAdapters?: Adapters) {
|
||||
this._descriptor = descriptor;
|
||||
|
@ -153,7 +154,7 @@ export class AbstractSource implements ISource {
|
|||
return false;
|
||||
}
|
||||
|
||||
getJoinsDisabledReason() {
|
||||
getJoinsDisabledReason(): string | 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
|
||||
async getPreIndexedShape(/* properties */): Promise<PreIndexedShape | null> {
|
||||
async getPreIndexedShape(properties: GeoJsonProperties): Promise<PreIndexedShape | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -183,11 +184,11 @@ export class AbstractSource implements ISource {
|
|||
return false;
|
||||
}
|
||||
|
||||
getMinZoom() {
|
||||
getMinZoom(): number {
|
||||
return MIN_ZOOM;
|
||||
}
|
||||
|
||||
getMaxZoom() {
|
||||
getMaxZoom(): number {
|
||||
return MAX_ZOOM;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
/* eslint-disable @typescript-eslint/consistent-type-definitions */
|
||||
|
||||
import { ISource } from './source';
|
||||
import { Adapters } from '../../../../../../src/plugins/inspector/common/adapters';
|
||||
|
||||
export type SourceRegistryEntry = {
|
||||
ConstructorFunction: new (
|
||||
sourceDescriptor: any, // this is the source-descriptor that corresponds specifically to the particular ISource instance
|
||||
inspectorAdapters?: object
|
||||
inspectorAdapters?: Adapters
|
||||
) => ISource;
|
||||
type: string;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 {};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 !== '';
|
||||
}
|
|
@ -29,8 +29,9 @@ import {
|
|||
getKibanaVersion,
|
||||
} from './kibana_services';
|
||||
import { getLicenseId } from './licensed_features';
|
||||
import { LayerConfig } from '../../../../src/plugins/region_map/config';
|
||||
|
||||
export function getKibanaRegionList(): unknown[] {
|
||||
export function getKibanaRegionList(): LayerConfig[] {
|
||||
return getRegionmapLayers();
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
import { extractFeaturesFromFilters } from '../../common/elasticsearch_util';
|
||||
import { MapStoreState } from '../reducers/store';
|
||||
import {
|
||||
AbstractSourceDescriptor,
|
||||
DataRequestDescriptor,
|
||||
DrawState,
|
||||
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);
|
||||
if (!source) {
|
||||
throw new Error(`Unrecognized sourceType ${sourceDescriptor.type}`);
|
||||
|
|
Loading…
Reference in a new issue