[ML] File datavisualizer - custom timestamp override functionality (#38727)
* show custom input option for timestamp format * show validation errors if custom timestamp format invalid * Update error messages. Remove unnecessary comments * use custom constant for delimiter override * fix i18n errors
This commit is contained in:
parent
25802019ae
commit
4260d82c97
|
@ -90,6 +90,9 @@ exports[`Overrides render overrides 1`] = `
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
options={
|
options={
|
||||||
Array [
|
Array [
|
||||||
|
Object {
|
||||||
|
"label": "custom",
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"label": "dd/MMM/yyyy:HH:mm:ss XX",
|
"label": "dd/MMM/yyyy:HH:mm:ss XX",
|
||||||
},
|
},
|
||||||
|
@ -279,87 +282,6 @@ exports[`Overrides render overrides 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"label": "yyyy-MM-dd HH:mm:ss:SSSSSSSSSXXX",
|
"label": "yyyy-MM-dd HH:mm:ss:SSSSSSSSSXXX",
|
||||||
},
|
},
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss,SSS",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss,SSSSSS",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss,SSSSSSSSS",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss.SSS",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss.SSSSSS",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss:SSS",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss:SSSSSS",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss:SSSSSSSSS",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss,SSSXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss,SSSSSSXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss,SSSSSSSSSXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss.SSSXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss:SSSXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss:SSSSSSXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss:SSSSSSSSSXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss,SSSXXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss,SSSSSSXXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss,SSSSSSSSSXXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss:SSSXXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss:SSSSSSXXX",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"label": "yyyy-MM-dd'T'HH:mm:ss:SSSSSSSSSXXX",
|
|
||||||
},
|
|
||||||
Object {
|
Object {
|
||||||
"label": "yyyy-MM-dd HH:mm:ssXX",
|
"label": "yyyy-MM-dd HH:mm:ssXX",
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,10 @@ export const FORMAT_OPTIONS = [
|
||||||
// 'xml',
|
// 'xml',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const CUSTOM_DROPDOWN_OPTION = 'custom';
|
||||||
|
|
||||||
export const TIMESTAMP_OPTIONS = [
|
export const TIMESTAMP_OPTIONS = [
|
||||||
|
CUSTOM_DROPDOWN_OPTION,
|
||||||
'dd/MMM/yyyy:HH:mm:ss XX',
|
'dd/MMM/yyyy:HH:mm:ss XX',
|
||||||
'EEE MMM dd HH:mm zzz yyyy',
|
'EEE MMM dd HH:mm zzz yyyy',
|
||||||
'EEE MMM dd HH:mm:ss yyyy',
|
'EEE MMM dd HH:mm:ss yyyy',
|
||||||
|
@ -96,36 +99,6 @@ export const TIMESTAMP_OPTIONS = [
|
||||||
'yyyy-MM-dd HH:mm:ss:SSSSSSXXX',
|
'yyyy-MM-dd HH:mm:ss:SSSSSSXXX',
|
||||||
'yyyy-MM-dd HH:mm:ss:SSSSSSSSSXXX',
|
'yyyy-MM-dd HH:mm:ss:SSSSSSSSSXXX',
|
||||||
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss,SSS`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss,SSSSSS`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss,SSSSSSSSS`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss.SSS`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss.SSSSSS`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss:SSS`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss:SSSSSS`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss:SSSSSSSSS`,
|
|
||||||
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss,SSSXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss,SSSSSSXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss,SSSSSSSSSXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss.SSSXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss.SSSSSSXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss:SSSXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss:SSSSSSXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss:SSSSSSSSSXX`,
|
|
||||||
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss,SSSXXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss,SSSSSSXXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss,SSSSSSSSSXXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss.SSSXXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss:SSSXXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss:SSSSSSXXX`,
|
|
||||||
`yyyy-MM-dd'T'HH:mm:ss:SSSSSSSSSXXX`,
|
|
||||||
|
|
||||||
'yyyy-MM-dd HH:mm:ssXX',
|
'yyyy-MM-dd HH:mm:ssXX',
|
||||||
'yyyy-MM-dd HH:mm:ssXXX',
|
'yyyy-MM-dd HH:mm:ssXXX',
|
||||||
'yyyyMMddHHmmss',
|
'yyyyMMddHHmmss',
|
||||||
|
@ -137,7 +110,7 @@ export const DELIMITER_OPTIONS = [
|
||||||
'semicolon',
|
'semicolon',
|
||||||
'pipe',
|
'pipe',
|
||||||
'space',
|
'space',
|
||||||
'other',
|
CUSTOM_DROPDOWN_OPTION,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const QUOTE_OPTIONS = [
|
export const QUOTE_OPTIONS = [
|
||||||
|
|
|
@ -30,6 +30,9 @@ import {
|
||||||
getQuoteOptions,
|
getQuoteOptions,
|
||||||
// getCharsetOptions,
|
// getCharsetOptions,
|
||||||
} from './options';
|
} from './options';
|
||||||
|
import { isTimestampFormatValid } from './overrides_validation';
|
||||||
|
|
||||||
|
import { TIMESTAMP_OPTIONS, CUSTOM_DROPDOWN_OPTION } from './options/option_lists';
|
||||||
|
|
||||||
const formatOptions = getFormatOptions();
|
const formatOptions = getFormatOptions();
|
||||||
const timestampFormatOptions = getTimestampFormatOptions();
|
const timestampFormatOptions = getTimestampFormatOptions();
|
||||||
|
@ -55,6 +58,11 @@ export class Overrides extends Component {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
customTimestampFormatErrors = i18n.translate('xpack.ml.fileDatavisualizer.editFlyout.overrides.customTimestampFormatErrorMessage', {
|
||||||
|
defaultMessage: `Timestamp format must be a combination of these Java date/time formats:
|
||||||
|
yy, yyyy, M, MM, MMM, MMMM, d, dd, EEE, EEEE, H, HH, h, mm, ss, S through SSSSSSSSS, a, XX, XXX, zzz`
|
||||||
|
});
|
||||||
|
|
||||||
static getDerivedStateFromProps(props, state) {
|
static getDerivedStateFromProps(props, state) {
|
||||||
const { originalSettings } = props;
|
const { originalSettings } = props;
|
||||||
|
|
||||||
|
@ -99,16 +107,31 @@ export class Overrides extends Component {
|
||||||
return {
|
return {
|
||||||
originalColumnNames,
|
originalColumnNames,
|
||||||
customDelimiter: (customD === undefined) ? '' : customD,
|
customDelimiter: (customD === undefined) ? '' : customD,
|
||||||
|
customTimestampFormat: '',
|
||||||
linesToSampleValid: true,
|
linesToSampleValid: true,
|
||||||
|
timestampFormatValid: true,
|
||||||
|
timestampFormatError: null,
|
||||||
overrides,
|
overrides,
|
||||||
...state,
|
...state,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
const originalTimestampFormat = (this.props && this.props.originalSettings && this.props.originalSettings.timestampFormat);
|
||||||
|
|
||||||
if (typeof this.props.setApplyOverrides === 'function') {
|
if (typeof this.props.setApplyOverrides === 'function') {
|
||||||
this.props.setApplyOverrides(this.applyOverrides);
|
this.props.setApplyOverrides(this.applyOverrides);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (originalTimestampFormat !== undefined) {
|
||||||
|
const optionExists = (TIMESTAMP_OPTIONS.some(option => option === originalTimestampFormat));
|
||||||
|
if (optionExists === false) {
|
||||||
|
// Incoming format does not exist in dropdown. Display custom input with incoming format as default value.
|
||||||
|
const overrides = { ...this.state.overrides };
|
||||||
|
overrides.timestampFormat = CUSTOM_DROPDOWN_OPTION;
|
||||||
|
this.setState({ customTimestampFormat: originalTimestampFormat, overrides });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -120,6 +143,9 @@ export class Overrides extends Component {
|
||||||
applyOverrides = () => {
|
applyOverrides = () => {
|
||||||
const overrides = { ...this.state.overrides };
|
const overrides = { ...this.state.overrides };
|
||||||
overrides.delimiter = convertDelimiterBack(overrides.delimiter, this.state.customDelimiter);
|
overrides.delimiter = convertDelimiterBack(overrides.delimiter, this.state.customDelimiter);
|
||||||
|
if (overrides.timestampFormat === CUSTOM_DROPDOWN_OPTION && this.state.customTimestampFormat !== '') {
|
||||||
|
overrides.timestampFormat = this.state.customTimestampFormat;
|
||||||
|
}
|
||||||
|
|
||||||
this.props.setOverrides(overrides);
|
this.props.setOverrides(overrides);
|
||||||
}
|
}
|
||||||
|
@ -137,6 +163,17 @@ export class Overrides extends Component {
|
||||||
onTimestampFormatChange = ([opt]) => {
|
onTimestampFormatChange = ([opt]) => {
|
||||||
const timestampFormat = opt ? opt.label : '';
|
const timestampFormat = opt ? opt.label : '';
|
||||||
this.setOverride({ timestampFormat });
|
this.setOverride({ timestampFormat });
|
||||||
|
if (opt !== CUSTOM_DROPDOWN_OPTION) {
|
||||||
|
this.props.setOverridesValid(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCustomTimestampFormatChange = (e) => {
|
||||||
|
this.setState({ customTimestampFormat: e.target.value });
|
||||||
|
// check whether the value is valid and set that to state.
|
||||||
|
const { isValid, errorMessage } = isTimestampFormatValid(e.target.value);
|
||||||
|
this.setState({ timestampFormatValid: isValid, timestampFormatError: errorMessage });
|
||||||
|
this.props.setOverridesValid(isValid);
|
||||||
}
|
}
|
||||||
|
|
||||||
onTimestampFieldChange = ([opt]) => {
|
onTimestampFieldChange = ([opt]) => {
|
||||||
|
@ -199,8 +236,11 @@ export class Overrides extends Component {
|
||||||
const { fields } = this.props;
|
const { fields } = this.props;
|
||||||
const {
|
const {
|
||||||
customDelimiter,
|
customDelimiter,
|
||||||
|
customTimestampFormat,
|
||||||
originalColumnNames,
|
originalColumnNames,
|
||||||
linesToSampleValid,
|
linesToSampleValid,
|
||||||
|
timestampFormatError,
|
||||||
|
timestampFormatValid,
|
||||||
overrides,
|
overrides,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
@ -219,6 +259,7 @@ export class Overrides extends Component {
|
||||||
} = overrides;
|
} = overrides;
|
||||||
|
|
||||||
const fieldOptions = getSortedFields(fields);
|
const fieldOptions = getSortedFields(fields);
|
||||||
|
const timestampFormatErrorsList = [this.customTimestampFormatErrors, timestampFormatError];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
|
@ -276,7 +317,7 @@ export class Overrides extends Component {
|
||||||
/>
|
/>
|
||||||
</EuiFormRow>
|
</EuiFormRow>
|
||||||
{
|
{
|
||||||
(delimiter === 'other') &&
|
(delimiter === CUSTOM_DROPDOWN_OPTION) &&
|
||||||
<EuiFormRow
|
<EuiFormRow
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -375,6 +416,25 @@ export class Overrides extends Component {
|
||||||
isClearable={false}
|
isClearable={false}
|
||||||
/>
|
/>
|
||||||
</EuiFormRow>
|
</EuiFormRow>
|
||||||
|
{
|
||||||
|
(timestampFormat === CUSTOM_DROPDOWN_OPTION) &&
|
||||||
|
<EuiFormRow
|
||||||
|
error={timestampFormatErrorsList}
|
||||||
|
isInvalid={(timestampFormatValid === false)}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.ml.fileDatavisualizer.editFlyout.overrides.customTimestampFormatFormRowLabel"
|
||||||
|
defaultMessage="Custom timestamp format"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<EuiFieldText
|
||||||
|
value={customTimestampFormat}
|
||||||
|
onChange={this.onCustomTimestampFormatChange}
|
||||||
|
isInvalid={(timestampFormatValid === false)}
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
}
|
||||||
|
|
||||||
<EuiFormRow
|
<EuiFormRow
|
||||||
label={
|
label={
|
||||||
|
@ -476,7 +536,7 @@ function convertDelimiter(d) {
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
delimiter: 'other',
|
delimiter: CUSTOM_DROPDOWN_OPTION,
|
||||||
customDelimiter: d,
|
customDelimiter: d,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -495,7 +555,7 @@ function convertDelimiterBack(delimiter, customDelimiter) {
|
||||||
return '|';
|
return '|';
|
||||||
case 'space':
|
case 'space':
|
||||||
return ' ';
|
return ' ';
|
||||||
case 'other':
|
case CUSTOM_DROPDOWN_OPTION:
|
||||||
return customDelimiter;
|
return customDelimiter;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* 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 { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
|
const FRACTIONAL_SECOND_SEPARATORS = ':.,';
|
||||||
|
|
||||||
|
const VALID_LETTER_GROUPS = {
|
||||||
|
'yyyy': true,
|
||||||
|
'yy': true,
|
||||||
|
'M': true,
|
||||||
|
'MM': true,
|
||||||
|
'MMM': true,
|
||||||
|
'MMMM': true,
|
||||||
|
'd': true,
|
||||||
|
'dd': true,
|
||||||
|
'EEE': true,
|
||||||
|
'EEEE': true,
|
||||||
|
'H': true,
|
||||||
|
'HH': true,
|
||||||
|
'h': true,
|
||||||
|
'mm': true,
|
||||||
|
'ss': true,
|
||||||
|
'a': true,
|
||||||
|
'XX': true,
|
||||||
|
'XXX': true,
|
||||||
|
'zzz': true,
|
||||||
|
};
|
||||||
|
|
||||||
|
function isLetter(str) {
|
||||||
|
return str.length === 1 && str.match(/[a-z]/i);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTimestampFormatValid(timestampFormat) {
|
||||||
|
const result = { isValid: true, errorMessage: null };
|
||||||
|
|
||||||
|
if (timestampFormat.indexOf('?') >= 0) {
|
||||||
|
result.isValid = false;
|
||||||
|
result.errorMessage = i18n.translate('xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampQuestionMarkValidationErrorMessage', {
|
||||||
|
defaultMessage: 'Timestamp format {timestampFormat} not supported because it contains a question mark character ({fieldPlaceholder})',
|
||||||
|
values: {
|
||||||
|
timestampFormat,
|
||||||
|
fieldPlaceholder: '?',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let notQuoted = true;
|
||||||
|
let prevChar = null;
|
||||||
|
let prevLetterGroup = null;
|
||||||
|
let pos = 0;
|
||||||
|
|
||||||
|
while (pos < timestampFormat.length) {
|
||||||
|
const curChar = timestampFormat.charAt(pos);
|
||||||
|
|
||||||
|
if (curChar === '\'') {
|
||||||
|
notQuoted = !notQuoted;
|
||||||
|
} else if (notQuoted && isLetter(curChar)) {
|
||||||
|
const startPos = pos;
|
||||||
|
let endPos = startPos + 1;
|
||||||
|
while (endPos < timestampFormat.length && timestampFormat.charAt(endPos) === curChar) {
|
||||||
|
++endPos;
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
const letterGroup = timestampFormat.substring(startPos, endPos);
|
||||||
|
|
||||||
|
if (VALID_LETTER_GROUPS[letterGroup] !== true) {
|
||||||
|
const length = letterGroup.length;
|
||||||
|
// Special case of fractional seconds
|
||||||
|
if (curChar !== 'S' || FRACTIONAL_SECOND_SEPARATORS.indexOf(prevChar) === -1 ||
|
||||||
|
!('ss' === prevLetterGroup) || endPos - startPos > 9) {
|
||||||
|
result.isValid = false;
|
||||||
|
|
||||||
|
result.errorMessage = i18n.translate('xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampLetterValidationErrorMessage', {
|
||||||
|
defaultMessage: 'Letter { length, plural, one { {lg} } other { group {lg} } } in {format} is not supported',
|
||||||
|
values: {
|
||||||
|
length,
|
||||||
|
lg: letterGroup,
|
||||||
|
format: timestampFormat
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (curChar === 'S') {
|
||||||
|
// disable exceeds maximum line length error so i18n check passes
|
||||||
|
result.errorMessage = i18n.translate(
|
||||||
|
'xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampLetterSValidationErrorMessage',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Letter { length, plural, one { {lg} } other { group {lg} } } in {format} is not supported because it is not preceded by ss and a separator from {sep}', // eslint-disable-line
|
||||||
|
values: {
|
||||||
|
length,
|
||||||
|
lg: letterGroup,
|
||||||
|
sep: FRACTIONAL_SECOND_SEPARATORS,
|
||||||
|
format: timestampFormat
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevLetterGroup = letterGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevChar = curChar;
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevLetterGroup == null) {
|
||||||
|
result.isValid = false;
|
||||||
|
result.errorMessage = i18n.translate('xpack.ml.fileDatavisualizer.editFlyout.overrides.timestampEmptyValidationErrorMessage', {
|
||||||
|
defaultMessage: 'No time format letter groups in timestamp format {timestampFormat}',
|
||||||
|
values: {
|
||||||
|
timestampFormat,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
Loading…
Reference in a new issue