From 05b5c916afbc7918bacb8ae1f1b321f28f753680 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Thu, 7 Jul 2022 16:01:50 +0200 Subject: [PATCH] [Lens] Refactor field select component as shared (#134773) * :recycle: Refactor field select component as shared * :rotating_light: Fix linting + types issues * :bug: Fix issue with classes * :label: Fix type issue Co-authored-by: Joe Reuter Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../indexpattern_datasource/datapanel.tsx | 2 +- .../dimension_panel/dimension_editor.tsx | 4 +- .../dimension_panel/field_select.tsx | 142 ++++-------------- .../dimension_panel/reference_editor.tsx | 4 +- .../indexpattern_datasource/field_item.tsx | 2 +- .../lens_field_icon.test.tsx.snap | 0 .../field_picker/field_picker.scss | 7 + .../field_picker/field_picker.tsx | 136 +++++++++++++++++ .../shared_components/field_picker/index.ts | 11 ++ .../field_picker}/lens_field_icon.test.tsx | 0 .../field_picker}/lens_field_icon.tsx | 4 +- .../field_picker}/truncated_label.test.tsx | 0 .../field_picker}/truncated_label.tsx | 0 .../shared_components/field_picker/types.ts | 26 ++++ .../lens/public/shared_components/index.ts | 2 + 15 files changed, 222 insertions(+), 118 deletions(-) rename x-pack/plugins/lens/public/{indexpattern_datasource => shared_components/field_picker}/__snapshots__/lens_field_icon.test.tsx.snap (100%) create mode 100644 x-pack/plugins/lens/public/shared_components/field_picker/field_picker.scss create mode 100644 x-pack/plugins/lens/public/shared_components/field_picker/field_picker.tsx create mode 100644 x-pack/plugins/lens/public/shared_components/field_picker/index.ts rename x-pack/plugins/lens/public/{indexpattern_datasource => shared_components/field_picker}/lens_field_icon.test.tsx (100%) rename x-pack/plugins/lens/public/{indexpattern_datasource => shared_components/field_picker}/lens_field_icon.tsx (82%) rename x-pack/plugins/lens/public/{indexpattern_datasource/dimension_panel => shared_components/field_picker}/truncated_label.test.tsx (100%) rename x-pack/plugins/lens/public/{indexpattern_datasource/dimension_panel => shared_components/field_picker}/truncated_label.tsx (100%) create mode 100644 x-pack/plugins/lens/public/shared_components/field_picker/types.ts diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 385f92b0dc05..551e3f85a64f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -46,7 +46,7 @@ import { trackUiEvent } from '../lens_ui_telemetry'; import { loadIndexPatterns, syncExistingFields } from './loader'; import { fieldExists } from './pure_helpers'; import { Loader } from '../loader'; -import { LensFieldIcon } from './lens_field_icon'; +import { LensFieldIcon } from '../shared_components/field_picker/lens_field_icon'; import { FieldGroups, FieldList } from './field_list'; export type Props = Omit, 'core'> & { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index a770497d52bb..4e667e2aa30d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -61,8 +61,8 @@ import { FieldInput } from './field_input'; import { NameInput } from '../../shared_components'; import { ParamEditorProps } from '../operations/definitions'; import { WrappingHelpPopover } from '../help_popover'; -import { FieldChoice } from './field_select'; import { isColumn } from '../operations/definitions/helpers'; +import { FieldChoiceWithOperationType } from './field_select'; export interface DimensionEditorProps extends IndexPatternDimensionEditorProps { selectedColumn?: GenericIndexPatternColumn; @@ -606,7 +606,7 @@ export function DimensionEditor(props: DimensionEditorProps) { }) ); }} - onChooseField={(choice: FieldChoice) => { + onChooseField={(choice: FieldChoiceWithOperationType) => { trackUiEvent('indexpattern_dimension_field_changed'); updateLayer( insertOrReplaceColumn({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx index 869fdeaf75af..16e70f5657db 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx @@ -7,30 +7,19 @@ import './field_select.scss'; import { partition } from 'lodash'; -import React, { useMemo, useRef } from 'react'; +import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import useEffectOnce from 'react-use/lib/useEffectOnce'; -import { - EuiComboBox, - EuiFlexGroup, - EuiFlexItem, - EuiComboBoxOptionOption, - EuiComboBoxProps, -} from '@elastic/eui'; -import classNames from 'classnames'; -import { LensFieldIcon } from '../lens_field_icon'; +import { EuiComboBoxOptionOption, EuiComboBoxProps } from '@elastic/eui'; import { trackUiEvent } from '../../lens_ui_telemetry'; import { fieldExists } from '../pure_helpers'; -import { TruncatedLabel } from './truncated_label'; import type { OperationType } from '../indexpattern'; -import type { DataType } from '../../types'; import type { OperationSupportMatrix } from './operation_support'; import type { IndexPattern, IndexPatternPrivateState } from '../types'; -export interface FieldChoice { - type: 'field'; - field: string; +import { FieldOption, FieldOptionValue, FieldPicker } from '../../shared_components/field_picker'; + +export type FieldChoiceWithOperationType = FieldOptionValue & { operationType: OperationType; -} +}; export interface FieldSelectProps extends EuiComboBoxProps { currentIndexPattern: IndexPattern; @@ -38,7 +27,7 @@ export interface FieldSelectProps extends EuiComboBoxProps void; + onChoose: (choice: FieldChoiceWithOperationType) => void; onDeleteColumn?: () => void; existingFields: IndexPatternPrivateState['existingFields']; fieldIsInvalid: boolean; @@ -46,10 +35,6 @@ export interface FieldSelectProps extends EuiComboBoxProps(null); - const [labelProps, setLabelProps] = React.useState<{ - width: number; - font: string; - }>({ - width: DEFAULT_COMBOBOX_WIDTH - COMBOBOX_PADDINGS, - font: DEFAULT_FONT, - }); - - const computeStyles = (_e: UIEvent | undefined, shouldRecomputeAll = false) => { - if (comboBoxRef.current) { - const current = { - ...labelProps, - width: comboBoxRef.current?.clientWidth - COMBOBOX_PADDINGS, - }; - if (shouldRecomputeAll) { - current.font = window.getComputedStyle(comboBoxRef.current).font; - } - setLabelProps(current); - } - }; - - useEffectOnce(() => { - if (comboBoxRef.current) { - computeStyles(undefined, true); - } - window.addEventListener('resize', computeStyles); - }); return ( -
- + selectedOptions={ + (selectedOperationType && selectedField + ? [ + { + label: + (selectedOperationType && + selectedField && + currentIndexPattern.getFieldByName(selectedField)?.displayName) ?? + selectedField, + value: { type: 'field', field: selectedField }, + }, + ] + : []) as unknown as Array> + } + options={memoizedFieldOptions as Array>} + onChoose={(choice) => { + if (choice && choice.field !== selectedField) { + trackUiEvent('indexpattern_dimension_field_changed'); + onChoose(choice); } - singleSelection={{ asPlainText: true }} - onChange={(choices) => { - if (choices.length === 0) { - onDeleteColumn?.(); - return; - } - - const choice = choices[0].value as unknown as FieldChoice; - - if (choice.field !== selectedField) { - trackUiEvent('indexpattern_dimension_field_changed'); - onChoose(choice); - } - }} - renderOption={(option, searchValue) => { - return ( - - - - - - - - - ); - }} - {...rest} - /> -
+ }} + onDelete={onDeleteColumn} + fieldIsInvalid={Boolean(incompleteOperation || fieldIsInvalid)} + data-test-subj={dataTestSub ?? 'indexPattern-dimension-field'} + /> ); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx index 53e87ab6620a..57782fa157ca 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx @@ -25,7 +25,7 @@ import { IncompleteColumn, GenericOperationDefinition, } from '../operations'; -import { FieldChoice, FieldSelect } from './field_select'; +import { FieldChoiceWithOperationType, FieldSelect } from './field_select'; import { hasField } from '../pure_utils'; import type { IndexPattern, @@ -96,7 +96,7 @@ export interface ReferenceEditorProps { | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) | GenericIndexPatternColumn ) => void; - onChooseField: (choice: FieldChoice) => void; + onChooseField: (choice: FieldChoiceWithOperationType) => void; onDeleteColumn: () => void; onChooseFunction: (operationType: string, field?: IndexPatternField) => void; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index 5e26e49a8ef1..27c774ed2963 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -47,7 +47,7 @@ import { DragDrop, DragDropIdentifier } from '../drag_drop'; import { DatasourceDataPanelProps, DataType } from '../types'; import { BucketedAggregation, DOCUMENT_FIELD_NAME, FieldStatsResponse } from '../../common'; import { IndexPattern, IndexPatternField, DraggedField } from './types'; -import { LensFieldIcon } from './lens_field_icon'; +import { LensFieldIcon } from '../shared_components/field_picker/lens_field_icon'; import { trackUiEvent } from '../lens_ui_telemetry'; import { VisualizeGeoFieldButton } from './visualize_geo_field_button'; import { getVisualizeGeoFieldMessage } from '../utils'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/__snapshots__/lens_field_icon.test.tsx.snap b/x-pack/plugins/lens/public/shared_components/field_picker/__snapshots__/lens_field_icon.test.tsx.snap similarity index 100% rename from x-pack/plugins/lens/public/indexpattern_datasource/__snapshots__/lens_field_icon.test.tsx.snap rename to x-pack/plugins/lens/public/shared_components/field_picker/__snapshots__/lens_field_icon.test.tsx.snap diff --git a/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.scss b/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.scss new file mode 100644 index 000000000000..d8e4e9b8f720 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.scss @@ -0,0 +1,7 @@ +.lnFieldPicker__option--incompatible { + color: $euiColorLightShade; +} + +.lnFieldPicker__option--nonExistant { + background-color: $euiColorLightestShade; +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.tsx b/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.tsx new file mode 100644 index 000000000000..bde920446d46 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.tsx @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import './field_picker.scss'; +import React, { useRef } from 'react'; +import { i18n } from '@kbn/i18n'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; +import { EuiComboBox, EuiComboBoxProps, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import classNames from 'classnames'; +import { DataType } from '../../types'; +import { LensFieldIcon } from './lens_field_icon'; +import { TruncatedLabel } from './truncated_label'; +import type { FieldOptionValue, FieldOption } from './types'; + +export interface FieldPickerProps + extends EuiComboBoxProps['value']> { + options: Array>; + selectedField?: string; + onChoose: (choice: T | undefined) => void; + onDelete?: () => void; + fieldIsInvalid: boolean; + 'data-test-subj'?: string; +} + +const DEFAULT_COMBOBOX_WIDTH = 305; +const COMBOBOX_PADDINGS = 90; +const DEFAULT_FONT = '14px Inter'; + +export function FieldPicker({ + selectedOptions, + options, + onChoose, + onDelete, + fieldIsInvalid, + ['data-test-subj']: dataTestSub, + ...rest +}: FieldPickerProps) { + const styledOptions = options?.map(({ compatible, exists, ...otherAttr }) => { + if (otherAttr.options) { + return { + ...otherAttr, + compatible, + exists, + options: otherAttr.options.map((fieldOption) => ({ + ...fieldOption, + className: classNames({ + 'lnFieldPicker__option--incompatible': !fieldOption.compatible, + 'lnFieldPicker__option--nonExistant': !fieldOption.exists, + }), + })), + }; + } + return { + ...otherAttr, + compatible, + exists, + className: classNames({ + 'lnFieldPicker__option--incompatible': !compatible, + 'lnFieldPicker__option--nonExistant': !exists, + }), + }; + }); + const comboBoxRef = useRef(null); + const [labelProps, setLabelProps] = React.useState<{ + width: number; + font: string; + }>({ + width: DEFAULT_COMBOBOX_WIDTH - COMBOBOX_PADDINGS, + font: DEFAULT_FONT, + }); + + const computeStyles = (_e: UIEvent | undefined, shouldRecomputeAll = false) => { + if (comboBoxRef.current) { + const current = { + ...labelProps, + width: comboBoxRef.current?.clientWidth - COMBOBOX_PADDINGS, + }; + if (shouldRecomputeAll) { + current.font = window.getComputedStyle(comboBoxRef.current).font; + } + setLabelProps(current); + } + }; + + useEffectOnce(() => { + if (comboBoxRef.current) { + computeStyles(undefined, true); + } + window.addEventListener('resize', computeStyles); + }); + + return ( +
+ { + if (choices.length === 0) { + onDelete?.(); + return; + } + onChoose(choices[0].value); + }} + renderOption={(option, searchValue) => { + return ( + + + + + + + + + ); + }} + {...rest} + /> +
+ ); +} diff --git a/x-pack/plugins/lens/public/shared_components/field_picker/index.ts b/x-pack/plugins/lens/public/shared_components/field_picker/index.ts new file mode 100644 index 000000000000..d9e18182dd0d --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/field_picker/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { LensFieldIcon } from './lens_field_icon'; +export { FieldPicker } from './field_picker'; +export { TruncatedLabel } from './truncated_label'; +export type { FieldOptionValue, FieldOption } from './types'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.test.tsx b/x-pack/plugins/lens/public/shared_components/field_picker/lens_field_icon.test.tsx similarity index 100% rename from x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.test.tsx rename to x-pack/plugins/lens/public/shared_components/field_picker/lens_field_icon.test.tsx diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx b/x-pack/plugins/lens/public/shared_components/field_picker/lens_field_icon.tsx similarity index 82% rename from x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx rename to x-pack/plugins/lens/public/shared_components/field_picker/lens_field_icon.tsx index be30712a9de0..fabb8cab6fd0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx +++ b/x-pack/plugins/lens/public/shared_components/field_picker/lens_field_icon.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { FieldIcon, FieldIconProps } from '@kbn/react-field'; -import { DataType } from '../types'; -import { normalizeOperationDataType } from './pure_utils'; +import { DataType } from '../../types'; +import { normalizeOperationDataType } from '../../indexpattern_datasource/pure_utils'; export function LensFieldIcon({ type, ...rest }: FieldIconProps & { type: DataType }) { return ( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.test.tsx b/x-pack/plugins/lens/public/shared_components/field_picker/truncated_label.test.tsx similarity index 100% rename from x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.test.tsx rename to x-pack/plugins/lens/public/shared_components/field_picker/truncated_label.test.tsx diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.tsx b/x-pack/plugins/lens/public/shared_components/field_picker/truncated_label.tsx similarity index 100% rename from x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.tsx rename to x-pack/plugins/lens/public/shared_components/field_picker/truncated_label.tsx diff --git a/x-pack/plugins/lens/public/shared_components/field_picker/types.ts b/x-pack/plugins/lens/public/shared_components/field_picker/types.ts new file mode 100644 index 000000000000..4d607b47ef91 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/field_picker/types.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { EuiComboBoxOptionOption } from '@elastic/eui'; +import type { DataType } from '../../types'; + +export interface FieldOptionValue { + type: 'field'; + field: string; + dataType?: DataType; +} + +interface FieldValue { + label: string; + value: T; + exists: boolean; + compatible: number | boolean; + 'data-test-subj'?: string; + // defined in groups + options?: Array>; +} + +export type FieldOption = FieldValue & EuiComboBoxOptionOption; diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index 7da63a694721..e3a9af00ad00 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -9,6 +9,8 @@ export type { ToolbarPopoverProps } from './toolbar_popover'; export { ToolbarPopover } from './toolbar_popover'; export { LegendSettingsPopover } from './legend_settings_popover'; export { PalettePicker } from './palette_picker'; +export { FieldPicker, LensFieldIcon, TruncatedLabel } from './field_picker'; +export type { FieldOption, FieldOptionValue } from './field_picker'; export { RangeInputField } from './range_input_field'; export { BucketAxisBoundsControl,