Fix non-idempotent application sync for viewSorts (subFieldName undefined vs null) (#22505)
## Summary Successive application syncs (`yarn twenty dev --once`) kept reporting the same viewSorts as updated, even with no manifest changes. The manifest converter never set `subFieldName`, so the manifest-derived flat viewSort carried `undefined` where the flat viewSort computed from the database carried `null`. The comparator (microdiff) treats `null` vs `undefined` as a change, producing a phantom update action on every sync that never converges — the resulting update is a no-op on the database. Fixes twentyhq/core-team-issues#2629 ## Changes - **Converter**: `fromViewSortManifestToUniversalFlatViewSort` now sets `subFieldName: viewSortManifest.subFieldName ?? null`, matching how the sibling converters (e.g. view filters) handle optional compared properties. - **Type definition**: added optional `subFieldName?: string` to `ViewSortManifest` in `twenty-shared`, mirroring `ViewFilterManifest` — this also makes sorts on composite sub-fields (e.g. `amountMicros`) expressible in app manifests, which the entity already supports. - **Tests**: - Asserts `subFieldName` is `null` (not `undefined`) when omitted — the idempotency regression. - Asserts `subFieldName` is passed through when provided. ## Verification - All 12 application-manifest converter suites pass (47 tests). - Flat-entity comparison/constants suites pass (36 tests, 21 snapshots). - `subFieldName` was already part of the viewSort compare properties, so no comparator/constants changes needed. https://claude.ai/code/session_018FrD42MMQtu1UvDyiEZbSq <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22505?utm_source=github" rel="nofollow noreferrer noopener" target="_blank">``<img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg">``</a>
This commit is contained in:
+17
@@ -29,6 +29,23 @@ describe('fromViewSortManifestToUniversalFlatViewSort', () => {
|
||||
expect(result.createdAt).toBe(now);
|
||||
expect(result.updatedAt).toBe(now);
|
||||
expect(result.deletedAt).toBeNull();
|
||||
expect(result.subFieldName).toBeNull();
|
||||
});
|
||||
|
||||
it('should pass through subFieldName when provided', () => {
|
||||
const result = fromViewSortManifestToUniversalFlatViewSort({
|
||||
viewSortManifest: {
|
||||
universalIdentifier: 'vsort-uuid-3',
|
||||
fieldMetadataUniversalIdentifier: 'field-uuid-3',
|
||||
direction: ViewSortDirection.ASC,
|
||||
subFieldName: 'amountMicros',
|
||||
},
|
||||
viewUniversalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
});
|
||||
|
||||
expect(result.subFieldName).toBe('amountMicros');
|
||||
});
|
||||
|
||||
it('should convert a view sort manifest with DESC direction', () => {
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ export const fromViewSortManifestToUniversalFlatViewSort = ({
|
||||
viewSortManifest.fieldMetadataUniversalIdentifier,
|
||||
viewUniversalIdentifier,
|
||||
direction: viewSortManifest.direction,
|
||||
subFieldName: viewSortManifest.subFieldName ?? null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
||||
@@ -61,6 +61,7 @@ export type ViewFieldGroupManifest = SyncableEntityOptions & {
|
||||
export type ViewSortManifest = SyncableEntityOptions & {
|
||||
fieldMetadataUniversalIdentifier: string;
|
||||
direction: ViewSortDirection;
|
||||
subFieldName?: string;
|
||||
};
|
||||
|
||||
export type ViewManifest = SyncableEntityOptions & {
|
||||
|
||||
Reference in New Issue
Block a user