mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[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:
parent
e783c84b8e
commit
eb92d109b2
5 changed files with 164 additions and 8 deletions
|
@ -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)$/;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue