import React from 'react'; import {Key, Plus, TrashBin} from '@gravity-ui/icons'; import { Button, Disclosure, HelpMark, Icon, SegmentedRadioGroup, Switch, Text, TextInput, } from '@gravity-ui/uikit'; import {Controller, useFormContext, useWatch} from 'react-hook-form'; import type {ColumnValueField} from '../../../../store/reducers/table/types'; import {prepareColumnValue} from '../../../../store/reducers/table/utils'; import {cn} from '../../../../utils/cn'; import {RangeInputPicker} from '../components/RangeInputPicker'; import {FormFieldError, FormRow, FormSection} from '../components/layout'; import {MAX_PARTITION_SIZE_MB, MIN_PARTITION_SIZE_MB} from '../constants'; import i18n from '../i18n'; import type {ColumnField, FormMode, FormValues} from '../types'; import {PartitionsType} from '../types'; import { acceptIntegerInput, formatOptionalIntegerInput, parseOptionalIntegerInput, partitionsTypeOptions, } from '../utils'; import type {SplitPointDialogState} from './SplitPointDialog'; import {SplitPointDialog, buildSplitPointEntries} from './SplitPointDialog'; const b = cn('ydb-table-form-dialog'); function formatPartitionSizeMark(value: number) { return `${value} ${i18n('value_megabyte')}`; } interface SettingsSectionProps { mode: FormMode; } export function SettingsSection({mode}: SettingsSectionProps) { const {control, setValue, getValues, trigger, formState} = useFormContext(); const partitionsType = useWatch({control, name: 'settings.partitionsType'}); const autoPartitionBySize = useWatch({control, name: 'settings.autoPartitionBySize'}); const autoPartitionByLoad = useWatch({control, name: 'settings.autoPartitionByLoad'}); const autoPartitionMinPartitions = useWatch({ control, name: 'settings.autoPartitionMinPartitions', }); const autoPartitionMaxPartitions = useWatch({ control, name: 'settings.autoPartitionMaxPartitions', }); const minMaxDisabled = !autoPartitionBySize && !autoPartitionByLoad; const uniformError = formState.errors.settings?.uniformPartitions?.message; const minPartitionsError = formState.errors.settings?.autoPartitionMinPartitions?.message; const maxPartitionsError = formState.errors.settings?.autoPartitionMaxPartitions?.message; const partitionsAtKeysError = formState.errors.settings?.partitionsAtKeys?.message; const [splitDialogState, setSplitDialogState] = React.useState({ open: false, index: 0, columns: [], values: [], }); const splitPointsRaw = useWatch({control, name: 'settings.partitionsAtKeys'}); const splitPoints = React.useMemo( () => splitPointsRaw ?? [], [splitPointsRaw], ); const columns = useWatch({control, name: 'columns'}); const splitPointPlaceholder = React.useMemo(() => { const pkNames = columns .filter(({key}) => Boolean(key)) .map(({name}) => name) .join(', '); return pkNames ? `(${pkNames})` : ''; }, [columns]); const appendSplit = React.useCallback(() => { const current = (getValues('settings.partitionsAtKeys') ?? []) as ColumnValueField[][]; setValue('settings.partitionsAtKeys', [...current, []], {shouldValidate: true}); }, [getValues, setValue]); const removeSplit = React.useCallback( (index: number) => { const current = (getValues('settings.partitionsAtKeys') ?? []) as ColumnValueField[][]; setValue( 'settings.partitionsAtKeys', current.filter((_, i) => i !== index), {shouldValidate: true}, ); }, [getValues, setValue], ); const openSplitDialog = React.useCallback( (index: number) => { const allColumns = getValues('columns') as ColumnField[]; const pkColumns = allColumns.filter(({key}) => Boolean(key)); const current = (getValues('settings.partitionsAtKeys') ?? []) as ColumnValueField[][]; const stored = current[index] ?? []; const draftValues = buildSplitPointEntries(pkColumns, stored); setSplitDialogState({ open: true, index, columns: pkColumns.map(({name, type, notNull}) => ({name, type, notNull})), values: draftValues, }); }, [getValues], ); const closeSplitDialog = React.useCallback(() => { setSplitDialogState((prev) => ({...prev, open: false})); }, []); const handleSplitSubmit = React.useCallback( (index: number, values: ColumnValueField[]) => { const current = (getValues('settings.partitionsAtKeys') ?? []) as ColumnValueField[][]; const next = current.map((row, i) => (i === index ? values : row)); setValue('settings.partitionsAtKeys', next, {shouldValidate: true}); }, [getValues, setValue], ); const handleAutoPartitionBySizeUpdate = React.useCallback( (enabled: boolean, onChange: (value: boolean) => void) => { if (enabled) { const currentSize = getValues('settings.autoPartitionBySizeMb'); if (typeof currentSize !== 'number' || Number.isNaN(currentSize)) { setValue('settings.autoPartitionBySizeMb', MAX_PARTITION_SIZE_MB, { shouldDirty: true, shouldValidate: true, }); } } onChange(enabled); }, [getValues, setValue], ); React.useEffect(() => { trigger([ 'settings.autoPartitionMinPartitions', 'settings.autoPartitionMaxPartitions', ]).catch(() => undefined); }, [autoPartitionMaxPartitions, autoPartitionMinPartitions, trigger]); return ( {mode === 'create' ? ( ( {partitionsTypeOptions.map((option) => ( {option.content} ))} )} /> {partitionsType === PartitionsType.Uniform ? ( ( { if (!acceptIntegerInput(value)) { return; } field.onChange(parseOptionalIntegerInput(value)); }} validationState={uniformError ? 'invalid' : undefined} errorMessage={uniformError} /> )} /> ) : null} {partitionsType === PartitionsType.Explicit ? (
{i18n('field_explicit-partitions')} {i18n('tooltip_explicit-partitions')}
{splitPoints.map((row, index) => { const display = row .map((column) => prepareColumnValue(column, column.value), ) .join(', '); return (
{`${index + 1}.`} {splitPoints.length > 1 ? ( ) : null}
); })}
) : null}
) : null} (
{ handleAutoPartitionBySizeUpdate(value, field.onChange); }} />
)} />
(
)} />
{i18n('label_section-advanced')} } className={b('disclosure')} >
{ const value = typeof field.value === 'number' && !Number.isNaN(field.value) ? field.value : undefined; return ( {i18n('value_megabyte')} } /> ); }} /> ( { if (!acceptIntegerInput(value)) { return; } field.onChange(parseOptionalIntegerInput(value)); }} disabled={minMaxDisabled} validationState={minPartitionsError ? 'invalid' : undefined} errorMessage={minPartitionsError} /> )} /> ( { if (!acceptIntegerInput(value)) { return; } field.onChange(parseOptionalIntegerInput(value)); }} disabled={minMaxDisabled} validationState={maxPartitionsError ? 'invalid' : undefined} errorMessage={maxPartitionsError} /> )} /> (
)} />
); }