Add field isLabelSyncedWithName (#8829)

## Context
The recent addition of object renaming introduced issues with enum
names. Enum names should follow the pattern
`${schemaName}.${tableName}_${columnName}_enum`. To address this, and to
allow users to customize the API name (which is included in the enum
name, columnName), this PR implements behavior similar to object
renaming by introducing a `isLabelSyncedWithName` boolean.

<img width="624" alt="Screenshot 2024-12-02 at 11 58 49"
src="https://github.com/user-attachments/assets/690fb71c-83f0-4922-80c0-946c92dacc30">
<img width="596" alt="Screenshot 2024-12-02 at 11 58 39"
src="https://github.com/user-attachments/assets/af9a0037-7cf5-40c3-9ed5-d51b340c8087">
This commit is contained in:
Weiko
2024-12-03 13:22:12 +01:00
committed by GitHub
parent 7e4277fbe4
commit 3c7805c6d0
27 changed files with 1118 additions and 125 deletions

View File

@ -2,7 +2,7 @@ import { useApolloClient } from '@apollo/client';
import { zodResolver } from '@hookform/resolvers/zod';
import omit from 'lodash.omit';
import pick from 'lodash.pick';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom';
import {
@ -49,6 +49,7 @@ type SettingsDataModelFieldEditFormValues = z.infer<
export const SettingsObjectFieldEdit = () => {
const navigate = useNavigate();
const { enqueueSnackBar } = useSnackBar();
const [isPersisting, setIsPersisting] = useState(false);
const { objectSlug = '', fieldSlug = '' } = useParams();
const { findObjectMetadataItemBySlug } = useFilteredObjectMetadataItems();
@ -87,14 +88,16 @@ export const SettingsObjectFieldEdit = () => {
type: fieldMetadataItem?.type as SettingsFieldType,
label: fieldMetadataItem?.label ?? '',
description: fieldMetadataItem?.description,
isLabelSyncedWithName: fieldMetadataItem?.isLabelSyncedWithName ?? true,
},
});
useEffect(() => {
if (isPersisting) return;
if (!objectMetadataItem || !fieldMetadataItem) {
navigate(AppPath.NotFound);
}
}, [fieldMetadataItem, objectMetadataItem, navigate]);
}, [navigate, objectMetadataItem, fieldMetadataItem, isPersisting]);
const { isDirty, isValid, isSubmitting } = formConfig.formState;
const canSave = isDirty && isValid && !isSubmitting;
@ -125,6 +128,8 @@ export const SettingsObjectFieldEdit = () => {
}) ?? {};
if (isDefined(relationFieldMetadataItem)) {
setIsPersisting(true);
await updateOneFieldMetadataItem({
objectMetadataId: objectMetadataItem.id,
fieldMetadataIdToUpdate: relationFieldMetadataItem.id,
@ -141,6 +146,8 @@ export const SettingsObjectFieldEdit = () => {
Object.keys(otherDirtyFields),
);
setIsPersisting(true);
await updateOneFieldMetadataItem({
objectMetadataId: objectMetadataItem.id,
fieldMetadataIdToUpdate: fieldMetadataItem.id,
@ -155,6 +162,8 @@ export const SettingsObjectFieldEdit = () => {
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
});
} finally {
setIsPersisting(false);
}
};
@ -210,6 +219,9 @@ export const SettingsObjectFieldEdit = () => {
disabled={!fieldMetadataItem.isCustom}
fieldMetadataItem={fieldMetadataItem}
maxLength={FIELD_NAME_MAXIMUM_LENGTH}
canToggleSyncLabelWithName={
fieldMetadataItem.type !== FieldMetadataType.Relation
}
/>
</Section>
<Section>

View File

@ -29,6 +29,7 @@ import { H2Title, Section } from 'twenty-ui';
import { z } from 'zod';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { DEFAULT_ICONS_BY_FIELD_TYPE } from '~/pages/settings/data-model/constants/DefaultIconsByFieldType';
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
type SettingsDataModelNewFieldFormValues = z.infer<
@ -67,6 +68,7 @@ export const SettingsObjectNewFieldConfigure = () => {
DEFAULT_ICONS_BY_FIELD_TYPE[fieldType] ?? DEFAULT_ICON_FOR_NEW_FIELD,
label: '',
description: '',
name: '',
},
});
@ -134,12 +136,22 @@ export const SettingsObjectNewFieldConfigure = () => {
await createOneRelationMetadata({
relationType: relationFormValues.type,
field: pick(fieldFormValues, ['icon', 'label', 'description']),
field: pick(fieldFormValues, [
'icon',
'label',
'description',
'name',
'isLabelSyncedWithName',
]),
objectMetadataId: activeObjectMetadataItem.id,
connect: {
field: {
icon: relationFormValues.field.icon,
label: relationFormValues.field.label,
name:
(relationFormValues.field.isLabelSyncedWithName ?? true)
? computeMetadataNameFromLabel(relationFormValues.field.label)
: relationFormValues.field.name,
},
objectMetadataId: relationFormValues.objectMetadataId,
},
@ -204,6 +216,9 @@ export const SettingsObjectNewFieldConfigure = () => {
/>
<SettingsDataModelFieldIconLabelForm
maxLength={FIELD_NAME_MAXIMUM_LENGTH}
canToggleSyncLabelWithName={
fieldType !== FieldMetadataType.Relation
}
/>
</Section>
<Section>