[Lens] Workspace panel dimensions by chart type (#168651)

## Summary

Close https://github.com/elastic/kibana/issues/136993

All charts remain the same except the following

## Metric
Each tile gets `200px` if there are multiple and `300px` if it's a
single. The default background is EUI empty.

<img width="600" alt="Screenshot 2023-10-30 at 3 55 33 PM"
src="74d7e6dc-c90a-4f60-bf56-94ab1556ad42">

<img width="600" alt="Screenshot 2023-10-30 at 3 56 36 PM"
src="3254160a-b18a-4c04-b059-f20b8f1f246a">

## XY
Vertical time-series charts have `16:9` ratio but will stretch to any
available width and won't shrink below 300px height.


3e056ae8-bc65-4851-9ad9-6c8a81bdf58a




## Gauge

Gauge gets `600px` for the long side, `300px` for the short side.

<img width="600" alt="Screenshot 2023-11-28 at 11 22 20 AM"
src="2b774515-f060-4c26-a0aa-efdeebfff0e5">

<img width="600" alt="Screenshot 2023-11-28 at 11 22 33 AM"
src="62181021-d88a-4cb6-862b-42768a2df13e">



## Known issues
- [ ] text resizing on the metric
https://github.com/elastic/elastic-charts/issues/2238


0162f461-b544-44a9-971c-b2a0265d7d3a

- [x] gauge isn't showing veil on willRender

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

Flaky test runner:
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/4755

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: nickofthyme <nicholas.partridge@elastic.co>
Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>
Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com>
This commit is contained in:
Drew Tate 2024-01-13 19:25:52 -07:00 committed by GitHub
parent 2a44f75de9
commit e6a5647ea1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 1063 additions and 245 deletions

View file

@ -52,7 +52,7 @@ pageLoadAssetSize:
expressionLegacyMetricVis: 23121
expressionMetric: 22238
expressionMetricVis: 23121
expressionPartitionVis: 28000
expressionPartitionVis: 29000
expressionRepeatImage: 22341
expressionRevealImage: 25675
expressions: 140958

View file

@ -0,0 +1,94 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { useEffect } from 'react';
import { useCallback, useRef, useState } from 'react';
import fastIsEqual from 'fast-deep-equal';
import { useResizeObserver } from '@elastic/eui';
import { euiThemeVars } from '@kbn/ui-theme';
import type { ChartSizeSpec } from './types';
/**
* This hook is used to show a veil over the chart while it is being resized
* in response to a change in the container dimensions.
*
* It is only relevant if client dimensions are being requested based on chart configuration.
*
* This whole feature is a nice-to-have. If it proves to be a source of bugs,
* we can consider removing it and accepting the aesthetic drawback.
*/
export function useSizeTransitionVeil(
chartSizeSpec: ChartSizeSpec,
setChartSize: (d: ChartSizeSpec) => void,
// should be retrieved from handlers.shouldUseSizeTransitionVeil function
shouldUseVeil: boolean
) {
const containerRef = useRef<HTMLDivElement>(null);
const containerSize = useResizeObserver(containerRef.current);
const currentContainerSize = useRef<{ width: number; height: number }>(containerSize);
// This useEffect hook is here to make up for the fact that the initial container size
// is not correctly reported by the useResizeObserver hook (see https://github.com/elastic/eui/issues/7458).
useEffect(() => {
currentContainerSize.current = {
width: containerRef.current?.clientWidth ?? 0,
height: containerRef.current?.clientHeight ?? 0,
};
}, []);
const [showVeil, setShowVeil] = useState(false);
const currentChartSizeSpec = useRef<ChartSizeSpec>();
const specJustChanged = useRef<boolean>(false);
useEffect(() => {
if (!fastIsEqual(containerSize, currentContainerSize.current) && specJustChanged.current) {
// If the container size has changed, we need to show the veil to hide the chart since it
// be rendered based on the previous container size before being updated to the current size.
//
// 1. we show the veil
// 2. the charts library will render the chart based on the previous container dimensions
// 3. the charts library will resize the chart to the updated container dimensions
// 4. we hide the veil
setShowVeil(true);
currentContainerSize.current = containerSize;
}
}, [setShowVeil, containerSize]);
useEffect(() => {
if (!fastIsEqual(chartSizeSpec, currentChartSizeSpec.current)) {
setChartSize(chartSizeSpec);
currentChartSizeSpec.current = chartSizeSpec;
specJustChanged.current = true;
setTimeout(() => {
specJustChanged.current = false;
}, 500);
}
}, [chartSizeSpec, setChartSize]);
const onResize = useCallback(() => {
setShowVeil(false);
}, []);
return {
veil: (
<div
css={{
height: '100%',
width: '100%',
backgroundColor: euiThemeVars.euiColorEmptyShade,
position: 'absolute',
zIndex: 1,
display: shouldUseVeil && showVeil ? 'block' : 'none',
}}
/>
),
onResize,
containerRef,
};
}

View file

@ -12,5 +12,7 @@ export {
getOverridesFor,
isOnAggBasedEditor,
} from './utils';
export type { Simplify, MakeOverridesSerializable } from './types';
export type { Simplify, MakeOverridesSerializable, ChartSizeSpec, ChartSizeEvent } from './types';
export { isChartSizeEvent } from './types';
export { getColorCategories } from './color_categories';
export { useSizeTransitionVeil } from './chart_size_transition_veil';

View file

@ -4,11 +4,13 @@
"outDir": "target/types",
"types": [
"jest",
"node"
"node",
"@emotion/react/types/css-prop",
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
@ -17,5 +19,6 @@
"@kbn/core-execution-context-common",
"@kbn/expressions-plugin",
"@kbn/data-plugin",
"@kbn/ui-theme",
]
}

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import type { ExpressionRendererEvent } from '@kbn/expressions-plugin/public';
import React from 'react';
export type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
@ -26,3 +27,27 @@ export type MakeOverridesSerializable<T> = {
? MakeOverridesSerializable<T[KeyType]>
: NonNullable<T[KeyType]>;
};
export interface ChartSizeEvent extends ExpressionRendererEvent {
name: 'chartSize';
data: ChartSizeSpec;
}
export type ChartSizeUnit = 'pixels' | 'percentage';
interface ChartSizeDimensions {
x?: { value: number; unit: ChartSizeUnit };
y?: { value: number; unit: ChartSizeUnit };
}
export interface ChartSizeSpec {
// if maxDimensions are provided, the aspect ratio will be computed from them
maxDimensions?: ChartSizeDimensions;
minDimensions?: ChartSizeDimensions;
aspectRatio?: { x: number; y: number };
}
export function isChartSizeEvent(event: ExpressionRendererEvent): event is ChartSizeEvent {
const expectedName: ChartSizeEvent['name'] = 'chartSize';
return event.name === expectedName;
}

View file

