From 87f04f3b1bb025044cd538eaca89d271fd0a9392 Mon Sep 17 00:00:00 2001 From: Caroline Horn <549577+cchaos@users.noreply.github.com> Date: Mon, 14 Sep 2020 18:23:13 -0400 Subject: [PATCH] [Lens] Move configuration popover to flyout (#76046) * Moving to a Flyout implementation * Fix up inner layout of flyout * Fix up form rows --- .../editor_frame/_frame_layout.scss | 15 +- .../editor_frame/config_panel/_index.scss | 2 - .../{_config_panel.scss => config_panel.scss} | 0 .../config_panel/config_panel.tsx | 1 + .../config_panel/dimension_container.scss | 29 ++ .../config_panel/dimension_container.tsx | 134 +++++++++ .../config_panel/dimension_popover.scss | 18 -- .../config_panel/dimension_popover.tsx | 50 ---- .../{_layer_panel.scss => layer_panel.scss} | 3 +- .../config_panel/layer_panel.test.tsx | 26 +- .../editor_frame/config_panel/layer_panel.tsx | 138 ++++----- .../editor_frame/config_panel/types.ts | 3 +- .../editor_frame/index.scss | 1 - .../indexpattern_datasource/_field_item.scss | 6 +- .../dimension_panel/bucket_nesting_editor.tsx | 1 + .../dimension_panel/dimension_editor.scss | 21 ++ ...opover_editor.tsx => dimension_editor.tsx} | 281 +++++++++--------- .../dimension_panel/dimension_panel.test.tsx | 14 +- .../dimension_panel/dimension_panel.tsx | 8 +- .../dimension_panel/field_select.tsx | 14 +- .../dimension_panel/format_selector.tsx | 138 ++++----- .../dimension_panel/popover_editor.scss | 20 -- .../indexpattern_datasource/field_item.tsx | 4 +- .../operations/definitions/date_histogram.tsx | 9 +- .../operations/definitions/terms.test.tsx | 2 +- .../operations/definitions/terms.tsx | 3 + x-pack/plugins/lens/public/types.ts | 2 +- x-pack/plugins/lens/readme.md | 2 +- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../test/functional/page_objects/lens_page.ts | 3 +- 31 files changed, 525 insertions(+), 429 deletions(-) delete mode 100644 x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_index.scss rename x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/{_config_panel.scss => config_panel.scss} (100%) create mode 100644 x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss create mode 100644 x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx delete mode 100644 x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.scss delete mode 100644 x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx rename x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/{_layer_panel.scss => layer_panel.scss} (96%) create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.scss rename x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/{popover_editor.tsx => dimension_editor.tsx} (64%) delete mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/popover_editor.scss diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_frame_layout.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_frame_layout.scss index c2e8d4f6c0049..9367e59b11717 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_frame_layout.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_frame_layout.scss @@ -11,7 +11,7 @@ .lnsFrameLayout__pageContent { display: flex; - overflow: auto; + overflow: hidden; flex-grow: 1; } @@ -39,11 +39,16 @@ } .lnsFrameLayout__sidebar--right { - @include euiScrollBar; + flex-basis: 25%; background-color: lightOrDarkTheme($euiColorLightestShade, $euiColorInk); min-width: $lnsPanelMinWidth + $euiSizeXL; - overflow-x: hidden; - overflow-y: scroll; - padding: $euiSize 0 $euiSize $euiSize; + max-width: $euiFormMaxWidth + $euiSizeXXL; max-height: 100%; + + .lnsConfigPanel { + @include euiScrollBar; + padding: $euiSize 0 $euiSize $euiSize; + overflow-x: hidden; + overflow-y: scroll; + } } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_index.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_index.scss deleted file mode 100644 index 954fbfadf159b..0000000000000 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'config_panel'; -@import 'layer_panel'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_config_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.scss similarity index 100% rename from x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_config_panel.scss rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.scss diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index 446f5b44c2e50..ad16038f44911 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import './config_panel.scss'; import React, { useMemo, memo } from 'react'; import { EuiFlexItem, EuiToolTip, EuiButton, EuiForm } from '@elastic/eui'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss new file mode 100644 index 0000000000000..f200e25453a2a --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss @@ -0,0 +1,29 @@ +@import '@elastic/eui/src/components/flyout/variables'; +@import '@elastic/eui/src/components/flyout/mixins'; + +.lnsDimensionContainer { + // Use the EuiFlyout style + @include euiFlyout; + // But with custom positioning to keep it within the sidebar contents + position: absolute; + right: 0; + left: 0; + top: 0; + bottom: 0; + animation: euiFlyout $euiAnimSpeedNormal $euiAnimSlightResistance; +} + +.lnsDimensionContainer--noAnimation { + animation: none; +} + +.lnsDimensionContainer__footer, +.lnsDimensionContainer__header { + padding: $euiSizeS; +} + +.lnsDimensionContainer__trigger { + width: 100%; + display: block; + word-break: break-word; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx new file mode 100644 index 0000000000000..d6b395ac74cce --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx @@ -0,0 +1,134 @@ +/* + * 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 './dimension_container.scss'; + +import React, { useState, useEffect } from 'react'; +import { + EuiFlyoutHeader, + EuiFlyoutFooter, + EuiTitle, + EuiButtonEmpty, + EuiFlexItem, + EuiFocusTrap, +} from '@elastic/eui'; + +import classNames from 'classnames'; +import { i18n } from '@kbn/i18n'; +import { VisualizationDimensionGroupConfig } from '../../../types'; +import { DimensionContainerState } from './types'; + +export function DimensionContainer({ + dimensionContainerState, + setDimensionContainerState, + groups, + accessor, + groupId, + trigger, + panel, + panelTitle, +}: { + dimensionContainerState: DimensionContainerState; + setDimensionContainerState: (newState: DimensionContainerState) => void; + groups: VisualizationDimensionGroupConfig[]; + accessor: string; + groupId: string; + trigger: React.ReactElement; + panel: React.ReactElement; + panelTitle: React.ReactNode; +}) { + const [openByCreation, setIsOpenByCreation] = useState( + dimensionContainerState.openId === accessor + ); + const [focusTrapIsEnabled, setFocusTrapIsEnabled] = useState(false); + const [flyoutIsVisible, setFlyoutIsVisible] = useState(false); + + const noMatch = dimensionContainerState.isOpen + ? !groups.some((d) => d.accessors.includes(accessor)) + : false; + + const closeFlyout = () => { + setDimensionContainerState({ + isOpen: false, + openId: null, + addingToGroupId: null, + }); + setIsOpenByCreation(false); + setFocusTrapIsEnabled(false); + setFlyoutIsVisible(false); + }; + + const openFlyout = () => { + setFlyoutIsVisible(true); + setTimeout(() => { + setFocusTrapIsEnabled(true); + }, 255); + }; + + const flyoutShouldBeOpen = + dimensionContainerState.isOpen && + (dimensionContainerState.openId === accessor || + (noMatch && dimensionContainerState.addingToGroupId === groupId)); + + useEffect(() => { + if (flyoutShouldBeOpen) { + openFlyout(); + } + }); + + useEffect(() => { + if (!flyoutShouldBeOpen) { + if (flyoutIsVisible) { + setFlyoutIsVisible(false); + } + if (focusTrapIsEnabled) { + setFocusTrapIsEnabled(false); + } + } + }, [flyoutShouldBeOpen, flyoutIsVisible, focusTrapIsEnabled]); + + const flyout = flyoutIsVisible && ( + +
+ + + + {panelTitle} + + + + + {panel} + + + + {i18n.translate('xpack.lens.dimensionContainer.close', { + defaultMessage: 'Close', + })} + + +
+
+ ); + + return ( + <> +
{trigger}
+ {flyout} + + ); +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.scss deleted file mode 100644 index 98036c7f31bd9..0000000000000 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.scss +++ /dev/null @@ -1,18 +0,0 @@ -.lnsDimensionPopover { - line-height: 0; - flex-grow: 1; - max-width: calc(100% - #{$euiSizeL}); -} - -.lnsDimensionPopover__trigger { - max-width: 100%; - display: block; - word-break: break-word; -} - -// todo: remove after closing https://github.com/elastic/eui/issues/3548 -.lnsDimensionPopover__fixTranslateDnd { - // sass-lint:disable-block no-important - transform: none !important; -} - diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx deleted file mode 100644 index a90bd8122d18e..0000000000000 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx +++ /dev/null @@ -1,50 +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 './dimension_popover.scss'; - -import React from 'react'; -import { EuiPopover } from '@elastic/eui'; -import { VisualizationDimensionGroupConfig } from '../../../types'; -import { DimensionPopoverState } from './types'; - -export function DimensionPopover({ - popoverState, - setPopoverState, - groups, - accessor, - groupId, - trigger, - panel, -}: { - popoverState: DimensionPopoverState; - setPopoverState: (newState: DimensionPopoverState) => void; - groups: VisualizationDimensionGroupConfig[]; - accessor: string; - groupId: string; - trigger: React.ReactElement; - panel: React.ReactElement; -}) { - const noMatch = popoverState.isOpen ? !groups.some((d) => d.accessors.includes(accessor)) : false; - return ( - { - setPopoverState({ isOpen: false, openId: null, addingToGroupId: null, tabId: null }); - }} - button={trigger} - anchorPosition="leftUp" - panelPaddingSize="none" - > - {panel} - - ); -} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss similarity index 96% rename from x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss index 0329c856f97f2..b85c3e843613d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss @@ -60,6 +60,5 @@ } .lnsLayerPanel__styleEditor { - width: $euiSize * 30; - padding: $euiSizeS; + padding: 0 $euiSizeS $euiSizeS; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index 85dbee6de524f..a9e2d6dc696ab 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -13,7 +13,7 @@ import { DatasourceMock, } from '../../mocks'; import { ChildDragDropProvider } from '../../../drag_drop'; -import { EuiFormRow, EuiPopover } from '@elastic/eui'; +import { EuiFormRow } from '@elastic/eui'; import { mount } from 'enzyme'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { Visualization } from '../../../types'; @@ -203,7 +203,7 @@ describe('LayerPanel', () => { expect(group).toHaveLength(1); }); - it('should render the datasource and visualization panels inside the dimension popover', () => { + it('should render the datasource and visualization panels inside the dimension container', () => { mockVisualization.getConfiguration.mockReturnValueOnce({ groups: [ { @@ -221,16 +221,16 @@ describe('LayerPanel', () => { const component = mountWithIntl(); - const group = component.find('DimensionPopover'); + const group = component.find('DimensionContainer'); const panel = mount(group.prop('panel')); - expect(panel.find('EuiTabbedContent').prop('tabs')).toHaveLength(2); + expect(panel.children()).toHaveLength(2); }); - it('should keep the popover open when configuring a new dimension', () => { + it('should keep the DimensionContainer open when configuring a new dimension', () => { /** * The ID generation system for new dimensions has been messy before, so - * this tests that the ID used in the first render is used to keep the popover + * this tests that the ID used in the first render is used to keep the container * open in future renders */ (generateId as jest.Mock).mockReturnValueOnce(`newid`); @@ -264,20 +264,20 @@ describe('LayerPanel', () => { const component = mountWithIntl(); - const group = component.find('DimensionPopover'); + const group = component.find('DimensionContainer'); const triggerButton = mountWithIntl(group.prop('trigger')); act(() => { triggerButton.find('[data-test-subj="lns-empty-dimension"]').first().simulate('click'); }); component.update(); - expect(component.find(EuiPopover).prop('isOpen')).toBe(true); + expect(component.find('EuiFlyoutHeader').exists()).toBe(true); }); - it('should close the popover when the active visualization changes', () => { + it('should close the DimensionContainer when the active visualization changes', () => { /** * The ID generation system for new dimensions has been messy before, so - * this tests that the ID used in the first render is used to keep the popover + * this tests that the ID used in the first render is used to keep the container * open in future renders */ @@ -312,18 +312,18 @@ describe('LayerPanel', () => { const component = mountWithIntl(); - const group = component.find('DimensionPopover'); + const group = component.find('DimensionContainer'); const triggerButton = mountWithIntl(group.prop('trigger')); act(() => { triggerButton.find('[data-test-subj="lns-empty-dimension"]').first().simulate('click'); }); component.update(); - expect(component.find(EuiPopover).prop('isOpen')).toBe(true); + expect(component.find('EuiFlyoutHeader').exists()).toBe(true); act(() => { component.setProps({ activeVisualizationId: 'vis2' }); }); component.update(); - expect(component.find(EuiPopover).prop('isOpen')).toBe(false); + expect(component.find('EuiFlyoutHeader').exists()).toBe(false); }); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index e9de2f935f0f3..46cd0292f2459 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import './layer_panel.scss'; import React, { useContext, useState, useEffect } from 'react'; import { @@ -13,7 +14,6 @@ import { EuiFlexItem, EuiButtonEmpty, EuiFormRow, - EuiTabbedContent, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -24,14 +24,13 @@ import { DragContext, DragDrop, ChildDragDropProvider } from '../../../drag_drop import { LayerSettings } from './layer_settings'; import { trackUiEvent } from '../../../lens_ui_telemetry'; import { generateId } from '../../../id_generator'; -import { ConfigPanelWrapperProps, DimensionPopoverState } from './types'; -import { DimensionPopover } from './dimension_popover'; +import { ConfigPanelWrapperProps, DimensionContainerState } from './types'; +import { DimensionContainer } from './dimension_container'; -const initialPopoverState = { +const initialDimensionContainerState = { isOpen: false, openId: null, addingToGroupId: null, - tabId: null, }; export function LayerPanel( @@ -50,13 +49,15 @@ export function LayerPanel( } ) { const dragDropContext = useContext(DragContext); - const [popoverState, setPopoverState] = useState(initialPopoverState); + const [dimensionContainerState, setDimensionContainerState] = useState( + initialDimensionContainerState + ); const { framePublicAPI, layerId, isOnlyLayer, onRemoveLayer, dataTestSubj } = props; const datasourcePublicAPI = framePublicAPI.datasourceLayers[layerId]; useEffect(() => { - setPopoverState(initialPopoverState); + setDimensionContainerState(initialDimensionContainerState); }, [props.activeVisualizationId]); if ( @@ -159,7 +160,9 @@ export function LayerPanel( return ( <> {group.accessors.map((accessor) => { - const tabs = [ - { - id: 'datasource', - name: i18n.translate('xpack.lens.editorFrame.quickFunctionsLabel', { - defaultMessage: 'Quick functions', - }), - content: ( + const datasourceDimensionEditor = ( + + ); + const visDimensionEditor = + activeVisualization.renderDimensionEditor && group.enableDimensionEditor ? ( +
- ), - }, - ]; - - if (activeVisualization.renderDimensionEditor && group.enableDimensionEditor) { - tabs.push({ - id: 'visualization', - name: i18n.translate('xpack.lens.editorFrame.formatStyleLabel', { - defaultMessage: 'Format & style', - }), - content: ( -
- -
- ), - }); - } - +
+ ) : null; return ( - { - if (popoverState.isOpen) { - setPopoverState(initialPopoverState); + onClick: () => { + if (dimensionContainerState.isOpen) { + setDimensionContainerState(initialDimensionContainerState); } else { - setPopoverState({ + setDimensionContainerState({ isOpen: true, openId: accessor, addingToGroupId: null, // not set for existing dimension - tabId: 'datasource', }); } }, @@ -298,22 +283,21 @@ export function LayerPanel( /> } panel={ - t.id === popoverState.tabId) || tabs[0]} - size="s" - onTabClick={(tab) => { - setPopoverState({ - ...popoverState, - tabId: tab.id as typeof popoverState['tabId'], - }); - }} - /> + <> + {datasourceDimensionEditor} + {visDimensionEditor} + } + panelTitle={i18n.translate('xpack.lens.configure.configurePanelTitle', { + defaultMessage: '{groupLabel} configuration', + values: { + groupLabel: group.groupLabel, + }, + })} /> - { - if (popoverState.isOpen) { - setPopoverState(initialPopoverState); + if (dimensionContainerState.isOpen) { + setDimensionContainerState(initialDimensionContainerState); } else { - setPopoverState({ + setDimensionContainerState({ isOpen: true, openId: newId, addingToGroupId: group.groupId, - tabId: 'datasource', }); } }} @@ -428,6 +411,12 @@ export function LayerPanel( } + panelTitle={i18n.translate('xpack.lens.configure.configurePanelTitle', { + defaultMessage: '{groupLabel} configuration', + values: { + groupLabel: group.groupLabel, + }, + })} panel={ <> button { + padding-top: 0; + padding-bottom: 0; +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/popover_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx similarity index 64% rename from x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/popover_editor.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index d5f0110f071f1..98e9389a85819 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/popover_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -4,21 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import './popover_editor.scss'; +import './dimension_editor.scss'; import _ from 'lodash'; import React, { useState, useMemo, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { - EuiFlexItem, - EuiFlexGroup, EuiListGroup, - EuiCallOut, EuiFormRow, EuiFieldText, EuiSpacer, EuiListGroupItemProps, } from '@elastic/eui'; -import classNames from 'classnames'; +import { EuiFormLabel } from '@elastic/eui'; import { IndexPatternColumn, OperationType } from '../indexpattern'; import { IndexPatternDimensionEditorProps, OperationFieldSupportMatrix } from './dimension_panel'; import { @@ -37,7 +34,7 @@ import { FormatSelector } from './format_selector'; const operationPanels = getOperationDisplay(); -export interface PopoverEditorProps extends IndexPatternDimensionEditorProps { +export interface DimensionEditorProps extends IndexPatternDimensionEditorProps { selectedColumn?: IndexPatternColumn; operationFieldSupportMatrix: OperationFieldSupportMatrix; currentIndexPattern: IndexPattern; @@ -72,16 +69,25 @@ const LabelInput = ({ value, onChange }: { value: string; onChange: (value: stri }; return ( - + + + ); }; -export function PopoverEditor(props: PopoverEditorProps) { +export function DimensionEditor(props: DimensionEditorProps) { const { selectedColumn, operationFieldSupportMatrix, @@ -128,8 +134,8 @@ export function PopoverEditor(props: PopoverEditorProps) { ); } - function getSideNavItems(): EuiListGroupItemProps[] { - return getOperationTypes().map(({ operationType, compatibleWithCurrentField }) => { + const sideNavItems: EuiListGroupItemProps[] = getOperationTypes().map( + ({ operationType, compatibleWithCurrentField }) => { const isActive = Boolean( incompatibleSelectedOperationType === operationType || (!incompatibleSelectedOperationType && @@ -220,13 +226,42 @@ export function PopoverEditor(props: PopoverEditorProps) { ); }, }; - }); - } + } + ); return ( -
- - +
+
+ + {i18n.translate('xpack.lens.indexPattern.functionsLabel', { + defaultMessage: 'Choose a function', + })} + + + 3 ? 'lnsIndexPatternDimensionEditor__columns' : ''} + gutterSize="none" + listItems={sideNavItems} + maxWidth={false} + /> +
+ +
+ - - - - - - - - {incompatibleSelectedOperationType && selectedColumn && ( - <> - - - - )} - {incompatibleSelectedOperationType && !selectedColumn && ( - <> - - - - )} - {!incompatibleSelectedOperationType && ParamEditor && ( - <> - - - - )} - {!incompatibleSelectedOperationType && selectedColumn && ( - - { - setState({ - ...state, - layers: { - ...state.layers, - [layerId]: { - ...state.layers[layerId], - columns: { - ...state.layers[layerId].columns, - [columnId]: { - ...selectedColumn, - label: value, - customLabel: true, - }, - }, - }, - }, - }); - }} - /> - - )} + - {!hideGrouping && ( - { - setState({ - ...state, - layers: { - ...state.layers, - [props.layerId]: { - ...state.layers[props.layerId], - columnOrder, - }, + {!incompatibleSelectedOperationType && ParamEditor && ( + <> + + + + )} +
+ + + +
+ {!incompatibleSelectedOperationType && selectedColumn && ( + { + setState({ + ...state, + layers: { + ...state.layers, + [layerId]: { + ...state.layers[layerId], + columns: { + ...state.layers[layerId].columns, + [columnId]: { + ...selectedColumn, + label: value, + customLabel: true, }, - }); - }} - /> - )} + }, + }, + }, + }); + }} + /> + )} - {selectedColumn && selectedColumn.dataType === 'number' ? ( - { - setState( - updateColumnParam({ - state, - layerId, - currentColumn: selectedColumn, - paramName: 'format', - value: newFormat, - }) - ); - }} - /> - ) : null} - - - - + {!hideGrouping && ( + { + setState({ + ...state, + layers: { + ...state.layers, + [props.layerId]: { + ...state.layers[props.layerId], + columnOrder, + }, + }, + }); + }} + /> + )} + + {selectedColumn && selectedColumn.dataType === 'number' ? ( + { + setState( + updateColumnParam({ + state, + layerId, + currentColumn: selectedColumn, + paramName: 'format', + value: newFormat, + }) + ); + }} + /> + ) : null} +
); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index f184d5628ab1c..dd7611ca7f41f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -7,7 +7,7 @@ import { ReactWrapper, ShallowWrapper } from 'enzyme'; import React from 'react'; import { act } from 'react-dom/test-utils'; -import { EuiComboBox, EuiListGroupItemProps, EuiListGroup, EuiFieldNumber } from '@elastic/eui'; +import { EuiComboBox, EuiListGroupItemProps, EuiListGroup, EuiRange } from '@elastic/eui'; import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { changeColumn } from '../state_helpers'; import { @@ -619,9 +619,9 @@ describe('IndexPatternDimensionEditorPanel', () => { .find('button[data-test-subj="lns-indexPatternDimension-terms incompatible"]') .simulate('click'); - expect(wrapper.find('[data-test-subj="indexPattern-invalid-operation"]')).not.toHaveLength( - 0 - ); + expect( + wrapper.find('[data-test-subj="indexPattern-field-selection-row"]').first().prop('error') + ).toBeDefined(); expect(setState).not.toHaveBeenCalled(); }); @@ -1188,7 +1188,7 @@ describe('IndexPatternDimensionEditorPanel', () => { expect( wrapper - .find(EuiFieldNumber) + .find(EuiRange) .filter('[data-test-subj="indexPattern-dimension-formatDecimals"]') .prop('value') ).toEqual(0); @@ -1224,9 +1224,9 @@ describe('IndexPatternDimensionEditorPanel', () => { act(() => { wrapper - .find(EuiFieldNumber) + .find(EuiRange) .filter('[data-test-subj="indexPattern-dimension-formatDecimals"]') - .prop('onChange')!({ target: { value: '0' } }); + .prop('onChange')!({ currentTarget: { value: '0' } }); }); expect(setState).toHaveBeenCalledWith({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx index 1fbbefd8f1117..923f7145d1c64 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx @@ -20,7 +20,7 @@ import { import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { IndexPatternColumn, OperationType } from '../indexpattern'; import { getAvailableOperationsByMetadata, buildColumn, changeField } from '../operations'; -import { PopoverEditor } from './popover_editor'; +import { DimensionEditor } from './dimension_editor'; import { changeColumn } from '../state_helpers'; import { isDraggedField, hasField } from '../utils'; import { IndexPatternPrivateState, IndexPatternField } from '../types'; @@ -239,9 +239,7 @@ export const IndexPatternDimensionTriggerComponent = function IndexPatternDimens { - props.togglePopover(); - }} + onClick={props.onClick} data-test-subj="lns-dimensionTrigger" aria-label={i18n.translate('xpack.lens.configure.editConfig', { defaultMessage: 'Edit configuration', @@ -267,7 +265,7 @@ export const IndexPatternDimensionEditorComponent = function IndexPatternDimensi props.state.layers[layerId].columns[props.columnId] || null; return ( - { currentIndexPattern: IndexPattern; fieldMap: Record; incompatibleSelectedOperationType: OperationType | null; @@ -47,6 +53,7 @@ export function FieldSelect({ onChoose, onDeleteColumn, existingFields, + ...rest }: FieldSelectProps) { const { operationByField } = operationFieldSupportMatrix; const memoizedFieldOptions = useMemo(() => { @@ -155,7 +162,7 @@ export function FieldSelect({ defaultMessage: 'Field', })} options={(memoizedFieldOptions as unknown) as EuiComboBoxOptionOption[]} - isInvalid={Boolean(incompatibleSelectedOperationType && selectedColumnOperationType)} + isInvalid={Boolean(incompatibleSelectedOperationType)} selectedOptions={ ((selectedColumnOperationType ? selectedColumnSourceField @@ -194,6 +201,7 @@ export function FieldSelect({
); }} + {...rest} /> ); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/format_selector.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/format_selector.tsx index b3b0190b9c400..8d9a91397cf2f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/format_selector.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/format_selector.tsx @@ -6,7 +6,7 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFormRow, EuiFieldNumber, EuiComboBox } from '@elastic/eui'; +import { EuiFormRow, EuiComboBox, EuiSpacer, EuiRange } from '@elastic/eui'; import { IndexPatternColumn } from '../indexpattern'; const supportedFormats: Record = { @@ -57,80 +57,84 @@ export function FormatSelector(props: FormatSelectorProps) { }), }; + const label = i18n.translate('xpack.lens.indexPattern.columnFormatLabel', { + defaultMessage: 'Value format', + }); + + const decimalsLabel = i18n.translate('xpack.lens.indexPattern.decimalPlacesLabel', { + defaultMessage: 'Decimals', + }); + return ( <> - - ({ - value: id, - label: format.title ?? id, - })), - ]} - selectedOptions={ - currentFormat - ? [ - { - value: currentFormat.id, - label: selectedFormat?.title ?? currentFormat.id, - }, - ] - : [defaultOption] - } - onChange={(choices) => { - if (choices.length === 0) { - return; - } - - if (!choices[0].value) { - onChange(); - return; + +
+ ({ + value: id, + label: format.title ?? id, + })), + ]} + selectedOptions={ + currentFormat + ? [ + { + value: currentFormat.id, + label: selectedFormat?.title ?? currentFormat.id, + }, + ] + : [defaultOption] } - onChange({ - id: choices[0].value, - params: { decimals: state.decimalPlaces }, - }); - }} - /> - + onChange={(choices) => { + if (choices.length === 0) { + return; + } - {currentFormat ? ( - - { - setState({ decimalPlaces: Number(e.target.value) }); + if (!choices[0].value) { + onChange(); + return; + } onChange({ - id: (selectedColumn.params as { format: { id: string } }).format.id, - params: { - decimals: Number(e.target.value), - }, + id: choices[0].value, + params: { decimals: state.decimalPlaces }, }); }} - compressed - fullWidth /> - - ) : null} + {currentFormat ? ( + <> + + { + setState({ decimalPlaces: Number(e.currentTarget.value) }); + onChange({ + id: (selectedColumn.params as { format: { id: string } }).format.id, + params: { + decimals: Number(e.currentTarget.value), + }, + }); + }} + data-test-subj="indexPattern-dimension-formatDecimals" + compressed + fullWidth + prepend={decimalsLabel} + aria-label={decimalsLabel} + /> + + ) : null} +
+
); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/popover_editor.scss b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/popover_editor.scss deleted file mode 100644 index 3f75b5ad86669..0000000000000 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/popover_editor.scss +++ /dev/null @@ -1,20 +0,0 @@ -.lnsIndexPatternDimensionEditor { - width: $euiSize * 30; - padding: $euiSizeS; - max-width: 100%; -} - -.lnsIndexPatternDimensionEditor__left, -.lnsIndexPatternDimensionEditor__right { - padding: $euiSizeS; -} - -.lnsIndexPatternDimensionEditor__left { - background-color: $euiPageBackgroundColor; - width: $euiSize * 8; -} - -.lnsIndexPatternDimensionEditor__operation > button { - padding-top: 0; - padding-bottom: 0; -} 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 cf15c29844053..1f6d7911b3a33 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -231,7 +231,7 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { isOpen={infoIsOpen} closePopover={() => setOpen(false)} anchorPosition="rightUp" - panelClassName="lnsFieldItem__fieldPopoverPanel" + panelClassName="lnsFieldItem__fieldPanel" > @@ -316,7 +316,7 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { if (histogram && histogram.buckets.length && topValues && topValues.buckets.length) { title = ( {!intervalIsRestricted && ( - + )} @@ -197,6 +198,8 @@ export const dateHistogramOperation: OperationDefinition {intervalIsRestricted ? ( ) : ( <> - + { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx index 2972ed2d0231b..6f67cb7531e1c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx @@ -345,7 +345,7 @@ describe('terms', () => { }); }); - describe('popover param editor', () => { + describe('param editor', () => { it('should render current order by value and options', () => { const setStateSpy = jest.fn(); const instance = shallow( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx index c1b19fd5549e7..20c421008a746 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx @@ -177,6 +177,7 @@ export const termsOperation: OperationDefinition = { defaultMessage: 'Number of values', })} display="columnCompressed" + fullWidth > = { defaultMessage: 'Order by', })} display="columnCompressed" + fullWidth > = { defaultMessage: 'Order direction', })} display="columnCompressed" + fullWidth > = DatasourceDimensionPro export type DatasourceDimensionTriggerProps = DatasourceDimensionProps & { dragDropContext: DragContextState; - togglePopover: () => void; + onClick: () => void; }; export interface DatasourceLayerPanelProps { diff --git a/x-pack/plugins/lens/readme.md b/x-pack/plugins/lens/readme.md index e69ba9ec9506d..98bb60827af42 100644 --- a/x-pack/plugins/lens/readme.md +++ b/x-pack/plugins/lens/readme.md @@ -30,7 +30,7 @@ Lens has a lot of UI elements – to make it easier to refer to them in issues o * **Config panel** Panel to the right showing configuration of the current chart, separated by layers * **Layer panel** One of multiple panels in the config panel, holding configuration for separate layers * **Dimension trigger** Chart dimension like "X axis", "Break down by" or "Slice by" in the config panel - * **Dimension popover** Popover shown when clicking a dimension trigger + * **Dimension container** Container shown when clicking a dimension trigger and contains the dimension settints * **Layer settings popover** Popover shown when clicking the button in the top left of a layer panel * **Workspace panel** Center panel containing the chart preview, title and toolbar * **Chart preview** Full-sized rendered chart in the center of the screen diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1f39703b98303..d77ec100ddb0b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9568,10 +9568,8 @@ "xpack.lens.editorFrame.emptyWorkspaceHeading": "レンズはビジュアライゼーションを作成するための新しいツールです", "xpack.lens.editorFrame.expandRenderingErrorButton": "エラーの詳細を表示", "xpack.lens.editorFrame.expressionFailure": "表現を正常に実行できませんでした", - "xpack.lens.editorFrame.formatStyleLabel": "書式とスタイル", "xpack.lens.editorFrame.goToForums": "リクエストとフィードバック", "xpack.lens.editorFrame.previewErrorLabel": "レンダリングのプレビューに失敗しました", - "xpack.lens.editorFrame.quickFunctionsLabel": "クイック機能", "xpack.lens.editorFrame.requiredDimensionWarningLabel": "必要な次元", "xpack.lens.editorFrame.suggestionPanelTitle": "提案", "xpack.lens.embeddable.failure": "ビジュアライゼーションを表示できませんでした", @@ -9625,7 +9623,6 @@ "xpack.lens.indexPattern.existenceErrorLabel": "フィールド情報を読み込めません", "xpack.lens.indexPattern.fieldDistributionLabel": "分布", "xpack.lens.indexPattern.fieldItemTooltip": "可視化するには、ドラッグアンドドロップします。", - "xpack.lens.indexPattern.fieldlessOperationLabel": "この関数を使用するには、フィールドを選択してください。", "xpack.lens.indexPattern.fieldPanelEmptyStringValue": "空の文字列", "xpack.lens.indexPattern.fieldPlaceholder": "フィールド", "xpack.lens.indexPattern.fieldStatsButtonLabel": "フィールドプレビューを表示するには、クリックします。可視化するには、ドラッグアンドドロップします。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e299bd1624460..2ffc81cd05293 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9574,10 +9574,8 @@ "xpack.lens.editorFrame.emptyWorkspaceHeading": "Lens 是用于创建可视化的新工具", "xpack.lens.editorFrame.expandRenderingErrorButton": "显示错误的详情", "xpack.lens.editorFrame.expressionFailure": "无法成功执行表达式", - "xpack.lens.editorFrame.formatStyleLabel": "格式和样式", "xpack.lens.editorFrame.goToForums": "提出请求并提供反馈", "xpack.lens.editorFrame.previewErrorLabel": "预览呈现失败", - "xpack.lens.editorFrame.quickFunctionsLabel": "快选函数", "xpack.lens.editorFrame.requiredDimensionWarningLabel": "所需尺寸", "xpack.lens.editorFrame.suggestionPanelTitle": "建议", "xpack.lens.embeddable.failure": "无法显示可视化", @@ -9631,7 +9629,6 @@ "xpack.lens.indexPattern.existenceErrorLabel": "无法加载字段信息", "xpack.lens.indexPattern.fieldDistributionLabel": "分布", "xpack.lens.indexPattern.fieldItemTooltip": "拖放以可视化。", - "xpack.lens.indexPattern.fieldlessOperationLabel": "要使用此函数,请选择字段。", "xpack.lens.indexPattern.fieldPanelEmptyStringValue": "空字符串", "xpack.lens.indexPattern.fieldPlaceholder": "字段", "xpack.lens.indexPattern.fieldStatsButtonLabel": "单击以进行字段预览,或拖放以进行可视化。", diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index acba3fa472b1a..edd7c2a43b343 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -105,13 +105,14 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont const target = await testSubjects.find('indexPattern-dimension-field'); await comboBox.openOptionsList(target); await comboBox.setElement(target, opts.field); + await testSubjects.click('lns-indexPattern-dimensionContainerTitle'); }, /** * Removes the dimension matching a specific test subject */ async removeDimension(dimensionTestSubj: string) { - await testSubjects.click(`${dimensionTestSubj} > indexPattern-dimensionPopover-remove`); + await testSubjects.click(`${dimensionTestSubj} > indexPattern-dimension-remove`); }, /**