Compare commits

...
Author SHA1 Message Date
Charles Bochet 78403581ae Try Charles 2026-04-13 17:10:56 +02:00
Thomas Trompette 7da89c5267 Rename util 2026-04-13 16:47:04 +02:00
Thomas Trompette 4c2a2dbd4a Rename select to fallback 2026-04-13 16:47:04 +02:00
Thomas Trompette 0313464931 Fix merge with null value + reset data virtualization before init load 2026-04-13 16:47:04 +02:00
7 changed files with 167 additions and 139 deletions
@@ -1,5 +1,5 @@
import { useCallback } from 'react';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { useRecordIndexTableLazyQuery } from '@/object-record/record-index/hooks/useRecordIndexTableLazyQuery';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
@@ -17,20 +17,20 @@ import { useReapplyRowSelection } from '@/object-record/record-table/virtualizat
import { useResetTableFocuses } from '@/object-record/record-table/virtualization/hooks/useResetTableFocuses';
import { useResetVirtualizedRowTreadmill } from '@/object-record/record-table/virtualization/hooks/useResetVirtualizedRowTreadmill';
import { dataLoadingStatusByRealIndexComponentState } from '@/object-record/record-table/virtualization/states/dataLoadingStatusByRealIndexComponentState';
import { dataPagesLoadedComponentState } from '@/object-record/record-table/virtualization/states/dataPagesLoadedComponentState';
import { isInitializingVirtualTableDataLoadingComponentState } from '@/object-record/record-table/virtualization/states/isInitializingVirtualTableDataLoadingComponentState';
import { lastRealIndexSetComponentState } from '@/object-record/record-table/virtualization/states/lastRealIndexSetComponentState';
import { lastScrollPositionComponentState } from '@/object-record/record-table/virtualization/states/lastScrollPositionComponentState';
import { recordIdByRealIndexComponentState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentState';
import { scrollAtRealIndexComponentState } from '@/object-record/record-table/virtualization/states/scrollAtRealIndexComponentState';
import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { SIGN_IN_BACKGROUND_MOCK_COMPANIES } from '@/sign-in-background-mock/constants/SignInBackgroundMockCompanies';
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
import { dataLoadingStatusByRealIndexComponentState } from '@/object-record/record-table/virtualization/states/dataLoadingStatusByRealIndexComponentState';
import { recordIdByRealIndexComponentState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentState';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
import { useAtomComponentSelectorCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorCallbackState';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
import { isDefined } from 'twenty-shared/utils';
export const useTriggerInitialRecordTableDataLoad = () => {
@@ -62,6 +62,11 @@ export const useTriggerInitialRecordTableDataLoad = () => {
const store = useStore();
// const recordIndexRecordIdsByGroupFamilyState =
// useAtomComponentFamilyStateCallbackState(
// recordIndexRecordIdsByGroupComponentFamilyState,
// );
const recordIdByRealIndexCallbackState = useAtomComponentStateCallbackState(
recordIdByRealIndexComponentState,
);
@@ -165,6 +170,11 @@ export const useTriggerInitialRecordTableDataLoad = () => {
newDataLoadingStatusByRealIndex,
);
// store.set(
// recordIndexRecordIdsByGroupFamilyState(NO_RECORD_GROUP_FAMILY_KEY),
// [],
// );
const { records: findManyRecords, totalCount: findManyTotalCount } =
await findManyRecordsLazy();
@@ -0,0 +1,128 @@
import { defaultMergeFieldValue } from 'src/engine/api/graphql/graphql-query-runner/utils/default-merge-field-value.util';
describe('defaultMergeFieldValue', () => {
it('should return priority record value when available', () => {
const recordsWithValues = [
{
recordId: '1',
value: 'priority value',
},
{
recordId: '2',
value: 'other value',
},
];
const result = defaultMergeFieldValue(recordsWithValues, '1');
expect(result).toBe('priority value');
});
it('should fall back to first record with value when priority record not found', () => {
const recordsWithValues = [
{
recordId: '2',
value: 'first value',
},
{
recordId: '3',
value: 'second value',
},
];
const result = defaultMergeFieldValue(recordsWithValues, '1');
expect(result).toBe('first value');
});
it('should fall back to other record when priority record has null value', () => {
const recordsWithValues = [
{
recordId: '1',
value: null,
},
{
recordId: '2',
value: 'fallback value',
},
];
const result = defaultMergeFieldValue(recordsWithValues, '1');
expect(result).toBe('fallback value');
});
it('should fall back to other record when priority record has empty string', () => {
const recordsWithValues = [
{
recordId: '1',
value: '',
},
{
recordId: '2',
value: 'fallback value',
},
];
const result = defaultMergeFieldValue(recordsWithValues, '1');
expect(result).toBe('fallback value');
});
it('should fall back to other record when priority record has undefined value', () => {
const recordsWithValues = [
{
recordId: '1',
value: undefined,
},
{
recordId: '2',
value: 'fallback value',
},
];
const result = defaultMergeFieldValue(recordsWithValues, '1');
expect(result).toBe('fallback value');
});
it('should return null when no records exist', () => {
const result = defaultMergeFieldValue([], '1');
expect(result).toBeNull();
});
it('should return null when no record has a value', () => {
const recordsWithValues = [
{
recordId: '1',
value: null,
},
{
recordId: '2',
value: '',
},
];
const result = defaultMergeFieldValue(recordsWithValues, '1');
expect(result).toBeNull();
});
it('should handle complex object values', () => {
const recordsWithValues = [
{
recordId: '1',
value: { name: 'priority object', id: 1 },
},
{
recordId: '2',
value: { name: 'fallback object', id: 2 },
},
];
const result = defaultMergeFieldValue(recordsWithValues, '1');
expect(result).toEqual({ name: 'priority object', id: 1 });
});
});
@@ -21,20 +21,19 @@ describe('mergeFieldValues', () => {
expect(result).toBe('value2');
});
it('should throw error when priority record is not found', () => {
const recordsWithoutPriorityValue = [
it('should fall back to first record with value when priority record is not found', () => {
const recordsWithValues = [
{ value: 'value1', recordId: 'record1' },
{ value: null, recordId: PRIORITY_RECORD_ID },
{ value: 'value3', recordId: 'record3' },
];
expect(() =>
mergeFieldValues(
FieldMetadataType.TEXT,
recordsWithoutPriorityValue,
'non-existent-id',
),
).toThrow('Priority record with ID non-existent-id not found');
const result = mergeFieldValues(
FieldMetadataType.TEXT,
recordsWithValues,
'non-existent-id',
);
expect(result).toBe('value1');
});
});
@@ -1,111 +0,0 @@
import { selectPriorityFieldValue } from 'src/engine/api/graphql/graphql-query-runner/utils/select-priority-field-value.util';
describe('selectPriorityFieldValue', () => {
it('should return priority record value when available', () => {
const recordsWithValues = [
{
recordId: '1',
value: 'priority value',
},
{
recordId: '2',
value: 'other value',
},
];
const result = selectPriorityFieldValue(recordsWithValues, '1');
expect(result).toBe('priority value');
});
it('should throw error when priority record not found', () => {
const recordsWithValues = [
{
recordId: '2',
value: 'first value',
},
{
recordId: '3',
value: 'second value',
},
];
expect(() => selectPriorityFieldValue(recordsWithValues, '1')).toThrow(
'Priority record with ID 1 not found in merge candidates',
);
});
it('should return null when priority record has no value', () => {
const recordsWithValues = [
{
recordId: '1',
value: null,
},
{
recordId: '2',
value: 'fallback value',
},
];
const result = selectPriorityFieldValue(recordsWithValues, '1');
expect(result).toBeNull();
});
it('should return null when priority record has empty string', () => {
const recordsWithValues = [
{
recordId: '1',
value: '',
},
{
recordId: '2',
value: 'fallback value',
},
];
const result = selectPriorityFieldValue(recordsWithValues, '1');
expect(result).toBeNull();
});
it('should return null when priority record has undefined value', () => {
const recordsWithValues = [
{
recordId: '1',
value: undefined,
},
{
recordId: '2',
value: 'fallback value',
},
];
const result = selectPriorityFieldValue(recordsWithValues, '1');
expect(result).toBeNull();
});
it('should throw error when no records exist', () => {
expect(() => selectPriorityFieldValue([], '1')).toThrow(
'Priority record with ID 1 not found in merge candidates',
);
});
it('should handle complex object values', () => {
const recordsWithValues = [
{
recordId: '1',
value: { name: 'priority object', id: 1 },
},
{
recordId: '2',
value: { name: 'fallback object', id: 2 },
},
];
const result = selectPriorityFieldValue(recordsWithValues, '1');
expect(result).toEqual({ name: 'priority object', id: 1 });
});
});
@@ -2,7 +2,7 @@ import { isDefined } from 'twenty-shared/utils';
import { hasRecordFieldValue } from 'src/engine/api/graphql/graphql-query-runner/utils/has-record-field-value.util';
export const selectPriorityFieldValue = <T>(
export const defaultMergeFieldValue = <T>(
recordsWithValues: { value: T; recordId: string }[],
priorityRecordId: string,
): T | null => {
@@ -10,14 +10,16 @@ export const selectPriorityFieldValue = <T>(
(record) => record.recordId === priorityRecordId,
);
if (!isDefined(priorityRecord)) {
throw new Error(
`Priority record with ID ${priorityRecordId} not found in merge candidates`,
);
if (isDefined(priorityRecord) && hasRecordFieldValue(priorityRecord.value)) {
return priorityRecord.value;
}
if (hasRecordFieldValue(priorityRecord.value)) {
return priorityRecord.value;
const fallbackRecord = recordsWithValues.find((record) =>
hasRecordFieldValue(record.value),
);
if (fallbackRecord) {
return fallbackRecord.value;
}
return null;
@@ -1,9 +1,9 @@
import {
FieldMetadataType,
type RelationType,
type EmailsMetadata,
type LinksMetadata,
type PhonesMetadata,
type RelationType,
} from 'twenty-shared/types';
import { mergeArrayFieldValues } from './merge-array-field-values.util';
@@ -11,7 +11,7 @@ import { mergeEmailsFieldValues } from './merge-emails-field-values.util';
import { mergeLinksFieldValues } from './merge-links-field-values.util';
import { mergePhonesFieldValues } from './merge-phones-field-values.util';
import { mergeRelationFieldValuesForDryRunRecord } from './merge-relation-field-values-for-dry-run-record.util';
import { selectPriorityFieldValue } from './select-priority-field-value.util';
import { defaultMergeFieldValue } from './default-merge-field-value.util';
export const mergeFieldValues = (
fieldType: FieldMetadataType,
@@ -34,7 +34,7 @@ export const mergeFieldValues = (
);
}
return selectPriorityFieldValue(recordsWithValues, priorityRecordId);
return defaultMergeFieldValue(recordsWithValues, priorityRecordId);
case FieldMetadataType.EMAILS:
return mergeEmailsFieldValues(
@@ -55,6 +55,6 @@ export const mergeFieldValues = (
);
default:
return selectPriorityFieldValue(recordsWithValues, priorityRecordId);
return defaultMergeFieldValue(recordsWithValues, priorityRecordId);
}
};
@@ -1,7 +1,7 @@
import { type ObjectRecord, RelationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { selectPriorityFieldValue } from 'src/engine/api/graphql/graphql-query-runner/utils/select-priority-field-value.util';
import { defaultMergeFieldValue } from 'src/engine/api/graphql/graphql-query-runner/utils/default-merge-field-value.util';
export const mergeRelationFieldValuesForDryRunRecord = (
recordsWithValues: { value: unknown; recordId: string }[],
@@ -12,7 +12,7 @@ export const mergeRelationFieldValuesForDryRunRecord = (
return mergeOneToManyRelationArrays(recordsWithValues);
}
return selectPriorityFieldValue(
return defaultMergeFieldValue(
recordsWithValues as { value: ObjectRecord | null; recordId: string }[],
priorityRecordId,
);