-
Notifications
You must be signed in to change notification settings - Fork 4.6k
feat: resolve preview merge record uses a fake one #15834
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
Conversation
|
🚀 Preview Environment Ready! Your preview environment is available at: http://bore.pub:60847 This environment will automatically shut down when the PR is closed or after 5 hours. |
packages/twenty-front/src/modules/object-record/record-merge/hooks/useMergePreview.ts
Show resolved
Hide resolved
Greptile Summary
Confidence Score: 2/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant useMergePreview
participant mergePreviewRecordFamilyState
participant useMergeManyRecords
participant Backend
participant recordStoreSelectors
User->>useMergePreview: "selects records to merge"
useMergePreview->>useMergePreview: "computes previewSignature"
useMergePreview->>useMergeManyRecords: "mergeManyRecords(preview: true)"
useMergeManyRecords->>Backend: "mutation with dryRun: true, no-cache"
Backend-->>useMergeManyRecords: "returns preview record"
useMergeManyRecords-->>useMergePreview: "returns preview record"
useMergePreview->>mergePreviewRecordFamilyState: "stores preview record"
User->>recordStoreSelectors: "views merged preview"
recordStoreSelectors->>mergePreviewRecordFamilyState: "checks for preview record first"
mergePreviewRecordFamilyState-->>recordStoreSelectors: "returns preview data"
recordStoreSelectors-->>User: "displays preview"
User->>useMergeManyRecords: "confirms merge"
useMergeManyRecords->>Backend: "mutation with dryRun: false"
Backend-->>useMergeManyRecords: "returns merged record"
useMergeManyRecords->>useMergeManyRecords: "triggers refetchQueries"
|
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.
7 files reviewed, 2 comments
Edit Code Review Agent Settings | Greptile
React with 👍 or 👎 to share your feedback on this new summary format
packages/twenty-front/src/modules/object-record/record-merge/hooks/useMergePreview.ts
Outdated
Show resolved
Hide resolved
packages/twenty-front/src/modules/object-record/record-merge/hooks/useMergePreview.ts
Show resolved
Hide resolved
| const [lastPreviewId, setLastPreviewId] = useState<string>(''); | ||
| const [lastPreviewSignature, setLastPreviewSignature] = useState<string>(''); | ||
| // eslint-disable-next-line @nx/workspace-no-state-useref | ||
| const currentRunIdRef = useRef(0); |
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.
@harshit078 we don't use useRef in the project
| commandMenuNavigationMorphItemsByPage | ||
| .get(CommandMenuPages.MergeRecords) | ||
| ?.map((morphItem) => morphItem.recordId) ?? []; | ||
| const selectedRecordIds = useMemo( |
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.
useMemo does not seems necessary here?
| ({ set }) => | ||
| (record: ObjectRecord | null, previousId: string) => { | ||
| if (previousId !== '') { | ||
| set(mergePreviewRecordFamilyState(previousId), null); |
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'm not sure about this, this does not seem robust, why do we need to clean the state?
| if (previousId !== '') { | ||
| set(mergePreviewRecordFamilyState(previousId), null); | ||
| } | ||
| if (record !== null) { |
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.
use isDefined, isNonEmptyString helpers
| useEffect(() => { | ||
| const fetchPreview = async () => { | ||
| if (selectedRecords.length < 2 || isMergeInProgress || isInitialized) | ||
| if (selectedRecordIds.length < 2 || isMergeInProgress) { |
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.
this code is not robust IMO, isInitialized seemed right. What issue are we trying to solve?
charlesBochet
left a comment
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.
@harshit078 ty
Feedbacks:
- let's not use useRef. What we should aim for: hooks that are called synchronously. Here, I think we should add records to the mergePreviewFamilyState in useOpenMergeRecordsPageInCommandMenu for instance. this would remove the need for useRef, useEffect, useMemo and all other hacks.
I think we should not try to clean the mergePreviewFamilyState, it does not worth it.
Also, I'm not convinced by the fact that recordStore is now loading data from two different sources but why not, it does not harm too much
|
@charlesBochet Hi charles, thanks for the comments. I am super aware that Twenty ooesnt use useRef but I couldnt find another approach round it which would pass lint test and was ideal. I'll address the comments and refactor the approach thanks ! |
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.
Pull Request Overview
This PR introduces a dedicated Recoil atom mergePreviewRecordFamilyState to store merge preview records separately from the main record store, preventing pollution of recordStoreFamilyState during merge operations.
Key Changes:
- Created
mergePreviewRecordFamilyStateto store preview records independently - Updated record store selectors to check preview state before falling back to main store
- Refactored
useMergePreviewhook to manage preview records in the new state and handle race conditions with run IDs
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
mergePreviewRecordFamilyState.ts |
New Recoil family state atom for storing merge preview records |
recordStoreFamilySelector.ts |
Updated to read from preview state first, then fall back to main store |
recordStoreIdentifierSelector.ts |
Added preview record check before reading from main store |
recordStoreIdentifiersSelector.ts |
Added preview record check in bulk identifier retrieval |
recordStoreFieldValueSelector.ts |
Enhanced to prioritize preview records for both regular and morph relation fields |
useMergePreview.ts |
Refactored to use new preview state with race condition handling via run IDs |
useMergeManyRecords.ts |
Improved conditional logic for preview vs. non-preview query options |
RecordTitleCellTextFieldDisplay.tsx |
Changed to use selector-based field value retrieval instead of direct record access |
Comments suppressed due to low confidence (1)
packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFamilySelector.ts:23
- The
setmethod of this selector doesn't handle preview records. When a field value is set, it always writes torecordStoreFamilyStateeven if the current value is being read from a preview record inmergePreviewRecordFamilyState. This could cause inconsistent behavior where reads come from the preview but writes go to the main store.
Consider adding logic to the set method to handle preview records appropriately, or document that this selector is read-only for preview records.
set:
<T>({ fieldName, recordId }: { fieldName: string; recordId: string }) =>
({ set }, newValue: T) =>
set(recordStoreFamilyState(recordId), (prevState) =>
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| mergeSettings, | ||
| setPreviewRecordById, | ||
| lastPreviewId, | ||
| lastPreviewSignature, |
Copilot
AI
Nov 20, 2025
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.
[nitpick] The dependency array includes lastPreviewSignature which is compared inside the effect on line 80. This creates a circular dependency: the effect updates lastPreviewSignature, which triggers the effect again. However, the early return on line 80-82 prevents infinite loops. While this works, it's an unconventional pattern. Consider restructuring to avoid having state that's both read and written in the same effect appear in the dependency array.
|
|
||
| const isEmpty = | ||
| recordValue?.[fieldDefinition.metadata.fieldName]?.trim() === ''; | ||
| const isEmpty = fieldValue?.toString().trim() === ''; |
Copilot
AI
Nov 20, 2025
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.
Using .toString() on fieldValue could produce unexpected results for complex field types (objects, arrays, null). For example, objects would display as [object Object]. Consider type checking and handling different field types appropriately, or use the original field value access pattern with proper typing.
| recordValue?.[fieldDefinition.metadata.fieldName] || | ||
| fieldDefinition.label | ||
| } | ||
| text={fieldValue?.toString() || fieldDefinition.label} |
Copilot
AI
Nov 20, 2025
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.
Similar to line 48, using .toString() on fieldValue can produce unexpected results for non-string field types. Objects would display as [object Object]. Consider ensuring the field value is properly typed as a string or handle different types appropriately.
| }, [ | ||
| selectedRecords, | ||
| mergeSettings, | ||
| previewSignature, |
Copilot
AI
Nov 20, 2025
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.
The previewSignature is included in the dependency array but uses values that are already in the array (selectedRecordIds and mergeSettings.conflictPriorityIndex). Since previewSignature is derived from these values using useMemo, including it creates redundant dependencies. Remove previewSignature from the dependency array and keep only the primitive dependencies.
| previewSignature, |
| // eslint-disable-next-line @nx/workspace-no-state-useref | ||
| const currentRunIdRef = useRef(0); |
Copilot
AI
Nov 20, 2025
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.
[nitpick] The comment eslint-disable-next-line @nx/workspace-no-state-useref suggests that using useRef here might violate a workspace rule. While the use of useRef for tracking run IDs is appropriate to prevent race conditions, consider if there's an alternative pattern that complies with your workspace rules or document why this exception is necessary.
|
@harshit078 I'm going to close it for now, let's re-open when ready |
Description
mergePreviewRecordFamilyStatewhich avoiding pollution of therecordStoreFamilyState