Skip to content

Remove number from label identifier list #12831

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 24, 2025
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { isDefined } from 'twenty-shared/utils';
import {
IconArchive,
IconDotsVertical,
Expand Down Expand Up @@ -65,14 +66,14 @@ export const SettingsObjectFieldActiveActionDropdown = ({
LeftIcon={isCustomField ? IconPencil : IconEye}
onClick={handleEdit}
/>
{!!onSetAsLabelIdentifier && (
{isDefined(onSetAsLabelIdentifier) && (
<MenuItem
text="Set as record text"
LeftIcon={IconTextSize}
onClick={handleSetAsLabelIdentifier}
/>
)}
{!!onDeactivate && (
{isDefined(onDeactivate) && (
<MenuItem
text="Deactivate"
LeftIcon={IconArchive}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { LABEL_IDENTIFIER_FIELD_METADATA_TYPES } from '@/object-metadata/constants/LabelIdentifierFieldMetadataTypes';
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
Expand All @@ -19,7 +18,10 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useMemo } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import {
isDefined,
isLabelIdentifierFieldMetadataTypes,
} from 'twenty-shared/utils';
import { IconMinus, IconPlus, useIcons } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { UndecoratedLink } from 'twenty-ui/navigation';
Expand Down Expand Up @@ -102,7 +104,7 @@ export const SettingsObjectFieldItemTableRow = ({
const canBeSetAsLabelIdentifier =
objectMetadataItem.isCustom &&
!isLabelIdentifier &&
LABEL_IDENTIFIER_FIELD_METADATA_TYPES.includes(fieldMetadataItem.type);
isLabelIdentifierFieldMetadataTypes(fieldMetadataItem.type);

const linkToNavigate = getSettingsPath(SettingsPath.ObjectFieldEdit, {
objectNamePlural: objectMetadataItem.namePlural,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { ZodError, isDirty, z } from 'zod';

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

Expand Down Expand Up @@ -84,7 +84,7 @@ export const SettingsDataModelObjectIdentifiersForm = ({
getActiveFieldMetadataItems(objectMetadataItem)
.filter(
({ id, type }) =>
LABEL_IDENTIFIER_FIELD_METADATA_TYPES.includes(type) ||
isLabelIdentifierFieldMetadataTypes(type) ||
objectMetadataItem.labelIdentifierFieldMetadataId === id,
)
.map<SelectOption<string | null>>((fieldMetadataItem) => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Field, InputType } from '@nestjs/graphql';

import { BeforeUpdateOne } from '@ptc-org/nestjs-query-graphql';
import { Type } from 'class-transformer';
import {
IsBoolean,
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
ValidateNested,
} from 'class-validator';

import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
Expand Down Expand Up @@ -76,6 +78,8 @@ export class UpdateObjectPayload {
@InputType()
@BeforeUpdateOne(BeforeUpdateOneObject)
export class UpdateOneObjectInput {
@Type(() => UpdateObjectPayload)
@ValidateNested()
@Field(() => UpdateObjectPayload)
update: UpdateObjectPayload;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '@ptc-org/nestjs-query-graphql';
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
import { isDefined } from 'twenty-shared/utils';
import { Equal, In, Repository } from 'typeorm';
import { Repository } from 'typeorm';

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

await this.validateIdentifierFields(instance, workspaceId);

return instance;
}

Expand Down Expand Up @@ -437,56 +435,4 @@ export class BeforeUpdateOneObject<T extends UpdateObjectPayload>
locale,
);
}

private async validateIdentifierFields(
instance: UpdateOneInputType<T>,
workspaceId: string,
): Promise<void> {
if (
!instance.update.labelIdentifierFieldMetadataId &&
!instance.update.imageIdentifierFieldMetadataId
) {
return;
}

const fields = await this.fieldMetadataRepository.findBy({
workspaceId: Equal(workspaceId),
objectMetadataId: Equal(instance.id.toString()),
id: In(
[
instance.update.labelIdentifierFieldMetadataId,
instance.update.imageIdentifierFieldMetadataId,
].filter((id) => id !== null),
),
});

const fieldIds = fields.map((field) => field.id);

this.validateLabelIdentifier(instance, fieldIds);
this.validateImageIdentifier(instance, fieldIds);
}

private validateLabelIdentifier(
instance: UpdateOneInputType<T>,
fieldIds: string[],
): void {
if (
instance.update.labelIdentifierFieldMetadataId &&
!fieldIds.includes(instance.update.labelIdentifierFieldMetadataId)
) {
throw new BadRequestException('This label identifier does not exist');
}
}

private validateImageIdentifier(
instance: UpdateOneInputType<T>,
fieldIds: string[],
): void {
if (
instance.update.imageIdentifierFieldMetadataId &&
!fieldIds.includes(instance.update.imageIdentifierFieldMetadataId)
) {
throw new BadRequestException('This image identifier does not exist');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service';
import { SearchVectorService } from 'src/engine/metadata-modules/search-vector/search-vector.service';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { validateMetadataIdentifierFieldMetadataIds } from 'src/engine/metadata-modules/utils/validate-metadata-identifier-field-metadata-id.utils';
import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
import { validatesNoOtherObjectWithSameNameExistsOrThrows } from 'src/engine/metadata-modules/utils/validate-no-other-object-with-same-name-exists-or-throw.util';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
Expand Down Expand Up @@ -300,6 +301,14 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
});
}

validateMetadataIdentifierFieldMetadataIds({
fieldMetadataItems: Object.values(existingObjectMetadata.fieldsById),
labelIdentifierFieldMetadataId:
inputPayload.labelIdentifierFieldMetadataId,
imageIdentifierFieldMetadataId:
inputPayload.imageIdentifierFieldMetadataId,
});

const updatedObject = await super.updateOne(inputId, inputPayload);

await this.handleObjectNameAndLabelUpdates(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {
isDefined,
isLabelIdentifierFieldMetadataTypes,
} from 'twenty-shared/utils';

import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';

import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import {
ObjectMetadataException,
ObjectMetadataExceptionCode,
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';

type Validator = {
validator: (args: {
fieldMetadataId: string;
matchingFieldMetadata?: FieldMetadataEntity | FieldMetadataInterface;
}) => boolean;
label: string;
};

type ValidateMetadataIdentifierFieldMetadataIdOrThrowArgs = {
fieldMetadataId: string;
fieldMetadataItems: FieldMetadataEntity[] | FieldMetadataInterface[];
validators: Validator[];
};
const validatorRunner = ({
fieldMetadataId,
fieldMetadataItems,
validators,
}: ValidateMetadataIdentifierFieldMetadataIdOrThrowArgs): void => {
const matchingFieldMetadata = fieldMetadataItems.find(
(fieldMetadata) => fieldMetadata.id === fieldMetadataId,
);

validators.forEach(({ label, validator }) => {
if (validator({ fieldMetadataId, matchingFieldMetadata })) {
throw new ObjectMetadataException(
label,
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
);
}
});
};

type ValidateMetadataIdentifierFieldMetadataIdsArgs = {
labelIdentifierFieldMetadataId: string | undefined;
imageIdentifierFieldMetadataId: string | undefined;
fieldMetadataItems: FieldMetadataEntity[] | FieldMetadataInterface[];
};
export const validateMetadataIdentifierFieldMetadataIds = ({
imageIdentifierFieldMetadataId,
labelIdentifierFieldMetadataId,
fieldMetadataItems,
}: ValidateMetadataIdentifierFieldMetadataIdsArgs) => {
const isMatchingFieldMetadataDefined: Validator['validator'] = ({
matchingFieldMetadata,
}) => !isDefined(matchingFieldMetadata);

if (isDefined(labelIdentifierFieldMetadataId)) {
validatorRunner({
fieldMetadataId: labelIdentifierFieldMetadataId,
fieldMetadataItems,
validators: [
{
validator: isMatchingFieldMetadataDefined,
label:
'labelIdentifierFieldMetadataId validation failed: related field metadata not found',
},
{
validator: ({ matchingFieldMetadata }) =>
isDefined(matchingFieldMetadata) &&
!isLabelIdentifierFieldMetadataTypes(matchingFieldMetadata.type),
label:
'labelIdentifierFieldMetadataId validation failed: it must be a TEXT or FULL_NAME field metadata type id',
},
],
});
}

if (isDefined(imageIdentifierFieldMetadataId)) {
validatorRunner({
fieldMetadataId: imageIdentifierFieldMetadataId,
fieldMetadataItems,
validators: [
{
validator: isMatchingFieldMetadataDefined,
label:
'imageIdentifierFieldMetadataId validation failed: related field metadata not found',
},
],
});
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Object metadata creation should fail when labelIdentifier is not a TEXT or NAME field 1`] = `
[
{
"extensions": {
"code": "BAD_USER_INPUT",
},
"message": "labelIdentifierFieldMetadataId validation failed: it must be a TEXT or FULL_NAME field metadata type id",
},
]
`;

exports[`Object metadata creation should fail when labelIdentifier is not a known field metadata id 1`] = `
[
{
"extensions": {
"code": "BAD_USER_INPUT",
},
"message": "labelIdentifierFieldMetadataId validation failed: related field metadata not found",
},
]
`;

exports[`Object metadata creation should fail when labelIdentifier is not a uuid 1`] = `
[
{
"extensions": {
"code": "BAD_USER_INPUT",
},
"message": "labelIdentifierFieldMetadataId must be a UUID",
},
]
`;
Loading
Loading