[ML] Add override for data which doesn't contain a time field (#147504)

Adds a checkbox to the overrides flyout to allow the user to tell the
find structure endpoint that the data does not contain a time field.


![image](https://user-images.githubusercontent.com/22172091/207582663-a510083e-be75-4ab8-bf7e-c91d72734c6f.png)

If the original analysis of the data does not find a time field, this
checkbox is unchecked by default.

Closes https://github.com/elastic/kibana/issues/146700

Also removes the jest snapshot test for the override flyout as it isn't
very useful.
This commit is contained in:
James Gowdy 2022-12-16 11:45:58 +00:00 committed by GitHub
parent 5047b05b7c
commit d14bf59730
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 91 additions and 434 deletions

View file

@ -19,6 +19,8 @@ export const MAX_FILE_SIZE_BYTES = 104857600; // 100MB
export const ABSOLUTE_MAX_FILE_SIZE_BYTES = 1073741274; // 1GB
export const FILE_SIZE_DISPLAY_FORMAT = '0,0.[0] b';
export const NO_TIME_FORMAT = 'null';
// Value to use in the Elasticsearch index mapping meta data to identify the
// index as having been created by the File Data Visualizer.
export const INDEX_META_DATA_CREATED_BY = 'file-data-visualizer';

View file

@ -7,7 +7,7 @@
import { isEqual } from 'lodash';
import type { AnalysisResult, InputOverrides } from '@kbn/file-upload-plugin/common';
import { MB, FILE_FORMATS } from '../../../../../common/constants';
import { MB, FILE_FORMATS, NO_TIME_FORMAT } from '../../../../../common/constants';
export const DEFAULT_LINES_TO_SAMPLE = 1000;
const UPLOAD_SIZE_MB = 5;
@ -117,10 +117,15 @@ export function createUrlOverrides(overrides: InputOverrides, originalSettings:
}
export function processResults({ results, overrides }: AnalysisResult) {
const timestampFormat =
results.java_timestamp_formats !== undefined && results.java_timestamp_formats.length
? results.java_timestamp_formats[0]
: undefined;
let timestampFormat;
if (
(overrides && overrides.timestamp_format === NO_TIME_FORMAT) ||
results.java_timestamp_formats === undefined
) {
timestampFormat = NO_TIME_FORMAT;
} else if (results.java_timestamp_formats.length) {
timestampFormat = results.java_timestamp_formats[0];
}
const linesToSample =
overrides !== undefined && overrides.lines_to_sample !== undefined

View file

@ -1,363 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Overrides render overrides 1`] = `
<EuiForm>
<EuiFormRow
describedByIds={Array []}
display="row"
error="Value must be greater than 3 and less than or equal to 1000000"
hasChildLabel={true}
hasEmptyLabelSpace={false}
isInvalid={false}
label={
<FormattedMessage
defaultMessage="Number of lines to sample"
id="xpack.dataVisualizer.file.editFlyout.overrides.linesToSampleFormRowLabel"
values={Object {}}
/>
}
labelType="label"
>
<EuiFieldNumber
isInvalid={false}
onChange={[Function]}
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="row"
hasChildLabel={true}
hasEmptyLabelSpace={false}
label={
<FormattedMessage
defaultMessage="Data format"
id="xpack.dataVisualizer.file.editFlyout.overrides.dataFormatFormRowLabel"
values={Object {}}
/>
}
labelType="label"
>
<EuiComboBox
async={false}
compressed={false}
fullWidth={false}
isClearable={false}
onChange={[Function]}
options={
Array [
Object {
"label": "delimited",
},
Object {
"label": "ndjson",
},
Object {
"label": "semi_structured_text",
},
]
}
selectedOptions={
Array [
Object {
"label": "",
},
]
}
singleSelection={
Object {
"asPlainText": true,
}
}
sortMatchesBy="none"
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="row"
hasChildLabel={true}
hasEmptyLabelSpace={false}
helpText={
<EuiText
size="xs"
>
<EuiLink
href="jest-metadata-mock-url"
target="_blank"
>
See more on accepted formats
</EuiLink>
</EuiText>
}
label={
<FormattedMessage
defaultMessage="Timestamp format"
id="xpack.dataVisualizer.file.editFlyout.overrides.timestampFormatFormRowLabel"
values={Object {}}
/>
}
labelType="label"
>
<EuiComboBox
async={false}
compressed={false}
fullWidth={false}
isClearable={false}
onChange={[Function]}
options={
Array [
Object {
"label": "custom",
},
Object {
"label": "dd/MMM/yyyy:HH:mm:ss XX",
},
Object {
"label": "EEE MMM dd HH:mm zzz yyyy",
},
Object {
"label": "EEE MMM dd HH:mm:ss yyyy",
},
Object {
"label": "EEE MMM dd HH:mm:ss zzz yyyy",
},
Object {
"label": "EEE MMM dd yyyy HH:mm zzz",
},
Object {
"label": "EEE MMM dd yyyy HH:mm:ss zzz",
},
Object {
"label": "EEE, dd MMM yyyy HH:mm XX",
},
Object {
"label": "EEE, dd MMM yyyy HH:mm XXX",
},
Object {
"label": "EEE, dd MMM yyyy HH:mm:ss XX",
},
Object {
"label": "EEE, dd MMM yyyy HH:mm:ss XXX",
},
Object {
"label": "ISO8601",
},
Object {
"label": "MMM dd HH:mm:ss",
},
Object {
"label": "MMM dd HH:mm:ss,SSS",
},
Object {
"label": "MMM dd HH:mm:ss,SSSSSS",
},
Object {
"label": "MMM dd HH:mm:ss,SSSSSSSSS",
},
Object {
"label": "MMM dd HH:mm:ss.SSS",
},
Object {
"label": "MMM dd HH:mm:ss.SSSSSS",
},
Object {
"label": "MMM dd HH:mm:ss.SSSSSSSSS",
},
Object {
"label": "MMM dd HH:mm:ss:SSS",
},
Object {
"label": "MMM dd HH:mm:ss:SSSSSS",
},
Object {
"label": "MMM dd HH:mm:ss:SSSSSSSSS",
},
Object {
"label": "MMM dd yyyy HH:mm:ss",
},
Object {
"label": "MMM dd, yyyy h:mm:ss a",
},
Object {
"label": "TAI64N",
},
Object {
"label": "UNIX",
},
Object {
"label": "UNIX_MS",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss,SSS",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss,SSSSSS",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss,SSSSSSSSS",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss.SSS",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss.SSSSSS",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss.SSSSSSSSS",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss:SSS",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss:SSSSSS",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss:SSSSSSSSS",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss,SSS XX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss,SSSSSS XX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss,SSSSSSSSS XX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss.SSS XX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss.SSSSSS XX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss.SSSSSSSSS XX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss:SSS XX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss:SSSSSS XX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss:SSSSSSSSS XX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss,SSSXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss,SSSSSSXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss,SSSSSSSSSXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss.SSSXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss.SSSSSSXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss.SSSSSSSSSXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss:SSSXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss:SSSSSSXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss:SSSSSSSSSXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss,SSSXXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss,SSSSSSXXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss,SSSSSSSSSXXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss.SSSXXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss.SSSSSSXXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss.SSSSSSSSSXXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss:SSSXXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss:SSSSSSXXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ss:SSSSSSSSSXXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ssXX",
},
Object {
"label": "yyyy-MM-dd HH:mm:ssXXX",
},
Object {
"label": "yyyyMMddHHmmss",
},
]
}
selectedOptions={
Array [
Object {
"label": "",
},
]
}
singleSelection={
Object {
"asPlainText": true,
}
}
sortMatchesBy="none"
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
display="row"
hasChildLabel={true}
hasEmptyLabelSpace={false}
label={
<FormattedMessage
defaultMessage="Time field"
id="xpack.dataVisualizer.file.editFlyout.overrides.timeFieldFormRowLabel"
values={Object {}}
/>
}
labelType="label"
>
<EuiComboBox
async={false}
compressed={false}
fullWidth={false}
isClearable={false}
onChange={[Function]}
options={Array []}
selectedOptions={
Array [
Object {
"label": "",
},
]
}
singleSelection={
Object {
"asPlainText": true,
}
}
sortMatchesBy="none"
/>
</EuiFormRow>
</EuiForm>
`;

View file

@ -23,7 +23,7 @@ import {
EuiTextArea,
} from '@elastic/eui';
import { FILE_FORMATS } from '../../../../../common/constants';
import { FILE_FORMATS, NO_TIME_FORMAT } from '../../../../../common/constants';
import {
getFormatOptions,
@ -129,6 +129,7 @@ class OverridesUI extends Component {
linesToSampleValid: true,
timestampFormatValid: true,
timestampFormatError: null,
containsTimeField: overrides.timestampFormat !== NO_TIME_FORMAT,
overrides,
...state,
};
@ -203,6 +204,17 @@ class OverridesUI extends Component {
this.setOverride({ timestampField });
};
onContainsTimeFieldChange = (e) => {
this.setState({ containsTimeField: e.target.checked });
if (e.target.checked === false) {
this.setOverride({ timestampFormat: NO_TIME_FORMAT });
} else {
this.setOverride({
timestampFormat: this.props.originalSettings.timestampFormat,
});
}
};
onDelimiterChange = ([opt]) => {
const delimiter = opt ? opt.label : '';
this.setOverride({ delimiter });
@ -272,6 +284,7 @@ class OverridesUI extends Component {
linesToSampleValid,
timestampFormatError,
timestampFormatValid,
containsTimeField,
overrides,
} = this.state;
@ -437,69 +450,77 @@ class OverridesUI extends Component {
</EuiFormRow>
</React.Fragment>
)}
<EuiFormRow
helpText={timestampFormatHelp}
label={
<FormattedMessage
id="xpack.dataVisualizer.file.editFlyout.overrides.timestampFormatFormRowLabel"
defaultMessage="Timestamp format"
/>
}
>
<EuiComboBox
options={timestampFormatOptions}
selectedOptions={selectedOption(timestampFormat)}
onChange={this.onTimestampFormatChange}
singleSelection={{ asPlainText: true }}
isClearable={false}
/>
</EuiFormRow>
{timestampFormat === CUSTOM_DROPDOWN_OPTION && (
<EuiFormRow
error={timestampFormatErrorsList}
isInvalid={timestampFormatValid === false}
<EuiFormRow>
<EuiCheckbox
id={'shouldTrimFields'}
label={
<FormattedMessage
id="xpack.dataVisualizer.file.editFlyout.overrides.customTimestampFormatFormRowLabel"
defaultMessage="Custom timestamp format"
id="xpack.dataVisualizer.file.editFlyout.overrides.containsTimeFieldLabel"
defaultMessage="Contains time field"
/>
}
>
<EuiFieldText
value={customTimestampFormat}
onChange={this.onCustomTimestampFormatChange}
isInvalid={timestampFormatValid === false}
/>
</EuiFormRow>
)}
<EuiFormRow
label={
<FormattedMessage
id="xpack.dataVisualizer.file.editFlyout.overrides.timeFieldFormRowLabel"
defaultMessage="Time field"
/>
}
>
<EuiComboBox
options={fieldOptions}
selectedOptions={selectedOption(timestampField)}
onChange={this.onTimestampFieldChange}
singleSelection={{ asPlainText: true }}
isClearable={false}
checked={containsTimeField}
onChange={this.onContainsTimeFieldChange}
/>
</EuiFormRow>
{/* <EuiFormRow
label="Charset"
>
<EuiComboBox
options={charsetOptions}
selectedOptions={selectedOption(charset)}
singleSelection={{ asPlainText: true }}
isClearable={false}
/>
</EuiFormRow> */}
{containsTimeField ? (
<>
<EuiFormRow
helpText={timestampFormatHelp}
label={
<FormattedMessage
id="xpack.dataVisualizer.file.editFlyout.overrides.timestampFormatFormRowLabel"
defaultMessage="Timestamp format"
/>
}
>
<EuiComboBox
options={timestampFormatOptions}
selectedOptions={selectedOption(timestampFormat)}
onChange={this.onTimestampFormatChange}
singleSelection={{ asPlainText: true }}
isClearable={false}
/>
</EuiFormRow>
{timestampFormat === CUSTOM_DROPDOWN_OPTION && (
<EuiFormRow
error={timestampFormatErrorsList}
isInvalid={timestampFormatValid === false}
label={
<FormattedMessage
id="xpack.dataVisualizer.file.editFlyout.overrides.customTimestampFormatFormRowLabel"
defaultMessage="Custom timestamp format"
/>
}
>
<EuiFieldText
value={customTimestampFormat}
onChange={this.onCustomTimestampFormatChange}
isInvalid={timestampFormatValid === false}
/>
</EuiFormRow>
)}
<EuiFormRow
label={
<FormattedMessage
id="xpack.dataVisualizer.file.editFlyout.overrides.timeFieldFormRowLabel"
defaultMessage="Time field"
/>
}
>
<EuiComboBox
options={fieldOptions}
selectedOptions={selectedOption(timestampField)}
onChange={this.onTimestampFieldChange}
singleSelection={{ asPlainText: true }}
isClearable={false}
/>
</EuiFormRow>
</>
) : null}
{format === FILE_FORMATS.DELIMITED && originalColumnNames.length > 0 && (
<React.Fragment>
<EuiSpacer />

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import React from 'react';
import { FILE_FORMATS } from '../../../../../common/constants';
@ -40,14 +40,6 @@ function getProps() {
}
describe('Overrides', () => {
test('render overrides', () => {
const props = getProps();
const component = shallowWithIntl(<Overrides {...props} />);
expect(component).toMatchSnapshot();
});
test('render overrides and trigger a state change', () => {
const FORMAT_1 = FILE_FORMATS.DELIMITED;
const FORMAT_2 = FILE_FORMATS.NDJSON;