From afb9b3e37517cfb7907ed38df4b5a8ff7f1e7e0a Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Fri, 15 Mar 2024 18:35:40 +0100 Subject: [PATCH] Prefetching views and favorites (#4421) * wip * Push * Complete work on prefetch * Add comment * Fix * Fix * Fix * Fix * Remove dead code * Simplify * Fix tests * Fix tests * Fix according to review * Fix according to review --------- Co-authored-by: Lucas Bordeau --- .../src/hooks/useDefaultHomePagePath.tsx | 18 ++---- packages/twenty-front/src/index.tsx | 27 +++++---- .../modules/favorites/hooks/useFavorites.ts | 20 +++---- .../components/ObjectMetadataNavItems.tsx | 30 ++-------- .../utils/mapObjectMetadataToGraphQLQuery.ts | 2 +- .../cache/hooks/useCachedRootQuery.ts | 47 ---------------- .../useUpsertFindManyRecordsQueryInCache.ts | 5 +- .../cache/utils/getRecordEdgeFromRecord.ts | 18 +++++- ...anyRecordsForMultipleMetadataItemsQuery.ts | 8 ++- .../query-keys/types/QueryKey.ts | 7 +++ .../__tests__/useMultiObjectSearch.test.tsx | 3 +- .../components/PrefetchDataProvider.tsx | 12 ++++ .../components/PrefetchRunQueriesEffect.tsx | 53 ++++++++++++++++++ .../prefetch/constants/PrefetchConfig.ts | 9 +++ .../hooks/internal/usePrefetchRunQuery.ts | 55 +++++++++++++++++++ .../prefetch/hooks/usePrefetchedData.ts | 27 +++++++++ .../query-keys/AllFavoritesQueryKey.ts | 8 +++ .../prefetch/query-keys/AllViewsQueryKey.ts | 8 +++ .../states/prefetchIsLoadedFamilyState.ts | 10 ++++ .../src/modules/prefetch/types/PrefetchKey.ts | 4 ++ .../views/components/ViewBarEffect.tsx | 37 +++++++------ 21 files changed, 279 insertions(+), 129 deletions(-) delete mode 100644 packages/twenty-front/src/modules/object-record/cache/hooks/useCachedRootQuery.ts create mode 100644 packages/twenty-front/src/modules/object-record/query-keys/types/QueryKey.ts create mode 100644 packages/twenty-front/src/modules/prefetch/components/PrefetchDataProvider.tsx create mode 100644 packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx create mode 100644 packages/twenty-front/src/modules/prefetch/constants/PrefetchConfig.ts create mode 100644 packages/twenty-front/src/modules/prefetch/hooks/internal/usePrefetchRunQuery.ts create mode 100644 packages/twenty-front/src/modules/prefetch/hooks/usePrefetchedData.ts create mode 100644 packages/twenty-front/src/modules/prefetch/query-keys/AllFavoritesQueryKey.ts create mode 100644 packages/twenty-front/src/modules/prefetch/query-keys/AllViewsQueryKey.ts create mode 100644 packages/twenty-front/src/modules/prefetch/states/prefetchIsLoadedFamilyState.ts create mode 100644 packages/twenty-front/src/modules/prefetch/types/PrefetchKey.ts diff --git a/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx b/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx index d8d599508..c0f332791 100644 --- a/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx +++ b/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx @@ -1,24 +1,18 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { QueryMethodName } from '@/object-metadata/types/QueryMethodName'; -import { useCachedRootQuery } from '@/object-record/cache/hooks/useCachedRootQuery'; +import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; export const useDefaultHomePagePath = () => { const { objectMetadataItem: companyObjectMetadataItem } = useObjectMetadataItem({ objectNameSingular: CoreObjectNameSingular.Company, }); - const { objectMetadataItem: viewObjectMetadataItem } = useObjectMetadataItem({ - objectNameSingular: CoreObjectNameSingular.View, - }); - const { cachedRootQuery } = useCachedRootQuery({ - objectMetadataItem: viewObjectMetadataItem, - queryMethodName: QueryMethodName.FindMany, - }); - const companyViewId = cachedRootQuery?.views?.edges?.find( - (view: any) => - view?.node?.objectMetadataId === companyObjectMetadataItem.id, + const { records } = usePrefetchedData(PrefetchKey.AllViews); + + const companyViewId = records.find( + (view: any) => view?.objectMetadataId === companyObjectMetadataItem.id, )?.node.id; const defaultHomePagePath = '/objects/companies' + (companyViewId ? `?view=${companyViewId}` : ''); diff --git a/packages/twenty-front/src/index.tsx b/packages/twenty-front/src/index.tsx index c30daece2..c23babc43 100644 --- a/packages/twenty-front/src/index.tsx +++ b/packages/twenty-front/src/index.tsx @@ -13,6 +13,7 @@ import { ExceptionHandlerProvider } from '@/error-handler/components/ExceptionHa import { PromiseRejectionEffect } from '@/error-handler/components/PromiseRejectionEffect'; import { ApolloMetadataClientProvider } from '@/object-metadata/components/ApolloMetadataClientProvider'; import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider'; +import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider'; import { IconsProvider } from '@/ui/display/icon/components/IconsProvider'; import { DialogManager } from '@/ui/feedback/dialog-manager/components/DialogManager'; import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope'; @@ -47,18 +48,20 @@ root.render( - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts b/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts index cf5f646aa..25595a409 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts @@ -6,38 +6,38 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe import { Favorite } from '@/favorites/types/Favorite'; import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; -import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { isDefined } from '~/utils/isDefined'; export const useFavorites = () => { const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState()); - const favoriteObjectNameSingular = 'favorite'; - const { objectMetadataItem: favoriteObjectMetadataItem } = useObjectMetadataItem({ - objectNameSingular: favoriteObjectNameSingular, + objectNameSingular: CoreObjectNameSingular.Favorite, }); const { deleteOneRecord } = useDeleteOneRecord({ - objectNameSingular: favoriteObjectNameSingular, + objectNameSingular: CoreObjectNameSingular.Favorite, }); const { updateOneRecord: updateOneFavorite } = useUpdateOneRecord({ - objectNameSingular: favoriteObjectNameSingular, + objectNameSingular: CoreObjectNameSingular.Favorite, }); const { createOneRecord: createOneFavorite } = useCreateOneRecord({ - objectNameSingular: favoriteObjectNameSingular, + objectNameSingular: CoreObjectNameSingular.Favorite, }); - const { records: favorites } = useFindManyRecords({ - objectNameSingular: favoriteObjectNameSingular, - }); + const { records: favorites } = usePrefetchedData( + PrefetchKey.AllFavorites, + ); const favoriteRelationFieldMetadataItems = useMemo( () => diff --git a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx index 547a7e67f..43f5210bd 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx @@ -1,37 +1,19 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { QueryMethodName } from '@/object-metadata/types/QueryMethodName'; -import { useCachedRootQuery } from '@/object-record/cache/hooks/useCachedRootQuery'; -import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { useIcons } from '@/ui/display/icon/hooks/useIcons'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; +import { GraphQLView } from '@/views/types/GraphQLView'; export const ObjectMetadataNavItems = () => { - const { activeObjectMetadataItems, findObjectMetadataItemByNamePlural } = - useObjectMetadataItemForSettings(); + const { activeObjectMetadataItems } = useObjectMetadataItemForSettings(); const navigate = useNavigate(); const { getIcon } = useIcons(); const currentPath = useLocation().pathname; - const viewObjectMetadataItem = findObjectMetadataItemByNamePlural('views'); - - const { cachedRootQuery } = useCachedRootQuery({ - objectMetadataItem: viewObjectMetadataItem, - queryMethodName: QueryMethodName.FindMany, - }); - - const { records } = useFindManyRecords({ - skip: cachedRootQuery?.views, - objectNameSingular: CoreObjectNameSingular.View, - useRecordsWithoutConnection: true, - }); - - const views = - records.length > 0 - ? records - : cachedRootQuery?.views?.edges?.map((edge: any) => edge?.node); + const { records } = usePrefetchedData(PrefetchKey.AllViews); return ( <> @@ -63,7 +45,7 @@ export const ObjectMetadataNavItems = () => { : -1; }), ].map((objectMetadataItem) => { - const viewId = views?.find( + const viewId = records?.find( (view: any) => view?.objectMetadataId === objectMetadataItem.id, )?.id; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts b/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts index e56fff7e1..444c566f2 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts @@ -11,7 +11,7 @@ export const mapObjectMetadataToGraphQLQuery = ({ eagerLoadedRelations, }: { objectMetadataItems: ObjectMetadataItem[]; - objectMetadataItem: Pick; + objectMetadataItem: Pick; depth?: number; eagerLoadedRelations?: Record; }): any => { diff --git a/packages/twenty-front/src/modules/object-record/cache/hooks/useCachedRootQuery.ts b/packages/twenty-front/src/modules/object-record/cache/hooks/useCachedRootQuery.ts deleted file mode 100644 index 8ffd3ad71..000000000 --- a/packages/twenty-front/src/modules/object-record/cache/hooks/useCachedRootQuery.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { useApolloClient } from '@apollo/client/react/hooks/useApolloClient'; -import gql from 'graphql-tag'; -import { useRecoilValue } from 'recoil'; - -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { QueryMethodName } from '@/object-metadata/types/QueryMethodName'; -import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; - -export const useCachedRootQuery = ({ - objectMetadataItem, - queryMethodName, -}: { - objectMetadataItem: ObjectMetadataItem | undefined; - queryMethodName: QueryMethodName; -}) => { - const apolloClient = useApolloClient(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState()); - - if (!objectMetadataItem) { - return { cachedRootQuery: null }; - } - - const cacheReadFragment = gql` - fragment RootQuery on Query { - ${ - QueryMethodName.FindMany === queryMethodName - ? objectMetadataItem.namePlural - : objectMetadataItem.nameSingular - } - ${QueryMethodName.FindMany === queryMethodName ? '{ edges { node ' : ''} - ${mapObjectMetadataToGraphQLQuery({ - objectMetadataItems, - objectMetadataItem, - depth: 0, - })} - ${QueryMethodName.FindMany === queryMethodName ? '}}' : ''} - } - `; - - const cachedRootQuery = apolloClient.readFragment({ - id: 'ROOT_QUERY', - fragment: cacheReadFragment, - }); - - return { cachedRootQuery }; -}; diff --git a/packages/twenty-front/src/modules/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache.ts b/packages/twenty-front/src/modules/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache.ts index 9a11224b1..297be5db3 100644 --- a/packages/twenty-front/src/modules/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache.ts +++ b/packages/twenty-front/src/modules/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache.ts @@ -10,7 +10,10 @@ import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQu export const useUpsertFindManyRecordsQueryInCache = ({ objectMetadataItem, }: { - objectMetadataItem: ObjectMetadataItem; + objectMetadataItem: Pick< + ObjectMetadataItem, + 'fields' | 'namePlural' | 'nameSingular' + >; }) => { const apolloClient = useApolloClient(); diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/getRecordEdgeFromRecord.ts b/packages/twenty-front/src/modules/object-record/cache/utils/getRecordEdgeFromRecord.ts index 8921edae2..b82b77ff8 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/getRecordEdgeFromRecord.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/getRecordEdgeFromRecord.ts @@ -1,5 +1,6 @@ import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename'; import { getNodeTypename } from '@/object-record/cache/utils/getNodeTypename'; +import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge'; @@ -10,11 +11,26 @@ export const getRecordEdgeFromRecord = ({ objectNameSingular: string; record: T; }) => { + const nestedRecord = Object.fromEntries( + Object.entries(record).map(([key, value]) => { + if (Array.isArray(value)) { + return [ + key, + getRecordConnectionFromRecords({ + objectNameSingular: key, + records: value as ObjectRecord[], + }), + ]; + } + return [key, value]; + }), + ) as T; // Todo fix typing once we have investigated apollo edges / nodes removal + return { __typename: getEdgeTypename({ objectNameSingular }), node: { __typename: getNodeTypename({ objectNameSingular }), - ...record, + ...nestedRecord, }, cursor: '', } as ObjectRecordEdge; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.ts index c453b0c1b..a85b4068d 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.ts @@ -1,5 +1,7 @@ import { gql } from '@apollo/client'; +import { useRecoilValue } from 'recoil'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { isNonEmptyArray } from '~/utils/isNonEmptyArray'; @@ -12,6 +14,7 @@ export const useGenerateFindManyRecordsForMultipleMetadataItemsQuery = ({ objectMetadataItems: ObjectMetadataItem[]; depth?: number; }) => { + const allObjectMetadataItems = useRecoilValue(objectMetadataItemsState()); const capitalizedObjectNameSingulars = objectMetadataItems.map( ({ nameSingular }) => capitalize(nameSingular), ); @@ -44,7 +47,7 @@ export const useGenerateFindManyRecordsForMultipleMetadataItemsQuery = ({ const limitPerMetadataItemArray = capitalizedObjectNameSingulars .map( (capitalizedObjectNameSingular) => - `$limit${capitalizedObjectNameSingular}: Float = 5`, + `$limit${capitalizedObjectNameSingular}: Float`, ) .join(', '); @@ -69,7 +72,7 @@ export const useGenerateFindManyRecordsForMultipleMetadataItemsQuery = ({ )}){ edges { node ${mapObjectMetadataToGraphQLQuery({ - objectMetadataItems, + objectMetadataItems: allObjectMetadataItems, objectMetadataItem, depth, })} @@ -80,6 +83,7 @@ export const useGenerateFindManyRecordsForMultipleMetadataItemsQuery = ({ startCursor endCursor } + totalCount }`, ) .join('\n')} diff --git a/packages/twenty-front/src/modules/object-record/query-keys/types/QueryKey.ts b/packages/twenty-front/src/modules/object-record/query-keys/types/QueryKey.ts new file mode 100644 index 000000000..fc1c16a14 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/query-keys/types/QueryKey.ts @@ -0,0 +1,7 @@ +import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables'; + +export type QueryKey = { + objectNameSingular: string; + variables: ObjectRecordQueryVariables; + depth: number; +}; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx index 9ce41b117..d48688e51 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx @@ -13,7 +13,7 @@ const query = gql` $filterNameSingular: NameSingularFilterInput $orderByNameSingular: NameSingularOrderByInput $lastCursorNameSingular: String - $limitNameSingular: Float = 5 + $limitNameSingular: Float ) { namePlural( filter: $filterNameSingular @@ -33,6 +33,7 @@ const query = gql` startCursor endCursor } + totalCount } } `; diff --git a/packages/twenty-front/src/modules/prefetch/components/PrefetchDataProvider.tsx b/packages/twenty-front/src/modules/prefetch/components/PrefetchDataProvider.tsx new file mode 100644 index 000000000..837d2498e --- /dev/null +++ b/packages/twenty-front/src/modules/prefetch/components/PrefetchDataProvider.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +import { PrefetchRunQueriesEffect } from '@/prefetch/components/PrefetchRunQueriesEffect'; + +export const PrefetchDataProvider = ({ children }: React.PropsWithChildren) => { + return ( + <> + + {children} + + ); +}; diff --git a/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx b/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx new file mode 100644 index 000000000..18a73506f --- /dev/null +++ b/packages/twenty-front/src/modules/prefetch/components/PrefetchRunQueriesEffect.tsx @@ -0,0 +1,53 @@ +import { useEffect } from 'react'; +import { useQuery } from '@apollo/client'; + +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useGenerateFindManyRecordsForMultipleMetadataItemsQuery } from '@/object-record/hooks/useGenerateFindManyRecordsForMultipleMetadataItemsQuery'; +import { MultiObjectRecordQueryResult } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; +import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { isDefined } from '~/utils/isDefined'; + +export const PrefetchRunQueriesEffect = () => { + const { + objectMetadataItem: objectMetadataItemView, + upsertRecordsInCache: upsertViewsInCache, + } = usePrefetchRunQuery({ + prefetchKey: PrefetchKey.AllViews, + objectNameSingular: CoreObjectNameSingular.View, + }); + + const { + objectMetadataItem: objectMetadataItemFavorite, + upsertRecordsInCache: upsertFavoritesInCache, + } = usePrefetchRunQuery({ + prefetchKey: PrefetchKey.AllFavorites, + objectNameSingular: CoreObjectNameSingular.Favorite, + }); + + const prefetchFindManyQuery = + useGenerateFindManyRecordsForMultipleMetadataItemsQuery({ + objectMetadataItems: [objectMetadataItemView, objectMetadataItemFavorite], + depth: 2, + }); + + if (!isDefined(prefetchFindManyQuery)) { + throw new Error('Could not prefetch recrds'); + } + + const { data } = useQuery( + prefetchFindManyQuery, + ); + + useEffect(() => { + if (isDefined(data?.views)) { + upsertViewsInCache(data.views); + } + + if (isDefined(data?.favorites)) { + upsertFavoritesInCache(data.favorites); + } + }, [data, upsertViewsInCache, upsertFavoritesInCache]); + + return <>; +}; diff --git a/packages/twenty-front/src/modules/prefetch/constants/PrefetchConfig.ts b/packages/twenty-front/src/modules/prefetch/constants/PrefetchConfig.ts new file mode 100644 index 000000000..a79631158 --- /dev/null +++ b/packages/twenty-front/src/modules/prefetch/constants/PrefetchConfig.ts @@ -0,0 +1,9 @@ +import { QueryKey } from '@/object-record/query-keys/types/QueryKey'; +import { ALL_FAVORITES_QUERY_KEY } from '@/prefetch/query-keys/AllFavoritesQueryKey'; +import { ALL_VIEWS_QUERY_KEY } from '@/prefetch/query-keys/AllViewsQueryKey'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; + +export const PREFETCH_CONFIG: Record = { + ALL_VIEWS: ALL_VIEWS_QUERY_KEY, + ALL_FAVORITES: ALL_FAVORITES_QUERY_KEY, +}; diff --git a/packages/twenty-front/src/modules/prefetch/hooks/internal/usePrefetchRunQuery.ts b/packages/twenty-front/src/modules/prefetch/hooks/internal/usePrefetchRunQuery.ts new file mode 100644 index 000000000..294c138a7 --- /dev/null +++ b/packages/twenty-front/src/modules/prefetch/hooks/internal/usePrefetchRunQuery.ts @@ -0,0 +1,55 @@ +import { useSetRecoilState } from 'recoil'; + +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache'; +import { useMapConnectionToRecords } from '@/object-record/hooks/useMapConnectionToRecords'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection'; +import { ALL_VIEWS_QUERY_KEY } from '@/prefetch/query-keys/AllViewsQueryKey'; +import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; + +export type UsePrefetchRunQuery = { + prefetchKey: PrefetchKey; + objectNameSingular: CoreObjectNameSingular; +}; + +export const usePrefetchRunQuery = ({ + prefetchKey, + objectNameSingular, +}: UsePrefetchRunQuery) => { + const setPrefetchDataIsLoadedLoaded = useSetRecoilState( + prefetchIsLoadedFamilyState(prefetchKey), + ); + const { objectMetadataItem } = useObjectMetadataItem({ + objectNameSingular: objectNameSingular, + }); + + const { upsertFindManyRecordsQueryInCache } = + useUpsertFindManyRecordsQueryInCache({ + objectMetadataItem: objectMetadataItem, + }); + + const mapConnectionToRecords = useMapConnectionToRecords(); + + const upsertRecordsInCache = (records: ObjectRecordConnection) => { + upsertFindManyRecordsQueryInCache({ + queryVariables: ALL_VIEWS_QUERY_KEY.variables, + depth: ALL_VIEWS_QUERY_KEY.depth, + objectRecordsToOverwrite: + mapConnectionToRecords({ + objectRecordConnection: records, + objectNameSingular: CoreObjectNameSingular.View, + depth: 2, + }) ?? [], + }); + setPrefetchDataIsLoadedLoaded(true); + }; + + return { + objectMetadataItem, + setPrefetchDataIsLoadedLoaded, + upsertRecordsInCache, + }; +}; diff --git a/packages/twenty-front/src/modules/prefetch/hooks/usePrefetchedData.ts b/packages/twenty-front/src/modules/prefetch/hooks/usePrefetchedData.ts new file mode 100644 index 000000000..3997bae73 --- /dev/null +++ b/packages/twenty-front/src/modules/prefetch/hooks/usePrefetchedData.ts @@ -0,0 +1,27 @@ +import { useRecoilValue } from 'recoil'; + +import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig'; +import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; + +export const usePrefetchedData = ( + prefetchKey: PrefetchKey, +) => { + const isDataPrefetched = useRecoilValue( + prefetchIsLoadedFamilyState(prefetchKey), + ); + const prefetchQueryKey = PREFETCH_CONFIG[prefetchKey]; + + const { records } = useFindManyRecords({ + skip: !isDataPrefetched, + ...prefetchQueryKey, + useRecordsWithoutConnection: true, + }); + + return { + isDataPrefetched, + records, + }; +}; diff --git a/packages/twenty-front/src/modules/prefetch/query-keys/AllFavoritesQueryKey.ts b/packages/twenty-front/src/modules/prefetch/query-keys/AllFavoritesQueryKey.ts new file mode 100644 index 000000000..a5e442715 --- /dev/null +++ b/packages/twenty-front/src/modules/prefetch/query-keys/AllFavoritesQueryKey.ts @@ -0,0 +1,8 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { QueryKey } from '@/object-record/query-keys/types/QueryKey'; + +export const ALL_FAVORITES_QUERY_KEY: QueryKey = { + objectNameSingular: CoreObjectNameSingular.Favorite, + variables: {}, + depth: 1, +}; diff --git a/packages/twenty-front/src/modules/prefetch/query-keys/AllViewsQueryKey.ts b/packages/twenty-front/src/modules/prefetch/query-keys/AllViewsQueryKey.ts new file mode 100644 index 000000000..df1c78163 --- /dev/null +++ b/packages/twenty-front/src/modules/prefetch/query-keys/AllViewsQueryKey.ts @@ -0,0 +1,8 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { QueryKey } from '@/object-record/query-keys/types/QueryKey'; + +export const ALL_VIEWS_QUERY_KEY: QueryKey = { + objectNameSingular: CoreObjectNameSingular.View, + variables: {}, + depth: 1, +}; diff --git a/packages/twenty-front/src/modules/prefetch/states/prefetchIsLoadedFamilyState.ts b/packages/twenty-front/src/modules/prefetch/states/prefetchIsLoadedFamilyState.ts new file mode 100644 index 000000000..38069ee05 --- /dev/null +++ b/packages/twenty-front/src/modules/prefetch/states/prefetchIsLoadedFamilyState.ts @@ -0,0 +1,10 @@ +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState'; + +export const prefetchIsLoadedFamilyState = createFamilyState< + boolean, + PrefetchKey +>({ + key: 'prefetchIsLoadedFamilyState', + defaultValue: false, +}); diff --git a/packages/twenty-front/src/modules/prefetch/types/PrefetchKey.ts b/packages/twenty-front/src/modules/prefetch/types/PrefetchKey.ts new file mode 100644 index 000000000..f7ebf434e --- /dev/null +++ b/packages/twenty-front/src/modules/prefetch/types/PrefetchKey.ts @@ -0,0 +1,4 @@ +export enum PrefetchKey { + AllViews = 'ALL_VIEWS', + AllFavorites = 'ALL_FAVORITES', +} diff --git a/packages/twenty-front/src/modules/views/components/ViewBarEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarEffect.tsx index b5e8dd3e3..b0b645f83 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarEffect.tsx @@ -2,8 +2,8 @@ import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { useViewBar } from '@/views/hooks/useViewBar'; import { GraphQLView } from '@/views/types/GraphQLView'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -31,25 +31,26 @@ export const ViewBarEffect = () => { const viewObjectMetadataId = useRecoilValue(viewObjectMetadataIdState); const setCurrentViewId = useSetRecoilState(currentViewIdState); - const { records: newViews } = useFindManyRecords({ - skip: !viewObjectMetadataId, - objectNameSingular: CoreObjectNameSingular.View, - filter: { - objectMetadataId: { eq: viewObjectMetadataId }, - }, - useRecordsWithoutConnection: true, - }); + const { records: newViews } = usePrefetchedData( + PrefetchKey.AllViews, + ); + + const newViewsOnCurrentObject = newViews.filter( + (view) => view.objectMetadataId === viewObjectMetadataId, + ); useEffect(() => { - if (!newViews.length) return; + if (!newViewsOnCurrentObject.length) return; - if (!isDeeplyEqual(views, newViews)) { - setViews(newViews); + if (!isDeeplyEqual(views, newViewsOnCurrentObject)) { + setViews(newViewsOnCurrentObject); } const currentView = - newViews.find((view) => view.id === currentViewIdFromUrl) ?? - newViews[0] ?? + newViewsOnCurrentObject.find( + (view) => view.id === currentViewIdFromUrl, + ) ?? + newViewsOnCurrentObject[0] ?? null; if (isUndefinedOrNull(currentView)) return; @@ -69,17 +70,17 @@ export const ViewBarEffect = () => { loadViewFields, loadViewFilters, loadViewSorts, - newViews, + newViewsOnCurrentObject, setCurrentViewId, setViews, views, ]); useEffect(() => { - if (!currentViewIdFromUrl || !newViews.length) return; + if (!currentViewIdFromUrl || !newViewsOnCurrentObject.length) return; loadView(currentViewIdFromUrl); - }, [currentViewIdFromUrl, loadView, newViews]); + }, [currentViewIdFromUrl, loadView, newViewsOnCurrentObject]); return <>; };