[Maps] Refactor draw filter UI to be similar to create filter from feature geometry UI (#44014) (#44200)

* refactor draw UI to use same UI as create geometry filter from features

* update FeatureGeometryFilterForm to use GeometryFilterForm

* add jest test for GeomemtryFilterForm component

* fix i18n translations

* update ToolsControl jest snapshots

* use __ in css class names
This commit is contained in:
Nathan Reese 2019-08-27 21:16:02 -06:00 committed by GitHub
parent 79be2e8141
commit 07c28b96f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 764 additions and 359 deletions

View file

@ -0,0 +1,282 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should not render relation select when geo field is geo_point 1`] = `
<EuiForm>
<EuiSpacer
size="s"
/>
<EuiFormRow
compressed={true}
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Geometry label"
labelType="label"
>
<EuiFieldText
compressed={false}
fullWidth={false}
isLoading={false}
onChange={[Function]}
value="My shape"
/>
</EuiFormRow>
<EuiFormRow
className="mapGeometryFilter__geoFieldSuperSelectWrapper"
compressed={true}
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Filtered field"
labelType="label"
>
<EuiSuperSelect
className="mapGeometryFilter__geoFieldSuperSelect"
compressed={true}
fullWidth={true}
hasDividers={true}
isInvalid={false}
itemClassName="mapGeometryFilter__geoFieldItem"
onChange={[Function]}
options={
Array [
Object {
"inputDisplay": <EuiText>
<EuiTextColor
color="subdued"
>
<small>
My index
</small>
</EuiTextColor>
<br />
my geo field
</EuiText>,
"value": "My index/my geo field",
},
]
}
valueOfSelected="My index/my geo field"
/>
</EuiFormRow>
<EuiButton
color="primary"
fill={false}
iconSide="left"
isDisabled={false}
onClick={[Function]}
size="s"
type="button"
>
Create filter
</EuiButton>
</EuiForm>
`;
exports[`should not show "within" relation when filter geometry is not closed 1`] = `
<EuiForm>
<EuiSpacer
size="s"
/>
<EuiFormRow
compressed={true}
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Geometry label"
labelType="label"
>
<EuiFieldText
compressed={false}
fullWidth={false}
isLoading={false}
onChange={[Function]}
value="My shape"
/>
</EuiFormRow>
<EuiFormRow
className="mapGeometryFilter__geoFieldSuperSelectWrapper"
compressed={true}
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Filtered field"
labelType="label"
>
<EuiSuperSelect
className="mapGeometryFilter__geoFieldSuperSelect"
compressed={true}
fullWidth={true}
hasDividers={true}
isInvalid={false}
itemClassName="mapGeometryFilter__geoFieldItem"
onChange={[Function]}
options={
Array [
Object {
"inputDisplay": <EuiText>
<EuiTextColor
color="subdued"
>
<small>
My index
</small>
</EuiTextColor>
<br />
my geo field
</EuiText>,
"value": "My index/my geo field",
},
]
}
valueOfSelected="My index/my geo field"
/>
</EuiFormRow>
<EuiFormRow
compressed={true}
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Spatial relation"
labelType="label"
>
<EuiSelect
compressed={false}
fullWidth={false}
hasNoInitialSelection={false}
isLoading={false}
onChange={[Function]}
options={
Array [
Object {
"text": "intersects",
"value": "INTERSECTS",
},
Object {
"text": "disjoint",
"value": "DISJOINT",
},
]
}
value="INTERSECTS"
/>
</EuiFormRow>
<EuiButton
color="primary"
fill={false}
iconSide="left"
isDisabled={false}
onClick={[Function]}
size="s"
type="button"
>
Create filter
</EuiButton>
</EuiForm>
`;
exports[`should render relation select when geo field is geo_shape 1`] = `
<EuiForm>
<EuiSpacer
size="s"
/>
<EuiFormRow
compressed={true}
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Geometry label"
labelType="label"
>
<EuiFieldText
compressed={false}
fullWidth={false}
isLoading={false}
onChange={[Function]}
value="My shape"
/>
</EuiFormRow>
<EuiFormRow
className="mapGeometryFilter__geoFieldSuperSelectWrapper"
compressed={true}
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Filtered field"
labelType="label"
>
<EuiSuperSelect
className="mapGeometryFilter__geoFieldSuperSelect"
compressed={true}
fullWidth={true}
hasDividers={true}
isInvalid={false}
itemClassName="mapGeometryFilter__geoFieldItem"
onChange={[Function]}
options={
Array [
Object {
"inputDisplay": <EuiText>
<EuiTextColor
color="subdued"
>
<small>
My index
</small>
</EuiTextColor>
<br />
my geo field
</EuiText>,
"value": "My index/my geo field",
},
]
}
valueOfSelected="My index/my geo field"
/>
</EuiFormRow>
<EuiFormRow
compressed={true}
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Spatial relation"
labelType="label"
>
<EuiSelect
compressed={false}
fullWidth={false}
hasNoInitialSelection={false}
isLoading={false}
onChange={[Function]}
options={
Array [
Object {
"text": "intersects",
"value": "INTERSECTS",
},
Object {
"text": "disjoint",
"value": "DISJOINT",
},
Object {
"text": "within",
"value": "WITHIN",
},
]
}
value="INTERSECTS"
/>
</EuiFormRow>
<EuiButton
color="primary"
fill={false}
iconSide="left"
isDisabled={false}
onClick={[Function]}
size="s"
type="button"
>
Create filter
</EuiButton>
</EuiForm>
`;

View file

@ -1,3 +1,15 @@
.mapMetricEditorPanel {
margin-bottom: $euiSizeS;
}
.mapGeometryFilter__geoFieldSuperSelect {
height: $euiSizeXL * 2;
}
.mapGeometryFilter__geoFieldSuperSelectWrapper {
height: $euiSizeXL * 3;
}
.mapGeometryFilter__geoFieldItem {
padding: $euiSizeXS;
}

View file

@ -0,0 +1,210 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
EuiForm,
EuiFormRow,
EuiSuperSelect,
EuiTextColor,
EuiText,
EuiFieldText,
EuiButton,
EuiSelect,
EuiSpacer,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import {
ES_GEO_FIELD_TYPE,
ES_SPATIAL_RELATIONS,
} from '../../common/constants';
import { getEsSpatialRelationLabel } from '../../common/i18n_getters';
const GEO_FIELD_VALUE_DELIMITER = '/'; // `/` is not allowed in index pattern name so should not have collisions
function createIndexGeoFieldName({ indexPatternTitle, geoFieldName }) {
return `${indexPatternTitle}${GEO_FIELD_VALUE_DELIMITER}${geoFieldName}`;
}
function splitIndexGeoFieldName(value) {
const split = value.split(GEO_FIELD_VALUE_DELIMITER);
return {
indexPatternTitle: split[0],
geoFieldName: split[1]
};
}
export class GeometryFilterForm extends Component {
static propTypes = {
buttonLabel: PropTypes.string.isRequired,
geoFields: PropTypes.array.isRequired,
intitialGeometryLabel: PropTypes.string.isRequired,
onSubmit: PropTypes.func.isRequired,
isFilterGeometryClosed: PropTypes.bool,
};
static defaultProps = {
isFilterGeometryClosed: true,
};
state = {
geoFieldTag: this.props.geoFields.length ? createIndexGeoFieldName(this.props.geoFields[0]) : '',
geometryLabel: this.props.intitialGeometryLabel,
relation: ES_SPATIAL_RELATIONS.INTERSECTS,
};
_getSelectedGeoField = () => {
if (!this.state.geoFieldTag) {
return null;
}
const {
indexPatternTitle,
geoFieldName
} = splitIndexGeoFieldName(this.state.geoFieldTag);
return this.props.geoFields.find(option => {
return option.indexPatternTitle === indexPatternTitle
&& option.geoFieldName === geoFieldName;
});
}
_onGeoFieldChange = selectedValue => {
this.setState({ geoFieldTag: selectedValue });
}
_onGeometryLabelChange = e => {
this.setState({
geometryLabel: e.target.value,
});
}
_onRelationChange = e => {
this.setState({
relation: e.target.value,
});
}
_onSubmit = () => {
const geoField = this._getSelectedGeoField();
this.props.onSubmit({
geometryLabel: this.state.geometryLabel,
indexPatternId: geoField.indexPatternId,
geoFieldName: geoField.geoFieldName,
geoFieldType: geoField.geoFieldType,
relation: this.state.relation,
});
}
_renderRelationInput() {
if (!this.state.geoFieldTag) {
return null;
}
const { geoFieldType } = this._getSelectedGeoField();
// relationship only used when filtering geo_shape fields
if (geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT) {
return null;
}
const spatialRelations = this.props.isFilterGeometryClosed
? Object.values(ES_SPATIAL_RELATIONS)
: Object.values(ES_SPATIAL_RELATIONS).filter(relation => {
// can not filter by within relation when filtering geometry is not closed
return relation !== ES_SPATIAL_RELATIONS.WITHIN;
});
const options = spatialRelations
.map(relation => {
return {
value: relation,
text: getEsSpatialRelationLabel(relation)
};
});
return (
<EuiFormRow
label={i18n.translate('xpack.maps.geometryFilterForm.relationLabel', {
defaultMessage: 'Spatial relation'
})}
compressed
>
<EuiSelect
options={options}
value={this.state.relation}
onChange={this._onRelationChange}
/>
</EuiFormRow>
);
}
render() {
const options = this.props.geoFields.map(({ indexPatternTitle, geoFieldName }) => {
return {
inputDisplay: (
<EuiText>
<EuiTextColor color="subdued">
<small>{indexPatternTitle}</small>
</EuiTextColor>
<br />
{geoFieldName}
</EuiText>
),
value: createIndexGeoFieldName({ indexPatternTitle, geoFieldName })
};
});
return (
<EuiForm className={this.props.className}>
<EuiSpacer size="s" />
<EuiFormRow
label={i18n.translate('xpack.maps.geometryFilterForm.geometryLabelLabel', {
defaultMessage: 'Geometry label'
})}
compressed
>
<EuiFieldText
value={this.state.geometryLabel}
onChange={this._onGeometryLabelChange}
/>
</EuiFormRow>
<EuiFormRow
className="mapGeometryFilter__geoFieldSuperSelectWrapper"
label={i18n.translate('xpack.maps.geometryFilterForm.geoFieldLabel', {
defaultMessage: 'Filtered field'
})}
compressed
>
<EuiSuperSelect
className="mapGeometryFilter__geoFieldSuperSelect"
options={options}
valueOfSelected={this.state.geoFieldTag}
onChange={this._onGeoFieldChange}
hasDividers={true}
fullWidth={true}
compressed={true}
itemClassName="mapGeometryFilter__geoFieldItem"
/>
</EuiFormRow>
{this._renderRelationInput()}
<EuiButton
size="s"
onClick={this._onSubmit}
isDisabled={!this.state.geometryLabel || !this.state.geoFieldTag}
>
{this.props.buttonLabel}
</EuiButton>
</EuiForm>
);
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { GeometryFilterForm } from './geometry_filter_form';
const defaultProps = {
buttonLabel: 'Create filter',
intitialGeometryLabel: 'My shape',
onSubmit: () => {},
};
test('should not render relation select when geo field is geo_point', async () => {
const component = shallow(
<GeometryFilterForm
{...defaultProps}
geoFields={[
{
geoFieldName: 'my geo field',
geoFieldType: 'geo_point',
indexPatternTitle: 'My index',
indexPatternId: 1
}
]}
/>
);
expect(component).toMatchSnapshot();
});
test('should render relation select when geo field is geo_shape', async () => {
const component = shallow(
<GeometryFilterForm
{...defaultProps}
geoFields={[
{
geoFieldName: 'my geo field',
geoFieldType: 'geo_shape',
indexPatternTitle: 'My index',
indexPatternId: 1
}
]}
/>
);
expect(component).toMatchSnapshot();
});
test('should not show "within" relation when filter geometry is not closed', async () => {
const component = shallow(
<GeometryFilterForm
{...defaultProps}
geoFields={[
{
geoFieldName: 'my geo field',
geoFieldType: 'geo_shape',
indexPatternTitle: 'My index',
indexPatternId: 1
}
]}
isFilterGeometryClosed={false}
/>
);
expect(component).toMatchSnapshot();
});

View file

@ -21,15 +21,3 @@
.mapFeatureTooltip__propertyValue {
max-width: $euiSizeXL * 5;
}
.mapFeatureTooltip__geoFieldItem {
padding: $euiSizeXS;
}
.mapFeatureTooltip_geoFieldSuperSelect {
height: $euiSizeXL * 2;
}
.mapFeatureTooltip_geoFieldSuperSelectWrapper {
height: $euiSizeXL * 3;
}

View file

@ -5,50 +5,17 @@
*/
import React, { Component, Fragment } from 'react';
import {
EuiIcon,
EuiForm,
EuiFormRow,
EuiSuperSelect,
EuiTextColor,
EuiText,
EuiFieldText,
EuiButton,
EuiSelect,
EuiSpacer,
} from '@elastic/eui';
import { EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { createSpatialFilterWithGeometry } from '../../elasticsearch_geo_utils';
import {
ES_GEO_FIELD_TYPE,
ES_SPATIAL_RELATIONS,
GEO_JSON_TYPE,
} from '../../../common/constants';
import { getEsSpatialRelationLabel } from '../../../common/i18n_getters';
const GEO_FIELD_VALUE_DELIMITER = '/'; // `/` is not allowed in index pattern name so should not have collisions
function createIndexGeoFieldName({ indexPatternTitle, geoFieldName }) {
return `${indexPatternTitle}${GEO_FIELD_VALUE_DELIMITER}${geoFieldName}`;
}
function splitIndexGeoFieldName(value) {
const split = value.split(GEO_FIELD_VALUE_DELIMITER);
return {
indexPatternTitle: split[0],
geoFieldName: split[1]
};
}
import { GeometryFilterForm } from '../../components/geometry_filter_form';
export class FeatureGeometryFilterForm extends Component {
state = {
geoFieldTag: createIndexGeoFieldName(this.props.geoFields[0]),
geometryLabel: this.props.feature.geometry.type.toLowerCase(),
relation: ES_SPATIAL_RELATIONS.INTERSECTS,
};
componentDidMount() {
this.props.reevaluateTooltipPosition();
}
@ -57,47 +24,14 @@ export class FeatureGeometryFilterForm extends Component {
this.props.reevaluateTooltipPosition();
}
_getSelectedGeoField = () => {
if (!this.state.geoFieldTag) {
return null;
}
const {
indexPatternTitle,
geoFieldName
} = splitIndexGeoFieldName(this.state.geoFieldTag);
return this.props.geoFields.find(option => {
return option.indexPatternTitle === indexPatternTitle
&& option.geoFieldName === geoFieldName;
});
}
_onGeoFieldChange = selectedValue => {
this.setState({ geoFieldTag: selectedValue });
}
_onGeometryLabelChange = e => {
this.setState({
geometryLabel: e.target.value,
});
}
_onRelationChange = e => {
this.setState({
relation: e.target.value,
});
}
_createFilter = () => {
const geoField = this._getSelectedGeoField();
_createFilter = ({ geometryLabel, indexPatternId, geoFieldName, geoFieldType, relation }) => {
const filter = createSpatialFilterWithGeometry({
geometry: this.props.feature.geometry,
geometryLabel: this.state.geometryLabel,
indexPatternId: geoField.indexPatternId,
geoFieldName: geoField.geoFieldName,
geoFieldType: geoField.geoFieldType,
relation: this.state.relation,
geometryLabel,
indexPatternId,
geoFieldName,
geoFieldType,
relation,
});
this.props.addFilters([filter]);
this.props.onClose();
@ -119,8 +53,8 @@ export class FeatureGeometryFilterForm extends Component {
<span className="euiContextMenu__text">
<FormattedMessage
id="xpack.maps.tooltip.geometryFilterForm.viewProperties"
defaultMessage="View properties"
id="xpack.maps.tooltip.showGeometryFilterViewLinkLabel"
defaultMessage="Filter by geometry"
/>
</span>
</span>
@ -128,115 +62,18 @@ export class FeatureGeometryFilterForm extends Component {
);
}
_renderRelationInput() {
if (!this.state.geoFieldTag) {
return null;
}
const { geoFieldType } = this._getSelectedGeoField();
// relationship only used when filtering geo_shape fields
if (geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT) {
return null;
}
const options = Object.values(ES_SPATIAL_RELATIONS)
.filter(relation => {
// line geometries can not filter by within relation since there is no closed shape
if (this.props.feature.geometry.type === GEO_JSON_TYPE.LINE_STRING
|| this.props.feature.geometry.type === GEO_JSON_TYPE.MULTI_LINE_STRING) {
return relation !== ES_SPATIAL_RELATIONS.WITHIN;
}
return true;
})
.map(relation => {
return {
value: relation,
text: getEsSpatialRelationLabel(relation)
};
});
return (
<EuiFormRow
label={i18n.translate('xpack.maps.tooltip.geometryFilterForm.relationLabel', {
defaultMessage: 'Spatial relation'
})}
compressed
>
<EuiSelect
options={options}
value={this.state.relation}
onChange={this._onRelationChange}
/>
</EuiFormRow>
);
}
_renderForm() {
const options = this.props.geoFields.map(({ indexPatternTitle, geoFieldName }) => {
return {
inputDisplay: (
<EuiText>
<EuiTextColor color="subdued">
<small>{indexPatternTitle}</small>
</EuiTextColor>
<br />
{geoFieldName}
</EuiText>
),
value: createIndexGeoFieldName({ indexPatternTitle, geoFieldName })
};
});
return (
<EuiForm>
<EuiSpacer size="s" />
<EuiFormRow
label={i18n.translate('xpack.maps.tooltip.geometryFilterForm.geometryLabelLabel', {
defaultMessage: 'Geometry label'
})}
compressed
>
<EuiFieldText
value={this.state.geometryLabel}
onChange={this._onGeometryLabelChange}
/>
</EuiFormRow>
<EuiFormRow
className="mapFeatureTooltip_geoFieldSuperSelectWrapper"
label={i18n.translate('xpack.maps.tooltip.geometryFilterForm.geoFieldLabel', {
defaultMessage: 'Filtered field'
})}
compressed
>
<EuiSuperSelect
className="mapFeatureTooltip_geoFieldSuperSelect"
options={options}
valueOfSelected={this.state.geoFieldTag}
onChange={this._onGeoFieldChange}
hasDividers={true}
fullWidth={true}
compressed={true}
itemClassName="mapFeatureTooltip__geoFieldItem"
/>
</EuiFormRow>
{this._renderRelationInput()}
<EuiButton
size="s"
onClick={this._createFilter}
isDisabled={!this.state.geometryLabel || !this.state.geoFieldTag}
>
<FormattedMessage
id="xpack.maps.tooltip.geometryFilterForm.createFilterButtonLabel"
defaultMessage="Create filter"
/>
</EuiButton>
</EuiForm>
<GeometryFilterForm
buttonLabel={i18n.translate('xpack.maps.tooltip.geometryFilterForm.createFilterButtonLabel', {
defaultMessage: 'Create filter'
})}
geoFields={this.props.geoFields}
intitialGeometryLabel={this.props.feature.geometry.type.toLowerCase()}
onSubmit={this._createFilter}
isFilterGeometryClosed={this.props.feature.geometry.type !== GEO_JSON_TYPE.LINE_STRING
&& this.props.feature.geometry.type !== GEO_JSON_TYPE.MULTI_LINE_STRING}
/>
);
}
@ -248,6 +85,4 @@ export class FeatureGeometryFilterForm extends Component {
</Fragment>
);
}
}

View file

@ -36,7 +36,6 @@ import chrome from 'ui/chrome';
import { spritesheet } from '@elastic/maki';
import sprites1 from '@elastic/maki/dist/sprite@1.png';
import sprites2 from '@elastic/maki/dist/sprite@2.png';
import { i18n } from '@kbn/i18n';
import { DrawTooltip } from './draw_tooltip';
const mbDrawModes = MapboxDraw.modes;
@ -104,20 +103,16 @@ export class MBMapContainer extends React.Component {
indexPatternId: this.props.drawState.indexPatternId,
geoFieldName: this.props.drawState.geoFieldName,
geoFieldType: this.props.drawState.geoFieldType,
geometryLabel: this.props.drawState.geometryLabel,
relation: this.props.drawState.relation,
};
const filter = isBoundingBox
? createSpatialFilterWithBoundingBox({
...options,
geometryLabel: i18n.translate('xpack.maps.drawControl.defaultEnvelopeLabel', {
defaultMessage: 'extent'
}),
geometry: getBoundingBoxGeometry(geometry)
})
: createSpatialFilterWithGeometry({
...options,
geometryLabel: i18n.translate('xpack.maps.drawControl.defaultShapeLabel', {
defaultMessage: 'shape'
}),
geometry
});
this.props.addFilters([filter]);

View file

@ -1,3 +1,5 @@
@import './tools_control/index';
.mapToolbarOverlay {
position: absolute;
top: ($euiSizeM + $euiSizeS) + ($euiSizeXL * 2); // Position and height of mapbox controls plus margin

View file

@ -37,17 +37,57 @@ exports[`Should render cancel button when drawing 1`] = `
"items": Array [
Object {
"name": "Draw shape to filter data",
"onClick": [Function],
"panel": undefined,
"panel": 1,
},
Object {
"name": "Draw bounds to filter data",
"onClick": [Function],
"panel": undefined,
"panel": 2,
},
],
"title": "Tools",
},
Object {
"content": <GeometryFilterForm
buttonLabel="Draw shape"
className="mapDrawControl__geometryFilterForm"
geoFields={
Array [
Object {
"geoFieldName": "location",
"geoFieldType": "geo_point",
"indexPatternId": "1",
"indexPatternTitle": "my_index",
},
]
}
intitialGeometryLabel="shape"
isFilterGeometryClosed={true}
onSubmit={[Function]}
/>,
"id": 1,
"title": "Draw shape to filter data",
},
Object {
"content": <GeometryFilterForm
buttonLabel="Draw bounds"
className="mapDrawControl__geometryFilterForm"
geoFields={
Array [
Object {
"geoFieldName": "location",
"geoFieldType": "geo_point",
"indexPatternId": "1",
"indexPatternTitle": "my_index",
},
]
}
intitialGeometryLabel="bounds"
isFilterGeometryClosed={true}
onSubmit={[Function]}
/>,
"id": 2,
"title": "Draw bounds to filter data",
},
]
}
/>
@ -105,17 +145,57 @@ exports[`renders 1`] = `
"items": Array [
Object {
"name": "Draw shape to filter data",
"onClick": [Function],
"panel": undefined,
"panel": 1,
},
Object {
"name": "Draw bounds to filter data",
"onClick": [Function],
"panel": undefined,
"panel": 2,
},
],
"title": "Tools",
},
Object {
"content": <GeometryFilterForm
buttonLabel="Draw shape"
className="mapDrawControl__geometryFilterForm"
geoFields={
Array [
Object {
"geoFieldName": "location",
"geoFieldType": "geo_point",
"indexPatternId": "1",
"indexPatternTitle": "my_index",
},
]
}
intitialGeometryLabel="shape"
isFilterGeometryClosed={true}
onSubmit={[Function]}
/>,
"id": 1,
"title": "Draw shape to filter data",
},
Object {
"content": <GeometryFilterForm
buttonLabel="Draw bounds"
className="mapDrawControl__geometryFilterForm"
geoFields={
Array [
Object {
"geoFieldName": "location",
"geoFieldType": "geo_point",
"indexPatternId": "1",
"indexPatternTitle": "my_index",
},
]
}
intitialGeometryLabel="bounds"
isFilterGeometryClosed={true}
onSubmit={[Function]}
/>,
"id": 2,
"title": "Draw bounds to filter data",
},
]
}
/>

View file

@ -0,0 +1,3 @@
.mapDrawControl__geometryFilterForm {
padding: $euiSizeS;
}

View file

@ -4,14 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Component, Fragment } from 'react';
import React, { Component } from 'react';
import {
EuiButtonIcon,
EuiPopover,
EuiContextMenu,
EuiSelectable,
EuiHighlight,
EuiTextColor,
EuiFlexGroup,
EuiFlexItem,
EuiButton,
@ -19,152 +16,101 @@ import {
import { i18n } from '@kbn/i18n';
import { DRAW_TYPE } from '../../../../common/constants';
import { FormattedMessage } from '@kbn/i18n/react';
import { GeometryFilterForm } from '../../../components/geometry_filter_form';
const RESET_STATE = {
isPopoverOpen: false,
drawType: null
};
const DRAW_SHAPE_LABEL = i18n.translate('xpack.maps.toolbarOverlay.drawShapeLabel', {
defaultMessage: 'Draw shape to filter data',
});
const DRAW_BOUNDS_LABEL = i18n.translate('xpack.maps.toolbarOverlay.drawBoundsLabel', {
defaultMessage: 'Draw bounds to filter data',
});
export class ToolsControl extends Component {
state = {
...RESET_STATE
isPopoverOpen: false
};
_togglePopover = () => {
this.setState(prevState => ({
...RESET_STATE,
isPopoverOpen: !prevState.isPopoverOpen,
}));
};
_closePopover = () => {
this.setState(RESET_STATE);
this.setState({ isPopoverOpen: false });
};
_onIndexPatternSelection = (options) => {
const selection = options.find((option) => option.checked);
this._initiateDraw(
this.state.drawType,
selection.value
);
};
_initiateDraw = (drawType, indexContext) => {
_initiateShapeDraw = (options) => {
this.props.initiateDraw({
drawType,
...indexContext
drawType: DRAW_TYPE.POLYGON,
...options
});
this._closePopover();
}
_initiateBoundsDraw = (options) => {
this.props.initiateDraw({
drawType: DRAW_TYPE.BOUNDS,
...options
});
this._closePopover();
};
_selectPolygonDrawType = () => {
this.setState({ drawType: DRAW_TYPE.POLYGON });
}
_selectBoundsDrawType = () => {
this.setState({ drawType: DRAW_TYPE.BOUNDS });
}
_getDrawPanels() {
const needsIndexPatternSelectionPanel = this.props.geoFields.length > 1;
const drawPolygonAction = {
name: i18n.translate('xpack.maps.toolbarOverlay.drawShapeLabel', {
defaultMessage: 'Draw shape to filter data',
}),
onClick: needsIndexPatternSelectionPanel
? this._selectPolygonDrawType
: () => {
this._initiateDraw(DRAW_TYPE.POLYGON, this.props.geoFields[0]);
},
panel: needsIndexPatternSelectionPanel
? this._getIndexPatternSelectionPanel(1)
: undefined
};
const drawBoundsAction = {
name: i18n.translate('xpack.maps.toolbarOverlay.drawBoundsLabel', {
defaultMessage: 'Draw bounds to filter data',
}),
onClick: needsIndexPatternSelectionPanel
? this._selectBoundsDrawType
: () => {
this._initiateDraw(DRAW_TYPE.BOUNDS, this.props.geoFields[0]);
},
panel: needsIndexPatternSelectionPanel
? this._getIndexPatternSelectionPanel(2)
: undefined
};
return flattenPanelTree({
id: 0,
title: i18n.translate('xpack.maps.toolbarOverlay.tools.toolbarTitle', {
defaultMessage: 'Tools',
}),
items: [drawPolygonAction, drawBoundsAction]
});
}
_getIndexPatternSelectionPanel(id) {
const options = this.props.geoFields.map((geoField) => {
return {
label: `${geoField.indexPatternTitle} : ${geoField.geoFieldName}`,
value: geoField
};
});
const renderGeoField = (option, searchValue) => {
return (
<Fragment>
<EuiTextColor color="subdued">
<small>
<EuiHighlight search={searchValue}>{option.value.indexPatternTitle}</EuiHighlight>
</small>
</EuiTextColor>
<br />
<EuiHighlight search={searchValue}>
{option.value.geoFieldName}
</EuiHighlight>
</Fragment>
);
};
const indexPatternSelection = (
<EuiSelectable
searchable
searchProps={{
placeholder: i18n.translate('xpack.maps.toolbarOverlay.indexPattern.filterListTitle', {
defaultMessage: 'Filter list',
}),
compressed: true,
}}
options={options}
onChange={this._onIndexPatternSelection}
renderOption={renderGeoField}
listProps={{
rowHeight: 50,
showIcons: false,
}}
>
{(list, search) => (
<div>
{search}
{list}
</div>
)}
</EuiSelectable>
);
return {
id: id,
title: i18n.translate('xpack.maps.toolbarOverlay.geofield.toolbarTitle', {
defaultMessage: 'Select geo field',
}),
content: indexPatternSelection
};
return [
{
id: 0,
title: i18n.translate('xpack.maps.toolbarOverlay.tools.toolbarTitle', {
defaultMessage: 'Tools',
}),
items: [
{
name: DRAW_SHAPE_LABEL,
panel: 1
},
{
name: DRAW_BOUNDS_LABEL,
panel: 2
}
]
},
{
id: 1,
title: DRAW_SHAPE_LABEL,
content: (
<GeometryFilterForm
className="mapDrawControl__geometryFilterForm"
buttonLabel={i18n.translate('xpack.maps.toolbarOverlay.drawShape.onSubmitButtonLabel', {
defaultMessage: 'Draw shape',
})}
geoFields={this.props.geoFields}
intitialGeometryLabel={i18n.translate('xpack.maps.toolbarOverlay.drawShape.initialGeometryLabel', {
defaultMessage: 'shape',
})}
onSubmit={this._initiateShapeDraw}
/>
)
},
{
id: 2,
title: DRAW_BOUNDS_LABEL,
content: (
<GeometryFilterForm
className="mapDrawControl__geometryFilterForm"
buttonLabel={i18n.translate('xpack.maps.toolbarOverlay.drawBounds.onSubmitButtonLabel', {
defaultMessage: 'Draw bounds',
})}
geoFields={this.props.geoFields}
intitialGeometryLabel={i18n.translate('xpack.maps.toolbarOverlay.drawBounds.initialGeometryLabel', {
defaultMessage: 'bounds',
})}
onSubmit={this._initiateBoundsDraw}
/>
)
}
];
}
_renderToolsButton() {
@ -227,18 +173,3 @@ export class ToolsControl extends Component {
);
}
}
function flattenPanelTree(tree, array = []) {
array.push(tree);
if (tree.items) {
tree.items.forEach(item => {
if (item.panel) {
flattenPanelTree(item.panel, array);
item.panel = item.panel.id;
}
});
}
return array;
}

View file

@ -5668,8 +5668,6 @@
"xpack.maps.styles.vector.symbolSizeLabel": "シンボルのサイズ",
"xpack.maps.toolbarOverlay.drawBoundsLabel": "境界を描いてデータをフィルタリング",
"xpack.maps.toolbarOverlay.drawShapeLabel": "シェイプを描いてデータをフィルタリング",
"xpack.maps.toolbarOverlay.geofield.toolbarTitle": "ジオフィールドを選択",
"xpack.maps.toolbarOverlay.indexPattern.filterListTitle": "フィルターリスト",
"xpack.maps.toolbarOverlay.tools.toolbarTitle": "ツール",
"xpack.maps.tooltip.closeAriaLabel": "ツールヒントを閉じる",
"xpack.maps.tooltip.filterOnPropertyAriaLabel": "プロパティのフィルター",

View file

@ -5812,8 +5812,6 @@
"xpack.maps.styles.vector.symbolSizeLabel": "符号大小",
"xpack.maps.toolbarOverlay.drawBoundsLabel": "绘制边界以筛选数据",
"xpack.maps.toolbarOverlay.drawShapeLabel": "绘制形状以筛选数据",
"xpack.maps.toolbarOverlay.geofield.toolbarTitle": "选择地理字段",
"xpack.maps.toolbarOverlay.indexPattern.filterListTitle": "筛选列表",
"xpack.maps.toolbarOverlay.tools.toolbarTitle": "工具",
"xpack.maps.tooltip.closeAriaLabel": "关闭工具提示",
"xpack.maps.tooltip.filterOnPropertyAriaLabel": "基于属性筛选",