Add RecordField abstraction (#13923)

This PR adds a new RecordField abstraction to mimick the behavior of
RecordFilter, RecordSort and other similar concepts that allow to have
something that is not related directly to views.

It is closely following the same pattern with an Effect component to
initialize it from views and CRUD hooks to modify it.

Although for this concept we save to view fields automatically for every
modification.

This PR does not implement saving to view fields, it is just a parallel
code path that is meant to be built to see if everything behaves the
same then we'll remove the old code path at the end by saving record
fields instead of the many states for columns, fields definitions, etc.
This commit is contained in:
Lucas Bordeau
2025-08-14 15:00:07 +02:00
committed by GitHub
parent 8c78314b52
commit fd2b063d84
31 changed files with 717 additions and 423 deletions
@@ -8,9 +8,7 @@ import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { AnimatePresence } from 'framer-motion';
@@ -50,35 +48,23 @@ export const CommandMenuContainer = ({
useCommandMenuHotKeys();
return (
<RecordFilterGroupsComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }}
>
<RecordFiltersComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }}
<RecordComponentInstanceContextsWrapper componentInstanceId={recordIndexId}>
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID }}
>
<RecordSortsComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }}
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID }}
>
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID }}
<AnimatePresence
mode="wait"
onExitComplete={commandMenuCloseAnimationCompleteCleanup}
>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID }}
>
<AnimatePresence
mode="wait"
onExitComplete={commandMenuCloseAnimationCompleteCleanup}
>
{isCommandMenuOpened && (
<CommandMenuOpenContainer>
{children}
</CommandMenuOpenContainer>
)}
</AnimatePresence>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordSortsComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
</RecordFilterGroupsComponentInstanceContext.Provider>
{isCommandMenuOpened && (
<CommandMenuOpenContainer>{children}</CommandMenuOpenContainer>
)}
</AnimatePresence>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordComponentInstanceContextsWrapper>
);
};
@@ -25,9 +25,7 @@ import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpe
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
import { HttpResponse, graphql } from 'msw';
import { IconDotsVertical } from 'twenty-ui/display';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
@@ -38,33 +36,25 @@ const openTimeout = 50;
const ContextStoreDecorator: Decorator = (Story) => {
return (
<RecordFilterGroupsComponentInstanceContext.Provider
value={{ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID }}
<RecordComponentInstanceContextsWrapper
componentInstanceId={COMMAND_MENU_COMPONENT_INSTANCE_ID}
>
<RecordFiltersComponentInstanceContext.Provider
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID }}
>
<RecordSortsComponentInstanceContext.Provider
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID }}
>
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID }}
<JestContextStoreSetter
contextStoreCurrentObjectMetadataNameSingular="company"
contextStoreCurrentViewId="1"
contextStoreCurrentViewType={ContextStoreViewType.Table}
>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID }}
>
<JestContextStoreSetter
contextStoreCurrentObjectMetadataNameSingular="company"
contextStoreCurrentViewId="1"
contextStoreCurrentViewType={ContextStoreViewType.Table}
>
<Story />
</JestContextStoreSetter>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordSortsComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
</RecordFilterGroupsComponentInstanceContext.Provider>
<Story />
</JestContextStoreSetter>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordComponentInstanceContextsWrapper>
);
};