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
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: Toledodev <rafael.toledo@engenharia.ufjf.br>
Hey @charlesBochet |
@gitstart-twenty could you rebase your PR on main, tests should be green :) |
Alright |
Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: Toledodev <rafael.toledo@engenharia.ufjf.br>
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 <vibenjamin6@gmail.com> Co-authored-by: Toledodev <rafael.toledo@engenharia.ufjf.br>
@FelixMalfait + refetchQueries: [getOperationName(findManyRecordsQuery) ?? ''], The error
|
Not sure sorry! |
Alright, debugging! |
Alright! |
Description
☑️ Refacto "Select All/Unselect all" on indexes
Refs
#4397
Demo
delete_records.mp4
Fixes #4397