[Maps] clean up source settings (#52644)

* [Maps] clean up source settings

* update es_search_source editor

* geo grid source

* pew pew source

* fix i18n

* doc updates for changing UI

* docs re-wording

* use correct image name

* update snapshot

* review feedback
This commit is contained in:
Nathan Reese 2019-12-11 13:53:58 -07:00 committed by GitHub
parent e3f3dd15ba
commit eb8c0e33dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 594 additions and 632 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 KiB

View file

@ -101,11 +101,6 @@ aggregation.
The remaining default settings are good, but there are a couple of
settings that you might want to change.
. Under *Source settings* > *Grid resolution*, select from the different heat map resolutions.
+
The default "Coarse" looks
good, but feel free to select a different resolution.
. Play around with the *Layer Style* >
*Color range* setting.
+

View file

@ -34,18 +34,23 @@ The point location is the weighted centroid for all geo-points in the gridded ce
[role="xpack"]
[[maps-top-hits-aggregation]]
=== Most recent entities
=== Top hits per entity
Most recent entities uses {es} {ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation] to group your documents by entity.
Then, {ref}/search-aggregations-metrics-top-hits-aggregation.html[top hits metric aggregation] accumulates the most recent documents for each entry.
You can display the most relevant documents per entity, for example, the most recent GPS tracks per flight.
To get this data, {es} first groups your data using a {ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation],
then accumulates the most relevant documents based on sort order for each entry using a {ref}/search-aggregations-metrics-top-hits-aggregation.html[top hits metric aggregation].
Most recent entities is available for <<vector-layer, vector layers>> with *Documents* source.
To enable most recent entities, click "Show most recent documents by entity" and configure the following:
Top hits per entity is available for <<vector-layer, vector layers>> with *Documents* source.
To enable top hits:
. In *Sorting*, select the *Show documents per entity* checkbox.
. Set *Entity* to the field that identifies entities in your documents.
This field will be used in the terms aggregation to group your documents into entity buckets.
. Set *Documents per entity* to configure the maximum number of documents accumulated per entity.
[role="screenshot"]
image::maps/images/top_hits.png[]
[role="xpack"]
[[point-to-point]]
=== Point to point

View file

@ -89,7 +89,7 @@ The most common cause for empty layers are searches for a field that exists in o
You can prevent the search bar from applying search context to a layer by configuring the following:
* In *Source settings*, clear the *Apply global filter to source* checkbox to turn off the global search context for the layer source.
* In *Filtering*, clear the *Apply global filter to layer data* checkbox to turn off the global search context for the layer source.
* In *Term joins*, clear the *Apply global filter to join* checkbox to turn off the global search context for the <<terms-join, term join>>.

View file

@ -2,20 +2,6 @@
exports[`TooltipSelector should render component 1`] = `
<div>
<EuiTitle
size="xxs"
>
<h6>
<FormattedMessage
defaultMessage="Fields to display in tooltip"
id="xpack.maps.tooltipSelectorLabel"
values={Object {}}
/>
</h6>
</EuiTitle>
<EuiSpacer
size="xs"
/>
<EuiDragDropContext
onDragEnd={[Function]}
>

View file

@ -6,13 +6,8 @@
import React from 'react';
import { EuiFormRow, EuiSwitch } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
const label = i18n.translate('xpack.maps.layerPanel.applyGlobalQueryCheckboxLabel', {
defaultMessage: `Apply global filter to source`,
});
export function GlobalFilterCheckbox({ applyGlobalQuery, customLabel, setApplyGlobalQuery }) {
export function GlobalFilterCheckbox({ applyGlobalQuery, label, setApplyGlobalQuery }) {
const onApplyGlobalQueryChange = event => {
setApplyGlobalQuery(event.target.checked);
};
@ -22,7 +17,7 @@ export function GlobalFilterCheckbox({ applyGlobalQuery, customLabel, setApplyGl
display="columnCompressedSwitch"
>
<EuiSwitch
label={customLabel ? customLabel : label}
label={label}
checked={applyGlobalQuery}
onChange={onApplyGlobalQueryChange}
data-test-subj="mapLayerPanelApplyGlobalQueryCheckbox"

View file

@ -13,11 +13,9 @@ import {
EuiDroppable,
EuiText,
EuiTextAlign,
EuiTitle,
EuiSpacer,
} from '@elastic/eui';
import { AddTooltipFieldPopover } from './add_tooltip_field_popover';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
// TODO import reorder from EUI once its exposed as service
@ -213,13 +211,6 @@ export class TooltipSelector extends Component {
render() {
return (
<div>
<EuiTitle size="xxs">
<h6>
<FormattedMessage id="xpack.maps.tooltipSelectorLabel" defaultMessage="Fields to display in tooltip" />
</h6>
</EuiTitle>
<EuiSpacer size="xs" />
{this._renderProperties()}
<EuiSpacer size="s" />

View file

@ -97,7 +97,9 @@ exports[`LayerPanel is rendered 1`] = `
>
<LayerErrors />
<LayerSettings />
<SourceSettings />
<div>
mockSourceSettings
</div>
<EuiPanel>
<JoinEditor />
</EuiPanel>

View file

@ -16,11 +16,14 @@ import {
EuiTextColor,
EuiTextAlign,
EuiButtonEmpty,
EuiFormRow,
EuiSwitch,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { indexPatternService } from '../../../kibana_services';
import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox';
import { start as data } from '../../../../../../../../src/legacy/core_plugins/data/public/legacy';
const { SearchBar } = data.ui;
@ -79,6 +82,14 @@ export class FilterEditor extends Component {
this._close();
};
_onFilterByMapBoundsChange = event => {
this.props.updateSourceProp(this.props.layer.getId(), 'filterByMapBounds', event.target.checked);
};
_onApplyGlobalQueryChange = applyGlobalQuery => {
this.props.updateSourceProp(this.props.layer.getId(), 'applyGlobalQuery', applyGlobalQuery);
};
_renderQueryPopover() {
const layerQuery = this.props.layer.getQuery();
const { uiSettings } = npStart.core;
@ -169,13 +180,29 @@ export class FilterEditor extends Component {
}
render() {
let filterByBoundsSwitch;
if (this.props.layer.getSource().isFilterByMapBoundsConfigurable()) {
filterByBoundsSwitch = (
<EuiFormRow display="rowCompressed">
<EuiSwitch
label={i18n.translate('xpack.maps.filterEditor.extentFilterLabel', {
defaultMessage: 'Dynamically filter for data in the visible map area',
})}
checked={this.props.layer.getSource().isFilterByMapBounds()}
onChange={this._onFilterByMapBoundsChange}
compressed
/>
</EuiFormRow>
);
}
return (
<Fragment>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.maps.layerPanel.filterEditor.title"
defaultMessage="Filter"
defaultMessage="Filtering"
/>
</h5>
</EuiTitle>
@ -185,6 +212,18 @@ export class FilterEditor extends Component {
{this._renderQuery()}
<EuiTextAlign textAlign="center">{this._renderQueryPopover()}</EuiTextAlign>
<EuiSpacer size="m" />
{filterByBoundsSwitch}
<GlobalFilterCheckbox
label={i18n.translate('xpack.maps.filterEditor.applyGlobalQueryCheckboxLabel', {
defaultMessage: `Apply global filter to layer data`,
})}
applyGlobalQuery={this.props.layer.getSource().getApplyGlobalQuery()}
setApplyGlobalQuery={this._onApplyGlobalQueryChange}
/>
</Fragment>
);
}

View file

@ -7,7 +7,7 @@
import { connect } from 'react-redux';
import { FilterEditor } from './filter_editor';
import { getSelectedLayer } from '../../../selectors/map_selectors';
import { setLayerQuery } from '../../../actions/map_actions';
import { setLayerQuery, updateSourceProp } from '../../../actions/map_actions';
function mapStateToProps(state = {}) {
return {
@ -19,7 +19,8 @@ function mapDispatchToProps(dispatch) {
return {
setLayerQuery: (layerId, query) => {
dispatch(setLayerQuery(layerId, query));
}
},
updateSourceProp: (id, propName, value) => dispatch(updateSourceProp(id, propName, value)),
};
}

View file

@ -8,7 +8,8 @@ import { connect } from 'react-redux';
import { LayerPanel } from './view';
import { getSelectedLayer } from '../../selectors/map_selectors';
import {
fitToLayerExtent
fitToLayerExtent,
updateSourceProp,
} from '../../actions/map_actions';
function mapStateToProps(state = {}) {
@ -21,7 +22,8 @@ function mapDispatchToProps(dispatch) {
return {
fitToBounds: (layerId) => {
dispatch(fitToLayerExtent(layerId));
}
},
updateSourceProp: (id, propName, value) => dispatch(updateSourceProp(id, propName, value)),
};
}

View file

@ -217,7 +217,7 @@ export class Join extends Component {
<GlobalFilterCheckbox
applyGlobalQuery={right.applyGlobalQuery}
setApplyGlobalQuery={this._onApplyGlobalQueryChange}
customLabel={i18n.translate('xpack.maps.layerPanel.join.applyGlobalQueryCheckboxLabel', {
label={i18n.translate('xpack.maps.layerPanel.join.applyGlobalQueryCheckboxLabel', {
defaultMessage: `Apply global filter to join`,
})}
/>

View file

@ -1,30 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Should render source settings editor 1`] = `
<Fragment>
<EuiPanel>
<EuiTitle
size="xs"
>
<h5>
<FormattedMessage
defaultMessage="Source settings"
id="xpack.maps.layerPanel.sourceSettingsTitle"
values={Object {}}
/>
</h5>
</EuiTitle>
<EuiSpacer
size="m"
/>
<div>
mockSourceEditor
</div>
</EuiPanel>
<EuiSpacer
size="s"
/>
</Fragment>
`;
exports[`should render nothing when source has no editor 1`] = `""`;

View file

@ -1,25 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { connect } from 'react-redux';
import { SourceSettings } from './source_settings';
import { getSelectedLayer } from '../../../selectors/map_selectors';
import { updateSourceProp } from '../../../actions/map_actions';
function mapStateToProps(state = {}) {
return {
layer: getSelectedLayer(state)
};
}
function mapDispatchToProps(dispatch) {
return {
updateSourceProp: (id, propName, value) => dispatch(updateSourceProp(id, propName, value)),
};
}
const connectedSourceSettings = connect(mapStateToProps, mapDispatchToProps)(SourceSettings);
export { connectedSourceSettings as SourceSettings };

View file

@ -1,44 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment } from 'react';
import { EuiTitle, EuiPanel, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
export function SourceSettings({ layer, updateSourceProp }) {
const onSourceChange = ({ propName, value }) => {
updateSourceProp(layer.getId(), propName, value);
};
const sourceSettingsEditor = layer.renderSourceSettingsEditor({ onChange: onSourceChange });
if (!sourceSettingsEditor) {
return null;
}
return (
<Fragment>
<EuiPanel>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.maps.layerPanel.sourceSettingsTitle"
defaultMessage="Source settings"
/>
</h5>
</EuiTitle>
<EuiSpacer size="m" />
{sourceSettingsEditor}
</EuiPanel>
<EuiSpacer size="s" />
</Fragment>
);
}

View file

@ -1,42 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { SourceSettings } from './source_settings';
test('Should render source settings editor', () => {
const mockLayer = {
renderSourceSettingsEditor: () => {
return (<div>mockSourceEditor</div>);
},
};
const component = shallow(
<SourceSettings
layer={mockLayer}
/>
);
expect(component)
.toMatchSnapshot();
});
test('should render nothing when source has no editor', () => {
const mockLayer = {
renderSourceSettingsEditor: () => {
return null;
},
};
const component = shallow(
<SourceSettings
layer={mockLayer}
/>
);
expect(component)
.toMatchSnapshot();
});

View file

@ -11,7 +11,6 @@ import { JoinEditor } from './join_editor';
import { FlyoutFooter } from './flyout_footer';
import { LayerErrors } from './layer_errors';
import { LayerSettings } from './layer_settings';
import { SourceSettings } from './source_settings';
import { StyleSettings } from './style_settings';
import {
EuiButtonIcon,
@ -96,6 +95,10 @@ export class LayerPanel extends React.Component {
}
}
_onSourceChange = ({ propName, value }) => {
this.props.updateSourceProp(this.props.selectedLayer.getId(), propName, value);
};
_renderFilterSection() {
if (!this.props.selectedLayer.supportsElasticsearchFilters()) {
return null;
@ -213,7 +216,7 @@ export class LayerPanel extends React.Component {
<LayerSettings/>
<SourceSettings/>
{this.props.selectedLayer.renderSourceSettingsEditor({ onChange: this._onSourceChange })}
{this._renderFilterSection()}

View file

@ -40,12 +40,6 @@ jest.mock('./layer_settings', () => ({
}
}));
jest.mock('./source_settings', () => ({
SourceSettings: () => {
return (<div>mockSourceSettings</div>);
}
}));
import React from 'react';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
@ -62,11 +56,13 @@ const mockLayer = {
isJoinable: () => { return true; },
supportsElasticsearchFilters: () => { return false; },
getLayerTypeIconName: () => { return 'vector'; },
renderSourceSettingsEditor: () => { return (<div>mockSourceSettings</div>); },
};
const defaultProps = {
selectedLayer: mockLayer,
fitToBounds: () => {},
updateSourceProp: () => {},
};
describe('LayerPanel', () => {

View file

@ -4,10 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Component } from 'react';
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { TooltipSelector } from '../../../components/tooltip_selector';
import { getEMSClient } from '../../../meta';
import { EuiTitle, EuiPanel, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
export class UpdateSourceEditor extends Component {
@ -53,13 +55,29 @@ export class UpdateSourceEditor extends Component {
};
render() {
return (
<TooltipSelector
tooltipFields={this.props.tooltipFields}
onChange={this._onTooltipPropertiesSelect}
fields={this.state.fields}
/>
<Fragment>
<EuiPanel>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.maps.emsSource.tooltipsTitle"
defaultMessage="Tooltip fields"
/>
</h5>
</EuiTitle>
<EuiSpacer size="m" />
<TooltipSelector
tooltipFields={this.props.tooltipFields}
onChange={this._onTooltipPropertiesSelect}
fields={this.state.fields}
/>
</EuiPanel>
<EuiSpacer size="s" />
</Fragment>
);
}
}

View file

@ -85,7 +85,6 @@ export class ESGeoGridSource extends AbstractESAggSource {
metrics={this._descriptor.metrics}
renderAs={this._descriptor.requestType}
resolution={this._descriptor.resolution}
applyGlobalQuery={this._descriptor.applyGlobalQuery}
/>
);
}

View file

@ -12,8 +12,7 @@ import { indexPatternService } from '../../../kibana_services';
import { ResolutionEditor } from './resolution_editor';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiSpacer, EuiTitle } from '@elastic/eui';
import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox';
import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
import { isMetricCountable } from '../../util/is_metric_countable';
export class UpdateSourceEditor extends Component {
@ -63,11 +62,7 @@ export class UpdateSourceEditor extends Component {
this.props.onChange({ propName: 'resolution', value: e });
};
_onApplyGlobalQueryChange = applyGlobalQuery => {
this.props.onChange({ propName: 'applyGlobalQuery', value: applyGlobalQuery });
};
_renderMetricsEditor() {
_renderMetricsPanel() {
const metricsFilter =
this.props.renderAs === RENDER_AS.HEATMAP
? metric => {
@ -77,13 +72,13 @@ export class UpdateSourceEditor extends Component {
: null;
const allowMultipleMetrics = this.props.renderAs !== RENDER_AS.HEATMAP;
return (
<div>
<EuiTitle size="xxs">
<EuiPanel>
<EuiTitle size="xs">
<h6>
<FormattedMessage id="xpack.maps.source.esGrid.metricsLabel" defaultMessage="Metrics" />
</h6>
</EuiTitle>
<EuiSpacer size="s" />
<EuiSpacer size="m" />
<MetricsEditor
allowMultipleMetrics={allowMultipleMetrics}
metricsFilter={metricsFilter}
@ -91,22 +86,27 @@ export class UpdateSourceEditor extends Component {
metrics={this.props.metrics}
onChange={this._onMetricsChange}
/>
</div>
</EuiPanel>
);
}
render() {
return (
<Fragment>
<ResolutionEditor resolution={this.props.resolution} onChange={this._onResolutionChange} />
<EuiSpacer size="m" />
{this._renderMetricsPanel()}
<EuiSpacer size="s" />
{this._renderMetricsEditor()}
<EuiPanel>
<EuiTitle size="xs">
<h6>
<FormattedMessage id="xpack.maps.source.esGrid.geoTileGridLabel" defaultMessage="Grid parameters" />
</h6>
</EuiTitle>
<EuiSpacer size="m" />
<ResolutionEditor resolution={this.props.resolution} onChange={this._onResolutionChange} />
</EuiPanel>
<EuiSpacer size="s" />
<GlobalFilterCheckbox
applyGlobalQuery={this.props.applyGlobalQuery}
setApplyGlobalQuery={this._onApplyGlobalQueryChange}
/>
</Fragment>
);
}

View file

@ -4,14 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Component } from 'react';
import React, { Component, Fragment } from 'react';
import { MetricsEditor } from '../../../components/metrics_editor';
import { indexPatternService } from '../../../kibana_services';
import { i18n } from '@kbn/i18n';
import { EuiTitle, EuiSpacer } from '@elastic/eui';
import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox';
export class UpdateSourceEditor extends Component {
state = {
@ -56,30 +55,25 @@ export class UpdateSourceEditor extends Component {
this.props.onChange({ propName: 'metrics', value: metrics });
};
_onApplyGlobalQueryChange = applyGlobalQuery => {
this.props.onChange({ propName: 'applyGlobalQuery', value: applyGlobalQuery });
};
render() {
return (
<>
<EuiTitle size="xxs">
<h6>
<FormattedMessage id="xpack.maps.source.pewPew.metricsLabel" defaultMessage="Metrics" />
</h6>
</EuiTitle>
<Fragment>
<EuiPanel>
<EuiTitle size="xs">
<h6>
<FormattedMessage id="xpack.maps.source.pewPew.metricsLabel" defaultMessage="Metrics" />
</h6>
</EuiTitle>
<EuiSpacer size="m" />
<MetricsEditor
allowMultipleMetrics={true}
fields={this.state.fields}
metrics={this.props.metrics}
onChange={this._onMetricsChange}
/>
</EuiPanel>
<EuiSpacer size="s" />
<MetricsEditor
allowMultipleMetrics={true}
fields={this.state.fields}
metrics={this.props.metrics}
onChange={this._onMetricsChange}
/>
<GlobalFilterCheckbox
applyGlobalQuery={this.props.applyGlobalQuery}
setApplyGlobalQuery={this._onApplyGlobalQueryChange}
/>
</>
</Fragment>
);
}
}

View file

@ -2,344 +2,386 @@
exports[`should enable sort order select when sort field provided 1`] = `
<Fragment>
<EuiFormRow
describedByIds={Array []}
display="row"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
labelType="label"
>
<EuiPanel>
<EuiTitle
size="xs"
>
<h5>
<FormattedMessage
defaultMessage="Tooltip fields"
id="xpack.maps.esSearch.tooltipsTitle"
values={Object {}}
/>
</h5>
</EuiTitle>
<EuiSpacer
size="m"
/>
<TooltipSelector
fields={null}
onChange={[Function]}
tooltipFields={Array []}
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="row"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Sort"
labelType="label"
>
<EuiFlexGroup
gutterSize="none"
justifyContent="flexEnd"
</EuiPanel>
<EuiSpacer
size="s"
/>
<EuiPanel>
<EuiTitle
size="xs"
>
<EuiFlexItem>
<SingleFieldSelect
compressed={true}
fields={null}
filterField={[Function]}
onChange={[Function]}
placeholder="Select sort field"
value="@timestamp"
<h5>
<FormattedMessage
defaultMessage="Sorting"
id="xpack.maps.esSearch.sortTitle"
values={Object {}}
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiSelect
compressed={true}
disabled={false}
fullWidth={false}
hasNoInitialSelection={false}
isLoading={false}
onChange={[Function]}
options={
Array [
Object {
"text": "ASC",
"value": "asc",
},
Object {
"text": "DESC",
"value": "desc",
},
]
}
value="DESC"
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="rowCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
labelType="label"
>
<EuiSwitch
checked={false}
compressed={true}
label="Show top documents based on sort order"
onChange={[Function]}
</h5>
</EuiTitle>
<EuiSpacer
size="m"
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="rowCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
labelType="label"
>
<EuiSwitch
checked={true}
compressed={true}
label="Dynamically filter for data in the visible map area"
onChange={[Function]}
<EuiFormRow
describedByIds={Array []}
display="columnCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Field"
labelType="label"
>
<SingleFieldSelect
compressed={true}
fields={null}
filterField={[Function]}
onChange={[Function]}
placeholder="Select sort field"
value="@timestamp"
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="columnCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Order"
labelType="label"
>
<EuiSelect
compressed={true}
disabled={false}
fullWidth={false}
hasNoInitialSelection={false}
isLoading={false}
onChange={[Function]}
options={
Array [
Object {
"text": "ascending",
"value": "asc",
},
Object {
"text": "descending",
"value": "desc",
},
]
}
value="DESC"
/>
</EuiFormRow>
<EuiHorizontalRule
margin="xs"
/>
</EuiFormRow>
<GlobalFilterCheckbox
setApplyGlobalQuery={[Function]}
<EuiFormRow
describedByIds={Array []}
display="columnCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Top hits"
labelType="label"
>
<EuiSwitch
checked={false}
compressed={true}
label="Show top hits per entity"
onChange={[Function]}
/>
</EuiFormRow>
</EuiPanel>
<EuiSpacer
size="s"
/>
</Fragment>
`;
exports[`should render top hits form when useTopHits is true 1`] = `
<Fragment>
<EuiFormRow
describedByIds={Array []}
display="row"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
labelType="label"
>
<EuiPanel>
<EuiTitle
size="xs"
>
<h5>
<FormattedMessage
defaultMessage="Tooltip fields"
id="xpack.maps.esSearch.tooltipsTitle"
values={Object {}}
/>
</h5>
</EuiTitle>
<EuiSpacer
size="m"
/>
<TooltipSelector
fields={null}
onChange={[Function]}
tooltipFields={Array []}
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="row"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Sort"
labelType="label"
>
<EuiFlexGroup
gutterSize="none"
justifyContent="flexEnd"
</EuiPanel>
<EuiSpacer
size="s"
/>
<EuiPanel>
<EuiTitle
size="xs"
>
<EuiFlexItem>
<SingleFieldSelect
compressed={true}
fields={null}
filterField={[Function]}
onChange={[Function]}
placeholder="Select sort field"
<h5>
<FormattedMessage
defaultMessage="Sorting"
id="xpack.maps.esSearch.sortTitle"
values={Object {}}
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiSelect
compressed={true}
disabled={true}
fullWidth={false}
hasNoInitialSelection={false}
isLoading={false}
onChange={[Function]}
options={
Array [
Object {
"text": "ASC",
"value": "asc",
},
Object {
"text": "DESC",
"value": "desc",
},
]
}
value="DESC"
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="rowCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
labelType="label"
>
<EuiSwitch
checked={true}
compressed={true}
label="Show top documents based on sort order"
onChange={[Function]}
</h5>
</EuiTitle>
<EuiSpacer
size="m"
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="rowCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Entity"
labelType="label"
>
<SingleFieldSelect
compressed={true}
fields={null}
filterField={[Function]}
onChange={[Function]}
placeholder="Select entity field"
value="trackId"
<EuiFormRow
describedByIds={Array []}
display="columnCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Field"
labelType="label"
>
<SingleFieldSelect
compressed={true}
fields={null}
filterField={[Function]}
onChange={[Function]}
placeholder="Select sort field"
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="columnCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Order"
labelType="label"
>
<EuiSelect
compressed={true}
disabled={true}
fullWidth={false}
hasNoInitialSelection={false}
isLoading={false}
onChange={[Function]}
options={
Array [
Object {
"text": "ascending",
"value": "asc",
},
Object {
"text": "descending",
"value": "desc",
},
]
}
value="DESC"
/>
</EuiFormRow>
<EuiHorizontalRule
margin="xs"
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="rowCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Documents per entity"
labelType="label"
>
<ValidatedRange
compressed={true}
data-test-subj="layerPanelTopHitsSize"
max={100}
min={1}
onChange={[Function]}
showInput={true}
showLabels={true}
showRange={true}
step={1}
value={1}
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="rowCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
labelType="label"
>
<EuiSwitch
checked={true}
compressed={true}
label="Dynamically filter for data in the visible map area"
onChange={[Function]}
/>
</EuiFormRow>
<GlobalFilterCheckbox
setApplyGlobalQuery={[Function]}
<EuiFormRow
describedByIds={Array []}
display="columnCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Top hits"
labelType="label"
>
<EuiSwitch
checked={true}
compressed={true}
label="Show top hits per entity"
onChange={[Function]}
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="columnCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Entity"
labelType="label"
>
<SingleFieldSelect
compressed={true}
fields={null}
filterField={[Function]}
onChange={[Function]}
placeholder="Select entity field"
value="trackId"
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="columnCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Documents per entity"
labelType="label"
>
<ValidatedRange
compressed={true}
data-test-subj="layerPanelTopHitsSize"
max={100}
min={1}
onChange={[Function]}
showInput={true}
showLabels={true}
showRange={true}
step={1}
value={1}
/>
</EuiFormRow>
</EuiPanel>
<EuiSpacer
size="s"
/>
</Fragment>
`;
exports[`should render update source editor 1`] = `
<Fragment>
<EuiFormRow
describedByIds={Array []}
display="row"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
labelType="label"
>
<EuiPanel>
<EuiTitle
size="xs"
>
<h5>
<FormattedMessage
defaultMessage="Tooltip fields"
id="xpack.maps.esSearch.tooltipsTitle"
values={Object {}}
/>
</h5>
</EuiTitle>
<EuiSpacer
size="m"
/>
<TooltipSelector
fields={null}
onChange={[Function]}
tooltipFields={Array []}
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="row"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Sort"
labelType="label"
>
<EuiFlexGroup
gutterSize="none"
justifyContent="flexEnd"
</EuiPanel>
<EuiSpacer
size="s"
/>
<EuiPanel>
<EuiTitle
size="xs"
>
<EuiFlexItem>
<SingleFieldSelect
compressed={true}
fields={null}
filterField={[Function]}
onChange={[Function]}
placeholder="Select sort field"
<h5>
<FormattedMessage
defaultMessage="Sorting"
id="xpack.maps.esSearch.sortTitle"
values={Object {}}
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiSelect
compressed={true}
disabled={true}
fullWidth={false}
hasNoInitialSelection={false}
isLoading={false}
onChange={[Function]}
options={
Array [
Object {
"text": "ASC",
"value": "asc",
},
Object {
"text": "DESC",
"value": "desc",
},
]
}
value="DESC"
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="rowCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
labelType="label"
>
<EuiSwitch
checked={false}
compressed={true}
label="Show top documents based on sort order"
onChange={[Function]}
</h5>
</EuiTitle>
<EuiSpacer
size="m"
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="rowCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
labelType="label"
>
<EuiSwitch
checked={true}
compressed={true}
label="Dynamically filter for data in the visible map area"
onChange={[Function]}
<EuiFormRow
describedByIds={Array []}
display="columnCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Field"
labelType="label"
>
<SingleFieldSelect
compressed={true}
fields={null}
filterField={[Function]}
onChange={[Function]}
placeholder="Select sort field"
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="columnCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Order"
labelType="label"
>
<EuiSelect
compressed={true}
disabled={true}
fullWidth={false}
hasNoInitialSelection={false}
isLoading={false}
onChange={[Function]}
options={
Array [
Object {
"text": "ascending",
"value": "asc",
},
Object {
"text": "descending",
"value": "desc",
},
]
}
value="DESC"
/>
</EuiFormRow>
<EuiHorizontalRule
margin="xs"
/>
</EuiFormRow>
<GlobalFilterCheckbox
setApplyGlobalQuery={[Function]}
<EuiFormRow
describedByIds={Array []}
display="columnCompressed"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label="Top hits"
labelType="label"
>
<EuiSwitch
checked={false}
compressed={true}
label="Show top hits per entity"
onChange={[Function]}
/>
</EuiFormRow>
</EuiPanel>
<EuiSpacer
size="s"
/>
</Fragment>
`;

View file

@ -85,14 +85,12 @@ export class ESSearchSource extends AbstractESSource {
source={this}
indexPatternId={this._descriptor.indexPatternId}
onChange={onChange}
filterByMapBounds={this._descriptor.filterByMapBounds}
tooltipFields={this._tooltipFields}
sortField={this._descriptor.sortField}
sortOrder={this._descriptor.sortOrder}
useTopHits={this._descriptor.useTopHits}
topHitsSplitField={this._descriptor.topHitsSplitField}
topHitsSize={this._descriptor.topHitsSize}
applyGlobalQuery={this._descriptor.applyGlobalQuery}
/>
);
}
@ -445,6 +443,10 @@ export class ESSearchSource extends AbstractESSource {
return _.get(this._descriptor, 'filterByMapBounds', false);
}
isFilterByMapBoundsConfigurable() {
return true;
}
async getLeftJoinFields() {
const indexPattern = await this.getIndexPattern();
// Left fields are retrieved from _source.

View file

@ -9,13 +9,14 @@ import PropTypes from 'prop-types';
import {
EuiFormRow,
EuiSwitch,
EuiFlexGroup,
EuiFlexItem,
EuiSelect,
EuiTitle,
EuiPanel,
EuiSpacer,
EuiHorizontalRule,
} from '@elastic/eui';
import { SingleFieldSelect } from '../../../components/single_field_select';
import { TooltipSelector } from '../../../components/tooltip_selector';
import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox';
import { indexPatternService } from '../../../kibana_services';
import { i18n } from '@kbn/i18n';
@ -23,12 +24,12 @@ import { getTermsFields, getSourceFields } from '../../../index_pattern_util';
import { ValidatedRange } from '../../../components/validated_range';
import { SORT_ORDER } from '../../../../common/constants';
import { ESDocField } from '../../fields/es_doc_field';
import { FormattedMessage } from '@kbn/i18n/react';
export class UpdateSourceEditor extends Component {
static propTypes = {
indexPatternId: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
filterByMapBounds: PropTypes.bool.isRequired,
tooltipFields: PropTypes.arrayOf(PropTypes.object).isRequired,
sortField: PropTypes.string,
sortOrder: PropTypes.string.isRequired,
@ -94,10 +95,6 @@ export class UpdateSourceEditor extends Component {
this.props.onChange({ propName: 'tooltipProperties', value: propertyNames });
};
_onFilterByMapBoundsChange = event => {
this.props.onChange({ propName: 'filterByMapBounds', value: event.target.checked });
};
onUseTopHitsChange = event => {
this.props.onChange({ propName: 'useTopHits', value: event.target.checked });
};
@ -118,13 +115,27 @@ export class UpdateSourceEditor extends Component {
this.props.onChange({ propName: 'topHitsSize', value: size });
};
_onApplyGlobalQueryChange = applyGlobalQuery => {
this.props.onChange({ propName: 'applyGlobalQuery', value: applyGlobalQuery });
};
renderTopHitsForm() {
const topHitsSwitch = (
<EuiFormRow
label={i18n.translate('xpack.maps.source.esSearch.topHitsLabel', {
defaultMessage: `Top hits`,
})}
display="columnCompressed"
>
<EuiSwitch
label={i18n.translate('xpack.maps.source.esSearch.useTopHitsLabel', {
defaultMessage: `Show top hits per entity`,
})}
checked={this.props.useTopHits}
onChange={this.onUseTopHitsChange}
compressed
/>
</EuiFormRow>
);
if (!this.props.useTopHits) {
return null;
return topHitsSwitch;
}
let sizeSlider;
@ -134,7 +145,7 @@ export class UpdateSourceEditor extends Component {
label={i18n.translate('xpack.maps.source.esSearch.topHitsSizeLabel', {
defaultMessage: 'Documents per entity',
})}
display="rowCompressed"
display="columnCompressed"
>
<ValidatedRange
min={1}
@ -154,11 +165,12 @@ export class UpdateSourceEditor extends Component {
return (
<Fragment>
{topHitsSwitch}
<EuiFormRow
label={i18n.translate('xpack.maps.source.esSearch.topHitsSplitFieldLabel', {
defaultMessage: 'Entity',
})}
display="rowCompressed"
display="columnCompressed"
>
<SingleFieldSelect
placeholder={i18n.translate(
@ -179,83 +191,106 @@ export class UpdateSourceEditor extends Component {
);
}
render() {
_renderTooltipsPanel() {
return (
<Fragment>
<EuiFormRow>
<TooltipSelector
tooltipFields={this.props.tooltipFields}
onChange={this._onTooltipPropertiesChange}
fields={this.state.sourceFields}
<EuiPanel>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.maps.esSearch.tooltipsTitle"
defaultMessage="Tooltip fields"
/>
</h5>
</EuiTitle>
<EuiSpacer size="m" />
<TooltipSelector
tooltipFields={this.props.tooltipFields}
onChange={this._onTooltipPropertiesChange}
fields={this.state.sourceFields}
/>
</EuiPanel>
);
}
_renderSortPanel() {
return (
<EuiPanel>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.maps.esSearch.sortTitle"
defaultMessage="Sorting"
/>
</h5>
</EuiTitle>
<EuiSpacer size="m" />
<EuiFormRow
label={i18n.translate('xpack.maps.source.esSearch.sortFieldLabel', {
defaultMessage: 'Field',
})}
display="columnCompressed"
>
<SingleFieldSelect
placeholder={i18n.translate(
'xpack.maps.source.esSearch.sortFieldSelectPlaceholder',
{
defaultMessage: 'Select sort field',
}
)}
value={this.props.sortField}
onChange={this.onSortFieldChange}
fields={this.state.sortFields}
compressed
/>
</EuiFormRow>
<EuiFormRow
label={i18n.translate('xpack.maps.source.esSearch.sortLabel', {
defaultMessage: `Sort`,
label={i18n.translate('xpack.maps.source.esSearch.sortOrderLabel', {
defaultMessage: 'Order',
})}
display="columnCompressed"
>
<EuiFlexGroup
gutterSize="none"
justifyContent="flexEnd"
>
<EuiFlexItem>
<SingleFieldSelect
placeholder={i18n.translate(
'xpack.maps.source.esSearch.sortFieldSelectPlaceholder',
{
defaultMessage: 'Select sort field',
}
)}
value={this.props.sortField}
onChange={this.onSortFieldChange}
fields={this.state.sortFields}
compressed
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSelect
disabled={!this.props.sortField}
options={[
{ text: 'ASC', value: SORT_ORDER.ASC },
{ text: 'DESC', value: SORT_ORDER.DESC }
]}
value={this.props.sortOrder}
onChange={this.onSortOrderChange}
compressed
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormRow>
<EuiFormRow display="rowCompressed">
<EuiSwitch
label={i18n.translate('xpack.maps.source.esSearch.useTopHitsLabel', {
defaultMessage: `Show top documents based on sort order`,
})}
checked={this.props.useTopHits}
onChange={this.onUseTopHitsChange}
<EuiSelect
disabled={!this.props.sortField}
options={[
{
text: i18n.translate('xpack.maps.source.esSearch.ascendingLabel', {
defaultMessage: 'ascending',
}),
value: SORT_ORDER.ASC
},
{
text: i18n.translate('xpack.maps.source.esSearch.descendingLabel', {
defaultMessage: 'descending',
}),
value: SORT_ORDER.DESC
}
]}
value={this.props.sortOrder}
onChange={this.onSortOrderChange}
compressed
/>
</EuiFormRow>
<EuiHorizontalRule margin="xs" />
{this.renderTopHitsForm()}
</EuiPanel>
);
}
<EuiFormRow display="rowCompressed">
<EuiSwitch
label={i18n.translate('xpack.maps.source.esSearch.extentFilterLabel', {
defaultMessage: `Dynamically filter for data in the visible map area`,
})}
checked={this.props.filterByMapBounds}
onChange={this._onFilterByMapBoundsChange}
compressed
/>
</EuiFormRow>
render() {
return (
<Fragment>
<GlobalFilterCheckbox
applyGlobalQuery={this.props.applyGlobalQuery}
setApplyGlobalQuery={this._onApplyGlobalQueryChange}
/>
{this._renderTooltipsPanel()}
<EuiSpacer size="s" />
{this._renderSortPanel()}
<EuiSpacer size="s" />
</Fragment>
);

View file

@ -79,6 +79,10 @@ export class AbstractVectorSource extends AbstractSource {
return false;
}
isFilterByMapBoundsConfigurable() {
return false;
}
isBoundsAware() {
return false;
}

View file

@ -6433,7 +6433,6 @@
"xpack.maps.layerControl.tocEntry.hideDetailsButtonTitle": "レイヤー詳細を非表示",
"xpack.maps.layerControl.tocEntry.showDetailsButtonAriaLabel": "レイヤー詳細を表示",
"xpack.maps.layerControl.tocEntry.showDetailsButtonTitle": "レイヤー詳細を表示",
"xpack.maps.layerPanel.applyGlobalQueryCheckboxLabel": "レイヤーにグローバルフィルターを適用",
"xpack.maps.layerPanel.filterEditor.addFilterButtonLabel": "フィルターを追加します",
"xpack.maps.layerPanel.filterEditor.editFilterButtonLabel": "フィルターを編集",
"xpack.maps.layerPanel.filterEditor.emptyState.description": "フィルターを追加してレイヤーデータを絞ります。",
@ -6468,7 +6467,6 @@
"xpack.maps.layerPanel.settingsPanel.unableToLoadTitle": "レイヤーを読み込めません",
"xpack.maps.layerPanel.settingsPanel.visibleZoomLabel": "レイヤー表示のズーム範囲",
"xpack.maps.layerPanel.sourceDetailsLabel": "ソースの詳細",
"xpack.maps.layerPanel.sourceSettingsTitle": "ソース設定",
"xpack.maps.layerPanel.styleSettingsTitle": "レイヤースタイル",
"xpack.maps.layerTocActions.cloneLayerTitle": "レイヤーおクローンを作成",
"xpack.maps.layerTocActions.editLayerTitle": "レイヤーを編集",
@ -6690,7 +6688,6 @@
"xpack.maps.layerPanel.settingsPanel.percentageLabel": "%",
"xpack.maps.layerPanel.settingsPanel.visibleZoom": "ズームレベル",
"xpack.maps.source.esSearch.sortFieldSelectPlaceholder": "ソートフィールドを選択",
"xpack.maps.source.esSearch.sortLabel": "並べ替え",
"xpack.maps.toolbarOverlay.drawBoundsLabelShort": "境界を描く",
"xpack.maps.toolbarOverlay.drawShapeLabelShort": "図形を描く",
"xpack.maps.tooltipSelector.addLabelWithCount": "{count} を追加",

View file

@ -6435,7 +6435,6 @@
"xpack.maps.layerControl.tocEntry.hideDetailsButtonTitle": "隐藏图层详情",
"xpack.maps.layerControl.tocEntry.showDetailsButtonAriaLabel": "显示图层详情",
"xpack.maps.layerControl.tocEntry.showDetailsButtonTitle": "显示图层详情",
"xpack.maps.layerPanel.applyGlobalQueryCheckboxLabel": "将全局筛选应用到图层",
"xpack.maps.layerPanel.filterEditor.addFilterButtonLabel": "添加筛选",
"xpack.maps.layerPanel.filterEditor.editFilterButtonLabel": "编辑筛选",
"xpack.maps.layerPanel.filterEditor.emptyState.description": "添加筛选以缩小图层数据范围。",
@ -6470,7 +6469,6 @@
"xpack.maps.layerPanel.settingsPanel.unableToLoadTitle": "无法加载图层",
"xpack.maps.layerPanel.settingsPanel.visibleZoomLabel": "图层可见性的缩放范围",
"xpack.maps.layerPanel.sourceDetailsLabel": "源详情",
"xpack.maps.layerPanel.sourceSettingsTitle": "源设置",
"xpack.maps.layerPanel.styleSettingsTitle": "图层样式",
"xpack.maps.layerTocActions.cloneLayerTitle": "克隆图层",
"xpack.maps.layerTocActions.editLayerTitle": "编辑图层",
@ -6692,7 +6690,6 @@
"xpack.maps.layerPanel.settingsPanel.percentageLabel": "%",
"xpack.maps.layerPanel.settingsPanel.visibleZoom": "缩放级别",
"xpack.maps.source.esSearch.sortFieldSelectPlaceholder": "选择排序字段",
"xpack.maps.source.esSearch.sortLabel": "排序",
"xpack.maps.toolbarOverlay.drawBoundsLabelShort": "绘制边界",
"xpack.maps.toolbarOverlay.drawShapeLabelShort": "绘制形状",
"xpack.maps.tooltipSelector.addLabelWithCount": "添加 {count} 个",