From ba9aa41bba08d0c3ee7a8dda919bc3943e5ed4b9 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 16 Mar 2026 00:38:11 +0100 Subject: [PATCH] refactor: metadata store cleanup, SSE unification, mock metadata loading & login redirect fix (#18651) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - **SSE unification**: Replaced 11 individual SSE effect components with a single generic `MetadataStoreSSEEffect` - **Metadata store cleanup**: Merged `metadataCollectionHashesState` into `metadataStoreState` (currentCollectionHash / draftCollectionHash per entity), moved `objectMetadataItemsSelector` to `object-metadata` domain, converted `navigationMenuItemsState` to a derived selector - **Naming clarity**: Renamed `isAppMetadataReadyState` → `isMinimalMetadataReadyState`, `MetadataGater` → `MinimalMetadataGater`, `useIsLogged` → `useHasAccessTokenPair`, `patchMetadataStoreFromSSEEvent` now takes named object params - **Mock metadata loading**: Added `generate-navigation-menu-items.ts` script, rewrote `useLoadMockedMinimalMetadata` to load full objects/fields/indexes/views/navItems from generated mock data, enabling proper sign-in background rendering (table columns, view picker, navigation) - **Login/logout transitions**: `MinimalMetadataLoadEffect` manages mocked↔real metadata transitions based on auth state, `MainContextStoreProvider` computes context on auth pages for view picker support - **Login redirect fix**: `handleLoadWorkspaceAfterAuthentication` now re-enables `isAppEffectRedirectEnabled` after `loadCurrentUser()` completes, fixing the blocked post-login navigation - **Dead code removal**: Deleted `useRefreshPageLayouts`, `useApplyPageLayouts`, `useStaleMetadataEntities`, `metadataCollectionHashesState`, and all individual SSE effects ## Test plan - [x] Login from welcome page redirects to companies page - [x] Logout transitions cleanly to mocked metadata on welcome page - [x] Sign-in background shows table columns, view picker, and navigation items - [x] SSE events still update metadata store entries correctly - [x] Navigation menu items persist across page refreshes - [ ] CI: lint, typecheck, tests pass --- .../scripts/generate-mock-data.ts | 8 +- .../mock-data/generate-minimal-metadata.ts | 51 ++ .../generate-navigation-menu-items.ts | 50 + ...etadata.ts => generate-object-metadata.ts} | 2 +- .../src/generated-metadata/graphql.ts | 8 +- ...sePageChangeEffectNavigateLocation.test.ts | 446 ++++----- .../usePageChangeEffectNavigateLocation.ts | 10 +- .../app/components/AppRouterProviders.tsx | 16 +- .../components/VerifyLoginTokenEffect.tsx | 6 +- .../auth/hooks/__tests__/useAuth.test.tsx | 6 - ....test.ts => useHasAccessTokenPair.test.ts} | 12 +- .../src/modules/auth/hooks/useAuth.ts | 21 +- ...seIsLogged.ts => useHasAccessTokenPair.ts} | 2 +- .../states/objectPermissionsFamilySelector.ts | 4 +- .../components/MainContextStoreProvider.tsx | 21 +- ...dataGater.tsx => MinimalMetadataGater.tsx} | 17 +- .../FieldMetadataSSEEffect.tsx | 33 - ...t.tsx => IsMinimalMetadataReadyEffect.tsx} | 31 +- .../MetadataProviderInitialEffects.tsx | 11 - .../MetadataStoreSSEEffect.tsx | 28 + .../MinimalMetadataLoadEffect.tsx | 79 +- .../NavigationMenuItemSSEEffect.tsx | 71 -- .../ObjectMetadataItemSSEEffect.tsx | 61 -- .../ObjectMetadataProviderInitialEffect.tsx | 47 - .../effect-components/PageLayoutSSEEffect.tsx | 37 - .../PageLayoutTabSSEEffect.tsx | 37 - .../PageLayoutWidgetSSEEffect.tsx | 37 - .../UserMetadataProviderInitialEffect.tsx | 10 +- .../effect-components/ViewFieldSSEEffect.tsx | 86 -- .../ViewFilterGroupSSEEffect.tsx | 73 -- .../effect-components/ViewFilterSSEEffect.tsx | 71 -- .../ViewMetadataProviderInitialEffect.tsx | 47 - .../ViewRelationsSSEEffect.tsx | 130 --- .../effect-components/ViewSSEEffect.tsx | 93 -- .../effect-components/ViewSortSSEEffect.tsx | 71 -- .../graphql/queries/findMinimalMetadata.ts | 5 +- .../hooks/useFetchAndLoadIndexViews.ts | 51 -- ...tadataItemIdFromViewRelationEventDetail.ts | 39 - ...GetViewIdFromViewSSERelationEventDetail.ts | 64 -- .../hooks/useLoadMinimalMetadata.ts | 58 +- .../hooks/useLoadMockedMinimalMetadata.ts | 64 ++ .../hooks/useLoadStaleMetadataEntities.ts | 206 +++++ .../metadata-store/hooks/useMetadataStore.ts | 18 +- .../hooks/useReloadWorkspaceMetadata.ts | 54 -- .../metadata-store/hooks/useSetIndexViews.ts | 29 - .../hooks/useStaleMetadataEntities.ts | 27 - ...tate.ts => isMinimalMetadataReadyState.ts} | 4 +- .../states/metadataCollectionHashesState.ts | 11 - .../states/metadataStoreState.ts | 2 + .../utils/patchMetadataStoreFromSSEEvent.ts | 34 +- ...rkspaceNavigationMenuItemFolderSubItem.tsx | 4 +- .../hooks/useHandleAddToNavigationDrop.ts | 4 +- .../hooks/useNavigationMenuItemsByFolder.ts | 4 +- ...seNavigationMenuObjectMetadataFromDraft.ts | 4 +- ...emoveNavigationMenuItemByTargetRecordId.ts | 23 +- ...avigationMenuItemEditItemObjectMetadata.ts | 4 +- .../hooks/useSortedNavigationMenuItems.ts | 4 +- .../hooks/useWorkspaceNavigationMenuItems.ts | 4 +- .../hooks/useWorkspaceSectionItems.ts | 4 +- .../states/navigationMenuItemsState.ts | 13 +- .../__tests__/useDefaultHomePagePath.test.ts | 23 +- .../hooks/useDefaultHomePagePath.ts | 4 +- ...tVisitedViewForObjectMetadataNamePlural.ts | 4 +- ...erSectionForWorkspaceItemObjectContent.tsx | 4 +- ...vigationDrawerSectionForWorkspaceItems.tsx | 4 +- .../__tests__/useFieldMetadataItem.test.tsx | 2 - .../useFindManyObjectMetadataItems.test.tsx | 48 - .../hooks/useCreateOneFieldMetadataItem.ts | 12 - .../hooks/useCreateOneObjectMetadataItem.ts | 17 - .../hooks/useDeleteOneFieldMetadataItem.ts | 12 - .../hooks/useDeleteOneObjectMetadataItem.ts | 10 - .../hooks/useFindManyObjectMetadataItems.ts | 48 - .../hooks/useLoadMockedObjectMetadataItems.ts | 9 +- .../hooks/useRefreshObjectMetadataItems.ts | 48 - .../hooks/useUpdateOneFieldMetadataItem.ts | 11 - .../hooks/useUpdateOneObjectMetadataItem.ts | 11 - .../activeObjectNameSingularsSelector.ts | 4 +- .../flatObjectMetadataItemsSelector.ts} | 4 +- ...ystemObjectByNameSingularFamilySelector.ts | 4 +- .../objectMetadataItemsWithFieldsSelector.ts | 4 +- .../useSetViewTypeFromLayoutOptionsMenu.ts | 37 +- .../RecordCalendarMonth.stories.tsx | 11 +- .../hooks/useHandleRecordGroupField.ts | 6 - .../hooks/useViewFieldAggregateOperation.tsx | 8 - .../onboarding/hooks/useOnboardingStatus.ts | 6 +- .../page-layout/hooks/useApplyPageLayouts.ts | 51 -- .../hooks/useRefreshPageLayouts.ts | 28 - .../hooks/useSaveFieldsWidgetGroups.ts | 6 - .../DashboardWidgetPlaceholder.stories.tsx | 4 +- .../__stories__/WidgetRenderer.stories.tsx | 30 +- .../__stories__/FieldWidget.stories.tsx | 36 +- .../__stories__/FieldsWidget.stories.tsx | 206 ++--- .../SettingsProtectedRouteWrapper.tsx | 6 +- .../SettingsObjectFieldItemTableRow.tsx | 1 - .../SettingsObjectRelationItemTableRow.tsx | 1 - .../roles/role/components/SettingsRole.tsx | 3 - .../SignInBackgroundMockContainer.tsx | 1 + .../components/SSEClientEffect.tsx | 8 +- .../components/SSEEventStreamEffect.tsx | 8 +- .../sse-db-event/components/SSEProvider.tsx | 24 +- .../components/LazyMetadataLoadEffect.tsx | 175 ---- .../modules/users/hooks/useLoadCurrentUser.ts | 19 +- .../components/UpdateViewButtonGroup.tsx | 8 - .../src/modules/views/components/ViewBar.tsx | 12 +- .../ViewBarFilterDropdown.stories.tsx | 11 +- ...GroupsToCurrentRecordFilterGroups.test.tsx | 6 +- ...ViewFiltersToCurrentRecordFilters.test.tsx | 8 +- ...rentViewSortsToCurrentRecordSorts.test.tsx | 10 +- .../internal/usePerformViewAPIPersist.ts | 1 - .../hooks/internal/usePerformViewAPIUpdate.ts | 33 +- .../internal/usePerformViewFieldAPIPersist.ts | 50 +- .../usePerformViewFieldGroupAPIPersist.ts | 37 - .../usePerformViewFilterAPIPersist.ts | 55 +- .../internal/usePerformViewGroupAPIPersist.ts | 22 +- .../internal/usePerformViewSortAPIPersist.ts | 60 +- .../useApplyCoreViewsForObjectMetadataId.ts | 173 ---- .../hooks/useCreateDefaultViewForObject.ts | 11 +- .../hooks/useCreateViewFromCurrentView.ts | 7 - .../views/hooks/useRefreshAllCoreViews.ts | 41 - .../useRefreshCoreViewsByObjectMetadataId.ts | 37 - ...aveRecordFilterGroupsToViewFilterGroups.ts | 32 - .../useSaveRecordFiltersToViewFilters.ts | 4 +- .../views/hooks/useUpdateCurrentView.ts | 6 - .../views/hooks/useUpdateViewAggregate.ts | 16 +- .../hooks/useViewsSideEffectsOnViewGroups.ts | 20 - ...seTriggerViewFieldGroupOptimisticEffect.ts | 163 ---- .../useTriggerViewFieldOptimisticEffect.ts | 147 --- .../useTriggerViewFilterOptimisticEffect.ts | 147 --- .../useTriggerViewGroupOptimisticEffect.ts | 146 --- .../useTriggerViewSortOptimisticEffect.ts | 149 --- .../src/modules/views/states/coreViewState.ts | 7 - ...wIdFromObjectMetadataItemFamilySelector.ts | 4 +- .../coreViewFromViewIdFamilySelector.ts | 4 +- ...IdsFromObjectMetadataItemFamilySelector.ts | 4 +- .../selectors/coreViewLengthFamilySelector.ts | 4 +- ...reViewsByObjectMetadataIdFamilySelector.ts | 21 +- ...ewsFromObjectMetadataItemFamilySelector.ts | 4 +- .../states/selectors/coreViewsSelector.ts | 95 ++ .../views/types/CoreViewWithoutRelations.ts | 11 - .../hooks/useDestroyViewFromCurrentState.ts | 9 - .../src/pages/auth/PasswordReset.tsx | 6 +- .../src/pages/onboarding/CreateWorkspace.tsx | 11 - .../data-model/SettingsObjectFieldEdit.tsx | 1 - .../appearance/components/LocalePicker.tsx | 11 +- .../src/testing/decorators/PageDecorator.tsx | 21 +- .../decorators/RecordTableDecorator.tsx | 17 +- .../twenty-front/src/testing/graphqlMocks.ts | 57 +- .../jest/JestObjectMetadataItemSetter.tsx | 9 +- .../getJestMetadataAndApolloMocksWrapper.tsx | 63 +- .../metadata/minimal/mock-minimal-metadata.ts | 864 ++++++++++++++++++ .../mock-navigation-menu-items-data.ts | 316 +++++++ .../utils/setTestCoreViewsInMetadataStore.ts | 63 ++ .../clients/generated/metadata/schema.graphql | 61 +- .../src/clients/generated/metadata/schema.ts | 83 +- .../src/clients/generated/metadata/types.ts | 601 ++++++------ .../dtos/collection-hash.dto.ts | 15 + .../dtos/minimal-metadata.dto.ts | 6 +- .../minimal-metadata.service.ts | 18 +- .../services/fields-widget-upsert.service.ts | 21 +- .../workspace-event-emitter.service.ts | 79 +- .../minimal-metadata.integration-spec.ts | 133 +++ 161 files changed, 3106 insertions(+), 4350 deletions(-) create mode 100644 packages/twenty-front/scripts/mock-data/generate-minimal-metadata.ts create mode 100644 packages/twenty-front/scripts/mock-data/generate-navigation-menu-items.ts rename packages/twenty-front/scripts/mock-data/{generate-metadata.ts => generate-object-metadata.ts} (98%) rename packages/twenty-front/src/modules/auth/hooks/__tests__/{useIsLogged.test.ts => useHasAccessTokenPair.test.ts} (71%) rename packages/twenty-front/src/modules/auth/hooks/{useIsLogged.ts => useHasAccessTokenPair.ts} (80%) rename packages/twenty-front/src/modules/metadata-store/components/{MetadataGater.tsx => MinimalMetadataGater.tsx} (62%) delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/FieldMetadataSSEEffect.tsx rename packages/twenty-front/src/modules/metadata-store/effect-components/{IsAppMetadataReadyEffect.tsx => IsMinimalMetadataReadyEffect.tsx} (67%) delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/MetadataProviderInitialEffects.tsx create mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/MetadataStoreSSEEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/NavigationMenuItemSSEEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/ObjectMetadataItemSSEEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/ObjectMetadataProviderInitialEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/PageLayoutSSEEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/PageLayoutTabSSEEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/PageLayoutWidgetSSEEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/ViewFieldSSEEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/ViewFilterGroupSSEEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/ViewFilterSSEEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/ViewMetadataProviderInitialEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/ViewRelationsSSEEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/ViewSSEEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/effect-components/ViewSortSSEEffect.tsx delete mode 100644 packages/twenty-front/src/modules/metadata-store/hooks/useFetchAndLoadIndexViews.ts delete mode 100644 packages/twenty-front/src/modules/metadata-store/hooks/useGetObjectMetadataItemIdFromViewRelationEventDetail.ts delete mode 100644 packages/twenty-front/src/modules/metadata-store/hooks/useGetViewIdFromViewSSERelationEventDetail.ts create mode 100644 packages/twenty-front/src/modules/metadata-store/hooks/useLoadMockedMinimalMetadata.ts create mode 100644 packages/twenty-front/src/modules/metadata-store/hooks/useLoadStaleMetadataEntities.ts delete mode 100644 packages/twenty-front/src/modules/metadata-store/hooks/useReloadWorkspaceMetadata.ts delete mode 100644 packages/twenty-front/src/modules/metadata-store/hooks/useSetIndexViews.ts delete mode 100644 packages/twenty-front/src/modules/metadata-store/hooks/useStaleMetadataEntities.ts rename packages/twenty-front/src/modules/metadata-store/states/{isAppMetadataReadyState.ts => isMinimalMetadataReadyState.ts} (50%) delete mode 100644 packages/twenty-front/src/modules/metadata-store/states/metadataCollectionHashesState.ts delete mode 100644 packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFindManyObjectMetadataItems.test.tsx delete mode 100644 packages/twenty-front/src/modules/object-metadata/hooks/useFindManyObjectMetadataItems.ts delete mode 100644 packages/twenty-front/src/modules/object-metadata/hooks/useRefreshObjectMetadataItems.ts rename packages/twenty-front/src/modules/{metadata-store/states/objectMetadataItemsSelector.ts => object-metadata/states/flatObjectMetadataItemsSelector.ts} (80%) delete mode 100644 packages/twenty-front/src/modules/page-layout/hooks/useApplyPageLayouts.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/hooks/useRefreshPageLayouts.ts delete mode 100644 packages/twenty-front/src/modules/users/components/LazyMetadataLoadEffect.tsx delete mode 100644 packages/twenty-front/src/modules/views/hooks/useApplyCoreViewsForObjectMetadataId.ts delete mode 100644 packages/twenty-front/src/modules/views/hooks/useRefreshAllCoreViews.ts delete mode 100644 packages/twenty-front/src/modules/views/hooks/useRefreshCoreViewsByObjectMetadataId.ts delete mode 100644 packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldGroupOptimisticEffect.ts delete mode 100644 packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect.ts delete mode 100644 packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFilterOptimisticEffect.ts delete mode 100644 packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect.ts delete mode 100644 packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewSortOptimisticEffect.ts delete mode 100644 packages/twenty-front/src/modules/views/states/coreViewState.ts create mode 100644 packages/twenty-front/src/modules/views/states/selectors/coreViewsSelector.ts delete mode 100644 packages/twenty-front/src/modules/views/types/CoreViewWithoutRelations.ts create mode 100644 packages/twenty-front/src/testing/mock-data/generated/metadata/minimal/mock-minimal-metadata.ts create mode 100644 packages/twenty-front/src/testing/mock-data/generated/metadata/navigation-menu-items/mock-navigation-menu-items-data.ts create mode 100644 packages/twenty-front/src/testing/utils/setTestCoreViewsInMetadataStore.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/minimal-metadata/dtos/collection-hash.dto.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/minimal-metadata/minimal-metadata.integration-spec.ts diff --git a/packages/twenty-front/scripts/generate-mock-data.ts b/packages/twenty-front/scripts/generate-mock-data.ts index 7b0e08bb34..dfc74a1625 100644 --- a/packages/twenty-front/scripts/generate-mock-data.ts +++ b/packages/twenty-front/scripts/generate-mock-data.ts @@ -1,19 +1,23 @@ /* oxlint-disable no-console */ import { generateApiKeys } from './mock-data/generate-api-keys.js'; import { generateBillingPlans } from './mock-data/generate-billing-plans.js'; -import { generateMetadata } from './mock-data/generate-metadata.js'; +import { generateObjectMetadata } from './mock-data/generate-object-metadata.js'; +import { generateMinimalMetadata } from './mock-data/generate-minimal-metadata.js'; import { generateRecordData } from './mock-data/generate-record-data.js'; import { generateRoles } from './mock-data/generate-roles.js'; +import { generateNavigationMenuItems } from './mock-data/generate-navigation-menu-items.js'; import { generateViews } from './mock-data/generate-views.js'; import { authenticate } from './mock-data/utils.js'; const main = async () => { const token = await authenticate(); - const metadata = await generateMetadata(token); + const metadata = await generateObjectMetadata(token); + await generateMinimalMetadata(token); await generateRecordData(token, metadata); await generateRoles(token); await generateViews(token); + await generateNavigationMenuItems(token); try { await generateBillingPlans(token); diff --git a/packages/twenty-front/scripts/mock-data/generate-minimal-metadata.ts b/packages/twenty-front/scripts/mock-data/generate-minimal-metadata.ts new file mode 100644 index 0000000000..736c4eec8d --- /dev/null +++ b/packages/twenty-front/scripts/mock-data/generate-minimal-metadata.ts @@ -0,0 +1,51 @@ +/* oxlint-disable no-console */ +import { graphqlRequest, writeGeneratedFile } from './utils.js'; + +const MINIMAL_METADATA_QUERY = ` + query FindMinimalMetadata { + minimalMetadata { + objectMetadataItems { + id + nameSingular + namePlural + labelSingular + labelPlural + icon + isCustom + isActive + isSystem + isRemote + } + views { + id + type + key + objectMetadataId + } + collectionHashes { + collectionName + hash + } + } + } +`; + +export const generateMinimalMetadata = async (token: string) => { + console.log('Fetching minimal metadata from /metadata ...'); + + const data = (await graphqlRequest( + '/metadata', + MINIMAL_METADATA_QUERY, + token, + )) as { + minimalMetadata: Record; + }; + + writeGeneratedFile( + 'metadata/minimal/mock-minimal-metadata.ts', + 'mockedMinimalMetadata', + 'MinimalMetadata', + "import { type MinimalMetadata } from '@/metadata-store/types/MinimalMetadata';", + data.minimalMetadata, + ); +}; diff --git a/packages/twenty-front/scripts/mock-data/generate-navigation-menu-items.ts b/packages/twenty-front/scripts/mock-data/generate-navigation-menu-items.ts new file mode 100644 index 0000000000..e6a38cdbd1 --- /dev/null +++ b/packages/twenty-front/scripts/mock-data/generate-navigation-menu-items.ts @@ -0,0 +1,50 @@ +/* oxlint-disable no-console, lingui/no-unlocalized-strings */ +import { graphqlRequest, writeGeneratedFile } from './utils.js'; + +const FIND_MANY_NAVIGATION_MENU_ITEMS_QUERY = ` + query FindManyNavigationMenuItems { + navigationMenuItems { + id + userWorkspaceId + targetRecordId + targetObjectMetadataId + viewId + folderId + name + link + icon + color + position + applicationId + createdAt + updatedAt + targetRecordIdentifier { + id + labelIdentifier + imageIdentifier + } + } + } +`; + +export const generateNavigationMenuItems = async (token: string) => { + console.log('Fetching navigation menu items from /metadata ...'); + + const data = (await graphqlRequest( + '/metadata', + FIND_MANY_NAVIGATION_MENU_ITEMS_QUERY, + token, + )) as { + navigationMenuItems: Record[]; + }; + + console.log(` Got ${data.navigationMenuItems.length} navigation menu items.`); + + writeGeneratedFile( + 'metadata/navigation-menu-items/mock-navigation-menu-items-data.ts', + 'mockedNavigationMenuItems', + 'NavigationMenuItem[]', + "import { type NavigationMenuItem } from '~/generated-metadata/graphql';", + data.navigationMenuItems, + ); +}; diff --git a/packages/twenty-front/scripts/mock-data/generate-metadata.ts b/packages/twenty-front/scripts/mock-data/generate-object-metadata.ts similarity index 98% rename from packages/twenty-front/scripts/mock-data/generate-metadata.ts rename to packages/twenty-front/scripts/mock-data/generate-object-metadata.ts index bb143bf222..62f9593e22 100644 --- a/packages/twenty-front/scripts/mock-data/generate-metadata.ts +++ b/packages/twenty-front/scripts/mock-data/generate-object-metadata.ts @@ -141,7 +141,7 @@ const METADATA_QUERY = ` } `; -export const generateMetadata = async (token: string) => { +export const generateObjectMetadata = async (token: string) => { console.log('Fetching object metadata from /metadata ...'); const metadata = await graphqlRequest('/metadata', METADATA_QUERY, token); diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index fc8ffad795..959fc0bdc6 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -831,6 +831,12 @@ export type ClientConfig = { support: Support; }; +export type CollectionHash = { + __typename?: 'CollectionHash'; + collectionName: AllMetadataName; + hash: Scalars['String']; +}; + export type CommandMenuItem = { __typename?: 'CommandMenuItem'; applicationId?: Maybe; @@ -2422,7 +2428,7 @@ export type MetadataEventWithQueryIds = { export type MinimalMetadata = { __typename?: 'MinimalMetadata'; - collectionHashes: Scalars['JSON']; + collectionHashes: Array; objectMetadataItems: Array; views: Array; }; diff --git a/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts b/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts index 3e465eb64e..bff5bd11e7 100644 --- a/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts +++ b/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts @@ -1,4 +1,4 @@ -import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair'; import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath'; import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus'; import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo'; @@ -39,9 +39,9 @@ const setupMockIsMatchingLocation = (pathname: string) => { ); }; -jest.mock('@/auth/hooks/useIsLogged'); -const setupMockIsLogged = (isLogged: boolean) => { - jest.mocked(useIsLogged).mockReturnValueOnce(isLogged); +jest.mock('@/auth/hooks/useHasAccessTokenPair'); +const setupMockHasAccessTokenPair = (hasAccessTokenPair: boolean) => { + jest.mocked(useHasAccessTokenPair).mockReturnValueOnce(hasAccessTokenPair); }; const defaultHomePagePath = '/objects/companies'; @@ -83,7 +83,7 @@ const setupMockState = ( // prettier-ignore const testCases: { loc: AppPath; - isLoggedIn: boolean; + hasAccessTokenPair: boolean; isWorkspaceSuspended: boolean; onboardingStatus: OnboardingStatus | undefined; res: string | undefined; @@ -93,248 +93,248 @@ const testCases: { verifyEmailRedirectPath?: string; returnToPath?: string; }[] = [ - { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.Verify, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, - { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, + { loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.Verify, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, + { loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, - { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.SignInUp, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, - { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, + { loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.SignInUp, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, + { loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, - { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' }, - { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.Invite, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, - { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' }, - { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' }, - { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' }, - { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' }, - { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, + { loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' }, + { loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.Invite, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, + { loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' }, + { loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' }, + { loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' }, + { loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' }, + { loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.Invite, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, - { loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' }, - { loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.ResetPassword, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, - { loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' }, - { loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' }, - { loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' }, - { loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' }, - { loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, + { loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' }, + { loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.ResetPassword, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, + { loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' }, + { loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' }, + { loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' }, + { loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' }, + { loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.ResetPassword, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, - { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, verifyEmailRedirectPath: '/nextPath?key=value', res: '/nextPath?key=value' }, - { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.VerifyEmail, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, verifyEmailRedirectPath: '/nextPath?key=value', res: undefined }, - { loc: AppPath.VerifyEmail, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, - { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, + { loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, verifyEmailRedirectPath: '/nextPath?key=value', res: '/nextPath?key=value' }, + { loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.VerifyEmail, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, verifyEmailRedirectPath: '/nextPath?key=value', res: undefined }, + { loc: AppPath.VerifyEmail, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, + { loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.VerifyEmail, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, - { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.CreateWorkspace, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined }, - { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, + { loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.CreateWorkspace, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined }, + { loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.CreateWorkspace, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, - { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.CreateProfile, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: undefined }, - { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, + { loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.CreateProfile, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: undefined }, + { loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.CreateProfile, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, - { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.SyncEmails, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: undefined }, - { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, + { loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.SyncEmails, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: undefined }, + { loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.SyncEmails, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, - { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.InviteTeam, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: undefined }, - { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, + { loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.InviteTeam, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: undefined }, + { loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.InviteTeam, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, - { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined }, - { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.BookCallDecision, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined }, - { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined }, - { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, + { loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined }, + { loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.BookCallDecision, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined }, + { loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined }, + { loc: AppPath.BookCallDecision, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, - { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined }, - { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.BookCall, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined }, - { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined }, - { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, + { loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined }, + { loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.BookCall, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined }, + { loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined }, + { loc: AppPath.BookCall, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, - { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined }, - { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.PlanRequired, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, + { loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined }, + { loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.PlanRequired, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.PlanRequired, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, - { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined }, - { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.PlanRequiredSuccess, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, + { loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined }, + { loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.PlanRequiredSuccess, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, - { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.Index, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, + { loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.Index, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, - { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.TasksPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, + { loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.TasksPage, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.TasksPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, - { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.OpportunitiesPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, + { loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.OpportunitiesPage, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.OpportunitiesPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, - { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.RecordIndexPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, - { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined, objectNamePluralFromParams: 'existing-object', objectNamePluralFromMetadata: 'existing-object' }, - { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: AppPath.NotFound, objectNamePluralFromParams: 'non-existing-object', objectNamePluralFromMetadata: 'existing-object' }, + { loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.RecordIndexPage, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, + { loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined, objectNamePluralFromParams: 'existing-object', objectNamePluralFromMetadata: 'existing-object' }, + { loc: AppPath.RecordIndexPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: AppPath.NotFound, objectNamePluralFromParams: 'non-existing-object', objectNamePluralFromMetadata: 'existing-object' }, - { loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.RecordShowPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, + { loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.RecordShowPage, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.RecordShowPage, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, - { loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, - { loc: AppPath.SettingsCatchAll, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, + { loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, + { loc: AppPath.SettingsCatchAll, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.SettingsCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, - { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.DevelopersCatchAll, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, + { loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.DevelopersCatchAll, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, - { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.Authorize, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, + { loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.Authorize, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.Authorize, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, - { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.NotFoundWildcard, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, + { loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.NotFoundWildcard, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.NotFoundWildcard, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, - { loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, - { loc: AppPath.NotFound, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, - { loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, - { loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, - { loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, - { loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, - { loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, - { loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, + { loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, + { loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.NotFound, hasAccessTokenPair: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, + { loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, + { loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, + { loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails }, + { loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam }, + { loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision }, + { loc: AppPath.NotFound, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, // returnToPath: should redirect to saved path instead of defaultHomePagePath - { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/authorize?clientId=abc', res: '/authorize?clientId=abc' }, - { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/objects/tasks', res: '/objects/tasks' }, - { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/settings/api-keys', res: '/settings/api-keys' }, + { loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/authorize?clientId=abc', res: '/authorize?clientId=abc' }, + { loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/objects/tasks', res: '/objects/tasks' }, + { loc: AppPath.Index, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, returnToPath: '/settings/api-keys', res: '/settings/api-keys' }, // isOnAWorkspace:false — on default domain, don't redirect to returnToPath or defaultHomePagePath from auth pages - { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, isOnAWorkspace: false, res: undefined }, - { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, isOnAWorkspace: false, res: undefined }, + { loc: AppPath.Verify, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, isOnAWorkspace: false, res: undefined }, + { loc: AppPath.SignInUp, hasAccessTokenPair: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, isOnAWorkspace: false, res: undefined }, ]; describe('usePageChangeEffectNavigateLocation', () => { @@ -344,7 +344,7 @@ describe('usePageChangeEffectNavigateLocation', () => { loc, onboardingStatus, isWorkspaceSuspended, - isLoggedIn, + hasAccessTokenPair, isOnAWorkspace, objectNamePluralFromParams, objectNamePluralFromMetadata, @@ -355,7 +355,7 @@ describe('usePageChangeEffectNavigateLocation', () => { setupMockIsMatchingLocation(loc); setupMockOnboardingStatus(onboardingStatus); setupMockIsWorkspaceActivationStatusEqualsTo(isWorkspaceSuspended); - setupMockIsLogged(isLoggedIn); + setupMockHasAccessTokenPair(hasAccessTokenPair); setupMockIsOnAWorkspace(isOnAWorkspace ?? true); setupMockUseParams(objectNamePluralFromParams); setupMockState( diff --git a/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts b/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts index 660bf2f95e..27bcab76e3 100644 --- a/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts +++ b/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts @@ -1,7 +1,7 @@ import { verifyEmailRedirectPathState } from '@/app/states/verifyEmailRedirectPathState'; import { ONBOARDING_PATHS } from '@/auth/constants/OnboardingPaths'; import { ONGOING_USER_CREATION_PATHS } from '@/auth/constants/OngoingUserCreationPaths'; -import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair'; import { returnToPathState } from '@/auth/states/returnToPathState'; import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState'; import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace'; @@ -26,7 +26,7 @@ const readReturnToPathFromUrlSearchParams = (): string | null => { }; export const usePageChangeEffectNavigateLocation = () => { - const isLoggedIn = useIsLogged(); + const hasAccessTokenPair = useHasAccessTokenPair(); const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace(); const onboardingStatus = useOnboardingStatus(); const isWorkspaceSuspended = useIsWorkspaceActivationStatusEqualsTo( @@ -54,7 +54,7 @@ export const usePageChangeEffectNavigateLocation = () => { : readReturnToPathFromUrlSearchParams(); if ( - (!isLoggedIn || (isLoggedIn && !isOnAWorkspace)) && + (!hasAccessTokenPair || (hasAccessTokenPair && !isOnAWorkspace)) && !someMatchingLocationOf([ ...ONGOING_USER_CREATION_PATHS, AppPath.ResetPassword, @@ -140,13 +140,13 @@ export const usePageChangeEffectNavigateLocation = () => { ...ONGOING_USER_CREATION_PATHS, ]) && !isMatchingLocation(location, AppPath.ResetPassword) && - isLoggedIn && + hasAccessTokenPair && isOnAWorkspace ) { return resolvedReturnToPath ?? defaultHomePagePath; } - if (isMatchingLocation(location, AppPath.Index) && isLoggedIn) { + if (isMatchingLocation(location, AppPath.Index) && hasAccessTokenPair) { return resolvedReturnToPath ?? defaultHomePagePath; } diff --git a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx index b03a42041f..082c3a1a8b 100644 --- a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx +++ b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx @@ -1,12 +1,12 @@ import { AgentChatProvider } from '@/ai/components/AgentChatProvider'; import { CommandMenuConfirmationModalManager } from '@/command-menu-item/confirmation-modal/components/CommandMenuConfirmationModalManager'; import { ApolloProvider } from '@/apollo/components/ApolloProvider'; -import { MetadataGater } from '@/metadata-store/components/MetadataGater'; -import { IsAppMetadataReadyEffect } from '@/metadata-store/effect-components/IsAppMetadataReadyEffect'; +import { MinimalMetadataGater } from '@/metadata-store/components/MinimalMetadataGater'; +import { IsMinimalMetadataReadyEffect } from '@/metadata-store/effect-components/IsMinimalMetadataReadyEffect'; import { GotoHotkeysEffectsProvider } from '@/app/effect-components/GotoHotkeysEffectsProvider'; import { MinimalMetadataLoadEffect } from '@/metadata-store/effect-components/MinimalMetadataLoadEffect'; -import { MetadataProviderInitialEffects } from '@/metadata-store/effect-components/MetadataProviderInitialEffects'; +import { UserMetadataProviderInitialEffect } from '@/metadata-store/effect-components/UserMetadataProviderInitialEffect'; import { PageChangeEffect } from '@/app/effect-components/PageChangeEffect'; import { AuthProvider } from '@/auth/components/AuthProvider'; import { CaptchaProvider } from '@/captcha/components/CaptchaProvider'; @@ -31,7 +31,6 @@ import { BaseThemeProvider } from '@/ui/theme/components/BaseThemeProvider'; import { UserThemeProviderEffect } from '@/ui/theme/components/UserThemeProviderEffect'; import { PageFavicon } from '@/ui/utilities/page-favicon/components/PageFavicon'; import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle'; -import { LazyMetadataLoadEffect } from '@/users/components/LazyMetadataLoadEffect'; import { WorkspaceProviderEffect } from '@/workspace/components/WorkspaceProviderEffect'; import { StrictMode } from 'react'; import { Outlet, useLocation } from 'react-router-dom'; @@ -45,16 +44,15 @@ export const AppRouterProviders = () => { + - - - + - + @@ -88,7 +86,7 @@ export const AppRouterProviders = () => { - + diff --git a/packages/twenty-front/src/modules/auth/components/VerifyLoginTokenEffect.tsx b/packages/twenty-front/src/modules/auth/components/VerifyLoginTokenEffect.tsx index 7aa67459a7..1bf270b56f 100644 --- a/packages/twenty-front/src/modules/auth/components/VerifyLoginTokenEffect.tsx +++ b/packages/twenty-front/src/modules/auth/components/VerifyLoginTokenEffect.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair'; import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin'; import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; @@ -13,7 +13,7 @@ export const VerifyLoginTokenEffect = () => { const [searchParams] = useSearchParams(); const loginToken = searchParams.get('loginToken'); - const isLogged = useIsLogged(); + const hasAccessTokenPair = useHasAccessTokenPair(); const navigate = useNavigateApp(); const { verifyLoginToken } = useVerifyLogin(); @@ -28,7 +28,7 @@ export const VerifyLoginTokenEffect = () => { if (isDefined(loginToken)) { verifyLoginToken(loginToken); - } else if (!isLogged) { + } else if (!hasAccessTokenPair) { navigate(AppPath.SignInUp); } // Verify only needs to run once at mount diff --git a/packages/twenty-front/src/modules/auth/hooks/__tests__/useAuth.test.tsx b/packages/twenty-front/src/modules/auth/hooks/__tests__/useAuth.test.tsx index 41d0c0ded9..7fb98304eb 100644 --- a/packages/twenty-front/src/modules/auth/hooks/__tests__/useAuth.test.tsx +++ b/packages/twenty-front/src/modules/auth/hooks/__tests__/useAuth.test.tsx @@ -30,12 +30,6 @@ jest.mock('@/domain-manager/hooks/useRedirect', () => ({ })), })); -jest.mock('@/object-metadata/hooks/useRefreshObjectMetadataItems', () => ({ - useRefreshObjectMetadataItems: jest.fn().mockImplementation(() => ({ - refreshObjectMetadataItems: jest.fn(), - })), -})); - jest.mock('@/domain-manager/hooks/useOrigin', () => ({ useOrigin: jest.fn().mockImplementation(() => ({ origin: 'http://localhost', diff --git a/packages/twenty-front/src/modules/auth/hooks/__tests__/useIsLogged.test.ts b/packages/twenty-front/src/modules/auth/hooks/__tests__/useHasAccessTokenPair.test.ts similarity index 71% rename from packages/twenty-front/src/modules/auth/hooks/__tests__/useIsLogged.test.ts rename to packages/twenty-front/src/modules/auth/hooks/__tests__/useHasAccessTokenPair.test.ts index e72b149dc0..2a3f88076f 100644 --- a/packages/twenty-front/src/modules/auth/hooks/__tests__/useIsLogged.test.ts +++ b/packages/twenty-front/src/modules/auth/hooks/__tests__/useHasAccessTokenPair.test.ts @@ -1,27 +1,27 @@ import { act, renderHook } from '@testing-library/react'; -import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair'; import { tokenPairState } from '@/auth/states/tokenPairState'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; const renderHooks = () => { const { result } = renderHook(() => { - const isLogged = useIsLogged(); + const hasAccessTokenPair = useHasAccessTokenPair(); const setTokenPair = useSetAtomState(tokenPairState); return { - isLogged, + hasAccessTokenPair, setTokenPair, }; }); return { result }; }; -describe('useIsLogged', () => { +describe('useHasAccessTokenPair', () => { it('should return correct value', async () => { const { result } = renderHooks(); - expect(result.current.isLogged).toBe(false); + expect(result.current.hasAccessTokenPair).toBe(false); await act(async () => { result.current.setTokenPair({ @@ -36,6 +36,6 @@ describe('useIsLogged', () => { }); }); - expect(result.current.isLogged).toBe(true); + expect(result.current.hasAccessTokenPair).toBe(true); }); }); diff --git a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts index 06b1bcf618..2d57cdebc9 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts @@ -34,7 +34,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useSignUpInNewWorkspace } from '@/auth/sign-in-up/hooks/useSignUpInNewWorkspace'; -import { useReloadWorkspaceMetadata } from '@/metadata-store/hooks/useReloadWorkspaceMetadata'; +import { useLoadMockedMinimalMetadata } from '@/metadata-store/hooks/useLoadMockedMinimalMetadata'; import { lastAuthenticatedMethodState } from '@/auth/states/lastAuthenticatedMethodState'; import { loginTokenState } from '@/auth/states/loginTokenState'; import { @@ -42,7 +42,6 @@ import { signInUpStepState, } from '@/auth/states/signInUpStepState'; import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState'; -import { coreViewsState } from '@/views/states/coreViewState'; import { type BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type'; import { countAvailableWorkspaces, @@ -88,8 +87,7 @@ export const useAuth = () => { const { loadCurrentUser } = useLoadCurrentUser(); const { clearSseClient } = useClearSseClient(); - const { reloadWorkspaceMetadata, resetToMockedMetadata } = - useReloadWorkspaceMetadata(); + const { loadMockedMinimalMetadata } = useLoadMockedMinimalMetadata(); const { createWorkspace } = useSignUpInNewWorkspace(); const setSignInUpStep = useSetAtomState(signInUpStepState); @@ -163,18 +161,17 @@ export const useAuth = () => { }); store.set(loginTokenState.atom, null); store.set(signInUpStepState.atom, SignInUpStep.Init); - store.set(coreViewsState.atom, []); await client.clearStore(); setLastAuthenticateWorkspaceDomain(null); - await resetToMockedMetadata(); + await loadMockedMinimalMetadata(); navigate(AppPath.SignInUp); store.set(isAppEffectRedirectEnabledState.atom, true); }, [ clearSseClient, client, setLastAuthenticateWorkspaceDomain, - resetToMockedMetadata, + loadMockedMinimalMetadata, navigate, store, ]); @@ -305,14 +302,10 @@ export const useAuth = () => { setIsAppEffectRedirectEnabled(false); await loadCurrentUser(); - await reloadWorkspaceMetadata(); + + setIsAppEffectRedirectEnabled(true); }, - [ - loadCurrentUser, - handleSetAuthTokens, - reloadWorkspaceMetadata, - setIsAppEffectRedirectEnabled, - ], + [loadCurrentUser, handleSetAuthTokens, setIsAppEffectRedirectEnabled], ); const handleGetAuthTokensFromLoginToken = useCallback( diff --git a/packages/twenty-front/src/modules/auth/hooks/useIsLogged.ts b/packages/twenty-front/src/modules/auth/hooks/useHasAccessTokenPair.ts similarity index 80% rename from packages/twenty-front/src/modules/auth/hooks/useIsLogged.ts rename to packages/twenty-front/src/modules/auth/hooks/useHasAccessTokenPair.ts index a7fda28fd9..845598a277 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useIsLogged.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useHasAccessTokenPair.ts @@ -1,7 +1,7 @@ import { tokenPairState } from '@/auth/states/tokenPairState'; import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; -export const useIsLogged = (): boolean => { +export const useHasAccessTokenPair = (): boolean => { const [tokenPair] = useAtomState(tokenPairState); return !!tokenPair; }; diff --git a/packages/twenty-front/src/modules/auth/states/objectPermissionsFamilySelector.ts b/packages/twenty-front/src/modules/auth/states/objectPermissionsFamilySelector.ts index 30264a24b7..c0a5348087 100644 --- a/packages/twenty-front/src/modules/auth/states/objectPermissionsFamilySelector.ts +++ b/packages/twenty-front/src/modules/auth/states/objectPermissionsFamilySelector.ts @@ -1,5 +1,5 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; -import { objectMetadataItemsSelector } from '@/metadata-store/states/objectMetadataItemsSelector'; +import { flatObjectMetadataItemsSelector } from '@/object-metadata/states/flatObjectMetadataItemsSelector'; import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; export const objectPermissionsFamilySelector = createAtomFamilySelector< @@ -14,7 +14,7 @@ export const objectPermissionsFamilySelector = createAtomFamilySelector< ({ objectNameSingular }) => ({ get }) => { const currentUserWorkspace = get(currentUserWorkspaceState); - const objectMetadataItems = get(objectMetadataItemsSelector); + const objectMetadataItems = get(flatObjectMetadataItemsSelector); const objectMetadataItem = objectMetadataItems.find( (item) => item.nameSingular === objectNameSingular, diff --git a/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx b/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx index 3ed5991e4a..2cf98c1eb8 100644 --- a/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx +++ b/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx @@ -6,7 +6,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal'; import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { useLocation, useParams, useSearchParams } from 'react-router-dom'; import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -38,6 +38,8 @@ const getViewId = ( return undefined; }; +const SIGN_IN_BACKGROUND_OBJECT_NAME_PLURAL = 'companies'; + export const MainContextStoreProvider = () => { const location = useLocation(); const isRecordIndexPage = isMatchingLocation( @@ -46,16 +48,21 @@ export const MainContextStoreProvider = () => { ); const isRecordShowPage = isMatchingLocation(location, AppPath.RecordShowPage); const isSettingsPage = useIsSettingsPage(); + const showAuthModal = useShowAuthModal(); - const objectNamePlural = useParams().objectNamePlural ?? ''; + const objectNamePluralFromParams = useParams().objectNamePlural ?? ''; const objectNameSingular = useParams().objectNameSingular ?? ''; + const objectNamePlural = showAuthModal + ? SIGN_IN_BACKGROUND_OBJECT_NAME_PLURAL + : objectNamePluralFromParams; + const [searchParams] = useSearchParams(); const viewIdQueryParamRaw = searchParams.get('viewId'); const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); const metadataStore = useAtomFamilyStateValue(metadataStoreState, 'views'); - const coreViews = useAtomStateValue(coreViewsState); + const coreViews = useAtomStateValue(coreViewsSelector); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => @@ -108,11 +115,11 @@ export const MainContextStoreProvider = () => { firstAvailableViewId, ); - const showAuthModal = useShowAuthModal(); - const shouldComputeContextStore = - (isRecordIndexPage || isRecordShowPage || isSettingsPage) && - !showAuthModal && + (isRecordIndexPage || + isRecordShowPage || + isSettingsPage || + showAuthModal) && metadataStore.status === 'up-to-date'; if (!shouldComputeContextStore) { diff --git a/packages/twenty-front/src/modules/metadata-store/components/MetadataGater.tsx b/packages/twenty-front/src/modules/metadata-store/components/MinimalMetadataGater.tsx similarity index 62% rename from packages/twenty-front/src/modules/metadata-store/components/MetadataGater.tsx rename to packages/twenty-front/src/modules/metadata-store/components/MinimalMetadataGater.tsx index fcb6767775..0adf45378d 100644 --- a/packages/twenty-front/src/modules/metadata-store/components/MetadataGater.tsx +++ b/packages/twenty-front/src/modules/metadata-store/components/MinimalMetadataGater.tsx @@ -1,9 +1,7 @@ import React from 'react'; -import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState'; -import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState'; import { useDateTimeFormat } from '@/localization/hooks/useDateTimeFormat'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { UserContext } from '@/users/contexts/UserContext'; import { useLocation } from 'react-router-dom'; @@ -11,10 +9,8 @@ import { AppPath } from 'twenty-shared/types'; import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; -export const MetadataGater = ({ children }: React.PropsWithChildren) => { - const isAppMetadataReady = useAtomStateValue(isAppMetadataReadyState); - const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); - const isLoggedIn = useIsLogged(); +export const MinimalMetadataGater = ({ children }: React.PropsWithChildren) => { + const isMinimalMetadataReady = useAtomStateValue(isMinimalMetadataReadyState); const location = useLocation(); const { dateFormat, timeFormat, timeZone } = useDateTimeFormat(); @@ -22,11 +18,12 @@ export const MetadataGater = ({ children }: React.PropsWithChildren) => { const isOnExcludedPath = isMatchingLocation(location, AppPath.Verify) || isMatchingLocation(location, AppPath.VerifyEmail) || + isMatchingLocation(location, AppPath.SignInUp) || + isMatchingLocation(location, AppPath.Invite) || + isMatchingLocation(location, AppPath.ResetPassword) || isMatchingLocation(location, AppPath.CreateWorkspace); - const shouldShowLoader = - (!isAppMetadataReady && isLoggedIn && !isOnExcludedPath) || - objectMetadataItems.length === 0; + const shouldShowLoader = !isMinimalMetadataReady && !isOnExcludedPath; if (shouldShowLoader) { return ; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/FieldMetadataSSEEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/FieldMetadataSSEEffect.tsx deleted file mode 100644 index ee6da8a242..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/FieldMetadataSSEEffect.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent'; -import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; -import { useStore } from 'jotai'; -import { AllMetadataName } from '~/generated-metadata/graphql'; - -export const FieldMetadataSSEEffect = () => { - const queryId = 'field-metadata-sse-effect'; - - const store = useStore(); - - useListenToEventsForQuery({ - queryId, - operationSignature: { - metadataName: AllMetadataName.fieldMetadata, - variables: {}, - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.fieldMetadata, - onMetadataOperationBrowserEvent: (eventDetail) => { - patchMetadataStoreFromSSEEvent( - store, - 'fieldMetadataItems', - eventDetail.operation, - eventDetail.updatedCollectionHash, - ); - }, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/IsAppMetadataReadyEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/IsMinimalMetadataReadyEffect.tsx similarity index 67% rename from packages/twenty-front/src/modules/metadata-store/effect-components/IsAppMetadataReadyEffect.tsx rename to packages/twenty-front/src/modules/metadata-store/effect-components/IsMinimalMetadataReadyEffect.tsx index baa586adee..10bd5b0183 100644 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/IsAppMetadataReadyEffect.tsx +++ b/packages/twenty-front/src/modules/metadata-store/effect-components/IsMinimalMetadataReadyEffect.tsx @@ -1,7 +1,7 @@ -import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair'; import { currentUserState } from '@/auth/states/currentUserState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState'; +import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState'; import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; @@ -10,8 +10,8 @@ import { useEffect } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace'; -export const IsAppMetadataReadyEffect = () => { - const isLoggedIn = useIsLogged(); +export const IsMinimalMetadataReadyEffect = () => { + const hasAccessTokenPair = useHasAccessTokenPair(); const currentUser = useAtomStateValue(currentUserState); const currentWorkspace = useAtomStateValue(currentWorkspaceState); const metadataStore = useAtomFamilyStateValue( @@ -23,7 +23,9 @@ export const IsAppMetadataReadyEffect = () => { metadataStoreState, 'views', ); - const setIsAppMetadataReady = useSetAtomState(isAppMetadataReadyState); + const setIsMinimalMetadataReady = useSetAtomState( + isMinimalMetadataReadyState, + ); useEffect(() => { const hasActiveWorkspace = isWorkspaceActiveOrSuspended(currentWorkspace); @@ -31,23 +33,26 @@ export const IsAppMetadataReadyEffect = () => { const areObjectsLoaded = metadataStore.status === 'up-to-date'; const areViewsLoaded = metadataStoreViews.status === 'up-to-date'; + const isReady = !areObjectsLoaded + ? false + : !hasAccessTokenPair || + (isDefined(currentUser) && (!hasActiveWorkspace || areViewsLoaded)); + if (!areObjectsLoaded) { - setIsAppMetadataReady(false); + setIsMinimalMetadataReady(false); return; } - const isReady = - !isLoggedIn || - (isDefined(currentUser) && (!hasActiveWorkspace || areViewsLoaded)); - - setIsAppMetadataReady(isReady); + if (isReady) { + setIsMinimalMetadataReady(true); + } }, [ - isLoggedIn, + hasAccessTokenPair, currentUser, currentWorkspace, metadataStore.status, metadataStoreViews.status, - setIsAppMetadataReady, + setIsMinimalMetadataReady, ]); return null; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/MetadataProviderInitialEffects.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/MetadataProviderInitialEffects.tsx deleted file mode 100644 index a787c23add..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/MetadataProviderInitialEffects.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { ObjectMetadataProviderInitialEffect } from '@/metadata-store/effect-components/ObjectMetadataProviderInitialEffect'; -import { UserMetadataProviderInitialEffect } from '@/metadata-store/effect-components/UserMetadataProviderInitialEffect'; -import { ViewMetadataProviderInitialEffect } from '@/metadata-store/effect-components/ViewMetadataProviderInitialEffect'; - -export const MetadataProviderInitialEffects = () => ( - <> - - - - -); diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/MetadataStoreSSEEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/MetadataStoreSSEEffect.tsx new file mode 100644 index 0000000000..8b9f9b9ca2 --- /dev/null +++ b/packages/twenty-front/src/modules/metadata-store/effect-components/MetadataStoreSSEEffect.tsx @@ -0,0 +1,28 @@ +import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; +import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent'; +import { mapAllMetadataNameToEntityKey } from '@/metadata-store/utils/mapAllMetadataNameToEntityKey'; +import { useStore } from 'jotai'; +import { isDefined } from 'twenty-shared/utils'; + +export const MetadataStoreSSEEffect = () => { + const store = useStore(); + + useListenToMetadataOperationBrowserEvent({ + onMetadataOperationBrowserEvent: (eventDetail) => { + const entityKey = mapAllMetadataNameToEntityKey(eventDetail.metadataName); + + if (!isDefined(entityKey)) { + return; + } + + patchMetadataStoreFromSSEEvent({ + store, + entityKey, + operation: eventDetail.operation, + updatedCollectionHash: eventDetail.updatedCollectionHash, + }); + }, + }); + + return null; +}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/MinimalMetadataLoadEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/MinimalMetadataLoadEffect.tsx index 7878587ae2..10bfb7c9a5 100644 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/MinimalMetadataLoadEffect.tsx +++ b/packages/twenty-front/src/modules/metadata-store/effect-components/MinimalMetadataLoadEffect.tsx @@ -1,62 +1,73 @@ -import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState'; import { useLoadMinimalMetadata } from '@/metadata-store/hooks/useLoadMinimalMetadata'; -import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; +import { useLoadMockedMinimalMetadata } from '@/metadata-store/hooks/useLoadMockedMinimalMetadata'; +import { useLoadStaleMetadataEntities } from '@/metadata-store/hooks/useLoadStaleMetadataEntities'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useStore } from 'jotai'; import { useEffect, useState } from 'react'; import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace'; +type LoadedState = 'none' | 'mocked' | 'real'; + +const computeDesiredLoadState = ( + hasAccessTokenPair: boolean, + isActiveWorkspace: boolean, +): LoadedState => { + if (hasAccessTokenPair && isActiveWorkspace) { + return 'real'; + } + + return 'mocked'; +}; + export const MinimalMetadataLoadEffect = () => { - const isLoggedIn = useIsLogged(); + const hasAccessTokenPair = useHasAccessTokenPair(); const isCurrentUserLoaded = useAtomStateValue(isCurrentUserLoadedState); const currentWorkspace = useAtomStateValue(currentWorkspaceState); - const store = useStore(); - const [hasLoaded, setHasLoaded] = useState(false); + const [loadedState, setLoadedState] = useState('none'); const { loadMinimalMetadata } = useLoadMinimalMetadata(); + const { loadMockedMinimalMetadata } = useLoadMockedMinimalMetadata(); + const { loadStaleMetadataEntities } = useLoadStaleMetadataEntities(); + + const isActiveWorkspace = isWorkspaceActiveOrSuspended(currentWorkspace); + + const desiredLoadState = computeDesiredLoadState( + hasAccessTokenPair, + isActiveWorkspace, + ); useEffect(() => { - if (hasLoaded) { + if (!isCurrentUserLoaded || loadedState === desiredLoadState) { return; } - if (!isCurrentUserLoaded || !isLoggedIn) { - return; - } + setLoadedState(desiredLoadState); - if (!isWorkspaceActiveOrSuspended(currentWorkspace)) { - return; - } + const performLoad = async () => { + if (desiredLoadState === 'mocked') { + await loadMockedMinimalMetadata(); + return; + } - const objectsEntry = store.get( - metadataStoreState.atomFamily('objectMetadataItems'), - ); - const viewsEntry = store.get(metadataStoreState.atomFamily('views')); + const result = await loadMinimalMetadata(); - const hasObjectsFromStorage = objectsEntry.status === 'up-to-date'; - const hasViewsFromStorage = viewsEntry.status === 'up-to-date'; - - if (hasObjectsFromStorage && hasViewsFromStorage) { - setHasLoaded(true); - - return; - } - - const load = async () => { - await loadMinimalMetadata(); - setHasLoaded(true); + if (result?.staleEntityKeys && result.staleEntityKeys.length > 0) { + await loadStaleMetadataEntities(result.staleEntityKeys); + } }; - load(); + performLoad(); }, [ - hasLoaded, isCurrentUserLoaded, - isLoggedIn, - currentWorkspace, - store, + hasAccessTokenPair, + isActiveWorkspace, + desiredLoadState, + loadedState, loadMinimalMetadata, + loadMockedMinimalMetadata, + loadStaleMetadataEntities, ]); return null; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/NavigationMenuItemSSEEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/NavigationMenuItemSSEEffect.tsx deleted file mode 100644 index 2ad80901d0..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/NavigationMenuItemSSEEffect.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; -import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent'; -import { navigationMenuItemsState } from '@/navigation-menu-item/states/navigationMenuItemsState'; -import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; -import { useStore } from 'jotai'; -import { isDefined } from 'twenty-shared/utils'; -import { useApolloClient } from '@apollo/client/react'; -import { - AllMetadataName, - FindManyNavigationMenuItemsDocument, -} from '~/generated-metadata/graphql'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; - -export const NavigationMenuItemSSEEffect = () => { - const queryId = 'navigation-menu-item-sse-effect'; - - const store = useStore(); - const { updateDraft, applyChanges } = useMetadataStore(); - const client = useApolloClient(); - - useListenToEventsForQuery({ - queryId, - operationSignature: { - metadataName: AllMetadataName.navigationMenuItem, - variables: {}, - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.navigationMenuItem, - onMetadataOperationBrowserEvent: async (eventDetail) => { - patchMetadataStoreFromSSEEvent( - store, - 'navigationMenuItems', - eventDetail.operation, - eventDetail.updatedCollectionHash, - ); - - const result = await client.query({ - query: FindManyNavigationMenuItemsDocument, - fetchPolicy: 'network-only', - }); - - if (!isDefined(result.data?.navigationMenuItems)) { - return; - } - - const existingNavigationMenuItems = store.get( - navigationMenuItemsState.atom, - ); - - if ( - !isDeeplyEqual( - existingNavigationMenuItems, - result.data.navigationMenuItems, - ) - ) { - store.set( - navigationMenuItemsState.atom, - result.data.navigationMenuItems, - ); - - updateDraft('navigationMenuItems', result.data.navigationMenuItems); - applyChanges(); - } - }, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/ObjectMetadataItemSSEEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/ObjectMetadataItemSSEEffect.tsx deleted file mode 100644 index 142779f6d5..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/ObjectMetadataItemSSEEffect.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent'; -import { navigationMenuItemsState } from '@/navigation-menu-item/states/navigationMenuItemsState'; -import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; -import { useStore } from 'jotai'; -import { useApolloClient } from '@apollo/client/react'; -import { - AllMetadataName, - FindManyNavigationMenuItemsDocument, -} from '~/generated-metadata/graphql'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; - -export const ObjectMetadataItemSSEEffect = () => { - const queryId = 'object-metadata-sse-effect'; - - const store = useStore(); - const client = useApolloClient(); - - useListenToEventsForQuery({ - queryId, - operationSignature: { - metadataName: AllMetadataName.objectMetadata, - variables: {}, - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.objectMetadata, - onMetadataOperationBrowserEvent: async (eventDetail) => { - patchMetadataStoreFromSSEEvent( - store, - 'objectMetadataItems', - eventDetail.operation, - eventDetail.updatedCollectionHash, - ); - - const navigationMenuItemsResult = await client.query({ - query: FindManyNavigationMenuItemsDocument, - fetchPolicy: 'network-only', - }); - - const existingNavigationMenuItems = store.get( - navigationMenuItemsState.atom, - ); - - if ( - !isDeeplyEqual( - existingNavigationMenuItems, - navigationMenuItemsResult.data?.navigationMenuItems, - ) - ) { - store.set( - navigationMenuItemsState.atom, - navigationMenuItemsResult.data?.navigationMenuItems ?? [], - ); - } - }, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/ObjectMetadataProviderInitialEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/ObjectMetadataProviderInitialEffect.tsx deleted file mode 100644 index 3d117ae5af..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/ObjectMetadataProviderInitialEffect.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState'; -import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems'; -import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useEffect, useState } from 'react'; -import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace'; - -export const ObjectMetadataProviderInitialEffect = () => { - const isCurrentUserLoaded = useAtomStateValue(isCurrentUserLoadedState); - const currentWorkspace = useAtomStateValue(currentWorkspaceState); - const [isInitialized, setIsInitialized] = useState(false); - - const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems(); - const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems(); - - useEffect(() => { - if (isInitialized) { - return; - } - if (!isCurrentUserLoaded) { - return; - } - - const shouldLoadReal = isWorkspaceActiveOrSuspended(currentWorkspace); - - const loadObjectMetadata = async () => { - if (shouldLoadReal) { - await refreshObjectMetadataItems(); - } else { - await loadMockedObjectMetadataItems(); - } - - setIsInitialized(true); - }; - - loadObjectMetadata(); - }, [ - isInitialized, - isCurrentUserLoaded, - currentWorkspace, - refreshObjectMetadataItems, - loadMockedObjectMetadataItems, - ]); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/PageLayoutSSEEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/PageLayoutSSEEffect.tsx deleted file mode 100644 index f95296ba57..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/PageLayoutSSEEffect.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent'; -import { useRefreshPageLayouts } from '@/page-layout/hooks/useRefreshPageLayouts'; -import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; -import { useStore } from 'jotai'; -import { AllMetadataName } from '~/generated-metadata/graphql'; - -export const PageLayoutSSEEffect = () => { - const queryId = 'page-layout-metadata-sse-effect'; - - const store = useStore(); - const { refreshPageLayouts } = useRefreshPageLayouts(); - - useListenToEventsForQuery({ - queryId, - operationSignature: { - metadataName: AllMetadataName.pageLayout, - variables: {}, - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.pageLayout, - onMetadataOperationBrowserEvent: (eventDetail) => { - patchMetadataStoreFromSSEEvent( - store, - 'pageLayouts', - eventDetail.operation, - eventDetail.updatedCollectionHash, - ); - - refreshPageLayouts(); - }, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/PageLayoutTabSSEEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/PageLayoutTabSSEEffect.tsx deleted file mode 100644 index 0108be39cc..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/PageLayoutTabSSEEffect.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent'; -import { useRefreshPageLayouts } from '@/page-layout/hooks/useRefreshPageLayouts'; -import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; -import { useStore } from 'jotai'; -import { AllMetadataName } from '~/generated-metadata/graphql'; - -export const PageLayoutTabSSEEffect = () => { - const queryId = 'page-layout-tab-sse-effect'; - - const store = useStore(); - const { refreshPageLayouts } = useRefreshPageLayouts(); - - useListenToEventsForQuery({ - queryId, - operationSignature: { - metadataName: AllMetadataName.pageLayoutTab, - variables: {}, - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.pageLayoutTab, - onMetadataOperationBrowserEvent: (eventDetail) => { - patchMetadataStoreFromSSEEvent( - store, - 'pageLayoutTabs', - eventDetail.operation, - eventDetail.updatedCollectionHash, - ); - - refreshPageLayouts(); - }, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/PageLayoutWidgetSSEEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/PageLayoutWidgetSSEEffect.tsx deleted file mode 100644 index 0bbd0b3fcf..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/PageLayoutWidgetSSEEffect.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent'; -import { useRefreshPageLayouts } from '@/page-layout/hooks/useRefreshPageLayouts'; -import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; -import { useStore } from 'jotai'; -import { AllMetadataName } from '~/generated-metadata/graphql'; - -export const PageLayoutWidgetSSEEffect = () => { - const queryId = 'page-layout-widget-sse-effect'; - - const store = useStore(); - const { refreshPageLayouts } = useRefreshPageLayouts(); - - useListenToEventsForQuery({ - queryId, - operationSignature: { - metadataName: AllMetadataName.pageLayoutWidget, - variables: {}, - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.pageLayoutWidget, - onMetadataOperationBrowserEvent: (eventDetail) => { - patchMetadataStoreFromSSEEvent( - store, - 'pageLayoutWidgets', - eventDetail.operation, - eventDetail.updatedCollectionHash, - ); - - refreshPageLayouts(); - }, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/UserMetadataProviderInitialEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/UserMetadataProviderInitialEffect.tsx index 9c781a825b..1ff6fddf9d 100644 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/UserMetadataProviderInitialEffect.tsx +++ b/packages/twenty-front/src/modules/metadata-store/effect-components/UserMetadataProviderInitialEffect.tsx @@ -1,4 +1,4 @@ -import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair'; import { availableWorkspacesState } from '@/auth/states/availableWorkspacesState'; import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; @@ -27,7 +27,7 @@ import { dateLocaleState } from '~/localization/states/dateLocaleState'; import { dynamicActivate } from '~/utils/i18n/dynamicActivate'; export const UserMetadataProviderInitialEffect = () => { - const isLoggedIn = useIsLogged(); + const hasAccessTokenPair = useHasAccessTokenPair(); const currentUser = useAtomStateValue(currentUserState); const store = useStore(); const [isInitialized, setIsInitialized] = useState(false); @@ -65,7 +65,7 @@ export const UserMetadataProviderInitialEffect = () => { [store], ); - const shouldSkipUserQuery = !isLoggedIn || isDefined(currentUser); + const shouldSkipUserQuery = !hasAccessTokenPair || isDefined(currentUser); const { data: userQueryData, loading: userQueryLoading } = useQuery( GetCurrentUserDocument, @@ -79,7 +79,7 @@ export const UserMetadataProviderInitialEffect = () => { return; } - if (!isLoggedIn) { + if (!hasAccessTokenPair) { setIsCurrentUserLoaded(true); setIsInitialized(true); return; @@ -165,7 +165,7 @@ export const UserMetadataProviderInitialEffect = () => { setIsInitialized(true); }, [ isInitialized, - isLoggedIn, + hasAccessTokenPair, userQueryLoading, userQueryData?.currentUser, setCurrentUser, diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewFieldSSEEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/ViewFieldSSEEffect.tsx deleted file mode 100644 index 22cc77d0e1..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewFieldSSEEffect.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent'; -import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; -import { coreViewsState } from '@/views/states/coreViewState'; -import { useStore } from 'jotai'; -import { isDefined } from 'twenty-shared/utils'; -import { - AllMetadataName, - type CoreViewField, -} from '~/generated-metadata/graphql'; - -export const ViewFieldSSEEffect = () => { - const store = useStore(); - - const queryId = 'view-field-metadata-sse-effect'; - - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - - useListenToEventsForQuery({ - queryId, - operationSignature: { - metadataName: AllMetadataName.viewField, - variables: {}, - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.viewField, - onMetadataOperationBrowserEvent: (eventDetail) => { - patchMetadataStoreFromSSEEvent( - store, - 'viewFields', - eventDetail.operation, - eventDetail.updatedCollectionHash, - ); - - const coreViews = store.get(coreViewsState.atom); - - let viewId: string | undefined; - - switch (eventDetail.operation.type) { - case 'create': { - const createdViewField = eventDetail.operation - .createdRecord as unknown as CoreViewField; - viewId = createdViewField.viewId; - break; - } - case 'update': { - const updatedViewField = eventDetail.operation - .updatedRecord as unknown as CoreViewField; - viewId = updatedViewField.viewId; - break; - } - case 'delete': { - const deletedViewFieldId = eventDetail.operation - .deletedRecordId as string; - - const view = coreViews.find((coreView) => - coreView.viewFields.some( - (viewField) => viewField.id === deletedViewFieldId, - ), - ); - - viewId = view?.id; - break; - } - } - - if (!isDefined(viewId)) { - return; - } - - const view = coreViews.find((coreView) => coreView.id === viewId); - - if (!isDefined(view)) { - return; - } - - refreshCoreViewsByObjectMetadataId(view.objectMetadataId); - }, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewFilterGroupSSEEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/ViewFilterGroupSSEEffect.tsx deleted file mode 100644 index 4a10c1cfc7..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewFilterGroupSSEEffect.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent'; -import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; -import { coreViewsState } from '@/views/states/coreViewState'; -import { useStore } from 'jotai'; -import { isDefined } from 'twenty-shared/utils'; -import { AllMetadataName } from '~/generated-metadata/graphql'; - -export const ViewFilterGroupSSEEffect = () => { - const store = useStore(); - - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - - useListenToEventsForQuery({ - queryId: 'view-filter-groups-sse-effect', - operationSignature: { - metadataName: AllMetadataName.viewFilterGroup, - variables: {}, - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.viewFilterGroup, - onMetadataOperationBrowserEvent: (eventDetail) => { - patchMetadataStoreFromSSEEvent( - store, - 'viewFilterGroups', - eventDetail.operation, - eventDetail.updatedCollectionHash, - ); - - const coreViews = store.get(coreViewsState.atom); - - let viewId: string | undefined; - - switch (eventDetail.operation.type) { - case 'create': { - viewId = (eventDetail.operation.createdRecord as { viewId?: string }) - .viewId; - break; - } - case 'update': { - viewId = (eventDetail.operation.updatedRecord as { viewId?: string }) - .viewId; - break; - } - case 'delete': { - const deletedId = eventDetail.operation.deletedRecordId as string; - viewId = coreViews.find((view) => - view.viewFilterGroups?.some( - (viewFilterGroup) => viewFilterGroup.id === deletedId, - ), - )?.id; - break; - } - } - - if (!isDefined(viewId)) { - return; - } - - const view = coreViews.find((coreView) => coreView.id === viewId); - - if (isDefined(view)) { - refreshCoreViewsByObjectMetadataId(view.objectMetadataId); - } - }, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewFilterSSEEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/ViewFilterSSEEffect.tsx deleted file mode 100644 index aaafada345..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewFilterSSEEffect.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent'; -import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; -import { coreViewsState } from '@/views/states/coreViewState'; -import { useStore } from 'jotai'; -import { isDefined } from 'twenty-shared/utils'; -import { AllMetadataName } from '~/generated-metadata/graphql'; - -export const ViewFilterSSEEffect = () => { - const store = useStore(); - - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - - useListenToEventsForQuery({ - queryId: 'view-filters-sse-effect', - operationSignature: { - metadataName: AllMetadataName.viewFilter, - variables: {}, - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.viewFilter, - onMetadataOperationBrowserEvent: (eventDetail) => { - patchMetadataStoreFromSSEEvent( - store, - 'viewFilters', - eventDetail.operation, - eventDetail.updatedCollectionHash, - ); - - const coreViews = store.get(coreViewsState.atom); - - let viewId: string | undefined; - - switch (eventDetail.operation.type) { - case 'create': { - viewId = (eventDetail.operation.createdRecord as { viewId?: string }) - .viewId; - break; - } - case 'update': { - viewId = (eventDetail.operation.updatedRecord as { viewId?: string }) - .viewId; - break; - } - case 'delete': { - const deletedId = eventDetail.operation.deletedRecordId as string; - viewId = coreViews.find((view) => - view.viewFilters.some((viewFilter) => viewFilter.id === deletedId), - )?.id; - break; - } - } - - if (!isDefined(viewId)) { - return; - } - - const view = coreViews.find((coreView) => coreView.id === viewId); - - if (isDefined(view)) { - refreshCoreViewsByObjectMetadataId(view.objectMetadataId); - } - }, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewMetadataProviderInitialEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/ViewMetadataProviderInitialEffect.tsx deleted file mode 100644 index 88c8f1893f..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewMetadataProviderInitialEffect.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useIsLogged } from '@/auth/hooks/useIsLogged'; -import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState'; -import { useFetchAndLoadIndexViews } from '@/metadata-store/hooks/useFetchAndLoadIndexViews'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useEffect, useState } from 'react'; -import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace'; - -export const ViewMetadataProviderInitialEffect = () => { - const isLoggedIn = useIsLogged(); - const isCurrentUserLoaded = useAtomStateValue(isCurrentUserLoadedState); - const currentWorkspace = useAtomStateValue(currentWorkspaceState); - - const [isInitialized, setIsInitialized] = useState(false); - - const { fetchAndLoadIndexViews } = useFetchAndLoadIndexViews(); - - useEffect(() => { - if (isInitialized) { - return; - } - if (!isCurrentUserLoaded) { - return; - } - - if (!isLoggedIn || !isWorkspaceActiveOrSuspended(currentWorkspace)) { - setIsInitialized(true); - return; - } - - const initialize = async () => { - await fetchAndLoadIndexViews(); - - setIsInitialized(true); - }; - - initialize(); - }, [ - isInitialized, - isCurrentUserLoaded, - isLoggedIn, - currentWorkspace, - fetchAndLoadIndexViews, - ]); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewRelationsSSEEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/ViewRelationsSSEEffect.tsx deleted file mode 100644 index fbed2437b5..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewRelationsSSEEffect.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { type MetadataOperationBrowserEventDetail } from '@/browser-event/types/MetadataOperationBrowserEventDetail'; -import { useGetObjectMetadataItemIdFromViewRelationEventDetail } from '@/metadata-store/hooks/useGetObjectMetadataItemIdFromViewRelationEventDetail'; -import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; -import { type ViewField } from '@/views/types/ViewField'; -import { type ViewFilter } from '@/views/types/ViewFilter'; -import { type ViewFilterGroup } from '@/views/types/ViewFilterGroup'; -import { type ViewSort } from '@/views/types/ViewSort'; -import { isDefined } from 'twenty-shared/utils'; -import { useDebouncedCallback } from 'use-debounce'; -import { AllMetadataName } from '~/generated-metadata/graphql'; - -export const ViewRelationsSSEEffect = () => { - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - - const { getObjectMetadataItemIdFromViewRelationEventDetail } = - useGetObjectMetadataItemIdFromViewRelationEventDetail(); - - useListenToEventsForQuery({ - queryId: 'view-filters-sse-effect', - operationSignature: { - metadataName: AllMetadataName.viewFilter, - variables: {}, - }, - }); - - useListenToEventsForQuery({ - queryId: 'view-sorts-sse-effect', - operationSignature: { - metadataName: AllMetadataName.viewSort, - variables: {}, - }, - }); - - useListenToEventsForQuery({ - queryId: 'view-fields-sse-effect', - operationSignature: { - metadataName: AllMetadataName.viewField, - variables: {}, - }, - }); - - useListenToEventsForQuery({ - queryId: 'view-filter-groups-sse-effect', - operationSignature: { - metadataName: AllMetadataName.viewFilterGroup, - variables: {}, - }, - }); - - // TODO: replace this with detailed SSE optimistic effects - const debouncedRefreshCoreViewsByObjectMetadataId = useDebouncedCallback( - (objectMetadataId: string) => { - refreshCoreViewsByObjectMetadataId(objectMetadataId); - }, - 500, - { - leading: false, - trailing: true, - }, - ); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.viewFilter, - onMetadataOperationBrowserEvent: ( - detail: MetadataOperationBrowserEventDetail, - ) => { - const objectMetadataItemId = - getObjectMetadataItemIdFromViewRelationEventDetail(detail); - - if (!isDefined(objectMetadataItemId)) { - return; - } - - debouncedRefreshCoreViewsByObjectMetadataId(objectMetadataItemId); - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.viewSort, - onMetadataOperationBrowserEvent: ( - detail: MetadataOperationBrowserEventDetail, - ) => { - const objectMetadataItemId = - getObjectMetadataItemIdFromViewRelationEventDetail(detail); - - if (!isDefined(objectMetadataItemId)) { - return; - } - - debouncedRefreshCoreViewsByObjectMetadataId(objectMetadataItemId); - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.viewFilterGroup, - onMetadataOperationBrowserEvent: ( - detail: MetadataOperationBrowserEventDetail, - ) => { - const objectMetadataItemId = - getObjectMetadataItemIdFromViewRelationEventDetail(detail); - - if (!isDefined(objectMetadataItemId)) { - return; - } - - debouncedRefreshCoreViewsByObjectMetadataId(objectMetadataItemId); - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.viewField, - onMetadataOperationBrowserEvent: ( - detail: MetadataOperationBrowserEventDetail, - ) => { - const objectMetadataItemId = - getObjectMetadataItemIdFromViewRelationEventDetail(detail); - - if (!isDefined(objectMetadataItemId)) { - return; - } - - debouncedRefreshCoreViewsByObjectMetadataId(objectMetadataItemId); - }, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewSSEEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/ViewSSEEffect.tsx deleted file mode 100644 index a52c32e2c7..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewSSEEffect.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent'; -import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; -import { coreViewsState } from '@/views/states/coreViewState'; -import { type CoreViewWithoutRelations } from '@/views/types/CoreViewWithoutRelations'; -import { useStore } from 'jotai'; -import { AllMetadataName } from '~/generated-metadata/graphql'; -import { sleep } from '~/utils/sleep'; - -export const ViewSSEEffect = () => { - const queryId = 'views-sse-effect'; - - const store = useStore(); - - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - - useListenToEventsForQuery({ - queryId, - operationSignature: { - metadataName: AllMetadataName.view, - variables: {}, - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.view, - onMetadataOperationBrowserEvent: async (eventDetail) => { - patchMetadataStoreFromSSEEvent( - store, - 'views', - eventDetail.operation, - eventDetail.updatedCollectionHash, - ); - - switch (eventDetail.operation.type) { - case 'create': { - const createdView = eventDetail.operation - .createdRecord as unknown as CoreViewWithoutRelations; - - store.set(coreViewsState.atom, (prevCoreViews) => [ - ...prevCoreViews, - { - ...createdView, - viewFields: [], - viewFieldGroups: [], - viewFilterGroups: [], - viewSorts: [], - viewFilters: [], - viewGroups: [], - }, - ]); - - await sleep(50); - - refreshCoreViewsByObjectMetadataId(createdView.objectMetadataId); - - break; - } - case 'update': { - const updatedView = eventDetail.operation - .updatedRecord as unknown as CoreViewWithoutRelations; - - store.set(coreViewsState.atom, (prevCoreViews) => - prevCoreViews.map((coreView) => - coreView.id === updatedView.id - ? { - ...coreView, - ...updatedView, - } - : coreView, - ), - ); - break; - } - case 'delete': { - const deletedViewId = eventDetail.operation.deletedRecordId as string; - - store.set(coreViewsState.atom, (prevCoreViews) => - prevCoreViews.filter((coreView) => coreView.id !== deletedViewId), - ); - - break; - } - default: - return; - } - }, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewSortSSEEffect.tsx b/packages/twenty-front/src/modules/metadata-store/effect-components/ViewSortSSEEffect.tsx deleted file mode 100644 index e665a476a7..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/ViewSortSSEEffect.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { patchMetadataStoreFromSSEEvent } from '@/metadata-store/utils/patchMetadataStoreFromSSEEvent'; -import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; -import { coreViewsState } from '@/views/states/coreViewState'; -import { useStore } from 'jotai'; -import { isDefined } from 'twenty-shared/utils'; -import { AllMetadataName } from '~/generated-metadata/graphql'; - -export const ViewSortSSEEffect = () => { - const store = useStore(); - - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - - useListenToEventsForQuery({ - queryId: 'view-sorts-sse-effect', - operationSignature: { - metadataName: AllMetadataName.viewSort, - variables: {}, - }, - }); - - useListenToMetadataOperationBrowserEvent({ - metadataName: AllMetadataName.viewSort, - onMetadataOperationBrowserEvent: (eventDetail) => { - patchMetadataStoreFromSSEEvent( - store, - 'viewSorts', - eventDetail.operation, - eventDetail.updatedCollectionHash, - ); - - const coreViews = store.get(coreViewsState.atom); - - let viewId: string | undefined; - - switch (eventDetail.operation.type) { - case 'create': { - viewId = (eventDetail.operation.createdRecord as { viewId?: string }) - .viewId; - break; - } - case 'update': { - viewId = (eventDetail.operation.updatedRecord as { viewId?: string }) - .viewId; - break; - } - case 'delete': { - const deletedId = eventDetail.operation.deletedRecordId as string; - viewId = coreViews.find((view) => - view.viewSorts.some((viewSort) => viewSort.id === deletedId), - )?.id; - break; - } - } - - if (!isDefined(viewId)) { - return; - } - - const view = coreViews.find((coreView) => coreView.id === viewId); - - if (isDefined(view)) { - refreshCoreViewsByObjectMetadataId(view.objectMetadataId); - } - }, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/graphql/queries/findMinimalMetadata.ts b/packages/twenty-front/src/modules/metadata-store/graphql/queries/findMinimalMetadata.ts index 44ef62f9e7..dc18632218 100644 --- a/packages/twenty-front/src/modules/metadata-store/graphql/queries/findMinimalMetadata.ts +++ b/packages/twenty-front/src/modules/metadata-store/graphql/queries/findMinimalMetadata.ts @@ -21,7 +21,10 @@ export const FIND_MINIMAL_METADATA = gql` key objectMetadataId } - collectionHashes + collectionHashes { + collectionName + hash + } } } `; diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useFetchAndLoadIndexViews.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useFetchAndLoadIndexViews.ts deleted file mode 100644 index 959fbd0bbe..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/hooks/useFetchAndLoadIndexViews.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; -import { useSetIndexViews } from '@/metadata-store/hooks/useSetIndexViews'; -import { splitViewWithRelated } from '@/metadata-store/utils/splitViewWithRelated'; -import { useCallback } from 'react'; -import { isDefined } from 'twenty-shared/utils'; -import { useApolloClient } from '@apollo/client/react'; -import { - ViewType, - FindAllCoreViewsDocument, -} from '~/generated-metadata/graphql'; - -const INDEX_VIEW_TYPES = [ViewType.TABLE, ViewType.KANBAN, ViewType.CALENDAR]; - -export const useFetchAndLoadIndexViews = () => { - const client = useApolloClient(); - const { updateDraft, applyChanges } = useMetadataStore(); - const { setIndexViews } = useSetIndexViews(); - - const fetchAndLoadIndexViews = useCallback(async () => { - const result = await client.query({ - query: FindAllCoreViewsDocument, - variables: { viewTypes: INDEX_VIEW_TYPES }, - fetchPolicy: 'network-only', - }); - - if (isDefined(result.data?.getCoreViews)) { - setIndexViews(result.data.getCoreViews); - - const { - flatViews, - flatViewFields, - flatViewFilters, - flatViewSorts, - flatViewGroups, - flatViewFilterGroups, - flatViewFieldGroups, - } = splitViewWithRelated(result.data.getCoreViews); - - updateDraft('views', flatViews); - updateDraft('viewFields', flatViewFields); - updateDraft('viewFilters', flatViewFilters); - updateDraft('viewSorts', flatViewSorts); - updateDraft('viewGroups', flatViewGroups); - updateDraft('viewFilterGroups', flatViewFilterGroups); - updateDraft('viewFieldGroups', flatViewFieldGroups); - applyChanges(); - } - }, [client, setIndexViews, updateDraft, applyChanges]); - - return { fetchAndLoadIndexViews }; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useGetObjectMetadataItemIdFromViewRelationEventDetail.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useGetObjectMetadataItemIdFromViewRelationEventDetail.ts deleted file mode 100644 index 2c3192faca..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/hooks/useGetObjectMetadataItemIdFromViewRelationEventDetail.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { type MetadataOperationBrowserEventDetail } from '@/browser-event/types/MetadataOperationBrowserEventDetail'; -import { useGetViewIdFromViewSSERelationEventDetail } from '@/metadata-store/hooks/useGetViewIdFromViewSSERelationEventDetail'; -import { useGetViewById } from '@/views/hooks/useGetViewById'; -import { type ViewField } from '@/views/types/ViewField'; -import { type ViewFilter } from '@/views/types/ViewFilter'; -import { type ViewFilterGroup } from '@/views/types/ViewFilterGroup'; -import { type ViewSort } from '@/views/types/ViewSort'; -import { isDefined } from 'twenty-shared/utils'; - -export const useGetObjectMetadataItemIdFromViewRelationEventDetail = () => { - const { getViewIdFromViewSSERelationEventDetail } = - useGetViewIdFromViewSSERelationEventDetail(); - - const { getViewById } = useGetViewById(); - - const getObjectMetadataItemIdFromViewRelationEventDetail = < - T extends ViewFilter | ViewFilterGroup | ViewField | ViewSort, - >( - eventDetail: MetadataOperationBrowserEventDetail, - ) => { - const viewId = getViewIdFromViewSSERelationEventDetail(eventDetail); - - if (!isDefined(viewId)) { - return null; - } - - const { view } = getViewById(viewId); - - if (!isDefined(view)) { - return null; - } - - return view.objectMetadataId; - }; - - return { - getObjectMetadataItemIdFromViewRelationEventDetail, - }; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useGetViewIdFromViewSSERelationEventDetail.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useGetViewIdFromViewSSERelationEventDetail.ts deleted file mode 100644 index 45350753ec..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/hooks/useGetViewIdFromViewSSERelationEventDetail.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { type MetadataOperationBrowserEventDetail } from '@/browser-event/types/MetadataOperationBrowserEventDetail'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { coreViewsState } from '@/views/states/coreViewState'; -import { type ViewField } from '@/views/types/ViewField'; -import { type ViewFilter } from '@/views/types/ViewFilter'; -import { type ViewFilterGroup } from '@/views/types/ViewFilterGroup'; -import { type ViewSort } from '@/views/types/ViewSort'; -import { type Nullable } from 'twenty-shared/types'; -import { AllMetadataName } from '~/generated-metadata/graphql'; - -export const useGetViewIdFromViewSSERelationEventDetail = () => { - const coreViews = useAtomStateValue(coreViewsState); - - const getViewIdFromViewSSERelationEventDetail = < - T extends ViewFilter | ViewFilterGroup | ViewField | ViewSort, - >( - eventDetail: MetadataOperationBrowserEventDetail, - ) => { - switch (eventDetail.operation.type) { - case 'create': { - return eventDetail.operation.createdRecord?.viewId as Nullable; - } - case 'update': { - return eventDetail.operation.updatedRecord?.viewId as Nullable; - } - case 'delete': { - const deletedRecordId = eventDetail.operation.deletedRecordId; - - switch (eventDetail.metadataName) { - case AllMetadataName.viewFilter: - return coreViews.find((view) => - view.viewFilters.some( - (viewFilter) => viewFilter.id === deletedRecordId, - ), - )?.id; - case AllMetadataName.viewField: - return coreViews.find((view) => - view.viewFields.some( - (viewField) => viewField.id === deletedRecordId, - ), - )?.id; - case AllMetadataName.viewFilterGroup: - return coreViews.find((view) => - view.viewFilterGroups?.some( - (viewFilterGroup) => viewFilterGroup.id === deletedRecordId, - ), - )?.id; - case AllMetadataName.viewSort: - return coreViews.find((view) => - view.viewSorts.some( - (viewSort) => viewSort.id === deletedRecordId, - ), - )?.id; - default: - return null; - } - } - } - }; - - return { - getViewIdFromViewSSERelationEventDetail, - }; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useLoadMinimalMetadata.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useLoadMinimalMetadata.ts index 7187a275ae..5b8cde955e 100644 --- a/packages/twenty-front/src/modules/metadata-store/hooks/useLoadMinimalMetadata.ts +++ b/packages/twenty-front/src/modules/metadata-store/hooks/useLoadMinimalMetadata.ts @@ -1,5 +1,4 @@ import { FIND_MINIMAL_METADATA } from '@/metadata-store/graphql/queries/findMinimalMetadata'; -import { metadataCollectionHashesState } from '@/metadata-store/states/metadataCollectionHashesState'; import { metadataStoreState, type MetadataEntityKey, @@ -30,51 +29,56 @@ export const useLoadMinimalMetadata = () => { const { objectMetadataItems, views, collectionHashes } = result.data.minimalMetadata; - const localHashes = store.get(metadataCollectionHashesState.atom); - - const serverHashes: Partial> = {}; + const staleEntityKeys: MetadataEntityKey[] = []; if (isDefined(collectionHashes)) { - for (const [metadataName, hash] of Object.entries( - collectionHashes as Record, - )) { - const entityKey = mapAllMetadataNameToEntityKey(metadataName); + for (const { collectionName, hash } of collectionHashes) { + const entityKey = mapAllMetadataNameToEntityKey(collectionName); - if (isDefined(entityKey)) { - serverHashes[entityKey] = hash; + if (!isDefined(entityKey)) { + continue; } + + const entry = store.get(metadataStoreState.atomFamily(entityKey)); + + if (entry.currentCollectionHash !== hash) { + staleEntityKeys.push(entityKey); + } + + store.set(metadataStoreState.atomFamily(entityKey), (prev) => ({ + ...prev, + draftCollectionHash: hash, + })); } } - store.set(metadataCollectionHashesState.atom, serverHashes); - - const staleEntityKeys: MetadataEntityKey[] = Object.entries(serverHashes) - .filter( - ([entityKey, hash]) => - localHashes[entityKey as MetadataEntityKey] !== hash, - ) - .map(([entityKey]) => entityKey as MetadataEntityKey); - const objectsEntry = store.get( metadataStoreState.atomFamily('objectMetadataItems'), ); if (objectsEntry.status === 'empty') { - store.set(metadataStoreState.atomFamily('objectMetadataItems'), { - current: objectMetadataItems as unknown as FlatObjectMetadataItem[], - draft: [], - status: 'up-to-date', - }); + store.set( + metadataStoreState.atomFamily('objectMetadataItems'), + (prev) => ({ + ...prev, + current: objectMetadataItems as unknown as FlatObjectMetadataItem[], + status: 'up-to-date', + currentCollectionHash: prev.draftCollectionHash, + draftCollectionHash: undefined, + }), + ); } const viewsEntry = store.get(metadataStoreState.atomFamily('views')); if (viewsEntry.status === 'empty') { - store.set(metadataStoreState.atomFamily('views'), { + store.set(metadataStoreState.atomFamily('views'), (prev) => ({ + ...prev, current: views as unknown as FlatView[], - draft: [], status: 'up-to-date', - }); + currentCollectionHash: prev.draftCollectionHash, + draftCollectionHash: undefined, + })); } return { staleEntityKeys }; diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useLoadMockedMinimalMetadata.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useLoadMockedMinimalMetadata.ts new file mode 100644 index 0000000000..bd96f94fbf --- /dev/null +++ b/packages/twenty-front/src/modules/metadata-store/hooks/useLoadMockedMinimalMetadata.ts @@ -0,0 +1,64 @@ +import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; +import { splitObjectMetadataItemWithRelated } from '@/metadata-store/utils/splitObjectMetadataItemWithRelated'; +import { splitViewWithRelated } from '@/metadata-store/utils/splitViewWithRelated'; +import { useCallback } from 'react'; + +const MOCKED_COLLECTION_HASH = 'mocked'; + +export const useLoadMockedMinimalMetadata = () => { + const { updateDraft, applyChanges, resetMetadataStore } = useMetadataStore(); + + const loadMockedMinimalMetadata = useCallback(async () => { + resetMetadataStore(); + const [ + { generatedMockObjectMetadataItems }, + { mockedCoreViews }, + { mockedNavigationMenuItems }, + ] = await Promise.all([ + import('~/testing/utils/generatedMockObjectMetadataItems'), + import('~/testing/mock-data/generated/metadata/views/mock-views-data'), + import( + '~/testing/mock-data/generated/metadata/navigation-menu-items/mock-navigation-menu-items-data' + ), + ]); + + const { flatObjects, flatFields, flatIndexes } = + splitObjectMetadataItemWithRelated(generatedMockObjectMetadataItems); + + updateDraft('objectMetadataItems', flatObjects, MOCKED_COLLECTION_HASH); + updateDraft('fieldMetadataItems', flatFields, MOCKED_COLLECTION_HASH); + updateDraft('indexMetadataItems', flatIndexes, MOCKED_COLLECTION_HASH); + + const { + flatViews, + flatViewFields, + flatViewFilters, + flatViewSorts, + flatViewGroups, + flatViewFilterGroups, + flatViewFieldGroups, + } = splitViewWithRelated(mockedCoreViews); + + updateDraft('views', flatViews, MOCKED_COLLECTION_HASH); + updateDraft('viewFields', flatViewFields, MOCKED_COLLECTION_HASH); + updateDraft('viewFilters', flatViewFilters, MOCKED_COLLECTION_HASH); + updateDraft('viewSorts', flatViewSorts, MOCKED_COLLECTION_HASH); + updateDraft('viewGroups', flatViewGroups, MOCKED_COLLECTION_HASH); + updateDraft( + 'viewFilterGroups', + flatViewFilterGroups, + MOCKED_COLLECTION_HASH, + ); + updateDraft('viewFieldGroups', flatViewFieldGroups, MOCKED_COLLECTION_HASH); + + updateDraft( + 'navigationMenuItems', + mockedNavigationMenuItems, + MOCKED_COLLECTION_HASH, + ); + + applyChanges(); + }, [updateDraft, applyChanges, resetMetadataStore]); + + return { loadMockedMinimalMetadata }; +}; diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useLoadStaleMetadataEntities.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useLoadStaleMetadataEntities.ts new file mode 100644 index 0000000000..4a7e958e85 --- /dev/null +++ b/packages/twenty-front/src/modules/metadata-store/hooks/useLoadStaleMetadataEntities.ts @@ -0,0 +1,206 @@ +import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; +import { type MetadataEntityKey } from '@/metadata-store/states/metadataStoreState'; +import { splitObjectMetadataItemWithRelated } from '@/metadata-store/utils/splitObjectMetadataItemWithRelated'; +import { splitPageLayoutWithRelated } from '@/metadata-store/utils/splitPageLayoutWithRelated'; +import { splitViewWithRelated } from '@/metadata-store/utils/splitViewWithRelated'; +import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries'; +import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '@/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems'; +import { transformPageLayout } from '@/page-layout/utils/transformPageLayout'; +import { logicFunctionsState } from '@/settings/logic-functions/states/logicFunctionsState'; +import { useApolloClient } from '@apollo/client/react'; +import { useStore } from 'jotai'; +import { useCallback } from 'react'; +import { isDefined } from 'twenty-shared/utils'; +import { + FindAllCoreViewsDocument, + FindAllRecordPageLayoutsDocument, + FindFieldsWidgetCoreViewsDocument, + FindManyLogicFunctionsDocument, + FindManyNavigationMenuItemsDocument, + type ObjectMetadataItemsQuery, + ViewType, +} from '~/generated-metadata/graphql'; + +const OBJECTS_GROUP_KEYS: MetadataEntityKey[] = [ + 'objectMetadataItems', + 'fieldMetadataItems', + 'indexMetadataItems', +]; + +const VIEWS_GROUP_KEYS: MetadataEntityKey[] = [ + 'views', + 'viewFields', + 'viewFilters', + 'viewSorts', + 'viewGroups', + 'viewFilterGroups', + 'viewFieldGroups', +]; + +const PAGE_LAYOUTS_GROUP_KEYS: MetadataEntityKey[] = [ + 'pageLayouts', + 'pageLayoutTabs', + 'pageLayoutWidgets', +]; + +const INDEX_VIEW_TYPES = [ViewType.TABLE, ViewType.KANBAN, ViewType.CALENDAR]; +const FIELDS_WIDGET_VIEW_TYPES = [ViewType.FIELDS_WIDGET]; + +const hasOverlap = ( + staleKeys: MetadataEntityKey[], + groupKeys: MetadataEntityKey[], +): boolean => staleKeys.some((key) => groupKeys.includes(key)); + +export const useLoadStaleMetadataEntities = () => { + const client = useApolloClient(); + const store = useStore(); + const { updateDraft, applyChanges } = useMetadataStore(); + + const loadStaleMetadataEntities = useCallback( + async (staleEntityKeys: MetadataEntityKey[]) => { + if (staleEntityKeys.length === 0) { + return; + } + + const fetchPromises: Promise[] = []; + + if (hasOverlap(staleEntityKeys, OBJECTS_GROUP_KEYS)) { + fetchPromises.push( + client + .query({ + query: FIND_MANY_OBJECT_METADATA_ITEMS, + fetchPolicy: 'network-only', + }) + .then((result) => { + const compositeObjects = + mapPaginatedObjectMetadataItemsToObjectMetadataItems({ + pagedObjectMetadataItems: result.data, + }); + + const { flatObjects, flatFields, flatIndexes } = + splitObjectMetadataItemWithRelated(compositeObjects); + + updateDraft('objectMetadataItems', flatObjects); + updateDraft('fieldMetadataItems', flatFields); + updateDraft('indexMetadataItems', flatIndexes); + }), + ); + } + + if (hasOverlap(staleEntityKeys, VIEWS_GROUP_KEYS)) { + fetchPromises.push( + Promise.all([ + client.query({ + query: FindAllCoreViewsDocument, + variables: { viewTypes: INDEX_VIEW_TYPES }, + fetchPolicy: 'network-only', + }), + client.query({ + query: FindFieldsWidgetCoreViewsDocument, + variables: { viewTypes: FIELDS_WIDGET_VIEW_TYPES }, + fetchPolicy: 'network-only', + }), + ]).then(([indexViewsResult, fieldsWidgetViewsResult]) => { + const allViews = [ + ...(indexViewsResult.data?.getCoreViews ?? []), + ...(fieldsWidgetViewsResult.data?.getCoreViews ?? []), + ]; + + const { + flatViews, + flatViewFields, + flatViewFilters, + flatViewSorts, + flatViewGroups, + flatViewFilterGroups, + flatViewFieldGroups, + } = splitViewWithRelated(allViews); + + updateDraft('views', flatViews); + updateDraft('viewFields', flatViewFields); + updateDraft('viewFilters', flatViewFilters); + updateDraft('viewSorts', flatViewSorts); + updateDraft('viewGroups', flatViewGroups); + updateDraft('viewFilterGroups', flatViewFilterGroups); + updateDraft('viewFieldGroups', flatViewFieldGroups); + }), + ); + } + + if (hasOverlap(staleEntityKeys, PAGE_LAYOUTS_GROUP_KEYS)) { + fetchPromises.push( + client + .query({ + query: FindAllRecordPageLayoutsDocument, + fetchPolicy: 'network-only', + }) + .then((result) => { + if (!isDefined(result.data?.getPageLayouts)) { + return; + } + + const transformed = + result.data.getPageLayouts.map(transformPageLayout); + + const { + flatPageLayouts, + flatPageLayoutTabs, + flatPageLayoutWidgets, + } = splitPageLayoutWithRelated(transformed); + + updateDraft('pageLayouts', flatPageLayouts); + updateDraft('pageLayoutTabs', flatPageLayoutTabs); + updateDraft('pageLayoutWidgets', flatPageLayoutWidgets); + }), + ); + } + + if (staleEntityKeys.includes('logicFunctions')) { + fetchPromises.push( + client + .query({ + query: FindManyLogicFunctionsDocument, + fetchPolicy: 'network-only', + }) + .then((result) => { + if (!isDefined(result.data?.findManyLogicFunctions)) { + return; + } + + store.set( + logicFunctionsState.atom, + result.data.findManyLogicFunctions, + ); + updateDraft('logicFunctions', result.data.findManyLogicFunctions); + }), + ); + } + + if (staleEntityKeys.includes('navigationMenuItems')) { + fetchPromises.push( + client + .query({ + query: FindManyNavigationMenuItemsDocument, + fetchPolicy: 'network-only', + }) + .then((result) => { + if (!isDefined(result.data?.navigationMenuItems)) { + return; + } + + updateDraft( + 'navigationMenuItems', + result.data.navigationMenuItems, + ); + }), + ); + } + + await Promise.all(fetchPromises); + applyChanges(); + }, + [client, store, updateDraft, applyChanges], + ); + + return { loadStaleMetadataEntities }; +}; diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useMetadataStore.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useMetadataStore.ts index dd4322d9af..f3d3f9e5b2 100644 --- a/packages/twenty-front/src/modules/metadata-store/hooks/useMetadataStore.ts +++ b/packages/twenty-front/src/modules/metadata-store/hooks/useMetadataStore.ts @@ -1,5 +1,4 @@ -import { metadataCollectionHashesState } from '@/metadata-store/states/metadataCollectionHashesState'; -import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState'; +import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState'; import { ALL_METADATA_ENTITY_KEYS, metadataStoreState, @@ -37,8 +36,7 @@ export const resetMetadataStore = (store: JotaiStore) => { store.set(metadataStoreState.atomFamily(key), EMPTY_ENTRY); } - store.set(metadataCollectionHashesState.atom, {}); - store.set(isAppMetadataReadyState.atom, false); + store.set(isMinimalMetadataReadyState.atom, false); }; const changeMetadataEntityAsUpToDate = ( @@ -51,6 +49,9 @@ const changeMetadataEntityAsUpToDate = ( current: entry.draft, draft: [], status: 'up-to-date', + currentCollectionHash: + entry.draftCollectionHash ?? entry.currentCollectionHash, + draftCollectionHash: undefined, }); }; @@ -58,7 +59,11 @@ export const useMetadataStore = () => { const store = useStore(); const updateDraft = useCallback( - (key: K, data: MetadataEntityTypeMap[K][]) => { + ( + key: K, + data: MetadataEntityTypeMap[K][], + collectionHash?: string, + ) => { const currentEntry = store.get(metadataStoreState.atomFamily(key)); if ( @@ -72,6 +77,9 @@ export const useMetadataStore = () => { ...prev, draft: data, status: 'draft-pending' as const, + ...(collectionHash !== undefined + ? { draftCollectionHash: collectionHash } + : {}), })); }, [store], diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useReloadWorkspaceMetadata.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useReloadWorkspaceMetadata.ts deleted file mode 100644 index 9f5c6f3d95..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/hooks/useReloadWorkspaceMetadata.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; -import { splitViewWithRelated } from '@/metadata-store/utils/splitViewWithRelated'; -import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems'; -import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; -import { coreViewsState } from '@/views/states/coreViewState'; -import { useStore } from 'jotai'; -import { useCallback } from 'react'; - -export const useReloadWorkspaceMetadata = () => { - const store = useStore(); - const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems(); - const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems(); - const { updateDraft, applyChanges, resetMetadataStore } = useMetadataStore(); - - const reloadWorkspaceMetadata = useCallback(async () => { - resetMetadataStore(); - - await refreshObjectMetadataItems(); - - const loadedViews = store.get(coreViewsState.atom); - const { - flatViews, - flatViewFields, - flatViewFilters, - flatViewSorts, - flatViewGroups, - flatViewFilterGroups, - flatViewFieldGroups, - } = splitViewWithRelated(loadedViews); - - updateDraft('views', flatViews); - updateDraft('viewFields', flatViewFields); - updateDraft('viewFilters', flatViewFilters); - updateDraft('viewSorts', flatViewSorts); - updateDraft('viewGroups', flatViewGroups); - updateDraft('viewFilterGroups', flatViewFilterGroups); - updateDraft('viewFieldGroups', flatViewFieldGroups); - applyChanges(); - }, [ - resetMetadataStore, - refreshObjectMetadataItems, - store, - updateDraft, - applyChanges, - ]); - - const resetToMockedMetadata = useCallback(async () => { - resetMetadataStore(); - - await loadMockedObjectMetadataItems(); - }, [resetMetadataStore, loadMockedObjectMetadataItems]); - - return { reloadWorkspaceMetadata, resetToMockedMetadata }; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useSetIndexViews.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useSetIndexViews.ts deleted file mode 100644 index a854006f9e..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/hooks/useSetIndexViews.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { coreViewsState } from '@/views/states/coreViewState'; -import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; -import { useStore } from 'jotai'; -import { useCallback } from 'react'; -import { ViewType } from '~/generated-metadata/graphql'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; - -export const useSetIndexViews = () => { - const store = useStore(); - - const setIndexViews = useCallback( - (indexViews: CoreViewWithRelations[]) => { - const existingCoreViews = store.get(coreViewsState.atom); - const existingFieldsWidgetViews = existingCoreViews.filter( - (view) => view.type === ViewType.FIELDS_WIDGET, - ); - const mergedViews = [...indexViews, ...existingFieldsWidgetViews]; - - if (!isDeeplyEqual(existingCoreViews, mergedViews)) { - store.set(coreViewsState.atom, mergedViews); - } - }, - [store], - ); - - return { - setIndexViews, - }; -}; diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useStaleMetadataEntities.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useStaleMetadataEntities.ts deleted file mode 100644 index 795ea76167..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/hooks/useStaleMetadataEntities.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { metadataCollectionHashesState } from '@/metadata-store/states/metadataCollectionHashesState'; -import { - ALL_METADATA_ENTITY_KEYS, - type MetadataEntityKey, -} from '@/metadata-store/states/metadataStoreState'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useMemo } from 'react'; - -// Returns the list of MetadataEntityKey whose local collection hash -// is missing or differs from the last known server collection hash. -export const useStaleMetadataEntities = (): MetadataEntityKey[] => { - const metadataCollectionHashes = useAtomStateValue( - metadataCollectionHashesState, - ); - - return useMemo(() => { - const staleKeys: MetadataEntityKey[] = []; - - for (const entityKey of ALL_METADATA_ENTITY_KEYS) { - if (metadataCollectionHashes[entityKey] === undefined) { - staleKeys.push(entityKey); - } - } - - return staleKeys; - }, [metadataCollectionHashes]); -}; diff --git a/packages/twenty-front/src/modules/metadata-store/states/isAppMetadataReadyState.ts b/packages/twenty-front/src/modules/metadata-store/states/isMinimalMetadataReadyState.ts similarity index 50% rename from packages/twenty-front/src/modules/metadata-store/states/isAppMetadataReadyState.ts rename to packages/twenty-front/src/modules/metadata-store/states/isMinimalMetadataReadyState.ts index aec5100ae9..57407d7d31 100644 --- a/packages/twenty-front/src/modules/metadata-store/states/isAppMetadataReadyState.ts +++ b/packages/twenty-front/src/modules/metadata-store/states/isMinimalMetadataReadyState.ts @@ -1,6 +1,6 @@ import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; -export const isAppMetadataReadyState = createAtomState({ - key: 'isAppMetadataReadyState', +export const isMinimalMetadataReadyState = createAtomState({ + key: 'isMinimalMetadataReadyState', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/metadata-store/states/metadataCollectionHashesState.ts b/packages/twenty-front/src/modules/metadata-store/states/metadataCollectionHashesState.ts deleted file mode 100644 index a7d40251ca..0000000000 --- a/packages/twenty-front/src/modules/metadata-store/states/metadataCollectionHashesState.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type MetadataEntityKey } from '@/metadata-store/states/metadataStoreState'; -import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; - -export const metadataCollectionHashesState = createAtomState< - Partial> ->({ - key: 'metadataCollectionHashesState', - defaultValue: {}, - useLocalStorage: true, - localStorageOptions: { getOnInit: true }, -}); diff --git a/packages/twenty-front/src/modules/metadata-store/states/metadataStoreState.ts b/packages/twenty-front/src/modules/metadata-store/states/metadataStoreState.ts index dcea5b2f96..b178ee1427 100644 --- a/packages/twenty-front/src/modules/metadata-store/states/metadataStoreState.ts +++ b/packages/twenty-front/src/modules/metadata-store/states/metadataStoreState.ts @@ -38,6 +38,8 @@ export type MetadataStoreItem = { current: object[]; draft: object[]; status: MetadataEntityStoreStatus; + currentCollectionHash?: string; + draftCollectionHash?: string; }; const METADATA_STORE_ITEM_INITIAL_VALUE: MetadataStoreItem = { diff --git a/packages/twenty-front/src/modules/metadata-store/utils/patchMetadataStoreFromSSEEvent.ts b/packages/twenty-front/src/modules/metadata-store/utils/patchMetadataStoreFromSSEEvent.ts index c9b863a19f..a97e21bbd2 100644 --- a/packages/twenty-front/src/modules/metadata-store/utils/patchMetadataStoreFromSSEEvent.ts +++ b/packages/twenty-front/src/modules/metadata-store/utils/patchMetadataStoreFromSSEEvent.ts @@ -1,4 +1,3 @@ -import { metadataCollectionHashesState } from '@/metadata-store/states/metadataCollectionHashesState'; import { metadataStoreState, type MetadataEntityKey, @@ -22,19 +21,29 @@ type SSEEventOperation = deletedRecordId: string; }; -export const patchMetadataStoreFromSSEEvent = ( - store: JotaiStore, - entityKey: MetadataEntityKey, - operation: SSEEventOperation, - updatedCollectionHash?: string, -) => { +export const patchMetadataStoreFromSSEEvent = ({ + store, + entityKey, + operation, + updatedCollectionHash, +}: { + store: JotaiStore; + entityKey: MetadataEntityKey; + operation: SSEEventOperation; + updatedCollectionHash?: string; +}) => { const entry = store.get(metadataStoreState.atomFamily(entityKey)); const currentItems = entry.current as Array<{ id: string }>; + const hashUpdate = isDefined(updatedCollectionHash) + ? { currentCollectionHash: updatedCollectionHash } + : {}; + switch (operation.type) { case 'create': { store.set(metadataStoreState.atomFamily(entityKey), { ...entry, + ...hashUpdate, current: [...currentItems, operation.createdRecord], }); break; @@ -42,6 +51,7 @@ export const patchMetadataStoreFromSSEEvent = ( case 'update': { store.set(metadataStoreState.atomFamily(entityKey), { ...entry, + ...hashUpdate, current: currentItems.map((item) => item.id === (operation.updatedRecord as { id: string }).id ? { ...item, ...operation.updatedRecord } @@ -53,6 +63,7 @@ export const patchMetadataStoreFromSSEEvent = ( case 'delete': { store.set(metadataStoreState.atomFamily(entityKey), { ...entry, + ...hashUpdate, current: currentItems.filter( (item) => item.id !== operation.deletedRecordId, ), @@ -60,13 +71,4 @@ export const patchMetadataStoreFromSSEEvent = ( break; } } - - if (isDefined(updatedCollectionHash)) { - const currentHashes = store.get(metadataCollectionHashesState.atom); - - store.set(metadataCollectionHashesState.atom, { - ...currentHashes, - [entityKey]: updatedCollectionHash, - }); - } }; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderSubItem.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderSubItem.tsx index b8a3775dec..f66c95a7f2 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderSubItem.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderSubItem.tsx @@ -11,7 +11,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem'; import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { ViewKey } from '@/views/types/ViewKey'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; @@ -38,7 +38,7 @@ export const WorkspaceNavigationMenuItemFolderSubItem = ({ isNavigationMenuInEditModeState, ); const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); - const coreViews = useAtomStateValue(coreViewsState); + const coreViews = useAtomStateValue(coreViewsSelector); const views = coreViews.map(convertCoreViewToView); const objectMetadataItem = navigationMenuItem.itemType === NavigationMenuItemType.VIEW || diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleAddToNavigationDrop.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleAddToNavigationDrop.ts index 0f18e2f96a..3871b48e95 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleAddToNavigationDrop.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleAddToNavigationDrop.ts @@ -24,7 +24,7 @@ import { validateAndExtractWorkspaceFolderId } from '@/navigation-menu-item/util import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { useStore } from 'jotai'; @@ -42,7 +42,7 @@ export const useHandleAddToNavigationDrop = () => { const { openNavigationMenuItemInSidePanel } = useOpenNavigationMenuItemInSidePanel(); const { objectMetadataItems } = useObjectMetadataItems(); - const coreViews = useAtomStateValue(coreViewsState); + const coreViews = useAtomStateValue(coreViewsSelector); const { getIcon } = useIcons(); const setIsNavigationMenuInEditMode = useSetAtomState( isNavigationMenuInEditModeState, diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsByFolder.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsByFolder.ts index 42bd08e915..7c4dc96592 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsByFolder.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsByFolder.ts @@ -6,7 +6,7 @@ import { recordIdentifierToObjectRecordIdentifier } from '@/navigation-menu-item import { sortNavigationMenuItems } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; @@ -26,7 +26,7 @@ type NavigationMenuItemFolderEntry = Pick< >; export const useNavigationMenuItemsByFolder = () => { - const coreViews = useAtomStateValue(coreViewsState); + const coreViews = useAtomStateValue(coreViewsSelector); const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); const { navigationMenuItems, workspaceNavigationMenuItems } = diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft.ts index 771793896c..efe83dbccb 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft.ts @@ -2,7 +2,7 @@ import { isDefined } from 'twenty-shared/utils'; import { getObjectMetadataIdsInDraft } from '@/navigation-menu-item/utils/getObjectMetadataIdsInDraft'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { ViewKey } from '@/views/types/ViewKey'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; @@ -15,7 +15,7 @@ type NavigationMenuItemDraft = { export const useNavigationMenuObjectMetadataFromDraft = ( currentDraft: NavigationMenuItemDraft[], ) => { - const coreViews = useAtomStateValue(coreViewsState); + const coreViews = useAtomStateValue(coreViewsSelector); const views = coreViews.map(convertCoreViewToView); const objectMetadataIdsInWorkspace = getObjectMetadataIdsInDraft( diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useRemoveNavigationMenuItemByTargetRecordId.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useRemoveNavigationMenuItemByTargetRecordId.ts index 8b70045cc1..91059ccae5 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useRemoveNavigationMenuItemByTargetRecordId.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useRemoveNavigationMenuItemByTargetRecordId.ts @@ -1,9 +1,12 @@ import { useCallback } from 'react'; +import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; +import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; -import { FindManyNavigationMenuItemsDocument } from '~/generated-metadata/graphql'; -import { navigationMenuItemsState } from '@/navigation-menu-item/states/navigationMenuItemsState'; -import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; +import { + FindManyNavigationMenuItemsDocument, + type NavigationMenuItem, +} from '~/generated-metadata/graphql'; import { isDefined } from 'twenty-shared/utils'; import { useStore } from 'jotai'; @@ -11,15 +14,16 @@ export const useRemoveNavigationMenuItemByTargetRecordId = () => { const store = useStore(); const apolloCoreClient = useApolloCoreClient(); const cache = apolloCoreClient.cache; - - const setNavigationMenuItems = useSetAtomState(navigationMenuItemsState); + const { updateDraft, applyChanges } = useMetadataStore(); const removeNavigationMenuItemsByTargetRecordIds = useCallback( (targetRecordIds: string[]) => { const targetRecordIdsSet = new Set(targetRecordIds); - const currentNavigationMenuItems = store.get( - navigationMenuItemsState.atom, + const entry = store.get( + metadataStoreState.atomFamily('navigationMenuItems'), ); + const currentNavigationMenuItems = + entry.current as unknown as NavigationMenuItem[]; const updatedNavigationMenuItems = currentNavigationMenuItems.filter( (item) => @@ -27,7 +31,8 @@ export const useRemoveNavigationMenuItemByTargetRecordId = () => { !targetRecordIdsSet.has(item.targetRecordId), ); - setNavigationMenuItems(updatedNavigationMenuItems); + updateDraft('navigationMenuItems', updatedNavigationMenuItems); + applyChanges(); cache.updateQuery( { query: FindManyNavigationMenuItemsDocument }, @@ -43,7 +48,7 @@ export const useRemoveNavigationMenuItemByTargetRecordId = () => { }, ); }, - [cache, setNavigationMenuItems, store], + [cache, store, updateDraft, applyChanges], ); return { diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata.ts index 2f45707e07..3d562d5bfb 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata.ts @@ -2,12 +2,12 @@ import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/ho import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; export const useSelectedNavigationMenuItemEditItemObjectMetadata = () => { const { selectedItem } = useSelectedNavigationMenuItemEditItem(); - const coreViews = useAtomStateValue(coreViewsState); + const coreViews = useAtomStateValue(coreViewsSelector); const views = coreViews.map(convertCoreViewToView); const { objectMetadataItems } = useObjectMetadataItems(); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSortedNavigationMenuItems.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSortedNavigationMenuItems.ts index c13a4ebb83..680d5bd1e8 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSortedNavigationMenuItems.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSortedNavigationMenuItems.ts @@ -7,7 +7,7 @@ import { recordIdentifierToObjectRecordIdentifier } from '@/navigation-menu-item import { sortNavigationMenuItems } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; @@ -16,7 +16,7 @@ import { useNavigationMenuItemsData } from './useNavigationMenuItemsData'; export const useSortedNavigationMenuItems = () => { const { navigationMenuItems, workspaceNavigationMenuItems } = useNavigationMenuItemsData(); - const coreViews = useAtomStateValue(coreViewsState).map( + const coreViews = useAtomStateValue(coreViewsSelector).map( convertCoreViewToView, ); const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceNavigationMenuItems.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceNavigationMenuItems.ts index 4a784e6987..743c833300 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceNavigationMenuItems.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceNavigationMenuItems.ts @@ -4,7 +4,7 @@ import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavig import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { isDefined } from 'twenty-shared/utils'; @@ -15,7 +15,7 @@ export const useWorkspaceNavigationMenuItems = (): { } => { const { workspaceNavigationMenuItems: rawWorkspaceNavigationMenuItems } = useNavigationMenuItemsData(); - const coreViews = useAtomStateValue(coreViewsState); + const coreViews = useAtomStateValue(coreViewsSelector); const views = coreViews.map(convertCoreViewToView); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceSectionItems.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceSectionItems.ts index 635dd4529b..6d9fc9886c 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceSectionItems.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceSectionItems.ts @@ -10,7 +10,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { isDefined } from 'twenty-shared/utils'; @@ -35,7 +35,7 @@ export const useWorkspaceSectionItems = (): FlatWorkspaceItem[] => { const { workspaceNavigationMenuItemsSorted } = useSortedNavigationMenuItems(); const { workspaceNavigationMenuItemsByFolder } = useNavigationMenuItemsByFolder(); - const coreViews = useAtomStateValue(coreViewsState); + const coreViews = useAtomStateValue(coreViewsSelector); const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/states/navigationMenuItemsState.ts b/packages/twenty-front/src/modules/navigation-menu-item/states/navigationMenuItemsState.ts index 6ecc295571..5970f9cc24 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/states/navigationMenuItemsState.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/states/navigationMenuItemsState.ts @@ -1,7 +1,14 @@ -import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; +import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; +import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; import { type NavigationMenuItem } from '~/generated-metadata/graphql'; -export const navigationMenuItemsState = createAtomState({ +export const navigationMenuItemsState = createAtomSelector< + NavigationMenuItem[] +>({ key: 'navigationMenuItemsState', - defaultValue: [], + get: ({ get }) => { + const entry = get(metadataStoreState, 'navigationMenuItems'); + + return entry.current as unknown as NavigationMenuItem[]; + }, }); diff --git a/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts b/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts index 52530a266d..b066ea3017 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts @@ -1,14 +1,12 @@ -import { renderHook, waitFor } from '@testing-library/react'; -import { createElement, useEffect, type ReactNode } from 'react'; -import { Provider as JotaiProvider } from 'jotai'; import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath'; -import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; -import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; -import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { renderHook, waitFor } from '@testing-library/react'; +import { Provider as JotaiProvider } from 'jotai'; +import { createElement, useEffect, type ReactNode } from 'react'; import { AppPath } from 'twenty-shared/types'; import { ViewOpenRecordIn, @@ -16,8 +14,10 @@ import { ViewVisibility, } from '~/generated-metadata/graphql'; import { mockedUserData } from '~/testing/mock-data/users'; -import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; +import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow'; +import { setTestCoreViewsInMetadataStore } from '~/testing/utils/setTestCoreViewsInMetadataStore'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; const Wrapper = ({ children }: { children: ReactNode }) => createElement(JotaiProvider, { store: jotaiStore }, children); @@ -40,11 +40,10 @@ const renderHooks = ({ const setCurrentUserWorkspace = useSetAtomState( currentUserWorkspaceState, ); - const setCoreViews = useSetAtomState(coreViewsState); useEffect(() => { if (withExistingView) { - setCoreViews([ + setTestCoreViewsInMetadataStore(jotaiStore, [ { id: 'viewId', name: 'Test View', @@ -69,14 +68,14 @@ const renderHooks = ({ }, ]); } else { - setCoreViews([]); + setTestCoreViewsInMetadataStore(jotaiStore, []); } if (withCurrentUser) { setCurrentUser(mockedUserData); setCurrentUserWorkspace(mockedUserData.currentUserWorkspace); } - }, [setCurrentUser, setCurrentUserWorkspace, setCoreViews]); + }, [setCurrentUser, setCurrentUserWorkspace]); return useDefaultHomePagePath(); }, diff --git a/packages/twenty-front/src/modules/navigation/hooks/useDefaultHomePagePath.ts b/packages/twenty-front/src/modules/navigation/hooks/useDefaultHomePagePath.ts index c3bbc5f191..5ed7d04c45 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/useDefaultHomePagePath.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/useDefaultHomePagePath.ts @@ -5,7 +5,7 @@ import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilte import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import isEmpty from 'lodash.isempty'; import { useCallback, useMemo } from 'react'; @@ -43,7 +43,7 @@ export const useDefaultHomePagePath = () => { [readableAlphaSortedActiveNonSystemObjectMetadataItems], ); - const coreViews = useAtomStateValue(coreViewsState); + const coreViews = useAtomStateValue(coreViewsSelector); const getFirstView = useCallback( (objectMetadataItemId: string | undefined | null) => { diff --git a/packages/twenty-front/src/modules/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural.ts b/packages/twenty-front/src/modules/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural.ts index cafd038901..ff639baacc 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural.ts @@ -1,6 +1,6 @@ import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; @@ -16,7 +16,7 @@ export const useSetLastVisitedViewForObjectMetadataNamePlural = () => { objectNamePlural: string; viewId: string; }) => { - const views = store.get(coreViewsState.atom); + const views = store.get(coreViewsSelector.atom); const view = views.find( (view: CoreViewWithRelations) => view.id === viewId, diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemObjectContent.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemObjectContent.tsx index 3400a3e224..44a35fe451 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemObjectContent.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemObjectContent.tsx @@ -4,7 +4,7 @@ import { NavigationDrawerItemForObjectMetadataItem } from '@/object-metadata/com import type { WorkspaceSectionItemContentProps } from '@/object-metadata/components/WorkspaceSectionItemContentProps'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; type NavigationDrawerSectionForWorkspaceItemObjectContentProps = @@ -17,7 +17,7 @@ export const NavigationDrawerSectionForWorkspaceItemObjectContent = ({ onActiveObjectMetadataItemClick, }: NavigationDrawerSectionForWorkspaceItemObjectContentProps) => { const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); - const coreViews = useAtomStateValue(coreViewsState); + const coreViews = useAtomStateValue(coreViewsSelector); const views = coreViews.map(convertCoreViewToView); const objectMetadataItem = getObjectMetadataForNavigationMenuItem( item as ProcessedNavigationMenuItem, diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx index 134185f620..dec368bc77 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx @@ -24,7 +24,7 @@ import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/compo import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle'; import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { styled } from '@linaria/react'; import { themeCssVariables } from 'twenty-ui/theme-constants'; @@ -64,7 +64,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({ ); const { toggleNavigationSection, isNavigationSectionOpen } = useNavigationSection('Workspace'); - const coreViews = useAtomStateValue(coreViewsState); + const coreViews = useAtomStateValue(coreViewsSelector); const views = coreViews.map(convertCoreViewToView); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFieldMetadataItem.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFieldMetadataItem.test.tsx index 9f6523bb76..b6b9ba21d6 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFieldMetadataItem.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFieldMetadataItem.test.tsx @@ -244,7 +244,6 @@ describe('useFieldMetadataItem', () => { await act(async () => { const res = await result.current.deleteMetadataField({ idToDelete: fieldMetadataItem.id, - objectMetadataId, }); jestExpectSuccessfulMetadataRequestResult(res); @@ -264,7 +263,6 @@ describe('useFieldMetadataItem', () => { await act(async () => { const res = await result.current.deleteMetadataField({ idToDelete: fieldRelationMetadataItem.id, - objectMetadataId, }); jestExpectSuccessfulMetadataRequestResult(res); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFindManyObjectMetadataItems.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFindManyObjectMetadataItems.test.tsx deleted file mode 100644 index e7d35d1ee7..0000000000 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFindManyObjectMetadataItems.test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { MockedProvider } from '@apollo/client/testing/react'; -import { renderHook } from '@testing-library/react'; -import { type ReactNode } from 'react'; - -import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems'; - -import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext'; -import { - query, - responseData, -} from '@/object-metadata/hooks/__mocks__/useFindManyObjectMetadataItems'; - -const mocks = [ - { - request: { - query, - }, - result: jest.fn(() => ({ - data: { - objects: responseData, - }, - })), - }, -]; - -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); - -describe('useFindManyObjectMetadataItems', () => { - it('should activateMetadataField', async () => { - const { result } = renderHook(() => useFindManyObjectMetadataItems(), { - wrapper: Wrapper, - }); - - const { loading, error, objectMetadataItems } = result.current; - - expect(loading).toBe(true); - expect(error).toBeUndefined(); - expect(objectMetadataItems).toEqual([]); - }); -}); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useCreateOneFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useCreateOneFieldMetadataItem.ts index c62a67d404..79a93a05f9 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useCreateOneFieldMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useCreateOneFieldMetadataItem.ts @@ -5,25 +5,17 @@ import { } from '~/generated-metadata/graphql'; import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; -import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { t } from '@lingui/core/macro'; import { CrudOperationType } from 'twenty-shared/types'; export const useCreateOneFieldMetadataItem = () => { - const { refreshObjectMetadataItems } = - useRefreshObjectMetadataItems('network-only'); - const [createOneFieldMetadataItemMutation] = useMutation( CreateOneFieldMetadataItemDocument, ); - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - const { handleMetadataError } = useMetadataErrorHandler(); const { enqueueErrorSnackBar } = useSnackBar(); @@ -43,10 +35,6 @@ export const useCreateOneFieldMetadataItem = () => { }, }); - await refreshObjectMetadataItems(); - - await refreshCoreViewsByObjectMetadataId(input.objectMetadataId); - return { status: 'successful', response, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useCreateOneObjectMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useCreateOneObjectMetadataItem.ts index f7bc6c6ba4..1784e12ed4 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useCreateOneObjectMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useCreateOneObjectMetadataItem.ts @@ -5,21 +5,12 @@ import { } from '~/generated-metadata/graphql'; import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; -import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { t } from '@lingui/core/macro'; import { CrudOperationType } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; - export const useCreateOneObjectMetadataItem = () => { - const { refreshObjectMetadataItems } = - useRefreshObjectMetadataItems('network-only'); - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - const [createOneObjectMetadataItemMutation] = useMutation( CreateOneObjectMetadataItemDocument, ); @@ -41,14 +32,6 @@ export const useCreateOneObjectMetadataItem = () => { }, }); - await refreshObjectMetadataItems(); - - if (isDefined(createdObjectMetadata.data?.createOneObject?.id)) { - await refreshCoreViewsByObjectMetadataId( - createdObjectMetadata.data.createOneObject.id, - ); - } - return { status: 'successful', response: createdObjectMetadata, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useDeleteOneFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useDeleteOneFieldMetadataItem.ts index 31be187924..a9f2982d63 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useDeleteOneFieldMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useDeleteOneFieldMetadataItem.ts @@ -3,7 +3,6 @@ import { DeleteOneFieldMetadataItemDocument } from '~/generated-metadata/graphql import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; -import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { recordIndexGroupAggregateFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupAggregateFieldMetadataItemComponentState'; import { recordIndexGroupAggregateOperationComponentState } from '@/object-record/record-index/states/recordIndexGroupAggregateOperationComponentState'; @@ -11,7 +10,6 @@ import { AggregateOperations } from '@/object-record/record-table/constants/Aggr import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { t } from '@lingui/core/macro'; import { CrudOperationType } from 'twenty-shared/types'; @@ -21,11 +19,6 @@ export const useDeleteOneFieldMetadataItem = () => { DeleteOneFieldMetadataItemDocument, ); - const { refreshObjectMetadataItems } = - useRefreshObjectMetadataItems('network-only'); - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - const { handleMetadataError } = useMetadataErrorHandler(); const { enqueueErrorSnackBar } = useSnackBar(); @@ -56,10 +49,8 @@ export const useDeleteOneFieldMetadataItem = () => { const deleteOneFieldMetadataItem = async ({ idToDelete, - objectMetadataId, }: { idToDelete: string; - objectMetadataId: string; }): Promise< MetadataRequestResult< Awaited> @@ -75,9 +66,6 @@ export const useDeleteOneFieldMetadataItem = () => { // TODO: see if we can remove this lin altogether await resetRecordIndexKanbanAggregateOperation(idToDelete); - await refreshObjectMetadataItems(); - await refreshCoreViewsByObjectMetadataId(objectMetadataId); - return { status: 'successful', response, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useDeleteOneObjectMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useDeleteOneObjectMetadataItem.ts index f924f0529e..1d3a3926ae 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useDeleteOneObjectMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useDeleteOneObjectMetadataItem.ts @@ -2,10 +2,8 @@ import { useMutation } from '@apollo/client/react'; import { DeleteOneObjectMetadataItemDocument } from '~/generated-metadata/graphql'; import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; -import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { useRefreshAllCoreViews } from '@/views/hooks/useRefreshAllCoreViews'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { t } from '@lingui/core/macro'; import { CrudOperationType } from 'twenty-shared/types'; @@ -15,11 +13,6 @@ export const useDeleteOneObjectMetadataItem = () => { DeleteOneObjectMetadataItemDocument, ); - const { refreshObjectMetadataItems } = - useRefreshObjectMetadataItems('network-only'); - - const { refreshAllCoreViews } = useRefreshAllCoreViews(); - const { handleMetadataError } = useMetadataErrorHandler(); const { enqueueErrorSnackBar } = useSnackBar(); @@ -37,9 +30,6 @@ export const useDeleteOneObjectMetadataItem = () => { }, }); - await refreshObjectMetadataItems(); - await refreshAllCoreViews(); - return { status: 'successful', response, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useFindManyObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useFindManyObjectMetadataItems.ts deleted file mode 100644 index e7e4ec721d..0000000000 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useFindManyObjectMetadataItems.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useQuery } from '@apollo/client/react'; -import { useMemo } from 'react'; - -import { useSnackBarOnQueryError } from '@/apollo/hooks/useSnackBarOnQueryError'; -import { - type ObjectMetadataItemsQuery, - type ObjectMetadataItemsQueryVariables, -} from '~/generated-metadata/graphql'; -import { enrichObjectMetadataItemsWithPermissions } from '@/object-metadata/utils/enrichObjectMetadataItemsWithPermissions'; -import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; -import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries'; -import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '@/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems'; - -export const useFindManyObjectMetadataItems = ({ - skip, -}: { - skip?: boolean; -} = {}) => { - const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); - - const { data, loading, error, refetch } = useQuery< - ObjectMetadataItemsQuery, - ObjectMetadataItemsQueryVariables - >(FIND_MANY_OBJECT_METADATA_ITEMS, { - skip, - }); - - useSnackBarOnQueryError(error); - - const objectMetadataItems = useMemo(() => { - const objectMetadataItemsArray = - mapPaginatedObjectMetadataItemsToObjectMetadataItems({ - pagedObjectMetadataItems: data, - }); - - return enrichObjectMetadataItemsWithPermissions({ - objectMetadataItems: objectMetadataItemsArray, - objectPermissionsByObjectMetadataId, - }); - }, [data, objectPermissionsByObjectMetadataId]); - - return { - objectMetadataItems, - loading, - error, - refetch, - }; -}; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useLoadMockedObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useLoadMockedObjectMetadataItems.ts index d73d9268c9..31481e3749 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useLoadMockedObjectMetadataItems.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useLoadMockedObjectMetadataItems.ts @@ -1,11 +1,8 @@ -import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState'; import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; import { splitObjectMetadataItemWithRelated } from '@/metadata-store/utils/splitObjectMetadataItemWithRelated'; -import { useStore } from 'jotai'; import { useCallback } from 'react'; export const useLoadMockedObjectMetadataItems = () => { - const store = useStore(); const { updateDraft, applyChanges } = useMetadataStore(); const loadMockedObjectMetadataItems = useCallback(async () => { @@ -20,11 +17,7 @@ export const useLoadMockedObjectMetadataItems = () => { updateDraft('fieldMetadataItems', flatFields); updateDraft('indexMetadataItems', flatIndexes); applyChanges(); - - if (store.get(isAppEffectRedirectEnabledState.atom) === false) { - store.set(isAppEffectRedirectEnabledState.atom, true); - } - }, [store, updateDraft, applyChanges]); + }, [updateDraft, applyChanges]); return { loadMockedObjectMetadataItems, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useRefreshObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useRefreshObjectMetadataItems.ts deleted file mode 100644 index 866bcba87c..0000000000 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useRefreshObjectMetadataItems.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState'; -import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; -import { splitObjectMetadataItemWithRelated } from '@/metadata-store/utils/splitObjectMetadataItemWithRelated'; -import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries'; -import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '@/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems'; -import { type FetchPolicy } from '@apollo/client'; -import { useApolloClient } from '@apollo/client/react'; -import { useStore } from 'jotai'; -import { useCallback } from 'react'; -import { type ObjectMetadataItemsQuery } from '~/generated-metadata/graphql'; - -export const useRefreshObjectMetadataItems = ( - fetchPolicy: FetchPolicy = 'network-only', -) => { - const store = useStore(); - const client = useApolloClient(); - const { updateDraft, applyChanges } = useMetadataStore(); - - const refreshObjectMetadataItems = useCallback(async () => { - const objectMetadataItemsResult = - await client.query({ - query: FIND_MANY_OBJECT_METADATA_ITEMS, - variables: {}, - fetchPolicy, - }); - - const compositeObjects = - mapPaginatedObjectMetadataItemsToObjectMetadataItems({ - pagedObjectMetadataItems: objectMetadataItemsResult.data, - }); - - const { flatObjects, flatFields, flatIndexes } = - splitObjectMetadataItemWithRelated(compositeObjects); - - updateDraft('objectMetadataItems', flatObjects); - updateDraft('fieldMetadataItems', flatFields); - updateDraft('indexMetadataItems', flatIndexes); - applyChanges(); - - if (store.get(isAppEffectRedirectEnabledState.atom) === false) { - store.set(isAppEffectRedirectEnabledState.atom, true); - } - }, [client, fetchPolicy, store, updateDraft, applyChanges]); - - return { - refreshObjectMetadataItems, - }; -}; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useUpdateOneFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useUpdateOneFieldMetadataItem.ts index d1315227a6..695eb898ef 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useUpdateOneFieldMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useUpdateOneFieldMetadataItem.ts @@ -5,11 +5,9 @@ import { } from '~/generated-metadata/graphql'; import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; -import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; import { lastFieldMetadataItemUpdateState } from '@/object-metadata/states/lastFieldMetadataItemUpdateState'; import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { t } from '@lingui/core/macro'; @@ -17,12 +15,6 @@ import { CrudOperationType } from 'twenty-shared/types'; import { v4 as uuidv4 } from 'uuid'; export const useUpdateOneFieldMetadataItem = () => { - const { refreshObjectMetadataItems } = - useRefreshObjectMetadataItems('network-only'); - - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - const [updateOneFieldMetadataItemMutation] = useMutation( UpdateOneFieldMetadataItemDocument, ); @@ -66,9 +58,6 @@ export const useUpdateOneFieldMetadataItem = () => { }, }); - await refreshObjectMetadataItems(); - await refreshCoreViewsByObjectMetadataId(objectMetadataId); - setLastFieldMetadataItemUpdate({ fieldMetadataItemId: fieldMetadataIdToUpdate, objectMetadataId, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useUpdateOneObjectMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useUpdateOneObjectMetadataItem.ts index a47d3cf40b..4cd8c26f97 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useUpdateOneObjectMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useUpdateOneObjectMetadataItem.ts @@ -5,10 +5,8 @@ import { } from '~/generated-metadata/graphql'; import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; -import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { t } from '@lingui/core/macro'; import { CrudOperationType } from 'twenty-shared/types'; @@ -19,12 +17,6 @@ export const useUpdateOneObjectMetadataItem = () => { UpdateOneObjectMetadataItemDocument, ); - const { refreshObjectMetadataItems } = - useRefreshObjectMetadataItems('network-only'); - - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - const { handleMetadataError } = useMetadataErrorHandler(); const { enqueueErrorSnackBar } = useSnackBar(); @@ -47,9 +39,6 @@ export const useUpdateOneObjectMetadataItem = () => { }, }); - await refreshObjectMetadataItems(); - await refreshCoreViewsByObjectMetadataId(idToUpdate); - return { status: 'successful', response, diff --git a/packages/twenty-front/src/modules/object-metadata/states/activeObjectNameSingularsSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/activeObjectNameSingularsSelector.ts index 16e6944cd4..45f1cd2341 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/activeObjectNameSingularsSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/activeObjectNameSingularsSelector.ts @@ -1,10 +1,10 @@ -import { objectMetadataItemsSelector } from '@/metadata-store/states/objectMetadataItemsSelector'; +import { flatObjectMetadataItemsSelector } from '@/object-metadata/states/flatObjectMetadataItemsSelector'; import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; export const activeObjectNameSingularsSelector = createAtomSelector({ key: 'activeObjectNameSingularsSelector', get: ({ get }) => { - const flatObjects = get(objectMetadataItemsSelector); + const flatObjects = get(flatObjectMetadataItemsSelector); return flatObjects .filter((flatObject) => flatObject.isActive) diff --git a/packages/twenty-front/src/modules/metadata-store/states/objectMetadataItemsSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/flatObjectMetadataItemsSelector.ts similarity index 80% rename from packages/twenty-front/src/modules/metadata-store/states/objectMetadataItemsSelector.ts rename to packages/twenty-front/src/modules/object-metadata/states/flatObjectMetadataItemsSelector.ts index 218ce280e3..9e91d8afcc 100644 --- a/packages/twenty-front/src/modules/metadata-store/states/objectMetadataItemsSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/flatObjectMetadataItemsSelector.ts @@ -2,10 +2,10 @@ import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem'; import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; -export const objectMetadataItemsSelector = createAtomSelector< +export const flatObjectMetadataItemsSelector = createAtomSelector< FlatObjectMetadataItem[] >({ - key: 'objectMetadataItemsSelector', + key: 'flatObjectMetadataItemsSelector', get: ({ get }) => { const storeItem = get(metadataStoreState, 'objectMetadataItems'); diff --git a/packages/twenty-front/src/modules/object-metadata/states/isSystemObjectByNameSingularFamilySelector.ts b/packages/twenty-front/src/modules/object-metadata/states/isSystemObjectByNameSingularFamilySelector.ts index b3e8f11331..226c4f07bc 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/isSystemObjectByNameSingularFamilySelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/isSystemObjectByNameSingularFamilySelector.ts @@ -1,4 +1,4 @@ -import { objectMetadataItemsSelector } from '@/metadata-store/states/objectMetadataItemsSelector'; +import { flatObjectMetadataItemsSelector } from '@/object-metadata/states/flatObjectMetadataItemsSelector'; import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; export const isSystemObjectByNameSingularFamilySelector = @@ -7,7 +7,7 @@ export const isSystemObjectByNameSingularFamilySelector = get: (nameSingular: string) => ({ get }) => { - const flatObjects = get(objectMetadataItemsSelector); + const flatObjects = get(flatObjectMetadataItemsSelector); return ( flatObjects.find( diff --git a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsWithFieldsSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsWithFieldsSelector.ts index 0e3c7c8720..1c70886dea 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsWithFieldsSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsWithFieldsSelector.ts @@ -1,7 +1,7 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { fieldMetadataItemsSelector } from '@/metadata-store/states/fieldMetadataItemsSelector'; import { indexMetadataItemsSelector } from '@/metadata-store/states/indexMetadataItemsSelector'; -import { objectMetadataItemsSelector } from '@/metadata-store/states/objectMetadataItemsSelector'; +import { flatObjectMetadataItemsSelector } from '@/object-metadata/states/flatObjectMetadataItemsSelector'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { getNonReadableFieldMetadataIdsFromObjectPermissions } from '@/object-metadata/utils/getNonReadableFieldMetadataIdsFromObjectPermissions'; import { getNonUpdatableFieldMetadataIdsFromObjectPermissions } from '@/object-metadata/utils/getNonUpdatableFieldMetadataIdsFromObjectPermissions'; @@ -15,7 +15,7 @@ export const objectMetadataItemsWithFieldsSelector = createAtomSelector< >({ key: 'objectMetadataItemsWithFieldsSelector', get: ({ get }) => { - const flatObjects = get(objectMetadataItemsSelector); + const flatObjects = get(flatObjectMetadataItemsSelector); const allFlatFields = get(fieldMetadataItemsSelector); const allFlatIndexes = get(indexMetadataItemsSelector); const currentUserWorkspace = get(currentUserWorkspaceState); diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu.ts b/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu.ts index 21948911f0..0f071c2b5a 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu.ts +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu.ts @@ -5,11 +5,10 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/useLoadRecordIndexStates'; import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState'; import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { type GraphQLView } from '@/views/types/GraphQLView'; import { ViewType, viewTypeIconMapping } from '@/views/types/ViewType'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; -import { convertViewTypeToCore } from '@/views/utils/convertViewTypeToCore'; import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useGetAvailableFieldsForCalendar'; import { useGetAvailableFieldsToGroupRecordsBy } from '@/views/view-picker/hooks/useGetAvailableFieldsToGroupRecordsBy'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; @@ -39,7 +38,7 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => { }), ); - const existingCoreViews = store.get(coreViewsState.atom); + const existingCoreViews = store.get(coreViewsSelector.atom); if (!isDefined(currentViewId)) { throw new Error('No view id found'); @@ -74,16 +73,6 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => { } setRecordIndexViewType(viewType); - store.set(coreViewsState.atom, [ - ...existingCoreViews.filter( - (coreView) => coreView.id !== currentView.id, - ), - { - ...currentCoreView, - type: convertViewTypeToCore(viewType), - mainGroupByFieldMetadataId, - }, - ]); await updateCurrentView(updateCurrentViewParams); return; } @@ -95,16 +84,6 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => { updateCurrentViewParams.mainGroupByFieldMetadataId = null; await updateCurrentView(updateCurrentViewParams); setRecordIndexViewType(viewType); - store.set(coreViewsState.atom, [ - ...existingCoreViews.filter( - (coreView) => coreView.id !== currentView.id, - ), - { - ...currentCoreView, - mainGroupByFieldMetadataId: null, - type: convertViewTypeToCore(viewType), - }, - ]); return; } case ViewType.Calendar: { @@ -115,18 +94,6 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => { const calendarFieldMetadataId = availableFieldsForCalendar[0].id; setRecordIndexViewType(viewType); - store.set(coreViewsState.atom, [ - ...existingCoreViews.filter( - (coreView) => coreView.id !== currentView.id, - ), - { - ...currentCoreView, - mainGroupByFieldMetadataId: null, - type: convertViewTypeToCore(viewType), - calendarLayout: ViewCalendarLayout.MONTH, - calendarFieldMetadataId, - }, - ]); loadRecordIndexStates( { diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/month/components/__stories__/RecordCalendarMonth.stories.tsx b/packages/twenty-front/src/modules/object-record/record-calendar/month/components/__stories__/RecordCalendarMonth.stories.tsx index 17c2620b68..d8b0fd8b5a 100644 --- a/packages/twenty-front/src/modules/object-record/record-calendar/month/components/__stories__/RecordCalendarMonth.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-calendar/month/components/__stories__/RecordCalendarMonth.stories.tsx @@ -4,12 +4,12 @@ import { useEffect, useMemo, useState } from 'react'; import { type TaskGroups } from '@/activities/tasks/components/TaskGroups'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; -import { CoreObjectNameSingular } from 'twenty-shared/types'; import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext'; import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; +import { CoreObjectNameSingular } from 'twenty-shared/types'; import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper'; @@ -18,9 +18,8 @@ import { RecordCalendarMonth } from '@/object-record/record-calendar/month/compo import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState'; import { type RecordField } from '@/object-record/record-field/types/RecordField'; import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { ViewBarFilterDropdownIds } from '@/views/constants/ViewBarFilterDropdownIds'; -import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; -import { coreViewsState } from '@/views/states/coreViewState'; import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing'; import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; @@ -28,6 +27,7 @@ import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadat import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { mockedCoreViews } from '~/testing/mock-data/generated/metadata/views/mock-views-data'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; +import { setTestCoreViewsInMetadataStore } from '~/testing/utils/setTestCoreViewsInMetadataStore'; const meta: Meta = { title: 'Modules/ObjectRecord/RecordCalendar/Month', @@ -44,8 +44,6 @@ const meta: Meta = { instanceId, ); - const setCoreViews = useSetAtomState(coreViewsState); - const mockCoreView = mockedCoreViews.find( (v) => v.name === 'All Companies', )!; @@ -73,12 +71,11 @@ const meta: Meta = { const [isLoaded, setIsLoaded] = useState(false); useEffect(() => { - setCoreViews([mockCoreView]); + setTestCoreViewsInMetadataStore(jotaiStore, [mockCoreView]); setContextStoreCurrentViewId(mockCoreView.id); setCurrentRecordFields(columns); setIsLoaded(true); }, [ - setCoreViews, setContextStoreCurrentViewId, setCurrentRecordFields, mockCoreView, diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleRecordGroupField.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleRecordGroupField.ts index c10f35c6d7..e487b95bfa 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleRecordGroupField.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleRecordGroupField.ts @@ -6,7 +6,6 @@ import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/use import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; import { usePerformViewAPIUpdate } from '@/views/hooks/internal/usePerformViewAPIUpdate'; import { useGetViewFromState } from '@/views/hooks/useGetViewFromState'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { type ViewGroup } from '@/views/types/ViewGroup'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { useStore } from 'jotai'; @@ -29,8 +28,6 @@ export const useHandleRecordGroupField = () => { const { performViewAPIUpdate } = usePerformViewAPIUpdate(); const { loadRecordIndexStates } = useLoadRecordIndexStates(); - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); const store = useStore(); @@ -121,8 +118,6 @@ export const useHandleRecordGroupField = () => { viewGroups: newViewGroupsList, objectMetadataItem, }); - - await refreshCoreViewsByObjectMetadataId(objectMetadataItem.id); }, [ currentViewIdCallbackState, @@ -130,7 +125,6 @@ export const useHandleRecordGroupField = () => { performViewAPIUpdate, setRecordGroupsFromViewGroups, objectMetadataItem, - refreshCoreViewsByObjectMetadataId, loadRecordIndexStates, store, ], diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation.tsx index 4e701b394f..d7ad0fd1f8 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation.tsx @@ -1,4 +1,3 @@ -import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext'; import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState'; import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; @@ -6,7 +5,6 @@ import { convertExtendedAggregateOperationToAggregateOperation } from '@/object- import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; import { usePerformViewFieldAPIPersist } from '@/views/hooks/internal/usePerformViewFieldAPIPersist'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { useContext } from 'react'; import { isDefined } from 'twenty-shared/utils'; @@ -20,10 +18,6 @@ export const useViewFieldAggregateOperation = () => { (viewField) => viewField.fieldMetadataId === fieldMetadataId, ); - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - - const { objectMetadataItem } = useRecordIndexContextOrThrow(); const { performViewFieldAPIUpdate } = usePerformViewFieldAPIPersist(); const updateViewFieldAggregateOperation = async ( aggregateOperation: ExtendedAggregateOperations | null, @@ -48,8 +42,6 @@ export const useViewFieldAggregateOperation = () => { }, }, ]); - - refreshCoreViewsByObjectMetadataId(objectMetadataItem.id); }; const viewFieldAggregateOperation = useAtomFamilyStateValue( diff --git a/packages/twenty-front/src/modules/onboarding/hooks/useOnboardingStatus.ts b/packages/twenty-front/src/modules/onboarding/hooks/useOnboardingStatus.ts index 186bfd2875..e3a48a6868 100644 --- a/packages/twenty-front/src/modules/onboarding/hooks/useOnboardingStatus.ts +++ b/packages/twenty-front/src/modules/onboarding/hooks/useOnboardingStatus.ts @@ -1,10 +1,10 @@ -import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair'; import { currentUserState } from '@/auth/states/currentUserState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { type OnboardingStatus } from '~/generated-metadata/graphql'; export const useOnboardingStatus = (): OnboardingStatus | null | undefined => { const currentUser = useAtomStateValue(currentUserState); - const isLoggedIn = useIsLogged(); - return isLoggedIn ? currentUser?.onboardingStatus : undefined; + const hasAccessTokenPair = useHasAccessTokenPair(); + return hasAccessTokenPair ? currentUser?.onboardingStatus : undefined; }; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useApplyPageLayouts.ts b/packages/twenty-front/src/modules/page-layout/hooks/useApplyPageLayouts.ts deleted file mode 100644 index 37c730eda7..0000000000 --- a/packages/twenty-front/src/modules/page-layout/hooks/useApplyPageLayouts.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; -import { splitPageLayoutWithRelated } from '@/metadata-store/utils/splitPageLayoutWithRelated'; -import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; -import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState'; -import { recordPageLayoutsState } from '@/page-layout/states/recordPageLayoutsState'; -import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts'; -import { transformPageLayout } from '@/page-layout/utils/transformPageLayout'; -import { useStore } from 'jotai'; -import { useCallback } from 'react'; -import { type PageLayout as PageLayoutGenerated } from '~/generated-metadata/graphql'; - -export const useApplyPageLayouts = () => { - const store = useStore(); - const { updateDraft, applyChanges } = useMetadataStore(); - - const applyPageLayouts = useCallback( - (pageLayouts: PageLayoutGenerated[]) => { - const transformedPageLayouts = pageLayouts.map(transformPageLayout); - - for (const pageLayout of transformedPageLayouts) { - store.set( - pageLayoutPersistedComponentState.atomFamily({ - instanceId: pageLayout.id, - }), - pageLayout, - ); - store.set( - pageLayoutCurrentLayoutsComponentState.atomFamily({ - instanceId: pageLayout.id, - }), - convertPageLayoutToTabLayouts(pageLayout), - ); - } - - store.set(recordPageLayoutsState.atom, transformedPageLayouts); - - const { flatPageLayouts, flatPageLayoutTabs, flatPageLayoutWidgets } = - splitPageLayoutWithRelated(transformedPageLayouts); - - updateDraft('pageLayouts', flatPageLayouts); - updateDraft('pageLayoutTabs', flatPageLayoutTabs); - updateDraft('pageLayoutWidgets', flatPageLayoutWidgets); - applyChanges(); - }, - [store, updateDraft, applyChanges], - ); - - return { - applyPageLayouts, - }; -}; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useRefreshPageLayouts.ts b/packages/twenty-front/src/modules/page-layout/hooks/useRefreshPageLayouts.ts deleted file mode 100644 index c71d64cb86..0000000000 --- a/packages/twenty-front/src/modules/page-layout/hooks/useRefreshPageLayouts.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useApplyPageLayouts } from '@/page-layout/hooks/useApplyPageLayouts'; -import { useCallback } from 'react'; -import { isDefined } from 'twenty-shared/utils'; -import { useApolloClient } from '@apollo/client/react'; -import { FindAllPageLayoutsDocument } from '~/generated-metadata/graphql'; - -export const useRefreshPageLayouts = () => { - const client = useApolloClient(); - - const { applyPageLayouts } = useApplyPageLayouts(); - - const refreshPageLayouts = useCallback(async () => { - const result = await client.query({ - query: FindAllPageLayoutsDocument, - fetchPolicy: 'network-only', - }); - - if (!isDefined(result.data?.getPageLayouts)) { - return; - } - - applyPageLayouts(result.data.getPageLayouts); - }, [client, applyPageLayouts]); - - return { - refreshPageLayouts, - }; -}; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useSaveFieldsWidgetGroups.ts b/packages/twenty-front/src/modules/page-layout/hooks/useSaveFieldsWidgetGroups.ts index 3a7b68a4f7..13a5b7c1d8 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useSaveFieldsWidgetGroups.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useSaveFieldsWidgetGroups.ts @@ -6,7 +6,6 @@ import { fieldsWidgetGroupsPersistedComponentState } from '@/page-layout/states/ import { fieldsWidgetUngroupedFieldsDraftComponentState } from '@/page-layout/states/fieldsWidgetUngroupedFieldsDraftComponentState'; import { fieldsWidgetUngroupedFieldsPersistedComponentState } from '@/page-layout/states/fieldsWidgetUngroupedFieldsPersistedComponentState'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; -import { useRefreshAllCoreViews } from '@/views/hooks/useRefreshAllCoreViews'; import { useMutation } from '@apollo/client/react'; import { useStore } from 'jotai'; import { useCallback } from 'react'; @@ -82,8 +81,6 @@ export const useSaveFieldsWidgetGroups = ({ { input: UpsertFieldsWidgetInput } >(UPSERT_FIELDS_WIDGET); - const { refreshAllCoreViews } = useRefreshAllCoreViews(); - const store = useStore(); const saveFieldsWidgetGroups = useCallback(async () => { @@ -165,8 +162,6 @@ export const useSaveFieldsWidgetGroups = ({ ); store.set(fieldsWidgetEditorModePersistedState, allEditorModes); - await refreshAllCoreViews(); - return { status: 'successful' as const }; }, [ fieldsWidgetGroupsDraftState, @@ -176,7 +171,6 @@ export const useSaveFieldsWidgetGroups = ({ fieldsWidgetEditorModeDraftState, fieldsWidgetEditorModePersistedState, upsertFieldsWidgetMutation, - refreshAllCoreViews, store, ]); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/DashboardWidgetPlaceholder.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/DashboardWidgetPlaceholder.stories.tsx index a05cc65fa3..af03c3be00 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/DashboardWidgetPlaceholder.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/DashboardWidgetPlaceholder.stories.tsx @@ -1,4 +1,4 @@ -import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState'; +import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState'; import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext'; @@ -37,7 +37,7 @@ const meta: Meta = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); jotaiStore.set( pageLayoutPersistedComponentState.atomFamily({ instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx index 004f2064d3..3415dedaf9 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx @@ -8,7 +8,7 @@ import { CatalogDecorator, type CatalogStory } from 'twenty-ui/testing'; import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext'; -import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState'; +import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState'; import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { CoreObjectNameSingular } from 'twenty-shared/types'; @@ -281,7 +281,7 @@ export const WithNumberChart: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget(widget); jotaiStore.set( pageLayoutPersistedComponentState.atomFamily({ @@ -368,7 +368,7 @@ export const WithGaugeChart: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget(widget); jotaiStore.set( pageLayoutPersistedComponentState.atomFamily({ @@ -459,7 +459,7 @@ export const WithBarChart: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget(widget); jotaiStore.set( pageLayoutPersistedComponentState.atomFamily({ @@ -553,7 +553,7 @@ export const SmallWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget(widget); jotaiStore.set( pageLayoutPersistedComponentState.atomFamily({ @@ -651,7 +651,7 @@ export const MediumWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget(widget); jotaiStore.set( pageLayoutPersistedComponentState.atomFamily({ @@ -749,7 +749,7 @@ export const LargeWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget(widget); jotaiStore.set( pageLayoutPersistedComponentState.atomFamily({ @@ -843,7 +843,7 @@ export const WideWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget(widget); jotaiStore.set( pageLayoutPersistedComponentState.atomFamily({ @@ -941,7 +941,7 @@ export const TallWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget(widget); jotaiStore.set( pageLayoutPersistedComponentState.atomFamily({ @@ -1035,7 +1035,7 @@ export const WithManyToOneRelationFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, PageLayoutType.RECORD_PAGE, @@ -1154,7 +1154,7 @@ export const WithOneToManyRelationFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, PageLayoutType.RECORD_PAGE, @@ -1265,7 +1265,7 @@ export const OneToManyRelationFieldWidgetWithSeeAllButton: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, PageLayoutType.RECORD_PAGE, @@ -1403,7 +1403,7 @@ export const OnMobile: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, PageLayoutType.RECORD_PAGE, @@ -1501,7 +1501,7 @@ export const InSidePanel: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, PageLayoutType.RECORD_PAGE, @@ -1660,7 +1660,7 @@ export const Catalog: CatalogStory = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); if (state === 'hover') { jotaiStore.set( diff --git a/packages/twenty-front/src/modules/page-layout/widgets/field/components/__stories__/FieldWidget.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/field/components/__stories__/FieldWidget.stories.tsx index ca016873ee..2f7f109ffa 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/field/components/__stories__/FieldWidget.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/field/components/__stories__/FieldWidget.stories.tsx @@ -5,7 +5,7 @@ import { MemoryRouter } from 'react-router-dom'; import { expect, userEvent, waitFor, within } from 'storybook/test'; import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext'; -import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState'; +import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState'; import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { CoreObjectNameSingular } from 'twenty-shared/types'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; @@ -360,7 +360,7 @@ export const TextFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -454,7 +454,7 @@ export const AddressFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -551,7 +551,7 @@ export const NumberFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -645,7 +645,7 @@ export const LinkFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -739,7 +739,7 @@ export const ManyToOneRelationFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -843,7 +843,7 @@ export const OneToManyRelationFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -939,7 +939,7 @@ export const BooleanFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -1032,7 +1032,7 @@ export const CurrencyFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -1125,7 +1125,7 @@ export const EmailsFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, personObjectMetadataItem.id, @@ -1219,7 +1219,7 @@ export const PhonesFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, personObjectMetadataItem.id, @@ -1313,7 +1313,7 @@ export const SelectFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, opportunityObjectMetadataItem.id, @@ -1408,7 +1408,7 @@ export const MultiSelectFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -1506,7 +1506,7 @@ export const TimelineActivityRelationFieldWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, timelineActivityObjectMetadataItem.id, @@ -1605,7 +1605,7 @@ export const ManyToOneRelationCardWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -1717,7 +1717,7 @@ export const OneToManyRelationCardWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -1812,7 +1812,7 @@ export const TimelineActivityRelationCardWidget: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, timelineActivityObjectMetadataItem.id, @@ -1973,7 +1973,7 @@ export const OneToManyRelationCardWidgetWithProgressiveLoading: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/fields/components/__stories__/FieldsWidget.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/fields/components/__stories__/FieldsWidget.stories.tsx index 0221c758b2..ac797e5ce5 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/fields/components/__stories__/FieldsWidget.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/fields/components/__stories__/FieldsWidget.stories.tsx @@ -4,10 +4,8 @@ import { type Meta, type StoryObj } from '@storybook/react-vite'; import { MemoryRouter } from 'react-router-dom'; import { expect, waitFor, within } from 'storybook/test'; -import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState'; +import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState'; import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext'; -import { CoreObjectNameSingular } from 'twenty-shared/types'; -import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext'; @@ -23,8 +21,8 @@ import { FieldsWidget } from '@/page-layout/widgets/fields/components/FieldsWidg import { WidgetComponentInstanceContext } from '@/page-layout/widgets/states/contexts/WidgetComponentInstanceContext'; import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; -import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; +import { CoreObjectNameSingular } from 'twenty-shared/types'; import { ComponentDecorator } from 'twenty-ui/testing'; import { ViewOpenRecordIn as CoreViewOpenRecordIn, @@ -41,6 +39,8 @@ import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMeta import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; import { getMockFieldMetadataItemOrThrow } from '~/testing/utils/getMockFieldMetadataItemOrThrow'; import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow'; +import { setTestCoreViewsInMetadataStore } from '~/testing/utils/setTestCoreViewsInMetadataStore'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; const companyObjectMetadataItem = getMockObjectMetadataItemOrThrow( CoreObjectNameSingular.Company, @@ -206,6 +206,39 @@ const createCoreView = ( ...overrides, }); +const createViewField = ( + id: string, + fieldMetadataId: string, + position: number, + viewFieldGroupId?: string, +) => ({ + id, + fieldMetadataId, + position, + isVisible: true, + size: 200, + aggregateOperation: null, + isOverridden: false, + viewId: FIELDS_VIEW_ID, + ...(viewFieldGroupId !== undefined && { viewFieldGroupId }), +}); + +const createViewFieldGroup = ( + id: string, + name: string, + position: number, + viewFields: ReturnType[], + isVisible = true, +) => ({ + id, + name, + position, + isVisible, + isOverridden: false, + viewId: FIELDS_VIEW_ID, + viewFields, +}); + const meta: Meta = { title: 'Modules/PageLayout/Widgets/FieldsWidget', component: FieldsWidget, @@ -229,88 +262,38 @@ type Story = StoryObj; export const WithViewFieldGroups: Story = { render: () => { + const contactInfoFields = [ + createViewField('vf-name', nameField.id, 0, 'group-contact-info'), + createViewField('vf-address', addressField.id, 1, 'group-contact-info'), + createViewField('vf-linkedin', linkedinField.id, 2, 'group-contact-info'), + ]; + + const businessFields = [ + createViewField('vf-employees', employeesField.id, 0, 'group-business'), + createViewField( + 'vf-arr', + annualRecurringRevenueField.id, + 1, + 'group-business', + ), + createViewField( + 'vf-icp', + idealCustomerProfileField.id, + 2, + 'group-business', + ), + ]; + const coreView = createCoreView({ + viewFields: [...contactInfoFields, ...businessFields], viewFieldGroups: [ - { - id: 'group-contact-info', - name: 'Contact Info', - position: 0, - isVisible: true, - isOverridden: false, - viewId: FIELDS_VIEW_ID, - viewFields: [ - { - id: 'vf-name', - fieldMetadataId: nameField.id, - position: 0, - isVisible: true, - size: 200, - aggregateOperation: null, - isOverridden: false, - viewId: FIELDS_VIEW_ID, - }, - { - id: 'vf-address', - fieldMetadataId: addressField.id, - position: 1, - isVisible: true, - size: 200, - aggregateOperation: null, - isOverridden: false, - viewId: FIELDS_VIEW_ID, - }, - { - id: 'vf-linkedin', - fieldMetadataId: linkedinField.id, - position: 2, - isVisible: true, - size: 200, - aggregateOperation: null, - isOverridden: false, - viewId: FIELDS_VIEW_ID, - }, - ], - }, - { - id: 'group-business', - name: 'Business', - position: 1, - isVisible: true, - isOverridden: false, - viewId: FIELDS_VIEW_ID, - viewFields: [ - { - id: 'vf-employees', - fieldMetadataId: employeesField.id, - position: 0, - isVisible: true, - size: 200, - aggregateOperation: null, - isOverridden: false, - viewId: FIELDS_VIEW_ID, - }, - { - id: 'vf-arr', - fieldMetadataId: annualRecurringRevenueField.id, - position: 1, - isVisible: true, - size: 200, - aggregateOperation: null, - isOverridden: false, - viewId: FIELDS_VIEW_ID, - }, - { - id: 'vf-icp', - fieldMetadataId: idealCustomerProfileField.id, - position: 2, - isVisible: true, - size: 200, - aggregateOperation: null, - isOverridden: false, - viewId: FIELDS_VIEW_ID, - }, - ], - }, + createViewFieldGroup( + 'group-contact-info', + 'Contact Info', + 0, + contactInfoFields, + ), + createViewFieldGroup('group-business', 'Business', 1, businessFields), ], }); @@ -325,8 +308,8 @@ export const WithViewFieldGroups: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); - jotaiStore.set(coreViewsState.atom, [coreView]); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); + setTestCoreViewsInMetadataStore(jotaiStore, [coreView]); jotaiStore.set( pageLayoutPersistedComponentState.atomFamily({ instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID, @@ -394,36 +377,9 @@ export const WithInlineViewFields: Story = { render: () => { const coreView = createCoreView({ viewFields: [ - { - id: 'vf-name', - fieldMetadataId: nameField.id, - position: 0, - isVisible: true, - size: 200, - aggregateOperation: null, - isOverridden: false, - viewId: FIELDS_VIEW_ID, - }, - { - id: 'vf-employees', - fieldMetadataId: employeesField.id, - position: 1, - isVisible: true, - size: 200, - aggregateOperation: null, - isOverridden: false, - viewId: FIELDS_VIEW_ID, - }, - { - id: 'vf-address', - fieldMetadataId: addressField.id, - position: 2, - isVisible: true, - size: 200, - aggregateOperation: null, - isOverridden: false, - viewId: FIELDS_VIEW_ID, - }, + createViewField('vf-name', nameField.id, 0), + createViewField('vf-employees', employeesField.id, 1), + createViewField('vf-address', addressField.id, 2), ], }); @@ -438,8 +394,8 @@ export const WithInlineViewFields: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); - jotaiStore.set(coreViewsState.atom, [coreView]); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); + setTestCoreViewsInMetadataStore(jotaiStore, [coreView]); jotaiStore.set( pageLayoutPersistedComponentState.atomFamily({ instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID, @@ -507,15 +463,7 @@ export const Empty: Story = { render: () => { const coreView = createCoreView({ viewFieldGroups: [ - { - id: 'group-empty', - name: 'Empty Group', - position: 0, - isVisible: false, - isOverridden: false, - viewId: FIELDS_VIEW_ID, - viewFields: [], - }, + createViewFieldGroup('group-empty', 'Empty Group', 0, [], false), ], }); @@ -530,8 +478,8 @@ export const Empty: Story = { jotaiStore, generatedMockObjectMetadataItems, ); - jotaiStore.set(isAppMetadataReadyState.atom, true); - jotaiStore.set(coreViewsState.atom, [coreView]); + jotaiStore.set(isMinimalMetadataReadyState.atom, true); + setTestCoreViewsInMetadataStore(jotaiStore, [coreView]); jotaiStore.set( pageLayoutPersistedComponentState.atomFamily({ instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID, diff --git a/packages/twenty-front/src/modules/settings/components/SettingsProtectedRouteWrapper.tsx b/packages/twenty-front/src/modules/settings/components/SettingsProtectedRouteWrapper.tsx index ae9500059e..ea2ab0160d 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsProtectedRouteWrapper.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsProtectedRouteWrapper.tsx @@ -1,4 +1,4 @@ -import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair'; import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { type ReactNode } from 'react'; @@ -21,13 +21,13 @@ export const SettingsProtectedRouteWrapper = ({ settingsPermission, requiredFeatureFlag, }: SettingsProtectedRouteWrapperProps) => { - const isLoggedIn = useIsLogged(); + const hasAccessTokenPair = useHasAccessTokenPair(); const hasPermission = useHasPermissionFlag(settingsPermission); const requiredFeatureFlagEnabled = useIsFeatureEnabled( requiredFeatureFlag || null, ); - if (!isLoggedIn) { + if (!hasAccessTokenPair) { return null; } diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx index 991110f7da..30f4f00162 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx @@ -262,7 +262,6 @@ export const SettingsObjectFieldItemTableRow = ({ onDelete={() => deleteOneFieldMetadataItem({ idToDelete: fieldMetadataItem.id, - objectMetadataId: objectMetadataItem.id, }) } /> diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationItemTableRow.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationItemTableRow.tsx index f99661492b..f253fc10a9 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationItemTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectRelationItemTableRow.tsx @@ -242,7 +242,6 @@ export const SettingsObjectRelationItemTableRow = ({ onDelete={() => deleteOneFieldMetadataItem({ idToDelete: fieldMetadataItem.id, - objectMetadataId: objectMetadataItem.id, }) } /> diff --git a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx index 6cdad08a61..1d1fd53544 100644 --- a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx @@ -1,4 +1,3 @@ -import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsRoleAssignment } from '@/settings/roles/role-assignment/components/SettingsRoleAssignment'; @@ -35,7 +34,6 @@ type SettingsRoleProps = { }; export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { - const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems(); const activeTabId = useAtomComponentStateValue( activeTabIdComponentState, SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID + '-' + roleId, @@ -126,7 +124,6 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { try { await saveDraftRoleToDB(); await loadCurrentUser(); - await refreshObjectMetadataItems(); } finally { setIsSaving(false); } diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx index 011cca8d37..85350c0da4 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx +++ b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx @@ -93,6 +93,7 @@ export const SignInBackgroundMockContainer = () => { } + isReadOnly /> { const store = useStore(); - const isLoggedIn = useIsLogged(); + const hasAccessTokenPair = useHasAccessTokenPair(); const [sseClient, setSseClient] = useAtomState(sseClientState); const tokenPair = useAtomStateValue(tokenPairState); @@ -32,7 +32,7 @@ export const SSEClientEffect = () => { useHandleSseClientConnectionRetry(); useEffect(() => { - if (isLoggedIn && !isDefined(sseClient) && isDefined(tokenPair)) { + if (hasAccessTokenPair && !isDefined(sseClient) && isDefined(tokenPair)) { const token = tokenPair?.accessOrWorkspaceAgnosticToken?.token; const newSseClient = createClient({ @@ -52,7 +52,7 @@ export const SSEClientEffect = () => { } }, [ handleSSEClientConnected, - isLoggedIn, + hasAccessTokenPair, setSseClient, sseClient, tokenPair, diff --git a/packages/twenty-front/src/modules/sse-db-event/components/SSEEventStreamEffect.tsx b/packages/twenty-front/src/modules/sse-db-event/components/SSEEventStreamEffect.tsx index dcb01b972c..cae51e3b4a 100644 --- a/packages/twenty-front/src/modules/sse-db-event/components/SSEEventStreamEffect.tsx +++ b/packages/twenty-front/src/modules/sse-db-event/components/SSEEventStreamEffect.tsx @@ -1,4 +1,4 @@ -import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair'; import { currentUserState } from '@/auth/states/currentUserState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useTriggerEventStreamCreation } from '@/sse-db-event/hooks/useTriggerEventStreamCreation'; @@ -27,7 +27,7 @@ export const SSEEventStreamEffect = () => { isDestroyingEventStreamState, ); - const isLoggedIn = useIsLogged(); + const hasAccessTokenPair = useHasAccessTokenPair(); const currentUser = useAtomStateValue(currentUserState); const { triggerEventStreamCreation } = useTriggerEventStreamCreation(); @@ -39,7 +39,7 @@ export const SSEEventStreamEffect = () => { const willCreateEventStream = isSseClientAvailable && - isLoggedIn && + hasAccessTokenPair && isDefined(currentUser) && currentUser.onboardingStatus === OnboardingStatus.COMPLETED && !shouldDestroyEventStream && @@ -59,7 +59,7 @@ export const SSEEventStreamEffect = () => { }, [ isCreatingSseEventStream, triggerEventStreamCreation, - isLoggedIn, + hasAccessTokenPair, currentUser, isDestroyingEventStream, triggerEventStreamDestroy, diff --git a/packages/twenty-front/src/modules/sse-db-event/components/SSEProvider.tsx b/packages/twenty-front/src/modules/sse-db-event/components/SSEProvider.tsx index 086945a7b6..2f352ff729 100644 --- a/packages/twenty-front/src/modules/sse-db-event/components/SSEProvider.tsx +++ b/packages/twenty-front/src/modules/sse-db-event/components/SSEProvider.tsx @@ -1,14 +1,4 @@ -import { FieldMetadataSSEEffect } from '@/metadata-store/effect-components/FieldMetadataSSEEffect'; -import { NavigationMenuItemSSEEffect } from '@/metadata-store/effect-components/NavigationMenuItemSSEEffect'; -import { ObjectMetadataItemSSEEffect } from '@/metadata-store/effect-components/ObjectMetadataItemSSEEffect'; -import { PageLayoutSSEEffect } from '@/metadata-store/effect-components/PageLayoutSSEEffect'; -import { PageLayoutTabSSEEffect } from '@/metadata-store/effect-components/PageLayoutTabSSEEffect'; -import { PageLayoutWidgetSSEEffect } from '@/metadata-store/effect-components/PageLayoutWidgetSSEEffect'; -import { ViewFieldSSEEffect } from '@/metadata-store/effect-components/ViewFieldSSEEffect'; -import { ViewFilterGroupSSEEffect } from '@/metadata-store/effect-components/ViewFilterGroupSSEEffect'; -import { ViewFilterSSEEffect } from '@/metadata-store/effect-components/ViewFilterSSEEffect'; -import { ViewSortSSEEffect } from '@/metadata-store/effect-components/ViewSortSSEEffect'; -import { ViewSSEEffect } from '@/metadata-store/effect-components/ViewSSEEffect'; +import { MetadataStoreSSEEffect } from '@/metadata-store/effect-components/MetadataStoreSSEEffect'; import { SSEClientEffect } from '@/sse-db-event/components/SSEClientEffect'; import { SSEEventStreamEffect } from '@/sse-db-event/components/SSEEventStreamEffect'; import { SSEQuerySubscribeEffect } from '@/sse-db-event/components/SSEQuerySubscribeEffect'; @@ -24,17 +14,7 @@ export const SSEProvider = ({ children }: SSEProviderProps) => { - - - - - - - - - - - + {children} ); diff --git a/packages/twenty-front/src/modules/users/components/LazyMetadataLoadEffect.tsx b/packages/twenty-front/src/modules/users/components/LazyMetadataLoadEffect.tsx deleted file mode 100644 index e4cc205eec..0000000000 --- a/packages/twenty-front/src/modules/users/components/LazyMetadataLoadEffect.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import { useIsLogged } from '@/auth/hooks/useIsLogged'; -import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; -import { splitPageLayoutWithRelated } from '@/metadata-store/utils/splitPageLayoutWithRelated'; -import { navigationMenuItemsState } from '@/navigation-menu-item/states/navigationMenuItemsState'; -import { recordPageLayoutsState } from '@/page-layout/states/recordPageLayoutsState'; -import { type PageLayout } from '@/page-layout/types/PageLayout'; -import { transformPageLayout } from '@/page-layout/utils/transformPageLayout'; -import { logicFunctionsState } from '@/settings/logic-functions/states/logicFunctionsState'; -import { coreViewsState } from '@/views/states/coreViewState'; -import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; -import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; -import { useStore } from 'jotai'; -import { useCallback, useEffect } from 'react'; -import { useLocation } from 'react-router-dom'; -import { AppPath } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; -import { useQuery } from '@apollo/client/react'; -import { - ViewType as CoreViewType, - FindAllRecordPageLayoutsDocument, - FindFieldsWidgetCoreViewsDocument, - FindManyLogicFunctionsDocument, - FindManyNavigationMenuItemsDocument, -} from '~/generated-metadata/graphql'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; -import { isMatchingLocation } from '~/utils/isMatchingLocation'; - -const FIELDS_WIDGET_VIEW_TYPES = [CoreViewType.FIELDS_WIDGET]; - -export const LazyMetadataLoadEffect = () => { - const location = useLocation(); - const isLoggedIn = useIsLogged(); - const store = useStore(); - - const setLogicFunctions = useSetAtomState(logicFunctionsState); - const setNavigationMenuItems = useSetAtomState(navigationMenuItemsState); - const { updateDraft, applyChanges } = useMetadataStore(); - - const isOnAuthPath = - isMatchingLocation(location, AppPath.Verify) || - isMatchingLocation(location, AppPath.VerifyEmail); - - const shouldSkip = !isLoggedIn || isOnAuthPath; - - const { data: queryDataFieldsWidgetCoreViews } = useQuery( - FindFieldsWidgetCoreViewsDocument, - { - skip: shouldSkip, - variables: { viewTypes: FIELDS_WIDGET_VIEW_TYPES }, - }, - ); - - const { data: queryDataRecordPageLayouts } = useQuery( - FindAllRecordPageLayoutsDocument, - { skip: shouldSkip }, - ); - - const { data: logicFunctionsData } = useQuery( - FindManyLogicFunctionsDocument, - { - skip: !isLoggedIn, - }, - ); - - const { data: navigationMenuItemsData } = useQuery( - FindManyNavigationMenuItemsDocument, - { skip: shouldSkip }, - ); - - const setFieldsWidgetCoreViews = useCallback( - (fieldsWidgetViews: CoreViewWithRelations[]) => { - const existingCoreViews = store.get(coreViewsState.atom); - const existingIndexViews = existingCoreViews.filter( - (view) => view.type !== CoreViewType.FIELDS_WIDGET, - ); - const mergedViews = [...existingIndexViews, ...fieldsWidgetViews]; - - if (!isDeeplyEqual(existingCoreViews, mergedViews)) { - store.set(coreViewsState.atom, mergedViews); - } - }, - [store], - ); - - const setRecordPageLayouts = useCallback( - (recordPageLayouts: PageLayout[]) => { - const existingRecordPageLayouts = store.get(recordPageLayoutsState.atom); - - if (!isDeeplyEqual(existingRecordPageLayouts, recordPageLayouts)) { - store.set(recordPageLayoutsState.atom, recordPageLayouts); - } - }, - [store], - ); - - useEffect(() => { - if (!isDefined(queryDataFieldsWidgetCoreViews?.getCoreViews)) { - return; - } - - setFieldsWidgetCoreViews(queryDataFieldsWidgetCoreViews.getCoreViews); - }, [queryDataFieldsWidgetCoreViews?.getCoreViews, setFieldsWidgetCoreViews]); - - useEffect(() => { - if (!isDefined(queryDataRecordPageLayouts?.getPageLayouts)) { - return; - } - - const transformedPageLayouts = - queryDataRecordPageLayouts.getPageLayouts.map(transformPageLayout); - - setRecordPageLayouts(transformedPageLayouts); - - const { flatPageLayouts, flatPageLayoutTabs, flatPageLayoutWidgets } = - splitPageLayoutWithRelated(transformedPageLayouts); - - updateDraft('pageLayouts', flatPageLayouts); - updateDraft('pageLayoutTabs', flatPageLayoutTabs); - updateDraft('pageLayoutWidgets', flatPageLayoutWidgets); - applyChanges(); - }, [ - queryDataRecordPageLayouts?.getPageLayouts, - setRecordPageLayouts, - updateDraft, - applyChanges, - ]); - - useEffect(() => { - if (!isDefined(logicFunctionsData?.findManyLogicFunctions)) { - return; - } - - setLogicFunctions(logicFunctionsData.findManyLogicFunctions); - updateDraft('logicFunctions', logicFunctionsData.findManyLogicFunctions); - applyChanges(); - }, [ - logicFunctionsData?.findManyLogicFunctions, - setLogicFunctions, - updateDraft, - applyChanges, - ]); - - useEffect(() => { - if (!isDefined(navigationMenuItemsData?.navigationMenuItems)) { - return; - } - - const existingNavigationMenuItems = store.get( - navigationMenuItemsState.atom, - ); - - if ( - !isDeeplyEqual( - existingNavigationMenuItems, - navigationMenuItemsData.navigationMenuItems, - ) - ) { - setNavigationMenuItems(navigationMenuItemsData.navigationMenuItems); - } - - updateDraft( - 'navigationMenuItems', - navigationMenuItemsData.navigationMenuItems, - ); - applyChanges(); - }, [ - navigationMenuItemsData?.navigationMenuItems, - setNavigationMenuItems, - store, - updateDraft, - applyChanges, - ]); - - return null; -}; diff --git a/packages/twenty-front/src/modules/users/hooks/useLoadCurrentUser.ts b/packages/twenty-front/src/modules/users/hooks/useLoadCurrentUser.ts index cae3362a2e..7845e09889 100644 --- a/packages/twenty-front/src/modules/users/hooks/useLoadCurrentUser.ts +++ b/packages/twenty-front/src/modules/users/hooks/useLoadCurrentUser.ts @@ -10,7 +10,6 @@ import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useL import { useInitializeFormatPreferences } from '@/localization/hooks/useInitializeFormatPreferences'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; -import { coreViewsState } from '@/views/states/coreViewState'; import { workspaceAuthBypassProvidersState } from '@/workspace/states/workspaceAuthBypassProvidersState'; import { useCallback } from 'react'; import { SOURCE_LOCALE, type APP_LOCALES } from 'twenty-shared/translations'; @@ -18,10 +17,7 @@ import { type ObjectPermissions } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { type ColorScheme } from 'twenty-ui/input'; import { useApolloClient } from '@apollo/client/react'; -import { - FindAllCoreViewsDocument, - GetCurrentUserDocument, -} from '~/generated-metadata/graphql'; +import { GetCurrentUserDocument } from '~/generated-metadata/graphql'; import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl'; import { dynamicActivate } from '~/utils/i18n/dynamicActivate'; @@ -39,7 +35,6 @@ export const useLoadCurrentUser = () => { ); const setCurrentWorkspace = useSetAtomState(currentWorkspaceState); const { initializeFormatPreferences } = useInitializeFormatPreferences(); - const setCoreViews = useSetAtomState(coreViewsState); const setWorkspaceAuthBypassProviders = useSetAtomState( workspaceAuthBypassProvidersState, ); @@ -55,13 +50,6 @@ export const useLoadCurrentUser = () => { fetchPolicy: 'network-only', }); - const coreViewsResult = isOnAWorkspace - ? await client.query({ - query: FindAllCoreViewsDocument, - fetchPolicy: 'network-only', - }) - : undefined; - if (isDefined(currentUserResult.error)) { throw new Error(currentUserResult.error.message); } @@ -139,10 +127,6 @@ export const useLoadCurrentUser = () => { }); } - if (isDefined(coreViewsResult?.data?.getCoreViews)) { - setCoreViews(coreViewsResult.data.getCoreViews); - } - return { user, workspaceMember, @@ -159,7 +143,6 @@ export const useLoadCurrentUser = () => { setCurrentWorkspaceMember, initializeFormatPreferences, setLastAuthenticateWorkspaceDomain, - setCoreViews, authProviders, setWorkspaceAuthBypassProviders, ]); diff --git a/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx b/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx index 35000db344..b47f54614b 100644 --- a/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx +++ b/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx @@ -1,7 +1,6 @@ import { styled } from '@linaria/react'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; -import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; @@ -17,7 +16,6 @@ import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreVie import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter } from '@/views/hooks/useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { useSaveCurrentViewFiltersAndSorts } from '@/views/hooks/useSaveCurrentViewFiltersAndSorts'; import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId'; import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode'; @@ -39,11 +37,6 @@ export const UpdateViewButtonGroup = () => { const { saveCurrentViewFilterAndSorts } = useSaveCurrentViewFiltersAndSorts(); const { canPersistChanges } = useCanPersistViewChanges(); - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - - const { objectMetadataItem } = useRecordIndexContextOrThrow(); - const { setViewPickerMode } = useViewPickerMode(); const contextStoreCurrentViewId = useAtomComponentStateValue( @@ -83,7 +76,6 @@ export const UpdateViewButtonGroup = () => { const handleUpdateViewClick = async () => { if (!canPersistChanges) return; await saveCurrentViewFilterAndSorts(); - await refreshCoreViewsByObjectMetadataId(objectMetadataItem.id); }; const { hasFiltersQueryParams } = useHasFiltersInQueryParams(); diff --git a/packages/twenty-front/src/modules/views/components/ViewBar.tsx b/packages/twenty-front/src/modules/views/components/ViewBar.tsx index d0fa9754b4..f7625a8b46 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBar.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBar.tsx @@ -1,6 +1,6 @@ import { type ReactNode } from 'react'; -import { useParams } from 'react-router-dom'; +import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { ObjectSortDropdownButton } from '@/object-record/object-sort-dropdown/components/ObjectSortDropdownButton'; import { TopBar } from '@/ui/layout/top-bar/components/TopBar'; import { QueryParamsFiltersEffect } from '@/views/components/QueryParamsFiltersEffect'; @@ -26,19 +26,27 @@ type ViewBarProps = { viewBarId: string; className?: string; optionsDropdownButton: ReactNode; + isReadOnly?: boolean; }; export const ViewBar = ({ viewBarId, className, optionsDropdownButton, + isReadOnly = false, }: ViewBarProps) => { - const { objectNamePlural } = useParams(); + const { objectNamePlural } = useRecordIndexContextOrThrow(); if (!objectNamePlural) { return; } + if (isReadOnly) { + return ( + } /> + ); + } + return ( = { title: 'Modules/Views/ViewBarFilterDropdown', @@ -43,8 +43,6 @@ const meta: Meta = { instanceId, ); - const setCoreViews = useSetAtomState(coreViewsState); - const mockCoreView = mockedCoreViews.find( (v) => v.name === 'All Companies', )!; @@ -72,12 +70,11 @@ const meta: Meta = { const [isLoaded, setIsLoaded] = useState(false); useEffect(() => { - setCoreViews([mockCoreView]); + setTestCoreViewsInMetadataStore(jotaiStore, [mockCoreView]); setContextStoreCurrentViewId(mockCoreView.id); setCurrentRecordFields(columns); setIsLoaded(true); }, [ - setCoreViews, setContextStoreCurrentViewId, setCurrentRecordFields, mockCoreView, diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups.test.tsx index 6fadc35c2c..0f176c2529 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups.test.tsx @@ -6,7 +6,6 @@ import { type RecordFilterGroup } from '@/object-record/record-filter-group/type import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { resetJotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups'; -import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; import { type View } from '@/views/types/View'; import { type ViewFilterGroup } from '@/views/types/ViewFilterGroup'; @@ -18,6 +17,7 @@ import { type CoreViewFilterGroup } from '~/generated-metadata/graphql'; import { getJestMetadataAndApolloMocksAndCommandMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndCommandMenuWrapper'; import { mockedCoreViews } from '~/testing/mock-data/generated/metadata/views/mock-views-data'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; +import { setTestCoreViewsInMetadataStore } from '~/testing/utils/setTestCoreViewsInMetadataStore'; const mockObjectMetadataItemNameSingular = 'company'; @@ -94,7 +94,7 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => { contextStoreCurrentObjectMetadataNameSingular: mockObjectMetadataItemNameSingular, onInitializeJotaiStore: (store) => { - store.set(coreViewsState.atom, [mockCoreView]); + setTestCoreViewsInMetadataStore(store, [mockCoreView]); }, }), }, @@ -186,7 +186,7 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => { contextStoreCurrentObjectMetadataNameSingular: mockObjectMetadataItemNameSingular, onInitializeJotaiStore: (store) => { - store.set(coreViewsState.atom, [ + setTestCoreViewsInMetadataStore(store, [ { ...mockCoreView, viewFilterGroups: [] }, ]); store.set( diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx index d2d31d120d..aeea87b039 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx @@ -5,7 +5,7 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { resetJotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '@/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; import { type View } from '@/views/types/View'; import { type ViewFilter } from '@/views/types/ViewFilter'; @@ -19,7 +19,7 @@ import { import { getJestMetadataAndApolloMocksAndCommandMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndCommandMenuWrapper'; import { mockedCoreViews } from '~/testing/mock-data/generated/metadata/views/mock-views-data'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; -import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '@/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters'; +import { setTestCoreViewsInMetadataStore } from '~/testing/utils/setTestCoreViewsInMetadataStore'; const mockObjectMetadataItemNameSingular = 'company'; @@ -105,7 +105,7 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { mockObjectMetadataItemNameSingular, contextStoreCurrentViewId: mockView.id, onInitializeJotaiStore: (store) => { - store.set(coreViewsState.atom, [mockCoreView]); + setTestCoreViewsInMetadataStore(store, [mockCoreView]); }, }), }, @@ -195,7 +195,7 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { contextStoreCurrentObjectMetadataNameSingular: mockObjectMetadataItemNameSingular, onInitializeJotaiStore: (store) => { - store.set(coreViewsState.atom, [ + setTestCoreViewsInMetadataStore(store, [ { ...mockCoreView, viewFilters: [] }, ]); store.set( diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewSortsToCurrentRecordSorts.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewSortsToCurrentRecordSorts.test.tsx index d383703529..516b88da41 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewSortsToCurrentRecordSorts.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewSortsToCurrentRecordSorts.test.tsx @@ -6,17 +6,17 @@ import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/use import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { useApplyCurrentViewSortsToCurrentRecordSorts } from '@/views/hooks/useApplyCurrentViewSortsToCurrentRecordSorts'; import { type CoreViewSortEssential } from '@/views/types/CoreViewSortEssential'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; import { type View } from '@/views/types/View'; +import { type ViewSort } from '@/views/types/ViewSort'; import { isDefined } from 'twenty-shared/utils'; import { ViewSortDirection } from '~/generated-metadata/graphql'; import { getJestMetadataAndApolloMocksAndCommandMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndCommandMenuWrapper'; import { mockedCoreViews } from '~/testing/mock-data/generated/metadata/views/mock-views-data'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; -import { useApplyCurrentViewSortsToCurrentRecordSorts } from '@/views/hooks/useApplyCurrentViewSortsToCurrentRecordSorts'; -import { type ViewSort } from '@/views/types/ViewSort'; +import { setTestCoreViewsInMetadataStore } from '~/testing/utils/setTestCoreViewsInMetadataStore'; const mockObjectMetadataItemNameSingular = 'company'; @@ -93,7 +93,7 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => { mockObjectMetadataItemNameSingular, contextStoreCurrentViewId: mockView.id, onInitializeJotaiStore: (store) => { - store.set(coreViewsState.atom, [mockCoreView]); + setTestCoreViewsInMetadataStore(store, [mockCoreView]); }, }), }, @@ -182,7 +182,7 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => { contextStoreCurrentObjectMetadataNameSingular: mockObjectMetadataItemNameSingular, onInitializeJotaiStore: (store) => { - store.set(coreViewsState.atom, [viewWithNoSorts]); + setTestCoreViewsInMetadataStore(store, [viewWithNoSorts]); store.set( contextStoreCurrentViewIdComponentState.atomFamily({ instanceId: 'instanceId', diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIPersist.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIPersist.ts index 9daac940d9..96fb6f0754 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIPersist.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIPersist.ts @@ -38,7 +38,6 @@ export const usePerformViewAPIPersist = () => { const newViewId = variables.input.id ?? v4(); if (variables.input.type === ViewType.KANBAN) { triggerViewGroupOptimisticEffectAtViewCreation({ - newViewId, objectMetadataItemId: objectMetadataItemId, mainGroupByFieldMetadataId: variables.input.mainGroupByFieldMetadataId, diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIUpdate.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIUpdate.ts index 15c06c98db..d1d6524b5b 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIUpdate.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIUpdate.ts @@ -3,15 +3,11 @@ import { useCallback } from 'react'; import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { coreViewsState } from '@/views/states/coreViewState'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { t } from '@lingui/core/macro'; -import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; import { CrudOperationType } from 'twenty-shared/types'; -import { upsertPropertiesOfItemIntoArrayOfObjectsComparingId } from 'twenty-shared/utils'; import { useMutation } from '@apollo/client/react'; import { - type CoreView, type UpdateCoreViewMutationVariables, UpdateCoreViewDocument, } from '~/generated-metadata/graphql'; @@ -22,8 +18,6 @@ export const usePerformViewAPIUpdate = () => { const { handleMetadataError } = useMetadataErrorHandler(); const { enqueueErrorSnackBar } = useSnackBar(); - const setCoreViews = useSetAtomState(coreViewsState); - const performViewAPIUpdate = useCallback( async ( variables: UpdateCoreViewMutationVariables, @@ -31,30 +25,10 @@ export const usePerformViewAPIUpdate = () => { MetadataRequestResult>> > => { try { - setCoreViews((currentCoreViews) => - upsertPropertiesOfItemIntoArrayOfObjectsComparingId( - currentCoreViews, - { - ...variables.input, - id: variables.id, - } as CoreView, - ), - ); - const result = await updateCoreViewMutation({ variables, }); - setCoreViews((currentCoreViews) => - upsertPropertiesOfItemIntoArrayOfObjectsComparingId( - currentCoreViews, - { - ...result.data?.updateCoreView, - id: variables.id, - } as CoreView, - ), - ); - return { status: 'successful', response: result, @@ -75,12 +49,7 @@ export const usePerformViewAPIUpdate = () => { }; } }, - [ - setCoreViews, - updateCoreViewMutation, - handleMetadataError, - enqueueErrorSnackBar, - ], + [updateCoreViewMutation, handleMetadataError, enqueueErrorSnackBar], ); return { performViewAPIUpdate }; diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFieldAPIPersist.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFieldAPIPersist.ts index bbef47ff71..94a43a85ea 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFieldAPIPersist.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFieldAPIPersist.ts @@ -3,11 +3,9 @@ import { useCallback } from 'react'; import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { useTriggerViewFieldOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { t } from '@lingui/core/macro'; import { CrudOperationType } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; import { useMutation } from '@apollo/client/react'; import { type CreateManyCoreViewFieldsMutationVariables, @@ -21,9 +19,6 @@ import { } from '~/generated-metadata/graphql'; export const usePerformViewFieldAPIPersist = () => { - const { triggerViewFieldOptimisticEffect } = - useTriggerViewFieldOptimisticEffect(); - const [createManyCoreViewFieldsMutation] = useMutation( CreateManyCoreViewFieldsDocument, ); @@ -61,16 +56,6 @@ export const usePerformViewFieldAPIPersist = () => { try { const result = await createManyCoreViewFieldsMutation({ variables: createCoreViewFieldInputs, - update: (_cache, { data }) => { - const createdViewFields = data?.createManyCoreViewFields; - if (!isDefined(createdViewFields)) { - return; - } - - triggerViewFieldOptimisticEffect({ - createdViewFields, - }); - }, }); return { @@ -94,7 +79,6 @@ export const usePerformViewFieldAPIPersist = () => { } }, [ - triggerViewFieldOptimisticEffect, createManyCoreViewFieldsMutation, handleMetadataError, enqueueErrorSnackBar, @@ -121,16 +105,6 @@ export const usePerformViewFieldAPIPersist = () => { createCoreViewFieldInputs.map((variables) => updateCoreViewFieldMutation({ variables, - update: (_cache, { data }) => { - const updatedViewField = data?.updateCoreViewField; - if (!isDefined(updatedViewField)) { - return; - } - - triggerViewFieldOptimisticEffect({ - updatedViewFields: [updatedViewField], - }); - }, }), ), ); @@ -155,12 +129,7 @@ export const usePerformViewFieldAPIPersist = () => { }; } }, - [ - triggerViewFieldOptimisticEffect, - updateCoreViewFieldMutation, - handleMetadataError, - enqueueErrorSnackBar, - ], + [updateCoreViewFieldMutation, handleMetadataError, enqueueErrorSnackBar], ); const performViewFieldAPIDelete = useCallback( @@ -183,16 +152,6 @@ export const usePerformViewFieldAPIPersist = () => { deleteCoreViewFieldInputs.map((variables) => deleteCoreViewFieldMutation({ variables, - update: (_cache, { data }) => { - const deletedViewField = data?.deleteCoreViewField; - if (!isDefined(deletedViewField)) { - return; - } - - triggerViewFieldOptimisticEffect({ - deletedViewFields: [deletedViewField], - }); - }, }), ), ); @@ -217,12 +176,7 @@ export const usePerformViewFieldAPIPersist = () => { }; } }, - [ - triggerViewFieldOptimisticEffect, - deleteCoreViewFieldMutation, - handleMetadataError, - enqueueErrorSnackBar, - ], + [deleteCoreViewFieldMutation, handleMetadataError, enqueueErrorSnackBar], ); const performViewFieldAPIDestroy = useCallback( diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFieldGroupAPIPersist.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFieldGroupAPIPersist.ts index 3883340b45..48e0a31ca4 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFieldGroupAPIPersist.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFieldGroupAPIPersist.ts @@ -6,7 +6,6 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { CREATE_MANY_CORE_VIEW_FIELD_GROUPS } from '@/views/graphql/mutations/createManyCoreViewFieldGroups'; import { DELETE_CORE_VIEW_FIELD_GROUP } from '@/views/graphql/mutations/deleteCoreViewFieldGroup'; import { UPDATE_CORE_VIEW_FIELD_GROUP } from '@/views/graphql/mutations/updateCoreViewFieldGroup'; -import { useTriggerViewFieldGroupOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewFieldGroupOptimisticEffect'; import { useMutation } from '@apollo/client/react'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { t } from '@lingui/core/macro'; @@ -32,9 +31,6 @@ type DeleteCoreViewFieldGroupMutationResult = { }; export const usePerformViewFieldGroupAPIPersist = () => { - const { triggerViewFieldGroupOptimisticEffect } = - useTriggerViewFieldGroupOptimisticEffect(); - const [createManyCoreViewFieldGroupsMutation] = useMutation< CreateManyCoreViewFieldGroupsMutationResult, MutationCreateManyCoreViewFieldGroupsArgs @@ -70,16 +66,6 @@ export const usePerformViewFieldGroupAPIPersist = () => { try { const result = await createManyCoreViewFieldGroupsMutation({ variables: createCoreViewFieldGroupInputs, - update: (_cache, { data }) => { - const createdViewFieldGroups = data?.createManyCoreViewFieldGroups; - if (!isDefined(createdViewFieldGroups)) { - return; - } - - triggerViewFieldGroupOptimisticEffect({ - createdViewFieldGroups, - }); - }, }); return { @@ -103,7 +89,6 @@ export const usePerformViewFieldGroupAPIPersist = () => { } }, [ - triggerViewFieldGroupOptimisticEffect, createManyCoreViewFieldGroupsMutation, handleMetadataError, enqueueErrorSnackBar, @@ -128,16 +113,6 @@ export const usePerformViewFieldGroupAPIPersist = () => { updateCoreViewFieldGroupInputs.map((variables) => updateCoreViewFieldGroupMutation({ variables, - update: (_cache, { data }) => { - const updatedViewFieldGroup = data?.updateCoreViewFieldGroup; - if (!isDefined(updatedViewFieldGroup)) { - return; - } - - triggerViewFieldGroupOptimisticEffect({ - updatedViewFieldGroups: [updatedViewFieldGroup], - }); - }, }), ), ); @@ -165,7 +140,6 @@ export const usePerformViewFieldGroupAPIPersist = () => { } }, [ - triggerViewFieldGroupOptimisticEffect, updateCoreViewFieldGroupMutation, handleMetadataError, enqueueErrorSnackBar, @@ -190,16 +164,6 @@ export const usePerformViewFieldGroupAPIPersist = () => { deleteCoreViewFieldGroupInputs.map((variables) => deleteCoreViewFieldGroupMutation({ variables, - update: (_cache, { data }) => { - const deletedViewFieldGroup = data?.deleteCoreViewFieldGroup; - if (!isDefined(deletedViewFieldGroup)) { - return; - } - - triggerViewFieldGroupOptimisticEffect({ - deletedViewFieldGroups: [deletedViewFieldGroup], - }); - }, }), ), ); @@ -227,7 +191,6 @@ export const usePerformViewFieldGroupAPIPersist = () => { } }, [ - triggerViewFieldGroupOptimisticEffect, deleteCoreViewFieldGroupMutation, handleMetadataError, enqueueErrorSnackBar, diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFilterAPIPersist.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFilterAPIPersist.ts index aaa83cc14d..1b4acd5d81 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFilterAPIPersist.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFilterAPIPersist.ts @@ -3,11 +3,9 @@ import { useCallback } from 'react'; import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { useTriggerViewFilterOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewFilterOptimisticEffect'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { t } from '@lingui/core/macro'; import { CrudOperationType } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; import { useMutation } from '@apollo/client/react'; import { type CreateCoreViewFilterMutationVariables, @@ -21,8 +19,6 @@ import { } from '~/generated-metadata/graphql'; export const usePerformViewFilterAPIPersist = () => { - const { triggerViewFilterOptimisticEffect } = - useTriggerViewFilterOptimisticEffect(); const [createCoreViewFilterMutation] = useMutation( CreateCoreViewFilterDocument, ); @@ -59,16 +55,6 @@ export const usePerformViewFilterAPIPersist = () => { createCoreViewFilterInputs.map((variables) => createCoreViewFilterMutation({ variables, - update: (_cache, { data }) => { - const createdViewFilter = data?.createCoreViewFilter; - if (!isDefined(createdViewFilter)) { - return; - } - - triggerViewFilterOptimisticEffect({ - createdViewFilters: [createdViewFilter], - }); - }, }), ), ); @@ -93,12 +79,7 @@ export const usePerformViewFilterAPIPersist = () => { }; } }, - [ - triggerViewFilterOptimisticEffect, - createCoreViewFilterMutation, - handleMetadataError, - enqueueErrorSnackBar, - ], + [createCoreViewFilterMutation, handleMetadataError, enqueueErrorSnackBar], ); const performViewFilterAPIUpdate = useCallback( @@ -121,16 +102,6 @@ export const usePerformViewFilterAPIPersist = () => { updateCoreViewFilterInputs.map((variables) => updateCoreViewFilterMutation({ variables, - update: (_cache, { data }) => { - const updatedViewFilter = data?.updateCoreViewFilter; - if (!isDefined(updatedViewFilter)) { - return; - } - - triggerViewFilterOptimisticEffect({ - updatedViewFilters: [updatedViewFilter], - }); - }, }), ), ); @@ -155,12 +126,7 @@ export const usePerformViewFilterAPIPersist = () => { }; } }, - [ - triggerViewFilterOptimisticEffect, - updateCoreViewFilterMutation, - handleMetadataError, - enqueueErrorSnackBar, - ], + [updateCoreViewFilterMutation, handleMetadataError, enqueueErrorSnackBar], ); const performViewFilterAPIDelete = useCallback( @@ -183,16 +149,6 @@ export const usePerformViewFilterAPIPersist = () => { deleteCoreViewFilterInputs.map((variables) => deleteCoreViewFilterMutation({ variables, - update: (_cache, { data }) => { - const deletedViewFilter = data?.deleteCoreViewFilter; - if (!isDefined(deletedViewFilter)) { - return; - } - - triggerViewFilterOptimisticEffect({ - deletedViewFilters: [deletedViewFilter], - }); - }, }), ), ); @@ -217,12 +173,7 @@ export const usePerformViewFilterAPIPersist = () => { }; } }, - [ - triggerViewFilterOptimisticEffect, - deleteCoreViewFilterMutation, - handleMetadataError, - enqueueErrorSnackBar, - ], + [deleteCoreViewFilterMutation, handleMetadataError, enqueueErrorSnackBar], ); const performViewFilterAPIDestroy = useCallback( diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewGroupAPIPersist.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewGroupAPIPersist.ts index 2b445f96cb..c963e8582f 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewGroupAPIPersist.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewGroupAPIPersist.ts @@ -3,11 +3,9 @@ import { useCallback } from 'react'; import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { useTriggerViewGroupOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { t } from '@lingui/core/macro'; import { CrudOperationType } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; import { useMutation } from '@apollo/client/react'; import { type UpdateCoreViewGroupMutationVariables, @@ -15,9 +13,6 @@ import { } from '~/generated-metadata/graphql'; export const usePerformViewGroupAPIPersist = () => { - const { triggerViewGroupOptimisticEffect } = - useTriggerViewGroupOptimisticEffect(); - const [updateCoreViewGroupMutation] = useMutation( UpdateCoreViewGroupDocument, ); @@ -45,16 +40,6 @@ export const usePerformViewGroupAPIPersist = () => { updateCoreViewGroupInputs.map((variables) => updateCoreViewGroupMutation({ variables, - update: (_cache, { data }) => { - const updatedViewGroup = data?.updateCoreViewGroup; - if (!isDefined(updatedViewGroup)) { - return; - } - - triggerViewGroupOptimisticEffect({ - updatedViewGroups: [updatedViewGroup], - }); - }, }), ), ); @@ -79,12 +64,7 @@ export const usePerformViewGroupAPIPersist = () => { }; } }, - [ - triggerViewGroupOptimisticEffect, - updateCoreViewGroupMutation, - handleMetadataError, - enqueueErrorSnackBar, - ], + [updateCoreViewGroupMutation, handleMetadataError, enqueueErrorSnackBar], ); return { diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewSortAPIPersist.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewSortAPIPersist.ts index e34cfc1afe..d402c30679 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewSortAPIPersist.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewSortAPIPersist.ts @@ -3,11 +3,9 @@ import { useCallback } from 'react'; import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { useTriggerViewSortOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewSortOptimisticEffect'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { t } from '@lingui/core/macro'; import { CrudOperationType } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; import { useMutation } from '@apollo/client/react'; import { type CreateCoreViewSortMutationVariables, @@ -21,8 +19,6 @@ import { } from '~/generated-metadata/graphql'; export const usePerformViewSortAPIPersist = () => { - const { triggerViewSortOptimisticEffect } = - useTriggerViewSortOptimisticEffect(); const [createCoreViewSortMutation] = useMutation(CreateCoreViewSortDocument); const [updateCoreViewSortMutation] = useMutation(UpdateCoreViewSortDocument); const [deleteCoreViewSortMutation] = useMutation(DeleteCoreViewSortDocument); @@ -53,16 +49,6 @@ export const usePerformViewSortAPIPersist = () => { createCoreViewSortInputs.map((variables) => createCoreViewSortMutation({ variables, - update: (_cache, { data }) => { - const createdViewSort = data?.createCoreViewSort; - if (!isDefined(createdViewSort)) { - return; - } - - triggerViewSortOptimisticEffect({ - createdViewSorts: [createdViewSort], - }); - }, }), ), ); @@ -86,12 +72,7 @@ export const usePerformViewSortAPIPersist = () => { }; } }, - [ - triggerViewSortOptimisticEffect, - createCoreViewSortMutation, - handleMetadataError, - enqueueErrorSnackBar, - ], + [createCoreViewSortMutation, handleMetadataError, enqueueErrorSnackBar], ); const performViewSortAPIUpdate = useCallback( @@ -114,16 +95,6 @@ export const usePerformViewSortAPIPersist = () => { updateCoreViewSortInputs.map((variables) => updateCoreViewSortMutation({ variables, - update: (_cache, { data }) => { - const updatedViewSort = data?.updateCoreViewSort; - if (!isDefined(updatedViewSort)) { - return; - } - - triggerViewSortOptimisticEffect({ - updatedViewSorts: [updatedViewSort], - }); - }, }), ), ); @@ -148,12 +119,7 @@ export const usePerformViewSortAPIPersist = () => { }; } }, - [ - triggerViewSortOptimisticEffect, - updateCoreViewSortMutation, - handleMetadataError, - enqueueErrorSnackBar, - ], + [updateCoreViewSortMutation, handleMetadataError, enqueueErrorSnackBar], ); const performViewSortAPIDelete = useCallback( @@ -176,21 +142,6 @@ export const usePerformViewSortAPIPersist = () => { deleteCoreViewSortInputs.map((variables) => deleteCoreViewSortMutation({ variables, - update: (_cache, { data }) => { - const deletedViewSort = data?.deleteCoreViewSort; - - if (!isDefined(deletedViewSort)) { - return; - } - - triggerViewSortOptimisticEffect({ - deletedViewSorts: [ - { - id: variables.input.id, - }, - ], - }); - }, }), ), ); @@ -215,12 +166,7 @@ export const usePerformViewSortAPIPersist = () => { }; } }, - [ - triggerViewSortOptimisticEffect, - deleteCoreViewSortMutation, - handleMetadataError, - enqueueErrorSnackBar, - ], + [deleteCoreViewSortMutation, handleMetadataError, enqueueErrorSnackBar], ); const performViewSortAPIDestroy = useCallback( diff --git a/packages/twenty-front/src/modules/views/hooks/useApplyCoreViewsForObjectMetadataId.ts b/packages/twenty-front/src/modules/views/hooks/useApplyCoreViewsForObjectMetadataId.ts deleted file mode 100644 index bd3e611d76..0000000000 --- a/packages/twenty-front/src/modules/views/hooks/useApplyCoreViewsForObjectMetadataId.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState'; -import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; -import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; -import { recordIndexShouldHideEmptyRecordGroupsComponentState } from '@/object-record/record-index/states/recordIndexShouldHideEmptyRecordGroupsComponentState'; -import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; -import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; -import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; -import { coreViewsByObjectMetadataIdFamilySelector } from '@/views/states/selectors/coreViewsByObjectMetadataIdFamilySelector'; -import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; -import { getFilterableFields } from '@/views/utils/getFilterableFields'; -import { mapViewFieldToRecordField } from '@/views/utils/mapViewFieldToRecordField'; -import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups'; -import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; -import { useCallback } from 'react'; -import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils'; -import { type FindManyCoreViewsQuery } from '~/generated-metadata/graphql'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; - -export const useApplyCoreViewsForObjectMetadataId = () => { - const applyCoreViewsForObjectMetadataId = useCallback( - ( - objectMetadataId: string, - coreViewsFromResult: FindManyCoreViewsQuery['getCoreViews'], - ) => { - const objectMetadataItems = jotaiStore.get(objectMetadataItemsState.atom); - - const objectMetadataItem = objectMetadataItems.find( - (item) => item.id === objectMetadataId, - ); - - if (!isDefined(objectMetadataItem)) { - return; - } - - const coreViewsForObjectMetadataId = jotaiStore.get( - coreViewsByObjectMetadataIdFamilySelector.selectorFamily( - objectMetadataId, - ), - ); - - if (isDeeplyEqual(coreViewsForObjectMetadataId, coreViewsFromResult)) { - return; - } - - jotaiStore.set( - coreViewsByObjectMetadataIdFamilySelector.selectorFamily( - objectMetadataId, - ), - coreViewsFromResult, - ); - - for (const coreView of coreViewsFromResult) { - const existingView = coreViewsForObjectMetadataId.find( - (coreViewForObjectMetadata) => - coreViewForObjectMetadata.id === coreView.id, - ); - - if (!isDefined(existingView)) { - continue; - } - - if ( - !isDeeplyEqual( - coreView.viewFields.map((viewField) => - removePropertiesFromRecord(viewField, ['updatedAt', 'createdAt']), - ), - existingView.viewFields, - ) - ) { - const view = convertCoreViewToView(coreView); - jotaiStore.set( - currentRecordFieldsComponentState.atomFamily({ - instanceId: getRecordIndexIdFromObjectNamePluralAndViewId( - objectMetadataItem.namePlural, - view.id, - ), - }), - view.viewFields - .filter(isDefined) - .map((viewField) => mapViewFieldToRecordField(viewField)), - ); - } - - if ( - !isDeeplyEqual( - coreView.viewFilters.map((viewFilter) => - removePropertiesFromRecord(viewFilter, [ - 'createdAt', - 'updatedAt', - ]), - ), - existingView.viewFilters, - ) - ) { - const view = convertCoreViewToView(coreView); - jotaiStore.set( - currentRecordFiltersComponentState.atomFamily({ - instanceId: getRecordIndexIdFromObjectNamePluralAndViewId( - objectMetadataItem.namePlural, - view.id, - ), - }), - mapViewFiltersToFilters( - view.viewFilters, - getFilterableFields(objectMetadataItem), - ), - ); - } - - if ( - !isDeeplyEqual( - coreView.viewFilterGroups, - existingView.viewFilterGroups, - ) - ) { - const view = convertCoreViewToView(coreView); - - jotaiStore.set( - currentRecordFilterGroupsComponentState.atomFamily({ - instanceId: getRecordIndexIdFromObjectNamePluralAndViewId( - objectMetadataItem.namePlural, - view.id, - ), - }), - mapViewFilterGroupsToRecordFilterGroups( - view.viewFilterGroups ?? [], - ), - ); - } - - if ( - !isDeeplyEqual( - coreView.viewSorts.map((viewSort) => - removePropertiesFromRecord(viewSort, ['createdAt', 'updatedAt']), - ), - existingView.viewSorts, - ) - ) { - const view = convertCoreViewToView(coreView); - - jotaiStore.set( - currentRecordSortsComponentState.atomFamily({ - instanceId: getRecordIndexIdFromObjectNamePluralAndViewId( - objectMetadataItem.namePlural, - view.id, - ), - }), - view.viewSorts, - ); - } - - if ( - coreView.shouldHideEmptyGroups !== existingView.shouldHideEmptyGroups - ) { - const view = convertCoreViewToView(coreView); - jotaiStore.set( - recordIndexShouldHideEmptyRecordGroupsComponentState.atomFamily({ - instanceId: getRecordIndexIdFromObjectNamePluralAndViewId( - objectMetadataItem.namePlural, - view.id, - ), - }), - view.shouldHideEmptyGroups, - ); - } - } - }, - [], - ); - - return { applyCoreViewsForObjectMetadataId }; -}; diff --git a/packages/twenty-front/src/modules/views/hooks/useCreateDefaultViewForObject.ts b/packages/twenty-front/src/modules/views/hooks/useCreateDefaultViewForObject.ts index 40abc7b590..fce223855d 100644 --- a/packages/twenty-front/src/modules/views/hooks/useCreateDefaultViewForObject.ts +++ b/packages/twenty-front/src/modules/views/hooks/useCreateDefaultViewForObject.ts @@ -2,7 +2,6 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField'; import { usePerformViewAPIPersist } from '@/views/hooks/internal/usePerformViewAPIPersist'; import { usePerformViewFieldAPIPersist } from '@/views/hooks/internal/usePerformViewFieldAPIPersist'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { useCallback } from 'react'; import { v4 } from 'uuid'; import { ViewType } from '~/generated-metadata/graphql'; @@ -16,8 +15,6 @@ const pendingViewCreations = new Set(); export const useCreateDefaultViewForObject = () => { const { performViewAPICreate } = usePerformViewAPIPersist(); const { performViewFieldAPICreate } = usePerformViewFieldAPIPersist(); - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); const createDefaultViewForObject = useCallback( async (objectMetadataItem: ObjectMetadataItem) => { @@ -76,17 +73,11 @@ export const useCreateDefaultViewForObject = () => { })); await performViewFieldAPICreate({ inputs: viewFieldInputs }); - - await refreshCoreViewsByObjectMetadataId(objectMetadataItem.id); } finally { pendingViewCreations.delete(objectMetadataItem.id); } }, - [ - performViewAPICreate, - performViewFieldAPICreate, - refreshCoreViewsByObjectMetadataId, - ], + [performViewAPICreate, performViewFieldAPICreate], ); return { diff --git a/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts b/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts index ea6e35e48a..927bf4edff 100644 --- a/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts +++ b/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts @@ -11,7 +11,6 @@ import { usePerformViewFieldAPIPersist } from '@/views/hooks/internal/usePerform import { usePerformViewFilterAPIPersist } from '@/views/hooks/internal/usePerformViewFilterAPIPersist'; import { usePerformViewFilterGroupAPIPersist } from '@/views/hooks/internal/usePerformViewFilterGroupAPIPersist'; import { usePerformViewSortAPIPersist } from '@/views/hooks/internal/usePerformViewSortAPIPersist'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; import { type GraphQLView } from '@/views/types/GraphQLView'; import { ViewType } from '@/views/types/ViewType'; @@ -54,9 +53,6 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { const store = useStore(); - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); - const currentRecordFilterGroups = useAtomComponentStateValue( currentRecordFilterGroupsComponentState, recordIndexId, @@ -246,8 +242,6 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { } } - await refreshCoreViewsByObjectMetadataId(objectMetadataItem.id); - return newViewId; }, [ @@ -256,7 +250,6 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { anyFieldFilterValue, objectMetadataItem, performViewFieldAPICreate, - refreshCoreViewsByObjectMetadataId, store, currentRecordFilterGroups, currentRecordFilters, diff --git a/packages/twenty-front/src/modules/views/hooks/useRefreshAllCoreViews.ts b/packages/twenty-front/src/modules/views/hooks/useRefreshAllCoreViews.ts deleted file mode 100644 index 48198e99fd..0000000000 --- a/packages/twenty-front/src/modules/views/hooks/useRefreshAllCoreViews.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { FIND_ALL_CORE_VIEWS } from '@/views/graphql/queries/findAllCoreViews'; -import { coreViewsState } from '@/views/states/coreViewState'; -import { type FetchPolicy } from '@apollo/client'; -import { useApolloClient } from '@apollo/client/react'; -import { useCallback } from 'react'; -import { isDefined } from 'twenty-shared/utils'; -import { type FindAllCoreViewsQuery } from '~/generated-metadata/graphql'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; -import { useStore } from 'jotai'; - -export const useRefreshAllCoreViews = ( - fetchPolicy: FetchPolicy = 'network-only', -) => { - const store = useStore(); - const client = useApolloClient(); - - const refreshAllCoreViews = useCallback(async () => { - const result = await client.query({ - query: FIND_ALL_CORE_VIEWS, - variables: {}, - fetchPolicy, - }); - - const currentCoreViews = store.get(coreViewsState.atom); - - const coreViewsFromResult = result.data?.getCoreViews; - - if ( - isDefined(coreViewsFromResult) && - !isDeeplyEqual(currentCoreViews, coreViewsFromResult) - ) { - store.set(coreViewsState.atom, coreViewsFromResult); - } - - return result.data?.getCoreViews; - }, [client, fetchPolicy, store]); - - return { - refreshAllCoreViews, - }; -}; diff --git a/packages/twenty-front/src/modules/views/hooks/useRefreshCoreViewsByObjectMetadataId.ts b/packages/twenty-front/src/modules/views/hooks/useRefreshCoreViewsByObjectMetadataId.ts deleted file mode 100644 index ff434ffa0b..0000000000 --- a/packages/twenty-front/src/modules/views/hooks/useRefreshCoreViewsByObjectMetadataId.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useApplyCoreViewsForObjectMetadataId } from '@/views/hooks/useApplyCoreViewsForObjectMetadataId'; -import { useCallback } from 'react'; -import { isDefined } from 'twenty-shared/utils'; -import { useApolloClient } from '@apollo/client/react'; -import { FindManyCoreViewsDocument } from '~/generated-metadata/graphql'; - -export const useRefreshCoreViewsByObjectMetadataId = () => { - const client = useApolloClient(); - const { applyCoreViewsForObjectMetadataId } = - useApplyCoreViewsForObjectMetadataId(); - - const refreshCoreViewsByObjectMetadataId = useCallback( - async (objectMetadataId: string) => { - const result = await client.query({ - query: FindManyCoreViewsDocument, - variables: { - objectMetadataId, - }, - fetchPolicy: 'network-only', - }); - - if (!isDefined(result.data?.getCoreViews)) { - return; - } - - applyCoreViewsForObjectMetadataId( - objectMetadataId, - result.data.getCoreViews, - ); - }, - [client, applyCoreViewsForObjectMetadataId], - ); - - return { - refreshCoreViewsByObjectMetadataId, - }; -}; diff --git a/packages/twenty-front/src/modules/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups.ts b/packages/twenty-front/src/modules/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups.ts index ac4a64abc9..9beb5da774 100644 --- a/packages/twenty-front/src/modules/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups.ts +++ b/packages/twenty-front/src/modules/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups.ts @@ -1,10 +1,8 @@ import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; -import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; import { usePerformViewFilterGroupAPIPersist } from '@/views/hooks/internal/usePerformViewFilterGroupAPIPersist'; import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; -import { coreViewsState } from '@/views/states/coreViewState'; import { getViewFilterGroupsToCreate } from '@/views/utils/getViewFilterGroupsToCreate'; import { getViewFilterGroupsToDelete } from '@/views/utils/getViewFilterGroupsToDelete'; import { getViewFilterGroupsToUpdate } from '@/views/utils/getViewFilterGroupsToUpdate'; @@ -28,8 +26,6 @@ export const useSaveRecordFilterGroupsToViewFilterGroups = () => { const store = useStore(); - const setCoreViews = useSetAtomState(coreViewsState); - const saveRecordFilterGroupsToViewFilterGroups = useCallback(async () => { if (!canPersistChanges || !isDefined(currentView)) { return; @@ -59,33 +55,6 @@ export const useSaveRecordFilterGroupsToViewFilterGroups = () => { newViewFilterGroups, ); - const viewFiltersToOptimisticallyCascadeDelete = - currentView.viewFilters.filter((viewFilter) => - viewFilterGroupsToDelete.some( - (viewFilterGroupToDelete) => - viewFilterGroupToDelete.id === viewFilter.viewFilterGroupId, - ), - ); - - for (const viewFilterToCascadeDelete of viewFiltersToOptimisticallyCascadeDelete) { - setCoreViews((currentCoreViews) => { - const updatedCoreViews = currentCoreViews.map((coreView) => { - if (coreView.id !== currentView.id) { - return coreView; - } - - return { - ...coreView, - viewFilters: coreView.viewFilters.filter( - (viewFilter) => viewFilter.id !== viewFilterToCascadeDelete.id, - ), - }; - }); - - return updatedCoreViews; - }); - } - const viewFilterGroupsToUpdate = getViewFilterGroupsToUpdate( currentViewFilterGroups, newViewFilterGroups, @@ -109,7 +78,6 @@ export const useSaveRecordFilterGroupsToViewFilterGroups = () => { performViewFilterGroupAPICreate, performViewFilterGroupAPIUpdate, performViewFilterGroupAPIDelete, - setCoreViews, ]); return { diff --git a/packages/twenty-front/src/modules/views/hooks/useSaveRecordFiltersToViewFilters.ts b/packages/twenty-front/src/modules/views/hooks/useSaveRecordFiltersToViewFilters.ts index 3f70424b50..4071e205a0 100644 --- a/packages/twenty-front/src/modules/views/hooks/useSaveRecordFiltersToViewFilters.ts +++ b/packages/twenty-front/src/modules/views/hooks/useSaveRecordFiltersToViewFilters.ts @@ -4,7 +4,7 @@ import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/h import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { usePerformViewFilterAPIPersist } from '@/views/hooks/internal/usePerformViewFilterAPIPersist'; import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { getViewFiltersToCreate } from '@/views/utils/getViewFiltersToCreate'; import { getViewFiltersToDelete } from '@/views/utils/getViewFiltersToDelete'; @@ -33,7 +33,7 @@ export const useSaveRecordFiltersToViewFilters = () => { const store = useStore(); const saveRecordFiltersToViewFilters = useCallback(async () => { - const views = store.get(coreViewsState.atom); + const views = store.get(coreViewsSelector.atom); const currentCoreView = views.find( (view) => view.id === contextStoreCurrentViewId, diff --git a/packages/twenty-front/src/modules/views/hooks/useUpdateCurrentView.ts b/packages/twenty-front/src/modules/views/hooks/useUpdateCurrentView.ts index c971057e5f..471c8e3502 100644 --- a/packages/twenty-front/src/modules/views/hooks/useUpdateCurrentView.ts +++ b/packages/twenty-front/src/modules/views/hooks/useUpdateCurrentView.ts @@ -7,7 +7,6 @@ import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/use import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; -import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; import { type GraphQLView } from '@/views/types/GraphQLView'; import { type View } from '@/views/types/View'; @@ -32,8 +31,6 @@ export const useUpdateCurrentView = () => { const store = useStore(); const [updateOneCoreView] = useMutation(UpdateCoreViewDocument); - const { refreshCoreViewsByObjectMetadataId } = - useRefreshCoreViewsByObjectMetadataId(); const getViewGroupsToCreateAtViewUpdate = useMemo(() => { return ({ @@ -143,8 +140,6 @@ export const useUpdateCurrentView = () => { if (isDefined(view.type)) { setRecordIndexViewType(view.type); } - - await refreshCoreViewsByObjectMetadataId(currentView.objectMetadataId); } }, [ @@ -153,7 +148,6 @@ export const useUpdateCurrentView = () => { getViewGroupsToCreateAtViewUpdate, loadRecordIndexStates, objectMetadataItem, - refreshCoreViewsByObjectMetadataId, setRecordIndexViewType, store, updateOneCoreView, diff --git a/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts b/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts index 30e508dbad..da94e582f4 100644 --- a/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts +++ b/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts @@ -6,18 +6,12 @@ import { convertExtendedAggregateOperationToAggregateOperation } from '@/object- import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { usePerformViewAPIUpdate } from '@/views/hooks/internal/usePerformViewAPIUpdate'; import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; -import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { useCallback } from 'react'; -import { - isDefined, - upsertIntoArrayOfObjectsComparingId, -} from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { type CoreView } from '~/generated-metadata/graphql'; -import { useStore } from 'jotai'; export const useUpdateViewAggregate = () => { - const store = useStore(); const { canPersistChanges } = useCanPersistViewChanges(); const contextStoreCurrentViewId = useAtomComponentStateValue( contextStoreCurrentViewIdComponentState, @@ -67,13 +61,6 @@ export const useUpdateViewAggregate = () => { return; } - store.set(coreViewsState.atom, (currentCoreViews) => - upsertIntoArrayOfObjectsComparingId( - currentCoreViews, - updatedCoreView, - ), - ); - const updatedView = convertCoreViewToView(updatedCoreView); loadRecordIndexStates(updatedView, objectMetadataItem); @@ -84,7 +71,6 @@ export const useUpdateViewAggregate = () => { contextStoreCurrentViewId, performViewAPIUpdate, loadRecordIndexStates, - store, ], ); diff --git a/packages/twenty-front/src/modules/views/hooks/useViewsSideEffectsOnViewGroups.ts b/packages/twenty-front/src/modules/views/hooks/useViewsSideEffectsOnViewGroups.ts index 4fa991b87f..82a3f5ccfa 100644 --- a/packages/twenty-front/src/modules/views/hooks/useViewsSideEffectsOnViewGroups.ts +++ b/packages/twenty-front/src/modules/views/hooks/useViewsSideEffectsOnViewGroups.ts @@ -1,23 +1,16 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { useTriggerViewGroupOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect'; import { type ViewGroup } from '@/views/types/ViewGroup'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; -import { type CoreViewGroup } from '~/generated-metadata/graphql'; const useViewsSideEffectsOnViewGroups = () => { - const { triggerViewGroupOptimisticEffect } = - useTriggerViewGroupOptimisticEffect(); - const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); const triggerViewGroupOptimisticEffectAtViewCreation = ({ - newViewId, mainGroupByFieldMetadataId, objectMetadataItemId, }: { - newViewId: string; mainGroupByFieldMetadataId?: string | null; objectMetadataItemId: string; }) => { @@ -63,19 +56,6 @@ const useViewsSideEffectsOnViewGroups = () => { } satisfies ViewGroup); } - triggerViewGroupOptimisticEffect({ - createdViewGroups: viewGroupsToCreate.map( - ({ __typename, ...viewGroup }) => - ({ - ...viewGroup, - viewId: newViewId, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - deletedAt: null, - }) as Omit, - ), - }); - return { viewGroupsToCreate }; }; diff --git a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldGroupOptimisticEffect.ts b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldGroupOptimisticEffect.ts deleted file mode 100644 index fcb089630d..0000000000 --- a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldGroupOptimisticEffect.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { coreViewsState } from '@/views/states/coreViewState'; -import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; -import { useApolloClient } from '@apollo/client/react'; -import { useCallback } from 'react'; -import { isDefined } from 'twenty-shared/utils'; -import { type CoreViewFieldGroup } from '~/generated-metadata/graphql'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; -import { useStore } from 'jotai'; - -type ViewFieldGroupForOptimisticEffect = Omit< - CoreViewFieldGroup, - 'workspaceId' ->; - -export const useTriggerViewFieldGroupOptimisticEffect = () => { - const store = useStore(); - const apolloClient = useApolloClient(); - - const cache = apolloClient.cache; - - const triggerViewFieldGroupOptimisticEffect = useCallback( - ({ - createdViewFieldGroups = [], - updatedViewFieldGroups = [], - deletedViewFieldGroups = [], - }: { - createdViewFieldGroups?: ViewFieldGroupForOptimisticEffect[]; - updatedViewFieldGroups?: ViewFieldGroupForOptimisticEffect[]; - deletedViewFieldGroups?: Pick[]; - }) => { - const coreViews = store.get(coreViewsState.atom); - let newCoreViews = [...coreViews]; - - createdViewFieldGroups.forEach((createdViewFieldGroup) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: createdViewFieldGroup.viewId, - }), - fields: { - viewFieldGroups: (existingViewFieldGroups, { toReference }) => - [ - ...(existingViewFieldGroups ?? []), - toReference(createdViewFieldGroup), - ].filter(isDefined), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === createdViewFieldGroup.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== createdViewFieldGroup.viewId, - ), - { - ...toBeModifiedCoreView, - viewFieldGroups: [ - ...(toBeModifiedCoreView.viewFieldGroups ?? []), - createdViewFieldGroup, - ], - }, - ]; - } - }); - - updatedViewFieldGroups.forEach((updatedViewFieldGroup) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: updatedViewFieldGroup.viewId, - }), - fields: { - viewFieldGroups: ( - existingViewFieldGroups, - { readField, toReference }, - ) => - existingViewFieldGroups - ?.map((viewFieldGroup) => { - const viewFieldGroupId = readField( - 'id', - viewFieldGroup, - ); - if (viewFieldGroupId === updatedViewFieldGroup.id) { - return toReference(updatedViewFieldGroup); - } - return viewFieldGroup; - }) - ?.filter(isDefined) ?? [], - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === updatedViewFieldGroup.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== updatedViewFieldGroup.viewId, - ), - { - ...toBeModifiedCoreView, - viewFieldGroups: [ - ...(toBeModifiedCoreView.viewFieldGroups ?? []).filter( - (viewFieldGroup) => - viewFieldGroup.id !== updatedViewFieldGroup.id, - ), - updatedViewFieldGroup, - ], - }, - ]; - } - }); - - deletedViewFieldGroups.forEach( - (deletedViewFieldGroup: Pick) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: deletedViewFieldGroup.viewId, - }), - fields: { - viewFieldGroups: (existingViewFieldGroups, { readField }) => - existingViewFieldGroups?.filter( - (viewFieldGroup) => - readField('id', viewFieldGroup) !== - deletedViewFieldGroup.id, - ) ?? [], - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === deletedViewFieldGroup.viewId, - ); - - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== deletedViewFieldGroup.viewId, - ), - { - ...toBeModifiedCoreView, - viewFieldGroups: ( - toBeModifiedCoreView.viewFieldGroups ?? [] - ).filter( - (viewFieldGroup) => - viewFieldGroup.id !== deletedViewFieldGroup.id, - ), - }, - ]; - } - }, - ); - - if (!isDeeplyEqual(coreViews, newCoreViews)) { - store.set(coreViewsState.atom, newCoreViews); - } - }, - [cache, store], - ); - - return { - triggerViewFieldGroupOptimisticEffect, - }; -}; diff --git a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect.ts b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect.ts deleted file mode 100644 index 6ca6d0ded3..0000000000 --- a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { coreViewsState } from '@/views/states/coreViewState'; -import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; -import { useApolloClient } from '@apollo/client/react'; -import { useCallback } from 'react'; -import { isDefined } from 'twenty-shared/utils'; -import { type CoreViewField } from '~/generated-metadata/graphql'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; -import { useStore } from 'jotai'; - -export const useTriggerViewFieldOptimisticEffect = () => { - const store = useStore(); - const apolloClient = useApolloClient(); - - const cache = apolloClient.cache; - - const triggerViewFieldOptimisticEffect = useCallback( - ({ - createdViewFields = [], - updatedViewFields = [], - deletedViewFields = [], - }: { - createdViewFields?: Omit[]; - updatedViewFields?: Omit[]; - deletedViewFields?: Pick[]; - }) => { - const coreViews = store.get(coreViewsState.atom); - let newCoreViews = [...coreViews]; - - createdViewFields.forEach((createdViewField) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: createdViewField.viewId, - }), - fields: { - viewFields: (existingViewFields, { toReference }) => - [ - ...(existingViewFields ?? []), - toReference(createdViewField), - ].filter(isDefined), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === createdViewField.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== createdViewField.viewId, - ), - { - ...toBeModifiedCoreView, - viewFields: [ - ...toBeModifiedCoreView.viewFields, - createdViewField, - ], - }, - ]; - } - }); - - updatedViewFields.forEach((updatedViewField) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: updatedViewField.viewId, - }), - fields: { - viewFields: (existingViewFields, { readField, toReference }) => - existingViewFields - .map((viewField) => { - const viewFieldId = readField('id', viewField); - if (viewFieldId === updatedViewField.id) { - return toReference(updatedViewField); - } - return viewField; - }) - .filter(isDefined), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === updatedViewField.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== updatedViewField.viewId, - ), - { - ...toBeModifiedCoreView, - viewFields: [ - ...toBeModifiedCoreView.viewFields.filter( - (viewField) => viewField.id !== updatedViewField.id, - ), - updatedViewField, - ], - }, - ]; - } - }); - - deletedViewFields.forEach( - (deletedViewField: Pick) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: deletedViewField.viewId, - }), - fields: { - viewFields: (existingViewFields, { readField }) => - existingViewFields.filter( - (viewField) => - readField('id', viewField) !== deletedViewField.id, - ), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === deletedViewField.viewId, - ); - - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== deletedViewField.viewId, - ), - { - ...toBeModifiedCoreView, - viewFields: toBeModifiedCoreView.viewFields.filter( - (viewField) => viewField.id !== deletedViewField.id, - ), - }, - ]; - } - }, - ); - - if (!isDeeplyEqual(coreViews, newCoreViews)) { - store.set(coreViewsState.atom, newCoreViews); - } - }, - [cache, store], - ); - - return { - triggerViewFieldOptimisticEffect, - }; -}; diff --git a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFilterOptimisticEffect.ts b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFilterOptimisticEffect.ts deleted file mode 100644 index 895383fdda..0000000000 --- a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFilterOptimisticEffect.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { coreViewsState } from '@/views/states/coreViewState'; -import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; -import { useApolloClient } from '@apollo/client/react'; -import { useCallback } from 'react'; -import { isDefined } from 'twenty-shared/utils'; -import { type CoreViewFilter } from '~/generated-metadata/graphql'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; -import { useStore } from 'jotai'; -type UpdatedDeletedCoreViewFilter = { - createdViewFilters?: Omit[]; - updatedViewFilters?: Omit[]; - deletedViewFilters?: Pick[]; -}; -export const useTriggerViewFilterOptimisticEffect = () => { - const store = useStore(); - const apolloClient = useApolloClient(); - - const cache = apolloClient.cache; - - const triggerViewFilterOptimisticEffect = useCallback( - ({ - createdViewFilters = [], - updatedViewFilters = [], - deletedViewFilters = [], - }: UpdatedDeletedCoreViewFilter) => { - const coreViews = store.get(coreViewsState.atom); - let newCoreViews = [...coreViews]; - - createdViewFilters.forEach((createdViewFilter) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: createdViewFilter.viewId, - }), - fields: { - viewFilters: (existingViewFilters, { toReference }) => - [ - ...(existingViewFilters ?? []), - toReference(createdViewFilter), - ].filter(isDefined), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === createdViewFilter.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== createdViewFilter.viewId, - ), - { - ...toBeModifiedCoreView, - viewFilters: [ - ...toBeModifiedCoreView.viewFilters, - createdViewFilter, - ], - }, - ]; - } - }); - - updatedViewFilters.forEach((updatedViewFilter) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: updatedViewFilter.viewId, - }), - fields: { - viewFilters: (existingViewFilters, { readField, toReference }) => - existingViewFilters - .map((viewFilter) => { - const viewFilterId = readField('id', viewFilter); - if (viewFilterId === updatedViewFilter.id) { - return toReference(updatedViewFilter); - } - return viewFilter; - }) - .filter(isDefined), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === updatedViewFilter.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== updatedViewFilter.viewId, - ), - { - ...toBeModifiedCoreView, - viewFilters: [ - ...toBeModifiedCoreView.viewFilters.filter( - (viewFilter) => viewFilter.id !== updatedViewFilter.id, - ), - updatedViewFilter, - ], - }, - ]; - } - }); - - deletedViewFilters.forEach( - (deletedViewFilter: Pick) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: deletedViewFilter.viewId, - }), - fields: { - viewFilters: (existingViewFilters, { readField }) => - existingViewFilters.filter( - (viewFilter) => - readField('id', viewFilter) !== deletedViewFilter.id, - ), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === deletedViewFilter.viewId, - ); - - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== deletedViewFilter.viewId, - ), - { - ...toBeModifiedCoreView, - viewFilters: toBeModifiedCoreView.viewFilters.filter( - (viewFilter) => viewFilter.id !== deletedViewFilter.id, - ), - }, - ]; - } - }, - ); - - if (!isDeeplyEqual(coreViews, newCoreViews)) { - store.set(coreViewsState.atom, newCoreViews); - } - }, - [cache, store], - ); - - return { - triggerViewFilterOptimisticEffect, - }; -}; diff --git a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect.ts b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect.ts deleted file mode 100644 index 6a55f53163..0000000000 --- a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { coreViewsState } from '@/views/states/coreViewState'; -import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; -import { useApolloClient } from '@apollo/client/react'; -import { useCallback } from 'react'; -import { isDefined } from 'twenty-shared/utils'; -import { type CoreViewGroup } from '~/generated-metadata/graphql'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; -import { useStore } from 'jotai'; - -type UpdatedDeletedCoreViewGroup = { - createdViewGroups?: Omit[]; - updatedViewGroups?: Omit[]; - deletedViewGroups?: Pick[]; -}; -export const useTriggerViewGroupOptimisticEffect = () => { - const store = useStore(); - const apolloClient = useApolloClient(); - - const cache = apolloClient.cache; - - const triggerViewGroupOptimisticEffect = useCallback( - ({ - createdViewGroups = [], - updatedViewGroups = [], - deletedViewGroups = [], - }: UpdatedDeletedCoreViewGroup) => { - const coreViews = store.get(coreViewsState.atom); - let newCoreViews = [...coreViews]; - - createdViewGroups.forEach((createdViewGroup) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: createdViewGroup.viewId, - }), - fields: { - viewGroups: (existingViewGroups, { toReference }) => - [ - ...(existingViewGroups ?? []), - toReference(createdViewGroup), - ].filter(isDefined), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === createdViewGroup.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== createdViewGroup.viewId, - ), - { - ...toBeModifiedCoreView, - viewGroups: [ - ...toBeModifiedCoreView.viewGroups, - createdViewGroup, - ], - }, - ]; - } - }); - - updatedViewGroups.forEach((updatedViewGroup) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: updatedViewGroup.viewId, - }), - fields: { - viewGroups: (existingViewGroups, { readField, toReference }) => - existingViewGroups - .map((viewGroup) => { - const viewGroupId = readField('id', viewGroup); - if (viewGroupId === updatedViewGroup.id) { - return toReference(updatedViewGroup); - } - return viewGroup; - }) - .filter(isDefined), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === updatedViewGroup.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== updatedViewGroup.viewId, - ), - { - ...toBeModifiedCoreView, - viewGroups: [ - ...toBeModifiedCoreView.viewGroups.filter( - (viewGroup) => viewGroup.id !== updatedViewGroup.id, - ), - updatedViewGroup, - ], - }, - ]; - } - }); - - deletedViewGroups.forEach((deletedViewGroup) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: deletedViewGroup.viewId, - }), - fields: { - viewGroups: (existingViewGroups, { readField }) => - existingViewGroups.filter( - (viewGroup) => - readField('id', viewGroup) !== deletedViewGroup.id, - ), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === deletedViewGroup.viewId, - ); - - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== deletedViewGroup.viewId, - ), - { - ...toBeModifiedCoreView, - viewGroups: toBeModifiedCoreView.viewGroups.filter( - (viewGroup) => viewGroup.id !== deletedViewGroup.id, - ), - }, - ]; - } - }); - - if (!isDeeplyEqual(coreViews, newCoreViews)) { - store.set(coreViewsState.atom, newCoreViews); - } - }, - [cache, store], - ); - - return { - triggerViewGroupOptimisticEffect, - }; -}; diff --git a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewSortOptimisticEffect.ts b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewSortOptimisticEffect.ts deleted file mode 100644 index 1d2b728da1..0000000000 --- a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewSortOptimisticEffect.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { coreViewsState } from '@/views/states/coreViewState'; -import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; -import { useApolloClient } from '@apollo/client/react'; -import { useStore } from 'jotai'; -import { useCallback } from 'react'; -import { isDefined } from 'twenty-shared/utils'; -import { type CoreViewSort } from '~/generated-metadata/graphql'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; - -export const useTriggerViewSortOptimisticEffect = () => { - const store = useStore(); - const apolloClient = useApolloClient(); - - const cache = apolloClient.cache; - - const triggerViewSortOptimisticEffect = useCallback( - ({ - createdViewSorts = [], - updatedViewSorts = [], - deletedViewSorts = [], - }: { - createdViewSorts?: Omit[]; - updatedViewSorts?: Omit[]; - deletedViewSorts?: Pick[]; - }) => { - const coreViews = store.get(coreViewsState.atom); - let newCoreViews = [...coreViews]; - - createdViewSorts.forEach((createdViewSort) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: createdViewSort.viewId, - }), - fields: { - viewSorts: (existingViewSorts, { toReference }) => - [ - ...(existingViewSorts ?? []), - toReference(createdViewSort), - ].filter(isDefined), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === createdViewSort.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== createdViewSort.viewId, - ), - { - ...toBeModifiedCoreView, - viewSorts: [...toBeModifiedCoreView.viewSorts, createdViewSort], - }, - ]; - } - }); - - updatedViewSorts.forEach((updatedViewSort) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: updatedViewSort.viewId, - }), - fields: { - viewSorts: (existingViewSorts, { readField, toReference }) => - existingViewSorts - .map((viewSort) => { - const viewSortId = readField('id', viewSort); - if (viewSortId === updatedViewSort.id) { - return toReference(updatedViewSort); - } - return viewSort; - }) - .filter(isDefined), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === updatedViewSort.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== updatedViewSort.viewId, - ), - { - ...toBeModifiedCoreView, - viewSorts: [ - ...toBeModifiedCoreView.viewSorts.filter( - (viewSort) => viewSort.id !== updatedViewSort.id, - ), - updatedViewSort, - ], - }, - ]; - } - }); - - deletedViewSorts.forEach((deletedViewSort: Pick) => { - const viewId = coreViews.find((coreView) => - coreView.viewSorts.some( - (viewSort) => viewSort.id === deletedViewSort.id, - ), - )?.id; - - if (!viewId) { - return; - } - - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: viewId, - }), - fields: { - viewSorts: (existingViewSorts, { readField }) => - existingViewSorts.filter( - (viewSort) => readField('id', viewSort) !== deletedViewSort.id, - ), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === viewId, - ); - - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter((coreView) => coreView.id !== viewId), - { - ...toBeModifiedCoreView, - viewSorts: toBeModifiedCoreView.viewSorts.filter( - (viewSort) => viewSort.id !== deletedViewSort.id, - ), - }, - ]; - } - }); - - if (!isDeeplyEqual(coreViews, newCoreViews)) { - store.set(coreViewsState.atom, newCoreViews); - } - }, - [cache, store], - ); - - return { - triggerViewSortOptimisticEffect, - }; -}; diff --git a/packages/twenty-front/src/modules/views/states/coreViewState.ts b/packages/twenty-front/src/modules/views/states/coreViewState.ts deleted file mode 100644 index eb74ade888..0000000000 --- a/packages/twenty-front/src/modules/views/states/coreViewState.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; -import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; - -export const coreViewsState = createAtomState({ - key: 'coreViewsState', - defaultValue: [], -}); diff --git a/packages/twenty-front/src/modules/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector.ts b/packages/twenty-front/src/modules/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector.ts index bc5baa0a9d..c8d50814cf 100644 --- a/packages/twenty-front/src/modules/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector.ts +++ b/packages/twenty-front/src/modules/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector.ts @@ -1,4 +1,4 @@ -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { ViewKey } from '@/views/types/ViewKey'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; @@ -12,7 +12,7 @@ export const coreIndexViewIdFromObjectMetadataItemFamilySelector = get: ({ objectMetadataItemId }) => ({ get }) => { - const coreViews = get(coreViewsState); + const coreViews = get(coreViewsSelector); const views = coreViews.map(convertCoreViewToView); return views?.find( (view) => diff --git a/packages/twenty-front/src/modules/views/states/selectors/coreViewFromViewIdFamilySelector.ts b/packages/twenty-front/src/modules/views/states/selectors/coreViewFromViewIdFamilySelector.ts index 5976508155..f7d62607a6 100644 --- a/packages/twenty-front/src/modules/views/states/selectors/coreViewFromViewIdFamilySelector.ts +++ b/packages/twenty-front/src/modules/views/states/selectors/coreViewFromViewIdFamilySelector.ts @@ -1,5 +1,5 @@ import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { type View } from '@/views/types/View'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; @@ -11,7 +11,7 @@ export const coreViewFromViewIdFamilySelector = createAtomFamilySelector< get: ({ viewId }) => ({ get }) => { - const coreViews = get(coreViewsState); + const coreViews = get(coreViewsSelector); const views = coreViews.map(convertCoreViewToView); return views?.find((view) => view.id === viewId); }, diff --git a/packages/twenty-front/src/modules/views/states/selectors/coreViewIdsFromObjectMetadataItemFamilySelector.ts b/packages/twenty-front/src/modules/views/states/selectors/coreViewIdsFromObjectMetadataItemFamilySelector.ts index 8a107edc8f..82cbde171d 100644 --- a/packages/twenty-front/src/modules/views/states/selectors/coreViewIdsFromObjectMetadataItemFamilySelector.ts +++ b/packages/twenty-front/src/modules/views/states/selectors/coreViewIdsFromObjectMetadataItemFamilySelector.ts @@ -1,4 +1,4 @@ -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; @@ -8,7 +8,7 @@ export const coreViewIdsFromObjectMetadataItemFamilySelector = get: ({ objectMetadataItemId }) => ({ get }) => { - const coreViews = get(coreViewsState); + const coreViews = get(coreViewsSelector); const views = coreViews.map(convertCoreViewToView); return views .filter((view) => view.objectMetadataId === objectMetadataItemId) diff --git a/packages/twenty-front/src/modules/views/states/selectors/coreViewLengthFamilySelector.ts b/packages/twenty-front/src/modules/views/states/selectors/coreViewLengthFamilySelector.ts index d28d2654e2..78c23308f2 100644 --- a/packages/twenty-front/src/modules/views/states/selectors/coreViewLengthFamilySelector.ts +++ b/packages/twenty-front/src/modules/views/states/selectors/coreViewLengthFamilySelector.ts @@ -1,10 +1,10 @@ -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; export const coreViewLengthFamilySelector = createAtomSelector({ key: 'coreViewLengthFamilySelector', get: ({ get }) => { - const coreViews = get(coreViewsState); + const coreViews = get(coreViewsSelector); return coreViews?.length ?? 0; }, }); diff --git a/packages/twenty-front/src/modules/views/states/selectors/coreViewsByObjectMetadataIdFamilySelector.ts b/packages/twenty-front/src/modules/views/states/selectors/coreViewsByObjectMetadataIdFamilySelector.ts index f5f277a771..5df272e6f1 100644 --- a/packages/twenty-front/src/modules/views/states/selectors/coreViewsByObjectMetadataIdFamilySelector.ts +++ b/packages/twenty-front/src/modules/views/states/selectors/coreViewsByObjectMetadataIdFamilySelector.ts @@ -1,29 +1,16 @@ -import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; -import { createAtomWritableFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomWritableFamilySelector'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; +import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; export const coreViewsByObjectMetadataIdFamilySelector = - createAtomWritableFamilySelector({ + createAtomFamilySelector({ key: 'coreViewsByObjectMetadataIdFamilySelector', get: (objectMetadataId) => ({ get }) => { - const coreViews = get(coreViewsState); + const coreViews = get(coreViewsSelector); return coreViews.filter( (view) => view.objectMetadataId === objectMetadataId, ); }, - set: - (objectMetadataId) => - ({ get, set }, newViewsForObject) => { - const currentCoreViews = get(coreViewsState); - const viewsFromOtherObjects = currentCoreViews.filter( - (view) => view.objectMetadataId !== objectMetadataId, - ); - const newCoreViews = [ - ...viewsFromOtherObjects, - ...(Array.isArray(newViewsForObject) ? newViewsForObject : []), - ]; - set(coreViewsState, newCoreViews); - }, }); diff --git a/packages/twenty-front/src/modules/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector.ts b/packages/twenty-front/src/modules/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector.ts index 78a7354dee..002f54a0ee 100644 --- a/packages/twenty-front/src/modules/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector.ts +++ b/packages/twenty-front/src/modules/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector.ts @@ -1,5 +1,5 @@ import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; -import { coreViewsState } from '@/views/states/coreViewState'; +import { coreViewsSelector } from '@/views/states/selectors/coreViewsSelector'; import { type View } from '@/views/types/View'; import { ViewType } from '@/views/types/ViewType'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; @@ -10,7 +10,7 @@ export const coreViewsFromObjectMetadataItemFamilySelector = get: ({ objectMetadataItemId }) => ({ get }) => { - const coreViews = get(coreViewsState); + const coreViews = get(coreViewsSelector); const views = coreViews.map(convertCoreViewToView); return views .filter( diff --git a/packages/twenty-front/src/modules/views/states/selectors/coreViewsSelector.ts b/packages/twenty-front/src/modules/views/states/selectors/coreViewsSelector.ts new file mode 100644 index 0000000000..7f1cf0bf10 --- /dev/null +++ b/packages/twenty-front/src/modules/views/states/selectors/coreViewsSelector.ts @@ -0,0 +1,95 @@ +import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; +import { type FlatView } from '@/metadata-store/types/FlatView'; +import { type FlatViewField } from '@/metadata-store/types/FlatViewField'; +import { type FlatViewFieldGroup } from '@/metadata-store/types/FlatViewFieldGroup'; +import { type FlatViewFilter } from '@/metadata-store/types/FlatViewFilter'; +import { type FlatViewFilterGroup } from '@/metadata-store/types/FlatViewFilterGroup'; +import { type FlatViewGroup } from '@/metadata-store/types/FlatViewGroup'; +import { type FlatViewSort } from '@/metadata-store/types/FlatViewSort'; +import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; +import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; + +export const coreViewsSelector = createAtomSelector({ + key: 'coreViewsSelector', + get: ({ get }) => { + const flatViews = get(metadataStoreState, 'views').current as FlatView[]; + const flatViewFields = get(metadataStoreState, 'viewFields') + .current as FlatViewField[]; + const flatViewFilters = get(metadataStoreState, 'viewFilters') + .current as FlatViewFilter[]; + const flatViewSorts = get(metadataStoreState, 'viewSorts') + .current as FlatViewSort[]; + const flatViewGroups = get(metadataStoreState, 'viewGroups') + .current as FlatViewGroup[]; + const flatViewFilterGroups = get(metadataStoreState, 'viewFilterGroups') + .current as FlatViewFilterGroup[]; + const flatViewFieldGroups = get(metadataStoreState, 'viewFieldGroups') + .current as FlatViewFieldGroup[]; + + const viewFieldsByViewId = new Map(); + const viewFiltersByViewId = new Map(); + const viewSortsByViewId = new Map(); + const viewGroupsByViewId = new Map(); + const viewFilterGroupsByViewId = new Map(); + const viewFieldGroupsByViewId = new Map(); + + for (const field of flatViewFields) { + const existing = viewFieldsByViewId.get(field.viewId) ?? []; + existing.push(field); + viewFieldsByViewId.set(field.viewId, existing); + } + + for (const filter of flatViewFilters) { + const existing = viewFiltersByViewId.get(filter.viewId) ?? []; + existing.push(filter); + viewFiltersByViewId.set(filter.viewId, existing); + } + + for (const sort of flatViewSorts) { + const existing = viewSortsByViewId.get(sort.viewId) ?? []; + existing.push(sort); + viewSortsByViewId.set(sort.viewId, existing); + } + + for (const group of flatViewGroups) { + const existing = viewGroupsByViewId.get(group.viewId) ?? []; + existing.push(group); + viewGroupsByViewId.set(group.viewId, existing); + } + + for (const filterGroup of flatViewFilterGroups) { + if ('viewId' in filterGroup && typeof filterGroup.viewId === 'string') { + const existing = viewFilterGroupsByViewId.get(filterGroup.viewId) ?? []; + existing.push(filterGroup); + viewFilterGroupsByViewId.set(filterGroup.viewId, existing); + } + } + + for (const fieldGroup of flatViewFieldGroups) { + if ('viewId' in fieldGroup && typeof fieldGroup.viewId === 'string') { + const existing = viewFieldGroupsByViewId.get(fieldGroup.viewId) ?? []; + existing.push(fieldGroup); + viewFieldGroupsByViewId.set(fieldGroup.viewId, existing); + } + } + + return flatViews.map((view) => ({ + ...view, + viewFields: viewFieldsByViewId.get(view.id) ?? [], + viewFilters: viewFiltersByViewId.get(view.id) ?? [], + viewSorts: viewSortsByViewId.get(view.id) ?? [], + viewGroups: viewGroupsByViewId.get(view.id) ?? [], + viewFilterGroups: viewFilterGroupsByViewId.get(view.id) ?? [], + viewFieldGroups: (viewFieldGroupsByViewId.get(view.id) ?? []).map( + (fieldGroup) => ({ + ...fieldGroup, + viewFields: (viewFieldsByViewId.get(view.id) ?? []).filter( + (field) => + 'viewFieldGroupId' in field && + field.viewFieldGroupId === fieldGroup.id, + ), + }), + ), + })) as CoreViewWithRelations[]; + }, +}); diff --git a/packages/twenty-front/src/modules/views/types/CoreViewWithoutRelations.ts b/packages/twenty-front/src/modules/views/types/CoreViewWithoutRelations.ts deleted file mode 100644 index 8299b61d0c..0000000000 --- a/packages/twenty-front/src/modules/views/types/CoreViewWithoutRelations.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type CoreView } from '~/generated-metadata/graphql'; - -export type CoreViewWithoutRelations = Exclude< - CoreView, - | 'viewFields' - | 'viewFieldGroups' - | 'viewGroups' - | 'viewFilters' - | 'viewFilterGroups' - | 'viewSorts' ->; diff --git a/packages/twenty-front/src/modules/views/view-picker/hooks/useDestroyViewFromCurrentState.ts b/packages/twenty-front/src/modules/views/view-picker/hooks/useDestroyViewFromCurrentState.ts index a2c55652e9..60bd22530b 100644 --- a/packages/twenty-front/src/modules/views/view-picker/hooks/useDestroyViewFromCurrentState.ts +++ b/packages/twenty-front/src/modules/views/view-picker/hooks/useDestroyViewFromCurrentState.ts @@ -7,7 +7,6 @@ import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/use import { usePerformViewAPIPersist } from '@/views/hooks/internal/usePerformViewAPIPersist'; import { useChangeView } from '@/views/hooks/useChangeView'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; -import { coreViewsByObjectMetadataIdFamilySelector } from '@/views/states/selectors/coreViewsByObjectMetadataIdFamilySelector'; import { coreViewsFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector'; import { useCloseAndResetViewPicker } from '@/views/view-picker/hooks/useCloseAndResetViewPicker'; import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState'; @@ -72,18 +71,10 @@ export const useDestroyViewFromCurrentState = (viewBarInstanceId?: string) => { changeView(remainingViews[0].id); } - store.set( - coreViewsByObjectMetadataIdFamilySelector.selectorFamily( - objectMetadataItem.id, - ), - (views) => views.filter((view) => view.id !== viewPickerReferenceViewId), - ); - await performViewAPIDestroy({ id: viewPickerReferenceViewId }); }, [ currentView, closeAndResetViewPicker, - objectMetadataItem.id, changeView, performViewAPIDestroy, store, diff --git a/packages/twenty-front/src/pages/auth/PasswordReset.tsx b/packages/twenty-front/src/pages/auth/PasswordReset.tsx index 55dcd9b4d6..d4502fbbb8 100644 --- a/packages/twenty-front/src/pages/auth/PasswordReset.tsx +++ b/packages/twenty-front/src/pages/auth/PasswordReset.tsx @@ -2,7 +2,7 @@ import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLo import { Logo } from '@/auth/components/Logo'; import { Title } from '@/auth/components/Title'; import { useAuth } from '@/auth/hooks/useAuth'; -import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair'; import { currentUserState } from '@/auth/states/currentUserState'; import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState'; import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex'; @@ -102,7 +102,7 @@ export const PasswordReset = () => { const [isTargetUserPasswordSet, setIsTargetUserPasswordSet] = useState(false); const passwordResetToken = useParams().passwordResetToken; - const isLoggedIn = useIsLogged(); + const hasAccessTokenPair = useHasAccessTokenPair(); const { control, handleSubmit } = useForm
({ mode: 'onChange', @@ -179,7 +179,7 @@ export const PasswordReset = () => { currentUser ? { ...currentUser, hasPassword: true } : currentUser, ); - if (isLoggedIn) { + if (hasAccessTokenPair) { enqueueSuccessSnackBar({ message: successMessage, }); diff --git a/packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx b/packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx index 1c12f1bd17..974899479f 100644 --- a/packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx +++ b/packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx @@ -9,8 +9,6 @@ import { Logo } from '@/auth/components/Logo'; import { SubTitle } from '@/auth/components/SubTitle'; import { Title } from '@/auth/components/Title'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { useFetchAndLoadIndexViews } from '@/metadata-store/hooks/useFetchAndLoadIndexViews'; -import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus'; import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; @@ -70,9 +68,6 @@ export const CreateWorkspace = () => { const { t } = useLingui(); const { enqueueErrorSnackBar } = useSnackBar(); const setNextOnboardingStatus = useSetNextOnboardingStatus(); - const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems(); - const { fetchAndLoadIndexViews } = useFetchAndLoadIndexViews(); - const { loadCurrentUser } = useLoadCurrentUser(); const [activateWorkspace] = useMutation(ActivateWorkspaceDocument); const [pendingCreationLoaderStep, setPendingCreationLoaderStep] = useState( @@ -126,10 +121,6 @@ export const CreateWorkspace = () => { throw result.error ?? new Error(t`Unknown error`); } - await refreshObjectMetadataItems(); - - await fetchAndLoadIndexViews(); - await loadCurrentUser(); setNextOnboardingStatus(); } catch (error: any) { @@ -144,8 +135,6 @@ export const CreateWorkspace = () => { activateWorkspace, enqueueErrorSnackBar, loadCurrentUser, - refreshObjectMetadataItems, - fetchAndLoadIndexViews, setNextOnboardingStatus, t, ], diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx index 4faad0dc7d..f9d43486a7 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx @@ -285,7 +285,6 @@ export const SettingsObjectFieldEdit = () => { const deleteResult = await deleteMetadataField({ idToDelete: fieldMetadataItem.id, - objectMetadataId: objectMetadataItem.id, }); if (deleteResult.status === 'successful') { diff --git a/packages/twenty-front/src/pages/settings/profile/appearance/components/LocalePicker.tsx b/packages/twenty-front/src/pages/settings/profile/appearance/components/LocalePicker.tsx index be6cde0ed0..85bf3c6463 100644 --- a/packages/twenty-front/src/pages/settings/profile/appearance/components/LocalePicker.tsx +++ b/packages/twenty-front/src/pages/settings/profile/appearance/components/LocalePicker.tsx @@ -7,9 +7,8 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { getDateFnsLocale } from '@/ui/field/display/utils/getDateFnsLocale'; import { Select } from '@/ui/input/components/Select'; -import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; +import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; import { useStore } from 'jotai'; -import { useRefreshAllCoreViews } from '@/views/hooks/useRefreshAllCoreViews'; import { useLingui } from '@lingui/react/macro'; import { enUS } from 'date-fns/locale'; import { APP_LOCALES } from 'twenty-shared/translations'; @@ -33,10 +32,7 @@ export const LocalePicker = () => { ); const { updateOneRecord } = useUpdateOneRecord(); - const { refreshObjectMetadataItems } = - useRefreshObjectMetadataItems('network-only'); - - const { refreshAllCoreViews } = useRefreshAllCoreViews('network-only'); + const { resetMetadataStore } = useMetadataStore(); const updateWorkspaceMember = async (changedFields: any) => { if (!currentWorkspaceMember?.id) { @@ -77,8 +73,7 @@ export const LocalePicker = () => { // oxlint-disable-next-line no-console console.log('Failed to save locale to localStorage:', error); } - await refreshObjectMetadataItems(); - await refreshAllCoreViews(); + resetMetadataStore(); }; const unsortedLocaleOptions: Array<{ diff --git a/packages/twenty-front/src/testing/decorators/PageDecorator.tsx b/packages/twenty-front/src/testing/decorators/PageDecorator.tsx index 661d0ad1dd..fae58488d7 100644 --- a/packages/twenty-front/src/testing/decorators/PageDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/PageDecorator.tsx @@ -14,10 +14,12 @@ import { ClientConfigProviderEffect } from '@/client-config/components/ClientCon import { ApolloCoreClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloCoreClientMockedProvider'; import { DefaultLayout } from '@/ui/layout/page/components/DefaultLayout'; -import { MetadataGater } from '@/metadata-store/components/MetadataGater'; -import { MetadataProviderInitialEffects } from '@/metadata-store/effect-components/MetadataProviderInitialEffects'; -import { IsAppMetadataReadyEffect } from '@/metadata-store/effect-components/IsAppMetadataReadyEffect'; +import { MinimalMetadataGater } from '@/metadata-store/components/MinimalMetadataGater'; +import { UserMetadataProviderInitialEffect } from '@/metadata-store/effect-components/UserMetadataProviderInitialEffect'; +import { IsMinimalMetadataReadyEffect } from '@/metadata-store/effect-components/IsMinimalMetadataReadyEffect'; +import { MinimalMetadataLoadEffect } from '@/metadata-store/effect-components/MinimalMetadataLoadEffect'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { useState } from 'react'; import { ClientConfigProvider } from '~/modules/client-config/components/ClientConfigProvider'; import { mockedApolloClient } from '~/testing/mockedApolloClient'; @@ -72,8 +74,10 @@ const ApolloStorybookDevLogEffect = () => { await dynamicActivate(SOURCE_LOCALE); const Providers = () => { + const [store] = useState(() => jotaiStore); + return ( - + @@ -82,10 +86,11 @@ const Providers = () => { - - + + + - + @@ -100,7 +105,7 @@ const Providers = () => { - + diff --git a/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx b/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx index 71f8bb5acc..766bf3bd40 100644 --- a/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx @@ -1,14 +1,11 @@ import { type Decorator } from '@storybook/react-vite'; -import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; -import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext'; import { getCommandMenuIdFromRecordIndexId } from '@/command-menu-item/utils/getCommandMenuIdFromRecordIndexId'; +import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext'; import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { labelIdentifierFieldMetadataItemSelector } from '@/object-metadata/states/labelIdentifierFieldMetadataItemSelector'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper'; @@ -28,9 +25,11 @@ import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/u import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId'; import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue'; +import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; -import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; import { type View } from '@/views/types/View'; import { mapViewFieldToRecordField } from '@/views/utils/mapViewFieldToRecordField'; @@ -38,6 +37,7 @@ import { useEffect, useMemo } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { mockedCompanyRecords } from '~/testing/mock-data/generated/data/companies/mock-companies-data'; import { mockedCoreViews } from '~/testing/mock-data/generated/metadata/views/mock-views-data'; +import { setTestCoreViewsInMetadataStore } from '~/testing/utils/setTestCoreViewsInMetadataStore'; const companyView = mockedCoreViews.find((v) => v.name === 'All Companies')!; @@ -57,8 +57,6 @@ const InternalTableStateLoaderEffect = ({ MAIN_CONTEXT_STORE_INSTANCE_ID, ); - const setCoreViews = useSetAtomState(coreViewsState); - const setRecordTableData = useSetRecordTableData({ recordTableId, }); @@ -81,7 +79,9 @@ const InternalTableStateLoaderEffect = ({ setCurrentRecordFields(recordFields); - setCoreViews([view as unknown as CoreViewWithRelations]); + setTestCoreViewsInMetadataStore(jotaiStore, [ + view as unknown as CoreViewWithRelations, + ]); setContextStoreCurrentViewId(view.id); }, [ @@ -91,7 +91,6 @@ const InternalTableStateLoaderEffect = ({ view, setCurrentRecordFields, setContextStoreCurrentViewId, - setCoreViews, ]); return null; diff --git a/packages/twenty-front/src/testing/graphqlMocks.ts b/packages/twenty-front/src/testing/graphqlMocks.ts index d79c387fd8..56de67ab44 100644 --- a/packages/twenty-front/src/testing/graphqlMocks.ts +++ b/packages/twenty-front/src/testing/graphqlMocks.ts @@ -1,6 +1,6 @@ -import { getOperationName } from '~/utils/getOperationName'; import { parse, type FieldNode } from 'graphql'; import { graphql, http, HttpResponse, type GraphQLQuery } from 'msw'; +import { getOperationName } from '~/utils/getOperationName'; import { TRACK_ANALYTICS } from '@/analytics/graphql/queries/track'; import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries'; @@ -9,10 +9,10 @@ import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { mockedClientConfig } from '~/testing/mock-data/config'; import { mockedNoteRecords } from '~/testing/mock-data/generated/data/notes/mock-notes-data'; import { mockedPersonRecords } from '~/testing/mock-data/generated/data/people/mock-people-data'; +import { mockedWorkspaceMemberRecords } from '~/testing/mock-data/generated/data/workspaceMembers/mock-workspaceMembers-data'; +import { mockedCoreViews } from '~/testing/mock-data/generated/metadata/views/mock-views-data'; import { mockedPublicWorkspaceDataBySubdomain } from '~/testing/mock-data/publicWorkspaceDataBySubdomain'; import { mockedUserData } from '~/testing/mock-data/users'; -import { mockedCoreViews } from '~/testing/mock-data/generated/metadata/views/mock-views-data'; -import { mockedWorkspaceMemberRecords } from '~/testing/mock-data/generated/data/workspaceMembers/mock-workspaceMembers-data'; import { GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN } from '@/auth/graphql/queries/getPublicWorkspaceDataByDomain'; import { LIST_PLANS } from '@/billing/graphql/queries/listPlans'; @@ -20,22 +20,25 @@ import { GET_ROLES } from '@/settings/roles/graphql/queries/getRolesQuery'; import { isDefined } from 'twenty-shared/utils'; import { mockBillingPlans } from '~/testing/mock-data/billing-plans'; import { mockedCompanyRecords } from '~/testing/mock-data/generated/data/companies/mock-companies-data'; +import { mockedTaskRecords } from '~/testing/mock-data/generated/data/tasks/mock-tasks-data'; import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/metadata/objects/mock-objects-metadata'; import { mockedRoles } from '~/testing/mock-data/generated/metadata/roles/mock-roles-data'; -import { mockedTaskRecords } from '~/testing/mock-data/generated/data/tasks/mock-tasks-data'; +import { type Task } from '@/activities/types/Task'; +import { FIND_MINIMAL_METADATA } from '@/metadata-store/graphql/queries/findMinimalMetadata'; +import { getConnectionTypename } from '@/object-record/cache/utils/getConnectionTypename'; +import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename'; +import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo'; +import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode'; +import { mockedApiKeys } from '~/testing/mock-data/generated/metadata/api-keys/mock-api-keys-data'; +import { mockedMinimalMetadata } from '~/testing/mock-data/generated/metadata/minimal/mock-minimal-metadata'; +import { mockedNavigationMenuItems } from '~/testing/mock-data/generated/metadata/navigation-menu-items/mock-navigation-menu-items-data'; import { getWorkflowMock, getWorkflowVersionsMock, workflowQueryResult, } from '~/testing/mock-data/workflow'; import { oneSucceededWorkflowRunQueryResult } from '~/testing/mock-data/workflow-run'; -import { type Task } from '@/activities/types/Task'; -import { getConnectionTypename } from '@/object-record/cache/utils/getConnectionTypename'; -import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename'; -import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo'; -import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode'; -import { mockedApiKeys } from '~/testing/mock-data/generated/metadata/api-keys/mock-api-keys-data'; const peopleMock = [...mockedPersonRecords]; const companiesMock = [...mockedCompanyRecords]; @@ -172,6 +175,40 @@ export const graphqlMocks = { }); }, ), + metadataGraphql.query(getOperationName(FIND_MINIMAL_METADATA) ?? '', () => { + return HttpResponse.json({ + data: { minimalMetadata: mockedMinimalMetadata }, + }); + }), + metadataGraphql.query('FindAllCoreViews', () => { + return HttpResponse.json({ + data: { getCoreViews: mockedCoreViews }, + }); + }), + metadataGraphql.query('FindFieldsWidgetCoreViews', () => { + return HttpResponse.json({ + data: { + getCoreViews: mockedCoreViews.filter( + (view) => view.type === 'FIELDS_WIDGET', + ), + }, + }); + }), + metadataGraphql.query('FindAllRecordPageLayouts', () => { + return HttpResponse.json({ + data: { getPageLayouts: [] }, + }); + }), + metadataGraphql.query('FindManyLogicFunctions', () => { + return HttpResponse.json({ + data: { findManyLogicFunctions: [] }, + }); + }), + metadataGraphql.query('FindManyNavigationMenuItems', () => { + return HttpResponse.json({ + data: { navigationMenuItems: mockedNavigationMenuItems }, + }); + }), graphql.query('SearchPeople', () => { return HttpResponse.json({ data: { diff --git a/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx b/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx index 68933d7526..9c351e924a 100644 --- a/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx +++ b/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx @@ -12,7 +12,7 @@ export const JestObjectMetadataItemSetter = ({ children: ReactNode; objectMetadataItems?: ObjectMetadataItem[]; }) => { - const { updateDraft, applyChanges, resetMetadataStore } = useMetadataStore(); + const { updateDraft, applyChanges } = useMetadataStore(); const [isLoaded, setIsLoaded] = useState(false); useEffect(() => { @@ -20,17 +20,12 @@ export const JestObjectMetadataItemSetter = ({ const { flatObjects, flatFields, flatIndexes } = splitObjectMetadataItemWithRelated(items); - // Reset first so updateDraft always proceeds and writes to localStorage. - // Without this, atomWithStorage's onMount re-reads from empty localStorage - // and overwrites in-memory values when the store is reused across tests. - resetMetadataStore(); - updateDraft('objectMetadataItems', flatObjects); updateDraft('fieldMetadataItems', flatFields); updateDraft('indexMetadataItems', flatIndexes); applyChanges(); setIsLoaded(true); - }, [objectMetadataItems, updateDraft, applyChanges, resetMetadataStore]); + }, [objectMetadataItems, updateDraft, applyChanges]); return isLoaded ? <>{children} : null; }; diff --git a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx index c4a10f8330..2a524b3572 100644 --- a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx +++ b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx @@ -1,14 +1,17 @@ import { type MockedResponse } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing/react'; import { Provider as JotaiProvider } from 'jotai'; -import { type ReactNode } from 'react'; +import { type ReactNode, useState } from 'react'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper'; import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext'; -import { resetJotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { + jotaiStore, + resetJotaiStore, +} from '@/ui/utilities/state/jotai/jotaiStore'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { type InMemoryCache } from '@apollo/client'; import type { Store } from 'jotai/vanilla/store'; @@ -28,33 +31,39 @@ export const getJestMetadataAndApolloMocksWrapper = ({ onInitializeJotaiStore?: (store: Store) => void; objectMetadataItems?: ObjectMetadataItem[]; }) => { - const store = resetJotaiStore(); + resetJotaiStore(); - onInitializeJotaiStore?.(store); + return ({ children }: { children: ReactNode }) => { + const [store] = useState(() => { + const currentStore = jotaiStore; + onInitializeJotaiStore?.(currentStore); + return currentStore; + }); - return ({ children }: { children: ReactNode }) => ( - - - - - - + + + + - - {children} - - - - - - - - ); + + {children} + + + + + + + + ); + }; }; diff --git a/packages/twenty-front/src/testing/mock-data/generated/metadata/minimal/mock-minimal-metadata.ts b/packages/twenty-front/src/testing/mock-data/generated/metadata/minimal/mock-minimal-metadata.ts new file mode 100644 index 0000000000..9e805da11f --- /dev/null +++ b/packages/twenty-front/src/testing/mock-data/generated/metadata/minimal/mock-minimal-metadata.ts @@ -0,0 +1,864 @@ +/* oxlint-disable */ +// @ts-nocheck +import { type MinimalMetadata } from '@/metadata-store/types/MinimalMetadata'; + +// This file was automatically generated — do not edit manually. + +// prettier-ignore +export const mockedMinimalMetadata: MinimalMetadata = +{ + "objectMetadataItems": [ + { + "id": "e24d3eee-cab9-48c9-9ec5-26192fd43c47", + "nameSingular": "attachment", + "namePlural": "attachments", + "labelSingular": "Attachment", + "labelPlural": "Attachments", + "icon": "IconFileImport", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "f8da2fb8-65bd-463a-b651-3c285eddc728", + "nameSingular": "blocklist", + "namePlural": "blocklists", + "labelSingular": "Blocklist", + "labelPlural": "Blocklists", + "icon": "IconForbid2", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "a4f42d2e-8955-43ee-86c6-54368eb442d2", + "nameSingular": "calendarChannelEventAssociation", + "namePlural": "calendarChannelEventAssociations", + "labelSingular": "Calendar Channel Event Association", + "labelPlural": "Calendar Channel Event Associations", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "618d3a73-85ac-4df2-a04f-e91ee413550a", + "nameSingular": "calendarChannel", + "namePlural": "calendarChannels", + "labelSingular": "Calendar Channel", + "labelPlural": "Calendar Channels", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "48547468-2e46-4a2a-a356-1eb449e7bd4e", + "nameSingular": "calendarEventParticipant", + "namePlural": "calendarEventParticipants", + "labelSingular": "Calendar event participant", + "labelPlural": "Calendar event participants", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "0dce0e6c-1a3d-4478-b03c-422105886383", + "nameSingular": "calendarEvent", + "namePlural": "calendarEvents", + "labelSingular": "Calendar event", + "labelPlural": "Calendar events", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "def5a0f9-6233-4572-8f58-3aba99bcf248", + "nameSingular": "company", + "namePlural": "companies", + "labelSingular": "Company", + "labelPlural": "Companies", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "11cb16db-271f-494d-a11e-8f5c8f4008d8", + "nameSingular": "connectedAccount", + "namePlural": "connectedAccounts", + "labelSingular": "Connected Account", + "labelPlural": "Connected Accounts", + "icon": "IconAt", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "bec46d6b-4fe4-45db-9bf0-6840864f0d21", + "nameSingular": "dashboard", + "namePlural": "dashboards", + "labelSingular": "Dashboard", + "labelPlural": "Dashboards", + "icon": "IconLayoutDashboard", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "f2ae50d3-41c6-4bb1-882a-8cd6eeb5b439", + "nameSingular": "favorite", + "namePlural": "favorites", + "labelSingular": "Favorite", + "labelPlural": "Favorites", + "icon": "IconHeart", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "d510c827-83bf-4d18-86b0-90183327a0ce", + "nameSingular": "favoriteFolder", + "namePlural": "favoriteFolders", + "labelSingular": "Favorite Folder", + "labelPlural": "Favorite Folders", + "icon": "IconFolder", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "9921d994-3888-43e4-b933-82eb199f9154", + "nameSingular": "messageChannelMessageAssociation", + "namePlural": "messageChannelMessageAssociations", + "labelSingular": "Message Channel Message Association", + "labelPlural": "Message Channel Message Associations", + "icon": "IconMessage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "1f1b593c-99ed-4849-8b27-6d8fb8f001af", + "nameSingular": "messageChannel", + "namePlural": "messageChannels", + "labelSingular": "Message Channel", + "labelPlural": "Message Channels", + "icon": "IconMessage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "13aa631f-8a01-491e-9e49-b24eaaeaac00", + "nameSingular": "messageFolder", + "namePlural": "messageFolders", + "labelSingular": "Message Folder", + "labelPlural": "Message Folders", + "icon": "IconFolder", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "c11d27bb-9e7d-4e07-aa2d-cb7f1930a2b2", + "nameSingular": "messageChannelMessageAssociationMessageFolder", + "namePlural": "messageChannelMessageAssociationMessageFolders", + "labelSingular": "Message Channel Message Association Message Folder", + "labelPlural": "Message Channel Message Association Message Folders", + "icon": "IconFolder", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "5267a212-5850-4dba-be1d-5f545cc8b1f3", + "nameSingular": "messageParticipant", + "namePlural": "messageParticipants", + "labelSingular": "Message Participant", + "labelPlural": "Message Participants", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "7caaa1ed-5955-4f33-a9a8-3f1f7834a957", + "nameSingular": "messageThread", + "namePlural": "messageThreads", + "labelSingular": "Message Thread", + "labelPlural": "Message Threads", + "icon": "IconMessage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "8a3aecb7-cf19-4d57-b8e5-d4480326c145", + "nameSingular": "message", + "namePlural": "messages", + "labelSingular": "Message", + "labelPlural": "Messages", + "icon": "IconMessage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "599e8013-e826-420e-a4d4-135826550a96", + "nameSingular": "note", + "namePlural": "notes", + "labelSingular": "Note", + "labelPlural": "Notes", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "1d0b6b96-c961-417d-aa33-f399fd975af3", + "nameSingular": "noteTarget", + "namePlural": "noteTargets", + "labelSingular": "Note Target", + "labelPlural": "Note Targets", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "2b9fd1c5-90ef-4d86-adbd-1a1df4bfd2da", + "nameSingular": "opportunity", + "namePlural": "opportunities", + "labelSingular": "Opportunity", + "labelPlural": "Opportunities", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "b6e21d5b-97f4-4d46-b3eb-84cd548fcf3e", + "nameSingular": "person", + "namePlural": "people", + "labelSingular": "Person", + "labelPlural": "People", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "2036c405-31bb-4f93-821a-79af46f9a00a", + "nameSingular": "task", + "namePlural": "tasks", + "labelSingular": "Task", + "labelPlural": "Tasks", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "a8d747b8-f2f1-4309-a011-19e95312a9c8", + "nameSingular": "taskTarget", + "namePlural": "taskTargets", + "labelSingular": "Task Target", + "labelPlural": "Task Targets", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "16ae84e9-e4eb-4a1a-8f7e-cd39c617d105", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities", + "labelSingular": "Timeline Activity", + "labelPlural": "Timeline Activities", + "icon": "IconTimelineEvent", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "769ea79c-a2cb-49ce-ae1d-73ce37a5c4d0", + "nameSingular": "workflow", + "namePlural": "workflows", + "labelSingular": "Workflow", + "labelPlural": "Workflows", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "bf481196-2767-485f-9f7d-982a96463d2f", + "nameSingular": "workflowAutomatedTrigger", + "namePlural": "workflowAutomatedTriggers", + "labelSingular": "Workflow Automated Trigger", + "labelPlural": "Workflow Automated Triggers", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "14ef07a6-5304-479a-8b33-dbe62bae2cef", + "nameSingular": "workflowRun", + "namePlural": "workflowRuns", + "labelSingular": "Workflow Run", + "labelPlural": "Workflow Runs", + "icon": "IconHistoryToggle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "b340fed6-3848-4571-9d18-7522d53e9ee9", + "nameSingular": "workflowVersion", + "namePlural": "workflowVersions", + "labelSingular": "Workflow Version", + "labelPlural": "Workflow Versions", + "icon": "IconVersions", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "45951e41-0540-48e1-8cb4-b18d68771481", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers", + "labelSingular": "Workspace Member", + "labelPlural": "Workspace Members", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isRemote": false + }, + { + "id": "8c77d57d-1c06-4550-bb7d-4dff29b16889", + "nameSingular": "rocket", + "namePlural": "rockets", + "labelSingular": "Rocket", + "labelPlural": "Rockets", + "icon": "IconRocket", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "96a6d580-c305-4a5e-9310-96d6a705c344", + "nameSingular": "pet", + "namePlural": "pets", + "labelSingular": "Pet", + "labelPlural": "Pets", + "icon": "IconCat", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "a32db57b-c2f1-4409-a9ce-5b80434fb1ce", + "nameSingular": "surveyResult", + "namePlural": "surveyResults", + "labelSingular": "Survey result", + "labelPlural": "Survey results", + "icon": "IconRulerMeasure", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "7e995e28-ad5d-4bef-b248-58f261584ed8", + "nameSingular": "employmentHistory", + "namePlural": "employmentHistories", + "labelSingular": "Employment History", + "labelPlural": "Employment Histories", + "icon": "IconBriefcase", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "4ea2d7fc-d992-49d0-877c-794f2661ee53", + "nameSingular": "petCareAgreement", + "namePlural": "petCareAgreements", + "labelSingular": "Pet Care Agreement", + "labelPlural": "Pet Care Agreements", + "icon": "IconPaw", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "05271aca-8403-491a-9971-d2a1fe623401", + "nameSingular": "vla", + "namePlural": "vlas", + "labelSingular": "Vla", + "labelPlural": "Vlas", + "icon": "IconListNumbers", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "31968ed5-2aa4-423f-acff-066565377f53", + "nameSingular": "tea", + "namePlural": "teas", + "labelSingular": "Tea", + "labelPlural": "Teas", + "icon": "IconListNumbers", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isRemote": false + }, + { + "id": "44b00550-b1cb-4e25-b18e-7c6bac97903b", + "nameSingular": "toto", + "namePlural": "totos", + "labelSingular": "Toto", + "labelPlural": "Totos", + "icon": "IconListNumbers", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isRemote": false + } + ], + "views": [ + { + "id": "f04ef9fc-b7f1-4e09-90aa-f6ecf00e70b5", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "e24d3eee-cab9-48c9-9ec5-26192fd43c47" + }, + { + "id": "7dba738a-6757-4f49-8a2a-735df085fafb", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "f8da2fb8-65bd-463a-b651-3c285eddc728" + }, + { + "id": "53d52e42-d15f-47eb-8b44-2b4b1abebc13", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "618d3a73-85ac-4df2-a04f-e91ee413550a" + }, + { + "id": "f583f6f0-2d3a-4295-82e8-3ece5f9e9dfa", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "a4f42d2e-8955-43ee-86c6-54368eb442d2" + }, + { + "id": "e20d1f78-cd39-4589-a431-b9d7064f145d", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "0dce0e6c-1a3d-4478-b03c-422105886383" + }, + { + "id": "6b7d26f9-a003-43d7-a5e0-e7d45dba88aa", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "48547468-2e46-4a2a-a356-1eb449e7bd4e" + }, + { + "id": "4fbe1837-e5b2-4d8b-9f0d-0191f11da17f", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "def5a0f9-6233-4572-8f58-3aba99bcf248" + }, + { + "id": "d0ee877c-57f4-4e89-b676-1bf78ae7a5eb", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "11cb16db-271f-494d-a11e-8f5c8f4008d8" + }, + { + "id": "f160aaf2-3e6a-4f5c-a61c-551d77c9bef3", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "bec46d6b-4fe4-45db-9bf0-6840864f0d21" + }, + { + "id": "2146a413-9339-49d4-9718-32815117e545", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "f2ae50d3-41c6-4bb1-882a-8cd6eeb5b439" + }, + { + "id": "287a7082-d5dc-4fcd-998f-8bb3c531c43b", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "d510c827-83bf-4d18-86b0-90183327a0ce" + }, + { + "id": "400e57d4-5ef5-4ba8-9b92-cae859471bb8", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "8a3aecb7-cf19-4d57-b8e5-d4480326c145" + }, + { + "id": "76f1ff8f-3b80-4b7f-8b50-0d9617da717f", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "1f1b593c-99ed-4849-8b27-6d8fb8f001af" + }, + { + "id": "f7edd044-5371-4f94-9318-723fdd70777b", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "9921d994-3888-43e4-b933-82eb199f9154" + }, + { + "id": "ad308c35-47f2-4d4d-8cd1-94544dafa0a8", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "c11d27bb-9e7d-4e07-aa2d-cb7f1930a2b2" + }, + { + "id": "04b4ba8c-55ca-4287-be43-7f073b055c90", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "13aa631f-8a01-491e-9e49-b24eaaeaac00" + }, + { + "id": "d524aade-4d12-4745-b693-243547a865d1", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "5267a212-5850-4dba-be1d-5f545cc8b1f3" + }, + { + "id": "f1557881-60a7-487d-98cf-fb2b1f248e2b", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "7caaa1ed-5955-4f33-a9a8-3f1f7834a957" + }, + { + "id": "3ea68976-f3ea-4800-919f-d10b6b8b82df", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "599e8013-e826-420e-a4d4-135826550a96" + }, + { + "id": "60927cc3-5dae-4bc2-9928-883b3c6ef436", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "1d0b6b96-c961-417d-aa33-f399fd975af3" + }, + { + "id": "014bcf01-837e-4ac0-8fa7-db8eb5853e0c", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "2b9fd1c5-90ef-4d86-adbd-1a1df4bfd2da" + }, + { + "id": "4b998b70-db02-4f5f-8d11-ebe47beec39d", + "type": "KANBAN", + "key": null, + "objectMetadataId": "2b9fd1c5-90ef-4d86-adbd-1a1df4bfd2da" + }, + { + "id": "07f3ccf3-0e66-48b9-a25a-9e3146487ac0", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "b6e21d5b-97f4-4d46-b3eb-84cd548fcf3e" + }, + { + "id": "baa47df1-d385-4abf-b0f8-41b71a1d874f", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "2036c405-31bb-4f93-821a-79af46f9a00a" + }, + { + "id": "c2380957-32b4-4c97-b678-8bdbb688cfe7", + "type": "KANBAN", + "key": null, + "objectMetadataId": "2036c405-31bb-4f93-821a-79af46f9a00a" + }, + { + "id": "0fd4340c-1c76-452a-988d-be4f1864e95f", + "type": "TABLE", + "key": null, + "objectMetadataId": "2036c405-31bb-4f93-821a-79af46f9a00a" + }, + { + "id": "e75fac35-cda0-4513-9e6c-494f926269dc", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "a8d747b8-f2f1-4309-a011-19e95312a9c8" + }, + { + "id": "a8550e48-63e6-4d36-97ff-ad8d6c31ec4c", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "16ae84e9-e4eb-4a1a-8f7e-cd39c617d105" + }, + { + "id": "60a2a05b-3c27-4c0b-b70e-d9e3230101d6", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "769ea79c-a2cb-49ce-ae1d-73ce37a5c4d0" + }, + { + "id": "6f7c0068-a35e-46ad-bda7-095a27e25592", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "bf481196-2767-485f-9f7d-982a96463d2f" + }, + { + "id": "d55fe3ad-7d0a-449c-816e-fcaa8e2e0c3d", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "14ef07a6-5304-479a-8b33-dbe62bae2cef" + }, + { + "id": "d954fdb7-fd84-4427-8a16-e5e573ab8260", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "b340fed6-3848-4571-9d18-7522d53e9ee9" + }, + { + "id": "fe2fe310-d2d8-4dec-b9c7-47ec3296379f", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "45951e41-0540-48e1-8cb4-b18d68771481" + }, + { + "id": "21e62404-bb05-47e3-89da-f515cadeb82e", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "8c77d57d-1c06-4550-bb7d-4dff29b16889" + }, + { + "id": "a5d1291c-69f2-4f99-9c7b-92b3d86c3355", + "type": "FIELDS_WIDGET", + "key": null, + "objectMetadataId": "8c77d57d-1c06-4550-bb7d-4dff29b16889" + }, + { + "id": "f4044ef2-99a9-4ae5-a5e0-d5664dfcd4ee", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "96a6d580-c305-4a5e-9310-96d6a705c344" + }, + { + "id": "6bcb5114-483f-43db-bdb3-8315025d3a2e", + "type": "FIELDS_WIDGET", + "key": null, + "objectMetadataId": "96a6d580-c305-4a5e-9310-96d6a705c344" + }, + { + "id": "cc83a36a-4c40-4aa9-8529-13c5fc9efe41", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "a32db57b-c2f1-4409-a9ce-5b80434fb1ce" + }, + { + "id": "6b3fcd9a-3e51-4557-a54e-a9d838170e67", + "type": "FIELDS_WIDGET", + "key": null, + "objectMetadataId": "a32db57b-c2f1-4409-a9ce-5b80434fb1ce" + }, + { + "id": "53fc7567-b6b6-440e-a06b-5ed8bb7164fb", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "7e995e28-ad5d-4bef-b248-58f261584ed8" + }, + { + "id": "d8e8b80c-a95c-4e2b-9bee-e29e400d8221", + "type": "FIELDS_WIDGET", + "key": null, + "objectMetadataId": "7e995e28-ad5d-4bef-b248-58f261584ed8" + }, + { + "id": "d555d97b-041f-4b83-a7a5-93c526598ff8", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "4ea2d7fc-d992-49d0-877c-794f2661ee53" + }, + { + "id": "b3e7efcf-214e-41d7-8663-4e1bcb83fa7b", + "type": "FIELDS_WIDGET", + "key": null, + "objectMetadataId": "4ea2d7fc-d992-49d0-877c-794f2661ee53" + }, + { + "id": "5af3f071-074b-4a7b-9f6a-bd7e5c4d2b15", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "05271aca-8403-491a-9971-d2a1fe623401" + }, + { + "id": "7631f50d-8476-42ee-8af0-081bd364807e", + "type": "FIELDS_WIDGET", + "key": null, + "objectMetadataId": "05271aca-8403-491a-9971-d2a1fe623401" + }, + { + "id": "31a5ae29-df94-46c7-b248-29f871707b11", + "type": "TABLE", + "key": null, + "objectMetadataId": "def5a0f9-6233-4572-8f58-3aba99bcf248" + }, + { + "id": "54379c74-3248-4474-b6a8-511bf79a2c72", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "31968ed5-2aa4-423f-acff-066565377f53" + }, + { + "id": "2fb84c8b-b7a9-4cc6-b48e-b46a8b0f147a", + "type": "FIELDS_WIDGET", + "key": null, + "objectMetadataId": "31968ed5-2aa4-423f-acff-066565377f53" + }, + { + "id": "c1332d82-c75d-40da-9d30-dc44c7d93444", + "type": "TABLE", + "key": "INDEX", + "objectMetadataId": "44b00550-b1cb-4e25-b18e-7c6bac97903b" + }, + { + "id": "a32b5909-5c66-4d02-b753-6444808263a4", + "type": "FIELDS_WIDGET", + "key": null, + "objectMetadataId": "44b00550-b1cb-4e25-b18e-7c6bac97903b" + } + ], + "collectionHashes": [ + { + "collectionName": "fieldMetadata", + "hash": "ce13f1e8-f54e-480b-890e-ce867ea6b2fa" + }, + { + "collectionName": "objectMetadata", + "hash": "6a2f420e-84f6-4990-9433-7b7a53ff9abc" + }, + { + "collectionName": "view", + "hash": "00c71b28-94a6-42c2-9128-6c31f13709db" + }, + { + "collectionName": "viewField", + "hash": "7a4d98c9-85b1-44a2-9443-79fcb9f58e15" + }, + { + "collectionName": "viewFieldGroup", + "hash": "d1ce28ed-0117-4f3c-9c43-ebc188defcc6" + }, + { + "collectionName": "viewGroup", + "hash": "ffe35afe-0d3c-4637-9a8a-1c16c6cd3047" + }, + { + "collectionName": "viewSort", + "hash": "2a3e7cc8-e330-489a-8016-5ec57869a578" + }, + { + "collectionName": "rowLevelPermissionPredicate", + "hash": "26f5dc80-f9db-4d92-938d-1192520ff19a" + }, + { + "collectionName": "rowLevelPermissionPredicateGroup", + "hash": "b18e8285-b308-4db9-96d1-2f64f8e1bd80" + }, + { + "collectionName": "viewFilterGroup", + "hash": "8a988096-5aa5-414b-8a4c-b040438acad4" + }, + { + "collectionName": "index", + "hash": "a2f51a56-3c4e-49bb-bcb7-1da9ec1277c1" + }, + { + "collectionName": "logicFunction", + "hash": "0e1af693-a8d2-4c8c-be85-45ed4412c0c5" + }, + { + "collectionName": "viewFilter", + "hash": "41db50cc-45ce-4630-a7dd-ae5881cd3e1f" + }, + { + "collectionName": "role", + "hash": "a7252385-a38c-400f-8411-4db5c6897023" + }, + { + "collectionName": "roleTarget", + "hash": "4ebb8b78-e7f1-46c5-b1dd-e4cf003ef0be" + }, + { + "collectionName": "agent", + "hash": "6a6a2ece-a771-4b47-86fd-48f8295a8092" + }, + { + "collectionName": "skill", + "hash": "8712eb59-fe28-403d-be23-d6a6d120661e" + }, + { + "collectionName": "pageLayout", + "hash": "f8f16b56-31df-49ad-8ba0-9857e9f1d745" + }, + { + "collectionName": "pageLayoutWidget", + "hash": "d161e497-38cd-4dc9-84fc-f5e9f290ce1b" + }, + { + "collectionName": "pageLayoutTab", + "hash": "55d14e7e-b0ad-428f-b85f-8726b2110425" + }, + { + "collectionName": "commandMenuItem", + "hash": "0ae53571-cd1d-4231-9ee3-1b9c728c9336" + }, + { + "collectionName": "navigationMenuItem", + "hash": "af2427b8-094a-4093-8c69-a01c87cc3a3e" + }, + { + "collectionName": "frontComponent", + "hash": "d99e9435-ec62-4889-84d3-d8adf2fd19a4" + } + ] +}; diff --git a/packages/twenty-front/src/testing/mock-data/generated/metadata/navigation-menu-items/mock-navigation-menu-items-data.ts b/packages/twenty-front/src/testing/mock-data/generated/metadata/navigation-menu-items/mock-navigation-menu-items-data.ts new file mode 100644 index 0000000000..fe9c161245 --- /dev/null +++ b/packages/twenty-front/src/testing/mock-data/generated/metadata/navigation-menu-items/mock-navigation-menu-items-data.ts @@ -0,0 +1,316 @@ +/* oxlint-disable */ +// @ts-nocheck +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; + +// This file was automatically generated — do not edit manually. + +// prettier-ignore +export const mockedNavigationMenuItems: NavigationMenuItem[] = +[ + { + "id": "6c5f8eeb-e0eb-419c-959f-35971c4307da", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "60a2a05b-3c27-4c0b-b70e-d9e3230101d6", + "folderId": "c626185c-ce8b-4d99-a239-07411fbb3e0c", + "name": null, + "link": null, + "icon": null, + "color": "gray", + "position": 0, + "applicationId": "76c2f582-7223-4e93-8413-75ff4949325b", + "createdAt": "2026-03-14T18:16:51.168Z", + "updatedAt": "2026-03-14T18:16:51.168Z", + "targetRecordIdentifier": null + }, + { + "id": "d9ac8e9f-26dc-4571-8d78-7a829862f37b", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "07f3ccf3-0e66-48b9-a25a-9e3146487ac0", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": "blue", + "position": 1, + "applicationId": "76c2f582-7223-4e93-8413-75ff4949325b", + "createdAt": "2026-03-14T18:16:51.168Z", + "updatedAt": "2026-03-14T18:16:51.168Z", + "targetRecordIdentifier": null + }, + { + "id": "ce60ce3c-28ba-45af-8391-457d9066884e", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "d55fe3ad-7d0a-449c-816e-fcaa8e2e0c3d", + "folderId": "c626185c-ce8b-4d99-a239-07411fbb3e0c", + "name": null, + "link": null, + "icon": null, + "color": "gray", + "position": 1, + "applicationId": "76c2f582-7223-4e93-8413-75ff4949325b", + "createdAt": "2026-03-14T18:16:51.168Z", + "updatedAt": "2026-03-14T18:16:51.168Z", + "targetRecordIdentifier": null + }, + { + "id": "f8a54baa-c1df-460c-9bd1-9fd3450a72af", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "014bcf01-837e-4ac0-8fa7-db8eb5853e0c", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": "red", + "position": 2, + "applicationId": "76c2f582-7223-4e93-8413-75ff4949325b", + "createdAt": "2026-03-14T18:16:51.168Z", + "updatedAt": "2026-03-14T18:16:51.168Z", + "targetRecordIdentifier": null + }, + { + "id": "e4bcdbe3-a511-42e9-b454-0914eedcd414", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "d954fdb7-fd84-4427-8a16-e5e573ab8260", + "folderId": "c626185c-ce8b-4d99-a239-07411fbb3e0c", + "name": null, + "link": null, + "icon": null, + "color": "gray", + "position": 2, + "applicationId": "76c2f582-7223-4e93-8413-75ff4949325b", + "createdAt": "2026-03-14T18:16:51.168Z", + "updatedAt": "2026-03-14T18:16:51.168Z", + "targetRecordIdentifier": null + }, + { + "id": "67258966-aaa8-42ac-a2d7-19a6d897f829", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "4fbe1837-e5b2-4d8b-9f0d-0191f11da17f", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": "blue", + "position": 2.5, + "applicationId": "76c2f582-7223-4e93-8413-75ff4949325b", + "createdAt": "2026-03-14T18:16:51.168Z", + "updatedAt": "2026-03-15T10:21:41.704Z", + "targetRecordIdentifier": null + }, + { + "id": "8a5dc1d9-05d3-4630-8c92-dc6503b6979a", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "baa47df1-d385-4abf-b0f8-41b71a1d874f", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": "turquoise", + "position": 3, + "applicationId": "76c2f582-7223-4e93-8413-75ff4949325b", + "createdAt": "2026-03-14T18:16:51.168Z", + "updatedAt": "2026-03-14T18:16:51.168Z", + "targetRecordIdentifier": null + }, + { + "id": "f91f583d-18f0-4f74-ab23-cbf8b27960f4", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "3ea68976-f3ea-4800-919f-d10b6b8b82df", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": "turquoise", + "position": 4, + "applicationId": "76c2f582-7223-4e93-8413-75ff4949325b", + "createdAt": "2026-03-14T18:16:51.168Z", + "updatedAt": "2026-03-14T18:16:51.168Z", + "targetRecordIdentifier": null + }, + { + "id": "535d064e-6a83-4a21-aa29-71ca764462da", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "f160aaf2-3e6a-4f5c-a61c-551d77c9bef3", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": "gray", + "position": 5, + "applicationId": "76c2f582-7223-4e93-8413-75ff4949325b", + "createdAt": "2026-03-14T18:16:51.168Z", + "updatedAt": "2026-03-14T18:16:51.168Z", + "targetRecordIdentifier": null + }, + { + "id": "c626185c-ce8b-4d99-a239-07411fbb3e0c", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": null, + "folderId": null, + "name": "Workflows", + "link": null, + "icon": "IconSettingsAutomation", + "color": "orange", + "position": 6, + "applicationId": "76c2f582-7223-4e93-8413-75ff4949325b", + "createdAt": "2026-03-14T18:16:51.168Z", + "updatedAt": "2026-03-14T18:16:51.168Z", + "targetRecordIdentifier": null + }, + { + "id": "05c88371-46e0-41e9-a3bc-e176148db9cf", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "21e62404-bb05-47e3-89da-f515cadeb82e", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": null, + "position": 7, + "applicationId": "42e22aff-5cbf-478e-b54c-229b2bc21805", + "createdAt": "2026-03-14T18:16:51.837Z", + "updatedAt": "2026-03-14T18:16:51.837Z", + "targetRecordIdentifier": null + }, + { + "id": "d18a347e-70ca-4e37-8f5c-4c38a944ed04", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "f4044ef2-99a9-4ae5-a5e0-d5664dfcd4ee", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": null, + "position": 8, + "applicationId": "42e22aff-5cbf-478e-b54c-229b2bc21805", + "createdAt": "2026-03-14T18:16:52.091Z", + "updatedAt": "2026-03-14T18:16:52.091Z", + "targetRecordIdentifier": null + }, + { + "id": "9bdde562-66b0-42b2-89fc-bd3ee42345f2", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "cc83a36a-4c40-4aa9-8529-13c5fc9efe41", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": null, + "position": 9, + "applicationId": "42e22aff-5cbf-478e-b54c-229b2bc21805", + "createdAt": "2026-03-14T18:16:52.352Z", + "updatedAt": "2026-03-14T18:16:52.352Z", + "targetRecordIdentifier": null + }, + { + "id": "1beeff3a-9245-4b01-b7e6-34581fa54340", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "53fc7567-b6b6-440e-a06b-5ed8bb7164fb", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": null, + "position": 10, + "applicationId": "42e22aff-5cbf-478e-b54c-229b2bc21805", + "createdAt": "2026-03-14T18:16:52.534Z", + "updatedAt": "2026-03-14T18:16:52.534Z", + "targetRecordIdentifier": null + }, + { + "id": "db51d71f-0cf3-4ae2-8670-0326b1dabe50", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "d555d97b-041f-4b83-a7a5-93c526598ff8", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": null, + "position": 11, + "applicationId": "42e22aff-5cbf-478e-b54c-229b2bc21805", + "createdAt": "2026-03-14T18:16:52.660Z", + "updatedAt": "2026-03-14T18:16:52.660Z", + "targetRecordIdentifier": null + }, + { + "id": "e31c0236-fdec-4b1e-a0bd-f4ae5159fdd4", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "5af3f071-074b-4a7b-9f6a-bd7e5c4d2b15", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": null, + "position": 12, + "applicationId": "42e22aff-5cbf-478e-b54c-229b2bc21805", + "createdAt": "2026-03-14T18:18:57.135Z", + "updatedAt": "2026-03-14T18:18:57.135Z", + "targetRecordIdentifier": null + }, + { + "id": "058e70ce-d136-46c6-84d9-7907622a4808", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "54379c74-3248-4474-b6a8-511bf79a2c72", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": null, + "position": 13, + "applicationId": "42e22aff-5cbf-478e-b54c-229b2bc21805", + "createdAt": "2026-03-15T09:09:39.132Z", + "updatedAt": "2026-03-15T09:09:39.132Z", + "targetRecordIdentifier": null + }, + { + "id": "14d0630a-a73f-4045-8613-1e98e58d09b5", + "userWorkspaceId": null, + "targetRecordId": null, + "targetObjectMetadataId": null, + "viewId": "c1332d82-c75d-40da-9d30-dc44c7d93444", + "folderId": null, + "name": null, + "link": null, + "icon": null, + "color": null, + "position": 14, + "applicationId": "42e22aff-5cbf-478e-b54c-229b2bc21805", + "createdAt": "2026-03-15T10:23:50.323Z", + "updatedAt": "2026-03-15T10:23:50.323Z", + "targetRecordIdentifier": null + } +]; diff --git a/packages/twenty-front/src/testing/utils/setTestCoreViewsInMetadataStore.ts b/packages/twenty-front/src/testing/utils/setTestCoreViewsInMetadataStore.ts new file mode 100644 index 0000000000..aa5d88b1e6 --- /dev/null +++ b/packages/twenty-front/src/testing/utils/setTestCoreViewsInMetadataStore.ts @@ -0,0 +1,63 @@ +import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; +import { splitViewWithRelated } from '@/metadata-store/utils/splitViewWithRelated'; +import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; +import { type createStore } from 'jotai'; + +type JotaiStore = ReturnType; + +export const setTestCoreViewsInMetadataStore = ( + store: JotaiStore, + views: CoreViewWithRelations[], +) => { + const { + flatViews, + flatViewFields, + flatViewFilters, + flatViewSorts, + flatViewGroups, + flatViewFilterGroups, + flatViewFieldGroups, + } = splitViewWithRelated(views); + + store.set(metadataStoreState.atomFamily('views'), { + current: flatViews, + draft: [], + status: 'up-to-date', + }); + + store.set(metadataStoreState.atomFamily('viewFields'), { + current: flatViewFields, + draft: [], + status: 'up-to-date', + }); + + store.set(metadataStoreState.atomFamily('viewFilters'), { + current: flatViewFilters, + draft: [], + status: 'up-to-date', + }); + + store.set(metadataStoreState.atomFamily('viewSorts'), { + current: flatViewSorts, + draft: [], + status: 'up-to-date', + }); + + store.set(metadataStoreState.atomFamily('viewGroups'), { + current: flatViewGroups, + draft: [], + status: 'up-to-date', + }); + + store.set(metadataStoreState.atomFamily('viewFilterGroups'), { + current: flatViewFilterGroups, + draft: [], + status: 'up-to-date', + }); + + store.set(metadataStoreState.atomFamily('viewFieldGroups'), { + current: flatViewFieldGroups, + draft: [], + status: 'up-to-date', + }); +}; diff --git a/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql b/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql index 57e5ac4e4c..7f626f5517 100644 --- a/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql +++ b/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql @@ -2205,6 +2205,38 @@ type AgentTurn { createdAt: DateTime! } +type CollectionHash { + collectionName: AllMetadataName! + hash: String! +} + +enum AllMetadataName { + fieldMetadata + objectMetadata + view + viewField + viewFieldGroup + viewGroup + viewSort + rowLevelPermissionPredicate + rowLevelPermissionPredicateGroup + viewFilterGroup + index + logicFunction + viewFilter + role + roleTarget + agent + skill + pageLayout + pageLayoutWidget + pageLayoutTab + commandMenuItem + navigationMenuItem + frontComponent + webhook +} + type MinimalObjectMetadata { id: UUID! nameSingular: String! @@ -2228,7 +2260,7 @@ type MinimalView { type MinimalMetadata { objectMetadataItems: [MinimalObjectMetadata!]! views: [MinimalView!]! - collectionHashes: JSON! + collectionHashes: [CollectionHash!]! } type Webhook { @@ -4250,33 +4282,6 @@ enum WorkspaceMigrationActionType { update } -enum AllMetadataName { - fieldMetadata - objectMetadata - view - viewField - viewFieldGroup - viewGroup - viewSort - rowLevelPermissionPredicate - rowLevelPermissionPredicateGroup - viewFilterGroup - index - logicFunction - viewFilter - role - roleTarget - agent - skill - pageLayout - pageLayoutWidget - pageLayoutTab - commandMenuItem - navigationMenuItem - frontComponent - webhook -} - enum FileFolder { ProfilePicture WorkspaceLogo diff --git a/packages/twenty-sdk/src/clients/generated/metadata/schema.ts b/packages/twenty-sdk/src/clients/generated/metadata/schema.ts index 59b0074b63..e0d7f47fe1 100644 --- a/packages/twenty-sdk/src/clients/generated/metadata/schema.ts +++ b/packages/twenty-sdk/src/clients/generated/metadata/schema.ts @@ -1886,6 +1886,14 @@ export interface AgentTurn { __typename: 'AgentTurn' } +export interface CollectionHash { + collectionName: AllMetadataName + hash: Scalars['String'] + __typename: 'CollectionHash' +} + +export type AllMetadataName = 'fieldMetadata' | 'objectMetadata' | 'view' | 'viewField' | 'viewFieldGroup' | 'viewGroup' | 'viewSort' | 'rowLevelPermissionPredicate' | 'rowLevelPermissionPredicateGroup' | 'viewFilterGroup' | 'index' | 'logicFunction' | 'viewFilter' | 'role' | 'roleTarget' | 'agent' | 'skill' | 'pageLayout' | 'pageLayoutWidget' | 'pageLayoutTab' | 'commandMenuItem' | 'navigationMenuItem' | 'frontComponent' | 'webhook' + export interface MinimalObjectMetadata { id: Scalars['UUID'] nameSingular: Scalars['String'] @@ -1911,7 +1919,7 @@ export interface MinimalView { export interface MinimalMetadata { objectMetadataItems: MinimalObjectMetadata[] views: MinimalView[] - collectionHashes: Scalars['JSON'] + collectionHashes: CollectionHash[] __typename: 'MinimalMetadata' } @@ -2870,8 +2878,6 @@ export type AnalyticsType = 'PAGEVIEW' | 'TRACK' export type WorkspaceMigrationActionType = 'delete' | 'create' | 'update' -export type AllMetadataName = 'fieldMetadata' | 'objectMetadata' | 'view' | 'viewField' | 'viewFieldGroup' | 'viewGroup' | 'viewSort' | 'rowLevelPermissionPredicate' | 'rowLevelPermissionPredicateGroup' | 'viewFilterGroup' | 'index' | 'logicFunction' | 'viewFilter' | 'role' | 'roleTarget' | 'agent' | 'skill' | 'pageLayout' | 'pageLayoutWidget' | 'pageLayoutTab' | 'commandMenuItem' | 'navigationMenuItem' | 'frontComponent' | 'webhook' - export type FileFolder = 'ProfilePicture' | 'WorkspaceLogo' | 'Attachment' | 'PersonPicture' | 'CorePicture' | 'File' | 'AgentChat' | 'BuiltLogicFunction' | 'BuiltFrontComponent' | 'PublicAsset' | 'Source' | 'FilesField' | 'Dependencies' | 'Workflow' | 'AppTarball' export interface Subscription { @@ -4880,6 +4886,13 @@ export interface AgentTurnGenqlSelection{ __scalar?: boolean | number } +export interface CollectionHashGenqlSelection{ + collectionName?: boolean | number + hash?: boolean | number + __typename?: boolean | number + __scalar?: boolean | number +} + export interface MinimalObjectMetadataGenqlSelection{ id?: boolean | number nameSingular?: boolean | number @@ -4907,7 +4920,7 @@ export interface MinimalViewGenqlSelection{ export interface MinimalMetadataGenqlSelection{ objectMetadataItems?: MinimalObjectMetadataGenqlSelection views?: MinimalViewGenqlSelection - collectionHashes?: boolean | number + collectionHashes?: CollectionHashGenqlSelection __typename?: boolean | number __scalar?: boolean | number } @@ -7676,6 +7689,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null + const CollectionHash_possibleTypes: string[] = ['CollectionHash'] + export const isCollectionHash = (obj?: { __typename?: any } | null): obj is CollectionHash => { + if (!obj?.__typename) throw new Error('__typename is missing in "isCollectionHash"') + return CollectionHash_possibleTypes.includes(obj.__typename) + } + + + const MinimalObjectMetadata_possibleTypes: string[] = ['MinimalObjectMetadata'] export const isMinimalObjectMetadata = (obj?: { __typename?: any } | null): obj is MinimalObjectMetadata => { if (!obj?.__typename) throw new Error('__typename is missing in "isMinimalObjectMetadata"') @@ -8746,6 +8767,33 @@ export const enumCommandMenuItemAvailabilityType = { FALLBACK: 'FALLBACK' as const } +export const enumAllMetadataName = { + fieldMetadata: 'fieldMetadata' as const, + objectMetadata: 'objectMetadata' as const, + view: 'view' as const, + viewField: 'viewField' as const, + viewFieldGroup: 'viewFieldGroup' as const, + viewGroup: 'viewGroup' as const, + viewSort: 'viewSort' as const, + rowLevelPermissionPredicate: 'rowLevelPermissionPredicate' as const, + rowLevelPermissionPredicateGroup: 'rowLevelPermissionPredicateGroup' as const, + viewFilterGroup: 'viewFilterGroup' as const, + index: 'index' as const, + logicFunction: 'logicFunction' as const, + viewFilter: 'viewFilter' as const, + role: 'role' as const, + roleTarget: 'roleTarget' as const, + agent: 'agent' as const, + skill: 'skill' as const, + pageLayout: 'pageLayout' as const, + pageLayoutWidget: 'pageLayoutWidget' as const, + pageLayoutTab: 'pageLayoutTab' as const, + commandMenuItem: 'commandMenuItem' as const, + navigationMenuItem: 'navigationMenuItem' as const, + frontComponent: 'frontComponent' as const, + webhook: 'webhook' as const +} + export const enumModelFamily = { OPENAI: 'OPENAI' as const, ANTHROPIC: 'ANTHROPIC' as const, @@ -8889,33 +8937,6 @@ export const enumWorkspaceMigrationActionType = { update: 'update' as const } -export const enumAllMetadataName = { - fieldMetadata: 'fieldMetadata' as const, - objectMetadata: 'objectMetadata' as const, - view: 'view' as const, - viewField: 'viewField' as const, - viewFieldGroup: 'viewFieldGroup' as const, - viewGroup: 'viewGroup' as const, - viewSort: 'viewSort' as const, - rowLevelPermissionPredicate: 'rowLevelPermissionPredicate' as const, - rowLevelPermissionPredicateGroup: 'rowLevelPermissionPredicateGroup' as const, - viewFilterGroup: 'viewFilterGroup' as const, - index: 'index' as const, - logicFunction: 'logicFunction' as const, - viewFilter: 'viewFilter' as const, - role: 'role' as const, - roleTarget: 'roleTarget' as const, - agent: 'agent' as const, - skill: 'skill' as const, - pageLayout: 'pageLayout' as const, - pageLayoutWidget: 'pageLayoutWidget' as const, - pageLayoutTab: 'pageLayoutTab' as const, - commandMenuItem: 'commandMenuItem' as const, - navigationMenuItem: 'navigationMenuItem' as const, - frontComponent: 'frontComponent' as const, - webhook: 'webhook' as const -} - export const enumFileFolder = { ProfilePicture: 'ProfilePicture' as const, WorkspaceLogo: 'WorkspaceLogo' as const, diff --git a/packages/twenty-sdk/src/clients/generated/metadata/types.ts b/packages/twenty-sdk/src/clients/generated/metadata/types.ts index 581dc80696..baa716fa38 100644 --- a/packages/twenty-sdk/src/clients/generated/metadata/types.ts +++ b/packages/twenty-sdk/src/clients/generated/metadata/types.ts @@ -53,29 +53,29 @@ export default { 218, 226, 227, - 243, - 244, - 249, - 252, - 258, - 259, + 237, + 245, + 246, + 251, + 254, + 260, 261, - 266, - 271, - 272, - 284, - 300, - 301, - 324, - 331, - 332, + 263, + 268, + 273, + 274, + 286, + 302, + 303, + 326, 333, + 334, 335, - 344, - 391, - 454, - 455, - 456 + 337, + 346, + 393, + 456, + 457 ], "types": { "BillingProductDTO": { @@ -4326,6 +4326,18 @@ export default { 1 ] }, + "CollectionHash": { + "collectionName": [ + 237 + ], + "hash": [ + 1 + ], + "__typename": [ + 1 + ] + }, + "AllMetadataName": {}, "MinimalObjectMetadata": { "id": [ 3 @@ -4380,13 +4392,13 @@ export default { }, "MinimalMetadata": { "objectMetadataItems": [ - 236 + 238 ], "views": [ - 237 + 239 ], "collectionHashes": [ - 15 + 236 ], "__typename": [ 1 @@ -4454,10 +4466,10 @@ export default { 1 ], "modelFamily": [ - 243 + 245 ], "inferenceProvider": [ - 244 + 246 ], "inputCostPerMillionTokensInCredits": [ 11 @@ -4466,7 +4478,7 @@ export default { 11 ], "nativeCapabilities": [ - 241 + 243 ], "deprecated": [ 6 @@ -4488,10 +4500,10 @@ export default { 1 ], "modelFamily": [ - 243 + 245 ], "inferenceProvider": [ - 244 + 246 ], "isAvailable": [ 6 @@ -4514,7 +4526,7 @@ export default { 6 ], "models": [ - 245 + 247 ], "__typename": [ 1 @@ -4528,7 +4540,7 @@ export default { 1 ], "trialPeriods": [ - 240 + 242 ], "__typename": [ 1 @@ -4536,7 +4548,7 @@ export default { }, "Support": { "supportDriver": [ - 249 + 251 ], "supportFrontChatId": [ 1 @@ -4562,7 +4574,7 @@ export default { }, "Captcha": { "provider": [ - 252 + 254 ], "siteKey": [ 1 @@ -4599,7 +4611,7 @@ export default { 164 ], "metadata": [ - 254 + 256 ], "__typename": [ 1 @@ -4613,10 +4625,10 @@ export default { 166 ], "billing": [ - 247 + 249 ], "aiModels": [ - 242 + 244 ], "signInPrefilled": [ 6 @@ -4637,28 +4649,28 @@ export default { 6 ], "support": [ - 248 + 250 ], "isAttachmentPreviewEnabled": [ 6 ], "sentry": [ - 250 + 252 ], "captcha": [ - 251 + 253 ], "chromeExtensionId": [ 1 ], "api": [ - 253 + 255 ], "canManageFeatureFlags": [ 6 ], "publicFeatureFlags": [ - 255 + 257 ], "isMicrosoftMessagingEnabled": [ 6 @@ -4708,13 +4720,13 @@ export default { 6 ], "source": [ - 258 + 260 ], "isEnvOnly": [ 6 ], "type": [ - 259 + 261 ], "options": [ 15 @@ -4727,10 +4739,10 @@ export default { "ConfigVariableType": {}, "ConfigVariablesGroupData": { "variables": [ - 257 + 259 ], "name": [ - 261 + 263 ], "description": [ 1 @@ -4745,7 +4757,7 @@ export default { "ConfigVariablesGroup": {}, "ConfigVariables": { "groups": [ - 260 + 262 ], "__typename": [ 1 @@ -4770,7 +4782,7 @@ export default { 21 ], "results": [ - 263 + 265 ], "__typename": [ 1 @@ -4787,7 +4799,7 @@ export default { 15 ], "state": [ - 266 + 268 ], "timestamp": [ 11 @@ -4837,7 +4849,7 @@ export default { }, "QueueJobsResponse": { "jobs": [ - 265 + 267 ], "count": [ 11 @@ -4849,7 +4861,7 @@ export default { 6 ], "retentionConfig": [ - 267 + 269 ], "__typename": [ 1 @@ -4860,7 +4872,7 @@ export default { 21 ], "results": [ - 263 + 265 ], "__typename": [ 1 @@ -4868,13 +4880,13 @@ export default { }, "SystemHealthService": { "id": [ - 271 + 273 ], "label": [ 1 ], "status": [ - 272 + 274 ], "__typename": [ 1 @@ -4884,7 +4896,7 @@ export default { "AdminPanelHealthServiceStatus": {}, "SystemHealth": { "services": [ - 270 + 272 ], "__typename": [ 1 @@ -4927,7 +4939,7 @@ export default { 152 ], "users": [ - 274 + 276 ], "featureFlags": [ 163 @@ -4938,10 +4950,10 @@ export default { }, "UserLookup": { "user": [ - 274 + 276 ], "workspaces": [ - 275 + 277 ], "__typename": [ 1 @@ -4966,7 +4978,7 @@ export default { 1 ], "status": [ - 272 + 274 ], "__typename": [ 1 @@ -4974,7 +4986,7 @@ export default { }, "AdminPanelHealthServiceData": { "id": [ - 271 + 273 ], "label": [ 1 @@ -4983,7 +4995,7 @@ export default { 1 ], "status": [ - 272 + 274 ], "errorMessage": [ 1 @@ -4992,7 +5004,7 @@ export default { 1 ], "queues": [ - 278 + 280 ], "__typename": [ 1 @@ -5014,7 +5026,7 @@ export default { 1 ], "data": [ - 280 + 282 ], "__typename": [ 1 @@ -5057,13 +5069,13 @@ export default { 11 ], "timeRange": [ - 284 + 286 ], "details": [ - 282 + 284 ], "data": [ - 281 + 283 ], "__typename": [ 1 @@ -5169,7 +5181,7 @@ export default { 1 ], "fields": [ - 289 + 291 ], "__typename": [ 1 @@ -5266,10 +5278,10 @@ export default { 6 ], "objectPermissions": [ - 293 + 295 ], "fieldPermissions": [ - 294 + 296 ], "permissionFlags": [ 1 @@ -5319,19 +5331,19 @@ export default { 1 ], "objects": [ - 290 - ], - "fields": [ - 289 - ], - "logicFunctions": [ - 291 - ], - "frontComponents": [ 292 ], + "fields": [ + 291 + ], + "logicFunctions": [ + 293 + ], + "frontComponents": [ + 294 + ], "defaultRole": [ - 295 + 297 ], "sourcePackage": [ 1 @@ -5391,13 +5403,13 @@ export default { 1 ], "driver": [ - 300 + 302 ], "status": [ - 301 + 303 ], "verificationRecords": [ - 298 + 300 ], "verifiedAt": [ 4 @@ -5444,7 +5456,7 @@ export default { 1 ], "location": [ - 303 + 305 ], "__typename": [ 1 @@ -5472,13 +5484,13 @@ export default { }, "ImapSmtpCaldavConnectionParameters": { "IMAP": [ - 305 + 307 ], "SMTP": [ - 305 + 307 ], "CALDAV": [ - 305 + 307 ], "__typename": [ 1 @@ -5498,7 +5510,7 @@ export default { 3 ], "connectionParameters": [ - 306 + 308 ], "__typename": [ 1 @@ -5562,7 +5574,7 @@ export default { 1 ], "series": [ - 311 + 313 ], "xAxisLabel": [ 1 @@ -5611,7 +5623,7 @@ export default { 1 ], "data": [ - 313 + 315 ], "__typename": [ 1 @@ -5619,7 +5631,7 @@ export default { }, "LineChartData": { "series": [ - 314 + 316 ], "xAxisLabel": [ 1 @@ -5656,7 +5668,7 @@ export default { }, "PieChartData": { "data": [ - 316 + 318 ], "showLegend": [ 6 @@ -5739,13 +5751,13 @@ export default { }, "EventLogQueryResult": { "records": [ - 319 + 321 ], "totalCount": [ 21 ], "pageInfo": [ - 320 + 322 ], "__typename": [ 1 @@ -5812,7 +5824,7 @@ export default { 32, { "input": [ - 323, + 325, "LogicFunctionIdInput!" ] } @@ -5824,7 +5836,7 @@ export default { 15, { "input": [ - 323, + 325, "LogicFunctionIdInput!" ] } @@ -5833,7 +5845,7 @@ export default { 1, { "input": [ - 323, + 325, "LogicFunctionIdInput!" ] } @@ -5990,7 +6002,7 @@ export default { 25, { "input": [ - 325, + 327, "AgentIdInput!" ] } @@ -6047,7 +6059,7 @@ export default { 2, { "input": [ - 326, + 328, "GetApiKeyInput!" ] } @@ -6260,10 +6272,10 @@ export default { 193 ], "webhooks": [ - 239 + 241 ], "webhook": [ - 239, + 241, { "id": [ 3, @@ -6272,7 +6284,7 @@ export default { } ], "minimalMetadata": [ - 238 + 240 ], "chatThread": [ 228, @@ -6315,11 +6327,11 @@ export default { "CursorPaging!" ], "filter": [ - 327, + 329, "AgentChatThreadFilter!" ], "sorting": [ - 330, + 332, "[AgentChatThreadSort!]!" ] } @@ -6334,43 +6346,43 @@ export default { } ], "eventLogs": [ - 321, + 323, { "input": [ - 334, + 336, "EventLogQueryInput!" ] } ], "pieChartData": [ - 317, + 319, { "input": [ - 338, + 340, "PieChartDataInput!" ] } ], "lineChartData": [ - 315, + 317, { "input": [ - 339, + 341, "LineChartDataInput!" ] } ], "barChartData": [ - 312, + 314, { "input": [ - 340, + 342, "BarChartDataInput!" ] } ], "getConnectedImapSmtpCaldavAccount": [ - 307, + 309, { "id": [ 3, @@ -6379,7 +6391,7 @@ export default { } ], "getAutoCompleteAddress": [ - 302, + 304, { "address": [ 1, @@ -6398,7 +6410,7 @@ export default { } ], "getAddressDetails": [ - 304, + 306, { "placeId": [ 1, @@ -6411,40 +6423,40 @@ export default { } ], "getConfigVariablesGrouped": [ - 262 + 264 ], "getSystemHealthStatus": [ - 273 + 275 ], "getIndicatorHealthStatus": [ - 279, + 281, { "indicatorId": [ - 271, + 273, "HealthIndicatorId!" ] } ], "getQueueMetrics": [ - 283, + 285, { "queueName": [ 1, "String!" ], "timeRange": [ - 284 + 286 ] } ], "versionInfo": [ - 277 + 279 ], "getAdminAiModels": [ - 246 + 248 ], "getDatabaseConfigVariable": [ - 257, + 259, { "key": [ 1, @@ -6453,14 +6465,14 @@ export default { } ], "getQueueJobs": [ - 268, + 270, { "queueName": [ 1, "String!" ], "state": [ - 266, + 268, "JobState!" ], "limit": [ @@ -6475,19 +6487,19 @@ export default { 7 ], "getPostgresCredentials": [ - 309 + 311 ], "findManyPublicDomains": [ - 297 - ], - "getEmailingDomains": [ 299 ], + "getEmailingDomains": [ + 301 + ], "findManyMarketplaceApps": [ - 296 + 298 ], "findOneMarketplaceApp": [ - 296, + 298, { "universalIdentifier": [ 1, @@ -6515,7 +6527,7 @@ export default { }, "LogicFunctionIdInput": { "id": [ - 324 + 326 ], "__typename": [ 1 @@ -6540,16 +6552,16 @@ export default { }, "AgentChatThreadFilter": { "and": [ - 327 + 329 ], "or": [ - 327 + 329 ], "id": [ 42 ], "updatedAt": [ - 328 + 330 ], "__typename": [ 1 @@ -6587,10 +6599,10 @@ export default { 4 ], "between": [ - 329 + 331 ], "notBetween": [ - 329 + 331 ], "__typename": [ 1 @@ -6609,13 +6621,13 @@ export default { }, "AgentChatThreadSort": { "field": [ - 331 + 333 ], "direction": [ - 332 + 334 ], "nulls": [ - 333 + 335 ], "__typename": [ 1 @@ -6626,10 +6638,10 @@ export default { "SortNulls": {}, "EventLogQueryInput": { "table": [ - 335 + 337 ], "filters": [ - 336 + 338 ], "first": [ 21 @@ -6650,7 +6662,7 @@ export default { 1 ], "dateRange": [ - 337 + 339 ], "recordId": [ 1 @@ -6711,7 +6723,7 @@ export default { 6, { "input": [ - 342, + 344, "AddQuerySubscriptionInput!" ] } @@ -6720,7 +6732,7 @@ export default { 6, { "input": [ - 343, + 345, "RemoveQueryFromEventStreamInput!" ] } @@ -6749,7 +6761,7 @@ export default { 122, { "type": [ - 344, + 346, "AnalyticsType!" ], "name": [ @@ -6767,7 +6779,7 @@ export default { 75, { "input": [ - 345, + 347, "CreatePageLayoutWidgetInput!" ] } @@ -6780,7 +6792,7 @@ export default { "String!" ], "input": [ - 347, + 349, "UpdatePageLayoutWidgetInput!" ] } @@ -6798,7 +6810,7 @@ export default { 110, { "input": [ - 348, + 350, "CreatePageLayoutTabInput!" ] } @@ -6811,7 +6823,7 @@ export default { "String!" ], "input": [ - 349, + 351, "UpdatePageLayoutTabInput!" ] } @@ -6829,7 +6841,7 @@ export default { 111, { "input": [ - 350, + 352, "CreatePageLayoutInput!" ] } @@ -6842,7 +6854,7 @@ export default { "String!" ], "input": [ - 351, + 353, "UpdatePageLayoutInput!" ] } @@ -6864,7 +6876,7 @@ export default { "String!" ], "input": [ - 352, + 354, "UpdatePageLayoutWithTabsInput!" ] } @@ -6873,7 +6885,7 @@ export default { 32, { "input": [ - 323, + 325, "LogicFunctionIdInput!" ] } @@ -6882,7 +6894,7 @@ export default { 32, { "input": [ - 355, + 357, "CreateLogicFunctionFromSourceInput!" ] } @@ -6891,7 +6903,7 @@ export default { 217, { "input": [ - 356, + 358, "ExecuteOneLogicFunctionInput!" ] } @@ -6900,7 +6912,7 @@ export default { 6, { "input": [ - 357, + 359, "UpdateLogicFunctionFromSourceInput!" ] } @@ -6909,7 +6921,7 @@ export default { 46, { "input": [ - 359, + 361, "CreateOneObjectInput!" ] } @@ -6918,7 +6930,7 @@ export default { 46, { "input": [ - 361, + 363, "DeleteOneObjectInput!" ] } @@ -6927,7 +6939,7 @@ export default { 46, { "input": [ - 362, + 364, "UpdateOneObjectInput!" ] } @@ -6936,7 +6948,7 @@ export default { 50, { "input": [ - 364, + 366, "UpdateViewFieldInput!" ] } @@ -6945,7 +6957,7 @@ export default { 50, { "input": [ - 366, + 368, "CreateViewFieldInput!" ] } @@ -6954,7 +6966,7 @@ export default { 50, { "inputs": [ - 366, + 368, "[CreateViewFieldInput!]!" ] } @@ -6963,7 +6975,7 @@ export default { 50, { "input": [ - 367, + 369, "DeleteViewFieldInput!" ] } @@ -6972,7 +6984,7 @@ export default { 50, { "input": [ - 368, + 370, "DestroyViewFieldInput!" ] } @@ -6981,7 +6993,7 @@ export default { 60, { "input": [ - 369, + 371, "CreateViewInput!" ] } @@ -6994,7 +7006,7 @@ export default { "String!" ], "input": [ - 370, + 372, "UpdateViewInput!" ] } @@ -7021,7 +7033,7 @@ export default { 57, { "input": [ - 371, + 373, "CreateViewSortInput!" ] } @@ -7030,7 +7042,7 @@ export default { 57, { "input": [ - 372, + 374, "UpdateViewSortInput!" ] } @@ -7039,7 +7051,7 @@ export default { 6, { "input": [ - 374, + 376, "DeleteViewSortInput!" ] } @@ -7048,7 +7060,7 @@ export default { 6, { "input": [ - 375, + 377, "DestroyViewSortInput!" ] } @@ -7057,7 +7069,7 @@ export default { 59, { "input": [ - 376, + 378, "UpdateViewFieldGroupInput!" ] } @@ -7066,7 +7078,7 @@ export default { 59, { "input": [ - 378, + 380, "CreateViewFieldGroupInput!" ] } @@ -7075,7 +7087,7 @@ export default { 59, { "inputs": [ - 378, + 380, "[CreateViewFieldGroupInput!]!" ] } @@ -7084,7 +7096,7 @@ export default { 59, { "input": [ - 379, + 381, "DeleteViewFieldGroupInput!" ] } @@ -7093,7 +7105,7 @@ export default { 59, { "input": [ - 380, + 382, "DestroyViewFieldGroupInput!" ] } @@ -7102,7 +7114,7 @@ export default { 60, { "input": [ - 381, + 383, "UpsertFieldsWidgetInput!" ] } @@ -7111,7 +7123,7 @@ export default { 225, { "input": [ - 384, + 386, "CreateCommandMenuItemInput!" ] } @@ -7120,7 +7132,7 @@ export default { 225, { "input": [ - 385, + 387, "UpdateCommandMenuItemInput!" ] } @@ -7138,7 +7150,7 @@ export default { 224, { "input": [ - 386, + 388, "CreateFrontComponentInput!" ] } @@ -7147,7 +7159,7 @@ export default { 224, { "input": [ - 387, + 389, "UpdateFrontComponentInput!" ] } @@ -7165,7 +7177,7 @@ export default { 25, { "input": [ - 389, + 391, "CreateAgentInput!" ] } @@ -7174,7 +7186,7 @@ export default { 25, { "input": [ - 390, + 392, "UpdateAgentInput!" ] } @@ -7183,7 +7195,7 @@ export default { 25, { "input": [ - 325, + 327, "AgentIdInput!" ] } @@ -7192,7 +7204,7 @@ export default { 148, { "file": [ - 391, + 393, "Upload!" ] } @@ -7201,7 +7213,7 @@ export default { 148, { "file": [ - 391, + 393, "Upload!" ] } @@ -7210,7 +7222,7 @@ export default { 148, { "file": [ - 391, + 393, "Upload!" ] } @@ -7219,7 +7231,7 @@ export default { 148, { "file": [ - 391, + 393, "Upload!" ] } @@ -7228,7 +7240,7 @@ export default { 148, { "file": [ - 391, + 393, "Upload!" ], "fieldMetadataId": [ @@ -7241,7 +7253,7 @@ export default { 148, { "file": [ - 391, + 393, "Upload!" ], "fieldMetadataUniversalIdentifier": [ @@ -7313,7 +7325,7 @@ export default { 216, { "input": [ - 392, + 394, "CreateNavigationMenuItemInput!" ] } @@ -7322,7 +7334,7 @@ export default { 216, { "input": [ - 393, + 395, "UpdateOneNavigationMenuItemInput!" ] } @@ -7340,7 +7352,7 @@ export default { 2, { "input": [ - 395, + 397, "CreateApiKeyInput!" ] } @@ -7349,7 +7361,7 @@ export default { 2, { "input": [ - 396, + 398, "UpdateApiKeyInput!" ] } @@ -7358,7 +7370,7 @@ export default { 2, { "input": [ - 397, + 399, "RevokeApiKeyInput!" ] } @@ -7393,7 +7405,7 @@ export default { 29, { "createRoleInput": [ - 398, + 400, "CreateRoleInput!" ] } @@ -7402,7 +7414,7 @@ export default { 29, { "updateRoleInput": [ - 399, + 401, "UpdateRoleInput!" ] } @@ -7420,7 +7432,7 @@ export default { 16, { "upsertObjectPermissionsInput": [ - 401, + 403, "UpsertObjectPermissionsInput!" ] } @@ -7429,7 +7441,7 @@ export default { 27, { "upsertPermissionFlagsInput": [ - 403, + 405, "UpsertPermissionFlagsInput!" ] } @@ -7438,7 +7450,7 @@ export default { 26, { "upsertFieldPermissionsInput": [ - 404, + 406, "UpsertFieldPermissionsInput!" ] } @@ -7447,7 +7459,7 @@ export default { 181, { "input": [ - 406, + 408, "UpsertRowLevelPermissionPredicatesInput!" ] } @@ -7514,7 +7526,7 @@ export default { 147, { "input": [ - 409, + 411, "CreateApprovedAccessDomainInput!" ] } @@ -7523,7 +7535,7 @@ export default { 6, { "input": [ - 410, + 412, "DeleteApprovedAccessDomainInput!" ] } @@ -7532,7 +7544,7 @@ export default { 147, { "input": [ - 411, + 413, "ValidateApprovedAccessDomainInput!" ] } @@ -7541,7 +7553,7 @@ export default { 34, { "input": [ - 412, + 414, "CreateOneFieldMetadataInput!" ] } @@ -7550,7 +7562,7 @@ export default { 34, { "input": [ - 414, + 416, "UpdateOneFieldMetadataInput!" ] } @@ -7559,7 +7571,7 @@ export default { 34, { "input": [ - 416, + 418, "DeleteOneFieldInput!" ] } @@ -7568,7 +7580,7 @@ export default { 56, { "input": [ - 417, + 419, "CreateViewGroupInput!" ] } @@ -7577,7 +7589,7 @@ export default { 56, { "inputs": [ - 417, + 419, "[CreateViewGroupInput!]!" ] } @@ -7586,7 +7598,7 @@ export default { 56, { "input": [ - 418, + 420, "UpdateViewGroupInput!" ] } @@ -7595,7 +7607,7 @@ export default { 56, { "input": [ - 420, + 422, "DeleteViewGroupInput!" ] } @@ -7604,7 +7616,7 @@ export default { 56, { "input": [ - 421, + 423, "DestroyViewGroupInput!" ] } @@ -7613,7 +7625,7 @@ export default { 54, { "input": [ - 422, + 424, "CreateViewFilterInput!" ] } @@ -7622,7 +7634,7 @@ export default { 54, { "input": [ - 423, + 425, "UpdateViewFilterInput!" ] } @@ -7631,7 +7643,7 @@ export default { 54, { "input": [ - 425, + 427, "DeleteViewFilterInput!" ] } @@ -7640,7 +7652,7 @@ export default { 54, { "input": [ - 426, + 428, "DestroyViewFilterInput!" ] } @@ -7649,7 +7661,7 @@ export default { 52, { "input": [ - 427, + 429, "CreateViewFilterGroupInput!" ] } @@ -7662,7 +7674,7 @@ export default { "String!" ], "input": [ - 428, + 430, "UpdateViewFilterGroupInput!" ] } @@ -7726,7 +7738,7 @@ export default { 66, { "data": [ - 429, + 431, "ActivateWorkspaceInput!" ] } @@ -7735,7 +7747,7 @@ export default { 66, { "data": [ - 430, + 432, "UpdateWorkspaceInput!" ] } @@ -7750,7 +7762,7 @@ export default { 203, { "input": [ - 431, + 433, "GetAuthorizationUrlForSSOInput!" ] } @@ -8004,7 +8016,7 @@ export default { 187, { "input": [ - 432, + 434, "CreateApplicationRegistrationInput!" ] } @@ -8013,7 +8025,7 @@ export default { 7, { "input": [ - 433, + 435, "UpdateApplicationRegistrationInput!" ] } @@ -8040,7 +8052,7 @@ export default { 5, { "input": [ - 435, + 437, "CreateApplicationRegistrationVariableInput!" ] } @@ -8049,7 +8061,7 @@ export default { 5, { "input": [ - 436, + 438, "UpdateApplicationRegistrationVariableInput!" ] } @@ -8067,7 +8079,7 @@ export default { 7, { "file": [ - 391, + 393, "Upload!" ], "universalIdentifier": [ @@ -8126,7 +8138,7 @@ export default { 194, { "input": [ - 438, + 440, "SetupOIDCSsoInput!" ] } @@ -8135,7 +8147,7 @@ export default { 194, { "input": [ - 439, + 441, "SetupSAMLSsoInput!" ] } @@ -8144,7 +8156,7 @@ export default { 190, { "input": [ - 440, + 442, "DeleteSsoInput!" ] } @@ -8153,31 +8165,31 @@ export default { 191, { "input": [ - 441, + 443, "EditSsoInput!" ] } ], "createWebhook": [ - 239, + 241, { "input": [ - 442, + 444, "CreateWebhookInput!" ] } ], "updateWebhook": [ - 239, + 241, { "input": [ - 443, + 445, "UpdateWebhookInput!" ] } ], "deleteWebhook": [ - 239, + 241, { "id": [ 3, @@ -8192,7 +8204,7 @@ export default { 222, { "input": [ - 445, + 447, "CreateSkillInput!" ] } @@ -8201,7 +8213,7 @@ export default { 222, { "input": [ - 446, + 448, "UpdateSkillInput!" ] } @@ -8256,7 +8268,7 @@ export default { } ], "duplicateDashboard": [ - 318, + 320, { "id": [ 3, @@ -8265,7 +8277,7 @@ export default { } ], "impersonate": [ - 285, + 287, { "userId": [ 3, @@ -8278,7 +8290,7 @@ export default { } ], "startChannelSync": [ - 310, + 312, { "connectedAccountId": [ 3, @@ -8287,7 +8299,7 @@ export default { } ], "saveImapSmtpCaldavAccount": [ - 308, + 310, { "accountOwnerId": [ 3, @@ -8298,7 +8310,7 @@ export default { "String!" ], "connectionParameters": [ - 447, + 449, "EmailAccountConnectionParameters!" ], "id": [ @@ -8310,13 +8322,13 @@ export default { 163, { "input": [ - 449, + 451, "UpdateLabPublicFeatureFlagInput!" ] } ], "userLookupAdminPanel": [ - 276, + 278, { "userIdentifier": [ 1, @@ -8390,7 +8402,7 @@ export default { } ], "retryJobs": [ - 269, + 271, { "queueName": [ 1, @@ -8403,7 +8415,7 @@ export default { } ], "deleteJobs": [ - 264, + 266, { "queueName": [ 1, @@ -8416,13 +8428,13 @@ export default { } ], "enablePostgresProxy": [ - 309 + 311 ], "disablePostgresProxy": [ - 309 + 311 ], "createPublicDomain": [ - 297, + 299, { "domain": [ 1, @@ -8449,14 +8461,14 @@ export default { } ], "createEmailingDomain": [ - 299, + 301, { "domain": [ 1, "String!" ], "driver": [ - 300, + 302, "EmailingDomainDriver!" ] } @@ -8471,7 +8483,7 @@ export default { } ], "verifyEmailingDomain": [ - 299, + 301, { "id": [ 1, @@ -8483,7 +8495,7 @@ export default { 68, { "input": [ - 450, + 452, "CreateOneAppTokenInput!" ] } @@ -8516,7 +8528,7 @@ export default { 6, { "workspaceMigration": [ - 452, + 454, "WorkspaceMigrationInput!" ] } @@ -8548,7 +8560,7 @@ export default { } ], "createDevelopmentApplication": [ - 286, + 288, { "universalIdentifier": [ 1, @@ -8570,7 +8582,7 @@ export default { } ], "syncApplication": [ - 287, + 289, { "manifest": [ 15, @@ -8579,10 +8591,10 @@ export default { } ], "uploadApplicationFile": [ - 288, + 290, { "file": [ - 391, + 393, "Upload!" ], "applicationUniversalIdentifier": [ @@ -8590,7 +8602,7 @@ export default { "String!" ], "fileFolder": [ - 456, + 457, "FileFolder!" ], "filePath": [ @@ -8665,7 +8677,7 @@ export default { 3 ], "gridPosition": [ - 346 + 348 ], "position": [ 15 @@ -8705,7 +8717,7 @@ export default { 3 ], "gridPosition": [ - 346 + 348 ], "position": [ 15 @@ -8787,7 +8799,7 @@ export default { 3 ], "tabs": [ - 353 + 355 ], "__typename": [ 1 @@ -8807,7 +8819,7 @@ export default { 1 ], "widgets": [ - 354 + 356 ], "__typename": [ 1 @@ -8830,7 +8842,7 @@ export default { 3 ], "gridPosition": [ - 346 + 348 ], "position": [ 15 @@ -8899,7 +8911,7 @@ export default { 3 ], "update": [ - 358 + 360 ], "__typename": [ 1 @@ -8945,7 +8957,7 @@ export default { }, "CreateOneObjectInput": { "object": [ - 360 + 362 ], "__typename": [ 1 @@ -9002,7 +9014,7 @@ export default { }, "UpdateOneObjectInput": { "update": [ - 363 + 365 ], "id": [ 3 @@ -9054,7 +9066,7 @@ export default { 3 ], "update": [ - 365 + 367 ], "__typename": [ 1 @@ -9253,7 +9265,7 @@ export default { 3 ], "update": [ - 373 + 375 ], "__typename": [ 1 @@ -9288,7 +9300,7 @@ export default { 3 ], "update": [ - 377 + 379 ], "__typename": [ 1 @@ -9352,10 +9364,10 @@ export default { 3 ], "groups": [ - 382 + 384 ], "fields": [ - 383 + 385 ], "__typename": [ 1 @@ -9375,7 +9387,7 @@ export default { 6 ], "fields": [ - 383 + 385 ], "__typename": [ 1 @@ -9496,7 +9508,7 @@ export default { 3 ], "update": [ - 388 + 390 ], "__typename": [ 1 @@ -9627,7 +9639,7 @@ export default { 3 ], "update": [ - 394 + 396 ], "__typename": [ 1 @@ -9744,7 +9756,7 @@ export default { }, "UpdateRoleInput": { "update": [ - 400 + 402 ], "id": [ 3 @@ -9799,7 +9811,7 @@ export default { 3 ], "objectPermissions": [ - 402 + 404 ], "__typename": [ 1 @@ -9841,7 +9853,7 @@ export default { 3 ], "fieldPermissions": [ - 405 + 407 ], "__typename": [ 1 @@ -9872,10 +9884,10 @@ export default { 3 ], "predicates": [ - 407 + 409 ], "predicateGroups": [ - 408 + 410 ], "__typename": [ 1 @@ -9965,7 +9977,7 @@ export default { }, "CreateOneFieldMetadataInput": { "field": [ - 413 + 415 ], "__typename": [ 1 @@ -10038,7 +10050,7 @@ export default { 3 ], "update": [ - 415 + 417 ], "__typename": [ 1 @@ -10127,7 +10139,7 @@ export default { 3 ], "update": [ - 419 + 421 ], "__typename": [ 1 @@ -10200,7 +10212,7 @@ export default { 3 ], "update": [ - 424 + 426 ], "__typename": [ 1 @@ -10421,7 +10433,7 @@ export default { 1 ], "update": [ - 434 + 436 ], "__typename": [ 1 @@ -10484,7 +10496,7 @@ export default { 1 ], "update": [ - 437 + 439 ], "__typename": [ 1 @@ -10585,7 +10597,7 @@ export default { 3 ], "update": [ - 444 + 446 ], "__typename": [ 1 @@ -10659,13 +10671,13 @@ export default { }, "EmailAccountConnectionParameters": { "IMAP": [ - 448 + 450 ], "SMTP": [ - 448 + 450 ], "CALDAV": [ - 448 + 450 ], "__typename": [ 1 @@ -10704,7 +10716,7 @@ export default { }, "CreateOneAppTokenInput": { "appToken": [ - 451 + 453 ], "__typename": [ 1 @@ -10720,7 +10732,7 @@ export default { }, "WorkspaceMigrationInput": { "actions": [ - 453 + 455 ], "__typename": [ 1 @@ -10728,10 +10740,10 @@ export default { }, "WorkspaceMigrationDeleteActionInput": { "type": [ - 454 + 456 ], "metadataName": [ - 455 + 237 ], "universalIdentifier": [ 1 @@ -10741,14 +10753,13 @@ export default { ] }, "WorkspaceMigrationActionType": {}, - "AllMetadataName": {}, "FileFolder": {}, "Subscription": { "onDbEvent": [ 121, { "input": [ - 458, + 459, "OnDbEventInput!" ] } @@ -10766,7 +10777,7 @@ export default { 219, { "input": [ - 459, + 460, "LogicFunctionLogsInput!" ] } diff --git a/packages/twenty-server/src/engine/metadata-modules/minimal-metadata/dtos/collection-hash.dto.ts b/packages/twenty-server/src/engine/metadata-modules/minimal-metadata/dtos/collection-hash.dto.ts new file mode 100644 index 0000000000..b90a0dc539 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/minimal-metadata/dtos/collection-hash.dto.ts @@ -0,0 +1,15 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { + ALL_METADATA_NAME, + type AllMetadataName, +} from 'twenty-shared/metadata'; + +@ObjectType('CollectionHash') +export class CollectionHashDTO { + @Field(() => ALL_METADATA_NAME) + collectionName: AllMetadataName; + + @Field(() => String) + hash: string; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/minimal-metadata/dtos/minimal-metadata.dto.ts b/packages/twenty-server/src/engine/metadata-modules/minimal-metadata/dtos/minimal-metadata.dto.ts index 0eec46bb05..9696f57d24 100644 --- a/packages/twenty-server/src/engine/metadata-modules/minimal-metadata/dtos/minimal-metadata.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/minimal-metadata/dtos/minimal-metadata.dto.ts @@ -1,6 +1,6 @@ import { Field, ObjectType } from '@nestjs/graphql'; -import { GraphQLJSON } from 'graphql-type-json'; +import { CollectionHashDTO } from 'src/engine/metadata-modules/minimal-metadata/dtos/collection-hash.dto'; import { MinimalObjectMetadataDTO } from 'src/engine/metadata-modules/minimal-metadata/dtos/minimal-object-metadata.dto'; import { MinimalViewDTO } from 'src/engine/metadata-modules/minimal-metadata/dtos/minimal-view.dto'; @@ -12,6 +12,6 @@ export class MinimalMetadataDTO { @Field(() => [MinimalViewDTO]) views: MinimalViewDTO[]; - @Field(() => GraphQLJSON) - collectionHashes: Record; + @Field(() => [CollectionHashDTO]) + collectionHashes: CollectionHashDTO[]; } diff --git a/packages/twenty-server/src/engine/metadata-modules/minimal-metadata/minimal-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/minimal-metadata/minimal-metadata.service.ts index 48623f5700..f0ba5e43d4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/minimal-metadata/minimal-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/minimal-metadata/minimal-metadata.service.ts @@ -9,13 +9,13 @@ import { isDefined, uncapitalize } from 'twenty-shared/utils'; import { ALL_FLAT_ENTITY_MAPS_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-flat-entity-maps-properties.constant'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { type CollectionHashDTO } from 'src/engine/metadata-modules/minimal-metadata/dtos/collection-hash.dto'; import { MinimalMetadataDTO } from 'src/engine/metadata-modules/minimal-metadata/dtos/minimal-metadata.dto'; import { MinimalObjectMetadataDTO } from 'src/engine/metadata-modules/minimal-metadata/dtos/minimal-object-metadata.dto'; import { MinimalViewDTO } from 'src/engine/metadata-modules/minimal-metadata/dtos/minimal-view.dto'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { type WorkspaceCacheKeyName } from 'src/engine/workspace-cache/types/workspace-cache-key.type'; -// Inverse of getMetadataFlatEntityMapsKey: "flatObjectMetadataMaps" -> "objectMetadata" const flatMapsKeyToMetadataName = ( flatMapsKey: string, ): AllMetadataName | undefined => { @@ -51,15 +51,17 @@ export class MinimalMetadataService { ), ]); - const collectionHashes: Record = {}; + const collectionHashes: CollectionHashDTO[] = Object.entries(cacheHashes) + .map(([cacheKey, hash]) => { + const metadataName = flatMapsKeyToMetadataName(cacheKey); - for (const [cacheKey, hash] of Object.entries(cacheHashes)) { - const metadataName = flatMapsKeyToMetadataName(cacheKey); + if (!isDefined(metadataName) || !isDefined(hash)) { + return undefined; + } - if (isDefined(metadataName) && isDefined(hash)) { - collectionHashes[metadataName] = hash; - } - } + return { collectionName: metadataName, hash }; + }) + .filter(isDefined); const objectMetadataItems: MinimalObjectMetadataDTO[] = Object.values( flatObjectMetadataMaps.byUniversalIdentifier, diff --git a/packages/twenty-server/src/engine/metadata-modules/view-field-group/services/fields-widget-upsert.service.ts b/packages/twenty-server/src/engine/metadata-modules/view-field-group/services/fields-widget-upsert.service.ts index bf19cca42a..7774841de1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-field-group/services/fields-widget-upsert.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-field-group/services/fields-widget-upsert.service.ts @@ -294,16 +294,17 @@ export class FieldsWidgetUpsertService { return []; } - const { viewFieldGroupUniversalIdentifier } = - resolveEntityRelationUniversalIdentifiers({ - metadataName: 'viewField', - foreignKeyValues: { - viewFieldGroupId: newViewFieldGroupId, - }, - flatEntityMaps: { - flatViewFieldGroupMaps: optimisticFlatViewFieldGroupMaps, - }, - }); + const { + viewFieldGroupUniversalIdentifier: _viewFieldGroupUniversalIdentifier, + } = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'viewField', + foreignKeyValues: { + viewFieldGroupId: newViewFieldGroupId, + }, + flatEntityMaps: { + flatViewFieldGroupMaps: optimisticFlatViewFieldGroupMaps, + }, + }); const shouldOverride = isCallerOverridingEntity({ callerApplicationUniversalIdentifier: applicationUniversalIdentifier, diff --git a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.service.ts b/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.service.ts index 11e10a3be7..81ac823136 100644 --- a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.service.ts +++ b/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.service.ts @@ -13,7 +13,6 @@ import { import { combineFilters, isDefined, - isMetadataGqlOperationSignature, isNonEmptyArray, isRecordGqlOperationSignature, } from 'twenty-shared/utils'; @@ -51,10 +50,7 @@ import { buildRowLevelPermissionRecordFilter } from 'src/engine/twenty-orm/utils import { isRecordMatchingRLSRowLevelPermissionPredicate } from 'src/engine/twenty-orm/utils/is-record-matching-rls-row-level-permission-predicate.util'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; -import { isMetadataRecordMatchingFilter } from 'src/engine/workspace-event-emitter/utils/is-metadata-record-matching-filter.util'; import { parseEventNameOrThrow } from 'src/engine/workspace-event-emitter/utils/parse-event-name'; -import { type MetadataEvent } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/metadata-event'; - @Injectable() export class WorkspaceEventEmitterService { constructor( @@ -183,36 +179,22 @@ export class WorkspaceEventEmitterService { private async processMetadataStreamEvents( streamChannelId: string, - streamData: EventStreamData, + _streamData: EventStreamData, metadataEventBatch: MetadataEventBatch, ): Promise { - const metadataEventsWithQueryIds: { - queryIds: string[]; - metadataEvent: MetadataEvent & { updatedCollectionHash?: string }; - }[] = []; + if (!isNonEmptyArray(metadataEventBatch.events)) { + return; + } - for (const metadataEvent of metadataEventBatch.events) { - const matchedQueryIds = this.getMatchingMetadataQueryIds( - streamData.queries, - metadataEvent, - ); - - if (!isNonEmptyArray(matchedQueryIds)) { - continue; - } - - metadataEventsWithQueryIds.push({ - queryIds: matchedQueryIds, + const metadataEventsWithQueryIds = metadataEventBatch.events.map( + (metadataEvent) => ({ + queryIds: [] as string[], metadataEvent: { ...metadataEvent, updatedCollectionHash: metadataEventBatch.updatedCollectionHash, }, - }); - } - - if (!isNonEmptyArray(metadataEventsWithQueryIds)) { - return; - } + }), + ); const payload: EventStreamPayload = { objectRecordEventsWithQueryIds: [], @@ -226,49 +208,6 @@ export class WorkspaceEventEmitterService { }); } - private getMatchingMetadataQueryIds( - queries: Record, - metadataEvent: MetadataEvent, - ): string[] { - const properties = metadataEvent.properties as { - after?: Record; - before?: Record; - }; - - const record = properties?.after ?? properties?.before; - - return Object.entries(queries) - .filter(([, operationSignature]) => { - if (!isMetadataGqlOperationSignature(operationSignature)) { - return false; - } - - if (operationSignature.metadataName !== metadataEvent.metadataName) { - return false; - } - - const queryFilter = ( - operationSignature.variables as { - filter?: Record; - } - )?.filter; - - if (!isDefined(queryFilter) || Object.keys(queryFilter).length === 0) { - return true; - } - - if (!isDefined(record)) { - return false; - } - - return isMetadataRecordMatchingFilter({ - record, - filter: queryFilter, - }); - }) - .map(([queryId]) => queryId); - } - private async processObjectRecordStreamEvents( streamChannelId: string, streamData: EventStreamData, diff --git a/packages/twenty-server/test/integration/metadata/suites/minimal-metadata/minimal-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/minimal-metadata/minimal-metadata.integration-spec.ts new file mode 100644 index 0000000000..2d16d35c64 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/minimal-metadata/minimal-metadata.integration-spec.ts @@ -0,0 +1,133 @@ +import gql from 'graphql-tag'; +import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; + +const FIND_MINIMAL_METADATA_QUERY = gql` + query FindMinimalMetadata { + minimalMetadata { + objectMetadataItems { + id + nameSingular + namePlural + labelSingular + labelPlural + icon + isCustom + isActive + isSystem + isRemote + } + views { + id + type + key + objectMetadataId + } + collectionHashes { + collectionName + hash + } + } + } +`; + +describe('minimalMetadata', () => { + it('should return objectMetadataItems, views, and collectionHashes', async () => { + const response = await makeMetadataAPIRequest({ + query: FIND_MINIMAL_METADATA_QUERY, + }); + + expect(response.body.errors).toBeUndefined(); + + const { minimalMetadata } = response.body.data; + + expect(minimalMetadata).toBeDefined(); + expect(Array.isArray(minimalMetadata.objectMetadataItems)).toBe(true); + expect(Array.isArray(minimalMetadata.views)).toBe(true); + expect(Array.isArray(minimalMetadata.collectionHashes)).toBe(true); + }); + + it('should return properly shaped objectMetadataItems', async () => { + const response = await makeMetadataAPIRequest({ + query: FIND_MINIMAL_METADATA_QUERY, + }); + + const { objectMetadataItems } = response.body.data.minimalMetadata; + + expect(objectMetadataItems.length).toBeGreaterThan(0); + + for (const item of objectMetadataItems) { + expect(item).toMatchObject({ + id: expect.any(String), + nameSingular: expect.any(String), + namePlural: expect.any(String), + labelSingular: expect.any(String), + labelPlural: expect.any(String), + isCustom: expect.any(Boolean), + isActive: true, + isSystem: expect.any(Boolean), + isRemote: expect.any(Boolean), + }); + } + }); + + it('should return properly shaped views', async () => { + const response = await makeMetadataAPIRequest({ + query: FIND_MINIMAL_METADATA_QUERY, + }); + + const { views } = response.body.data.minimalMetadata; + + expect(views.length).toBeGreaterThan(0); + + for (const view of views) { + expect(view).toMatchObject({ + id: expect.any(String), + type: expect.any(String), + objectMetadataId: expect.any(String), + }); + } + }); + + it('should return properly shaped collectionHashes', async () => { + const response = await makeMetadataAPIRequest({ + query: FIND_MINIMAL_METADATA_QUERY, + }); + + const { collectionHashes } = response.body.data.minimalMetadata; + + expect(collectionHashes.length).toBeGreaterThan(0); + + for (const collectionHash of collectionHashes) { + expect(collectionHash).toMatchObject({ + collectionName: expect.any(String), + hash: expect.any(String), + }); + } + }); + + it('should include well-known standard objects', async () => { + const response = await makeMetadataAPIRequest({ + query: FIND_MINIMAL_METADATA_QUERY, + }); + + const { objectMetadataItems } = response.body.data.minimalMetadata; + + const objectNames = objectMetadataItems.map( + (item: { namePlural: string }) => item.namePlural, + ); + + expect(objectNames).toContain('people'); + expect(objectNames).toContain('companies'); + expect(objectNames).toContain('opportunities'); + }); + + it('should reject requests with an invalid token', async () => { + const response = await makeMetadataAPIRequest( + { query: FIND_MINIMAL_METADATA_QUERY }, + INVALID_ACCESS_TOKEN, + ); + + expect(response.body.errors).toBeDefined(); + expect(response.body.errors.length).toBeGreaterThan(0); + }); +});