mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* Convert to typescript * Move related files directly into plugin * Implement toExpressionAst * Remove build_pipeline dedicated fn * Async import converter * Create a custom renderer * Remove ExprVis instance usage in maps visualizations * Use uiState updates * Create wrapper component * Update rendering * Create region map expression renderer * Remove resize subscription * Fix custom visualization expression * Update interpreter functional tests * Use types from geojson Co-authored-by: Alexey Antonov <alexwizp@gmail.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Alexey Antonov <alexwizp@gmail.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
52710be7ad
commit
ae139f927f
53 changed files with 841 additions and 387 deletions
|
@ -110,7 +110,7 @@ export class ExpressionRenderHandler {
|
|||
};
|
||||
}
|
||||
|
||||
render = async (value: any, uiState: any = {}) => {
|
||||
render = async (value: any, uiState?: any) => {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return this.handleRenderError(new Error('invalid data provided to the expression renderer'));
|
||||
}
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { TmsLayer } from '../../index';
|
||||
import { MapTypes } from './map_types';
|
||||
import { TmsLayer } from '..';
|
||||
|
||||
export interface WMSOptions {
|
||||
selectedTmsLayer?: TmsLayer;
|
||||
|
@ -33,15 +32,3 @@ export interface WMSOptions {
|
|||
styles?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TileMapVisParams {
|
||||
colorSchema: string;
|
||||
mapType: MapTypes;
|
||||
isDesaturated: boolean;
|
||||
addTooltip: boolean;
|
||||
heatClusterSize: number;
|
||||
legendPosition: 'bottomright' | 'bottomleft' | 'topright' | 'topleft';
|
||||
mapZoom: number;
|
||||
mapCenter: [number, number];
|
||||
wms: WMSOptions;
|
||||
}
|
|
@ -22,7 +22,7 @@ import { EuiLink, EuiSpacer, EuiText, EuiScreenReaderOnly } from '@elastic/eui';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { TextInputOption } from '../../../vis_default_editor/public';
|
||||
import { WMSOptions } from '../common/types/external_basemap_types';
|
||||
import { WMSOptions } from '../common/types';
|
||||
|
||||
interface WmsInternalOptions {
|
||||
wms: WMSOptions;
|
||||
|
|
|
@ -23,20 +23,19 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { TmsLayer } from '../index';
|
||||
import { Vis } from '../../../visualizations/public';
|
||||
import { RegionMapVisParams } from '../common/types/region_map_types';
|
||||
import { SelectOption, SwitchOption } from '../../../vis_default_editor/public';
|
||||
import { WmsInternalOptions } from './wms_internal_options';
|
||||
import { WMSOptions, TileMapVisParams } from '../common/types/external_basemap_types';
|
||||
import { WMSOptions } from '../common/types';
|
||||
|
||||
interface Props {
|
||||
stateParams: TileMapVisParams | RegionMapVisParams;
|
||||
interface Props<K> {
|
||||
stateParams: K;
|
||||
setValue: (title: 'wms', options: WMSOptions) => void;
|
||||
vis: Vis;
|
||||
}
|
||||
|
||||
const mapLayerForOption = ({ id }: TmsLayer) => ({ text: id, value: id });
|
||||
|
||||
function WmsOptions({ stateParams, setValue, vis }: Props) {
|
||||
function WmsOptions<K extends { wms: WMSOptions }>({ stateParams, setValue, vis }: Props<K>) {
|
||||
const { wms } = stateParams;
|
||||
const { tmsLayers } = vis.type.editorConfig.collections;
|
||||
const tmsLayerOptions = useMemo(() => tmsLayers.map(mapLayerForOption), [tmsLayers]);
|
||||
|
|
|
@ -17,17 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import { PluginInitializerContext } from 'kibana/public';
|
||||
import { MapsLegacyPlugin } from './plugin';
|
||||
// @ts-ignore
|
||||
import * as colorUtil from './map/color_util';
|
||||
// @ts-ignore
|
||||
import { KibanaMapLayer } from './map/kibana_map_layer';
|
||||
// @ts-ignore
|
||||
import { convertToGeoJson } from './map/convert_to_geojson';
|
||||
// @ts-ignore
|
||||
import { getPrecision, geoContains } from './map/decode_geo_hash';
|
||||
import {
|
||||
VectorLayer,
|
||||
FileLayerField,
|
||||
|
@ -46,10 +41,7 @@ export function plugin(initializerContext: PluginInitializerContext) {
|
|||
|
||||
/** @public */
|
||||
export {
|
||||
getPrecision,
|
||||
geoContains,
|
||||
colorUtil,
|
||||
convertToGeoJson,
|
||||
IServiceSettings,
|
||||
KibanaMapLayer,
|
||||
VectorLayer,
|
||||
|
|
|
@ -34,8 +34,9 @@ export function BaseMapsVisualizationProvider() {
|
|||
* @constructor
|
||||
*/
|
||||
return class BaseMapsVisualization {
|
||||
constructor(element, vis) {
|
||||
this.vis = vis;
|
||||
constructor(element, handlers, initialVisParams) {
|
||||
this.handlers = handlers;
|
||||
this._params = initialVisParams;
|
||||
this._container = element;
|
||||
this._kibanaMap = null;
|
||||
this._chartData = null; //reference to data currently on the map.
|
||||
|
@ -61,25 +62,31 @@ export function BaseMapsVisualizationProvider() {
|
|||
* @param status
|
||||
* @return {Promise}
|
||||
*/
|
||||
async render(esResponse, visParams) {
|
||||
async render(esResponse = this._esResponse, visParams = this._params) {
|
||||
await this._mapIsLoaded;
|
||||
|
||||
if (!this._kibanaMap) {
|
||||
//the visualization has been destroyed;
|
||||
return;
|
||||
}
|
||||
|
||||
await this._mapIsLoaded;
|
||||
this._kibanaMap.resize();
|
||||
this.resize();
|
||||
this._params = visParams;
|
||||
await this._updateParams();
|
||||
|
||||
if (this._hasESResponseChanged(esResponse)) {
|
||||
this._esResponse = esResponse;
|
||||
await this._updateData(esResponse);
|
||||
}
|
||||
this._kibanaMap.useUiStateFromVisualization(this.vis);
|
||||
this._kibanaMap.useUiStateFromVisualization(this.handlers.uiState);
|
||||
|
||||
await this._whenBaseLayerIsLoaded();
|
||||
}
|
||||
|
||||
resize() {
|
||||
this._kibanaMap?.resize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of a kibana-map with a single baselayer and assigns it to the this._kibanaMap property.
|
||||
* Clients can override this method to customize the initialization.
|
||||
|
@ -87,11 +94,11 @@ export function BaseMapsVisualizationProvider() {
|
|||
*/
|
||||
async _makeKibanaMap() {
|
||||
const options = {};
|
||||
const uiState = this.vis.getUiState();
|
||||
const zoomFromUiState = parseInt(uiState.get('mapZoom'));
|
||||
const centerFromUIState = uiState.get('mapCenter');
|
||||
options.zoom = !isNaN(zoomFromUiState) ? zoomFromUiState : this.vis.params.mapZoom;
|
||||
options.center = centerFromUIState ? centerFromUIState : this.vis.params.mapCenter;
|
||||
const zoomFromUiState = parseInt(this.handlers.uiState?.get('mapZoom'));
|
||||
const centerFromUIState = this.handlers.uiState?.get('mapCenter');
|
||||
const { mapZoom, mapCenter } = this._getMapsParams();
|
||||
options.zoom = !isNaN(zoomFromUiState) ? zoomFromUiState : mapZoom;
|
||||
options.center = centerFromUIState ? centerFromUIState : mapCenter;
|
||||
|
||||
const modules = await lazyLoadMapsLegacyModules();
|
||||
this._kibanaMap = new modules.KibanaMap(this._container, options);
|
||||
|
@ -100,7 +107,7 @@ export function BaseMapsVisualizationProvider() {
|
|||
|
||||
this._kibanaMap.addLegendControl();
|
||||
this._kibanaMap.addFitControl();
|
||||
this._kibanaMap.persistUiStateForVisualization(this.vis);
|
||||
this._kibanaMap.persistUiStateForVisualization(this.handlers.uiState);
|
||||
|
||||
this._kibanaMap.on('baseLayer:loaded', () => {
|
||||
this._baseLayerDirty = false;
|
||||
|
@ -212,7 +219,7 @@ export function BaseMapsVisualizationProvider() {
|
|||
}
|
||||
|
||||
_hasESResponseChanged(data) {
|
||||
return this._chartData !== data;
|
||||
return this._esResponse !== data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -223,15 +230,11 @@ export function BaseMapsVisualizationProvider() {
|
|||
await this._updateBaseLayer();
|
||||
this._kibanaMap.setLegendPosition(mapParams.legendPosition);
|
||||
this._kibanaMap.setShowTooltip(mapParams.addTooltip);
|
||||
this._kibanaMap.useUiStateFromVisualization(this.vis);
|
||||
this._kibanaMap.useUiStateFromVisualization(this.handlers.uiState);
|
||||
}
|
||||
|
||||
_getMapsParams() {
|
||||
return {
|
||||
...this.vis.type.visConfig.defaults,
|
||||
type: this.vis.type.name,
|
||||
...this._params,
|
||||
};
|
||||
return this._params;
|
||||
}
|
||||
|
||||
_whenBaseLayerIsLoaded() {
|
||||
|
|
27
src/plugins/maps_legacy/public/map/geohash_columns.test.ts
Normal file
27
src/plugins/maps_legacy/public/map/geohash_columns.test.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { geohashColumns } from './geohash_columns';
|
||||
|
||||
test('geohashColumns', () => {
|
||||
expect(geohashColumns(1)).toBe(8);
|
||||
expect(geohashColumns(2)).toBe(8 * 4);
|
||||
expect(geohashColumns(3)).toBe(8 * 4 * 8);
|
||||
expect(geohashColumns(4)).toBe(8 * 4 * 8 * 4);
|
||||
});
|
38
src/plugins/maps_legacy/public/map/geohash_columns.ts
Normal file
38
src/plugins/maps_legacy/public/map/geohash_columns.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export function geohashColumns(precision: number): number {
|
||||
return geohashCells(precision, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of geohash cells for a given precision
|
||||
*
|
||||
* @param {number} precision the geohash precision (1<=precision<=12).
|
||||
* @param {number} axis constant for the axis 0=lengthwise (ie. columns, along longitude), 1=heightwise (ie. rows, along latitude).
|
||||
* @returns {number} Number of geohash cells (rows or columns) at that precision
|
||||
*/
|
||||
function geohashCells(precision: number, axis: number) {
|
||||
let cells = 1;
|
||||
for (let i = 1; i <= precision; i += 1) {
|
||||
/* On odd precisions, rows divide by 4 and columns by 8. Vice-versa on even precisions */
|
||||
cells *= i % 2 === axis ? 4 : 8;
|
||||
}
|
||||
return cells;
|
||||
}
|
|
@ -672,14 +672,13 @@ export class KibanaMap extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
persistUiStateForVisualization(visualization) {
|
||||
persistUiStateForVisualization(uiState) {
|
||||
function persistMapStateInUiState() {
|
||||
const uiState = visualization.getUiState();
|
||||
const centerFromUIState = uiState.get('mapCenter');
|
||||
const zoomFromUiState = parseInt(uiState.get('mapZoom'));
|
||||
|
||||
if (isNaN(zoomFromUiState) || this.getZoomLevel() !== zoomFromUiState) {
|
||||
visualization.uiStateVal('mapZoom', this.getZoomLevel());
|
||||
uiState.set('mapZoom', this.getZoomLevel());
|
||||
}
|
||||
const centerFromMap = this.getCenter();
|
||||
if (
|
||||
|
@ -687,24 +686,17 @@ export class KibanaMap extends EventEmitter {
|
|||
centerFromMap.lon !== centerFromUIState[1] ||
|
||||
centerFromMap.lat !== centerFromUIState[0]
|
||||
) {
|
||||
visualization.uiStateVal('mapCenter', [centerFromMap.lat, centerFromMap.lon]);
|
||||
uiState.set('mapCenter', [centerFromMap.lat, centerFromMap.lon]);
|
||||
}
|
||||
}
|
||||
|
||||
this._leafletMap.on('resize', () => {
|
||||
visualization.sessionState.mapBounds = this.getBounds();
|
||||
});
|
||||
this._leafletMap.on('load', () => {
|
||||
visualization.sessionState.mapBounds = this.getBounds();
|
||||
});
|
||||
this.on('dragend', persistMapStateInUiState);
|
||||
this.on('zoomend', persistMapStateInUiState);
|
||||
}
|
||||
|
||||
useUiStateFromVisualization(visualization) {
|
||||
const uiState = visualization.getUiState();
|
||||
const zoomFromUiState = parseInt(uiState.get('mapZoom'));
|
||||
const centerFromUIState = uiState.get('mapCenter');
|
||||
useUiStateFromVisualization(uiState) {
|
||||
const zoomFromUiState = parseInt(uiState?.get('mapZoom'));
|
||||
const centerFromUIState = uiState?.get('mapCenter');
|
||||
if (!isNaN(zoomFromUiState)) {
|
||||
this.setZoomLevel(zoomFromUiState);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
// @ts-ignore
|
||||
import { getUiSettings } from '../kibana_services';
|
||||
import { geohashColumns } from './decode_geo_hash';
|
||||
import { geohashColumns } from './geohash_columns';
|
||||
|
||||
/**
|
||||
* Get the number of geohash columns (world-wide) for a given precision
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { geohashColumns } from './decode_geo_hash';
|
||||
import { geohashColumns } from './geohash_columns';
|
||||
|
||||
const defaultMaxPrecision = 12;
|
||||
const minGeoHashPixels = 16;
|
||||
|
|
|
@ -2,12 +2,9 @@
|
|||
|
||||
exports[`interpreter/functions#regionmap returns an object with the correct structure 1`] = `
|
||||
Object {
|
||||
"as": "visualization",
|
||||
"as": "region_map_vis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
"visConfig": Object {
|
||||
"addTooltip": true,
|
||||
"colorSchema": "Yellow to Red",
|
29
src/plugins/region_map/public/components/index.tsx
Normal file
29
src/plugins/region_map/public/components/index.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { lazy } from 'react';
|
||||
import { IServiceSettings } from 'src/plugins/maps_legacy/public';
|
||||
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
|
||||
import { RegionMapVisParams } from '../region_map_types';
|
||||
|
||||
const RegionMapOptions = lazy(() => import('./region_map_options'));
|
||||
|
||||
export const createRegionMapOptions = (getServiceSettings: () => Promise<IServiceSettings>) => (
|
||||
props: VisOptionsProps<RegionMapVisParams>
|
||||
) => <RegionMapOptions {...props} getServiceSettings={getServiceSettings} />;
|
|
@ -24,7 +24,8 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
|
||||
import { FileLayerField, VectorLayer, IServiceSettings } from '../../../maps_legacy/public';
|
||||
import { SelectOption, SwitchOption, NumberInputOption } from '../../../vis_default_editor/public';
|
||||
import { RegionMapVisParams, WmsOptions } from '../../../maps_legacy/public';
|
||||
import { WmsOptions } from '../../../maps_legacy/public';
|
||||
import { RegionMapVisParams } from '../region_map_types';
|
||||
|
||||
const mapLayerForOption = ({ layerId, name }: VectorLayer) => ({
|
||||
text: name,
|
||||
|
@ -212,4 +213,6 @@ function RegionMapOptions(props: RegionMapOptionsProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export { RegionMapOptions };
|
||||
// default export required for React.Lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { RegionMapOptions as default };
|
||||
|
|
|
@ -44,9 +44,10 @@ import { RegionMapsConfigType } from './index';
|
|||
import { MapsLegacyConfig } from '../../maps_legacy/config';
|
||||
import { KibanaLegacyStart } from '../../kibana_legacy/public';
|
||||
import { SharePluginStart } from '../../share/public';
|
||||
import { getRegionMapRenderer } from './region_map_renderer';
|
||||
|
||||
/** @private */
|
||||
interface RegionMapVisualizationDependencies {
|
||||
export interface RegionMapVisualizationDependencies {
|
||||
uiSettings: IUiSettingsClient;
|
||||
regionmapsConfig: RegionMapsConfig;
|
||||
getServiceSettings: () => Promise<IServiceSettings>;
|
||||
|
@ -107,6 +108,7 @@ export class RegionMapPlugin implements Plugin<RegionMapPluginSetup, RegionMapPl
|
|||
};
|
||||
|
||||
expressions.registerFunction(createRegionMapFn);
|
||||
expressions.registerRenderer(getRegionMapRenderer(visualizationDependencies));
|
||||
|
||||
visualizations.createBaseVisualization(
|
||||
createRegionMapTypeDefinition(visualizationDependencies)
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line
|
||||
import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils';
|
||||
import { createRegionMapFn } from './region_map_fn';
|
||||
|
||||
|
@ -57,11 +56,7 @@ describe('interpreter/functions#regionmap', () => {
|
|||
};
|
||||
|
||||
it('returns an object with the correct structure', () => {
|
||||
const actual = fn(
|
||||
context,
|
||||
{ visConfig: JSON.stringify(visConfig) },
|
||||
{ logDatatable: jest.fn() }
|
||||
);
|
||||
const actual = fn(context, { visConfig: JSON.stringify(visConfig) });
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -19,7 +19,27 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const createRegionMapFn = () => ({
|
||||
import type { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public';
|
||||
import { RegionMapVisConfig } from './region_map_types';
|
||||
|
||||
interface Arguments {
|
||||
visConfig: string | null;
|
||||
}
|
||||
|
||||
export interface RegionMapVisRenderValue {
|
||||
visData: Datatable;
|
||||
visType: 'region_map';
|
||||
visConfig: RegionMapVisConfig;
|
||||
}
|
||||
|
||||
export type RegionMapExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
'regionmap',
|
||||
Datatable,
|
||||
Arguments,
|
||||
Render<RegionMapVisRenderValue>
|
||||
>;
|
||||
|
||||
export const createRegionMapFn = (): RegionMapExpressionFunctionDefinition => ({
|
||||
name: 'regionmap',
|
||||
type: 'render',
|
||||
context: {
|
||||
|
@ -32,24 +52,22 @@ export const createRegionMapFn = () => ({
|
|||
visConfig: {
|
||||
types: ['string', 'null'],
|
||||
default: '"{}"',
|
||||
help: '',
|
||||
},
|
||||
},
|
||||
fn(context, args, handlers) {
|
||||
const visConfig = JSON.parse(args.visConfig);
|
||||
const visConfig = args.visConfig && JSON.parse(args.visConfig);
|
||||
|
||||
if (handlers?.inspectorAdapters?.tables) {
|
||||
handlers.inspectorAdapters.tables.logDatatable('default', context);
|
||||
}
|
||||
return {
|
||||
type: 'render',
|
||||
as: 'visualization',
|
||||
as: 'region_map_vis',
|
||||
value: {
|
||||
visData: context,
|
||||
visType: 'region_map',
|
||||
visConfig,
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
52
src/plugins/region_map/public/region_map_renderer.tsx
Normal file
52
src/plugins/region_map/public/region_map_renderer.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { lazy } from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
|
||||
import { ExpressionRenderDefinition } from 'src/plugins/expressions';
|
||||
import { VisualizationContainer } from '../../visualizations/public';
|
||||
import { RegionMapVisualizationDependencies } from './plugin';
|
||||
import { RegionMapVisRenderValue } from './region_map_fn';
|
||||
|
||||
const RegionMapVisualization = lazy(() => import('./region_map_visualization_component'));
|
||||
|
||||
export const getRegionMapRenderer: (
|
||||
deps: RegionMapVisualizationDependencies
|
||||
) => ExpressionRenderDefinition<RegionMapVisRenderValue> = (deps) => ({
|
||||
name: 'region_map_vis',
|
||||
reuseDomNode: true,
|
||||
render: async (domNode, { visConfig, visData }, handlers) => {
|
||||
handlers.onDestroy(() => {
|
||||
unmountComponentAtNode(domNode);
|
||||
});
|
||||
|
||||
render(
|
||||
<VisualizationContainer handlers={handlers}>
|
||||
<RegionMapVisualization
|
||||
deps={deps}
|
||||
handlers={handlers}
|
||||
visConfig={visConfig}
|
||||
visData={visData}
|
||||
/>
|
||||
</VisualizationContainer>,
|
||||
domNode
|
||||
);
|
||||
},
|
||||
});
|
|
@ -16,19 +16,24 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { mapToLayerWithId } from './util';
|
||||
import { createRegionMapVisualization } from './region_map_visualization';
|
||||
import { RegionMapOptions } from './components/region_map_options';
|
||||
|
||||
import { BaseVisTypeOptions } from '../../visualizations/public';
|
||||
import { truncatedColorSchemas } from '../../charts/public';
|
||||
import { ORIGIN } from '../../maps_legacy/public';
|
||||
|
||||
import { getDeprecationMessage } from './get_deprecation_message';
|
||||
import { RegionMapVisualizationDependencies } from './plugin';
|
||||
import { createRegionMapOptions } from './components';
|
||||
import { toExpressionAst } from './to_ast';
|
||||
import { RegionMapVisParams } from './region_map_types';
|
||||
import { mapToLayerWithId } from './util';
|
||||
|
||||
export function createRegionMapTypeDefinition(dependencies) {
|
||||
const { uiSettings, regionmapsConfig, getServiceSettings } = dependencies;
|
||||
const visualization = createRegionMapVisualization(dependencies);
|
||||
|
||||
export function createRegionMapTypeDefinition({
|
||||
uiSettings,
|
||||
regionmapsConfig,
|
||||
getServiceSettings,
|
||||
}: RegionMapVisualizationDependencies): BaseVisTypeOptions<RegionMapVisParams> {
|
||||
return {
|
||||
name: 'region_map',
|
||||
getInfoMessage: getDeprecationMessage,
|
||||
|
@ -50,14 +55,11 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
|||
mapZoom: 2,
|
||||
mapCenter: [0, 0],
|
||||
outlineWeight: 1,
|
||||
showAllShapes: true, //still under consideration
|
||||
showAllShapes: true, // still under consideration
|
||||
},
|
||||
},
|
||||
visualization,
|
||||
editorConfig: {
|
||||
optionsTemplate: (props) => (
|
||||
<RegionMapOptions {...props} getServiceSettings={getServiceSettings} />
|
||||
),
|
||||
optionsTemplate: createRegionMapOptions(getServiceSettings),
|
||||
collections: {
|
||||
colorSchemas: truncatedColorSchemas,
|
||||
vectorLayers: [],
|
||||
|
@ -99,6 +101,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
|||
},
|
||||
],
|
||||
},
|
||||
toExpressionAst,
|
||||
setup: async (vis) => {
|
||||
const serviceSettings = await getServiceSettings();
|
||||
const tmsLayers = await serviceSettings.getTMSServices();
|
||||
|
@ -111,7 +114,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
|||
mapToLayerWithId.bind(null, ORIGIN.KIBANA_YML)
|
||||
);
|
||||
let selectedLayer = vectorLayers[0];
|
||||
let selectedJoinField = selectedLayer ? selectedLayer.fields[0] : null;
|
||||
let selectedJoinField = selectedLayer ? selectedLayer.fields[0] : undefined;
|
||||
if (regionmapsConfig.includeElasticMapsService) {
|
||||
const layers = await serviceSettings.getFileLayers();
|
||||
const newLayers = layers
|
||||
|
@ -132,7 +135,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
|||
vis.type.editorConfig.collections.vectorLayers = [...vectorLayers, ...newLayers];
|
||||
|
||||
[selectedLayer] = vis.type.editorConfig.collections.vectorLayers;
|
||||
selectedJoinField = selectedLayer ? selectedLayer.fields[0] : null;
|
||||
selectedJoinField = selectedLayer ? selectedLayer.fields[0] : undefined;
|
||||
|
||||
if (selectedLayer && !vis.params.selectedLayer && selectedLayer.isEMS) {
|
||||
vis.params.emsHotLink = await serviceSettings.getEMSHotLink(selectedLayer);
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { VectorLayer, FileLayerField } from '../../index';
|
||||
import { WMSOptions } from './external_basemap_types';
|
||||
import { SchemaConfig } from 'src/plugins/visualizations/public';
|
||||
import { VectorLayer, FileLayerField, WMSOptions } from '../../maps_legacy/public/index';
|
||||
|
||||
export interface RegionMapVisParams {
|
||||
readonly addTooltip: true;
|
||||
|
@ -34,3 +34,8 @@ export interface RegionMapVisParams {
|
|||
selectedJoinField?: FileLayerField;
|
||||
wms: WMSOptions;
|
||||
}
|
||||
|
||||
export interface RegionMapVisConfig extends RegionMapVisParams {
|
||||
metric: SchemaConfig;
|
||||
bucket?: SchemaConfig;
|
||||
}
|
|
@ -30,9 +30,8 @@ export function createRegionMapVisualization({
|
|||
getServiceSettings,
|
||||
}) {
|
||||
return class RegionMapsVisualization extends BaseMapsVisualization {
|
||||
constructor(container, vis) {
|
||||
super(container, vis);
|
||||
this._vis = this.vis;
|
||||
constructor(container, handlers, initialVisParams) {
|
||||
super(container, handlers, initialVisParams);
|
||||
this._choroplethLayer = null;
|
||||
this._tooltipFormatter = mapTooltipProvider(container, tooltipFormatter);
|
||||
}
|
||||
|
@ -88,7 +87,7 @@ export function createRegionMapVisualization({
|
|||
);
|
||||
}
|
||||
|
||||
this._kibanaMap.useUiStateFromVisualization(this._vis);
|
||||
this._kibanaMap.useUiStateFromVisualization(this.handlers.uiState);
|
||||
}
|
||||
|
||||
async _loadConfig(fileLayerConfig) {
|
||||
|
@ -201,11 +200,18 @@ export function createRegionMapVisualization({
|
|||
this._choroplethLayer.on('select', (event) => {
|
||||
const { rows, columns } = this._chartData;
|
||||
const rowIndex = rows.findIndex((row) => row[columns[0].id] === event);
|
||||
this._vis.API.events.filter({
|
||||
table: this._chartData,
|
||||
column: 0,
|
||||
row: rowIndex,
|
||||
value: event,
|
||||
this.handlers.event({
|
||||
name: 'filterBucket',
|
||||
data: {
|
||||
data: [
|
||||
{
|
||||
table: this._chartData,
|
||||
column: 0,
|
||||
row: rowIndex,
|
||||
value: event,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
.rgmChart__wrapper, .rgmChart {
|
||||
flex: 1 1 0;
|
||||
display: flex;
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import { EuiResizeObserver } from '@elastic/eui';
|
||||
import { throttle } from 'lodash';
|
||||
|
||||
import { IInterpreterRenderHandlers, Datatable } from 'src/plugins/expressions';
|
||||
import { PersistedState } from 'src/plugins/visualizations/public';
|
||||
import { RegionMapVisualizationDependencies } from './plugin';
|
||||
import { RegionMapVisConfig } from './region_map_types';
|
||||
// @ts-expect-error
|
||||
import { createRegionMapVisualization } from './region_map_visualization';
|
||||
|
||||
import './region_map_visualization.scss';
|
||||
|
||||
interface RegionMapVisController {
|
||||
render(visData?: Datatable, visConfig?: RegionMapVisConfig): Promise<void>;
|
||||
resize(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
interface TileMapVisualizationProps {
|
||||
deps: RegionMapVisualizationDependencies;
|
||||
handlers: IInterpreterRenderHandlers;
|
||||
visData: Datatable;
|
||||
visConfig: RegionMapVisConfig;
|
||||
}
|
||||
|
||||
const RegionMapVisualization = ({
|
||||
deps,
|
||||
handlers,
|
||||
visData,
|
||||
visConfig,
|
||||
}: TileMapVisualizationProps) => {
|
||||
const chartDiv = useRef<HTMLDivElement>(null);
|
||||
const visController = useRef<RegionMapVisController | null>(null);
|
||||
const isFirstRender = useRef(true);
|
||||
const uiState = handlers.uiState as PersistedState | undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (chartDiv.current && isFirstRender.current) {
|
||||
isFirstRender.current = false;
|
||||
const Controller = createRegionMapVisualization(deps);
|
||||
visController.current = new Controller(chartDiv.current, handlers, visConfig);
|
||||
}
|
||||
}, [deps, handlers, visConfig, visData]);
|
||||
|
||||
useEffect(() => {
|
||||
visController.current?.render(visData, visConfig).then(handlers.done);
|
||||
}, [visData, visConfig, handlers.done]);
|
||||
|
||||
useEffect(() => {
|
||||
const onUiStateChange = () => {
|
||||
visController.current?.render().then(handlers.done);
|
||||
};
|
||||
|
||||
uiState?.on('change', onUiStateChange);
|
||||
|
||||
return () => {
|
||||
uiState?.off('change', onUiStateChange);
|
||||
};
|
||||
}, [uiState, handlers.done]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
visController.current?.destroy();
|
||||
visController.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const updateChartSize = useMemo(() => throttle(() => visController.current?.resize(), 300), []);
|
||||
|
||||
return (
|
||||
<EuiResizeObserver onResize={updateChartSize}>
|
||||
{(resizeRef) => (
|
||||
<div className="rgmChart__wrapper" ref={resizeRef}>
|
||||
<div className="rgmChart" ref={chartDiv} />
|
||||
</div>
|
||||
)}
|
||||
</EuiResizeObserver>
|
||||
);
|
||||
};
|
||||
|
||||
// default export required for React.Lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { RegionMapVisualization as default };
|
59
src/plugins/region_map/public/to_ast.ts
Normal file
59
src/plugins/region_map/public/to_ast.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EsaggsExpressionFunctionDefinition,
|
||||
IndexPatternLoadExpressionFunctionDefinition,
|
||||
} from '../../data/public';
|
||||
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
|
||||
import { getVisSchemas, Vis, BuildPipelineParams } from '../../visualizations/public';
|
||||
import { RegionMapExpressionFunctionDefinition } from './region_map_fn';
|
||||
import { RegionMapVisConfig, RegionMapVisParams } from './region_map_types';
|
||||
|
||||
export const toExpressionAst = (vis: Vis<RegionMapVisParams>, params: BuildPipelineParams) => {
|
||||
const esaggs = buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
|
||||
index: buildExpression([
|
||||
buildExpressionFunction<IndexPatternLoadExpressionFunctionDefinition>('indexPatternLoad', {
|
||||
id: vis.data.indexPattern!.id!,
|
||||
}),
|
||||
]),
|
||||
metricsAtAllLevels: false,
|
||||
partialRows: false,
|
||||
aggs: vis.data.aggs!.aggs.map((agg) => buildExpression(agg.toExpressionAst())),
|
||||
});
|
||||
|
||||
const schemas = getVisSchemas(vis, params);
|
||||
|
||||
const visConfig: RegionMapVisConfig = {
|
||||
...vis.params,
|
||||
metric: schemas.metric[0],
|
||||
};
|
||||
|
||||
if (schemas.segment) {
|
||||
visConfig.bucket = schemas.segment[0];
|
||||
}
|
||||
|
||||
const regionmap = buildExpressionFunction<RegionMapExpressionFunctionDefinition>('regionmap', {
|
||||
visConfig: JSON.stringify(visConfig),
|
||||
});
|
||||
|
||||
const ast = buildExpression([esaggs, regionmap]);
|
||||
|
||||
return ast.toAst();
|
||||
};
|
|
@ -2,12 +2,9 @@
|
|||
|
||||
exports[`interpreter/functions#tilemap returns an object with the correct structure 1`] = `
|
||||
Object {
|
||||
"as": "visualization",
|
||||
"as": "tile_map_vis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
"visConfig": Object {
|
||||
"addTooltip": true,
|
||||
"colorSchema": "Yellow to Red",
|
|
@ -1,15 +0,0 @@
|
|||
// SASSTODO: Does this selector exist today?
|
||||
.tilemap {
|
||||
margin-bottom: 6px;
|
||||
border: $euiBorderThin;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Visualizations have some padding by default but tilemaps look nice flush against the edge to maximize viewing
|
||||
* space.
|
||||
*/
|
||||
// SASSTODO: Does this selector exist today?
|
||||
.tile_map {
|
||||
padding: 0; /* 1. */
|
||||
}
|
25
src/plugins/tile_map/public/components/index.tsx
Normal file
25
src/plugins/tile_map/public/components/index.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { lazy } from 'react';
|
||||
import type { TileMapOptionsProps } from './tile_map_options';
|
||||
|
||||
const TileMapOptions = lazy(() => import('./tile_map_options'));
|
||||
|
||||
export const TileMapOptionsLazy = (props: TileMapOptionsProps) => <TileMapOptions {...props} />;
|
|
@ -28,7 +28,9 @@ import {
|
|||
SwitchOption,
|
||||
RangeOption,
|
||||
} from '../../../vis_default_editor/public';
|
||||
import { WmsOptions, TileMapVisParams, MapTypes } from '../../../maps_legacy/public';
|
||||
import { WmsOptions } from '../../../maps_legacy/public';
|
||||
import { TileMapVisParams } from '../types';
|
||||
import { MapTypes } from '../utils/map_types';
|
||||
|
||||
export type TileMapOptionsProps = VisOptionsProps<TileMapVisParams>;
|
||||
|
||||
|
@ -102,4 +104,6 @@ function TileMapOptions(props: TileMapOptionsProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export { TileMapOptions };
|
||||
// default export required for React.Lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { TileMapOptions as default };
|
||||
|
|
|
@ -19,11 +19,12 @@
|
|||
|
||||
import { min, isEqual } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { KibanaMapLayer, MapTypes } from '../../maps_legacy/public';
|
||||
import { KibanaMapLayer } from '../../maps_legacy/public';
|
||||
import { HeatmapMarkers } from './markers/heatmap';
|
||||
import { ScaledCirclesMarkers } from './markers/scaled_circles';
|
||||
import { ShadedCirclesMarkers } from './markers/shaded_circles';
|
||||
import { GeohashGridMarkers } from './markers/geohash_grid';
|
||||
import { MapTypes } from './utils/map_types';
|
||||
|
||||
export class GeohashLayer extends KibanaMapLayer {
|
||||
constructor(featureCollection, featureCollectionMetaData, options, zoom, kibanaMap, leaflet) {
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
// Prefix all styles with "tlm" to avoid conflicts.
|
||||
// Examples
|
||||
// tlmChart
|
||||
// tlmChart__legend
|
||||
// tlmChart__legend--small
|
||||
// tlmChart__legend-isLoading
|
||||
|
||||
@import 'tile_map';
|
|
@ -25,13 +25,6 @@ import {
|
|||
} from 'kibana/public';
|
||||
import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public';
|
||||
import { VisualizationsSetup } from '../../visualizations/public';
|
||||
// TODO: Determine why visualizations don't populate without this
|
||||
import 'angular-sanitize';
|
||||
|
||||
// @ts-ignore
|
||||
import { createTileMapFn } from './tile_map_fn';
|
||||
// @ts-ignore
|
||||
import { createTileMapTypeDefinition } from './tile_map_type';
|
||||
import { IServiceSettings, MapsLegacyPluginSetup } from '../../maps_legacy/public';
|
||||
import { DataPublicPluginStart } from '../../data/public';
|
||||
import {
|
||||
|
@ -44,12 +37,16 @@ import {
|
|||
import { KibanaLegacyStart } from '../../kibana_legacy/public';
|
||||
import { SharePluginStart } from '../../share/public';
|
||||
|
||||
import { createTileMapFn } from './tile_map_fn';
|
||||
import { createTileMapTypeDefinition } from './tile_map_type';
|
||||
import { getTileMapRenderer } from './tile_map_renderer';
|
||||
|
||||
export interface TileMapConfigType {
|
||||
tilemap: any;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
interface TileMapVisualizationDependencies {
|
||||
export interface TileMapVisualizationDependencies {
|
||||
uiSettings: IUiSettingsClient;
|
||||
getZoomPrecision: any;
|
||||
getPrecision: any;
|
||||
|
@ -98,7 +95,8 @@ export class TileMapPlugin implements Plugin<TileMapPluginSetup, TileMapPluginSt
|
|||
getServiceSettings,
|
||||
};
|
||||
|
||||
expressions.registerFunction(() => createTileMapFn(visualizationDependencies));
|
||||
expressions.registerFunction(createTileMapFn);
|
||||
expressions.registerRenderer(getTileMapRenderer(visualizationDependencies));
|
||||
|
||||
visualizations.createBaseVisualization(createTileMapTypeDefinition(visualizationDependencies));
|
||||
|
||||
|
|
|
@ -17,11 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line
|
||||
import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils';
|
||||
import { createTileMapFn } from './tile_map_fn';
|
||||
|
||||
jest.mock('../../maps_legacy/public', () => ({
|
||||
jest.mock('./utils', () => ({
|
||||
convertToGeoJson: jest.fn().mockReturnValue({
|
||||
featureCollection: {
|
||||
type: 'FeatureCollection',
|
||||
|
@ -36,7 +35,7 @@ jest.mock('../../maps_legacy/public', () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
import { convertToGeoJson } from '../../maps_legacy/public';
|
||||
import { convertToGeoJson } from './utils';
|
||||
|
||||
describe('interpreter/functions#tilemap', () => {
|
||||
const fn = functionWrapper(createTileMapFn());
|
||||
|
@ -79,18 +78,14 @@ describe('interpreter/functions#tilemap', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns an object with the correct structure', () => {
|
||||
const actual = fn(
|
||||
context,
|
||||
{ visConfig: JSON.stringify(visConfig) },
|
||||
{ logDatatable: jest.fn() }
|
||||
);
|
||||
it('returns an object with the correct structure', async () => {
|
||||
const actual = await fn(context, { visConfig: JSON.stringify(visConfig) });
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('calls response handler with correct values', () => {
|
||||
it('calls response handler with correct values', async () => {
|
||||
const { geohash, metric, geocentroid } = visConfig.dimensions;
|
||||
fn(context, { visConfig: JSON.stringify(visConfig) }, { logDatatable: jest.fn() });
|
||||
await fn(context, { visConfig: JSON.stringify(visConfig) });
|
||||
expect(convertToGeoJson).toHaveBeenCalledTimes(1);
|
||||
expect(convertToGeoJson).toHaveBeenCalledWith(context, {
|
||||
geohash,
|
|
@ -16,10 +16,30 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { convertToGeoJson } from '../../maps_legacy/public';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const createTileMapFn = () => ({
|
||||
import type { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public';
|
||||
import { TileMapVisConfig, TileMapVisData } from './types';
|
||||
|
||||
interface Arguments {
|
||||
visConfig: string | null;
|
||||
}
|
||||
|
||||
export interface TileMapVisRenderValue {
|
||||
visData: TileMapVisData;
|
||||
visType: 'tile_map';
|
||||
visConfig: TileMapVisConfig;
|
||||
}
|
||||
|
||||
export type TileMapExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
'tilemap',
|
||||
Datatable,
|
||||
Arguments,
|
||||
Promise<Render<TileMapVisRenderValue>>
|
||||
>;
|
||||
|
||||
export const createTileMapFn = (): TileMapExpressionFunctionDefinition => ({
|
||||
name: 'tilemap',
|
||||
type: 'render',
|
||||
context: {
|
||||
|
@ -32,34 +52,30 @@ export const createTileMapFn = () => ({
|
|||
visConfig: {
|
||||
types: ['string', 'null'],
|
||||
default: '"{}"',
|
||||
help: '',
|
||||
},
|
||||
},
|
||||
fn(context, args, handlers) {
|
||||
const visConfig = JSON.parse(args.visConfig);
|
||||
async fn(context, args, handlers) {
|
||||
const visConfig = args.visConfig && JSON.parse(args.visConfig);
|
||||
const { geohash, metric, geocentroid } = visConfig.dimensions;
|
||||
|
||||
const { convertToGeoJson } = await import('./utils');
|
||||
const convertedData = convertToGeoJson(context, {
|
||||
geohash,
|
||||
metric,
|
||||
geocentroid,
|
||||
});
|
||||
|
||||
if (geohash && geohash.accessor) {
|
||||
convertedData.meta.geohash = context.columns[geohash.accessor].meta;
|
||||
}
|
||||
|
||||
if (handlers?.inspectorAdapters?.tables) {
|
||||
handlers.inspectorAdapters.tables.logDatatable('default', context);
|
||||
}
|
||||
return {
|
||||
type: 'render',
|
||||
as: 'visualization',
|
||||
as: 'tile_map_vis',
|
||||
value: {
|
||||
visData: convertedData,
|
||||
visType: 'tile_map',
|
||||
visConfig,
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
52
src/plugins/tile_map/public/tile_map_renderer.tsx
Normal file
52
src/plugins/tile_map/public/tile_map_renderer.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { lazy } from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
|
||||
import { ExpressionRenderDefinition } from 'src/plugins/expressions';
|
||||
import { VisualizationContainer } from '../../visualizations/public';
|
||||
import { TileMapVisualizationDependencies } from './plugin';
|
||||
import { TileMapVisRenderValue } from './tile_map_fn';
|
||||
|
||||
const TileMapVisualization = lazy(() => import('./tile_map_visualization_component'));
|
||||
|
||||
export const getTileMapRenderer: (
|
||||
deps: TileMapVisualizationDependencies
|
||||
) => ExpressionRenderDefinition<TileMapVisRenderValue> = (deps) => ({
|
||||
name: 'tile_map_vis',
|
||||
reuseDomNode: true,
|
||||
render: async (domNode, { visConfig, visData }, handlers) => {
|
||||
handlers.onDestroy(() => {
|
||||
unmountComponentAtNode(domNode);
|
||||
});
|
||||
|
||||
render(
|
||||
<VisualizationContainer handlers={handlers}>
|
||||
<TileMapVisualization
|
||||
deps={deps}
|
||||
handlers={handlers}
|
||||
visData={visData}
|
||||
visConfig={visConfig}
|
||||
/>
|
||||
</VisualizationContainer>,
|
||||
domNode
|
||||
);
|
||||
},
|
||||
});
|
|
@ -17,17 +17,22 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { convertToGeoJson, MapTypes } from '../../maps_legacy/public';
|
||||
import { createTileMapVisualization } from './tile_map_visualization';
|
||||
import { TileMapOptions } from './components/tile_map_options';
|
||||
import { supportsCssFilters } from './css_filters';
|
||||
import { BaseVisTypeOptions } from 'src/plugins/visualizations/public';
|
||||
import { truncatedColorSchemas } from '../../charts/public';
|
||||
import { getDeprecationMessage } from './get_deprecation_message';
|
||||
|
||||
export function createTileMapTypeDefinition(dependencies) {
|
||||
const CoordinateMapsVisualization = createTileMapVisualization(dependencies);
|
||||
// @ts-expect-error
|
||||
import { supportsCssFilters } from './css_filters';
|
||||
import { TileMapOptionsLazy } from './components';
|
||||
import { getDeprecationMessage } from './get_deprecation_message';
|
||||
import { TileMapVisualizationDependencies } from './plugin';
|
||||
import { toExpressionAst } from './to_ast';
|
||||
import { TileMapVisParams } from './types';
|
||||
import { MapTypes } from './utils/map_types';
|
||||
|
||||
export function createTileMapTypeDefinition(
|
||||
dependencies: TileMapVisualizationDependencies
|
||||
): BaseVisTypeOptions<TileMapVisParams> {
|
||||
const { uiSettings, getServiceSettings } = dependencies;
|
||||
|
||||
return {
|
||||
|
@ -54,8 +59,7 @@ export function createTileMapTypeDefinition(dependencies) {
|
|||
wms: uiSettings.get('visualization:tileMap:WMSdefaults'),
|
||||
},
|
||||
},
|
||||
visualization: CoordinateMapsVisualization,
|
||||
responseHandler: convertToGeoJson,
|
||||
toExpressionAst,
|
||||
editorConfig: {
|
||||
collections: {
|
||||
colorSchemas: truncatedColorSchemas,
|
||||
|
@ -113,7 +117,7 @@ export function createTileMapTypeDefinition(dependencies) {
|
|||
],
|
||||
tmsLayers: [],
|
||||
},
|
||||
optionsTemplate: (props) => <TileMapOptions {...props} />,
|
||||
optionsTemplate: TileMapOptionsLazy,
|
||||
schemas: [
|
||||
{
|
||||
group: 'metrics',
|
|
@ -19,12 +19,9 @@
|
|||
|
||||
import { get, round } from 'lodash';
|
||||
import { getFormatService, getQueryService, getKibanaLegacy } from './services';
|
||||
import {
|
||||
geoContains,
|
||||
mapTooltipProvider,
|
||||
lazyLoadMapsLegacyModules,
|
||||
} from '../../maps_legacy/public';
|
||||
import { mapTooltipProvider, lazyLoadMapsLegacyModules } from '../../maps_legacy/public';
|
||||
import { tooltipFormatter } from './tooltip_formatter';
|
||||
import { geoContains } from './utils';
|
||||
|
||||
function scaleBounds(bounds) {
|
||||
const scale = 0.5; // scale bounds by 50%
|
||||
|
@ -57,8 +54,8 @@ export const createTileMapVisualization = (dependencies) => {
|
|||
const { getZoomPrecision, getPrecision, BaseMapsVisualization } = dependencies;
|
||||
|
||||
return class CoordinateMapsVisualization extends BaseMapsVisualization {
|
||||
constructor(element, vis) {
|
||||
super(element, vis);
|
||||
constructor(element, handlers, initialVisParams) {
|
||||
super(element, handlers, initialVisParams);
|
||||
|
||||
this._geohashLayer = null;
|
||||
this._tooltipFormatter = mapTooltipProvider(element, tooltipFormatter);
|
||||
|
@ -84,10 +81,10 @@ export const createTileMapVisualization = (dependencies) => {
|
|||
// todo: autoPrecision should be vis parameter, not aggConfig one
|
||||
const zoomPrecision = getZoomPrecision();
|
||||
updateVarsObject.data.precision = geohashAgg.sourceParams.params.autoPrecision
|
||||
? zoomPrecision[this.vis.getUiState().get('mapZoom')]
|
||||
? zoomPrecision[this.handlers.uiState.get('mapZoom')]
|
||||
: getPrecision(geohashAgg.sourceParams.params.precision);
|
||||
|
||||
this.vis.eventsSubject.next(updateVarsObject);
|
||||
this.handlers.event(updateVarsObject);
|
||||
};
|
||||
|
||||
async render(esResponse, visParams) {
|
||||
|
@ -96,13 +93,12 @@ export const createTileMapVisualization = (dependencies) => {
|
|||
}
|
||||
|
||||
async _makeKibanaMap() {
|
||||
await super._makeKibanaMap();
|
||||
await super._makeKibanaMap(this._params);
|
||||
|
||||
let previousPrecision = this._kibanaMap.getGeohashPrecision();
|
||||
let precisionChange = false;
|
||||
|
||||
const uiState = this.vis.getUiState();
|
||||
uiState.on('change', (prop) => {
|
||||
this.handlers.uiState.on('change', (prop) => {
|
||||
if (prop === 'mapZoom' || prop === 'mapCenter') {
|
||||
this.updateGeohashAgg();
|
||||
}
|
||||
|
@ -250,8 +246,6 @@ export const createTileMapVisualization = (dependencies) => {
|
|||
|
||||
const { filterManager } = getQueryService();
|
||||
filterManager.addFilters([filter]);
|
||||
|
||||
this.vis.updateState();
|
||||
}
|
||||
|
||||
_getGeoHashAgg() {
|
||||
|
|
4
src/plugins/tile_map/public/tile_map_visualization.scss
Normal file
4
src/plugins/tile_map/public/tile_map_visualization.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
.tlmChart__wrapper, .tlmChart {
|
||||
flex: 1 1 0;
|
||||
display: flex;
|
||||
}
|
103
src/plugins/tile_map/public/tile_map_visualization_component.tsx
Normal file
103
src/plugins/tile_map/public/tile_map_visualization_component.tsx
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import { EuiResizeObserver } from '@elastic/eui';
|
||||
import { throttle } from 'lodash';
|
||||
|
||||
import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
|
||||
import { PersistedState } from 'src/plugins/visualizations/public';
|
||||
import { TileMapVisualizationDependencies } from './plugin';
|
||||
import { TileMapVisConfig, TileMapVisData } from './types';
|
||||
// @ts-expect-error
|
||||
import { createTileMapVisualization } from './tile_map_visualization';
|
||||
|
||||
import './tile_map_visualization.scss';
|
||||
|
||||
interface TileMapVisController {
|
||||
render(visData?: TileMapVisData, visConfig?: TileMapVisConfig): Promise<void>;
|
||||
resize(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
interface TileMapVisualizationProps {
|
||||
deps: TileMapVisualizationDependencies;
|
||||
handlers: IInterpreterRenderHandlers;
|
||||
visData: TileMapVisData;
|
||||
visConfig: TileMapVisConfig;
|
||||
}
|
||||
|
||||
const TileMapVisualization = ({
|
||||
deps,
|
||||
handlers,
|
||||
visData,
|
||||
visConfig,
|
||||
}: TileMapVisualizationProps) => {
|
||||
const chartDiv = useRef<HTMLDivElement>(null);
|
||||
const visController = useRef<TileMapVisController | null>(null);
|
||||
const isFirstRender = useRef(true);
|
||||
const uiState = handlers.uiState as PersistedState;
|
||||
|
||||
useEffect(() => {
|
||||
if (chartDiv.current && isFirstRender.current) {
|
||||
isFirstRender.current = false;
|
||||
const Controller = createTileMapVisualization(deps);
|
||||
visController.current = new Controller(chartDiv.current, handlers, visConfig);
|
||||
}
|
||||
}, [deps, handlers, visConfig, visData]);
|
||||
|
||||
useEffect(() => {
|
||||
visController.current?.render(visData, visConfig).then(handlers.done);
|
||||
}, [visData, visConfig, handlers.done]);
|
||||
|
||||
useEffect(() => {
|
||||
const onUiStateChange = () => {
|
||||
visController.current?.render().then(handlers.done);
|
||||
};
|
||||
|
||||
uiState.on('change', onUiStateChange);
|
||||
|
||||
return () => {
|
||||
uiState.off('change', onUiStateChange);
|
||||
};
|
||||
}, [uiState, handlers.done]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
visController.current?.destroy();
|
||||
visController.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const updateChartSize = useMemo(() => throttle(() => visController.current?.resize(), 300), []);
|
||||
|
||||
return (
|
||||
<EuiResizeObserver onResize={updateChartSize}>
|
||||
{(resizeRef) => (
|
||||
<div className="tlmChart__wrapper" ref={resizeRef}>
|
||||
<div className="tlmChart" ref={chartDiv} />
|
||||
</div>
|
||||
)}
|
||||
</EuiResizeObserver>
|
||||
);
|
||||
};
|
||||
|
||||
// default export required for React.Lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { TileMapVisualization as default };
|
59
src/plugins/tile_map/public/to_ast.ts
Normal file
59
src/plugins/tile_map/public/to_ast.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EsaggsExpressionFunctionDefinition,
|
||||
IndexPatternLoadExpressionFunctionDefinition,
|
||||
} from '../../data/public';
|
||||
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
|
||||
import { getVisSchemas, Vis, BuildPipelineParams } from '../../visualizations/public';
|
||||
import { TileMapExpressionFunctionDefinition } from './tile_map_fn';
|
||||
import { TileMapVisConfig, TileMapVisParams } from './types';
|
||||
|
||||
export const toExpressionAst = (vis: Vis<TileMapVisParams>, params: BuildPipelineParams) => {
|
||||
const esaggs = buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
|
||||
index: buildExpression([
|
||||
buildExpressionFunction<IndexPatternLoadExpressionFunctionDefinition>('indexPatternLoad', {
|
||||
id: vis.data.indexPattern!.id!,
|
||||
}),
|
||||
]),
|
||||
metricsAtAllLevels: false,
|
||||
partialRows: false,
|
||||
aggs: vis.data.aggs!.aggs.map((agg) => buildExpression(agg.toExpressionAst())),
|
||||
});
|
||||
|
||||
const schemas = getVisSchemas(vis, params);
|
||||
|
||||
const visConfig: TileMapVisConfig = {
|
||||
...vis.params,
|
||||
dimensions: {
|
||||
metric: schemas.metric[0],
|
||||
geohash: schemas.segment ? schemas.segment[0] : null,
|
||||
geocentroid: schemas.geo_centroid ? schemas.geo_centroid[0] : null,
|
||||
},
|
||||
};
|
||||
|
||||
const tilemap = buildExpressionFunction<TileMapExpressionFunctionDefinition>('tilemap', {
|
||||
visConfig: JSON.stringify(visConfig),
|
||||
});
|
||||
|
||||
const ast = buildExpression([esaggs, tilemap]);
|
||||
|
||||
return ast.toAst();
|
||||
};
|
57
src/plugins/tile_map/public/types.ts
Normal file
57
src/plugins/tile_map/public/types.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { FeatureCollection } from 'geojson';
|
||||
import type { SchemaConfig } from 'src/plugins/visualizations/public';
|
||||
import type { DatatableColumnMeta } from 'src/plugins/expressions';
|
||||
import type { WMSOptions } from 'src/plugins/maps_legacy/public';
|
||||
import type { MapTypes } from './utils/map_types';
|
||||
|
||||
export interface TileMapVisData {
|
||||
featureCollection: FeatureCollection;
|
||||
meta: {
|
||||
min: number;
|
||||
max: number;
|
||||
geohash?: DatatableColumnMeta;
|
||||
geohashPrecision: number | undefined;
|
||||
geohashGridDimensionsAtEquator: [number, number] | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TileMapVisDimensions {
|
||||
metric: SchemaConfig;
|
||||
geohash: SchemaConfig | null;
|
||||
geocentroid: SchemaConfig | null;
|
||||
}
|
||||
|
||||
export interface TileMapVisParams {
|
||||
colorSchema: string;
|
||||
mapType: MapTypes;
|
||||
isDesaturated: boolean;
|
||||
addTooltip: boolean;
|
||||
heatClusterSize: number;
|
||||
legendPosition: 'bottomright' | 'bottomleft' | 'topright' | 'topleft';
|
||||
mapZoom: number;
|
||||
mapCenter: [number, number];
|
||||
wms: WMSOptions;
|
||||
}
|
||||
|
||||
export interface TileMapVisConfig extends TileMapVisParams {
|
||||
dimensions: TileMapVisDimensions;
|
||||
}
|
|
@ -17,11 +17,17 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Feature } from 'geojson';
|
||||
import type { Datatable } from '../../../expressions/public';
|
||||
import type { TileMapVisDimensions, TileMapVisData } from '../types';
|
||||
import { decodeGeoHash } from './decode_geo_hash';
|
||||
import { gridDimensions } from './grid_dimensions';
|
||||
|
||||
export function convertToGeoJson(tabifiedResponse, { geohash, geocentroid, metric }) {
|
||||
let features;
|
||||
export function convertToGeoJson(
|
||||
tabifiedResponse: Datatable,
|
||||
{ geohash, geocentroid, metric }: TileMapVisDimensions
|
||||
): TileMapVisData {
|
||||
let features: Feature[];
|
||||
let min = Infinity;
|
||||
let max = -Infinity;
|
||||
|
||||
|
@ -41,7 +47,7 @@ export function convertToGeoJson(tabifiedResponse, { geohash, geocentroid, metri
|
|||
if (!geohashValue) return false;
|
||||
const geohashLocation = decodeGeoHash(geohashValue);
|
||||
|
||||
let pointCoordinates;
|
||||
let pointCoordinates: number[];
|
||||
if (geocentroidColumn) {
|
||||
const location = row[geocentroidColumn.id];
|
||||
pointCoordinates = [location.lon, location.lat];
|
||||
|
@ -58,7 +64,7 @@ export function convertToGeoJson(tabifiedResponse, { geohash, geocentroid, metri
|
|||
|
||||
const centerLatLng = [geohashLocation.latitude[2], geohashLocation.longitude[2]];
|
||||
|
||||
if (geohash.params.useGeocentroid) {
|
||||
if (geohash?.params.useGeocentroid) {
|
||||
// see https://github.com/elastic/elasticsearch/issues/24694 for why clampGrid is used
|
||||
pointCoordinates[0] = clampGrid(
|
||||
pointCoordinates[0],
|
||||
|
@ -86,35 +92,41 @@ export function convertToGeoJson(tabifiedResponse, { geohash, geocentroid, metri
|
|||
geohash: geohashValue,
|
||||
geohash_meta: {
|
||||
center: centerLatLng,
|
||||
rectangle: rectangle,
|
||||
rectangle,
|
||||
},
|
||||
value: value,
|
||||
value,
|
||||
},
|
||||
};
|
||||
} as Feature;
|
||||
})
|
||||
.filter((row) => row);
|
||||
.filter((row): row is Feature => !!row);
|
||||
}
|
||||
} else {
|
||||
features = [];
|
||||
}
|
||||
|
||||
const featureCollection = {
|
||||
type: 'FeatureCollection',
|
||||
features: features,
|
||||
};
|
||||
|
||||
return {
|
||||
featureCollection: featureCollection,
|
||||
const convertedData: TileMapVisData = {
|
||||
featureCollection: {
|
||||
type: 'FeatureCollection',
|
||||
features,
|
||||
},
|
||||
meta: {
|
||||
min: min,
|
||||
max: max,
|
||||
geohashPrecision: geohash && geohash.params.precision,
|
||||
geohashGridDimensionsAtEquator: geohash && gridDimensions(geohash.params.precision),
|
||||
min,
|
||||
max,
|
||||
geohashPrecision: geohash?.params.precision,
|
||||
geohashGridDimensionsAtEquator: geohash?.params.precision
|
||||
? gridDimensions(geohash.params.precision)
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
if (geohash && geohash.accessor) {
|
||||
convertedData.meta.geohash = tabifiedResponse.columns[geohash.accessor].meta;
|
||||
}
|
||||
|
||||
return convertedData;
|
||||
}
|
||||
|
||||
function clampGrid(val, min, max) {
|
||||
function clampGrid(val: number, min: number, max: number) {
|
||||
if (val > max) val = max;
|
||||
else if (val < min) val = min;
|
||||
return val;
|
|
@ -17,14 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { geohashColumns, decodeGeoHash } from './decode_geo_hash';
|
||||
|
||||
test('geohashColumns', () => {
|
||||
expect(geohashColumns(1)).toBe(8);
|
||||
expect(geohashColumns(2)).toBe(8 * 4);
|
||||
expect(geohashColumns(3)).toBe(8 * 4 * 8);
|
||||
expect(geohashColumns(4)).toBe(8 * 4 * 8 * 4);
|
||||
});
|
||||
import { decodeGeoHash } from './decode_geo_hash';
|
||||
|
||||
test('decodeGeoHash', () => {
|
||||
expect(decodeGeoHash('drm3btev3e86')).toEqual({
|
|
@ -55,10 +55,11 @@ export function decodeGeoHash(geohash: string): DecodedGeoHash {
|
|||
});
|
||||
lat[2] = (lat[0] + lat[1]) / 2;
|
||||
lon[2] = (lon[0] + lon[1]) / 2;
|
||||
|
||||
return {
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
} as DecodedGeoHash;
|
||||
};
|
||||
}
|
||||
|
||||
function refineInterval(interval: number[], cd: number, mask: number) {
|
||||
|
@ -69,26 +70,6 @@ function refineInterval(interval: number[], cd: number, mask: number) {
|
|||
}
|
||||
}
|
||||
|
||||
export function geohashColumns(precision: number): number {
|
||||
return geohashCells(precision, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of geohash cells for a given precision
|
||||
*
|
||||
* @param {number} precision the geohash precision (1<=precision<=12).
|
||||
* @param {number} axis constant for the axis 0=lengthwise (ie. columns, along longitude), 1=heightwise (ie. rows, along latitude).
|
||||
* @returns {number} Number of geohash cells (rows or columns) at that precision
|
||||
*/
|
||||
function geohashCells(precision: number, axis: number) {
|
||||
let cells = 1;
|
||||
for (let i = 1; i <= precision; i += 1) {
|
||||
/* On odd precisions, rows divide by 4 and columns by 8. Vice-versa on even precisions */
|
||||
cells *= i % 2 === axis ? 4 : 8;
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
interface GeoBoundingBoxCoordinate {
|
||||
lat: number;
|
||||
lon: number;
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
// geohash precision mapping of geohash grid cell dimensions (width x height, in meters) at equator.
|
||||
// https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator
|
||||
const gridAtEquator = {
|
||||
const gridAtEquator: { [key: number]: [number, number] } = {
|
||||
1: [5009400, 4992600],
|
||||
2: [1252300, 624100],
|
||||
3: [156500, 156000],
|
||||
|
@ -34,6 +34,6 @@ const gridAtEquator = {
|
|||
12: [0.037, 0.019],
|
||||
};
|
||||
|
||||
export function gridDimensions(precision) {
|
||||
export function gridDimensions(precision: number) {
|
||||
return gridAtEquator[precision];
|
||||
}
|
|
@ -17,10 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Use * syntax so that these exports do not break when internal
|
||||
* types are stripped.
|
||||
*/
|
||||
export * from './external_basemap_types';
|
||||
export * from './map_types';
|
||||
export * from './region_map_types';
|
||||
export { convertToGeoJson } from './convert_to_geojson';
|
||||
export { geoContains } from './decode_geo_hash';
|
|
@ -1,9 +1,3 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipeline calls toExpression on vis_type if it exists 1`] = `"kibana | kibana_context | test"`;
|
||||
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function with buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"bucket\\":1}' "`;
|
||||
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function without buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}}' "`;
|
||||
|
||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tile_map function 1`] = `"tilemap visConfig='{\\"metric\\":{},\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"geohash\\":1,\\"geocentroid\\":3}}' "`;
|
||||
|
|
|
@ -17,14 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
prepareJson,
|
||||
prepareString,
|
||||
buildPipelineVisFunction,
|
||||
buildPipeline,
|
||||
SchemaConfig,
|
||||
Schemas,
|
||||
} from './build_pipeline';
|
||||
import { prepareJson, prepareString, buildPipeline } from './build_pipeline';
|
||||
import { Vis } from '..';
|
||||
import { dataPluginMock } from '../../../../plugins/data/public/mocks';
|
||||
import { parseExpression } from '../../../expressions/common';
|
||||
|
@ -74,53 +67,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('buildPipelineVisFunction', () => {
|
||||
let schemaConfig: SchemaConfig;
|
||||
let schemasDef: Schemas;
|
||||
let uiState: any;
|
||||
|
||||
beforeEach(() => {
|
||||
schemaConfig = {
|
||||
accessor: 0,
|
||||
label: '',
|
||||
format: {},
|
||||
params: {},
|
||||
aggType: '',
|
||||
};
|
||||
|
||||
schemasDef = { metric: [schemaConfig] };
|
||||
uiState = {};
|
||||
});
|
||||
|
||||
describe('handles region_map function', () => {
|
||||
it('without buckets', () => {
|
||||
const params = { metric: {} };
|
||||
const actual = buildPipelineVisFunction.region_map(params, schemasDef, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('with buckets', () => {
|
||||
const schemas = {
|
||||
...schemasDef,
|
||||
segment: [1, 2],
|
||||
};
|
||||
const actual = buildPipelineVisFunction.region_map({}, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles tile_map function', () => {
|
||||
const params = { metric: {} };
|
||||
const schemas = {
|
||||
...schemasDef,
|
||||
segment: [1, 2],
|
||||
geo_centroid: [3, 4],
|
||||
};
|
||||
const actual = buildPipelineVisFunction.tile_map(params, schemas, uiState);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildPipeline', () => {
|
||||
const dataStart = dataPluginMock.createStartContract();
|
||||
|
||||
|
|
|
@ -23,9 +23,7 @@ import {
|
|||
SerializedFieldFormat,
|
||||
} from '../../../../plugins/expressions/public';
|
||||
import { IAggConfig, search, TimefilterContract } from '../../../../plugins/data/public';
|
||||
|
||||
import { Vis, VisParams } from '../types';
|
||||
|
||||
import { Vis } from '../types';
|
||||
const { isDateHistogramBucketAggConfig } = search.aggs;
|
||||
|
||||
interface SchemaConfigParams {
|
||||
|
@ -55,25 +53,6 @@ export interface Schemas {
|
|||
// catch all for schema name
|
||||
[key: string]: any[] | undefined;
|
||||
}
|
||||
|
||||
type BuildVisFunction = (
|
||||
params: VisParams,
|
||||
schemas: Schemas,
|
||||
uiState: any,
|
||||
meta?: { savedObjectId?: string }
|
||||
) => string;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
type buildVisConfigFunction = (schemas: Schemas, visParams?: VisParams) => VisParams;
|
||||
|
||||
interface BuildPipelineVisFunction {
|
||||
[key: string]: BuildVisFunction;
|
||||
}
|
||||
|
||||
interface BuildVisConfigFunction {
|
||||
[key: string]: buildVisConfigFunction;
|
||||
}
|
||||
|
||||
export interface BuildPipelineParams {
|
||||
timefilter: TimefilterContract;
|
||||
timeRange?: any;
|
||||
|
@ -224,43 +203,6 @@ export const prepareDimension = (variable: string, data: any) => {
|
|||
return expr;
|
||||
};
|
||||
|
||||
export const buildPipelineVisFunction: BuildPipelineVisFunction = {
|
||||
region_map: (params, schemas) => {
|
||||
const visConfig = {
|
||||
...params,
|
||||
...buildVisConfig.region_map(schemas),
|
||||
};
|
||||
return `regionmap ${prepareJson('visConfig', visConfig)}`;
|
||||
},
|
||||
tile_map: (params, schemas) => {
|
||||
const visConfig = {
|
||||
...params,
|
||||
...buildVisConfig.tile_map(schemas),
|
||||
};
|
||||
return `tilemap ${prepareJson('visConfig', visConfig)}`;
|
||||
},
|
||||
};
|
||||
|
||||
const buildVisConfig: BuildVisConfigFunction = {
|
||||
region_map: (schemas) => {
|
||||
const visConfig = {} as any;
|
||||
visConfig.metric = schemas.metric[0];
|
||||
if (schemas.segment) {
|
||||
visConfig.bucket = schemas.segment[0];
|
||||
}
|
||||
return visConfig;
|
||||
},
|
||||
tile_map: (schemas) => {
|
||||
const visConfig = {} as any;
|
||||
visConfig.dimensions = {
|
||||
metric: schemas.metric[0],
|
||||
geohash: schemas.segment ? schemas.segment[0] : null,
|
||||
geocentroid: schemas.geo_centroid ? schemas.geo_centroid[0] : null,
|
||||
};
|
||||
return visConfig;
|
||||
},
|
||||
};
|
||||
|
||||
export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => {
|
||||
const { indexPattern, searchSource } = vis.data;
|
||||
const query = searchSource!.getField('query');
|
||||
|
@ -299,17 +241,8 @@ export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => {
|
|||
});
|
||||
}
|
||||
pipeline += `| `;
|
||||
}
|
||||
|
||||
const schemas = getSchemas(vis, params);
|
||||
|
||||
if (buildPipelineVisFunction[vis.type.name]) {
|
||||
pipeline += buildPipelineVisFunction[vis.type.name](
|
||||
{ title, ...vis.params },
|
||||
schemas,
|
||||
uiState
|
||||
);
|
||||
} else {
|
||||
const schemas = getSchemas(vis, params);
|
||||
const visConfig = { ...vis.params };
|
||||
visConfig.dimensions = schemas;
|
||||
visConfig.title = title;
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 10 KiB |
|
@ -1 +1 @@
|
|||
{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0},"metric":{"accessor":1,"format":{"id":"number"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"region_map"}}
|
||||
{"as":"region_map_vis","type":"render","value":{"visConfig":{"addTooltip":true,"bucket":{"accessor":0},"colorSchema":"Yellow to Red","isDisplayWarning":true,"legendPosition":"bottomright","mapCenter":[0,0],"mapZoom":2,"metric":{"accessor":1,"format":{"id":"number"}},"outlineWeight":1,"selectedJoinField":{},"selectedLayer":{},"showAllShapes":true,"wms":{}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"region_map"}}
|
|
@ -1 +1 @@
|
|||
{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0},"metric":{"accessor":1,"format":{"id":"number"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"region_map"}}
|
||||
{"as":"region_map_vis","type":"render","value":{"visConfig":{"addTooltip":true,"bucket":{"accessor":0},"colorSchema":"Yellow to Red","isDisplayWarning":true,"legendPosition":"bottomright","mapCenter":[0,0],"mapZoom":2,"metric":{"accessor":1,"format":{"id":"number"}},"outlineWeight":1,"selectedJoinField":{},"selectedLayer":{},"showAllShapes":true,"wms":{}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"region_map"}}
|
|
@ -110,7 +110,7 @@ export default function ({
|
|||
await expectExpression('partial_test_2', metricExpr, context).toMatchSnapshot()
|
||||
).toMatchScreenshot();
|
||||
|
||||
const regionMapExpr = `regionmap visConfig='{"metric":{"accessor":1,"format":{"id":"number"}},"bucket":{"accessor":0}}'`;
|
||||
const regionMapExpr = `regionmap visConfig='{"metric":{"accessor":1,"format":{"id":"number"}},"bucket":{"accessor":0},"legendPosition":"bottomright","addTooltip":true,"colorSchema":"Yellow to Red","isDisplayWarning":true,"wms":{},"mapZoom":2,"mapCenter":[0,0],"outlineWeight":1,"showAllShapes":true,"selectedLayer":{},"selectedJoinField":{}}'`;
|
||||
await (
|
||||
await expectExpression('partial_test_3', regionMapExpr, context).toMatchSnapshot()
|
||||
).toMatchScreenshot();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue