[Lens] Introduces new chart switcher (#91844)

Co-authored-by: Wylie Conlon <wylieconlon@gmail.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Marco Liberati 2021-03-02 11:25:35 +01:00 committed by GitHub
parent 657c273866
commit 03cc5cc0c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 346 additions and 155 deletions

View file

@ -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<DatatableVisualizationState> = {
id: 'lnsDatatable',
@ -44,8 +48,9 @@ export const datatableVisualization: Visualization<DatatableVisualizationState>
{
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<DatatableVisualizationState>
getDescription() {
return {
icon: LensIconChartDatatable,
label: i18n.translate('xpack.lens.datatable.label', {
defaultMessage: 'Data table',
}),
label: visualizationLabel,
};
},

View file

@ -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',
},
],
};

View file

@ -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',
},
],
};

View file

@ -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 <div {...otherProps}>{children({ width: 100, height: 100 })}</div>;
};
});
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: () => [

View file

@ -18,5 +18,5 @@ img.lnsChartSwitch__chartIcon { // stylelint-disable-line selector-no-qualifying
}
.lnsChartSwitch__search {
width: 4 * $euiSizeXXL;
width: 7 * $euiSizeXXL;
}

View file

@ -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 <div>{children({ width: 100, height: 100 })}</div>;
};
});
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', () => {

View file

@ -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<boolean>(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: (
<EuiIcon className="lnsChartSwitch__chartIcon" type={v.icon || 'empty'} />
),
append:
v.selection.dataLoss !== 'nothing' ? (
<EuiIconTip
aria-label={i18n.translate('xpack.lens.chartSwitch.dataLossLabel', {
defaultMessage: 'Warning',
})}
type="alert"
color="warning"
content={i18n.translate('xpack.lens.chartSwitch.dataLossDescription', {
defaultMessage:
'Selecting this chart type will result in a partial loss of currently applied configuration selections.',
})}
iconProps={{
className: 'lnsChartSwitch__chartIcon',
'data-test-subj': `lnsChartSwitchPopoverAlert_${v.id}`,
}}
/>
) : 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 = (
<EuiPopover
id="lnsChartSwitchPopover"
ownFocus
initialFocus=".lnsChartSwitch__popoverPanel"
panelClassName="lnsChartSwitch__popoverPanel"
panelPaddingSize="s"
button={
<ToolbarButton
onClick={() => setFlyoutOpen(!flyoutOpen)}
data-test-subj="lnsChartSwitchPopover"
fontWeight="bold"
>
<VisualizationSummary {...props} />
</ToolbarButton>
}
isOpen={flyoutOpen}
closePopover={() => setFlyoutOpen(false)}
anchorPosition="downLeft"
>
<EuiPopoverTitle>
<EuiFlexGroup alignItems="center" responsive={false}>
<EuiFlexItem>
{i18n.translate('xpack.lens.configPanel.chartType', {
defaultMessage: 'Chart type',
})}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFieldSearch
compressed
fullWidth={false}
className="lnsChartSwitch__search"
value={searchTerm}
data-test-subj="lnsChartSwitchSearch"
onChange={(e) => setSearchTerm(e.target.value)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPopoverTitle>
<EuiKeyPadMenu>
{(visualizationTypes || []).map((v) => (
<EuiKeyPadMenuItem
key={`${v.visualizationId}:${v.id}`}
label={<span data-test-subj="visTypeTitle">{v.label}</span>}
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 (
<div className="lnsChartSwitch__header">
<EuiPopover
id="lnsChartSwitchPopover"
ownFocus
initialFocus=".lnsChartSwitch__popoverPanel"
panelClassName="lnsChartSwitch__popoverPanel"
panelPaddingSize="s"
button={
<ToolbarButton
onClick={() => setFlyoutOpen(!flyoutOpen)}
data-test-subj="lnsChartSwitchPopover"
fontWeight="bold"
>
<EuiIcon className="lnsChartSwitch__chartIcon" type={v.icon || 'empty'} size="l" />
</EuiKeyPadMenuItem>
))}
</EuiKeyPadMenu>
{searchTerm && (visualizationTypes || []).length === 0 && (
<EuiSelectableMessage>
<FormattedMessage
id="xpack.lens.chartSwitch.noResults"
defaultMessage="No results found for {term}."
values={{
term: <strong>{searchTerm}</strong>,
}}
/>
</EuiSelectableMessage>
)}
</EuiPopover>
<VisualizationSummary {...props} />
</ToolbarButton>
}
isOpen={flyoutOpen}
closePopover={() => setFlyoutOpen(false)}
anchorPosition="downLeft"
>
<EuiPopoverTitle>
<EuiFlexGroup alignItems="center" responsive={false}>
<EuiFlexItem>
{i18n.translate('xpack.lens.configPanel.chartType', {
defaultMessage: 'Chart type',
})}
</EuiFlexItem>
</EuiFlexGroup>
</EuiPopoverTitle>
<EuiSelectable
height={computeListHeight(visualizationTypes, MAX_LIST_HEIGHT)}
searchable
singleSelection
isPreFiltered
data-test-subj="lnsChartSwitchList"
searchProps={{
incremental: true,
className: 'lnsChartSwitch__search',
'data-test-subj': 'lnsChartSwitchSearch',
onSearch: (value) => 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={
<FormattedMessage
id="xpack.lens.chartSwitch.noResults"
defaultMessage="No results found for {term}."
values={{
term: <strong>{searchTerm}</strong>,
}}
/>
}
>
{(list, search) => (
<>
{search}
{list}
</>
)}
</EuiSelectable>
</EuiPopover>
</div>
);
return <div className="lnsChartSwitch__header">{popover}</div>;
});
function getTopSuggestion(

View file

@ -29,6 +29,7 @@ export function createMockVisualization(): jest.Mocked<Visualization> {
icon: 'empty',
id: 'TEST_VIS',
label: 'TEST',
groupLabel: 'TEST_VISGroup',
},
],
getVisualizationTypeId: jest.fn((_state) => 'empty'),

View file

@ -52,6 +52,9 @@ export const metricVisualization: Visualization<MetricState> = {
label: i18n.translate('xpack.lens.metric.label', {
defaultMessage: 'Metric',
}),
groupLabel: i18n.translate('xpack.lens.metric.groupLabel', {
defaultMessage: 'Tabular and single value',
}),
},
],

View file

@ -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,
},
};

View file

@ -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,
},
],

View file

@ -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<T = unknown> {

View file

@ -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,
},
];

View file

@ -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'
);
});
});

View file

@ -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,
};
}

View file

@ -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: [

View file

@ -11418,7 +11418,6 @@
"xpack.lens.xySuggestions.unstackedChartTitle": "スタックが解除されました",
"xpack.lens.xySuggestions.yAxixConjunctionSign": " &amp; ",
"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 {必要です}}。",
@ -11430,11 +11429,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",

View file

@ -11446,7 +11446,6 @@
"xpack.lens.xySuggestions.unstackedChartTitle": "非堆叠",
"xpack.lens.xySuggestions.yAxixConjunctionSign": " &amp; ",
"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}的字段。",
@ -11458,11 +11457,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",

View file

@ -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
);
},