Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Maps] Autocomplete for custom color palettes and custom icon palettes #56446

Merged
merged 17 commits into from
Feb 12, 2020
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions x-pack/legacy/plugins/maps/public/kibana_services.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
22 changes: 22 additions & 0 deletions x-pack/legacy/plugins/maps/public/layers/sources/es_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { AbstractVectorSource } from './vector_source';
import {
autocompleteService,
fetchSearchSourceAndRecordWithInspector,
indexPatternService,
SearchSource,
Expand Down Expand Up @@ -344,4 +345,25 @@ export class AbstractESSource extends AbstractVectorSource {

return resp.aggregations;
}

getValueSuggestions = async (fieldName, query) => {
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
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 [];
}
};
}
4 changes: 4 additions & 0 deletions x-pack/legacy/plugins/maps/public/layers/sources/source.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,8 @@ export class AbstractSource {
async loadStylePropsMeta() {
throw new Error(`Source#loadStylePropsMeta not implemented`);
}

async getValueSuggestions(/* fieldName, query */) {
return [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export class ColorMapSelect extends Component {
<ColorStopsCategorical
colorStops={this.state.customColorMap}
onChange={this._onCustomColorMapChange}
getValueSuggestions={this.props.getValueSuggestions}
/>
</Fragment>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,16 @@ 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 },
],
onChange,
getValueSuggestions,
}) => {
const sanitizeStopInput = value => {
return value;
};

const getStopError = (stop, index) => {
let count = 0;
for (let i = 1; i < colorStops.length; i++) {
Expand All @@ -49,34 +47,21 @@ export const ColorStopsCategorical = ({
if (index === 0) {
return (
<EuiFieldText
aria-label={i18n.translate(
'xpack.maps.styles.colorStops.categoricalStop.defaultCategoryAriaLabel',
{
defaultMessage: 'Default stop',
}
)}
value={stopValue}
aria-label={getOtherCategoryLabel()}
placeholder={getOtherCategoryLabel()}
disabled
onChange={onStopChange}
compressed
/>
);
} else {
return (
<EuiFieldText
aria-label={i18n.translate(
'xpack.maps.styles.colorStops.categoricalStop.categoryAriaLabel',
{
defaultMessage: 'Category',
}
)}
value={stopValue}
onChange={onStopChange}
compressed
/>
);
}

return (
<StopInput
getValueSuggestions={getValueSuggestions}
value={stopValue}
onChange={onStopChange}
/>
);
};

const canDeleteStop = (colorStops, index) => {
Expand All @@ -88,7 +73,6 @@ export const ColorStopsCategorical = ({
onChange={onChange}
colorStops={colorStops}
isStopsInvalid={isCategoricalStopsInvalid}
sanitizeStopInput={sanitizeStopInput}
getStopError={getStopError}
renderStopInput={renderStopInput}
canDeleteStop={canDeleteStop}
Expand All @@ -114,4 +98,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,
};
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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 (
<EuiFieldNumber
aria-label={i18n.translate('xpack.maps.styles.colorStops.ordinalStop.stopLabel', {
defaultMessage: 'Stop',
})}
value={stop}
onChange={onStopChange}
onChange={handleOnChangeEvent}
compressed
/>
);
Expand All @@ -65,7 +65,6 @@ export const ColorStopsOrdinal = ({
onChange={onChange}
colorStops={colorStops}
isStopsInvalid={isOrdinalStopsInvalid}
sanitizeStopInput={sanitizeStopInput}
getStopError={getStopError}
renderStopInput={renderStopInput}
canDeleteStop={canDeleteStop}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function DynamicColorForm({
color={styleOptions.color}
customColorMap={styleOptions.customColorRamp}
useCustomColorMap={_.get(styleOptions, 'useCustomColorRamp', false)}
compressed
getValueSuggestions={styleProperty.getValueSuggestions}
/>
);
}
Expand All @@ -83,7 +83,7 @@ export function DynamicColorForm({
color={styleOptions.colorCategory}
customColorMap={styleOptions.customColorPalette}
useCustomColorMap={_.get(styleOptions, 'useCustomColorPalette', false)}
compressed
getValueSuggestions={styleProperty.getValueSuggestions}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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 } from '@elastic/eui';

export class StopInput extends Component {
state = {
suggestions: [],
isLoadingSuggestions: false,
hasPrevFocus: false,
};

componentDidMount() {
this._isMounted = true;
}

componentWillUnmount() {
this._isMounted = false;
this._loadSuggestions.cancel();
}

_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);

render() {
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);
}

thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
return (
<EuiComboBox
options={suggestionOptions}
selectedOptions={selectedOptions}
singleSelection={{ asPlainText: true }}
onChange={this._onChange}
onSearchChange={this._onSearchChange}
onCreateOption={this._onCreateOption}
isClearable={false}
isLoading={this.state.isLoadingSuggestions}
onFocus={this._onFocus}
compressed
/>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function DynamicIconForm({
return (
<IconMapSelect
{...styleOptions}
getValueSuggestions={styleProperty.getValueSuggestions}
onChange={onIconMapChange}
isDarkMode={isDarkMode}
symbolOptions={symbolOptions}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { IconStops } from './icon_stops';

export function IconMapSelect({
customIconStops,
getValueSuggestions,
iconPaletteId,
isDarkMode,
onChange,
Expand All @@ -30,6 +31,7 @@ export function IconMapSelect({
function renderCustomIconStopsInput(onCustomMapChange) {
return (
<IconStops
getValueSuggestions={getValueSuggestions}
iconStops={customIconStops}
isDarkMode={isDarkMode}
onChange={onCustomMapChange}
Expand Down
Loading