(Fix) Upsert no longer rewrites position on existing records#21375
(Fix) Upsert no longer rewrites position on existing records#21375ijreilly wants to merge 4 commits into
position on existing records#21375Conversation
|
👋 Thanks for contributing to Twenty! Your PR has been set to draft while you work on it. Once you're done, mark it as Ready for review and our automated checks will run. Looking forward to your contribution! |
There was a problem hiding this comment.
Pull request overview
This PR fixes an upsert-side effect where createX(..., upsert: true) was unintentionally recomputing and rewriting position for records that resolve to updates (when the payload omitted position). The change aligns upsert behavior with existing update runners by only backfilling position for newly inserted rows.
Changes:
- Disable
positionbackfill during argument processing whenupsert: true(create-one and create-many paths). - Backfill
"first"positions only forrecordsToInsertafter upsert categorization, viaRecordPositionService. - Add integration tests covering “upsert update without position does not change position” and “upsert insert assigns a position”.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| packages/twenty-server/test/integration/graphql/suites/upsert/upsert.integration-spec.ts | Adds integration coverage for position behavior on upsert update vs insert. |
| packages/twenty-server/src/engine/api/common/common-query-runners/common-create-one-query-runner.service.ts | Prevents position backfill during create-one arg processing when upsert: true. |
| packages/twenty-server/src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service.ts | Moves position backfill to post-categorization and applies it only to insert candidates during upsert. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…date-position-each-time-we-upsert
🔍 Automated Pre-Review✅ No issues detected - This PR is ready for human review. Automated pre-review — human approval still required. |
| it('should assign a position to a record inserted via upsert', async () => { | ||
| const upsertResponse = await makeGraphqlAPIRequest({ | ||
| query: createRecordsWithPositionQuery, | ||
| variables: { | ||
| data: [ | ||
| { | ||
| firstUniqueTestField: 'insertedViaUpsertField', | ||
| secondUniqueTestField: 'insertedViaUpsertSecondField', | ||
| name: 'insertedRecord', | ||
| }, | ||
| ], | ||
| upsert: true, | ||
| }, | ||
| }); | ||
|
|
||
| const insertedRecord = upsertResponse.body.data.createTestRecordObjects[0]; | ||
|
|
||
| expect(insertedRecord.id).toEqual(expect.any(String)); | ||
| expect(typeof insertedRecord.position).toBe('number'); | ||
| }); |
There was a problem hiding this comment.
i don't understand this test, can we provide newPosition: initialPosition+1 for example and check position is properly updated?
Fix: upsert no longer rewrites
positionon existing recordsProblem
createX(..., upsert: true)resets thepositionof records that resolve to an update, even when the payload doesn't include aposition.The create-many/upsert runner backfills
position(to"first") incomputeArgsover the whole batch, before records are split into insert vs update. So existing rows get a freshly recomputedpositionwritten on every upsert. For callers that re-upsert their full dataset on a schedule (e.g. a daily sync), this rewritespositionfor every record on each run and drifts the values steadily negative — and it floods audit/event logs with position churn.The dedicated
updateOne/updateManyrunners already passshouldBackfillPositionIfUndefined: false; the upsert path did not.Fix
Only backfill
positionfor records that are actually inserted:computeArgsnow passesshouldBackfillPositionIfUndefined: !args.upsertin both the create-many and create-one runners, so undefined positions are left untouched on upsert.performUpsertOperationbackfills"first"positions forrecordsToInsertonly, after categorization, viaRecordPositionService.Explicit
positionvalues ("first","last", or a number) in the payload are still honored. Plain (non-upsert) create behavior is unchanged.Behavior
positionsentpositionrewrittenpositionuntouchedpositionsent"first""first"(unchanged)positionon upsert