[Lens] Switch to unified metric renderer (#126019)

* Switch to unified metric renderer

* Fix tests

* Fix snapshots

* Remove unused translation

* Fix tests

* Fix font size mapping

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Uladzislau Lasitsa 2022-03-09 13:53:41 +03:00 committed by GitHub
parent 2836018b0e
commit 9f880f207f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 229 additions and 1262 deletions

View file

@ -43,6 +43,7 @@ export const MetricVisValue = ({
style={autoScale && colorFullBackground ? {} : { backgroundColor: metric.bgColor }}
>
<div
data-test-subj="metric_value"
className="mtrVis__value"
style={{
...(style.spec as CSSProperties),
@ -60,6 +61,7 @@ export const MetricVisValue = ({
/>
{labelConfig.show && (
<div
data-test-subj="metric_label"
style={{
...(labelConfig.style.spec as CSSProperties),
order: labelConfig.position === 'top' ? -1 : 2,

View file

@ -33,6 +33,7 @@ export const getMetricVisRenderer = (
render(
<KibanaThemeProvider theme$={theme.theme$}>
<VisualizationContainer
data-test-subj="mtrVis"
className="mtrVis"
showNoResult={!visData.rows?.length}
handlers={handlers}

View file

@ -11,7 +11,6 @@ export * from './rename_columns';
export * from './merge_tables';
export * from './time_scale';
export * from './datatable';
export * from './metric_chart';
export * from './xy_chart';
export * from './expression_types';

View file

@ -1,9 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './types';
export * from './metric_chart';

View file

@ -1,113 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import { ColorMode } from '../../../../../../src/plugins/charts/common';
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
import type { LensMultiTable } from '../../types';
import type { MetricConfig } from './types';
interface MetricRender {
type: 'render';
as: 'lens_metric_chart_renderer';
value: MetricChartProps;
}
export interface MetricChartProps {
data: LensMultiTable;
args: MetricConfig;
}
export const metricChart: ExpressionFunctionDefinition<
'lens_metric_chart',
LensMultiTable,
Omit<MetricConfig, 'layerId' | 'layerType'>,
MetricRender
> = {
name: 'lens_metric_chart',
type: 'render',
help: 'A metric chart',
args: {
title: {
types: ['string'],
help: i18n.translate('xpack.lens.metric.title.help', {
defaultMessage: 'The visualization title.',
}),
},
size: {
types: ['string'],
help: i18n.translate('xpack.lens.metric.size.help', {
defaultMessage: 'The visualization text size.',
}),
default: 'xl',
},
titlePosition: {
types: ['string'],
help: i18n.translate('xpack.lens.metric.titlePosition.help', {
defaultMessage: 'The visualization title position.',
}),
default: 'bottom',
},
textAlign: {
types: ['string'],
help: i18n.translate('xpack.lens.metric.textAlignPosition.help', {
defaultMessage: 'The visualization text alignment position.',
}),
default: 'center',
},
description: {
types: ['string'],
help: '',
},
metricTitle: {
types: ['string'],
help: i18n.translate('xpack.lens.metric.metricTitle.help', {
defaultMessage: 'The title of the metric shown.',
}),
},
accessor: {
types: ['string'],
help: i18n.translate('xpack.lens.metric.accessor.help', {
defaultMessage: 'The column whose value is being displayed',
}),
},
mode: {
types: ['string'],
options: ['reduced', 'full'],
default: 'full',
help: i18n.translate('xpack.lens.metric.mode.help', {
defaultMessage:
'The display mode of the chart - reduced will only show the metric itself without min size',
}),
},
colorMode: {
types: ['string'],
default: `"${ColorMode.None}"`,
options: [ColorMode.None, ColorMode.Labels, ColorMode.Background],
help: i18n.translate('xpack.lens.metric.colorMode.help', {
defaultMessage: 'Which part of metric to color',
}),
},
palette: {
types: ['palette'],
help: i18n.translate('xpack.lens.metric.palette.help', {
defaultMessage: 'Provides colors for the values',
}),
},
},
inputTypes: ['lens_multitable'],
fn(data, args) {
return {
type: 'render',
as: 'lens_metric_chart_renderer',
value: {
data,
args,
},
} as MetricRender;
},
};

View file

@ -1,33 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
ColorMode,
CustomPaletteState,
PaletteOutput,
} from '../../../../../../src/plugins/charts/common';
import { CustomPaletteParams, LayerType } from '../../types';
export interface MetricState {
layerId: string;
accessor?: string;
layerType: LayerType;
colorMode?: ColorMode;
palette?: PaletteOutput<CustomPaletteParams>;
titlePosition?: 'top' | 'bottom';
size?: string;
textAlign?: 'left' | 'right' | 'center';
}
export interface MetricConfig extends Omit<MetricState, 'palette' | 'colorMode'> {
title: string;
description: string;
metricTitle: string;
mode: 'reduced' | 'full';
colorMode: ColorMode;
palette: PaletteOutput<CustomPaletteState>;
}

View file

@ -13,8 +13,11 @@ import type {
SerializedFieldFormat,
} from '../../../../src/plugins/field_formats/common';
import type { Datatable } from '../../../../src/plugins/expressions/common';
import type { PaletteContinuity } from '../../../../src/plugins/charts/common';
import type { PaletteOutput } from '../../../../src/plugins/charts/common';
import type {
PaletteContinuity,
PaletteOutput,
ColorMode,
} from '../../../../src/plugins/charts/common';
import {
CategoryDisplay,
layerTypes,
@ -121,3 +124,13 @@ export interface PieVisualizationState {
layers: PieLayerState[];
palette?: PaletteOutput;
}
export interface MetricState {
layerId: string;
accessor?: string;
layerType: LayerType;
colorMode?: ColorMode;
palette?: PaletteOutput<CustomPaletteParams>;
titlePosition?: 'top' | 'bottom';
size?: string;
textAlign?: 'left' | 'right' | 'center';
}

View file

@ -24,8 +24,7 @@ import type { LensByReferenceInput, LensByValueInput } from './embeddable';
import type { Document } from '../persistence';
import type { IndexPatternPersistedState } from '../indexpattern_datasource/types';
import type { XYState } from '../xy_visualization/types';
import type { MetricState } from '../../common/expressions';
import type { PieVisualizationState } from '../../common';
import type { PieVisualizationState, MetricState } from '../../common';
import type { DatatableVisualizationState } from '../datatable_visualization/visualization';
import type { HeatmapVisualizationState } from '../heatmap_visualization/types';
import type { GaugeVisualizationState } from '../visualizations/gauge/constants';

View file

@ -30,7 +30,6 @@ import { renameColumns } from '../common/expressions/rename_columns/rename_colum
import { formatColumn } from '../common/expressions/format_column';
import { counterRate } from '../common/expressions/counter_rate';
import { getTimeScale } from '../common/expressions/time_scale/time_scale';
import { metricChart } from '../common/expressions/metric_chart/metric_chart';
import { lensMultitable } from '../common/expressions';
export const setupExpressions = (
@ -44,7 +43,6 @@ export const setupExpressions = (
xyChart,
mergeTables,
counterRate,
metricChart,
yAxisConfig,
dataLayerConfig,
referenceLineLayerConfig,

View file

@ -14,7 +14,6 @@ export type {
export type { XYState } from './xy_visualization/types';
export type { DataType, OperationMetadata, Visualization } from './types';
export type {
MetricState,
AxesSettingsConfig,
XYLayerConfig,
LegendConfig,

View file

@ -1,67 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { computeScale, AutoScale } from './auto_scale';
import { render } from 'enzyme';
const mockElement = (clientWidth = 100, clientHeight = 200) => ({
clientHeight,
clientWidth,
});
describe('AutoScale', () => {
describe('computeScale', () => {
it('is 1 if any element is null', () => {
expect(computeScale(null, null)).toBe(1);
expect(computeScale(mockElement(), null)).toBe(1);
expect(computeScale(null, mockElement())).toBe(1);
});
it('is never over 1', () => {
expect(computeScale(mockElement(2000, 2000), mockElement(1000, 1000))).toBe(1);
});
it('is never under 0.3 in default case', () => {
expect(computeScale(mockElement(2000, 1000), mockElement(1000, 10000))).toBe(0.3);
});
it('is never under specified min scale if specified', () => {
expect(computeScale(mockElement(2000, 1000), mockElement(1000, 10000), 0.1)).toBe(0.1);
});
it('is the lesser of the x or y scale', () => {
expect(computeScale(mockElement(2000, 2000), mockElement(3000, 5000))).toBe(0.4);
expect(computeScale(mockElement(2000, 3000), mockElement(4000, 3200))).toBe(0.5);
});
});
describe('AutoScale', () => {
it('renders', () => {
expect(
render(
<AutoScale>
<h1>Hoi!</h1>
</AutoScale>
)
).toMatchInlineSnapshot(`
<div
style="display:flex;justify-content:center;align-items:center;max-width:100%;max-height:100%;overflow:hidden;line-height:1.5"
>
<div
class="lnsMetricExpression__containerScale"
style="transform:scale(0)"
>
<h1>
Hoi!
</h1>
</div>
</div>
`);
});
});
});

View file

@ -1,134 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { throttle } from 'lodash';
import classNames from 'classnames';
import { EuiResizeObserver } from '@elastic/eui';
import { MetricState } from '../../common/expressions';
interface Props extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode | React.ReactNode[];
minScale?: number;
size?: MetricState['size'];
titlePosition?: MetricState['titlePosition'];
textAlign?: MetricState['textAlign'];
}
interface State {
scale: number;
}
export class AutoScale extends React.Component<Props, State> {
private child: Element | null = null;
private parent: Element | null = null;
private scale: () => void;
constructor(props: Props) {
super(props);
this.scale = throttle(() => {
const scale = computeScale(this.parent, this.child, this.props.minScale);
// Prevent an infinite render loop
if (this.state.scale !== scale) {
this.setState({ scale });
}
});
// An initial scale of 0 means we always redraw
// at least once, which is sub-optimal, but it
// prevents an annoying flicker.
this.state = { scale: 0 };
}
setParent = (el: Element | null) => {
if (el && this.parent !== el) {
this.parent = el;
setTimeout(() => this.scale());
}
};
setChild = (el: Element | null) => {
if (el && this.child !== el) {
this.child = el;
setTimeout(() => this.scale());
}
};
render() {
const { children, minScale, size, textAlign, titlePosition, ...rest } = this.props;
const { scale } = this.state;
const style = this.props.style || {};
return (
<EuiResizeObserver onResize={this.scale}>
{(resizeRef) => (
<div
{...rest}
ref={(el) => {
this.setParent(el);
resizeRef(el);
}}
style={{
...style,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
maxWidth: '100%',
maxHeight: '100%',
overflow: 'hidden',
lineHeight: 1.5,
}}
>
<div
ref={this.setChild}
style={{
transform: `scale(${scale})`,
}}
className={classNames('lnsMetricExpression__containerScale', {
alignLeft: textAlign === 'left',
alignRight: textAlign === 'right',
alignCenter: textAlign === 'center',
[`titleSize${size?.toUpperCase()}`]: Boolean(size),
})}
>
{children}
</div>
</div>
)}
</EuiResizeObserver>
);
}
}
interface ClientDimensionable {
clientWidth: number;
clientHeight: number;
}
const MAX_SCALE = 1;
const MIN_SCALE = 0.3;
/**
* computeScale computes the ratio by which the child needs to shrink in order
* to fit into the parent. This function is only exported for testing purposes.
*/
export function computeScale(
parent: ClientDimensionable | null,
child: ClientDimensionable | null,
minScale: number = MIN_SCALE
) {
if (!parent || !child) {
return 1;
}
const scaleX = parent.clientWidth / child.clientWidth;
const scaleY = parent.clientHeight / child.clientHeight;
return Math.max(Math.min(MAX_SCALE, Math.min(scaleX, scaleY)), minScale);
}

View file

@ -16,7 +16,7 @@ import { ColorMode, PaletteOutput, PaletteRegistry } from 'src/plugins/charts/pu
import { act } from 'react-dom/test-utils';
import { CustomizablePalette, PalettePanelContainer } from '../shared_components';
import { CustomPaletteParams, layerTypes } from '../../common';
import { MetricState } from '../../common/expressions';
import type { MetricState } from '../../common/types';
// mocking random id generator function
jest.mock('@elastic/eui', () => {

View file

@ -17,7 +17,8 @@ import { i18n } from '@kbn/i18n';
import React, { useCallback, useState } from 'react';
import { ColorMode } from '../../../../../src/plugins/charts/common';
import type { PaletteRegistry } from '../../../../../src/plugins/charts/public';
import { isNumericFieldForDatatable, MetricState } from '../../common/expressions';
import type { MetricState } from '../../common/types';
import { isNumericFieldForDatatable } from '../../common/expressions';
import {
applyPaletteParams,
CustomizablePalette,

View file

@ -1,87 +0,0 @@
.lnsMetricExpression__container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
text-align: center;
.lnsMetricExpression__value {
font-size: $euiFontSizeXXL * 2;
font-weight: $euiFontWeightSemiBold;
border-radius: $euiBorderRadius;
}
.lnsMetricExpression__title {
font-size: $euiFontSizeXXL;
color: $euiTextColor;
&.reverseOrder {
order: 1;
}
}
.lnsMetricExpression__containerScale {
display: flex;
align-items: center;
flex-direction: column;
&.alignLeft {
align-items: start;
}
&.alignRight {
align-items: end;
}
&.alignCenter {
align-items: center;
}
&.titleSizeXS {
.lnsMetricExpression__title {
font-size: $euiFontSizeXS;
}
.lnsMetricExpression__value {
font-size: $euiFontSizeXS * 2;
}
}
&.titleSizeS {
.lnsMetricExpression__title {
font-size: $euiFontSizeS;
}
.lnsMetricExpression__value {
font-size: $euiFontSizeM * 2.25;
}
}
&.titleSizeM {
.lnsMetricExpression__title {
font-size: $euiFontSizeM;
}
.lnsMetricExpression__value {
font-size: $euiFontSizeL * 2;
}
}
&.titleSizeL {
.lnsMetricExpression__title {
font-size: $euiFontSizeL;
}
.lnsMetricExpression__value {
font-size: $euiFontSizeXL * 2;
}
}
&.titleSizeXL {
.lnsMetricExpression__title {
font-size: $euiFontSizeXL;
}
.lnsMetricExpression__value {
font-size: $euiFontSizeXXL * 2;
}
}
&.titleSizeXXL {
.lnsMetricExpression__title {
font-size: $euiFontSizeXXL;
}
.lnsMetricExpression__value {
font-size: $euiFontSizeXXL * 3;
}
}
}
}

View file

@ -1,552 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { MetricChart } from './expression';
import { MetricConfig, metricChart } from '../../common/expressions';
import React from 'react';
import { shallow, mount } from 'enzyme';
import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks';
import type { IFieldFormat } from '../../../../../src/plugins/field_formats/common';
import { layerTypes } from '../../common';
import type { LensMultiTable } from '../../common';
import { IUiSettingsClient } from 'kibana/public';
import { ColorMode } from 'src/plugins/charts/common';
function sampleArgs() {
const data: LensMultiTable = {
type: 'lens_multitable',
tables: {
l1: {
type: 'datatable',
columns: [
// Simulating a calculated column like a formula
{ id: 'a', name: 'a', meta: { type: 'string', params: { id: 'string' } } },
{ id: 'b', name: 'b', meta: { type: 'string' } },
{
id: 'c',
name: 'c',
meta: { type: 'number', params: { id: 'percent', params: { format: '0.000%' } } },
},
],
rows: [{ a: 'last', b: 'last', c: 3 }],
},
},
};
const args: MetricConfig = {
accessor: 'c',
layerId: 'l1',
layerType: layerTypes.DATA,
title: 'My fanci metric chart',
description: 'Fancy chart description',
metricTitle: 'My fanci metric chart',
mode: 'full',
colorMode: ColorMode.None,
palette: { type: 'palette', name: 'status' },
};
const noAttributesArgs: MetricConfig = {
accessor: 'c',
layerId: 'l1',
layerType: layerTypes.DATA,
title: '',
description: '',
metricTitle: 'My fanci metric chart',
mode: 'full',
colorMode: ColorMode.None,
palette: { type: 'palette', name: 'status' },
};
return { data, args, noAttributesArgs };
}
describe('metric_expression', () => {
describe('metricChart', () => {
test('it renders with the specified data and args', () => {
const { data, args } = sampleArgs();
const result = metricChart.fn(data, args, createMockExecutionContext());
expect(result).toEqual({
type: 'render',
as: 'lens_metric_chart_renderer',
value: { data, args },
});
});
});
describe('MetricChart component', () => {
test('it renders all attributes when passed (title, description, metricTitle, value)', () => {
const { data, args } = sampleArgs();
expect(
shallow(
<MetricChart
data={data}
args={args}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
)
).toMatchInlineSnapshot(`
<VisualizationContainer
className="lnsMetricExpression__container"
style={Object {}}
>
<AutoScale
key="3"
>
<div
className="lnsMetricExpression__title"
data-test-subj="lns_metric_title"
>
My fanci metric chart
</div>
<div
className="lnsMetricExpression__value"
data-test-subj="lns_metric_value"
>
3
</div>
</AutoScale>
</VisualizationContainer>
`);
});
test('it renders strings', () => {
const { data, args } = sampleArgs();
args.accessor = 'a';
expect(
shallow(
<MetricChart
data={data}
args={args}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
)
).toMatchInlineSnapshot(`
<VisualizationContainer
className="lnsMetricExpression__container"
style={Object {}}
>
<AutoScale
key="last"
>
<div
className="lnsMetricExpression__title"
data-test-subj="lns_metric_title"
>
My fanci metric chart
</div>
<div
className="lnsMetricExpression__value"
data-test-subj="lns_metric_value"
>
last
</div>
</AutoScale>
</VisualizationContainer>
`);
});
test('it renders only chart content when title and description are empty strings', () => {
const { data, noAttributesArgs } = sampleArgs();
expect(
shallow(
<MetricChart
data={data}
args={noAttributesArgs}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
)
).toMatchInlineSnapshot(`
<VisualizationContainer
className="lnsMetricExpression__container"
style={Object {}}
>
<AutoScale
key="3"
>
<div
className="lnsMetricExpression__title"
data-test-subj="lns_metric_title"
>
My fanci metric chart
</div>
<div
className="lnsMetricExpression__value"
data-test-subj="lns_metric_value"
>
3
</div>
</AutoScale>
</VisualizationContainer>
`);
});
test('it does not render metricTitle in reduced mode', () => {
const { data, noAttributesArgs } = sampleArgs();
expect(
shallow(
<MetricChart
data={data}
args={{ ...noAttributesArgs, mode: 'reduced' }}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
)
).toMatchInlineSnapshot(`
<VisualizationContainer
className="lnsMetricExpression__container"
style={Object {}}
>
<AutoScale
key="3"
minScale={0.05}
>
<div
className="lnsMetricExpression__value"
data-test-subj="lns_metric_value"
>
3
</div>
</AutoScale>
</VisualizationContainer>
`);
});
test('it renders an EmptyPlaceholder when no tables is passed as data', () => {
const { data, noAttributesArgs } = sampleArgs();
expect(
shallow(
<MetricChart
data={{ ...data, tables: {} }}
args={noAttributesArgs}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
)
).toMatchInlineSnapshot(`
<VisualizationContainer
className="lnsMetricExpression__container"
>
<EmptyPlaceholder
icon={[Function]}
/>
</VisualizationContainer>
`);
});
test('it renders an EmptyPlaceholder when null value is passed as data', () => {
const { data, noAttributesArgs } = sampleArgs();
data.tables.l1.rows[0].c = null;
expect(
shallow(
<MetricChart
data={data}
args={noAttributesArgs}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
)
).toMatchInlineSnapshot(`
<VisualizationContainer
className="lnsMetricExpression__container"
>
<EmptyPlaceholder
icon={[Function]}
/>
</VisualizationContainer>
`);
});
test('it renders 0 value', () => {
const { data, noAttributesArgs } = sampleArgs();
data.tables.l1.rows[0].c = 0;
expect(
shallow(
<MetricChart
data={data}
args={noAttributesArgs}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
)
).toMatchInlineSnapshot(`
<VisualizationContainer
className="lnsMetricExpression__container"
style={Object {}}
>
<AutoScale
key="0"
>
<div
className="lnsMetricExpression__title"
data-test-subj="lns_metric_title"
>
My fanci metric chart
</div>
<div
className="lnsMetricExpression__value"
data-test-subj="lns_metric_value"
>
0
</div>
</AutoScale>
</VisualizationContainer>
`);
});
test('it finds the right column to format', () => {
const { data, args } = sampleArgs();
const factory = jest.fn(() => ({ convert: (x) => x } as IFieldFormat));
shallow(
<MetricChart
data={data}
args={args}
formatFactory={factory}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
);
expect(factory).toHaveBeenCalledWith({ id: 'percent', params: { format: '0.000%' } });
});
test('it renders the correct color styling for numeric value if coloring config is passed', () => {
const { data, args } = sampleArgs();
args.colorMode = ColorMode.Labels;
args.palette.params = {
rangeMin: 0,
rangeMax: 400,
stops: [100, 200, 400],
gradient: false,
range: 'number',
colors: ['red', 'yellow', 'green'],
};
const instance = mount(
<MetricChart
data={data}
args={args}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
);
expect(
instance.find('[data-test-subj="lnsVisualizationContainer"]').first().prop('style')
).toEqual(
expect.objectContaining({
color: 'red',
})
);
});
test('it renders no color styling for numeric value if value is lower then rangeMin and continuity is "above"', () => {
const { data, args } = sampleArgs();
data.tables.l1.rows[0].c = -1;
args.colorMode = ColorMode.Labels;
args.palette.params = {
rangeMin: 0,
rangeMax: 400,
stops: [100, 200, 400],
gradient: false,
range: 'number',
colors: ['red', 'yellow', 'green'],
continuity: 'above',
};
const instance = mount(
<MetricChart
data={data}
args={args}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
);
expect(
instance.find('[data-test-subj="lnsVisualizationContainer"]').first().prop('style')
).not.toEqual(
expect.objectContaining({
color: expect.any(String),
})
);
});
test('it renders no color styling for numeric value if value is higher than rangeMax and continuity is "below"', () => {
const { data, args } = sampleArgs();
data.tables.l1.rows[0].c = 500;
args.colorMode = ColorMode.Labels;
args.palette.params = {
rangeMin: 0,
rangeMax: 400,
stops: [100, 200, 400],
gradient: false,
range: 'number',
colors: ['red', 'yellow', 'green'],
continuity: 'below',
};
const instance = mount(
<MetricChart
data={data}
args={args}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
);
expect(
instance.find('[data-test-subj="lnsVisualizationContainer"]').first().prop('style')
).not.toEqual(
expect.objectContaining({
color: expect.any(String),
})
);
});
test('it renders no color styling for numeric value if value is higher than rangeMax', () => {
const { data, args } = sampleArgs();
data.tables.l1.rows[0].c = 500;
args.colorMode = ColorMode.Labels;
args.palette.params = {
rangeMin: 0,
rangeMax: 400,
stops: [100, 200, 400],
gradient: false,
range: 'number',
colors: ['red', 'yellow', 'green'],
};
const instance = mount(
<MetricChart
data={data}
args={args}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
);
expect(
instance.find('[data-test-subj="lnsVisualizationContainer"]').first().prop('style')
).not.toEqual(
expect.objectContaining({
color: expect.any(String),
})
);
});
test('it renders no color styling for numeric value if value is lower than rangeMin', () => {
const { data, args } = sampleArgs();
data.tables.l1.rows[0].c = -1;
args.colorMode = ColorMode.Labels;
args.palette.params = {
rangeMin: 0,
rangeMax: 400,
stops: [100, 200, 400],
gradient: false,
range: 'number',
colors: ['red', 'yellow', 'green'],
};
const instance = mount(
<MetricChart
data={data}
args={args}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
);
expect(
instance.find('[data-test-subj="lnsVisualizationContainer"]').first().prop('style')
).not.toEqual(
expect.objectContaining({
color: expect.any(String),
})
);
});
test('it renders the correct color styling for numeric value if user select auto detect max value', () => {
const { data, args } = sampleArgs();
data.tables.l1.rows[0].c = 500;
args.colorMode = ColorMode.Labels;
args.palette.params = {
rangeMin: 20,
rangeMax: Infinity,
stops: [100, 200, 400],
gradient: false,
range: 'number',
colors: ['red', 'yellow', 'green'],
};
const instance = mount(
<MetricChart
data={data}
args={args}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
);
expect(
instance.find('[data-test-subj="lnsVisualizationContainer"]').first().prop('style')
).toEqual(
expect.objectContaining({
color: 'green',
})
);
});
test('it renders the correct color styling for numeric value if user select auto detect min value', () => {
const { data, args } = sampleArgs();
data.tables.l1.rows[0].c = -1;
args.colorMode = ColorMode.Labels;
args.palette.params = {
rangeMin: -Infinity,
rangeMax: 400,
stops: [-Infinity, 200, 400],
gradient: false,
range: 'number',
colors: ['red', 'yellow', 'green'],
};
const instance = mount(
<MetricChart
data={data}
args={args}
formatFactory={() => ({ convert: (x) => x } as IFieldFormat)}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
/>
);
expect(
instance.find('[data-test-subj="lnsVisualizationContainer"]').first().prop('style')
).toEqual(
expect.objectContaining({
color: 'red',
})
);
});
});
});

View file

@ -1,172 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import './expression.scss';
import { I18nProvider } from '@kbn/i18n-react';
import React from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import { IUiSettingsClient, ThemeServiceStart } from 'kibana/public';
import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public';
import type {
ExpressionRenderDefinition,
IInterpreterRenderHandlers,
} from '../../../../../src/plugins/expressions/public';
import {
ColorMode,
CustomPaletteState,
PaletteOutput,
} from '../../../../../src/plugins/charts/public';
import { AutoScale } from './auto_scale';
import { VisualizationContainer } from '../visualization_container';
import { getContrastColor } from '../shared_components';
import { EmptyPlaceholder } from '../../../../../src/plugins/charts/public';
import { LensIconChartMetric } from '../assets/chart_metric';
import type { FormatFactory } from '../../common';
import type { MetricChartProps } from '../../common/expressions';
export type { MetricChartProps, MetricState, MetricConfig } from '../../common/expressions';
export const getMetricChartRenderer = (
formatFactory: FormatFactory,
uiSettings: IUiSettingsClient,
theme: ThemeServiceStart
): ExpressionRenderDefinition<MetricChartProps> => ({
name: 'lens_metric_chart_renderer',
displayName: 'Metric chart',
help: 'Metric chart renderer',
validate: () => undefined,
reuseDomNode: true,
render: (domNode: Element, config: MetricChartProps, handlers: IInterpreterRenderHandlers) => {
ReactDOM.render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<MetricChart {...config} formatFactory={formatFactory} uiSettings={uiSettings} />
</I18nProvider>
</KibanaThemeProvider>,
domNode,
() => {
handlers.done();
}
);
handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode));
},
});
function getColorStyling(
value: number,
colorMode: ColorMode,
palette: PaletteOutput<CustomPaletteState> | undefined,
isDarkTheme: boolean
) {
if (
colorMode === ColorMode.None ||
!palette?.params ||
!palette?.params.colors?.length ||
isNaN(value)
) {
return {};
}
const { rangeMin, rangeMax, stops, colors } = palette.params;
if (value > rangeMax) {
return {};
}
if (value < rangeMin) {
return {};
}
const cssProp = colorMode === ColorMode.Background ? 'backgroundColor' : 'color';
let rawIndex = stops.findIndex((v) => v > value);
if (!isFinite(rangeMax) && value > stops[stops.length - 1]) {
rawIndex = stops.length - 1;
}
// in this case first stop is -Infinity
if (!isFinite(rangeMin) && value < (isFinite(stops[0]) ? stops[0] : stops[1])) {
rawIndex = 0;
}
const colorIndex = rawIndex;
const color = colors[colorIndex];
const styling = {
[cssProp]: color,
};
if (colorMode === ColorMode.Background && color) {
// set to "euiTextColor" for both light and dark color, depending on the theme
styling.color = getContrastColor(color, isDarkTheme, 'euiTextColor', 'euiTextColor');
}
return styling;
}
export function MetricChart({
data,
args,
formatFactory,
uiSettings,
}: MetricChartProps & { formatFactory: FormatFactory; uiSettings: IUiSettingsClient }) {
const { metricTitle, accessor, mode, colorMode, palette, titlePosition, textAlign, size } = args;
const firstTable = Object.values(data.tables)[0];
const getEmptyState = () => (
<VisualizationContainer className="lnsMetricExpression__container">
<EmptyPlaceholder icon={LensIconChartMetric} />
</VisualizationContainer>
);
if (!accessor || !firstTable) {
return getEmptyState();
}
const column = firstTable.columns.find(({ id }) => id === accessor);
const row = firstTable.rows[0];
if (!column || !row) {
return getEmptyState();
}
const rawValue = row[accessor];
// NOTE: Cardinality and Sum never receives "null" as value, but always 0, even for empty dataset.
// Mind falsy values here as 0!
if (!['number', 'string'].includes(typeof rawValue)) {
return getEmptyState();
}
const value =
column && column.meta?.params
? formatFactory(column.meta?.params).convert(rawValue)
: Number(Number(rawValue).toFixed(3)).toString();
const color = getColorStyling(rawValue, colorMode, palette, uiSettings.get('theme:darkMode'));
return (
<VisualizationContainer className="lnsMetricExpression__container" style={color}>
<AutoScale
key={value}
titlePosition={titlePosition}
textAlign={textAlign}
size={size}
minScale={mode === 'full' ? undefined : 0.05}
>
{mode === 'full' && (
<div
data-test-subj="lns_metric_title"
className={classNames('lnsMetricExpression__title', {
reverseOrder: ['bottom', 'right'].includes(titlePosition ?? ''),
})}
style={colorMode === ColorMode.Background ? color : undefined}
>
{metricTitle}
</div>
)}
<div data-test-subj="lns_metric_value" className="lnsMetricExpression__value">
{value}
</div>
</AutoScale>
</VisualizationContainer>
);
}

View file

@ -6,30 +6,20 @@
*/
import type { CoreSetup } from 'kibana/public';
import type { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
import type { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
import type { EditorFrameSetup } from '../types';
import type { FormatFactory } from '../../common';
export interface MetricVisualizationPluginSetupPlugins {
expressions: ExpressionsSetup;
formatFactory: FormatFactory;
editorFrame: EditorFrameSetup;
charts: ChartsPluginSetup;
}
export class MetricVisualization {
setup(
core: CoreSetup,
{ expressions, formatFactory, editorFrame, charts }: MetricVisualizationPluginSetupPlugins
) {
setup(core: CoreSetup, { editorFrame, charts }: MetricVisualizationPluginSetupPlugins) {
editorFrame.registerVisualization(async () => {
const { getMetricVisualization, getMetricChartRenderer } = await import('../async_services');
const { getMetricVisualization } = await import('../async_services');
const palettes = await charts.palettes.getPalettes();
expressions.registerRenderer(() =>
getMetricChartRenderer(formatFactory, core.uiSettings, core.theme)
);
return getMetricVisualization({ paletteService: palettes, theme: core.theme });
});
}

View file

@ -8,7 +8,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonGroup } from '@elastic/eui';
import { MetricState } from '../../../common/expressions';
import { MetricState } from '../../../common/types';
export interface TitlePositionProps {
state: MetricState;

View file

@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
import { ToolbarPopover, TooltipWrapper } from '../../shared_components';
import { TitlePositionOptions } from './title_position_option';
import { FramePublicAPI } from '../../types';
import { MetricState } from '../../../common/expressions';
import type { MetricState } from '../../../common/types';
import { TextFormattingOptions } from './text_formatting_options';
export interface VisualOptionsPopoverProps {

View file

@ -8,9 +8,9 @@
import React, { memo } from 'react';
import { EuiFlexGroup, EuiFlexItem, htmlIdGenerator } from '@elastic/eui';
import type { VisualizationToolbarProps } from '../../types';
import type { MetricState } from '../../../common/types';
import { AppearanceOptionsPopover } from './appearance_options_popover';
import { MetricState } from '../../../common/expressions';
export const MetricToolbar = memo(function MetricToolbar(
props: VisualizationToolbarProps<MetricState>

View file

@ -8,7 +8,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonIcon, EuiSuperSelect } from '@elastic/eui';
import { MetricState } from '../../../common/expressions';
import type { MetricState } from '../../../common/types';
export interface TitlePositionProps {
state: MetricState;

View file

@ -8,7 +8,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiFormRow, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { MetricState } from '../../../common/expressions';
import type { MetricState } from '../../../common/types';
import { SizeOptions } from './size_options';
import { AlignOptions } from './align_options';

View file

@ -8,7 +8,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonGroup, EuiFormRow } from '@elastic/eui';
import { MetricState } from '../../../common/expressions';
import type { MetricState } from '../../../common/types';
export interface TitlePositionProps {
state: MetricState;

View file

@ -6,7 +6,7 @@
*/
import { SuggestionRequest, VisualizationSuggestion, TableSuggestion } from '../types';
import type { MetricState } from '../../common/expressions';
import type { MetricState } from '../../common/types';
import { layerTypes } from '../../common';
import { LensIconChartMetric } from '../assets/chart_metric';
import { supportedTypes } from './visualization';

View file

@ -5,5 +5,4 @@
* 2.0.
*/
export * from './expression';
export * from './visualization';

View file

@ -6,7 +6,7 @@
*/
import { getMetricVisualization } from './visualization';
import { MetricState } from '../../common/expressions';
import type { MetricState } from '../../common/types';
import { layerTypes } from '../../common';
import { createMockDatasource, createMockFramePublicAPI } from '../mocks';
import { generateId } from '../id_generator';
@ -286,36 +286,93 @@ describe('metric_visualization', () => {
"chain": Array [
Object {
"arguments": Object {
"accessor": Array [
"a",
"autoScale": Array [
true,
],
"colorFullBackground": Array [
true,
],
"colorMode": Array [
"None",
],
"description": Array [
"",
"font": Array [
Object {
"chain": Array [
Object {
"arguments": Object {
"align": Array [
"center",
],
"lHeight": Array [
127.5,
],
"size": Array [
85,
],
"sizeUnit": Array [
"px",
],
"weight": Array [
"600",
],
},
"function": "font",
"type": "function",
},
],
"type": "expression",
},
],
"metricTitle": Array [
"shazm",
"labelFont": Array [
Object {
"chain": Array [
Object {
"arguments": Object {
"align": Array [
"center",
],
"lHeight": Array [
40.5,
],
"size": Array [
27,
],
"sizeUnit": Array [
"px",
],
},
"function": "font",
"type": "function",
},
],
"type": "expression",
},
],
"mode": Array [
"full",
],
"palette": Array [],
"size": Array [
"xl",
],
"textAlign": Array [
"center",
],
"title": Array [
"",
],
"titlePosition": Array [
"labelPosition": Array [
"bottom",
],
"metric": Array [
Object {
"chain": Array [
Object {
"arguments": Object {
"accessor": Array [
"a",
],
},
"function": "visdimension",
"type": "function",
},
],
"type": "expression",
},
],
"palette": Array [],
"showLabels": Array [
true,
],
},
"function": "lens_metric_chart",
"function": "metricVis",
"type": "function",
},
],

View file

@ -8,23 +8,45 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n-react';
import { euiThemeVars } from '@kbn/ui-theme';
import { render } from 'react-dom';
import { Ast } from '@kbn/interpreter';
import { ThemeServiceStart } from 'kibana/public';
import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public';
import { ColorMode } from '../../../../../src/plugins/charts/common';
import {
ColorMode,
CustomPaletteState,
PaletteOutput,
} from '../../../../../src/plugins/charts/common';
import { PaletteRegistry } from '../../../../../src/plugins/charts/public';
import { getSuggestions } from './metric_suggestions';
import { LensIconChartMetric } from '../assets/chart_metric';
import { Visualization, OperationMetadata, DatasourcePublicAPI } from '../types';
import type { MetricConfig, MetricState } from '../../common/expressions';
import type { MetricState } from '../../common/types';
import { layerTypes } from '../../common';
import { CUSTOM_PALETTE, shiftPalette } from '../shared_components';
import { MetricDimensionEditor } from './dimension_editor';
import { MetricToolbar } from './metric_config_panel';
interface MetricConfig extends Omit<MetricState, 'palette' | 'colorMode'> {
title: string;
description: string;
metricTitle: string;
mode: 'reduced' | 'full';
colorMode: ColorMode;
palette: PaletteOutput<CustomPaletteState>;
}
export const supportedTypes = new Set(['string', 'boolean', 'number', 'ip', 'date']);
const getFontSizeAndUnit = (fontSize: string) => {
const [size, sizeUnit] = fontSize.split(/(\d+)/).filter(Boolean);
return {
size: Number(size),
sizeUnit,
};
};
const toExpression = (
paletteService: PaletteRegistry,
state: MetricState,
@ -56,22 +78,87 @@ const toExpression = (
reverse: false,
};
const fontSizes: Record<string, { size: number; sizeUnit: string }> = {
xs: getFontSizeAndUnit(euiThemeVars.euiFontSizeXS),
s: getFontSizeAndUnit(euiThemeVars.euiFontSizeS),
m: getFontSizeAndUnit(euiThemeVars.euiFontSizeM),
l: getFontSizeAndUnit(euiThemeVars.euiFontSizeL),
xl: getFontSizeAndUnit(euiThemeVars.euiFontSizeXL),
xxl: getFontSizeAndUnit(euiThemeVars.euiFontSizeXXL),
};
const labelFont = fontSizes[state?.size || 'xl'];
const labelToMetricFontSizeMap: Record<string, number> = {
xs: fontSizes.xs.size * 2,
s: fontSizes.m.size * 2.5,
m: fontSizes.l.size * 2.5,
l: fontSizes.xl.size * 2.5,
xl: fontSizes.xxl.size * 2.5,
xxl: fontSizes.xxl.size * 3,
};
const metricFontSize = labelToMetricFontSizeMap[state?.size || 'xl'];
return {
type: 'expression',
chain: [
{
type: 'function',
function: 'lens_metric_chart',
function: 'metricVis',
arguments: {
title: [attributes?.title || ''],
size: [state?.size || 'xl'],
titlePosition: [state?.titlePosition || 'bottom'],
textAlign: [state?.textAlign || 'center'],
description: [attributes?.description || ''],
metricTitle: [operation?.label || ''],
accessor: [state.accessor],
mode: [attributes?.mode || 'full'],
labelPosition: [state?.titlePosition || 'bottom'],
font: [
{
type: 'expression',
chain: [
{
type: 'function',
function: 'font',
arguments: {
align: [state?.textAlign || 'center'],
size: [metricFontSize],
weight: ['600'],
lHeight: [metricFontSize * 1.5],
sizeUnit: [labelFont.sizeUnit],
},
},
],
},
],
labelFont: [
{
type: 'expression',
chain: [
{
type: 'function',
function: 'font',
arguments: {
align: [state?.textAlign || 'center'],
size: [labelFont.size],
lHeight: [labelFont.size * 1.5],
sizeUnit: [labelFont.sizeUnit],
},
},
],
},
],
metric: [
{
type: 'expression',
chain: [
{
type: 'function',
function: 'visdimension',
arguments: {
accessor: [state.accessor],
},
},
],
},
],
showLabels: [!attributes?.mode || attributes?.mode === 'full'],
colorMode: !canColor ? [ColorMode.None] : [state?.colorMode || ColorMode.None],
autoScale: [true],
colorFullBackground: [true],
palette:
state?.colorMode && state?.colorMode !== ColorMode.None
? [paletteService.get(CUSTOM_PALETTE).toExpression(paletteParams)]
@ -81,6 +168,7 @@ const toExpression = (
],
};
};
export const getMetricVisualization = ({
paletteService,
theme,

View file

@ -9,7 +9,6 @@ import type { CoreSetup } from 'kibana/server';
import {
xyChart,
counterRate,
metricChart,
yAxisConfig,
dataLayerConfig,
referenceLineLayerConfig,
@ -38,7 +37,6 @@ export const setupExpressions = (
[
xyChart,
counterRate,
metricChart,
yAxisConfig,
dataLayerConfig,
referenceLineLayerConfig,

View file

@ -711,19 +711,13 @@
"xpack.lens.layerPanel.missingDataView": "データビューが見つかりません",
"xpack.lens.legacyUrlConflict.objectNoun": "レンズビジュアライゼーション",
"xpack.lens.lensSavedObjectLabel": "レンズビジュアライゼーション",
"xpack.lens.metric.accessor.help": "値が表示されている列",
"xpack.lens.metric.addLayer": "ビジュアライゼーションレイヤーを追加",
"xpack.lens.metric.colorMode.help": "色を変更するメトリックの部分",
"xpack.lens.metric.dynamicColoring.background": "塗りつぶし",
"xpack.lens.metric.dynamicColoring.label": "値別の色",
"xpack.lens.metric.dynamicColoring.none": "なし",
"xpack.lens.metric.dynamicColoring.text": "テキスト",
"xpack.lens.metric.groupLabel": "目標値と単一の値",
"xpack.lens.metric.label": "メトリック",
"xpack.lens.metric.metricTitle.help": "表示されるメトリックのタイトル。",
"xpack.lens.metric.mode.help": "グラフの表示モード。減らすと、最小サイズ内でメトリックのみが表示されます",
"xpack.lens.metric.palette.help": "値の色を指定します",
"xpack.lens.metric.title.help": "グラフタイトル。",
"xpack.lens.pageTitle": "レンズ",
"xpack.lens.paletteHeatmapGradient.customize": "編集",
"xpack.lens.paletteHeatmapGradient.customizeLong": "パレットを編集",

View file

@ -717,19 +717,13 @@
"xpack.lens.layerPanel.missingDataView": "找不到数据视图",
"xpack.lens.legacyUrlConflict.objectNoun": "Lens 可视化",
"xpack.lens.lensSavedObjectLabel": "Lens 可视化",
"xpack.lens.metric.accessor.help": "正显示值的列",
"xpack.lens.metric.addLayer": "添加可视化图层",
"xpack.lens.metric.colorMode.help": "指标的哪部分要上色",
"xpack.lens.metric.dynamicColoring.background": "填充",
"xpack.lens.metric.dynamicColoring.label": "按值上色",
"xpack.lens.metric.dynamicColoring.none": "无",
"xpack.lens.metric.dynamicColoring.text": "文本",
"xpack.lens.metric.groupLabel": "目标值和单值",
"xpack.lens.metric.label": "指标",
"xpack.lens.metric.metricTitle.help": "显示的指标标题。",
"xpack.lens.metric.mode.help": "图表的显示模式 - 缩减模式将仅显示指标本身,无最小大小",
"xpack.lens.metric.palette.help": "为值提供颜色",
"xpack.lens.metric.title.help": "图标标题。",
"xpack.lens.pageTitle": "Lens",
"xpack.lens.paletteHeatmapGradient.customize": "编辑",
"xpack.lens.paletteHeatmapGradient.customizeLong": "编辑调色板",

View file

@ -36,7 +36,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.lens.switchToVisualization('lnsMetric');
await PageObjects.lens.waitForVisualization();
await PageObjects.lens.waitForVisualization('mtrVis');
await PageObjects.lens.assertMetric('Average of bytes', '5,727.322');
};
@ -304,10 +304,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.lens.switchToVisualization('lnsMetric');
await PageObjects.lens.waitForVisualization();
await PageObjects.lens.waitForVisualization('mtrVis');
await PageObjects.lens.assertMetric('Average of bytes', '5,727.322');
await PageObjects.lens.waitForVisualization();
await PageObjects.lens.waitForVisualization('mtrVis');
await testSubjects.click('lnsApp_saveButton');
const hasOptions = await testSubjects.exists('add-to-dashboard-options');
@ -350,10 +350,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.lens.switchToVisualization('lnsMetric');
await PageObjects.lens.waitForVisualization();
await PageObjects.lens.waitForVisualization('mtrVis');
await PageObjects.lens.assertMetric('Average of bytes', '5,727.322');
await PageObjects.lens.waitForVisualization();
await PageObjects.lens.waitForVisualization('mtrVis');
await testSubjects.click('lnsApp_saveButton');
const hasOptions = await testSubjects.exists('add-to-dashboard-options');

View file

@ -37,7 +37,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('should switch to gauge and render a gauge with default values', async () => {
await PageObjects.lens.switchToVisualization('horizontalBullet', 'gauge');
await PageObjects.lens.waitForVisualization();
await PageObjects.lens.waitForVisualization('gaugeChart');
const elementWithInfo = await find.byCssSelector('.echScreenReaderOnly');
const textContent = await elementWithInfo.getAttribute('textContent');
expect(textContent).to.contain('Average of bytes'); // it gets default title
@ -63,25 +63,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.lens.retrySetValue('lnsToolbarGaugeLabelMinor-select', 'custom', {});
await PageObjects.lens.retrySetValue('lnsToolbarGaugeLabelMinor', 'custom subtitle');
await PageObjects.lens.waitForVisualization();
await PageObjects.lens.waitForVisualization('gaugeChart');
await PageObjects.lens.openDimensionEditor(
'lnsGauge_goalDimensionPanel > lns-empty-dimension'
);
await PageObjects.lens.waitForVisualization();
await PageObjects.lens.waitForVisualization('gaugeChart');
await PageObjects.lens.closeDimensionEditor();
await PageObjects.lens.openDimensionEditor(
'lnsGauge_minDimensionPanel > lns-empty-dimension-suggested-value'
);
await PageObjects.lens.retrySetValue('lns-indexPattern-static_value-input', '1000');
await PageObjects.lens.waitForVisualization();
await PageObjects.lens.waitForVisualization('gaugeChart');
await PageObjects.lens.closeDimensionEditor();
await PageObjects.lens.openDimensionEditor(
'lnsGauge_maxDimensionPanel > lns-empty-dimension-suggested-value'
);
await PageObjects.lens.retrySetValue('lns-indexPattern-static_value-input', '25000');
await PageObjects.lens.waitForVisualization();
await PageObjects.lens.waitForVisualization('gaugeChart');
await PageObjects.lens.closeDimensionEditor();
const elementWithInfo = await find.byCssSelector('.echScreenReaderOnly');

View file

@ -37,21 +37,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await testSubjects.setValue('lnsPalettePanel_dynamicColoring_range_value_1', '21000', {
clearWithKeyboard: true,
});
await PageObjects.lens.waitForVisualization();
await PageObjects.lens.waitForVisualization('mtrVis');
const styleObj = await PageObjects.lens.getMetricStyle();
expect(styleObj.color).to.be('rgb(32, 146, 128)');
});
it('should change the color when reverting the palette', async () => {
await testSubjects.click('lnsPalettePanel_dynamicColoring_reverseColors');
await PageObjects.lens.waitForVisualization();
await PageObjects.lens.waitForVisualization('mtrVis');
const styleObj = await PageObjects.lens.getMetricStyle();
expect(styleObj.color).to.be('rgb(204, 86, 66)');
});
it('should reset the color stops when changing palette to a predefined one', async () => {
await PageObjects.lens.changePaletteTo('temperature');
await PageObjects.lens.waitForVisualization();
await PageObjects.lens.waitForVisualization('mtrVis');
const styleObj = await PageObjects.lens.getMetricStyle();
expect(styleObj.color).to.be('rgb(235, 239, 245)');
});

View file

@ -1054,8 +1054,8 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
* @param count - expected count of metric
*/
async assertMetric(title: string, count: string) {
await this.assertExactText('[data-test-subj="lns_metric_title"]', title);
await this.assertExactText('[data-test-subj="lns_metric_value"]', count);
await this.assertExactText('[data-test-subj="metric_label"]', title);
await this.assertExactText('[data-test-subj="metric_value"]', count);
},
async setMetricDynamicColoring(coloringType: 'none' | 'labels' | 'background') {
@ -1063,7 +1063,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
},
async getMetricStyle() {
const el = await testSubjects.find('lnsVisualizationContainer');
const el = await testSubjects.find('metric_value');
const styleString = await el.getAttribute('style');
return styleString.split(';').reduce<Record<string, string>>((memo, cssLine) => {
const [prop, value] = cssLine.split(':');
@ -1152,9 +1152,11 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
expect(focusedElementText).to.eql(name);
},
async waitForVisualization() {
async waitForVisualization(visDataTestSubj?: string) {
async function getRenderingCount() {
const visualizationContainer = await testSubjects.find('lnsVisualizationContainer');
const visualizationContainer = await testSubjects.find(
visDataTestSubj || 'lnsVisualizationContainer'
);
const renderingCount = await visualizationContainer.getAttribute('data-rendering-count');
return Number(renderingCount);
}