-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support custom object renaming #7504
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
This PR implements custom object renaming functionality in the Twenty application. Key changes include:
- Added 'areLabelAndNameSync' field to ObjectMetadataEntity for controlling label and API name synchronization
- Updated SettingsDataModelObjectAboutForm with new UI elements for advanced settings and API name fields
- Modified ObjectMetadataService to handle object renaming and related metadata updates
- Introduced SyncObjectLabelAndNameToggle component for toggling synchronization
- Updated validation schemas and GraphQL queries to support the new renaming feature
20 file(s) reviewed, 10 comment(s)
Edit PR Review Bot Settings
packages/twenty-front/src/modules/settings/data-model/objects/SyncObjectLabelAndNameToggle.tsx
Outdated
Show resolved
Hide resolved
const areLabelAndNameSync = watch('areLabelAndNameSync'); | ||
const labelSingular = watch('labelSingular'); | ||
const labelPlural = watch('labelPlural'); | ||
const apiNameTooltipText = areLabelAndNameSync | ||
? 'Deactivate "Synchronize Objects Labels and API Names" to set a custom API name' | ||
: 'Input must be in camel case and cannot start with a number'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Consider memoizing these watch calls to optimize performance
useEffect(() => { | ||
if (areLabelAndNameSync) { | ||
labelSingular && | ||
setValue( | ||
'nameSingular', | ||
computeMetadataNameFromLabelOrThrow(labelSingular), | ||
{ shouldDirty: true }, | ||
); | ||
} | ||
}, [areLabelAndNameSync, labelSingular, setValue]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Consider combining this effect with the previous one to reduce code duplication
)} | ||
/> | ||
</StyledSectionWrapper> | ||
<AnimatePresence> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Large conditional render block may impact performance. Consider extracting to a separate component
private async updateObjectNamesandLabels( | ||
oldObject: ObjectMetadataEntity, | ||
updatedObject: ObjectMetadataEntity, | ||
input: UpdateOneObjectInput, | ||
) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syntax: Method name has a typo: 'updateObjectNamesandLabels' should be 'updateObjectNamesAndLabels'
); | ||
} | ||
|
||
if (updatedObject.areLabelAndNameSync) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: This block doesn't handle the case where both label and name are updated simultaneously. Consider adding a check for this scenario
updatedObjectWithSyncedName, | ||
); | ||
} else { | ||
if (input.update.nameSingular || input.update.namePlural) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Similar to the previous comment, this block doesn't handle simultaneous updates to both singular and plural names
@@ -40,6 +43,8 @@ const reservedKeywords = [ | |||
'addresses', | |||
]; | |||
|
|||
const METADATA_NAME_VALID_PATTERN = /^[a-zA-Z][a-zA-Z0-9]*$/; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Consider using a more descriptive name for this regex constant, e.g., VALID_METADATA_NAME_PATTERN
); | ||
|
||
if (!formattedString.match(METADATA_NAME_VALID_PATTERN)) { | ||
throw new Error(`"${string}" is not a valid name`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: This error message could be more specific about why the name is invalid
if (formattedString.match(METADATA_NAME_VALID_PATTERN) !== null) { | ||
return toCamelCase(formattedString); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: This condition might allow some invalid names through. Consider using a stricter check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
(updates since last review)
This PR continues the implementation of custom object renaming functionality in the Twenty application. The main changes include:
- Added 'areLabelAndNameSync' property to various test files and schemas
- Updated GraphQL queries and mutations to support the new renaming feature
- Modified ObjectMetadataService to handle object renaming and related metadata updates
Key points to highlight:
- The 'areLabelAndNameSync' property has been added to multiple test files, reflecting the new feature for syncing labels and API names in custom objects
- The settingsCreateObjectInputSchema and settingsUpdateObjectInputSchema have been updated to include new fields for custom object renaming
- The ObjectMetadataService has been modified to improve handling of object name and label updates
The changes align with the requirements outlined in the related issue, particularly in supporting independent API names and labels for custom objects.
8 file(s) reviewed, 2 comment(s)
Edit PR Review Bot Settings
); | ||
} | ||
|
||
if (updatedObject.areLabelAndNameSync) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: This block doesn't handle the case where both label and name are updated simultaneously. Consider adding a check for this scenario
updatedObjectWithSyncedName, | ||
); | ||
} else { | ||
if (input.update.nameSingular || input.update.namePlural) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Similar to the previous comment, this block doesn't handle simultaneous updates to both singular and plural names
indicated on discord as actually still in internal review, @gitstart-twenty can you confirm? |
Hi @ijreilly, this PR is ready for your review. We have clarified it here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, I have noticed some important errors/unexpected behaviours while testing
- When advanced settings are on, if label plural is updated, it updates the label singular, I am not sure this is expected? (see video)
- When advanced settings are on, if I am only updating the label singular, it says object already exists, probably because it tries to save the object with the same name that is already taken (i suppose) (also in the video)
- Label updates does not seem to work, I updated it and still see the old "Rockets" label everywhere
Can you please make sure to fully test the feature:
- update of any of the labelSingular, labelPlural, nameSingular, namePlural independently works: check on the app + in the database (fieldMetadata, name of columns etc)
- no weird behaviour on the form like unexpected dependencies between form fields (try updating a field then another, hiding and showing advanced mode, etc.)
Let us know if we can help!
errors1and2.mov
@@ -189,6 +189,7 @@ export type CreateFieldInput = { | |||
}; | |||
|
|||
export type CreateObjectInput = { | |||
areLabelAndNameSync?: InputMaybe<Scalars['Boolean']['input']>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we rename to shouldSyncLabelAndName?
packages/twenty-front/src/modules/settings/data-model/objects/SyncObjectLabelAndNameToggle.tsx
Outdated
Show resolved
Hide resolved
const { contentRef, motionAnimationVariants } = useExpandedHeightAnimation( | ||
isAdvancedModeEnabled, | ||
); | ||
const infoCircleElementId = `info-circle-id-${Math.random().toString(36).slice(2)}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this is used elsewhere but why is this id computed like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we were using this to avoid duplicated ids, but the duplication is not happening, so we will update this
@@ -111,7 +111,8 @@ export class BeforeUpdateOneObject<T extends UpdateObjectPayload> | |||
) { | |||
if ( | |||
update.nameSingular && | |||
update.nameSingular !== objectMetadata.nameSingular | |||
update.nameSingular !== objectMetadata.nameSingular && | |||
!objectMetadata.isCustom |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure about this condition here. Shouldn't it be
if (update.nameSingular && areLabelAndNameSync && (update.nameSingular !== objectMetadata.nameSingular))
? this check only being valid if label and name are sync
@@ -120,22 +121,28 @@ export class BeforeUpdateOneObject<T extends UpdateObjectPayload> | |||
|
|||
if ( | |||
update.labelSingular && | |||
update.labelSingular !== objectMetadata.labelSingular | |||
update.labelSingular !== objectMetadata.labelSingular && | |||
!objectMetadata.isCustom | |||
) { | |||
throw new BadRequestException( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually with this feature we want to enable udpate of label and names !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with the changes in this file we are enabling the update but only for custom objects
@@ -441,7 +445,29 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt | |||
): Promise<ObjectMetadataEntity> { | |||
validateObjectMetadataInputOrThrow(input.update); | |||
|
|||
const updatedObject = await super.updateOne(input.id, input.update); | |||
const oldObject = await this.objectMetadataRepository.findOne({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const oldObject = await this.objectMetadataRepository.findOne({ | |
const existingObjectMetadata = await this.objectMetadataRepository.findOne({ |
oldObject: ObjectMetadataEntity, | ||
updatedObject: ObjectMetadataEntity, | ||
) { | ||
const oldTableName = computeObjectTargetTable(oldObject); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const oldTableName = computeObjectTargetTable(oldObject); | |
const existingTableName = computeObjectTargetTable(oldObject); |
name: `${oldObject.nameSingular}Id`, | ||
}; | ||
|
||
const fieldWithNonCustomRelation = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const fieldWithNonCustomRelation = | |
const fieldWihStandardRelation = |
Hello @ijreilly |
Hi @gitstart-twenty, thanks for your work I will take it from here ! |
I'm a bit overwhelmed, @Weiko will take the review on this one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
This PR was created by GitStart to address the requirements from this ticket: TWNTY-5491.
This ticket was imported from: TWNTY-5491
Description
How To Test:\
Reset db using
npx nx database:reset twenty-server
on this PRRun both backend and frontend
Navigate to
settings/data-model/objects/
pageSelect a
Custom
object from the list or create a newCustom
objectNavigate to custom object details page and click on edit button
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?
### Demo
https://www.loom.com/share/64ecb57efad7498d99085cb11480b5dd?sid=28d0868c-e54f-454d-8432-3f789be9e2b7
Refs
#5491