[Fix] Forbid default value removal for non-nullable select field (#16465)
Permanently fixes https://github.com/twentyhq/private-issues/issues/389 On a non-nullable select field, default value should not be removable, otherwise users won't be able to create new records from the interface. This PR enforces this in FE and BE. https://github.com/user-attachments/assets/1dfa2bcc-b9df-4a00-9915-679d36ec1b25
This commit is contained in:
+6
@@ -17,6 +17,7 @@ import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
||||
import { toSpliced } from '~/utils/array/toSpliced';
|
||||
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
|
||||
|
||||
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
|
||||
import { AdvancedSettingsWrapper } from '@/settings/components/AdvancedSettingsWrapper';
|
||||
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
|
||||
import { useTheme } from '@emotion/react';
|
||||
@@ -119,6 +120,10 @@ export const SettingsDataModelFieldSelectForm = ({
|
||||
useSelectSettingsFormInitialValues({
|
||||
fieldMetadataId: existingFieldMetadataId,
|
||||
});
|
||||
const { fieldMetadataItem } = useFieldMetadataItemById(
|
||||
existingFieldMetadataId,
|
||||
);
|
||||
const isNullable = fieldMetadataItem?.isNullable;
|
||||
|
||||
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
|
||||
|
||||
@@ -340,6 +345,7 @@ export const SettingsDataModelFieldSelectForm = ({
|
||||
onChange(nextOptions);
|
||||
}}
|
||||
isDefault={isOptionDefaultValue(option.value)}
|
||||
fieldIsNullable={!!isNullable}
|
||||
onSetAsDefault={() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
|
||||
+45
-35
@@ -32,6 +32,7 @@ type SettingsDataModelFieldSelectFormOptionRowProps = {
|
||||
onInputEnter?: () => void;
|
||||
option: FieldMetadataItemOption;
|
||||
isNewRow?: boolean;
|
||||
fieldIsNullable?: boolean;
|
||||
};
|
||||
|
||||
const StyledRow = styled.div`
|
||||
@@ -76,6 +77,7 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
||||
onInputEnter,
|
||||
option,
|
||||
isNewRow,
|
||||
fieldIsNullable,
|
||||
}: SettingsDataModelFieldSelectFormOptionRowProps) => {
|
||||
const theme = useTheme();
|
||||
const SELECT_COLOR_DROPDOWN_ID = `select-color-dropdown-${option.id}`;
|
||||
@@ -84,6 +86,8 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
||||
const { closeDropdown: closeColorDropdown } = useCloseDropdown();
|
||||
const { closeDropdown: closeActionsDropdown } = useCloseDropdown();
|
||||
|
||||
const shouldForbidRemoveAsDefault = isDefault && !fieldIsNullable;
|
||||
|
||||
const handleInputEnter = () => {
|
||||
onInputEnter?.();
|
||||
};
|
||||
@@ -157,43 +161,49 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
||||
dropdownId={SELECT_ACTIONS_DROPDOWN_ID}
|
||||
dropdownPlacement="right-start"
|
||||
clickableComponent={
|
||||
<StyledLightIconButton accent="tertiary" Icon={IconDotsVertical} />
|
||||
<StyledLightIconButton
|
||||
accent="tertiary"
|
||||
Icon={IconDotsVertical}
|
||||
disabled={shouldForbidRemoveAsDefault}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
{isDefault ? (
|
||||
<MenuItem
|
||||
LeftIcon={IconX}
|
||||
text={t`Remove as default`}
|
||||
onClick={() => {
|
||||
onRemoveAsDefault?.();
|
||||
closeActionsDropdown(SELECT_ACTIONS_DROPDOWN_ID);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MenuItem
|
||||
LeftIcon={IconCheck}
|
||||
text={t`Set as default`}
|
||||
onClick={() => {
|
||||
onSetAsDefault?.();
|
||||
closeActionsDropdown(SELECT_ACTIONS_DROPDOWN_ID);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!!onRemove && !isDefault && (
|
||||
<MenuItem
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
text={t`Remove option`}
|
||||
onClick={() => {
|
||||
onRemove();
|
||||
closeActionsDropdown(SELECT_ACTIONS_DROPDOWN_ID);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
shouldForbidRemoveAsDefault ? null : (
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
{isDefault ? (
|
||||
<MenuItem
|
||||
LeftIcon={IconX}
|
||||
text={t`Remove as default`}
|
||||
onClick={() => {
|
||||
onRemoveAsDefault?.();
|
||||
closeActionsDropdown(SELECT_ACTIONS_DROPDOWN_ID);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MenuItem
|
||||
LeftIcon={IconCheck}
|
||||
text={t`Set as default`}
|
||||
onClick={() => {
|
||||
onSetAsDefault?.();
|
||||
closeActionsDropdown(SELECT_ACTIONS_DROPDOWN_ID);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!!onRemove && !isDefault && (
|
||||
<MenuItem
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
text={t`Remove option`}
|
||||
onClick={() => {
|
||||
onRemove();
|
||||
closeActionsDropdown(SELECT_ACTIONS_DROPDOWN_ID);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</StyledRow>
|
||||
|
||||
+11
@@ -158,6 +158,17 @@ export class FlatFieldMetadataValidatorService {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
flatFieldMetadataToValidate.isNullable === false &&
|
||||
flatFieldMetadataToValidate.defaultValue === null
|
||||
) {
|
||||
validationResult.errors.push({
|
||||
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
message: 'Default value cannot be null for non-nullable fields',
|
||||
userFriendlyMessage: msg`Default value cannot be null.`,
|
||||
});
|
||||
}
|
||||
|
||||
const fieldMetadataTypeValidationErrors =
|
||||
this.flatFieldMetadataTypeValidatorService.validateFlatFieldMetadataTypeSpecificities(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user