[FIX] Optimistically compute position only for objectMetadataItem that has the field (#10510)
# Introduction In this PR https://github.com/twentyhq/twenty/pull/10493 introduced a regression on optimistic cache for record creation, by expecting a `position` `fieldMetadataItem` on every `ObjectMetadataItem` which is a wrong assertion Some `Tasks` and `ApiKeys` do not have one ## Fix Dynamically compute optimistic record input position depending on current `ObjectMetadataItem` ## Refactor Refactored a failing test following [jest each](https://jestjs.io/docs/api#describeeachtablename-fn-timeout) pattern to avoid error prone duplicated env tests. Created a "standard' and applied it to an other test also using `it.each`.
This commit is contained in:
@ -1,7 +1,7 @@
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { FieldMetadataType } from 'twenty-shared';
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
|
|
||||||
export const checkObjectMetadataItemHasFieldCreatedBy = (
|
export const hasObjectMetadataItemFieldCreatedBy = (
|
||||||
objectMetadataItem: ObjectMetadataItem,
|
objectMetadataItem: ObjectMetadataItem,
|
||||||
) =>
|
) =>
|
||||||
objectMetadataItem.fields.some(
|
objectMetadataItem.fields.some(
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
|
|
||||||
|
export const hasObjectMetadataItemPositionField = (
|
||||||
|
objectMetadataItem: ObjectMetadataItem,
|
||||||
|
) =>
|
||||||
|
!objectMetadataItem.isRemote &&
|
||||||
|
objectMetadataItem.fields.some(
|
||||||
|
(field) => field.type === FieldMetadataType.POSITION,
|
||||||
|
);
|
||||||
@ -1,4 +0,0 @@
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
|
||||||
|
|
||||||
export const hasPositionField = (objectMetadataItem: ObjectMetadataItem) =>
|
|
||||||
!objectMetadataItem.isRemote;
|
|
||||||
@ -6,7 +6,7 @@ import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effec
|
|||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||||
import { checkObjectMetadataItemHasFieldCreatedBy } from '@/object-metadata/utils/checkObjectMetadataItemHasFieldCreatedBy';
|
import { hasObjectMetadataItemFieldCreatedBy } from '@/object-metadata/utils/hasObjectMetadataItemFieldCreatedBy';
|
||||||
import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreateOneRecordInCache';
|
import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreateOneRecordInCache';
|
||||||
import { deleteRecordFromCache } from '@/object-record/cache/utils/deleteRecordFromCache';
|
import { deleteRecordFromCache } from '@/object-record/cache/utils/deleteRecordFromCache';
|
||||||
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
|
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
|
||||||
@ -49,7 +49,7 @@ export const useCreateManyRecords = <
|
|||||||
});
|
});
|
||||||
|
|
||||||
const objectMetadataHasCreatedByField =
|
const objectMetadataHasCreatedByField =
|
||||||
checkObjectMetadataItemHasFieldCreatedBy(objectMetadataItem);
|
hasObjectMetadataItemFieldCreatedBy(objectMetadataItem);
|
||||||
|
|
||||||
const computedRecordGqlFields =
|
const computedRecordGqlFields =
|
||||||
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
|
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effec
|
|||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||||
import { checkObjectMetadataItemHasFieldCreatedBy } from '@/object-metadata/utils/checkObjectMetadataItemHasFieldCreatedBy';
|
|
||||||
import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreateOneRecordInCache';
|
import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreateOneRecordInCache';
|
||||||
import { deleteRecordFromCache } from '@/object-record/cache/utils/deleteRecordFromCache';
|
import { deleteRecordFromCache } from '@/object-record/cache/utils/deleteRecordFromCache';
|
||||||
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
|
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
|
||||||
@ -16,8 +15,8 @@ import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types
|
|||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||||
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
||||||
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
||||||
import { FieldActorForInputValue } from '@/object-record/record-field/types/FieldMetadata';
|
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
import { computeOptimisticCreateRecordBaseRecordInput } from '@/object-record/utils/computeOptimisticCreateRecordBaseRecordInput';
|
||||||
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
|
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
|
||||||
import { getCreateOneRecordMutationResponseField } from '@/object-record/utils/getCreateOneRecordMutationResponseField';
|
import { getCreateOneRecordMutationResponseField } from '@/object-record/utils/getCreateOneRecordMutationResponseField';
|
||||||
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
||||||
@ -46,9 +45,6 @@ export const useCreateOneRecord = <
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
const objectMetadataHasCreatedByField =
|
|
||||||
checkObjectMetadataItemHasFieldCreatedBy(objectMetadataItem);
|
|
||||||
|
|
||||||
const computedRecordGqlFields =
|
const computedRecordGqlFields =
|
||||||
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
|
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
|
||||||
|
|
||||||
@ -84,25 +80,14 @@ export const useCreateOneRecord = <
|
|||||||
id: idForCreation,
|
id: idForCreation,
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseOptimisticRecordInputCreatedBy:
|
|
||||||
| { createdBy: FieldActorForInputValue }
|
|
||||||
| undefined = objectMetadataHasCreatedByField
|
|
||||||
? {
|
|
||||||
createdBy: {
|
|
||||||
source: 'MANUAL',
|
|
||||||
context: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
const optimisticRecordInput = computeOptimisticRecordFromInput({
|
const optimisticRecordInput = computeOptimisticRecordFromInput({
|
||||||
cache: apolloClient.cache,
|
cache: apolloClient.cache,
|
||||||
currentWorkspaceMember: currentWorkspaceMember,
|
currentWorkspaceMember: currentWorkspaceMember,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
recordInput: {
|
recordInput: {
|
||||||
...baseOptimisticRecordInputCreatedBy,
|
...computeOptimisticCreateRecordBaseRecordInput(objectMetadataItem),
|
||||||
...recordInput,
|
...recordInput,
|
||||||
position: Number.NEGATIVE_INFINITY,
|
|
||||||
id: idForCreation,
|
id: idForCreation,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,11 +1,23 @@
|
|||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { RecordGqlOperationOrderBy } from '@/object-record/graphql/types/RecordGqlOperationOrderBy';
|
||||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||||
import { RecordSort } from '@/object-record/record-sort/types/RecordSort';
|
import { RecordSort } from '@/object-record/record-sort/types/RecordSort';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
import { EachTestingContext } from '~/types/EachTestingContext';
|
||||||
|
|
||||||
const objectMetadataItem: ObjectMetadataItem = {
|
const objectMetadataItemWithPositionField: ObjectMetadataItem = {
|
||||||
id: 'object1',
|
id: 'object1',
|
||||||
fields: [],
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
updatedAt: '2021-01-01',
|
||||||
|
createdAt: '2021-01-01',
|
||||||
|
id: '20202020-18b3-4099-86e3-c46b2d5d42f2',
|
||||||
|
type: FieldMetadataType.POSITION,
|
||||||
|
label: 'label',
|
||||||
|
},
|
||||||
|
],
|
||||||
indexMetadatas: [],
|
indexMetadatas: [],
|
||||||
createdAt: '2021-01-01',
|
createdAt: '2021-01-01',
|
||||||
updatedAt: '2021-01-01',
|
updatedAt: '2021-01-01',
|
||||||
@ -22,81 +34,126 @@ const objectMetadataItem: ObjectMetadataItem = {
|
|||||||
isLabelSyncedWithName: true,
|
isLabelSyncedWithName: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('turnSortsIntoOrderBy', () => {
|
type PartialFieldMetadaItemWithRequiredId = Pick<FieldMetadataItem, 'id'> &
|
||||||
it('should sort by recordPosition if no sorts', () => {
|
Partial<Omit<FieldMetadataItem, 'id'>>;
|
||||||
const fields = [{ id: 'field1', name: 'createdAt' }] as FieldMetadataItem[];
|
const getMockFieldMetadataItem = (
|
||||||
expect(turnSortsIntoOrderBy({ ...objectMetadataItem, fields }, [])).toEqual(
|
overrides: PartialFieldMetadaItemWithRequiredId,
|
||||||
[
|
): FieldMetadataItem => ({
|
||||||
|
name: 'name',
|
||||||
|
updatedAt: '2021-01-01',
|
||||||
|
createdAt: '2021-01-01',
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
label: 'label',
|
||||||
|
...overrides,
|
||||||
|
});
|
||||||
|
|
||||||
|
type TurnSortsIntoOrderTestContext = EachTestingContext<{
|
||||||
|
fields: PartialFieldMetadaItemWithRequiredId[];
|
||||||
|
expected: RecordGqlOperationOrderBy;
|
||||||
|
sort: RecordSort[];
|
||||||
|
objectMetadataItemOverrides?: Partial<Omit<ObjectMetadataItem, 'fields'>>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const turnSortsIntoOrderByTestUseCases: TurnSortsIntoOrderTestContext[] = [
|
||||||
|
{
|
||||||
|
title: 'It should sort by recordPosition if no sorts',
|
||||||
|
context: {
|
||||||
|
fields: [{ id: 'field1', name: 'field1' }],
|
||||||
|
sort: [],
|
||||||
|
expected: [
|
||||||
{
|
{
|
||||||
position: 'AscNullsFirst',
|
position: 'AscNullsFirst',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
);
|
},
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
title: 'It should create OrderByField with single sort',
|
||||||
|
context: {
|
||||||
|
fields: [{ id: 'field1', name: 'field1' }],
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
id: 'id',
|
||||||
|
fieldMetadataId: 'field1',
|
||||||
|
direction: 'asc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
expected: [{ field1: 'AscNullsFirst' }, { position: 'AscNullsFirst' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'It should create OrderByField with multiple sorts',
|
||||||
|
context: {
|
||||||
|
fields: [
|
||||||
|
{ id: 'field1', name: 'field1' },
|
||||||
|
{ id: 'field2', name: 'field2' },
|
||||||
|
],
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
id: 'id',
|
||||||
|
fieldMetadataId: 'field1',
|
||||||
|
direction: 'asc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'id',
|
||||||
|
fieldMetadataId: 'field2',
|
||||||
|
direction: 'desc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
expected: [
|
||||||
|
{ field1: 'AscNullsFirst' },
|
||||||
|
{ field2: 'DescNullsLast' },
|
||||||
|
{ position: 'AscNullsFirst' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'It should ignore if field not found',
|
||||||
|
context: {
|
||||||
|
fields: [],
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
id: 'id',
|
||||||
|
fieldMetadataId: 'invalidField',
|
||||||
|
direction: 'asc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
expected: [{ position: 'AscNullsFirst' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'It should not return position for remotes',
|
||||||
|
context: {
|
||||||
|
fields: [],
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
id: 'id',
|
||||||
|
fieldMetadataId: 'invalidField',
|
||||||
|
direction: 'asc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
expected: [],
|
||||||
|
objectMetadataItemOverrides: {
|
||||||
|
isRemote: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
it('should create OrderByField with single sort', () => {
|
describe('turnSortsIntoOrderBy', () => {
|
||||||
const sorts: RecordSort[] = [
|
it.each<TurnSortsIntoOrderTestContext>(turnSortsIntoOrderByTestUseCases)(
|
||||||
{
|
'.$title',
|
||||||
id: 'id',
|
({ context: { fields, sort, expected, objectMetadataItemOverrides } }) => {
|
||||||
fieldMetadataId: 'field1',
|
const newFields = fields.map(getMockFieldMetadataItem);
|
||||||
direction: 'asc',
|
const objectMetadataItemWithNewFields = {
|
||||||
},
|
...objectMetadataItemWithPositionField,
|
||||||
];
|
...objectMetadataItemOverrides,
|
||||||
const fields = [{ id: 'field1', name: 'field1' }] as FieldMetadataItem[];
|
fields: [...objectMetadataItemWithPositionField.fields, ...newFields],
|
||||||
expect(
|
};
|
||||||
turnSortsIntoOrderBy({ ...objectMetadataItem, fields }, sorts),
|
|
||||||
).toEqual([{ field1: 'AscNullsFirst' }, { position: 'AscNullsFirst' }]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create OrderByField with multiple sorts', () => {
|
expect(
|
||||||
const sorts: RecordSort[] = [
|
turnSortsIntoOrderBy(objectMetadataItemWithNewFields, sort),
|
||||||
{
|
).toEqual(expected);
|
||||||
id: 'id',
|
},
|
||||||
fieldMetadataId: 'field1',
|
);
|
||||||
direction: 'asc',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'id',
|
|
||||||
fieldMetadataId: 'field2',
|
|
||||||
direction: 'desc',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const fields = [
|
|
||||||
{ id: 'field1', name: 'field1' },
|
|
||||||
{ id: 'field2', name: 'field2' },
|
|
||||||
] as FieldMetadataItem[];
|
|
||||||
expect(
|
|
||||||
turnSortsIntoOrderBy({ ...objectMetadataItem, fields }, sorts),
|
|
||||||
).toEqual([
|
|
||||||
{ field1: 'AscNullsFirst' },
|
|
||||||
{ field2: 'DescNullsLast' },
|
|
||||||
{ position: 'AscNullsFirst' },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should ignore if field not found', () => {
|
|
||||||
const sorts: RecordSort[] = [
|
|
||||||
{
|
|
||||||
id: 'id',
|
|
||||||
fieldMetadataId: 'invalidField',
|
|
||||||
direction: 'asc',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
expect(turnSortsIntoOrderBy(objectMetadataItem, sorts)).toEqual([
|
|
||||||
{ position: 'AscNullsFirst' },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not return position for remotes', () => {
|
|
||||||
const sorts: RecordSort[] = [
|
|
||||||
{
|
|
||||||
id: 'id',
|
|
||||||
fieldMetadataId: 'invalidField',
|
|
||||||
direction: 'asc',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
expect(
|
|
||||||
turnSortsIntoOrderBy({ ...objectMetadataItem, isRemote: true }, sorts),
|
|
||||||
).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
|
||||||
import { hasPositionField } from '@/object-metadata/utils/hasPositionField';
|
|
||||||
import { RecordGqlOperationOrderBy } from '@/object-record/graphql/types/RecordGqlOperationOrderBy';
|
import { RecordGqlOperationOrderBy } from '@/object-record/graphql/types/RecordGqlOperationOrderBy';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
|
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
|
||||||
@ -8,6 +7,7 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
|||||||
|
|
||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { getOrderByForFieldMetadataType } from '@/object-metadata/utils/getOrderByForFieldMetadataType';
|
import { getOrderByForFieldMetadataType } from '@/object-metadata/utils/getOrderByForFieldMetadataType';
|
||||||
|
import { hasObjectMetadataItemPositionField } from '@/object-metadata/utils/hasObjectMetadataItemPositionField';
|
||||||
import { RecordSort } from '@/object-record/record-sort/types/RecordSort';
|
import { RecordSort } from '@/object-record/record-sort/types/RecordSort';
|
||||||
import { OrderBy } from '@/types/OrderBy';
|
import { OrderBy } from '@/types/OrderBy';
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ export const turnSortsIntoOrderBy = (
|
|||||||
})
|
})
|
||||||
.filter(isDefined);
|
.filter(isDefined);
|
||||||
|
|
||||||
if (hasPositionField(objectMetadataItem)) {
|
if (hasObjectMetadataItemPositionField(objectMetadataItem)) {
|
||||||
const positionOrderBy = [
|
const positionOrderBy = [
|
||||||
{
|
{
|
||||||
position: 'AscNullsFirst',
|
position: 'AscNullsFirst',
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { getObjectMetadataIdentifierFields } from '@/object-metadata/utils/getObjectMetadataIdentifierFields';
|
import { getObjectMetadataIdentifierFields } from '@/object-metadata/utils/getObjectMetadataIdentifierFields';
|
||||||
import { hasPositionField } from '@/object-metadata/utils/hasPositionField';
|
import { hasObjectMetadataItemPositionField } from '@/object-metadata/utils/hasObjectMetadataItemPositionField';
|
||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||||
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
|
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
|
||||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||||
@ -58,7 +58,9 @@ export const useRecordBoardRecordGqlFields = ({
|
|||||||
true,
|
true,
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
...(hasPositionField(objectMetadataItem) ? { position: true } : undefined),
|
...(hasObjectMetadataItemPositionField(objectMetadataItem)
|
||||||
|
? { position: true }
|
||||||
|
: undefined),
|
||||||
...identifierQueryFields,
|
...identifierQueryFields,
|
||||||
noteTargets: generateDepthOneRecordGqlFields({
|
noteTargets: generateDepthOneRecordGqlFields({
|
||||||
objectMetadataItem: noteTargetObjectMetadataItem,
|
objectMetadataItem: noteTargetObjectMetadataItem,
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { hasObjectMetadataItemFieldCreatedBy } from '@/object-metadata/utils/hasObjectMetadataItemFieldCreatedBy';
|
||||||
|
import { hasObjectMetadataItemPositionField } from '@/object-metadata/utils/hasObjectMetadataItemPositionField';
|
||||||
|
import { FieldActorForInputValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
|
||||||
|
export const computeOptimisticCreateRecordBaseRecordInput = (
|
||||||
|
objectMetadataItem: ObjectMetadataItem,
|
||||||
|
) => {
|
||||||
|
const baseRecordInput: Partial<ObjectRecord> = {};
|
||||||
|
|
||||||
|
if (hasObjectMetadataItemFieldCreatedBy(objectMetadataItem)) {
|
||||||
|
baseRecordInput.createdBy = {
|
||||||
|
source: 'MANUAL',
|
||||||
|
context: {},
|
||||||
|
} satisfies FieldActorForInputValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasObjectMetadataItemPositionField(objectMetadataItem)) {
|
||||||
|
baseRecordInput.position = Number.NEGATIVE_INFINITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseRecordInput;
|
||||||
|
};
|
||||||
4
packages/twenty-front/src/types/EachTestingContext.ts
Normal file
4
packages/twenty-front/src/types/EachTestingContext.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export type EachTestingContext<T> = {
|
||||||
|
title: string;
|
||||||
|
context: T;
|
||||||
|
};
|
||||||
@ -1,23 +1,40 @@
|
|||||||
|
import { EachTestingContext } from '~/types/EachTestingContext';
|
||||||
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
|
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
|
||||||
|
|
||||||
|
type BuildRecordFromKeysWithSameValueTestContext = EachTestingContext<{
|
||||||
|
array: string[];
|
||||||
|
expected: Record<string, boolean | string>;
|
||||||
|
arg?: boolean | string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const buildRecordFromKeysWithSameValueTestUseCases: BuildRecordFromKeysWithSameValueTestContext[] =
|
||||||
|
[
|
||||||
|
{
|
||||||
|
title: 'It should create record from array and fill with boolean',
|
||||||
|
context: {
|
||||||
|
array: ['foo', 'bar'] as const,
|
||||||
|
expected: { foo: true, bar: true },
|
||||||
|
arg: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'It should create record from array and fill with string',
|
||||||
|
context: {
|
||||||
|
array: ['foo', 'bar'],
|
||||||
|
expected: { foo: 'oui', bar: 'oui' },
|
||||||
|
arg: 'oui',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'It should create empty record from empty array',
|
||||||
|
context: { array: [], expected: {}, arg: undefined },
|
||||||
|
},
|
||||||
|
];
|
||||||
describe('buildRecordFromKeysWithSameValue', () => {
|
describe('buildRecordFromKeysWithSameValue', () => {
|
||||||
test.each([
|
test.each<BuildRecordFromKeysWithSameValueTestContext>(
|
||||||
{ array: [], expected: {}, arg: undefined },
|
buildRecordFromKeysWithSameValueTestUseCases,
|
||||||
{
|
)('.$title', ({ context: { array, arg, expected } }) => {
|
||||||
array: ['foo', 'bar'],
|
const result = buildRecordFromKeysWithSameValue(array, arg);
|
||||||
expected: { foo: 'oui', bar: 'oui' },
|
expect(result).toEqual(expected);
|
||||||
arg: 'oui',
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
array: ['foo', 'bar'] as const,
|
|
||||||
expected: { foo: true, bar: true },
|
|
||||||
arg: true,
|
|
||||||
},
|
|
||||||
])(
|
|
||||||
'.buildRecordFromKeysWithSameValue($array, $arg)',
|
|
||||||
({ array, arg, expected }) => {
|
|
||||||
const result = buildRecordFromKeysWithSameValue(array, arg);
|
|
||||||
expect(result).toEqual(expected);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user