From 825e1c2857fcdaef9f2ee0bab8045ef04520a3fe Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 29 Jan 2020 11:15:06 -0500 Subject: [PATCH] [Maps] style icons by category (#55747) * dynamic icons * split symbols UI into 2 parts * static dynamic icon editor UI * rename style property symbolMarker to icon * add field select to dynamic icon form * icon map select component * create property classes for icon style property * dynamic icons from palette * changes * fix image problem * implement legend details * fix image-anchor setting for dynamic images * update functional test expect because of migration * fix jest tests * migrate SIEM style descriptors * modify IconSelect to show icon in input * fix jest test Co-authored-by: Elastic Machine --- .../legacy/plugins/maps/common/constants.js | 7 + .../migrate_symbol_style_descriptor.js | 46 +++++ .../migrate_symbol_style_descriptor.test.js | 131 ++++++++++++++ x-pack/legacy/plugins/maps/migrations.js | 9 + .../maps/public/layers/styles/_index.scss | 1 + .../vector_style_symbol_editor.test.js.snap | 98 ---------- .../color/color_stops_categorical.js | 2 +- .../components/get_vector_style_label.js | 4 + .../vector/components/legend/category.js | 43 +++++ .../vector/components/legend/symbol_icon.js | 18 +- .../vector/components/style_map_select.js | 92 ++++++++++ .../__snapshots__/icon_select.test.js.snap | 81 +++++++++ .../components/symbol/_icon_select.scss | 3 + .../components/symbol/dynamic_icon_form.js | 70 ++++++++ .../components/symbol/icon_map_select.js | 54 ++++++ .../vector/components/symbol/icon_select.js | 138 ++++++++++++++ .../components/symbol/icon_select.test.js | 28 +++ .../vector/components/symbol/icon_stops.js | 132 ++++++++++++++ .../components/symbol/static_icon_form.js | 35 ++++ .../symbol/vector_style_icon_editor.js | 31 ++++ .../vector_style_symbolize_as_editor.js | 61 +++++++ .../vector/components/vector_style_editor.js | 68 ++++--- .../components/vector_style_symbol_editor.js | 136 -------------- .../vector_style_symbol_editor.test.js | 46 ----- .../dynamic_color_property.test.js.snap | 168 +++++------------- .../properties/dynamic_color_property.js | 92 ++++------ .../properties/dynamic_icon_property.js | 159 +++++++++++++++++ .../properties/dynamic_size_property.js | 17 +- .../vector/properties/static_icon_property.js | 16 ++ .../vector/properties/static_size_property.js | 12 +- .../properties/symbolize_as_property.js | 18 ++ .../public/layers/styles/vector/style_util.js | 29 +++ .../layers/styles/vector/style_util.test.js | 41 ++++- .../layers/styles/vector/symbol_utils.js | 59 ++++++ .../layers/styles/vector/vector_constants.js | 10 -- .../layers/styles/vector/vector_style.js | 54 ++++-- .../layers/styles/vector/vector_style.test.js | 11 +- .../styles/vector/vector_style_defaults.js | 26 ++- .../components/embeddables/__mocks__/mock.ts | 48 +++-- .../components/embeddables/map_config.ts | 24 ++- .../api_integration/apis/maps/migrations.js | 2 +- 41 files changed, 1561 insertions(+), 559 deletions(-) create mode 100644 x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.js create mode 100644 x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.test.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/__snapshots__/vector_style_symbol_editor.test.js.snap create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/category.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_map_select.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.test.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_constants.js diff --git a/x-pack/legacy/plugins/maps/common/constants.js b/x-pack/legacy/plugins/maps/common/constants.js index eef621e6a2cd6..2570341aa5756 100644 --- a/x-pack/legacy/plugins/maps/common/constants.js +++ b/x-pack/legacy/plugins/maps/common/constants.js @@ -151,3 +151,10 @@ export const COLOR_MAP_TYPE = { export const COLOR_PALETTE_MAX_SIZE = 10; export const CATEGORICAL_DATA_TYPES = ['string', 'ip', 'boolean']; + +export const SYMBOLIZE_AS_TYPES = { + CIRCLE: 'circle', + ICON: 'icon', +}; + +export const DEFAULT_ICON = 'airfield'; diff --git a/x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.js b/x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.js new file mode 100644 index 0000000000000..0ae20ffd142c5 --- /dev/null +++ b/x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.js @@ -0,0 +1,46 @@ +/* + * 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 { DEFAULT_ICON, LAYER_TYPE, STYLE_TYPE, SYMBOLIZE_AS_TYPES } from '../constants'; + +function isVectorLayer(layerDescriptor) { + const layerType = _.get(layerDescriptor, 'type'); + return layerType === LAYER_TYPE.VECTOR; +} + +export function migrateSymbolStyleDescriptor({ attributes }) { + if (!attributes.layerListJSON) { + return attributes; + } + + const layerList = JSON.parse(attributes.layerListJSON); + layerList.forEach(layerDescriptor => { + if (!isVectorLayer(layerDescriptor) || !_.has(layerDescriptor, 'style.properties')) { + return; + } + + const symbolizeAs = _.get( + layerDescriptor, + 'style.properties.symbol.options.symbolizeAs', + SYMBOLIZE_AS_TYPES.CIRCLE + ); + layerDescriptor.style.properties.symbolizeAs = { + options: { value: symbolizeAs }, + }; + const iconId = _.get(layerDescriptor, 'style.properties.symbol.options.symbolId', DEFAULT_ICON); + layerDescriptor.style.properties.icon = { + type: STYLE_TYPE.STATIC, + options: { value: iconId }, + }; + delete layerDescriptor.style.properties.symbol; + }); + + return { + ...attributes, + layerListJSON: JSON.stringify(layerList), + }; +} diff --git a/x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.test.js b/x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.test.js new file mode 100644 index 0000000000000..2811b83f46d8f --- /dev/null +++ b/x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.test.js @@ -0,0 +1,131 @@ +/* + * 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 { migrateSymbolStyleDescriptor } from './migrate_symbol_style_descriptor'; +import { LAYER_TYPE, STYLE_TYPE } from '../constants'; + +describe('migrateSymbolStyleDescriptor', () => { + test('Should handle missing layerListJSON attribute', () => { + const attributes = { + title: 'my map', + }; + expect(migrateSymbolStyleDescriptor({ attributes })).toEqual({ + title: 'my map', + }); + }); + + test('Should ignore non-vector layers', () => { + const layerListJSON = JSON.stringify([ + { + type: LAYER_TYPE.HEATMAP, + style: { + type: 'HEATMAP', + colorRampName: 'Greens', + }, + }, + ]); + const attributes = { + title: 'my map', + layerListJSON, + }; + expect(migrateSymbolStyleDescriptor({ attributes })).toEqual({ + title: 'my map', + layerListJSON, + }); + }); + + test('Should migrate "symbol" style descriptor', () => { + const layerListJSON = JSON.stringify([ + { + type: LAYER_TYPE.VECTOR, + style: { + properties: { + fillColor: { + type: STYLE_TYPE.STATIC, + options: { color: '#54B399' }, + }, + symbol: { + options: { + symbolizeAs: 'icon', + symbolId: 'square', + }, + }, + }, + }, + }, + ]); + const attributes = { + title: 'my map', + layerListJSON, + }; + expect(migrateSymbolStyleDescriptor({ attributes })).toEqual({ + title: 'my map', + layerListJSON: JSON.stringify([ + { + type: LAYER_TYPE.VECTOR, + style: { + properties: { + fillColor: { + type: STYLE_TYPE.STATIC, + options: { color: '#54B399' }, + }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: STYLE_TYPE.STATIC, + options: { value: 'square' }, + }, + }, + }, + }, + ]), + }); + }); + + test('Should migrate style descriptor without "symbol"', () => { + const layerListJSON = JSON.stringify([ + { + type: LAYER_TYPE.VECTOR, + style: { + properties: { + fillColor: { + type: STYLE_TYPE.STATIC, + options: { color: '#54B399' }, + }, + }, + }, + }, + ]); + const attributes = { + title: 'my map', + layerListJSON, + }; + expect(migrateSymbolStyleDescriptor({ attributes })).toEqual({ + title: 'my map', + layerListJSON: JSON.stringify([ + { + type: LAYER_TYPE.VECTOR, + style: { + properties: { + fillColor: { + type: STYLE_TYPE.STATIC, + options: { color: '#54B399' }, + }, + symbolizeAs: { + options: { value: 'circle' }, + }, + icon: { + type: STYLE_TYPE.STATIC, + options: { value: 'airfield' }, + }, + }, + }, + }, + ]), + }); + }); +}); diff --git a/x-pack/legacy/plugins/maps/migrations.js b/x-pack/legacy/plugins/maps/migrations.js index 023695335ec28..9622f6ba63fac 100644 --- a/x-pack/legacy/plugins/maps/migrations.js +++ b/x-pack/legacy/plugins/maps/migrations.js @@ -9,6 +9,7 @@ import { emsRasterTileToEmsVectorTile } from './common/migrations/ems_raster_til import { topHitsTimeToSort } from './common/migrations/top_hits_time_to_sort'; import { moveApplyGlobalQueryToSources } from './common/migrations/move_apply_global_query'; import { addFieldMetaOptions } from './common/migrations/add_field_meta_options'; +import { migrateSymbolStyleDescriptor } from './common/migrations/migrate_symbol_style_descriptor'; export const migrations = { map: { @@ -46,5 +47,13 @@ export const migrations = { attributes: attributesPhase2, }; }, + '7.7.0': doc => { + const attributes = migrateSymbolStyleDescriptor(doc); + + return { + ...doc, + attributes, + }; + }, }, }; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss b/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss index 9060a4a98a4bc..6d332ee878d95 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss +++ b/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss @@ -1,3 +1,4 @@ @import './components/color_gradient'; @import './vector/components/static_dynamic_style_row'; @import './vector/components/color/color_stops'; +@import './vector/components/symbol/icon_select'; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/__snapshots__/vector_style_symbol_editor.test.js.snap b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/__snapshots__/vector_style_symbol_editor.test.js.snap deleted file mode 100644 index 43ba2108242d5..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/__snapshots__/vector_style_symbol_editor.test.js.snap +++ /dev/null @@ -1,98 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Should render icon select when symbolized as Icon 1`] = ` - - - - - - - -`; - -exports[`Should render symbol select when symbolized as Circle 1`] = ` - - - - - -`; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js index d5948d5539bae..d52c3dbcfa1df 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js @@ -11,12 +11,12 @@ import { EuiFieldText } from '@elastic/eui'; import { addCategoricalRow, isCategoricalStopsInvalid, - getOtherCategoryLabel, DEFAULT_CUSTOM_COLOR, DEFAULT_NEXT_COLOR, } from './color_stops_utils'; import { i18n } from '@kbn/i18n'; import { ColorStops } from './color_stops'; +import { getOtherCategoryLabel } from '../../style_util'; export const ColorStopsCategorical = ({ colorStops = [ diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js index 16cfd34c95ab3..c5fdda70c4eb4 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js @@ -22,6 +22,10 @@ export function getVectorStyleLabel(styleName) { return i18n.translate('xpack.maps.styles.vector.borderWidthLabel', { defaultMessage: 'Border width', }); + case VECTOR_STYLES.ICON: + return i18n.translate('xpack.maps.styles.vector.iconLabel', { + defaultMessage: 'Icon', + }); case VECTOR_STYLES.ICON_SIZE: return i18n.translate('xpack.maps.styles.vector.symbolSizeLabel', { defaultMessage: 'Symbol size', diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/category.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/category.js new file mode 100644 index 0000000000000..cf65a807ae83e --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/category.js @@ -0,0 +1,43 @@ +/* + * 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 { VECTOR_STYLES } from '../../vector_style_defaults'; +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { VectorIcon } from './vector_icon'; + +export function Category({ styleName, label, color, isLinesOnly, isPointsOnly, symbolId }) { + function renderIcon() { + if (styleName === VECTOR_STYLES.LABEL_COLOR) { + return ( + + Tx + + ); + } + + return ( + + ); + } + + return ( + + + + {label} + + {renderIcon()} + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js index 8ba4d69c35ec6..301d64e676703 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js @@ -76,7 +76,23 @@ export class SymbolIcon extends Component { return null; } - return {this.props.symbolId}; + const { + symbolId, // eslint-disable-line no-unused-vars + fill, // eslint-disable-line no-unused-vars + stroke, // eslint-disable-line no-unused-vars + strokeWidth, // eslint-disable-line no-unused-vars + ...rest + } = this.props; + + return ( + {this.props.symbolId} + ); } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_map_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_map_select.js new file mode 100644 index 0000000000000..28d5454afa4ba --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_map_select.js @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component, Fragment } from 'react'; + +import { EuiSuperSelect, EuiSpacer } from '@elastic/eui'; + +const CUSTOM_MAP = 'CUSTOM_MAP'; + +export class StyleMapSelect extends Component { + state = {}; + + static getDerivedStateFromProps(nextProps, prevState) { + if (nextProps.customMapStops === prevState.prevPropsCustomMapStops) { + return null; + } + + return { + prevPropsCustomMapStops: nextProps.customMapStops, // reset tracker to latest value + customMapStops: nextProps.customMapStops, // reset customMapStops to latest value + }; + } + + _onMapSelect = selectedValue => { + const useCustomMap = selectedValue === CUSTOM_MAP; + this.props.onChange({ + selectedMapId: useCustomMap ? null : selectedValue, + useCustomMap, + }); + }; + + _onCustomMapChange = ({ customMapStops, isInvalid }) => { + // Manage invalid custom map in local state + if (isInvalid) { + this.setState({ customMapStops }); + return; + } + + this.props.onChange({ + useCustomMap: true, + customMapStops, + }); + }; + + _renderCustomStopsInput() { + if (!this.props.useCustomMap) { + return null; + } + + return ( + + + {this.props.renderCustomStopsInput(this._onCustomMapChange)} + + ); + } + + render() { + const mapOptionsWithCustom = [ + { + value: CUSTOM_MAP, + inputDisplay: this.props.customOptionLabel, + }, + ...this.props.options, + ]; + + let valueOfSelected; + if (this.props.useCustomMap) { + valueOfSelected = CUSTOM_MAP; + } else { + valueOfSelected = this.props.options.find(option => option.value === this.props.selectedMapId) + ? this.props.selectedMapId + : ''; + } + + return ( + + + {this._renderCustomStopsInput()} + + ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap new file mode 100644 index 0000000000000..b4b7a3fcf28fa --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap @@ -0,0 +1,81 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Should render icon select 1`] = ` + + + } + readOnly={true} + value="symbol1" + /> + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="s" +> + + , + "value": "symbol1", + }, + Object { + "label": "symbol2", + "prepend": , + "value": "symbol2", + }, + ] + } + searchable={true} + singleSelection={false} + > + + + + +`; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss new file mode 100644 index 0000000000000..5e69d97131095 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss @@ -0,0 +1,3 @@ +.mapIconSelectSymbol__inputButton { + margin-left: $euiSizeS; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js new file mode 100644 index 0000000000000..9a0d73cef616c --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js @@ -0,0 +1,70 @@ +/* + * 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 { FieldSelect } from '../field_select'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { IconMapSelect } from './icon_map_select'; + +export function DynamicIconForm({ + fields, + isDarkMode, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, + symbolOptions, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + const { name, origin } = field; + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + field: { name, origin }, + }); + }; + + const onIconMapChange = newOptions => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + ...newOptions, + }); + }; + + function renderIconMapSelect() { + if (!styleOptions.field || !styleOptions.field.name) { + return null; + } + + return ( + + ); + } + + return ( + + + {staticDynamicSelect} + + + + + + {renderIconMapSelect()} + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js new file mode 100644 index 0000000000000..a8bb94d1d9ce4 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js @@ -0,0 +1,54 @@ +/* + * 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 { StyleMapSelect } from '../style_map_select'; +import { i18n } from '@kbn/i18n'; +import { getIconPaletteOptions } from '../../symbol_utils'; +import { IconStops } from './icon_stops'; + +export function IconMapSelect({ + customIconStops, + iconPaletteId, + isDarkMode, + onChange, + symbolOptions, + useCustomIconMap, +}) { + function onMapSelectChange({ customMapStops, selectedMapId, useCustomMap }) { + onChange({ + customIconStops: customMapStops, + iconPaletteId: selectedMapId, + useCustomIconMap: useCustomMap, + }); + } + + function renderCustomIconStopsInput(onCustomMapChange) { + return ( + + ); + } + + return ( + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js new file mode 100644 index 0000000000000..03cd1ac14a013 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component } from 'react'; +import { + EuiFormControlLayout, + EuiFieldText, + EuiPopover, + EuiPopoverTitle, + EuiFocusTrap, + keyCodes, + EuiSelectable, +} from '@elastic/eui'; +import { SymbolIcon } from '../legend/symbol_icon'; + +function isKeyboardEvent(event) { + return typeof event === 'object' && 'keyCode' in event; +} + +export class IconSelect extends Component { + state = { + isPopoverOpen: false, + }; + + _closePopover = () => { + this.setState({ isPopoverOpen: false }); + }; + + _openPopover = () => { + this.setState({ isPopoverOpen: true }); + }; + + _togglePopover = () => { + this.setState(prevState => ({ + isPopoverOpen: !prevState.isPopoverOpen, + })); + }; + + _handleKeyboardActivity = e => { + if (isKeyboardEvent(e)) { + if (e.keyCode === keyCodes.ENTER) { + e.preventDefault(); + this._togglePopover(); + } else if (e.keyCode === keyCodes.DOWN) { + this._openPopover(); + } + } + }; + + _onIconSelect = options => { + const selectedOption = options.find(option => { + return option.checked === 'on'; + }); + + if (selectedOption) { + this.props.onChange(selectedOption.value); + } + this._closePopover(); + }; + + _renderPopoverButton() { + const { isDarkMode, value } = this.props; + return ( + + + } + /> + + ); + } + + _renderIconSelectable() { + const { isDarkMode } = this.props; + const options = this.props.symbolOptions.map(({ value, label }) => { + return { + value, + label, + prepend: ( + + ), + }; + }); + + return ( + + {(list, search) => ( +
+ {search} + {list} +
+ )} +
+ ); + } + + render() { + return ( + + {this._renderIconSelectable()} + + ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js new file mode 100644 index 0000000000000..56dce6fad8386 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js @@ -0,0 +1,28 @@ +/* + * 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 { IconSelect } from './icon_select'; + +const symbolOptions = [ + { value: 'symbol1', label: 'symbol1' }, + { value: 'symbol2', label: 'symbol2' }, +]; + +test('Should render icon select', () => { + const component = shallow( + {}} + symbolOptions={symbolOptions} + isDarkMode={false} + /> + ); + + expect(component).toMatchSnapshot(); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js new file mode 100644 index 0000000000000..a655a4434ddaa --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js @@ -0,0 +1,132 @@ +/* + * 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 { DEFAULT_ICON } from '../../../../../../common/constants'; +import { i18n } from '@kbn/i18n'; +import { getOtherCategoryLabel } from '../../style_util'; +import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { IconSelect } from './icon_select'; + +function isDuplicateStop(targetStop, iconStops) { + const stops = iconStops.filter(({ stop }) => { + return targetStop === stop; + }); + return stops.length > 1; +} + +const DEFAULT_ICON_STOPS = [ + { stop: null, icon: DEFAULT_ICON }, //first stop is the "other" color + { stop: '', icon: DEFAULT_ICON }, +]; + +export function IconStops({ iconStops = DEFAULT_ICON_STOPS, isDarkMode, onChange, symbolOptions }) { + return iconStops.map(({ stop, icon }, index) => { + const onIconSelect = selectedIconId => { + const newIconStops = [...iconStops]; + newIconStops[index] = { + ...iconStops[index], + icon: selectedIconId, + }; + onChange({ customMapStops: newIconStops }); + }; + const onStopChange = e => { + const newStopValue = e.target.value; + const newIconStops = [...iconStops]; + newIconStops[index] = { + ...iconStops[index], + stop: newStopValue, + }; + onChange({ + customMapStops: newIconStops, + isInvalid: isDuplicateStop(newStopValue, iconStops), + }); + }; + const onAdd = () => { + onChange({ + customMapStops: [ + ...iconStops.slice(0, index + 1), + { + stop: '', + icon: DEFAULT_ICON, + }, + ...iconStops.slice(index + 1), + ], + }); + }; + const onRemove = () => { + onChange({ + iconStops: [...iconStops.slice(0, index), ...iconStops.slice(index + 1)], + }); + }; + + let deleteButton; + if (index > 0) { + deleteButton = ( + + ); + } + + const errors = []; + // TODO check for duplicate values and add error messages here + + const isOtherCategoryRow = index === 0; + return ( + +
+ + + + + + + + +
+ {deleteButton} + +
+
+
+ ); + }); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js new file mode 100644 index 0000000000000..b20d8f2eba162 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js @@ -0,0 +1,35 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { IconSelect } from './icon_select'; + +export function StaticIconForm({ + isDarkMode, + onStaticStyleChange, + staticDynamicSelect, + styleProperty, + symbolOptions, +}) { + const onChange = selectedIconId => { + onStaticStyleChange(styleProperty.getStyleName(), { value: selectedIconId }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js new file mode 100644 index 0000000000000..d5ec09f515954 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js @@ -0,0 +1,31 @@ +/* + * 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 chrome from 'ui/chrome'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicIconForm } from './dynamic_icon_form'; +import { StaticIconForm } from './static_icon_form'; +import { SYMBOL_OPTIONS } from '../../symbol_utils'; + +export function VectorStyleIconEditor(props) { + const iconForm = props.styleProperty.isDynamic() ? ( + + ) : ( + + ); + + return {iconForm}; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js new file mode 100644 index 0000000000000..9394e5c068569 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js @@ -0,0 +1,61 @@ +/* + * 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 { EuiFormRow, EuiButtonGroup } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { SYMBOLIZE_AS_TYPES } from '../../../../../../common/constants'; +import { VECTOR_STYLES } from '../../vector_style_defaults'; + +const SYMBOLIZE_AS_OPTIONS = [ + { + id: SYMBOLIZE_AS_TYPES.CIRCLE, + label: i18n.translate('xpack.maps.vector.symbolAs.circleLabel', { + defaultMessage: 'marker', + }), + }, + { + id: SYMBOLIZE_AS_TYPES.ICON, + label: i18n.translate('xpack.maps.vector.symbolAs.IconLabel', { + defaultMessage: 'icon', + }), + }, +]; + +export function VectorStyleSymbolizeAsEditor({ styleProperty, handlePropertyChange }) { + const styleOptions = styleProperty.getOptions(); + const selectedOption = SYMBOLIZE_AS_OPTIONS.find(({ id }) => { + return id === styleOptions.value; + }); + + const onSymbolizeAsChange = optionId => { + const styleDescriptor = { + options: { + value: optionId, + }, + }; + handlePropertyChange(VECTOR_STYLES.SYMBOLIZE_AS, styleDescriptor); + }; + + return ( + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js index 0784e2a8cc355..9299022b7895b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js @@ -7,10 +7,10 @@ import _ from 'lodash'; import React, { Component, Fragment } from 'react'; -import chrome from 'ui/chrome'; import { VectorStyleColorEditor } from './color/vector_style_color_editor'; import { VectorStyleSizeEditor } from './size/vector_style_size_editor'; -import { VectorStyleSymbolEditor } from './vector_style_symbol_editor'; +import { VectorStyleSymbolizeAsEditor } from './symbol/vector_style_symbolize_as_editor'; +import { VectorStyleIconEditor } from './symbol/vector_style_icon_editor'; import { VectorStyleLabelEditor } from './label/vector_style_label_editor'; import { VectorStyleLabelBorderSizeEditor } from './label/vector_style_label_border_size_editor'; import { VectorStyle } from '../vector_style'; @@ -22,9 +22,7 @@ import { } from '../vector_style_defaults'; import { DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS } from '../../color_utils'; import { VECTOR_SHAPE_TYPES } from '../../../sources/vector_feature_types'; -import { SYMBOLIZE_AS_ICON } from '../vector_constants'; import { i18n } from '@kbn/i18n'; -import { SYMBOL_OPTIONS } from '../symbol_utils'; import { EuiSpacer, EuiButtonGroup, EuiFormRow, EuiSwitch } from '@elastic/eui'; @@ -288,34 +286,55 @@ export class VectorStyleEditor extends Component { } _renderPointProperties() { - let iconOrientation; - if (this.props.symbolDescriptor.options.symbolizeAs === SYMBOLIZE_AS_ICON) { - iconOrientation = ( - + let iconOrientationEditor; + let iconEditor; + if (this.props.styleProperties[VECTOR_STYLES.SYMBOLIZE_AS].isSymbolizedAsIcon()) { + iconOrientationEditor = ( + + + + + ); + iconEditor = ( + + + + ); } return ( - + {iconEditor} + {this._renderFillColor()} @@ -325,8 +344,7 @@ export class VectorStyleEditor extends Component { {this._renderLineWidth()} - {iconOrientation} - + {iconOrientationEditor} {this._renderSymbolSize()} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.js deleted file mode 100644 index 29be736b432f9..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.js +++ /dev/null @@ -1,136 +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 { - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - EuiButtonGroup, - EuiSpacer, - EuiComboBox, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { SYMBOLIZE_AS_CIRCLE, SYMBOLIZE_AS_ICON } from '../vector_constants'; -import { SymbolIcon } from './legend/symbol_icon'; - -const SYMBOLIZE_AS_OPTIONS = [ - { - id: SYMBOLIZE_AS_CIRCLE, - label: i18n.translate('xpack.maps.vector.symbolAs.circleLabel', { - defaultMessage: 'circle marker', - }), - }, - { - id: SYMBOLIZE_AS_ICON, - label: i18n.translate('xpack.maps.vector.symbolAs.IconLabel', { - defaultMessage: 'icon', - }), - }, -]; - -export function VectorStyleSymbolEditor({ - styleOptions, - handlePropertyChange, - symbolOptions, - isDarkMode, -}) { - const renderSymbolizeAsSelect = () => { - const selectedOption = SYMBOLIZE_AS_OPTIONS.find(({ id }) => { - return id === styleOptions.symbolizeAs; - }); - - const onSymbolizeAsChange = optionId => { - const styleDescriptor = { - options: { - ...styleOptions, - symbolizeAs: optionId, - }, - }; - handlePropertyChange('symbol', styleDescriptor); - }; - - return ( - - ); - }; - - const renderSymbolSelect = () => { - const selectedOption = symbolOptions.find(({ value }) => { - return value === styleOptions.symbolId; - }); - - const onSymbolChange = selectedOptions => { - if (!selectedOptions || selectedOptions.length === 0) { - return; - } - - const styleDescriptor = { - options: { - ...styleOptions, - symbolId: selectedOptions[0].value, - }, - }; - handlePropertyChange('symbol', styleDescriptor); - }; - - const renderOption = ({ value, label }) => { - return ( - - - - - {label} - - ); - }; - - return ( - - ); - }; - - return ( - - - {renderSymbolizeAsSelect()} - - - {styleOptions.symbolizeAs !== SYMBOLIZE_AS_CIRCLE && ( - - - {renderSymbolSelect()} - - )} - - ); -} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.test.js deleted file mode 100644 index 4e5c8e8a2563b..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.test.js +++ /dev/null @@ -1,46 +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 { SYMBOLIZE_AS_CIRCLE, SYMBOLIZE_AS_ICON } from '../vector_constants'; -import { VectorStyleSymbolEditor } from './vector_style_symbol_editor'; - -const symbolOptions = [ - { value: 'symbol1', label: 'symbol1' }, - { value: 'symbol2', label: 'symbol2' }, -]; - -const defaultProps = { - styleOptions: { - symbolizeAs: SYMBOLIZE_AS_CIRCLE, - symbolId: symbolOptions[0].value, - }, - handlePropertyChange: () => {}, - symbolOptions, - isDarkMode: false, -}; - -test('Should render symbol select when symbolized as Circle', () => { - const component = shallow(); - - expect(component).toMatchSnapshot(); -}); - -test('Should render icon select when symbolized as Icon', () => { - const component = shallow( - - ); - - expect(component).toMatchSnapshot(); -}); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap index 97acffae15a85..ab47e88bb3143 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap @@ -11,82 +11,36 @@ exports[`Should render categorical legend with breaks from default 1`] = ` direction="column" gutterSize="none" > - - - - - US_format - - - - - - - - - - - - CN_format - - - - - - - - - - - - - Other - - - - - - - - + + + + Other + + } + styleName="lineColor" + /> - - - - - 0_format - - - - - - - - - - - - 10_format - - - - - - - + label="0_format" + styleName="lineColor" + /> + - Tx - - ); - } - - const fillColor = this.getStyleName() === VECTOR_STYLES.FILL_COLOR ? color : 'none'; - return ( - - ); - } - _getColorRampStops() { return this._options.useCustomColorRamp && this._options.customColorRamp ? this._options.customColorRamp @@ -289,48 +263,42 @@ export class DynamicColorProperty extends DynamicStyleProperty { } } - _renderColorbreaks({ isLinesOnly, isPointsOnly, symbolId }) { + renderBreakedLegend({ fieldLabel, isPointsOnly, isLinesOnly, symbolId }) { + const categories = []; const { stops, defaultColor } = this._getColorStops(); - const colorAndLabels = stops.map(config => { - return { - label: this.formatField(config.stop), - color: config.color, - }; + stops.map(({ stop, color }) => { + categories.push( + + ); }); if (defaultColor) { - colorAndLabels.push({ - label: {getOtherCategoryLabel()}, - color: defaultColor, - }); - } - - return colorAndLabels.map((config, index) => { - return ( - - - - {config.label} - - - {this._renderStopIcon(config.color, isLinesOnly, isPointsOnly, symbolId)} - - - + categories.push( + {getOtherCategoryLabel()}} + color={defaultColor} + isLinesOnly={isLinesOnly} + isPointsOnly={isPointsOnly} + symbolId={symbolId} + /> ); - }); - } + } - renderBreakedLegend({ fieldLabel, isPointsOnly, isLinesOnly, symbolId }) { return (
- - {this._renderColorbreaks({ - isPointsOnly, - isLinesOnly, - symbolId, - })} + + {categories} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js new file mode 100644 index 0000000000000..c0e56f962db74 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js @@ -0,0 +1,159 @@ +/* + * 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 from 'react'; +import { getOtherCategoryLabel, assignCategoriesToPalette } from '../style_util'; +import { DynamicStyleProperty } from './dynamic_style_property'; +import { getIconPalette, getMakiIconId, getMakiSymbolAnchor } from '../symbol_utils'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, + EuiToolTip, + EuiTextColor, +} from '@elastic/eui'; +import { Category } from '../components/legend/category'; + +export class DynamicIconProperty extends DynamicStyleProperty { + isOrdinal() { + return false; + } + + isCategorical() { + return true; + } + + syncIconWithMb(symbolLayerId, mbMap, iconPixelSize) { + if (this._isIconDynamicConfigComplete()) { + mbMap.setLayoutProperty( + symbolLayerId, + 'icon-image', + this._getMbIconImageExpression(iconPixelSize) + ); + mbMap.setLayoutProperty(symbolLayerId, 'icon-anchor', this._getMbIconAnchorExpression()); + } else { + mbMap.setLayoutProperty(symbolLayerId, 'icon-image', null); + mbMap.setLayoutProperty(symbolLayerId, 'icon-anchor', null); + } + } + + _getPaletteStops() { + if (this._options.useCustomIconMap && this._options.customIconStops) { + const stops = []; + for (let i = 1; i < this._options.customIconStops.length; i++) { + const { stop, icon } = this._options.customIconStops[i]; + stops.push({ + stop, + style: icon, + }); + } + + return { + fallback: + this._options.customIconStops.length > 0 ? this._options.customIconStops[0].icon : null, + stops, + }; + } + + return assignCategoriesToPalette({ + categories: _.get(this.getFieldMeta(), 'categories', []), + paletteValues: getIconPalette(this._options.iconPaletteId), + }); + } + + _getMbIconImageExpression(iconPixelSize) { + const { stops, fallback } = this._getPaletteStops(); + + if (stops.length < 1 || !fallback) { + //occurs when no data + return null; + } + + const mbStops = []; + stops.forEach(({ stop, style }) => { + mbStops.push(`${stop}`); + mbStops.push(getMakiIconId(style, iconPixelSize)); + }); + mbStops.push(getMakiIconId(fallback, iconPixelSize)); //last item is fallback style for anything that does not match provided stops + return ['match', ['to-string', ['get', this._options.field.name]], ...mbStops]; + } + + _getMbIconAnchorExpression() { + const { stops, fallback } = this._getPaletteStops(); + + if (stops.length < 1 || !fallback) { + //occurs when no data + return null; + } + + const mbStops = []; + stops.forEach(({ stop, style }) => { + mbStops.push(`${stop}`); + mbStops.push(getMakiSymbolAnchor(style)); + }); + mbStops.push(getMakiSymbolAnchor(fallback)); //last item is fallback style for anything that does not match provided stops + return ['match', ['to-string', ['get', this._options.field.name]], ...mbStops]; + } + + _isIconDynamicConfigComplete() { + return this._field && this._field.isValid(); + } + + renderBreakedLegend({ fieldLabel, isPointsOnly, isLinesOnly }) { + const categories = []; + const { stops, fallback } = this._getPaletteStops(); + stops.map(({ stop, style }) => { + categories.push( + + ); + }); + + if (fallback) { + categories.push( + {getOtherCategoryLabel()}} + color="grey" + isLinesOnly={isLinesOnly} + isPointsOnly={isPointsOnly} + symbolId={fallback} + /> + ); + } + + return ( +
+ + + {categories} + + + + + + + {fieldLabel} + + + + + +
+ ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js index 5a4da1a80c918..dfc5c530cc90f 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js @@ -67,15 +67,15 @@ export class DynamicSizeProperty extends DynamicStyleProperty { mbMap.setPaintProperty(mbLayerId, 'icon-halo-width', haloWidth); } - syncIconImageAndSizeWithMb(symbolLayerId, mbMap, symbolId) { - if (this._isSizeDynamicConfigComplete(this._options)) { - const iconPixels = - this._options.maxSize >= HALF_LARGE_MAKI_ICON_SIZE - ? LARGE_MAKI_ICON_SIZE - : SMALL_MAKI_ICON_SIZE; - mbMap.setLayoutProperty(symbolLayerId, 'icon-image', `${symbolId}-${iconPixels}`); + getIconPixelSize() { + return this._options.maxSize >= HALF_LARGE_MAKI_ICON_SIZE + ? LARGE_MAKI_ICON_SIZE + : SMALL_MAKI_ICON_SIZE; + } - const halfIconPixels = iconPixels / 2; + syncIconSizeWithMb(symbolLayerId, mbMap) { + if (this._isSizeDynamicConfigComplete(this._options)) { + const halfIconPixels = this.getIconPixelSize() / 2; const targetName = this.getComputedFieldName(); // Using property state instead of feature-state because layout properties do not support feature-state mbMap.setLayoutProperty(symbolLayerId, 'icon-size', [ @@ -88,7 +88,6 @@ export class DynamicSizeProperty extends DynamicStyleProperty { this._options.maxSize / halfIconPixels, ]); } else { - mbMap.setLayoutProperty(symbolLayerId, 'icon-image', null); mbMap.setLayoutProperty(symbolLayerId, 'icon-size', null); } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js new file mode 100644 index 0000000000000..3b5be083dd3c9 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js @@ -0,0 +1,16 @@ +/* + * 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 { StaticStyleProperty } from './static_style_property'; +import { getMakiSymbolAnchor, getMakiIconId } from '../symbol_utils'; + +export class StaticIconProperty extends StaticStyleProperty { + syncIconWithMb(symbolLayerId, mbMap, iconPixelSize) { + const symbolId = this._options.value; + mbMap.setLayoutProperty(symbolLayerId, 'icon-anchor', getMakiSymbolAnchor(symbolId)); + mbMap.setLayoutProperty(symbolLayerId, 'icon-image', getMakiIconId(symbolId, iconPixelSize)); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js index 024b446369851..2383a5932cb9b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js @@ -24,12 +24,14 @@ export class StaticSizeProperty extends StaticStyleProperty { mbMap.setPaintProperty(mbLayerId, 'icon-halo-width', this._options.size); } - syncIconImageAndSizeWithMb(symbolLayerId, mbMap, symbolId) { - const iconPixels = - this._options.size >= HALF_LARGE_MAKI_ICON_SIZE ? LARGE_MAKI_ICON_SIZE : SMALL_MAKI_ICON_SIZE; + getIconPixelSize() { + return this._options.size >= HALF_LARGE_MAKI_ICON_SIZE + ? LARGE_MAKI_ICON_SIZE + : SMALL_MAKI_ICON_SIZE; + } - mbMap.setLayoutProperty(symbolLayerId, 'icon-image', `${symbolId}-${iconPixels}`); - const halfIconPixels = iconPixels / 2; + syncIconSizeWithMb(symbolLayerId, mbMap) { + const halfIconPixels = this.getIconPixelSize() / 2; mbMap.setLayoutProperty(symbolLayerId, 'icon-size', this._options.size / halfIconPixels); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js new file mode 100644 index 0000000000000..9ae1ef5054e30 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js @@ -0,0 +1,18 @@ +/* + * 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 { AbstractStyleProperty } from './style_property'; +import { SYMBOLIZE_AS_TYPES } from '../../../../../common/constants'; + +export class SymbolizeAsProperty extends AbstractStyleProperty { + constructor(options, styleName) { + super(options, styleName); + } + + isSymbolizedAsIcon = () => { + return this.getOptions().value === SYMBOLIZE_AS_TYPES.ICON; + }; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js index 7bd60ea6502bc..2859b8c0e5a56 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js @@ -4,6 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; + +export function getOtherCategoryLabel() { + return i18n.translate('xpack.maps.styles.categorical.otherCategoryLabel', { + defaultMessage: 'Other', + }); +} + export function getComputedFieldName(styleName, fieldName) { return `${getComputedFieldNamePrefix(fieldName)}__${styleName}`; } @@ -41,3 +49,24 @@ export function scaleValue(value, range) { return (value - range.min) / range.delta; } + +export function assignCategoriesToPalette({ categories, paletteValues }) { + const stops = []; + let fallback = null; + + if (categories && categories.length && paletteValues && paletteValues.length) { + const maxLength = Math.min(paletteValues.length, categories.length + 1); + fallback = paletteValues[maxLength - 1]; + for (let i = 0; i < maxLength - 1; i++) { + stops.push({ + stop: categories[i].key, + style: paletteValues[i], + }); + } + } + + return { + stops, + fallback, + }; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js index e4df2f2bc0f58..2be31c0107193 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isOnlySingleFeatureType, scaleValue } from './style_util'; +import { isOnlySingleFeatureType, scaleValue, assignCategoriesToPalette } from './style_util'; import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types'; describe('isOnlySingleFeatureType', () => { @@ -87,3 +87,42 @@ describe('scaleValue', () => { expect(scaleValue(5, undefined)).toBe(-1); }); }); + +describe('assignCategoriesToPalette', () => { + test('Categories and palette values have same length', () => { + const categories = [{ key: 'alpah' }, { key: 'bravo' }, { key: 'charlie' }, { key: 'delta' }]; + const paletteValues = ['red', 'orange', 'yellow', 'green']; + expect(assignCategoriesToPalette({ categories, paletteValues })).toEqual({ + stops: [ + { stop: 'alpah', style: 'red' }, + { stop: 'bravo', style: 'orange' }, + { stop: 'charlie', style: 'yellow' }, + ], + fallback: 'green', + }); + }); + + test('Should More categories than palette values', () => { + const categories = [{ key: 'alpah' }, { key: 'bravo' }, { key: 'charlie' }, { key: 'delta' }]; + const paletteValues = ['red', 'orange', 'yellow']; + expect(assignCategoriesToPalette({ categories, paletteValues })).toEqual({ + stops: [ + { stop: 'alpah', style: 'red' }, + { stop: 'bravo', style: 'orange' }, + ], + fallback: 'yellow', + }); + }); + + test('Less categories than palette values', () => { + const categories = [{ key: 'alpah' }, { key: 'bravo' }]; + const paletteValues = ['red', 'orange', 'yellow', 'green', 'blue']; + expect(assignCategoriesToPalette({ categories, paletteValues })).toEqual({ + stops: [ + { stop: 'alpah', style: 'red' }, + { stop: 'bravo', style: 'orange' }, + ], + fallback: 'yellow', + }); + }); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js index 162b22319e6ef..b577d4080b879 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js @@ -4,9 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; import maki from '@elastic/maki'; import xml2js from 'xml2js'; import { parseXmlString } from '../../../../common/parse_xml_string'; +import { SymbolIcon } from './components/legend/symbol_icon'; export const LARGE_MAKI_ICON_SIZE = 15; const LARGE_MAKI_ICON_SIZE_AS_STRING = LARGE_MAKI_ICON_SIZE.toString(); @@ -55,6 +57,12 @@ export function getMakiSymbolAnchor(symbolId) { } } +// Style descriptor stores symbolId, for example 'aircraft' +// Icons are registered in Mapbox with full maki ids, for example 'aircraft-11' +export function getMakiIconId(symbolId, iconPixelSize) { + return `${symbolId}-${iconPixelSize}`; +} + export function buildSrcUrl(svgString) { const domUrl = window.URL || window.webkitURL || window; const svg = new Blob([svgString], { type: 'image/svg+xml' }); @@ -77,3 +85,54 @@ export async function styleSvg(svgString, fill, stroke, strokeWidth) { const builder = new xml2js.Builder(); return builder.buildObject(svgXml); } + +const ICON_PALETTES = [ + { + id: 'filledShapes', + icons: ['circle', 'marker', 'square', 'star', 'triangle', 'hospital'], + }, + { + id: 'hollowShapes', + icons: [ + 'circle-stroked', + 'marker-stroked', + 'square-stroked', + 'star-stroked', + 'triangle-stroked', + ], + }, +]; + +export function getIconPaletteOptions(isDarkMode) { + return ICON_PALETTES.map(({ id, icons }) => { + const iconsDisplay = icons.map(iconId => { + const style = { + width: '10%', + position: 'relative', + height: '100%', + display: 'inline-block', + paddingTop: '4px', + }; + return ( +
+ +
+ ); + }); + return { + value: id, + inputDisplay:
{iconsDisplay}
, + }; + }); +} + +export function getIconPalette(paletteId) { + const palette = ICON_PALETTES.find(({ id }) => id === paletteId); + return palette ? [...palette.icons] : null; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_constants.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_constants.js deleted file mode 100644 index 027d558d3c033..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_constants.js +++ /dev/null @@ -1,10 +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. - */ - -export const SYMBOLIZE_AS_CIRCLE = 'circle'; -export const SYMBOLIZE_AS_ICON = 'icon'; - -export const DEFAULT_ICON_SIZE = 6; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js index 558df73f74595..97259a908f1e4 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js @@ -21,12 +21,11 @@ import { SOURCE_META_ID_ORIGIN, SOURCE_FORMATTERS_ID_ORIGIN, LAYER_STYLE_TYPE, + DEFAULT_ICON, } from '../../../../common/constants'; import { VectorIcon } from './components/legend/vector_icon'; import { VectorStyleLegend } from './components/legend/vector_style_legend'; import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types'; -import { SYMBOLIZE_AS_CIRCLE, SYMBOLIZE_AS_ICON } from './vector_constants'; -import { getMakiSymbolAnchor } from './symbol_utils'; import { getComputedFieldName, isOnlySingleFeatureType } from './style_util'; import { StaticStyleProperty } from './properties/static_style_property'; import { DynamicStyleProperty } from './properties/dynamic_style_property'; @@ -40,6 +39,9 @@ import { StaticTextProperty } from './properties/static_text_property'; import { DynamicTextProperty } from './properties/dynamic_text_property'; import { LabelBorderSizeProperty } from './properties/label_border_size_property'; import { extractColorFromStyleProperty } from './components/legend/extract_color_from_style_property'; +import { SymbolizeAsProperty } from './properties/symbolize_as_property'; +import { StaticIconProperty } from './properties/static_icon_property'; +import { DynamicIconProperty } from './properties/dynamic_icon_property'; const POINTS = [GEO_JSON_TYPE.POINT, GEO_JSON_TYPE.MULTI_POINT]; const LINES = [GEO_JSON_TYPE.LINE_STRING, GEO_JSON_TYPE.MULTI_LINE_STRING]; @@ -69,6 +71,10 @@ export class VectorStyle extends AbstractStyle { ...VectorStyle.createDescriptor(descriptor.properties, descriptor.isTimeAware), }; + this._symbolizeAsStyleProperty = new SymbolizeAsProperty( + this._descriptor.properties[VECTOR_STYLES.SYMBOLIZE_AS].options, + VECTOR_STYLES.SYMBOLIZE_AS + ); this._lineColorStyleProperty = this._makeColorProperty( this._descriptor.properties[VECTOR_STYLES.LINE_COLOR], VECTOR_STYLES.LINE_COLOR @@ -81,10 +87,13 @@ export class VectorStyle extends AbstractStyle { this._descriptor.properties[VECTOR_STYLES.LINE_WIDTH], VECTOR_STYLES.LINE_WIDTH ); + this._iconStyleProperty = this._makeIconProperty( + this._descriptor.properties[VECTOR_STYLES.ICON] + ); this._iconSizeStyleProperty = this._makeSizeProperty( this._descriptor.properties[VECTOR_STYLES.ICON_SIZE], VECTOR_STYLES.ICON_SIZE, - this._descriptor.properties[VECTOR_STYLES.SYMBOL].options.symbolizeAs === SYMBOLIZE_AS_ICON + this._symbolizeAsStyleProperty.isSymbolizedAsIcon() ); this._iconOrientationProperty = this._makeOrientationProperty( this._descriptor.properties[VECTOR_STYLES.ICON_ORIENTATION], @@ -114,6 +123,8 @@ export class VectorStyle extends AbstractStyle { _getAllStyleProperties() { return [ + this._symbolizeAsStyleProperty, + this._iconStyleProperty, this._lineColorStyleProperty, this._fillColorStyleProperty, this._lineWidthStyleProperty, @@ -153,7 +164,6 @@ export class VectorStyle extends AbstractStyle { { @@ -527,7 +537,7 @@ export class VectorStyle extends AbstractStyle { } arePointsSymbolizedAsCircles() { - return this._descriptor.properties.symbol.options.symbolizeAs === SYMBOLIZE_AS_CIRCLE; + return !this._symbolizeAsStyleProperty.isSymbolizedAsIcon(); } setMBPaintProperties({ alpha, mbMap, fillLayerId, lineLayerId }) { @@ -554,23 +564,22 @@ export class VectorStyle extends AbstractStyle { } setMBSymbolPropertiesForPoints({ mbMap, symbolLayerId, alpha }) { - const symbolId = this._descriptor.properties.symbol.options.symbolId; mbMap.setLayoutProperty(symbolLayerId, 'icon-ignore-placement', true); - mbMap.setLayoutProperty(symbolLayerId, 'icon-anchor', getMakiSymbolAnchor(symbolId)); mbMap.setPaintProperty(symbolLayerId, 'icon-opacity', alpha); + this._iconStyleProperty.syncIconWithMb( + symbolLayerId, + mbMap, + this._iconSizeStyleProperty.getIconPixelSize() + ); // icon-color is only supported on SDF icons. this._fillColorStyleProperty.syncIconColorWithMb(symbolLayerId, mbMap); this._lineColorStyleProperty.syncHaloBorderColorWithMb(symbolLayerId, mbMap); this._lineWidthStyleProperty.syncHaloWidthWithMb(symbolLayerId, mbMap); - this._iconSizeStyleProperty.syncIconImageAndSizeWithMb(symbolLayerId, mbMap, symbolId); + this._iconSizeStyleProperty.syncIconSizeWithMb(symbolLayerId, mbMap); this._iconOrientationProperty.syncIconRotationWithMb(symbolLayerId, mbMap); } - arePointsSymbolizedAsCircles() { - return this._descriptor.properties.symbol.options.symbolizeAs === SYMBOLIZE_AS_CIRCLE; - } - _makeField(fieldDescriptor) { if (!fieldDescriptor || !fieldDescriptor.name) { return null; @@ -660,4 +669,23 @@ export class VectorStyle extends AbstractStyle { throw new Error(`${descriptor} not implemented`); } } + + _makeIconProperty(descriptor) { + if (!descriptor || !descriptor.options) { + return new StaticIconProperty({ value: DEFAULT_ICON }, VECTOR_STYLES.ICON); + } else if (descriptor.type === StaticStyleProperty.type) { + return new StaticIconProperty(descriptor.options, VECTOR_STYLES.ICON); + } else if (descriptor.type === DynamicStyleProperty.type) { + const field = this._makeField(descriptor.options.field); + return new DynamicIconProperty( + descriptor.options, + VECTOR_STYLES.ICON, + field, + this._getFieldMeta, + this._getFieldFormatter + ); + } else { + throw new Error(`${descriptor} not implemented`); + } + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js index 84e539794b150..cc52d44aed8d3 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js @@ -87,6 +87,12 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => { options: {}, type: 'STATIC', }, + icon: { + options: { + value: 'airfield', + }, + type: 'STATIC', + }, iconOrientation: { options: { orientation: 0, @@ -138,10 +144,9 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => { }, type: 'STATIC', }, - symbol: { + symbolizeAs: { options: { - symbolId: 'airfield', - symbolizeAs: 'circle', + value: 'circle', }, }, }); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js index 54af55b61ab2e..952f8766a6156 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js @@ -5,7 +5,7 @@ */ import { VectorStyle } from './vector_style'; -import { SYMBOLIZE_AS_CIRCLE, DEFAULT_ICON_SIZE } from './vector_constants'; +import { DEFAULT_ICON, SYMBOLIZE_AS_TYPES } from '../../../../common/constants'; import { COLOR_GRADIENTS, COLOR_PALETTES, @@ -14,14 +14,13 @@ import { } from '../color_utils'; import chrome from 'ui/chrome'; -const DEFAULT_ICON = 'airfield'; - export const MIN_SIZE = 1; export const MAX_SIZE = 64; export const DEFAULT_MIN_SIZE = 4; export const DEFAULT_MAX_SIZE = 32; export const DEFAULT_SIGMA = 3; export const DEFAULT_LABEL_SIZE = 14; +export const DEFAULT_ICON_SIZE = 6; export const LABEL_BORDER_SIZES = { NONE: 'NONE', @@ -31,10 +30,11 @@ export const LABEL_BORDER_SIZES = { }; export const VECTOR_STYLES = { - SYMBOL: 'symbol', + SYMBOLIZE_AS: 'symbolizeAs', FILL_COLOR: 'fillColor', LINE_COLOR: 'lineColor', LINE_WIDTH: 'lineWidth', + ICON: 'icon', ICON_SIZE: 'iconSize', ICON_ORIENTATION: 'iconOrientation', LABEL_TEXT: 'labelText', @@ -54,10 +54,9 @@ export const POLYGON_STYLES = [ export function getDefaultProperties(mapColors = []) { return { ...getDefaultStaticProperties(mapColors), - [VECTOR_STYLES.SYMBOL]: { + [VECTOR_STYLES.SYMBOLIZE_AS]: { options: { - symbolizeAs: SYMBOLIZE_AS_CIRCLE, - symbolId: DEFAULT_ICON, + value: SYMBOLIZE_AS_TYPES.CIRCLE, }, }, [VECTOR_STYLES.LABEL_BORDER_SIZE]: { @@ -78,6 +77,12 @@ export function getDefaultStaticProperties(mapColors = []) { const isDarkMode = chrome.getUiSettingsClient().get('theme:darkMode', false); return { + [VECTOR_STYLES.ICON]: { + type: VectorStyle.STYLE_TYPE.STATIC, + options: { + value: DEFAULT_ICON, + }, + }, [VECTOR_STYLES.FILL_COLOR]: { type: VectorStyle.STYLE_TYPE.STATIC, options: { @@ -137,6 +142,13 @@ export function getDefaultStaticProperties(mapColors = []) { export function getDefaultDynamicProperties() { return { + [VECTOR_STYLES.ICON]: { + type: VectorStyle.STYLE_TYPE.DYNAMIC, + options: { + iconPaletteId: 'filledShapes', + field: undefined, + }, + }, [VECTOR_STYLES.FILL_COLOR]: { type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts index 1f06385e12c94..7834bb4511dc6 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts @@ -51,8 +51,12 @@ export const mockSourceLayer = { type: 'STATIC', options: { orientation: 0 }, }, - symbol: { - options: { symbolizeAs: 'icon', symbolId: 'home' }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: 'STATIC', + options: { value: 'home' }, }, }, }, @@ -103,8 +107,12 @@ export const mockDestinationLayer = { type: 'STATIC', options: { orientation: 0 }, }, - symbol: { - options: { symbolizeAs: 'icon', symbolId: 'marker' }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: 'STATIC', + options: { value: 'marker' }, }, }, }, @@ -154,8 +162,12 @@ export const mockClientLayer = { type: 'STATIC', options: { orientation: 0 }, }, - symbol: { - options: { symbolizeAs: 'icon', symbolId: 'home' }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: 'STATIC', + options: { value: 'home' }, }, }, }, @@ -206,8 +218,12 @@ export const mockServerLayer = { type: 'STATIC', options: { orientation: 0 }, }, - symbol: { - options: { symbolizeAs: 'icon', symbolId: 'marker' }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: 'STATIC', + options: { value: 'marker' }, }, }, }, @@ -266,8 +282,12 @@ export const mockLineLayer = { type: 'STATIC', options: { orientation: 0 }, }, - symbol: { - options: { symbolizeAs: 'circle', symbolId: 'airfield' }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: 'STATIC', + options: { value: 'airfield' }, }, }, }, @@ -326,8 +346,12 @@ export const mockClientServerLineLayer = { type: 'STATIC', options: { orientation: 0 }, }, - symbol: { - options: { symbolizeAs: 'circle', symbolId: 'airfield' }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: 'STATIC', + options: { value: 'airfield' }, }, }, }, diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts index f34376421e9b2..e8b267122f86f 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts @@ -164,8 +164,12 @@ export const getSourceLayer = ( type: 'STATIC', options: { orientation: 0 }, }, - symbol: { - options: { symbolizeAs: 'icon', symbolId: 'home' }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: 'STATIC', + options: { value: 'home' }, }, }, }, @@ -223,8 +227,12 @@ export const getDestinationLayer = ( type: 'STATIC', options: { orientation: 0 }, }, - symbol: { - options: { symbolizeAs: 'icon', symbolId: 'marker' }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: 'STATIC', + options: { value: 'marker' }, }, }, }, @@ -303,8 +311,12 @@ export const getLineLayer = ( type: 'STATIC', options: { orientation: 0 }, }, - symbol: { - options: { symbolizeAs: 'circle', symbolId: 'airfield' }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: 'STATIC', + options: { value: 'airfield' }, }, }, }, diff --git a/x-pack/test/api_integration/apis/maps/migrations.js b/x-pack/test/api_integration/apis/maps/migrations.js index fea36f7ceea74..c4587530e160b 100644 --- a/x-pack/test/api_integration/apis/maps/migrations.js +++ b/x-pack/test/api_integration/apis/maps/migrations.js @@ -41,7 +41,7 @@ export default function({ getService }) { type: 'index-pattern', }, ]); - expect(resp.body.migrationVersion).to.eql({ map: '7.6.0' }); + expect(resp.body.migrationVersion).to.eql({ map: '7.7.0' }); expect(resp.body.attributes.layerListJSON.includes('indexPatternRefName')).to.be(true); }); });