mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ML] Transform: Adds ability to create index pattern time field when creating transform (#68842) (#68931)
This commit is contained in:
parent
0d78bfcf22
commit
0d0ea7030c
5 changed files with 167 additions and 9 deletions
|
@ -59,11 +59,12 @@ interface Props {
|
|||
transformId: string;
|
||||
transformConfig: any;
|
||||
overrides: StepDetailsExposedState;
|
||||
timeFieldName?: string | undefined;
|
||||
onChange(s: StepDetailsExposedState): void;
|
||||
}
|
||||
|
||||
export const StepCreateForm: FC<Props> = React.memo(
|
||||
({ createIndexPattern, transformConfig, transformId, onChange, overrides }) => {
|
||||
({ createIndexPattern, transformConfig, transformId, onChange, overrides, timeFieldName }) => {
|
||||
const defaults = { ...getDefaultStepCreateState(), ...overrides };
|
||||
|
||||
const [redirectToTransformManagement, setRedirectToTransformManagement] = useState(false);
|
||||
|
@ -187,8 +188,8 @@ export const StepCreateForm: FC<Props> = React.memo(
|
|||
Object.assign(newIndexPattern, {
|
||||
id: '',
|
||||
title: indexPatternName,
|
||||
timeFieldName,
|
||||
});
|
||||
|
||||
const id = await newIndexPattern.create();
|
||||
|
||||
await indexPatterns.clearCache();
|
||||
|
|
|
@ -23,10 +23,17 @@ import { ToastNotificationText } from '../../../../components';
|
|||
import { useDocumentationLinks } from '../../../../hooks/use_documentation_links';
|
||||
import { SearchItems } from '../../../../hooks/use_search_items';
|
||||
import { useApi } from '../../../../hooks/use_api';
|
||||
|
||||
import { isTransformIdValid, TransformPivotConfig } from '../../../../common';
|
||||
import { StepDetailsTimeField } from './step_details_time_field';
|
||||
import {
|
||||
getPivotQuery,
|
||||
getPreviewRequestBody,
|
||||
isTransformIdValid,
|
||||
TransformPivotConfig,
|
||||
} from '../../../../common';
|
||||
import { EsIndexName, IndexPatternTitle } from './common';
|
||||
import { delayValidator } from '../../../../common/validators';
|
||||
import { StepDefineExposedState } from '../step_define/common';
|
||||
import { dictionaryToArray } from '../../../../../../common/types/common';
|
||||
|
||||
export interface StepDetailsExposedState {
|
||||
continuousModeDateField: string;
|
||||
|
@ -38,6 +45,7 @@ export interface StepDetailsExposedState {
|
|||
transformId: TransformId;
|
||||
transformDescription: string;
|
||||
valid: boolean;
|
||||
indexPatternDateField?: string | undefined;
|
||||
}
|
||||
|
||||
export function getDefaultStepDetailsState(): StepDetailsExposedState {
|
||||
|
@ -51,6 +59,7 @@ export function getDefaultStepDetailsState(): StepDetailsExposedState {
|
|||
destinationIndex: '',
|
||||
touched: false,
|
||||
valid: false,
|
||||
indexPatternDateField: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -74,10 +83,11 @@ interface Props {
|
|||
overrides?: StepDetailsExposedState;
|
||||
onChange(s: StepDetailsExposedState): void;
|
||||
searchItems: SearchItems;
|
||||
stepDefineState: StepDefineExposedState;
|
||||
}
|
||||
|
||||
export const StepDetailsForm: FC<Props> = React.memo(
|
||||
({ overrides = {}, onChange, searchItems }) => {
|
||||
({ overrides = {}, onChange, searchItems, stepDefineState }) => {
|
||||
const deps = useAppDependencies();
|
||||
const toastNotifications = useToastNotifications();
|
||||
const { esIndicesCreateIndex } = useDocumentationLinks();
|
||||
|
@ -93,8 +103,28 @@ export const StepDetailsForm: FC<Props> = React.memo(
|
|||
);
|
||||
const [transformIds, setTransformIds] = useState<TransformId[]>([]);
|
||||
const [indexNames, setIndexNames] = useState<EsIndexName[]>([]);
|
||||
|
||||
// Index pattern state
|
||||
const [indexPatternTitles, setIndexPatternTitles] = useState<IndexPatternTitle[]>([]);
|
||||
const [createIndexPattern, setCreateIndexPattern] = useState(defaults.createIndexPattern);
|
||||
const [previewDateColumns, setPreviewDateColumns] = useState<string[]>([]);
|
||||
const [indexPatternDateField, setIndexPatternDateField] = useState<string | undefined>();
|
||||
|
||||
const onTimeFieldChanged = React.useCallback(
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const value = e.target.value;
|
||||
// If the value is an empty string, it's not a valid selection
|
||||
if (value === '') {
|
||||
return;
|
||||
}
|
||||
// Find the time field based on the selected value
|
||||
// this is to account for undefined when user chooses not to use a date field
|
||||
const timeField = previewDateColumns.find((col) => col === value);
|
||||
|
||||
setIndexPatternDateField(timeField);
|
||||
},
|
||||
[setIndexPatternDateField, previewDateColumns]
|
||||
);
|
||||
|
||||
// Continuous mode state
|
||||
const [isContinuousModeEnabled, setContinuousModeEnabled] = useState(
|
||||
|
@ -107,6 +137,37 @@ export const StepDetailsForm: FC<Props> = React.memo(
|
|||
useEffect(() => {
|
||||
// use an IIFE to avoid returning a Promise to useEffect.
|
||||
(async function () {
|
||||
try {
|
||||
const { searchQuery, groupByList, aggList } = stepDefineState;
|
||||
const pivotAggsArr = dictionaryToArray(aggList);
|
||||
const pivotGroupByArr = dictionaryToArray(groupByList);
|
||||
const pivotQuery = getPivotQuery(searchQuery);
|
||||
const previewRequest = getPreviewRequestBody(
|
||||
searchItems.indexPattern.title,
|
||||
pivotQuery,
|
||||
pivotGroupByArr,
|
||||
pivotAggsArr
|
||||
);
|
||||
|
||||
const transformPreview = await api.getTransformsPreview(previewRequest);
|
||||
const properties = transformPreview.generated_dest_index.mappings.properties;
|
||||
const datetimeColumns: string[] = Object.keys(properties).filter(
|
||||
(col) => properties[col].type === 'date'
|
||||
);
|
||||
|
||||
setPreviewDateColumns(datetimeColumns);
|
||||
setIndexPatternDateField(datetimeColumns[0]);
|
||||
} catch (e) {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformPreview', {
|
||||
defaultMessage: 'An error occurred getting transform preview',
|
||||
}),
|
||||
text: toMountPoint(
|
||||
<ToastNotificationText overlays={deps.overlays} text={getErrorMessage(e)} />
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
setTransformIds(
|
||||
(await api.getTransforms()).transforms.map(
|
||||
|
@ -198,6 +259,7 @@ export const StepDetailsForm: FC<Props> = React.memo(
|
|||
destinationIndex,
|
||||
touched: true,
|
||||
valid,
|
||||
indexPatternDateField,
|
||||
});
|
||||
// custom comparison
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
|
@ -210,6 +272,7 @@ export const StepDetailsForm: FC<Props> = React.memo(
|
|||
transformDescription,
|
||||
destinationIndex,
|
||||
valid,
|
||||
indexPatternDateField,
|
||||
/* eslint-enable react-hooks/exhaustive-deps */
|
||||
]);
|
||||
|
||||
|
@ -318,6 +381,7 @@ export const StepDetailsForm: FC<Props> = React.memo(
|
|||
data-test-subj="transformDestinationIndexInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow
|
||||
isInvalid={createIndexPattern && indexPatternTitleExists}
|
||||
error={
|
||||
|
@ -339,6 +403,13 @@ export const StepDetailsForm: FC<Props> = React.memo(
|
|||
data-test-subj="transformCreateIndexPatternSwitch"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{createIndexPattern && !indexPatternTitleExists && previewDateColumns.length > 0 && (
|
||||
<StepDetailsTimeField
|
||||
previewDateColumns={previewDateColumns}
|
||||
indexPatternDateField={indexPatternDateField}
|
||||
onTimeFieldChanged={onTimeFieldChanged}
|
||||
/>
|
||||
)}
|
||||
<EuiFormRow
|
||||
helpText={
|
||||
isContinuousModeAvailable === false
|
||||
|
@ -378,7 +449,7 @@ export const StepDetailsForm: FC<Props> = React.memo(
|
|||
)}
|
||||
>
|
||||
<EuiSelect
|
||||
options={dateFieldNames.map((text) => ({ text }))}
|
||||
options={dateFieldNames.map((text: string) => ({ text }))}
|
||||
value={continuousModeDateField}
|
||||
onChange={(e) => setContinuousModeDateField(e.target.value)}
|
||||
data-test-subj="transformContinuousDateFieldSelect"
|
||||
|
|
|
@ -8,7 +8,7 @@ import React, { FC } from 'react';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiFieldText, EuiFormRow } from '@elastic/eui';
|
||||
import { EuiFieldText, EuiFormRow, EuiSelect } from '@elastic/eui';
|
||||
|
||||
import { StepDetailsExposedState } from './step_details_form';
|
||||
|
||||
|
@ -21,6 +21,7 @@ export const StepDetailsSummary: FC<StepDetailsExposedState> = React.memo(
|
|||
transformDescription,
|
||||
destinationIndex,
|
||||
touched,
|
||||
indexPatternDateField,
|
||||
}) => {
|
||||
if (touched === false) {
|
||||
return null;
|
||||
|
@ -56,6 +57,21 @@ export const StepDetailsSummary: FC<StepDetailsExposedState> = React.memo(
|
|||
>
|
||||
<EuiFieldText defaultValue={destinationIndex} disabled={true} />
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
helpText={i18n.translate(
|
||||
'xpack.transform.stepDetailsSummary.indexPatternTimeFilterLabel',
|
||||
{
|
||||
defaultMessage: 'Time filter',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiSelect
|
||||
options={[{ text: indexPatternDateField }]}
|
||||
value={indexPatternDateField}
|
||||
disabled={true}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
{isContinuousModeEnabled && (
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 React, { FC } from 'react';
|
||||
import { EuiFormRow, EuiSelect } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
interface Props {
|
||||
previewDateColumns: string[];
|
||||
indexPatternDateField: string | undefined;
|
||||
onTimeFieldChanged: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
}
|
||||
|
||||
export const StepDetailsTimeField: FC<Props> = ({
|
||||
previewDateColumns,
|
||||
indexPatternDateField,
|
||||
onTimeFieldChanged,
|
||||
}) => {
|
||||
const noTimeFieldLabel = i18n.translate(
|
||||
'xpack.transform.stepDetailsForm.noTimeFieldOptionLabel',
|
||||
{
|
||||
defaultMessage: "I don't want to use the Time Filter",
|
||||
}
|
||||
);
|
||||
|
||||
const noTimeFieldOption = {
|
||||
text: noTimeFieldLabel,
|
||||
value: undefined,
|
||||
};
|
||||
|
||||
const disabledDividerOption = {
|
||||
disabled: true,
|
||||
text: '───',
|
||||
value: '',
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.transform.stepDetailsForm.indexPatternTimeFilterLabel"
|
||||
defaultMessage="Time Filter field name"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.transform.stepDetailsForm.indexPatternTimeFilterHelpText"
|
||||
defaultMessage="The Time Filter will use this field to filter your data by time. You can choose not to have a time field, but you will not be able to narrow down your data by a time range."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSelect
|
||||
options={[
|
||||
...previewDateColumns.map((text) => ({ text })),
|
||||
disabledDividerOption,
|
||||
noTimeFieldOption,
|
||||
]}
|
||||
value={indexPatternDateField}
|
||||
onChange={onTimeFieldChanged}
|
||||
data-test-subj="transformIndexPatternDateFieldSelect"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
};
|
|
@ -91,6 +91,8 @@ export const CreateTransformWizardContext = createContext<{ indexPattern: IndexP
|
|||
});
|
||||
|
||||
export const Wizard: FC<WizardProps> = React.memo(({ cloneConfig, searchItems }) => {
|
||||
const { indexPattern } = searchItems;
|
||||
|
||||
// The current WIZARD_STEP
|
||||
const [currentStep, setCurrentStep] = useState(WIZARD_STEPS.DEFINE);
|
||||
|
||||
|
@ -110,6 +112,7 @@ export const Wizard: FC<WizardProps> = React.memo(({ cloneConfig, searchItems })
|
|||
onChange={setStepDetailsState}
|
||||
overrides={stepDetailsState}
|
||||
searchItems={searchItems}
|
||||
stepDefineState={stepDefineState}
|
||||
/>
|
||||
) : (
|
||||
<StepDetailsSummary {...stepDetailsState} />
|
||||
|
@ -146,8 +149,6 @@ export const Wizard: FC<WizardProps> = React.memo(({ cloneConfig, searchItems })
|
|||
}
|
||||
}, []);
|
||||
|
||||
const { indexPattern } = searchItems;
|
||||
|
||||
const transformConfig = getCreateRequestBody(
|
||||
indexPattern.title,
|
||||
stepDefineState,
|
||||
|
@ -162,6 +163,7 @@ export const Wizard: FC<WizardProps> = React.memo(({ cloneConfig, searchItems })
|
|||
transformConfig={transformConfig}
|
||||
onChange={setStepCreateState}
|
||||
overrides={stepCreateState}
|
||||
timeFieldName={stepDetailsState.indexPatternDateField}
|
||||
/>
|
||||
) : (
|
||||
<StepCreateSummary />
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue