-
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
☑️ Refacto "Select All/Unselect all" on indexes #5320
Conversation
Co-authored-by: v1b3m <[email protected]> Co-authored-by: Toledodev <[email protected]>
Hey @charlesBochet |
@gitstart-twenty could you rebase your PR on main, tests should be green :) |
Alright |
Co-authored-by: v1b3m <[email protected]> Co-authored-by: Toledodev <[email protected]>
Co-authored-by: v1b3m <[email protected]> Co-authored-by: Toledodev <[email protected]>
Hey @charlesBochet, // packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts
import { useCallback, useMemo } from 'react';
import {
ApolloQueryResult,
FetchMoreQueryOptions,
OperationVariables,
useQuery,
WatchQueryFetchPolicy,
} from '@apollo/client';
import { isNonEmptyArray } from '@apollo/client/utilities';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
import { RecordGqlEdge } from '@/object-record/graphql/types/RecordGqlEdge';
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { filterUniqueRecordEdgesByCursor } from '@/object-record/utils/filterUniqueRecordEdgesByCursor';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { isDefined } from '~/utils/isDefined';
import { logError } from '~/utils/logError';
import { capitalize } from '~/utils/string/capitalize';
import { cursorFamilyState } from '../states/cursorFamilyState';
import { hasNextPageFamilyState } from '../states/hasNextPageFamilyState';
import { isFetchingMoreRecordsFamilyState } from '../states/isFetchingMoreRecordsFamilyState';
export type UseFindManyRecordsParams<T> = ObjectMetadataItemIdentifier &
RecordGqlOperationVariables & {
onCompleted?: (
records: T[],
options?: {
pageInfo?: RecordGqlConnection['pageInfo'];
totalCount?: number;
},
) => void;
skip?: boolean;
recordGqlFields?: RecordGqlOperationGqlRecordFields;
fetchPolicy?: WatchQueryFetchPolicy;
};
type UseFindManyRecordsStateParams<
T,
TData = RecordGqlOperationFindManyResult,
> = Omit<
UseFindManyRecordsParams<T>,
'skip' | 'recordGqlFields' | 'fetchPolicy'
> & {
data: RecordGqlOperationFindManyResult | undefined;
fetchMore<
TFetchData = TData,
TFetchVars extends OperationVariables = OperationVariables,
>(
fetchMoreOptions: FetchMoreQueryOptions<TFetchVars, TFetchData> & {
updateQuery?: (
previousQueryResult: TData,
options: {
fetchMoreResult: TFetchData;
variables: TFetchVars;
},
) => TData;
},
): Promise<ApolloQueryResult<TFetchData>>;
objectMetadataItem: ObjectMetadataItem;
};
export const useFindManyRecordsState = <T extends ObjectRecord = ObjectRecord>({
objectNameSingular,
filter,
orderBy,
limit,
onCompleted,
data,
fetchMore,
objectMetadataItem,
}: UseFindManyRecordsStateParams<T>) => {
const findManyQueryStateIdentifier =
objectNameSingular +
JSON.stringify(filter) +
JSON.stringify(orderBy) +
limit;
const [lastCursor, setLastCursor] = useRecoilState(
cursorFamilyState(findManyQueryStateIdentifier),
);
const [hasNextPage, setHasNextPage] = useRecoilState(
hasNextPageFamilyState(findManyQueryStateIdentifier),
);
const setIsFetchingMoreObjects = useSetRecoilState(
isFetchingMoreRecordsFamilyState(findManyQueryStateIdentifier),
);
const { enqueueSnackBar } = useSnackBar();
const fetchMoreRecords = useCallback(async () => {
if (hasNextPage) {
setIsFetchingMoreObjects(true);
try {
await fetchMore({
variables: {
filter,
orderBy,
lastCursor: isNonEmptyString(lastCursor) ? lastCursor : undefined,
},
updateQuery: (prev, { fetchMoreResult }) => {
const previousEdges = prev?.[objectMetadataItem.namePlural]?.edges;
const nextEdges =
fetchMoreResult?.[objectMetadataItem.namePlural]?.edges;
let newEdges: RecordGqlEdge[] = [];
if (isNonEmptyArray(previousEdges) && isNonEmptyArray(nextEdges)) {
newEdges = filterUniqueRecordEdgesByCursor([
...(prev?.[objectMetadataItem.namePlural]?.edges ?? []),
...(fetchMoreResult?.[objectMetadataItem.namePlural]?.edges ??
[]),
]);
}
const pageInfo =
fetchMoreResult?.[objectMetadataItem.namePlural]?.pageInfo;
if (isDefined(data?.[objectMetadataItem.namePlural])) {
setLastCursor(pageInfo.endCursor ?? '');
setHasNextPage(pageInfo.hasNextPage ?? false);
}
const records = getRecordsFromRecordConnection({
recordConnection: {
edges: newEdges,
pageInfo,
},
}) as T[];
onCompleted?.(records, {
pageInfo,
totalCount:
fetchMoreResult?.[objectMetadataItem.namePlural]?.totalCount,
});
return Object.assign({}, prev, {
[objectMetadataItem.namePlural]: {
__typename: `${capitalize(
objectMetadataItem.nameSingular,
)}Connection`,
edges: newEdges,
pageInfo:
fetchMoreResult?.[objectMetadataItem.namePlural].pageInfo,
totalCount:
fetchMoreResult?.[objectMetadataItem.namePlural].totalCount,
},
} as RecordGqlOperationFindManyResult);
},
});
} catch (error) {
logError(
`fetchMoreObjects for "${objectMetadataItem.namePlural}" error : ` +
error,
);
enqueueSnackBar(
`Error during fetchMoreObjects for "${objectMetadataItem.namePlural}", ${error}`,
{
variant: 'error',
},
);
} finally {
setIsFetchingMoreObjects(false);
}
}
}, [
hasNextPage,
setIsFetchingMoreObjects,
fetchMore,
filter,
orderBy,
lastCursor,
objectMetadataItem.namePlural,
objectMetadataItem.nameSingular,
onCompleted,
data,
setLastCursor,
setHasNextPage,
enqueueSnackBar,
]);
const totalCount = data?.[objectMetadataItem.namePlural].totalCount ?? 0;
const records = useMemo(
() =>
data?.[objectMetadataItem.namePlural]
? getRecordsFromRecordConnection<T>({
recordConnection: data?.[objectMetadataItem.namePlural],
})
: ([] as T[]),
[data, objectMetadataItem.namePlural],
);
return {
findManyQueryStateIdentifier,
setLastCursor,
setHasNextPage,
fetchMoreRecords,
totalCount,
records,
};
};
export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
objectNameSingular,
filter,
orderBy,
limit,
onCompleted,
skip,
recordGqlFields,
fetchPolicy,
}: UseFindManyRecordsParams<T>) => {
const { enqueueSnackBar } = useSnackBar();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { findManyRecordsQuery } = useFindManyRecordsQuery({
objectNameSingular,
recordGqlFields,
});
const { data, loading, error, fetchMore } =
useQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
skip: skip || !objectMetadataItem || !currentWorkspaceMember,
variables: {
filter,
limit,
orderBy,
},
fetchPolicy: fetchPolicy,
onCompleted: (data) => {
if (!isDefined(data)) {
onCompleted?.([]);
}
const pageInfo = data?.[objectMetadataItem.namePlural]?.pageInfo;
const records = getRecordsFromRecordConnection({
recordConnection: data?.[objectMetadataItem.namePlural],
}) as T[];
onCompleted?.(records, {
pageInfo,
totalCount: data?.[objectMetadataItem.namePlural]?.totalCount,
});
if (isDefined(data?.[objectMetadataItem.namePlural])) {
setLastCursor(pageInfo.endCursor ?? '');
setHasNextPage(pageInfo.hasNextPage ?? false);
}
},
onError: (error) => {
logError(
`useFindManyRecords for "${objectMetadataItem.namePlural}" error : ` +
error,
);
enqueueSnackBar(
`Error during useFindManyRecords for "${objectMetadataItem.namePlural}", ${error.message}`,
{
variant: 'error',
},
);
},
});
const {
findManyQueryStateIdentifier,
setLastCursor,
setHasNextPage,
fetchMoreRecords,
totalCount,
records,
} = useFindManyRecordsState<T>({
objectNameSingular,
filter,
orderBy,
limit,
onCompleted,
fetchMore,
data,
objectMetadataItem,
});
return {
objectMetadataItem,
records,
totalCount,
loading,
error,
fetchMoreRecords,
queryStateIdentifier: findManyQueryStateIdentifier,
};
}; // packages/twenty-front/src/modules/object-record/hooks/useLazyFindManyRecords.ts
import { useLazyQuery } from '@apollo/client';
import { useRecoilValue } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
import {
UseFindManyRecordsParams,
useFindManyRecordsState,
} from '@/object-record/hooks/useFindManyRecords';
import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { isDefined } from '~/utils/isDefined';
import { logError } from '~/utils/logError';
type UseLazyFindManyRecordsParams<T> = Omit<
UseFindManyRecordsParams<T>,
'skip'
>;
export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
objectNameSingular,
filter,
orderBy,
limit,
onCompleted,
recordGqlFields,
fetchPolicy,
}: UseLazyFindManyRecordsParams<T>) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { findManyRecordsQuery } = useFindManyRecordsQuery({
objectNameSingular,
recordGqlFields,
});
const { enqueueSnackBar } = useSnackBar();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const [findManyRecords, { data, loading, error, fetchMore }] =
useLazyQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
variables: {
filter,
limit,
orderBy,
},
fetchPolicy: fetchPolicy,
onCompleted: (data) => {
if (!isDefined(data)) {
onCompleted?.([]);
}
const pageInfo = data?.[objectMetadataItem.namePlural]?.pageInfo;
const records = getRecordsFromRecordConnection({
recordConnection: data?.[objectMetadataItem.namePlural],
}) as T[];
onCompleted?.(records, {
pageInfo,
totalCount: data?.[objectMetadataItem.namePlural]?.totalCount,
});
if (isDefined(data?.[objectMetadataItem.namePlural])) {
setLastCursor(pageInfo.endCursor ?? '');
setHasNextPage(pageInfo.hasNextPage ?? false);
}
},
onError: (error) => {
logError(
`useFindManyRecords for "${objectMetadataItem.namePlural}" error : ` +
error,
);
enqueueSnackBar(
`Error during useFindManyRecords for "${objectMetadataItem.namePlural}", ${error.message}`,
{
variant: 'error',
},
);
},
});
const {
findManyQueryStateIdentifier,
setLastCursor,
setHasNextPage,
fetchMoreRecords,
totalCount,
records,
} = useFindManyRecordsState<T>({
objectNameSingular,
filter,
orderBy,
limit,
onCompleted,
fetchMore,
data,
objectMetadataItem,
});
return {
objectMetadataItem,
records,
totalCount,
loading,
error,
fetchMoreRecords,
queryStateIdentifier: findManyQueryStateIdentifier,
findManyRecords: currentWorkspaceMember ? findManyRecords : () => {},
};
}; |
Hey, video looks great (nice improvement!) but the total count in the view switcher doesn't get refreshed apparently, we should fix that if that hasn't been fixed already. |
@gitstart-twenty Seems like a good DRY increment for me, this starts to be a big file and difficult to read but it would need a more in depth refactor. |
Though I would name it according to its context which is mainly about pagination. Maybe something like useFindManyRecordsPaginationUtils ? |
Hey @lucasbordeau |
Hey @FelixMalfait |
Co-authored-by: v1b3m <[email protected]> Co-authored-by: Toledodev <[email protected]>
@FelixMalfait + refetchQueries: [getOperationName(findManyRecordsQuery) ?? ''], The error
|
Not sure sorry! |
Alright, debugging! |
Alright! |
Co-authored-by: v1b3m <[email protected]> Co-authored-by: Toledodev <[email protected]>
Co-authored-by: v1b3m <[email protected]> Co-authored-by: Toledodev <[email protected]>
Co-authored-by: v1b3m <[email protected]> Co-authored-by: Toledodev <[email protected]>
Seems to work now! |
Something(new changes) broke the deletion |
@gitstart-twenty Do you need something from our side to move forward ? |
@gitstart-twenty I'll take on from here as this PR is connected to multiple other refactors and another issue. |
const firstQueryResult = | ||
findManyRecordsDataResult?.data?.[objectMetadataItem.namePlural]; | ||
|
||
const totalCount = firstQueryResult?.totalCount ?? 1; |
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.
why 1 as default and not 0?
|
||
let lastCursor = firstQueryResult?.pageInfo.endCursor ?? ''; | ||
|
||
for (let i = 0; i < remainingPages; i++) { |
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.
pageIndex rather than i
|
||
resetTableRowSelection(); | ||
|
||
for (const recordIdToDelete of recordIdsToDelete) { |
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.
not a big fan this is the frontend that has to think about deleting favorites
Closes #4397
Closes #5169
Enregistrement.de.l.ecran.2024-07-09.a.17.58.47.mov