From d105e2831865f72a78f7068eb5ebfde8acb544bd Mon Sep 17 00:00:00 2001 From: Zabilsya Date: Mon, 31 Mar 2025 23:38:38 +0600 Subject: [PATCH] [DOP-22294] add transfer resources --- src/app/styles/antd.less | 4 ++ src/app/styles/variables.less | 1 + src/entities/file/@x/transfer.ts | 1 + src/entities/file/constants.ts | 6 +- src/entities/file/types.ts | 12 ++-- .../file/utils/parseFileSize/index.ts | 12 ++-- src/entities/transfer/api/types.ts | 7 ++ src/entities/transfer/index.ts | 1 + src/entities/transfer/utils/index.ts | 2 + .../prepareTransferResourcesForm/index.ts | 10 +++ .../prepareTransferResourcesRequest/index.ts | 10 +++ .../MutateTransferForm/MutateTransferForm.tsx | 4 +- .../components/TooltipText/index.tsx | 19 ++++++ .../components/TooltipText/types.ts | 4 ++ .../TransferResources/components/index.ts | 1 + .../components/TransferResources/constants.ts | 8 +++ .../components/TransferResources/index.tsx | 68 +++++++++++++++++++ .../MutateTransferForm/components/index.ts | 1 + .../components/TransferResources/index.tsx | 24 +++++++ .../components/TransferResources/types.ts | 6 ++ .../TransferDetailInfo/components/index.ts | 1 + .../transfer/TransferDetailInfo/index.tsx | 5 +- src/shared/config/i18n/translations/en.json | 17 +++-- src/shared/config/i18n/translations/ru.json | 17 +++-- src/shared/constants/errorMessage.ts | 1 + src/shared/constants/index.ts | 1 + src/shared/constants/regexp.ts | 3 + src/widgets/transfer/CreateTransfer/index.tsx | 5 +- src/widgets/transfer/UpdateTransfer/index.tsx | 12 +++- 29 files changed, 232 insertions(+), 31 deletions(-) create mode 100644 src/entities/transfer/utils/index.ts create mode 100644 src/entities/transfer/utils/prepareTransferResourcesForm/index.ts create mode 100644 src/entities/transfer/utils/prepareTransferResourcesRequest/index.ts create mode 100644 src/features/transfer/MutateTransferForm/components/TransferResources/components/TooltipText/index.tsx create mode 100644 src/features/transfer/MutateTransferForm/components/TransferResources/components/TooltipText/types.ts create mode 100644 src/features/transfer/MutateTransferForm/components/TransferResources/components/index.ts create mode 100644 src/features/transfer/MutateTransferForm/components/TransferResources/constants.ts create mode 100644 src/features/transfer/MutateTransferForm/components/TransferResources/index.tsx create mode 100644 src/features/transfer/TransferDetailInfo/components/TransferResources/index.tsx create mode 100644 src/features/transfer/TransferDetailInfo/components/TransferResources/types.ts create mode 100644 src/shared/constants/errorMessage.ts diff --git a/src/app/styles/antd.less b/src/app/styles/antd.less index 93fbbc14..f3745e77 100644 --- a/src/app/styles/antd.less +++ b/src/app/styles/antd.less @@ -59,3 +59,7 @@ display: flex; justify-content: space-between; } + +p:last-of-type { + margin-bottom: 0; +} diff --git a/src/app/styles/variables.less b/src/app/styles/variables.less index a0d0cba0..f0ad101e 100644 --- a/src/app/styles/variables.less +++ b/src/app/styles/variables.less @@ -22,4 +22,5 @@ @picker-bg: #f1f3f5; @tooltip-bg: #000000; +@tooltip-max-width: 300px; @line-height-base: 1.5; diff --git a/src/entities/file/@x/transfer.ts b/src/entities/file/@x/transfer.ts index 73ae16d9..804add0f 100644 --- a/src/entities/file/@x/transfer.ts +++ b/src/entities/file/@x/transfer.ts @@ -1,3 +1,4 @@ /** Cross-import entities using public API in FSD https://feature-sliced.design/ru/docs/reference/public-api#public-api-for-cross-imports */ export type { FileFormat, Json } from '../types'; +export { FileSizeUnitValue, FileSizeUnit } from '../types'; export { FileFormatParams, FileNameTemplate } from '../ui'; diff --git a/src/entities/file/constants.ts b/src/entities/file/constants.ts index 02e2271b..4e2cb0a2 100644 --- a/src/entities/file/constants.ts +++ b/src/entities/file/constants.ts @@ -2,7 +2,7 @@ import { FileSizeUnit } from './types'; export const FILE_SIZE_UNIT_DISPLAY = { [FileSizeUnit.B]: 'b', - [FileSizeUnit.KB]: 'kb', - [FileSizeUnit.MB]: 'mb', - [FileSizeUnit.GB]: 'gb', + [FileSizeUnit.KiB]: 'kib', + [FileSizeUnit.MiB]: 'mib', + [FileSizeUnit.GiB]: 'gib', } as const; diff --git a/src/entities/file/types.ts b/src/entities/file/types.ts index 1d7acba5..cd1283d5 100644 --- a/src/entities/file/types.ts +++ b/src/entities/file/types.ts @@ -1,15 +1,15 @@ export enum FileSizeUnit { B = 'B', - KB = 'KB', - MB = 'MB', - GB = 'GB', + KiB = 'KiB', + MiB = 'MiB', + GiB = 'GiB', } export enum FileSizeUnitValue { B = 1, - KB = 1000, - MB = 1000 * 1000, - GB = 1000 * 1000 * 1000, + KiB = 1024, + MiB = 1024 * 1024, + GiB = 1024 * 1024 * 1024, } export enum FileCompression { diff --git a/src/entities/file/utils/parseFileSize/index.ts b/src/entities/file/utils/parseFileSize/index.ts index 56d39e45..f2924311 100644 --- a/src/entities/file/utils/parseFileSize/index.ts +++ b/src/entities/file/utils/parseFileSize/index.ts @@ -4,14 +4,14 @@ import { ParseFileSizeReturn } from './types'; /** Util for parsing file size in bytes to appropriate unit and value */ export const parseFileSize = (bytes: number): ParseFileSizeReturn => { - if (bytes >= FileSizeUnitValue.GB) { - return { value: bytes / FileSizeUnitValue.GB, unit: FileSizeUnit.GB }; + if (bytes >= FileSizeUnitValue.GiB) { + return { value: bytes / FileSizeUnitValue.GiB, unit: FileSizeUnit.GiB }; } - if (bytes >= FileSizeUnitValue.MB) { - return { value: bytes / FileSizeUnitValue.MB, unit: FileSizeUnit.MB }; + if (bytes >= FileSizeUnitValue.MiB) { + return { value: bytes / FileSizeUnitValue.MiB, unit: FileSizeUnit.MiB }; } - if (bytes >= FileSizeUnitValue.KB) { - return { value: bytes / FileSizeUnitValue.KB, unit: FileSizeUnit.KB }; + if (bytes >= FileSizeUnitValue.KiB) { + return { value: bytes / FileSizeUnitValue.KiB, unit: FileSizeUnit.KiB }; } return { value: bytes, unit: FileSizeUnit.B }; }; diff --git a/src/entities/transfer/api/types.ts b/src/entities/transfer/api/types.ts index 0d2a526e..bec0d1b9 100644 --- a/src/entities/transfer/api/types.ts +++ b/src/entities/transfer/api/types.ts @@ -15,6 +15,7 @@ export interface Transfer { strategy_params: TransferStrategyParams; is_scheduled: boolean; schedule: string; + resources: TransferResources; transformations: Transformations; } @@ -35,6 +36,12 @@ export interface TransferStrategyParams { increment_by?: TransferConnectionFileIncrementBy | string; } +export interface TransferResources { + max_parallel_tasks: number; + cpu_cores_per_task: number; + ram_bytes_per_task: number | string; +} + export interface TransferSourceConnectionFileType { type: | ConnectionType.FTP diff --git a/src/entities/transfer/index.ts b/src/entities/transfer/index.ts index 1a554c55..a2835f50 100644 --- a/src/entities/transfer/index.ts +++ b/src/entities/transfer/index.ts @@ -1,2 +1,3 @@ export * from './api'; export * from './ui'; +export * from './utils'; diff --git a/src/entities/transfer/utils/index.ts b/src/entities/transfer/utils/index.ts new file mode 100644 index 00000000..b200194b --- /dev/null +++ b/src/entities/transfer/utils/index.ts @@ -0,0 +1,2 @@ +export * from './prepareTransferResourcesForm'; +export * from './prepareTransferResourcesRequest'; diff --git a/src/entities/transfer/utils/prepareTransferResourcesForm/index.ts b/src/entities/transfer/utils/prepareTransferResourcesForm/index.ts new file mode 100644 index 00000000..56bc9c46 --- /dev/null +++ b/src/entities/transfer/utils/prepareTransferResourcesForm/index.ts @@ -0,0 +1,10 @@ +import { FileSizeUnitValue } from '@entities/file/@x/transfer'; +import { TransferResources } from '@entities/transfer'; + +/** Util for mapping of transfer resources data from backend to appropriate form value */ +export const prepareTransferResourcesForm = (data: TransferResources): TransferResources => { + return { + ...data, + ram_bytes_per_task: +data.ram_bytes_per_task / FileSizeUnitValue.GiB, + }; +}; diff --git a/src/entities/transfer/utils/prepareTransferResourcesRequest/index.ts b/src/entities/transfer/utils/prepareTransferResourcesRequest/index.ts new file mode 100644 index 00000000..96991652 --- /dev/null +++ b/src/entities/transfer/utils/prepareTransferResourcesRequest/index.ts @@ -0,0 +1,10 @@ +import { FileSizeUnit } from '@entities/file/@x/transfer'; +import { TransferResources } from '@entities/transfer'; + +/** Util for mapping of transfer resources data from form value to appropriate value for backend */ +export const prepareTransferResourcesRequest = (data: TransferResources): TransferResources => { + return { + ...data, + ram_bytes_per_task: `${data.ram_bytes_per_task}${FileSizeUnit.GiB}`, + }; +}; diff --git a/src/features/transfer/MutateTransferForm/MutateTransferForm.tsx b/src/features/transfer/MutateTransferForm/MutateTransferForm.tsx index e40ee8b0..f17b4d31 100644 --- a/src/features/transfer/MutateTransferForm/MutateTransferForm.tsx +++ b/src/features/transfer/MutateTransferForm/MutateTransferForm.tsx @@ -5,7 +5,7 @@ import { Form, Input } from 'antd'; import { useTranslation } from 'react-i18next'; import { MutateTransferFormProps } from './types'; -import { StrategyParams, TransferConnections, TransferSchedule } from './components'; +import { StrategyParams, TransferConnections, TransferResources, TransferSchedule } from './components'; export const MutateTransferForm = ({ group, onCancel }: MutateTransferFormProps) => { const { t } = useTranslation(); @@ -39,6 +39,8 @@ export const MutateTransferForm = ({ group, onCancel }: MutateTransferFormProps) + + diff --git a/src/features/transfer/MutateTransferForm/components/TransferResources/components/TooltipText/index.tsx b/src/features/transfer/MutateTransferForm/components/TransferResources/components/TooltipText/index.tsx new file mode 100644 index 00000000..56022fe9 --- /dev/null +++ b/src/features/transfer/MutateTransferForm/components/TransferResources/components/TooltipText/index.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { TooltipTextProps } from './types'; + +export const TooltipText = ({ minValue, maxValue }: TooltipTextProps) => { + const { t } = useTranslation('transfer'); + + return ( + <> +

