mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* Visual Builder duration in second showing as number Fix: #35641 , #35805 * fix PR comments * Visual Builder duration in second showing as number * Visual Builder duration in second showing as number -fix placeholder
This commit is contained in:
parent
d0a0c3fc7e
commit
e10c35156f
4 changed files with 193 additions and 96 deletions
|
@ -23,35 +23,37 @@ import _ from 'lodash';
|
|||
import {
|
||||
htmlIdGenerator, EuiComboBox, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiFieldText, EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import { durationOutputOptions, durationInputOptions } from './lib/durations';
|
||||
import { durationOutputOptions, durationInputOptions, isDuration } from './lib/durations';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
|
||||
const durationFormatTest = /[pnumshdwMY]+,[pnumshdwMY]+/;
|
||||
|
||||
const DEFAULT_OUTPUT_PRECISION = '2';
|
||||
|
||||
class DataFormatPickerUI extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleCustomChange = this.handleCustomChange.bind(this);
|
||||
let from = 'ms';
|
||||
let to = 'ms';
|
||||
let decimals = 2;
|
||||
if (durationFormatTest.test(props.value)) {
|
||||
|
||||
let from;
|
||||
let to;
|
||||
let decimals;
|
||||
|
||||
if (isDuration(props.value)) {
|
||||
[from, to, decimals] = props.value.split(',');
|
||||
}
|
||||
|
||||
this.state = {
|
||||
from,
|
||||
to,
|
||||
decimals
|
||||
from: from || 'ms',
|
||||
to: to || 'ms',
|
||||
decimals: decimals || '',
|
||||
};
|
||||
}
|
||||
|
||||
handleCustomChange() {
|
||||
handleCustomChange = () => {
|
||||
this.props.onChange([{ value: this.custom && this.custom.value || '' }]);
|
||||
}
|
||||
};
|
||||
|
||||
handleChange(selectedOptions) {
|
||||
handleChange = selectedOptions => {
|
||||
if (selectedOptions.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
@ -61,12 +63,12 @@ class DataFormatPickerUI extends Component {
|
|||
} else if (selectedOptions[0].value === 'duration') {
|
||||
const { from, to, decimals } = this.state;
|
||||
this.props.onChange([{
|
||||
value: `${from},${to},${decimals}`
|
||||
value: `${from},${to},${decimals}`,
|
||||
}]);
|
||||
} else {
|
||||
this.props.onChange(selectedOptions);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleDurationChange(name) {
|
||||
return (selectedOptions) => {
|
||||
|
@ -82,11 +84,11 @@ class DataFormatPickerUI extends Component {
|
|||
}
|
||||
|
||||
this.setState({
|
||||
[name]: newValue
|
||||
[name]: newValue,
|
||||
}, () => {
|
||||
const { from, to, decimals } = this.state;
|
||||
this.props.onChange([{
|
||||
value: `${from},${to},${decimals}`
|
||||
value: `${from},${to},${decimals}`,
|
||||
}]);
|
||||
});
|
||||
};
|
||||
|
@ -99,16 +101,31 @@ class DataFormatPickerUI extends Component {
|
|||
if (!_.includes(['bytes', 'number', 'percent'], value)) {
|
||||
defaultValue = 'custom';
|
||||
}
|
||||
if (durationFormatTest.test(value)) {
|
||||
if (isDuration(value)) {
|
||||
defaultValue = 'duration';
|
||||
}
|
||||
const { intl } = this.props;
|
||||
const options = [
|
||||
{ label: intl.formatMessage({ id: 'tsvb.dataFormatPicker.bytesLabel', defaultMessage: 'Bytes' }), value: 'bytes' },
|
||||
{ label: intl.formatMessage({ id: 'tsvb.dataFormatPicker.numberLabel', defaultMessage: 'Number' }), value: 'number' },
|
||||
{ label: intl.formatMessage({ id: 'tsvb.dataFormatPicker.percentLabel', defaultMessage: 'Percent' }), value: 'percent' },
|
||||
{ label: intl.formatMessage({ id: 'tsvb.dataFormatPicker.durationLabel', defaultMessage: 'Duration' }), value: 'duration' },
|
||||
{ label: intl.formatMessage({ id: 'tsvb.dataFormatPicker.customLabel', defaultMessage: 'Custom' }), value: 'custom' }
|
||||
{
|
||||
label: intl.formatMessage({ id: 'tsvb.dataFormatPicker.bytesLabel', defaultMessage: 'Bytes' }),
|
||||
value: 'bytes',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ id: 'tsvb.dataFormatPicker.numberLabel', defaultMessage: 'Number' }),
|
||||
value: 'number',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ id: 'tsvb.dataFormatPicker.percentLabel', defaultMessage: 'Percent' }),
|
||||
value: 'percent',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ id: 'tsvb.dataFormatPicker.durationLabel', defaultMessage: 'Duration' }),
|
||||
value: 'duration',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ id: 'tsvb.dataFormatPicker.customLabel', defaultMessage: 'Custom' }),
|
||||
value: 'custom',
|
||||
},
|
||||
];
|
||||
const selectedOption = options.find(option => {
|
||||
return defaultValue === option.value;
|
||||
|
@ -118,7 +135,7 @@ class DataFormatPickerUI extends Component {
|
|||
if (defaultValue === 'duration') {
|
||||
const [from, to, decimals] = value.split(',');
|
||||
const selectedFrom = durationInputOptions.find(option => from === option.value);
|
||||
const selectedTo = durationOutputOptions.find(option => to === option.value);
|
||||
const selectedTo = durationOutputOptions.find(option => to === option.value);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup responsive={false} gutterSize="s">
|
||||
|
@ -167,21 +184,26 @@ class DataFormatPickerUI extends Component {
|
|||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFormRow
|
||||
id={htmlId('decimal')}
|
||||
label={(<FormattedMessage
|
||||
id="tsvb.dataFormatPicker.decimalPlacesLabel"
|
||||
defaultMessage="Decimal places"
|
||||
/>)}
|
||||
>
|
||||
<EuiFieldText
|
||||
defaultValue={decimals}
|
||||
inputRef={(el) => this.decimals = el}
|
||||
onChange={this.handleDurationChange('decimals')}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
|
||||
{selectedTo && selectedTo.value !== 'humanize' && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFormRow
|
||||
id={htmlId('decimal')}
|
||||
label={(<FormattedMessage
|
||||
id="tsvb.dataFormatPicker.decimalPlacesLabel"
|
||||
defaultMessage="Decimal places"
|
||||
/>)}
|
||||
>
|
||||
<EuiFieldText
|
||||
defaultValue={decimals}
|
||||
inputRef={(el) => this.decimals = el}
|
||||
placeholder={DEFAULT_OUTPUT_PRECISION}
|
||||
onChange={this.handleDurationChange('decimals')}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>)
|
||||
}
|
||||
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
@ -198,7 +220,9 @@ class DataFormatPickerUI extends Component {
|
|||
<FormattedMessage
|
||||
id="tsvb.dataFormatPicker.formatStringHelpText"
|
||||
defaultMessage="See {numeralJsLink}"
|
||||
values={{ numeralJsLink: (<EuiLink href="http://numeraljs.com/#format" target="_BLANK">Numeral.js</EuiLink>) }}
|
||||
values={{
|
||||
numeralJsLink: (<EuiLink href="http://numeraljs.com/#format" target="_BLANK">Numeral.js</EuiLink>),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
|
@ -232,13 +256,13 @@ class DataFormatPickerUI extends Component {
|
|||
}
|
||||
|
||||
DataFormatPickerUI.defaultProps = {
|
||||
label: i18n.translate('tsvb.defaultDataFormatterLabel', { defaultMessage: 'Data Formatter' })
|
||||
label: i18n.translate('tsvb.defaultDataFormatterLabel', { defaultMessage: 'Data Formatter' }),
|
||||
};
|
||||
|
||||
DataFormatPickerUI.propTypes = {
|
||||
value: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
onChange: PropTypes.func
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
export const DataFormatPicker = injectI18n(DataFormatPickerUI);
|
||||
|
|
|
@ -19,53 +19,94 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const durationBaseOptions = [
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.millisecondsLabel', { defaultMessage: 'Milliseconds' }),
|
||||
value: 'ms',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.secondsLabel', { defaultMessage: 'Seconds' }),
|
||||
value: 's',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.minutesLabel', { defaultMessage: 'Minutes' }),
|
||||
value: 'm',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.hoursLabel', { defaultMessage: 'Hours' }),
|
||||
value: 'h',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.daysLabel', { defaultMessage: 'Days' }),
|
||||
value: 'd',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.weeksLabel', { defaultMessage: 'Weeks' }),
|
||||
value: 'w',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.monthsLabel', { defaultMessage: 'Months' }),
|
||||
value: 'M',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.yearsLabel', { defaultMessage: 'Years' }),
|
||||
value: 'Y',
|
||||
},
|
||||
];
|
||||
|
||||
export const durationOutputOptions = [
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.millisecondsLabel', { defaultMessage: 'milliseconds' }),
|
||||
value: 'ms'
|
||||
label: i18n.translate('tsvb.durationOptions.humanize', { defaultMessage: 'Human readable' }),
|
||||
value: 'humanize',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.secondsLabel', { defaultMessage: 'seconds' }),
|
||||
value: 's'
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.minutesLabel', { defaultMessage: 'minutes' }),
|
||||
value: 'm'
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.hoursLabel', { defaultMessage: 'hours' }),
|
||||
value: 'h'
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.daysLabel', { defaultMessage: 'days' }),
|
||||
value: 'd'
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.weeksLabel', { defaultMessage: 'weeks' }),
|
||||
value: 'w'
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.monthsLabel', { defaultMessage: 'months' }),
|
||||
value: 'M'
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.yearsLabel', { defaultMessage: 'years' }),
|
||||
value: 'Y'
|
||||
}
|
||||
...durationBaseOptions,
|
||||
];
|
||||
|
||||
export const durationInputOptions = [
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.picosecondsLabel', { defaultMessage: 'picoseconds' }),
|
||||
value: 'ps'
|
||||
label: i18n.translate('tsvb.durationOptions.picosecondsLabel', { defaultMessage: 'Picoseconds' }),
|
||||
value: 'ps',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.nanosecondsLabel', { defaultMessage: 'nanoseconds' }),
|
||||
value: 'ns'
|
||||
label: i18n.translate('tsvb.durationOptions.nanosecondsLabel', { defaultMessage: 'Nanoseconds' }),
|
||||
value: 'ns',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('tsvb.durationOptions.microsecondsLabel', { defaultMessage: 'microseconds' }),
|
||||
value: 'us' },
|
||||
...durationOutputOptions
|
||||
label: i18n.translate('tsvb.durationOptions.microsecondsLabel', { defaultMessage: 'Microseconds' }),
|
||||
value: 'us',
|
||||
},
|
||||
...durationBaseOptions,
|
||||
];
|
||||
|
||||
export const inputFormats = {
|
||||
'ps': 'picoseconds',
|
||||
'ns': 'nanoseconds',
|
||||
'us': 'microseconds',
|
||||
'ms': 'milliseconds',
|
||||
's': 'seconds',
|
||||
'm': 'minutes',
|
||||
'h': 'hours',
|
||||
'd': 'days',
|
||||
'w': 'weeks',
|
||||
'M': 'months',
|
||||
'Y': 'years',
|
||||
};
|
||||
|
||||
export const outputFormats = {
|
||||
'humanize': 'humanize',
|
||||
'ms': 'asMilliseconds',
|
||||
's': 'asSeconds',
|
||||
'm': 'asMinutes',
|
||||
'h': 'asHours',
|
||||
'd': 'asDays',
|
||||
'w': 'asWeeks',
|
||||
'M': 'asMonths',
|
||||
'Y': 'asYears',
|
||||
};
|
||||
|
||||
export const isDuration = format => {
|
||||
const splittedFormat = format.split(',');
|
||||
const [input, output] = splittedFormat;
|
||||
|
||||
return Boolean(inputFormats[input] && outputFormats[output]) && splittedFormat.length === 3;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { isDuration } from './durations';
|
||||
|
||||
describe('durations', () => {
|
||||
describe('isDuration', () => {
|
||||
test('should return true for valid duration formats', () => {
|
||||
expect(isDuration('ps,m,2')).toBeTruthy();
|
||||
expect(isDuration('h,h,1')).toBeTruthy();
|
||||
expect(isDuration('m,d,')).toBeTruthy();
|
||||
expect(isDuration('s,Y,4')).toBeTruthy();
|
||||
expect(isDuration('ps,humanize,')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should return false for invalid duration formats', () => {
|
||||
expect(isDuration('ps,j,2')).toBeFalsy();
|
||||
expect(isDuration('i,h,1')).toBeFalsy();
|
||||
expect(isDuration('m,d')).toBeFalsy();
|
||||
expect(isDuration('s')).toBeFalsy();
|
||||
expect(isDuration('humanize,s,2')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -18,36 +18,29 @@
|
|||
*/
|
||||
|
||||
import handlebars from 'handlebars/dist/handlebars';
|
||||
import { durationInputOptions } from './durations';
|
||||
import { capitalize, isNumber } from 'lodash';
|
||||
import { isNumber } from 'lodash';
|
||||
import { fieldFormats } from 'ui/registry/field_formats';
|
||||
|
||||
const durationsLookup = durationInputOptions.reduce((acc, row) => {
|
||||
acc[row.value] = row.label;
|
||||
return acc;
|
||||
}, {});
|
||||
import { inputFormats, outputFormats, isDuration } from '../lib/durations';
|
||||
|
||||
export const tickFormatter = (format = '0,0.[00]', template, getConfig = null) => {
|
||||
if (!template) template = '{{value}}';
|
||||
const render = handlebars.compile(template, { knownHelpersOnly: true });
|
||||
const durationFormatTest = /[pnumshdwMY]+,[pnumshdwMY]+,\d+/;
|
||||
let formatter;
|
||||
if (durationFormatTest.test(format)) {
|
||||
|
||||
if (isDuration(format)) {
|
||||
const [from, to, decimals] = format.split(',');
|
||||
const inputFormat = durationsLookup[from];
|
||||
const outputFormat = `as${capitalize(durationsLookup[to])}`;
|
||||
const DurationFormat = fieldFormats.getType('duration');
|
||||
|
||||
formatter = new DurationFormat({
|
||||
inputFormat,
|
||||
outputFormat,
|
||||
outputPrecision: decimals
|
||||
inputFormat: inputFormats[from],
|
||||
outputFormat: outputFormats[to],
|
||||
outputPrecision: decimals,
|
||||
});
|
||||
} else {
|
||||
let FieldFormat = fieldFormats.getType(format);
|
||||
if (FieldFormat) {
|
||||
formatter = new FieldFormat(null, getConfig);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
FieldFormat = fieldFormats.getType('number');
|
||||
formatter = new FieldFormat({ pattern: format }, getConfig);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue