diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx index fc69c914deb68..b7ff23cdb6e35 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx @@ -10,7 +10,7 @@ import { render } from 'react-dom'; import { Ast } from '@kbn/interpreter/common'; import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import type { +import { SuggestionRequest, Visualization, VisualizationSuggestion, @@ -37,6 +37,10 @@ export interface DatatableVisualizationState { sorting?: SortingState; } +const visualizationLabel = i18n.translate('xpack.lens.datatable.label', { + defaultMessage: 'Table', +}); + export const datatableVisualization: Visualization = { id: 'lnsDatatable', @@ -44,8 +48,9 @@ export const datatableVisualization: Visualization { id: 'lnsDatatable', icon: LensIconChartDatatable, - label: i18n.translate('xpack.lens.datatable.label', { - defaultMessage: 'Data table', + label: visualizationLabel, + groupLabel: i18n.translate('xpack.lens.datatable.groupLabel', { + defaultMessage: 'Tabular and single value', }), }, ], @@ -68,9 +73,7 @@ export const datatableVisualization: Visualization getDescription() { return { icon: LensIconChartDatatable, - label: i18n.translate('xpack.lens.datatable.label', { - defaultMessage: 'Data table', - }), + label: visualizationLabel, }; }, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx index 66f944e9f9998..3d499b7b7b45a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx @@ -72,6 +72,7 @@ describe('ConfigPanel', () => { icon: 'empty', id: 'testVis', label: 'TEST1', + groupLabel: 'testVisGroup', }, ], }; @@ -85,6 +86,7 @@ describe('ConfigPanel', () => { icon: 'empty', id: 'testVis2', label: 'TEST2', + groupLabel: 'testVis2Group', }, ], }; 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 52726afcffe8d..5c27958aa1786 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 @@ -82,6 +82,7 @@ describe('LayerPanel', () => { icon: 'empty', id: 'testVis', label: 'TEST1', + groupLabel: 'testVisGroup', }, ], }; @@ -94,6 +95,7 @@ describe('LayerPanel', () => { icon: 'empty', id: 'testVis2', label: 'TEST2', + groupLabel: 'testVis2Group', }, ], }; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index 108e4aa84418f..31c4d357685c2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -7,6 +7,19 @@ import React, { ReactElement } from 'react'; import { ReactWrapper } from 'enzyme'; + +// Tests are executed in a jsdom environment who does not have sizing methods, +// thus the AutoSizer will always compute a 0x0 size space +// Mock the AutoSizer inside EuiSelectable (Chart Switch) and return some dimensions > 0 +jest.mock('react-virtualized-auto-sizer', () => { + return function (props: { + children: (dimensions: { width: number; height: number }) => React.ReactNode; + }) { + const { children, ...otherProps } = props; + return
{children({ width: 100, height: 100 })}
; + }; +}); + import { EuiPanel, EuiToolTip } from '@elastic/eui'; import { mountWithIntl as mount } from '@kbn/test/jest'; import { EditorFrame } from './editor_frame'; @@ -83,6 +96,7 @@ describe('editor_frame', () => { icon: 'empty', id: 'testVis', label: 'TEST1', + groupLabel: 'testVisGroup', }, ], }; @@ -94,6 +108,7 @@ describe('editor_frame', () => { icon: 'empty', id: 'testVis2', label: 'TEST2', + groupLabel: 'testVis2Group', }, ], }; @@ -1372,6 +1387,7 @@ describe('editor_frame', () => { icon: 'empty', id: 'testVis3', label: 'TEST3', + groupLabel: 'testVis3Group', }, ], getSuggestions: () => [ diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.scss index 0a4f7b0debf22..9f4b60b6d3c67 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.scss @@ -18,5 +18,5 @@ img.lnsChartSwitch__chartIcon { // stylelint-disable-line selector-no-qualifying } .lnsChartSwitch__search { - width: 4 * $euiSizeXXL; + width: 7 * $euiSizeXXL; } \ No newline at end of file diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx index d2c140d385f87..46e287297828d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx @@ -12,7 +12,19 @@ import { createMockFramePublicAPI, createMockDatasource, } from '../../mocks'; -import { EuiKeyPadMenuItem } from '@elastic/eui'; + +// Tests are executed in a jsdom environment who does not have sizing methods, +// thus the AutoSizer will always compute a 0x0 size space +// Mock the AutoSizer inside EuiSelectable (Chart Switch) and return some dimensions > 0 +jest.mock('react-virtualized-auto-sizer', () => { + return function (props: { + children: (dimensions: { width: number; height: number }) => React.ReactNode; + }) { + const { children } = props; + return
{children({ width: 100, height: 100 })}
; + }; +}); + import { mountWithIntl as mount } from '@kbn/test/jest'; import { Visualization, FramePublicAPI, DatasourcePublicAPI } from '../../../types'; import { Action } from '../state_management'; @@ -30,6 +42,7 @@ describe('chart_switch', () => { icon: 'empty', id, label: `Label ${id}`, + groupLabel: `${id}Group`, }, ], initialize: jest.fn((_frame, state?: unknown) => { @@ -70,16 +83,19 @@ describe('chart_switch', () => { icon: 'empty', id: 'subvisC1', label: 'C1', + groupLabel: 'visCGroup', }, { icon: 'empty', id: 'subvisC2', label: 'C2', + groupLabel: 'visCGroup', }, { icon: 'empty', id: 'subvisC3', label: 'C3', + groupLabel: 'visCGroup', }, ], getVisualizationTypeId: jest.fn((state) => state.type), @@ -166,10 +182,7 @@ describe('chart_switch', () => { function getMenuItem(subType: string, component: ReactWrapper) { showFlyout(component); - return component - .find(EuiKeyPadMenuItem) - .find(`[data-test-subj="lnsChartSwitchPopover_${subType}"]`) - .first(); + return component.find(`[data-test-subj="lnsChartSwitchPopover_${subType}"]`).first(); } it('should use suggested state if there is a suggestion from the target visualization', () => { @@ -281,7 +294,12 @@ describe('chart_switch', () => { /> ); - expect(getMenuItem('visB', component).prop('betaBadgeIconType')).toEqual('alert'); + expect( + getMenuItem('visB', component) + .find('[data-test-subj="lnsChartSwitchPopoverAlert_visB"]') + .first() + .props().type + ).toEqual('alert'); }); it('should indicate data loss if not all layers will be used', () => { @@ -301,7 +319,12 @@ describe('chart_switch', () => { /> ); - expect(getMenuItem('visB', component).prop('betaBadgeIconType')).toEqual('alert'); + expect( + getMenuItem('visB', component) + .find('[data-test-subj="lnsChartSwitchPopoverAlert_visB"]') + .first() + .props().type + ).toEqual('alert'); }); it('should support multi-layer suggestions without data loss', () => { @@ -344,7 +367,9 @@ describe('chart_switch', () => { /> ); - expect(getMenuItem('visB', component).prop('betaBadgeIconType')).toBeUndefined(); + expect( + getMenuItem('visB', component).find('[data-test-subj="lnsChartSwitchPopoverAlert_visB"]') + ).toHaveLength(0); }); it('should indicate data loss if no data will be used', () => { @@ -365,7 +390,12 @@ describe('chart_switch', () => { /> ); - expect(getMenuItem('visB', component).prop('betaBadgeIconType')).toEqual('alert'); + expect( + getMenuItem('visB', component) + .find('[data-test-subj="lnsChartSwitchPopoverAlert_visB"]') + .first() + .props().type + ).toEqual('alert'); }); it('should not indicate data loss if there is no data', () => { @@ -387,7 +417,9 @@ describe('chart_switch', () => { /> ); - expect(getMenuItem('visB', component).prop('betaBadgeIconType')).toBeUndefined(); + expect( + getMenuItem('visB', component).find('[data-test-subj="lnsChartSwitchPopoverAlert_visB"]') + ).toHaveLength(0); }); it('should not show a warning when the subvisualization is the same', () => { @@ -411,7 +443,11 @@ describe('chart_switch', () => { /> ); - expect(getMenuItem('subvisC2', component).prop('betaBadgeIconType')).not.toBeDefined(); + expect( + getMenuItem('subvisC2', component).find( + '[data-test-subj="lnsChartSwitchPopoverAlert_subvisC2"]' + ) + ).toHaveLength(0); }); it('should get suggestions when switching subvisualization', () => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx index 218ceb8206080..ef8c0798bb91e 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx @@ -11,17 +11,15 @@ import { EuiIcon, EuiPopover, EuiPopoverTitle, - EuiKeyPadMenu, - EuiKeyPadMenuItem, - EuiFieldSearch, EuiFlexGroup, EuiFlexItem, - EuiSelectableMessage, + EuiSelectable, + EuiIconTip, + EuiSelectableOption, } from '@elastic/eui'; -import { flatten } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Visualization, FramePublicAPI, Datasource } from '../../../types'; +import { Visualization, FramePublicAPI, Datasource, VisualizationType } from '../../../types'; import { Action } from '../state_management'; import { getSuggestions, switchToSuggestion, Suggestion } from '../suggestion_helpers'; import { trackUiEvent } from '../../../lens_ui_telemetry'; @@ -54,6 +52,8 @@ interface Props { >; } +type SelectableEntry = EuiSelectableOption<{ value: string }>; + function VisualizationSummary(props: Props) { const visualization = props.visualizationMap[props.visualizationId || '']; @@ -79,6 +79,23 @@ function VisualizationSummary(props: Props) { ); } +const MAX_LIST_HEIGHT = 380; +const ENTRY_HEIGHT = 32; + +function computeListHeight(list: SelectableEntry[], maxHeight: number): number { + if (list.length === 0) { + return 0; + } + return Math.min(list.length * ENTRY_HEIGHT, maxHeight); +} + +function getCurrentVisualizationId( + activeVisualization: Visualization, + visualizationState: unknown +) { + return activeVisualization.getVisualizationTypeId(visualizationState); +} + export const ChartSwitch = memo(function ChartSwitch(props: Props) { const [flyoutOpen, setFlyoutOpen] = useState(false); @@ -189,28 +206,112 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { const [searchTerm, setSearchTerm] = useState(''); - const visualizationTypes = useMemo( - () => - flyoutOpen && - flatten( - Object.values(props.visualizationMap).map((v) => - v.visualizationTypes.map((t) => ({ - visualizationId: v.id, - ...t, - icon: t.icon, - })) - ) - ) - .filter( - (visualizationType) => - visualizationType.label.toLowerCase().includes(searchTerm.toLowerCase()) || - (visualizationType.fullLabel && - visualizationType.fullLabel.toLowerCase().includes(searchTerm.toLowerCase())) - ) - .map((visualizationType) => ({ - ...visualizationType, - selection: getSelection(visualizationType.visualizationId, visualizationType.id), - })), + const { visualizationTypes, visualizationsLookup } = useMemo( + () => { + if (!flyoutOpen) { + return { visualizationTypes: [], visualizationsLookup: {} }; + } + const subVisualizationId = getCurrentVisualizationId( + props.visualizationMap[props.visualizationId || ''], + props.visualizationState + ); + const lowercasedSearchTerm = searchTerm.toLowerCase(); + // reorganize visualizations in groups + const grouped: Record< + string, + Array< + VisualizationType & { + visualizationId: string; + selection: VisualizationSelection; + } + > + > = {}; + // Will need it later on to quickly pick up the metadata from it + const lookup: Record< + string, + VisualizationType & { + visualizationId: string; + selection: VisualizationSelection; + } + > = {}; + Object.entries(props.visualizationMap).forEach(([visualizationId, v]) => { + for (const visualizationType of v.visualizationTypes) { + const isSearchMatch = + visualizationType.label.toLowerCase().includes(lowercasedSearchTerm) || + visualizationType.fullLabel?.toLowerCase().includes(lowercasedSearchTerm); + if (isSearchMatch) { + grouped[visualizationType.groupLabel] = grouped[visualizationType.groupLabel] || []; + const visualizationEntry = { + ...visualizationType, + visualizationId, + selection: getSelection(visualizationId, visualizationType.id), + }; + grouped[visualizationType.groupLabel].push(visualizationEntry); + lookup[`${visualizationId}:${visualizationType.id}`] = visualizationEntry; + } + } + }); + + return { + visualizationTypes: Object.keys(grouped) + .sort() + .flatMap((group): SelectableEntry[] => { + const visualizations = grouped[group]; + if (visualizations.length === 0) { + return []; + } + return [ + { + key: group, + label: group, + isGroupLabel: true, + 'aria-label': group, + 'data-test-subj': `lnsChartSwitchPopover_${group}`, + } as SelectableEntry, + ].concat( + visualizations + // alphabetical order within each group + .sort((a, b) => { + return (a.fullLabel || a.label).localeCompare(b.fullLabel || b.label); + }) + .map( + (v): SelectableEntry => ({ + 'aria-label': v.fullLabel || v.label, + isGroupLabel: false, + key: `${v.visualizationId}:${v.id}`, + value: `${v.visualizationId}:${v.id}`, + 'data-test-subj': `lnsChartSwitchPopover_${v.id}`, + label: v.fullLabel || v.label, + prepend: ( + + ), + append: + v.selection.dataLoss !== 'nothing' ? ( + + ) : null, + // Apparently checked: null is not valid for TS + ...(subVisualizationId === v.id && { checked: 'on' }), + }) + ) + ); + }), + visualizationsLookup: lookup, + }; + }, // eslint-disable-next-line react-hooks/exhaustive-deps [ flyoutOpen, @@ -222,89 +323,77 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { ] ); - const popover = ( - setFlyoutOpen(!flyoutOpen)} - data-test-subj="lnsChartSwitchPopover" - fontWeight="bold" - > - - - } - isOpen={flyoutOpen} - closePopover={() => setFlyoutOpen(false)} - anchorPosition="downLeft" - > - - - - {i18n.translate('xpack.lens.configPanel.chartType', { - defaultMessage: 'Chart type', - })} - - - setSearchTerm(e.target.value)} - /> - - - - - {(visualizationTypes || []).map((v) => ( - {v.label}} - title={v.fullLabel} - role="menuitem" - data-test-subj={`lnsChartSwitchPopover_${v.id}`} - onClick={() => commitSelection(v.selection)} - betaBadgeLabel={ - v.selection.dataLoss !== 'nothing' - ? i18n.translate('xpack.lens.chartSwitch.dataLossLabel', { - defaultMessage: 'Data loss', - }) - : undefined - } - betaBadgeTooltipContent={ - v.selection.dataLoss !== 'nothing' - ? i18n.translate('xpack.lens.chartSwitch.dataLossDescription', { - defaultMessage: 'Switching to this chart will lose some of the configuration', - }) - : undefined - } - betaBadgeIconType={v.selection.dataLoss !== 'nothing' ? 'alert' : undefined} + return ( +
+ setFlyoutOpen(!flyoutOpen)} + data-test-subj="lnsChartSwitchPopover" + fontWeight="bold" > - - - ))} - - {searchTerm && (visualizationTypes || []).length === 0 && ( - - {searchTerm}, - }} - /> - - )} - + + + } + isOpen={flyoutOpen} + closePopover={() => setFlyoutOpen(false)} + anchorPosition="downLeft" + > + + + + {i18n.translate('xpack.lens.configPanel.chartType', { + defaultMessage: 'Chart type', + })} + + + + setSearchTerm(value), + }} + options={visualizationTypes} + onChange={(newOptions) => { + const chosenType = newOptions.find(({ checked }) => checked === 'on')!; + if (!chosenType) { + return; + } + const id = chosenType.value!; + commitSelection(visualizationsLookup[id].selection); + }} + noMatchesMessage={ + {searchTerm}, + }} + /> + } + > + {(list, search) => ( + <> + {search} + {list} + + )} + + +
); - - return
{popover}
; }); function getTopSuggestion( diff --git a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx index db3b29bb74d31..7f256dc588c25 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx @@ -29,6 +29,7 @@ export function createMockVisualization(): jest.Mocked { icon: 'empty', id: 'TEST_VIS', label: 'TEST', + groupLabel: 'TEST_VISGroup', }, ], getVisualizationTypeId: jest.fn((_state) => 'empty'), diff --git a/x-pack/plugins/lens/public/metric_visualization/visualization.tsx b/x-pack/plugins/lens/public/metric_visualization/visualization.tsx index 91516b7b7319b..34b9e4d2b2526 100644 --- a/x-pack/plugins/lens/public/metric_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/visualization.tsx @@ -52,6 +52,9 @@ export const metricVisualization: Visualization = { label: i18n.translate('xpack.lens.metric.label', { defaultMessage: 'Metric', }), + groupLabel: i18n.translate('xpack.lens.metric.groupLabel', { + defaultMessage: 'Tabular and single value', + }), }, ], diff --git a/x-pack/plugins/lens/public/pie_visualization/constants.ts b/x-pack/plugins/lens/public/pie_visualization/constants.ts index 37d67597b8784..9a2f39e7d34a5 100644 --- a/x-pack/plugins/lens/public/pie_visualization/constants.ts +++ b/x-pack/plugins/lens/public/pie_visualization/constants.ts @@ -10,24 +10,33 @@ import { LensIconChartDonut } from '../assets/chart_donut'; import { LensIconChartPie } from '../assets/chart_pie'; import { LensIconChartTreemap } from '../assets/chart_treemap'; +const groupLabel = i18n.translate('xpack.lens.pie.groupLabel', { + defaultMessage: 'Proportion', +}); + export const CHART_NAMES = { donut: { icon: LensIconChartDonut, label: i18n.translate('xpack.lens.pie.donutLabel', { defaultMessage: 'Donut', }), + groupLabel, }, pie: { icon: LensIconChartPie, label: i18n.translate('xpack.lens.pie.pielabel', { defaultMessage: 'Pie', }), + + groupLabel, }, treemap: { icon: LensIconChartTreemap, label: i18n.translate('xpack.lens.pie.treemaplabel', { defaultMessage: 'Treemap', }), + + groupLabel, }, }; diff --git a/x-pack/plugins/lens/public/pie_visualization/visualization.tsx b/x-pack/plugins/lens/public/pie_visualization/visualization.tsx index 683acc49859b6..00d0158364e45 100644 --- a/x-pack/plugins/lens/public/pie_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/visualization.tsx @@ -45,16 +45,19 @@ export const getPieVisualization = ({ id: 'donut', icon: CHART_NAMES.donut.icon, label: CHART_NAMES.donut.label, + groupLabel: CHART_NAMES.donut.groupLabel, }, { id: 'pie', icon: CHART_NAMES.pie.icon, label: CHART_NAMES.pie.label, + groupLabel: CHART_NAMES.pie.groupLabel, }, { id: 'treemap', icon: CHART_NAMES.treemap.icon, label: CHART_NAMES.treemap.label, + groupLabel: CHART_NAMES.treemap.groupLabel, }, ], diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 6ac2d98994be3..fd0c6aff27351 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -516,6 +516,10 @@ export interface VisualizationType { * Optional label used in chart type search if chart switcher is expanded and for tooltips */ fullLabel?: string; + /** + * The group the visualization belongs to + */ + groupLabel: string; } export interface Visualization { diff --git a/x-pack/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts index da290b225e164..126be41e7b129 100644 --- a/x-pack/plugins/lens/public/xy_visualization/types.ts +++ b/x-pack/plugins/lens/public/xy_visualization/types.ts @@ -431,14 +431,22 @@ export interface XYState { } export type State = XYState; +const groupLabelForBar = i18n.translate('xpack.lens.xyVisualization.barGroupLabel', { + defaultMessage: 'Bar', +}); + +const groupLabelForLineAndArea = i18n.translate('xpack.lens.xyVisualization.lineGroupLabel', { + defaultMessage: 'Line and area', +}); export const visualizationTypes: VisualizationType[] = [ { id: 'bar', icon: LensIconChartBar, label: i18n.translate('xpack.lens.xyVisualization.barLabel', { - defaultMessage: 'Bar', + defaultMessage: 'Bar vertical', }), + groupLabel: groupLabelForBar, }, { id: 'bar_horizontal', @@ -447,22 +455,25 @@ export const visualizationTypes: VisualizationType[] = [ defaultMessage: 'H. Bar', }), fullLabel: i18n.translate('xpack.lens.xyVisualization.barHorizontalFullLabel', { - defaultMessage: 'Horizontal bar', + defaultMessage: 'Bar horizontal', }), + groupLabel: groupLabelForBar, }, { id: 'bar_stacked', icon: LensIconChartBarStacked, label: i18n.translate('xpack.lens.xyVisualization.stackedBarLabel', { - defaultMessage: 'Stacked bar', + defaultMessage: 'Bar vertical stacked', }), + groupLabel: groupLabelForBar, }, { id: 'bar_percentage_stacked', icon: LensIconChartBarPercentage, label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageBarLabel', { - defaultMessage: 'Percentage bar', + defaultMessage: 'Bar vertical percentage', }), + groupLabel: groupLabelForBar, }, { id: 'bar_horizontal_stacked', @@ -471,8 +482,9 @@ export const visualizationTypes: VisualizationType[] = [ defaultMessage: 'H. Stacked bar', }), fullLabel: i18n.translate('xpack.lens.xyVisualization.stackedBarHorizontalFullLabel', { - defaultMessage: 'Horizontal stacked bar', + defaultMessage: 'Bar horizontal stacked', }), + groupLabel: groupLabelForBar, }, { id: 'bar_horizontal_percentage_stacked', @@ -483,9 +495,10 @@ export const visualizationTypes: VisualizationType[] = [ fullLabel: i18n.translate( 'xpack.lens.xyVisualization.stackedPercentageBarHorizontalFullLabel', { - defaultMessage: 'Horizontal percentage bar', + defaultMessage: 'Bar horizontal percentage', } ), + groupLabel: groupLabelForBar, }, { id: 'area', @@ -493,20 +506,23 @@ export const visualizationTypes: VisualizationType[] = [ label: i18n.translate('xpack.lens.xyVisualization.areaLabel', { defaultMessage: 'Area', }), + groupLabel: groupLabelForLineAndArea, }, { id: 'area_stacked', icon: LensIconChartAreaStacked, label: i18n.translate('xpack.lens.xyVisualization.stackedAreaLabel', { - defaultMessage: 'Stacked area', + defaultMessage: 'Area stacked', }), + groupLabel: groupLabelForLineAndArea, }, { id: 'area_percentage_stacked', icon: LensIconChartAreaPercentage, label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageAreaLabel', { - defaultMessage: 'Percentage area', + defaultMessage: 'Area percentage', }), + groupLabel: groupLabelForLineAndArea, }, { id: 'line', @@ -514,5 +530,6 @@ export const visualizationTypes: VisualizationType[] = [ label: i18n.translate('xpack.lens.xyVisualization.lineLabel', { defaultMessage: 'Line', }), + groupLabel: groupLabelForLineAndArea, }, ]; diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index c244fa7fdfc89..319879d511a1e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -62,7 +62,7 @@ describe('xy_visualization', () => { const desc = xyVisualization.getDescription(mixedState()); expect(desc.icon).toEqual(LensIconChartBar); - expect(desc.label).toEqual('Bar'); + expect(desc.label).toEqual('Bar vertical'); }); it('should show mixed horizontal bar chart when multiple horizontal bar types', () => { @@ -70,23 +70,23 @@ describe('xy_visualization', () => { mixedState('bar_horizontal', 'bar_horizontal_stacked') ); - expect(desc.label).toEqual('Mixed H. bar'); + expect(desc.label).toEqual('Mixed bar horizontal'); }); it('should show bar chart when bar only', () => { const desc = xyVisualization.getDescription(mixedState('bar_horizontal', 'bar_horizontal')); - expect(desc.label).toEqual('H. Bar'); + expect(desc.label).toEqual('Bar horizontal'); }); it('should show the chart description if not mixed', () => { expect(xyVisualization.getDescription(mixedState('area')).label).toEqual('Area'); expect(xyVisualization.getDescription(mixedState('line')).label).toEqual('Line'); expect(xyVisualization.getDescription(mixedState('area_stacked')).label).toEqual( - 'Stacked area' + 'Area stacked' ); expect(xyVisualization.getDescription(mixedState('bar_horizontal_stacked')).label).toEqual( - 'H. Stacked bar' + 'Bar horizontal stacked' ); }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 1ee4b2e050f3e..f03115aaca21b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -58,7 +58,7 @@ function getDescription(state?: State) { return { icon: LensIconChartBarHorizontal, label: i18n.translate('xpack.lens.xyVisualization.mixedBarHorizontalLabel', { - defaultMessage: 'Mixed H. bar', + defaultMessage: 'Mixed bar horizontal', }), }; } @@ -74,7 +74,7 @@ function getDescription(state?: State) { return { icon: visualizationType.icon, - label: visualizationType.label, + label: visualizationType.fullLabel || visualizationType.label, }; } diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts index 9ab5f446066f0..277ca4467aeda 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts @@ -484,7 +484,7 @@ describe('xy_suggestions', () => { }); expect(rest).toHaveLength(visualizationTypes.length - 1); - expect(suggestion.title).toEqual('Stacked bar'); + expect(suggestion.title).toEqual('Bar vertical stacked'); expect(suggestion.state).toEqual( expect.objectContaining({ layers: [ diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 42552d756313b..b06f8f4583c7c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -11421,7 +11421,6 @@ "xpack.lens.xySuggestions.unstackedChartTitle": "スタックが解除されました", "xpack.lens.xySuggestions.yAxixConjunctionSign": " & ", "xpack.lens.xyVisualization.areaLabel": "エリア", - "xpack.lens.xyVisualization.barHorizontalFullLabel": "横棒", "xpack.lens.xyVisualization.barHorizontalLabel": "横棒", "xpack.lens.xyVisualization.barLabel": "棒", "xpack.lens.xyVisualization.dataFailureSplitLong": "{layers, plural, other {レイヤー}} {layersList} には {axis} のフィールドが{layers, plural, other {必要です}}。", @@ -11433,11 +11432,9 @@ "xpack.lens.xyVisualization.mixedLabel": "ミックスされた XY", "xpack.lens.xyVisualization.noDataLabel": "結果が見つかりませんでした", "xpack.lens.xyVisualization.stackedAreaLabel": "スタックされたエリア", - "xpack.lens.xyVisualization.stackedBarHorizontalFullLabel": "積み上げ横棒", "xpack.lens.xyVisualization.stackedBarHorizontalLabel": "横積み上げ棒", "xpack.lens.xyVisualization.stackedBarLabel": "積み上げ棒", "xpack.lens.xyVisualization.stackedPercentageAreaLabel": "割合エリア", - "xpack.lens.xyVisualization.stackedPercentageBarHorizontalFullLabel": "割合横棒", "xpack.lens.xyVisualization.stackedPercentageBarHorizontalLabel": "横割合棒", "xpack.lens.xyVisualization.stackedPercentageBarLabel": "割合棒", "xpack.lens.xyVisualization.xyLabel": "XY", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ee9f1aefeae9b..2f0d58b56854f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -11449,7 +11449,6 @@ "xpack.lens.xySuggestions.unstackedChartTitle": "非堆叠", "xpack.lens.xySuggestions.yAxixConjunctionSign": " & ", "xpack.lens.xyVisualization.areaLabel": "面积图", - "xpack.lens.xyVisualization.barHorizontalFullLabel": "水平条形图", "xpack.lens.xyVisualization.barHorizontalLabel": "水平条形图", "xpack.lens.xyVisualization.barLabel": "条形图", "xpack.lens.xyVisualization.dataFailureSplitLong": "{layers, plural, other {图层}} {layersList} {layers, plural, other {需要}}一个针对{axis}的字段。", @@ -11461,11 +11460,9 @@ "xpack.lens.xyVisualization.mixedLabel": "混合 XY", "xpack.lens.xyVisualization.noDataLabel": "找不到结果", "xpack.lens.xyVisualization.stackedAreaLabel": "堆叠面积图", - "xpack.lens.xyVisualization.stackedBarHorizontalFullLabel": "水平堆叠条形图", "xpack.lens.xyVisualization.stackedBarHorizontalLabel": "水平堆叠条形图", "xpack.lens.xyVisualization.stackedBarLabel": "堆叠条形图", "xpack.lens.xyVisualization.stackedPercentageAreaLabel": "百分比面积图", - "xpack.lens.xyVisualization.stackedPercentageBarHorizontalFullLabel": "水平百分比条形图", "xpack.lens.xyVisualization.stackedPercentageBarHorizontalLabel": "水平百分比条形图", "xpack.lens.xyVisualization.stackedPercentageBarLabel": "百分比条形图", "xpack.lens.xyVisualization.xyLabel": "XY", diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index d9ec9ca5d3f62..2ecf6f3163d7e 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -418,19 +418,20 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont * @param subVisualizationId - the ID of the sub-visualization to switch to, such as * lnsDatatable or bar_stacked */ - async switchToVisualization(subVisualizationId: string) { + async switchToVisualization(subVisualizationId: string, searchTerm?: string) { await this.openChartSwitchPopover(); + await this.searchOnChartSwitch(subVisualizationId, searchTerm); await testSubjects.click(`lnsChartSwitchPopover_${subVisualizationId}`); await PageObjects.header.waitUntilLoadingHasFinished(); }, async openChartSwitchPopover() { - if (await testSubjects.exists('visTypeTitle')) { + if (await testSubjects.exists('lnsChartSwitchList')) { return; } await retry.try(async () => { await testSubjects.click('lnsChartSwitchPopover'); - await testSubjects.existOrFail('visTypeTitle'); + await testSubjects.existOrFail('lnsChartSwitchList'); }); }, @@ -451,17 +452,28 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont return errors?.length ?? 0; }, + async searchOnChartSwitch(subVisualizationId: string, searchTerm?: string) { + // Because the new chart switcher is now a virtualized list, the process needs some help + // So either pass a search string or pick the last 3 letters from the id (3 because pie + // is the smallest chart name) and use them to search + const queryTerm = searchTerm ?? subVisualizationId.substring(subVisualizationId.length - 3); + return await testSubjects.setValue('lnsChartSwitchSearch', queryTerm, { + clearWithKeyboard: true, + }); + }, + /** * Checks a specific subvisualization in the chart switcher for a "data loss" indicator * * @param subVisualizationId - the ID of the sub-visualization to switch to, such as * lnsDatatable or bar_stacked */ - async hasChartSwitchWarning(subVisualizationId: string) { + async hasChartSwitchWarning(subVisualizationId: string, searchTerm?: string) { await this.openChartSwitchPopover(); + await this.searchOnChartSwitch(subVisualizationId, searchTerm); const element = await testSubjects.find(`lnsChartSwitchPopover_${subVisualizationId}`); - return await find.descendantExistsByCssSelector( - '.euiKeyPadMenuItem__betaBadgeWrapper', + return await testSubjects.descendantExists( + `lnsChartSwitchPopoverAlert_${subVisualizationId}`, element ); },