@ -11,6 +11,7 @@ import type { PersistedState } from '@kbn/visualizations-plugin/public';
import type { ChartsPluginSetup } from '@kbn/charts-plugin/public';
import type { IFieldFormat, SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
import type { AllowedSettingsOverrides, AllowedChartOverrides } from '@kbn/charts-plugin/common';
import type { ChartSizeSpec } from '@kbn/chart-expressions-common';
import type { AllowedGaugeOverrides, GaugeExpressionProps } from './expression_functions';
export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat;
@ -22,4 +23,6 @@ export type GaugeRenderProps = GaugeExpressionProps & {
renderComplete: () => void;
uiState: PersistedState;
overrides?: AllowedGaugeOverrides & AllowedSettingsOverrides & AllowedChartOverrides;
shouldUseVeil: boolean;
setChartSize: (d: ChartSizeSpec) => void;
};

View file

@ -536,6 +536,7 @@ exports[`GaugeComponent renders the chart 1`] = `
/>
}
onRenderChange={[Function]}
onResize={[Function]}
theme={
Array [
Object {

View file

@ -102,6 +102,8 @@ describe('GaugeComponent', function () {
paletteService: await paletteThemeService.getPalettes(),
uiState,
renderComplete: jest.fn(),
setChartSize: jest.fn(),
shouldUseVeil: false,
};
});

View file

@ -12,7 +12,11 @@ import type { PaletteOutput } from '@kbn/coloring';
import { FieldFormat } from '@kbn/field-formats-plugin/common';
import type { CustomPaletteState } from '@kbn/charts-plugin/public';
import { EmptyPlaceholder } from '@kbn/charts-plugin/public';
import { getOverridesFor } from '@kbn/chart-expressions-common';
import {
type ChartSizeSpec,
getOverridesFor,
useSizeTransitionVeil,
} from '@kbn/chart-expressions-common';
import { isVisDimension } from '@kbn/visualizations-plugin/common/utils';
import { i18n } from '@kbn/i18n';
import {
@ -178,6 +182,8 @@ export const GaugeComponent: FC<GaugeRenderProps> = memo(
chartsThemeService,
renderComplete,
overrides,
shouldUseVeil,
setChartSize,
}) => {
const {
shape: gaugeType,
@ -253,6 +259,26 @@ export const GaugeComponent: FC<GaugeRenderProps> = memo(
[renderComplete]
);
const chartSizeSpec: ChartSizeSpec = {
maxDimensions: {
...(gaugeType === GaugeShapes.HORIZONTAL_BULLET
? {
x: { value: 600, unit: 'pixels' },
y: { value: 300, unit: 'pixels' },
}
: {
y: { value: 600, unit: 'pixels' },
x: { value: 300, unit: 'pixels' },
}),
},
};
const { veil, onResize, containerRef } = useSizeTransitionVeil(
chartSizeSpec,
setChartSize,
shouldUseVeil
);
const table = data;
const accessors = getAccessorsFromArgs(args, table.columns);
@ -359,7 +385,8 @@ export const GaugeComponent: FC<GaugeRenderProps> = memo(
: {};
return (
<div className="gauge__wrapper">
<div className="gauge__wrapper" ref={containerRef}>
{veil}
<Chart {...getOverridesFor(overrides, 'chart')}>
<Settings
noResults={<EmptyPlaceholder icon={icon} renderComplete={onRenderChange} />}
@ -369,6 +396,7 @@ export const GaugeComponent: FC<GaugeRenderProps> = memo(
ariaLabel={args.ariaLabel}
ariaUseDefaultSummary={!args.ariaLabel}
onRenderChange={onRenderChange}
onResize={onResize}
locale={i18n.getLocale()}
{...getOverridesFor(overrides, 'settings')}
/>

View file

@ -13,7 +13,11 @@ import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { ExpressionRenderDefinition } from '@kbn/expressions-plugin/common/expression_renderers';
import { StartServicesGetter } from '@kbn/kibana-utils-plugin/public';
import { METRIC_TYPE } from '@kbn/analytics';
import { extractContainerType, extractVisualizationType } from '@kbn/chart-expressions-common';
import {
ChartSizeEvent,
extractContainerType,
extractVisualizationType,
} from '@kbn/chart-expressions-common';
import { ExpressionGaugePluginStart } from '../plugin';
import { EXPRESSION_GAUGE_NAME, GaugeExpressionProps, GaugeShapes } from '../../common';
import { getFormatService, getPaletteService } from '../services';
@ -66,16 +70,27 @@ export const gaugeRenderer: (
handlers.done();
};
const setChartSize = (chartSizeSpec: ChartSizeEvent['data']) => {
const event: ChartSizeEvent = {
name: 'chartSize',
data: chartSizeSpec,
};
handlers.event(event);
};
const { GaugeComponent } = await import('../components/gauge_component');
render(
<KibanaThemeProvider theme$={core.theme.theme$}>
<div className="gauge-container" data-test-subj="gaugeChart">
<GaugeComponent
{...config}
setChartSize={setChartSize}
formatFactory={getFormatService().deserialize}
chartsThemeService={plugins.charts.theme}
paletteService={getPaletteService()}
renderComplete={renderComplete}
shouldUseVeil={handlers.shouldUseSizeTransitionVeil()}
uiState={handlers.uiState as PersistedState}
/>
</div>

View file

@ -14,7 +14,11 @@ import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { ExpressionRenderDefinition } from '@kbn/expressions-plugin/common/expression_renderers';
import { StartServicesGetter } from '@kbn/kibana-utils-plugin/public';
import { METRIC_TYPE } from '@kbn/analytics';
import { extractContainerType, extractVisualizationType } from '@kbn/chart-expressions-common';
import {
ChartSizeEvent,
extractContainerType,
extractVisualizationType,
} from '@kbn/chart-expressions-common';
import { I18nProvider } from '@kbn/i18n-react';
import { MultiFilterEvent } from '../../common/types';
import { ExpressionHeatmapPluginStart } from '../plugin';
@ -78,6 +82,18 @@ export const heatmapRenderer: (
handlers.done();
};
const chartSizeEvent: ChartSizeEvent = {
name: 'chartSize',
data: {
maxDimensions: {
x: { value: 100, unit: 'percentage' },
y: { value: 100, unit: 'percentage' },
},
},
};
handlers.event(chartSizeEvent);
const timeZone = getTimeZone(getUISettings());
const { HeatmapComponent } = await import('../components/heatmap_component');
const { isInteractive } = handlers;

View file

@ -21,7 +21,11 @@ import {
import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils';
import { Datatable } from '@kbn/expressions-plugin/common';
import { StartServicesGetter } from '@kbn/kibana-utils-plugin/public';
import { extractContainerType, extractVisualizationType } from '@kbn/chart-expressions-common';
import {
ChartSizeEvent,
extractContainerType,
extractVisualizationType,
} from '@kbn/chart-expressions-common';
import { ExpressionLegacyMetricPluginStart } from '../plugin';
import { EXPRESSION_METRIC_NAME, MetricVisRenderConfig, VisParams } from '../../common';
@ -92,6 +96,18 @@ export const getMetricVisRenderer: (
handlers.done();
};
const chartSizeEvent: ChartSizeEvent = {
name: 'chartSize',
data: {
maxDimensions: {
x: { value: 100, unit: 'percentage' },
y: { value: 100, unit: 'percentage' },
},
},
};
handlers.event(chartSizeEvent);
render(
<KibanaThemeProvider theme$={core.theme.theme$}>
<VisualizationContainer

View file

@ -22,7 +22,6 @@ import {
import { SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
import { SerializableRecord } from '@kbn/utility-types';
import type { IUiSettingsClient } from '@kbn/core/public';
import { HtmlAttributes } from 'csstype';
import { CustomPaletteState } from '@kbn/charts-plugin/common/expressions/palette/types';
import { DimensionsVisParam } from '../../common';
import { euiThemeVars } from '@kbn/ui-theme';
@ -208,7 +207,7 @@ const defaultProps = {
filterable: true,
renderMode: 'view',
uiSettings: {} as unknown as IUiSettingsClient,
} as Pick<MetricVisComponentProps, 'renderComplete' | 'fireEvent' | 'filterable' | 'renderMode'>;
} as Pick<MetricVisComponentProps, 'renderComplete' | 'fireEvent' | 'filterable'>;
describe('MetricVisComponent', function () {
afterEach(() => {
@ -239,7 +238,7 @@ describe('MetricVisComponent', function () {
expect(visConfig).toMatchInlineSnapshot(`
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"extra": <span />,
"icon": [Function],
"subtitle": undefined,
@ -299,7 +298,7 @@ describe('MetricVisComponent', function () {
expect(configWithPrefix).toMatchInlineSnapshot(`
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"extra": <span>
secondary prefix
number-13.6328125
@ -348,7 +347,7 @@ describe('MetricVisComponent', function () {
expect(configWithProgress).toMatchInlineSnapshot(`
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"domainMax": 28.984375,
"extra": <span />,
"icon": [Function],
@ -425,7 +424,7 @@ describe('MetricVisComponent', function () {
expect(visConfig).toMatchInlineSnapshot(`
Array [
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"extra": <span />,
"icon": undefined,
"subtitle": "Median products.base_price",
@ -434,7 +433,7 @@ describe('MetricVisComponent', function () {
"valueFormatter": [Function],
},
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"extra": <span />,
"icon": undefined,
"subtitle": "Median products.base_price",
@ -443,7 +442,7 @@ describe('MetricVisComponent', function () {
"valueFormatter": [Function],
},
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"extra": <span />,
"icon": undefined,
"subtitle": "Median products.base_price",
@ -452,7 +451,7 @@ describe('MetricVisComponent', function () {
"valueFormatter": [Function],
},
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"extra": <span />,
"icon": undefined,
"subtitle": "Median products.base_price",
@ -461,7 +460,7 @@ describe('MetricVisComponent', function () {
"valueFormatter": [Function],
},
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"extra": <span />,
"icon": undefined,
"subtitle": "Median products.base_price",
@ -590,7 +589,7 @@ describe('MetricVisComponent', function () {
Array [
Array [
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"extra": <span />,
"icon": undefined,
"subtitle": "Median products.base_price",
@ -599,7 +598,7 @@ describe('MetricVisComponent', function () {
"valueFormatter": [Function],
},
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"extra": <span />,
"icon": undefined,
"subtitle": "Median products.base_price",
@ -608,7 +607,7 @@ describe('MetricVisComponent', function () {
"valueFormatter": [Function],
},
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"extra": <span />,
"icon": undefined,
"subtitle": "Median products.base_price",
@ -617,7 +616,7 @@ describe('MetricVisComponent', function () {
"valueFormatter": [Function],
},
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"extra": <span />,
"icon": undefined,
"subtitle": "Median products.base_price",
@ -626,7 +625,7 @@ describe('MetricVisComponent', function () {
"valueFormatter": [Function],
},
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"extra": <span />,
"icon": undefined,
"subtitle": "Median products.base_price",
@ -637,7 +636,7 @@ describe('MetricVisComponent', function () {
],
Array [
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"extra": <span />,
"icon": undefined,
"subtitle": "Median products.base_price",
@ -678,7 +677,7 @@ describe('MetricVisComponent', function () {
Array [
Array [
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"domainMax": 28.984375,
"extra": <span />,
"icon": undefined,
@ -689,7 +688,7 @@ describe('MetricVisComponent', function () {
"valueFormatter": [Function],
},
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"domainMax": 28.984375,
"extra": <span />,
"icon": undefined,
@ -700,7 +699,7 @@ describe('MetricVisComponent', function () {
"valueFormatter": [Function],
},
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"domainMax": 25.984375,
"extra": <span />,
"icon": undefined,
@ -711,7 +710,7 @@ describe('MetricVisComponent', function () {
"valueFormatter": [Function],
},
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"domainMax": 25.784375,
"extra": <span />,
"icon": undefined,
@ -722,7 +721,7 @@ describe('MetricVisComponent', function () {
"valueFormatter": [Function],
},
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"domainMax": 25.348011363636363,
"extra": <span />,
"icon": undefined,
@ -735,7 +734,7 @@ describe('MetricVisComponent', function () {
],
Array [
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"domainMax": 24.984375,
"extra": <span />,
"icon": undefined,
@ -849,124 +848,60 @@ describe('MetricVisComponent', function () {
});
});
describe('rendering with no data', () => {});
it('should constrain dimensions in edit mode', () => {
const getContainerStyles = (editMode: boolean, multipleTiles: boolean) =>
(
shallow(
<MetricVis
data={table}
config={{
metric: {
progressDirection: 'vertical',
maxCols: 5,
},
dimensions: {
metric: basePriceColumnId,
breakdownBy: multipleTiles ? dayOfWeekColumnId : undefined,
},
}}
{...defaultProps}
renderMode={editMode ? 'edit' : 'view'}
/>
)
.find('div')
.at(0)
.props() as HtmlAttributes & { css: { styles: string } }
).css.styles;
const getDimensionsRequest = (multipleTiles: boolean) => {
const fireEvent = jest.fn();
const wrapper = shallow(
<MetricVis
data={table}
config={{
metric: {
progressDirection: 'vertical',
maxCols: 5,
},
dimensions: {
metric: basePriceColumnId,
breakdownBy: multipleTiles ? dayOfWeekColumnId : undefined,
},
}}
{...defaultProps}
fireEvent={fireEvent}
/>
);
expect(getContainerStyles(false, false)).toMatchInlineSnapshot(`
"
height: 100%;
width: 100%;
max-height: 100%;
max-width: 100%;
overflow-y: auto;
scrollbar-width: thin;
wrapper.find(Settings).props().onWillRender!();
&::-webkit-scrollbar {
inline-size: 16px;
block-size: 16px;
}
return fireEvent.mock.calls[0][0].data;
};
&::-webkit-scrollbar-thumb {
background-color: rgba(105,112,125,0.5);
background-clip: content-box;
border-radius: 16px;
border: calc(8px * 0.75) solid transparent;
}
&::-webkit-scrollbar-corner,
&::-webkit-scrollbar-track {
background-color: transparent;
}
scrollbar-color: rgba(105,112,125,0.5) transparent;
"
expect(getDimensionsRequest(false)).toMatchInlineSnapshot(`
Object {
"maxDimensions": Object {
"x": Object {
"unit": "pixels",
"value": 300,
},
"y": Object {
"unit": "pixels",
"value": 300,
},
},
}
`);
expect(getContainerStyles(true, false)).toMatchInlineSnapshot(`
"
height: 300px;
width: 300px;
max-height: 100%;
max-width: 100%;
overflow-y: auto;
scrollbar-width: thin;
&::-webkit-scrollbar {
inline-size: 16px;
block-size: 16px;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(105,112,125,0.5);
background-clip: content-box;
border-radius: 16px;
border: calc(8px * 0.75) solid transparent;
}
&::-webkit-scrollbar-corner,
&::-webkit-scrollbar-track {
background-color: transparent;
}
scrollbar-color: rgba(105,112,125,0.5) transparent;
"
`);
expect(getContainerStyles(true, true)).toMatchInlineSnapshot(`
"
height: 400px;
width: 1000px;
max-height: 100%;
max-width: 100%;
overflow-y: auto;
scrollbar-width: thin;
&::-webkit-scrollbar {
inline-size: 16px;
block-size: 16px;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(105,112,125,0.5);
background-clip: content-box;
border-radius: 16px;
border: calc(8px * 0.75) solid transparent;
}
&::-webkit-scrollbar-corner,
&::-webkit-scrollbar-track {
background-color: transparent;
}
scrollbar-color: rgba(105,112,125,0.5) transparent;
"
expect(getDimensionsRequest(true)).toMatchInlineSnapshot(`
Object {
"maxDimensions": Object {
"x": Object {
"unit": "pixels",
"value": 1000,
},
"y": Object {
"unit": "pixels",
"value": 400,
},
},
}
`);
});
@ -1308,7 +1243,7 @@ describe('MetricVisComponent', function () {
const [[datum]] = component.find(Metric).props().data!;
expect(datum!.color).toBe(euiThemeVars.euiColorLightestShade);
expect(datum!.color).toBe(euiThemeVars.euiColorEmptyShade);
expect(mockGetColorForValue).not.toHaveBeenCalled();
});
});

View file

@ -29,7 +29,6 @@ import type {
DatatableColumn,
DatatableRow,
IInterpreterRenderHandlers,
RenderMode,
} from '@kbn/expressions-plugin/common';
import { CustomPaletteState } from '@kbn/charts-plugin/public';
import {
@ -41,13 +40,13 @@ import { css } from '@emotion/react';
import { euiThemeVars } from '@kbn/ui-theme';
import { useResizeObserver, useEuiScrollBar, EuiIcon } from '@elastic/eui';
import { AllowedChartOverrides, AllowedSettingsOverrides } from '@kbn/charts-plugin/common';
import { getOverridesFor } from '@kbn/chart-expressions-common';
import { type ChartSizeEvent, getOverridesFor } from '@kbn/chart-expressions-common';
import { DEFAULT_TRENDLINE_NAME } from '../../common/constants';
import { VisParams } from '../../common';
import { getPaletteService, getThemeService, getFormatService } from '../services';
import { getDataBoundsForPalette } from '../utils';
export const defaultColor = euiThemeVars.euiColorLightestShade;
export const defaultColor = euiThemeVars.euiColorEmptyShade;
function enhanceFieldFormat(serializedFieldFormat: SerializedFieldFormat | undefined) {
const formatId = serializedFieldFormat?.id || 'number';
@ -140,7 +139,6 @@ export interface MetricVisComponentProps {
config: Pick<VisParams, 'metric' | 'dimensions'>;
renderComplete: IInterpreterRenderHandlers['done'];
fireEvent: IInterpreterRenderHandlers['event'];
renderMode: RenderMode;
filterable: boolean;
overrides?: AllowedSettingsOverrides & AllowedChartOverrides;
}
@ -150,10 +148,11 @@ export const MetricVis = ({
config,
renderComplete,
fireEvent,
renderMode,
filterable,
overrides,
}: MetricVisComponentProps) => {
const grid = useRef<MetricSpec['data']>([[]]);
const onRenderChange = useCallback<RenderChangeListener>(
(isRendered) => {
if (isRendered) {
@ -163,6 +162,20 @@ export const MetricVis = ({
[renderComplete]
);
const onWillRender = useCallback(() => {
const maxTileSideLength = grid.current.length * grid.current[0].length > 1 ? 200 : 300;
const event: ChartSizeEvent = {
name: 'chartSize',
data: {
maxDimensions: {
y: { value: grid.current.length * maxTileSideLength, unit: 'pixels' },
x: { value: grid.current[0]?.length * maxTileSideLength, unit: 'pixels' },
},
},
};
fireEvent(event);
}, [fireEvent, grid]);
const [scrollChildHeight, setScrollChildHeight] = useState<string>('100%');
const scrollContainerRef = useRef<HTMLDivElement>(null);
const scrollDimensions = useResizeObserver(scrollContainerRef.current);
@ -289,28 +302,19 @@ export const MetricVis = ({
'settings'
) as Partial<SettingsProps>;
const grid: MetricSpec['data'] = [];
const newGrid: MetricSpec['data'] = [];
for (let i = 0; i < metricConfigs.length; i += maxCols) {
grid.push(metricConfigs.slice(i, i + maxCols));
newGrid.push(metricConfigs.slice(i, i + maxCols));
}
let pixelHeight;
let pixelWidth;
if (renderMode === 'edit') {
// In the editor, we constrain the maximum size of the tiles for aesthetic reasons
const maxTileSideLength = metricConfigs.flat().length > 1 ? 200 : 300;
pixelHeight = grid.length * maxTileSideLength;
pixelWidth = grid[0]?.length * maxTileSideLength;
}
grid.current = newGrid;
return (
<div
ref={scrollContainerRef}
css={css`
height: ${pixelHeight ? `${pixelHeight}px` : '100%'};
width: ${pixelWidth ? `${pixelWidth}px` : '100%'};
max-height: 100%;
max-width: 100%;
height: 100%;
width: 100%;
overflow-y: auto;
${useEuiScrollBar()}
`}
@ -322,6 +326,7 @@ export const MetricVis = ({
>
<Chart {...getOverridesFor(overrides, 'chart')}>
<Settings
onWillRender={onWillRender}
locale={i18n.getLocale()}
theme={[
{
@ -342,7 +347,7 @@ export const MetricVis = ({
filterable
? (events) => {
const colRef = breakdownByColumn ?? primaryMetricColumn;
const rowLength = grid[0].length;
const rowLength = grid.current[0].length;
events.forEach((event) => {
if (isMetricElementEvent(event)) {
const colIdx = data.columns.findIndex((col) => col === colRef);
@ -360,7 +365,7 @@ export const MetricVis = ({
}
{...settingsOverrides}
/>
<Metric id="metric" data={grid} />
<Metric id="metric" data={grid.current} />
</Chart>
</div>
</div>

View file

@ -101,7 +101,6 @@ export const getMetricVisRenderer = (
config={visConfig}
renderComplete={renderComplete}
fireEvent={handlers.event}
renderMode={handlers.getRenderMode()}
filterable={filterable}
overrides={overrides}
/>

View file

@ -22,6 +22,7 @@ import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { METRIC_TYPE } from '@kbn/analytics';
import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils';
import {
type ChartSizeEvent,
extractContainerType,
extractVisualizationType,
isOnAggBasedEditor,
@ -116,6 +117,18 @@ export const getPartitionVisRenderer: (
const hasOpenedOnAggBasedEditor = isOnAggBasedEditor(handlers.getExecutionContext());
const chartSizeEvent: ChartSizeEvent = {
name: 'chartSize',
data: {
maxDimensions: {
x: { value: 100, unit: 'percentage' },
y: { value: 100, unit: 'percentage' },
},
},
};
handlers.event(chartSizeEvent);
render(
<I18nProvider>
<KibanaThemeProvider theme$={core.theme.theme$}>

View file

@ -15,7 +15,11 @@ import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { VisualizationContainer } from '@kbn/visualizations-plugin/public';
import { ExpressionRenderDefinition } from '@kbn/expressions-plugin/common/expression_renderers';
import { METRIC_TYPE } from '@kbn/analytics';
import { extractContainerType, extractVisualizationType } from '@kbn/chart-expressions-common';
import {
type ChartSizeEvent,
extractContainerType,
extractVisualizationType,
} from '@kbn/chart-expressions-common';
import { ExpressionTagcloudRendererDependencies } from '../plugin';
import { TagcloudRendererConfig } from '../../common/types';
@ -66,6 +70,18 @@ export const tagcloudRenderer: (
handlers.done();
};
const chartSizeEvent: ChartSizeEvent = {
name: 'chartSize',
data: {
maxDimensions: {
x: { value: 100, unit: 'percentage' },
y: { value: 100, unit: 'percentage' },
},
},
};
handlers.event(chartSizeEvent);
const palettesRegistry = await plugins.charts.palettes.getPalettes();
let isDarkMode = false;
plugins.charts.theme.darkModeEnabled$

View file

@ -300,6 +300,18 @@ exports[`XYChart component it renders area 1`] = `
}
}
>
<div
css={
Object {
"backgroundColor": "#ffffff",
"display": "none",
"height": "100%",
"position": "absolute",
"width": "100%",
"zIndex": 1,
}
}
/>
<ContextProvider
value={
Object {
@ -1129,6 +1141,7 @@ exports[`XYChart component it renders area 1`] = `
onElementClick={[Function]}
onPointerUpdate={[Function]}
onRenderChange={[Function]}
onResize={[Function]}
rotation={0}
showLegend={false}
showLegendExtra={false}
@ -1836,6 +1849,18 @@ exports[`XYChart component it renders bar 1`] = `
}
}
>
<div
css={
Object {
"backgroundColor": "#ffffff",
"display": "none",
"height": "100%",
"position": "absolute",
"width": "100%",
"zIndex": 1,
}
}
/>
<ContextProvider
value={
Object {
@ -2665,6 +2690,7 @@ exports[`XYChart component it renders bar 1`] = `
onElementClick={[Function]}
onPointerUpdate={[Function]}
onRenderChange={[Function]}
onResize={[Function]}
rotation={0}
showLegend={false}
showLegendExtra={false}
@ -3372,6 +3398,18 @@ exports[`XYChart component it renders horizontal bar 1`] = `
}
}
>
<div
css={
Object {
"backgroundColor": "#ffffff",
"display": "none",
"height": "100%",
"position": "absolute",
"width": "100%",
"zIndex": 1,
}
}
/>
<ContextProvider
value={
Object {
@ -4201,6 +4239,7 @@ exports[`XYChart component it renders horizontal bar 1`] = `
onElementClick={[Function]}
onPointerUpdate={[Function]}
onRenderChange={[Function]}
onResize={[Function]}
rotation={90}
showLegend={false}
showLegendExtra={false}
@ -4908,6 +4947,18 @@ exports[`XYChart component it renders line 1`] = `
}
}
>
<div
css={
Object {
"backgroundColor": "#ffffff",
"display": "none",
"height": "100%",
"position": "absolute",
"width": "100%",
"zIndex": 1,
}
}
/>
<ContextProvider
value={
Object {
@ -5737,6 +5788,7 @@ exports[`XYChart component it renders line 1`] = `
onElementClick={[Function]}
onPointerUpdate={[Function]}
onRenderChange={[Function]}
onResize={[Function]}
rotation={0}
showLegend={false}
showLegendExtra={false}
@ -6444,6 +6496,18 @@ exports[`XYChart component it renders stacked area 1`] = `
}
}
>
<div
css={
Object {
"backgroundColor": "#ffffff",
"display": "none",
"height": "100%",
"position": "absolute",
"width": "100%",
"zIndex": 1,
}
}
/>
<ContextProvider
value={
Object {
@ -7273,6 +7337,7 @@ exports[`XYChart component it renders stacked area 1`] = `
onElementClick={[Function]}
onPointerUpdate={[Function]}
onRenderChange={[Function]}
onResize={[Function]}
rotation={0}
showLegend={false}
showLegendExtra={false}
@ -7980,6 +8045,18 @@ exports[`XYChart component it renders stacked bar 1`] = `
}
}
>
<div
css={
Object {
"backgroundColor": "#ffffff",
"display": "none",
"height": "100%",
"position": "absolute",
"width": "100%",
"zIndex": 1,
}
}
/>
<ContextProvider
value={
Object {
@ -8809,6 +8886,7 @@ exports[`XYChart component it renders stacked bar 1`] = `
onElementClick={[Function]}
onPointerUpdate={[Function]}
onRenderChange={[Function]}
onResize={[Function]}
rotation={0}
showLegend={false}
showLegendExtra={false}
@ -9516,6 +9594,18 @@ exports[`XYChart component it renders stacked horizontal bar 1`] = `
}
}
>
<div
css={
Object {
"backgroundColor": "#ffffff",
"display": "none",
"height": "100%",
"position": "absolute",
"width": "100%",
"zIndex": 1,
}
}
/>
<ContextProvider
value={
Object {
@ -10345,6 +10435,7 @@ exports[`XYChart component it renders stacked horizontal bar 1`] = `
onElementClick={[Function]}
onPointerUpdate={[Function]}
onRenderChange={[Function]}
onResize={[Function]}
rotation={90}
showLegend={false}
showLegendExtra={false}
@ -11052,6 +11143,18 @@ exports[`XYChart component split chart should render split chart if both, splitR
}
}
>
<div
css={
Object {
"backgroundColor": "#ffffff",
"display": "none",
"height": "100%",
"position": "absolute",
"width": "100%",
"zIndex": 1,
}
}
/>
<ContextProvider
value={
Object {
@ -11911,6 +12014,7 @@ exports[`XYChart component split chart should render split chart if both, splitR
onElementClick={[Function]}
onPointerUpdate={[Function]}
onRenderChange={[Function]}
onResize={[Function]}
rotation={0}
showLegend={false}
showLegendExtra={false}
@ -12832,6 +12936,18 @@ exports[`XYChart component split chart should render split chart if splitColumnA
}
}
>
<div
css={
Object {
"backgroundColor": "#ffffff",
"display": "none",
"height": "100%",
"position": "absolute",
"width": "100%",
"zIndex": 1,
}
}
/>
<ContextProvider
value={
Object {
@ -13685,6 +13801,7 @@ exports[`XYChart component split chart should render split chart if splitColumnA
onElementClick={[Function]}
onPointerUpdate={[Function]}
onRenderChange={[Function]}
onResize={[Function]}
rotation={0}
showLegend={false}
showLegendExtra={false}
@ -14599,6 +14716,18 @@ exports[`XYChart component split chart should render split chart if splitRowAcce
}
}
>
<div
css={
Object {
"backgroundColor": "#ffffff",
"display": "none",
"height": "100%",
"position": "absolute",
"width": "100%",
"zIndex": 1,
}
}
/>
<ContextProvider
value={
Object {
@ -15452,6 +15581,7 @@ exports[`XYChart component split chart should render split chart if splitRowAcce
onElementClick={[Function]}
onPointerUpdate={[Function]}
onRenderChange={[Function]}
onResize={[Function]}
rotation={0}
showLegend={false}
showLegendExtra={false}

View file

@ -129,6 +129,8 @@ describe('XYChart component', () => {
eventAnnotationService: eventAnnotationServiceMock,
renderComplete: jest.fn(),
timeFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',
setChartSize: jest.fn(),
shouldUseVeil: false,
};
});

View file

@ -52,7 +52,11 @@ import {
LegendSizeToPixels,
} from '@kbn/visualizations-plugin/common/constants';
import { PersistedState } from '@kbn/visualizations-plugin/public';
import { getOverridesFor } from '@kbn/chart-expressions-common';
import {
useSizeTransitionVeil,
getOverridesFor,
ChartSizeSpec,
} from '@kbn/chart-expressions-common';
import type {
FilterEvent,
BrushEvent,
@ -144,8 +148,11 @@ export type XYChartRenderProps = Omit<XYChartProps, 'canNavigateToLens'> & {
syncCursor: boolean;
eventAnnotationService: EventAnnotationServiceType;
renderComplete: () => void;
shouldUseVeil: boolean;
uiState?: PersistedState;
timeFormat: string;
setChartSize: (chartSizeSpec: ChartSizeSpec) => void;
shouldShowLegendAction?: (actionId: string) => boolean;
};
function nonNullable<T>(v: T): v is NonNullable<T> {
@ -199,12 +206,16 @@ export function XYChart({
onClickMultiValue,
layerCellValueActions,
onSelectRange,
setChartSize,
interactive = true,
syncColors,
syncTooltips,
syncCursor,
shouldUseVeil,
useLegacyTimeAxis,
renderComplete,
uiState,
timeFormat,
overrides,
@ -293,6 +304,34 @@ export function XYChart({
);
const dataLayers: CommonXYDataLayerConfig[] = filteredLayers.filter(isDataLayer);
const isTimeViz = isTimeChart(dataLayers);
const chartSizeSpec: ChartSizeSpec =
isTimeViz && !isHorizontalChart(dataLayers)
? {
aspectRatio: {
x: 16,
y: 9,
},
minDimensions: {
y: { value: 300, unit: 'pixels' },
x: { value: 100, unit: 'percentage' },
},
}
: {
maxDimensions: {
x: { value: 100, unit: 'percentage' },
y: { value: 100, unit: 'percentage' },
},
};
const { veil, onResize, containerRef } = useSizeTransitionVeil(
chartSizeSpec,
setChartSize,
shouldUseVeil
);
const formattedDatatables = useMemo(
() =>
getFormattedTablesByLayers(dataLayers, formatFactory, splitColumnAccessor, splitRowAccessor),
@ -371,8 +410,6 @@ export function XYChart({
(layer) => isDataLayer(layer) && layer.splitAccessors && layer.splitAccessors.length
);
const isTimeViz = isTimeChart(dataLayers);
const defaultXScaleType = isTimeViz ? XScaleTypes.TIME : XScaleTypes.ORDINAL;
const isHistogramViz = dataLayers.every((l) => l.isHistogram);
@ -711,7 +748,8 @@ export function XYChart({
);
return (
<div css={chartContainerStyle}>
<div ref={containerRef} css={chartContainerStyle}>
{veil}
{showLegend !== undefined && uiState && (
<LegendToggle
onClick={toggleLegend}
@ -785,6 +823,7 @@ export function XYChart({
/>
}
onRenderChange={onRenderChange}
onResize={onResize}
onPointerUpdate={syncCursor ? handleCursorUpdate : undefined}
externalPointerEvents={{
tooltip: { visible: syncTooltips, placement: Placement.Right },

View file

@ -26,7 +26,12 @@ import { FormatFactory } from '@kbn/field-formats-plugin/common';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils';
import { extractContainerType, extractVisualizationType } from '@kbn/chart-expressions-common';
import {
type ChartSizeEvent,
type ChartSizeSpec,
extractContainerType,
extractVisualizationType,
} from '@kbn/chart-expressions-common';
import type { getDataLayers } from '../helpers';
import { LayerTypes, SeriesTypes } from '../../common/constants';
@ -215,6 +220,10 @@ export const getXyChartRenderer = ({
const onClickMultiValue = (data: MultiFilterEvent['data']) => {
handlers.event({ name: 'multiFilter', data });
};
const setChartSize = (data: ChartSizeSpec) => {
const event: ChartSizeEvent = { name: 'chartSize', data };
handlers.event(event);
};
const layerCellValueActions = await getLayerCellValueActions(
getDataLayers(config.args.layers),
@ -275,8 +284,10 @@ export const getXyChartRenderer = ({
syncColors={config.syncColors}
syncTooltips={config.syncTooltips}
syncCursor={config.syncCursor}
shouldUseVeil={handlers.shouldUseSizeTransitionVeil()}
uiState={handlers.uiState as PersistedState}
renderComplete={renderComplete}
setChartSize={setChartSize}
/>
</div>
</I18nProvider>

View file

@ -287,6 +287,7 @@ export class Execution<
isSyncColorsEnabled: () => execution.params.syncColors!,
isSyncCursorEnabled: () => execution.params.syncCursor!,
isSyncTooltipsEnabled: () => execution.params.syncTooltips!,
shouldUseSizeTransitionVeil: () => execution.params.shouldUseSizeTransitionVeil!,
...execution.executor.context,
getExecutionContext: () => execution.params.executionContext,
};

View file

@ -72,6 +72,11 @@ export interface ExecutionContext<InspectorAdapters extends Adapters = Adapters>
*/
isSyncTooltipsEnabled?: () => boolean;
/**
* Returns whether or not to use the size transition veil when resizing visualizations.
*/
shouldUseSizeTransitionVeil?: () => boolean;
/**
* Contains the meta-data about the source of the expression.
*/

View file

@ -97,6 +97,9 @@ export interface IInterpreterRenderHandlers {
isSyncCursorEnabled(): boolean;
isSyncTooltipsEnabled(): boolean;
shouldUseSizeTransitionVeil(): boolean;
/**
* This uiState interface is actually `PersistedState` from the visualizations plugin,
* but expressions cannot know about vis or it creates a mess of circular dependencies.

View file

@ -156,6 +156,11 @@ export interface ExpressionExecutionParams {
syncTooltips?: boolean;
// if this is set to true, a veil will be shown when resizing visualizations in response
// to a chart resize event (see src/plugins/chart_expressions/common/chart_size_transition_veil.tsx).
// This should be only set to true if the client will be responding to the resize events
shouldUseSizeTransitionVeil?: boolean;
inspectorAdapters?: Adapters;
executionContext?: KibanaExecutionContext;

View file

@ -60,6 +60,7 @@ export class ExpressionLoader {
syncColors: params?.syncColors,
syncTooltips: params?.syncTooltips,
syncCursor: params?.syncCursor,
shouldUseSizeTransitionVeil: params?.shouldUseSizeTransitionVeil,
hasCompatibleActions: params?.hasCompatibleActions,
getCompatibleCellValueActions: params?.getCompatibleCellValueActions,
executionContext: params?.executionContext,
@ -148,6 +149,7 @@ export class ExpressionLoader {
syncColors: params.syncColors,
syncCursor: params?.syncCursor,
syncTooltips: params.syncTooltips,
shouldUseSizeTransitionVeil: params.shouldUseSizeTransitionVeil,
executionContext: params.executionContext,
partial: params.partial,
throttle: params.throttle,

View file

@ -33,6 +33,7 @@ export interface ExpressionRenderHandlerParams {
syncCursor?: boolean;
syncTooltips?: boolean;
interactive?: boolean;
shouldUseSizeTransitionVeil?: boolean;
hasCompatibleActions?: (event: ExpressionRendererEvent) => Promise<boolean>;
getCompatibleCellValueActions?: (data: object[]) => Promise<unknown[]>;
executionContext?: KibanaExecutionContext;
@ -62,6 +63,7 @@ export class ExpressionRenderHandler {
syncColors,
syncTooltips,
syncCursor,
shouldUseSizeTransitionVeil,
interactive,
hasCompatibleActions = async () => false,
getCompatibleCellValueActions = async () => [],
@ -113,6 +115,9 @@ export class ExpressionRenderHandler {
isSyncCursorEnabled: () => {
return syncCursor || true;
},
shouldUseSizeTransitionVeil: () => {
return Boolean(shouldUseSizeTransitionVeil);
},
isInteractive: () => {
return interactive ?? true;
},

View file

@ -52,6 +52,10 @@ export interface IExpressionLoaderParams {
syncColors?: boolean;
syncCursor?: boolean;
syncTooltips?: boolean;
// if this is set to true, a veil will be shown when resizing visualizations in response
// to a chart resize event (see src/plugins/chart_expressions/common/chart_size_transition_veil.tsx).
// This should be only set to true if the client will be responding to the resize events
shouldUseSizeTransitionVeil?: boolean;
hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions'];
getCompatibleCellValueActions?: ExpressionRenderHandlerParams['getCompatibleCellValueActions'];
executionContext?: KibanaExecutionContext;

View file

@ -18,6 +18,7 @@ export const defaultHandlers: IInterpreterRenderHandlers = {
isSyncColorsEnabled: () => false,
isSyncCursorEnabled: () => true,
isSyncTooltipsEnabled: () => false,
shouldUseSizeTransitionVeil: () => false,
isInteractive: () => true,
getExecutionContext: () => undefined,
done: action('done'),

View file

@ -40,6 +40,7 @@ import {
import type { RenderMode } from '@kbn/expressions-plugin/common';
import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/public';
import { mapAndFlattenFilters } from '@kbn/data-plugin/public';
import { isChartSizeEvent } from '@kbn/chart-expressions-common';
import { isFallbackDataView } from '../visualize_app/utils';
import { VisualizationMissedSavedObjectError } from '../components/visualization_missed_saved_object_error';
import VisualizationError from '../components/visualization_error';
@ -477,6 +478,10 @@ export class VisualizeEmbeddable
this.handler.events$
.pipe(
mergeMap(async (event) => {
// Visualize doesn't respond to sizing events, so ignore.
if (isChartSizeEvent(event)) {
return;
}
if (!this.input.disableTriggers) {
const triggerId = get(VIS_EVENT_TO_TRIGGER, event.name, VIS_EVENT_TO_TRIGGER.filter);
let context;

View file

@ -66,6 +66,7 @@
"@kbn/search-response-warnings",
"@kbn/logging",
"@kbn/content-management-table-list-view-common",
"@kbn/chart-expressions-common",
"@kbn/shared-ux-utility"
],
"exclude": [

View file

@ -17,6 +17,7 @@ export const defaultHandlers: RendererHandlers = {
isSyncColorsEnabled: () => false,
isSyncCursorEnabled: () => true,
isSyncTooltipsEnabled: () => false,
shouldUseSizeTransitionVeil: () => false,
isInteractive: () => true,
onComplete: (fn) => undefined,
onEmbeddableDestroyed: action('onEmbeddableDestroyed'),

View file

@ -29,6 +29,7 @@ export const createBaseHandlers = (): IInterpreterRenderHandlers => ({
isSyncColorsEnabled: () => false,
isSyncTooltipsEnabled: () => false,
isSyncCursorEnabled: () => true,
shouldUseSizeTransitionVeil: () => false,
isInteractive: () => true,
getExecutionContext: () => undefined,
});

View file

@ -27,6 +27,7 @@ import type { Datatable } from '@kbn/expressions-plugin/public';
import { DropIllustration } from '@kbn/chart-icons';
import { DragDrop, useDragDropContext, DragDropIdentifier } from '@kbn/dom-drag-drop';
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
import { ChartSizeSpec, isChartSizeEvent } from '@kbn/chart-expressions-common';
import { trackUiCounterEvents } from '../../../lens_ui_telemetry';
import { getSearchWarningMessages } from '../../../utils';
import {
@ -43,6 +44,7 @@ import {
UserMessagesGetter,
AddUserMessages,
isMessageRemovable,
VisualizationDisplayOptions,
} from '../../../types';
import { switchToSuggestion } from '../suggestion_helpers';
import { buildExpression } from '../expression_helpers';
@ -413,6 +415,8 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
}
}, [expressionExists, localState.expressionToRender]);
const [chartSizeSpec, setChartSize] = useState<ChartSizeSpec | undefined>();
const onEvent = useCallback(
(event: ExpressionRendererEvent) => {
if (!plugins.uiActions) {
@ -443,10 +447,15 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
})
);
}
if (isChartSizeEvent(event)) {
setChartSize(event.data);
}
},
[plugins.data.datatableUtilities, plugins.uiActions, activeVisualization, dispatchLens]
);
const displayOptions = activeVisualization?.getDisplayOptions?.();
const hasCompatibleActions = useCallback(
async (event: ExpressionRendererEvent) => {
if (!plugins.uiActions) {
@ -478,6 +487,10 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
const IS_DARK_THEME: boolean = useObservable(core.theme.theme$, { darkMode: false }).darkMode;
const renderDragDropPrompt = () => {
if (chartSizeSpec) {
setChartSize(undefined);
}
return (
<EuiText
className={classNames('lnsWorkspacePanel__emptyContent')}
@ -532,6 +545,10 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
};
const renderApplyChangesPrompt = () => {
if (chartSizeSpec) {
setChartSize(undefined);
}
const applyChangesString = i18n.translate('xpack.lens.editorFrame.applyChanges', {
defaultMessage: 'Apply changes',
});
@ -590,6 +607,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
onComponentRendered={() => {
visualizationRenderStartTime.current = performance.now();
}}
displayOptions={displayOptions}
/>
);
};
@ -639,7 +657,6 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
return (
<WorkspacePanelWrapper
framePublicAPI={framePublicAPI}
visualizationState={visualization.state}
visualizationId={visualization.activeId}
datasourceStates={datasourceStates}
datasourceMap={datasourceMap}
@ -647,6 +664,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
isFullscreen={isFullscreen}
lensInspector={lensInspector}
getUserMessages={getUserMessages}
displayOptions={chartSizeSpec}
>
{renderWorkspace()}
</WorkspacePanelWrapper>
@ -686,6 +704,7 @@ export const VisualizationWrapper = ({
onRender$,
onData$,
onComponentRendered,
displayOptions,
}: {
expression: string | null | undefined;
lensInspector: LensInspector;
@ -699,6 +718,7 @@ export const VisualizationWrapper = ({
onRender$: () => void;
onData$: (data: unknown, adapters?: Partial<DefaultInspectorAdapters>) => void;
onComponentRendered: () => void;
displayOptions: VisualizationDisplayOptions | undefined;
}) => {
useEffect(() => {
onComponentRendered();
@ -751,7 +771,7 @@ export const VisualizationWrapper = ({
>
<ExpressionRendererComponent
className="lnsExpressionRenderer__component"
padding="m"
padding={displayOptions?.noPadding ? undefined : 'm'}
expression={expression!}
searchContext={searchContext}
searchSessionId={searchSessionId}

View file

@ -26,7 +26,7 @@
background: $euiColorEmptyShade;
height: 100%;
> * {
&>* {
flex: 1 1 100%;
display: flex;
align-items: center;
@ -37,6 +37,7 @@
&.lnsWorkspacePanelWrapper--fullscreen {
margin-bottom: 0;
.lnsWorkspacePanelWrapper__pageContentBody {
box-shadow: none;
}
@ -45,8 +46,6 @@
}
.lnsWorkspacePanel__dragDrop {
border: $euiBorderWidthThin solid transparent;
&.domDragDrop-isDropTarget {
p {
transition: filter $euiAnimSpeedFast ease-in-out;
@ -120,9 +119,7 @@
// Hard-coded px values OK (@cchaos)
// sass-lint:disable-block indentation
filter:
drop-shadow(0 6px 12px transparentize($euiShadowColor, .8))
drop-shadow(0 4px 4px transparentize($euiShadowColor, .8))
drop-shadow(0 2px 2px transparentize($euiShadowColor, .8));
drop-shadow(0 6px 12px transparentize($euiShadowColor, .8)) drop-shadow(0 4px 4px transparentize($euiShadowColor, .8)) drop-shadow(0 2px 2px transparentize($euiShadowColor, .8));
}
.lnsDropIllustration__adjustFill {
@ -134,20 +131,51 @@
}
@keyframes lnsWorkspacePanel__illustrationPulseArrow {
0% { transform: translateY(0%); }
65% { transform: translateY(0%); }
72% { transform: translateY(10%); }
79% { transform: translateY(7%); }
86% { transform: translateY(10%); }
95% { transform: translateY(0); }
0% {
transform: translateY(0%);
}
65% {
transform: translateY(0%);
}
72% {
transform: translateY(10%);
}
79% {
transform: translateY(7%);
}
86% {
transform: translateY(10%);
}
95% {
transform: translateY(0);
}
}
@keyframes lnsWorkspacePanel__illustrationPulseContinuous {
0% { transform: translateY(10%); }
25% { transform: translateY(15%); }
50% { transform: translateY(10%); }
75% { transform: translateY(15%); }
100% { transform: translateY(10%); }
0% {
transform: translateY(10%);
}
25% {
transform: translateY(15%);
}
50% {
transform: translateY(10%);
}
75% {
transform: translateY(15%);
}
100% {
transform: translateY(10%);
}
}
.lnsVisualizationToolbar--fixed {
@ -155,4 +183,4 @@
width: 100%;
z-index: 1;
background-color: $euiColorLightestShade;
}
}

View file

@ -34,7 +34,6 @@ describe('workspace_panel_wrapper', () => {
<>
<WorkspacePanelWrapper
framePublicAPI={mockFrameAPI}
visualizationState={{ internalState: 123 }}
visualizationId="myVis"
visualizationMap={{
myVis: { ...mockVisualization, ToolbarComponent: ToolbarComponentMock },
@ -45,6 +44,7 @@ describe('workspace_panel_wrapper', () => {
lensInspector={{} as unknown as LensInspector}
getUserMessages={() => []}
children={<span />}
displayOptions={undefined}
{...propsOverrides}
/>
<SettingsMenu

View file

@ -11,6 +11,9 @@ import React, { useCallback } from 'react';
import { EuiPageTemplate, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
import classNames from 'classnames';
import { FormattedMessage } from '@kbn/i18n-react';
import { ChartSizeSpec } from '@kbn/chart-expressions-common';
import { ChartSizeUnit } from '@kbn/chart-expressions-common/types';
import { Interpolation, Theme, css } from '@emotion/react';
import {
DatasourceMap,
FramePublicAPI,
@ -25,22 +28,20 @@ import {
useLensDispatch,
updateVisualizationState,
DatasourceStates,
VisualizationState,
useLensSelector,
selectChangesApplied,
applyChanges,
selectAutoApplyEnabled,
selectVisualizationState,
} from '../../../state_management';
import { WorkspaceTitle } from './title';
import { LensInspector } from '../../../lens_inspector_service';
import { WorkspaceTitle } from './title';
export const AUTO_APPLY_DISABLED_STORAGE_KEY = 'autoApplyDisabled';
export interface WorkspacePanelWrapperProps {
children: React.ReactNode | React.ReactNode[];
framePublicAPI: FramePublicAPI;
visualizationState: VisualizationState['state'];
visualizationMap: VisualizationMap;
visualizationId: string | null;
datasourceMap: DatasourceMap;
@ -48,8 +49,29 @@ export interface WorkspacePanelWrapperProps {
isFullscreen: boolean;
lensInspector: LensInspector;
getUserMessages: UserMessagesGetter;
displayOptions: ChartSizeSpec | undefined;
}
const unitToCSSUnit: Record<ChartSizeUnit, string> = {
pixels: 'px',
percentage: '%',
};
const getAspectRatioStyles = ({ x, y }: { x: number; y: number }) => {
return {
aspectRatio: `${x}/${y}`,
...(y > x
? {
height: '100%',
width: 'auto',
}
: {
height: 'auto',
width: '100%',
}),
};
};
export function VisualizationToolbar(props: {
activeVisualization: Visualization | null;
framePublicAPI: FramePublicAPI;
@ -98,12 +120,12 @@ export function VisualizationToolbar(props: {
export function WorkspacePanelWrapper({
children,
framePublicAPI,
visualizationState,
visualizationId,
visualizationMap,
datasourceMap,
isFullscreen,
getUserMessages,
displayOptions,
}: WorkspacePanelWrapperProps) {
const dispatchLens = useLensDispatch();
@ -113,6 +135,34 @@ export function WorkspacePanelWrapper({
const activeVisualization = visualizationId ? visualizationMap[visualizationId] : null;
const userMessages = getUserMessages('toolbar');
const aspectRatio = displayOptions?.aspectRatio;
const maxDimensions = displayOptions?.maxDimensions;
const minDimensions = displayOptions?.minDimensions;
let visDimensionsCSS: Interpolation<Theme> = {};
if (aspectRatio) {
visDimensionsCSS = getAspectRatioStyles(aspectRatio ?? maxDimensions);
}
if (maxDimensions) {
visDimensionsCSS.maxWidth = maxDimensions.x
? `${maxDimensions.x.value}${unitToCSSUnit[maxDimensions.x.unit]}`
: '';
visDimensionsCSS.maxHeight = maxDimensions.y
? `${maxDimensions.y.value}${unitToCSSUnit[maxDimensions.y.unit]}`
: '';
}
if (minDimensions) {
visDimensionsCSS.minWidth = minDimensions.x
? `${minDimensions.x.value}${unitToCSSUnit[minDimensions.x.unit]}`
: '';
visDimensionsCSS.minHeight = minDimensions.y
? `${minDimensions.y.value}${unitToCSSUnit[minDimensions.y.unit]}`
: '';
}
return (
<EuiPageTemplate
direction="column"
@ -190,6 +240,7 @@ export function WorkspacePanelWrapper({
</EuiFlexGroup>
</EuiPageTemplate.Section>
)}
<EuiPageTemplate.Section
grow={true}
paddingSize="none"
@ -199,10 +250,32 @@ export function WorkspacePanelWrapper({
className={classNames('lnsWorkspacePanelWrapper stretch-for-sharing', {
'lnsWorkspacePanelWrapper--fullscreen': isFullscreen,
})}
css={{ height: '100%' }}
color="transparent"
>
<WorkspaceTitle />
{children}
<EuiFlexGroup
gutterSize="none"
alignItems="center"
justifyContent="center"
direction="column"
css={css`
height: 100%;
`}
>
<EuiFlexItem
data-test-subj="lnsWorkspacePanelWrapper__innerContent"
grow={false}
css={{
flexGrow: 0,
height: '100%',
width: '100%',
...visDimensionsCSS,
}}
>
<WorkspaceTitle />
{children}
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageTemplate.Section>
</EuiPageTemplate>
);

View file

@ -92,6 +92,7 @@ export function ExpressionWrapper({
syncTooltips={syncTooltips}
syncCursor={syncCursor}
executionContext={executionContext}
shouldUseSizeTransitionVeil={true}
renderError={(errorMessage, error) => {
const messages = getOriginalRequestErrorMessages(error || null);
addUserMessages(messages);

View file

@ -42,6 +42,7 @@ import { CellValueContext } from '@kbn/embeddable-plugin/public';
import { EventAnnotationGroupConfig } from '@kbn/event-annotation-common';
import type { DraggingIdentifier, DragDropIdentifier, DropType } from '@kbn/dom-drag-drop';
import type { AccessorConfig } from '@kbn/visualization-ui-components';
import type { ChartSizeEvent } from '@kbn/chart-expressions-common';
import type { DateRange, LayerType, SortingHint } from '../common/types';
import type {
LensSortActionData,
@ -1391,6 +1392,7 @@ export interface ILensInterpreterRenderHandlers extends IInterpreterRenderHandle
| BrushTriggerEvent
| LensEditEvent<LensEditSupportedActions>
| LensTableRowContextMenuEvent
| ChartSizeEvent
) => void;
}

View file

@ -18,6 +18,7 @@ import type {
IInterpreterRenderHandlers,
} from '@kbn/expressions-plugin/common';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { ChartSizeEvent } from '@kbn/chart-expressions-common';
import { trackUiCounterEvents } from '../../lens_ui_telemetry';
import { DatatableComponent } from './components/table_basic';
@ -103,6 +104,18 @@ export const getDatatableRenderer = (dependencies: {
handlers.done();
};
const chartSizeEvent: ChartSizeEvent = {
name: 'chartSize',
data: {
maxDimensions: {
x: { value: 100, unit: 'percentage' },
y: { value: 100, unit: 'percentage' },
},
},
};
handlers.event(chartSizeEvent);
// An entry for each table row, whether it has any actions attached to
// ROW_CLICK_TRIGGER trigger.
let rowHasRowClickTriggerActions: boolean[] = [];

View file

@ -143,7 +143,7 @@ describe('metric visualization', () => {
).toMatchInlineSnapshot(`
Array [
Object {
"color": "#f5f7fa",
"color": "#ffffff",
"columnId": "metric-col-id",
"triggerIconType": "color",
},
@ -727,7 +727,7 @@ describe('metric visualization', () => {
datasourceLayers
) as ExpressionAstExpression
).chain[1].arguments.color[0]
).toBe(euiLightVars.euiColorLightestShade);
).toBe(euiLightVars.euiColorEmptyShade);
expect(
(
@ -741,7 +741,7 @@ describe('metric visualization', () => {
datasourceLayers
) as ExpressionAstExpression
).chain[1].arguments.color[0]
).toBe(euiLightVars.euiColorLightestShade);
).toBe(euiLightVars.euiColorEmptyShade);
// this case isn't currently relevant because other parts of the code don't allow showBar to be
// set when there isn't a max dimension but this test covers the branch anyhow
@ -757,7 +757,7 @@ describe('metric visualization', () => {
datasourceLayers
) as ExpressionAstExpression
).chain[1].arguments.color[0]
).toEqual(euiThemeVars.euiColorLightestShade);
).toEqual(euiThemeVars.euiColorEmptyShade);
});
});

View file

@ -45,7 +45,7 @@ export const showingBar = (
export const getDefaultColor = (state: MetricVisualizationState, isMetricNumeric?: boolean) => {
return showingBar(state) && isMetricNumeric
? euiLightVars.euiColorPrimary
: euiThemeVars.euiColorLightestShade;
: euiThemeVars.euiColorEmptyShade;
};
export interface MetricVisualizationState {

View file

@ -13,6 +13,7 @@ import { METRIC_TYPE } from '@kbn/analytics';
import type { CoreSetup, CoreStart } from '@kbn/core/public';
import type { FileLayer } from '@elastic/ems-client';
import type { KibanaExecutionContext } from '@kbn/core-execution-context-common';
import { ChartSizeEvent } from '@kbn/chart-expressions-common';
import type { MapsPluginStartDependencies } from '../../plugin';
import type { ChoroplethChartProps } from './types';
import type { MapEmbeddableInput, MapEmbeddableOutput } from '../../embeddable';
@ -92,6 +93,18 @@ export function getExpressionRenderer(coreSetup: CoreSetup<MapsPluginStartDepend
handlers.done();
};
const chartSizeEvent: ChartSizeEvent = {
name: 'chartSize',
data: {
maxDimensions: {
x: { value: 100, unit: 'percentage' },
y: { value: 100, unit: 'percentage' },
},
},
};
handlers.event(chartSizeEvent);
ReactDOM.render(
<ChoroplethChart
{...config}

View file

@ -74,6 +74,7 @@
"@kbn/content-management-table-list-view",
"@kbn/serverless",
"@kbn/logging",
"@kbn/chart-expressions-common",
"@kbn/search-errors",
"@kbn/search-response-warnings",
"@kbn/calculate-width-from-char-count",

View file

@ -72,7 +72,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
subtitle: undefined,
extraText: 'Maximum of bytes 19,986',
value: '5,727.322',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingTrendline: false,
showingBar: false,
@ -138,7 +138,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
subtitle: undefined,
extraText: '',
value: '5,727.322',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingTrendline: false,
showingBar: false,

View file

@ -80,8 +80,9 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext
loadTestFile(require.resolve('./inspector')); // 1m 19s
loadTestFile(require.resolve('./error_handling')); // 1m 8s
loadTestFile(require.resolve('./lens_tagging')); // 1m 9s
loadTestFile(require.resolve('./workspace_size'));
// keep these last in the group in this order because they are messing with the default saved objects
loadTestFile(require.resolve('./lens_reporting')); // 3m
// keep these two last in the group in this order because they are messing with the default saved objects
loadTestFile(require.resolve('./rollup')); // 1m 30s
loadTestFile(require.resolve('./no_data')); // 36s
});

View file

@ -127,8 +127,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
subtitle: 'Average of bytes',
extraText: 'Average of bytes 19,755',
value: '19,755',
color: 'rgba(245, 247, 250, 1)',
trendlineColor: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: 'rgba(255, 255, 255, 1)',
showingTrendline: true,
showingBar: false,
},
@ -137,8 +137,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
subtitle: 'Average of bytes',
extraText: 'Average of bytes 18,994',
value: '18,994',
color: 'rgba(245, 247, 250, 1)',
trendlineColor: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: 'rgba(255, 255, 255, 1)',
showingTrendline: true,
showingBar: false,
},
@ -147,8 +147,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
subtitle: 'Average of bytes',
extraText: 'Average of bytes 17,246',
value: '17,246',
color: 'rgba(245, 247, 250, 1)',
trendlineColor: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: 'rgba(255, 255, 255, 1)',
showingTrendline: true,
showingBar: false,
},
@ -157,8 +157,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
subtitle: 'Average of bytes',
extraText: 'Average of bytes 15,687',
value: '15,687',
color: 'rgba(245, 247, 250, 1)',
trendlineColor: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: 'rgba(255, 255, 255, 1)',
showingTrendline: true,
showingBar: false,
},
@ -167,8 +167,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
subtitle: 'Average of bytes',
extraText: 'Average of bytes 15,614.333',
value: '15,614.333',
color: 'rgba(245, 247, 250, 1)',
trendlineColor: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: 'rgba(255, 255, 255, 1)',
showingTrendline: true,
showingBar: false,
},
@ -177,8 +177,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
subtitle: 'Average of bytes',
extraText: 'Average of bytes 5,722.775',
value: '5,722.775',
color: 'rgba(245, 247, 250, 1)',
trendlineColor: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: 'rgba(255, 255, 255, 1)',
showingTrendline: true,
showingBar: false,
},

