diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/index.ts index 782b4bc938bae7..db9b85ce6feefa 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/index.ts +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { useEditAction } from './use_edit_action'; +export { useEditAction, type EditAction } from './use_edit_action'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx index e4927fff97070d..19d60b8b643815 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.tsx @@ -17,6 +17,7 @@ import { useSearchItems } from '../../../../hooks/use_search_items'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { TransformConfigUnion } from '../../../../../../common/types/transform'; +export type EditAction = ReturnType; export const useEditAction = (forceDisable: boolean, transformNodes: number) => { const { canCreateTransform } = useContext(AuthorizationContext).capabilities; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_api_error_callout.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_api_error_callout.tsx new file mode 100644 index 00000000000000..5925dd9e66c859 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_api_error_callout.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { type FC } from 'react'; + +import { EuiCallOut, EuiSpacer } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { useEditTransformFlyout } from './use_edit_transform_flyout'; + +export const EditTransformApiErrorCallout: FC = () => { + const apiErrorMessage = useEditTransformFlyout('apiErrorMessage'); + + return ( + <> + {apiErrorMessage !== undefined && ( + <> + + +

{apiErrorMessage}

+
+ + )} + + ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx index e6648c5214dac9..c3ff7198a44fa3 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx @@ -5,154 +5,87 @@ * 2.0. */ -import React, { useState, FC } from 'react'; +import React, { type FC } from 'react'; import { i18n } from '@kbn/i18n'; import { - EuiButton, EuiButtonEmpty, - EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiFlyout, EuiFlyoutBody, EuiFlyoutFooter, EuiFlyoutHeader, - EuiSpacer, EuiTitle, } from '@elastic/eui'; -import { isPostTransformsUpdateResponseSchema } from '../../../../../../common/api_schemas/type_guards'; -import { TransformConfigUnion } from '../../../../../../common/types/transform'; - -import { getErrorMessage } from '../../../../../../common/utils/errors'; +import { isManagedTransform } from '../../../../common/managed_transforms_utils'; -import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../../../../common'; -import { useToastNotifications } from '../../../../app_dependencies'; -import { useApi } from '../../../../hooks/use_api'; +import { ManagedTransformsWarningCallout } from '../managed_transforms_callout/managed_transforms_callout'; +import type { EditAction } from '../action_edit'; +import { EditTransformApiErrorCallout } from './edit_transform_api_error_callout'; import { EditTransformFlyoutCallout } from './edit_transform_flyout_callout'; import { EditTransformFlyoutForm } from './edit_transform_flyout_form'; -import { - applyFormStateToTransformConfig, - useEditTransformFlyout, -} from './use_edit_transform_flyout'; -import { ManagedTransformsWarningCallout } from '../managed_transforms_callout/managed_transforms_callout'; -import { isManagedTransform } from '../../../../common/managed_transforms_utils'; +import { EditTransformFlyoutProvider } from './use_edit_transform_flyout'; +import { EditTransformUpdateButton } from './edit_transform_update_button'; -interface EditTransformFlyoutProps { - closeFlyout: () => void; - config: TransformConfigUnion; - dataViewId?: string; -} - -export const EditTransformFlyout: FC = ({ +export const EditTransformFlyout: FC = ({ closeFlyout, config, dataViewId, -}) => { - const api = useApi(); - const toastNotifications = useToastNotifications(); - - const [state, dispatch] = useEditTransformFlyout(config); - const [errorMessage, setErrorMessage] = useState(undefined); - - async function submitFormHandler() { - setErrorMessage(undefined); - const requestConfig = applyFormStateToTransformConfig(config, state); - const transformId = config.id; - - const resp = await api.updateTransform(transformId, requestConfig); - - if (!isPostTransformsUpdateResponseSchema(resp)) { - setErrorMessage(getErrorMessage(resp)); - return; - } - - toastNotifications.addSuccess( - i18n.translate('xpack.transform.transformList.editTransformSuccessMessage', { - defaultMessage: 'Transform {transformId} updated.', - values: { transformId }, - }) - ); - closeFlyout(); - refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); - } - - const isUpdateButtonDisabled = !state.isFormValid || !state.isFormTouched; - - return ( - - - -

- {i18n.translate('xpack.transform.transformList.editFlyoutTitle', { - defaultMessage: 'Edit {transformId}', - values: { - transformId: config.id, - }, - })} -

-
-
- {isManagedTransform({ config }) ? ( - - ) : null} - }> - - {errorMessage !== undefined && ( - <> - - -

{errorMessage}

-
- - )} -
- - - - - {i18n.translate('xpack.transform.transformList.editFlyoutCancelButtonText', { - defaultMessage: 'Cancel', - })} - - - - - {i18n.translate('xpack.transform.transformList.editFlyoutUpdateButtonText', { - defaultMessage: 'Update', + isFlyoutVisible, +}) => + config && isFlyoutVisible ? ( + + + + +

+ {i18n.translate('xpack.transform.transformList.editFlyoutTitle', { + defaultMessage: 'Edit {transformId}', + values: { + transformId: config.id, + }, })} - - - - - - ); -}; +

+
+
+ {isManagedTransform({ config }) ? ( + + ) : null} + }> + + + + + + + + {i18n.translate('xpack.transform.transformList.editFlyoutCancelButtonText', { + defaultMessage: 'Cancel', + })} + + + + + + + +
+
+ ) : null; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx index 1d35b6d64e7a14..eb4e9cd792b3a5 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx @@ -5,384 +5,128 @@ * 2.0. */ -import React, { FC, useEffect, useMemo, useState } from 'react'; +import React, { type FC } from 'react'; -import { - EuiAccordion, - EuiComboBox, - EuiForm, - EuiFormRow, - EuiSelect, - EuiSpacer, - EuiSwitch, -} from '@elastic/eui'; +import { EuiAccordion, EuiForm, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { KBN_FIELD_TYPES } from '@kbn/field-types'; -import { isEsIngestPipelines } from '../../../../../../common/api_schemas/type_guards'; import { EditTransformFlyoutFormTextInput } from './edit_transform_flyout_form_text_input'; -import { UseEditTransformFlyoutReturnType } from './use_edit_transform_flyout'; -import { useAppDependencies } from '../../../../app_dependencies'; -import { useApi } from '../../../../hooks/use_api'; - -interface EditTransformFlyoutFormProps { - editTransformFlyout: UseEditTransformFlyoutReturnType; - dataViewId?: string; -} - -export const EditTransformFlyoutForm: FC = ({ - editTransformFlyout: [state, dispatch], - dataViewId, -}) => { - const { formFields, formSections } = state; - const [dateFieldNames, setDateFieldNames] = useState([]); - const [ingestPipelineNames, setIngestPipelineNames] = useState([]); - - const isRetentionPolicyAvailable = dateFieldNames.length > 0; - - const appDeps = useAppDependencies(); - const dataViewsClient = appDeps.data.dataViews; - const api = useApi(); - - useEffect( - function getDateFields() { - let unmounted = false; - if (dataViewId !== undefined) { - dataViewsClient.get(dataViewId).then((dataView) => { - if (dataView) { - const dateTimeFields = dataView.fields - .filter((f) => f.type === KBN_FIELD_TYPES.DATE) - .map((f) => f.name) - .sort(); - if (!unmounted) { - setDateFieldNames(dateTimeFields); - } - } - }); - return () => { - unmounted = true; - }; - } - }, - [dataViewId, dataViewsClient] - ); - - useEffect(function fetchPipelinesOnMount() { - let unmounted = false; - - async function getIngestPipelineNames() { - const ingestPipelines = await api.getEsIngestPipelines(); - - if (!unmounted && isEsIngestPipelines(ingestPipelines)) { - setIngestPipelineNames(ingestPipelines.map(({ name }) => name)); - } - } - - getIngestPipelineNames(); - - return () => { - unmounted = true; - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const retentionDateFieldOptions = useMemo(() => { - return Array.isArray(dateFieldNames) - ? dateFieldNames.map((text: string) => ({ text, value: text })) - : []; - }, [dateFieldNames]); - - return ( - - dispatch({ field: 'description', value })} - value={formFields.description.value} - /> - - dispatch({ field: 'frequency', value })} - placeholder={i18n.translate( - 'xpack.transform.transformList.editFlyoutFormFrequencyPlaceholderText', - { - defaultMessage: 'Default: {defaultValue}', - values: { defaultValue: formFields.frequency.defaultValue }, - } - )} - value={formFields.frequency.value} - /> - - - - - dispatch({ - section: 'retentionPolicy', - enabled: e.target.checked, - }) +import { EditTransformRetentionPolicy } from './edit_transform_retention_policy'; +import { EditTransformIngestPipeline } from './edit_transform_ingest_pipeline'; + +export const EditTransformFlyoutForm: FC = () => ( + + + + + + + + + + + - {formSections.retentionPolicy.enabled && ( -
- - { - // If data view or date fields info not available - // gracefully defaults to text input - dataViewId ? ( - 0} - error={formFields.retentionPolicyField.errorMessages} - helpText={i18n.translate( - 'xpack.transform.transformList.editFlyoutFormRetentionPolicyDateFieldHelpText', - { - defaultMessage: - 'Select the date field that can be used to identify out of date documents in the destination index.', - } - )} - > - - dispatch({ field: 'retentionPolicyField', value: e.target.value }) - } - hasNoInitialSelection={ - !retentionDateFieldOptions - .map((d) => d.text) - .includes(formFields.retentionPolicyField.value) - } - /> - - ) : ( - dispatch({ field: 'retentionPolicyField', value })} - value={formFields.retentionPolicyField.value} - /> - ) - } - dispatch({ field: 'retentionPolicyMaxAge', value })} - value={formFields.retentionPolicyMaxAge.value} - /> -
)} - - - - -
- dispatch({ field: 'destinationIndex', value })} - value={formFields.destinationIndex.value} - /> - - - -
+ paddingSize="s" + > +
+ 0} - error={formFields.destinationIngestPipeline.errorMessages} - > - ({ label }))} - selectedOptions={[{ label: formFields.destinationIngestPipeline.value }]} - onChange={(o) => - dispatch({ field: 'destinationIngestPipeline', value: o[0]?.label ?? '' }) - } - /> - - ) : ( - dispatch({ field: 'destinationIngestPipeline', value })} - value={formFields.destinationIngestPipeline.value} - /> - ) + defaultMessage: 'Destination index', } -
-
- - - + )} + /> - -
- dispatch({ field: 'docsPerSecond', value })} - value={formFields.docsPerSecond.value} - /> + - dispatch({ field: 'maxPageSearchSize', value })} - value={formFields.maxPageSearchSize.value} - placeholder={i18n.translate( - 'xpack.transform.transformList.editFlyoutFormMaxPageSearchSizePlaceholderText', - { - defaultMessage: 'Default: {defaultValue}', - values: { defaultValue: formFields.maxPageSearchSize.defaultValue }, - } - )} - /> - dispatch({ field: 'numFailureRetries', value })} - value={formFields.numFailureRetries.value} - /> +
+
- - - ); -}; +
+
+ + + + +
+ + + +
+
+ +); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form_text_input.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form_text_input.tsx index 87656a58269fa0..a337d73ca54b33 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form_text_input.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form_text_input.tsx @@ -9,25 +9,34 @@ import React, { FC } from 'react'; import { EuiFieldText, EuiFormRow } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { + useEditTransformFlyout, + type EditTransformHookTextInputSelectors, +} from './use_edit_transform_flyout'; + +function capitalizeFirstLetter(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + interface EditTransformFlyoutFormTextInputProps { - dataTestSubj: string; - errorMessages: string[]; - helpText?: string; + field: EditTransformHookTextInputSelectors; label: string; - onChange: (value: string) => void; - placeholder?: string; - value: string; + helpText?: string; + placeHolder?: boolean; } export const EditTransformFlyoutFormTextInput: FC = ({ - dataTestSubj, - errorMessages, - helpText, + field, label, - onChange, - placeholder, - value, + helpText, + placeHolder = false, }) => { + const { defaultValue, errorMessages, value } = useEditTransformFlyout(field); + const { formField } = useEditTransformFlyout('actions'); + const upperCaseField = capitalizeFirstLetter(field); + return ( 0} value={value} - onChange={(e) => onChange(e.target.value)} + onChange={(e) => formField({ field, value: e.target.value })} aria-label={label} /> diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_ingest_pipeline.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_ingest_pipeline.tsx new file mode 100644 index 00000000000000..b5bb7f3fb258f0 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_ingest_pipeline.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState, type FC } from 'react'; + +import { useEuiTheme, EuiComboBox, EuiFormRow, EuiSkeletonRectangle } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { isEsIngestPipelines } from '../../../../../../common/api_schemas/type_guards'; + +import { useApi } from '../../../../hooks/use_api'; + +import { EditTransformFlyoutFormTextInput } from './edit_transform_flyout_form_text_input'; +import { useEditTransformFlyout } from './use_edit_transform_flyout'; + +const ingestPipelineLabel = i18n.translate( + 'xpack.transform.transformList.editFlyoutFormDestinationIngestPipelineLabel', + { + defaultMessage: 'Ingest Pipeline', + } +); + +export const EditTransformIngestPipeline: FC = () => { + const { euiTheme } = useEuiTheme(); + const { errorMessages, value } = useEditTransformFlyout('destinationIngestPipeline'); + const { formField } = useEditTransformFlyout('actions'); + + const api = useApi(); + + const [ingestPipelineNames, setIngestPipelineNames] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(function fetchPipelinesOnMount() { + let unmounted = false; + + async function getIngestPipelineNames() { + try { + const ingestPipelines = await api.getEsIngestPipelines(); + + if (!unmounted && isEsIngestPipelines(ingestPipelines)) { + setIngestPipelineNames(ingestPipelines.map(({ name }) => name)); + } + } finally { + if (!unmounted) { + setIsLoading(false); + } + } + } + + getIngestPipelineNames(); + + return () => { + unmounted = true; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + <> + { + // If the list of ingest pipelines is not available + // gracefully defaults to text input + ingestPipelineNames.length > 0 || isLoading ? ( + 0} + error={errorMessages} + > + + ({ label }))} + selectedOptions={[{ label: value }]} + onChange={(o) => + formField({ field: 'destinationIngestPipeline', value: o[0]?.label ?? '' }) + } + /> + + + ) : ( + + ) + } + + ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_retention_policy.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_retention_policy.tsx new file mode 100644 index 00000000000000..3b916c33c23a77 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_retention_policy.tsx @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useMemo, useState, type FC } from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { EuiFormRow, EuiSelect, EuiSpacer, EuiSwitch } from '@elastic/eui'; + +import { KBN_FIELD_TYPES } from '@kbn/field-types'; + +import { useAppDependencies } from '../../../../app_dependencies'; + +import { EditTransformFlyoutFormTextInput } from './edit_transform_flyout_form_text_input'; +import { useEditTransformFlyout } from './use_edit_transform_flyout'; + +export const EditTransformRetentionPolicy: FC = () => { + const appDeps = useAppDependencies(); + const dataViewsClient = appDeps.data.dataViews; + + const dataViewId = useEditTransformFlyout('dataViewId'); + const formSections = useEditTransformFlyout('stateFormSection'); + const retentionPolicyField = useEditTransformFlyout('retentionPolicyField'); + const { formField, formSection } = useEditTransformFlyout('actions'); + + const [dateFieldNames, setDateFieldNames] = useState([]); + + useEffect( + function getDateFields() { + let unmounted = false; + if (dataViewId !== undefined) { + dataViewsClient.get(dataViewId).then((dataView) => { + if (dataView) { + const dateTimeFields = dataView.fields + .filter((f) => f.type === KBN_FIELD_TYPES.DATE) + .map((f) => f.name) + .sort(); + if (!unmounted) { + setDateFieldNames(dateTimeFields); + } + } + }); + return () => { + unmounted = true; + }; + } + }, + [dataViewId, dataViewsClient] + ); + + const isRetentionPolicyAvailable = dateFieldNames.length > 0; + const retentionDateFieldOptions = useMemo(() => { + return Array.isArray(dateFieldNames) + ? dateFieldNames.map((text: string) => ({ text, value: text })) + : []; + }, [dateFieldNames]); + + return ( + <> + + formSection({ + section: 'retentionPolicy', + enabled: e.target.checked, + }) + } + disabled={!isRetentionPolicyAvailable} + data-test-subj="transformEditRetentionPolicySwitch" + /> + {formSections.retentionPolicy.enabled && ( +
+ + { + // If data view or date fields info not available + // gracefully defaults to text input + dataViewId ? ( + 0} + error={retentionPolicyField.errorMessages} + helpText={i18n.translate( + 'xpack.transform.transformList.editFlyoutFormRetentionPolicyDateFieldHelpText', + { + defaultMessage: + 'Select the date field that can be used to identify out of date documents in the destination index.', + } + )} + > + + formField({ field: 'retentionPolicyField', value: e.target.value }) + } + hasNoInitialSelection={ + !retentionDateFieldOptions + .map((d) => d.text) + .includes(retentionPolicyField.value) + } + /> + + ) : ( + + ) + } + +
+ )} + + ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_update_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_update_button.tsx new file mode 100644 index 00000000000000..06e30d584de463 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_update_button.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { type FC } from 'react'; + +import { EuiButton } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { isPostTransformsUpdateResponseSchema } from '../../../../../../common/api_schemas/type_guards'; +import { getErrorMessage } from '../../../../../../common/utils/errors'; + +import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../../../../common'; +import { useToastNotifications } from '../../../../app_dependencies'; +import { useApi } from '../../../../hooks/use_api'; + +import { useEditTransformFlyout } from './use_edit_transform_flyout'; + +interface EditTransformUpdateButtonProps { + closeFlyout: () => void; +} + +export const EditTransformUpdateButton: FC = ({ closeFlyout }) => { + const api = useApi(); + const toastNotifications = useToastNotifications(); + + const requestConfig = useEditTransformFlyout('requestConfig'); + const isUpdateButtonDisabled = useEditTransformFlyout('isUpdateButtonDisabled'); + const config = useEditTransformFlyout('config'); + const { apiError } = useEditTransformFlyout('actions'); + + async function submitFormHandler() { + apiError(undefined); + const transformId = config.id; + + const resp = await api.updateTransform(transformId, requestConfig); + + if (!isPostTransformsUpdateResponseSchema(resp)) { + apiError(getErrorMessage(resp)); + return; + } + + toastNotifications.addSuccess( + i18n.translate('xpack.transform.transformList.editTransformSuccessMessage', { + defaultMessage: 'Transform {transformId} updated.', + values: { transformId }, + }) + ); + closeFlyout(); + refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); + } + + return ( + + {i18n.translate('xpack.transform.transformList.editFlyoutUpdateButtonText', { + defaultMessage: 'Update', + })} + + ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.test.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.test.ts index be366f57271cae..1aeb7a425331f1 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.test.ts +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.test.ts @@ -196,24 +196,33 @@ describe('Transform: formReducerFactory()', () => { const reducer = formReducerFactory(transformConfigMock); const state1 = reducer(getDefaultState(transformConfigMock), { - field: 'description', - value: 'the-updated-description', + name: 'form_field', + payload: { + field: 'description', + value: 'the-updated-description', + }, }); expect(state1.isFormTouched).toBe(true); expect(state1.isFormValid).toBe(true); const state2 = reducer(state1, { - field: 'description', - value: transformConfigMock.description as string, + name: 'form_field', + payload: { + field: 'description', + value: transformConfigMock.description as string, + }, }); expect(state2.isFormTouched).toBe(false); expect(state2.isFormValid).toBe(true); const state3 = reducer(state2, { - field: 'frequency', - value: 'the-invalid-value', + name: 'form_field', + payload: { + field: 'frequency', + value: 'the-invalid-value', + }, }); expect(state3.isFormTouched).toBe(true); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.tsx similarity index 81% rename from x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts rename to x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.tsx index c4d43cfd8285ad..b678dc0377cc28 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.tsx @@ -5,13 +5,13 @@ * 2.0. */ +import constate from 'constate'; import { isEqual, merge } from 'lodash'; -import { useReducer } from 'react'; +import { useMemo, useReducer } from 'react'; import { i18n } from '@kbn/i18n'; import { numberValidator } from '@kbn/ml-agg-utils'; import { getNestedProperty, setNestedProperty } from '@kbn/ml-nested-property'; -import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { PostTransformsUpdateRequestSchema } from '../../../../../../common/api_schemas/update_transforms'; import { @@ -35,16 +35,16 @@ import { // The outer most level reducer defines a flat structure of names for form fields. // This is a flat structure regardless of whether the final request object will be nested. // For example, `destinationIndex` and `destinationIngestPipeline` will later be nested under `dest`. -type EditTransformFormFields = +export type EditTransformFormFields = | 'description' | 'destinationIndex' | 'destinationIngestPipeline' - | 'frequency' | 'docsPerSecond' + | 'frequency' | 'maxPageSearchSize' + | 'numFailureRetries' | 'retentionPolicyField' - | 'retentionPolicyMaxAge' - | 'numFailureRetries'; + | 'retentionPolicyMaxAge'; type EditTransformFlyoutFieldsState = Record; @@ -278,28 +278,33 @@ export const initializeSection = ( }; export interface EditTransformFlyoutState { + apiErrorMessage?: string; formFields: EditTransformFlyoutFieldsState; formSections: EditTransformFlyoutSectionsState; isFormTouched: boolean; isFormValid: boolean; } -// Actions for fields and sections -interface FormFieldAction { - field: EditTransformFormFields; - value: string; +// Actions +interface ApiErrorAction { + name: 'api_error'; + payload: string | undefined; } -function isFormFieldAction(action: unknown): action is FormFieldAction { - return isPopulatedObject(action, ['field']); +interface FormFieldAction { + name: 'form_field'; + payload: { + field: EditTransformFormFields; + value: string; + }; } interface FormSectionAction { - section: EditTransformFormSections; - enabled: boolean; -} -function isFormSectionAction(action: unknown): action is FormSectionAction { - return isPopulatedObject(action, ['section']); + name: 'form_section'; + payload: { + section: EditTransformFormSections; + enabled: boolean; + }; } -type Action = FormFieldAction | FormSectionAction; +type Action = ApiErrorAction | FormFieldAction | FormSectionAction; // Takes a value from form state and applies it to the structure // of the expected final configuration request object. @@ -512,22 +517,31 @@ export const formReducerFactory = (config: TransformConfigUnion) => { const defaultSectionValues = getSectionValues(defaultState.formSections); return (state: EditTransformFlyoutState, action: Action): EditTransformFlyoutState => { - const formFields = isFormFieldAction(action) - ? { - ...state.formFields, - [action.field]: formFieldReducer(state.formFields[action.field], action.value), - } - : state.formFields; - - const formSections = isFormSectionAction(action) - ? { - ...state.formSections, - [action.section]: formSectionReducer(state.formSections[action.section], action.enabled), - } - : state.formSections; + const formFields = + action.name === 'form_field' + ? { + ...state.formFields, + [action.payload.field]: formFieldReducer( + state.formFields[action.payload.field], + action.payload.value + ), + } + : state.formFields; + + const formSections = + action.name === 'form_section' + ? { + ...state.formSections, + [action.payload.section]: formSectionReducer( + state.formSections[action.payload.section], + action.payload.enabled + ), + } + : state.formSections; return { ...state, + apiErrorMessage: action.name === 'api_error' ? action.payload : state.apiErrorMessage, formFields, formSections, isFormTouched: @@ -538,8 +552,93 @@ export const formReducerFactory = (config: TransformConfigUnion) => { }; }; -export const useEditTransformFlyout = (config: TransformConfigUnion) => { - return useReducer(formReducerFactory(config), getDefaultState(config)); +interface EditTransformFlyoutOptions { + config: TransformConfigUnion; + dataViewId?: string; +} + +const useEditTransformFlyoutInternal = ({ config, dataViewId }: EditTransformFlyoutOptions) => { + const [formState, dispatch] = useReducer(formReducerFactory(config), getDefaultState(config)); + + const actions = useMemo( + () => ({ + apiError: (payload: ApiErrorAction['payload']) => dispatch({ name: 'api_error', payload }), + formField: (payload: FormFieldAction['payload']) => + dispatch({ + name: 'form_field', + payload, + }), + formSection: (payload: FormSectionAction['payload']) => + dispatch({ name: 'form_section', payload }), + }), + [] + ); + + const requestConfig = useMemo( + () => applyFormStateToTransformConfig(config, formState), + [config, formState] + ); + + const isUpdateButtonDisabled = useMemo( + () => !formState.isFormValid || !formState.isFormTouched, + [formState.isFormValid, formState.isFormTouched] + ); + + return { config, dataViewId, formState, actions, requestConfig, isUpdateButtonDisabled }; +}; + +// wrap hook with the constate factory to create context provider and custom hooks based on selectors +const [EditTransformFlyoutProvider, ...editTransformHooks] = constate( + useEditTransformFlyoutInternal, + (d) => d.config, + (d) => d.dataViewId, + (d) => d.actions, + (d) => d.formState.apiErrorMessage, + (d) => d.formState.formSections, + (d) => d.formState.formFields.description, + (d) => d.formState.formFields.destinationIndex, + (d) => d.formState.formFields.docsPerSecond, + (d) => d.formState.formFields.frequency, + (d) => d.formState.formFields.destinationIngestPipeline, + (d) => d.formState.formFields.maxPageSearchSize, + (d) => d.formState.formFields.numFailureRetries, + (d) => d.formState.formFields.retentionPolicyField, + (d) => d.formState.formFields.retentionPolicyMaxAge, + (d) => d.requestConfig, + (d) => d.isUpdateButtonDisabled +); + +export enum EDIT_TRANSFORM_HOOK_SELECTORS { + config, + dataViewId, + actions, + apiErrorMessage, + stateFormSection, + description, + destinationIndex, + docsPerSecond, + frequency, + destinationIngestPipeline, + maxPageSearchSize, + numFailureRetries, + retentionPolicyField, + retentionPolicyMaxAge, + requestConfig, + isUpdateButtonDisabled, +} + +export type EditTransformHookTextInputSelectors = Extract< + keyof typeof EDIT_TRANSFORM_HOOK_SELECTORS, + EditTransformFormFields +>; + +type EditTransformHookSelectors = keyof typeof EDIT_TRANSFORM_HOOK_SELECTORS; +type EditTransformHooks = typeof editTransformHooks; + +export const useEditTransformFlyout = (hookKey: K) => { + return editTransformHooks[EDIT_TRANSFORM_HOOK_SELECTORS[hookKey]]() as ReturnType< + EditTransformHooks[typeof EDIT_TRANSFORM_HOOK_SELECTORS[K]] + >; }; -export type UseEditTransformFlyoutReturnType = ReturnType; +export { EditTransformFlyoutProvider }; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx index 986adb89bd41e7..5b0a51bbf7e035 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx @@ -48,13 +48,7 @@ export const useActions = ({ {startAction.isModalVisible && } {stopAction.isModalVisible && } - {editAction.config && editAction.isFlyoutVisible && ( - - )} + {deleteAction.isModalVisible && } ), diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index a5c8b198267d4a..9e23807197c66c 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -34179,8 +34179,6 @@ "xpack.transform.transformList.deleteModalTitle": "Supprimer {transformId} ?", "xpack.transform.transformList.deleteTransformErrorMessage": "Une erreur s'est produite lors de la suppression de la transformation {transformId}", "xpack.transform.transformList.deleteTransformSuccessMessage": "La requête pour supprimer la transformation {transformId} a été reconnue.", - "xpack.transform.transformList.editFlyoutFormFrequencyPlaceholderText": "Par défaut : {defaultValue}", - "xpack.transform.transformList.editFlyoutFormMaxPageSearchSizePlaceholderText": "Par défaut : {defaultValue}", "xpack.transform.transformList.editFlyoutTitle": "Modifier {transformId}", "xpack.transform.transformList.editTransformSuccessMessage": "Transformation {transformId} mise à jour.", "xpack.transform.transformList.resetModalTitle": "Réinitialiser {transformId} ?", @@ -34487,7 +34485,6 @@ "xpack.transform.transformList.editFlyoutFormNumberRange10To10000NotValidErrorMessage": "La valeur doit être un entier compris entre 10 et 10 000.", "xpack.transform.transformList.editFlyoutFormNumFailureRetriesHelpText": "Le nombre de nouvelles tentatives après un échec récupérable avant que la tâche de transformation soit marquée comme ayant échoué. Définissez-le à -1 pour des tentatives infinies.", "xpack.transform.transformList.editFlyoutFormRequiredErrorMessage": "Champs requis.", - "xpack.transform.transformList.editFlyoutFormRetentionMaxAgeFieldLabel": "Âge maximal", "xpack.transform.transformList.editFlyoutFormRetentionPolicyDateFieldHelpText": "Sélectionnez le champ de date pouvant être utilisé pour identifier les documents obsolètes dans l'index de destination.", "xpack.transform.transformList.editFlyoutFormRetentionPolicyFieldLabel": "Champ", "xpack.transform.transformList.editFlyoutFormRetentionPolicyFieldSelectAriaLabel": "Champ de date pour définir la politique de conservation", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0a14c736eed38a..720eac36e9e81b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -34149,8 +34149,6 @@ "xpack.transform.transformList.deleteModalTitle": "{transformId}を削除しますか?", "xpack.transform.transformList.deleteTransformErrorMessage": "変換 {transformId} の削除中にエラーが発生しました", "xpack.transform.transformList.deleteTransformSuccessMessage": "データフレームジョブ {transformId} が削除されました。", - "xpack.transform.transformList.editFlyoutFormFrequencyPlaceholderText": "デフォルト:{defaultValue}", - "xpack.transform.transformList.editFlyoutFormMaxPageSearchSizePlaceholderText": "デフォルト:{defaultValue}", "xpack.transform.transformList.editFlyoutTitle": "{transformId}を編集", "xpack.transform.transformList.editTransformSuccessMessage": "変換{transformId}が更新されました。", "xpack.transform.transformList.resetModalTitle": "{transformId}をリセットしますか?", @@ -34457,7 +34455,6 @@ "xpack.transform.transformList.editFlyoutFormNumberRange10To10000NotValidErrorMessage": "値は10~10000の範囲の整数でなければなりません。", "xpack.transform.transformList.editFlyoutFormNumFailureRetriesHelpText": "回復可能な失敗時の再試行回数。この回数を超えると、変換タスクが失敗に設定されます。-1を指定すると、再試行回数が無制限に設定されます。", "xpack.transform.transformList.editFlyoutFormRequiredErrorMessage": "必須フィールド。", - "xpack.transform.transformList.editFlyoutFormRetentionMaxAgeFieldLabel": "最大年齢", "xpack.transform.transformList.editFlyoutFormRetentionPolicyDateFieldHelpText": "デスティネーションインデックスで古いドキュメントを特定するために使用できる日付フィールドを選択します。", "xpack.transform.transformList.editFlyoutFormRetentionPolicyFieldLabel": "フィールド", "xpack.transform.transformList.editFlyoutFormRetentionPolicyFieldSelectAriaLabel": "保持ポリシーを設定する日付フィールド", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d020f2a55fcb2a..3de4232c9b8d74 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -34185,8 +34185,6 @@ "xpack.transform.transformList.deleteModalTitle": "删除 {transformId}?", "xpack.transform.transformList.deleteTransformErrorMessage": "删除数据帧转换 {transformId} 时发生错误", "xpack.transform.transformList.deleteTransformSuccessMessage": "数据帧作业 {transformId} 删除成功。", - "xpack.transform.transformList.editFlyoutFormFrequencyPlaceholderText": "默认值:{defaultValue}", - "xpack.transform.transformList.editFlyoutFormMaxPageSearchSizePlaceholderText": "默认值:{defaultValue}", "xpack.transform.transformList.editFlyoutTitle": "编辑 {transformId}", "xpack.transform.transformList.editTransformSuccessMessage": "转换 {transformId} 已更新。", "xpack.transform.transformList.resetModalTitle": "重置 {transformId}?", @@ -34493,7 +34491,6 @@ "xpack.transform.transformList.editFlyoutFormNumberRange10To10000NotValidErrorMessage": "值必须是介于 10 到 10000 之间的整数。", "xpack.transform.transformList.editFlyoutFormNumFailureRetriesHelpText": "将转换任务标记为失败前可恢复失败的重试次数。将其设置为 -1 表示无限重试。", "xpack.transform.transformList.editFlyoutFormRequiredErrorMessage": "必填字段。", - "xpack.transform.transformList.editFlyoutFormRetentionMaxAgeFieldLabel": "最大存在时间", "xpack.transform.transformList.editFlyoutFormRetentionPolicyDateFieldHelpText": "选择可用于从目标索引中识别出日期文档的日期字段。", "xpack.transform.transformList.editFlyoutFormRetentionPolicyFieldLabel": "字段", "xpack.transform.transformList.editFlyoutFormRetentionPolicyFieldSelectAriaLabel": "设置保留策略的日期字段",