Support custom object renaming (#7504)

This PR was created by [GitStart](https://gitstart.com/) to address the
requirements from this ticket:
[TWNTY-5491](https://clients.gitstart.com/twenty/5449/tickets/TWNTY-5491).
This ticket was imported from:
[TWNTY-5491](https://github.com/twentyhq/twenty/issues/5491)

 --- 

### Description

**How To Test:**\
1. Reset db using `npx nx database:reset twenty-server` on this PR

1. Run both backend and frontend
2. Navigate to `settings/data-model/objects/ `page
3. Select a `Custom `object from the list or create a new `Custom
`object
4. Navigate to custom object details page and click on edit button
5. Finally edit the object details.

**Issues and bugs**
The Typecheck is failing but we could not see this error locally
There is a bug after updating the label of a custom object. View title
is not updated till refreshing the page. We could not find a consistent
way to update this, should we reload the page after editing an object?


![](https://assets-service.gitstart.com/45430/03cd560f-a4f6-4ce2-9d78-6d3a9f56d197.png)###
Demo



<https://www.loom.com/share/64ecb57efad7498d99085cb11480b5dd?sid=28d0868c-e54f-454d-8432-3f789be9e2b7>

### Refs

#5491

---------

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: gitstart-twenty <140154534+gitstart-twenty@users.noreply.github.com>
Co-authored-by: Marie Stoppa <marie.stoppa@essec.edu>
Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
gitstart-app[bot]
2024-10-24 11:52:30 +00:00
committed by GitHub
parent c6ef14acc4
commit 414f2ac498
29 changed files with 900 additions and 192 deletions

View File

@ -1,5 +1,6 @@
/* eslint-disable react/jsx-props-no-spreading */
import { useLastVisitedObjectMetadataItem } from '@/navigation/hooks/useLastVisitedObjectMetadataItem';
import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
@ -20,13 +21,16 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { zodResolver } from '@hookform/resolvers/zod';
import pick from 'lodash.pick';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { Button, H2Title, IconArchive } from 'twenty-ui';
import { z } from 'zod';
import { computeMetadataNameFromLabelOrThrow } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils';
const objectEditFormSchema = z
.object({})
@ -45,6 +49,9 @@ export const SettingsObjectEdit = () => {
const { findActiveObjectMetadataItemBySlug } =
useFilteredObjectMetadataItems();
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
const { lastVisitedObjectMetadataItemId } =
useLastVisitedObjectMetadataItem();
const { getLastVisitedViewIdFromObjectMetadataItemId } = useLastVisitedView();
const activeObjectMetadataItem =
findActiveObjectMetadataItemBySlug(objectSlug);
@ -56,6 +63,10 @@ export const SettingsObjectEdit = () => {
resolver: zodResolver(objectEditFormSchema),
});
const setNavigationMemorizedUrl = useSetRecoilState(
navigationMemorizedUrlState,
);
useEffect(() => {
if (!activeObjectMetadataItem) navigate(AppPath.NotFound);
}, [activeObjectMetadataItem, navigate]);
@ -65,25 +76,72 @@ export const SettingsObjectEdit = () => {
const { isDirty, isValid, isSubmitting } = formConfig.formState;
const canSave = isDirty && isValid && !isSubmitting;
const handleSave = async (
const getUpdatePayload = (
formValues: SettingsDataModelObjectEditFormValues,
) => {
let values = formValues;
if (
formValues.shouldSyncLabelAndName ??
activeObjectMetadataItem.shouldSyncLabelAndName
) {
values = {
...values,
...(values.labelSingular
? {
nameSingular: computeMetadataNameFromLabelOrThrow(
formValues.labelSingular,
),
}
: {}),
...(values.labelPlural
? {
namePlural: computeMetadataNameFromLabelOrThrow(
formValues.labelPlural,
),
}
: {}),
};
}
const dirtyFieldKeys = Object.keys(
formConfig.formState.dirtyFields,
) as (keyof SettingsDataModelObjectEditFormValues)[];
return settingsUpdateObjectInputSchema.parse(
pick(values, [
...dirtyFieldKeys,
...(values.namePlural ? ['namePlural'] : []),
...(values.nameSingular ? ['nameSingular'] : []),
]),
);
};
const handleSave = async (
formValues: SettingsDataModelObjectEditFormValues,
) => {
try {
const updatePayload = getUpdatePayload(formValues);
await updateOneObjectMetadataItem({
idToUpdate: activeObjectMetadataItem.id,
updatePayload: settingsUpdateObjectInputSchema.parse(
pick(formValues, dirtyFieldKeys),
),
updatePayload,
});
const objectNamePluralForRedirection =
updatePayload.namePlural ?? activeObjectMetadataItem.namePlural;
if (lastVisitedObjectMetadataItemId === activeObjectMetadataItem.id) {
const lastVisitedView = getLastVisitedViewIdFromObjectMetadataItemId(
activeObjectMetadataItem.id,
);
setNavigationMemorizedUrl(
`/objects/${objectNamePluralForRedirection}?view=${lastVisitedView}`,
);
}
navigate(
`${settingsObjectsPagePath}/${getObjectSlug({
...formValues,
namePlural: formValues.labelPlural,
...updatePayload,
namePlural: objectNamePluralForRedirection,
})}`,
);
} catch (error) {
@ -142,7 +200,7 @@ export const SettingsObjectEdit = () => {
/>
<SettingsDataModelObjectAboutForm
disabled={!activeObjectMetadataItem.isCustom}
disableNameEdit
disableNameEdit={!activeObjectMetadataItem.isCustom}
objectMetadataItem={activeObjectMetadataItem}
/>
</Section>

View File

@ -2,5 +2,8 @@ import { METADATA_NAME_VALID_PATTERN } from '~/pages/settings/data-model/constan
import { transliterateAndFormatOrThrow } from '~/pages/settings/data-model/utils/transliterate-and-format.utils';
export const computeMetadataNameFromLabelOrThrow = (label: string): string => {
if (label === '') {
return '';
}
return transliterateAndFormatOrThrow(label, METADATA_NAME_VALID_PATTERN);
};