View file

@ -0,0 +1,254 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['visualize', 'lens', 'common']);
const browser = getService('browser');
const testSubjects = getService('testSubjects');
const retry = getService('retry');
const log = getService('log');
describe('lens workspace size', () => {
let originalWindowSize: {
height: number;
width: number;
x: number;
y: number;
};
const DEFAULT_WINDOW_SIZE = [1400, 900];
before(async () => {
originalWindowSize = await browser.getWindowSize();
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');
await PageObjects.lens.goToTimeRange();
await PageObjects.lens.configureDimension({
dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension',
operation: 'average',
field: 'bytes',
});
});
beforeEach(async () => {
await browser.setWindowSize(DEFAULT_WINDOW_SIZE[0], DEFAULT_WINDOW_SIZE[1]);
});
after(async () => {
await browser.setWindowSize(originalWindowSize.width, originalWindowSize.height);
});
const pxToN = (pixels: string) => Number(pixels.substring(0, pixels.length - 2));
const assertWorkspaceDimensions = async (expectedWidth: string, expectedHeight: string) => {
const tolerance = 1;
await retry.try(async () => {
const { width, height } = await PageObjects.lens.getWorkspaceVisContainerDimensions();
expect(pxToN(width)).to.within(
pxToN(expectedWidth) - tolerance,
pxToN(expectedWidth) + tolerance
);
expect(pxToN(height)).to.within(
pxToN(expectedHeight) - tolerance,
pxToN(expectedHeight) + tolerance
);
});
};
const assertWorkspaceAspectRatio = async (expectedRatio: number) => {
const tolerance = 0.05;
await retry.try(async () => {
const { width, height } = await PageObjects.lens.getWorkspaceVisContainerDimensions();
expect(pxToN(width) / pxToN(height)).to.within(
expectedRatio - tolerance,
expectedRatio + tolerance
);
});
};
const assertWorkspaceStyles = async (expectedStyles: {
aspectRatio: string;
minHeight: string;
minWidth: string;
maxHeight: string;
maxWidth: string;
}) => {
const actualStyles = await PageObjects.lens.getWorkspaceVisContainerStyles();
expect(actualStyles).to.eql(expectedStyles);
};
const VERTICAL_16_9 = 16 / 9;
const outerWorkspaceDimensions = { width: 690, height: 400 };
const UNCONSTRAINED = outerWorkspaceDimensions.width / outerWorkspaceDimensions.height;
it('workspace size recovers from special vis types', async () => {
/**
* This list is specifically designed to test dimension transitions.
*
* I have attempted to order the vis types to maximize the number of transitions.
*
* Excluding XY charts since they are tested separately.
*/
const visTypes: Array<{
id: string;
searchText?: string;
expectedHeight?: string;
expectedWidth?: string;
aspectRatio?: number;
}> = [
{
id: 'lnsMetric',
expectedWidth: '300px',
expectedHeight: '300px',
},
{ id: 'lnsDatatable', aspectRatio: UNCONSTRAINED },
{
id: 'lnsMetric',
expectedWidth: '300px',
expectedHeight: '300px',
},
{ id: 'lnsLegacyMetric', aspectRatio: UNCONSTRAINED },
{
id: 'lnsMetric',
expectedWidth: '300px',
expectedHeight: '300px',
},
{ id: 'donut', aspectRatio: UNCONSTRAINED },
{
id: 'lnsMetric',
expectedWidth: '300px',
expectedHeight: '300px',
},
{ id: 'mosaic', aspectRatio: UNCONSTRAINED },
{
id: 'lnsMetric',
expectedWidth: '300px',
expectedHeight: '300px',
},
{ id: 'pie', aspectRatio: UNCONSTRAINED },
{
id: 'lnsMetric',
expectedWidth: '300px',
expectedHeight: '300px',
},
{ id: 'treemap', aspectRatio: UNCONSTRAINED },
{
id: 'lnsMetric',
expectedWidth: '300px',
expectedHeight: '300px',
},
{ id: 'waffle', aspectRatio: UNCONSTRAINED },
// { id: 'heatmap', ...UNCONSTRAINED }, // heatmap blocks render unless it's given two dimensions. This stops the expression renderer from requesting new dimensions.
// { id: 'lnsChoropleth', ...UNCONSTRAINED }, // choropleth currently erases all dimensions
// { id: 'lnsTagcloud', ...UNCONSTRAINED }, // tag cloud currently erases all dimensions
];
while (visTypes.length) {
const vis = visTypes.pop()!;
await retry.try(async () => {
await PageObjects.lens.switchToVisualization(vis.id, vis.searchText);
});
log.debug(`Testing ${vis.id}... expecting ${vis.expectedWidth}x${vis.expectedHeight}`);
if (vis.aspectRatio) {
await assertWorkspaceAspectRatio(vis.aspectRatio);
} else {
await assertWorkspaceDimensions(vis.expectedWidth!, vis.expectedHeight!);
}
}
});
it('metric size (absolute pixels)', async () => {
await retry.try(async () => {
await PageObjects.lens.switchToVisualization('lnsMetric');
});
await assertWorkspaceDimensions('300px', '300px');
await PageObjects.lens.configureDimension({
dimension: 'lnsMetric_breakdownByDimensionPanel > lns-empty-dimension',
operation: 'terms',
field: 'ip',
});
await assertWorkspaceDimensions('600px', '400px');
await PageObjects.lens.openDimensionEditor('lnsMetric_breakdownByDimensionPanel');
await testSubjects.setValue('lnsMetric_max_cols', '2');
await assertWorkspaceDimensions('400px', '400px');
});
it('gauge size (absolute pixels)', async () => {
await retry.try(async () => {
await PageObjects.lens.switchToVisualization('horizontalBullet', 'gauge');
});
await assertWorkspaceDimensions('600px', '300px');
await retry.try(async () => {
await PageObjects.lens.switchToVisualization('verticalBullet', 'gauge');
});
// this height is below the requested 600px
// that is because the window size isn't large enough to fit the requested dimensions
// and the chart is forced to shrink.
//
// this is a good thing because it makes this a test case for that scenario
await assertWorkspaceDimensions('300px', '400px');
});
it('XY chart size', async () => {
// XY charts should have 100% width and 100% height unless they are a vertical chart with a time dimension
await retry.try(async () => {
// not important that this is specifically a line chart
await PageObjects.lens.switchToVisualization('line');
});
await assertWorkspaceStyles({
aspectRatio: 'auto',
minHeight: 'auto',
minWidth: 'auto',
maxHeight: '100%',
maxWidth: '100%',
});
await PageObjects.lens.configureDimension({
dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension',
operation: 'date_histogram',
field: '@timestamp',
});
await assertWorkspaceStyles({
aspectRatio: '16 / 9',
minHeight: '300px',
minWidth: '100%',
maxHeight: 'none',
maxWidth: 'none',
});
await assertWorkspaceAspectRatio(VERTICAL_16_9);
await retry.try(async () => {
await PageObjects.lens.switchToVisualization('bar_horizontal_stacked');
});
await assertWorkspaceAspectRatio(UNCONSTRAINED);
});
});
}

