fieldmetadatatype + featurelfag creation (#13021)

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Guillim
2025-07-08 12:23:28 +02:00
committed by GitHub
parent 56607c0449
commit a5deddaffd
47 changed files with 1447 additions and 793 deletions
@@ -1,15 +1,30 @@
import { Test, TestingModule } from '@nestjs/testing';
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import { FieldMetadataValidationService } from 'src/engine/metadata-modules/field-metadata/field-metadata-validation.service';
import { FieldMetadataException } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { FieldMetadataEnumValidationService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-enum-validation.service';
import { FieldMetadataValidationService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-validation.service';
describe('FieldMetadataValidationService', () => {
let service: FieldMetadataValidationService;
beforeAll(() => {
service = new FieldMetadataValidationService();
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
FieldMetadataValidationService,
{
provide: FieldMetadataEnumValidationService,
useValue: {},
},
],
}).compile();
service = module.get<FieldMetadataValidationService>(
FieldMetadataValidationService,
);
});
it('should validate NUMBER settings successfully', async () => {
@@ -0,0 +1,42 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { FieldStandardOverridesDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-standard-overrides.dto';
import { UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
export const buildUpdatableStandardFieldInput = (
fieldMetadataInput: UpdateFieldInput,
existingFieldMetadata: Pick<
FieldMetadataInterface,
'type' | 'isNullable' | 'defaultValue' | 'options'
>,
) => {
const updatableStandardFieldInput: UpdateFieldInput & {
standardOverrides?: FieldStandardOverridesDTO;
} = {
id: fieldMetadataInput.id,
isActive: fieldMetadataInput.isActive,
workspaceId: fieldMetadataInput.workspaceId,
defaultValue: fieldMetadataInput.defaultValue,
settings: fieldMetadataInput.settings,
isLabelSyncedWithName: fieldMetadataInput.isLabelSyncedWithName,
};
if ('standardOverrides' in fieldMetadataInput) {
updatableStandardFieldInput.standardOverrides =
fieldMetadataInput.standardOverrides as FieldStandardOverridesDTO;
}
if (
existingFieldMetadata.type === FieldMetadataType.SELECT ||
existingFieldMetadata.type === FieldMetadataType.MULTI_SELECT
) {
return {
...updatableStandardFieldInput,
options: fieldMetadataInput.options,
};
}
return updatableStandardFieldInput;
};
@@ -0,0 +1,23 @@
import { v4 } from 'uuid';
import {
FieldMetadataComplexOption,
FieldMetadataDefaultOption,
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties';
export const prepareCustomFieldMetadataOptions = (
options: FieldMetadataDefaultOption[] | FieldMetadataComplexOption[],
): undefined | Pick<FieldMetadataEntity, 'options'> => {
return {
options: options.map((option) => ({
id: v4(),
...trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(option, [
'label',
'value',
'id',
]),
})),
};
};
@@ -0,0 +1,42 @@
import { v4 } from 'uuid';
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { generateDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/generate-default-value';
import { generateNullable } from 'src/engine/metadata-modules/field-metadata/utils/generate-nullable';
import { prepareCustomFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/utils/prepare-custom-field-metadata-for-options.util';
export const prepareCustomFieldMetadataForCreation = (
fieldMetadataInput: CreateFieldInput,
) => {
const options = fieldMetadataInput.options
? prepareCustomFieldMetadataOptions(fieldMetadataInput.options)
: undefined;
const defaultValue =
fieldMetadataInput.defaultValue ??
generateDefaultValue(fieldMetadataInput.type);
return {
id: v4(),
createdAt: new Date(),
updatedAt: new Date(),
name: fieldMetadataInput.name,
label: fieldMetadataInput.label,
icon: fieldMetadataInput.icon,
type: fieldMetadataInput.type,
isLabelSyncedWithName: fieldMetadataInput.isLabelSyncedWithName,
objectMetadataId: fieldMetadataInput.objectMetadataId,
workspaceId: fieldMetadataInput.workspaceId,
isNullable: generateNullable(
fieldMetadataInput.type,
fieldMetadataInput.isNullable,
fieldMetadataInput.isRemoteCreation,
),
relationTargetObjectMetadataId:
fieldMetadataInput?.relationCreationPayload?.targetObjectMetadataId,
defaultValue,
...options,
isActive: true,
isCustom: true,
settings: fieldMetadataInput.settings,
};
};
@@ -0,0 +1,36 @@
import { i18n } from '@lingui/core';
import { isDefined } from 'class-validator';
import { APP_LOCALES } from 'twenty-shared/translations';
import { generateMessageId } from 'src/engine/core-modules/i18n/utils/generateMessageId';
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
export const resolveOverridableString = (
fieldMetadata: Pick<
FieldMetadataDTO,
'label' | 'description' | 'icon' | 'isCustom' | 'standardOverrides'
>,
labelKey: 'label' | 'description' | 'icon',
locale: keyof typeof APP_LOCALES | undefined,
): string => {
if (fieldMetadata.isCustom) {
return fieldMetadata[labelKey] ?? '';
}
const translationValue =
// @ts-expect-error legacy noImplicitAny
fieldMetadata.standardOverrides?.translations?.[locale]?.[labelKey];
if (isDefined(translationValue)) {
return translationValue;
}
const messageId = generateMessageId(fieldMetadata[labelKey] ?? '');
const translatedMessage = i18n._(messageId);
if (translatedMessage === messageId) {
return fieldMetadata[labelKey] ?? '';
}
return translatedMessage;
};