[Canvas] Format argument for the metric element (#42007) (#42779)

* Added numberFormat arg type and metricFormat arg to metric function

    Updated function reference doc

    Fixed default arg value

* Added stories for NumberFormatArgInput

* Added arg alias

* Added stories for metric renderer

* Fixed ts errors and added comments

* Updated comments

* Removed extra test

* Added snapshots

* Addressing feedback

* Fixed typo

* Updated metricFormat help text

* Removed redundant help prop
This commit is contained in:
Catherine Liu 2019-08-06 16:26:25 -07:00 committed by GitHub
parent a594fdbee6
commit ddfe6e8dea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 931 additions and 64 deletions

View file

@ -1209,17 +1209,23 @@ Aliases: `label`, `text`, `description`
Default: `""`
|`labelFont`
|`style`
|The CSS font properties for the label. For example, `font-family` or `font-weight`.
Default: `{font size=14 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align=center}`.
|`metricFont`
|`style`
|The CSS font properties for the metric. For example, `font-family` or `font-weight`.
Default: `{font size=48 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align=center lHeight=48}`.
|`labelFont`
|`style`
|The CSS font properties for the label. For example, `font-family` or `font-weight`.
|`metricFormat`
Default: `{font size=14 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align=center}`.
Alias: `format`
|`string`
|A NumeralJS format string. For example, `"0.0a"` or `"0%"`. See http://numeraljs.com/#format.
|===
*Returns:* `render`

View file

@ -6,6 +6,7 @@
import { openSans } from '../../../common/lib/fonts';
import header from './header.png';
import { AdvancedSettings } from '../../../public/lib/kibana_advanced_settings';
import { ElementFactory } from '../../../types';
export const metric: ElementFactory = () => ({
@ -22,5 +23,6 @@ export const metric: ElementFactory = () => ({
| metric "Countries"
metricFont={font size=48 family="${openSans.value}" color="#000000" align="center" lHeight=48}
labelFont={font size=14 family="${openSans.value}" color="#000000" align="center"}
metricFormat="${AdvancedSettings.get('format:number:defaultPattern')}"
| render`,
});

View file

@ -38,7 +38,7 @@ describe('metric', () => {
});
});
describe('metricStyle', () => {
describe('metricFont', () => {
it('sets the font style for the metric', () => {
const result = fn(null, {
metricFont: fontStyle,
@ -51,7 +51,7 @@ describe('metric', () => {
// it("sets a default style for the metric when not provided, () => {});
});
describe('labelStyle', () => {
describe('labelFont', () => {
it('sets the font style for the label', () => {
const result = fn(null, {
labelFont: fontStyle,
@ -63,5 +63,15 @@ describe('metric', () => {
// TODO: write test when using an instance of the interpreter
// it("sets a default style for the label when not provided, () => {});
});
describe('metricFormat', () => {
it('sets the number format of the metric value', () => {
const result = fn(null, {
metricFormat: '0.0%',
});
expect(result.value).toHaveProperty('metricFormat', '0.0%');
});
});
});
});

View file

@ -14,6 +14,7 @@ type Context = number | string | null;
interface Arguments {
label: string;
metricFont: Style;
metricFormat: string;
labelFont: Style;
}
@ -35,26 +36,32 @@ export function metric(): ExpressionFunction<'metric', Context, Arguments, Rende
help: argHelp.label,
default: '""',
},
metricFont: {
types: ['style'],
help: argHelp.metricFont,
default: `{font size=48 family="${openSans.value}" color="#000000" align=center lHeight=48}`,
},
labelFont: {
types: ['style'],
help: argHelp.labelFont,
default: `{font size=14 family="${openSans.value}" color="#000000" align=center}`,
},
metricFont: {
types: ['style'],
help: argHelp.metricFont,
default: `{font size=48 family="${openSans.value}" color="#000000" align=center lHeight=48}`,
},
metricFormat: {
types: ['string'],
aliases: ['format'],
help: argHelp.metricFormat,
},
},
fn: (context, { label, metricFont, labelFont }) => {
fn: (context, { label, labelFont, metricFont, metricFormat }) => {
return {
type: 'render',
as: 'metric',
value: {
metric: context === null ? '?' : context,
label,
metricFont,
labelFont,
metricFont,
metricFormat,
},
};
},

View file

@ -0,0 +1,263 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots renderers/Metric with formatted string metric and a specified format 1`] = `
<div
style={
Object {
"width": "200px",
}
}
>
<div
className="canvasMetric"
>
<div
className="canvasMetric__metric"
style={
Object {
"color": "#b83c6f",
"fontFamily": "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif",
"fontSize": "48px",
"fontStyle": "normal",
"fontWeight": "bold",
"lineHeight": "1",
"textAlign": "center",
"textDecoration": "none",
}
}
>
$10m
</div>
<div
className="canvasMetric__label"
style={
Object {
"color": "#000000",
"fontFamily": "Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif",
"fontSize": "24px",
"fontStyle": "italic",
"fontWeight": "normal",
"lineHeight": "1",
"textAlign": "center",
"textDecoration": "none",
}
}
>
Total Revenue
</div>
</div>
</div>
`;
exports[`Storyshots renderers/Metric with invalid metricFont 1`] = `
<div
style={
Object {
"width": "200px",
}
}
>
<div
className="canvasMetric"
>
<div
className="canvasMetric__metric"
style={
Object {
"color": "#b83c6f",
"fontFamily": "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif",
"fontSize": "48px",
"fontStyle": "normal",
"fontWeight": "bold",
"lineHeight": "1",
"textAlign": "center",
"textDecoration": "none",
}
}
>
$10m
</div>
<div
className="canvasMetric__label"
style={
Object {
"color": "#000000",
"fontFamily": "Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif",
"fontSize": "24px",
"fontStyle": "italic",
"fontWeight": "normal",
"lineHeight": "1",
"textAlign": "center",
"textDecoration": "none",
}
}
>
Total Revenue
</div>
</div>
</div>
`;
exports[`Storyshots renderers/Metric with label 1`] = `
<div
style={
Object {
"width": "200px",
}
}
>
<div
className="canvasMetric"
>
<div
className="canvasMetric__metric"
style={
Object {
"color": "#b83c6f",
"fontFamily": "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif",
"fontSize": "48px",
"fontStyle": "normal",
"fontWeight": "bold",
"lineHeight": "1",
"textAlign": "center",
"textDecoration": "none",
}
}
>
$12.34
</div>
<div
className="canvasMetric__label"
style={
Object {
"color": "#000000",
"fontFamily": "Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif",
"fontSize": "24px",
"fontStyle": "italic",
"fontWeight": "normal",
"lineHeight": "1",
"textAlign": "center",
"textDecoration": "none",
}
}
>
Average price
</div>
</div>
</div>
`;
exports[`Storyshots renderers/Metric with null metric 1`] = `
<div
style={
Object {
"width": "200px",
}
}
>
<div
className="canvasMetric"
>
<div
className="canvasMetric__metric"
style={Object {}}
/>
</div>
</div>
`;
exports[`Storyshots renderers/Metric with number metric 1`] = `
<div
style={
Object {
"width": "200px",
}
}
>
<div
className="canvasMetric"
>
<div
className="canvasMetric__metric"
style={
Object {
"color": "#b83c6f",
"fontFamily": "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif",
"fontSize": "48px",
"fontStyle": "normal",
"fontWeight": "bold",
"lineHeight": "1",
"textAlign": "center",
"textDecoration": "none",
}
}
>
12345.6789
</div>
</div>
</div>
`;
exports[`Storyshots renderers/Metric with number metric and a specified format 1`] = `
<div
style={
Object {
"width": "200px",
}
}
>
<div
className="canvasMetric"
>
<div
className="canvasMetric__metric"
style={
Object {
"color": "#b83c6f",
"fontFamily": "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif",
"fontSize": "48px",
"fontStyle": "normal",
"fontWeight": "bold",
"lineHeight": "1",
"textAlign": "center",
"textDecoration": "none",
}
}
>
-0.24%
</div>
</div>
</div>
`;
exports[`Storyshots renderers/Metric with string metric 1`] = `
<div
style={
Object {
"width": "200px",
}
}
>
<div
className="canvasMetric"
>
<div
className="canvasMetric__metric"
style={
Object {
"color": "#b83c6f",
"fontFamily": "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif",
"fontSize": "48px",
"fontStyle": "normal",
"fontWeight": "bold",
"lineHeight": "1",
"textAlign": "center",
"textDecoration": "none",
}
}
>
$12.34
</div>
</div>
</div>
`;

View file

@ -0,0 +1,84 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { storiesOf } from '@storybook/react';
import React, { CSSProperties } from 'react';
import { Metric } from '../metric';
const labelFontSpec: CSSProperties = {
fontFamily: "Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif",
fontWeight: 'normal',
fontStyle: 'italic',
textDecoration: 'none',
textAlign: 'center',
fontSize: '24px',
lineHeight: '1',
color: '#000000',
};
const metricFontSpec: CSSProperties = {
fontFamily:
"Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif",
fontWeight: 'bold',
fontStyle: 'normal',
textDecoration: 'none',
textAlign: 'center',
fontSize: '48px',
lineHeight: '1',
color: '#b83c6f',
};
storiesOf('renderers/Metric', module)
.addDecorator(story => (
<div
style={{
width: '200px',
}}
>
{story()}
</div>
))
.add('with null metric', () => <Metric metric={null} metricFont={{}} labelFont={{}} />)
.add('with number metric', () => (
<Metric metric="12345.6789" labelFont={{}} metricFont={metricFontSpec} />
))
.add('with string metric', () => (
<Metric metric="$12.34" labelFont={labelFontSpec} metricFont={metricFontSpec} />
))
.add('with label', () => (
<Metric
label="Average price"
metric="$12.34"
labelFont={labelFontSpec}
metricFont={metricFontSpec}
/>
))
.add('with number metric and a specified format', () => (
<Metric
metric="-0.0024"
labelFont={labelFontSpec}
metricFont={metricFontSpec}
metricFormat="0.00%"
/>
))
.add('with formatted string metric and a specified format', () => (
<Metric
label="Total Revenue"
metric="$10000000.00"
labelFont={labelFontSpec}
metricFont={metricFontSpec}
metricFormat="$0a"
/>
))
.add('with invalid metricFont', () => (
<Metric
label="Total Revenue"
metric="$10000000.00"
labelFont={labelFontSpec}
metricFont={metricFontSpec}
metricFormat="$0a"
/>
));

View file

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

View file

@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FunctionComponent, CSSProperties } from 'react';
import numeral from '@elastic/numeral';
interface Props {
/** The text to display under the metric */
label?: string;
/** CSS font properties for the label */
labelFont: CSSProperties;
/** Value of the metric to display */
metric: string | number | null;
/** CSS font properties for the metric */
metricFont: CSSProperties;
/** NumeralJS format string */
metricFormat?: string;
}
export const Metric: FunctionComponent<Props> = ({
label,
metric,
labelFont,
metricFont,
metricFormat,
}) => (
<div className="canvasMetric">
<div className="canvasMetric__metric" style={metricFont}>
{metricFormat ? numeral(metric).format(metricFormat) : metric}
</div>
{label && (
<div className="canvasMetric__label" style={labelFont}>
{label}
</div>
)}
</div>
);

View file

@ -1,34 +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;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import ReactDOM from 'react-dom';
export const metric = () => ({
name: 'metric',
displayName: 'Metric',
help: 'Render HTML Markup for the Metric element',
reuseDomNode: true,
render(domNode, config, handlers) {
const metricFontStyle = config.metricFont ? config.metricFont.spec : {};
const labelFontStyle = config.labelFont ? config.labelFont.spec : {};
ReactDOM.render(
<div className="canvasMetric">
<div className="canvasMetric__metric" style={metricFontStyle}>
{config.metric}
</div>
<div className="canvasMetric__label" style={labelFontStyle}>
{config.label}
</div>
</div>,
domNode,
() => handlers.done()
);
handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode));
},
});

View file

@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { CSSProperties } from 'react';
import ReactDOM from 'react-dom';
import { RendererFactory, Style } from '../../../types';
import { Metric } from './component/metric';
export interface Config {
/** The text to display under the metric */
label: string;
/** Font settings for the label */
labelFont: Style;
/** Value of the metric to display */
metric: string | number | null;
/** Font settings for the metric */
metricFont: Style;
/** NumeralJS format string */
metricFormat: string;
}
export const metric: RendererFactory<Config> = () => ({
name: 'metric',
displayName: 'Metric',
help: 'Render HTML Markup for the Metric element',
reuseDomNode: true,
render(domNode, config, handlers) {
ReactDOM.render(
<Metric
label={config.label}
labelFont={config.labelFont ? (config.labelFont.spec as CSSProperties) : {}}
metric={config.metric}
metricFont={config.metricFont ? (config.metricFont.spec as CSSProperties) : {}}
metricFormat={config.metricFormat}
/>,
domNode,
() => handlers.done()
);
handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode));
},
});