View file

@ -48,7 +48,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: undefined,
extraText: '',
value: '140.05%',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: true,
showingTrendline: false,
@ -80,7 +80,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: undefined,
extraText: '',
value: '131,040,360.81%',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: true,
showingTrendline: false,
@ -112,7 +112,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: undefined,
extraText: '',
value: '14.37%',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: true,
showingTrendline: false,
@ -156,7 +156,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '65,047,486.03',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: true,
showingTrendline: false,
@ -166,7 +166,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '66,144,823.35',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: true,
showingTrendline: false,
@ -176,7 +176,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '65,933,477.76',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: true,
showingTrendline: false,
@ -186,7 +186,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '65,157,898.23',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: true,
showingTrendline: false,
@ -196,7 +196,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '65,365,950.93',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: true,
showingTrendline: false,

View file

@ -49,7 +49,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: undefined,
extraText: '',
value: '14,005',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: false,
showingTrendline: false,
@ -80,7 +80,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: undefined,
extraText: '',
value: '13,104,036,080.615',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: false,
showingTrendline: false,
@ -111,7 +111,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: undefined,
extraText: '',
value: '1,437',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: false,
showingTrendline: false,
@ -166,7 +166,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '13,228,964,670.613',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: false,
showingTrendline: false,
@ -176,7 +176,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '13,186,695,551.251',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: false,
showingTrendline: false,
@ -186,7 +186,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '13,073,190,186.423',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: false,
showingTrendline: false,
@ -196,7 +196,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '13,031,579,645.108',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: false,
showingTrendline: false,
@ -206,7 +206,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '13,009,497,206.823',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
trendlineColor: undefined,
showingBar: false,
showingTrendline: false,

