From 923add64b6f90606b444960299fa34f0d5c09878 Mon Sep 17 00:00:00 2001 From: dej611 Date: Mon, 20 Jun 2022 19:23:52 +0200 Subject: [PATCH 1/4] :recycle: Refactor field select component as shared --- .../indexpattern_datasource/datapanel.tsx | 2 +- .../dimension_panel/field_select.tsx | 139 ++++-------------- .../indexpattern_datasource/field_item.tsx | 2 +- .../field_picker/field_picker.scss | 7 + .../field_picker/field_picker.tsx | 120 +++++++++++++++ .../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 | 22 +++ .../lens/public/shared_components/index.ts | 7 + 12 files changed, 201 insertions(+), 113 deletions(-) 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 385f92b0dc05d..551e3f85a64fb 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/field_select.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx index 6bbe335d66307..afaa24d3d34b1 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: fieldIsInvalid + ? 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/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index e3d10c61c5caa..d7cde6ec7f755 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/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 0000000000000..d8e4e9b8f7207 --- /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 0000000000000..1a298c27c6b4e --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.tsx @@ -0,0 +1,120 @@ +/* + * 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 }) => ({ + ...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 0000000000000..d9e18182dd0d8 --- /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 be30712a9de01..fabb8cab6fd0e 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 0000000000000..fdbc532f7b13e --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/field_picker/types.ts @@ -0,0 +1,22 @@ +/* + * 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; +} + +export interface FieldOption extends EuiComboBoxOptionOption { + label: string; + value: T; + exists: boolean; + compatible: number | boolean; + 'data-test-subj'?: string; +} diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index 7da63a6947211..a8bc3499fc1a1 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -9,6 +9,13 @@ 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, + FieldOption, + FieldOptionValue, +} from './field_picker'; export { RangeInputField } from './range_input_field'; export { BucketAxisBoundsControl, From 8fee29b12b4d449132f4d2019d849163d6142a49 Mon Sep 17 00:00:00 2001 From: dej611 Date: Fri, 24 Jun 2022 15:15:34 +0200 Subject: [PATCH 2/4] :rotating_light: Fix linting + types issues --- .../__snapshots__/lens_field_icon.test.tsx.snap | 0 x-pack/plugins/lens/public/shared_components/index.ts | 9 ++------- 2 files changed, 2 insertions(+), 7 deletions(-) rename x-pack/plugins/lens/public/{indexpattern_datasource => shared_components/field_picker}/__snapshots__/lens_field_icon.test.tsx.snap (100%) 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/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index a8bc3499fc1a1..e3a9af00ad005 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -9,13 +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, - FieldOption, - FieldOptionValue, -} from './field_picker'; +export { FieldPicker, LensFieldIcon, TruncatedLabel } from './field_picker'; +export type { FieldOption, FieldOptionValue } from './field_picker'; export { RangeInputField } from './range_input_field'; export { BucketAxisBoundsControl, From 096338f50220ff0bcd8357f44d39ddf0b9cd6d11 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 6 Jul 2022 10:40:21 +0200 Subject: [PATCH 3/4] :bug: Fix issue with classes --- .../field_picker/field_picker.tsx | 34 ++++++++++++++----- .../shared_components/field_picker/types.ts | 6 +++- 2 files changed, 30 insertions(+), 10 deletions(-) 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 index 1a298c27c6b4e..bde920446d468 100644 --- 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 @@ -39,15 +39,31 @@ export function FieldPicker({ ['data-test-subj']: dataTestSub, ...rest }: FieldPickerProps) { - const styledOptions = options?.map(({ compatible, exists, ...otherAttr }) => ({ - ...otherAttr, - compatible, - exists, - className: classNames({ - 'lnFieldPicker__option--incompatible': !compatible, - 'lnFieldPicker__option--nonExistant': !exists, - }), - })); + 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; 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 index fdbc532f7b13e..4d607b47ef914 100644 --- a/x-pack/plugins/lens/public/shared_components/field_picker/types.ts +++ b/x-pack/plugins/lens/public/shared_components/field_picker/types.ts @@ -13,10 +13,14 @@ export interface FieldOptionValue { dataType?: DataType; } -export interface FieldOption extends EuiComboBoxOptionOption { +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; From bb72cc76a3013e8378094f0c4b27041cc2b55d1b Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 6 Jul 2022 17:53:13 +0200 Subject: [PATCH 4/4] :label: Fix type issue --- .../dimension_panel/dimension_editor.tsx | 4 ++-- .../dimension_panel/reference_editor.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 a770497d52bb2..4e667e2aa30d6 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/reference_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx index 53e87ab6620a2..57782fa157ca7 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;