[ML] Data Frames: Continuous mode support for wizard (#39804)

Adds a section to the job details section to configure continuous mode for a data frame transform.
- The switch to enable the mode is only available if the index pattern features at least one date field.
- If enabled, the user can pick a date field from a dropdown and configure the delay.
This commit is contained in:
Walter Rafelsberger 2019-06-28 11:00:13 +02:00 committed by GitHub
parent e783c84b8e
commit eb92d109b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 164 additions and 8 deletions

View file

@ -18,7 +18,12 @@ export interface DataFrameJob {
source: {
index: IndexPattern;
};
sync?: object;
sync?: {
time: {
field: string;
delay: string;
};
};
}
export interface DataFrameTransform extends DataFrameJob {
@ -31,3 +36,6 @@ export interface DataFrameTransform extends DataFrameJob {
export interface DataFrameTransformWithId extends DataFrameTransform {
id: string;
}
// Don't allow intervals of '0', don't allow floating intervals.
export const delayFormatRegex = /^[1-9][0-9]*(nanos|micros|ms|s|m|h|d)$/;

View file

@ -101,7 +101,10 @@ describe('Data Frame: Common', () => {
valid: true,
};
const jobDetailsState: JobDetailsExposedState = {
continuousModeDateField: 'the-continuous-mode-date-field',
continuousModeDelay: 'the-continuous-mode-delay',
createIndexPattern: false,
isContinuousModeEnabled: false,
jobId: 'the-job-id',
destinationIndex: 'the-destination-index',
touched: true,

View file

@ -148,6 +148,17 @@ export function getDataFrameRequest(
dest: {
index: jobDetailsState.destinationIndex,
},
// conditionally add continuous mode config
...(jobDetailsState.isContinuousModeEnabled
? {
sync: {
time: {
field: jobDetailsState.continuousModeDateField,
delay: jobDetailsState.continuousModeDelay,
},
},
}
: {}),
};
return request;

View file

@ -4,20 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { SFC, useContext, useEffect, useState } from 'react';
import React, { Fragment, SFC, useContext, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { toastNotifications } from 'ui/notify';
import { EuiSwitch, EuiFieldText, EuiForm, EuiFormRow } from '@elastic/eui';
import { EuiSwitch, EuiFieldText, EuiForm, EuiFormRow, EuiSelect } from '@elastic/eui';
import { ml } from '../../../services/ml_api_service';
import { DataFrameJobConfig, KibanaContext, isKibanaContext } from '../../common';
import { DataFrameJobConfig, delayFormatRegex, KibanaContext, isKibanaContext } from '../../common';
import { EsIndexName, IndexPatternTitle, JobId } from './common';
export interface JobDetailsExposedState {
continuousModeDateField: string;
continuousModeDelay: string;
createIndexPattern: boolean;
isContinuousModeEnabled: boolean;
jobId: JobId;
destinationIndex: EsIndexName;
touched: boolean;
@ -26,7 +29,10 @@ export interface JobDetailsExposedState {
export function getDefaultJobDetailsState(): JobDetailsExposedState {
return {
continuousModeDateField: '',
continuousModeDelay: '60s',
createIndexPattern: true,
isContinuousModeEnabled: false,
jobId: '',
destinationIndex: '',
touched: false,
@ -55,6 +61,21 @@ export const JobDetailsForm: SFC<Props> = React.memo(({ overrides = {}, onChange
const [indexPatternTitles, setIndexPatternTitles] = useState<IndexPatternTitle[]>([]);
const [createIndexPattern, setCreateIndexPattern] = useState(defaults.createIndexPattern);
// Continuous mode state
const [isContinuousModeEnabled, setContinuousModeEnabled] = useState(
defaults.isContinuousModeEnabled
);
const dateFieldNames = kibanaContext.currentIndexPattern.fields
.filter(f => f.type === 'date')
.map(f => f.name)
.sort();
const isContinuousModeAvailable = dateFieldNames.length > 0;
const [continuousModeDateField, setContinuousModeDateField] = useState(
isContinuousModeAvailable ? dateFieldNames[0] : ''
);
const [continuousModeDelay, setContinuousModeDelay] = useState(defaults.continuousModeDelay);
const isContinuousModeDelayValid = continuousModeDelay.match(delayFormatRegex) !== null;
// fetch existing job IDs and indices once for form validation
useEffect(() => {
// use an IIFE to avoid returning a Promise to useEffect.
@ -106,14 +127,32 @@ export const JobDetailsForm: SFC<Props> = React.memo(({ overrides = {}, onChange
destinationIndex !== '' &&
!jobIdExists &&
!indexNameExists &&
(!indexPatternTitleExists || !createIndexPattern);
(!indexPatternTitleExists || !createIndexPattern) &&
(!isContinuousModeAvailable || (isContinuousModeAvailable && isContinuousModeDelayValid));
// expose state to wizard
useEffect(
() => {
onChange({ createIndexPattern, jobId, destinationIndex, touched: true, valid });
onChange({
continuousModeDateField,
continuousModeDelay,
createIndexPattern,
isContinuousModeEnabled,
jobId,
destinationIndex,
touched: true,
valid,
});
},
[createIndexPattern, jobId, destinationIndex, valid]
[
continuousModeDateField,
continuousModeDelay,
createIndexPattern,
isContinuousModeEnabled,
jobId,
destinationIndex,
valid,
]
);
return (
@ -187,6 +226,82 @@ export const JobDetailsForm: SFC<Props> = React.memo(({ overrides = {}, onChange
onChange={() => setCreateIndexPattern(!createIndexPattern)}
/>
</EuiFormRow>
<EuiFormRow
helpText={
isContinuousModeAvailable === false
? i18n.translate('xpack.ml.dataframe.jobDetailsForm.continuousModeError', {
defaultMessage: 'Continuous mode is not available for indices without date fields.',
})
: ''
}
>
<EuiSwitch
name="mlDataFrameContinuousMode"
label={i18n.translate('xpack.ml.dataframe.jobCreateForm.continuousModeLabel', {
defaultMessage: 'Continuous mode',
})}
checked={isContinuousModeEnabled === true}
onChange={() => setContinuousModeEnabled(!isContinuousModeEnabled)}
disabled={isContinuousModeAvailable === false}
/>
</EuiFormRow>
{isContinuousModeEnabled && (
<Fragment>
<EuiFormRow
label={i18n.translate(
'xpack.ml.dataframe.jobDetailsForm.continuousModeDateFieldLabel',
{
defaultMessage: 'Date field',
}
)}
helpText={i18n.translate(
'xpack.ml.dataframe.jobDetailsForm.continuousModeDateFieldHelpText',
{
defaultMessage:
'Pick a date field for the time based continuous data frame transform that reflects ingestion time.',
}
)}
>
<EuiSelect
options={dateFieldNames.map(text => ({ text }))}
value={continuousModeDateField}
onChange={e => setContinuousModeDateField(e.target.value)}
/>
</EuiFormRow>
<EuiFormRow
label={i18n.translate('xpack.ml.dataframe.jobDetailsForm.continuousModeDelayLabel', {
defaultMessage: 'Delay',
})}
isInvalid={!isContinuousModeDelayValid}
error={
!isContinuousModeDelayValid && [
i18n.translate('xpack.ml.dataframe.jobDetailsForm.continuousModeDelayError', {
defaultMessage: 'Invalid delay format',
}),
]
}
helpText={i18n.translate(
'xpack.ml.dataframe.jobDetailsForm.continuousModeDelayHelpText',
{
defaultMessage: 'Time delay between current time and latest input data time.',
}
)}
>
<EuiFieldText
placeholder="delay"
value={continuousModeDelay}
onChange={e => setContinuousModeDelay(e.target.value)}
aria-label={i18n.translate(
'xpack.ml.dataframe.jobDetailsForm.continuousModeAriaLabel',
{
defaultMessage: 'Choose a delay.',
}
)}
isInvalid={!isContinuousModeDelayValid}
/>
</EuiFormRow>
</Fragment>
)}
</EuiForm>
);
});

View file

@ -13,7 +13,14 @@ import { EuiFieldText, EuiFormRow } from '@elastic/eui';
import { JobDetailsExposedState } from './job_details_form';
export const JobDetailsSummary: SFC<JobDetailsExposedState> = React.memo(
({ createIndexPattern, jobId, destinationIndex, touched }) => {
({
continuousModeDateField,
createIndexPattern,
isContinuousModeEnabled,
jobId,
destinationIndex,
touched,
}) => {
if (touched === false) {
return null;
}
@ -41,6 +48,18 @@ export const JobDetailsSummary: SFC<JobDetailsExposedState> = React.memo(
>
<EuiFieldText defaultValue={destinationIndex} disabled={true} />
</EuiFormRow>
{isContinuousModeEnabled && (
<EuiFormRow
label={i18n.translate(
'xpack.ml.dataframe.jobDetailsSummary.continuousModeDateFieldLabel',
{
defaultMessage: 'Continuous mode date field',
}
)}
>
<EuiFieldText defaultValue={continuousModeDateField} disabled={true} />
</EuiFormRow>
)}
</Fragment>
);
}