View file

@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n';
import { metric } from '../../functions/common/metric';
import { FunctionHelp } from '.';
import { FunctionFactory } from '../../../types';
import { FONT_FAMILY, FONT_WEIGHT, CSS } from '../constants';
import { FONT_FAMILY, FONT_WEIGHT, CSS, NUMERALJS } from '../constants';
export const help: FunctionHelp<FunctionFactory<typeof metric>> = {
help: i18n.translate('xpack.canvas.functions.metricHelpText', {
@ -18,15 +18,6 @@ export const help: FunctionHelp<FunctionFactory<typeof metric>> = {
label: i18n.translate('xpack.canvas.functions.metric.args.labelHelpText', {
defaultMessage: 'The text describing the metric.',
}),
metricFont: i18n.translate('xpack.canvas.functions.metric.args.metricFontHelpText', {
defaultMessage:
'The {CSS} font properties for the metric. For example, {FONT_FAMILY} or {FONT_WEIGHT}.',
values: {
CSS,
FONT_FAMILY,
FONT_WEIGHT,
},
}),
labelFont: i18n.translate('xpack.canvas.functions.metric.args.labelFontHelpText', {
defaultMessage:
'The {CSS} font properties for the label. For example, {FONT_FAMILY} or {FONT_WEIGHT}.',
@ -36,5 +27,24 @@ export const help: FunctionHelp<FunctionFactory<typeof metric>> = {
FONT_WEIGHT,
},
}),
metricFont: i18n.translate('xpack.canvas.functions.metric.args.metricFontHelpText', {
defaultMessage:
'The {CSS} font properties for the metric. For example, {FONT_FAMILY} or {FONT_WEIGHT}.',
values: {
CSS,
FONT_FAMILY,
FONT_WEIGHT,
},
}),
metricFormat: i18n.translate('xpack.canvas.functions.metric.args.metricFormatHelpText', {
defaultMessage:
'A {NUMERALJS} format string. For example, {example1} or {example2}. See {url}.',
values: {
example1: `"0.0a"`,
example2: `"0%"`,
NUMERALJS,
url: 'http://numeraljs.com/#format',
},
}),
},
};

View file

@ -9,6 +9,7 @@ import { datacolumn } from './datacolumn';
import { filterGroup } from './filter_group';
import { imageUpload } from './image_upload';
import { number } from './number';
import { numberFormat } from './number_format';
import { palette } from './palette';
import { percentage } from './percentage';
import { range } from './range';
@ -24,6 +25,7 @@ export const args = [
filterGroup,
imageUpload,
number,
numberFormat,
palette,
percentage,
range,

View file

@ -0,0 +1,242 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots arguments/NumberFormat with custom format 1`] = `
Array [
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<select
className="euiSelect euiSelect--compressed"
id="NumberFormatExample3"
onChange={[Function]}
onMouseUp={[Function]}
value=""
>
<option
value="0.0[000]"
>
Number
</option>
<option
value="0.0%"
>
Percent
</option>
<option
value="$0.00"
>
Currency
</option>
<option
value="00:00:00"
>
Duration
</option>
<option
value="0.0b"
>
Bytes
</option>
<option
value=""
>
Custom
</option>
</select>
<div
className="euiFormControlLayoutIcons euiFormControlLayoutIcons--right"
>
<span
className="euiFormControlLayoutCustomIcon"
>
<svg
aria-hidden="true"
className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon"
focusable="false"
height={16}
style={null}
viewBox="0 0 16 16"
width={16}
xmlns="http://www.w3.org/2000/svg"
/>
</span>
</div>
</div>
</div>,
<div
className="euiSpacer euiSpacer--s"
/>,
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
placeholder="0.0a"
type="text"
value="0.0[000]a"
/>
</div>
</div>,
]
`;
exports[`Storyshots arguments/NumberFormat with no format 1`] = `
Array [
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<select
className="euiSelect euiSelect--compressed"
id="NumberFormatExample1"
onChange={[Function]}
onMouseUp={[Function]}
value=""
>
<option
value="0.0[000]"
>
Number
</option>
<option
value="0.0%"
>
Percent
</option>
<option
value="$0.00"
>
Currency
</option>
<option
value="00:00:00"
>
Duration
</option>
<option
value="0.0b"
>
Bytes
</option>
<option
value=""
>
Custom
</option>
</select>
<div
className="euiFormControlLayoutIcons euiFormControlLayoutIcons--right"
>
<span
className="euiFormControlLayoutCustomIcon"
>
<svg
aria-hidden="true"
className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon"
focusable="false"
height={16}
style={null}
viewBox="0 0 16 16"
width={16}
xmlns="http://www.w3.org/2000/svg"
/>
</span>
</div>
</div>
</div>,
<div
className="euiSpacer euiSpacer--s"
/>,
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<input
className="euiFieldText euiFieldText--compressed"
onChange={[Function]}
placeholder="0.0a"
type="text"
value=""
/>
</div>
</div>,
]
`;
exports[`Storyshots arguments/NumberFormat with preset format 1`] = `
<div
className="euiFormControlLayout euiFormControlLayout--compressed"
>
<div
className="euiFormControlLayout__childrenWrapper"
>
<select
className="euiSelect euiSelect--compressed"
id="NumberFormatExample2"
onChange={[Function]}
onMouseUp={[Function]}
value="$0.00"
>
<option
value="0.0[000]"
>
Number
</option>
<option
value="0.0%"
>
Percent
</option>
<option
value="$0.00"
>
Currency
</option>
<option
value="00:00:00"
>
Duration
</option>
<option
value="0.0b"
>
Bytes
</option>
<option
value=""
>
Custom
</option>
</select>
<div
className="euiFormControlLayoutIcons euiFormControlLayoutIcons--right"
>
<span
className="euiFormControlLayoutCustomIcon"
>
<svg
aria-hidden="true"
className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon"
focusable="false"
height={16}
style={null}
viewBox="0 0 16 16"
width={16}
xmlns="http://www.w3.org/2000/svg"
/>
</span>
</div>
</div>
</div>
`;

View file

@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { storiesOf } from '@storybook/react';
import React from 'react';
import { action } from '@storybook/addon-actions';
import { NumberFormatArgInput } from '../number_format';
const numberFormats = [
{ value: '0.0[000]', text: 'Number' },
{ value: '0.0%', text: 'Percent' },
{ value: '$0.00', text: 'Currency' },
{ value: '00:00:00', text: 'Duration' },
{ value: '0.0b', text: 'Bytes' },
];
storiesOf('arguments/NumberFormat', module)
.add('with no format', () => (
<NumberFormatArgInput
numberFormats={numberFormats}
onValueChange={action('onValueChange')}
argValue=""
argId="NumberFormatExample1"
/>
))
.add('with preset format', () => (
<NumberFormatArgInput
numberFormats={numberFormats}
onValueChange={action('onValueChange')}
argValue="$0.00"
argId="NumberFormatExample2"
/>
))
.add('with custom format', () => (
<NumberFormatArgInput
numberFormats={numberFormats}
onValueChange={action('onValueChange')}
argValue="0.0[000]a"
argId="NumberFormatExample3"
/>
));

View file

@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { compose, withProps } from 'recompose';
import { NumberFormatArgInput as Component, Props as ComponentProps } from './number_format';
import { AdvancedSettings } from '../../../../public/lib/kibana_advanced_settings';
// @ts-ignore untyped local lib
import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component';
const formatMap = {
NUMBER: AdvancedSettings.get('format:number:defaultPattern'),
PERCENT: AdvancedSettings.get('format:percent:defaultPattern'),
CURRENCY: AdvancedSettings.get('format:currency:defaultPattern'),
DURATION: '00:00:00',
BYTES: AdvancedSettings.get('format:bytes:defaultPattern'),
};
const numberFormats = [
{ value: formatMap.NUMBER, text: 'Number' },
{ value: formatMap.PERCENT, text: 'Percent' },
{ value: formatMap.CURRENCY, text: 'Currency' },
{ value: formatMap.DURATION, text: 'Duration' },
{ value: formatMap.BYTES, text: 'Bytes' },
];
export const NumberFormatArgInput = compose<ComponentProps, null>(withProps({ numberFormats }))(
Component
);
export const numberFormat = () => ({
name: 'numberFormat',
displayName: 'Number Format',
help: 'Select or enter a valid NumeralJS format',
simpleTemplate: templateFromReactComponent(NumberFormatArgInput),
});

View file

@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment, ChangeEvent, FunctionComponent } from 'react';
import PropTypes from 'prop-types';
import { EuiSelect, EuiFieldText, EuiSpacer } from '@elastic/eui';
interface NumberFormatOption {
/** A NumeralJS format string */
value: string;
/** The name to display for the format */
text: string;
}
export interface Props {
/** An array of number formats options */
numberFormats: NumberFormatOption[];
/** The handler to invoke when value changes */
onValueChange: (value: string) => void;
/** The value of the argument */
argValue: string;
/** The ID for the argument */
argId: string;
}
export const NumberFormatArgInput: FunctionComponent<Props> = ({
numberFormats,
onValueChange,
argValue,
argId,
}) => {
const formatOptions = numberFormats.concat({ value: '', text: 'Custom' });
const handleTextChange = (ev: ChangeEvent<HTMLInputElement>) => onValueChange(ev.target.value);
const handleSelectChange = (ev: ChangeEvent<HTMLSelectElement>) => {
const { value } = formatOptions[ev.target.selectedIndex];
return onValueChange(value || '0.0a');
};
// checks if the argValue is one of the preset formats
const isCustomFormat = !argValue || !formatOptions.map(({ value }) => value).includes(argValue);
return (
<Fragment>
<EuiSelect
compressed
id={argId}
value={isCustomFormat ? '' : argValue}
options={formatOptions}
onChange={handleSelectChange}
/>
{isCustomFormat && (
<Fragment>
<EuiSpacer size="s" />
<EuiFieldText
placeholder="0.0a"
value={argValue}
compressed
onChange={handleTextChange}
/>
</Fragment>
)}
</Fragment>
);
};
NumberFormatArgInput.propTypes = {
onValueChange: PropTypes.func.isRequired,
argValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]).isRequired,
argId: PropTypes.string.isRequired,
};

View file

@ -5,6 +5,7 @@
*/
import { openSans } from '../../../common/lib/fonts';
import { AdvancedSettings } from '../../../public/lib/kibana_advanced_settings';
export const metric = () => ({
name: 'metric',
@ -19,6 +20,13 @@ export const metric = () => ({
argType: 'string',
default: '""',
},
{
name: 'labelFont',
displayName: 'Label text settings',
help: 'Fonts, alignment and color',
argType: 'font',
default: `{font size=18 family="${openSans.value}" color="#000000" align=center}`,
},
{
name: 'metricFont',
displayName: 'Metric text settings',
@ -27,11 +35,10 @@ export const metric = () => ({
default: `{font size=48 family="${openSans.value}" color="#000000" align=center lHeight=48}`,
},
{
name: 'labelFont',
displayName: 'Label text settings',
help: 'Fonts, alignment and color',
argType: 'font',
default: `{font size=18 family="${openSans.value}" color="#000000" align=center}`,
name: 'metricFormat',
displayName: 'Metric Format',
argType: 'numberFormat',
default: `"${AdvancedSettings.get('format:number:defaultPattern')}"`,
},
],
});

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import chrome from 'ui/chrome';
export const AdvancedSettings = chrome.getUiSettingsClient();

View file

@ -7,19 +7,32 @@
type GenericCallback = (callback: () => void) => void;
export interface RendererHandlers {
/** Handler to invoke when an element has finished rendering */
done: () => void;
getFilter: () => string;
/** Handler to invoke when an element is deleted or changes to a different render type */
onDestroy: GenericCallback;
/** Handler to invoke when an element's dimensions have changed*/
onResize: GenericCallback;
/** Retrieves the value of the filter property on the element object persisted on the workpad */
getFilter: () => string;
/** Sets the value of the filter property on the element object persisted on the workpad */
setFilter: (filter: string) => void;
}
export interface RendererSpec<RendererConfig = {}> {
/** The render type */
name: string;
/** The name to display */
displayName: string;
/** A description of what is rendered */
help: string;
/** Indicate whether the element should reuse the existing DOM element when re-rendering */
reuseDomNode: boolean;
height: number;
/** The default width of the element in pixels */
width?: number;
/** The default height of the element in pixels */
height?: number;
/** A function that renders an element into the specified DOM element */
render: (domNode: HTMLElement, config: RendererConfig, handlers: RendererHandlers) => void;
}