import React from 'react'; import * as NiceModal from '@ebay/nice-modal-react'; import type {DialogFooterProps} from '@gravity-ui/uikit'; import {Dialog, Flex, Switch, Text, TextInput} from '@gravity-ui/uikit'; import {Controller} from 'react-hook-form'; import {Loader} from '../../../../../../components/Loader'; import {configsApi} from '../../../../../../store/reducers/configs'; import {cn} from '../../../../../../utils/cn'; import {BRAND_BUTTON_CLASS} from '../../../../../../utils/constants'; import {formatNumber} from '../../../../../../utils/dataFormatters/dataFormatters'; import {prepareErrorMessage} from '../../../../../../utils/prepareErrorMessage'; import {SplitUnitSelect} from './SplitUnitSelect'; import {MANAGE_PARTITIONING_DIALOG, UNIT_OPTIONS} from './constants'; import i18n from './i18n'; import type {ManagePartitioningFormOutput, ManagePartitioningFormState} from './types'; import {useManagePartitioningForm} from './useManagePartitionForm'; import {getMaxSplitSizeBytes, getMaxSplitSizeGb} from './utils'; import './ManagePartitioningDialog.scss'; const b = cn(MANAGE_PARTITIONING_DIALOG); interface CommonDialogProps { initialValue?: ManagePartitioningFormState; database?: string; // The form output is produced after Zod coercion, so numeric fields are // numbers (ManagePartitioningFormOutput), not the raw string form state. onApply?: (value: ManagePartitioningFormOutput) => void | Promise; } interface ManagePartitioningDialogNiceModalProps extends CommonDialogProps, DialogFooterProps { onClose?: () => void; } interface ManagePartitioningDialogProps extends CommonDialogProps, DialogFooterProps { onClose: () => void; open: boolean; } interface ManagePartitioningDialogFormProps extends DialogFooterProps { onClose: () => void; initialValue?: ManagePartitioningFormState; onApply?: (value: ManagePartitioningFormOutput) => void | Promise; maxSplitSizeBytes?: number; } // The form is created only after the config request resolves and the limit is // either known or unavailable. This guarantees react-hook-form initializes (and // computes `isValid`) against the correct schema. function ManagePartitioningDialogForm({ onClose, renderButtons, initialValue, onApply, maxSplitSizeBytes, }: ManagePartitioningDialogFormProps) { const [apiError, setApiError] = React.useState(null); const [isSubmitting, setIsSubmitting] = React.useState(false); // When available, the displayed maximum is floored to match the exact byte // limit used by validation. Rounding could show a value that gets rejected. const maxSplitSizeGb = React.useMemo( () => (maxSplitSizeBytes === undefined ? undefined : getMaxSplitSizeGb(maxSplitSizeBytes)), [maxSplitSizeBytes], ); const { control, handleSubmit, watch, trigger, formState: {errors, isValid}, } = useManagePartitioningForm({ initialValue, maxSplitSizeBytes, }); const splitSizeEnabled = watch('splitSizeEnabled'); const handleApply = handleSubmit(async (data) => { setApiError(null); setIsSubmitting(true); try { await onApply?.(data); } catch (error) { setApiError(prepareErrorMessage(error)); } finally { setIsSubmitting(false); } }); return (
{i18n('title_partitioning')} ( { field.onChange(next); trigger('splitSize'); }} /> )} /> {splitSizeEnabled ? ( ( ( { unitField.onChange(nextUnit); trigger('splitSize'); }} /> )} /> } /> )} /> {typeof maxSplitSizeBytes === 'number' ? ( {i18n('context_split-size-maximum', { maxGb: maxSplitSizeGb, })} ) : null} ) : null} ( )} /> {i18n('title_limits')} ( { field.onChange(next); trigger('maximum'); // revalidate dependent field to clear stale error }} className={b('input')} errorMessage={errors.minimum?.message} validationState={errors.minimum ? 'invalid' : undefined} /> )} /> ( { field.onChange(next); trigger('minimum'); // revalidate dependent field to clear stale error }} className={b('input')} errorMessage={errors.maximum?.message} validationState={errors.maximum ? 'invalid' : undefined} /> )} /> {apiError && (
{apiError}
)}
); } function ManagePartitioningDialog({ onClose, open, renderButtons, initialValue, database, onApply, }: ManagePartitioningDialogProps) { const {currentData: config, isLoading, isError} = configsApi.useGetConfigQuery({database}); // Maximum split size is taken from the database config field // ImmediateControlsConfig.SchemeShardControls.ForceShardSplitDataSize // (falls back to the default 2 GiB when it is not present). If the config // request fails, the limit is unknown and must not be enforced in the UI. const maxSplitSizeBytes = React.useMemo( () => (isError ? undefined : getMaxSplitSizeBytes(config?.current)), [config, isError], ); return ( {i18n('title_manage-partitioning')}} /> {isLoading ? ( ) : ( )} ); } export const ManagePartitioningDialogNiceModal = NiceModal.create( (props: ManagePartitioningDialogNiceModalProps) => { const modal = NiceModal.useModal(); const handleClose = () => { modal.hide(); modal.remove(); }; return ( { await props.onApply?.(value); modal.resolve(value); handleClose(); }} onClose={() => { props.onClose?.(); modal.resolve(null); handleClose(); }} open={modal.visible} /> ); }, ); NiceModal.register(MANAGE_PARTITIONING_DIALOG, ManagePartitioningDialogNiceModal); export function openManagePartitioningDialog( props?: Omit, ): Promise { return NiceModal.show(MANAGE_PARTITIONING_DIALOG, { id: MANAGE_PARTITIONING_DIALOG, ...props, }) as Promise; }