[Maps] split out DrawFilterControl and DrawControl (#95255)
* [Maps] split out DrawFilterControl and DrawControl * clean up * update i18n id * give 'global_index_pattern_management_all' permission to functional test because new check blocks access without it * revert last change Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e01f317d9c
commit
ba029aa95e
|
@ -12,20 +12,10 @@ import MapboxDraw from '@mapbox/mapbox-gl-draw';
|
|||
// @ts-expect-error
|
||||
import DrawRectangle from 'mapbox-gl-draw-rectangle-mode';
|
||||
import { Map as MbMap } from 'mapbox-gl';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Filter } from 'src/plugins/data/public';
|
||||
import { Feature, Polygon } from 'geojson';
|
||||
import { DRAW_TYPE, ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../../../../common/constants';
|
||||
import { DrawState } from '../../../../common/descriptor_types';
|
||||
import { DrawCircle, DrawCircleProperties } from './draw_circle';
|
||||
import {
|
||||
createDistanceFilterWithMeta,
|
||||
createSpatialFilterWithGeometry,
|
||||
getBoundingBoxGeometry,
|
||||
roundCoordinates,
|
||||
} from '../../../../common/elasticsearch_util';
|
||||
import { Feature } from 'geojson';
|
||||
import { DRAW_TYPE } from '../../../../common/constants';
|
||||
import { DrawCircle } from './draw_circle';
|
||||
import { DrawTooltip } from './draw_tooltip';
|
||||
import { getToasts } from '../../../kibana_services';
|
||||
|
||||
const DRAW_RECTANGLE = 'draw_rectangle';
|
||||
const DRAW_CIRCLE = 'draw_circle';
|
||||
|
@ -35,10 +25,8 @@ mbDrawModes[DRAW_RECTANGLE] = DrawRectangle;
|
|||
mbDrawModes[DRAW_CIRCLE] = DrawCircle;
|
||||
|
||||
export interface Props {
|
||||
addFilters: (filters: Filter[], actionId: string) => Promise<void>;
|
||||
disableDrawState: () => void;
|
||||
drawState?: DrawState;
|
||||
isDrawingFilter: boolean;
|
||||
drawType?: DRAW_TYPE;
|
||||
onDraw: (event: { features: Feature[] }) => void;
|
||||
mbMap: MbMap;
|
||||
}
|
||||
|
||||
|
@ -70,100 +58,26 @@ export class DrawControl extends Component<Props, {}> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.props.isDrawingFilter) {
|
||||
if (this.props.drawType) {
|
||||
this._updateDrawControl();
|
||||
} else {
|
||||
this._removeDrawControl();
|
||||
}
|
||||
}, 0);
|
||||
|
||||
_onDraw = async (e: { features: Feature[] }) => {
|
||||
if (
|
||||
!e.features.length ||
|
||||
!this.props.drawState ||
|
||||
!this.props.drawState.geoFieldName ||
|
||||
!this.props.drawState.indexPatternId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let filter: Filter | undefined;
|
||||
if (this.props.drawState.drawType === DRAW_TYPE.DISTANCE) {
|
||||
const circle = e.features[0] as Feature & { properties: DrawCircleProperties };
|
||||
const distanceKm = _.round(
|
||||
circle.properties.radiusKm,
|
||||
circle.properties.radiusKm > 10 ? 0 : 2
|
||||
);
|
||||
// Only include as much precision as needed for distance
|
||||
let precision = 2;
|
||||
if (distanceKm <= 1) {
|
||||
precision = 5;
|
||||
} else if (distanceKm <= 10) {
|
||||
precision = 4;
|
||||
} else if (distanceKm <= 100) {
|
||||
precision = 3;
|
||||
}
|
||||
filter = createDistanceFilterWithMeta({
|
||||
alias: this.props.drawState.filterLabel ? this.props.drawState.filterLabel : '',
|
||||
distanceKm,
|
||||
geoFieldName: this.props.drawState.geoFieldName,
|
||||
indexPatternId: this.props.drawState.indexPatternId,
|
||||
point: [
|
||||
_.round(circle.properties.center[0], precision),
|
||||
_.round(circle.properties.center[1], precision),
|
||||
],
|
||||
});
|
||||
} else {
|
||||
const geometry = e.features[0].geometry as Polygon;
|
||||
// MapboxDraw returns coordinates with 12 decimals. Round to a more reasonable number
|
||||
roundCoordinates(geometry.coordinates);
|
||||
|
||||
filter = createSpatialFilterWithGeometry({
|
||||
geometry:
|
||||
this.props.drawState.drawType === DRAW_TYPE.BOUNDS
|
||||
? getBoundingBoxGeometry(geometry)
|
||||
: geometry,
|
||||
indexPatternId: this.props.drawState.indexPatternId,
|
||||
geoFieldName: this.props.drawState.geoFieldName,
|
||||
geoFieldType: this.props.drawState.geoFieldType
|
||||
? this.props.drawState.geoFieldType
|
||||
: ES_GEO_FIELD_TYPE.GEO_POINT,
|
||||
geometryLabel: this.props.drawState.geometryLabel ? this.props.drawState.geometryLabel : '',
|
||||
relation: this.props.drawState.relation
|
||||
? this.props.drawState.relation
|
||||
: ES_SPATIAL_RELATIONS.INTERSECTS,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await this.props.addFilters([filter!], this.props.drawState.actionId);
|
||||
} catch (error) {
|
||||
getToasts().addWarning(
|
||||
i18n.translate('xpack.maps.drawControl.unableToCreatFilter', {
|
||||
defaultMessage: `Unable to create filter, error: '{errorMsg}'.`,
|
||||
values: {
|
||||
errorMsg: error.message,
|
||||
},
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
this.props.disableDrawState();
|
||||
}
|
||||
};
|
||||
|
||||
_removeDrawControl() {
|
||||
if (!this._mbDrawControlAdded) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.mbMap.getCanvas().style.cursor = '';
|
||||
this.props.mbMap.off('draw.create', this._onDraw);
|
||||
this.props.mbMap.off('draw.create', this.props.onDraw);
|
||||
this.props.mbMap.removeControl(this._mbDrawControl);
|
||||
this._mbDrawControlAdded = false;
|
||||
}
|
||||
|
||||
_updateDrawControl() {
|
||||
if (!this.props.drawState) {
|
||||
if (!this.props.drawType) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -171,27 +85,27 @@ export class DrawControl extends Component<Props, {}> {
|
|||
this.props.mbMap.addControl(this._mbDrawControl);
|
||||
this._mbDrawControlAdded = true;
|
||||
this.props.mbMap.getCanvas().style.cursor = 'crosshair';
|
||||
this.props.mbMap.on('draw.create', this._onDraw);
|
||||
this.props.mbMap.on('draw.create', this.props.onDraw);
|
||||
}
|
||||
|
||||
const drawMode = this._mbDrawControl.getMode();
|
||||
if (drawMode !== DRAW_RECTANGLE && this.props.drawState.drawType === DRAW_TYPE.BOUNDS) {
|
||||
if (drawMode !== DRAW_RECTANGLE && this.props.drawType === DRAW_TYPE.BOUNDS) {
|
||||
this._mbDrawControl.changeMode(DRAW_RECTANGLE);
|
||||
} else if (drawMode !== DRAW_CIRCLE && this.props.drawState.drawType === DRAW_TYPE.DISTANCE) {
|
||||
} else if (drawMode !== DRAW_CIRCLE && this.props.drawType === DRAW_TYPE.DISTANCE) {
|
||||
this._mbDrawControl.changeMode(DRAW_CIRCLE);
|
||||
} else if (
|
||||
drawMode !== this._mbDrawControl.modes.DRAW_POLYGON &&
|
||||
this.props.drawState.drawType === DRAW_TYPE.POLYGON
|
||||
this.props.drawType === DRAW_TYPE.POLYGON
|
||||
) {
|
||||
this._mbDrawControl.changeMode(this._mbDrawControl.modes.DRAW_POLYGON);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.isDrawingFilter || !this.props.drawState) {
|
||||
if (!this.props.drawType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <DrawTooltip mbMap={this.props.mbMap} drawState={this.props.drawState} />;
|
||||
return <DrawTooltip mbMap={this.props.mbMap} drawType={this.props.drawType} />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { Map as MbMap } from 'mapbox-gl';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Filter } from 'src/plugins/data/public';
|
||||
import { Feature, Polygon } from 'geojson';
|
||||
import {
|
||||
DRAW_TYPE,
|
||||
ES_GEO_FIELD_TYPE,
|
||||
ES_SPATIAL_RELATIONS,
|
||||
} from '../../../../../common/constants';
|
||||
import { DrawState } from '../../../../../common/descriptor_types';
|
||||
import {
|
||||
createDistanceFilterWithMeta,
|
||||
createSpatialFilterWithGeometry,
|
||||
getBoundingBoxGeometry,
|
||||
roundCoordinates,
|
||||
} from '../../../../../common/elasticsearch_util';
|
||||
import { getToasts } from '../../../../kibana_services';
|
||||
import { DrawControl } from '../draw_control';
|
||||
import { DrawCircleProperties } from '../draw_circle';
|
||||
|
||||
export interface Props {
|
||||
addFilters: (filters: Filter[], actionId: string) => Promise<void>;
|
||||
disableDrawState: () => void;
|
||||
drawState?: DrawState;
|
||||
isDrawingFilter: boolean;
|
||||
mbMap: MbMap;
|
||||
}
|
||||
|
||||
export class DrawFilterControl extends Component<Props, {}> {
|
||||
_onDraw = async (e: { features: Feature[] }) => {
|
||||
if (
|
||||
!e.features.length ||
|
||||
!this.props.drawState ||
|
||||
!this.props.drawState.geoFieldName ||
|
||||
!this.props.drawState.indexPatternId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let filter: Filter | undefined;
|
||||
if (this.props.drawState.drawType === DRAW_TYPE.DISTANCE) {
|
||||
const circle = e.features[0] as Feature & { properties: DrawCircleProperties };
|
||||
const distanceKm = _.round(
|
||||
circle.properties.radiusKm,
|
||||
circle.properties.radiusKm > 10 ? 0 : 2
|
||||
);
|
||||
// Only include as much precision as needed for distance
|
||||
let precision = 2;
|
||||
if (distanceKm <= 1) {
|
||||
precision = 5;
|
||||
} else if (distanceKm <= 10) {
|
||||
precision = 4;
|
||||
} else if (distanceKm <= 100) {
|
||||
precision = 3;
|
||||
}
|
||||
filter = createDistanceFilterWithMeta({
|
||||
alias: this.props.drawState.filterLabel ? this.props.drawState.filterLabel : '',
|
||||
distanceKm,
|
||||
geoFieldName: this.props.drawState.geoFieldName,
|
||||
indexPatternId: this.props.drawState.indexPatternId,
|
||||
point: [
|
||||
_.round(circle.properties.center[0], precision),
|
||||
_.round(circle.properties.center[1], precision),
|
||||
],
|
||||
});
|
||||
} else {
|
||||
const geometry = e.features[0].geometry as Polygon;
|
||||
// MapboxDraw returns coordinates with 12 decimals. Round to a more reasonable number
|
||||
roundCoordinates(geometry.coordinates);
|
||||
|
||||
filter = createSpatialFilterWithGeometry({
|
||||
geometry:
|
||||
this.props.drawState.drawType === DRAW_TYPE.BOUNDS
|
||||
? getBoundingBoxGeometry(geometry)
|
||||
: geometry,
|
||||
indexPatternId: this.props.drawState.indexPatternId,
|
||||
geoFieldName: this.props.drawState.geoFieldName,
|
||||
geoFieldType: this.props.drawState.geoFieldType
|
||||
? this.props.drawState.geoFieldType
|
||||
: ES_GEO_FIELD_TYPE.GEO_POINT,
|
||||
geometryLabel: this.props.drawState.geometryLabel ? this.props.drawState.geometryLabel : '',
|
||||
relation: this.props.drawState.relation
|
||||
? this.props.drawState.relation
|
||||
: ES_SPATIAL_RELATIONS.INTERSECTS,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await this.props.addFilters([filter!], this.props.drawState.actionId);
|
||||
} catch (error) {
|
||||
getToasts().addWarning(
|
||||
i18n.translate('xpack.maps.drawFilterControl.unableToCreatFilter', {
|
||||
defaultMessage: `Unable to create filter, error: '{errorMsg}'.`,
|
||||
values: {
|
||||
errorMsg: error.message,
|
||||
},
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
this.props.disableDrawState();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DrawControl
|
||||
drawType={
|
||||
this.props.isDrawingFilter && this.props.drawState
|
||||
? this.props.drawState.drawType
|
||||
: undefined
|
||||
}
|
||||
onDraw={this._onDraw}
|
||||
mbMap={this.props.mbMap}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { AnyAction } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { connect } from 'react-redux';
|
||||
import { DrawFilterControl } from './draw_filter_control';
|
||||
import { updateDrawState } from '../../../../actions';
|
||||
import { getDrawState, isDrawingFilter } from '../../../../selectors/map_selectors';
|
||||
import { MapStoreState } from '../../../../reducers/store';
|
||||
|
||||
function mapStateToProps(state: MapStoreState) {
|
||||
return {
|
||||
isDrawingFilter: isDrawingFilter(state),
|
||||
drawState: getDrawState(state),
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: ThunkDispatch<MapStoreState, void, AnyAction>) {
|
||||
return {
|
||||
disableDrawState() {
|
||||
dispatch(updateDrawState(null));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const connected = connect(mapStateToProps, mapDispatchToProps)(DrawFilterControl);
|
||||
export { connected as DrawFilterControl };
|
|
@ -11,13 +11,12 @@ import { EuiPopover, EuiText } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { Map as MbMap } from 'mapbox-gl';
|
||||
import { DRAW_TYPE } from '../../../../common/constants';
|
||||
import { DrawState } from '../../../../common/descriptor_types';
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
interface Props {
|
||||
mbMap: MbMap;
|
||||
drawState: DrawState;
|
||||
drawType: DRAW_TYPE;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -58,16 +57,16 @@ export class DrawTooltip extends Component<Props, State> {
|
|||
}
|
||||
|
||||
let instructions;
|
||||
if (this.props.drawState.drawType === DRAW_TYPE.BOUNDS) {
|
||||
if (this.props.drawType === DRAW_TYPE.BOUNDS) {
|
||||
instructions = i18n.translate('xpack.maps.drawTooltip.boundsInstructions', {
|
||||
defaultMessage:
|
||||
'Click to start rectangle. Move mouse to adjust rectangle size. Click again to finish.',
|
||||
});
|
||||
} else if (this.props.drawState.drawType === DRAW_TYPE.DISTANCE) {
|
||||
} else if (this.props.drawType === DRAW_TYPE.DISTANCE) {
|
||||
instructions = i18n.translate('xpack.maps.drawTooltip.distanceInstructions', {
|
||||
defaultMessage: 'Click to set point. Move mouse to adjust distance. Click to finish.',
|
||||
});
|
||||
} else if (this.props.drawState.drawType === DRAW_TYPE.POLYGON) {
|
||||
} else if (this.props.drawType === DRAW_TYPE.POLYGON) {
|
||||
instructions = i18n.translate('xpack.maps.drawTooltip.polygonInstructions', {
|
||||
defaultMessage: 'Click to start shape. Click to add vertex. Double click to finish.',
|
||||
});
|
||||
|
|
|
@ -5,28 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { AnyAction } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { connect } from 'react-redux';
|
||||
import { DrawControl } from './draw_control';
|
||||
import { updateDrawState } from '../../../actions';
|
||||
import { getDrawState, isDrawingFilter } from '../../../selectors/map_selectors';
|
||||
import { MapStoreState } from '../../../reducers/store';
|
||||
|
||||
function mapStateToProps(state: MapStoreState) {
|
||||
return {
|
||||
isDrawingFilter: isDrawingFilter(state),
|
||||
drawState: getDrawState(state),
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: ThunkDispatch<MapStoreState, void, AnyAction>) {
|
||||
return {
|
||||
disableDrawState() {
|
||||
dispatch(updateDrawState(null));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const connected = connect(mapStateToProps, mapDispatchToProps)(DrawControl);
|
||||
export { connected as DrawControl };
|
||||
export { DrawFilterControl } from './draw_filter_control';
|
||||
|
|
|
@ -17,7 +17,7 @@ import sprites2 from '@elastic/maki/dist/sprite@2.png';
|
|||
import { Adapters } from 'src/plugins/inspector/public';
|
||||
import { Filter } from 'src/plugins/data/public';
|
||||
import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public';
|
||||
import { DrawControl } from './draw_control';
|
||||
import { DrawFilterControl } from './draw_control';
|
||||
import { ScaleControl } from './scale_control';
|
||||
// @ts-expect-error
|
||||
import { TooltipControl } from './tooltip_control';
|
||||
|
@ -418,7 +418,7 @@ export class MBMap extends Component<Props, State> {
|
|||
let scaleControl;
|
||||
if (this.state.mbMap) {
|
||||
drawControl = this.props.addFilters ? (
|
||||
<DrawControl mbMap={this.state.mbMap} addFilters={this.props.addFilters} />
|
||||
<DrawFilterControl mbMap={this.state.mbMap} addFilters={this.props.addFilters} />
|
||||
) : null;
|
||||
tooltipControl = !this.props.settings.disableTooltipControl ? (
|
||||
<TooltipControl
|
||||
|
|
Loading…
Reference in a new issue