[Maps] show dynamic style ranges in legend (#35417) (#36229)

* show grab and edit buttons when hovering over layer name

* display layer details when layer name is clicked

* add range to dynamic style property state

* render dynamic ranges in TOC details

* render symbol size and border width header in legend

* simplify VectorStyle.getIcon

* removed unused component

* open TOC details on map load

* save open TOC details state in embeddable config

* show gradients for dynmaic fill color icon

* round corners of dynamic icon if points only

* add tooltip to legend label

* add edit panel action to action panel

* add functional test for details in legend

* fix broken gis_page function doesLayerExist

* add unit tests for VectorStyle.getDescriptorWithDynamicRanges

* open actions menu on layer title click, add arrow up/down for togging layer details

* Design cleanup of layers panel (#31)

* fix functional test

* update jest snapshots

* refactor StylePropertyLegendRow to use same function to render lineWidth and iconSize legends

* fix functional test

* fix another functional test

* make escapeLayerName function instead of instance method

* move _isLayerDetailsOpen into prop from redux connector

* remove index.js file

* do not show legend details toggle when layer has not legend details

* rename FillableVector to FillableRectangle

* use mixin pattern instead of encapulated function call
This commit is contained in:
Nathan Reese 2019-05-07 16:11:37 -06:00 committed by GitHub
parent b9f826f011
commit 9801e5631e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1124 additions and 401 deletions

View file

@ -33,7 +33,8 @@ import {
updateFlyout,
FLYOUT_STATE,
setReadOnly,
setIsLayerTOCOpen
setIsLayerTOCOpen,
setOpenTOCDetails,
} from '../store/ui';
import { getQueryableUniqueIndexPatternIds } from '../selectors/map_selectors';
import { getInspectorAdapters } from '../store/non_serializable_instances';
@ -147,6 +148,7 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage
if (savedMap.uiStateJSON) {
const uiState = JSON.parse(savedMap.uiStateJSON);
store.dispatch(setIsLayerTOCOpen(_.get(uiState, 'isLayerTOCOpen', DEFAULT_IS_LAYER_TOC_OPEN)));
store.dispatch(setOpenTOCDetails(_.get(uiState, 'openTOCDetails', [])));
}
const layerList = getInitialLayers(savedMap.layerListJSON);

View file

@ -17,7 +17,7 @@ import {
getRefreshConfig,
getQuery,
} from '../../selectors/map_selectors';
import { getIsLayerTOCOpen } from '../../store/ui';
import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../store/ui';
import { convertMapExtentToPolygon } from '../../elasticsearch_geo_utils';
import { copyPersistentState } from '../../store/util';
import { extractReferences, injectReferences } from '../../../common/migrations/references';
@ -101,7 +101,8 @@ module.factory('SavedGisMap', function (Private) {
});
this.uiStateJSON = JSON.stringify({
isLayerTOCOpen: getIsLayerTOCOpen(state)
isLayerTOCOpen: getIsLayerTOCOpen(state),
openTOCDetails: getOpenTOCDetails(state),
});
this.bounds = convertMapExtentToPolygon(getMapExtent(state));

View file

@ -13,9 +13,12 @@
pointer-events: none; /* 1 */
}
.mapWidgetOverlay__rightSideWrapper {
overflow: hidden; // Fixes Chrome overflow
}
.mapWidgetOverlay__rightSide {
min-width: 19rem;
max-width: 24rem;
width: $euiSize * 20;
}
.mapWidgetOverlay__layerWrapper {

View file

@ -2,6 +2,8 @@
@include euiScrollBar;
overflow-y: auto;
flex-basis: auto !important; // Fixes IE and ensures the layer items are visible
padding-bottom: $euiSizeS + 1px;
border-top: 1px solid $euiColorLightestShade;
}
.mapLayerControl__addLayerButton,
@ -18,5 +20,5 @@
.mapLayerControl__openLayerTOCButton,
.mapLayerControl__closeLayerTOCButton {
@include size($euiSizeXL);
background-color: $euiColorEmptyShade;
background-color: $euiColorEmptyShade !important; // During all states
}

View file

@ -11,24 +11,7 @@ exports[`LayerTOC is rendered 1`] = `
droppableId="mapLayerTOC"
spacing="none"
>
<EuiDraggable
customDragHandle={true}
draggableId="2"
index={0}
key="2"
spacing="none"
>
<Component />
</EuiDraggable>
<EuiDraggable
customDragHandle={true}
draggableId="1"
index={1}
key="1"
spacing="none"
>
<Component />
</EuiDraggable>
<Component />
</EuiDroppable>
</EuiDragDropContext>
</div>

View file

@ -6,70 +6,68 @@ exports[`TOCEntry is rendered 1`] = `
data-layerid="1"
id="1"
>
<EuiFlexGroup
alignItems="center"
<div
className="mapTocEntry-visible"
gutterSize="none"
responsive={false}
>
<EuiFlexItem
grow={false}
>
<LayerTocActions
cloneLayer={[Function]}
displayName="layer 1"
fitToBounds={[Function]}
layer={
Object {
"getDisplayName": [Function],
"getId": [Function],
"getTOCDetails": [Function],
"hasErrors": [Function],
"isVisible": [Function],
"showAtZoomLevel": [Function],
}
<LayerTocActions
cloneLayer={[Function]}
displayName="layer 1"
editLayer={[Function]}
escapedDisplayName="layer_1"
fitToBounds={[Function]}
layer={
Object {
"getDisplayName": [Function],
"getId": [Function],
"getLegendDetails": [Function],
"hasErrors": [Function],
"hasLegendDetails": [Function],
"isVisible": [Function],
"showAtZoomLevel": [Function],
}
toggleVisible={[Function]}
zoom={0}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiLink
color="text"
data-test-subj="mapOpenLayerButtonlayer_1"
onClick={[Function]}
type="button"
>
<div
className="eui-textTruncate eui-textLeft"
style={
Object {
"width": 180,
}
}
>
layer 1
</div>
</EuiLink>
</EuiFlexItem>
<EuiFlexItem
grow={false}
}
toggleVisible={[Function]}
zoom={0}
/>
<div
className="mapTocEntry__layerIcons"
>
<span
<EuiButtonIcon
aria-label="Edit layer"
color="primary"
iconSize="m"
iconType="pencil"
onClick={[Function]}
title="Edit layer"
type="button"
/>
<EuiButtonIcon
aria-label="Reorder layer"
className="mapTocEntry__grab"
>
<EuiIcon
type="grab"
/>
</span>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer
size="s"
/>
<div>
TOC details mock
color="subdued"
iconSize="m"
iconType="grab"
title="Reorder layer"
type="button"
/>
</div>
</div>
<span
className="mapTocEntry__detailsToggle"
>
<button
aria-label="Show layer details"
className="mapTocEntry__detailsToggleButton"
onClick={[Function]}
title="Show layer details"
>
<EuiIcon
className="eui-alignBaseline"
size="s"
type="arrowDown"
/>
</button>
</span>
</div>
`;
@ -79,54 +77,125 @@ exports[`TOCEntry props isReadOnly 1`] = `
data-layerid="1"
id="1"
>
<EuiFlexGroup
alignItems="center"
<div
className="mapTocEntry-visible"
gutterSize="none"
responsive={false}
>
<EuiFlexItem
grow={false}
>
<LayerTocActions
cloneLayer={[Function]}
displayName="layer 1"
fitToBounds={[Function]}
isReadOnly={true}
layer={
Object {
"getDisplayName": [Function],
"getId": [Function],
"getTOCDetails": [Function],
"hasErrors": [Function],
"isVisible": [Function],
"showAtZoomLevel": [Function],
}
<LayerTocActions
cloneLayer={[Function]}
displayName="layer 1"
editLayer={[Function]}
escapedDisplayName="layer_1"
fitToBounds={[Function]}
isReadOnly={true}
layer={
Object {
"getDisplayName": [Function],
"getId": [Function],
"getLegendDetails": [Function],
"hasErrors": [Function],
"hasLegendDetails": [Function],
"isVisible": [Function],
"showAtZoomLevel": [Function],
}
toggleVisible={[Function]}
zoom={0}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiText>
<div
className="eui-textTruncate eui-textLeft"
style={
Object {
"width": 180,
}
}
>
layer 1
</div>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer
size="s"
/>
<div>
TOC details mock
}
toggleVisible={[Function]}
zoom={0}
/>
</div>
<span
className="mapTocEntry__detailsToggle"
>
<button
aria-label="Show layer details"
className="mapTocEntry__detailsToggleButton"
onClick={[Function]}
title="Show layer details"
>
<EuiIcon
className="eui-alignBaseline"
size="s"
type="arrowDown"
/>
</button>
</span>
</div>
`;
exports[`TOCEntry props should display layer details when isLegendDetailsOpen is true 1`] = `
<div
className="mapTocEntry"
data-layerid="1"
id="1"
>
<div
className="mapTocEntry-visible"
>
<LayerTocActions
cloneLayer={[Function]}
displayName="layer 1"
editLayer={[Function]}
escapedDisplayName="layer_1"
fitToBounds={[Function]}
layer={
Object {
"getDisplayName": [Function],
"getId": [Function],
"getLegendDetails": [Function],
"hasErrors": [Function],
"hasLegendDetails": [Function],
"isVisible": [Function],
"showAtZoomLevel": [Function],
}
}
toggleVisible={[Function]}
zoom={0}
/>
<div
className="mapTocEntry__layerIcons"
>
<EuiButtonIcon
aria-label="Edit layer"
color="primary"
iconSize="m"
iconType="pencil"
onClick={[Function]}
title="Edit layer"
type="button"
/>
<EuiButtonIcon
aria-label="Reorder layer"
className="mapTocEntry__grab"
color="subdued"
iconSize="m"
iconType="grab"
title="Reorder layer"
type="button"
/>
</div>
</div>
<div
className="mapTocEntry__layerDetails"
data-test-subj="mapLayerTOCDetailslayer_1"
>
<div>
TOC details mock
</div>
</div>
<span
className="mapTocEntry__detailsToggle"
>
<button
aria-label="Hide layer details"
className="mapTocEntry__detailsToggleButton"
onClick={[Function]}
title="Hide layer details"
>
<EuiIcon
className="eui-alignBaseline"
size="s"
type="arrowUp"
/>
</button>
</span>
</div>
`;

View file

@ -1,22 +1,121 @@
/**
* 1. Truncate the layer name
* 2. For showing the layer details toggle above the following entry
*/
.mapTocEntry {
padding: $euiSizeS $euiSize;
position: relative;
padding: $euiSizeS;
border-bottom: 1px solid $euiColorLightestShade;
&:hover,
&:focus,
&:focus-within {
z-index: 2; /* 2 */
.mapTocEntry__layerIcons,
.mapTocEntry__detailsToggle {
display: block;
animation: mapTocEntryBecomeVisible $euiAnimSpeedFast $euiAnimSlightResistance;
}
}
.mapTocEntry__layerIcons,
.mapTocEntry__detailsToggle {
&:hover,
&:focus {
display: block;
animation: mapTocEntryBecomeVisible $euiAnimSpeedFast $euiAnimSlightResistance;
}
}
}
.mapTocEntry-isDragging {
@include euiBottomShadowMedium;
}
.mapTocEntry-isDraggingOver {
background-color: $euiColorEmptyShade;
// Don't allow interaction events while layer is being re-ordered
pointer-events: none !important;
}
.mapTocEntry-visible {
opacity: 1;
}
.mapTocEntry-visible,
.mapTocEntry-notVisible {
opacity: 0.5;
display: flex;
}
.mapTocEntry__grab {
margin-left: $euiSizeXS;
.mapLayTocActions {
overflow: hidden; /* 1 */
flex-grow: 1;
}
.mapLayTocActions__popoverAnchor {
max-width: 100%;
}
.mapTocEntry-notVisible .mapTocEntry__layerName {
opacity: 0.5;
}
.mapTocEntry__grab:hover {
cursor: grab;
}
.mapTocEntry__layerName {
font-weight: $euiFontWeightMedium;
}
.mapTocEntry__layerNameText {
display: flex;
align-items: center;
}
.mapTocEntry__layerNameIcon {
flex-shrink: 0;
margin-right: $euiSizeS;
> * {
vertical-align: sub;
}
}
.mapTocEntry__layerIcons {
flex-shrink: 0;
display: none;
}
.mapTocEntry__detailsToggle {
position: absolute;
display: none;
left: 50%;
top: $euiSizeXL;
transform: translateX(-50%);
}
.mapTocEntry__detailsToggleButton {
background-color: $euiColorEmptyShade;
border: $euiBorderThin;
color: $euiTextColor;
border-radius: $euiBorderRadius / 2;
height: $euiSize;
width: $euiSizeXL;
line-height: $euiSize;
text-align: center;
&:focus {
@include euiFocusRing;
}
}
.mapTocEntry__layerDetails {
@include euiOverflowShadow(transparentize($euiShadowColor, .7));
background-color: $euiPageBackgroundColor;
padding: $euiSize $euiSizeS $euiSizeS;
margin: $euiSizeS (-$euiSizeS) (-$euiSizeS);
}
@keyframes mapTocEntryBecomeVisible {
0% { opacity: 0; }
100% { opacity: 1; }
}

View file

@ -7,7 +7,14 @@
import _ from 'lodash';
import { connect } from 'react-redux';
import { TOCEntry } from './view';
import { getIsReadOnly, updateFlyout, FLYOUT_STATE } from '../../../../../store/ui';
import {
getIsReadOnly,
updateFlyout,
FLYOUT_STATE,
getOpenTOCDetails,
hideTOCDetails,
showTOCDetails,
} from '../../../../../store/ui';
import {
fitToLayerExtent,
setSelectedLayer,
@ -18,12 +25,13 @@ import {
import { hasDirtyState, getSelectedLayer } from '../../../../../selectors/map_selectors';
function mapStateToProps(state = {}) {
function mapStateToProps(state = {}, ownProps) {
return {
isReadOnly: getIsReadOnly(state),
zoom: _.get(state, 'map.mapState.zoom', 0),
selectedLayer: getSelectedLayer(state),
hasDirtyStateSelector: hasDirtyState(state),
isLegendDetailsOpen: getOpenTOCDetails(state).includes(ownProps.layer.getId()),
};
}
@ -42,7 +50,13 @@ function mapDispatchToProps(dispatch) {
},
cloneLayer: layerId => {
dispatch(cloneLayer(layerId));
}
},
hideTOCDetails: layerId => {
dispatch(hideTOCDetails(layerId));
},
showTOCDetails: layerId => {
dispatch(showTOCDetails(layerId));
},
});
}

View file

@ -4,22 +4,27 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment } from 'react';
import React from 'react';
import classNames from 'classnames';
import {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiSpacer,
EuiOverlayMask,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiButton,
EuiButtonEmpty,
EuiLink,
EuiText,
EuiButtonIcon,
} from '@elastic/eui';
import { LayerTocActions } from '../../../../../shared/components/layer_toc_actions';
import { i18n } from '@kbn/i18n';
function escapeLayerName(name) {
return name
? name.split(' ').join('_')
: '';
}
export class TOCEntry extends React.Component {
@ -37,6 +42,18 @@ export class TOCEntry extends React.Component {
this._isMounted = false;
}
componentDidUpdate() {
this._updateDisplayName();
}
_toggleLayerDetailsVisibility = () => {
if (this.props.isLegendDetailsOpen) {
this.props.hideTOCDetails(this.props.layer.getId());
} else {
this.props.showTOCDetails(this.props.layer.getId());
}
}
async _updateDisplayName() {
const label = await this.props.layer.getDisplayName();
if (this._isMounted) {
@ -48,10 +65,6 @@ export class TOCEntry extends React.Component {
}
}
componentDidUpdate() {
this._updateDisplayName();
}
_openLayerPanelWithCheck = () => {
const { selectedLayer, hasDirtyStateSelector } = this.props;
if (selectedLayer && selectedLayer.getId() === this.props.layer.getId()) {
@ -112,33 +125,73 @@ export class TOCEntry extends React.Component {
);
}
_renderLayerName() {
const displayName = (
<div style={{ width: 180 }} className="eui-textTruncate eui-textLeft">
{this.state.displayName}
</div>
);
_renderLayerIcons() {
if (this.props.isReadOnly) {
return (
<EuiText>
{displayName}
</EuiText>
);
return null;
}
return (
<EuiLink
color="text"
onClick={this._openLayerPanelWithCheck}
data-test-subj={
`mapOpenLayerButton${this.state.displayName
? this.state.displayName.replace(' ', '_')
: ''}`
}
>
{displayName}
</EuiLink>
<div className="mapTocEntry__layerIcons">
<EuiButtonIcon
iconType="pencil"
aria-label={i18n.translate('xpack.maps.layerControl.tocEntry.editButtonAriaLabel', {
defaultMessage: 'Edit layer'
})}
title={i18n.translate('xpack.maps.layerControl.tocEntry.editButtonTitle', {
defaultMessage: 'Edit layer'
})}
onClick={this._openLayerPanelWithCheck}
/>
<EuiButtonIcon
iconType="grab"
color="subdued"
title={i18n.translate('xpack.maps.layerControl.tocEntry.grabButtonTitle', {
defaultMessage: 'Reorder layer'
})}
aria-label={i18n.translate('xpack.maps.layerControl.tocEntry.grabButtonAriaLabel', {
defaultMessage: 'Reorder layer'
})}
className="mapTocEntry__grab"
{...this.props.dragHandleProps}
/>
</div>
);
}
_renderDetailsToggle() {
if (!this.props.layer.hasLegendDetails()) {
return null;
}
const { isLegendDetailsOpen } = this.props;
return (
<span className="mapTocEntry__detailsToggle">
<button
className="mapTocEntry__detailsToggleButton"
aria-label={isLegendDetailsOpen
? i18n.translate('xpack.maps.layerControl.tocEntry.hideDetailsButtonAriaLabel', {
defaultMessage: 'Hide layer details'
})
: i18n.translate('xpack.maps.layerControl.tocEntry.showDetailsButtonAriaLabel', {
defaultMessage: 'Show layer details'
})
}
title={isLegendDetailsOpen
? i18n.translate('xpack.maps.layerControl.tocEntry.hideDetailsButtonTitle', {
defaultMessage: 'Hide layer details'
})
: i18n.translate('xpack.maps.layerControl.tocEntry.showDetailsButtonTitle', {
defaultMessage: 'Show layer details'
})
}
onClick={this._toggleLayerDetailsVisibility}
>
<EuiIcon className="eui-alignBaseline" type={isLegendDetailsOpen ? 'arrowUp' : 'arrowDown'} size="s" />
</button>
</span>
);
}
@ -152,78 +205,78 @@ export class TOCEntry extends React.Component {
fitToBounds
} = this.props;
let sortIcon;
if (!isReadOnly) {
sortIcon = (
<EuiFlexItem grow={false}>
<span className="mapTocEntry__grab" {...this.props.dragHandleProps}>
<EuiIcon type="grab"/>
</span>
</EuiFlexItem>
);
}
return (
<EuiFlexGroup
gutterSize="none"
alignItems="center"
responsive={false}
<div
className={
layer.isVisible() && layer.showAtZoomLevel(zoom)
&& !layer.hasErrors() ? 'mapTocEntry-visible' : 'mapTocEntry-notVisible'
}
>
<EuiFlexItem grow={false}>
<LayerTocActions
layer={layer}
fitToBounds={() => {
fitToBounds(layer.getId());
}}
zoom={zoom}
toggleVisible={() => {
toggleVisible(layer.getId());
}}
displayName={this.state.displayName}
cloneLayer={() => {
cloneLayer(layer.getId());
}}
isReadOnly={this.props.isReadOnly}
/>
</EuiFlexItem>
<EuiFlexItem>
{this._renderLayerName()}
</EuiFlexItem>
{sortIcon}
</EuiFlexGroup>
<LayerTocActions
layer={layer}
fitToBounds={() => {
fitToBounds(layer.getId());
}}
zoom={zoom}
toggleVisible={() => {
toggleVisible(layer.getId());
}}
displayName={this.state.displayName}
escapedDisplayName={escapeLayerName(this.state.displayName)}
cloneLayer={() => {
cloneLayer(layer.getId());
}}
editLayer={this._openLayerPanelWithCheck}
isReadOnly={isReadOnly}
/>
{this._renderLayerIcons()}
</div>
);
}
_renderLayerDetails() {
const tocDetails = this.props.layer.getTOCDetails();
_renderLegendDetails = () => {
if (!this.props.isLegendDetailsOpen || !this.props.layer.hasLegendDetails()) {
return null;
}
const tocDetails = this.props.layer.getLegendDetails();
if (!tocDetails) {
return null;
}
return (
<Fragment>
<EuiSpacer size="s"/>
<div
className="mapTocEntry__layerDetails"
data-test-subj={`mapLayerTOCDetails${escapeLayerName(this.state.displayName)}`}
>
{tocDetails}
</Fragment>
</div>
);
}
render() {
const classes = classNames(
'mapTocEntry',
{
'mapTocEntry-isDragging': this.props.isDragging,
'mapTocEntry-isDraggingOver': this.props.isDraggingOver,
},
);
return (
<div
className="mapTocEntry"
className={classes}
id={this.props.layer.getId()}
data-layerid={this.props.layer.getId()}
>
{this._renderCancelModal()}
{this._renderLayerHeader()}
{this._renderLayerDetails()}
{this._renderLegendDetails()}
{this._renderDetailsToggle()}
{this._renderCancelModal()}
</div>
);
}

View file

@ -9,13 +9,16 @@ import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { TOCEntry } from './view';
const LAYER_ID = '1';
const mockLayer = {
getId: () => { return '1'; },
getTOCDetails: () => { return (<div>TOC details mock</div>); },
getId: () => { return LAYER_ID; },
getLegendDetails: () => { return (<div>TOC details mock</div>); },
getDisplayName: () => { return 'layer 1'; },
isVisible: () => { return true; },
showAtZoomLevel: () => { return true; },
hasErrors: () => { return false; },
hasLegendDetails: () => { return true; },
};
const defaultProps = {
@ -26,6 +29,7 @@ const defaultProps = {
getSelectedLayerSelector: () => {},
hasDirtyStateSelector: () => {},
zoom: 0,
isLegendDetailsOpen: false,
};
describe('TOCEntry', () => {
@ -62,5 +66,22 @@ describe('TOCEntry', () => {
expect(component)
.toMatchSnapshot();
});
test('should display layer details when isLegendDetailsOpen is true', async () => {
const component = shallowWithIntl(
<TOCEntry
{...defaultProps}
isLegendDetailsOpen={true}
/>
);
// Ensure all promises resolve
await new Promise(resolve => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();
expect(component)
.toMatchSnapshot();
});
});
});

View file

@ -65,21 +65,30 @@ export class LayerTOC extends React.Component {
});
}
const draggableLayers = reverseLayerList.map((layer, idx) => (
<EuiDraggable spacing="none" key={layer.getId()} index={idx} draggableId={layer.getId()} customDragHandle={true}>
{(provided) => (
<TOCEntry
layer={layer}
dragHandleProps={provided.dragHandleProps}
/>
)}
</EuiDraggable>
));
return (
<EuiDragDropContext onDragEnd={this._onDragEnd}>
<EuiDroppable droppableId="mapLayerTOC" spacing="none">
{draggableLayers}
{(provided, snapshot) => (
reverseLayerList.map((layer, idx) => (
<EuiDraggable
spacing="none"
key={layer.getId()}
index={idx}
draggableId={layer.getId()}
customDragHandle={true}
disableInteractiveElementBlocking // Allows button to be drag handle
>
{(provided, state) => (
<TOCEntry
layer={layer}
dragHandleProps={provided.dragHandleProps}
isDragging={state.isDragging}
isDraggingOver={snapshot.isDraggingOver}
/>
)}
</EuiDraggable>
))
)}
</EuiDroppable>
</EuiDragDropContext>
);

View file

@ -16,7 +16,7 @@ import { AttributionControl } from './attribution_control';
export function WidgetOverlay() {
return (
<EuiFlexGroup className="mapWidgetOverlay" responsive={false} direction="column" alignItems="flexEnd" gutterSize="s">
<EuiFlexItem>
<EuiFlexItem className="mapWidgetOverlay__rightSideWrapper">
<EuiFlexGroup
className="mapWidgetOverlay__rightSide"
direction="column"

View file

@ -26,9 +26,11 @@ import {
import {
DEFAULT_IS_LAYER_TOC_OPEN,
getIsLayerTOCOpen,
getOpenTOCDetails,
setReadOnly,
setFilterable,
setIsLayerTOCOpen
setIsLayerTOCOpen,
setOpenTOCDetails,
} from '../store/ui';
import { getInspectorAdapters } from '../store/non_serializable_instances';
import { getMapCenter, getMapZoom } from '../selectors/map_selectors';
@ -99,6 +101,13 @@ export class MapEmbeddable extends Embeddable {
this._store.dispatch(setIsLayerTOCOpen(_.get(uiState, 'isLayerTOCOpen', DEFAULT_IS_LAYER_TOC_OPEN)));
}
if (_.has(this._embeddableConfig, 'openTOCDetails')) {
this._store.dispatch(setOpenTOCDetails(this._embeddableConfig.openTOCDetails));
} else if (this._savedMap.uiStateJSON) {
const uiState = JSON.parse(this._savedMap.uiStateJSON);
this._store.dispatch(setOpenTOCDetails(_.get(uiState, 'openTOCDetails', [])));
}
if (this._embeddableConfig.mapCenter) {
this._store.dispatch(setGotoWithCenter({
lat: this._embeddableConfig.mapCenter.lat,
@ -168,12 +177,17 @@ export class MapEmbeddable extends Embeddable {
}
const isLayerTOCOpen = getIsLayerTOCOpen(this._store.getState());
if (!this._embeddableConfig.isLayerTOCOpen
|| this._embeddableConfig.isLayerTOCOpen !== isLayerTOCOpen) {
if (this._embeddableConfig.isLayerTOCOpen !== isLayerTOCOpen) {
embeddableConfigChanged = true;
this._embeddableConfig.isLayerTOCOpen = isLayerTOCOpen;
}
const openTOCDetails = getOpenTOCDetails(this._store.getState());
if (!_.isEqual(this._embeddableConfig.openTOCDetails, openTOCDetails)) {
embeddableConfigChanged = true;
this._embeddableConfig.openTOCDetails = openTOCDetails;
}
if (embeddableConfigChanged) {
this._onEmbeddableStateChanged({
customization: this._embeddableConfig

View file

@ -2,9 +2,11 @@
exports[`LayerTocActions is rendered 1`] = `
<EuiPopover
anchorClassName="mapLayTocActions__popoverAnchor"
anchorPosition="leftUp"
button={
<EuiButtonEmpty
className="mapTocEntry__layerName eui-textLeft"
color="text"
data-test-subj="layerTocActionsPanelToggleButtonlayer1"
flush="left"
@ -13,11 +15,17 @@ exports[`LayerTocActions is rendered 1`] = `
size="xs"
type="button"
>
<div>
icon mock
</div>
<span
className="mapTocEntry__layerNameIcon"
>
<div>
icon mock
</div>
</span>
layer 1
</EuiButtonEmpty>
}
className="mapLayTocActions"
closePopover={[Function]}
hasArrow={true}
id="contextMenu"
@ -54,6 +62,15 @@ exports[`LayerTocActions is rendered 1`] = `
"name": "Hide layer",
"onClick": [Function],
},
Object {
"data-test-subj": "editLayerButton",
"icon": <EuiIcon
size="m"
type="pencil"
/>,
"name": "Edit layer",
"onClick": [Function],
},
Object {
"data-test-subj": "cloneLayerButton",
"icon": <EuiIcon
@ -74,9 +91,11 @@ exports[`LayerTocActions is rendered 1`] = `
exports[`LayerTocActions should disable fit to data when supportsFitToBounds is false 1`] = `
<EuiPopover
anchorClassName="mapLayTocActions__popoverAnchor"
anchorPosition="leftUp"
button={
<EuiButtonEmpty
className="mapTocEntry__layerName eui-textLeft"
color="text"
data-test-subj="layerTocActionsPanelToggleButtonlayer1"
flush="left"
@ -85,11 +104,17 @@ exports[`LayerTocActions should disable fit to data when supportsFitToBounds is
size="xs"
type="button"
>
<div>
icon mock
</div>
<span
className="mapTocEntry__layerNameIcon"
>
<div>
icon mock
</div>
</span>
layer 1
</EuiButtonEmpty>
}
className="mapLayTocActions"
closePopover={[Function]}
hasArrow={true}
id="contextMenu"
@ -126,6 +151,15 @@ exports[`LayerTocActions should disable fit to data when supportsFitToBounds is
"name": "Hide layer",
"onClick": [Function],
},
Object {
"data-test-subj": "editLayerButton",
"icon": <EuiIcon
size="m"
type="pencil"
/>,
"name": "Edit layer",
"onClick": [Function],
},
Object {
"data-test-subj": "cloneLayerButton",
"icon": <EuiIcon
@ -146,9 +180,11 @@ exports[`LayerTocActions should disable fit to data when supportsFitToBounds is
exports[`LayerTocActions should display spinner when layer is loading 1`] = `
<EuiPopover
anchorClassName="mapLayTocActions__popoverAnchor"
anchorPosition="leftUp"
button={
<EuiButtonEmpty
className="mapTocEntry__layerName eui-textLeft"
color="text"
data-test-subj="layerTocActionsPanelToggleButtonlayer1"
flush="left"
@ -157,11 +193,17 @@ exports[`LayerTocActions should display spinner when layer is loading 1`] = `
size="xs"
type="button"
>
<EuiLoadingSpinner
size="m"
/>
<span
className="mapTocEntry__layerNameIcon"
>
<EuiLoadingSpinner
size="m"
/>
</span>
layer 1
</EuiButtonEmpty>
}
className="mapLayTocActions"
closePopover={[Function]}
hasArrow={true}
id="contextMenu"
@ -198,6 +240,15 @@ exports[`LayerTocActions should display spinner when layer is loading 1`] = `
"name": "Hide layer",
"onClick": [Function],
},
Object {
"data-test-subj": "editLayerButton",
"icon": <EuiIcon
size="m"
type="pencil"
/>,
"name": "Edit layer",
"onClick": [Function],
},
Object {
"data-test-subj": "cloneLayerButton",
"icon": <EuiIcon
@ -218,9 +269,11 @@ exports[`LayerTocActions should display spinner when layer is loading 1`] = `
exports[`LayerTocActions should not show edit actions in read only mode 1`] = `
<EuiPopover
anchorClassName="mapLayTocActions__popoverAnchor"
anchorPosition="leftUp"
button={
<EuiButtonEmpty
className="mapTocEntry__layerName eui-textLeft"
color="text"
data-test-subj="layerTocActionsPanelToggleButtonlayer1"
flush="left"
@ -229,11 +282,17 @@ exports[`LayerTocActions should not show edit actions in read only mode 1`] = `
size="xs"
type="button"
>
<div>
icon mock
</div>
<span
className="mapTocEntry__layerNameIcon"
>
<div>
icon mock
</div>
</span>
layer 1
</EuiButtonEmpty>
}
className="mapLayTocActions"
closePopover={[Function]}
hasArrow={true}
id="contextMenu"
@ -281,9 +340,11 @@ exports[`LayerTocActions should not show edit actions in read only mode 1`] = `
exports[`LayerTocActions should provide feedback when layer is not visible because of current zoom level 1`] = `
<EuiPopover
anchorClassName="mapLayTocActions__popoverAnchor"
anchorPosition="leftUp"
button={
<EuiButtonEmpty
className="mapTocEntry__layerName eui-textLeft"
color="text"
data-test-subj="layerTocActionsPanelToggleButtonlayer1"
flush="left"
@ -292,18 +353,24 @@ exports[`LayerTocActions should provide feedback when layer is not visible becau
size="xs"
type="button"
>
<EuiToolTip
content="Map is at zoom level 0.
This layer is only visible between zoom levels 2 to 3."
delay="regular"
position="top"
<span
className="mapTocEntry__layerNameIcon"
>
<div>
icon mock
</div>
</EuiToolTip>
<EuiToolTip
content="Map is at zoom level 0.
This layer is only visible between zoom levels 2 to 3."
delay="regular"
position="top"
>
<div>
icon mock
</div>
</EuiToolTip>
</span>
layer 1
</EuiButtonEmpty>
}
className="mapLayTocActions"
closePopover={[Function]}
hasArrow={true}
id="contextMenu"
@ -340,6 +407,15 @@ exports[`LayerTocActions should provide feedback when layer is not visible becau
"name": "Hide layer",
"onClick": [Function],
},
Object {
"data-test-subj": "editLayerButton",
"icon": <EuiIcon
size="m"
type="pencil"
/>,
"name": "Edit layer",
"onClick": [Function],
},
Object {
"data-test-subj": "cloneLayerButton",
"icon": <EuiIcon
@ -360,9 +436,11 @@ exports[`LayerTocActions should provide feedback when layer is not visible becau
exports[`LayerTocActions should show visible toggle when layer is not visible 1`] = `
<EuiPopover
anchorClassName="mapLayTocActions__popoverAnchor"
anchorPosition="leftUp"
button={
<EuiButtonEmpty
className="mapTocEntry__layerName eui-textLeft"
color="text"
data-test-subj="layerTocActionsPanelToggleButtonlayer1"
flush="left"
@ -371,11 +449,17 @@ exports[`LayerTocActions should show visible toggle when layer is not visible 1`
size="xs"
type="button"
>
<div>
icon mock
</div>
<span
className="mapTocEntry__layerNameIcon"
>
<div>
icon mock
</div>
</span>
layer 1
</EuiButtonEmpty>
}
className="mapLayTocActions"
closePopover={[Function]}
hasArrow={true}
id="contextMenu"
@ -412,6 +496,15 @@ exports[`LayerTocActions should show visible toggle when layer is not visible 1`
"name": "Show layer",
"onClick": [Function],
},
Object {
"data-test-subj": "editLayerButton",
"icon": <EuiIcon
size="m"
type="pencil"
/>,
"name": "Edit layer",
"onClick": [Function],
},
Object {
"data-test-subj": "cloneLayerButton",
"icon": <EuiIcon
@ -432,9 +525,11 @@ exports[`LayerTocActions should show visible toggle when layer is not visible 1`
exports[`LayerTocActions should show warning when layer has errors 1`] = `
<EuiPopover
anchorClassName="mapLayTocActions__popoverAnchor"
anchorPosition="leftUp"
button={
<EuiButtonEmpty
className="mapTocEntry__layerName eui-textLeft"
color="text"
data-test-subj="layerTocActionsPanelToggleButtonlayer1"
flush="left"
@ -443,15 +538,21 @@ exports[`LayerTocActions should show warning when layer has errors 1`] = `
size="xs"
type="button"
>
<EuiIconTip
aria-label="Load warning"
color="warning"
content="simulated layer error"
size="m"
type="alert"
/>
<span
className="mapTocEntry__layerNameIcon"
>
<EuiIconTip
aria-label="Load warning"
color="warning"
content="simulated layer error"
size="m"
type="alert"
/>
</span>
layer 1
</EuiButtonEmpty>
}
className="mapLayTocActions"
closePopover={[Function]}
hasArrow={true}
id="contextMenu"
@ -488,6 +589,15 @@ exports[`LayerTocActions should show warning when layer has errors 1`] = `
"name": "Hide layer",
"onClick": [Function],
},
Object {
"data-test-subj": "editLayerButton",
"icon": <EuiIcon
size="m"
type="pencil"
/>,
"name": "Edit layer",
"onClick": [Function],
},
Object {
"data-test-subj": "cloneLayerButton",
"icon": <EuiIcon

View file

@ -13,17 +13,10 @@ import {
EuiIcon,
EuiLoadingSpinner,
EuiToolTip,
EuiIconTip
EuiIconTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
function cleanDisplayName(displayName) {
if (!displayName) {
return displayName;
}
return displayName.split(' ').join('');
}
export class LayerTocActions extends Component {
state = {
@ -63,13 +56,16 @@ export class LayerTocActions extends Component {
const icon = this._renderIcon();
return (
<EuiButtonEmpty
className="mapTocEntry__layerName eui-textLeft"
size="xs"
flush="left"
color="text"
onClick={this._onClick}
data-test-subj={`layerTocActionsPanelToggleButton${cleanDisplayName(this.props.displayName)}`}
data-test-subj={`layerTocActionsPanelToggleButton${this.props.escapedDisplayName}`}
// textProps="mapTocEntry__layerNameText"
>
{icon}
<span className="mapTocEntry__layerNameIcon">{icon}</span>
{this.props.displayName}
</EuiButtonEmpty>);
}
@ -160,6 +156,22 @@ export class LayerTocActions extends Component {
];
if (!this.props.isReadOnly) {
actionItems.push({
name: i18n.translate('xpack.maps.layerTocActions.editLayerTitle', {
defaultMessage: 'Edit layer',
}),
icon: (
<EuiIcon
type="pencil"
size="m"
/>
),
'data-test-subj': 'editLayerButton',
onClick: () => {
this._closePopover();
this.props.editLayer();
}
});
actionItems.push({
name: i18n.translate('xpack.maps.layerTocActions.cloneLayerTitle', {
defaultMessage: 'Clone layer',
@ -193,17 +205,19 @@ export class LayerTocActions extends Component {
return (
<EuiPopover
id="contextMenu"
className="mapLayTocActions"
button={this._renderButton()}
isOpen={this.state.isPopoverOpen}
closePopover={this._closePopover}
panelPaddingSize="none"
withTitle
anchorPosition="leftUp"
anchorClassName="mapLayTocActions__popoverAnchor"
>
<EuiContextMenu
initialPanelId={0}
panels={this._getPanels()}
data-test-subj={`layerTocActionsPanel${cleanDisplayName(this.props.displayName)}`}
data-test-subj={`layerTocActionsPanel${this.props.escapedDisplayName}`}
/>
</EuiPopover>);
}

View file

@ -26,7 +26,8 @@ const layerMock = {
};
const defaultProps = {
displayName: 'layer1',
displayName: 'layer 1',
escapedDisplayName: 'layer1',
zoom: 0,
layer: layerMock,
};

View file

@ -44,7 +44,7 @@ export const FillableCircle = ({ style }) => (
</svg>
);
export const FillableVector = ({ style }) => (
export const FillableRectangle = ({ style }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"

View file

@ -7,23 +7,25 @@
import React from 'react';
import { vislibColorMaps } from 'ui/vislib/components/color/colormaps';
import { getLegendColors } from 'ui/vis/map/color_util';
import classNames from 'classnames';
const GRADIENT_INTERVALS = 7;
const COLOR_KEYS = Object.keys(vislibColorMaps);
export const ColorGradient = ({ color }) => {
export const ColorGradient = ({ color, className }) => {
if (!color || !COLOR_KEYS.includes(color)) {
return null;
} else {
const rgbColorStrings = getLegendColors(vislibColorMaps[color].value, GRADIENT_INTERVALS);
const background = getLinearGradient(rgbColorStrings, GRADIENT_INTERVALS);
return (
<div
className="mapColorGradient"
style={{ background }}
/>
);
}
const classes = classNames('mapColorGradient', className);
const rgbColorStrings = getLegendColors(vislibColorMaps[color].value, GRADIENT_INTERVALS);
const background = getLinearGradient(rgbColorStrings, GRADIENT_INTERVALS);
return (
<div
className={classes}
style={{ background }}
/>
);
};
function getLinearGradient(colorStrings, intervals) {

View file

@ -105,7 +105,11 @@ export class AbstractLayer {
console.warn('Icon not available for this layer type');
}
getTOCDetails() {
hasLegendDetails() {
return false;
}
getLegendDetails() {
return null;
}

View file

@ -105,7 +105,7 @@ export class StaticDynamicStyleRow extends React.Component {
return (
<EuiFlexGroup gutterSize="s">
<EuiFlexItem className={isDynamic ? 'mapStaticDynamicSylingOption__dynamicSizeHack' : undefined}>
<EuiFormRow label={this.props.name && this.props.name}>
<EuiFormRow label={this.props.label && this.props.label}>
{this._renderStyleSelector()}
</EuiFormRow>
</EuiFlexItem>

View file

@ -7,6 +7,7 @@
import _ from 'lodash';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { dynamicColorShape } from '../style_option_shapes';
import { FieldSelect, fieldShape } from '../field_select';
import { ColorRampSelect } from './color_ramp_select';
import { EuiSpacer } from '@elastic/eui';
@ -38,9 +39,6 @@ export function DynamicColorSelection({ ordinalFields, onChange, styleOptions })
DynamicColorSelection.propTypes = {
ordinalFields: PropTypes.arrayOf(fieldShape).isRequired,
styleOptions: PropTypes.shape({
color: PropTypes.string.isRequired,
field: fieldShape,
}).isRequired,
styleOptions: dynamicColorShape.isRequired,
onChange: PropTypes.func.isRequired
};

View file

@ -10,6 +10,7 @@ import {
EuiColorPicker,
EuiFormControlLayout
} from '@elastic/eui';
import { staticColorShape } from '../style_option_shapes';
export function StaticColorSelection({ onChange, styleOptions }) {
const onColorChange = color => {
@ -28,8 +29,6 @@ export function StaticColorSelection({ onChange, styleOptions }) {
}
StaticColorSelection.propTypes = {
styleOptions: PropTypes.shape({
color: PropTypes.string.isRequired,
}).isRequired,
styleOptions: staticColorShape.isRequired,
onChange: PropTypes.func.isRequired
};

View file

@ -9,13 +9,14 @@ import React from 'react';
import { StaticDynamicStyleRow } from '../../static_dynamic_style_row';
import { DynamicColorSelection } from './dynamic_color_selection';
import { StaticColorSelection } from './static_color_selection';
import { getVectorStyleLabel } from '../get_vector_style_label';
export function VectorStyleColorEditor(props) {
return (
<StaticDynamicStyleRow
ordinalFields={props.ordinalFields}
property={props.styleProperty}
name={props.stylePropertyName}
label={getVectorStyleLabel(props.styleProperty)}
styleDescriptor={props.styleDescriptor}
handlePropertyChange={props.handlePropertyChange}
DynamicSelector={DynamicColorSelection}

View file

@ -0,0 +1,30 @@
/*
* 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 { i18n } from '@kbn/i18n';
export function getVectorStyleLabel(styleName) {
switch (styleName) {
case 'fillColor':
return i18n.translate('xpack.maps.styles.vector.fillColorLabel', {
defaultMessage: 'Fill color'
});
case 'lineColor':
return i18n.translate('xpack.maps.styles.vector.borderColorLabel', {
defaultMessage: 'Border color'
});
case 'lineWidth':
return i18n.translate('xpack.maps.styles.vector.borderWidthLabel', {
defaultMessage: 'Border width'
});
case 'iconSize':
return i18n.translate('xpack.maps.styles.vector.symbolSizeLabel', {
defaultMessage: 'Symbol size'
});
default:
return styleName;
}
}

View file

@ -0,0 +1,134 @@
/*
* 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 _ from 'lodash';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { styleOptionShapes, rangeShape } from '../style_option_shapes';
import { VectorStyle } from '../../../vector_style';
import { ColorGradient } from '../../../../../icons/color_gradient';
import { FillableCircle } from '../../../../../icons/additional_layer_icons';
import { getVectorStyleLabel } from '../get_vector_style_label';
import {
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiSpacer,
EuiToolTip,
EuiHorizontalRule,
} from '@elastic/eui';
function getLineWidthIcons() {
const defaultStyle = {
stroke: 'grey',
fill: 'none',
width: '12px',
};
return [
<FillableCircle style={{ ...defaultStyle, strokeWidth: '1px' }}/>,
<FillableCircle style={{ ...defaultStyle, strokeWidth: '2px' }}/>,
<FillableCircle style={{ ...defaultStyle, strokeWidth: '3px' }}/>,
];
}
function getSymbolSizeIcons() {
const defaultStyle = {
stroke: 'grey',
strokeWidth: 'none',
fill: 'grey',
};
return [
<FillableCircle style={{ ...defaultStyle, width: '4px' }}/>,
<FillableCircle style={{ ...defaultStyle, width: '8px' }}/>,
<FillableCircle style={{ ...defaultStyle, width: '12px' }}/>,
];
}
function renderHeaderWithIcons(icons) {
return (
<EuiFlexGroup gutterSize="s" justifyContent="spaceBetween" alignItems="center">
{
icons.map((icon, index) => {
const isLast = index === icons.length - 1;
let spacer;
if (!isLast) {
spacer = (
<EuiFlexItem>
<EuiHorizontalRule margin="xs" />
</EuiFlexItem>
);
}
return (
<Fragment key={index}>
<EuiFlexItem grow={false}>
{icon}
</EuiFlexItem>
{spacer}
</Fragment>
);
})
}
</EuiFlexGroup>
);
}
export function StylePropertyLegendRow({ name, type, options, range }) {
if (type === VectorStyle.STYLE_TYPE.STATIC ||
!options.field || !options.field.name) {
return null;
}
let header;
if (options.color) {
header = <ColorGradient color={options.color}/>;
} else if (name === 'lineWidth') {
header = renderHeaderWithIcons(getLineWidthIcons());
} else if (name === 'iconSize') {
header = renderHeaderWithIcons(getSymbolSizeIcons());
}
return (
<div>
<EuiSpacer size="xs"/>
{header}
<EuiFlexGroup gutterSize="xs" justifyContent="spaceBetween">
<EuiFlexItem grow={true}>
<EuiText size="xs">
<small>{_.get(range, 'min', '')}</small>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip
position="top"
title={getVectorStyleLabel(name)}
content={options.field.label}
>
<EuiText
className="eui-textTruncate"
size="xs"
style={{ maxWidth: '180px' }}
>
<small><strong>{options.field.label}</strong></small>
</EuiText>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<EuiText textAlign="right" size="xs">
<small>{_.get(range, 'max', '')}</small>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</div>
);
}
StylePropertyLegendRow.propTypes = {
name: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
options: PropTypes.oneOfType(styleOptionShapes).isRequired,
range: rangeShape,
};

View file

@ -0,0 +1,52 @@
/*
* 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 PropTypes from 'prop-types';
import { dynamicColorShape, staticColorShape } from '../style_option_shapes';
import { FillableCircle, FillableRectangle } from '../../../../../icons/additional_layer_icons';
import { VectorStyle } from '../../../vector_style';
import { getColorRampCenterColor } from '../../../../../utils/color_utils';
export function VectorIcon({ fillColor, lineColor, isPointsOnly }) {
const style = {
stroke: extractColorFromStyleProperty(lineColor, 'none'),
strokeWidth: '1px',
fill: extractColorFromStyleProperty(fillColor, 'grey'),
};
return isPointsOnly
? <FillableCircle style={style}/>
: <FillableRectangle style={style}/>;
}
function extractColorFromStyleProperty(colorStyleProperty, defaultColor) {
if (!colorStyleProperty) {
return defaultColor;
}
if (colorStyleProperty.type === VectorStyle.STYLE_TYPE.STATIC) {
return colorStyleProperty.options.color;
}
// return middle of gradient for dynamic style property
return getColorRampCenterColor(colorStyleProperty.options.color);
}
const colorStylePropertyShape = PropTypes.shape({
type: PropTypes.string.isRequired,
options: PropTypes.oneOfType([
dynamicColorShape,
staticColorShape
]).isRequired,
});
VectorIcon.propTypes = {
fillColor: colorStylePropertyShape,
lineColor: colorStylePropertyShape,
isPointsOnly: PropTypes.bool,
};

View file

@ -0,0 +1,36 @@
/*
* 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 PropTypes from 'prop-types';
import { styleOptionShapes, rangeShape } from '../style_option_shapes';
import { StylePropertyLegendRow } from './style_property_legend_row';
export function VectorStyleLegend({ styleProperties }) {
return styleProperties.map(styleProperty => {
return (
<StylePropertyLegendRow
key={styleProperty.name}
name={styleProperty.name}
type={styleProperty.type}
options={styleProperty.options}
range={styleProperty.range}
/>
);
});
}
const stylePropertyShape = PropTypes.shape({
name: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
options: PropTypes.oneOfType(styleOptionShapes).isRequired,
range: rangeShape,
});
VectorStyleLegend.propTypes = {
styleProperties: PropTypes.arrayOf(stylePropertyShape).isRequired
};

View file

@ -7,6 +7,7 @@
import _ from 'lodash';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { dynamicSizeShape } from '../style_option_shapes';
import { FieldSelect, fieldShape } from '../field_select';
import { SizeRangeSelector } from './size_range_selector';
import { EuiSpacer } from '@elastic/eui';
@ -39,10 +40,6 @@ export function DynamicSizeSelection({ ordinalFields, styleOptions, onChange })
DynamicSizeSelection.propTypes = {
ordinalFields: PropTypes.arrayOf(fieldShape).isRequired,
styleOptions: PropTypes.shape({
minSize: PropTypes.number.isRequired,
maxSize: PropTypes.number.isRequired,
field: fieldShape,
}).isRequired,
styleOptions: dynamicSizeShape.isRequired,
onChange: PropTypes.func.isRequired
};

View file

@ -6,6 +6,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { staticSizeShape } from '../style_option_shapes';
import { ValidatedRange } from '../../../../../components/validated_range';
export function StaticSizeSelection({ onChange, styleOptions }) {
@ -27,8 +28,6 @@ export function StaticSizeSelection({ onChange, styleOptions }) {
}
StaticSizeSelection.propTypes = {
styleOptions: PropTypes.shape({
size: PropTypes.number.isRequired,
}).isRequired,
styleOptions: staticSizeShape.isRequired,
onChange: PropTypes.func.isRequired
};

View file

@ -9,13 +9,14 @@ import React from 'react';
import { StaticDynamicStyleRow } from '../../static_dynamic_style_row';
import { DynamicSizeSelection } from './dynamic_size_selection';
import { StaticSizeSelection } from './static_size_selection';
import { getVectorStyleLabel } from '../get_vector_style_label';
export function VectorStyleSizeEditor(props) {
return (
<StaticDynamicStyleRow
ordinalFields={props.ordinalFields}
property={props.styleProperty}
name={props.stylePropertyName}
label={getVectorStyleLabel(props.styleProperty)}
styleDescriptor={props.styleDescriptor}
handlePropertyChange={props.handlePropertyChange}
DynamicSelector={DynamicSizeSelection}

View file

@ -0,0 +1,39 @@
/*
* 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 PropTypes from 'prop-types';
import { fieldShape } from './field_select';
export const staticColorShape = PropTypes.shape({
color: PropTypes.string.isRequired,
});
export const dynamicColorShape = PropTypes.shape({
color: PropTypes.string.isRequired,
field: fieldShape,
});
export const staticSizeShape = PropTypes.shape({
size: PropTypes.number.isRequired,
});
export const dynamicSizeShape = PropTypes.shape({
minSize: PropTypes.number.isRequired,
maxSize: PropTypes.number.isRequired,
field: fieldShape,
});
export const styleOptionShapes = [
staticColorShape,
dynamicColorShape,
staticSizeShape,
dynamicSizeShape
];
export const rangeShape = PropTypes.shape({
min: PropTypes.number.isRequired,
max: PropTypes.number.isRequired,
});

View file

@ -12,7 +12,6 @@ import { VectorStyleSizeEditor } from './size/vector_style_size_editor';
import { getDefaultDynamicProperties, getDefaultStaticProperties } from '../../vector_style_defaults';
import { EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
export class VectorStyleEditor extends Component {
state = {
@ -50,11 +49,6 @@ export class VectorStyleEditor extends Component {
<VectorStyleColorEditor
styleProperty="fillColor"
stylePropertyName={
i18n.translate('xpack.maps.styles.vector.fillColorLabel', {
defaultMessage: 'Fill color'
})
}
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.fillColor}
ordinalFields={this.state.ordinalFields}
@ -66,11 +60,6 @@ export class VectorStyleEditor extends Component {
<VectorStyleColorEditor
styleProperty="lineColor"
stylePropertyName={
i18n.translate('xpack.maps.styles.vector.borderColorLabel', {
defaultMessage: 'Border color'
})
}
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.lineColor}
ordinalFields={this.state.ordinalFields}
@ -82,11 +71,6 @@ export class VectorStyleEditor extends Component {
<VectorStyleSizeEditor
styleProperty="lineWidth"
stylePropertyName={
i18n.translate('xpack.maps.styles.vector.borderWidthLabel', {
defaultMessage: 'Border width'
})
}
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.lineWidth}
ordinalFields={this.state.ordinalFields}
@ -98,11 +82,6 @@ export class VectorStyleEditor extends Component {
<VectorStyleSizeEditor
styleProperty="iconSize"
stylePropertyName={
i18n.translate('xpack.maps.styles.vector.symbolSizeLabel', {
defaultMessage: 'Symbol size'
})
}
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.iconSize}
ordinalFields={this.state.ordinalFields}

View file

@ -7,13 +7,13 @@
import _ from 'lodash';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { FillableCircle, FillableVector } from '../../icons/additional_layer_icons';
import { ColorGradient } from '../../icons/color_gradient';
import { getHexColorRangeStrings } from '../../utils/color_utils';
import { VectorStyleEditor } from './components/vector/vector_style_editor';
import { getDefaultStaticProperties } from './vector_style_defaults';
import { AbstractStyle } from './abstract_style';
import { SOURCE_DATA_ID_ORIGIN } from '../../../../common/constants';
import { VectorIcon } from './components/vector/legend/vector_icon';
import { VectorStyleLegend } from './components/vector/legend/vector_style_legend';
export class VectorStyle extends AbstractStyle {
@ -131,7 +131,7 @@ export class VectorStyle extends AbstractStyle {
return {};
}
const scaledFields = this._getDynamicPropertiesArray()
const scaledFields = this.getDynamicPropertiesArray()
.map(({ options }) => {
return {
name: options.field.name,
@ -194,7 +194,7 @@ export class VectorStyle extends AbstractStyle {
return this._descriptor.properties || {};
}
_getDynamicPropertiesArray() {
getDynamicPropertiesArray() {
const styles = this.getProperties();
return Object.keys(styles)
.map(styleName => {
@ -224,48 +224,29 @@ export class VectorStyle extends AbstractStyle {
}
getIcon = () => {
let style = {
stroke: 'grey',
strokeWidth: '1px',
fill: 'none'
};
const isDynamic = this._isPropertyDynamic('fillColor');
if (!isDynamic) {
const { fillColor, lineColor } = this._descriptor.properties;
const stroke = _.get(lineColor, 'options.color');
const fill = _.get(fillColor, 'options.color');
style = {
...style,
...stroke && { stroke },
...fill && { fill },
};
}
const styles = this.getProperties();
return (
this._getIsPointsOnly()
? <FillableCircle style={style}/>
: <FillableVector style={style}/>
<VectorIcon
isPointsOnly={this._getIsPointsOnly()}
fillColor={styles.fillColor}
lineColor={styles.lineColor}
/>
);
}
getColorRamp() {
const color = _.get(this._descriptor, 'properties.fillColor.options.color');
return color && this._isPropertyDynamic('fillColor')
? <ColorGradient color={color}/>
: null;
}
getLegendDetails() {
const styles = this.getProperties();
const styleProperties = Object.keys(styles).map(styleName => {
const { type, options } = styles[styleName];
return {
name: styleName,
type,
options,
range: options && options.field && options.field.name ? this._getFieldRange(options.field.name) : null,
};
});
getTOCDetails() {
const isDynamic = this._isPropertyDynamic('fillColor');
if (isDynamic) {
return (
<React.Fragment>
{this.getColorRamp()}
</React.Fragment>
);
}
return null;
return (<VectorStyleLegend styleProperties={styleProperties}/>);
}
addScaledPropertiesBasedOnStyle(featureCollection) {
@ -273,7 +254,7 @@ export class VectorStyle extends AbstractStyle {
return false;
}
const scaledFields = this._getDynamicPropertiesArray()
const scaledFields = this.getDynamicPropertiesArray()
.map(({ options }) => {
const name = options.field.name;
return {

View file

@ -86,8 +86,12 @@ export class VectorLayer extends AbstractLayer {
return 'vector';
}
getTOCDetails() {
return this._style.getTOCDetails();
hasLegendDetails() {
return this._style.getDynamicPropertiesArray().length > 0;
}
getLegendDetails() {
return this._style.getLegendDetails();
}
_getBoundsBasedOnData() {

View file

@ -5,20 +5,29 @@
*/
import { vislibColorMaps } from 'ui/vislib/components/color/colormaps';
import { getLegendColors } from 'ui/vis/map/color_util';
import { getLegendColors, getColor } from 'ui/vis/map/color_util';
import chroma from 'chroma-js';
export function getRGBColorRangeStrings(colorName, numberColors) {
const colorKeys = Object.keys(vislibColorMaps);
if (!colorKeys.includes(colorName)) {
//This is an internal error and should never occur. If it does, then it is a bug.
throw new Error(`${colorName} not found. Expected one of following values: \
${colorKeys}`);
function getColorRamp(colorRampName) {
const colorRamp = vislibColorMaps[colorRampName];
if (!colorRamp) {
throw new Error(`${colorRampName} not found. Expected one of following values: ${Object.keys(vislibColorMaps)}`);
}
return getLegendColors(vislibColorMaps[colorName].value, numberColors);
return colorRamp;
}
export function getHexColorRangeStrings(colorName, numberColors) {
return getRGBColorRangeStrings(colorName, numberColors)
export function getRGBColorRangeStrings(colorRampName, numberColors) {
const colorRamp = getColorRamp(colorRampName);
return getLegendColors(colorRamp.value, numberColors);
}
export function getHexColorRangeStrings(colorRampName, numberColors) {
return getRGBColorRangeStrings(colorRampName, numberColors)
.map(rgbColor => chroma(rgbColor).hex());
}
export function getColorRampCenterColor(colorRampName) {
const colorRamp = getColorRamp(colorRampName);
const centerIndex = Math.floor(colorRamp.value.length / 2);
return getColor(colorRamp.value, centerIndex);
}

View file

@ -10,6 +10,10 @@ export const SET_IS_LAYER_TOC_OPEN = 'SET_IS_LAYER_TOC_OPEN';
export const SET_FULL_SCREEN = 'SET_FULL_SCREEN';
export const SET_READ_ONLY = 'SET_READ_ONLY';
export const SET_FILTERABLE = 'IS_FILTERABLE';
export const SET_OPEN_TOC_DETAILS = 'SET_OPEN_TOC_DETAILS';
export const SHOW_TOC_DETAILS = 'SHOW_TOC_DETAILS';
export const HIDE_TOC_DETAILS = 'HIDE_TOC_DETAILS';
export const FLYOUT_STATE = {
NONE: 'NONE',
LAYER_PANEL: 'LAYER_PANEL',
@ -23,7 +27,10 @@ const INITIAL_STATE = {
isFullScreen: false,
isReadOnly: false,
isLayerTOCOpen: DEFAULT_IS_LAYER_TOC_OPEN,
isFilterable: false
isFilterable: false,
// storing TOC detail visibility outside of map.layerList because its UI state and not map rendering state.
// This also makes for easy read/write access for embeddables.
openTOCDetails: [],
};
// Reducer
@ -43,6 +50,23 @@ export function ui(state = INITIAL_STATE, action) {
return { ...state, isReadOnly: action.isReadOnly };
case SET_FILTERABLE:
return { ...state, isFilterable: action.isFilterable };
case SET_OPEN_TOC_DETAILS:
return { ...state, openTOCDetails: action.layerIds };
case SHOW_TOC_DETAILS:
return {
...state,
openTOCDetails: [
...state.openTOCDetails,
action.layerId
]
};
case HIDE_TOC_DETAILS:
return {
...state,
openTOCDetails: state.openTOCDetails.filter(layerId => {
return layerId !== action.layerId;
})
};
default:
return state;
}
@ -97,11 +121,33 @@ export function setFilterable(isFilterable) {
};
}
export function setOpenTOCDetails(layerIds) {
return {
type: SET_OPEN_TOC_DETAILS,
layerIds
};
}
export function showTOCDetails(layerId) {
return {
type: SHOW_TOC_DETAILS,
layerId
};
}
export function hideTOCDetails(layerId) {
return {
type: HIDE_TOC_DETAILS,
layerId
};
}
// Selectors
export const getFlyoutDisplay = ({ ui }) => ui && ui.flyoutDisplay
|| INITIAL_STATE.flyoutDisplay;
export const getIsSetViewOpen = ({ ui }) => ui.isSetViewOpen;
export const getIsLayerTOCOpen = ({ ui }) => ui.isLayerTOCOpen;
export const getOpenTOCDetails = ({ ui }) => ui.openTOCDetails;
export const getIsFullScreen = ({ ui }) => ui.isFullScreen;
export const getIsReadOnly = ({ ui }) => ui.isReadOnly;
export const getIsFilterable = ({ ui }) => ui.isFilterable;

View file

@ -45,6 +45,17 @@ export default function ({ getPageObjects, getService }) {
expect(beforeRefreshTimerTimestamp).not.to.equal(afterRefreshTimerTimestamp);
});
it('should show dynamic data range in legend', async () => {
const layerTOCDetails = await PageObjects.maps.getLayerTOCDetails('geo_shapes*');
const split = layerTOCDetails.trim().split('\n');
const min = split[0];
expect(min).to.equal('3');
const max = split[2];
expect(max).to.equal('12');
});
it('should decorate feature properties with join property', async () => {
const mapboxStyle = await PageObjects.maps.getMapboxStyle();
expect(mapboxStyle.sources[VECTOR_SOURCE_ID].data.features.length).to.equal(4);
@ -60,7 +71,6 @@ export default function ({ getPageObjects, getService }) {
});
});
it('should style fills, points and lines independently', async () => {
const mapboxStyle = await PageObjects.maps.getMapboxStyle();
const layersForVectorSource = mapboxStyle.layers.filter(mbLayer => {

View file

@ -110,7 +110,7 @@
"type": "envelope"
},
"description": "",
"layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{\"alphaValue\":1}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"alphaValue\":1},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
"layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
"mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}",
"title": "document example",
"uiStateJSON": "{\"isDarkMode\":false}"
@ -141,7 +141,7 @@
"type": "envelope"
},
"description": "",
"layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{\"alphaValue\":1}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"z52lq\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[\"machine.os\"]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"alphaValue\":1},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
"layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"z52lq\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[\"machine.os\"]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
"mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"machine.os.raw : \\\"ios\\\"\",\"language\":\"kuery\"}}",
"title": "document example with query",
"uiStateJSON": "{\"isDarkMode\":false}"
@ -172,10 +172,10 @@
"type": "envelope"
},
"description": "",
"layerListJSON": "[{\"id\":\"0hmz5\",\"label\":\"EMS base layer (road_map)\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{\"alphaValue\":1}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[],\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name\",\"origin\":\"join\"},\"color\":\"Blues\"}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"alphaValue\":1},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"indexPatternRefName\":\"layer_1_join_0_index_pattern\"}}]}]",
"layerListJSON": "[{\"id\":\"0hmz5\",\"label\":\"EMS base layer (road_map)\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[],\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name\",\"origin\":\"join\"},\"color\":\"Blues\"}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"indexPatternRefName\":\"layer_1_join_0_index_pattern\"}}]}]",
"mapStateJSON": "{\"zoom\":3.02,\"center\":{\"lon\":77.33426,\"lat\":-0.04647},\"timeFilters\":{\"from\":\"now-17m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}",
"title": "join example",
"uiStateJSON": "{\"isDarkMode\":false}"
"uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"n1t6f\"]}"
},
"type": "map",
"references" : [
@ -217,7 +217,7 @@
],
"type": "envelope"
},
"layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{\"alphaValue\":1}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"3xlvm\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"427aa49d-a552-4e7d-a629-67c47db27128\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"heatmap\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"HEATMAP\",\"refinement\":\"coarse\",\"properties\":{\"alphaValue\":1},\"previousStyle\":null},\"type\":\"HEATMAP\"}]",
"layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"3xlvm\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"427aa49d-a552-4e7d-a629-67c47db27128\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"heatmap\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"HEATMAP\",\"refinement\":\"coarse\",\"properties\":{},\"previousStyle\":null},\"type\":\"HEATMAP\"}]",
"mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}",
"title": "geo grid heatmap example",
"uiStateJSON": "{\"isDarkMode\":false}"
@ -248,7 +248,7 @@
"type": "envelope"
},
"description": "",
"layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{\"alphaValue\":1}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max of bytes\",\"name\":\"max_of_bytes\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}},\"alphaValue\":1},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"}]",
"layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max of bytes\",\"name\":\"max_of_bytes\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"}]",
"mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}",
"title": "geo grid vector grid example",
"uiStateJSON": "{\"isDarkMode\":false}"
@ -293,7 +293,7 @@
"type": "polygon"
},
"description": "",
"layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{\"alphaValue\":1}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"frk92\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"7d807c75-088a-44b7-920a-e7e47f4fc038\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"1035e930-1811-11e9-b78a-23d706cd2507\",\"geoField\":\"location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"alphaValue\":1},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
"layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"frk92\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"7d807c75-088a-44b7-920a-e7e47f4fc038\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"1035e930-1811-11e9-b78a-23d706cd2507\",\"geoField\":\"location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
"mapStateJSON": "{\"zoom\":8.8,\"center\":{\"lon\":-179.98743,\"lat\":-0.09561},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"}}",
"title": "antimeridian points example",
"uiStateJSON": "{\"isDarkMode\":false}"
@ -338,7 +338,7 @@
"type": "polygon"
},
"description": "",
"layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{\"alphaValue\":1}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"ad9fj\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"4e4b5628-dbdc-40bb-93f0-8a7a48be1141\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"502886a0-18f8-11e9-97c8-5da5e037299c\",\"geoField\":\"location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"alphaValue\":1}},\"type\":\"VECTOR\"}]",
"layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"ad9fj\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"4e4b5628-dbdc-40bb-93f0-8a7a48be1141\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"502886a0-18f8-11e9-97c8-5da5e037299c\",\"geoField\":\"location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}}},\"type\":\"VECTOR\"}]",
"mapStateJSON": "{\"zoom\":5.65,\"center\":{\"lon\":179.03193,\"lat\":0.09593},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"}}",
"title": "antimeridian shapes example",
"uiStateJSON": "{\"isDarkMode\":false}"
@ -383,7 +383,7 @@
"type": "polygon"
},
"description": "",
"layerListJSON" : "[{\"sourceDescriptor\":{\"type\":\"KIBANA_TILEMAP\"},\"id\":\"ap0ys\",\"label\":\"Custom_TMS\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{},\"previousStyle\":null},\"type\":\"TILE\"},{\"sourceDescriptor\":{\"type\":\"REGIONMAP_FILE\",\"name\":\"nameThatDoesNotExitForKibanaRegionmapSource\"},\"temporary\":false,\"id\":\"0sabv\",\"label\":\"Custom_vector_shapes\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#3cb44b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"idThatDoesNotExitForEMSTile\"},\"temporary\":false,\"id\":\"plw9l\",\"label\":\"EMS_tiles\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"TILE\",\"properties\":{},\"previousStyle\":null},\"type\":\"TILE\"},{\"sourceDescriptor\":{\"type\":\"EMS_FILE\",\"id\":\"idThatDoesNotExitForEMSFileSource\"},\"temporary\":false,\"id\":\"2gro0\",\"label\":\"EMS_vector_shapes\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"type\":\"ES_GEO_GRID\",\"id\":\"f67fe707-95dd-46d6-89b8-82617b251b61\",\"indexPatternId\":\"idThatDoesNotExitForESGeoGridSource\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"resolution\":\"COARSE\"},\"temporary\":false,\"id\":\"pl5qd\",\"label\":\"\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"id\":\"a07072bb-3a92-4320-bd37-250ef6d04db7\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"idThatDoesNotExitForESSearchSource\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"temporary\":false,\"id\":\"9bw8h\",\"label\":\"\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"561253e0-f731-11e8-8487-11b9dd924f96\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name\",\"origin\":\"join\"},\"color\":\"Blues\"}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"alphaValue\":1},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternId\":\"idThatDoesNotExitForESJoinSource\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}]}}]}]",
"layerListJSON" : "[{\"sourceDescriptor\":{\"type\":\"KIBANA_TILEMAP\"},\"id\":\"ap0ys\",\"label\":\"Custom_TMS\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{},\"previousStyle\":null},\"type\":\"TILE\"},{\"sourceDescriptor\":{\"type\":\"REGIONMAP_FILE\",\"name\":\"nameThatDoesNotExitForKibanaRegionmapSource\"},\"temporary\":false,\"id\":\"0sabv\",\"label\":\"Custom_vector_shapes\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#3cb44b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"idThatDoesNotExitForEMSTile\"},\"temporary\":false,\"id\":\"plw9l\",\"label\":\"EMS_tiles\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"TILE\",\"properties\":{},\"previousStyle\":null},\"type\":\"TILE\"},{\"sourceDescriptor\":{\"type\":\"EMS_FILE\",\"id\":\"idThatDoesNotExitForEMSFileSource\"},\"temporary\":false,\"id\":\"2gro0\",\"label\":\"EMS_vector_shapes\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"type\":\"ES_GEO_GRID\",\"id\":\"f67fe707-95dd-46d6-89b8-82617b251b61\",\"indexPatternId\":\"idThatDoesNotExitForESGeoGridSource\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"resolution\":\"COARSE\"},\"temporary\":false,\"id\":\"pl5qd\",\"label\":\"\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"id\":\"a07072bb-3a92-4320-bd37-250ef6d04db7\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"idThatDoesNotExitForESSearchSource\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"temporary\":false,\"id\":\"9bw8h\",\"label\":\"\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"561253e0-f731-11e8-8487-11b9dd924f96\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name\",\"origin\":\"join\"},\"color\":\"Blues\"}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternId\":\"idThatDoesNotExitForESJoinSource\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}]}}]}]",
"mapStateJSON": "{\"zoom\":0.71,\"center\":{\"lon\":0.10268,\"lat\":0},\"timeFilters\":{\"from\":\"now-7d\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"}}",
"title": "layer with errors",
"uiStateJSON": "{}"

View file

@ -17,6 +17,10 @@ export function GisPageProvider({ getService, getPageObjects }) {
const queryBar = getService('queryBar');
const comboBox = getService('comboBox');
function escapeLayerName(layerName) {
return layerName.split(' ').join('_');
}
class GisPage {
constructor() {
@ -247,16 +251,21 @@ export function GisPageProvider({ getService, getPageObjects }) {
}
async openLayerTocActionsPanel(layerName) {
const cleanLayerName = layerName.split(' ').join('');
const isOpen = await testSubjects.exists(`layerTocActionsPanel${cleanLayerName}`);
const escapedDisplayName = escapeLayerName(layerName);
const isOpen = await testSubjects.exists(`layerTocActionsPanel${escapedDisplayName}`);
if (!isOpen) {
await testSubjects.click(`layerTocActionsPanelToggleButton${cleanLayerName}`);
await testSubjects.click(`layerTocActionsPanelToggleButton${escapedDisplayName}`);
}
}
async openLayerPanel(layerName) {
log.debug(`Open layer panel, layer: ${layerName}`);
await testSubjects.click(`mapOpenLayerButton${layerName}`);
await this.openLayerTocActionsPanel(layerName);
await testSubjects.click('editLayerButton');
}
async getLayerTOCDetails(layerName) {
return await testSubjects.getVisibleText(`mapLayerTOCDetails${escapeLayerName(layerName)}`);
}
async disableApplyGlobalQuery() {
@ -276,9 +285,7 @@ export function GisPageProvider({ getService, getPageObjects }) {
}
async doesLayerExist(layerName) {
layerName = layerName.replace(' ', '_');
log.debug(`Open layer panel, layer: ${layerName}`);
return await testSubjects.exists(`mapOpenLayerButton${layerName}`);
return await testSubjects.exists(`layerTocActionsPanelToggleButton${escapeLayerName(layerName)}`);
}
/*