From c06ec043f34fdbb0e420cfb8e8dcde7996689e57 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Wed, 12 Feb 2020 13:51:56 -0800 Subject: [PATCH 1/3] [DOCS] Fixes typo in release highlights (#57487) --- docs/release-notes/highlights-7.2.0.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/highlights-7.2.0.asciidoc b/docs/release-notes/highlights-7.2.0.asciidoc index 9c29faa703fefd..746793e05bab4d 100644 --- a/docs/release-notes/highlights-7.2.0.asciidoc +++ b/docs/release-notes/highlights-7.2.0.asciidoc @@ -175,7 +175,7 @@ pivoting the data. There is also a preview of the transform for reviewing the co image::release-notes/images/7.2-data-frames.png[{dataframes-cap}] {transforms-cap} are managed on a new list page, displaying the details and -status of each {tranform}, and controls for starting, stopping, or deleting {transforms}. +status of each {transform}, and controls for starting, stopping, or deleting {transforms}. [role="screenshot"] image::release-notes/images/7.2-data-frames-list-view.png[{dataframes-cap}] From a423636ce9c318121a72de5c6e8c14266fa7b6ad Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 12 Feb 2020 14:15:07 -0800 Subject: [PATCH 2/3] Add "coerce" to dev tools autocomplete (#56862) (#57498) Co-authored-by: AndyHunt66 --- .../console/server/lib/spec_definitions/es_6_0/mappings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/console/server/lib/spec_definitions/es_6_0/mappings.js b/src/plugins/console/server/lib/spec_definitions/es_6_0/mappings.js index 46f32fe58089b4..8c31e5bc6fbb25 100644 --- a/src/plugins/console/server/lib/spec_definitions/es_6_0/mappings.js +++ b/src/plugins/console/server/lib/spec_definitions/es_6_0/mappings.js @@ -96,6 +96,7 @@ export default function(api) { doc_values: BOOLEAN, eager_global_ordinals: BOOLEAN, norms: BOOLEAN, + coerce: BOOLEAN, // Not actually available in V6 of ES. Add when updating the autocompletion system. // index_phrases: BOOLEAN, From 2c5a5131baa11b05aab357d2e96b2c33403b6ccd Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 12 Feb 2020 16:08:11 -0700 Subject: [PATCH 3/3] [Maps] Autocomplete for custom color palettes and custom icon palettes (#56446) (#57504) * [Maps] type ahead for stop values for custom color maps and custom icon maps * use Popover to show type ahead suggestions * datalist version * use EuiComboBox * clean up * wire ColorStopsCategorical to use StopInput component for autocomplete * clean up * cast suggestion values to string so boolean fields work * review feedback * fix problem with stall suggestions from previous field Co-authored-by: Elastic Machine Co-authored-by: Elastic Machine --- .../plugins/maps/public/kibana_services.js | 1 + .../maps/public/layers/sources/es_source.js | 22 +++ .../maps/public/layers/sources/source.js | 4 + .../components/color/color_map_select.js | 2 + .../vector/components/color/color_stops.js | 11 +- .../color/color_stops_categorical.js | 45 +++--- .../components/color/color_stops_ordinal.js | 13 +- .../components/color/dynamic_color_form.js | 4 +- .../styles/vector/components/stop_input.js | 148 ++++++++++++++++++ .../components/symbol/dynamic_icon_form.js | 1 + .../components/symbol/icon_map_select.js | 3 + .../vector/components/symbol/icon_stops.js | 45 ++++-- .../properties/dynamic_size_property.js | 12 +- .../properties/dynamic_style_property.js | 8 +- .../layers/styles/vector/vector_style.js | 10 +- 15 files changed, 264 insertions(+), 65 deletions(-) create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js diff --git a/x-pack/legacy/plugins/maps/public/kibana_services.js b/x-pack/legacy/plugins/maps/public/kibana_services.js index 60fda398b4f3ee..a1b1c9ec1518e6 100644 --- a/x-pack/legacy/plugins/maps/public/kibana_services.js +++ b/x-pack/legacy/plugins/maps/public/kibana_services.js @@ -14,6 +14,7 @@ import { npStart } from 'ui/new_platform'; export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER; export { SearchSource } from '../../../../../src/plugins/data/public'; export const indexPatternService = npStart.plugins.data.indexPatterns; +export const autocompleteService = npStart.plugins.data.autocomplete; let licenseId; export const setLicenseId = latestLicenseId => (licenseId = latestLicenseId); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js index 26cc7ece667539..d78d3038f870d9 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js @@ -6,6 +6,7 @@ import { AbstractVectorSource } from './vector_source'; import { + autocompleteService, fetchSearchSourceAndRecordWithInspector, indexPatternService, SearchSource, @@ -344,4 +345,25 @@ export class AbstractESSource extends AbstractVectorSource { return resp.aggregations; } + + getValueSuggestions = async (fieldName, query) => { + if (!fieldName) { + return []; + } + + try { + const indexPattern = await this.getIndexPattern(); + const field = indexPattern.fields.getByName(fieldName); + return await autocompleteService.getValueSuggestions({ + indexPattern, + field, + query, + }); + } catch (error) { + console.warn( + `Unable to fetch suggestions for field: ${fieldName}, query: ${query}, error: ${error.message}` + ); + return []; + } + }; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.js b/x-pack/legacy/plugins/maps/public/layers/sources/source.js index cc5d62bbdfeefc..3c6ddb74bedeba 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/source.js @@ -139,4 +139,8 @@ export class AbstractSource { async loadStylePropsMeta() { throw new Error(`Source#loadStylePropsMeta not implemented`); } + + async getValueSuggestions(/* fieldName, query */) { + return []; + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js index fde088ab4475e6..e8d5754ef42067 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js @@ -72,6 +72,8 @@ export class ColorMapSelect extends Component { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js index 6b403ff61532da..47c2d037e0c793 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js @@ -59,26 +59,23 @@ export const ColorStops = ({ onChange, colorStops, isStopsInvalid, - sanitizeStopInput, getStopError, renderStopInput, addNewRow, canDeleteStop, }) => { function getStopInput(stop, index) { - const onStopChange = e => { + const onStopChange = newStopValue => { const newColorStops = _.cloneDeep(colorStops); - newColorStops[index].stop = sanitizeStopInput(e.target.value); - const invalid = isStopsInvalid(newColorStops); + newColorStops[index].stop = newStopValue; onChange({ colorStops: newColorStops, - isInvalid: invalid, + isInvalid: isStopsInvalid(newColorStops), }); }; - const error = getStopError(stop, index); return { - stopError: error, + stopError: getStopError(stop, index), stopInput: renderStopInput(stop, onStopChange, index), }; } 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 d52c3dbcfa1df9..124c2bf0cff556 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 @@ -17,18 +17,17 @@ import { import { i18n } from '@kbn/i18n'; import { ColorStops } from './color_stops'; import { getOtherCategoryLabel } from '../../style_util'; +import { StopInput } from '../stop_input'; export const ColorStopsCategorical = ({ colorStops = [ { stop: null, color: DEFAULT_CUSTOM_COLOR }, //first stop is the "other" color { stop: '', color: DEFAULT_NEXT_COLOR }, ], + field, onChange, + getValueSuggestions, }) => { - const sanitizeStopInput = value => { - return value; - }; - const getStopError = (stop, index) => { let count = 0; for (let i = 1; i < colorStops.length; i++) { @@ -49,34 +48,23 @@ export const ColorStopsCategorical = ({ if (index === 0) { return ( - ); - } else { - return ( - ); } + + return ( + + ); }; const canDeleteStop = (colorStops, index) => { @@ -88,7 +76,6 @@ export const ColorStopsCategorical = ({ onChange={onChange} colorStops={colorStops} isStopsInvalid={isCategoricalStopsInvalid} - sanitizeStopInput={sanitizeStopInput} getStopError={getStopError} renderStopInput={renderStopInput} canDeleteStop={canDeleteStop} @@ -114,4 +101,8 @@ ColorStopsCategorical.propTypes = { * Callback for when the color stops changes. Called with { colorStops, isInvalid } */ onChange: PropTypes.func.isRequired, + /** + * Callback for fetching stop value suggestions. Called with query. + */ + getValueSuggestions: PropTypes.func.isRequired, }; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js index 61fbb376ad601f..0f6a0583d3dbcc 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js @@ -21,11 +21,6 @@ export const ColorStopsOrdinal = ({ colorStops = [{ stop: 0, color: DEFAULT_CUSTOM_COLOR }], onChange, }) => { - const sanitizeStopInput = value => { - const sanitizedValue = parseFloat(value); - return isNaN(sanitizedValue) ? '' : sanitizedValue; - }; - const getStopError = (stop, index) => { let error; if (isOrdinalStopInvalid(stop)) { @@ -44,13 +39,18 @@ export const ColorStopsOrdinal = ({ }; const renderStopInput = (stop, onStopChange) => { + function handleOnChangeEvent(event) { + const sanitizedValue = parseFloat(event.target.value); + const newStopValue = isNaN(sanitizedValue) ? '' : sanitizedValue; + onStopChange(newStopValue); + } return ( ); @@ -65,7 +65,6 @@ export const ColorStopsOrdinal = ({ onChange={onChange} colorStops={colorStops} isStopsInvalid={isOrdinalStopsInvalid} - sanitizeStopInput={sanitizeStopInput} getStopError={getStopError} renderStopInput={renderStopInput} canDeleteStop={canDeleteStop} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js index 5491d5d567f842..af5e5b37f5467f 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js @@ -67,7 +67,7 @@ export function DynamicColorForm({ color={styleOptions.color} customColorMap={styleOptions.customColorRamp} useCustomColorMap={_.get(styleOptions, 'useCustomColorRamp', false)} - compressed + styleProperty={styleProperty} /> ); } @@ -83,7 +83,7 @@ export function DynamicColorForm({ color={styleOptions.colorCategory} customColorMap={styleOptions.customColorPalette} useCustomColorMap={_.get(styleOptions, 'useCustomColorPalette', false)} - compressed + styleProperty={styleProperty} /> ); }; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js new file mode 100644 index 00000000000000..d12a3d77d0b294 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js @@ -0,0 +1,148 @@ +/* + * 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, { Component } from 'react'; + +import { EuiComboBox, EuiFieldText } from '@elastic/eui'; + +export class StopInput extends Component { + constructor(props) { + super(props); + this.state = { + suggestions: [], + isLoadingSuggestions: false, + hasPrevFocus: false, + fieldDataType: undefined, + localFieldTextValue: props.value, + }; + } + + componentDidMount() { + this._isMounted = true; + this._loadFieldDataType(); + } + + componentWillUnmount() { + this._isMounted = false; + this._loadSuggestions.cancel(); + } + + async _loadFieldDataType() { + const fieldDataType = await this.props.field.getDataType(); + if (this._isMounted) { + this.setState({ fieldDataType }); + } + } + + _onFocus = () => { + if (!this.state.hasPrevFocus) { + this.setState({ hasPrevFocus: true }); + this._onSearchChange(''); + } + }; + + _onChange = selectedOptions => { + this.props.onChange(_.get(selectedOptions, '[0].label', '')); + }; + + _onCreateOption = newValue => { + this.props.onChange(newValue); + }; + + _onSearchChange = async searchValue => { + this.setState( + { + isLoadingSuggestions: true, + searchValue, + }, + () => { + this._loadSuggestions(searchValue); + } + ); + }; + + _loadSuggestions = _.debounce(async searchValue => { + let suggestions = []; + try { + suggestions = await this.props.getValueSuggestions(searchValue); + } catch (error) { + // ignore suggestions error + } + + if (this._isMounted && searchValue === this.state.searchValue) { + this.setState({ + isLoadingSuggestions: false, + suggestions, + }); + } + }, 300); + + _onFieldTextChange = event => { + this.setState({ localFieldTextValue: event.target.value }); + // onChange can cause UI lag, ensure smooth input typing by debouncing onChange + this._debouncedOnFieldTextChange(); + }; + + _debouncedOnFieldTextChange = _.debounce(() => { + this.props.onChange(this.state.localFieldTextValue); + }, 500); + + _renderSuggestionInput() { + const suggestionOptions = this.state.suggestions.map(suggestion => { + return { label: `${suggestion}` }; + }); + + const selectedOptions = []; + if (this.props.value) { + let option = suggestionOptions.find(({ label }) => { + return label === this.props.value; + }); + if (!option) { + option = { label: this.props.value }; + suggestionOptions.unshift(option); + } + selectedOptions.push(option); + } + + return ( + + ); + } + + _renderTextInput() { + return ( + + ); + } + + render() { + if (!this.state.fieldDataType) { + return null; + } + + // autocomplete service can not provide suggestions for non string fields (and boolean) because it uses + // term aggregation include parameter. Include paramerter uses a regular expressions that only supports string type + return this.state.fieldDataType === 'string' || this.state.fieldDataType === 'boolean' + ? this._renderSuggestionInput() + : this._renderTextInput(); + } +} 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 index 9a0d73cef616c8..afa11daf452178 100644 --- 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 @@ -43,6 +43,7 @@ export function DynamicIconForm({ return ( { @@ -23,7 +24,14 @@ const DEFAULT_ICON_STOPS = [ { stop: '', icon: DEFAULT_ICON }, ]; -export function IconStops({ iconStops = DEFAULT_ICON_STOPS, isDarkMode, onChange, symbolOptions }) { +export function IconStops({ + field, + getValueSuggestions, + iconStops = DEFAULT_ICON_STOPS, + isDarkMode, + onChange, + symbolOptions, +}) { return iconStops.map(({ stop, icon }, index) => { const onIconSelect = selectedIconId => { const newIconStops = [...iconStops]; @@ -33,8 +41,7 @@ export function IconStops({ iconStops = DEFAULT_ICON_STOPS, isDarkMode, onChange }; onChange({ customMapStops: newIconStops }); }; - const onStopChange = e => { - const newStopValue = e.target.value; + const onStopChange = newStopValue => { const newIconStops = [...iconStops]; newIconStops[index] = { ...iconStops[index], @@ -83,7 +90,24 @@ export function IconStops({ iconStops = DEFAULT_ICON_STOPS, isDarkMode, onChange const errors = []; // TODO check for duplicate values and add error messages here - const isOtherCategoryRow = index === 0; + const stopInput = + index === 0 ? ( + + ) : ( + + ); + return (
- - - + {stopInput} { + const fieldName = this.getFieldName(); + return this._source && fieldName ? this._source.getValueSuggestions(fieldName, query) : []; + }; + getFieldMeta() { return this._getFieldMeta && this._field ? this._getFieldMeta(this._field.getName()) : null; } 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 97259a908f1e47..1f96c37c9d2868 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 @@ -612,6 +612,7 @@ export class VectorStyle extends AbstractStyle { field, this._getFieldMeta, this._getFieldFormatter, + this._source, isSymbolizedAsIcon ); } else { @@ -631,7 +632,8 @@ export class VectorStyle extends AbstractStyle { styleName, field, this._getFieldMeta, - this._getFieldFormatter + this._getFieldFormatter, + this._source ); } else { throw new Error(`${descriptor} not implemented`); @@ -663,7 +665,8 @@ export class VectorStyle extends AbstractStyle { VECTOR_STYLES.LABEL_TEXT, field, this._getFieldMeta, - this._getFieldFormatter + this._getFieldFormatter, + this._source ); } else { throw new Error(`${descriptor} not implemented`); @@ -682,7 +685,8 @@ export class VectorStyle extends AbstractStyle { VECTOR_STYLES.ICON, field, this._getFieldMeta, - this._getFieldFormatter + this._getFieldFormatter, + this._source ); } else { throw new Error(`${descriptor} not implemented`);