[Fix] : fix: Allow label identifier system fields in view creation and fix resulting duplicate header columns (#19009)

fixes #18994 

After :
<img width="767" height="230" alt="Screenshot 2026-03-26 at 7 11 04 PM"
src="https://github.com/user-attachments/assets/73de0154-7da1-48fa-92fc-d51a3ef5b06e"
/>

Before : 
<img width="905" height="314" alt="Screenshot 2026-03-26 at 6 56 38 PM"
src="https://github.com/user-attachments/assets/89b1249b-8889-4034-a6c8-d41330154c1a"
/>

---------

Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Arun
2026-06-14 20:15:28 +05:30
committed by GitHub
parent 7c0136b97b
commit f45c54679c
5 changed files with 88 additions and 33 deletions
@@ -1,4 +1,3 @@
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableHeaderAddColumnButton } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderAddColumnButton';
@@ -15,7 +14,6 @@ import { isRecordTableColumnHeadersReadOnlyComponentState } from '@/object-recor
import { isRecordTableDragColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableDragColumnHiddenComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { styled } from '@linaria/react';
import { filterOutByProperty } from 'twenty-shared/utils';
const StyledHeaderContainer = styled.div`
display: flex;
@@ -27,7 +25,6 @@ const StyledHeaderContainer = styled.div`
export const RecordTableHeader = () => {
const { visibleRecordFields } = useRecordTableContextOrThrow();
const { labelIdentifierFieldMetadataItem } = useRecordIndexContextOrThrow();
const isRecordTableColumnHeadersReadOnly = useAtomComponentStateValue(
isRecordTableColumnHeadersReadOnlyComponentState,
@@ -41,14 +38,7 @@ export const RecordTableHeader = () => {
isRecordTableCheckboxColumnHiddenComponentState,
);
const recordFieldsWithoutLabelIdentifierAndFirstOne = visibleRecordFields
.filter(
filterOutByProperty(
'fieldMetadataItemId',
labelIdentifierFieldMetadataItem?.id,
),
)
.slice(1);
const recordFieldsWithoutFirstTwo = visibleRecordFields.slice(2);
useResizeTableHeader();
@@ -60,15 +50,13 @@ export const RecordTableHeader = () => {
)}
<RecordTableHeaderFirstCell />
<RecordTableHeaderFirstScrollableCell />
{recordFieldsWithoutLabelIdentifierAndFirstOne.map(
(recordField, index) => (
<RecordTableHeaderCell
key={recordField.fieldMetadataItemId}
recordField={recordField}
recordFieldIndex={index + 2}
/>
),
)}
{recordFieldsWithoutFirstTwo.map((recordField, index) => (
<RecordTableHeaderCell
key={recordField.fieldMetadataItemId}
recordField={recordField}
recordFieldIndex={index + 2}
/>
))}
{isRecordTableColumnHeadersReadOnly ? (
<RecordTableHeaderEmptyLastColumn />
) : (
@@ -1,5 +1,4 @@
import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableColumnHead } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHead';
@@ -21,7 +20,7 @@ import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hoo
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { cx } from '@linaria/core';
import { filterOutByProperty, isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared/utils';
export const RecordTableHeaderFirstScrollableCell = () => {
const { objectMetadataItem, visibleRecordFields } =
@@ -45,14 +44,7 @@ export const RecordTableHeaderFirstScrollableCell = () => {
0,
);
const { labelIdentifierFieldMetadataItem } = useRecordIndexContextOrThrow();
const recordField = visibleRecordFields.filter(
filterOutByProperty(
'fieldMetadataItemId',
labelIdentifierFieldMetadataItem?.id,
),
)[0] as RecordField | undefined;
const recordField = visibleRecordFields[1] as RecordField | undefined;
const isRecordTableRowFocusActive = useAtomComponentStateValue(
isRecordTableRowFocusActiveComponentState,
@@ -0,0 +1,67 @@
import { filterFieldsForRecordTableViewCreation } from '@/page-layout/widgets/record-table/utils/filterFieldsForRecordTableViewCreation';
import { getMockFieldMetadataItemOrThrow } from '~/testing/utils/getMockFieldMetadataItemOrThrow';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
describe('filterFieldsForRecordTableViewCreation', () => {
const objectMetadataItem = getMockObjectMetadataItemOrThrow('company');
const labelIdentifierFieldMetadataId =
objectMetadataItem.labelIdentifierFieldMetadataId;
// domainName is an active, non-system field that is not the label identifier
const nonSystemField = getMockFieldMetadataItemOrThrow({
objectMetadataItem,
fieldName: 'domainName',
});
// id is a hidden system field
const systemField = getMockFieldMetadataItemOrThrow({
objectMetadataItem,
fieldName: 'id',
});
// position is a hidden system field
const hiddenSystemField = getMockFieldMetadataItemOrThrow({
objectMetadataItem,
fieldName: 'position',
});
it('should include active non-system fields', () => {
expect(
filterFieldsForRecordTableViewCreation(
nonSystemField,
labelIdentifierFieldMetadataId,
),
).toBe(true);
});
it('should exclude inactive fields', () => {
expect(
filterFieldsForRecordTableViewCreation(
{ ...nonSystemField, isActive: false },
labelIdentifierFieldMetadataId,
),
).toBe(false);
});
it('should exclude system fields that are not the label identifier', () => {
expect(
filterFieldsForRecordTableViewCreation(
systemField,
labelIdentifierFieldMetadataId,
),
).toBe(false);
});
it('should include system fields when they are the label identifier', () => {
expect(
filterFieldsForRecordTableViewCreation(systemField, systemField.id),
).toBe(true);
});
it('should include hidden system fields when they are the label identifier', () => {
expect(
filterFieldsForRecordTableViewCreation(
hiddenSystemField,
hiddenSystemField.id,
),
).toBe(true);
});
});
@@ -33,8 +33,11 @@ export const buildRecordTableWidgetViewSnapshot = (
isActive: true,
};
const eligibleFields = objectMetadataItem.fields.filter(
filterFieldsForRecordTableViewCreation,
const eligibleFields = objectMetadataItem.fields.filter((field) =>
filterFieldsForRecordTableViewCreation(
field,
objectMetadataItem.labelIdentifierFieldMetadataId,
),
);
const sortedFields = eligibleFields.toSorted(
@@ -3,6 +3,11 @@ import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField
export const filterFieldsForRecordTableViewCreation = (
field: FieldMetadataItem,
labelIdentifierFieldMetadataId: string,
) => {
return field.isActive && !field.isSystem && !isHiddenSystemField(field);
const isLabelIdentifier = field.id === labelIdentifierFieldMetadataId;
return (
field.isActive &&
(isLabelIdentifier || (!field.isSystem && !isHiddenSystemField(field)))
);
};