Cleaned FieldDefinition props and removed recoil calls (#14220)

This PR removes some unused props from FieldDefinition type and put some
recoil calls higher up in the hierarchy.

This PR fixes a regression on Chip introduced by
https://github.com/twentyhq/twenty/pull/11498, where we don't want
"Untitled" to be displayed in compact mode.

# FieldDefinition refactor

Removed `isLabelIdentifierCompact` and `labelIdentifierLink` which can
be derived directly in the chip component or computed in the record
table context.

Removed `disableTooltip` and `infoTooltipContent` because they were not
used.

Introduced `fieldMetadataItemId`, as optional for now, to have the
ability to progressively refactor the different things that could be
derived from this and `recordId`.

# Performance improvements

There's not a great deal to be gained for now on those modifications,
which specifically concern the identifier column.
This commit is contained in:
Lucas Bordeau
2025-09-02 10:49:55 +02:00
committed by GitHub
parent 90432f5b13
commit bfad5b0477
18 changed files with 78 additions and 64 deletions
@@ -6,6 +6,7 @@ import { type CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
import { useIsRecordReadOnly } from '@/object-record/read-only/hooks/useIsRecordReadOnly';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
@@ -15,7 +16,6 @@ import { Chip, ChipAccent, ChipSize, ChipVariant } from 'twenty-ui/components';
import { IconCalendarEvent } from 'twenty-ui/display';
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
import { useIsRecordReadOnly } from '@/object-record/read-only/hooks/useIsRecordReadOnly';
type CalendarEventDetailsProps = {
calendarEvent: CalendarEvent;
@@ -21,12 +21,11 @@ export type RecordUpdateHook = () => [
];
export type GenericFieldContextType = {
fieldMetadataItemId?: string;
recordId: string;
fieldDefinition: FieldDefinition<FieldMetadata>;
useUpdateRecord?: RecordUpdateHook;
recordId: string;
isLabelIdentifier: boolean;
isLabelIdentifierCompact?: boolean;
labelIdentifierLink?: string;
clearable?: boolean;
maxWidth?: number;
isCentered?: boolean;
@@ -1,20 +1,30 @@
import { RecordChip } from '@/object-record/components/RecordChip';
import { useChipFieldDisplay } from '@/object-record/record-field/ui/meta-types/hooks/useChipFieldDisplay';
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { isDefined } from 'twenty-shared/utils';
import { ChipSize } from 'twenty-ui/components';
import { useIsMobile } from 'twenty-ui/utilities';
export const ChipFieldDisplay = () => {
const {
recordValue,
objectNameSingular,
labelIdentifierLink,
isLabelIdentifierCompact,
disableChipClick,
maxWidth,
triggerEvent,
onRecordChipClick,
} = useChipFieldDisplay();
const isMobile = useIsMobile();
const isRecordTableScrolledLeftComponent = useRecoilComponentValue(
isRecordTableScrolledLeftComponentState,
);
const isLabelIdentifierCompact =
isMobile && !isRecordTableScrolledLeftComponent;
if (!isDefined(recordValue)) {
return null;
}
@@ -7,23 +7,30 @@ import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { isFieldActor } from '@/object-record/record-field/ui/types/guards/isFieldActor';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { isDefined } from 'twenty-shared/utils';
import { FieldContext } from '../../contexts/FieldContext';
export const useChipFieldDisplay = () => {
const {
fieldMetadataItemId,
recordId,
fieldDefinition,
isLabelIdentifier,
labelIdentifierLink,
isLabelIdentifierCompact,
disableChipClick,
maxWidth,
triggerEvent,
onRecordChipClick,
} = useContext(FieldContext);
const { indexIdentifierUrl, labelIdentifierFieldMetadataItem } =
useRecordIndexContextOrThrow();
const isLabelIdentifier =
labelIdentifierFieldMetadataItem?.id === fieldMetadataItemId;
const labelIdentifierLink = indexIdentifierUrl(recordId);
const { chipGeneratorPerObjectPerField } = useContext(
PreComputedChipGeneratorsContext,
);
@@ -51,7 +58,6 @@ export const useChipFieldDisplay = () => {
recordValue,
isLabelIdentifier,
labelIdentifierLink,
isLabelIdentifierCompact,
disableChipClick,
maxWidth,
triggerEvent,
@@ -7,12 +7,10 @@ export type FieldDefinition<T extends FieldMetadata> = {
fieldMetadataId: string;
label: string;
showLabel?: boolean;
disableTooltip?: boolean;
labelWidth?: number;
iconName: string;
type: FieldMetadataType;
metadata: T;
infoTooltipContent?: string;
defaultValue?: any;
editButtonIcon?: IconComponent;
isUIReadOnly?: boolean;
@@ -127,7 +127,7 @@ export const RecordInlineCellContainer = () => {
</StyledLabelContainer>
)}
{/* TODO: Displaying Tooltips on the board is causing performance issues https://react-tooltip.com/docs/examples/render */}
{!showLabel && !fieldDefinition?.disableTooltip && (
{!showLabel && (
<AppTooltip
anchorSelect={`#${labelId}`}
content={label}
@@ -5,12 +5,16 @@ import { RecordTableContextProvider as RecordTableContextInternalProvider } from
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { useRecoilValue } from 'recoil';
type RecordTableContextProviderProps = {
viewBarId: string;
recordTableId: string;
objectNameSingular: string;
onRecordIdentifierClick?: (rowIndex: number, recordId: string) => void;
children: ReactNode;
};
@@ -18,6 +22,7 @@ export const RecordTableContextProvider = ({
viewBarId,
recordTableId,
objectNameSingular,
onRecordIdentifierClick,
children,
}: RecordTableContextProviderProps) => {
const { objectMetadataItem } = useObjectMetadataItem({
@@ -33,6 +38,12 @@ export const RecordTableContextProvider = ({
recordTableId,
);
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
const triggerEvent =
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
? 'CLICK'
: 'MOUSE_DOWN';
return (
<RecordTableContextInternalProvider
value={{
@@ -42,6 +53,8 @@ export const RecordTableContextProvider = ({
objectNameSingular,
objectPermissions,
visibleRecordFields,
onRecordIdentifierClick,
triggerEvent,
}}
>
{children}
@@ -1,9 +1,12 @@
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView';
import { RecordTable } from '@/object-record/record-table/components/RecordTable';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
import { EntityDeleteContext } from '@/object-record/record-table/contexts/EntityDeleteHookContext';
import { useSelectAllRows } from '@/object-record/record-table/hooks/internal/useSelectAllRows';
import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow';
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow';
import { PageFocusId } from '@/types/PageFocusId';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
@@ -47,6 +50,16 @@ export const RecordTableWithWrappers = ({
},
});
const { activateRecordTableRow } = useActiveRecordTableRow(recordTableId);
const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordTableId);
const { openRecordFromIndexView } = useOpenRecordFromIndexView();
const handleRecordIdentifierClick = (rowIndex: number, recordId: string) => {
activateRecordTableRow(rowIndex);
unfocusRecordTableRow();
openRecordFromIndexView({ recordId });
};
const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular });
return (
@@ -55,6 +68,7 @@ export const RecordTableWithWrappers = ({
recordTableId={recordTableId}
viewBarId={viewBarId}
objectNameSingular={objectNameSingular}
onRecordIdentifierClick={handleRecordIdentifierClick}
>
<EntityDeleteContext.Provider value={deleteOneRecord}>
<ScrollWrapper
@@ -161,6 +161,8 @@ const meta: Meta = {
objectMetadataId: mockPerformance.objectMetadataItem.id,
},
visibleRecordFields,
onRecordIdentifierClick: () => {},
triggerEvent: 'CLICK',
}}
>
<RecordTableComponentInstance recordTableId={'recordTableId'}>
@@ -10,6 +10,8 @@ type RecordTableContextValue = {
objectMetadataItem: ObjectMetadataItem;
objectPermissions: ObjectPermission;
visibleRecordFields: RecordField[];
onRecordIdentifierClick?: (rowIndex: number, recordId: string) => void;
triggerEvent: 'CLICK' | 'MOUSE_DOWN';
};
export const [RecordTableContextProvider, useRecordTableContextOrThrow] =
@@ -26,7 +26,6 @@ export const RecordTableCellFieldContextGeneric = ({
useRecordTableContextOrThrow();
const {
indexIdentifierUrl,
objectPermissionsByObjectMetadataId,
fieldDefinitionByFieldMetadataItemId,
} = useRecordIndexContextOrThrow();
@@ -37,6 +36,7 @@ export const RecordTableCellFieldContextGeneric = ({
const updateRecord = useContext(RecordUpdateContext);
let hasObjectReadPermissions = objectPermissions.canReadObjectRecords;
// todo @guillim : adjust this to handle morph relations permissions display
if (
isFieldRelationToOneObject(fieldDefinition) ||
@@ -56,10 +56,10 @@ export const RecordTableCellFieldContextGeneric = ({
return (
<FieldContext.Provider
value={{
fieldMetadataItemId: recordField.fieldMetadataItemId,
recordId,
fieldDefinition: fieldDefinition,
useUpdateRecord: () => [updateRecord, {}],
labelIdentifierLink: indexIdentifierUrl(recordId),
isLabelIdentifier: isLabelIdentifierField({
fieldMetadataItem: {
id: fieldDefinition.fieldMetadataId,
@@ -2,20 +2,11 @@ import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObject
import { isRecordFieldReadOnly } from '@/object-record/read-only/utils/isRecordFieldReadOnly';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow';
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow';
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { useContext, type ReactNode } from 'react';
import { useRecoilValue } from 'recoil';
import { useIsMobile } from 'twenty-ui/utilities';
type RecordTableCellFieldContextLabelIdentifierProps = {
children: ReactNode;
@@ -24,22 +15,16 @@ type RecordTableCellFieldContextLabelIdentifierProps = {
export const RecordTableCellFieldContextLabelIdentifier = ({
children,
}: RecordTableCellFieldContextLabelIdentifierProps) => {
const { indexIdentifierUrl, objectPermissionsByObjectMetadataId } =
useRecordIndexContextOrThrow();
const { recordId, isRecordReadOnly } = useRecordTableRowContextOrThrow();
const { fieldDefinitionByFieldMetadataItemId } =
useRecordIndexContextOrThrow();
const {
objectPermissionsByObjectMetadataId,
fieldDefinitionByFieldMetadataItemId,
} = useRecordIndexContextOrThrow();
const { recordId, isRecordReadOnly, rowIndex } =
useRecordTableRowContextOrThrow();
const { recordField } = useContext(RecordTableCellContext);
const { objectMetadataItem, recordTableId } = useRecordTableContextOrThrow();
const { rowIndex } = useRecordTableRowContextOrThrow();
const { activateRecordTableRow } = useActiveRecordTableRow(recordTableId);
const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordTableId);
const isMobile = useIsMobile();
const isRecordTableScrolledLeftComponent = useRecoilComponentValue(
isRecordTableScrolledLeftComponentState,
);
const { objectMetadataItem, onRecordIdentifierClick, triggerEvent } =
useRecordTableContextOrThrow();
const objectPermissions = getObjectPermissionsForObject(
objectPermissionsByObjectMetadataId,
@@ -50,29 +35,20 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
const updateRecord = useContext(RecordUpdateContext);
const isLabelIdentifierCompact =
isMobile && !isRecordTableScrolledLeftComponent;
const { openRecordFromIndexView } = useOpenRecordFromIndexView();
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
const triggerEvent =
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
? 'CLICK'
: 'MOUSE_DOWN';
const fieldDefinition =
fieldDefinitionByFieldMetadataItemId[recordField.fieldMetadataItemId];
const handleChipClick = () => {
onRecordIdentifierClick?.(rowIndex, recordId);
};
return (
<FieldContext.Provider
value={{
recordId,
fieldDefinition,
useUpdateRecord: () => [updateRecord, {}],
labelIdentifierLink: indexIdentifierUrl(recordId),
isLabelIdentifier: true,
isLabelIdentifierCompact,
displayedMaxRows: 1,
isRecordFieldReadOnly: isRecordFieldReadOnly({
isRecordReadOnly: isRecordReadOnly ?? false,
@@ -83,11 +59,7 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
},
}),
maxWidth: recordField.size,
onRecordChipClick: () => {
activateRecordTableRow(rowIndex);
unfocusRecordTableRow();
openRecordFromIndexView({ recordId });
},
onRecordChipClick: handleChipClick,
isForbidden: !hasObjectReadPermissions,
triggerEvent,
}}
@@ -35,6 +35,7 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
recordTableId={recordTableId}
viewBarId="viewBarId"
objectNameSingular={CoreObjectNameSingular.Person}
onRecordIdentifierClick={() => {}}
>
<RecordComponentInstanceContextsWrapper
componentInstanceId={recordTableId}
@@ -89,7 +89,6 @@ describe('mapViewFieldsToColumnDefinitions', () => {
fieldMetadataId: '1',
label: 'label 1',
metadata: { fieldName: 'fieldName 1' },
infoTooltipContent: 'infoTooltipContent 1',
iconName: 'iconName 1',
type: FieldMetadataType.TEXT,
position: 1,
@@ -109,7 +108,6 @@ describe('mapViewFieldsToColumnDefinitions', () => {
fieldMetadataId: '2',
label: 'label 2',
metadata: { fieldName: 'fieldName 2' },
infoTooltipContent: 'infoTooltipContent 2',
iconName: 'iconName 2',
type: FieldMetadataType.TEXT,
position: 2,
@@ -129,7 +127,6 @@ describe('mapViewFieldsToColumnDefinitions', () => {
fieldMetadataId: '3',
label: 'label 3',
metadata: { fieldName: 'fieldName 3' },
infoTooltipContent: 'infoTooltipContent 3',
iconName: 'iconName 3',
type: FieldMetadataType.TEXT,
position: 3,
@@ -146,7 +143,6 @@ describe('mapViewFieldsToColumnDefinitions', () => {
label: 'label 1',
position: 1,
metadata: { fieldName: 'fieldName 1' },
infoTooltipContent: 'infoTooltipContent 1',
iconName: 'iconName 1',
type: FieldMetadataType.TEXT,
size: 1,
@@ -156,7 +152,6 @@ describe('mapViewFieldsToColumnDefinitions', () => {
label: 'label 3',
position: 3,
metadata: { fieldName: 'fieldName 3' },
infoTooltipContent: 'infoTooltipContent 3',
iconName: 'iconName 3',
type: FieldMetadataType.TEXT,
size: 3,
@@ -168,7 +163,6 @@ describe('mapViewFieldsToColumnDefinitions', () => {
fieldMetadataId: '1',
label: 'label 1',
metadata: { fieldName: 'fieldName 1' },
infoTooltipContent: 'infoTooltipContent 1',
iconName: 'iconName 1',
type: FieldMetadataType.TEXT,
size: 1,
@@ -180,7 +174,6 @@ describe('mapViewFieldsToColumnDefinitions', () => {
fieldMetadataId: '3',
label: 'label 3',
metadata: { fieldName: 'fieldName 3' },
infoTooltipContent: 'infoTooltipContent 3',
iconName: 'iconName 3',
type: FieldMetadataType.TEXT,
size: 3,
@@ -39,7 +39,6 @@ export const mapViewFieldsToColumnDefinitions = ({
fieldMetadataId: viewField.fieldMetadataId,
label: correspondingColumnDefinition.label,
metadata: correspondingColumnDefinition.metadata,
infoTooltipContent: correspondingColumnDefinition.infoTooltipContent,
iconName: correspondingColumnDefinition.iconName,
type: correspondingColumnDefinition.type,
position: isLabelIdentifier ? 0 : viewField.position,
@@ -133,6 +133,8 @@ const InternalTableContextProviders = ({
}),
);
const triggerEvent = 'CLICK';
return (
<RecordIndexContextProvider
value={{
@@ -160,6 +162,8 @@ const InternalTableContextProviders = ({
objectMetadataId: objectMetadataItem.id,
}),
visibleRecordFields,
onRecordIdentifierClick: () => {},
triggerEvent,
}}
>
<RecordTableBodyContextProvider
@@ -129,6 +129,7 @@ export const getFieldDecorator =
>
<FieldContext.Provider
value={{
fieldMetadataItemId: fieldMetadataItem.id,
recordId: record.id,
isLabelIdentifier,
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
@@ -166,7 +166,7 @@ export const Chip = ({
{leftComponent}
{!isLabelHidden && label && label.trim() ? (
<OverflowingTextWithTooltip size={size} text={label} />
) : !forceEmptyText ? (
) : !forceEmptyText && !isLabelHidden ? (
<StyledDiv>Untitled</StyledDiv>
) : (
''