+ {t('minValue')} = {minValue} +

+

+ {t('maxValue')} = {maxValue} +

+ + ); +}; diff --git a/src/features/transfer/MutateTransferForm/components/TransferResources/components/TooltipText/types.ts b/src/features/transfer/MutateTransferForm/components/TransferResources/components/TooltipText/types.ts new file mode 100644 index 00000000..9033279c --- /dev/null +++ b/src/features/transfer/MutateTransferForm/components/TransferResources/components/TooltipText/types.ts @@ -0,0 +1,4 @@ +export interface TooltipTextProps { + minValue: string | number; + maxValue: string | number; +} diff --git a/src/features/transfer/MutateTransferForm/components/TransferResources/components/index.ts b/src/features/transfer/MutateTransferForm/components/TransferResources/components/index.ts new file mode 100644 index 00000000..93a7a09c --- /dev/null +++ b/src/features/transfer/MutateTransferForm/components/TransferResources/components/index.ts @@ -0,0 +1 @@ +export * from './TooltipText'; diff --git a/src/features/transfer/MutateTransferForm/components/TransferResources/constants.ts b/src/features/transfer/MutateTransferForm/components/TransferResources/constants.ts new file mode 100644 index 00000000..15249fe0 --- /dev/null +++ b/src/features/transfer/MutateTransferForm/components/TransferResources/constants.ts @@ -0,0 +1,8 @@ +export const MIN_PARALLEL_TASKS = 1; +export const MAX_PARALLEL_TASKS = 100; + +export const MIN_CPU_CORES_PER_TASKS = 1; +export const MAX_CPU_CORES_PER_TASKS = 32; + +export const MIN_RAM_PER_TASK = 0.5; +export const MAX_RAM_PER_TASK = 64; diff --git a/src/features/transfer/MutateTransferForm/components/TransferResources/index.tsx b/src/features/transfer/MutateTransferForm/components/TransferResources/index.tsx new file mode 100644 index 00000000..4481b6fd --- /dev/null +++ b/src/features/transfer/MutateTransferForm/components/TransferResources/index.tsx @@ -0,0 +1,68 @@ +import { Fieldset } from '@shared/ui'; +import { Form, InputNumber } from 'antd'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { validateFormFieldByPattern } from '@shared/utils'; +import { INTEGER_ERROR_DISPLAY, INTEGER_REGEXP } from '@shared/constants'; + +import { + MAX_PARALLEL_TASKS, + MAX_CPU_CORES_PER_TASKS, + MIN_PARALLEL_TASKS, + MIN_CPU_CORES_PER_TASKS, + MIN_RAM_PER_TASK, + MAX_RAM_PER_TASK, +} from './constants'; +import { TooltipText } from './components'; + +export const TransferResources = () => { + const { t } = useTranslation('error'); + + return ( +
+ validateFormFieldByPattern(rule, value, t), + }, + ]} + tooltip={} + > + + + validateFormFieldByPattern(rule, value, t), + }, + ]} + tooltip={} + > + + + + } + > + + +
+ ); +}; diff --git a/src/features/transfer/MutateTransferForm/components/index.ts b/src/features/transfer/MutateTransferForm/components/index.ts index 12227410..f78d3fca 100644 --- a/src/features/transfer/MutateTransferForm/components/index.ts +++ b/src/features/transfer/MutateTransferForm/components/index.ts @@ -4,3 +4,4 @@ export * from './TransferSchedule'; export * from './TransferConnections'; export * from './StrategyParams'; export * from './TransferConnectionsCanvas'; +export * from './TransferResources'; diff --git a/src/features/transfer/TransferDetailInfo/components/TransferResources/index.tsx b/src/features/transfer/TransferDetailInfo/components/TransferResources/index.tsx new file mode 100644 index 00000000..d080ca20 --- /dev/null +++ b/src/features/transfer/TransferDetailInfo/components/TransferResources/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Descriptions } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { FileSizeUnitValue } from '@entities/file'; + +import { TransferResourcesProps } from './types'; + +export const TransferResources = ({ data, ...props }: TransferResourcesProps) => { + const { t } = useTranslation('transfer'); + + return ( + + + {data.max_parallel_tasks} + + + {data.cpu_cores_per_task} + + + {+data.ram_bytes_per_task / FileSizeUnitValue.GiB} {t('gib', { ns: 'file' })} + + + ); +}; diff --git a/src/features/transfer/TransferDetailInfo/components/TransferResources/types.ts b/src/features/transfer/TransferDetailInfo/components/TransferResources/types.ts new file mode 100644 index 00000000..be9c8729 --- /dev/null +++ b/src/features/transfer/TransferDetailInfo/components/TransferResources/types.ts @@ -0,0 +1,6 @@ +import { Transfer } from '@entities/transfer'; +import { DescriptionsProps } from 'antd'; + +export interface TransferResourcesProps extends DescriptionsProps { + data: Transfer['resources']; +} diff --git a/src/features/transfer/TransferDetailInfo/components/index.ts b/src/features/transfer/TransferDetailInfo/components/index.ts index bb8613d7..8b1422f2 100644 --- a/src/features/transfer/TransferDetailInfo/components/index.ts +++ b/src/features/transfer/TransferDetailInfo/components/index.ts @@ -1,3 +1,4 @@ export * from './TransferParams'; export * from './TransferFileFormatData'; export * from './TransferStrategyParams'; +export * from './TransferResources'; diff --git a/src/features/transfer/TransferDetailInfo/index.tsx b/src/features/transfer/TransferDetailInfo/index.tsx index c2f47300..94b588c9 100644 --- a/src/features/transfer/TransferDetailInfo/index.tsx +++ b/src/features/transfer/TransferDetailInfo/index.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; import { TransferDetailInfoProps } from './types'; import classes from './styles.module.less'; -import { TransferParams, TransferStrategyParams } from './components'; +import { TransferParams, TransferResources, TransferStrategyParams } from './components'; export const TransferDetailInfo = ({ transfer, @@ -45,6 +45,9 @@ export const TransferDetailInfo = ({ + + + {connectionSource.name} diff --git a/src/shared/config/i18n/translations/en.json b/src/shared/config/i18n/translations/en.json index fc29090d..b1b0fa2b 100644 --- a/src/shared/config/i18n/translations/en.json +++ b/src/shared/config/i18n/translations/en.json @@ -9,7 +9,8 @@ "formErrorHasOccurred": "Form error has occurred", "fieldIsRequired": "Field is required", "fieldPatternInvalid": "Field value does not match the pattern", - "invalidRegularExpression": "Invalid regular expressions" + "invalidRegularExpression": "Invalid regular expressions", + "integerError": "Field value must be integer" }, "auth": { "auth": "Authorization", @@ -83,9 +84,9 @@ }, "file": { "b": "B", - "kb": "KB", - "mb": "MB", - "gb": "GB", + "kib": "KiB", + "mib": "MiB", + "gib": "GiB", "fileFormat": "File format", "selectFileFormat": "Select file format", "compression": "Compression", @@ -179,7 +180,13 @@ "incremental": "incremental", "target": "Target", "basic": "Basic", - "advanced": "Advanced" + "advanced": "Advanced", + "resources": "Resources", + "minValue": "Min. value", + "maxValue": "Max. value", + "maxParallelTasks": "Max parallel tasks", + "cpuCoresPerTask": "CPU cores per task", + "ramPerTask": "RAM per task (GiB)" }, "transformation": { "transformations": "Transformations:", diff --git a/src/shared/config/i18n/translations/ru.json b/src/shared/config/i18n/translations/ru.json index 9cb1d72e..f14b0dd0 100644 --- a/src/shared/config/i18n/translations/ru.json +++ b/src/shared/config/i18n/translations/ru.json @@ -9,7 +9,8 @@ "formErrorHasOccurred": "Произошла ошибка формы", "fieldIsRequired": "Поле обязательно", "fieldPatternInvalid": "Значение поля не соответствует шаблону", - "invalidRegularExpression": "Некорректное регулярное выражение" + "invalidRegularExpression": "Некорректное регулярное выражение", + "integerError": "Значение поля должно быть целым числом" }, "auth": { "auth": "Авторизация", @@ -83,9 +84,9 @@ }, "file": { "b": "Б", - "kb": "КБ", - "mb": "МБ", - "gb": "ГБ", + "kib": "КиБ", + "mib": "МиБ", + "gib": "ГиБ", "fileFormat": "Формат файла", "selectFileFormat": "Выбрать формат файла", "compression": "Сжатие", @@ -179,7 +180,13 @@ "incremental": "инкрементальная", "target": "Цель", "basic": "Обычный", - "advanced": "Продвинутый" + "advanced": "Продвинутый", + "resources": "Ресурсы", + "minValue": "Мин. значение", + "maxValue": "Макс. значение", + "maxParallelTasks": "Макс. количество параллельных задач", + "cpuCoresPerTask": "Количество процессорных ядер на задачу", + "ramPerTask": "Количество оперативной памяти на задачу (ГиБ)" }, "transformation": { "transformations": "Трансформации:", diff --git a/src/shared/constants/errorMessage.ts b/src/shared/constants/errorMessage.ts new file mode 100644 index 00000000..408137b0 --- /dev/null +++ b/src/shared/constants/errorMessage.ts @@ -0,0 +1 @@ +export const INTEGER_ERROR_DISPLAY = 'integerError'; diff --git a/src/shared/constants/index.ts b/src/shared/constants/index.ts index 57b7d5a3..345ffeb2 100644 --- a/src/shared/constants/index.ts +++ b/src/shared/constants/index.ts @@ -2,3 +2,4 @@ export * from './storage'; export * from './antd'; export * from './regexp'; export * from './role'; +export * from './errorMessage'; diff --git a/src/shared/constants/regexp.ts b/src/shared/constants/regexp.ts index 355c14af..804892fe 100644 --- a/src/shared/constants/regexp.ts +++ b/src/shared/constants/regexp.ts @@ -3,3 +3,6 @@ export const ABSOLUTE_PATH_REGEXP = /^(?:[a-zA-Z]:\\|\/)/; /** Regexp to input only digits and digits with fractional part (e.g. 123.22) */ export const NUMBER_REGEXP = /\d*\.?\d+$/; + +/** Regexp to input only digits and (e.g. 354) */ +export const INTEGER_REGEXP = /^\d+$/; diff --git a/src/widgets/transfer/CreateTransfer/index.tsx b/src/widgets/transfer/CreateTransfer/index.tsx index cd2654c2..bc9d36a4 100644 --- a/src/widgets/transfer/CreateTransfer/index.tsx +++ b/src/widgets/transfer/CreateTransfer/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ManagedForm } from '@shared/ui'; import { useNavigate } from 'react-router-dom'; -import { Transfer, TransferQueryKey, transferService } from '@entities/transfer'; +import { prepareTransferResourcesRequest, Transfer, TransferQueryKey, transferService } from '@entities/transfer'; import { MutateTransferForm } from '@features/transfer'; import { prepareTransformationRequest } from '@entities/transformation'; import { useTranslation } from 'react-i18next'; @@ -13,10 +13,11 @@ export const CreateTransfer = ({ group }: CreateTransferProps) => { const { t } = useTranslation('transfer'); const navigate = useNavigate(); - const handleCreateTransfer = ({ transformations, ...values }: CreateTransferForm) => { + const handleCreateTransfer = ({ transformations, resources, ...values }: CreateTransferForm) => { return transferService.createTransfer({ group_id: group.id, transformations: prepareTransformationRequest(transformations), + resources: prepareTransferResourcesRequest(resources), ...values, }); }; diff --git a/src/widgets/transfer/UpdateTransfer/index.tsx b/src/widgets/transfer/UpdateTransfer/index.tsx index 3ebfd5a6..64532937 100644 --- a/src/widgets/transfer/UpdateTransfer/index.tsx +++ b/src/widgets/transfer/UpdateTransfer/index.tsx @@ -1,7 +1,13 @@ import React from 'react'; import { ManagedForm } from '@shared/ui'; import { useNavigate } from 'react-router-dom'; -import { Transfer, TransferQueryKey, transferService } from '@entities/transfer'; +import { + prepareTransferResourcesForm, + prepareTransferResourcesRequest, + Transfer, + TransferQueryKey, + transferService, +} from '@entities/transfer'; import { MutateTransferForm } from '@features/transfer'; import { prepareTransformationForm, prepareTransformationRequest } from '@entities/transformation'; import { useTranslation } from 'react-i18next'; @@ -13,11 +19,12 @@ export const UpdateTransfer = ({ transfer, group }: UpdateTransferProps) => { const { t } = useTranslation('transfer'); const navigate = useNavigate(); - const handleUpdateTransfer = ({ transformations, ...values }: UpdateTransferForm) => { + const handleUpdateTransfer = ({ transformations, resources, ...values }: UpdateTransferForm) => { return transferService.updateTransfer({ id: transferId, group_id, transformations: prepareTransformationRequest(transformations), + resources: prepareTransferResourcesRequest(resources), ...values, }); }; @@ -35,6 +42,7 @@ export const UpdateTransfer = ({ transfer, group }: UpdateTransferProps) => { initialValues={{ ...transferInitialValues, transformations: prepareTransformationForm(transferInitialValues.transformations), + resources: prepareTransferResourcesForm(transferInitialValues.resources), }} mutationFunction={handleUpdateTransfer} onSuccess={onSuccess}