From 6291ca5b178d1ccff9758d69377d43b98eb433b6 Mon Sep 17 00:00:00 2001 From: guillim Date: Tue, 19 Nov 2024 14:53:23 +0100 Subject: [PATCH 1/8] Adding the DefaultCountry in the backend --- .../field-metadata-validation.service.ts | 22 ++++- .../field-metadata-settings.interface.ts | 5 ++ .../field-metadata-validation.service.spec.ts | 81 +++++++++++++++++++ 3 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/field-metadata-validation.service.spec.ts diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts index 5eec57d2afb1..263e6f81c463 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts @@ -5,6 +5,7 @@ import { IsEnum, IsInt, IsOptional, + IsString, Max, Min, validateOrReject, @@ -23,7 +24,7 @@ enum ValueType { NUMBER = 'number', } -class SettingsValidation { +class NumberSettingsValidation { @IsOptional() @IsInt() @Min(0) @@ -32,7 +33,9 @@ class SettingsValidation { @IsOptional() @IsEnum(ValueType) type?: 'percentage' | 'number'; +} +class TextSettingsValidation { @IsOptional() @IsInt() @Min(0) @@ -40,6 +43,12 @@ class SettingsValidation { displayedMaxRows?: number; } +class AddressSettingsValidation { + @IsOptional() + @IsString() + defaultCountry?: string; +} + @Injectable() export class FieldMetadataValidationService< T extends FieldMetadataType | 'default' = 'default', @@ -55,17 +64,22 @@ export class FieldMetadataValidationService< }) { switch (fieldType) { case FieldMetadataType.NUMBER: + await this.validateSettings(NumberSettingsValidation, settings); + break; case FieldMetadataType.TEXT: - await this.validateSettings(settings); + await this.validateSettings(TextSettingsValidation, settings); + break; + case FieldMetadataType.ADDRESS: + await this.validateSettings(AddressSettingsValidation, settings); break; default: break; } } - private async validateSettings(settings: any) { + private async validateSettings(validator: any, settings: any) { try { - const settingsInstance = plainToInstance(SettingsValidation, settings); + const settingsInstance = plainToInstance(validator, settings); await validateOrReject(settingsInstance); } catch (error) { diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts index 6afede943d6a..6f373695013c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts @@ -28,11 +28,16 @@ type FieldMetadataDateTimeSettings = { displayAsRelativeDate?: boolean; }; +type FieldMetadataAddressSettings = { + defaultCountry?: string; +}; + type FieldMetadataSettingsMapping = { [FieldMetadataType.NUMBER]: FieldMetadataNumberSettings; [FieldMetadataType.DATE]: FieldMetadataDateSettings; [FieldMetadataType.DATE_TIME]: FieldMetadataDateTimeSettings; [FieldMetadataType.TEXT]: FieldMetadataTextSettings; + [FieldMetadataType.ADDRESS]: FieldMetadataAddressSettings; }; type SettingsByFieldMetadata = diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/field-metadata-validation.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/field-metadata-validation.service.spec.ts new file mode 100644 index 000000000000..1b896f5577eb --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/field-metadata-validation.service.spec.ts @@ -0,0 +1,81 @@ +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 { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { FieldMetadataException } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; + +describe('FieldMetadataValidationService', () => { + let service: FieldMetadataValidationService; + + beforeAll(() => { + service = new FieldMetadataValidationService(); + }); + + it('should validate NUMBER settings successfully', async () => { + const settings = { decimals: 2, type: 'number' } as FieldMetadataSettings; + + await expect( + service.validateSettingsOrThrow({ + fieldType: FieldMetadataType.NUMBER, + settings, + }), + ).resolves.not.toThrow(); + }); + + it('should throw an error for invalid NUMBER settings', async () => { + const settings = { type: 'invalidType' } as FieldMetadataSettings; + + await expect( + service.validateSettingsOrThrow({ + fieldType: FieldMetadataType.NUMBER, + settings, + }), + ).rejects.toThrow(FieldMetadataException); + }); + + it('should validate TEXT settings successfully', async () => { + const settings = { displayedMaxRows: 10 } as FieldMetadataSettings; + + await expect( + service.validateSettingsOrThrow({ + fieldType: FieldMetadataType.TEXT, + settings, + }), + ).resolves.not.toThrow(); + }); + + it('should throw an error for invalid TEXT settings', async () => { + const settings = { + displayedMaxRows: 'NotANumber', + } as FieldMetadataSettings; + + await expect( + service.validateSettingsOrThrow({ + fieldType: FieldMetadataType.TEXT, + settings, + }), + ).rejects.toThrow(FieldMetadataException); + }); + + it('should validate ADDRESS settings successfully', async () => { + const settings = { defaultCountry: 'France' } as FieldMetadataSettings; + + await expect( + service.validateSettingsOrThrow({ + fieldType: FieldMetadataType.ADDRESS, + settings, + }), + ).resolves.not.toThrow(); + }); + + it('should throw an error for invalid ADDRESS settings', async () => { + const settings = { defaultCountry: 123 } as FieldMetadataSettings; + + await expect( + service.validateSettingsOrThrow({ + fieldType: FieldMetadataType.ADDRESS, + settings, + }), + ).rejects.toThrow(FieldMetadataException); + }); +}); From 13527067854d2f3870867901654f302e1cd85e87 Mon Sep 17 00:00:00 2001 From: guillim Date: Tue, 19 Nov 2024 17:42:29 +0100 Subject: [PATCH 2/8] adding the frontend settings option to select countries by default --- .../SettingsDataModelFieldAddressForm.tsx | 76 +++++++++++++++++++ ...sDataModelFieldAddressSettingsFormCard.tsx | 45 +++++++++++ ...SettingsDataModelFieldSettingsFormCard.tsx | 18 +++++ 3 files changed, 139 insertions(+) create mode 100644 packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx create mode 100644 packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressSettingsFormCard.tsx diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx new file mode 100644 index 000000000000..e74cd9a74636 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx @@ -0,0 +1,76 @@ +import { Controller, useFormContext } from 'react-hook-form'; + +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { useCountries } from '@/ui/input/components/internal/hooks/useCountries'; +import { Select } from '@/ui/input/components/Select'; +import styled from '@emotion/styled'; +import { CardContent } from 'twenty-ui'; +import { z } from 'zod'; + +const StyledFormCardTitle = styled.div` + color: ${({ theme }) => theme.font.color.light}; + font-size: ${({ theme }) => theme.font.size.xs}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + margin-bottom: ${({ theme }) => theme.spacing(1)}; +`; + +type SettingsDataModelFieldAddressFormProps = { + disabled?: boolean; + defaultCountry?: string; + fieldMetadataItem: Pick< + FieldMetadataItem, + 'icon' | 'label' | 'type' | 'defaultValue' | 'settings' + >; +}; + +export const addressFieldDefaultValueSchema = z.object({ + defaultCountry: z.string().nullable(), +}); + +export const settingsDataModelFieldAddressFormSchema = z.object({ + settings: addressFieldDefaultValueSchema, +}); + +export type SettingsDataModelFieldTextFormValues = z.infer< + typeof settingsDataModelFieldAddressFormSchema +>; + +export const SettingsDataModelFieldAddressForm = ({ + disabled, + fieldMetadataItem, +}: SettingsDataModelFieldAddressFormProps) => { + const { control } = useFormContext(); + const countries = useCountries().map(country => ({ + label: country.countryName, + value: country.countryName + })) + return ( + + { + const defaultCountry = value?.defaultCountry ?? 0; + + return ( + <> + Default Country + onChange({ defaultCountry: value })} - withSearchInput={true} - dropdownWidthAuto={true} - /> + onChange({ defaultCountry: value })} + disabled={disabled} + options={countries} + /> ); }} /> - ); }; From a454623923aac192d8c59ecb5e46d1784ecf283b Mon Sep 17 00:00:00 2001 From: guillim Date: Wed, 20 Nov 2024 17:05:57 +0100 Subject: [PATCH 4/8] Default country in the input --- .../utils/computeDraftValueFromFieldValue.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts index 47a404873b14..3625d46d7084 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts @@ -1,6 +1,7 @@ import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress'; import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency'; import { isFieldCurrencyValue } from '@/object-record/record-field/types/guards/isFieldCurrencyValue'; import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber'; @@ -42,6 +43,20 @@ export const computeDraftValueFromFieldValue = ({ } as unknown as FieldInputDraftValue; } + if(isFieldAddress(fieldDefinition)){ + if( + isFieldValueEmpty({ fieldValue, fieldDefinition }) + && fieldDefinition.metadata.settings?.defaultCountry + ){ + return { + ...fieldValue, + addressCountry: fieldDefinition.metadata.settings.defaultCountry + } as unknown as FieldInputDraftValue; + } + + return fieldValue as FieldInputDraftValue; + } + if ( isFieldNumber(fieldDefinition) && isFieldNumberValue(fieldValue) && From 5ab4f71bc6ce6ae6debb7364778ed605cdeedfc1 Mon Sep 17 00:00:00 2001 From: guillim Date: Wed, 20 Nov 2024 17:34:05 +0100 Subject: [PATCH 5/8] fix --- .../country/components/CountrySelect.tsx | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/country/components/CountrySelect.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/country/components/CountrySelect.tsx index 376f738d1f2e..ce9861e9261f 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/country/components/CountrySelect.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/country/components/CountrySelect.tsx @@ -1,5 +1,5 @@ import { useMemo } from 'react'; -import { IconComponentProps } from 'twenty-ui'; +import { IconCircleOff, IconComponentProps } from 'twenty-ui'; import { SELECT_COUNTRY_DROPDOWN_ID } from '@/ui/input/components/internal/country/constants/SelectCountryDropdownId'; import { useCountries } from '@/ui/input/components/internal/hooks/useCountries'; @@ -15,12 +15,20 @@ export const CountrySelect = ({ const countries = useCountries(); const options: SelectOption[] = useMemo(() => { - return countries.map>(({ countryName, Flag }) => ({ - label: countryName, - value: countryName, - Icon: (props: IconComponentProps) => - Flag({ width: props.size, height: props.size }), // TODO : improve this ? - })); + const countryList = countries.map>( + ({ countryName, Flag }) => ({ + label: countryName, + value: countryName, + Icon: (props: IconComponentProps) => + Flag({ width: props.size, height: props.size }), // TODO : improve this ? + }), + ); + countryList.unshift({ + label: 'No country', + value: '', + Icon: IconCircleOff, + }); + return countryList; }, [countries]); return ( From c89a07a20e520e102891b5c639d55a262dc46d9b Mon Sep 17 00:00:00 2001 From: guillim Date: Wed, 20 Nov 2024 18:03:35 +0100 Subject: [PATCH 6/8] lint --- .../utils/computeDraftValueFromFieldValue.ts | 14 +++---- .../SettingsDataModelFieldAddressForm.tsx | 37 ++++++++++--------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts index 3625d46d7084..40323ab5e9bd 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts @@ -43,14 +43,14 @@ export const computeDraftValueFromFieldValue = ({ } as unknown as FieldInputDraftValue; } - if(isFieldAddress(fieldDefinition)){ - if( - isFieldValueEmpty({ fieldValue, fieldDefinition }) - && fieldDefinition.metadata.settings?.defaultCountry - ){ + if (isFieldAddress(fieldDefinition)) { + if ( + isFieldValueEmpty({ fieldValue, fieldDefinition }) && + !!fieldDefinition.metadata.settings?.defaultCountry + ) { return { - ...fieldValue, - addressCountry: fieldDefinition.metadata.settings.defaultCountry + ...fieldValue, + addressCountry: fieldDefinition.metadata.settings.defaultCountry, } as unknown as FieldInputDraftValue; } diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx index ea4a3ba5af55..69dec74d9301 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx @@ -40,23 +40,24 @@ export const SettingsDataModelFieldAddressForm = ({ fieldMetadataItem, }: SettingsDataModelFieldAddressFormProps) => { const { control } = useFormContext(); - const countries = useCountries().map(country => ({ + const countries = useCountries().map((country) => ({ label: country.countryName, - value: country.countryName - })) + value: country.countryName, + })); return ( - { - const defaultCountry = value?.defaultCountry ?? 0; + { + const defaultCountry = value?.defaultCountry ?? 0; - return ( - <> - + - - ); - }} - /> + + ); + }} + /> ); }; From a7739d1ccef9876e9c5c5564b210c70ac88c5d10 Mon Sep 17 00:00:00 2001 From: guillim Date: Wed, 20 Nov 2024 18:08:19 +0100 Subject: [PATCH 7/8] lint --- .../components/SettingsDataModelFieldAddressForm.tsx | 8 -------- .../components/SettingsDataModelFieldSettingsFormCard.tsx | 1 - 2 files changed, 9 deletions(-) diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx index 69dec74d9301..6799be893faa 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx @@ -3,17 +3,9 @@ import { Controller, useFormContext } from 'react-hook-form'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect'; import { useCountries } from '@/ui/input/components/internal/hooks/useCountries'; -import styled from '@emotion/styled'; import { IconMap } from 'twenty-ui'; import { z } from 'zod'; -const StyledFormCardTitle = styled.div` - color: ${({ theme }) => theme.font.color.light}; - font-size: ${({ theme }) => theme.font.size.xs}; - font-weight: ${({ theme }) => theme.font.weight.semiBold}; - margin-bottom: ${({ theme }) => theme.spacing(1)}; -`; - type SettingsDataModelFieldAddressFormProps = { disabled?: boolean; defaultCountry?: string; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx index 4feda0db8f13..14a7a65e02d7 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx @@ -209,7 +209,6 @@ export const SettingsDataModelFieldSettingsFormCard = ({ } if (fieldMetadataItem.type === FieldMetadataType.Address) { - console.log('fieldMetadataItem', fieldMetadataItem); return ( Date: Thu, 21 Nov 2024 17:16:49 +0100 Subject: [PATCH 8/8] moving towards defaultValue --- .../utils/formatFieldMetadataItemInput.ts | 3 +- .../types/guards/isFieldAddressValue.ts | 2 +- .../utils/computeDraftValueFromFieldValue.ts | 4 +- .../SettingsDataModelFieldAddressForm.tsx | 36 ++++++++---- .../compute-metadata-defaultValue-utils.ts | 57 +++++++++++++++++++ .../field-metadata-validation.service.ts | 10 ---- .../field-metadata-settings.interface.ts | 5 -- .../field-metadata-validation.service.spec.ts | 22 ------- 8 files changed, 87 insertions(+), 52 deletions(-) create mode 100644 packages/twenty-front/src/pages/settings/data-model/utils/compute-metadata-defaultValue-utils.ts diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts index bfcca728d674..816f95219f40 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts @@ -1,4 +1,5 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { computeMetadataDefaultValue } from '~/pages/settings/data-model/utils/compute-metadata-defaultValue-utils'; import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils'; export const formatFieldMetadataItemInput = ( @@ -18,7 +19,7 @@ export const formatFieldMetadataItemInput = ( const label = input.label?.trim(); return { - defaultValue: input.defaultValue, + defaultValue: computeMetadataDefaultValue(input.defaultValue), description: input.description?.trim() ?? null, icon: input.icon, label, diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldAddressValue.ts b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldAddressValue.ts index 8bc33766e803..1524f4bd2d74 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldAddressValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldAddressValue.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { FieldAddressValue } from '../FieldMetadata'; -const addressSchema = z.object({ +export const addressSchema = z.object({ addressStreet1: z.string(), addressStreet2: z.string().nullable(), addressCity: z.string().nullable(), diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts index 40323ab5e9bd..5e080f05fa45 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts @@ -46,11 +46,11 @@ export const computeDraftValueFromFieldValue = ({ if (isFieldAddress(fieldDefinition)) { if ( isFieldValueEmpty({ fieldValue, fieldDefinition }) && - !!fieldDefinition.metadata.settings?.defaultCountry + !!fieldDefinition?.defaultValue?.addressCountry ) { return { ...fieldValue, - addressCountry: fieldDefinition.metadata.settings.defaultCountry, + addressCountry: fieldDefinition?.defaultValue?.addressCountry, } as unknown as FieldInputDraftValue; } diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx index 6799be893faa..6591a9dc3b35 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm.tsx @@ -1,10 +1,12 @@ import { Controller, useFormContext } from 'react-hook-form'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { addressSchema as addressFieldDefaultValueSchema } from '@/object-record/record-field/types/guards/isFieldAddressValue'; import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect'; import { useCountries } from '@/ui/input/components/internal/hooks/useCountries'; import { IconMap } from 'twenty-ui'; import { z } from 'zod'; +import { removeSingleQuotesFromStrings } from '~/pages/settings/data-model/utils/compute-metadata-defaultValue-utils'; type SettingsDataModelFieldAddressFormProps = { disabled?: boolean; @@ -15,12 +17,8 @@ type SettingsDataModelFieldAddressFormProps = { >; }; -export const addressFieldDefaultValueSchema = z.object({ - defaultCountry: z.string().nullable(), -}); - export const settingsDataModelFieldAddressFormSchema = z.object({ - settings: addressFieldDefaultValueSchema, + defaultValue: addressFieldDefaultValueSchema, }); export type SettingsDataModelFieldTextFormValues = z.infer< @@ -36,17 +34,31 @@ export const SettingsDataModelFieldAddressForm = ({ label: country.countryName, value: country.countryName, })); + countries.unshift({ label: 'No country', value: '' }); + const defaultValueInstance = { + addressStreet1: '', + addressStreet2: null, + addressCity: null, + addressState: null, + addressPostcode: null, + addressCountry: null, + addressLat: null, + addressLng: null, + }; + const fieldMetadataItemDefaultValue = fieldMetadataItem?.defaultValue + ? removeSingleQuotesFromStrings(fieldMetadataItem?.defaultValue) + : fieldMetadataItem?.defaultValue; + return ( { - const defaultCountry = value?.defaultCountry ?? 0; - + const defaultCountry = value?.addressCountry || ''; return ( <> onChange({ defaultCountry: value })} + onChange={(newCountry) => + onChange({ ...value, addressCountry: newCountry }) + } disabled={disabled} options={countries} /> diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/compute-metadata-defaultValue-utils.ts b/packages/twenty-front/src/pages/settings/data-model/utils/compute-metadata-defaultValue-utils.ts new file mode 100644 index 000000000000..4d0e5927dc64 --- /dev/null +++ b/packages/twenty-front/src/pages/settings/data-model/utils/compute-metadata-defaultValue-utils.ts @@ -0,0 +1,57 @@ +export const computeMetadataDefaultValue = (input: any): any => { + if (typeof input !== 'object') { + throw new Error('Input type for DefaultValue is not handled yet'); + } + return addSingleQuotesToStrings(input); +}; + +export const addSingleQuotesToStrings = (obj: any): any => { + if (typeof obj === 'string') { + if (obj === '') { + return "''"; + } + if (obj === "''") { + return "''"; + } + + obj = "'" + obj + "'"; + + if (obj.startsWith("''") === true) { + obj = obj.slice(1); + } + if (obj.endsWith("''") === true) { + obj = obj.slice(1); + } + return obj; + } else if (Array.isArray(obj)) { + return obj.map(addSingleQuotesToStrings); + } else if (typeof obj === 'object' && obj !== null) { + const newObj: any = {}; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key) === true) { + newObj[key] = addSingleQuotesToStrings(obj[key]); + } + } + return newObj; + } + return obj; +}; +export const removeSingleQuotesFromStrings = (obj: any): any => { + if (typeof obj === 'string') { + if (obj.startsWith("'") && obj.endsWith("'")) { + return obj.slice(1, -1); + } + return obj; + } else if (Array.isArray(obj)) { + return obj.map(removeSingleQuotesFromStrings); + } else if (typeof obj === 'object' && obj !== null) { + const newObj: any = {}; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key) === true) { + newObj[key] = removeSingleQuotesFromStrings(obj[key]); + } + } + return newObj; + } + return obj; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts index 263e6f81c463..e1c29d5fc0d3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts @@ -5,7 +5,6 @@ import { IsEnum, IsInt, IsOptional, - IsString, Max, Min, validateOrReject, @@ -43,12 +42,6 @@ class TextSettingsValidation { displayedMaxRows?: number; } -class AddressSettingsValidation { - @IsOptional() - @IsString() - defaultCountry?: string; -} - @Injectable() export class FieldMetadataValidationService< T extends FieldMetadataType | 'default' = 'default', @@ -69,9 +62,6 @@ export class FieldMetadataValidationService< case FieldMetadataType.TEXT: await this.validateSettings(TextSettingsValidation, settings); break; - case FieldMetadataType.ADDRESS: - await this.validateSettings(AddressSettingsValidation, settings); - break; default: break; } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts index 6f373695013c..6afede943d6a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts @@ -28,16 +28,11 @@ type FieldMetadataDateTimeSettings = { displayAsRelativeDate?: boolean; }; -type FieldMetadataAddressSettings = { - defaultCountry?: string; -}; - type FieldMetadataSettingsMapping = { [FieldMetadataType.NUMBER]: FieldMetadataNumberSettings; [FieldMetadataType.DATE]: FieldMetadataDateSettings; [FieldMetadataType.DATE_TIME]: FieldMetadataDateTimeSettings; [FieldMetadataType.TEXT]: FieldMetadataTextSettings; - [FieldMetadataType.ADDRESS]: FieldMetadataAddressSettings; }; type SettingsByFieldMetadata = diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/field-metadata-validation.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/field-metadata-validation.service.spec.ts index 1b896f5577eb..1e1b0bf63f07 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/field-metadata-validation.service.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/field-metadata-validation.service.spec.ts @@ -56,26 +56,4 @@ describe('FieldMetadataValidationService', () => { }), ).rejects.toThrow(FieldMetadataException); }); - - it('should validate ADDRESS settings successfully', async () => { - const settings = { defaultCountry: 'France' } as FieldMetadataSettings; - - await expect( - service.validateSettingsOrThrow({ - fieldType: FieldMetadataType.ADDRESS, - settings, - }), - ).resolves.not.toThrow(); - }); - - it('should throw an error for invalid ADDRESS settings', async () => { - const settings = { defaultCountry: 123 } as FieldMetadataSettings; - - await expect( - service.validateSettingsOrThrow({ - fieldType: FieldMetadataType.ADDRESS, - settings, - }), - ).rejects.toThrow(FieldMetadataException); - }); });