Skip to content

Commit 7c3de15

Browse files
prastoinabdulrahmancodes
authored andcommitted
Remove number from label identifier list (twentyhq#12831)
1 parent b3a66bc commit 7c3de15

File tree

14 files changed

+258
-120
lines changed

14 files changed

+258
-120
lines changed

packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent
33
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
44
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
55
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
6+
import { isDefined } from 'twenty-shared/utils';
67
import {
78
IconArchive,
89
IconDotsVertical,
@@ -65,14 +66,14 @@ export const SettingsObjectFieldActiveActionDropdown = ({
6566
LeftIcon={isCustomField ? IconPencil : IconEye}
6667
onClick={handleEdit}
6768
/>
68-
{!!onSetAsLabelIdentifier && (
69+
{isDefined(onSetAsLabelIdentifier) && (
6970
<MenuItem
7071
text="Set as record text"
7172
LeftIcon={IconTextSize}
7273
onClick={handleSetAsLabelIdentifier}
7374
/>
7475
)}
75-
{!!onDeactivate && (
76+
{isDefined(onDeactivate) && (
7677
<MenuItem
7778
text="Deactivate"
7879
LeftIcon={IconArchive}

packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { LABEL_IDENTIFIER_FIELD_METADATA_TYPES } from '@/object-metadata/constants/LabelIdentifierFieldMetadataTypes';
21
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
32
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
43
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
@@ -19,7 +18,10 @@ import { useTheme } from '@emotion/react';
1918
import styled from '@emotion/styled';
2019
import { useMemo } from 'react';
2120
import { useRecoilState, useRecoilValue } from 'recoil';
22-
import { isDefined } from 'twenty-shared/utils';
21+
import {
22+
isDefined,
23+
isLabelIdentifierFieldMetadataTypes,
24+
} from 'twenty-shared/utils';
2325
import { IconMinus, IconPlus, useIcons } from 'twenty-ui/display';
2426
import { LightIconButton } from 'twenty-ui/input';
2527
import { UndecoratedLink } from 'twenty-ui/navigation';
@@ -102,7 +104,7 @@ export const SettingsObjectFieldItemTableRow = ({
102104
const canBeSetAsLabelIdentifier =
103105
objectMetadataItem.isCustom &&
104106
!isLabelIdentifier &&
105-
LABEL_IDENTIFIER_FIELD_METADATA_TYPES.includes(fieldMetadataItem.type);
107+
isLabelIdentifierFieldMetadataTypes(fieldMetadataItem.type);
106108

107109
const linkToNavigate = getSettingsPath(SettingsPath.ObjectFieldEdit, {
108110
objectNamePlural: objectMetadataItem.namePlural,

packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectIdentifiersForm.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { useMemo } from 'react';
33
import { Controller, useForm } from 'react-hook-form';
44
import { ZodError, isDirty, z } from 'zod';
55

6-
import { LABEL_IDENTIFIER_FIELD_METADATA_TYPES } from '@/object-metadata/constants/LabelIdentifierFieldMetadataTypes';
76
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
87
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
98
import { getActiveFieldMetadataItems } from '@/object-metadata/utils/getActiveFieldMetadataItems';
@@ -14,6 +13,7 @@ import { Select } from '@/ui/input/components/Select';
1413
import { zodResolver } from '@hookform/resolvers/zod';
1514
import { t } from '@lingui/core/macro';
1615
import { useNavigate } from 'react-router-dom';
16+
import { isLabelIdentifierFieldMetadataTypes } from 'twenty-shared/utils';
1717
import { IconCircleOff, IconPlus, useIcons } from 'twenty-ui/display';
1818
import { SelectOption } from 'twenty-ui/input';
1919

@@ -84,7 +84,7 @@ export const SettingsDataModelObjectIdentifiersForm = ({
8484
getActiveFieldMetadataItems(objectMetadataItem)
8585
.filter(
8686
({ id, type }) =>
87-
LABEL_IDENTIFIER_FIELD_METADATA_TYPES.includes(type) ||
87+
isLabelIdentifierFieldMetadataTypes(type) ||
8888
objectMetadataItem.labelIdentifierFieldMetadataId === id,
8989
)
9090
.map<SelectOption<string | null>>((fieldMetadataItem) => ({

packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/update-object.input.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { Field, InputType } from '@nestjs/graphql';
22

33
import { BeforeUpdateOne } from '@ptc-org/nestjs-query-graphql';
4+
import { Type } from 'class-transformer';
45
import {
56
IsBoolean,
67
IsNotEmpty,
78
IsOptional,
89
IsString,
910
IsUUID,
11+
ValidateNested,
1012
} from 'class-validator';
1113

1214
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@@ -76,6 +78,8 @@ export class UpdateObjectPayload {
7678
@InputType()
7779
@BeforeUpdateOne(BeforeUpdateOneObject)
7880
export class UpdateOneObjectInput {
81+
@Type(() => UpdateObjectPayload)
82+
@ValidateNested()
7983
@Field(() => UpdateObjectPayload)
8084
update: UpdateObjectPayload;
8185

packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/__tests__/before-update-one-object.hook.spec.ts

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -656,34 +656,6 @@ describe('BeforeUpdateOneObject', () => {
656656
expect(result).toEqual(instance);
657657
});
658658

659-
it('should throw BadRequestException if label identifier field does not exist', async () => {
660-
const labelIdentifierFieldId = 'nonexistent-field-id';
661-
const instance: UpdateOneInputType<UpdateObjectPayloadForTest> = {
662-
id: mockObjectId,
663-
update: {
664-
labelIdentifierFieldMetadataId: labelIdentifierFieldId,
665-
},
666-
};
667-
668-
const mockObject: Partial<ObjectMetadataEntity> = {
669-
id: mockObjectId,
670-
isCustom: true,
671-
};
672-
673-
jest
674-
.spyOn(objectMetadataService, 'findOneWithinWorkspace')
675-
.mockResolvedValue(mockObject as ObjectMetadataEntity);
676-
677-
jest.spyOn(fieldMetadataRepository, 'findBy').mockResolvedValue([]);
678-
679-
await expect(
680-
hook.run(instance as UpdateOneInputType<UpdateObjectPayload>, {
681-
workspaceId: mockWorkspaceId,
682-
locale: undefined,
683-
}),
684-
).rejects.toThrow('This label identifier does not exist');
685-
});
686-
687659
it('should validate image identifier field correctly for custom objects', async () => {
688660
const imageIdentifierFieldId = 'image-field-id';
689661
const instance: UpdateOneInputType<UpdateObjectPayloadForTest> = {
@@ -722,32 +694,4 @@ describe('BeforeUpdateOneObject', () => {
722694

723695
expect(result).toEqual(instance);
724696
});
725-
726-
it('should throw BadRequestException if image identifier field does not exist', async () => {
727-
const imageIdentifierFieldId = 'nonexistent-field-id';
728-
const instance: UpdateOneInputType<UpdateObjectPayloadForTest> = {
729-
id: mockObjectId,
730-
update: {
731-
imageIdentifierFieldMetadataId: imageIdentifierFieldId,
732-
},
733-
};
734-
735-
const mockObject: Partial<ObjectMetadataEntity> = {
736-
id: mockObjectId,
737-
isCustom: true,
738-
};
739-
740-
jest
741-
.spyOn(objectMetadataService, 'findOneWithinWorkspace')
742-
.mockResolvedValue(mockObject as ObjectMetadataEntity);
743-
744-
jest.spyOn(fieldMetadataRepository, 'findBy').mockResolvedValue([]);
745-
746-
await expect(
747-
hook.run(instance as UpdateOneInputType<UpdateObjectPayload>, {
748-
workspaceId: mockWorkspaceId,
749-
locale: undefined,
750-
}),
751-
).rejects.toThrow('This image identifier does not exist');
752-
});
753697
});

packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts

Lines changed: 1 addition & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
} from '@ptc-org/nestjs-query-graphql';
1313
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
1414
import { isDefined } from 'twenty-shared/utils';
15-
import { Equal, In, Repository } from 'typeorm';
15+
import { Repository } from 'typeorm';
1616

1717
import { generateMessageId } from 'src/engine/core-modules/i18n/utils/generateMessageId';
1818
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
@@ -57,8 +57,6 @@ export class BeforeUpdateOneObject<T extends UpdateObjectPayload>
5757
return this.handleStandardObjectUpdate(instance, objectMetadata, locale);
5858
}
5959

60-
await this.validateIdentifierFields(instance, workspaceId);
61-
6260
return instance;
6361
}
6462

@@ -437,56 +435,4 @@ export class BeforeUpdateOneObject<T extends UpdateObjectPayload>
437435
locale,
438436
);
439437
}
440-
441-
private async validateIdentifierFields(
442-
instance: UpdateOneInputType<T>,
443-
workspaceId: string,
444-
): Promise<void> {
445-
if (
446-
!instance.update.labelIdentifierFieldMetadataId &&
447-
!instance.update.imageIdentifierFieldMetadataId
448-
) {
449-
return;
450-
}
451-
452-
const fields = await this.fieldMetadataRepository.findBy({
453-
workspaceId: Equal(workspaceId),
454-
objectMetadataId: Equal(instance.id.toString()),
455-
id: In(
456-
[
457-
instance.update.labelIdentifierFieldMetadataId,
458-
instance.update.imageIdentifierFieldMetadataId,
459-
].filter((id) => id !== null),
460-
),
461-
});
462-
463-
const fieldIds = fields.map((field) => field.id);
464-
465-
this.validateLabelIdentifier(instance, fieldIds);
466-
this.validateImageIdentifier(instance, fieldIds);
467-
}
468-
469-
private validateLabelIdentifier(
470-
instance: UpdateOneInputType<T>,
471-
fieldIds: string[],
472-
): void {
473-
if (
474-
instance.update.labelIdentifierFieldMetadataId &&
475-
!fieldIds.includes(instance.update.labelIdentifierFieldMetadataId)
476-
) {
477-
throw new BadRequestException('This label identifier does not exist');
478-
}
479-
}
480-
481-
private validateImageIdentifier(
482-
instance: UpdateOneInputType<T>,
483-
fieldIds: string[],
484-
): void {
485-
if (
486-
instance.update.imageIdentifierFieldMetadataId &&
487-
!fieldIds.includes(instance.update.imageIdentifierFieldMetadataId)
488-
) {
489-
throw new BadRequestException('This image identifier does not exist');
490-
}
491-
}
492438
}

packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service';
3535
import { SearchVectorService } from 'src/engine/metadata-modules/search-vector/search-vector.service';
3636
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
37+
import { validateMetadataIdentifierFieldMetadataIds } from 'src/engine/metadata-modules/utils/validate-metadata-identifier-field-metadata-id.utils';
3738
import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
3839
import { validatesNoOtherObjectWithSameNameExistsOrThrows } from 'src/engine/metadata-modules/utils/validate-no-other-object-with-same-name-exists-or-throw.util';
3940
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
@@ -300,6 +301,14 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
300301
});
301302
}
302303

304+
validateMetadataIdentifierFieldMetadataIds({
305+
fieldMetadataItems: Object.values(existingObjectMetadata.fieldsById),
306+
labelIdentifierFieldMetadataId:
307+
inputPayload.labelIdentifierFieldMetadataId,
308+
imageIdentifierFieldMetadataId:
309+
inputPayload.imageIdentifierFieldMetadataId,
310+
});
311+
303312
const updatedObject = await super.updateOne(inputId, inputPayload);
304313

305314
await this.handleObjectNameAndLabelUpdates(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import {
2+
isDefined,
3+
isLabelIdentifierFieldMetadataTypes,
4+
} from 'twenty-shared/utils';
5+
6+
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
7+
8+
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
9+
import {
10+
ObjectMetadataException,
11+
ObjectMetadataExceptionCode,
12+
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
13+
14+
type Validator = {
15+
validator: (args: {
16+
fieldMetadataId: string;
17+
matchingFieldMetadata?: FieldMetadataEntity | FieldMetadataInterface;
18+
}) => boolean;
19+
label: string;
20+
};
21+
22+
type ValidateMetadataIdentifierFieldMetadataIdOrThrowArgs = {
23+
fieldMetadataId: string;
24+
fieldMetadataItems: FieldMetadataEntity[] | FieldMetadataInterface[];
25+
validators: Validator[];
26+
};
27+
const validatorRunner = ({
28+
fieldMetadataId,
29+
fieldMetadataItems,
30+
validators,
31+
}: ValidateMetadataIdentifierFieldMetadataIdOrThrowArgs): void => {
32+
const matchingFieldMetadata = fieldMetadataItems.find(
33+
(fieldMetadata) => fieldMetadata.id === fieldMetadataId,
34+
);
35+
36+
validators.forEach(({ label, validator }) => {
37+
if (validator({ fieldMetadataId, matchingFieldMetadata })) {
38+
throw new ObjectMetadataException(
39+
label,
40+
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
41+
);
42+
}
43+
});
44+
};
45+
46+
type ValidateMetadataIdentifierFieldMetadataIdsArgs = {
47+
labelIdentifierFieldMetadataId: string | undefined;
48+
imageIdentifierFieldMetadataId: string | undefined;
49+
fieldMetadataItems: FieldMetadataEntity[] | FieldMetadataInterface[];
50+
};
51+
export const validateMetadataIdentifierFieldMetadataIds = ({
52+
imageIdentifierFieldMetadataId,
53+
labelIdentifierFieldMetadataId,
54+
fieldMetadataItems,
55+
}: ValidateMetadataIdentifierFieldMetadataIdsArgs) => {
56+
const isMatchingFieldMetadataDefined: Validator['validator'] = ({
57+
matchingFieldMetadata,
58+
}) => !isDefined(matchingFieldMetadata);
59+
60+
if (isDefined(labelIdentifierFieldMetadataId)) {
61+
validatorRunner({
62+
fieldMetadataId: labelIdentifierFieldMetadataId,
63+
fieldMetadataItems,
64+
validators: [
65+
{
66+
validator: isMatchingFieldMetadataDefined,
67+
label:
68+
'labelIdentifierFieldMetadataId validation failed: related field metadata not found',
69+
},
70+
{
71+
validator: ({ matchingFieldMetadata }) =>
72+
isDefined(matchingFieldMetadata) &&
73+
!isLabelIdentifierFieldMetadataTypes(matchingFieldMetadata.type),
74+
label:
75+
'labelIdentifierFieldMetadataId validation failed: it must be a TEXT or FULL_NAME field metadata type id',
76+
},
77+
],
78+
});
79+
}
80+
81+
if (isDefined(imageIdentifierFieldMetadataId)) {
82+
validatorRunner({
83+
fieldMetadataId: imageIdentifierFieldMetadataId,
84+
fieldMetadataItems,
85+
validators: [
86+
{
87+
validator: isMatchingFieldMetadataDefined,
88+
label:
89+
'imageIdentifierFieldMetadataId validation failed: related field metadata not found',
90+
},
91+
],
92+
});
93+
}
94+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Object metadata update should fail when labelIdentifier is not a TEXT or NAME field 1`] = `
4+
[
5+
{
6+
"extensions": {
7+
"code": "BAD_USER_INPUT",
8+
},
9+
"message": "labelIdentifierFieldMetadataId validation failed: it must be a TEXT or FULL_NAME field metadata type id",
10+
},
11+
]
12+
`;
13+
14+
exports[`Object metadata update should fail when labelIdentifier is not a known field metadata id 1`] = `
15+
[
16+
{
17+
"extensions": {
18+
"code": "BAD_USER_INPUT",
19+
},
20+
"message": "labelIdentifierFieldMetadataId validation failed: related field metadata not found",
21+
},
22+
]
23+
`;
24+
25+
exports[`Object metadata update should fail when labelIdentifier is not a uuid 1`] = `
26+
[
27+
{
28+
"extensions": {
29+
"code": "BAD_USER_INPUT",
30+
},
31+
"message": "labelIdentifierFieldMetadataId must be a UUID",
32+
},
33+
]
34+
`;

0 commit comments

Comments
 (0)