diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx index abe8f3dc022..0c0071b5f32 100644 --- a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx +++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx @@ -6,8 +6,8 @@ import { Provider as JotaiProvider } from 'jotai'; import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext'; @@ -140,8 +140,8 @@ describe('useActivityTargetObjectRecords', () => { }), ); - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); diff --git a/packages/twenty-front/src/modules/auth/states/objectPermissionsFamilySelector.ts b/packages/twenty-front/src/modules/auth/states/objectPermissionsFamilySelector.ts index 79af920d645..30264a24b77 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 { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsSelector } from '@/metadata-store/states/objectMetadataItemsSelector'; 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(objectMetadataItemsState); + const objectMetadataItems = get(objectMetadataItemsSelector); const objectMetadataItem = objectMetadataItems.find( (item) => item.nameSingular === objectNameSingular, diff --git a/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx b/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx index e95413eb7fe..d4b75364715 100644 --- a/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx @@ -3,7 +3,7 @@ import { type Meta, type StoryObj, } from '@storybook/react-vite'; -import { expect, userEvent, within } from 'storybook/test'; +import { expect, userEvent, waitFor, within } from 'storybook/test'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; @@ -152,8 +152,10 @@ export const LimitedPermissions: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(await canvas.findByText('Go to People')).toBeVisible(); - expect(canvas.queryByText('Go to Opportunities')).not.toBeInTheDocument(); - expect(canvas.queryByText('Go to Tasks')).not.toBeInTheDocument(); + await waitFor(() => { + expect(canvas.queryByText('Go to Opportunities')).not.toBeInTheDocument(); + expect(canvas.queryByText('Go to Tasks')).not.toBeInTheDocument(); + }); expect(await canvas.findByText('Go to Settings')).toBeVisible(); expect(await canvas.findByText('Go to Notes')).toBeVisible(); }, 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 index 35fef1073c6..2b9b06b1ea9 100644 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/FieldMetadataSSEEffect.tsx +++ b/packages/twenty-front/src/modules/metadata-store/effect-components/FieldMetadataSSEEffect.tsx @@ -1,7 +1,6 @@ import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; -import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; +import { type FlatFieldMetadataItem } from '@/metadata-store/types/FlatFieldMetadataItem'; import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; import { useStore } from 'jotai'; import { AllMetadataName } from '~/generated-metadata/graphql'; @@ -11,9 +10,6 @@ export const FieldMetadataSSEEffect = () => { const store = useStore(); - const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems(); - const { updateDraft, applyChanges } = useMetadataStore(); - useListenToEventsForQuery({ queryId, operationSignature: { @@ -24,12 +20,52 @@ export const FieldMetadataSSEEffect = () => { useListenToMetadataOperationBrowserEvent({ metadataName: AllMetadataName.fieldMetadata, - onMetadataOperationBrowserEvent: async () => { - await refreshObjectMetadataItems(); + onMetadataOperationBrowserEvent: (eventDetail) => { + const entry = store.get( + metadataStoreState.atomFamily('fieldMetadataItems'), + ); + const currentFields = entry.current as FlatFieldMetadataItem[]; - const loadedObjects = store.get(objectMetadataItemsState.atom); - updateDraft('objectMetadataItems', loadedObjects); - applyChanges(); + switch (eventDetail.operation.type) { + case 'create': { + const createdField = eventDetail.operation + .createdRecord as unknown as FlatFieldMetadataItem; + + store.set(metadataStoreState.atomFamily('fieldMetadataItems'), { + ...entry, + current: [...currentFields, createdField], + }); + break; + } + case 'update': { + const updatedField = eventDetail.operation + .updatedRecord as unknown as FlatFieldMetadataItem; + + store.set(metadataStoreState.atomFamily('fieldMetadataItems'), { + ...entry, + current: currentFields.map((field) => + field.id === updatedField.id + ? { ...field, ...updatedField } + : field, + ), + }); + break; + } + case 'delete': { + const deletedFieldId = eventDetail.operation + .deletedRecordId as string; + + store.set(metadataStoreState.atomFamily('fieldMetadataItems'), { + ...entry, + current: currentFields.filter( + (field) => field.id !== deletedFieldId, + ), + }); + break; + } + default: + return; + } }, }); 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 index 96e013d842e..3cbe602bf55 100644 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/ObjectMetadataItemSSEEffect.tsx +++ b/packages/twenty-front/src/modules/metadata-store/effect-components/ObjectMetadataItemSSEEffect.tsx @@ -1,7 +1,6 @@ import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent'; -import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; -import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; +import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem'; import { navigationMenuItemsState } from '@/navigation-menu-item/states/navigationMenuItemsState'; import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery'; import { useStore } from 'jotai'; @@ -18,9 +17,6 @@ export const ObjectMetadataItemSSEEffect = () => { const store = useStore(); const client = useApolloClient(); - const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems(); - const { updateDraft, applyChanges } = useMetadataStore(); - useListenToEventsForQuery({ queryId, operationSignature: { @@ -31,12 +27,52 @@ export const ObjectMetadataItemSSEEffect = () => { useListenToMetadataOperationBrowserEvent({ metadataName: AllMetadataName.objectMetadata, - onMetadataOperationBrowserEvent: async () => { - await refreshObjectMetadataItems(); + onMetadataOperationBrowserEvent: async (eventDetail) => { + const entry = store.get( + metadataStoreState.atomFamily('objectMetadataItems'), + ); + const currentObjects = entry.current as FlatObjectMetadataItem[]; - const loadedObjects = store.get(objectMetadataItemsState.atom); - updateDraft('objectMetadataItems', loadedObjects); - applyChanges(); + switch (eventDetail.operation.type) { + case 'create': { + const createdObject = eventDetail.operation + .createdRecord as unknown as FlatObjectMetadataItem; + + store.set(metadataStoreState.atomFamily('objectMetadataItems'), { + ...entry, + current: [...currentObjects, createdObject], + }); + break; + } + case 'update': { + const updatedObject = eventDetail.operation + .updatedRecord as unknown as FlatObjectMetadataItem; + + store.set(metadataStoreState.atomFamily('objectMetadataItems'), { + ...entry, + current: currentObjects.map((object) => + object.id === updatedObject.id + ? { ...object, ...updatedObject } + : object, + ), + }); + break; + } + case 'delete': { + const deletedObjectId = eventDetail.operation + .deletedRecordId as string; + + store.set(metadataStoreState.atomFamily('objectMetadataItems'), { + ...entry, + current: currentObjects.filter( + (object) => object.id !== deletedObjectId, + ), + }); + break; + } + default: + return; + } const navigationMenuItemsResult = await client.query({ query: FindManyNavigationMenuItemsDocument, 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 index 201ba6b1137..3d117ae5af3 100644 --- a/packages/twenty-front/src/modules/metadata-store/effect-components/ObjectMetadataProviderInitialEffect.tsx +++ b/packages/twenty-front/src/modules/metadata-store/effect-components/ObjectMetadataProviderInitialEffect.tsx @@ -1,23 +1,18 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState'; -import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems'; import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useStore } from 'jotai'; import { useEffect, useState } from 'react'; import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace'; export const ObjectMetadataProviderInitialEffect = () => { const isCurrentUserLoaded = useAtomStateValue(isCurrentUserLoadedState); const currentWorkspace = useAtomStateValue(currentWorkspaceState); - const store = useStore(); const [isInitialized, setIsInitialized] = useState(false); const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems(); const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems(); - const { updateDraft, applyChanges } = useMetadataStore(); useEffect(() => { if (isInitialized) { @@ -36,9 +31,6 @@ export const ObjectMetadataProviderInitialEffect = () => { await loadMockedObjectMetadataItems(); } - const loadedItems = store.get(objectMetadataItemsState.atom); - updateDraft('objectMetadataItems', loadedItems); - applyChanges(); setIsInitialized(true); }; @@ -49,9 +41,6 @@ export const ObjectMetadataProviderInitialEffect = () => { currentWorkspace, refreshObjectMetadataItems, loadMockedObjectMetadataItems, - store, - updateDraft, - applyChanges, ]); return null; diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useFetchAndLoadIndexViews.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useFetchAndLoadIndexViews.ts index 8ec0de0b6bc..7f65c805c6a 100644 --- a/packages/twenty-front/src/modules/metadata-store/hooks/useFetchAndLoadIndexViews.ts +++ b/packages/twenty-front/src/modules/metadata-store/hooks/useFetchAndLoadIndexViews.ts @@ -1,5 +1,6 @@ import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; import { useSetIndexViews } from '@/metadata-store/hooks/useSetIndexViews'; +import { type View } from '@/views/types/View'; import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { useApolloClient } from '@apollo/client/react'; @@ -24,7 +25,8 @@ export const useFetchAndLoadIndexViews = () => { if (isDefined(result.data?.getCoreViews)) { setIndexViews(result.data.getCoreViews); - updateDraft('views', result.data.getCoreViews); + // TODO: align generated ViewType with app ViewType to remove this cast + updateDraft('views', result.data.getCoreViews as unknown as View[]); applyChanges(); } }, [client, setIndexViews, updateDraft, applyChanges]); 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 50f1fa08d2b..64f69f51556 100644 --- a/packages/twenty-front/src/modules/metadata-store/hooks/useMetadataStore.ts +++ b/packages/twenty-front/src/modules/metadata-store/hooks/useMetadataStore.ts @@ -5,6 +5,7 @@ import { type MetadataEntityKey, type MetadataStoreItem, } from '@/metadata-store/states/metadataStoreState'; +import { type MetadataEntityTypeMap } from '@/metadata-store/types/MetadataEntityTypeMap'; import { useStore, type createStore } from 'jotai'; import { useCallback } from 'react'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -55,7 +56,7 @@ export const useMetadataStore = () => { const store = useStore(); const updateDraft = useCallback( - (key: MetadataEntityKey, data: object[]) => { + (key: K, data: MetadataEntityTypeMap[K][]) => { const currentEntry = store.get(metadataStoreState.atomFamily(key)); if ( diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useReloadWorkspaceMetadata.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useReloadWorkspaceMetadata.ts index 760f9a9488d..2b6746f4631 100644 --- a/packages/twenty-front/src/modules/metadata-store/hooks/useReloadWorkspaceMetadata.ts +++ b/packages/twenty-front/src/modules/metadata-store/hooks/useReloadWorkspaceMetadata.ts @@ -1,7 +1,7 @@ import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems'; import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { type View } from '@/views/types/View'; import { coreViewsState } from '@/views/states/coreViewState'; import { useStore } from 'jotai'; import { useCallback } from 'react'; @@ -16,11 +16,9 @@ export const useReloadWorkspaceMetadata = () => { resetMetadataStore(); await refreshObjectMetadataItems(); - const loadedObjects = store.get(objectMetadataItemsState.atom); - updateDraft('objectMetadataItems', loadedObjects); - applyChanges(); - const loadedViews = store.get(coreViewsState.atom); + // TODO: align generated ViewType with app ViewType to remove this cast + const loadedViews = store.get(coreViewsState.atom) as unknown as View[]; updateDraft('views', loadedViews); applyChanges(); }, [ @@ -35,16 +33,7 @@ export const useReloadWorkspaceMetadata = () => { resetMetadataStore(); await loadMockedObjectMetadataItems(); - const loadedObjects = store.get(objectMetadataItemsState.atom); - updateDraft('objectMetadataItems', loadedObjects); - applyChanges(); - }, [ - resetMetadataStore, - loadMockedObjectMetadataItems, - store, - updateDraft, - applyChanges, - ]); + }, [resetMetadataStore, loadMockedObjectMetadataItems]); return { reloadWorkspaceMetadata, resetToMockedMetadata }; }; diff --git a/packages/twenty-front/src/modules/metadata-store/states/fieldMetadataItemsSelector.ts b/packages/twenty-front/src/modules/metadata-store/states/fieldMetadataItemsSelector.ts new file mode 100644 index 00000000000..c3391c2466c --- /dev/null +++ b/packages/twenty-front/src/modules/metadata-store/states/fieldMetadataItemsSelector.ts @@ -0,0 +1,14 @@ +import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; +import { type FlatFieldMetadataItem } from '@/metadata-store/types/FlatFieldMetadataItem'; +import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; + +export const fieldMetadataItemsSelector = createAtomSelector< + FlatFieldMetadataItem[] +>({ + key: 'fieldMetadataItemsSelector', + get: ({ get }) => { + const storeItem = get(metadataStoreState, 'fieldMetadataItems'); + + return storeItem.current as FlatFieldMetadataItem[]; + }, +}); diff --git a/packages/twenty-front/src/modules/metadata-store/states/indexMetadataItemsSelector.ts b/packages/twenty-front/src/modules/metadata-store/states/indexMetadataItemsSelector.ts new file mode 100644 index 00000000000..02e66799134 --- /dev/null +++ b/packages/twenty-front/src/modules/metadata-store/states/indexMetadataItemsSelector.ts @@ -0,0 +1,14 @@ +import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; +import { type FlatIndexMetadataItem } from '@/metadata-store/types/FlatIndexMetadataItem'; +import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; + +export const indexMetadataItemsSelector = createAtomSelector< + FlatIndexMetadataItem[] +>({ + key: 'indexMetadataItemsSelector', + get: ({ get }) => { + const storeItem = get(metadataStoreState, 'indexMetadataItems'); + + return storeItem.current as FlatIndexMetadataItem[]; + }, +}); 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 85dbafd1685..cce7ff57c20 100644 --- a/packages/twenty-front/src/modules/metadata-store/states/metadataStoreState.ts +++ b/packages/twenty-front/src/modules/metadata-store/states/metadataStoreState.ts @@ -8,6 +8,7 @@ export type MetadataEntityStoreStatus = export const ALL_METADATA_ENTITY_KEYS = [ 'objectMetadataItems', 'fieldMetadataItems', + 'indexMetadataItems', 'views', 'viewFields', 'viewFilters', diff --git a/packages/twenty-front/src/modules/metadata-store/states/metadataStoreStatusFamilySelector.ts b/packages/twenty-front/src/modules/metadata-store/states/metadataStoreStatusFamilySelector.ts new file mode 100644 index 00000000000..8280418bb45 --- /dev/null +++ b/packages/twenty-front/src/modules/metadata-store/states/metadataStoreStatusFamilySelector.ts @@ -0,0 +1,20 @@ +import { + metadataStoreState, + type MetadataEntityKey, + type MetadataEntityStoreStatus, +} from '@/metadata-store/states/metadataStoreState'; +import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; + +export const metadataStoreStatusFamilySelector = createAtomFamilySelector< + MetadataEntityStoreStatus, + MetadataEntityKey +>({ + key: 'metadataStoreStatusFamilySelector', + get: + (entityKey: MetadataEntityKey) => + ({ get }) => { + const storeItem = get(metadataStoreState, entityKey); + + return storeItem.status; + }, +}); diff --git a/packages/twenty-front/src/modules/metadata-store/states/objectMetadataItemsSelector.ts b/packages/twenty-front/src/modules/metadata-store/states/objectMetadataItemsSelector.ts new file mode 100644 index 00000000000..218ce280e31 --- /dev/null +++ b/packages/twenty-front/src/modules/metadata-store/states/objectMetadataItemsSelector.ts @@ -0,0 +1,14 @@ +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< + FlatObjectMetadataItem[] +>({ + key: 'objectMetadataItemsSelector', + get: ({ get }) => { + const storeItem = get(metadataStoreState, 'objectMetadataItems'); + + return storeItem.current as FlatObjectMetadataItem[]; + }, +}); diff --git a/packages/twenty-front/src/modules/metadata-store/types/FlatFieldMetadataItem.ts b/packages/twenty-front/src/modules/metadata-store/types/FlatFieldMetadataItem.ts new file mode 100644 index 00000000000..e2f0ba79db4 --- /dev/null +++ b/packages/twenty-front/src/modules/metadata-store/types/FlatFieldMetadataItem.ts @@ -0,0 +1,5 @@ +import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; + +export type FlatFieldMetadataItem = FieldMetadataItem & { + objectMetadataId: string; +}; diff --git a/packages/twenty-front/src/modules/metadata-store/types/FlatIndexMetadataItem.ts b/packages/twenty-front/src/modules/metadata-store/types/FlatIndexMetadataItem.ts new file mode 100644 index 00000000000..eb906839b53 --- /dev/null +++ b/packages/twenty-front/src/modules/metadata-store/types/FlatIndexMetadataItem.ts @@ -0,0 +1,5 @@ +import { type IndexMetadataItem } from '@/object-metadata/types/IndexMetadataItem'; + +export type FlatIndexMetadataItem = IndexMetadataItem & { + objectMetadataId: string; +}; diff --git a/packages/twenty-front/src/modules/metadata-store/types/FlatObjectMetadataItem.ts b/packages/twenty-front/src/modules/metadata-store/types/FlatObjectMetadataItem.ts new file mode 100644 index 00000000000..bcb0af15830 --- /dev/null +++ b/packages/twenty-front/src/modules/metadata-store/types/FlatObjectMetadataItem.ts @@ -0,0 +1,6 @@ +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +export type FlatObjectMetadataItem = Omit< + ObjectMetadataItem, + 'fields' | 'readableFields' | 'updatableFields' | 'indexMetadatas' +>; diff --git a/packages/twenty-front/src/modules/metadata-store/types/MetadataEntityTypeMap.ts b/packages/twenty-front/src/modules/metadata-store/types/MetadataEntityTypeMap.ts new file mode 100644 index 00000000000..2e9fe2ddfaf --- /dev/null +++ b/packages/twenty-front/src/modules/metadata-store/types/MetadataEntityTypeMap.ts @@ -0,0 +1,23 @@ +import { type FlatFieldMetadataItem } from '@/metadata-store/types/FlatFieldMetadataItem'; +import { type FlatIndexMetadataItem } from '@/metadata-store/types/FlatIndexMetadataItem'; +import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem'; +import { type PageLayout } from '@/page-layout/types/PageLayout'; +import { type LogicFunction } from '@/settings/logic-functions/states/logicFunctionsState'; +import { type View } from '@/views/types/View'; +import { type ViewField } from '@/views/types/ViewField'; +import { type ViewFilter } from '@/views/types/ViewFilter'; +import { type ViewSort } from '@/views/types/ViewSort'; +import { type NavigationMenuItem } from '~/generated-metadata/graphql'; + +export type MetadataEntityTypeMap = { + objectMetadataItems: FlatObjectMetadataItem; + fieldMetadataItems: FlatFieldMetadataItem; + indexMetadataItems: FlatIndexMetadataItem; + views: View; + viewFields: ViewField; + viewFilters: ViewFilter; + viewSorts: ViewSort; + pageLayouts: PageLayout; + logicFunctions: LogicFunction; + navigationMenuItems: NavigationMenuItem; +}; diff --git a/packages/twenty-front/src/modules/metadata-store/utils/splitObjectMetadataItemWithRelated.ts b/packages/twenty-front/src/modules/metadata-store/utils/splitObjectMetadataItemWithRelated.ts new file mode 100644 index 00000000000..b063a0657e2 --- /dev/null +++ b/packages/twenty-front/src/modules/metadata-store/utils/splitObjectMetadataItemWithRelated.ts @@ -0,0 +1,49 @@ +import { type FlatFieldMetadataItem } from '@/metadata-store/types/FlatFieldMetadataItem'; +import { type FlatIndexMetadataItem } from '@/metadata-store/types/FlatIndexMetadataItem'; +import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem'; +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +export type ObjectMetadataItemWithRelated = Omit< + ObjectMetadataItem, + 'readableFields' | 'updatableFields' +>; + +type SplitResult = { + flatObjects: FlatObjectMetadataItem[]; + flatFields: FlatFieldMetadataItem[]; + flatIndexes: FlatIndexMetadataItem[]; +}; + +export const splitObjectMetadataItemWithRelated = ( + objectMetadataItemsWithRelated: ObjectMetadataItemWithRelated[], +): SplitResult => { + const flatObjects: FlatObjectMetadataItem[] = []; + const flatFields: FlatFieldMetadataItem[] = []; + const flatIndexes: FlatIndexMetadataItem[] = []; + + for (const objectMetadataItemWithRelated of objectMetadataItemsWithRelated) { + const { + fields = [], + indexMetadatas = [], + ...objectProperties + } = objectMetadataItemWithRelated; + + flatObjects.push(objectProperties); + + for (const field of fields) { + flatFields.push({ + ...field, + objectMetadataId: objectMetadataItemWithRelated.id, + }); + } + + for (const index of indexMetadatas) { + flatIndexes.push({ + ...index, + objectMetadataId: objectMetadataItemWithRelated.id, + }); + } + } + + return { flatObjects, flatFields, flatIndexes }; +}; 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 e44fa40d301..52530a266d5 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 @@ -4,8 +4,8 @@ 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 { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; 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'; @@ -29,8 +29,8 @@ const renderHooks = ({ withCurrentUser: boolean; withExistingView: boolean; }) => { - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx index 8144b461169..0b2496e08db 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx @@ -8,8 +8,8 @@ import { variables, } from '@/object-metadata/hooks/__mocks__/useFilteredObjectMetadataItems'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { isDefined } from 'twenty-shared/utils'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; @@ -28,8 +28,8 @@ const mocks = [ ]; const Wrapper = ({ children }: { children: ReactNode }) => { - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx index 3cdea075394..4b32403c809 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx @@ -2,8 +2,8 @@ import { renderHook } from '@testing-library/react'; import { Provider as JotaiProvider } from 'jotai'; import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; const Wrapper = ({ children }: { children: React.ReactNode }) => ( @@ -12,8 +12,8 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => ( describe('useGetObjectRecordIdentifierByNameSingular', () => { beforeEach(() => { - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); }); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx index 67b722be16f..614ad0c387c 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx @@ -4,8 +4,8 @@ import { type ReactNode } from 'react'; import { Provider as JotaiProvider } from 'jotai'; import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; const Wrapper = ({ children }: { children: ReactNode }) => ( @@ -16,8 +16,8 @@ const Wrapper = ({ children }: { children: ReactNode }) => ( describe('useGetRelationMetadata', () => { beforeEach(() => { - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); }); @@ -50,11 +50,11 @@ describe('useGetRelationMetadata', () => { (field) => field.name === 'pointOfContact', ); - expect(relationObjectMetadataItem).toEqual( - expectedRelationObjectMetadataItem, + expect(relationObjectMetadataItem).toMatchObject( + expectedRelationObjectMetadataItem!, ); - expect(relationFieldMetadataItem).toEqual( - expectedRelationFieldMetadataItem, + expect(relationFieldMetadataItem).toMatchObject( + expectedRelationFieldMetadataItem!, ); expect(relationType).toBe('ONE_TO_MANY'); }); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useFilteredObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useFilteredObjectMetadataItems.ts index 2b43e705107..f8c9cf5116a 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useFilteredObjectMetadataItems.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useFilteredObjectMetadataItems.ts @@ -1,9 +1,12 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { useMemo } from 'react'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; export const useFilteredObjectMetadataItems = () => { - const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); + const objectMetadataItemsWithFields = useAtomStateValue( + objectMetadataItemsWithFieldsSelector, + ); + const objectMetadataItems = objectMetadataItemsWithFields; const activeNonSystemObjectMetadataItems = useMemo( () => 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 ff938a9b99c..d73d9268c9d 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useLoadMockedObjectMetadataItems.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useLoadMockedObjectMetadataItems.ts @@ -1,32 +1,30 @@ import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { useCallback } from 'react'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; +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 () => { const { generatedMockObjectMetadataItems } = await import( '~/testing/utils/generatedMockObjectMetadataItems' ); - if ( - !isDeeplyEqual( - store.get(objectMetadataItemsState.atom), - generatedMockObjectMetadataItems, - ) - ) { - store.set( - objectMetadataItemsState.atom, - generatedMockObjectMetadataItems, - ); - } + const { flatObjects, flatFields, flatIndexes } = + splitObjectMetadataItemWithRelated(generatedMockObjectMetadataItems); + + updateDraft('objectMetadataItems', flatObjects); + updateDraft('fieldMetadataItems', flatFields); + updateDraft('indexMetadataItems', flatIndexes); + applyChanges(); if (store.get(isAppEffectRedirectEnabledState.atom) === false) { store.set(isAppEffectRedirectEnabledState.atom, true); } - }, [store]); + }, [store, updateDraft, applyChanges]); return { loadMockedObjectMetadataItems, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts index 9e6b31a3ce9..2b2a0aaaec7 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts @@ -1,6 +1,6 @@ import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError'; import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; @@ -18,7 +18,10 @@ export const useObjectMetadataItem = ({ }, ); - const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); + const objectMetadataItemsWithFields = useAtomStateValue( + objectMetadataItemsWithFieldsSelector, + ); + const objectMetadataItems = objectMetadataItemsWithFields; if (!isDefined(objectMetadataItem)) { throw new ObjectMetadataItemNotFoundError( diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItems.ts index 12d2cfabc0e..76b00948785 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItems.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItems.ts @@ -1,9 +1,11 @@ +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; - export const useObjectMetadataItems = () => { - const objectMetadataItems = useAtomStateValue(objectMetadataItemsState); + const objectMetadataItemsWithFields = useAtomStateValue( + objectMetadataItemsWithFieldsSelector, + ); + const objectMetadataItems = objectMetadataItemsWithFields; return { objectMetadataItems, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useRefreshObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useRefreshObjectMetadataItems.ts index 0d5e5c469d2..866bcba87c8 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useRefreshObjectMetadataItems.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useRefreshObjectMetadataItems.ts @@ -1,75 +1,22 @@ import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState'; -import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; +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 { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { enrichObjectMetadataItemsWithPermissions } from '@/object-metadata/utils/enrichObjectMetadataItemsWithPermissions'; import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '@/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems'; import { type FetchPolicy } from '@apollo/client'; import { useApolloClient } from '@apollo/client/react'; -import { useCallback } from 'react'; -import { type ObjectPermissions } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; -import { type ObjectMetadataItemsQuery } from '~/generated-metadata/graphql'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; 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 replaceObjectMetadataItemIfDifferent = useCallback( - ( - toSetObjectMetadataItems: Omit< - ObjectMetadataItem, - 'readableFields' | 'updatableFields' - >[], - ) => { - const currentUserWorkspace = store.get(currentUserWorkspaceState.atom); - - if (!isDefined(currentUserWorkspace)) { - return; - } - - const objectPermissionsByObjectMetadataId = - currentUserWorkspace.objectsPermissions.reduce( - (acc, objectPermission) => { - acc[objectPermission.objectMetadataId] = objectPermission; - return acc; - }, - {} as Record< - string, - ObjectPermissions & { objectMetadataId: string } - >, - ); - - const newObjectMetadataItems = enrichObjectMetadataItemsWithPermissions({ - objectMetadataItems: toSetObjectMetadataItems, - objectPermissionsByObjectMetadataId, - }); - - if ( - !isDeeplyEqual( - store.get(objectMetadataItemsState.atom), - newObjectMetadataItems, - ) && - newObjectMetadataItems.length > 0 - ) { - store.set(objectMetadataItemsState.atom, newObjectMetadataItems); - } - - if (store.get(isAppEffectRedirectEnabledState.atom) === false) { - store.set(isAppEffectRedirectEnabledState.atom, true); - } - - return newObjectMetadataItems; - }, - [store], - ); - - const refreshObjectMetadataItems = async () => { + const refreshObjectMetadataItems = useCallback(async () => { const objectMetadataItemsResult = await client.query({ query: FIND_MANY_OBJECT_METADATA_ITEMS, @@ -77,13 +24,23 @@ export const useRefreshObjectMetadataItems = ( fetchPolicy, }); - const objectMetadataItems = + const compositeObjects = mapPaginatedObjectMetadataItemsToObjectMetadataItems({ pagedObjectMetadataItems: objectMetadataItemsResult.data, }); - return replaceObjectMetadataItemIfDifferent(objectMetadataItems); - }; + 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/states/activeObjectNameSingularsSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/activeObjectNameSingularsSelector.ts new file mode 100644 index 00000000000..16e6944cd45 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/states/activeObjectNameSingularsSelector.ts @@ -0,0 +1,13 @@ +import { objectMetadataItemsSelector } from '@/metadata-store/states/objectMetadataItemsSelector'; +import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; + +export const activeObjectNameSingularsSelector = createAtomSelector({ + key: 'activeObjectNameSingularsSelector', + get: ({ get }) => { + const flatObjects = get(objectMetadataItemsSelector); + + return flatObjects + .filter((flatObject) => flatObject.isActive) + .map((flatObject) => flatObject.nameSingular); + }, +}); diff --git a/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector.ts b/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector.ts index f0f64ebc37f..aaf0ca76a40 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector.ts @@ -1,5 +1,5 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { getFilterFilterableFieldMetadataItems } from '@/object-metadata/utils/getFilterFilterableFieldMetadataItems'; import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; @@ -17,7 +17,7 @@ export const availableFieldMetadataItemsForFilterFamilySelector = ({ objectMetadataItemId }: { objectMetadataItemId: string }) => ({ get }) => { const currentWorkspace = get(currentWorkspaceState); - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector); const objectMetadataItem = objectMetadataItems.find( (item) => item.id === objectMetadataItemId, diff --git a/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector.ts b/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector.ts index 1c86c74552e..27541f326dc 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector.ts @@ -1,4 +1,4 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { filterSortableFieldMetadataItems } from '@/object-metadata/utils/filterSortableFieldMetadataItems'; import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; @@ -13,7 +13,7 @@ export const availableFieldMetadataItemsForSortFamilySelector = get: ({ objectMetadataItemId }: { objectMetadataItemId: string }) => ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector); const objectMetadataItem = objectMetadataItems.find( (item) => item.id === objectMetadataItemId, diff --git a/packages/twenty-front/src/modules/object-metadata/states/fieldMetadataItemByIdSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/fieldMetadataItemByIdSelector.ts index 8fe8aee9e29..e878c187474 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/fieldMetadataItemByIdSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/fieldMetadataItemByIdSelector.ts @@ -1,5 +1,5 @@ import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; import { findById, isDefined } from 'twenty-shared/utils'; @@ -8,7 +8,7 @@ export const fieldMetadataItemByIdSelector = createAtomFamilySelector({ get: ({ fieldMetadataItemId }: { fieldMetadataItemId: string }) => ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector); const flattenedFieldMetadataItems = get( flattenedFieldMetadataItemsSelector, ); diff --git a/packages/twenty-front/src/modules/object-metadata/states/flattenedFieldMetadataItemsSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/flattenedFieldMetadataItemsSelector.ts index a5c89ff7efe..cce73b146ee 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/flattenedFieldMetadataItemsSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/flattenedFieldMetadataItemsSelector.ts @@ -1,4 +1,4 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; @@ -7,7 +7,7 @@ export const flattenedFieldMetadataItemsSelector = createAtomSelector< >({ key: 'flattenedFieldMetadataItemsSelector', get: ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector); return objectMetadataItems.flatMap( (objectMetadataItem) => objectMetadataItem.fields, diff --git a/packages/twenty-front/src/modules/object-metadata/states/flattenedReadableFieldMetadataItemIdsSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/flattenedReadableFieldMetadataItemIdsSelector.ts index ce698344e1f..08ca20a02bd 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/flattenedReadableFieldMetadataItemIdsSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/flattenedReadableFieldMetadataItemIdsSelector.ts @@ -1,4 +1,4 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; @@ -7,7 +7,7 @@ export const flattenedReadableFieldMetadataItemsSelector = createAtomSelector< >({ key: 'flattenedReadableFieldMetadataItemsSelector', get: ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector); return objectMetadataItems.flatMap( (objectMetadataItem) => objectMetadataItem.readableFields, diff --git a/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemFilterableAndSortableSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemFilterableAndSortableSelector.ts index b0e4cba1753..2088e5f677e 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemFilterableAndSortableSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemFilterableAndSortableSelector.ts @@ -1,7 +1,7 @@ import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector'; import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; import { findById, isDefined } from 'twenty-shared/utils'; @@ -11,7 +11,7 @@ export const isFieldMetadataItemFilterableAndSortableSelector = get: ({ fieldMetadataItemId }: { fieldMetadataItemId: string }) => ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector); const flattenedFieldMetadataItems = get( flattenedFieldMetadataItemsSelector, ); diff --git a/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector.ts index 8ab417daeab..e7f0485fea2 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector.ts @@ -1,4 +1,4 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField'; import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; import { findById, isDefined } from 'twenty-shared/utils'; @@ -9,7 +9,7 @@ export const isFieldMetadataItemLabelIdentifierSelector = get: ({ fieldMetadataItemId }: { fieldMetadataItemId: string }) => ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector); const foundObjectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => diff --git a/packages/twenty-front/src/modules/object-metadata/states/isSystemObjectByNameSingularFamilySelector.ts b/packages/twenty-front/src/modules/object-metadata/states/isSystemObjectByNameSingularFamilySelector.ts new file mode 100644 index 00000000000..b3e8f11331b --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/states/isSystemObjectByNameSingularFamilySelector.ts @@ -0,0 +1,18 @@ +import { objectMetadataItemsSelector } from '@/metadata-store/states/objectMetadataItemsSelector'; +import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; + +export const isSystemObjectByNameSingularFamilySelector = + createAtomFamilySelector({ + key: 'isSystemObjectByNameSingularFamilySelector', + get: + (nameSingular: string) => + ({ get }) => { + const flatObjects = get(objectMetadataItemsSelector); + + return ( + flatObjects.find( + (flatObject) => flatObject.nameSingular === nameSingular, + )?.isSystem ?? false + ); + }, + }); diff --git a/packages/twenty-front/src/modules/object-metadata/states/labelIdentifierFieldMetadataItemSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/labelIdentifierFieldMetadataItemSelector.ts index 3825573ec6b..74bc18381da 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/labelIdentifierFieldMetadataItemSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/labelIdentifierFieldMetadataItemSelector.ts @@ -1,4 +1,4 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField'; import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; import { isDefined } from 'twenty-shared/utils'; @@ -9,7 +9,7 @@ export const labelIdentifierFieldMetadataItemSelector = get: ({ objectMetadataItemId }: { objectMetadataItemId: string }) => ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => diff --git a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemFamilySelector.ts b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemFamilySelector.ts index 12c98acb406..91497ce7473 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemFamilySelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemFamilySelector.ts @@ -1,4 +1,4 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; @@ -15,7 +15,7 @@ export const objectMetadataItemFamilySelector = createAtomFamilySelector< get: ({ objectNameType, objectName }: ObjectMetadataItemSelector) => ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector); if (objectNameType === 'singular') { return ( diff --git a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNamePluralMapSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNamePluralMapSelector.ts index 3a68ef631df..46b9f3bb2d0 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNamePluralMapSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNamePluralMapSelector.ts @@ -1,4 +1,4 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; @@ -7,7 +7,7 @@ export const objectMetadataItemsByNamePluralMapSelector = createAtomSelector< >({ key: 'objectMetadataItemsByNamePluralMapSelector', get: ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector); return new Map( objectMetadataItems.map((objectMetadataItem) => [ diff --git a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNameSingularMapSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNameSingularMapSelector.ts index 4b9c2107035..2b07413146a 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNameSingularMapSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNameSingularMapSelector.ts @@ -1,4 +1,4 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; @@ -7,7 +7,7 @@ export const objectMetadataItemsByNameSingularMapSelector = createAtomSelector< >({ key: 'objectMetadataItemsByNameSingularMapSelector', get: ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector); return new Map( objectMetadataItems.map((objectMetadataItem) => [ diff --git a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsBySingularNameSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsBySingularNameSelector.ts index 41a437af479..4fdc8727298 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsBySingularNameSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsBySingularNameSelector.ts @@ -1,4 +1,4 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; @@ -8,7 +8,7 @@ export const objectMetadataItemsBySingularNameSelector = get: (objectNameSingulars: string[]) => ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector); return objectNameSingulars.flatMap((objectNameSingular) => { const found = objectMetadataItems.find( diff --git a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsState.ts b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsState.ts index 30e11b7fc6a..1c77d115a85 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsState.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsState.ts @@ -1,7 +1,10 @@ +import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; +import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; -export const objectMetadataItemsState = createAtomState({ +export const objectMetadataItemsState = createAtomSelector< + ObjectMetadataItem[] +>({ key: 'objectMetadataItemsState', - defaultValue: [], + get: ({ get }) => get(objectMetadataItemsWithFieldsSelector), }); diff --git a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsWithFieldsSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsWithFieldsSelector.ts new file mode 100644 index 00000000000..0e3c7c8720a --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsWithFieldsSelector.ts @@ -0,0 +1,95 @@ +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 { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { getNonReadableFieldMetadataIdsFromObjectPermissions } from '@/object-metadata/utils/getNonReadableFieldMetadataIdsFromObjectPermissions'; +import { getNonUpdatableFieldMetadataIdsFromObjectPermissions } from '@/object-metadata/utils/getNonUpdatableFieldMetadataIdsFromObjectPermissions'; +import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId'; +import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; +import { type ObjectPermissions } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +export const objectMetadataItemsWithFieldsSelector = createAtomSelector< + ObjectMetadataItem[] +>({ + key: 'objectMetadataItemsWithFieldsSelector', + get: ({ get }) => { + const flatObjects = get(objectMetadataItemsSelector); + const allFlatFields = get(fieldMetadataItemsSelector); + const allFlatIndexes = get(indexMetadataItemsSelector); + const currentUserWorkspace = get(currentUserWorkspaceState); + + const fieldsByObjectId = new Map< + string, + (typeof allFlatFields)[number][] + >(); + + for (const field of allFlatFields) { + const existing = fieldsByObjectId.get(field.objectMetadataId); + + if (isDefined(existing)) { + existing.push(field); + } else { + fieldsByObjectId.set(field.objectMetadataId, [field]); + } + } + + const indexesByObjectId = new Map< + string, + (typeof allFlatIndexes)[number][] + >(); + + for (const index of allFlatIndexes) { + const existing = indexesByObjectId.get(index.objectMetadataId); + + if (isDefined(existing)) { + existing.push(index); + } else { + indexesByObjectId.set(index.objectMetadataId, [index]); + } + } + + const objectPermissionsByObjectMetadataId = + currentUserWorkspace?.objectsPermissions.reduce( + (accumulator, objectPermission) => { + accumulator[objectPermission.objectMetadataId] = objectPermission; + + return accumulator; + }, + {} as Record, + ) ?? {}; + + return flatObjects.map((flatObject) => { + const fields = fieldsByObjectId.get(flatObject.id) ?? []; + const indexMetadatas = indexesByObjectId.get(flatObject.id) ?? []; + + const objectPermissions = getObjectPermissionsFromMapByObjectMetadataId({ + objectPermissionsByObjectMetadataId, + objectMetadataId: flatObject.id, + }); + + const nonReadableFieldMetadataIds = + getNonReadableFieldMetadataIdsFromObjectPermissions({ + objectPermissions, + }); + + const nonUpdatableFieldMetadataIds = + getNonUpdatableFieldMetadataIdsFromObjectPermissions({ + objectPermissions, + }); + + return { + ...flatObject, + fields, + indexMetadatas, + readableFields: fields.filter( + (field) => !nonReadableFieldMetadataIds.includes(field.id), + ), + updatableFields: fields.filter( + (field) => !nonUpdatableFieldMetadataIds.includes(field.id), + ), + } satisfies ObjectMetadataItem; + }); + }, +}); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx index e1bd33187e0..52e40426ca4 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx @@ -1,13 +1,16 @@ import { renderHook } from '@testing-library/react'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; -jotaiStore.set(objectMetadataItemsState.atom, generatedMockObjectMetadataItems); +setTestObjectMetadataItemsInMetadataStore( + jotaiStore, + generatedMockObjectMetadataItems, +); const Wrapper = getJestMetadataAndApolloMocksWrapper({ apolloMocks: [], diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecords.test.tsx index 5b620de4564..5975f74d25e 100644 --- a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecords.test.tsx @@ -1,8 +1,8 @@ import { gql } from '@apollo/client'; import { renderHook, waitFor } from '@testing-library/react'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { type RecordGqlOperationSignature } from 'twenty-shared/types'; import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords'; import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; @@ -123,8 +123,8 @@ const renderUseCombinedFindManyRecordsHook = async ({ }, ]; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/__stories__/ObjectOptionsDropdownContent.stories.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/__stories__/ObjectOptionsDropdownContent.stories.tsx index f74ae30401c..367244ec7e4 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/__stories__/ObjectOptionsDropdownContent.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/__stories__/ObjectOptionsDropdownContent.stories.tsx @@ -1,7 +1,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { ObjectOptionsDropdownContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownContent'; import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId'; import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext'; @@ -32,8 +32,8 @@ const meta: Meta = { decorators: [ (Story) => { useEffect(() => { - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); }, []); diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx index c5c576e13c3..ddd2f29fa08 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx @@ -1,7 +1,8 @@ import { type Meta, type StoryObj } from '@storybook/react-vite'; import { useEffect } from 'react'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; +import { splitObjectMetadataItemWithRelated } from '@/metadata-store/utils/splitObjectMetadataItemWithRelated'; import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; @@ -15,7 +16,6 @@ import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory'; import { labelIdentifierFieldMetadataItemSelector } from '@/object-metadata/states/labelIdentifierFieldMetadataItemSelector'; import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue'; -import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper'; @@ -53,7 +53,7 @@ const RelationFieldValueSetterEffect = () => { 'recordTableId', ); - const [, setObjectMetadataItems] = useAtomState(objectMetadataItemsState); + const { updateDraft, applyChanges } = useMetadataStore(); useEffect(() => { setRecordStore(mockPerformance.entityValue); @@ -71,11 +71,18 @@ const RelationFieldValueSetterEffect = () => { ), ); - setObjectMetadataItems(generatedMockObjectMetadataItems); + const { flatObjects, flatFields, flatIndexes } = + splitObjectMetadataItemWithRelated(generatedMockObjectMetadataItems); + + updateDraft('objectMetadataItems', flatObjects); + updateDraft('fieldMetadataItems', flatFields); + updateDraft('indexMetadataItems', flatIndexes); + applyChanges(); }, [ setRecordStore, setRelationRecordStore, - setObjectMetadataItems, + updateDraft, + applyChanges, setCurrentRecordFields, ]); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx index 40f22e21efe..703b5dd861c 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx @@ -1,8 +1,8 @@ import { act, renderHook } from '@testing-library/react'; import { Provider as JotaiProvider } from 'jotai'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from 'twenty-shared/types'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper'; import { textfieldDefinition } from '@/object-record/record-field/ui/__mocks__/fieldDefinitions'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; @@ -26,8 +26,8 @@ import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockO const recordTableId = 'record-table-id'; const Wrapper = ({ children }: { children: React.ReactNode }) => { - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx index 386e8829535..44349b36313 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx @@ -1,8 +1,8 @@ import { act, renderHook } from '@testing-library/react'; import { Provider as JotaiProvider } from 'jotai'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from 'twenty-shared/types'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper'; import { textfieldDefinition } from '@/object-record/record-field/ui/__mocks__/fieldDefinitions'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; @@ -26,8 +26,8 @@ import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockO const recordTableId = 'record-table-id'; const Wrapper = ({ children }: { children: React.ReactNode }) => { - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); diff --git a/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/__tests__/useBuildSpreadSheetImportFields.test.tsx b/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/__tests__/useBuildSpreadSheetImportFields.test.tsx index bcc92a050ea..26bbf10e9bf 100644 --- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/__tests__/useBuildSpreadSheetImportFields.test.tsx +++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/__tests__/useBuildSpreadSheetImportFields.test.tsx @@ -4,8 +4,8 @@ import { type ReactNode } from 'react'; import { useIcons } from 'twenty-ui/display'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { type IndexMetadataItem } from '@/object-metadata/types/IndexMetadataItem'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; @@ -34,7 +34,7 @@ describe('useBuildSpreadSheetImportFields', () => { getIcons: () => ({}), }); jest.clearAllMocks(); - jotaiStore.set(objectMetadataItemsState.atom, []); + setTestObjectMetadataItemsInMetadataStore(jotaiStore, []); }); const createMockFieldMetadataItem = ( 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 996e9d98145..a05cc65fa35 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,5 +1,5 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext'; import { @@ -33,8 +33,8 @@ const meta: Meta = { component: DashboardWidgetPlaceholder, decorators: [ (Story) => { - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); 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 4fa543087c1..004f2064d32 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,8 +8,8 @@ import { CatalogDecorator, type CatalogStory } from 'twenty-ui/testing'; import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { CoreObjectNameSingular } from 'twenty-shared/types'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; @@ -277,8 +277,8 @@ export const WithNumberChart: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -364,8 +364,8 @@ export const WithGaugeChart: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -455,8 +455,8 @@ export const WithBarChart: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -549,8 +549,8 @@ export const SmallWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -647,8 +647,8 @@ export const MediumWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -745,8 +745,8 @@ export const LargeWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -839,8 +839,8 @@ export const WideWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -937,8 +937,8 @@ export const TallWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1031,8 +1031,8 @@ export const WithManyToOneRelationFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1150,8 +1150,8 @@ export const WithOneToManyRelationFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1261,8 +1261,8 @@ export const OneToManyRelationFieldWidgetWithSeeAllButton: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1399,8 +1399,8 @@ export const OnMobile: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1497,8 +1497,8 @@ export const InSidePanel: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1656,8 +1656,8 @@ export const Catalog: CatalogStory = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); 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 4a5ba57ce00..ca016873eef 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,8 +5,8 @@ import { MemoryRouter } from 'react-router-dom'; import { expect, userEvent, waitFor, within } from 'storybook/test'; import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { CoreObjectNameSingular } from 'twenty-shared/types'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; @@ -356,8 +356,8 @@ export const TextFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -450,8 +450,8 @@ export const AddressFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -547,8 +547,8 @@ export const NumberFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -641,8 +641,8 @@ export const LinkFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -735,8 +735,8 @@ export const ManyToOneRelationFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -839,8 +839,8 @@ export const OneToManyRelationFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -935,8 +935,8 @@ export const BooleanFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1028,8 +1028,8 @@ export const CurrencyFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1121,8 +1121,8 @@ export const EmailsFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1215,8 +1215,8 @@ export const PhonesFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1309,8 +1309,8 @@ export const SelectFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1404,8 +1404,8 @@ export const MultiSelectFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1502,8 +1502,8 @@ export const TimelineActivityRelationFieldWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1601,8 +1601,8 @@ export const ManyToOneRelationCardWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1713,8 +1713,8 @@ export const OneToManyRelationCardWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1808,8 +1808,8 @@ export const TimelineActivityRelationCardWidget: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -1969,8 +1969,8 @@ export const OneToManyRelationCardWidgetWithProgressiveLoading: Story = { deletedAt: null, }; - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); 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 66aab3fe33c..0221c758b28 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 @@ -6,8 +6,8 @@ import { expect, waitFor, within } from 'storybook/test'; import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState'; import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; 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'; @@ -321,8 +321,8 @@ export const WithViewFieldGroups: Story = { companyObjectMetadataItem.id, ); - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -434,8 +434,8 @@ export const WithInlineViewFields: Story = { companyObjectMetadataItem.id, ); - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); @@ -526,8 +526,8 @@ export const Empty: Story = { companyObjectMetadataItem.id, ); - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); jotaiStore.set(isAppMetadataReadyState.atom, true); diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/__tests__/useChartSettingsValues.test.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/__tests__/useChartSettingsValues.test.tsx index b918aebce08..f8b49f243c6 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/__tests__/useChartSettingsValues.test.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/__tests__/useChartSettingsValues.test.tsx @@ -4,8 +4,8 @@ import { Provider as JotaiProvider } from 'jotai'; import { type ChartConfiguration } from '@/side-panel/pages/page-layout/types/ChartConfiguration'; import { CHART_CONFIGURATION_SETTING_IDS } from '@/side-panel/pages/page-layout/types/ChartConfigurationSettingIds'; import { type TypedBarChartConfiguration } from '@/side-panel/pages/page-layout/types/TypedBarChartConfiguration'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { AggregateOperations, AxisNameDisplay, @@ -80,7 +80,9 @@ const buildBarChartConfiguration = ( }) as TypedBarChartConfiguration; const renderUseChartSettingsValues = (configuration: ChartConfiguration) => { - jotaiStore.set(objectMetadataItemsState.atom, [mockObjectMetadataItem]); + setTestObjectMetadataItemsInMetadataStore(jotaiStore, [ + mockObjectMetadataItem, + ]); return renderHook( () => @@ -411,7 +413,9 @@ describe('useChartSettingsValues', () => { it('should handle missing objectMetadataItem gracefully', () => { const config = buildBarChartConfiguration({}); - jotaiStore.set(objectMetadataItemsState.atom, [mockObjectMetadataItem]); + setTestObjectMetadataItemsInMetadataStore(jotaiStore, [ + mockObjectMetadataItem, + ]); const { result } = renderHook( () => diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx index f89bbab166d..cef4f417e64 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx @@ -3,8 +3,8 @@ import { useEffect } from 'react'; import { expect, within } from 'storybook/test'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; import { SettingsPath } from 'twenty-shared/types'; import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator'; @@ -56,8 +56,8 @@ const meta: Meta = { currentWorkspaceMemberState, ); useEffect(() => { - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); setCurrentWorkspaceMember(mockedWorkspaceMemberData); diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown.test.tsx index 74b16ac1dc4..1ab77579e80 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown.test.tsx @@ -6,7 +6,6 @@ import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-rec import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown } from '@/views/hooks/useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { jotaiStore, resetJotaiStore, @@ -26,6 +25,7 @@ import { ViewBarFilterDropdownIds } from '@/views/constants/ViewBarFilterDropdow import { getFilterTypeFromFieldType } from 'twenty-shared/utils'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; const mockPushFocusItemToFocusStack = jest.fn(); @@ -46,8 +46,8 @@ const personCreatedAtFieldMetadataItemMock = ); const wrapper = ({ children }: { children: React.ReactNode }) => { - jotaiStore.set( - objectMetadataItemsState.atom, + setTestObjectMetadataItemsInMetadataStore( + jotaiStore, generatedMockObjectMetadataItems, ); return ( diff --git a/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsForCalendar.test.tsx b/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsForCalendar.test.tsx index 3f59d68d85f..3b585a90f0e 100644 --- a/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsForCalendar.test.tsx +++ b/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsForCalendar.test.tsx @@ -1,5 +1,5 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState'; import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useGetAvailableFieldsForCalendar'; @@ -22,7 +22,7 @@ const createMockObjectMetadataItem = (fields: any[]) => ({ const createWrapper = (objectMetadataItems: any[]) => { return ({ children }: { children: ReactNode }) => { - jotaiStore.set(objectMetadataItemsState.atom, objectMetadataItems); + setTestObjectMetadataItemsInMetadataStore(jotaiStore, objectMetadataItems); jotaiStore.set( viewObjectMetadataIdComponentState.atomFamily({ instanceId: mockViewInstanceId, @@ -80,7 +80,7 @@ describe('useGetAvailableFieldsForCalendar', () => { }); expect(result.current.availableFieldsForCalendar).toHaveLength(2); - expect(result.current.availableFieldsForCalendar).toEqual([ + expect(result.current.availableFieldsForCalendar).toMatchObject([ { id: '1', type: FieldMetadataType.DATE, diff --git a/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsToGroupRecordsBy.test.tsx b/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsToGroupRecordsBy.test.tsx index 0ed7f578b98..6bd4f1feddb 100644 --- a/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsToGroupRecordsBy.test.tsx +++ b/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsToGroupRecordsBy.test.tsx @@ -1,5 +1,5 @@ -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState'; import { useGetAvailableFieldsToGroupRecordsBy } from '@/views/view-picker/hooks/useGetAvailableFieldsToGroupRecordsBy'; @@ -22,7 +22,7 @@ const createMockObjectMetadataItem = (fields: any[]) => ({ const createWrapper = (objectMetadataItems: any[]) => { return ({ children }: { children: ReactNode }) => { - jotaiStore.set(objectMetadataItemsState.atom, objectMetadataItems); + setTestObjectMetadataItemsInMetadataStore(jotaiStore, objectMetadataItems); jotaiStore.set( viewObjectMetadataIdComponentState.atomFamily({ instanceId: mockViewInstanceId, diff --git a/packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx b/packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx index 5bda5680019..1c12f1bd178 100644 --- a/packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx +++ b/packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx @@ -10,9 +10,7 @@ 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 { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus'; import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; @@ -22,7 +20,7 @@ import { useLoadCurrentUser } from '@/users/hooks/useLoadCurrentUser'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { Trans, useLingui } from '@lingui/react/macro'; import { isNonEmptyString } from '@sniptt/guards'; -import { useStore } from 'jotai'; + import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { isDefined } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; @@ -73,9 +71,7 @@ export const CreateWorkspace = () => { const { enqueueErrorSnackBar } = useSnackBar(); const setNextOnboardingStatus = useSetNextOnboardingStatus(); const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems(); - const { updateDraft, applyChanges } = useMetadataStore(); const { fetchAndLoadIndexViews } = useFetchAndLoadIndexViews(); - const store = useStore(); const { loadCurrentUser } = useLoadCurrentUser(); const [activateWorkspace] = useMutation(ActivateWorkspaceDocument); @@ -132,10 +128,6 @@ export const CreateWorkspace = () => { await refreshObjectMetadataItems(); - const loadedObjects = store.get(objectMetadataItemsState.atom); - updateDraft('objectMetadataItems', loadedObjects); - applyChanges(); - await fetchAndLoadIndexViews(); await loadCurrentUser(); @@ -153,9 +145,6 @@ export const CreateWorkspace = () => { enqueueErrorSnackBar, loadCurrentUser, refreshObjectMetadataItems, - updateDraft, - applyChanges, - store, fetchAndLoadIndexViews, setNextOnboardingStatus, t, diff --git a/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx b/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx index aa3b47adfb5..9c351e924a7 100644 --- a/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx +++ b/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx @@ -1,8 +1,8 @@ import { type ReactNode, useEffect, useState } from 'react'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore'; +import { splitObjectMetadataItemWithRelated } from '@/metadata-store/utils/splitObjectMetadataItemWithRelated'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; export const JestObjectMetadataItemSetter = ({ @@ -12,15 +12,20 @@ export const JestObjectMetadataItemSetter = ({ children: ReactNode; objectMetadataItems?: ObjectMetadataItem[]; }) => { - const setObjectMetadataItems = useSetAtomState(objectMetadataItemsState); + const { updateDraft, applyChanges } = useMetadataStore(); const [isLoaded, setIsLoaded] = useState(false); useEffect(() => { - setObjectMetadataItems( - objectMetadataItems ?? generatedMockObjectMetadataItems, - ); + const items = objectMetadataItems ?? generatedMockObjectMetadataItems; + const { flatObjects, flatFields, flatIndexes } = + splitObjectMetadataItemWithRelated(items); + + updateDraft('objectMetadataItems', flatObjects); + updateDraft('fieldMetadataItems', flatFields); + updateDraft('indexMetadataItems', flatIndexes); + applyChanges(); setIsLoaded(true); - }, [objectMetadataItems, setObjectMetadataItems]); + }, [objectMetadataItems, updateDraft, applyChanges]); return isLoaded ? <>{children} : null; }; diff --git a/packages/twenty-front/src/testing/utils/setTestObjectMetadataItemsInMetadataStore.ts b/packages/twenty-front/src/testing/utils/setTestObjectMetadataItemsInMetadataStore.ts new file mode 100644 index 00000000000..f7cf9c3e129 --- /dev/null +++ b/packages/twenty-front/src/testing/utils/setTestObjectMetadataItemsInMetadataStore.ts @@ -0,0 +1,32 @@ +import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; +import { splitObjectMetadataItemWithRelated } from '@/metadata-store/utils/splitObjectMetadataItemWithRelated'; +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { type createStore } from 'jotai'; + +type JotaiStore = ReturnType; + +export const setTestObjectMetadataItemsInMetadataStore = ( + store: JotaiStore, + objectMetadataItems: ObjectMetadataItem[], +) => { + const { flatObjects, flatFields, flatIndexes } = + splitObjectMetadataItemWithRelated(objectMetadataItems); + + store.set(metadataStoreState.atomFamily('objectMetadataItems'), { + current: flatObjects, + draft: [], + status: 'up-to-date', + }); + + store.set(metadataStoreState.atomFamily('fieldMetadataItems'), { + current: flatFields, + draft: [], + status: 'up-to-date', + }); + + store.set(metadataStoreState.atomFamily('indexMetadataItems'), { + current: flatIndexes, + draft: [], + status: 'up-to-date', + }); +}; diff --git a/packages/twenty-front/vite.config.ts b/packages/twenty-front/vite.config.ts index ab7e3fa18e6..6bcf5ab7dcc 100644 --- a/packages/twenty-front/vite.config.ts +++ b/packages/twenty-front/vite.config.ts @@ -88,8 +88,12 @@ export default defineConfig(({ mode }) => { include: [path.resolve(__dirname, 'src') + '/**/*.{ts,tsx}'], exclude: [ '**/generated-metadata/**', - '**/testing/mock-data/generated/**', - '**/testing/**', + '**/testing/mock-data/**', + '**/testing/jest/**', + '**/testing/hooks/**', + '**/testing/utils/**', + '**/testing/constants/**', + '**/testing/cache/**', '**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}', '**/*.stories.{ts,tsx}',