Refactor board field definitions to use record fields (#14165)

This PR removes board field definitions usage in favor of record fields,
like we've already done on table with table columns.

This PR also introduces a first step in the refactoring of the
RecordIndexContext as a new way to store in-memory a normalized cache
for some states used pretty much everywhere like field metadata items or
field definition (for now but it will soon be removed).

Since most of those states do not change if we don't modify the
metadata, then we can safely store them in a context, which has the
fastest access time in React.

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2025-08-30 13:36:28 +02:00
committed by GitHub
co-authored by Charles Bochet
parent 74eeb98293
commit db5897d4d0
28 changed files with 386 additions and 288 deletions
@@ -11,10 +11,15 @@ import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewCompon
import { type MockedResponse } from '@apollo/client/testing';
import gql from 'graphql-tag';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { peopleQueryResult } from '~/testing/mock-data/people';
import { JestRecordIndexContextProviderWrapper } from '~/testing/jest/JestRecordIndexContextProviderWrapper';
import {
getMockPersonObjectMetadataItem,
peopleQueryResult,
} from '~/testing/mock-data/people';
const recordTableId = 'people';
const objectNameSingular = 'person';
const mockPersonObjectMetadataItem = getMockPersonObjectMetadataItem();
const ObjectNamePluralSetter = ({ children }: { children: ReactNode }) => {
return <>{children}</>;
@@ -660,29 +665,33 @@ jest.mock('react-router-dom', () => ({
const Wrapper = ({ children }: { children: ReactNode }) => {
return (
<HookMockWrapper>
<RecordTableContextProvider
objectNameSingular={objectNameSingular}
recordTableId={recordTableId}
viewBarId="instanceId"
<ViewComponentInstanceContext.Provider
value={{ instanceId: 'instanceId' }}
>
<ObjectNamePluralSetter>
<ViewComponentInstanceContext.Provider
value={{ instanceId: 'instanceId' }}
<RecordComponentInstanceContextsWrapper
componentInstanceId={recordTableId}
>
<JestRecordIndexContextProviderWrapper
objectMetadataItem={mockPersonObjectMetadataItem}
>
<RecordComponentInstanceContextsWrapper
componentInstanceId={recordTableId}
<RecordTableContextProvider
objectNameSingular={objectNameSingular}
recordTableId={recordTableId}
viewBarId="instanceId"
>
<RecordTableComponentInstance recordTableId={recordTableId}>
<RecordGroupContext.Provider
value={{ recordGroupId: 'default' }}
>
{children}
</RecordGroupContext.Provider>
</RecordTableComponentInstance>
</RecordComponentInstanceContextsWrapper>
</ViewComponentInstanceContext.Provider>
</ObjectNamePluralSetter>
</RecordTableContextProvider>
<ObjectNamePluralSetter>
<RecordTableComponentInstance recordTableId={recordTableId}>
<RecordGroupContext.Provider
value={{ recordGroupId: 'default' }}
>
{children}
</RecordGroupContext.Provider>
</RecordTableComponentInstance>
</ObjectNamePluralSetter>
</RecordTableContextProvider>
</JestRecordIndexContextProviderWrapper>
</RecordComponentInstanceContextsWrapper>
</ViewComponentInstanceContext.Provider>
</HookMockWrapper>
);
};
@@ -7,6 +7,7 @@ import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropd
import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
import { type ObjectOptionsContentId } from '@/object-record/object-options-dropdown/types/ObjectOptionsContentId';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
@@ -77,6 +78,16 @@ const createStory = (contentId: ObjectOptionsContentId | null): Story => ({
(item) => item.nameSingular === 'company',
)!;
const {
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
} = useRecordIndexFieldMetadataDerivedStates(
companyObjectMetadataItem,
instanceId,
);
return (
<RecordIndexContextProvider
value={{
@@ -87,6 +98,10 @@ const createStory = (contentId: ObjectOptionsContentId | null): Story => ({
objectNameSingular: 'company',
objectMetadataItem: companyObjectMetadataItem,
recordIndexId: instanceId,
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
}}
>
<ObjectOptionsDropdownContext.Provider
@@ -1,13 +1,12 @@
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
import { useRecordDragState } from '@/object-record/record-drag/shared/hooks/useRecordDragState';
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
import { isRecordBoardCardActiveComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardActiveComponentFamilyState';
import { isRecordBoardCardFocusedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardFocusedComponentFamilyState';
import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState';
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
import { useRecordDragState } from '@/object-record/record-drag/shared/hooks/useRecordDragState';
import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard';
import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard';
@@ -140,10 +139,6 @@ export const RecordBoardCard = () => {
multiDragState.originalSelection.includes(recordId) &&
recordId !== multiDragState.primaryDraggedRecordId;
const visibleFieldDefinitions = useRecoilComponentValue(
recordBoardVisibleFieldDefinitionsComponentSelector,
);
const isCompactModeActive = useRecoilComponentValue(
isRecordBoardCompactModeActiveComponentState,
);
@@ -223,10 +218,6 @@ export const RecordBoardCard = () => {
rootMargin: '1000px',
});
const visibleFieldDefinitionsFiltered = visibleFieldDefinitions.filter(
(boardField) => !boardField.isLabelIdentifier,
);
return (
<RecordBoardCardComponentInstanceContext.Provider
value={{
@@ -270,9 +261,7 @@ export const RecordBoardCard = () => {
isOpen={isCardExpanded || !isCompactModeActive}
initial={false}
>
<RecordBoardCardBody
fieldDefinitions={visibleFieldDefinitionsFiltered}
/>
<RecordBoardCardBody />
</AnimatedEaseInOut>
</StyledBoardCard>
</StyledCardContainer>
@@ -5,29 +5,30 @@ import { StopPropagationContainer } from '@/object-record/record-board/record-bo
import { RECORD_BOARD_CARD_INPUT_ID_PREFIX } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardInputIdPrefix';
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
import { recordBoardCardHoverPositionComponentState } from '@/object-record/record-board/record-board-card/states/recordBoardCardHoverPositionComponentState';
import { type RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition';
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import {
FieldContext,
type RecordUpdateHook,
type RecordUpdateHookParams,
} from '@/object-record/record-field/ui/contexts/FieldContext';
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { getFieldButtonIcon } from '@/object-record/record-field/ui/utils/getFieldButtonIcon';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useContext } from 'react';
export const RecordBoardCardBody = ({
fieldDefinitions,
}: {
fieldDefinitions: RecordBoardFieldDefinition<FieldMetadata>[];
}) => {
export const RecordBoardCardBody = () => {
const { recordId, isRecordReadOnly } = useContext(RecordBoardCardContext);
const { updateOneRecord, objectPermissions } = useContext(RecordBoardContext);
const {
labelIdentifierFieldMetadataItem,
fieldDefinitionByFieldMetadataItemId,
} = useRecordIndexContextOrThrow();
const useUpdateOneRecordHook: RecordUpdateHook = () => {
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
updateOneRecord?.({
@@ -39,18 +40,13 @@ export const RecordBoardCardBody = ({
return [updateEntity, { loading: false }];
};
const fieldDefinitionsWithReadOnly = fieldDefinitions.map(
(fieldDefinition) => ({
...fieldDefinition,
isRecordFieldReadOnly: isRecordFieldReadOnly({
isRecordReadOnly,
objectPermissions,
fieldMetadataItem: {
id: fieldDefinition.fieldMetadataId,
isUIReadOnly: fieldDefinition.metadata.isUIReadOnly ?? false,
},
}),
}),
const visibleRecordFields = useRecoilComponentValue(
visibleRecordFieldsComponentSelector,
);
const visibleRecordFieldsExceptLabelIdentifier = visibleRecordFields.filter(
(recordField) =>
recordField.fieldMetadataItemId !== labelIdentifierFieldMetadataItem?.id,
);
const setRecordBoardCardHoverPosition = useSetRecoilComponentState(
@@ -63,50 +59,52 @@ export const RecordBoardCardBody = ({
return (
<RecordBoardCardBodyContainer>
{fieldDefinitionsWithReadOnly.map((fieldDefinition, index) => (
<StopPropagationContainer key={fieldDefinition.fieldMetadataId}>
<FieldContext.Provider
value={{
recordId,
maxWidth: 156,
isLabelIdentifier: false,
isRecordFieldReadOnly: fieldDefinition.isRecordFieldReadOnly,
fieldDefinition: {
disableTooltip: false,
fieldMetadataId: fieldDefinition.fieldMetadataId,
label: fieldDefinition.label,
iconName: fieldDefinition.iconName,
type: fieldDefinition.type,
metadata: fieldDefinition.metadata,
defaultValue: fieldDefinition.defaultValue,
editButtonIcon: getFieldButtonIcon({
metadata: fieldDefinition.metadata,
type: fieldDefinition.type,
}),
},
useUpdateRecord: useUpdateOneRecordHook,
isDisplayModeFixHeight: true,
triggerEvent: 'CLICK',
anchorId: `${RECORD_BOARD_CARD_INPUT_ID_PREFIX}-${recordId}-${fieldDefinition.metadata.fieldName}`,
onMouseEnter: () => handleMouseEnter(index),
}}
>
<RecordFieldComponentInstanceContext.Provider
{visibleRecordFieldsExceptLabelIdentifier.map((recordField, index) => {
const correspondingFieldDefinition =
fieldDefinitionByFieldMetadataItemId[recordField.fieldMetadataItemId];
return (
<StopPropagationContainer key={recordField.fieldMetadataItemId}>
<FieldContext.Provider
value={{
instanceId: getRecordFieldInputInstanceId({
recordId,
fieldName: fieldDefinition.metadata.fieldName,
prefix: RECORD_BOARD_CARD_INPUT_ID_PREFIX,
recordId,
maxWidth: 156,
isLabelIdentifier: false,
isRecordFieldReadOnly: isRecordFieldReadOnly({
isRecordReadOnly,
objectPermissions,
fieldMetadataItem: {
id: recordField.fieldMetadataItemId,
isUIReadOnly:
correspondingFieldDefinition.metadata.isUIReadOnly ??
false,
},
}),
fieldDefinition: correspondingFieldDefinition,
useUpdateRecord: useUpdateOneRecordHook,
isDisplayModeFixHeight: true,
triggerEvent: 'CLICK',
anchorId: `${RECORD_BOARD_CARD_INPUT_ID_PREFIX}-${recordId}-${correspondingFieldDefinition.metadata.fieldName}`,
onMouseEnter: () => handleMouseEnter(index),
}}
>
<RecordInlineCell
instanceIdPrefix={RECORD_BOARD_CARD_INPUT_ID_PREFIX}
/>
</RecordFieldComponentInstanceContext.Provider>
</FieldContext.Provider>
</StopPropagationContainer>
))}
<RecordFieldComponentInstanceContext.Provider
value={{
instanceId: getRecordFieldInputInstanceId({
recordId,
fieldName: correspondingFieldDefinition.metadata.fieldName,
prefix: RECORD_BOARD_CARD_INPUT_ID_PREFIX,
}),
}}
>
<RecordInlineCell
instanceIdPrefix={RECORD_BOARD_CARD_INPUT_ID_PREFIX}
/>
</RecordFieldComponentInstanceContext.Provider>
</FieldContext.Provider>
</StopPropagationContainer>
);
})}
</RecordBoardCardBodyContainer>
);
};
@@ -1,7 +1,8 @@
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { recordBoardCardEditModePositionComponentState } from '@/object-record/record-board/record-board-card/states/recordBoardCardEditModePositionComponentState';
import { recordBoardCardHoverPositionComponentState } from '@/object-record/record-board/record-board-card/states/recordBoardCardHoverPositionComponentState';
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
@@ -17,33 +18,36 @@ export const useRecordBoardCardMetadataFromPosition = () => {
recordBoardCardEditModePositionComponentState,
);
const visibleFieldDefinitions = useRecoilComponentValue(
recordBoardVisibleFieldDefinitionsComponentSelector,
const visibleRecordFields = useRecoilComponentValue(
visibleRecordFieldsComponentSelector,
);
const visibleFieldDefinitionsFiltered = visibleFieldDefinitions.filter(
(boardField) => !boardField.isLabelIdentifier,
const { labelIdentifierFieldMetadataItem } = useRecordIndexContextOrThrow();
const visibleRecordFieldsFiltered = visibleRecordFields.filter(
(recordField) =>
labelIdentifierFieldMetadataItem?.id !== recordField.fieldMetadataItemId,
);
const hoveredFieldDefinition = isDefined(hoverPosition)
? visibleFieldDefinitionsFiltered.at(hoverPosition)
const hoveredRecordField = isDefined(hoverPosition)
? visibleRecordFieldsFiltered.at(hoverPosition)
: undefined;
const editedFieldDefinition = isDefined(editModePosition)
? visibleFieldDefinitionsFiltered.at(editModePosition)
const editedRecordField = isDefined(editModePosition)
? visibleRecordFieldsFiltered.at(editModePosition)
: undefined;
const hoveredFieldMetadataItem = isDefined(hoveredFieldDefinition)
const hoveredFieldMetadataItem = isDefined(hoveredRecordField)
? objectMetadataItem.fields.find(
(fieldMetadataItem) =>
fieldMetadataItem.id === hoveredFieldDefinition.fieldMetadataId,
fieldMetadataItem.id === hoveredRecordField.fieldMetadataItemId,
)
: undefined;
const editedFieldMetadataItem = isDefined(editedFieldDefinition)
const editedFieldMetadataItem = isDefined(editedRecordField)
? objectMetadataItem.fields.find(
(fieldMetadataItem) =>
fieldMetadataItem.id === editedFieldDefinition.fieldMetadataId,
fieldMetadataItem.id === editedRecordField.fieldMetadataItemId,
)
: undefined;
@@ -10,7 +10,7 @@ import { RecordBoardColumnNewRecordButton } from '@/object-record/record-board/r
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { getNumberOfCardsPerColumnForSkeletonLoading } from '@/object-record/record-board/record-board-column/utils/getNumberOfCardsPerColumnForSkeletonLoading';
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { isRecordIndexBoardColumnLoadingFamilyState } from '@/object-record/states/isRecordBoardColumnLoadingFamilyState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
@@ -52,11 +52,11 @@ export const RecordBoardColumnCardsContainer = ({
isRecordIndexBoardColumnLoadingFamilyState(columnId),
);
const visibleFieldDefinitions = useRecoilComponentValue(
recordBoardVisibleFieldDefinitionsComponentSelector,
const visibleRecordFields = useRecoilComponentValue(
visibleRecordFieldsComponentSelector,
);
const numberOfFields = visibleFieldDefinitions.length;
const numberOfFields = visibleRecordFields.length;
const isCompactModeActive = useRecoilComponentValue(
isRecordBoardCompactModeActiveComponentState,
@@ -1,12 +0,0 @@
import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext';
import { type RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition';
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const recordBoardFieldDefinitionsComponentState = createComponentState<
RecordBoardFieldDefinition<FieldMetadata>[]
>({
key: 'recordBoardFieldDefinitionsComponentState',
defaultValue: [],
componentInstanceContext: RecordBoardComponentInstanceContext,
});
@@ -1,17 +0,0 @@
import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext';
import { recordBoardFieldDefinitionsComponentState } from '@/object-record/record-board/states/recordBoardFieldDefinitionsComponentState';
import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
export const recordBoardVisibleFieldDefinitionsComponentSelector =
createComponentSelector({
key: 'recordBoardVisibleFieldDefinitionsComponentSelector',
get:
({ instanceId }) =>
({ get }) =>
get(
recordBoardFieldDefinitionsComponentState.atomFamily({ instanceId }),
)
.filter((field) => field.isVisible)
.sort((a, b) => a.position - b.position),
componentInstanceContext: RecordBoardComponentInstanceContext,
});
@@ -4,6 +4,7 @@ import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlur
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ChipFieldDisplay } from '@/object-record/record-field/ui/meta-types/display/components/ChipFieldDisplay';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { ComponentDecorator } from 'twenty-ui/testing';
import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDecorator';
@@ -22,6 +23,16 @@ const meta: Meta = {
(item) => item.nameSingular === CoreObjectNameSingular.Company,
)!;
const {
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
} = useRecordIndexFieldMetadataDerivedStates(
companyObjectMetadataItem,
instanceId,
);
return (
<RecordTableComponentInstanceContext.Provider
value={{
@@ -37,6 +48,10 @@ const meta: Meta = {
objectNameSingular: CoreObjectNameSingular.Company,
objectMetadataItem: companyObjectMetadataItem,
recordIndexId: instanceId,
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
}}
>
<Story />
@@ -3,9 +3,7 @@ import { useRecoilValue } from 'recoil';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
import { recordBoardFieldDefinitionsComponentState } from '@/object-record/record-board/states/recordBoardFieldDefinitionsComponentState';
import { recordBoardSelectedRecordIdsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardSelectedRecordIdsComponentSelector';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { recordIndexIsCompactModeActiveState } from '@/object-record/record-index/states/recordIndexIsCompactModeActiveState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
@@ -17,19 +15,10 @@ type RecordIndexBoardDataLoaderEffectProps = {
export const RecordIndexBoardDataLoaderEffect = ({
recordBoardId,
}: RecordIndexBoardDataLoaderEffectProps) => {
const recordIndexFieldDefinitions = useRecoilValue(
recordIndexFieldDefinitionsState,
);
const recordIndexIsCompactModeActive = useRecoilValue(
recordIndexIsCompactModeActiveState,
);
const setRecordBoardFieldDefinitions = useSetRecoilComponentState(
recordBoardFieldDefinitionsComponentState,
recordBoardId,
);
const selectedRecordIds = useRecoilComponentValue(
recordBoardSelectedRecordIdsComponentSelector,
recordBoardId,
@@ -44,10 +33,6 @@ export const RecordIndexBoardDataLoaderEffect = ({
setIsCompactModeActive(recordIndexIsCompactModeActive);
}, [recordIndexIsCompactModeActive, setIsCompactModeActive]);
useEffect(() => {
setRecordBoardFieldDefinitions(recordIndexFieldDefinitions);
}, [recordIndexFieldDefinitions, setRecordBoardFieldDefinitions]);
const setContextStoreTargetedRecords = useSetRecoilComponentState(
contextStoreTargetedRecordsRuleComponentState,
);
@@ -11,6 +11,7 @@ import { RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect } from '@
import { RecordIndexLoadBaseOnContextStoreEffect } from '@/object-record/record-index/components/RecordIndexLoadBaseOnContextStoreEffect';
import { RecordIndexPageHeader } from '@/object-record/record-index/components/RecordIndexPageHeader';
import { useHandleIndexIdentifierClick } from '@/object-record/record-index/hooks/useHandleIndexIdentifierClick';
import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { PageBody } from '@/ui/layout/page/components/PageBody';
import { RECORD_INDEX_DRAG_SELECT_BOUNDARY_CLASS } from '@/ui/utilities/drag-select/constants/RecordIndecDragSelectBoundaryClass';
@@ -52,6 +53,16 @@ export const RecordIndexContainerGater = () => {
const hasObjectReadPermissions = objectPermissions.canReadObjectRecords;
const {
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
} = useRecordIndexFieldMetadataDerivedStates(
objectMetadataItem,
recordIndexId,
);
if (!hasObjectReadPermissions) {
return <NotFound />;
}
@@ -67,6 +78,10 @@ export const RecordIndexContainerGater = () => {
objectMetadataItem,
onIndexRecordsLoaded: handleIndexRecordsLoaded,
indexIdentifierUrl,
recordFieldByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
fieldMetadataItemByFieldMetadataItemId,
fieldDefinitionByFieldMetadataItemId,
}}
>
<ViewComponentInstanceContext.Provider
@@ -1,4 +1,8 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { type ObjectPermissions } from 'twenty-shared/types';
import { createRequiredContext } from '~/utils/createRequiredContext';
@@ -13,6 +17,13 @@ export type RecordIndexContextValue = {
ObjectPermissions & { objectMetadataId: string }
>;
recordIndexId: string;
recordFieldByFieldMetadataItemId: Record<string, RecordField>;
labelIdentifierFieldMetadataItem: FieldMetadataItem | undefined;
fieldMetadataItemByFieldMetadataItemId: Record<string, FieldMetadataItem>;
fieldDefinitionByFieldMetadataItemId: Record<
string,
ColumnDefinition<FieldMetadata>
>;
};
export const [RecordIndexContextProvider, useRecordIndexContextOrThrow] =
@@ -3,8 +3,9 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { generateDepthOneWithoutRelationsRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneWithoutRelationsRecordGqlFields';
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { isDefined } from 'twenty-shared/utils';
@@ -15,11 +16,14 @@ export const useRecordBoardRecordGqlFields = ({
recordBoardId: string;
objectMetadataItem: ObjectMetadataItem;
}) => {
const visibleFieldDefinitions = useRecoilComponentValue(
recordBoardVisibleFieldDefinitionsComponentSelector,
const visibleRecordFields = useRecoilComponentValue(
visibleRecordFieldsComponentSelector,
recordBoardId,
);
const { fieldMetadataItemByFieldMetadataItemId } =
useRecordIndexContextOrThrow();
const recordGroupFieldMetadata = useRecoilComponentValue(
recordGroupFieldMetadataComponentState,
recordBoardId,
@@ -40,11 +44,20 @@ export const useRecordBoardRecordGqlFields = ({
objectMetadataItem,
});
const visibleFieldMetadataItems = visibleRecordFields
.map(
(recordField) =>
fieldMetadataItemByFieldMetadataItemId[
recordField.fieldMetadataItemId
] ?? null,
)
.filter(isDefined);
const recordGqlFields: Record<string, any> = {
...allDepthOneWithoutRelationsRecordGqlFields,
...Object.fromEntries(
visibleFieldDefinitions.map((visibleFieldDefinition) => [
visibleFieldDefinition.metadata.fieldName,
visibleFieldMetadataItems.map((visibleFieldMetadataItem) => [
visibleFieldMetadataItem.name,
true,
]),
),
@@ -0,0 +1,63 @@
import { labelIdentifierFieldMetadataItemSelector } from '@/object-metadata/states/labelIdentifierFieldMetadataItemSelector';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
export const useRecordIndexFieldMetadataDerivedStates = (
objectMetadataItem: ObjectMetadataItem | undefined,
recordIndexId?: string | undefined,
) => {
const fieldMetadataItems = objectMetadataItem?.fields ?? [];
const fieldMetadataItemByFieldMetadataItemId = Object.fromEntries(
fieldMetadataItems.map((fieldMetadataItem) => [
fieldMetadataItem.id,
fieldMetadataItem,
]),
);
const currentRecordFields = useRecoilComponentValue(
currentRecordFieldsComponentState,
recordIndexId,
);
const recordFieldByFieldMetadataItemId = Object.fromEntries(
currentRecordFields.map((recordField) => [
recordField.fieldMetadataItemId,
recordField,
]),
);
const fieldDefinitionByFieldMetadataItemId = isDefined(objectMetadataItem)
? Object.fromEntries(
fieldMetadataItems.map((fieldMetadataItem) => [
fieldMetadataItem.id,
formatFieldMetadataItemAsColumnDefinition({
field: fieldMetadataItem,
objectMetadataItem,
position:
recordFieldByFieldMetadataItemId[fieldMetadataItem.id]
?.position ?? 0,
labelWidth:
recordFieldByFieldMetadataItemId[fieldMetadataItem.id]?.size ?? 0,
}),
]),
)
: {};
const labelIdentifierFieldMetadataItem = useRecoilValue(
labelIdentifierFieldMetadataItemSelector({
objectMetadataItemId: objectMetadataItem?.id ?? '',
}),
);
return {
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
fieldDefinitionByFieldMetadataItemId,
recordFieldByFieldMetadataItemId,
};
};
@@ -4,7 +4,7 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { generateDepthOneWithoutRelationsRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneWithoutRelationsRecordGqlFields';
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { FieldMetadataType } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
@@ -19,7 +19,7 @@ export const useRecordTableRecordGqlFields = ({
);
const { fieldMetadataItemByFieldMetadataItemId } =
useRecordTableContextOrThrow();
useRecordIndexContextOrThrow();
const { objectMetadataItem: noteTargetObjectMetadataItem } =
useObjectMetadataItem({
@@ -3,13 +3,9 @@ import { type ReactNode } from 'react';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordTableContextProvider as RecordTableContextInternalProvider } from '@/object-record/record-table/contexts/RecordTableContext';
import { labelIdentifierFieldMetadataItemSelector } from '@/object-metadata/states/labelIdentifierFieldMetadataItemSelector';
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilValue } from 'recoil';
type RecordTableContextProviderProps = {
viewBarId: string;
@@ -37,47 +33,6 @@ export const RecordTableContextProvider = ({
recordTableId,
);
const currentRecordFields = useRecoilComponentValue(
currentRecordFieldsComponentState,
recordTableId,
);
const fieldMetadataItems = objectMetadataItem.fields;
const fieldMetadataItemByFieldMetadataItemId = Object.fromEntries(
fieldMetadataItems.map((fieldMetadataItem) => [
fieldMetadataItem.id,
fieldMetadataItem,
]),
);
const recordFieldByFieldMetadataItemId = Object.fromEntries(
currentRecordFields.map((recordField) => [
recordField.fieldMetadataItemId,
recordField,
]),
);
const fieldDefinitionByFieldMetadataItemId = Object.fromEntries(
fieldMetadataItems.map((fieldMetadataItem) => [
fieldMetadataItem.id,
formatFieldMetadataItemAsColumnDefinition({
field: fieldMetadataItem,
objectMetadataItem,
position:
recordFieldByFieldMetadataItemId[fieldMetadataItem.id]?.position ?? 0,
labelWidth:
recordFieldByFieldMetadataItemId[fieldMetadataItem.id]?.size ?? 0,
}),
]),
);
const labelIdentifierFieldMetadataItem = useRecoilValue(
labelIdentifierFieldMetadataItemSelector({
objectMetadataItemId: objectMetadataItem.id,
}),
);
return (
<RecordTableContextInternalProvider
value={{
@@ -86,11 +41,7 @@ export const RecordTableContextProvider = ({
recordTableId,
objectNameSingular,
objectPermissions,
recordFieldByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
visibleRecordFields,
fieldMetadataItemByFieldMetadataItemId,
fieldDefinitionByFieldMetadataItemId,
}}
>
{children}
@@ -142,6 +142,10 @@ const meta: Meta = {
// TODO: update performance mocks with new data, and merge with common mocks if possible
objectMetadataItem: mockPerformance.objectMetadataItem as any,
recordIndexId: 'recordIndexId',
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
}}
>
<RecordComponentInstanceContextsWrapper componentInstanceId="recordTableId">
@@ -156,10 +160,6 @@ const meta: Meta = {
objectPermissions: {
objectMetadataId: mockPerformance.objectMetadataItem.id,
},
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
visibleRecordFields,
}}
>
@@ -1,8 +1,5 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { type ObjectPermission } from '~/generated/graphql';
import { createRequiredContext } from '~/utils/createRequiredContext';
@@ -13,13 +10,6 @@ type RecordTableContextValue = {
objectMetadataItem: ObjectMetadataItem;
objectPermissions: ObjectPermission;
visibleRecordFields: RecordField[];
recordFieldByFieldMetadataItemId: Record<string, RecordField>;
labelIdentifierFieldMetadataItem: FieldMetadataItem | undefined;
fieldMetadataItemByFieldMetadataItemId: Record<string, FieldMetadataItem>;
fieldDefinitionByFieldMetadataItemId: Record<
string,
ColumnDefinition<FieldMetadata>
>;
};
export const [RecordTableContextProvider, useRecordTableContextOrThrow] =
@@ -22,13 +22,14 @@ export const RecordTableCellFieldContextGeneric = ({
}: RecordTableCellFieldContextGenericProps) => {
const { recordId, isRecordReadOnly } = useRecordTableRowContextOrThrow();
const { objectMetadataItem, objectPermissions } =
useRecordTableContextOrThrow();
const {
objectMetadataItem,
objectPermissions,
indexIdentifierUrl,
objectPermissionsByObjectMetadataId,
fieldDefinitionByFieldMetadataItemId,
} = useRecordTableContextOrThrow();
const { indexIdentifierUrl, objectPermissionsByObjectMetadataId } =
useRecordIndexContextOrThrow();
} = useRecordIndexContextOrThrow();
const fieldDefinition =
fieldDefinitionByFieldMetadataItemId[recordField.fieldMetadataItemId];
@@ -28,7 +28,7 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
useRecordIndexContextOrThrow();
const { recordId, isRecordReadOnly } = useRecordTableRowContextOrThrow();
const { fieldDefinitionByFieldMetadataItemId } =
useRecordTableContextOrThrow();
useRecordIndexContextOrThrow();
const { recordField } = useContext(RecordTableCellContext);
const { objectMetadataItem, recordTableId } = useRecordTableContextOrThrow();
@@ -1,8 +1,7 @@
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RECORD_TABLE_CELL_INPUT_ID_PREFIX } from '@/object-record/record-table/constants/RecordTableCellInputIdPrefix';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableCellFieldContextGeneric } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextGeneric';
import { RecordTableCellFieldContextLabelIdentifier } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextLabelIdentifier';
@@ -19,8 +18,11 @@ export const RecordTableCellFieldContextWrapper = ({
children,
}: RecordTableCellFieldContextWrapperProps) => {
const { recordId } = useRecordTableRowContextOrThrow();
const { objectMetadataItem, fieldMetadataItemByFieldMetadataItemId } =
useRecordTableContextOrThrow();
const {
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
} = useRecordIndexContextOrThrow();
const fieldMetadataItem =
fieldMetadataItemByFieldMetadataItemId[recordField.fieldMetadataItemId];
@@ -31,13 +33,8 @@ export const RecordTableCellFieldContextWrapper = ({
prefix: RECORD_TABLE_CELL_INPUT_ID_PREFIX,
});
const isLabelIdentifier = isLabelIdentifierField({
fieldMetadataItem: {
id: recordField.fieldMetadataItemId,
name: fieldMetadataItem.name,
},
objectMetadataItem,
});
const isLabelIdentifier =
labelIdentifierFieldMetadataItem?.id === recordField.fieldMetadataItemId;
return (
<RecordFieldComponentInstanceContext.Provider value={{ instanceId }}>
@@ -6,6 +6,7 @@ import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObje
import { useUpdateRecordField } from '@/object-record/record-field/hooks/useUpdateRecordField';
import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
@@ -207,7 +208,7 @@ export const RecordTableHeaderCell = ({
const isMobile = useIsMobile();
const { labelIdentifierFieldMetadataItem } = useRecordTableContextOrThrow();
const { labelIdentifierFieldMetadataItem } = useRecordIndexContextOrThrow();
const isLabelIdentifier =
recordField.fieldMetadataItemId === labelIdentifierFieldMetadataItem?.id;
@@ -7,6 +7,7 @@ import { ContextStoreComponentInstanceContext } from '@/context-store/states/con
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates';
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
@@ -39,6 +40,16 @@ export const SignInBackgroundMockContainer = () => {
(objectMetadataItem) => objectMetadataItem.id === objectMetadataItemId,
);
const {
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
} = useRecordIndexFieldMetadataDerivedStates(
objectMetadataItem,
recordIndexId,
);
return (
<StyledContainer>
<RecordIndexContextProvider
@@ -50,6 +61,10 @@ export const SignInBackgroundMockContainer = () => {
objectMetadataItem: objectMetadataItem ?? objectMetadataItems[0],
onIndexRecordsLoaded: () => {},
indexIdentifierUrl: () => '',
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
}}
>
<ViewComponentInstanceContext.Provider
@@ -16,6 +16,7 @@ import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainCo
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates';
import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId';
import { coreViewsState } from '@/views/states/coreViewState';
import { within } from '@storybook/test';
@@ -79,6 +80,16 @@ const meta: Meta<typeof ViewBarFilterDropdown> = {
setCurrentRecordFields(columns);
const {
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
} = useRecordIndexFieldMetadataDerivedStates(
companyObjectMetadataItem,
instanceId,
);
return (
<RecordIndexContextProvider
value={{
@@ -89,6 +100,10 @@ const meta: Meta<typeof ViewBarFilterDropdown> = {
objectNameSingular: CoreObjectNameSingular.Company,
objectMetadataItem: companyObjectMetadataItem,
recordIndexId: instanceId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
}}
>
<RecordComponentInstanceContextsWrapper
@@ -11,6 +11,7 @@ import { type RecordFilterGroup } from '@/object-record/record-filter-group/type
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates';
import { InputLabel } from '@/ui/input/components/InputLabel';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowFindRecordsFilters } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsFilters';
@@ -70,6 +71,7 @@ export const WorkflowEditActionFindRecords = ({
filter: action.settings.input.filter as FindRecordsActionFilter,
});
const isFormDisabled = actionOptions.readonly;
const instanceId = `workflow-edit-action-record-find-records-${action.id}-${formData.objectNameSingular}`;
const selectedObjectMetadataItem = activeNonSystemObjectMetadataItems.find(
(item) => item.nameSingular === formData.objectNameSingular,
@@ -80,6 +82,16 @@ export const WorkflowEditActionFindRecords = ({
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const {
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
} = useRecordIndexFieldMetadataDerivedStates(
selectedObjectMetadataItem,
instanceId,
);
const saveAction = useDebouncedCallback(
async (formData: FindRecordsFormData) => {
if (actionOptions.readonly === true) {
@@ -118,7 +130,6 @@ export const WorkflowEditActionFindRecords = ({
action,
defaultTitle: 'Search Records',
});
const instanceId = `workflow-edit-action-record-find-records-${action.id}-${formData.objectNameSingular}`;
return (
<>
@@ -174,6 +185,10 @@ export const WorkflowEditActionFindRecords = ({
objectMetadataItem: selectedObjectMetadataItem,
recordIndexId: instanceId,
objectPermissionsByObjectMetadataId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
}}
>
<RecordFilterGroupsComponentInstanceContext.Provider
@@ -143,6 +143,10 @@ const InternalTableContextProviders = ({
objectMetadataItem: objectMetadataItem,
objectPermissionsByObjectMetadataId,
recordIndexId: 'record-index',
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
}}
>
<RecordTableContextProvider
@@ -155,11 +159,7 @@ const InternalTableContextProviders = ({
objectPermissionsByObjectMetadataId,
objectMetadataId: objectMetadataItem.id,
}),
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
visibleRecordFields,
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
}}
>
<RecordTableBodyContextProvider
@@ -0,0 +1,40 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates';
import { type PropsWithChildren } from 'react';
type JestRecordIndexContextProviderWrapperProps = {
objectMetadataItem: ObjectMetadataItem;
} & PropsWithChildren;
export const JestRecordIndexContextProviderWrapper = ({
objectMetadataItem,
children,
}: JestRecordIndexContextProviderWrapperProps) => {
const {
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
} = useRecordIndexFieldMetadataDerivedStates(objectMetadataItem);
return (
<RecordIndexContextProvider
value={{
objectPermissionsByObjectMetadataId: {},
indexIdentifierUrl: () => 'indexIdentifierUrl',
onIndexRecordsLoaded: () => {},
objectNamePlural: objectMetadataItem.namePlural,
objectNameSingular: objectMetadataItem.nameSingular,
objectMetadataItem: objectMetadataItem,
recordIndexId: 'recordIndexId',
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
}}
>
{children}
</RecordIndexContextProvider>
);
};
@@ -1,7 +1,5 @@
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { type MockedResponse } from '@apollo/client/testing';
import { type ReactNode } from 'react';
import { type MutableSnapshot } from 'recoil';
@@ -10,6 +8,7 @@ import {
JestContextStoreSetter,
type JestContextStoreSetterMocks,
} from '~/testing/jest/JestContextStoreSetter';
import { JestRecordIndexContextProviderWrapper } from '~/testing/jest/JestRecordIndexContextProviderWrapper';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
@@ -49,48 +48,34 @@ export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({
return ({ children }: { children: ReactNode }) => (
<Wrapper>
<RecordComponentInstanceContextsWrapper
componentInstanceId={componentInstanceId}
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: componentInstanceId }}
>
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: componentInstanceId }}
<ActionMenuComponentInstanceContext.Provider
value={{
instanceId: componentInstanceId,
}}
>
<ActionMenuComponentInstanceContext.Provider
value={{
instanceId: componentInstanceId,
}}
<JestRecordIndexContextProviderWrapper
objectMetadataItem={mockObjectMetadataItem}
>
<RecordIndexContextProvider
value={{
objectPermissionsByObjectMetadataId: {},
indexIdentifierUrl: () => 'indexIdentifierUrl',
onIndexRecordsLoaded: () => {},
objectNamePlural: mockObjectMetadataItem.namePlural,
objectNameSingular: mockObjectMetadataItem.nameSingular,
objectMetadataItem: mockObjectMetadataItem,
recordIndexId: 'recordIndexId',
}}
<JestContextStoreSetter
contextStoreCurrentViewId={contextStoreCurrentViewId}
contextStoreFilters={contextStoreFilters}
contextStoreTargetedRecordsRule={contextStoreTargetedRecordsRule}
contextStoreNumberOfSelectedRecords={
contextStoreNumberOfSelectedRecords
}
contextStoreCurrentObjectMetadataNameSingular={
contextStoreCurrentObjectMetadataNameSingular
}
contextStoreCurrentViewType={contextStoreCurrentViewType}
>
<JestContextStoreSetter
contextStoreCurrentViewId={contextStoreCurrentViewId}
contextStoreFilters={contextStoreFilters}
contextStoreTargetedRecordsRule={
contextStoreTargetedRecordsRule
}
contextStoreNumberOfSelectedRecords={
contextStoreNumberOfSelectedRecords
}
contextStoreCurrentObjectMetadataNameSingular={
contextStoreCurrentObjectMetadataNameSingular
}
contextStoreCurrentViewType={contextStoreCurrentViewType}
>
{children}
</JestContextStoreSetter>
</RecordIndexContextProvider>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordComponentInstanceContextsWrapper>
{children}
</JestContextStoreSetter>
</JestRecordIndexContextProviderWrapper>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</Wrapper>
);
};