View file

@ -1947,5 +1947,28 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
await this.closeDimensionEditor();
},
async getWorkspaceVisContainerDimensions() {
const visContainer = await testSubjects.find('lnsWorkspacePanelWrapper__innerContent');
const [width, height] = await Promise.all([
visContainer.getComputedStyle('width'),
visContainer.getComputedStyle('height'),
]);
return { width, height };
},
async getWorkspaceVisContainerStyles() {
const visContainer = await testSubjects.find('lnsWorkspacePanelWrapper__innerContent');
const [maxWidth, maxHeight, minWidth, minHeight, aspectRatio] = await Promise.all([
visContainer.getComputedStyle('max-width'),
visContainer.getComputedStyle('max-height'),
visContainer.getComputedStyle('min-width'),
visContainer.getComputedStyle('min-height'),
visContainer.getComputedStyle('aspect-ratio'),
]);
return { maxWidth, maxHeight, minWidth, minHeight, aspectRatio };
},
});
}

View file

@ -50,7 +50,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: undefined,
extraText: '',
value: '140.05%',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
showingBar: true,
showingTrendline: false,
},
@ -77,7 +77,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: undefined,
extraText: '',
value: '131,040,360.81%',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
showingBar: true,
showingTrendline: false,
},
@ -105,7 +105,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: undefined,
extraText: '',
value: '14.37%',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
showingBar: true,
showingTrendline: false,
},
@ -133,7 +133,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '13,228,964,670.613',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
showingTrendline: false,
showingBar: true,
},
@ -142,7 +142,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '13,186,695,551.251',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
showingTrendline: false,
showingBar: true,
},
@ -151,7 +151,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '13,073,190,186.423',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
showingTrendline: false,
showingBar: true,
},
@ -160,7 +160,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '13,031,579,645.108',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
showingTrendline: false,
showingBar: true,
},
@ -169,7 +169,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: 'Average machine.ram',
extraText: '',
value: '13,009,497,206.823',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
showingTrendline: false,
showingBar: true,
},

View file

@ -46,7 +46,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: undefined,
extraText: '',
value: '14,005',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
showingBar: false,
showingTrendline: false,
},
@ -72,7 +72,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: undefined,
extraText: '',
value: '13,104,036,080.615',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
showingBar: false,
showingTrendline: false,
},
@ -99,7 +99,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
subtitle: undefined,
extraText: '',
value: '1,437',
color: 'rgba(245, 247, 250, 1)',
color: 'rgba(255, 255, 255, 1)',
showingBar: false,
showingTrendline: false,
},