[Gauge] Visdimensions support. (#124927)

* Shape argument turned to required.

* Added checks for the gauge arguments.

* Moved (metric/min/max/goal)Accessor arguments to (metric/min/max/goal).

* Split gauge and lens state.

* Added support of vis_dimensions.

* Some fixes for uniformity.

* Moved findAccessor out of dimensions.

* Made accessors/vis_dimension functionality reusable for other plugins.

* Fixed test snapshots.

* Fixed snapshots.

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Yaroslav Kuznietsov 2022-02-16 11:52:46 +02:00 committed by GitHub
parent b7b5088244
commit 47ec1039ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 516 additions and 324 deletions

View file

@ -10,22 +10,22 @@ export const EXPRESSION_GAUGE_NAME = 'gauge';
export const GAUGE_FUNCTION_RENDERER_NAME = 'gauge_renderer';
export const GaugeShapes = {
horizontalBullet: 'horizontalBullet',
verticalBullet: 'verticalBullet',
HORIZONTAL_BULLET: 'horizontalBullet',
VERTICAL_BULLET: 'verticalBullet',
} as const;
export const GaugeTicksPositions = {
auto: 'auto',
bands: 'bands',
AUTO: 'auto',
BANDS: 'bands',
} as const;
export const GaugeLabelMajorModes = {
auto: 'auto',
custom: 'custom',
none: 'none',
AUTO: 'auto',
CUSTOM: 'custom',
NONE: 'none',
} as const;
export const GaugeColorModes = {
palette: 'palette',
none: 'none',
PALETTE: 'palette',
NONE: 'none',
} as const;

View file

@ -8,13 +8,13 @@ Object {
"args": Object {
"ariaLabel": undefined,
"colorMode": "none",
"goalAccessor": undefined,
"goal": undefined,
"labelMajor": "title",
"labelMajorMode": "custom",
"labelMinor": undefined,
"maxAccessor": undefined,
"metricAccessor": "col-0-1",
"minAccessor": "col-1-2",
"max": undefined,
"metric": "col-0-1",
"min": "col-1-2",
"palette": undefined,
"shape": "horizontalBullet",
"ticksPosition": "auto",

View file

@ -7,9 +7,10 @@
*/
import { gaugeFunction } from './gauge_function';
import type { GaugeArguments } from '..';
import { GaugeArguments, GaugeShapes } from '..';
import { functionWrapper } from '../../../../expressions/common/expression_functions/specs/tests/utils';
import { Datatable } from '../../../../expressions/common/expression_types/specs';
import { GaugeColorModes, GaugeLabelMajorModes, GaugeTicksPositions } from '../constants';
describe('interpreter/functions#gauge', () => {
const fn = functionWrapper(gaugeFunction());
@ -22,13 +23,13 @@ describe('interpreter/functions#gauge', () => {
],
};
const args: GaugeArguments = {
ticksPosition: 'auto',
labelMajorMode: 'custom',
ticksPosition: GaugeTicksPositions.AUTO,
labelMajorMode: GaugeLabelMajorModes.CUSTOM,
labelMajor: 'title',
shape: 'horizontalBullet',
colorMode: 'none',
minAccessor: 'col-1-2',
metricAccessor: 'col-0-1',
shape: GaugeShapes.HORIZONTAL_BULLET,
colorMode: GaugeColorModes.NONE,
min: 'col-1-2',
metric: 'col-0-1',
};
it('returns an object with the correct structure', () => {

View file

@ -7,101 +7,161 @@
*/
import { i18n } from '@kbn/i18n';
import { findAccessorOrFail } from '../../../../visualizations/common/utils';
import type { ExpressionValueVisDimension } from '../../../../visualizations/common';
import type { DatatableColumn } from '../../../../expressions';
import { GaugeExpressionFunctionDefinition } from '../types';
import { EXPRESSION_GAUGE_NAME } from '../constants';
import {
EXPRESSION_GAUGE_NAME,
GaugeColorModes,
GaugeLabelMajorModes,
GaugeShapes,
GaugeTicksPositions,
} from '../constants';
export const errors = {
invalidShapeError: () =>
i18n.translate('expressionGauge.functions.gauge.errors.invalidShapeError', {
defaultMessage: `Invalid shape is specified. Supported shapes: {shapes}`,
values: { shapes: Object.values(GaugeShapes).join(', ') },
}),
invalidColorModeError: () =>
i18n.translate('expressionGauge.functions.gauge.errors.invalidColorModeError', {
defaultMessage: `Invalid color mode is specified. Supported color modes: {colorModes}`,
values: { colorModes: Object.values(GaugeColorModes).join(', ') },
}),
invalidTicksPositionError: () =>
i18n.translate('expressionGauge.functions.gauge.errors.invalidTicksPositionError', {
defaultMessage: `Invalid ticks position is specified. Supported ticks positions: {ticksPositions}`,
values: { ticksPositions: Object.values(GaugeTicksPositions).join(', ') },
}),
invalidLabelMajorModeError: () =>
i18n.translate('expressionGauge.functions.gauge.errors.invalidLabelMajorModeError', {
defaultMessage: `Invalid label major mode is specified. Supported label major modes: {labelMajorModes}`,
values: { labelMajorModes: Object.values(GaugeLabelMajorModes).join(', ') },
}),
};
const validateAccessor = (
accessor: string | undefined | ExpressionValueVisDimension,
columns: DatatableColumn[]
) => {
if (accessor && typeof accessor === 'string') {
findAccessorOrFail(accessor, columns);
}
};
const validateOptions = (
value: string,
availableOptions: Record<string, string>,
getErrorMessage: () => string
) => {
if (!Object.values(availableOptions).includes(value)) {
throw new Error(getErrorMessage());
}
};
export const gaugeFunction = (): GaugeExpressionFunctionDefinition => ({
name: EXPRESSION_GAUGE_NAME,
type: 'render',
inputTypes: ['datatable'],
help: i18n.translate('expressionGauge.functions.help', {
help: i18n.translate('expressionGauge.functions.gauge.help', {
defaultMessage: 'Gauge visualization',
}),
args: {
shape: {
types: ['string'],
options: ['horizontalBullet', 'verticalBullet'],
help: i18n.translate('expressionGauge.functions.shape.help', {
options: [GaugeShapes.HORIZONTAL_BULLET, GaugeShapes.VERTICAL_BULLET],
help: i18n.translate('expressionGauge.functions.gauge.args.shape.help', {
defaultMessage: 'Type of gauge chart',
}),
required: true,
},
metricAccessor: {
types: ['string'],
help: i18n.translate('expressionGauge.functions.metricAccessor.help', {
metric: {
types: ['string', 'vis_dimension'],
help: i18n.translate('expressionGauge.functions.gauge.args.metric.help', {
defaultMessage: 'Current value',
}),
},
minAccessor: {
types: ['string'],
help: i18n.translate('expressionGauge.functions.minAccessor.help', {
min: {
types: ['string', 'vis_dimension'],
help: i18n.translate('expressionGauge.functions.gauge.args.min.help', {
defaultMessage: 'Minimum value',
}),
},
maxAccessor: {
types: ['string'],
help: i18n.translate('expressionGauge.functions.maxAccessor.help', {
max: {
types: ['string', 'vis_dimension'],
help: i18n.translate('expressionGauge.functions.gauge.args.max.help', {
defaultMessage: 'Maximum value',
}),
},
goalAccessor: {
types: ['string'],
help: i18n.translate('expressionGauge.functions.goalAccessor.help', {
goal: {
types: ['string', 'vis_dimension'],
help: i18n.translate('expressionGauge.functions.gauge.args.goal.help', {
defaultMessage: 'Goal Value',
}),
},
colorMode: {
types: ['string'],
default: 'none',
options: ['none', 'palette'],
help: i18n.translate('expressionGauge.functions.colorMode.help', {
default: GaugeColorModes.NONE,
options: [GaugeColorModes.NONE, GaugeColorModes.PALETTE],
help: i18n.translate('expressionGauge.functions.gauge.args.colorMode.help', {
defaultMessage: 'If set to palette, the palette colors will be applied to the bands',
}),
},
palette: {
types: ['palette'],
help: i18n.translate('expressionGauge.functions..metric.palette.help', {
help: i18n.translate('expressionGauge.functions.gauge.args.palette.help', {
defaultMessage: 'Provides colors for the values',
}),
},
ticksPosition: {
types: ['string'],
options: ['auto', 'bands'],
help: i18n.translate('expressionGauge.functions..gaugeChart.config.ticksPosition.help', {
default: GaugeTicksPositions.AUTO,
options: [GaugeTicksPositions.AUTO, GaugeTicksPositions.BANDS],
help: i18n.translate('expressionGauge.functions.gauge.args.ticksPosition.help', {
defaultMessage: 'Specifies the placement of ticks',
}),
required: true,
},
labelMajor: {
types: ['string'],
help: i18n.translate('expressionGauge.functions..gaugeChart.config.labelMajor.help', {
help: i18n.translate('expressionGauge.functions.gauge.args.labelMajor.help', {
defaultMessage: 'Specifies the labelMajor of the gauge chart displayed inside the chart.',
}),
required: false,
},
labelMajorMode: {
types: ['string'],
options: ['none', 'auto', 'custom'],
help: i18n.translate('expressionGauge.functions..gaugeChart.config.labelMajorMode.help', {
options: [GaugeLabelMajorModes.NONE, GaugeLabelMajorModes.AUTO, GaugeLabelMajorModes.CUSTOM],
help: i18n.translate('expressionGauge.functions.gauge.args.labelMajorMode.help', {
defaultMessage: 'Specifies the mode of labelMajor',
}),
required: true,
default: GaugeLabelMajorModes.AUTO,
},
labelMinor: {
types: ['string'],
help: i18n.translate('expressionGauge.functions..gaugeChart.config.labelMinor.help', {
help: i18n.translate('expressionGauge.functions.gauge.args.labelMinor.help', {
defaultMessage: 'Specifies the labelMinor of the gauge chart',
}),
required: false,
},
ariaLabel: {
types: ['string'],
help: i18n.translate('expressionGauge.functions.gaugeChart.config.ariaLabel.help', {
defaultMessage: 'Specifies the aria label of the gauge chart',
}),
required: false,
},
},
fn(data, args, handlers) {
validateOptions(args.shape, GaugeShapes, errors.invalidShapeError);
validateOptions(args.colorMode, GaugeColorModes, errors.invalidColorModeError);
validateOptions(args.ticksPosition, GaugeTicksPositions, errors.invalidTicksPositionError);
validateOptions(args.labelMajorMode, GaugeLabelMajorModes, errors.invalidLabelMajorModeError);
validateAccessor(args.metric, data.columns);
validateAccessor(args.min, data.columns);
validateAccessor(args.max, data.columns);
validateAccessor(args.goal, data.columns);
return {
type: 'render',
as: EXPRESSION_GAUGE_NAME,

View file

@ -21,6 +21,7 @@ export type {
GaugeLabelMajorMode,
GaugeTicksPosition,
GaugeState,
Accessors,
} from './types';
export { gaugeFunction } from './expression_functions';

View file

@ -6,11 +6,13 @@
* Side Public License, v 1.
*/
import { $Values } from '@kbn/utility-types';
import {
Datatable,
ExpressionFunctionDefinition,
ExpressionValueRender,
} from '../../../../expressions';
import { ExpressionValueVisDimension } from '../../../../visualizations/public';
import { CustomPaletteState, PaletteOutput } from '../../../../charts/common';
import {
EXPRESSION_GAUGE_NAME,
@ -22,17 +24,16 @@ import {
} from '../constants';
import { CustomPaletteParams } from '.';
export type GaugeType = 'gauge';
export type GaugeColorMode = keyof typeof GaugeColorModes;
export type GaugeShape = keyof typeof GaugeShapes;
export type GaugeLabelMajorMode = keyof typeof GaugeLabelMajorModes;
export type GaugeTicksPosition = keyof typeof GaugeTicksPositions;
export type GaugeColorMode = $Values<typeof GaugeColorModes>;
export type GaugeShape = $Values<typeof GaugeShapes>;
export type GaugeLabelMajorMode = $Values<typeof GaugeLabelMajorModes>;
export type GaugeTicksPosition = $Values<typeof GaugeTicksPositions>;
export interface GaugeState {
metricAccessor?: string;
minAccessor?: string;
maxAccessor?: string;
goalAccessor?: string;
metric?: string | ExpressionValueVisDimension;
min?: string | ExpressionValueVisDimension;
max?: string | ExpressionValueVisDimension;
goal?: string | ExpressionValueVisDimension;
ticksPosition: GaugeTicksPosition;
labelMajorMode: GaugeLabelMajorMode;
labelMajor?: string;
@ -68,3 +69,10 @@ export type GaugeExpressionFunctionDefinition = ExpressionFunctionDefinition<
GaugeArguments,
ExpressionValueRender<GaugeExpressionProps>
>;
export interface Accessors {
min?: string;
max?: string;
metric?: string;
goal?: string;
}

View file

@ -12,7 +12,14 @@ import { fieldFormatsServiceMock } from '../../../../field_formats/public/mocks'
import type { Datatable } from '../../../../expressions/public';
import { DatatableColumn, DatatableRow } from 'src/plugins/expressions/common';
import { shallowWithIntl } from '@kbn/test-jest-helpers';
import { GaugeRenderProps, GaugeArguments, GaugeLabelMajorMode, ColorStop } from '../../common';
import {
GaugeRenderProps,
GaugeArguments,
ColorStop,
GaugeLabelMajorModes,
GaugeTicksPositions,
GaugeColorModes,
} from '../../common';
import GaugeComponent from './gauge_component';
import { Chart, Goal } from '@elastic/charts';
@ -50,14 +57,14 @@ const chartsThemeService = chartPluginMock.createSetupContract().theme;
const formatService = fieldFormatsServiceMock.createStartContract();
const args: GaugeArguments = {
labelMajor: 'Gauge',
metricAccessor: 'metric-accessor',
minAccessor: '',
maxAccessor: '',
goalAccessor: '',
metric: 'metric-accessor',
min: '',
max: '',
goal: '',
shape: 'verticalBullet',
colorMode: 'none',
ticksPosition: 'auto',
labelMajorMode: 'auto',
colorMode: GaugeColorModes.NONE,
ticksPosition: GaugeTicksPositions.AUTO,
labelMajorMode: GaugeLabelMajorModes.AUTO,
};
const createData = (
@ -87,14 +94,14 @@ describe('GaugeComponent', function () {
expect(component.find(Chart)).toMatchSnapshot();
});
it('returns null when metricAccessor is not provided', async () => {
it('returns null when metric is not provided', async () => {
const customProps = {
...wrapperProps,
args: {
...wrapperProps.args,
metricAccessor: undefined,
minAccessor: 'min-accessor',
maxAccessor: 'max-accessor',
metric: undefined,
min: 'min-accessor',
max: 'max-accessor',
},
data: createData({ 'min-accessor': 0, 'max-accessor': 10 }),
};
@ -107,9 +114,9 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
metricAccessor: 'metric-accessor',
minAccessor: 'min-accessor',
maxAccessor: 'max-accessor',
metric: 'metric-accessor',
min: 'min-accessor',
max: 'max-accessor',
},
data: createData({ 'metric-accessor': 0, 'min-accessor': 0, 'max-accessor': 0 }),
};
@ -121,9 +128,9 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
metricAccessor: 'metric-accessor',
minAccessor: 'min-accessor',
maxAccessor: 'max-accessor',
metric: 'metric-accessor',
min: 'min-accessor',
max: 'max-accessor',
},
data: createData({ 'metric-accessor': 0, 'min-accessor': 0, 'max-accessor': -10 }),
};
@ -135,10 +142,10 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
ticksPosition: 'bands',
metricAccessor: 'metric-accessor',
minAccessor: 'min-accessor',
maxAccessor: 'max-accessor',
ticksPosition: GaugeTicksPositions.BANDS,
metric: 'metric-accessor',
min: 'min-accessor',
max: 'max-accessor',
},
data: createData({ 'metric-accessor': 12, 'min-accessor': 0, 'max-accessor': 10 }),
} as GaugeRenderProps;
@ -152,7 +159,7 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
labelMajorMode: 'none' as GaugeLabelMajorMode,
labelMajorMode: GaugeLabelMajorModes.NONE,
labelMinor: '',
},
};
@ -165,7 +172,7 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
labelMajorMode: 'custom' as GaugeLabelMajorMode,
labelMajorMode: GaugeLabelMajorModes.CUSTOM,
labelMajor: 'custom labelMajor',
labelMinor: 'custom labelMinor',
},
@ -179,7 +186,7 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
labelMajorMode: 'auto' as GaugeLabelMajorMode,
labelMajorMode: GaugeLabelMajorModes.AUTO,
labelMajor: '',
},
};
@ -194,9 +201,9 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
metricAccessor: 'metric-accessor',
minAccessor: 'min-accessor',
maxAccessor: 'max-accessor',
metric: 'metric-accessor',
min: 'min-accessor',
max: 'max-accessor',
},
};
const goal = shallowWithIntl(<GaugeComponent {...customProps} />).find(Goal);
@ -219,9 +226,9 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
metricAccessor: 'metric-accessor',
minAccessor: 'min-accessor',
maxAccessor: 'max-accessor',
metric: 'metric-accessor',
min: 'min-accessor',
max: 'max-accessor',
palette,
},
} as GaugeRenderProps;
@ -245,11 +252,11 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
metricAccessor: 'metric-accessor',
minAccessor: 'min-accessor',
maxAccessor: 'max-accessor',
metric: 'metric-accessor',
min: 'min-accessor',
max: 'max-accessor',
palette,
ticksPosition: 'bands',
ticksPosition: GaugeTicksPositions.BANDS,
},
} as GaugeRenderProps;
const goal = shallowWithIntl(<GaugeComponent {...customProps} />).find(Goal);
@ -273,11 +280,11 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
metricAccessor: 'metric-accessor',
minAccessor: 'min-accessor',
maxAccessor: 'max-accessor',
metric: 'metric-accessor',
min: 'min-accessor',
max: 'max-accessor',
palette,
ticksPosition: 'bands',
ticksPosition: GaugeTicksPositions.BANDS,
},
} as GaugeRenderProps;
const goal = shallowWithIntl(<GaugeComponent {...customProps} />).find(Goal);
@ -301,11 +308,11 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
metricAccessor: 'metric-accessor',
minAccessor: 'min-accessor',
maxAccessor: 'max-accessor',
metric: 'metric-accessor',
min: 'min-accessor',
max: 'max-accessor',
palette,
ticksPosition: 'bands',
ticksPosition: GaugeTicksPositions.BANDS,
},
} as GaugeRenderProps;
const goal = shallowWithIntl(<GaugeComponent {...customProps} />).find(Goal);
@ -329,11 +336,11 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
metricAccessor: 'metric-accessor',
minAccessor: 'min-accessor',
maxAccessor: 'max-accessor',
metric: 'metric-accessor',
min: 'min-accessor',
max: 'max-accessor',
palette,
ticksPosition: 'bands',
ticksPosition: GaugeTicksPositions.BANDS,
},
} as GaugeRenderProps;
const goal = shallowWithIntl(<GaugeComponent {...customProps} />).find(Goal);
@ -357,11 +364,11 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
metricAccessor: 'metric-accessor',
minAccessor: 'min-accessor',
maxAccessor: 'max-accessor',
metric: 'metric-accessor',
min: 'min-accessor',
max: 'max-accessor',
palette,
ticksPosition: 'bands',
ticksPosition: GaugeTicksPositions.BANDS,
},
} as GaugeRenderProps;
const goal = shallowWithIntl(<GaugeComponent {...customProps} />).find(Goal);
@ -385,12 +392,12 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
colorMode: 'palette',
colorMode: GaugeColorModes.PALETTE,
palette,
ticksPosition: 'bands',
metricAccessor: 'metric-accessor',
minAccessor: 'min-accessor',
maxAccessor: 'max-accessor',
ticksPosition: GaugeTicksPositions.BANDS,
metric: 'metric-accessor',
min: 'min-accessor',
max: 'max-accessor',
},
} as GaugeRenderProps;
const goal = shallowWithIntl(<GaugeComponent {...customProps} />).find(Goal);
@ -414,12 +421,12 @@ describe('GaugeComponent', function () {
...wrapperProps,
args: {
...wrapperProps.args,
colorMode: 'palette',
colorMode: GaugeColorModes.PALETTE,
palette,
ticksPosition: 'bands',
metricAccessor: 'metric-accessor',
minAccessor: 'min-accessor',
maxAccessor: 'max-accessor',
ticksPosition: GaugeTicksPositions.BANDS,
metric: 'metric-accessor',
min: 'min-accessor',
max: 'max-accessor',
},
} as GaugeRenderProps;
const goal = shallowWithIntl(<GaugeComponent {...customProps} />).find(Goal);

View file

@ -15,10 +15,11 @@ import {
GaugeLabelMajorMode,
GaugeTicksPosition,
GaugeLabelMajorModes,
GaugeColorModes,
} from '../../common';
import { GaugeShapes, GaugeTicksPositions } from '../../common';
import { GaugeIconVertical, GaugeIconHorizontal } from './gauge_icon';
import { getMaxValue, getMinValue, getValueFromAccessor } from './utils';
import { getAccessorsFromArgs, getMaxValue, getMinValue, getValueFromAccessor } from './utils';
import './index.scss';
declare global {
interface Window {
@ -103,9 +104,11 @@ function getTitle(
labelMajor?: string,
fallbackTitle?: string
) {
if (labelMajorMode === GaugeLabelMajorModes.none) {
if (labelMajorMode === GaugeLabelMajorModes.NONE) {
return '';
} else if (labelMajorMode === GaugeLabelMajorModes.auto) {
}
if (labelMajorMode === GaugeLabelMajorModes.AUTO) {
return `${fallbackTitle || ''} `; // added extra space for nice rendering
}
return `${labelMajor || fallbackTitle || ''} `; // added extra space for nice rendering
@ -131,7 +134,7 @@ function getTicks(
range: [number, number],
colorBands?: number[]
) {
if (ticksPosition === GaugeTicksPositions.bands && colorBands) {
if (ticksPosition === GaugeTicksPositions.BANDS && colorBands) {
return colorBands && getTicksLabels(colorBands);
}
const TICKS_NO = 3;
@ -150,7 +153,6 @@ export const GaugeComponent: FC<GaugeRenderProps> = memo(
({ data, args, formatFactory, chartsThemeService }) => {
const {
shape: subtype,
metricAccessor,
palette,
colorMode,
labelMinor,
@ -158,32 +160,35 @@ export const GaugeComponent: FC<GaugeRenderProps> = memo(
labelMajorMode,
ticksPosition,
} = args;
if (!metricAccessor) {
const table = data;
const accessors = getAccessorsFromArgs(args, table.columns);
if (!accessors || !accessors.metric) {
// Chart is not ready
return null;
}
const chartTheme = chartsThemeService.useChartsTheme();
const table = data;
const metricColumn = table.columns.find((col) => col.id === metricAccessor);
const metricColumn = table.columns.find((col) => col.id === accessors.metric);
const chartData = table.rows.filter(
(v) => typeof v[metricAccessor!] === 'number' || Array.isArray(v[metricAccessor!])
(v) => typeof v[accessors.metric!] === 'number' || Array.isArray(v[accessors.metric!])
);
const row = chartData?.[0];
const metricValue = getValueFromAccessor('metricAccessor', row, args);
const metricValue = args.metric ? getValueFromAccessor(accessors.metric, row) : undefined;
const icon = subtype === GaugeShapes.horizontalBullet ? GaugeIconHorizontal : GaugeIconVertical;
const icon =
subtype === GaugeShapes.HORIZONTAL_BULLET ? GaugeIconHorizontal : GaugeIconVertical;
if (typeof metricValue !== 'number') {
return <EmptyPlaceholder icon={icon} />;
}
const goal = getValueFromAccessor('goalAccessor', row, args);
const min = getMinValue(row, args);
const max = getMaxValue(row, args);
const goal = accessors.goal ? getValueFromAccessor(accessors.goal, row) : undefined;
const min = getMinValue(row, accessors);
const max = getMaxValue(row, accessors);
if (min === max) {
return (
@ -197,7 +202,9 @@ export const GaugeComponent: FC<GaugeRenderProps> = memo(
}
/>
);
} else if (min > max) {
}
if (min > max) {
return (
<EmptyPlaceholder
icon={icon}
@ -247,7 +254,7 @@ export const GaugeComponent: FC<GaugeRenderProps> = memo(
bands={bands}
ticks={getTicks(ticksPosition, [min, max], bands)}
bandFillColor={
colorMode === 'palette' && colors
colorMode === GaugeColorModes.PALETTE && colors
? (val) => {
const index = bands && bands.indexOf(val.value) - 1;
return colors && index >= 0 && colors[index]

View file

@ -14,78 +14,78 @@ describe('expression gauge utils', () => {
metric: 2,
min: [10, 20],
};
const state = {
metricAccessor: 'metric',
minAccessor: 'min',
const accessors = {
metric: 'metric',
min: 'min',
};
it('returns accessor value for number', () => {
expect(getValueFromAccessor('metricAccessor', row, state)).toEqual(2);
expect(getValueFromAccessor(accessors.metric, row)).toEqual(2);
});
it('returns accessor value for array', () => {
expect(getValueFromAccessor('minAccessor', row, state)).toEqual(20);
expect(getValueFromAccessor(accessors.min, row)).toEqual(20);
});
it('returns undefined for not number', () => {
expect(getValueFromAccessor('maxAccessor', row, state)).toEqual(undefined);
expect(getValueFromAccessor('max', row)).toEqual(undefined);
});
});
describe('getMaxValue', () => {
const state = {
metricAccessor: 'metric',
minAccessor: 'min',
const accessors = {
metric: 'metric',
min: 'min',
};
it('returns correct value for existing maxAccessor', () => {
it('returns correct value for existing max', () => {
const row = {
metric: 2,
min: 0,
max: 5,
};
expect(getMaxValue(row, { ...state, maxAccessor: 'max' })).toEqual(5);
expect(getMaxValue(row, { ...accessors, max: 'max' })).toEqual(5);
});
it('returns fallback value', () => {
const localState = { ...state, maxAccessor: 'max' };
expect(getMaxValue({ min: 0 }, localState)).toEqual(100);
expect(getMaxValue({}, localState)).toEqual(100);
const localAccessors = { ...accessors, max: 'max' };
expect(getMaxValue({ min: 0 }, localAccessors)).toEqual(100);
expect(getMaxValue({}, localAccessors)).toEqual(100);
});
it('returns correct value for multiple cases', () => {
const localState = { ...state, maxAccessor: 'max' };
expect(getMaxValue({ metric: 10 }, localState)).toEqual(15);
expect(getMaxValue({ min: 0, metric: 2 }, localState)).toEqual(4);
expect(getMaxValue({ min: -100, metric: 2 }, localState)).toEqual(50);
expect(getMaxValue({ min: -0.001, metric: 0 }, localState)).toEqual(1);
expect(getMaxValue({ min: -2000, metric: -1000 }, localState)).toEqual(-500);
expect(getMaxValue({ min: 0.5, metric: 1.5 }, localState)).toEqual(2);
const localAccessors = { ...accessors, max: 'max' };
expect(getMaxValue({ metric: 10 }, localAccessors)).toEqual(15);
expect(getMaxValue({ min: 0, metric: 2 }, localAccessors)).toEqual(4);
expect(getMaxValue({ min: -100, metric: 2 }, localAccessors)).toEqual(50);
expect(getMaxValue({ min: -0.001, metric: 0 }, localAccessors)).toEqual(1);
expect(getMaxValue({ min: -2000, metric: -1000 }, localAccessors)).toEqual(-500);
expect(getMaxValue({ min: 0.5, metric: 1.5 }, localAccessors)).toEqual(2);
});
});
describe('getMinValue', () => {
it('returns 0 for max values greater than 0', () => {
const state = {
metricAccessor: 'metric',
minAccessor: 'min',
const accessors = {
metric: 'metric',
min: 'min',
};
const localState = { ...state, maxAccessor: 'max' };
expect(getMinValue({ max: 1000, metric: 1.5 }, localState)).toEqual(0);
expect(getMinValue({ max: 5, metric: 2 }, localState)).toEqual(0);
const localAccessors = { ...accessors, max: 'max' };
expect(getMinValue({ max: 1000, metric: 1.5 }, localAccessors)).toEqual(0);
expect(getMinValue({ max: 5, metric: 2 }, localAccessors)).toEqual(0);
});
it('returns correct value for negative values', () => {
const state = {
metricAccessor: 'metric',
minAccessor: 'min',
const accessors = {
metric: 'metric',
min: 'min',
};
const localState = { ...state, maxAccessor: 'max' };
expect(getMinValue({ metric: 0 }, localState)).toEqual(-10);
expect(getMinValue({ metric: -1000 }, localState)).toEqual(-1010);
expect(getMinValue({ max: 1000, metric: 1.5 }, localState)).toEqual(0);
const localAccessors = { ...accessors, max: 'max' };
expect(getMinValue({ metric: 0 }, localAccessors)).toEqual(-10);
expect(getMinValue({ metric: -1000 }, localAccessors)).toEqual(-1010);
expect(getMinValue({ max: 1000, metric: 1.5 }, localAccessors)).toEqual(0);
});
});
describe('getGoalValue', () => {
it('returns correct value', () => {
const state = {
metricAccessor: 'metric',
minAccessor: 'min',
maxAccessor: 'max',
const accessors = {
metric: 'metric',
min: 'min',
max: 'max',
};
expect(getGoalValue({ max: 1000, min: 0 }, state)).toEqual(750);
expect(getGoalValue({ min: 3, max: 6 }, state)).toEqual(5);
expect(getGoalValue({ max: 1000, min: 0 }, accessors)).toEqual(750);
expect(getGoalValue({ min: 3, max: 6 }, accessors)).toEqual(5);
});
});
});

View file

@ -6,29 +6,27 @@
* Side Public License, v 1.
*/
import type { DatatableRow } from 'src/plugins/expressions';
import type { GaugeState } from '../../common/types/expression_functions';
type GaugeAccessors = 'maxAccessor' | 'minAccessor' | 'goalAccessor' | 'metricAccessor';
type GaugeAccessorsType = Pick<GaugeState, GaugeAccessors>;
import type { DatatableColumn, DatatableRow } from 'src/plugins/expressions';
import { getAccessorByDimension } from '../../../../visualizations/common/utils';
import { Accessors, GaugeArguments } from '../../common';
export const getValueFromAccessor = (
accessorName: GaugeAccessors,
row?: DatatableRow,
state?: GaugeAccessorsType
) => {
if (row && state) {
const accessor = state[accessorName];
const value = accessor && row[accessor];
if (typeof value === 'number') {
return value;
}
if (value?.length) {
if (typeof value[value.length - 1] === 'number') {
return value[value.length - 1];
}
}
accessor: string,
row?: DatatableRow
): DatatableRow[string] | number | undefined => {
if (!row || !accessor) return;
const value = accessor && row[accessor];
if (value === null || (Array.isArray(value) && !value.length)) {
return;
}
if (typeof value === 'number') {
return value;
}
if (Array.isArray(value) && typeof value[value.length - 1] === 'number') {
return value[value.length - 1];
}
};
@ -56,17 +54,17 @@ function getNiceNumber(localRange: number) {
return niceFraction * Math.pow(10, exponent);
}
export const getMaxValue = (row?: DatatableRow, state?: GaugeAccessorsType): number => {
export const getMaxValue = (row?: DatatableRow, accessors?: Accessors): number => {
const FALLBACK_VALUE = 100;
const currentValue = getValueFromAccessor('maxAccessor', row, state);
if (currentValue != null) {
const currentValue = accessors?.max ? getValueFromAccessor(accessors.max, row) : undefined;
if (currentValue !== undefined && currentValue !== null) {
return currentValue;
}
if (row && state) {
const { metricAccessor, goalAccessor } = state;
const metricValue = metricAccessor && row[metricAccessor];
const goalValue = goalAccessor && row[goalAccessor];
const minValue = getMinValue(row, state);
if (row && accessors) {
const { metric, goal } = accessors;
const metricValue = metric && row[metric];
const goalValue = goal && row[goal];
const minValue = getMinValue(row, accessors);
if (metricValue != null) {
const numberValues = [minValue, goalValue, metricValue].filter((v) => typeof v === 'number');
const maxValue = Math.max(...numberValues);
@ -76,16 +74,16 @@ export const getMaxValue = (row?: DatatableRow, state?: GaugeAccessorsType): num
return FALLBACK_VALUE;
};
export const getMinValue = (row?: DatatableRow, state?: GaugeAccessorsType) => {
const currentValue = getValueFromAccessor('minAccessor', row, state);
if (currentValue != null) {
export const getMinValue = (row?: DatatableRow, accessors?: Accessors) => {
const currentValue = accessors?.min ? getValueFromAccessor(accessors.min, row) : undefined;
if (currentValue !== undefined && currentValue !== null) {
return currentValue;
}
const FALLBACK_VALUE = 0;
if (row && state) {
const { metricAccessor, maxAccessor } = state;
const metricValue = metricAccessor && row[metricAccessor];
const maxValue = maxAccessor && row[maxAccessor];
if (row && accessors) {
const { metric, max } = accessors;
const metricValue = metric && row[metric];
const maxValue = max && row[max];
const numberValues = [metricValue, maxValue].filter((v) => typeof v === 'number');
if (Math.min(...numberValues) <= 0) {
return Math.min(...numberValues) - 10; // TODO: TO THINK THROUGH
@ -94,12 +92,30 @@ export const getMinValue = (row?: DatatableRow, state?: GaugeAccessorsType) => {
return FALLBACK_VALUE;
};
export const getGoalValue = (row?: DatatableRow, state?: GaugeAccessorsType) => {
const currentValue = getValueFromAccessor('goalAccessor', row, state);
if (currentValue != null) {
export const getGoalValue = (row?: DatatableRow, accessors?: Accessors) => {
const currentValue = accessors?.goal ? getValueFromAccessor(accessors.goal, row) : undefined;
if (currentValue !== undefined && currentValue !== null) {
return currentValue;
}
const minValue = getMinValue(row, state);
const maxValue = getMaxValue(row, state);
const minValue = getMinValue(row, accessors);
const maxValue = getMaxValue(row, accessors);
return Math.round((maxValue - minValue) * 0.75 + minValue);
};
export const getAccessorsFromArgs = (
args: GaugeArguments,
columns: DatatableColumn[]
): Accessors | undefined => {
const { metric, min, max, goal } = args;
if (!metric && !min && !max && !goal) {
return;
}
return {
min: min ? getAccessorByDimension(min, columns) : undefined,
max: max ? getAccessorByDimension(max, columns) : undefined,
goal: goal ? getAccessorByDimension(goal, columns) : undefined,
metric: metric ? getAccessorByDimension(metric, columns) : undefined,
};
};

View file

@ -9,7 +9,7 @@
import { i18n } from '@kbn/i18n';
import type { DatatableColumn } from '../../../../expressions/public';
import { ExpressionValueVisDimension } from '../../../../visualizations/common';
import { prepareLogTable, Dimension } from '../../../../visualizations/common/prepare_log_table';
import { prepareLogTable, Dimension } from '../../../../visualizations/common/utils';
import { HeatmapExpressionFunctionDefinition } from '../types';
import {
EXPRESSION_HEATMAP_NAME,

View file

@ -9,7 +9,7 @@
import { i18n } from '@kbn/i18n';
import { visType } from '../types';
import { prepareLogTable, Dimension } from '../../../../visualizations/common/prepare_log_table';
import { prepareLogTable, Dimension } from '../../../../visualizations/common/utils';
import { ColorMode } from '../../../../charts/common';
import { MetricVisExpressionFunctionDefinition } from '../types';
import { EXPRESSION_METRIC_NAME, LabelPosition } from '../constants';

View file

@ -7,7 +7,7 @@
*/
import { LegendDisplay, PartitionVisParams } from '../types/expression_renderers';
import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table';
import { prepareLogTable } from '../../../../visualizations/common/utils';
import { ChartTypes, MosaicVisExpressionFunctionDefinition } from '../types';
import {
PARTITION_LABELS_FUNCTION,

View file

@ -8,7 +8,7 @@
import { Position } from '@elastic/charts';
import { EmptySizeRatios, LegendDisplay, PartitionVisParams } from '../types/expression_renderers';
import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table';
import { prepareLogTable } from '../../../../visualizations/common/utils';
import { ChartTypes, PieVisExpressionFunctionDefinition } from '../types';
import {
PARTITION_LABELS_FUNCTION,

View file

@ -7,7 +7,7 @@
*/
import { LegendDisplay, PartitionVisParams } from '../types/expression_renderers';
import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table';
import { prepareLogTable } from '../../../../visualizations/common/utils';
import { ChartTypes, TreemapVisExpressionFunctionDefinition } from '../types';
import {
PARTITION_LABELS_FUNCTION,

View file

@ -7,7 +7,7 @@
*/
import { LegendDisplay, PartitionVisParams } from '../types/expression_renderers';
import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table';
import { prepareLogTable } from '../../../../visualizations/common/utils';
import { ChartTypes, WaffleVisExpressionFunctionDefinition } from '../types';
import {
PARTITION_LABELS_FUNCTION,

View file

@ -7,7 +7,7 @@
*/
import { i18n } from '@kbn/i18n';
import { prepareLogTable, Dimension } from '../../../../visualizations/common/prepare_log_table';
import { prepareLogTable, Dimension } from '../../../../visualizations/common/utils';
import { TagCloudRendererParams } from '../types';
import { ExpressionTagcloudFunction } from '../types';
import { EXPRESSION_NAME } from '../constants';

View file

@ -58,13 +58,15 @@ describe('interpreter/functions#vis_dimension', () => {
const accessor = input.columns.length;
const args: Arguments = { accessor };
expect(() => fn(input, args)).toThrowError('Column name or index provided is invalid');
expect(() => fn(input, args)).toThrowError('Provided column name or index is invalid: 2');
});
it("should throw error when the passed column doesn't exist in columns", () => {
const accessor = column1 + '_modified';
const args: Arguments = { accessor };
expect(() => fn(input, args)).toThrowError('Column name or index provided is invalid');
expect(() => fn(input, args)).toThrowError(
'Provided column name or index is invalid: username_modified'
);
});
});

View file

@ -13,6 +13,7 @@ import {
Datatable,
DatatableColumn,
} from '../../../expressions/common';
import { findAccessorOrFail } from '../utils/accessors';
export interface Arguments {
accessor: string | number;
@ -31,12 +32,6 @@ export type ExpressionValueVisDimension = ExpressionValueBoxed<
}
>;
const getAccessorByIndex = (accessor: number, columns: Datatable['columns']) =>
columns.length > accessor ? accessor : undefined;
const getAccessorById = (accessor: DatatableColumn['id'], columns: Datatable['columns']) =>
columns.find((c) => c.id === accessor);
export const visDimension = (): ExpressionFunctionDefinition<
'visdimension',
Datatable,
@ -73,19 +68,7 @@ export const visDimension = (): ExpressionFunctionDefinition<
},
},
fn: (input, args) => {
const accessor =
typeof args.accessor === 'number'
? getAccessorByIndex(args.accessor, input.columns)
: getAccessorById(args.accessor, input.columns);
if (accessor === undefined) {
throw new Error(
i18n.translate('visualizations.function.visDimension.error.accessor', {
defaultMessage: 'Column name or index provided is invalid',
})
);
}
const accessor = findAccessorOrFail(args.accessor, input.columns);
return {
type: 'vis_dimension',
accessor,

View file

@ -11,5 +11,5 @@
/** @public types */
export * from './types';
export * from './prepare_log_table';
export * from './utils';
export * from './expression_functions';

View file

@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import { Datatable, DatatableColumn } from '../../../expressions/common';
import { ExpressionValueVisDimension } from '../expression_functions';
const getAccessorByIndex = (accessor: number, columns: Datatable['columns']) =>
columns.length > accessor ? accessor : undefined;
const getAccessorById = (accessor: DatatableColumn['id'], columns: Datatable['columns']) =>
columns.find((c) => c.id === accessor);
export const findAccessorOrFail = (accessor: string | number, columns: DatatableColumn[]) => {
const foundAccessor =
typeof accessor === 'number'
? getAccessorByIndex(accessor, columns)
: getAccessorById(accessor, columns);
if (foundAccessor === undefined) {
throw new Error(
i18n.translate('visualizations.function.findAccessorOrFail.error.accessor', {
defaultMessage: 'Provided column name or index is invalid: {accessor}',
values: { accessor },
})
);
}
return foundAccessor;
};
export const getAccessorByDimension = (
dimension: string | ExpressionValueVisDimension,
columns: DatatableColumn[]
) => {
if (typeof dimension === 'string') {
return dimension;
}
const accessor = dimension.accessor;
if (typeof accessor === 'number') {
return columns[accessor].id;
}
return accessor.id;
};

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { prepareLogTable } from './prepare_log_table';
export type { Dimension } from './prepare_log_table';
export { findAccessorOrFail, getAccessorByDimension } from './accessors';

View file

@ -6,9 +6,9 @@
* Side Public License, v 1.
*/
import { ExpressionValueVisDimension } from './expression_functions/vis_dimension';
import { ExpressionValueXYDimension } from './expression_functions/xy_dimension';
import { Datatable, DatatableColumn } from '../../expressions/common/expression_types/specs';
import { ExpressionValueVisDimension } from '../expression_functions/vis_dimension';
import { ExpressionValueXYDimension } from '../expression_functions/xy_dimension';
import { Datatable, DatatableColumn } from '../../../expressions/common/expression_types/specs';
export type Dimension = [
Array<ExpressionValueVisDimension | ExpressionValueXYDimension> | undefined,

View file

@ -16,7 +16,7 @@
],
"optionalPlugins": [ "home", "share", "usageCollection", "spaces", "savedObjectsTaggingOss"],
"requiredBundles": ["kibanaUtils", "discover", "kibanaReact", "home"],
"extraPublicDirs": ["common/constants", "common/prepare_log_table", "common/expression_functions"],
"extraPublicDirs": ["common/constants", "common/utils", "common/expression_functions"],
"owner": {
"name": "Vis Editors",
"githubTeam": "kibana-vis-editors"

View file

@ -1 +1 @@
"[metricVis] > [visdimension] > Column name or index provided is invalid"
"[metricVis] > [visdimension] > Provided column name or index is invalid: 0"

View file

@ -1 +1 @@
"[tagcloud] > [visdimension] > Column name or index provided is invalid"
"[tagcloud] > [visdimension] > Provided column name or index is invalid: 0"

View file

@ -0,0 +1 @@
"[metricVis] > [visdimension] > Provided column name or index is invalid: 0"

View file

@ -0,0 +1 @@
"[tagcloud] > [visdimension] > Provided column name or index is invalid: 0"

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { GaugeState } from '../../../../../../src/plugins/chart_expressions/expression_gauge/common';
import { GaugeState as GaugeStateOriginal } from '../../../../../../src/plugins/chart_expressions/expression_gauge/common';
import { LayerType } from '../../../common/';
export const LENS_GAUGE_ID = 'lnsGauge';
@ -17,7 +17,19 @@ export const GROUP_ID = {
GOAL: 'goal',
} as const;
type GaugeState = Omit<GaugeStateOriginal, 'metric' | 'goal' | 'min' | 'max'> & {
metricAccessor?: string;
minAccessor?: string;
maxAccessor?: string;
goalAccessor?: string;
};
export type GaugeVisualizationState = GaugeState & {
layerId: string;
layerType: LayerType;
};
export type GaugeExpressionState = GaugeStateOriginal & {
layerId: string;
layerType: LayerType;
};

View file

@ -39,6 +39,7 @@ import type { VisualizationDimensionEditorProps } from '../../types';
import './dimension_editor.scss';
import { GaugeVisualizationState } from './constants';
import { defaultPaletteParams } from './palette_config';
import { getAccessorsFromState } from './utils';
export function GaugeDimensionEditor(
props: VisualizationDimensionEditorProps<GaugeVisualizationState> & {
@ -57,11 +58,13 @@ export function GaugeDimensionEditor(
return null;
}
const accessors = getAccessorsFromState(state);
const hasDynamicColoring = state?.colorMode === 'palette';
const currentMinMax = {
min: getMinValue(firstRow, state),
max: getMaxValue(firstRow, state),
min: getMinValue(firstRow, accessors),
max: getMaxValue(firstRow, accessors),
};
const activePalette = state?.palette || {
@ -107,12 +110,12 @@ export function GaugeDimensionEditor(
stops: displayStops,
},
},
ticksPosition: GaugeTicksPositions.bands,
colorMode: GaugeColorModes.palette,
ticksPosition: GaugeTicksPositions.BANDS,
colorMode: GaugeColorModes.PALETTE,
}
: {
ticksPosition: GaugeTicksPositions.auto,
colorMode: GaugeColorModes.none,
ticksPosition: GaugeTicksPositions.AUTO,
colorMode: GaugeColorModes.NONE,
};
setState({
@ -221,14 +224,14 @@ export function GaugeDimensionEditor(
})}
data-test-subj="lens-toolbar-gauge-ticks-position-switch"
showLabel={false}
checked={state.ticksPosition === GaugeTicksPositions.bands}
checked={state.ticksPosition === GaugeTicksPositions.BANDS}
onChange={() => {
setState({
...state,
ticksPosition:
state.ticksPosition === GaugeTicksPositions.bands
? GaugeTicksPositions.auto
: GaugeTicksPositions.bands,
state.ticksPosition === GaugeTicksPositions.BANDS
? GaugeTicksPositions.AUTO
: GaugeTicksPositions.BANDS,
});
}}
/>

View file

@ -41,7 +41,7 @@ describe('gauge suggestions', () => {
changeType: 'unchanged' as const,
},
state: {
shape: GaugeShapes.horizontalBullet,
shape: GaugeShapes.HORIZONTAL_BULLET,
layerId: 'first',
layerType: layerTypes.DATA,
} as GaugeVisualizationState,
@ -75,7 +75,7 @@ describe('gauge suggestions', () => {
changeType: 'initial',
},
state: {
shape: GaugeShapes.horizontalBullet,
shape: GaugeShapes.HORIZONTAL_BULLET,
layerId: 'first',
layerType: layerTypes.DATA,
minAccessor: 'some-field',
@ -150,7 +150,7 @@ describe('shows suggestions', () => {
state: {
layerId: 'first',
layerType: layerTypes.DATA,
shape: GaugeShapes.horizontalBullet,
shape: GaugeShapes.HORIZONTAL_BULLET,
metricAccessor: 'metric-column',
labelMajorMode: 'auto',
ticksPosition: 'auto',
@ -169,7 +169,7 @@ describe('shows suggestions', () => {
layerId: 'first',
layerType: 'data',
metricAccessor: 'metric-column',
shape: GaugeShapes.verticalBullet,
shape: GaugeShapes.VERTICAL_BULLET,
ticksPosition: 'auto',
labelMajorMode: 'auto',
},
@ -188,17 +188,17 @@ describe('shows suggestions', () => {
state: {
layerId: 'first',
layerType: layerTypes.DATA,
shape: GaugeShapes.horizontalBullet,
shape: GaugeShapes.HORIZONTAL_BULLET,
metricAccessor: 'metric-column',
} as GaugeVisualizationState,
keptLayerIds: ['first'],
subVisualizationId: GaugeShapes.verticalBullet,
subVisualizationId: GaugeShapes.VERTICAL_BULLET,
})
).toEqual([
{
state: {
layerType: layerTypes.DATA,
shape: GaugeShapes.verticalBullet,
shape: GaugeShapes.VERTICAL_BULLET,
metricAccessor: 'metric-column',
labelMajorMode: 'auto',
ticksPosition: 'auto',

View file

@ -48,9 +48,9 @@ export const getSuggestions: Visualization<GaugeVisualizationState>['getSuggesti
}
const shape: GaugeShape =
state?.shape === GaugeShapes.verticalBullet
? GaugeShapes.verticalBullet
: GaugeShapes.horizontalBullet;
state?.shape === GaugeShapes.VERTICAL_BULLET
? GaugeShapes.VERTICAL_BULLET
: GaugeShapes.HORIZONTAL_BULLET;
const baseSuggestion = {
state: {
@ -58,8 +58,8 @@ export const getSuggestions: Visualization<GaugeVisualizationState>['getSuggesti
shape,
layerId: table.layerId,
layerType: layerTypes.DATA,
ticksPosition: GaugeTicksPositions.auto,
labelMajorMode: GaugeLabelMajorModes.auto,
ticksPosition: GaugeTicksPositions.AUTO,
labelMajorMode: GaugeLabelMajorModes.AUTO,
},
title: i18n.translate('xpack.lens.gauge.gaugeLabel', {
defaultMessage: 'Gauge',
@ -77,9 +77,9 @@ export const getSuggestions: Visualization<GaugeVisualizationState>['getSuggesti
...baseSuggestion.state,
...state,
shape:
state?.shape === GaugeShapes.verticalBullet
? GaugeShapes.horizontalBullet
: GaugeShapes.verticalBullet,
state?.shape === GaugeShapes.VERTICAL_BULLET
? GaugeShapes.HORIZONTAL_BULLET
: GaugeShapes.VERTICAL_BULLET,
},
},
]
@ -97,9 +97,9 @@ export const getSuggestions: Visualization<GaugeVisualizationState>['getSuggesti
...baseSuggestion.state,
metricAccessor: table.columns[0].columnId,
shape:
state?.shape === GaugeShapes.verticalBullet
? GaugeShapes.horizontalBullet
: GaugeShapes.verticalBullet,
state?.shape === GaugeShapes.VERTICAL_BULLET
? GaugeShapes.HORIZONTAL_BULLET
: GaugeShapes.VERTICAL_BULLET,
},
},
];

View file

@ -0,0 +1,16 @@
/*
* 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 type { Accessors } from '../../../../../../src/plugins/chart_expressions/expression_gauge/common';
import { GaugeVisualizationState } from './constants';
export const getAccessorsFromState = (state?: GaugeVisualizationState): Accessors | undefined => {
const { minAccessor, maxAccessor, goalAccessor, metricAccessor } = state ?? {};
if (!metricAccessor && !maxAccessor && !goalAccessor && !metricAccessor) {
return;
}
return { min: minAccessor, max: maxAccessor, goal: goalAccessor, metric: metricAccessor };
};

View file

@ -396,7 +396,7 @@ describe('gauge', () => {
colorMode: 'palette',
ticksPosition: 'bands',
};
test('removes metricAccessor correctly', () => {
test('removes metric correctly', () => {
expect(
getGaugeVisualization({
paletteService,
@ -411,7 +411,7 @@ describe('gauge', () => {
minAccessor: 'min-accessor',
});
});
test('removes minAccessor correctly', () => {
test('removes min correctly', () => {
expect(
getGaugeVisualization({
paletteService,
@ -443,7 +443,7 @@ describe('gauge', () => {
it('should return the type only if the layer is in the state', () => {
const state: GaugeVisualizationState = {
...exampleState(),
minAccessor: 'minAccessor',
minAccessor: 'min-accessor',
goalAccessor: 'value-accessor',
};
const instance = getGaugeVisualization({
@ -487,10 +487,10 @@ describe('gauge', () => {
type: 'function',
function: 'gauge',
arguments: {
metricAccessor: ['metric-accessor'],
minAccessor: ['min-accessor'],
maxAccessor: ['max-accessor'],
goalAccessor: ['goal-accessor'],
metric: ['metric-accessor'],
min: ['min-accessor'],
max: ['max-accessor'],
goal: ['goal-accessor'],
colorMode: ['none'],
ticksPosition: ['auto'],
labelMajorMode: ['auto'],
@ -507,7 +507,7 @@ describe('gauge', () => {
const state: GaugeVisualizationState = {
...exampleState(),
layerId: 'first',
minAccessor: 'minAccessor',
minAccessor: 'min-accessor',
};
expect(
getGaugeVisualization({

View file

@ -27,12 +27,18 @@ import {
import { PaletteRegistry } from '../../../../../../src/plugins/charts/public';
import type { DatasourcePublicAPI, OperationMetadata, Visualization } from '../../types';
import { getSuggestions } from './suggestions';
import { GROUP_ID, LENS_GAUGE_ID, GaugeVisualizationState } from './constants';
import {
GROUP_ID,
LENS_GAUGE_ID,
GaugeVisualizationState,
GaugeExpressionState,
} from './constants';
import { GaugeToolbar } from './toolbar_component';
import { applyPaletteParams, CUSTOM_PALETTE } from '../../shared_components';
import { GaugeDimensionEditor } from './dimension_editor';
import { CustomPaletteParams, layerTypes } from '../../../common';
import { generateId } from '../../id_generator';
import { getAccessorsFromState } from './utils';
const groupLabelForGauge = i18n.translate('xpack.lens.metric.groupLabel', {
defaultMessage: 'Goal and single value',
@ -79,9 +85,11 @@ const checkInvalidConfiguration = (row?: DatatableRow, state?: GaugeVisualizatio
if (!row || !state) {
return;
}
const minValue = getValueFromAccessor('minAccessor', row, state);
const maxValue = getValueFromAccessor('maxAccessor', row, state);
if (maxValue != null && minValue != null) {
const minAccessor = state?.minAccessor;
const maxAccessor = state?.maxAccessor;
const minValue = minAccessor ? getValueFromAccessor(minAccessor, row) : undefined;
const maxValue = maxAccessor ? getValueFromAccessor(maxAccessor, row) : undefined;
if (maxValue !== null && maxValue !== undefined && minValue != null && minValue !== undefined) {
if (maxValue < minValue) {
return {
invalid: true,
@ -108,7 +116,7 @@ const toExpression = (
paletteService: PaletteRegistry,
state: GaugeVisualizationState,
datasourceLayers: Record<string, DatasourcePublicAPI>,
attributes?: Partial<Omit<GaugeArguments, keyof GaugeVisualizationState | 'ariaLabel'>>
attributes?: Partial<Omit<GaugeArguments, keyof GaugeExpressionState | 'ariaLabel'>>
): Ast | null => {
const datasource = datasourceLayers[state.layerId];
@ -124,11 +132,11 @@ const toExpression = (
type: 'function',
function: EXPRESSION_GAUGE_NAME,
arguments: {
metricAccessor: [state.metricAccessor ?? ''],
minAccessor: [state.minAccessor ?? ''],
maxAccessor: [state.maxAccessor ?? ''],
goalAccessor: [state.goalAccessor ?? ''],
shape: [state.shape ?? GaugeShapes.horizontalBullet],
metric: state.metricAccessor ? [state.metricAccessor] : [],
min: state.minAccessor ? [state.minAccessor] : [],
max: state.maxAccessor ? [state.maxAccessor] : [],
goal: state.goalAccessor ? [state.goalAccessor] : [],
shape: [state.shape ?? GaugeShapes.HORIZONTAL_BULLET],
colorMode: [state?.colorMode ?? 'none'],
palette: state.palette?.params
? [
@ -157,12 +165,12 @@ export const getGaugeVisualization = ({
visualizationTypes: [
{
...CHART_NAMES.horizontalBullet,
id: GaugeShapes.horizontalBullet,
id: GaugeShapes.HORIZONTAL_BULLET,
showExperimentalBadge: true,
},
{
...CHART_NAMES.verticalBullet,
id: GaugeShapes.verticalBullet,
id: GaugeShapes.VERTICAL_BULLET,
showExperimentalBadge: true,
},
],
@ -184,7 +192,7 @@ export const getGaugeVisualization = ({
},
getDescription(state) {
if (state.shape === GaugeShapes.horizontalBullet) {
if (state.shape === GaugeShapes.HORIZONTAL_BULLET) {
return CHART_NAMES.horizontalBullet;
}
return CHART_NAMES.verticalBullet;
@ -194,9 +202,9 @@ export const getGaugeVisualization = ({
return {
...state,
shape:
visualizationTypeId === GaugeShapes.horizontalBullet
? GaugeShapes.horizontalBullet
: GaugeShapes.verticalBullet,
visualizationTypeId === GaugeShapes.HORIZONTAL_BULLET
? GaugeShapes.HORIZONTAL_BULLET
: GaugeShapes.VERTICAL_BULLET,
};
},
@ -205,7 +213,7 @@ export const getGaugeVisualization = ({
state || {
layerId: addNewLayer(),
layerType: layerTypes.DATA,
shape: GaugeShapes.horizontalBullet,
shape: GaugeShapes.HORIZONTAL_BULLET,
palette: mainPalette,
ticksPosition: 'auto',
labelMajorMode: 'auto',
@ -218,9 +226,16 @@ export const getGaugeVisualization = ({
const hasColoring = Boolean(state.colorMode !== 'none' && state.palette?.params?.stops);
const row = state?.layerId ? frame?.activeData?.[state?.layerId]?.rows?.[0] : undefined;
const { metricAccessor } = state ?? {};
const accessors = getAccessorsFromState(state);
let palette;
if (!(row == null || state?.metricAccessor == null || state?.palette == null || !hasColoring)) {
const currentMinMax = { min: getMinValue(row, state), max: getMaxValue(row, state) };
if (!(row == null || metricAccessor == null || state?.palette == null || !hasColoring)) {
const currentMinMax = {
min: getMinValue(row, accessors),
max: getMaxValue(row, accessors),
};
const displayStops = applyPaletteParams(paletteService, state?.palette, currentMinMax);
palette = displayStops.map(({ color }) => color);
@ -236,22 +251,22 @@ export const getGaugeVisualization = ({
groupLabel: i18n.translate('xpack.lens.gauge.metricLabel', {
defaultMessage: 'Metric',
}),
accessors: state.metricAccessor
accessors: metricAccessor
? [
palette
? {
columnId: state.metricAccessor,
columnId: metricAccessor,
triggerIcon: 'colorBy',
palette,
}
: {
columnId: state.metricAccessor,
columnId: metricAccessor,
triggerIcon: 'none',
},
]
: [],
filterOperations: isNumericDynamicMetric,
supportsMoreColumns: !state.metricAccessor,
supportsMoreColumns: !metricAccessor,
required: true,
dataTestSubj: 'lnsGauge_metricDimensionPanel',
enableDimensionEditor: true,
@ -269,7 +284,7 @@ export const getGaugeVisualization = ({
supportsMoreColumns: !state.minAccessor,
dataTestSubj: 'lnsGauge_minDimensionPanel',
prioritizedOperation: 'min',
suggestedValue: () => (state.metricAccessor ? getMinValue(row, state) : undefined),
suggestedValue: () => (state.metricAccessor ? getMinValue(row, accessors) : undefined),
...invalidProps,
},
{
@ -285,7 +300,7 @@ export const getGaugeVisualization = ({
supportsMoreColumns: !state.maxAccessor,
dataTestSubj: 'lnsGauge_maxDimensionPanel',
prioritizedOperation: 'max',
suggestedValue: () => (state.metricAccessor ? getMaxValue(row, state) : undefined),
suggestedValue: () => (state.metricAccessor ? getMaxValue(row, accessors) : undefined),
...invalidProps,
},
{
@ -368,10 +383,10 @@ export const getGaugeVisualization = ({
getSupportedLayers(state, frame) {
const row = state?.layerId ? frame?.activeData?.[state?.layerId]?.rows?.[0] : undefined;
const minAccessorValue = getMinValue(row, state);
const maxAccessorValue = getMaxValue(row, state);
const goalAccessorValue = getGoalValue(row, state);
const accessors = getAccessorsFromState(state);
const minValue = getMinValue(row, accessors);
const maxValue = getMaxValue(row, accessors);
const goalValue = getGoalValue(row, accessors);
return [
{
@ -386,21 +401,21 @@ export const getGaugeVisualization = ({
columnId: generateId(),
dataType: 'number',
label: 'minAccessor',
staticValue: minAccessorValue,
staticValue: minValue,
},
{
groupId: 'max',
columnId: generateId(),
dataType: 'number',
label: 'maxAccessor',
staticValue: maxAccessorValue,
staticValue: maxValue,
},
{
groupId: 'goal',
columnId: generateId(),
dataType: 'number',
label: 'goalAccessor',
staticValue: goalAccessorValue,
staticValue: goalValue,
},
]
: undefined,

View file

@ -6152,7 +6152,6 @@
"visualizations.function.range.help": "範囲オブジェクトを生成します",
"visualizations.function.range.to.help": "範囲の終了",
"visualizations.function.visDimension.accessor.help": "使用するデータセット内の列(列インデックスまたは列名)",
"visualizations.function.visDimension.error.accessor": "入力された列名またはインデックスが無効です",
"visualizations.function.visDimension.format.help": "フォーマット",
"visualizations.function.visDimension.formatParams.help": "フォーマットパラメーター",
"visualizations.function.visDimension.help": "visConfig ディメンションオブジェクトを生成します",

View file

@ -5953,7 +5953,6 @@
"visualizations.function.range.help": "生成范围对象",
"visualizations.function.range.to.help": "范围结束",
"visualizations.function.visDimension.accessor.help": "要使用的数据集列(列索引或列名称)",
"visualizations.function.visDimension.error.accessor": "提供的列名称或索引无效",
"visualizations.function.visDimension.format.help": "格式",
"visualizations.function.visDimension.formatParams.help": "格式参数",
"visualizations.function.visDimension.help": "生成 visConfig 维度对象",