mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* 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
b1cd593d82
commit
d41c1adc70
4 changed files with 194 additions and 115 deletions
|
@ -90,6 +90,9 @@ exports[`Overrides render overrides 1`] = `
|
|||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "custom",
|
||||
},
|
||||
Object {
|
||||
"label": "dd/MMM/yyyy:HH:mm:ss XX",
|
||||
},
|
||||
|
@ -279,87 +282,6 @@ exports[`Overrides render overrides 1`] = `
|
|||
Object {
|
||||
"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 {
|
||||
"label": "yyyy-MM-dd HH:mm:ssXX",
|
||||
},
|
||||
|
|
|
@ -12,7 +12,10 @@ export const FORMAT_OPTIONS = [
|
|||
// 'xml',
|
||||
];
|
||||
|
||||
export const CUSTOM_DROPDOWN_OPTION = 'custom';
|
||||
|
||||
export const TIMESTAMP_OPTIONS = [
|
||||
CUSTOM_DROPDOWN_OPTION,
|
||||
'dd/MMM/yyyy:HH:mm:ss XX',
|
||||
'EEE MMM dd HH:mm zzz 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: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:ssXXX',
|
||||
'yyyyMMddHHmmss',
|
||||
|
@ -137,7 +110,7 @@ export const DELIMITER_OPTIONS = [
|
|||
'semicolon',
|
||||
'pipe',
|
||||
'space',
|
||||
'other',
|
||||
CUSTOM_DROPDOWN_OPTION,
|
||||
];
|
||||
|
||||
export const QUOTE_OPTIONS = [
|
||||
|
|
|
@ -30,6 +30,9 @@ import {
|
|||
getQuoteOptions,
|
||||
// getCharsetOptions,
|
||||
} from './options';
|
||||
import { isTimestampFormatValid } from './overrides_validation';
|
||||
|
||||
import { TIMESTAMP_OPTIONS, CUSTOM_DROPDOWN_OPTION } from './options/option_lists';
|
||||
|
||||
const formatOptions = getFormatOptions();
|
||||
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) {
|
||||
const { originalSettings } = props;
|
||||
|
||||
|
@ -99,16 +107,31 @@ export class Overrides extends Component {
|
|||
return {
|
||||
originalColumnNames,
|
||||
customDelimiter: (customD === undefined) ? '' : customD,
|
||||
customTimestampFormat: '',
|
||||
linesToSampleValid: true,
|
||||
timestampFormatValid: true,
|
||||
timestampFormatError: null,
|
||||
overrides,
|
||||
...state,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const originalTimestampFormat = (this.props && this.props.originalSettings && this.props.originalSettings.timestampFormat);
|
||||
|
||||
if (typeof this.props.setApplyOverrides === 'function') {
|
||||
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() {
|
||||
|
@ -120,6 +143,9 @@ export class Overrides extends Component {
|
|||
applyOverrides = () => {
|
||||
const overrides = { ...this.state.overrides };
|
||||
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);
|
||||
}
|
||||
|
@ -137,6 +163,17 @@ export class Overrides extends Component {
|
|||
onTimestampFormatChange = ([opt]) => {
|
||||
const timestampFormat = opt ? opt.label : '';
|
||||
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]) => {
|
||||
|
@ -199,8 +236,11 @@ export class Overrides extends Component {
|
|||
const { fields } = this.props;
|
||||
const {
|
||||
customDelimiter,
|
||||
customTimestampFormat,
|
||||
originalColumnNames,
|
||||
linesToSampleValid,
|
||||
timestampFormatError,
|
||||
timestampFormatValid,
|
||||
overrides,
|
||||
} = this.state;
|
||||
|
||||
|
@ -219,6 +259,7 @@ export class Overrides extends Component {
|
|||
} = overrides;
|
||||
|
||||
const fieldOptions = getSortedFields(fields);
|
||||
const timestampFormatErrorsList = [this.customTimestampFormatErrors, timestampFormatError];
|
||||
|
||||
return (
|
||||
|
||||
|
@ -276,7 +317,7 @@ export class Overrides extends Component {
|
|||
/>
|
||||
</EuiFormRow>
|
||||
{
|
||||
(delimiter === 'other') &&
|
||||
(delimiter === CUSTOM_DROPDOWN_OPTION) &&
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
|
@ -375,6 +416,25 @@ export class Overrides extends Component {
|
|||
isClearable={false}
|
||||
/>
|
||||
</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
|
||||
label={
|
||||
|
@ -476,7 +536,7 @@ function convertDelimiter(d) {
|
|||
|
||||
default:
|
||||
return {
|
||||
delimiter: 'other',
|
||||
delimiter: CUSTOM_DROPDOWN_OPTION,
|
||||
customDelimiter: d,
|
||||
};
|
||||
}
|
||||
|
@ -495,7 +555,7 @@ function convertDelimiterBack(delimiter, customDelimiter) {
|
|||
return '|';
|
||||
case 'space':
|
||||
return ' ';
|
||||
case 'other':
|
||||
case CUSTOM_DROPDOWN_OPTION:
|
||||
return customDelimiter;
|
||||
|
||||
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…
Add table
Add a link
Reference in a new issue