mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Lens] Metric style options improvements (#186929)
## Summary Adds 3 new options to the new `Metric` vis including: - Title/subtitle alignment - Value alignment - Icon alignment - Value fontSizing
This commit is contained in:
parent
f2614a45d9
commit
234f97ab69
30 changed files with 848 additions and 234 deletions
|
@ -53,7 +53,7 @@ export class EuiButtonGroupTestHarness {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns selected value of button group
|
||||
* Returns selected option of button group
|
||||
*/
|
||||
public get selected() {
|
||||
return within(this.#buttonGroup).getByRole('button', { pressed: true });
|
||||
|
@ -136,3 +136,59 @@ export class EuiSuperDatePickerTestHarness {
|
|||
userEvent.click(screen.getByRole('button', { name: 'Refresh' }));
|
||||
}
|
||||
}
|
||||
|
||||
export class EuiSelectTestHarness {
|
||||
#testId: string;
|
||||
|
||||
/**
|
||||
* Returns select or throws
|
||||
*/
|
||||
get #selectEl() {
|
||||
return screen.getByTestId(this.#testId);
|
||||
}
|
||||
|
||||
constructor(testId: string) {
|
||||
this.#testId = testId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `data-test-subj` of select
|
||||
*/
|
||||
public get testId() {
|
||||
return this.#testId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns button select if found, otherwise `null`
|
||||
*/
|
||||
public get self() {
|
||||
return screen.queryByTestId(this.#testId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all options of select
|
||||
*/
|
||||
public get options(): HTMLOptionElement[] {
|
||||
return within(this.#selectEl).getAllByRole('option');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected option
|
||||
*/
|
||||
public get selected() {
|
||||
return (this.#selectEl as HTMLSelectElement).value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select option by value
|
||||
*/
|
||||
public select(optionName: string | RegExp) {
|
||||
const option = this.options.find((o) => o.value === optionName)?.value;
|
||||
|
||||
if (!option) {
|
||||
throw new Error(`Option [${optionName}] not found`);
|
||||
}
|
||||
|
||||
fireEvent.change(this.#selectEl, { target: { value: option } });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,10 @@ describe('interpreter/functions#metricVis', () => {
|
|||
progressDirection: 'horizontal',
|
||||
maxCols: 1,
|
||||
inspectorTableId: 'random-id',
|
||||
titlesTextAlign: 'left',
|
||||
valuesTextAlign: 'right',
|
||||
iconAlign: 'left',
|
||||
valueFontSize: 'default',
|
||||
};
|
||||
|
||||
it('should pass over overrides from variables', async () => {
|
||||
|
|
|
@ -77,6 +77,30 @@ export const metricVisFunction = (): MetricVisExpressionFunctionDefinition => ({
|
|||
'The direction the progress bar should grow. Must be provided to render a progress bar.',
|
||||
}),
|
||||
},
|
||||
titlesTextAlign: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionMetricVis.function.titlesTextAlign.help', {
|
||||
defaultMessage: 'The alignment of the Title and Subtitle.',
|
||||
}),
|
||||
},
|
||||
valuesTextAlign: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionMetricVis.function.valuesTextAlign.help', {
|
||||
defaultMessage: 'The alignment of the Primary and Secondary Metric.',
|
||||
}),
|
||||
},
|
||||
iconAlign: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionMetricVis.function.iconAlign.help', {
|
||||
defaultMessage: 'The alignment of icon.',
|
||||
}),
|
||||
},
|
||||
valueFontSize: {
|
||||
types: ['string', 'number'],
|
||||
help: i18n.translate('expressionMetricVis.function.valueFontSize.help', {
|
||||
defaultMessage: 'The value font size.',
|
||||
}),
|
||||
},
|
||||
color: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionMetricVis.function.color.help', {
|
||||
|
@ -189,6 +213,10 @@ export const metricVisFunction = (): MetricVisExpressionFunctionDefinition => ({
|
|||
icon: args.icon,
|
||||
palette: args.palette?.params,
|
||||
progressDirection: args.progressDirection,
|
||||
titlesTextAlign: args.titlesTextAlign,
|
||||
valuesTextAlign: args.valuesTextAlign,
|
||||
iconAlign: args.iconAlign,
|
||||
valueFontSize: args.valueFontSize,
|
||||
maxCols: args.maxCols,
|
||||
minTiles: args.minTiles,
|
||||
trends: args.trendline?.trends,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import type { PaletteOutput } from '@kbn/coloring';
|
||||
import { LayoutDirection, MetricWTrend } from '@elastic/charts';
|
||||
import { LayoutDirection, MetricStyle, MetricWTrend } from '@elastic/charts';
|
||||
import { $Values } from '@kbn/utility-types';
|
||||
import {
|
||||
Datatable,
|
||||
|
@ -38,6 +38,10 @@ export interface MetricArguments {
|
|||
subtitle?: string;
|
||||
secondaryPrefix?: string;
|
||||
progressDirection?: LayoutDirection;
|
||||
titlesTextAlign: MetricStyle['titlesTextAlign'];
|
||||
valuesTextAlign: MetricStyle['valuesTextAlign'];
|
||||
iconAlign: MetricStyle['iconAlign'];
|
||||
valueFontSize: MetricStyle['valueFontSize'];
|
||||
color?: string;
|
||||
icon?: string;
|
||||
palette?: PaletteOutput<CustomPaletteState>;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
|
||||
import { CustomPaletteState } from '@kbn/charts-plugin/common';
|
||||
import { LayoutDirection } from '@elastic/charts';
|
||||
import { LayoutDirection, MetricStyle } from '@elastic/charts';
|
||||
import { TrendlineResult } from './expression_functions';
|
||||
|
||||
export const visType = 'metric';
|
||||
|
@ -27,6 +27,10 @@ export interface MetricVisParam {
|
|||
icon?: string;
|
||||
palette?: CustomPaletteState;
|
||||
progressDirection?: LayoutDirection;
|
||||
titlesTextAlign: MetricStyle['titlesTextAlign'];
|
||||
valuesTextAlign: MetricStyle['valuesTextAlign'];
|
||||
iconAlign: MetricStyle['iconAlign'];
|
||||
valueFontSize: MetricStyle['valueFontSize'];
|
||||
maxCols: number;
|
||||
minTiles?: number;
|
||||
trends?: TrendlineResult['trends'];
|
||||
|
|
|
@ -23,7 +23,7 @@ import { SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
|
|||
import { SerializableRecord } from '@kbn/utility-types';
|
||||
import type { IUiSettingsClient } from '@kbn/core/public';
|
||||
import { CustomPaletteState } from '@kbn/charts-plugin/common/expressions/palette/types';
|
||||
import { DimensionsVisParam } from '../../common';
|
||||
import { DimensionsVisParam, MetricVisParam } from '../../common';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { DEFAULT_TRENDLINE_NAME } from '../../common/constants';
|
||||
import faker from 'faker';
|
||||
|
@ -73,6 +73,15 @@ const dayOfWeekColumnId = 'col-0-0';
|
|||
const basePriceColumnId = 'col-1-1';
|
||||
const minPriceColumnId = 'col-2-2';
|
||||
|
||||
const defaultMetricParams: MetricVisParam = {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
titlesTextAlign: 'left',
|
||||
valuesTextAlign: 'right',
|
||||
iconAlign: 'left',
|
||||
valueFontSize: 'default',
|
||||
};
|
||||
|
||||
const table: Datatable = {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
|
@ -217,8 +226,7 @@ describe('MetricVisComponent', function () {
|
|||
describe('single metric', () => {
|
||||
const config: Props['config'] = {
|
||||
metric: {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
...defaultMetricParams,
|
||||
icon: 'empty',
|
||||
},
|
||||
dimensions: {
|
||||
|
@ -402,8 +410,7 @@ describe('MetricVisComponent', function () {
|
|||
describe('metric grid', () => {
|
||||
const config: Props['config'] = {
|
||||
metric: {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
...defaultMetricParams,
|
||||
},
|
||||
dimensions: {
|
||||
metric: basePriceColumnId,
|
||||
|
@ -856,8 +863,7 @@ describe('MetricVisComponent', function () {
|
|||
data={table}
|
||||
config={{
|
||||
metric: {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
...defaultMetricParams,
|
||||
},
|
||||
dimensions: {
|
||||
metric: basePriceColumnId,
|
||||
|
@ -911,8 +917,7 @@ describe('MetricVisComponent', function () {
|
|||
<MetricVis
|
||||
config={{
|
||||
metric: {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
...defaultMetricParams,
|
||||
},
|
||||
dimensions: {
|
||||
metric: basePriceColumnId,
|
||||
|
@ -953,8 +958,7 @@ describe('MetricVisComponent', function () {
|
|||
<MetricVis
|
||||
config={{
|
||||
metric: {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
...defaultMetricParams,
|
||||
},
|
||||
dimensions: {
|
||||
metric: metricId,
|
||||
|
@ -980,8 +984,7 @@ describe('MetricVisComponent', function () {
|
|||
<MetricVis
|
||||
config={{
|
||||
metric: {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
...defaultMetricParams,
|
||||
},
|
||||
dimensions: {
|
||||
metric: basePriceColumnId,
|
||||
|
@ -1057,8 +1060,7 @@ describe('MetricVisComponent', function () {
|
|||
<MetricVis
|
||||
config={{
|
||||
metric: {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
...defaultMetricParams,
|
||||
},
|
||||
dimensions: {
|
||||
metric: basePriceColumnId,
|
||||
|
@ -1088,8 +1090,7 @@ describe('MetricVisComponent', function () {
|
|||
metric: basePriceColumnId,
|
||||
},
|
||||
metric: {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
...defaultMetricParams,
|
||||
// should be overridden
|
||||
color: 'static-color',
|
||||
palette: {
|
||||
|
@ -1141,9 +1142,8 @@ describe('MetricVisComponent', function () {
|
|||
config={{
|
||||
dimensions,
|
||||
metric: {
|
||||
...defaultMetricParams,
|
||||
palette,
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
},
|
||||
}}
|
||||
data={table}
|
||||
|
@ -1205,8 +1205,7 @@ describe('MetricVisComponent', function () {
|
|||
metric: basePriceColumnId,
|
||||
},
|
||||
metric: {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
...defaultMetricParams,
|
||||
color: staticColor,
|
||||
palette: undefined,
|
||||
},
|
||||
|
@ -1230,8 +1229,7 @@ describe('MetricVisComponent', function () {
|
|||
metric: basePriceColumnId,
|
||||
},
|
||||
metric: {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
...defaultMetricParams,
|
||||
color: undefined,
|
||||
palette: undefined,
|
||||
},
|
||||
|
@ -1260,8 +1258,7 @@ describe('MetricVisComponent', function () {
|
|||
) => {
|
||||
const config: Props['config'] = {
|
||||
metric: {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
...defaultMetricParams,
|
||||
},
|
||||
dimensions: {
|
||||
metric: '1',
|
||||
|
@ -1416,8 +1413,7 @@ describe('MetricVisComponent', function () {
|
|||
<MetricVis
|
||||
config={{
|
||||
metric: {
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
...defaultMetricParams,
|
||||
},
|
||||
dimensions: {
|
||||
metric: basePriceColumnId,
|
||||
|
|
|
@ -335,6 +335,10 @@ export const MetricVis = ({
|
|||
barBackground: euiThemeVars.euiColorLightShade,
|
||||
emptyBackground: euiThemeVars.euiColorEmptyShade,
|
||||
blendingBackground: euiThemeVars.euiColorEmptyShade,
|
||||
titlesTextAlign: config.metric.titlesTextAlign,
|
||||
valuesTextAlign: config.metric.valuesTextAlign,
|
||||
iconAlign: config.metric.iconAlign,
|
||||
valueFontSize: config.metric.valueFontSize,
|
||||
},
|
||||
},
|
||||
...(Array.isArray(settingsThemeOverrides)
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
.lnsVisToolbar__popover {
|
||||
width: 404px;
|
||||
width: 410px;
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ export const HeatmapToolbar = memo(
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" responsive={false}>
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.shared.curveLabel', {
|
||||
title={i18n.translate('xpack.lens.shared.visualOptionsLabel', {
|
||||
defaultMessage: 'Visual options',
|
||||
})}
|
||||
type="visualOptions"
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { OptionalKeys } from 'utility-types';
|
||||
import { MetricVisualizationState } from './types';
|
||||
|
||||
export const LENS_METRIC_ID = 'lnsMetric';
|
||||
|
||||
export const GROUP_ID = {
|
||||
|
@ -17,3 +20,23 @@ export const GROUP_ID = {
|
|||
TREND_TIME: 'trendTime',
|
||||
TREND_BREAKDOWN_BY: 'trendBreakdownBy',
|
||||
} as const;
|
||||
|
||||
type MetricVisualizationStateOptionals = Pick<
|
||||
MetricVisualizationState,
|
||||
OptionalKeys<MetricVisualizationState>
|
||||
>;
|
||||
|
||||
/**
|
||||
* Defaults for select optional Metric vis state options
|
||||
*/
|
||||
export const metricStateDefaults: Required<
|
||||
Pick<
|
||||
MetricVisualizationStateOptionals,
|
||||
'titlesTextAlign' | 'valuesTextAlign' | 'iconAlign' | 'valueFontMode'
|
||||
>
|
||||
> = {
|
||||
titlesTextAlign: 'left',
|
||||
valuesTextAlign: 'right',
|
||||
iconAlign: 'left',
|
||||
valueFontMode: 'default',
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
|
|||
import { euiLightVars } from '@kbn/ui-theme';
|
||||
import { CustomPaletteParams, PaletteOutput, PaletteRegistry } from '@kbn/coloring';
|
||||
import { VisualizationDimensionEditorProps } from '../../types';
|
||||
import { MetricVisualizationState } from './visualization';
|
||||
import { MetricVisualizationState } from './types';
|
||||
import {
|
||||
DimensionEditor,
|
||||
DimensionEditorAdditionalSection,
|
||||
|
@ -59,6 +59,10 @@ describe('dimension editor', () => {
|
|||
palette,
|
||||
icon: 'tag',
|
||||
showBar: true,
|
||||
titlesTextAlign: 'left',
|
||||
valuesTextAlign: 'right',
|
||||
iconAlign: 'left',
|
||||
valueFontMode: 'default',
|
||||
trendlineLayerId: 'second',
|
||||
trendlineLayerType: 'metricTrendline',
|
||||
trendlineMetricAccessor: 'trendline-metric-col-id',
|
||||
|
|
|
@ -34,14 +34,10 @@ import { isNumericFieldForDatatable } from '../../../common/expressions/datatabl
|
|||
import { applyPaletteParams, PalettePanelContainer } from '../../shared_components';
|
||||
import type { VisualizationDimensionEditorProps } from '../../types';
|
||||
import { defaultNumberPaletteParams, defaultPercentagePaletteParams } from './palette_config';
|
||||
import {
|
||||
DEFAULT_MAX_COLUMNS,
|
||||
getDefaultColor,
|
||||
MetricVisualizationState,
|
||||
showingBar,
|
||||
} from './visualization';
|
||||
import { DEFAULT_MAX_COLUMNS, getDefaultColor, showingBar } from './visualization';
|
||||
import { CollapseSetting } from '../../shared_components/collapse_setting';
|
||||
import { iconsSet } from './icon_set';
|
||||
import { MetricVisualizationState } from './types';
|
||||
|
||||
export type SupportingVisType = 'none' | 'bar' | 'trendline';
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { getSuggestions } from './suggestions';
|
||||
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
|
||||
import { MetricVisualizationState } from './visualization';
|
||||
import { MetricVisualizationState } from './types';
|
||||
import { IconChartMetric } from '@kbn/chart-icons';
|
||||
|
||||
const metricColumn = {
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
import { IconChartMetric } from '@kbn/chart-icons';
|
||||
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
|
||||
import type { TableSuggestion, Visualization } from '../../types';
|
||||
import { metricLabel, MetricVisualizationState, supportedDataTypes } from './visualization';
|
||||
import { MetricVisualizationState } from './types';
|
||||
import { metricLabel, supportedDataTypes } from './visualization';
|
||||
|
||||
const MAX_BUCKETED_COLUMNS = 1;
|
||||
const MAX_METRIC_COLUMNS = 2; // primary and secondary metric
|
||||
|
|
|
@ -17,7 +17,9 @@ import { CollapseArgs, CollapseFunction } from '../../../common/expressions';
|
|||
import { CollapseExpressionFunction } from '../../../common/expressions/collapse/types';
|
||||
import { DatasourceLayers } from '../../types';
|
||||
import { showingBar } from './metric_visualization';
|
||||
import { DEFAULT_MAX_COLUMNS, getDefaultColor, MetricVisualizationState } from './visualization';
|
||||
import { DEFAULT_MAX_COLUMNS, getDefaultColor } from './visualization';
|
||||
import { MetricVisualizationState } from './types';
|
||||
import { metricStateDefaults } from './constants';
|
||||
|
||||
// TODO - deduplicate with gauges?
|
||||
function computePaletteParams(params: CustomPaletteParams) {
|
||||
|
@ -148,6 +150,10 @@ export const toExpression = (
|
|||
progressDirection: showingBar(state)
|
||||
? state.progressDirection || LayoutDirection.Vertical
|
||||
: undefined,
|
||||
titlesTextAlign: state.titlesTextAlign ?? metricStateDefaults.titlesTextAlign,
|
||||
valuesTextAlign: state.valuesTextAlign ?? metricStateDefaults.valuesTextAlign,
|
||||
iconAlign: state.iconAlign ?? metricStateDefaults.iconAlign,
|
||||
valueFontSize: state.valueFontMode ?? metricStateDefaults.valueFontMode,
|
||||
color: state.color || getDefaultColor(state, isMetricNumeric),
|
||||
icon: state.icon,
|
||||
palette:
|
||||
|
|
|
@ -1,93 +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 { CustomPaletteParams, PaletteOutput } from '@kbn/coloring';
|
||||
import { Toolbar } from './toolbar';
|
||||
import { MetricVisualizationState } from './visualization';
|
||||
import { createMockFramePublicAPI } from '../../mocks';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
|
||||
describe('metric toolbar', () => {
|
||||
const palette: PaletteOutput<CustomPaletteParams> = {
|
||||
type: 'palette',
|
||||
name: 'foo',
|
||||
params: {
|
||||
rangeType: 'percent',
|
||||
},
|
||||
};
|
||||
|
||||
const fullState: Required<MetricVisualizationState> = {
|
||||
layerId: 'first',
|
||||
layerType: 'data',
|
||||
metricAccessor: 'metric-col-id',
|
||||
secondaryMetricAccessor: 'secondary-metric-col-id',
|
||||
maxAccessor: 'max-metric-col-id',
|
||||
breakdownByAccessor: 'breakdown-col-id',
|
||||
collapseFn: 'sum',
|
||||
subtitle: 'subtitle',
|
||||
secondaryPrefix: 'extra-text',
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
color: 'static-color',
|
||||
icon: 'compute',
|
||||
palette,
|
||||
showBar: true,
|
||||
trendlineLayerId: 'second',
|
||||
trendlineLayerType: 'metricTrendline',
|
||||
trendlineMetricAccessor: 'trendline-metric-col-id',
|
||||
trendlineSecondaryMetricAccessor: 'trendline-secondary-metric-col-id',
|
||||
trendlineTimeAccessor: 'trendline-time-col-id',
|
||||
trendlineBreakdownByAccessor: 'trendline-breakdown-col-id',
|
||||
};
|
||||
|
||||
const frame = createMockFramePublicAPI();
|
||||
|
||||
const mockSetState = jest.fn();
|
||||
|
||||
const renderToolbar = (state: MetricVisualizationState) => {
|
||||
return { ...render(<Toolbar state={state} setState={mockSetState} frame={frame} />) };
|
||||
};
|
||||
|
||||
afterEach(() => mockSetState.mockClear());
|
||||
|
||||
describe('text options', () => {
|
||||
it('sets a subtitle', async () => {
|
||||
renderToolbar({
|
||||
...fullState,
|
||||
breakdownByAccessor: undefined,
|
||||
});
|
||||
const textOptionsButton = screen.getByTestId('lnsLabelsButton');
|
||||
textOptionsButton.click();
|
||||
|
||||
const newSubtitle = 'new subtitle hey';
|
||||
const subtitleField = screen.getByDisplayValue('subtitle');
|
||||
// cannot use userEvent because the element cannot be clicked on
|
||||
fireEvent.change(subtitleField, { target: { value: newSubtitle + ' 1' } });
|
||||
await waitFor(() => expect(mockSetState).toHaveBeenCalled());
|
||||
fireEvent.change(subtitleField, { target: { value: newSubtitle + ' 2' } });
|
||||
await waitFor(() => expect(mockSetState).toHaveBeenCalledTimes(2));
|
||||
fireEvent.change(subtitleField, { target: { value: newSubtitle + ' 3' } });
|
||||
await waitFor(() => expect(mockSetState).toHaveBeenCalledTimes(3));
|
||||
expect(mockSetState.mock.calls.map(([state]) => state.subtitle)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"new subtitle hey 1",
|
||||
"new subtitle hey 2",
|
||||
"new subtitle hey 3",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('hides text options when has breakdown by', () => {
|
||||
renderToolbar({
|
||||
...fullState,
|
||||
breakdownByAccessor: 'some-accessor',
|
||||
});
|
||||
expect(screen.queryByTestId('lnsLabelsButton')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,62 +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, { useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiFormRow, EuiFieldText } from '@elastic/eui';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { VisualizationToolbarProps } from '../../types';
|
||||
import { ToolbarPopover } from '../../shared_components';
|
||||
import { MetricVisualizationState } from './visualization';
|
||||
|
||||
export function Toolbar(props: VisualizationToolbarProps<MetricVisualizationState>) {
|
||||
const { state, setState } = props;
|
||||
|
||||
const setSubtitle = useCallback(
|
||||
(prefix: string) => setState({ ...state, subtitle: prefix }),
|
||||
[setState, state]
|
||||
);
|
||||
|
||||
const { inputValue: subtitleInputVal, handleInputChange: handleSubtitleChange } =
|
||||
useDebouncedValue<string>(
|
||||
{
|
||||
onChange: setSubtitle,
|
||||
value: state.subtitle || '',
|
||||
},
|
||||
{ allowFalsyValue: true }
|
||||
);
|
||||
|
||||
const hasBreakdownBy = Boolean(state.breakdownByAccessor);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" responsive={false}>
|
||||
{!hasBreakdownBy && (
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.metric.labels', {
|
||||
defaultMessage: 'Labels',
|
||||
})}
|
||||
type="labels"
|
||||
groupPosition="none"
|
||||
buttonDataTestSubj="lnsLabelsButton"
|
||||
>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.lens.metric.subtitleLabel', {
|
||||
defaultMessage: 'Subtitle',
|
||||
})}
|
||||
fullWidth
|
||||
display="columnCompressed"
|
||||
>
|
||||
<EuiFieldText
|
||||
value={subtitleInputVal}
|
||||
onChange={({ target: { value } }) => handleSubtitleChange(value)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</ToolbarPopover>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 { Toolbar } from './toolbar';
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { CustomPaletteParams, PaletteOutput } from '@kbn/coloring';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { MetricVisualizationState } from '../types';
|
||||
import { LabelOptionsPopover } from './label_options_popover';
|
||||
|
||||
describe('LabelOptionsPopover', () => {
|
||||
const palette: PaletteOutput<CustomPaletteParams> = {
|
||||
type: 'palette',
|
||||
name: 'foo',
|
||||
params: {
|
||||
rangeType: 'percent',
|
||||
},
|
||||
};
|
||||
|
||||
const fullState: Required<MetricVisualizationState> = {
|
||||
layerId: 'first',
|
||||
layerType: 'data',
|
||||
metricAccessor: 'metric-col-id',
|
||||
secondaryMetricAccessor: 'secondary-metric-col-id',
|
||||
maxAccessor: 'max-metric-col-id',
|
||||
breakdownByAccessor: 'breakdown-col-id',
|
||||
collapseFn: 'sum',
|
||||
subtitle: 'subtitle',
|
||||
secondaryPrefix: 'extra-text',
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
color: 'static-color',
|
||||
icon: 'compute',
|
||||
palette,
|
||||
showBar: true,
|
||||
trendlineLayerId: 'second',
|
||||
trendlineLayerType: 'metricTrendline',
|
||||
trendlineMetricAccessor: 'trendline-metric-col-id',
|
||||
trendlineSecondaryMetricAccessor: 'trendline-secondary-metric-col-id',
|
||||
trendlineTimeAccessor: 'trendline-time-col-id',
|
||||
trendlineBreakdownByAccessor: 'trendline-breakdown-col-id',
|
||||
titlesTextAlign: 'left',
|
||||
valuesTextAlign: 'right',
|
||||
iconAlign: 'left',
|
||||
valueFontMode: 'default',
|
||||
};
|
||||
|
||||
const mockSetState = jest.fn();
|
||||
|
||||
const renderToolbarOptions = (state: MetricVisualizationState) => {
|
||||
return {
|
||||
...render(<LabelOptionsPopover state={state} setState={mockSetState} />),
|
||||
};
|
||||
};
|
||||
|
||||
afterEach(() => mockSetState.mockClear());
|
||||
|
||||
it('should set a subtitle', async () => {
|
||||
renderToolbarOptions({
|
||||
...fullState,
|
||||
breakdownByAccessor: undefined,
|
||||
});
|
||||
const labelOptionsButton = screen.getByTestId('lnsLabelsButton');
|
||||
labelOptionsButton.click();
|
||||
|
||||
const newSubtitle = 'new subtitle hey';
|
||||
const subtitleField = screen.getByDisplayValue('subtitle');
|
||||
// cannot use userEvent because the element cannot be clicked on
|
||||
fireEvent.change(subtitleField, { target: { value: newSubtitle + ' 1' } });
|
||||
await waitFor(() => expect(mockSetState).toHaveBeenCalled());
|
||||
fireEvent.change(subtitleField, { target: { value: newSubtitle + ' 2' } });
|
||||
await waitFor(() => expect(mockSetState).toHaveBeenCalledTimes(2));
|
||||
fireEvent.change(subtitleField, { target: { value: newSubtitle + ' 3' } });
|
||||
await waitFor(() => expect(mockSetState).toHaveBeenCalledTimes(3));
|
||||
expect(mockSetState.mock.calls.map(([state]) => state.subtitle)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"new subtitle hey 1",
|
||||
"new subtitle hey 2",
|
||||
"new subtitle hey 3",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should disable labels options when Metric has breakdown by', () => {
|
||||
renderToolbarOptions({
|
||||
...fullState,
|
||||
breakdownByAccessor: 'some-accessor',
|
||||
});
|
||||
expect(screen.getByTestId('lnsLabelsButton')).toBeDisabled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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, { FC, useCallback } from 'react';
|
||||
|
||||
import { EuiFormRow, EuiFieldText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { TooltipWrapper } from '@kbn/visualization-utils';
|
||||
import { ToolbarPopover } from '../../../shared_components';
|
||||
import { MetricVisualizationState } from '../types';
|
||||
|
||||
export interface LabelOptionsPopoverProps {
|
||||
state: MetricVisualizationState;
|
||||
setState: (newState: MetricVisualizationState) => void;
|
||||
}
|
||||
|
||||
export const LabelOptionsPopover: FC<LabelOptionsPopoverProps> = ({ state, setState }) => {
|
||||
const setSubtitle = useCallback(
|
||||
(prefix: string) => setState({ ...state, subtitle: prefix }),
|
||||
[setState, state]
|
||||
);
|
||||
|
||||
const { inputValue: subtitleInputVal, handleInputChange: handleSubtitleChange } =
|
||||
useDebouncedValue<string>(
|
||||
{
|
||||
onChange: setSubtitle,
|
||||
value: state.subtitle || '',
|
||||
},
|
||||
{ allowFalsyValue: true }
|
||||
);
|
||||
|
||||
const hasBreakdownBy = Boolean(state.breakdownByAccessor);
|
||||
|
||||
return (
|
||||
<TooltipWrapper
|
||||
tooltipContent={i18n.translate('xpack.lens.metric.toolbarLabelOptions.disabled', {
|
||||
defaultMessage: 'Not supported with Break down by',
|
||||
})}
|
||||
condition={hasBreakdownBy}
|
||||
position="bottom"
|
||||
>
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.metric.labels', {
|
||||
defaultMessage: 'Labels',
|
||||
})}
|
||||
type="labels"
|
||||
groupPosition="right"
|
||||
buttonDataTestSubj="lnsLabelsButton"
|
||||
isDisabled={hasBreakdownBy}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.lens.metric.subtitleLabel', {
|
||||
defaultMessage: 'Subtitle',
|
||||
})}
|
||||
fullWidth
|
||||
display="columnCompressed"
|
||||
>
|
||||
<EuiFieldText
|
||||
value={subtitleInputVal}
|
||||
onChange={({ target: { value } }) => handleSubtitleChange(value)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</ToolbarPopover>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { VisualizationToolbarProps } from '../../../types';
|
||||
import { LabelOptionsPopover } from './label_options_popover';
|
||||
import { VisualOptionsPopover } from './visual_options_popover';
|
||||
import { MetricVisualizationState } from '../types';
|
||||
|
||||
export function Toolbar(props: VisualizationToolbarProps<MetricVisualizationState>) {
|
||||
const { state, setState } = props;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" responsive={false}>
|
||||
<VisualOptionsPopover state={state} setState={setState} />
|
||||
<LabelOptionsPopover state={state} setState={setState} />
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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 { CustomPaletteParams, PaletteOutput } from '@kbn/coloring';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MetricVisualizationState } from '../types';
|
||||
import { VisualOptionsPopover } from './visual_options_popover';
|
||||
import { EuiButtonGroupTestHarness } from '@kbn/test-eui-helpers';
|
||||
|
||||
jest.mock('lodash', () => ({
|
||||
...jest.requireActual('lodash'),
|
||||
debounce: (fn: unknown) => fn,
|
||||
}));
|
||||
|
||||
describe('VisualOptionsPopover', () => {
|
||||
const palette: PaletteOutput<CustomPaletteParams> = {
|
||||
type: 'palette',
|
||||
name: 'foo',
|
||||
params: {
|
||||
rangeType: 'percent',
|
||||
},
|
||||
};
|
||||
|
||||
const fullState: Required<MetricVisualizationState> = {
|
||||
layerId: 'first',
|
||||
layerType: 'data',
|
||||
metricAccessor: 'metric-col-id',
|
||||
secondaryMetricAccessor: 'secondary-metric-col-id',
|
||||
maxAccessor: 'max-metric-col-id',
|
||||
breakdownByAccessor: 'breakdown-col-id',
|
||||
collapseFn: 'sum',
|
||||
subtitle: 'subtitle',
|
||||
secondaryPrefix: 'extra-text',
|
||||
progressDirection: 'vertical',
|
||||
maxCols: 5,
|
||||
color: 'static-color',
|
||||
icon: 'compute',
|
||||
palette,
|
||||
showBar: true,
|
||||
trendlineLayerId: 'second',
|
||||
trendlineLayerType: 'metricTrendline',
|
||||
trendlineMetricAccessor: 'trendline-metric-col-id',
|
||||
trendlineSecondaryMetricAccessor: 'trendline-secondary-metric-col-id',
|
||||
trendlineTimeAccessor: 'trendline-time-col-id',
|
||||
trendlineBreakdownByAccessor: 'trendline-breakdown-col-id',
|
||||
titlesTextAlign: 'left',
|
||||
valuesTextAlign: 'right',
|
||||
iconAlign: 'left',
|
||||
valueFontMode: 'default',
|
||||
};
|
||||
|
||||
const mockSetState = jest.fn();
|
||||
|
||||
const renderToolbarOptions = (state: MetricVisualizationState) => {
|
||||
return {
|
||||
...render(<VisualOptionsPopover state={state} setState={mockSetState} />),
|
||||
};
|
||||
};
|
||||
|
||||
afterEach(() => mockSetState.mockClear());
|
||||
|
||||
it('should set titlesTextAlign', async () => {
|
||||
renderToolbarOptions({ ...fullState });
|
||||
const textOptionsButton = screen.getByTestId('lnsVisualOptionsButton');
|
||||
textOptionsButton.click();
|
||||
|
||||
const titlesAlignBtnGroup = new EuiButtonGroupTestHarness('lens-titles-alignment-btn');
|
||||
|
||||
titlesAlignBtnGroup.select('Right');
|
||||
titlesAlignBtnGroup.select('Center');
|
||||
titlesAlignBtnGroup.select('Left');
|
||||
|
||||
expect(mockSetState.mock.calls.map(([s]) => s.titlesTextAlign)).toEqual([
|
||||
'right',
|
||||
'center',
|
||||
'left',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should set valuesTextAlign', async () => {
|
||||
renderToolbarOptions({ ...fullState });
|
||||
const textOptionsButton = screen.getByTestId('lnsVisualOptionsButton');
|
||||
textOptionsButton.click();
|
||||
|
||||
const valueAlignBtnGroup = new EuiButtonGroupTestHarness('lens-values-alignment-btn');
|
||||
|
||||
valueAlignBtnGroup.select('Center');
|
||||
valueAlignBtnGroup.select('Left');
|
||||
valueAlignBtnGroup.select('Right');
|
||||
|
||||
expect(mockSetState.mock.calls.map(([s]) => s.valuesTextAlign)).toEqual([
|
||||
'center',
|
||||
'left',
|
||||
'right',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should set valueFontMode', async () => {
|
||||
renderToolbarOptions({ ...fullState });
|
||||
const textOptionsButton = screen.getByTestId('lnsVisualOptionsButton');
|
||||
textOptionsButton.click();
|
||||
|
||||
const modeBtnGroup = new EuiButtonGroupTestHarness('lens-value-font-mode-btn');
|
||||
|
||||
expect(modeBtnGroup.selected.textContent).toBe('Default');
|
||||
|
||||
modeBtnGroup.select('Fit');
|
||||
modeBtnGroup.select('Default');
|
||||
|
||||
expect(mockSetState.mock.calls.map(([s]) => s.valueFontMode)).toEqual(['fit', 'default']);
|
||||
});
|
||||
|
||||
it('should set iconAlign', async () => {
|
||||
renderToolbarOptions({ ...fullState, icon: 'sortUp' });
|
||||
const textOptionsButton = screen.getByTestId('lnsVisualOptionsButton');
|
||||
textOptionsButton.click();
|
||||
|
||||
const iconAlignBtnGroup = new EuiButtonGroupTestHarness('lens-icon-alignment-btn');
|
||||
|
||||
expect(iconAlignBtnGroup.selected.textContent).toBe('Left');
|
||||
|
||||
iconAlignBtnGroup.select('Right');
|
||||
iconAlignBtnGroup.select('Left');
|
||||
|
||||
expect(mockSetState.mock.calls.map(([s]) => s.iconAlign)).toEqual(['right', 'left']);
|
||||
});
|
||||
|
||||
it.each([undefined, 'empty'])('should hide iconAlign option when icon is %j', async (icon) => {
|
||||
renderToolbarOptions({ ...fullState, icon });
|
||||
const textOptionsButton = screen.getByTestId('lnsVisualOptionsButton');
|
||||
textOptionsButton.click();
|
||||
|
||||
expect(screen.queryByTestId('lens-icon-alignment-btn')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
|
||||
import { EuiFormRow, EuiIconTip, EuiButtonGroup } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { MetricStyle } from '@elastic/charts';
|
||||
import { ToolbarPopover } from '../../../shared_components';
|
||||
import { MetricVisualizationState, ValueFontMode } from '../types';
|
||||
import { metricStateDefaults } from '../constants';
|
||||
|
||||
export interface VisualOptionsPopoverProps {
|
||||
state: MetricVisualizationState;
|
||||
setState: (newState: MetricVisualizationState) => void;
|
||||
}
|
||||
|
||||
export const VisualOptionsPopover: FC<VisualOptionsPopoverProps> = ({ state, setState }) => {
|
||||
return (
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.shared.visualOptionsLabel', {
|
||||
defaultMessage: 'Visual options',
|
||||
})}
|
||||
type="visualOptions"
|
||||
groupPosition="left"
|
||||
buttonDataTestSubj="lnsVisualOptionsButton"
|
||||
>
|
||||
<TitlesAlignmentOption
|
||||
value={state.titlesTextAlign ?? metricStateDefaults.titlesTextAlign}
|
||||
onChange={(titlesTextAlign) => {
|
||||
setState({ ...state, titlesTextAlign });
|
||||
}}
|
||||
/>
|
||||
<ValuesAlignmentOption
|
||||
value={state.valuesTextAlign ?? metricStateDefaults.valuesTextAlign}
|
||||
onChange={(valuesTextAlign) => {
|
||||
setState({ ...state, valuesTextAlign });
|
||||
}}
|
||||
/>
|
||||
{state.icon && state.icon !== 'empty' && (
|
||||
<IconAlignmentOption
|
||||
value={state.iconAlign ?? metricStateDefaults.iconAlign}
|
||||
onChange={(iconAlign) => {
|
||||
setState({ ...state, iconAlign });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ValueFontOption
|
||||
value={state.valueFontMode ?? metricStateDefaults.valueFontMode}
|
||||
onChange={(value) => {
|
||||
setState({ ...state, valueFontMode: value });
|
||||
}}
|
||||
/>
|
||||
</ToolbarPopover>
|
||||
);
|
||||
};
|
||||
|
||||
const valueFontModes: Array<{
|
||||
id: ValueFontMode;
|
||||
label: string;
|
||||
}> = [
|
||||
{
|
||||
id: 'default',
|
||||
label: i18n.translate('xpack.lens.metric.toolbarVisOptions.default', {
|
||||
defaultMessage: 'Default',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'fit',
|
||||
label: i18n.translate('xpack.lens.metric.toolbarVisOptions.fit', {
|
||||
defaultMessage: 'Fit',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
function ValueFontOption({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: typeof valueFontModes[number]['id'];
|
||||
onChange: (mode: ValueFontMode) => void;
|
||||
}) {
|
||||
const label = i18n.translate('xpack.lens.metric.toolbarVisOptions.valueFontSize', {
|
||||
defaultMessage: 'Value fontSize',
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={
|
||||
<span>
|
||||
{label}{' '}
|
||||
<EuiIconTip
|
||||
content={i18n.translate('xpack.lens.metric.toolbarVisOptions.valueFontSizeTip', {
|
||||
defaultMessage: 'Font size of the Primary metric value',
|
||||
})}
|
||||
iconProps={{
|
||||
className: 'eui-alignTop',
|
||||
}}
|
||||
color="subdued"
|
||||
position="top"
|
||||
size="s"
|
||||
type="questionInCircle"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<EuiButtonGroup
|
||||
isFullWidth
|
||||
legend={label}
|
||||
data-test-subj="lens-value-font-mode-btn"
|
||||
buttonSize="compressed"
|
||||
idSelected={value}
|
||||
options={valueFontModes}
|
||||
onChange={(mode) => {
|
||||
onChange(mode as ValueFontMode);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
const alignmentOptions: Array<{
|
||||
id: MetricStyle['titlesTextAlign'] | MetricStyle['valuesTextAlign'];
|
||||
label: string;
|
||||
}> = [
|
||||
{
|
||||
id: 'left',
|
||||
label: i18n.translate('xpack.lens.shared.left', {
|
||||
defaultMessage: 'Left',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'center',
|
||||
label: i18n.translate('xpack.lens.shared.center', {
|
||||
defaultMessage: 'Center',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'right',
|
||||
label: i18n.translate('xpack.lens.shared.right', {
|
||||
defaultMessage: 'Right',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
function TitlesAlignmentOption({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: MetricStyle['titlesTextAlign'];
|
||||
onChange: (alignment: MetricStyle['titlesTextAlign']) => void;
|
||||
}) {
|
||||
const label = i18n.translate('xpack.lens.metric.toolbarVisOptions.titlesAlignment', {
|
||||
defaultMessage: 'Titles alignment',
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={
|
||||
<span>
|
||||
{label}{' '}
|
||||
<EuiIconTip
|
||||
content={i18n.translate('xpack.lens.metric.toolbarVisOptions.titlesAlignmentTip', {
|
||||
defaultMessage: 'Alignment of the Title and Subtitle',
|
||||
})}
|
||||
iconProps={{
|
||||
className: 'eui-alignTop',
|
||||
}}
|
||||
color="subdued"
|
||||
position="top"
|
||||
size="s"
|
||||
type="questionInCircle"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<EuiButtonGroup
|
||||
isFullWidth
|
||||
legend={label}
|
||||
data-test-subj="lens-titles-alignment-btn"
|
||||
buttonSize="compressed"
|
||||
options={alignmentOptions}
|
||||
idSelected={value}
|
||||
onChange={(alignment) => {
|
||||
onChange(alignment as MetricStyle['titlesTextAlign']);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
function ValuesAlignmentOption({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: MetricStyle['valuesTextAlign'];
|
||||
onChange: (alignment: MetricStyle['valuesTextAlign']) => void;
|
||||
}) {
|
||||
const label = i18n.translate('xpack.lens.metric.toolbarVisOptions.valuesAlignment', {
|
||||
defaultMessage: 'Values alignment',
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={
|
||||
<span>
|
||||
{label}{' '}
|
||||
<EuiIconTip
|
||||
color="subdued"
|
||||
content={i18n.translate('xpack.lens.metric.toolbarVisOptions.valuesAlignmentTip', {
|
||||
defaultMessage: 'Alignment of the Primary and Secondary Metric',
|
||||
})}
|
||||
iconProps={{
|
||||
className: 'eui-alignTop',
|
||||
}}
|
||||
position="top"
|
||||
size="s"
|
||||
type="questionInCircle"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<EuiButtonGroup
|
||||
isFullWidth
|
||||
legend={label}
|
||||
data-test-subj="lens-values-alignment-btn"
|
||||
buttonSize="compressed"
|
||||
options={alignmentOptions}
|
||||
idSelected={value}
|
||||
onChange={(alignment) => {
|
||||
onChange(alignment as MetricStyle['valuesTextAlign']);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
const iconAlignmentOptions: Array<{
|
||||
id: MetricStyle['titlesTextAlign'] | MetricStyle['valuesTextAlign'];
|
||||
label: string;
|
||||
}> = [
|
||||
{
|
||||
id: 'left',
|
||||
label: i18n.translate('xpack.lens.shared.left', {
|
||||
defaultMessage: 'Left',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'right',
|
||||
label: i18n.translate('xpack.lens.shared.right', {
|
||||
defaultMessage: 'Right',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
function IconAlignmentOption({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: MetricStyle['iconAlign'];
|
||||
onChange: (alignment: MetricStyle['iconAlign']) => void;
|
||||
}) {
|
||||
const label = i18n.translate('xpack.lens.metric.toolbarVisOptions.iconAlignment', {
|
||||
defaultMessage: 'Icon alignment',
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFormRow display="columnCompressed" label={label}>
|
||||
<EuiButtonGroup
|
||||
isFullWidth
|
||||
legend={label}
|
||||
data-test-subj="lens-icon-alignment-btn"
|
||||
buttonSize="compressed"
|
||||
options={iconAlignmentOptions}
|
||||
idSelected={value}
|
||||
onChange={(alignment) => {
|
||||
onChange(alignment as MetricStyle['iconAlign']);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
|
@ -5,11 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { LayoutDirection } from '@elastic/charts';
|
||||
import type { LayoutDirection, MetricStyle } from '@elastic/charts';
|
||||
import type { PaletteOutput, CustomPaletteParams } from '@kbn/coloring';
|
||||
import type { CollapseFunction } from '@kbn/visualizations-plugin/common';
|
||||
import type { LayerType } from '../../../common/types';
|
||||
|
||||
export type ValueFontMode = Exclude<MetricStyle['valueFontSize'], number>;
|
||||
|
||||
export interface MetricVisualizationState {
|
||||
layerId: string;
|
||||
layerType: LayerType;
|
||||
|
@ -24,7 +26,12 @@ export interface MetricVisualizationState {
|
|||
secondaryPrefix?: string;
|
||||
progressDirection?: LayoutDirection;
|
||||
showBar?: boolean;
|
||||
titlesTextAlign?: MetricStyle['titlesTextAlign'];
|
||||
valuesTextAlign?: MetricStyle['valuesTextAlign'];
|
||||
iconAlign?: MetricStyle['iconAlign'];
|
||||
valueFontMode?: ValueFontMode;
|
||||
color?: string;
|
||||
icon?: string;
|
||||
palette?: PaletteOutput<CustomPaletteParams>;
|
||||
maxCols?: number;
|
||||
|
||||
|
|
|
@ -19,10 +19,11 @@ import {
|
|||
Visualization,
|
||||
} from '../../types';
|
||||
import { GROUP_ID } from './constants';
|
||||
import { getMetricVisualization, MetricVisualizationState } from './visualization';
|
||||
import { getMetricVisualization } from './visualization';
|
||||
import { themeServiceMock } from '@kbn/core/public/mocks';
|
||||
import { Ast } from '@kbn/interpreter';
|
||||
import { LayoutDirection } from '@elastic/charts';
|
||||
import { MetricVisualizationState } from './types';
|
||||
|
||||
const paletteService = chartPluginMock.createPaletteRegistry();
|
||||
const theme = themeServiceMock.createStartContract();
|
||||
|
@ -76,6 +77,10 @@ describe('metric visualization', () => {
|
|||
color: 'static-color',
|
||||
palette,
|
||||
showBar: false,
|
||||
titlesTextAlign: 'left',
|
||||
valuesTextAlign: 'right',
|
||||
iconAlign: 'left',
|
||||
valueFontMode: 'default',
|
||||
};
|
||||
|
||||
const fullStateWTrend: Required<MetricVisualizationState> = {
|
||||
|
@ -316,6 +321,9 @@ describe('metric visualization', () => {
|
|||
"icon": Array [
|
||||
"empty",
|
||||
],
|
||||
"iconAlign": Array [
|
||||
"left",
|
||||
],
|
||||
"inspectorTableId": Array [
|
||||
"first",
|
||||
],
|
||||
|
@ -353,7 +361,16 @@ describe('metric visualization', () => {
|
|||
"subtitle": Array [
|
||||
"subtitle",
|
||||
],
|
||||
"titlesTextAlign": Array [
|
||||
"left",
|
||||
],
|
||||
"trendline": Array [],
|
||||
"valueFontSize": Array [
|
||||
"default",
|
||||
],
|
||||
"valuesTextAlign": Array [
|
||||
"right",
|
||||
],
|
||||
},
|
||||
"function": "metricVis",
|
||||
"type": "function",
|
||||
|
@ -380,6 +397,9 @@ describe('metric visualization', () => {
|
|||
"icon": Array [
|
||||
"empty",
|
||||
],
|
||||
"iconAlign": Array [
|
||||
"left",
|
||||
],
|
||||
"inspectorTableId": Array [
|
||||
"first",
|
||||
],
|
||||
|
@ -420,7 +440,16 @@ describe('metric visualization', () => {
|
|||
"subtitle": Array [
|
||||
"subtitle",
|
||||
],
|
||||
"titlesTextAlign": Array [
|
||||
"left",
|
||||
],
|
||||
"trendline": Array [],
|
||||
"valueFontSize": Array [
|
||||
"default",
|
||||
],
|
||||
"valuesTextAlign": Array [
|
||||
"right",
|
||||
],
|
||||
},
|
||||
"function": "metricVis",
|
||||
"type": "function",
|
||||
|
@ -778,8 +807,12 @@ describe('metric visualization', () => {
|
|||
expect(visualization.clearLayer(fullState, 'some-id', 'indexPattern1')).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"icon": "empty",
|
||||
"iconAlign": "left",
|
||||
"layerId": "first",
|
||||
"layerType": "data",
|
||||
"titlesTextAlign": "left",
|
||||
"valueFontMode": "default",
|
||||
"valuesTextAlign": "right",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -7,16 +7,13 @@
|
|||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { PaletteOutput, PaletteRegistry, CustomPaletteParams } from '@kbn/coloring';
|
||||
import { PaletteRegistry } from '@kbn/coloring';
|
||||
import { ThemeServiceStart } from '@kbn/core/public';
|
||||
import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
|
||||
import { LayoutDirection } from '@elastic/charts';
|
||||
import { euiLightVars, euiThemeVars } from '@kbn/ui-theme';
|
||||
import { IconChartMetric } from '@kbn/chart-icons';
|
||||
import { AccessorConfig } from '@kbn/visualization-ui-components';
|
||||
import { isNumericFieldForDatatable } from '../../../common/expressions/datatable/utils';
|
||||
import { CollapseFunction } from '../../../common/expressions';
|
||||
import type { LayerType } from '../../../common/types';
|
||||
import { layerTypes } from '../../../common/layer_types';
|
||||
import type { FormBasedPersistedState } from '../../datasources/form_based/types';
|
||||
import { getSuggestions } from './suggestions';
|
||||
|
@ -35,6 +32,7 @@ import { generateId } from '../../id_generator';
|
|||
import { toExpression } from './to_expression';
|
||||
import { nonNullable } from '../../utils';
|
||||
import { METRIC_NUMERIC_MAX } from '../../user_messages_ids';
|
||||
import { MetricVisualizationState } from './types';
|
||||
|
||||
export const DEFAULT_MAX_COLUMNS = 3;
|
||||
|
||||
|
@ -49,33 +47,6 @@ export const getDefaultColor = (state: MetricVisualizationState, isMetricNumeric
|
|||
: euiThemeVars.euiColorEmptyShade;
|
||||
};
|
||||
|
||||
export interface MetricVisualizationState {
|
||||
layerId: string;
|
||||
layerType: LayerType;
|
||||
metricAccessor?: string;
|
||||
secondaryMetricAccessor?: string;
|
||||
maxAccessor?: string;
|
||||
breakdownByAccessor?: string;
|
||||
// the dimensions can optionally be single numbers
|
||||
// computed by collapsing all rows
|
||||
collapseFn?: CollapseFunction;
|
||||
subtitle?: string;
|
||||
secondaryPrefix?: string;
|
||||
progressDirection?: LayoutDirection;
|
||||
showBar?: boolean;
|
||||
color?: string;
|
||||
icon?: string;
|
||||
palette?: PaletteOutput<CustomPaletteParams>;
|
||||
maxCols?: number;
|
||||
|
||||
trendlineLayerId?: string;
|
||||
trendlineLayerType?: LayerType;
|
||||
trendlineTimeAccessor?: string;
|
||||
trendlineMetricAccessor?: string;
|
||||
trendlineSecondaryMetricAccessor?: string;
|
||||
trendlineBreakdownByAccessor?: string;
|
||||
}
|
||||
|
||||
export const supportedDataTypes = new Set(['string', 'boolean', 'number', 'ip', 'date']);
|
||||
|
||||
const isSupportedMetric = (op: OperationMetadata) =>
|
||||
|
|
|
@ -79,7 +79,7 @@ export const VisualOptionsPopover: React.FC<VisualOptionsPopoverProps> = ({
|
|||
return (
|
||||
<TooltipWrapper tooltipContent={valueLabelsDisabledReason} condition={isDisabled}>
|
||||
<ToolbarPopover
|
||||
title={i18n.translate('xpack.lens.shared.curveLabel', {
|
||||
title={i18n.translate('xpack.lens.shared.visualOptionsLabel', {
|
||||
defaultMessage: 'Visual options',
|
||||
})}
|
||||
type="visualOptions"
|
||||
|
|
|
@ -23425,7 +23425,7 @@
|
|||
"xpack.lens.shared.axisNameLabel": "Titre de l'axe",
|
||||
"xpack.lens.shared.chartValueLabelVisibilityLabel": "Étiquettes",
|
||||
"xpack.lens.shared.chartValueLabelVisibilityTooltip": "Si l'espace est insuffisant, les étiquettes de valeurs pourront être masquées",
|
||||
"xpack.lens.shared.curveLabel": "Options visuelles",
|
||||
"xpack.lens.shared.visualOptionsLabel": "Options visuelles",
|
||||
"xpack.lens.shared.legendAlignmentLabel": "Alignement",
|
||||
"xpack.lens.shared.legendInsideColumnsLabel": "Nombre de colonnes",
|
||||
"xpack.lens.shared.legendInsideLocationAlignmentLabel": "Alignement",
|
||||
|
|
|
@ -23406,7 +23406,7 @@
|
|||
"xpack.lens.shared.axisNameLabel": "軸のタイトル",
|
||||
"xpack.lens.shared.chartValueLabelVisibilityLabel": "ラベル",
|
||||
"xpack.lens.shared.chartValueLabelVisibilityTooltip": "十分なスペースがない場合、値ラベルが非表示になることがあります。",
|
||||
"xpack.lens.shared.curveLabel": "視覚オプション",
|
||||
"xpack.lens.shared.visualOptionsLabel": "視覚オプション",
|
||||
"xpack.lens.shared.legendAlignmentLabel": "アラインメント",
|
||||
"xpack.lens.shared.legendInsideColumnsLabel": "列の数",
|
||||
"xpack.lens.shared.legendInsideLocationAlignmentLabel": "アラインメント",
|
||||
|
|
|
@ -23439,7 +23439,7 @@
|
|||
"xpack.lens.shared.axisNameLabel": "轴标题",
|
||||
"xpack.lens.shared.chartValueLabelVisibilityLabel": "标签",
|
||||
"xpack.lens.shared.chartValueLabelVisibilityTooltip": "如果没有足够的空间,可能会隐藏值标签",
|
||||
"xpack.lens.shared.curveLabel": "视觉选项",
|
||||
"xpack.lens.shared.visualOptionsLabel": "视觉选项",
|
||||
"xpack.lens.shared.legendAlignmentLabel": "对齐方式",
|
||||
"xpack.lens.shared.legendInsideColumnsLabel": "列数目",
|
||||
"xpack.lens.shared.legendInsideLocationAlignmentLabel": "对齐方式",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue