diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 88fc20d34c5e1..980c6487b4d3c 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -116,4 +116,5 @@ pageLoadAssetSize: expressionRepeatImage: 22341 expressionImage: 19288 expressionMetric: 22238 - expressionShape: 30033 + expressionShape: 34008 + diff --git a/src/plugins/expression_shape/common/constants.ts b/src/plugins/expression_shape/common/constants.ts index ba048e376dabc..40575516fd30c 100644 --- a/src/plugins/expression_shape/common/constants.ts +++ b/src/plugins/expression_shape/common/constants.ts @@ -8,4 +8,10 @@ export const PLUGIN_ID = 'expressionShape'; export const PLUGIN_NAME = 'expressionShape'; + export const SVG = 'SVG'; +export const CSS = 'CSS'; +export const FONT_FAMILY = '`font-family`'; +export const FONT_WEIGHT = '`font-weight`'; +export const BOOLEAN_TRUE = '`true`'; +export const BOOLEAN_FALSE = '`false`'; diff --git a/src/plugins/expression_shape/common/expression_functions/index.ts b/src/plugins/expression_shape/common/expression_functions/index.ts index fb19cf244a9dd..bdb343a0f39ac 100644 --- a/src/plugins/expression_shape/common/expression_functions/index.ts +++ b/src/plugins/expression_shape/common/expression_functions/index.ts @@ -7,3 +7,4 @@ */ export { shapeFunction } from './shape_function'; +export { progressFunction } from './progress_function'; diff --git a/src/plugins/expression_shape/common/expression_functions/progress_function.test.ts b/src/plugins/expression_shape/common/expression_functions/progress_function.test.ts new file mode 100644 index 0000000000000..489808fb52b13 --- /dev/null +++ b/src/plugins/expression_shape/common/expression_functions/progress_function.test.ts @@ -0,0 +1,213 @@ +/* + * 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 { ExecutionContext } from '../../../expressions'; +import { functionWrapper, fontStyle } from '../../../presentation_util/common/lib'; +import { progressFunction, errors } from './progress_function'; + +describe('progress', () => { + const fn = functionWrapper(progressFunction); + const value = 0.33; + + it('returns a render as progress', () => { + const result = fn(0.2, {}, {} as ExecutionContext); + expect(result).toHaveProperty('type', 'render'); + expect(result).toHaveProperty('as', 'progress'); + }); + + it('sets the progress to context', () => { + const result = fn(0.58, {}, {} as ExecutionContext); + expect(result.value).toHaveProperty('value', 0.58); + }); + + it(`throws when context is outside of the valid range`, async () => { + expect.assertions(1); + try { + await fn(3, {}, {} as ExecutionContext); + } catch (e: any) { + expect(e.message).toEqual(errors.invalidValue(3).message); + } + }); + + describe('args', () => { + describe('shape', () => { + it('sets the progress element shape', () => { + const result = fn( + value, + { + shape: 'wheel', + }, + {} as ExecutionContext + ); + expect(result.value).toHaveProperty('shape', 'wheel'); + }); + + it(`defaults to 'gauge'`, () => { + const result = fn(value, {}, {} as ExecutionContext); + expect(result.value).toHaveProperty('shape', 'gauge'); + }); + }); + + describe('max', () => { + it('sets the maximum value', () => { + const result = fn( + value, + { + max: 2, + }, + {} as ExecutionContext + ); + expect(result.value).toHaveProperty('max', 2); + }); + + it('defaults to 1', () => { + const result = fn(value, {}, {} as ExecutionContext); + expect(result.value).toHaveProperty('max', 1); + }); + + it('throws if max <= 0', async () => { + expect.assertions(1); + try { + await fn(value, { max: -0.5 }, {} as ExecutionContext); + } catch (e: any) { + expect(e.message).toEqual(errors.invalidMaxValue(-0.5).message); + } + }); + }); + + describe('valueColor', () => { + it('sets the color of the progress bar', () => { + const result = fn( + value, + { + valueColor: '#000000', + }, + {} as ExecutionContext + ); + expect(result.value).toHaveProperty('valueColor', '#000000'); + }); + + it(`defaults to '#1785b0'`, () => { + const result = fn(value, {}, {} as ExecutionContext); + expect(result.value).toHaveProperty('valueColor', '#1785b0'); + }); + }); + + describe('barColor', () => { + it('sets the color of the background bar', () => { + const result = fn( + value, + { + barColor: '#FFFFFF', + }, + {} as ExecutionContext + ); + expect(result.value).toHaveProperty('barColor', '#FFFFFF'); + }); + + it(`defaults to '#f0f0f0'`, () => { + const result = fn(value, {}, {} as ExecutionContext); + expect(result.value).toHaveProperty('barColor', '#f0f0f0'); + }); + }); + + describe('valueWeight', () => { + it('sets the thickness of the bars', () => { + const result = fn( + value, + { + valuWeight: 100, + }, + {} as ExecutionContext + ); + + expect(result.value).toHaveProperty('valuWeight', 100); + }); + + it(`defaults to 20`, () => { + const result = fn(value, {}, {} as ExecutionContext); + expect(result.value).toHaveProperty('barWeight', 20); + }); + }); + + describe('barWeight', () => { + it('sets the thickness of the bars', () => { + const result = fn( + value, + { + barWeight: 50, + }, + {} as ExecutionContext + ); + + expect(result.value).toHaveProperty('barWeight', 50); + }); + + it(`defaults to 20`, () => { + const result = fn(value, {}, {} as ExecutionContext); + expect(result.value).toHaveProperty('barWeight', 20); + }); + }); + + describe('label', () => { + it('sets the label of the progress', () => { + const result = fn(value, { label: 'foo' }, {} as ExecutionContext); + + expect(result.value).toHaveProperty('label', 'foo'); + }); + + it('hides the label if false', () => { + const result = fn( + value, + { + label: false, + }, + {} as ExecutionContext + ); + expect(result.value).toHaveProperty('label', ''); + }); + + it('defaults to true which sets the context as the label', () => { + const result = fn(value, {}, {} as ExecutionContext); + expect(result.value).toHaveProperty('label', '0.33'); + }); + }); + + describe('font', () => { + it('sets the font style for the label', () => { + const result = fn( + value, + { + font: fontStyle, + }, + {} as ExecutionContext + ); + + expect(result.value).toHaveProperty('font'); + expect(Object.keys(result.value.font).sort()).toEqual(Object.keys(fontStyle).sort()); + expect(Object.keys(result.value.font.spec).sort()).toEqual( + Object.keys(fontStyle.spec).sort() + ); + }); + + it('sets fill to color', () => { + const result = fn( + value, + { + font: fontStyle, + }, + {} as ExecutionContext + ); + expect(result.value.font.spec).toHaveProperty('fill', fontStyle.spec.color); + }); + + // TODO: write test when using an instance of the interpreter + // it("sets a default style for the label when not provided", () => {}); + }); + }); +}); diff --git a/src/plugins/expression_shape/common/expression_functions/progress_function.ts b/src/plugins/expression_shape/common/expression_functions/progress_function.ts new file mode 100644 index 0000000000000..56ee8878e531c --- /dev/null +++ b/src/plugins/expression_shape/common/expression_functions/progress_function.ts @@ -0,0 +1,174 @@ +/* + * 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 { get } from 'lodash'; +import { Style, openSans } from '../../../expressions/common'; +import { CSS, FONT_FAMILY, FONT_WEIGHT, BOOLEAN_TRUE, BOOLEAN_FALSE } from '../constants'; +import { ExpressionProgressFunction, Progress } from '../types'; + +export const strings = { + help: i18n.translate('expressionShape.functions.progressHelpText', { + defaultMessage: 'Configures a progress element.', + }), + args: { + barColor: i18n.translate('expressionShape.functions.progress.args.barColorHelpText', { + defaultMessage: 'The color of the background bar.', + }), + barWeight: i18n.translate('expressionShape.functions.progress.args.barWeightHelpText', { + defaultMessage: 'The thickness of the background bar.', + }), + font: i18n.translate('expressionShape.functions.progress.args.fontHelpText', { + defaultMessage: + 'The {CSS} font properties for the label. For example, {FONT_FAMILY} or {FONT_WEIGHT}.', + values: { + CSS, + FONT_FAMILY, + FONT_WEIGHT, + }, + }), + label: i18n.translate('expressionShape.functions.progress.args.labelHelpText', { + defaultMessage: + 'To show or hide the label, use {BOOLEAN_TRUE} or {BOOLEAN_FALSE}. Alternatively, provide a string to display as a label.', + values: { + BOOLEAN_TRUE, + BOOLEAN_FALSE, + }, + }), + max: i18n.translate('expressionShape.functions.progress.args.maxHelpText', { + defaultMessage: 'The maximum value of the progress element.', + }), + shape: i18n.translate('expressionShape.functions.progress.args.shapeHelpText', { + defaultMessage: `Select {list}, or {end}.`, + values: { + list: Object.values(Progress) + .slice(0, -1) + .map((shape) => `\`"${shape}"\``) + .join(', '), + end: `\`"${Object.values(Progress).slice(-1)[0]}"\``, + }, + }), + valueColor: i18n.translate('expressionShape.functions.progress.args.valueColorHelpText', { + defaultMessage: 'The color of the progress bar.', + }), + valueWeight: i18n.translate('expressionShape.functions.progress.args.valueWeightHelpText', { + defaultMessage: 'The thickness of the progress bar.', + }), + }, +}; + +export const errors = { + invalidMaxValue: (max: number) => + new Error( + i18n.translate('expressionShape.functions.progress.invalidMaxValueErrorMessage', { + defaultMessage: "Invalid {arg} value: '{max, number}'. '{arg}' must be greater than 0", + values: { + arg: 'max', + max, + }, + }) + ), + invalidValue: (value: number, max: number = 1) => + new Error( + i18n.translate('expressionShape.functions.progress.invalidValueErrorMessage', { + defaultMessage: + "Invalid value: '{value, number}'. Value must be between 0 and {max, number}", + values: { + value, + max, + }, + }) + ), +}; + +export const progressFunction: ExpressionProgressFunction = () => { + const { help, args: argHelp } = strings; + + return { + name: 'progress', + aliases: [], + type: 'render', + inputTypes: ['number'], + help, + args: { + shape: { + aliases: ['_'], + types: ['string'], + help: argHelp.shape, + options: Object.values(Progress), + default: 'gauge', + }, + barColor: { + types: ['string'], + help: argHelp.barColor, + default: `#f0f0f0`, + }, + barWeight: { + types: ['number'], + help: argHelp.barWeight, + default: 20, + }, + font: { + types: ['style'], + help: argHelp.font, + default: `{font size=24 family="${openSans.value}" color="#000000" align=center}`, + }, + label: { + types: ['boolean', 'string'], + help: argHelp.label, + default: true, + }, + max: { + types: ['number'], + help: argHelp.max, + default: 1, + }, + valueColor: { + types: ['string'], + help: argHelp.valueColor, + default: `#1785b0`, + }, + valueWeight: { + types: ['number'], + help: argHelp.valueWeight, + default: 20, + }, + }, + fn: (value, args) => { + if (args.max <= 0) { + throw errors.invalidMaxValue(args.max); + } + if (value > args.max || value < 0) { + throw errors.invalidValue(value, args.max); + } + + let label = ''; + if (args.label) { + label = typeof args.label === 'string' ? args.label : `${value}`; + } + + let font: Style = {} as Style; + + if (get(args, 'font.spec')) { + font = { ...args.font }; + font.spec.fill = args.font.spec.color; // SVG uses fill for font color + } + + return { + type: 'render', + as: 'progress', + value: { + value, + ...args, + label, + font, + }, + }; + }, + }; +}; diff --git a/src/plugins/expression_shape/common/index.ts b/src/plugins/expression_shape/common/index.ts index 7a56f49eb38cb..94dba27576094 100755 --- a/src/plugins/expression_shape/common/index.ts +++ b/src/plugins/expression_shape/common/index.ts @@ -9,4 +9,4 @@ export * from './constants'; export * from './types'; -export { getAvailableShapes } from './lib/available_shapes'; +export { getAvailableShapes, getAvailableProgressShapes } from './lib/available_shapes'; diff --git a/src/plugins/expression_shape/common/lib/available_shapes.ts b/src/plugins/expression_shape/common/lib/available_shapes.ts index a8883f76e0c98..b1e2ab8e63434 100644 --- a/src/plugins/expression_shape/common/lib/available_shapes.ts +++ b/src/plugins/expression_shape/common/lib/available_shapes.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ -import { Shape } from '../types'; +import { Shape, Progress } from '../types'; export const getAvailableShapes = () => Object.values(Shape); +export const getAvailableProgressShapes = () => Object.values(Progress); diff --git a/src/plugins/expression_shape/common/lib/get_id.ts b/src/plugins/expression_shape/common/lib/get_id.ts new file mode 100644 index 0000000000000..9e4e4d6486349 --- /dev/null +++ b/src/plugins/expression_shape/common/lib/get_id.ts @@ -0,0 +1,13 @@ +/* + * 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 uuid from 'uuid/v4'; + +export function getId(type: string): string { + return `${type}-${uuid()}`; +} diff --git a/src/plugins/expression_shape/common/lib/index.ts b/src/plugins/expression_shape/common/lib/index.ts index d62a0d96078be..726d63929ebd8 100644 --- a/src/plugins/expression_shape/common/lib/index.ts +++ b/src/plugins/expression_shape/common/lib/index.ts @@ -8,3 +8,4 @@ export * from './view_box'; export * from './available_shapes'; +export * from './get_id'; diff --git a/src/plugins/expression_shape/common/lib/view_box.ts b/src/plugins/expression_shape/common/lib/view_box.ts index 3028d7a846ed4..b931a6dab1a38 100644 --- a/src/plugins/expression_shape/common/lib/view_box.ts +++ b/src/plugins/expression_shape/common/lib/view_box.ts @@ -9,7 +9,10 @@ import { ParentNodeParams, ViewBoxParams } from '../types'; export function viewBoxToString(viewBox?: ViewBoxParams): undefined | string { - if (!viewBox) return; + if (!viewBox) { + return; + } + return `${viewBox?.minX} ${viewBox?.minY} ${viewBox?.width} ${viewBox?.height}`; } diff --git a/src/plugins/expression_shape/common/types/expression_functions.ts b/src/plugins/expression_shape/common/types/expression_functions.ts index 4f0fad62fde04..2486be13f51bf 100644 --- a/src/plugins/expression_shape/common/types/expression_functions.ts +++ b/src/plugins/expression_shape/common/types/expression_functions.ts @@ -5,7 +5,8 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { ExpressionFunctionDefinition } from 'src/plugins/expressions'; + +import { ExpressionFunctionDefinition, ExpressionValueRender, Style } from '../../../expressions'; export enum Shape { ARROW = 'arrow', @@ -44,3 +45,36 @@ export type ExpressionShapeFunction = () => ExpressionFunctionDefinition< Arguments, Output >; + +export enum Progress { + GAUGE = 'gauge', + HORIZONTAL_BAR = 'horizontalBar', + HORIZONTAL_PILL = 'horizontalPill', + SEMICIRCLE = 'semicircle', + UNICORN = 'unicorn', + VERTICAL_BAR = 'verticalBar', + VERTICAL_PILL = 'verticalPill', + WHEEL = 'wheel', +} + +export interface ProgressArguments { + barColor: string; + barWeight: number; + font: Style; + label: boolean | string; + max: number; + shape: Progress; + valueColor: string; + valueWeight: number; +} + +export type ProgressOutput = ProgressArguments & { + value: number; +}; + +export type ExpressionProgressFunction = () => ExpressionFunctionDefinition< + 'progress', + number, + ProgressArguments, + ExpressionValueRender +>; diff --git a/src/plugins/expression_shape/common/types/expression_renderers.ts b/src/plugins/expression_shape/common/types/expression_renderers.ts index c61d8292ddff6..8c95687d099a6 100644 --- a/src/plugins/expression_shape/common/types/expression_renderers.ts +++ b/src/plugins/expression_shape/common/types/expression_renderers.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { Shape } from './expression_functions'; +import { Shape, ProgressOutput as Arguments } from './expression_functions'; export type OriginString = 'bottom' | 'left' | 'top' | 'right'; export interface ShapeRendererConfig { @@ -33,3 +33,5 @@ export interface ViewBoxParams { width: number; height: number; } + +export type ProgressRendererConfig = Arguments; diff --git a/src/plugins/expression_shape/public/components/progress/index.ts b/src/plugins/expression_shape/public/components/progress/index.ts new file mode 100644 index 0000000000000..3aa7e6212cf09 --- /dev/null +++ b/src/plugins/expression_shape/public/components/progress/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { lazy } from 'react'; + +export const LazyProgressComponent = lazy(() => import('./progress_component')); +export const LazyProgressDrawer = lazy(() => import('./progress_drawer')); diff --git a/src/plugins/expression_shape/public/components/progress/progress_component.tsx b/src/plugins/expression_shape/public/components/progress/progress_component.tsx new file mode 100644 index 0000000000000..e9c71753e8ff6 --- /dev/null +++ b/src/plugins/expression_shape/public/components/progress/progress_component.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { CSSProperties, RefCallback, useCallback, useEffect, useRef, useState } from 'react'; +import { useResizeObserver } from '@elastic/eui'; +import { IInterpreterRenderHandlers } from '../../../../expressions'; +import { NodeDimensions, ProgressRendererConfig } from '../../../common/types'; +import { ShapeRef, SvgConfig, SvgTextAttributes } from '../reusable/types'; +import { getShapeContentElement } from '../reusable/shape_factory'; +import { withSuspense } from '../../../../presentation_util/public'; +import { getTextAttributes, getViewBox } from './utils'; +import { getId } from '../../../common/lib'; +import { getDefaultShapeData } from '../reusable'; +import { LazyProgressDrawer } from '../..'; + +const ProgressDrawer = withSuspense(LazyProgressDrawer); + +interface ProgressComponentProps extends ProgressRendererConfig { + onLoaded: IInterpreterRenderHandlers['done']; + parentNode: HTMLElement; +} + +function ProgressComponent({ + onLoaded, + parentNode, + shape: shapeType, + value, + max, + valueColor, + barColor, + valueWeight, + barWeight, + label, + font, +}: ProgressComponentProps) { + const parentNodeDimensions = useResizeObserver(parentNode); + const [dimensions, setDimensions] = useState({ + width: parentNode.offsetWidth, + height: parentNode.offsetHeight, + }); + const [shapeData, setShapeData] = useState(getDefaultShapeData()); + const shapeRef = useCallback>((node) => { + if (node !== null) { + setShapeData(node.getData()); + } + }, []); + + const [totalLength, setTotalLength] = useState(0); + + useEffect(() => { + setDimensions({ + width: parentNode.offsetWidth, + height: parentNode.offsetHeight, + }); + onLoaded(); + }, [onLoaded, parentNode, parentNodeDimensions]); + + const progressRef = useRef< + SVGCircleElement & SVGPathElement & SVGPolygonElement & SVGRectElement + >(null); + const textRef = useRef(null); + + useEffect(() => { + setTotalLength(progressRef.current ? progressRef.current.getTotalLength() : 0); + }, [shapeType, shapeData, progressRef]); + + const BarProgress = shapeData.shapeType ? getShapeContentElement(shapeData.shapeType) : null; + + const shapeContentAttributes = { + fill: 'none', + stroke: barColor, + strokeWidth: `${barWeight}px`, + }; + + const percent = value / max; + const to = totalLength * (1 - percent); + + const barProgressAttributes = { + ...shapeData.shapeProps, + fill: 'none', + stroke: valueColor, + strokeWidth: `${valueWeight}px`, + strokeDasharray: totalLength, + strokeDashoffset: Math.max(0, to), + }; + + const { width: labelWidth, height: labelHeight } = textRef.current + ? textRef.current.getBBox() + : { width: 0, height: 0 }; + + const offset = Math.max(valueWeight, barWeight); + + const updatedTextAttributes = shapeData.textAttributes + ? getTextAttributes(shapeType, shapeData.textAttributes, offset, label) + : {}; + + const textAttributes: SvgTextAttributes = { + style: font.spec as CSSProperties, + ...updatedTextAttributes, + }; + + const updatedViewBox = getViewBox(shapeType, shapeData.viewBox, offset, labelWidth, labelHeight); + const shapeAttributes = { + id: getId('svg'), + ...(dimensions || {}), + viewBox: updatedViewBox, + }; + + return ( +
+ + {BarProgress && } + +
+ ); +} + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { ProgressComponent as default }; diff --git a/src/plugins/expression_shape/public/components/progress/progress_drawer.tsx b/src/plugins/expression_shape/public/components/progress/progress_drawer.tsx new file mode 100644 index 0000000000000..0d5ed96d35dd5 --- /dev/null +++ b/src/plugins/expression_shape/public/components/progress/progress_drawer.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React, { Ref } from 'react'; +import { ShapeDrawer, ShapeRef, ShapeDrawerComponentProps } from '../reusable'; +import { getShape } from './shapes'; + +const ProgressDrawerComponent = React.forwardRef( + (props: ShapeDrawerComponentProps, ref: Ref) => ( + + ) +); + +// eslint-disable-next-line import/no-default-export +export { ProgressDrawerComponent as default }; diff --git a/src/plugins/expression_shape/public/components/progress/shapes/gauge.tsx b/src/plugins/expression_shape/public/components/progress/shapes/gauge.tsx new file mode 100644 index 0000000000000..c7eebf7be4982 --- /dev/null +++ b/src/plugins/expression_shape/public/components/progress/shapes/gauge.tsx @@ -0,0 +1,29 @@ +/* + * 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 { createShape } from '../../reusable/shape_factory'; +import { SvgElementTypes } from '../../reusable'; + +export const Gauge = createShape({ + viewBox: { + minX: 0, + minY: 0, + width: 120, + height: 120, + }, + shapeProps: { + d: 'M 15 100 A 60 60 0 1 1 105 100', + }, + shapeType: SvgElementTypes.path, + textAttributes: { + x: '60', + y: '60', + textAnchor: 'middle', + dominantBaseline: 'central', + }, +}); diff --git a/src/plugins/expression_shape/public/components/progress/shapes/horizontal_bar.tsx b/src/plugins/expression_shape/public/components/progress/shapes/horizontal_bar.tsx new file mode 100644 index 0000000000000..81e6cb6bf0cca --- /dev/null +++ b/src/plugins/expression_shape/public/components/progress/shapes/horizontal_bar.tsx @@ -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 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 { createShape } from '../../reusable/shape_factory'; +import { SvgElementTypes } from '../../reusable'; + +export const HorizontalBar = createShape({ + viewBox: { + minX: 0, + minY: 0, + width: 208, + height: 1, + }, + shapeType: SvgElementTypes.path, + shapeProps: { + d: 'M 0 1 L 200 1', + }, + textAttributes: { + x: 208, + y: 0, + textAnchor: 'start', + dominantBaseline: 'central', + }, +}); diff --git a/src/plugins/expression_shape/public/components/progress/shapes/horizontal_pill.tsx b/src/plugins/expression_shape/public/components/progress/shapes/horizontal_pill.tsx new file mode 100644 index 0000000000000..084438ddd7910 --- /dev/null +++ b/src/plugins/expression_shape/public/components/progress/shapes/horizontal_pill.tsx @@ -0,0 +1,29 @@ +/* + * 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 { createShape } from '../../reusable/shape_factory'; +import { SvgElementTypes } from '../../reusable'; + +export const HorizontalPill = createShape({ + viewBox: { + minX: 0, + minY: 0, + width: 208, + height: 1, + }, + shapeType: SvgElementTypes.path, + shapeProps: { + d: 'M 0 1 L 200 1', + strokeLinecap: 'round', + }, + textAttributes: { + x: 208, + y: 0, + textAnchor: 'start', + dominantBaseline: 'central', + }, +}); diff --git a/src/plugins/expression_shape/public/components/progress/shapes/index.ts b/src/plugins/expression_shape/public/components/progress/shapes/index.ts new file mode 100644 index 0000000000000..ef7b4a7b1ec0a --- /dev/null +++ b/src/plugins/expression_shape/public/components/progress/shapes/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { ShapeType } from '../../reusable'; +import { Gauge as gauge } from './gauge'; +import { HorizontalBar as horizontalBar } from './horizontal_bar'; +import { HorizontalPill as horizontalPill } from './horizontal_pill'; +import { Semicircle as semicircle } from './semicircle'; +import { Unicorn as unicorn } from './unicorn'; +import { VerticalBar as verticalBar } from './vertical_bar'; +import { VerticalPill as verticalPill } from './vertical_pill'; +import { Wheel as wheel } from './wheel'; + +const shapes: { [key: string]: ShapeType } = { + gauge, + horizontalBar, + horizontalPill, + semicircle, + unicorn, + verticalBar, + verticalPill, + wheel, +}; + +export const getShape = (shapeType: string) => shapes[shapeType]; diff --git a/src/plugins/expression_shape/public/components/progress/shapes/semicircle.tsx b/src/plugins/expression_shape/public/components/progress/shapes/semicircle.tsx new file mode 100644 index 0000000000000..a624a0bdf3137 --- /dev/null +++ b/src/plugins/expression_shape/public/components/progress/shapes/semicircle.tsx @@ -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 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 { createShape } from '../../reusable/shape_factory'; +import { SvgElementTypes } from '../../reusable'; + +export const Semicircle = createShape({ + viewBox: { + minX: 0, + minY: 0, + width: 120, + height: 60, + }, + shapeType: SvgElementTypes.path, + shapeProps: { + d: 'M 0 60 A 60 60 0 1 1 120 60', + }, + textAttributes: { + x: 60, + y: 60, + textAnchor: 'middle', + dy: '-1', + }, +}); diff --git a/src/plugins/expression_shape/public/components/progress/shapes/unicorn.tsx b/src/plugins/expression_shape/public/components/progress/shapes/unicorn.tsx new file mode 100644 index 0000000000000..020176244e444 --- /dev/null +++ b/src/plugins/expression_shape/public/components/progress/shapes/unicorn.tsx @@ -0,0 +1,29 @@ +/* + * 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 { createShape } from '../../reusable/shape_factory'; +import { SvgElementTypes } from '../../reusable'; + +export const Unicorn = createShape({ + viewBox: { + minX: 0, + minY: 0, + width: 200, + height: 200, + }, + shapeType: SvgElementTypes.path, + shapeProps: { + d: + 'M 123 189 C 93 141 129 126 102 96 L 78 102 L 48 117 L 42 129 Q 30 132 21 126 L 18 114 L 27 90 L 42 72 L 48 57 L 3 6 L 57 42 L 63 33 L 60 15 L 69 27 L 69 15 L 84 27 Q 162 36 195 108 Q 174 159 123 189 Z', + }, + textAttributes: { + x: '0', + y: '200', + textAnchor: 'start', + dominantBaseline: 'text-after-edge', + }, +}); diff --git a/src/plugins/expression_shape/public/components/progress/shapes/vertical_bar.tsx b/src/plugins/expression_shape/public/components/progress/shapes/vertical_bar.tsx new file mode 100644 index 0000000000000..bedb0e93dfd88 --- /dev/null +++ b/src/plugins/expression_shape/public/components/progress/shapes/vertical_bar.tsx @@ -0,0 +1,27 @@ +/* + * 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 { createShape } from '../../reusable/shape_factory'; +import { SvgElementTypes } from '../../reusable'; + +export const VerticalBar = createShape({ + viewBox: { + minX: 0, + minY: -8, + width: 1, + height: 208, + }, + shapeType: SvgElementTypes.path, + shapeProps: { + d: 'M 1 200 L 1 0', + }, + textAttributes: { + x: '0', + y: '-8', + textAnchor: 'middle', + }, +}); diff --git a/src/plugins/expression_shape/public/components/progress/shapes/vertical_pill.tsx b/src/plugins/expression_shape/public/components/progress/shapes/vertical_pill.tsx new file mode 100644 index 0000000000000..9226cc03e16a9 --- /dev/null +++ b/src/plugins/expression_shape/public/components/progress/shapes/vertical_pill.tsx @@ -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 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 { createShape } from '../../reusable/shape_factory'; +import { SvgElementTypes } from '../../reusable'; + +export const VerticalPill = createShape({ + viewBox: { + minX: 0, + minY: -8, + width: 1, + height: 208, + }, + shapeType: SvgElementTypes.path, + shapeProps: { + d: 'M 1 200 L 1 0', + strokeLinecap: 'round', + }, + textAttributes: { + x: '0', + y: '-8', + textAnchor: 'middle', + }, +}); diff --git a/src/plugins/expression_shape/public/components/progress/shapes/wheel.tsx b/src/plugins/expression_shape/public/components/progress/shapes/wheel.tsx new file mode 100644 index 0000000000000..484738335a07d --- /dev/null +++ b/src/plugins/expression_shape/public/components/progress/shapes/wheel.tsx @@ -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 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 { createShape } from '../../reusable/shape_factory'; +import { SvgElementTypes } from '../../reusable'; + +export const Wheel = createShape({ + viewBox: { + minX: 0, + minY: 0, + width: 120, + height: 120, + }, + shapeType: SvgElementTypes.path, + shapeProps: { + d: 'M 60 0 A 60 60 0 1 1 60 120 A 60 60 0 1 1 60 0 Z', + }, + textAttributes: { + x: '60', + y: '60', + textAnchor: 'middle', + dominantBaseline: 'central', + }, +}); diff --git a/src/plugins/expression_shape/public/components/progress/utils.ts b/src/plugins/expression_shape/public/components/progress/utils.ts new file mode 100644 index 0000000000000..890e6739675a4 --- /dev/null +++ b/src/plugins/expression_shape/public/components/progress/utils.ts @@ -0,0 +1,146 @@ +/* + * 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 { Progress, ViewBoxParams } from '../../../common'; +import { SvgTextAttributes } from '../reusable'; + +type GetViewBox = ( + shapeType: Progress, + initialViewBox: ViewBoxParams, + offset: number, + labelWidth: number, + labelHeight: number +) => ViewBoxParams; + +type GetViewBoxArguments = Parameters; +type GetViewBoxParam = (...args: GetViewBoxArguments) => number; + +const getMinX: GetViewBoxParam = (shapeType, viewBox, offset = 0) => { + let { minX } = viewBox; + + if (shapeType !== Progress.HORIZONTAL_BAR) { + minX -= offset / 2; + } + + return minX; +}; + +const getMinY: GetViewBoxParam = (shapeType, viewBox, offset = 0, labelWidth, labelHeight = 0) => { + let { minY } = viewBox; + + if (shapeType === Progress.SEMICIRCLE) { + minY -= offset / 2; + } + if (shapeType !== Progress.SEMICIRCLE && shapeType !== Progress.VERTICAL_BAR) { + minY -= offset / 2; + } + if (shapeType === Progress.VERTICAL_BAR || shapeType === Progress.VERTICAL_PILL) { + minY -= labelHeight; + } + + return minY; +}; + +const getWidth: GetViewBoxParam = (shapeType, viewBox, offset = 0, labelWidth = 0) => { + let { width } = viewBox; + + if (shapeType !== Progress.HORIZONTAL_BAR) { + width += offset; + } + if (shapeType === Progress.HORIZONTAL_BAR || shapeType === Progress.HORIZONTAL_PILL) { + width += labelWidth; + } + + return width; +}; + +const getHeight: GetViewBoxParam = ( + shapeType, + viewBox, + offset = 0, + labelWidth = 0, + labelHeight = 0 +) => { + let { height } = viewBox; + + if (shapeType === Progress.SEMICIRCLE) { + height += offset / 2; + } + if (shapeType !== Progress.SEMICIRCLE && shapeType !== Progress.VERTICAL_BAR) { + height += offset; + } + if (shapeType === Progress.VERTICAL_BAR || shapeType === Progress.VERTICAL_PILL) { + height += labelHeight; + } + + return height; +}; + +const updateMinxAndWidthIfNecessary = ( + shapeType: Progress, + labelWidth: number, + minX: number, + width: number +) => { + if ( + (shapeType === Progress.VERTICAL_BAR || shapeType === Progress.VERTICAL_PILL) && + labelWidth > width + ) { + minX = -labelWidth / 2; + width = labelWidth; + } + return [minX, width]; +}; + +export const getViewBox: GetViewBox = function ( + shapeType, + viewBox, + offset = 0, + labelWidth = 0, + labelHeight = 0 +): ViewBoxParams { + const args: GetViewBoxArguments = [shapeType, viewBox, offset, labelWidth, labelHeight]; + const minX = getMinX(...args); + const minY = getMinY(...args); + const width = getWidth(...args); + const height = getHeight(...args); + const [updatedMinX, updatedWidth] = updateMinxAndWidthIfNecessary( + shapeType, + labelWidth, + minX, + width + ); + return { minX: updatedMinX, minY, width: updatedWidth, height }; +}; + +export function getTextAttributes( + shapeType: Progress, + textAttributes: SvgTextAttributes, + offset: number = 0, + label: string | boolean = '' +) { + if (!label) { + return textAttributes; + } + + let { x, y, textContent } = textAttributes; + + textContent = label ? label.toString() : ''; + + if (shapeType === Progress.HORIZONTAL_PILL) { + x = parseInt(String(x)!, 10) + offset / 2; + } + if (shapeType === Progress.VERTICAL_PILL) { + y = parseInt(String(y)!, 10) - offset / 2; + } + if (shapeType === Progress.HORIZONTAL_BAR || shapeType === Progress.HORIZONTAL_PILL) { + x = parseInt(String(x)!, 10); + } + + return { x, y, textContent }; +} diff --git a/src/plugins/expression_shape/public/components/reusable/shape_factory.tsx b/src/plugins/expression_shape/public/components/reusable/shape_factory.tsx index 9e775616726bf..c51daa36fe004 100644 --- a/src/plugins/expression_shape/public/components/reusable/shape_factory.tsx +++ b/src/plugins/expression_shape/public/components/reusable/shape_factory.tsx @@ -10,32 +10,59 @@ import React from 'react'; import { viewBoxToString } from '../../../common/lib'; import { ShapeProps, SvgConfig, SvgElementTypes } from './types'; -const getShapeComponent = (svgParams: SvgConfig) => - function Shape({ shapeAttributes, shapeContentAttributes }: ShapeProps) { - const { viewBox: initialViewBox, shapeProps, shapeType } = svgParams; +export const getShapeComponent = (svgParams: SvgConfig) => + function Shape({ + shapeAttributes, + shapeContentAttributes, + children, + textAttributes, + }: ShapeProps) { + const { + viewBox: initialViewBox, + shapeProps: defaultShapeContentAttributes, + textAttributes: defaultTextAttributes, + shapeType, + } = svgParams; const viewBox = shapeAttributes?.viewBox ? viewBoxToString(shapeAttributes?.viewBox) : viewBoxToString(initialViewBox); const SvgContentElement = getShapeContentElement(shapeType); + + const TextElement = textAttributes + ? React.forwardRef((props, ref) => ) + : null; + return ( - + + {children} + {TextElement && ( + + {textAttributes?.textContent} + + )} ); }; -function getShapeContentElement(type?: SvgElementTypes) { +export function getShapeContentElement(type?: SvgElementTypes) { switch (type) { case SvgElementTypes.circle: - return (props: SvgConfig['shapeProps']) => ; + return React.forwardRef((props, ref) => ( + + )); case SvgElementTypes.rect: - return (props: SvgConfig['shapeProps']) => ; + return React.forwardRef((props, ref) => ); case SvgElementTypes.path: - return (props: SvgConfig['shapeProps']) => ; + return React.forwardRef((props, ref) => ); default: - return (props: SvgConfig['shapeProps']) => ; + return React.forwardRef((props, ref) => ( + + )); } } diff --git a/src/plugins/expression_shape/public/components/reusable/types.tsx b/src/plugins/expression_shape/public/components/reusable/types.tsx index f779633e08a87..79360393ff545 100644 --- a/src/plugins/expression_shape/public/components/reusable/types.tsx +++ b/src/plugins/expression_shape/public/components/reusable/types.tsx @@ -6,14 +6,16 @@ * Side Public License, v 1. */ -import { Ref, SVGProps } from 'react'; +import { Component, CSSProperties, Ref, SVGProps } from 'react'; import { ViewBoxParams } from '../../../common/types'; import type { ShapeType } from './shape_factory'; -export interface ShapeProps { +export type ShapeProps = { shapeAttributes?: ShapeAttributes; - shapeContentAttributes?: ShapeContentAttributes; -} + shapeContentAttributes?: ShapeContentAttributes & + SpecificShapeContentAttributes & { ref?: React.RefObject }; + textAttributes?: SvgTextAttributes; +} & Component['props'] & { ref?: React.RefObject }; export enum SvgElementTypes { polygon, @@ -39,36 +41,48 @@ export interface ShapeContentAttributes { vectorEffect?: SVGProps['vectorEffect']; strokeMiterlimit?: SVGProps['strokeMiterlimit']; } +export interface SvgConfig { + shapeType?: SvgElementTypes; + viewBox: ViewBoxParams; + shapeProps: ShapeContentAttributes & + SpecificShapeContentAttributes & + Component['props'] & { ref?: React.RefObject }; + textAttributes?: SvgTextAttributes; +} + +export type SvgTextAttributes = Partial & { + x?: SVGProps['x']; + y?: SVGProps['y']; + textAnchor?: SVGProps['textAnchor']; + dominantBaseline?: SVGProps['dominantBaseline']; + dx?: SVGProps['dx']; + dy?: SVGProps['dy']; +} & { style?: CSSProperties } & { ref?: React.RefObject }; -interface CircleParams { +export interface CircleParams { r: SVGProps['r']; cx: SVGProps['cx']; cy: SVGProps['cy']; } -interface RectParams { +export interface RectParams { x: SVGProps['x']; y: SVGProps['y']; width: SVGProps['width']; height: SVGProps['height']; } -interface PathParams { +export interface PathParams { d: SVGProps['d']; + strokeLinecap?: SVGProps['strokeLinecap']; } -interface PolygonParams { +export interface PolygonParams { points?: SVGProps['points']; strokeLinejoin?: SVGProps['strokeLinejoin']; } -type SpecificShapeContentAttributes = CircleParams | RectParams | PathParams | PolygonParams; - -export interface SvgConfig { - shapeType?: SvgElementTypes; - viewBox: ViewBoxParams; - shapeProps: ShapeContentAttributes & SpecificShapeContentAttributes; -} +export type SpecificShapeContentAttributes = CircleParams | RectParams | PathParams | PolygonParams; export type ShapeDrawerProps = { shapeType: string; @@ -80,4 +94,6 @@ export interface ShapeRef { getData: () => SvgConfig; } +export type ShapeDrawerComponentProps = Omit; + export type { ShapeType }; diff --git a/src/plugins/expression_shape/public/components/shape/shape_drawer.tsx b/src/plugins/expression_shape/public/components/shape/shape_drawer.tsx index 90906c203332a..7647b07aa58cb 100644 --- a/src/plugins/expression_shape/public/components/shape/shape_drawer.tsx +++ b/src/plugins/expression_shape/public/components/shape/shape_drawer.tsx @@ -6,9 +6,8 @@ * Side Public License, v 1. */ import React, { Ref } from 'react'; -import { ShapeDrawer, ShapeRef } from '../reusable'; +import { ShapeDrawer, ShapeRef, ShapeDrawerComponentProps } from '../reusable'; import { getShape } from './shapes'; -import { ShapeDrawerComponentProps } from './types'; const ShapeDrawerComponent = React.forwardRef( (props: ShapeDrawerComponentProps, ref: Ref) => ( diff --git a/src/plugins/expression_shape/public/components/shape/types.ts b/src/plugins/expression_shape/public/components/shape/types.ts index d99d4c386db53..279bf29575214 100644 --- a/src/plugins/expression_shape/public/components/shape/types.ts +++ b/src/plugins/expression_shape/public/components/shape/types.ts @@ -8,7 +8,6 @@ import { IInterpreterRenderHandlers } from '../../../../../../src/plugins/expressions'; import { ShapeRendererConfig } from '../../../common/types'; -import { ShapeDrawerProps } from '../reusable/types'; export interface ShapeComponentProps extends ShapeRendererConfig { onLoaded: IInterpreterRenderHandlers['done']; @@ -19,4 +18,3 @@ export interface Dimensions { width: number; height: number; } -export type ShapeDrawerComponentProps = Omit; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/__stories__/progress.stories.tsx b/src/plugins/expression_shape/public/expression_renderers/__stories__/progress_renderer.stories.tsx similarity index 54% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/__stories__/progress.stories.tsx rename to src/plugins/expression_shape/public/expression_renderers/__stories__/progress_renderer.stories.tsx index 4ba699d0c05cb..dcf2daaafcfc1 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/__stories__/progress.stories.tsx +++ b/src/plugins/expression_shape/public/expression_renderers/__stories__/progress_renderer.stories.tsx @@ -1,15 +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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import React from 'react'; import { storiesOf } from '@storybook/react'; -import { progress } from '../'; -import { Render } from '../../__stories__/render'; -import { Shape } from '../../../functions/common/progress'; +import { Render } from '../../../../presentation_util/public/__stories__'; +import { progressRenderer } from '../progress_renderer'; +import { Progress } from '../../../common'; storiesOf('renderers/progress', module).add('default', () => { const config = { @@ -22,11 +23,11 @@ storiesOf('renderers/progress', module).add('default', () => { }, label: '66%', max: 1, - shape: Shape.UNICORN, + shape: Progress.UNICORN, value: 0.66, valueColor: '#000', valueWeight: 15, }; - return ; + return ; }); diff --git a/src/plugins/expression_shape/public/expression_renderers/index.ts b/src/plugins/expression_shape/public/expression_renderers/index.ts index 7dba64d7728db..fc031c4a03c8a 100644 --- a/src/plugins/expression_shape/public/expression_renderers/index.ts +++ b/src/plugins/expression_shape/public/expression_renderers/index.ts @@ -7,7 +7,8 @@ */ import { shapeRenderer } from './shape_renderer'; +import { progressRenderer } from './progress_renderer'; -export const renderers = [shapeRenderer]; +export const renderers = [shapeRenderer, progressRenderer]; -export { shapeRenderer }; +export { shapeRenderer, progressRenderer }; diff --git a/src/plugins/expression_shape/public/expression_renderers/progress_renderer.tsx b/src/plugins/expression_shape/public/expression_renderers/progress_renderer.tsx new file mode 100644 index 0000000000000..5f81ffcffd3d9 --- /dev/null +++ b/src/plugins/expression_shape/public/expression_renderers/progress_renderer.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { i18n } from '@kbn/i18n'; +import { ProgressRendererConfig } from '../../common/types'; +import { LazyProgressComponent } from '../components/progress'; +import { withSuspense } from '../../../presentation_util/public'; + +const ProgressComponent = withSuspense(LazyProgressComponent); + +const strings = { + getDisplayName: () => + i18n.translate('expressionShape.renderer.progress.displayName', { + defaultMessage: 'Progress', + }), + getHelpDescription: () => + i18n.translate('expressionShape.renderer.progress.helpDescription', { + defaultMessage: 'Render a basic progress', + }), +}; + +export const progressRenderer = (): ExpressionRenderDefinition => ({ + name: 'progress', + displayName: strings.getDisplayName(), + help: strings.getHelpDescription(), + reuseDomNode: true, + render: async ( + domNode: HTMLElement, + config: ProgressRendererConfig, + handlers: IInterpreterRenderHandlers + ) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + render( + , + domNode + ); + }, +}); diff --git a/src/plugins/expression_shape/public/index.ts b/src/plugins/expression_shape/public/index.ts index c5933a8cd06ed..882d1ea699d1d 100755 --- a/src/plugins/expression_shape/public/index.ts +++ b/src/plugins/expression_shape/public/index.ts @@ -16,6 +16,7 @@ export function plugin() { export * from './expression_renderers'; export { LazyShapeDrawer } from './components/shape'; +export { LazyProgressDrawer } from './components/progress'; export { getDefaultShapeData } from './components/reusable'; export * from './components/shape/types'; export * from './components/reusable/types'; diff --git a/src/plugins/expression_shape/public/plugin.ts b/src/plugins/expression_shape/public/plugin.ts index b20f357d52a9b..0b8fa274e2b08 100755 --- a/src/plugins/expression_shape/public/plugin.ts +++ b/src/plugins/expression_shape/public/plugin.ts @@ -8,8 +8,8 @@ import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public'; -import { shapeRenderer } from './expression_renderers'; -import { shapeFunction } from '../common/expression_functions'; +import { shapeRenderer, progressRenderer } from './expression_renderers'; +import { shapeFunction, progressFunction } from '../common/expression_functions'; interface SetupDeps { expressions: ExpressionsSetup; @@ -26,7 +26,9 @@ export class ExpressionShapePlugin implements Plugin { public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionShapePluginSetup { expressions.registerFunction(shapeFunction); + expressions.registerFunction(progressFunction); expressions.registerRenderer(shapeRenderer); + expressions.registerRenderer(progressRenderer); } public start(core: CoreStart): ExpressionShapePluginStart {} diff --git a/src/plugins/expression_shape/server/plugin.ts b/src/plugins/expression_shape/server/plugin.ts index c03acaa04f1ec..d6eb7a8e717cd 100644 --- a/src/plugins/expression_shape/server/plugin.ts +++ b/src/plugins/expression_shape/server/plugin.ts @@ -8,7 +8,7 @@ import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { ExpressionsServerStart, ExpressionsServerSetup } from '../../expressions/server'; -import { shapeFunction } from '../common/expression_functions'; +import { shapeFunction, progressFunction } from '../common/expression_functions'; interface SetupDeps { expressions: ExpressionsServerSetup; @@ -25,6 +25,7 @@ export class ExpressionShapePlugin implements Plugin { public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionShapePluginSetup { expressions.registerFunction(shapeFunction); + expressions.registerFunction(progressFunction); } public start(core: CoreStart): ExpressionShapePluginStart {} diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts index a34930a5d18a8..b46307ef92502 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts @@ -35,7 +35,6 @@ import { lte } from './lte'; import { mapCenter } from './map_center'; import { neq } from './neq'; import { ply } from './ply'; -import { progress } from './progress'; import { render } from './render'; import { replace } from './replace'; import { rounddate } from './rounddate'; @@ -83,7 +82,6 @@ export const functions = [ mapCenter, neq, ply, - progress, render, replace, rounddate, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.test.js deleted file mode 100644 index 567421a969d92..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.test.js +++ /dev/null @@ -1,175 +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 expect from '@kbn/expect'; -import { - functionWrapper, - fontStyle, -} from '../../../../../../src/plugins/presentation_util/common/lib'; -import { getFunctionErrors } from '../../../i18n'; -import { progress } from './progress'; - -const errors = getFunctionErrors().progress; - -// TODO: this test was not running and is not up to date -describe.skip('progress', () => { - const fn = functionWrapper(progress); - - const value = 0.33; - - it('returns a render as progress', () => { - const result = fn(0.2); - expect(result).to.have.property('type', 'render').and.to.have.property('as', 'progress'); - }); - - it('sets the progress to context', () => { - const result = fn(0.58); - expect(result.value).to.have.property('value', 0.58); - }); - - it(`throws when context is outside of the valid range`, () => { - expect(fn) - .withArgs(3) - .to.throwException(new RegExp(errors.invalidValue(3).message)); - }); - - describe('args', () => { - describe('shape', () => { - it('sets the progress element shape', () => { - const result = fn(value, { - shape: 'wheel', - }); - expect(result.value).to.have.property('shape', 'wheel'); - }); - - it(`defaults to 'gauge'`, () => { - const result = fn(value); - expect(result.value).to.have.property('shape', 'gauge'); - }); - }); - - describe('max', () => { - it('sets the maximum value', () => { - const result = fn(value, { - max: 2, - }); - expect(result.value).to.have.property('max', 2); - }); - - it('defaults to 1', () => { - const result = fn(value); - expect(result.value).to.have.property('max', 1); - }); - - it('throws if max <= 0', () => { - expect(fn) - .withArgs(value, { max: -0.5 }) - .to.throwException(new RegExp(errors.invalidMaxValue(-0.5).message)); - }); - }); - - describe('valueColor', () => { - it('sets the color of the progress bar', () => { - const result = fn(value, { - valueColor: '#000000', - }); - expect(result.value).to.have.property('valueColor', '#000000'); - }); - - it(`defaults to '#1785b0'`, () => { - const result = fn(value); - expect(result.value).to.have.property('valueColor', '#1785b0'); - }); - }); - - describe('barColor', () => { - it('sets the color of the background bar', () => { - const result = fn(value, { - barColor: '#FFFFFF', - }); - expect(result.value).to.have.property('barColor', '#FFFFFF'); - }); - - it(`defaults to '#f0f0f0'`, () => { - const result = fn(value); - expect(result.value).to.have.property('barColor', '#f0f0f0'); - }); - }); - - describe('valueWeight', () => { - it('sets the thickness of the bars', () => { - const result = fn(value, { - valuWeight: 100, - }); - - expect(result.value).to.have.property('valuWeight', 100); - }); - - it(`defaults to 20`, () => { - const result = fn(value); - expect(result.value).to.have.property('barWeight', 20); - }); - }); - - describe('barWeight', () => { - it('sets the thickness of the bars', () => { - const result = fn(value, { - barWeight: 50, - }); - - expect(result.value).to.have.property('barWeight', 50); - }); - - it(`defaults to 20`, () => { - const result = fn(value); - expect(result.value).to.have.property('barWeight', 20); - }); - }); - - describe('label', () => { - it('sets the label of the progress', () => { - const result = fn(value, { label: 'foo' }); - - expect(result.value).to.have.property('label', 'foo'); - }); - - it('hides the label if false', () => { - const result = fn(value, { - label: false, - }); - expect(result.value).to.have.property('label', ''); - }); - - it('defaults to true which sets the context as the label', () => { - const result = fn(value); - expect(result.value).to.have.property('label', '0.33'); - }); - }); - - describe('font', () => { - it('sets the font style for the label', () => { - const result = fn(value, { - font: fontStyle, - }); - - expect(result.value).to.have.property('font'); - expect(result.value.font).to.have.keys(Object.keys(fontStyle)); - expect(result.value.font.spec).to.have.keys(Object.keys(fontStyle.spec)); - }); - - it('sets fill to color', () => { - const result = fn(value, { - font: fontStyle, - }); - expect(result.value.font.spec).to.have.property('fill', fontStyle.spec.color); - }); - - // TODO: write test when using an instance of the interpreter - // it("sets a default style for the label when not provided", () => {}); - }); - }); -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.ts deleted file mode 100644 index 3e78b6dd83947..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.ts +++ /dev/null @@ -1,130 +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 { get } from 'lodash'; -import { openSans } from '../../../common/lib/fonts'; -import { Render, Style, ExpressionFunctionDefinition } from '../../../types'; -import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; - -export enum Shape { - GAUGE = 'gauge', - HORIZONTAL_BAR = 'horizontalBar', - HORIZONTAL_PILL = 'horizontalPill', - SEMICIRCLE = 'semicircle', - UNICORN = 'unicorn', - VERTICAL_BAR = 'verticalBar', - VERTICAL_PILL = 'verticalPill', - WHEEL = 'wheel', -} - -export interface Arguments { - barColor: string; - barWeight: number; - font: Style; - label: boolean | string; - max: number; - shape: Shape; - valueColor: string; - valueWeight: number; -} - -export type Output = Arguments & { - value: number; -}; - -export function progress(): ExpressionFunctionDefinition< - 'progress', - number, - Arguments, - Render -> { - const { help, args: argHelp } = getFunctionHelp().progress; - const errors = getFunctionErrors().progress; - - return { - name: 'progress', - aliases: [], - type: 'render', - inputTypes: ['number'], - help, - args: { - shape: { - aliases: ['_'], - types: ['string'], - help: argHelp.shape, - options: Object.values(Shape), - default: 'gauge', - }, - barColor: { - types: ['string'], - help: argHelp.barColor, - default: `#f0f0f0`, - }, - barWeight: { - types: ['number'], - help: argHelp.barWeight, - default: 20, - }, - font: { - types: ['style'], - help: argHelp.font, - default: `{font size=24 family="${openSans.value}" color="#000000" align=center}`, - }, - label: { - types: ['boolean', 'string'], - help: argHelp.label, - default: true, - }, - max: { - types: ['number'], - help: argHelp.max, - default: 1, - }, - valueColor: { - types: ['string'], - help: argHelp.valueColor, - default: `#1785b0`, - }, - valueWeight: { - types: ['number'], - help: argHelp.valueWeight, - default: 20, - }, - }, - fn: (value, args) => { - if (args.max <= 0) { - throw errors.invalidMaxValue(args.max); - } - if (value > args.max || value < 0) { - throw errors.invalidValue(value, args.max); - } - - let label = ''; - if (args.label) { - label = typeof args.label === 'string' ? args.label : `${value}`; - } - - let font: Style = {} as Style; - - if (get(args, 'font.spec')) { - font = { ...args.font }; - font.spec.fill = args.font.spec.color; // SVG uses fill for font color - } - - return { - type: 'render', - as: 'progress', - value: { - value, - ...args, - label, - font, - }, - }; - }, - }; -} diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts index 5ec2af8d7773f..d7bde45f90d77 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts @@ -8,10 +8,9 @@ import { markdown } from './markdown'; import { pie } from './pie'; import { plot } from './plot'; -import { progress } from './progress'; import { text } from './text'; import { table } from './table'; -export const renderFunctions = [markdown, pie, plot, progress, table, text]; +export const renderFunctions = [markdown, pie, plot, table, text]; export const renderFunctionFactories = []; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts index 57d5ae69c99bb..94aadf6598b5a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts @@ -10,7 +10,10 @@ import { metricRenderer } from '../../../../../src/plugins/expression_metric/pub import { errorRenderer, debugRenderer } from '../../../../../src/plugins/expression_error/public'; import { repeatImageRenderer } from '../../../../../src/plugins/expression_repeat_image/public'; import { revealImageRenderer } from '../../../../../src/plugins/expression_reveal_image/public'; -import { shapeRenderer } from '../../../../../src/plugins/expression_shape/public'; +import { + shapeRenderer, + progressRenderer, +} from '../../../../../src/plugins/expression_shape/public'; export const renderFunctions = [ debugRenderer, @@ -20,6 +23,7 @@ export const renderFunctions = [ revealImageRenderer, shapeRenderer, repeatImageRenderer, + progressRenderer, ]; export const renderFunctionFactories = []; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/__stories__/__snapshots__/progress.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/__stories__/__snapshots__/progress.stories.storyshot deleted file mode 100644 index 1fe884656ef3b..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/__stories__/__snapshots__/progress.stories.storyshot +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Storyshots renderers/progress default 1`] = ` -
- -
-`; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/index.ts deleted file mode 100644 index 33565b8b34dae..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/index.ts +++ /dev/null @@ -1,126 +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 { getId } from '../../../public/lib/get_id'; -import { RendererStrings } from '../../../i18n'; -import { shapes } from './shapes'; -import { Output as Arguments } from '../../functions/common/progress'; -import { RendererFactory } from '../../../types'; - -const { progress: strings } = RendererStrings; - -export const progress: RendererFactory = () => ({ - name: 'progress', - displayName: strings.getDisplayName(), - help: strings.getHelpDescription(), - reuseDomNode: true, - render(domNode, config, handlers) { - const { shape, value, max, valueColor, barColor, valueWeight, barWeight, label, font } = config; - const percent = value / max; - const shapeDef = shapes[shape]; - const offset = Math.max(valueWeight, barWeight); - - if (shapeDef) { - const parser = new DOMParser(); - const shapeSvg = parser - .parseFromString(shapeDef, 'image/svg+xml') - .getElementsByTagName('svg') - .item(0)!; - - const initialViewBox = shapeSvg - .getAttribute('viewBox')! - .split(' ') - .map((v) => parseInt(v, 10)); - let [minX, minY, width, height] = initialViewBox; - - if (shape !== 'horizontalBar') { - minX -= offset / 2; - width += offset; - } - - if (shape === 'semicircle') { - minY -= offset / 2; - height += offset / 2; - } else if (shape !== 'verticalBar') { - minY -= offset / 2; - height += offset; - } - - shapeSvg.setAttribute('className', 'canvasProgress'); - - const svgId = getId('svg'); - shapeSvg.id = svgId; - - const bar = shapeSvg.getElementsByTagName('path').item(0)!; - bar.setAttribute('className', 'canvasProgress__background'); - bar.setAttribute('fill', 'none'); - bar.setAttribute('stroke', barColor); - bar.setAttribute('stroke-width', `${barWeight}px`); - - const valueSvg = bar.cloneNode(true) as SVGPathElement; - valueSvg.setAttribute('className', 'canvasProgress__value'); - valueSvg.setAttribute('stroke', valueColor); - valueSvg.setAttribute('stroke-width', `${valueWeight}px`); - - const length = valueSvg.getTotalLength(); - const to = length * (1 - percent); - valueSvg.setAttribute('stroke-dasharray', String(length)); - valueSvg.setAttribute('stroke-dashoffset', String(Math.max(0, to))); - - shapeSvg.appendChild(valueSvg); - - const text = shapeSvg.getElementsByTagName('text').item(0); - - if (label && text) { - text.textContent = String(label); - text.setAttribute('className', 'canvasProgress__label'); - - if (shape === 'horizontalPill') { - text.setAttribute('x', String(parseInt(text.getAttribute('x')!, 10) + offset / 2)); - } - if (shape === 'verticalPill') { - text.setAttribute('y', String(parseInt(text.getAttribute('y')!, 10) - offset / 2)); - } - - Object.assign(text.style, font.spec); - shapeSvg.appendChild(text); - domNode.appendChild(shapeSvg); - - const { width: labelWidth, height: labelHeight } = text.getBBox(); - - if (shape === 'horizontalBar' || shape === 'horizontalPill') { - text.setAttribute('x', String(parseInt(text.getAttribute('x')!, 10))); - width += labelWidth; - } - if (shape === 'verticalBar' || shape === 'verticalPill') { - if (labelWidth > width) { - minX = -labelWidth / 2; - width = labelWidth; - } - minY -= labelHeight; - height += labelHeight; - } - } - - shapeSvg.setAttribute('viewBox', [minX, minY, width, height].join(' ')); - shapeSvg.setAttribute('width', String(domNode.offsetWidth)); - shapeSvg.setAttribute('height', String(domNode.offsetHeight)); - - if (domNode.firstChild) { - domNode.removeChild(domNode.firstChild); - } - domNode.appendChild(shapeSvg); - - handlers.onResize(() => { - shapeSvg.setAttribute('width', String(domNode.offsetWidth)); - shapeSvg.setAttribute('height', String(domNode.offsetHeight)); - }); - } - - handlers.done(); - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/gauge.svg b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/gauge.svg deleted file mode 100644 index 1aa07d8c23d73..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/gauge.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/horizontal_bar.svg b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/horizontal_bar.svg deleted file mode 100644 index 63e5c8a28d774..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/horizontal_bar.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/horizontal_pill.svg b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/horizontal_pill.svg deleted file mode 100644 index 1dcb9e3816e6e..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/horizontal_pill.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/index.ts deleted file mode 100644 index 7ccc615dff252..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/index.ts +++ /dev/null @@ -1,26 +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 gauge from '!!raw-loader!./gauge.svg'; -import horizontalBar from '!!raw-loader!./horizontal_bar.svg'; -import horizontalPill from '!!raw-loader!./horizontal_pill.svg'; -import semicircle from '!!raw-loader!./semicircle.svg'; -import unicorn from '!!raw-loader!./unicorn.svg'; -import verticalBar from '!!raw-loader!./vertical_bar.svg'; -import verticalPill from '!!raw-loader!./vertical_pill.svg'; -import wheel from '!!raw-loader!./wheel.svg'; - -export const shapes = { - gauge, - horizontalBar, - horizontalPill, - semicircle, - unicorn, - verticalBar, - verticalPill, - wheel, -}; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/semicircle.svg b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/semicircle.svg deleted file mode 100644 index 5538275825da7..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/semicircle.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/unicorn.svg b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/unicorn.svg deleted file mode 100644 index a6b2a91f3b5a0..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/unicorn.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_bar.svg b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_bar.svg deleted file mode 100644 index 9d9b20aa5198c..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_bar.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_pill.svg b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_pill.svg deleted file mode 100644 index f87b8a06f2ddf..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_pill.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/wheel.svg b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/wheel.svg deleted file mode 100644 index 9d1f9ac0c2628..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/wheel.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/progress.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/progress.js index 9246f79e39287..340b770db1e6a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/progress.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/progress.js @@ -6,7 +6,7 @@ */ import { openSans } from '../../../common/lib/fonts'; -import { shapes } from '../../renderers/progress/shapes'; +import { getAvailableProgressShapes } from '../../../../../../src/plugins/expression_shape/common'; import { ViewStrings } from '../../../i18n'; const { Progress: strings } = ViewStrings; @@ -23,7 +23,7 @@ export const progress = () => ({ help: strings.getShapeHelp(), argType: 'select', options: { - choices: Object.keys(shapes).map((key) => ({ + choices: getAvailableProgressShapes().map((key) => ({ value: key, //turns camel into title case name: key[0].toUpperCase() + key.slice(1).replace(/([A-Z])/g, ' $1'), diff --git a/x-pack/plugins/canvas/i18n/functions/dict/progress.ts b/x-pack/plugins/canvas/i18n/functions/dict/progress.ts deleted file mode 100644 index f644431774cc6..0000000000000 --- a/x-pack/plugins/canvas/i18n/functions/dict/progress.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { progress } from '../../../canvas_plugin_src/functions/common/progress'; -import { FunctionHelp } from '../function_help'; -import { FunctionFactory } from '../../../types'; - -import { Shape } from '../../../canvas_plugin_src/functions/common/progress'; -import { CSS, FONT_FAMILY, FONT_WEIGHT, BOOLEAN_TRUE, BOOLEAN_FALSE } from '../../constants'; - -export const help: FunctionHelp> = { - help: i18n.translate('xpack.canvas.functions.progressHelpText', { - defaultMessage: 'Configures a progress element.', - }), - args: { - barColor: i18n.translate('xpack.canvas.functions.progress.args.barColorHelpText', { - defaultMessage: 'The color of the background bar.', - }), - barWeight: i18n.translate('xpack.canvas.functions.progress.args.barWeightHelpText', { - defaultMessage: 'The thickness of the background bar.', - }), - font: i18n.translate('xpack.canvas.functions.progress.args.fontHelpText', { - defaultMessage: - 'The {CSS} font properties for the label. For example, {FONT_FAMILY} or {FONT_WEIGHT}.', - values: { - CSS, - FONT_FAMILY, - FONT_WEIGHT, - }, - }), - label: i18n.translate('xpack.canvas.functions.progress.args.labelHelpText', { - defaultMessage: - 'To show or hide the label, use {BOOLEAN_TRUE} or {BOOLEAN_FALSE}. Alternatively, provide a string to display as a label.', - values: { - BOOLEAN_TRUE, - BOOLEAN_FALSE, - }, - }), - max: i18n.translate('xpack.canvas.functions.progress.args.maxHelpText', { - defaultMessage: 'The maximum value of the progress element.', - }), - shape: i18n.translate('xpack.canvas.functions.progress.args.shapeHelpText', { - defaultMessage: `Select {list}, or {end}.`, - values: { - list: Object.values(Shape) - .slice(0, -1) - .map((shape) => `\`"${shape}"\``) - .join(', '), - end: `\`"${Object.values(Shape).slice(-1)[0]}"\``, - }, - }), - valueColor: i18n.translate('xpack.canvas.functions.progress.args.valueColorHelpText', { - defaultMessage: 'The color of the progress bar.', - }), - valueWeight: i18n.translate('xpack.canvas.functions.progress.args.valueWeightHelpText', { - defaultMessage: 'The thickness of the progress bar.', - }), - }, -}; - -export const errors = { - invalidMaxValue: (max: number) => - new Error( - i18n.translate('xpack.canvas.functions.progress.invalidMaxValueErrorMessage', { - defaultMessage: "Invalid {arg} value: '{max, number}'. '{arg}' must be greater than 0", - values: { - arg: 'max', - max, - }, - }) - ), - invalidValue: (value: number, max: number = 1) => - new Error( - i18n.translate('xpack.canvas.functions.progress.invalidValueErrorMessage', { - defaultMessage: - "Invalid value: '{value, number}'. Value must be between 0 and {max, number}", - values: { - value, - max, - }, - }) - ), -}; diff --git a/x-pack/plugins/canvas/i18n/functions/function_errors.ts b/x-pack/plugins/canvas/i18n/functions/function_errors.ts index 1e515ece63569..1502a0fda5fdc 100644 --- a/x-pack/plugins/canvas/i18n/functions/function_errors.ts +++ b/x-pack/plugins/canvas/i18n/functions/function_errors.ts @@ -17,7 +17,6 @@ import { errors as getCell } from './dict/get_cell'; import { errors as joinRows } from './dict/join_rows'; import { errors as ply } from './dict/ply'; import { errors as pointseries } from './dict/pointseries'; -import { errors as progress } from './dict/progress'; import { errors as timefilter } from './dict/timefilter'; import { errors as to } from './dict/to'; @@ -34,7 +33,6 @@ export const getFunctionErrors = () => ({ joinRows, ply, pointseries, - progress, timefilter, to, }); diff --git a/x-pack/plugins/canvas/i18n/functions/function_help.ts b/x-pack/plugins/canvas/i18n/functions/function_help.ts index f2ca1da1576a1..c6aff9322e7a3 100644 --- a/x-pack/plugins/canvas/i18n/functions/function_help.ts +++ b/x-pack/plugins/canvas/i18n/functions/function_help.ts @@ -51,7 +51,6 @@ import { help as pie } from './dict/pie'; import { help as plot } from './dict/plot'; import { help as ply } from './dict/ply'; import { help as pointseries } from './dict/pointseries'; -import { help as progress } from './dict/progress'; import { help as render } from './dict/render'; import { help as replace } from './dict/replace'; import { help as rounddate } from './dict/rounddate'; @@ -207,7 +206,6 @@ export const getFunctionHelp = (): FunctionHelpDict => ({ plot, ply, pointseries, - progress, render, replace, rounddate, diff --git a/x-pack/plugins/canvas/i18n/renderers.ts b/x-pack/plugins/canvas/i18n/renderers.ts index 03e0ab3a19ae3..3cf2f99051c81 100644 --- a/x-pack/plugins/canvas/i18n/renderers.ts +++ b/x-pack/plugins/canvas/i18n/renderers.ts @@ -89,16 +89,6 @@ export const RendererStrings = { defaultMessage: 'Render an XY plot from your data', }), }, - progress: { - getDisplayName: () => - i18n.translate('xpack.canvas.renderer.progress.displayName', { - defaultMessage: 'Progress indicator', - }), - getHelpDescription: () => - i18n.translate('xpack.canvas.renderer.progress.helpDescription', { - defaultMessage: 'Render a progress indicator that reveals a percentage of an element', - }), - }, table: { getDisplayName: () => i18n.translate('xpack.canvas.renderer.table.displayName', { diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts index e3824798d1df1..6c658f728e73c 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts +++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts @@ -8,12 +8,11 @@ import { Dispatch } from 'redux'; import { connect } from 'react-redux'; import { get } from 'lodash'; - -import { getId } from '../../lib/get_id'; // @ts-expect-error untyped local import { findExistingAsset } from '../../lib/find_existing_asset'; import { VALID_IMAGE_TYPES } from '../../../common/lib/constants'; import { encode } from '../../../../../../src/plugins/presentation_util/public'; +import { getId } from '../../lib/get_id'; // @ts-expect-error untyped local import { elementsRegistry } from '../../lib/elements_registry'; // @ts-expect-error untyped local diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_upload_workpad.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_upload_workpad.ts index 5c01ce631f5a7..caec30e083d40 100644 --- a/x-pack/plugins/canvas/public/components/home/hooks/use_upload_workpad.ts +++ b/x-pack/plugins/canvas/public/components/home/hooks/use_upload_workpad.ts @@ -12,6 +12,7 @@ import { i18n } from '@kbn/i18n'; import { CANVAS, JSON as JSONString } from '../../../../i18n/constants'; import { useNotifyService } from '../../../services'; import { getId } from '../../../lib/get_id'; + import { useCreateWorkpad } from './use_create_workpad'; import type { CanvasWorkpad } from '../../../../types'; diff --git a/x-pack/plugins/canvas/public/components/shape_preview/shape_preview.tsx b/x-pack/plugins/canvas/public/components/shape_preview/shape_preview.tsx index 48a6874eace0c..4edc1d9ef9e7c 100644 --- a/x-pack/plugins/canvas/public/components/shape_preview/shape_preview.tsx +++ b/x-pack/plugins/canvas/public/components/shape_preview/shape_preview.tsx @@ -37,7 +37,9 @@ export const ShapePreview: FC = ({ shape }) => { const [shapeData, setShapeData] = useState(getDefaultShapeData()); const shapeRef = useCallback>((node) => { - if (node !== null) setShapeData(node.getData()); + if (node !== null) { + setShapeData(node.getData()); + } }, []); if (!shape) return
; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx index 2907e8c4d5dd7..937912570b77f 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx @@ -15,11 +15,11 @@ import { EuiContextMenuPanelItemDescriptor, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { getId } from '../../../lib/get_id'; import { Popover, ClosePopoverFn } from '../../popover'; import { CONTEXT_MENU_TOP_BORDER_CLASSNAME } from '../../../../common/lib'; import { ElementSpec } from '../../../../types'; import { flattenPanelTree } from '../../../lib/flatten_panel_tree'; -import { getId } from '../../../lib/get_id'; import { AssetManager } from '../../asset_manager'; import { SavedElementsModal } from '../../saved_elements_modal'; diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/layout_functions.js b/x-pack/plugins/canvas/public/lib/aeroelastic/layout_functions.js index e42578211d863..f620de03bea4c 100644 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/layout_functions.js +++ b/x-pack/plugins/canvas/public/lib/aeroelastic/layout_functions.js @@ -5,6 +5,8 @@ * 2.0. */ +import { getId as rawGetId } from '../get_id'; + import { insideAABB, landmarkPoint, shapesAt } from './geometry'; import { @@ -40,8 +42,6 @@ import { removeDuplicates, } from './functional'; -import { getId as rawGetId } from './../../lib/get_id'; - const idMap = {}; const getId = (name, extension) => { // ensures that `axisAlignedBoundingBoxShape` is pure-ish - a new call with the same input will not yield a new id diff --git a/x-pack/plugins/canvas/public/services/storybook/workpad.ts b/x-pack/plugins/canvas/public/services/storybook/workpad.ts index cdf4137e1d84c..3ecac4b49310e 100644 --- a/x-pack/plugins/canvas/public/services/storybook/workpad.ts +++ b/x-pack/plugins/canvas/public/services/storybook/workpad.ts @@ -9,7 +9,6 @@ import moment from 'moment'; import { action } from '@storybook/addon-actions'; import { PluginServiceFactory } from '../../../../../../src/plugins/presentation_util/public'; - import { getId } from '../../lib/get_id'; // @ts-expect-error import { getDefaultWorkpad } from '../../state/defaults'; diff --git a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js index 0217351af9cb6..e9cf74f1fba44 100644 --- a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js +++ b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js @@ -8,7 +8,6 @@ import { markdown } from '../canvas_plugin_src/renderers/markdown'; import { pie } from '../canvas_plugin_src/renderers/pie'; import { plot } from '../canvas_plugin_src/renderers/plot'; -import { progress } from '../canvas_plugin_src/renderers/progress'; import { table } from '../canvas_plugin_src/renderers/table'; import { text } from '../canvas_plugin_src/renderers/text'; import { imageRenderer as image } from '../../../../src/plugins/expression_image/public'; @@ -18,7 +17,10 @@ import { } from '../../../../src/plugins/expression_error/public'; import { repeatImageRenderer as repeatImage } from '../../../../src/plugins/expression_repeat_image/public'; import { revealImageRenderer as revealImage } from '../../../../src/plugins/expression_reveal_image/public'; -import { shapeRenderer as shape } from '../../../../src/plugins/expression_shape/public'; +import { + shapeRenderer as shape, + progressRenderer as progress, +} from '../../../../src/plugins/expression_shape/public'; import { metricRenderer as metric } from '../../../../src/plugins/expression_metric/public'; /** diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 168a2d910930d..4b1dcac9cadba 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6731,17 +6731,17 @@ "xpack.canvas.functions.pointseries.args.yHelpText": "Y軸の値です。", "xpack.canvas.functions.pointseries.unwrappedExpressionErrorMessage": "表現は {fn} などの関数で囲む必要があります", "xpack.canvas.functions.pointseriesHelpText": "{DATATABLE} を点の配列モデルに変換します。現在 {TINYMATH} 式でディメンションのメジャーを区別します。{TINYMATH_URL} をご覧ください。引数に {TINYMATH} 式が入力された場合、その引数をメジャーとして使用し、そうでない場合はディメンションになります。ディメンションを組み合わせて固有のキーを作成します。その後メジャーはそれらのキーで、指定された {TINYMATH} 関数を使用して複製されます。", - "xpack.canvas.functions.progress.args.barColorHelpText": "背景バーの色です。", - "xpack.canvas.functions.progress.args.barWeightHelpText": "背景バーの太さです。", - "xpack.canvas.functions.progress.args.fontHelpText": "ラベルの {CSS} フォントプロパティです。例:{FONT_FAMILY} または {FONT_WEIGHT}。", - "xpack.canvas.functions.progress.args.labelHelpText": "ラベルの表示・非表示を切り替えるには、{BOOLEAN_TRUE}または{BOOLEAN_FALSE}を使用します。また、ラベルとして表示する文字列を入力することもできます。", - "xpack.canvas.functions.progress.args.maxHelpText": "進捗エレメントの最高値です。", - "xpack.canvas.functions.progress.args.shapeHelpText": "{list} または {end} を選択します。", - "xpack.canvas.functions.progress.args.valueColorHelpText": "進捗バーの色です。", - "xpack.canvas.functions.progress.args.valueWeightHelpText": "進捗バーの太さです。", - "xpack.canvas.functions.progress.invalidMaxValueErrorMessage": "無効な {arg} 値:「{max, number}」。「{arg}」は 0 より大きい必要があります", - "xpack.canvas.functions.progress.invalidValueErrorMessage": "無効な値:「{value, number}」。値は 0 と {max, number} の間でなければなりません", - "xpack.canvas.functions.progressHelpText": "進捗エレメントを構成します。", + "expressionShape.functions.progress.args.barColorHelpText": "背景バーの色です。", + "expressionShape.functions.progress.args.barWeightHelpText": "背景バーの太さです。", + "expressionShape.functions.progress.args.fontHelpText": "ラベルの {CSS} フォントプロパティです。例:{FONT_FAMILY} または {FONT_WEIGHT}。", + "expressionShape.functions.progress.args.labelHelpText": "ラベルの表示・非表示を切り替えるには、{BOOLEAN_TRUE}または{BOOLEAN_FALSE}を使用します。また、ラベルとして表示する文字列を入力することもできます。", + "expressionShape.functions.progress.args.maxHelpText": "進捗エレメントの最高値です。", + "expressionShape.functions.progress.args.shapeHelpText": "{list} または {end} を選択します。", + "expressionShape.functions.progress.args.valueColorHelpText": "進捗バーの色です。", + "expressionShape.functions.progress.args.valueWeightHelpText": "進捗バーの太さです。", + "expressionShape.functions.progress.invalidMaxValueErrorMessage": "無効な {arg} 値:「{max, number}」。「{arg}」は 0 より大きい必要があります", + "expressionShape.functions.progress.invalidValueErrorMessage": "無効な値:「{value, number}」。値は 0 と {max, number} の間でなければなりません", + "expressionShape.functions.progressHelpText": "進捗エレメントを構成します。", "xpack.canvas.functions.render.args.asHelpText": "レンダリングに使用するエレメントタイプです。代わりに {plotFn} や {shapeFn} などの特殊な関数を使用するほうがいいでしょう。", "xpack.canvas.functions.render.args.containerStyleHelpText": "背景、境界、透明度を含む、コンテナーのスタイルです。", "xpack.canvas.functions.render.args.cssHelpText": "このエレメントの対象となるカスタム {CSS} のブロックです。", @@ -6930,8 +6930,8 @@ "xpack.canvas.renderer.pie.helpDescription": "データから円グラフをレンダリングします", "xpack.canvas.renderer.plot.displayName": "座標プロット", "xpack.canvas.renderer.plot.helpDescription": "データから XY プロットをレンダリングします", - "xpack.canvas.renderer.progress.displayName": "進捗インジケーター", - "xpack.canvas.renderer.progress.helpDescription": "エレメントのパーセンテージを示す進捗インジケーターをレンダリングします", + "expressionShape.renderer.progress.displayName": "進捗インジケーター", + "expressionShape.renderer.progress.helpDescription": "エレメントのパーセンテージを示す進捗インジケーターをレンダリングします", "xpack.canvas.renderer.table.displayName": "データテーブル", "xpack.canvas.renderer.table.helpDescription": "表形式データを {HTML} としてレンダリングします", "xpack.canvas.renderer.text.displayName": "プレインテキスト", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f610616bd1509..dfffafc0c57d2 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6771,17 +6771,17 @@ "xpack.canvas.functions.pointseries.args.yHelpText": "Y 轴上的值。", "xpack.canvas.functions.pointseries.unwrappedExpressionErrorMessage": "表达式必须包装在函数中,例如 {fn}", "xpack.canvas.functions.pointseriesHelpText": "将 {DATATABLE} 转成点序列模型。当前我们通过寻找 {TINYMATH} 表达式来区分度量和维度。请参阅 {TINYMATH_URL}。如果在参数中输入 {TINYMATH} 表达式,我们将该参数视为度量,否则该参数为维度。维度将进行组合以创建唯一键。然后,这些键使用指定的 {TINYMATH} 函数消除重复的度量", - "xpack.canvas.functions.progress.args.barColorHelpText": "背景条形的颜色。", - "xpack.canvas.functions.progress.args.barWeightHelpText": "背景条形的粗细。", - "xpack.canvas.functions.progress.args.fontHelpText": "标签的 {CSS} 字体属性。例如 {FONT_FAMILY} 或 {FONT_WEIGHT}。", - "xpack.canvas.functions.progress.args.labelHelpText": "要显示或隐藏标签,请使用 {BOOLEAN_TRUE} 或 {BOOLEAN_FALSE}。或者,提供字符串以显示为标签。", - "xpack.canvas.functions.progress.args.maxHelpText": "进度元素的最大值。", - "xpack.canvas.functions.progress.args.shapeHelpText": "选择 {list} 或 {end}。", - "xpack.canvas.functions.progress.args.valueColorHelpText": "进度条的颜色。", - "xpack.canvas.functions.progress.args.valueWeightHelpText": "进度条的粗细。", - "xpack.canvas.functions.progress.invalidMaxValueErrorMessage": "无效的 {arg} 值:“{max, number}”。“{arg}”必须大于 0", - "xpack.canvas.functions.progress.invalidValueErrorMessage": "无效的值:“{value, number}”。值必须介于 0 和 {max, number} 之间", - "xpack.canvas.functions.progressHelpText": "配置进度元素。", + "expressionShape.functions.progress.args.barColorHelpText": "背景条形的颜色。", + "expressionShape.functions.progress.args.barWeightHelpText": "背景条形的粗细。", + "expressionShape.functions.progress.args.fontHelpText": "标签的 {CSS} 字体属性。例如 {FONT_FAMILY} 或 {FONT_WEIGHT}。", + "expressionShape.functions.progress.args.labelHelpText": "要显示或隐藏标签,请使用 {BOOLEAN_TRUE} 或 {BOOLEAN_FALSE}。或者,提供字符串以显示为标签。", + "expressionShape.functions.progress.args.maxHelpText": "进度元素的最大值。", + "expressionShape.functions.progress.args.shapeHelpText": "选择 {list} 或 {end}。", + "expressionShape.functions.progress.args.valueColorHelpText": "进度条的颜色。", + "expressionShape.functions.progress.args.valueWeightHelpText": "进度条的粗细。", + "expressionShape.functions.progress.invalidMaxValueErrorMessage": "无效的 {arg} 值:“{max, number}”。“{arg}”必须大于 0", + "expressionShape.functions.progress.invalidValueErrorMessage": "无效的值:“{value, number}”。值必须介于 0 和 {max, number} 之间", + "expressionShape.functions.progressHelpText": "配置进度元素。", "xpack.canvas.functions.render.args.asHelpText": "要渲染的元素类型。您可能需要专门的函数,例如 {plotFn} 或 {shapeFn}。", "xpack.canvas.functions.render.args.containerStyleHelpText": "容器的样式,包括背景、边框和透明度。", "xpack.canvas.functions.render.args.cssHelpText": "要限定于元素的任何定制 {CSS} 块。", @@ -6970,8 +6970,8 @@ "xpack.canvas.renderer.pie.helpDescription": "根据您的数据呈现饼图", "xpack.canvas.renderer.plot.displayName": "坐标图", "xpack.canvas.renderer.plot.helpDescription": "根据您的数据呈现 XY 坐标图", - "xpack.canvas.renderer.progress.displayName": "进度指示", - "xpack.canvas.renderer.progress.helpDescription": "呈现显示元素百分比的进度指示", + "expressionShape.renderer.progress.displayName": "进度指示", + "expressionShape.renderer.progress.helpDescription": "呈现显示元素百分比的进度指示", "xpack.canvas.renderer.table.displayName": "数据表", "xpack.canvas.renderer.table.helpDescription": "将表格数据呈现为 {HTML}", "xpack.canvas.renderer.text.displayName": "纯文本",