Skip to content

Commit 84d2353

Browse files
thomtrpThomas Trompette
authored andcommitted
Sync stripe tables (#5475)
Stripe tables do not support `hasNextPage` and `totalCount`. This may be because of stripe wrapper do not properly support `COUNT` request. Waiting on pg_graphql answer [here](supabase/pg_graphql#519). This PR: - removes `totalCount` and `hasNextPage` form queries for remote objects. Even if it works for postgres, this may really be inefficient - adapt the `fetchMore` functions so it works despite `hasNextPage` missing - remove `totalCount` display for remotes - fix `orderBy` --------- Co-authored-by: Thomas Trompette <[email protected]>
1 parent e6c4d8f commit 84d2353

22 files changed

+191
-87
lines changed

packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
77
import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename';
88
import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs';
99
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
10+
import { isDefined } from '~/utils/isDefined';
1011

1112
/*
1213
TODO: for now new records are added to all cached record lists, no matter what the variables (filters, orderBy, etc.) are.
@@ -61,11 +62,10 @@ export const triggerCreateRecordsOptimisticEffect = ({
6162
rootQueryCachedObjectRecordConnection,
6263
);
6364

64-
const rootQueryCachedRecordTotalCount =
65-
readField<number>(
66-
'totalCount',
67-
rootQueryCachedObjectRecordConnection,
68-
) || 0;
65+
const rootQueryCachedRecordTotalCount = readField<number | undefined>(
66+
'totalCount',
67+
rootQueryCachedObjectRecordConnection,
68+
);
6969

7070
const nextRootQueryCachedRecordEdges = rootQueryCachedRecordEdges
7171
? [...rootQueryCachedRecordEdges]
@@ -113,7 +113,9 @@ export const triggerCreateRecordsOptimisticEffect = ({
113113
return {
114114
...rootQueryCachedObjectRecordConnection,
115115
edges: nextRootQueryCachedRecordEdges,
116-
totalCount: rootQueryCachedRecordTotalCount + 1,
116+
totalCount: isDefined(rootQueryCachedRecordTotalCount)
117+
? rootQueryCachedRecordTotalCount + 1
118+
: undefined,
117119
};
118120
},
119121
},

packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,10 @@ export const triggerDeleteRecordsOptimisticEffect = ({
5050
rootQueryCachedObjectRecordConnection,
5151
);
5252

53-
const totalCount =
54-
readField<number>(
55-
'totalCount',
56-
rootQueryCachedObjectRecordConnection,
57-
) || 0;
53+
const totalCount = readField<number | undefined>(
54+
'totalCount',
55+
rootQueryCachedObjectRecordConnection,
56+
);
5857

5958
const nextCachedEdges =
6059
cachedEdges?.filter((cachedEdge) => {
@@ -77,7 +76,9 @@ export const triggerDeleteRecordsOptimisticEffect = ({
7776
return {
7877
...rootQueryCachedObjectRecordConnection,
7978
edges: nextCachedEdges,
80-
totalCount: totalCount - recordIdsToDelete.length,
79+
totalCount: isDefined(totalCount)
80+
? totalCount - recordIdsToDelete.length
81+
: undefined,
8182
};
8283
},
8384
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
2+
3+
export const hasPositionField = (objectMetadataItem: ObjectMetadataItem) =>
4+
!objectMetadataItem.isRemote;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
2+
3+
export const isAggregationEnabled = (objectMetadataItem: ObjectMetadataItem) =>
4+
!objectMetadataItem.isRemote;

packages/twenty-front/src/modules/object-record/hooks/useFindDuplicateRecords.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export const useFindDuplicateRecords = <T extends ObjectRecord = ObjectRecord>({
7676
return {
7777
objectMetadataItem,
7878
records,
79-
totalCount: objectRecordConnection?.totalCount || 0,
79+
totalCount: objectRecordConnection?.totalCount,
8080
loading,
8181
error,
8282
queryStateIdentifier: findDuplicateQueryStateIdentifier,

packages/twenty-front/src/modules/object-record/hooks/useFindDuplicatesRecordsQuery.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useRecoilValue } from 'recoil';
33

44
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
55
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
6+
import { isAggregationEnabled } from '@/object-metadata/utils/isAggregationEnabled';
67
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
78
import { getFindDuplicateRecordsQueryResponseField } from '@/object-record/utils/getFindDuplicateRecordsQueryResponseField';
89
import { capitalize } from '~/utils/string/capitalize';
@@ -33,11 +34,11 @@ export const useFindDuplicateRecordsQuery = ({
3334
cursor
3435
}
3536
pageInfo {
36-
hasNextPage
37+
${isAggregationEnabled(objectMetadataItem) ? 'hasNextPage' : ''}
3738
startCursor
3839
endCursor
3940
}
40-
totalCount
41+
${isAggregationEnabled(objectMetadataItem) ? 'totalCount' : ''}
4142
}
4243
}
4344
`;

packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts

+10-8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
77
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
88
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
99
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
10+
import { isAggregationEnabled } from '@/object-metadata/utils/isAggregationEnabled';
1011
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
1112
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
1213
import { RecordGqlEdge } from '@/object-record/graphql/types/RecordGqlEdge';
@@ -122,7 +123,8 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
122123
});
123124

124125
const fetchMoreRecords = useCallback(async () => {
125-
if (hasNextPage) {
126+
// Remote objects does not support hasNextPage. We cannot rely on it to fetch more records.
127+
if (hasNextPage || (!isAggregationEnabled(objectMetadataItem) && !error)) {
126128
setIsFetchingMoreObjects(true);
127129

128130
try {
@@ -137,11 +139,11 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
137139
const nextEdges =
138140
fetchMoreResult?.[objectMetadataItem.namePlural]?.edges;
139141

140-
let newEdges: RecordGqlEdge[] = [];
142+
let newEdges: RecordGqlEdge[] = previousEdges ?? [];
141143

142-
if (isNonEmptyArray(previousEdges) && isNonEmptyArray(nextEdges)) {
144+
if (isNonEmptyArray(nextEdges)) {
143145
newEdges = filterUniqueRecordEdgesByCursor([
144-
...(prev?.[objectMetadataItem.namePlural]?.edges ?? []),
146+
...newEdges,
145147
...(fetchMoreResult?.[objectMetadataItem.namePlural]?.edges ??
146148
[]),
147149
]);
@@ -199,21 +201,21 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
199201
}
200202
}, [
201203
hasNextPage,
204+
objectMetadataItem,
205+
error,
202206
setIsFetchingMoreObjects,
203207
fetchMore,
204208
filter,
205209
orderBy,
206210
lastCursor,
207-
objectMetadataItem.namePlural,
208-
objectMetadataItem.nameSingular,
209-
onCompleted,
210211
data,
212+
onCompleted,
211213
setLastCursor,
212214
setHasNextPage,
213215
enqueueSnackBar,
214216
]);
215217

216-
const totalCount = data?.[objectMetadataItem.namePlural].totalCount ?? 0;
218+
const totalCount = data?.[objectMetadataItem.namePlural]?.totalCount;
217219

218220
const records = useMemo(
219221
() =>

packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/__tests__/turnSortsIntoOrderBy.test.tsx

+46-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
2+
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
13
import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';
24
import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition';
35
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
@@ -8,12 +10,30 @@ const sortDefinition: SortDefinition = {
810
iconName: 'icon',
911
};
1012

13+
const objectMetadataItem: ObjectMetadataItem = {
14+
id: 'object1',
15+
fields: [],
16+
createdAt: '2021-01-01',
17+
updatedAt: '2021-01-01',
18+
nameSingular: 'object1',
19+
namePlural: 'object1s',
20+
icon: 'icon',
21+
isActive: true,
22+
isSystem: false,
23+
isCustom: false,
24+
isRemote: false,
25+
labelPlural: 'object1s',
26+
labelSingular: 'object1',
27+
};
28+
1129
describe('turnSortsIntoOrderBy', () => {
1230
it('should sort by recordPosition if no sorts', () => {
13-
const fields = [{ id: 'field1', name: 'createdAt' }];
14-
expect(turnSortsIntoOrderBy([], fields)).toEqual({
15-
position: 'AscNullsFirst',
16-
});
31+
const fields = [{ id: 'field1', name: 'createdAt' }] as FieldMetadataItem[];
32+
expect(turnSortsIntoOrderBy({ ...objectMetadataItem, fields }, [])).toEqual(
33+
{
34+
position: 'AscNullsFirst',
35+
},
36+
);
1737
});
1838

1939
it('should create OrderByField with single sort', () => {
@@ -24,8 +44,10 @@ describe('turnSortsIntoOrderBy', () => {
2444
definition: sortDefinition,
2545
},
2646
];
27-
const fields = [{ id: 'field1', name: 'field1' }];
28-
expect(turnSortsIntoOrderBy(sorts, fields)).toEqual({
47+
const fields = [{ id: 'field1', name: 'field1' }] as FieldMetadataItem[];
48+
expect(
49+
turnSortsIntoOrderBy({ ...objectMetadataItem, fields }, sorts),
50+
).toEqual({
2951
field1: 'AscNullsFirst',
3052
position: 'AscNullsFirst',
3153
});
@@ -47,8 +69,10 @@ describe('turnSortsIntoOrderBy', () => {
4769
const fields = [
4870
{ id: 'field1', name: 'field1' },
4971
{ id: 'field2', name: 'field2' },
50-
];
51-
expect(turnSortsIntoOrderBy(sorts, fields)).toEqual({
72+
] as FieldMetadataItem[];
73+
expect(
74+
turnSortsIntoOrderBy({ ...objectMetadataItem, fields }, sorts),
75+
).toEqual({
5276
field1: 'AscNullsFirst',
5377
field2: 'DescNullsLast',
5478
position: 'AscNullsFirst',
@@ -63,8 +87,21 @@ describe('turnSortsIntoOrderBy', () => {
6387
definition: sortDefinition,
6488
},
6589
];
66-
expect(turnSortsIntoOrderBy(sorts, [])).toEqual({
90+
expect(turnSortsIntoOrderBy(objectMetadataItem, sorts)).toEqual({
6791
position: 'AscNullsFirst',
6892
});
6993
});
94+
95+
it('should not return position for remotes', () => {
96+
const sorts: Sort[] = [
97+
{
98+
fieldMetadataId: 'invalidField',
99+
direction: 'asc',
100+
definition: sortDefinition,
101+
},
102+
];
103+
expect(
104+
turnSortsIntoOrderBy({ ...objectMetadataItem, isRemote: true }, sorts),
105+
).toEqual({});
106+
});
70107
});

packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
12
import { OrderBy } from '@/object-metadata/types/OrderBy';
3+
import { hasPositionField } from '@/object-metadata/utils/hasPositionColumn';
24
import { RecordGqlOperationOrderBy } from '@/object-record/graphql/types/RecordGqlOperationOrderBy';
35
import { Field } from '~/generated/graphql';
46
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
@@ -8,9 +10,10 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
810
import { Sort } from '../types/Sort';
911

1012
export const turnSortsIntoOrderBy = (
13+
objectMetadataItem: ObjectMetadataItem,
1114
sorts: Sort[],
12-
fields: Pick<Field, 'id' | 'name'>[],
1315
): RecordGqlOperationOrderBy => {
16+
const fields: Pick<Field, 'id' | 'name'>[] = objectMetadataItem?.fields ?? [];
1417
const fieldsById = mapArrayToObject(fields, ({ id }) => id);
1518
const sortsOrderBy = Object.fromEntries(
1619
sorts
@@ -29,8 +32,12 @@ export const turnSortsIntoOrderBy = (
2932
.filter(isDefined),
3033
);
3134

32-
return {
33-
...sortsOrderBy,
34-
position: 'AscNullsFirst',
35-
};
35+
if (hasPositionField(objectMetadataItem)) {
36+
return {
37+
...sortsOrderBy,
38+
position: 'AscNullsFirst',
39+
};
40+
}
41+
42+
return sortsOrderBy;
3643
};

packages/twenty-front/src/modules/object-record/record-action-bar/hooks/useRecordActionBar.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import { useFavorites } from '@/favorites/hooks/useFavorites';
1515
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
1616
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
1717
import { useExecuteQuickActionOnOneRecord } from '@/object-record/hooks/useExecuteQuickActionOnOneRecord';
18-
import { useExportTableData } from '@/object-record/record-index/options/hooks/useExportTableData';
18+
import {
19+
displayedExportProgress,
20+
useExportTableData,
21+
} from '@/object-record/record-index/options/hooks/useExportTableData';
1922
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
2023
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
2124
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
@@ -111,7 +114,7 @@ export const useRecordActionBar = ({
111114
const baseActions: ContextMenuEntry[] = useMemo(
112115
() => [
113116
{
114-
label: `${progress === undefined ? `Export` : `Export (${progress}%)`}`,
117+
label: displayedExportProgress(progress),
115118
Icon: IconFileExport,
116119
accent: 'default',
117120
onClick: () => download(),

packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexBoard.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@ export const useLoadRecordIndexBoard = ({
4848
recordIndexFilters,
4949
objectMetadataItem?.fields ?? [],
5050
);
51-
const orderBy = !objectMetadataItem.isRemote
52-
? turnSortsIntoOrderBy(recordIndexSorts, objectMetadataItem?.fields ?? [])
53-
: undefined;
51+
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, recordIndexSorts);
5452

5553
const recordIndexIsCompactModeActive = useRecoilValue(
5654
recordIndexIsCompactModeActiveState,

packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts

+2-9
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,7 @@ export const useFindManyParams = (
2929
objectMetadataItem?.fields ?? [],
3030
);
3131

32-
if (objectMetadataItem?.isRemote) {
33-
return { objectNameSingular, filter };
34-
}
35-
36-
const orderBy = turnSortsIntoOrderBy(
37-
tableSorts,
38-
objectMetadataItem?.fields ?? [],
39-
);
32+
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, tableSorts);
4033

4134
return { objectNameSingular, filter, orderBy };
4235
};
@@ -71,7 +64,7 @@ export const useLoadRecordIndexTable = (objectNameSingular: string) => {
7164
currentWorkspace?.activationStatus === 'active'
7265
? records
7366
: SIGN_IN_BACKGROUND_MOCK_COMPANIES,
74-
totalCount: totalCount || 0,
67+
totalCount: totalCount,
7568
loading,
7669
fetchMoreRecords,
7770
queryStateIdentifier,

packages/twenty-front/src/modules/object-record/record-index/options/components/RecordIndexOptionsDropdownContent.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import {
99
} from 'twenty-ui';
1010

1111
import { RECORD_INDEX_OPTIONS_DROPDOWN_ID } from '@/object-record/record-index/options/constants/RecordIndexOptionsDropdownId';
12-
import { useExportTableData } from '@/object-record/record-index/options/hooks/useExportTableData';
12+
import {
13+
displayedExportProgress,
14+
useExportTableData,
15+
} from '@/object-record/record-index/options/hooks/useExportTableData';
1316
import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard';
1417
import { useRecordIndexOptionsForTable } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForTable';
1518
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
@@ -123,7 +126,7 @@ export const RecordIndexOptionsDropdownContent = ({
123126
<MenuItem
124127
onClick={download}
125128
LeftIcon={IconFileExport}
126-
text={progress === undefined ? `Export` : `Export (${progress}%)`}
129+
text={displayedExportProgress(progress)}
127130
/>
128131
</DropdownMenuItemsContainer>
129132
)}

0 commit comments

Comments
 (0)