From 51c54d4c5b65e053756f5a4981833976152dd285 Mon Sep 17 00:00:00 2001 From: Ketan Mehta <45426198+ketanMehtaa@users.noreply.github.com> Date: Thu, 14 Nov 2024 22:16:18 +0530 Subject: [PATCH] validation on Select field (#8316) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #8204 I changed "API keys" to "API values". Stopped inputting special characters in Select field option keys. @lucasbordeau please check the changes and tell me if I need to do any other changes. :) --------- Co-authored-by: Félix Malfait --- .../hooks/__mocks__/useFieldMetadataItem.ts | 2 +- .../utils/formatFieldMetadataItemInput.ts | 4 +- .../validation-schemas/metadataLabelSchema.ts | 8 +-- .../validation-schemas/selectOptionsSchema.ts | 4 +- .../SettingsDataModelFieldSelectForm.tsx | 2 +- ...tingsDataModelFieldSelectFormOptionRow.tsx | 8 +-- .../useSelectSettingsFormInitialValues.ts | 4 +- .../__tests__/getOptionValueFromLabel.test.ts | 39 ------------ .../select/utils/generateNewSelectOption.ts | 4 +- .../select/utils/getOptionValueFromLabel.ts | 14 ----- .../components/tabs/ObjectSettings.tsx | 8 +-- .../SettingsDataModelObjectAboutForm.tsx | 12 ++-- .../settingsCreateObjectInputSchema.ts | 7 +-- .../constants/MetadataLabelValidPattern.ts | 2 +- .../constants/OptionValueValidPattern.ts | 2 +- .../compute-metadata-name-from-label.test.ts | 22 +------ .../compute-option-value-from-label.test.ts | 27 --------- .../compute-metadata-name-from-label.utils.ts | 23 ++++++-- .../compute-option-value-from-label.utils.ts | 19 ++++-- .../utils/transliterate-and-format.utils.ts | 25 -------- .../validate-object-metadata-input.util.ts | 59 +++++++++---------- .../src/display/tag/components/Tag.tsx | 5 +- 22 files changed, 96 insertions(+), 204 deletions(-) delete mode 100644 packages/twenty-front/src/modules/settings/data-model/fields/forms/select/utils/__tests__/getOptionValueFromLabel.test.ts delete mode 100644 packages/twenty-front/src/modules/settings/data-model/fields/forms/select/utils/getOptionValueFromLabel.ts delete mode 100644 packages/twenty-front/src/pages/settings/data-model/utils/__tests__/compute-option-value-from-label.test.ts delete mode 100644 packages/twenty-front/src/pages/settings/data-model/utils/transliterate-and-format.utils.ts diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts index b69870f2244a..0facf2c51245 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts @@ -116,7 +116,7 @@ export const variables = { description: null, icon: undefined, label: 'fieldLabel', - name: 'fieldLabel', + name: 'fieldlabel', options: undefined, settings: undefined, objectMetadataId, 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 5900beed1faa..bfcca728d674 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts @@ -1,5 +1,5 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { computeMetadataNameFromLabelOrThrow } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils'; +import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils'; export const formatFieldMetadataItemInput = ( input: Partial< @@ -22,7 +22,7 @@ export const formatFieldMetadataItemInput = ( description: input.description?.trim() ?? null, icon: input.icon, label, - name: label ? computeMetadataNameFromLabelOrThrow(label) : undefined, + name: label ? computeMetadataNameFromLabel(label) : undefined, options: input.options, settings: input.settings, }; diff --git a/packages/twenty-front/src/modules/object-metadata/validation-schemas/metadataLabelSchema.ts b/packages/twenty-front/src/modules/object-metadata/validation-schemas/metadataLabelSchema.ts index 4e196f0fa10b..96a1602409e1 100644 --- a/packages/twenty-front/src/modules/object-metadata/validation-schemas/metadataLabelSchema.ts +++ b/packages/twenty-front/src/modules/object-metadata/validation-schemas/metadataLabelSchema.ts @@ -2,7 +2,7 @@ import { errors } from '@/settings/data-model/fields/forms/utils/errorMessages'; import { z } from 'zod'; import { METADATA_LABEL_VALID_PATTERN } from '~/pages/settings/data-model/constants/MetadataLabelValidPattern'; -import { computeMetadataNameFromLabelOrThrow } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils'; +import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils'; export const metadataLabelSchema = (existingLabels?: string[]) => { return z .string() @@ -12,7 +12,7 @@ export const metadataLabelSchema = (existingLabels?: string[]) => { .refine( (label) => { try { - computeMetadataNameFromLabelOrThrow(label); + computeMetadataNameFromLabel(label); return true; } catch (error) { return false; @@ -28,9 +28,7 @@ export const metadataLabelSchema = (existingLabels?: string[]) => { if (!existingLabels || !label?.length) { return true; } - return !existingLabels.includes( - computeMetadataNameFromLabelOrThrow(label), - ); + return !existingLabels.includes(computeMetadataNameFromLabel(label)); } catch (error) { return false; } diff --git a/packages/twenty-front/src/modules/object-metadata/validation-schemas/selectOptionsSchema.ts b/packages/twenty-front/src/modules/object-metadata/validation-schemas/selectOptionsSchema.ts index 3893306c87ce..b91bdfade9b5 100644 --- a/packages/twenty-front/src/modules/object-metadata/validation-schemas/selectOptionsSchema.ts +++ b/packages/twenty-front/src/modules/object-metadata/validation-schemas/selectOptionsSchema.ts @@ -2,7 +2,7 @@ import { themeColorSchema } from 'twenty-ui'; import { z } from 'zod'; import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem'; -import { computeOptionValueFromLabelOrThrow } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils'; +import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils'; const selectOptionSchema = z .object({ @@ -15,7 +15,7 @@ const selectOptionSchema = z .refine( (option) => { try { - computeOptionValueFromLabelOrThrow(option.label); + computeOptionValueFromLabel(option.label); return true; } catch (error) { return false; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx index 6796f34a28ac..48af1e38ed7e 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx @@ -266,7 +266,7 @@ export const SettingsDataModelFieldSelectForm = ({ color={MAIN_COLORS.yellow} /> - API keys + API values )} diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectFormOptionRow.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectFormOptionRow.tsx index fd169354b58c..b92c97e5d515 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectFormOptionRow.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectFormOptionRow.tsx @@ -18,7 +18,6 @@ import { v4 } from 'uuid'; import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem'; import { EXPANDED_WIDTH_ANIMATION_VARIANTS } from '@/settings/constants/ExpandedWidthAnimationVariants'; import { OPTION_VALUE_MAXIMUM_LENGTH } from '@/settings/data-model/constants/OptionValueMaximumLength'; -import { getOptionValueFromLabel } from '@/settings/data-model/fields/forms/select/utils/getOptionValueFromLabel'; import { TextInput } from '@/ui/input/components/TextInput'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; @@ -27,6 +26,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; import { AnimatePresence, motion } from 'framer-motion'; import { useRecoilValue } from 'recoil'; +import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils'; type SettingsDataModelFieldSelectFormOptionRowProps = { className?: string; @@ -124,7 +124,7 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({ onChange={(input) => onChange({ ...option, - value: getOptionValueFromLabel(input), + value: computeOptionValueFromLabel(input), }) } RightIcon={isDefault ? IconCheck : undefined} @@ -162,14 +162,14 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({ value={option.label} onChange={(label) => { const optionNameHasBeenEdited = !( - option.value === getOptionValueFromLabel(option.label) + option.value === computeOptionValueFromLabel(option.label) ); onChange({ ...option, label, value: optionNameHasBeenEdited ? option.value - : getOptionValueFromLabel(label), + : computeOptionValueFromLabel(label), }); }} RightIcon={isDefault ? IconCheck : undefined} diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues.ts b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues.ts index 2d19e050740a..dc1f745bd3ef 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues.ts @@ -7,14 +7,14 @@ import { FieldMetadataItemOption, } from '@/object-metadata/types/FieldMetadataItem'; import { SettingsDataModelFieldSelectFormValues } from '@/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm'; -import { getOptionValueFromLabel } from '@/settings/data-model/fields/forms/select/utils/getOptionValueFromLabel'; +import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils'; const DEFAULT_OPTION: FieldMetadataItemOption = { color: 'green', id: v4(), label: 'Option 1', position: 0, - value: getOptionValueFromLabel('Option 1'), + value: computeOptionValueFromLabel('Option 1'), }; export const useSelectSettingsFormInitialValues = ({ diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/utils/__tests__/getOptionValueFromLabel.test.ts b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/utils/__tests__/getOptionValueFromLabel.test.ts deleted file mode 100644 index 07b60304eaea..000000000000 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/utils/__tests__/getOptionValueFromLabel.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { getOptionValueFromLabel } from '../getOptionValueFromLabel'; - -describe('getOptionValueFromLabel', () => { - it('should return the option value from the label', () => { - const label = 'Example Label'; - const expected = 'EXAMPLE_LABEL'; - - const result = getOptionValueFromLabel(label); - - expect(result).toEqual(expected); - }); - - it('should handle labels with accents', () => { - const label = 'Éxàmplè Làbèl'; - const expected = 'EXAMPLE_LABEL'; - - const result = getOptionValueFromLabel(label); - - expect(result).toEqual(expected); - }); - - it('should handle labels with special characters', () => { - const label = 'Example!@#$%^&*() Label'; - const expected = 'EXAMPLE_LABEL'; - - const result = getOptionValueFromLabel(label); - - expect(result).toEqual(expected); - }); - - it('should handle labels with emojis', () => { - const label = '📱 Example Label'; - const expected = 'EXAMPLE_LABEL'; - - const result = getOptionValueFromLabel(label); - - expect(result).toEqual(expected); - }); -}); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/utils/generateNewSelectOption.ts b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/utils/generateNewSelectOption.ts index d8289070e829..851dd8892574 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/utils/generateNewSelectOption.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/utils/generateNewSelectOption.ts @@ -3,7 +3,7 @@ import { v4 } from 'uuid'; import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem'; import { generateNewSelectOptionLabel } from '@/settings/data-model/fields/forms/select/utils/generateNewSelectOptionLabel'; -import { getOptionValueFromLabel } from '@/settings/data-model/fields/forms/select/utils/getOptionValueFromLabel'; +import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils'; export const generateNewSelectOption = ( options: FieldMetadataItemOption[], @@ -15,6 +15,6 @@ export const generateNewSelectOption = ( id: v4(), label: newOptionLabel, position: options.length, - value: getOptionValueFromLabel(newOptionLabel), + value: computeOptionValueFromLabel(newOptionLabel), }; }; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/utils/getOptionValueFromLabel.ts b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/utils/getOptionValueFromLabel.ts deleted file mode 100644 index 9a263fa6ced7..000000000000 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/utils/getOptionValueFromLabel.ts +++ /dev/null @@ -1,14 +0,0 @@ -import snakeCase from 'lodash.snakecase'; - -import { computeOptionValueFromLabelOrThrow } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils'; - -export const getOptionValueFromLabel = (label: string) => { - let transliteratedLabel = label; - try { - transliteratedLabel = computeOptionValueFromLabelOrThrow(label); - } catch (error) { - return label; - } - - return snakeCase(transliteratedLabel).toUpperCase(); -}; diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx index 1054430823b5..5cdd83653666 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx @@ -29,7 +29,7 @@ import isEmpty from 'lodash.isempty'; import pick from 'lodash.pick'; import { useSetRecoilState } from 'recoil'; import { updatedObjectSlugState } from '~/pages/settings/data-model/states/updatedObjectSlugState'; -import { computeMetadataNameFromLabelOrThrow } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils'; +import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils'; const objectEditFormSchema = z .object({}) @@ -93,16 +93,14 @@ export const ObjectSettings = ({ objectMetadataItem }: ObjectSettingsProps) => { ...values, ...(values.labelSingular && dirtyFieldKeys.includes('labelSingular') ? { - nameSingular: computeMetadataNameFromLabelOrThrow( + nameSingular: computeMetadataNameFromLabel( formValues.labelSingular, ), } : {}), ...(values.labelPlural && dirtyFieldKeys.includes('labelPlural') ? { - namePlural: computeMetadataNameFromLabelOrThrow( - formValues.labelPlural, - ), + namePlural: computeMetadataNameFromLabel(formValues.labelPlural), } : {}), }; diff --git a/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx index 1e7752d9a3aa..05056a1e5cec 100644 --- a/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx @@ -21,7 +21,7 @@ import { TooltipDelay, } from 'twenty-ui'; import { z } from 'zod'; -import { computeMetadataNameFromLabelOrThrow } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils'; +import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils'; import { isDefined } from '~/utils/isDefined'; export const settingsDataModelObjectAboutFormSchema = objectMetadataItemSchema @@ -144,16 +144,14 @@ export const SettingsDataModelObjectAboutForm = ({ const fillNameSingularFromLabelSingular = (labelSingular: string) => { isDefined(labelSingular) && - setValue( - 'nameSingular', - computeMetadataNameFromLabelOrThrow(labelSingular), - { shouldDirty: true }, - ); + setValue('nameSingular', computeMetadataNameFromLabel(labelSingular), { + shouldDirty: true, + }); }; const fillNamePluralFromLabelPlural = (labelPlural: string) => { isDefined(labelPlural) && - setValue('namePlural', computeMetadataNameFromLabelOrThrow(labelPlural), { + setValue('namePlural', computeMetadataNameFromLabel(labelPlural), { shouldDirty: true, }); }; diff --git a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts index 8fa20cdcfdb0..37cc3c2d32c8 100644 --- a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts +++ b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts @@ -1,6 +1,6 @@ import { settingsDataModelObjectAboutFormSchema } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm'; import { CreateObjectInput } from '~/generated-metadata/graphql'; -import { computeMetadataNameFromLabelOrThrow } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils'; +import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils'; export const settingsCreateObjectInputSchema = settingsDataModelObjectAboutFormSchema.transform( @@ -8,10 +8,9 @@ export const settingsCreateObjectInputSchema = ...values, nameSingular: values.nameSingular ?? - computeMetadataNameFromLabelOrThrow(values.labelSingular), + computeMetadataNameFromLabel(values.labelSingular), namePlural: - values.namePlural ?? - computeMetadataNameFromLabelOrThrow(values.labelPlural), + values.namePlural ?? computeMetadataNameFromLabel(values.labelPlural), isLabelSyncedWithName: values.isLabelSyncedWithName ?? true, }), ); diff --git a/packages/twenty-front/src/pages/settings/data-model/constants/MetadataLabelValidPattern.ts b/packages/twenty-front/src/pages/settings/data-model/constants/MetadataLabelValidPattern.ts index 8cc887e6a488..379d1c9519e0 100644 --- a/packages/twenty-front/src/pages/settings/data-model/constants/MetadataLabelValidPattern.ts +++ b/packages/twenty-front/src/pages/settings/data-model/constants/MetadataLabelValidPattern.ts @@ -1 +1 @@ -export const METADATA_LABEL_VALID_PATTERN = /^[^0-9].*$/; +export const METADATA_LABEL_VALID_PATTERN = /^.*$/; diff --git a/packages/twenty-front/src/pages/settings/data-model/constants/OptionValueValidPattern.ts b/packages/twenty-front/src/pages/settings/data-model/constants/OptionValueValidPattern.ts index 2d8ef0210eb9..3db373acc14a 100644 --- a/packages/twenty-front/src/pages/settings/data-model/constants/OptionValueValidPattern.ts +++ b/packages/twenty-front/src/pages/settings/data-model/constants/OptionValueValidPattern.ts @@ -1 +1 @@ -export const OPTION_VALUE_VALID_PATTERN = /^[a-zA-Z0-9]+$/; +export const OPTION_VALUE_VALID_PATTERN = /^[A-Z_][A-Z0-9_]*$/; diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/compute-metadata-name-from-label.test.ts b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/compute-metadata-name-from-label.test.ts index 972bcaafd9a8..eb9673278151 100644 --- a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/compute-metadata-name-from-label.test.ts +++ b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/compute-metadata-name-from-label.test.ts @@ -1,27 +1,9 @@ -import { computeMetadataNameFromLabelOrThrow } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils'; +import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils'; describe('computeMetadataNameFromLabel', () => { - it('throws if empty label', () => { - const label = ' '; - - expect(() => computeMetadataNameFromLabelOrThrow(label)).toThrow(); - }); - - it('computes name for 1 char long label', () => { - const label = 'a'; - - expect(computeMetadataNameFromLabelOrThrow(label)).toEqual('a'); - }); - - it('throws if label starts with digits', () => { - const label = '1string'; - - expect(() => computeMetadataNameFromLabelOrThrow(label)).toThrow(); - }); - it('computes name for label with non-latin char', () => { const label = 'λλλ!'; - expect(computeMetadataNameFromLabelOrThrow(label)).toEqual('lll'); + expect(computeMetadataNameFromLabel(label)).toEqual('lll'); }); }); diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/compute-option-value-from-label.test.ts b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/compute-option-value-from-label.test.ts deleted file mode 100644 index 72b524d8d0ce..000000000000 --- a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/compute-option-value-from-label.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { computeOptionValueFromLabelOrThrow } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils'; - -describe('computeOptionValueFromLabel', () => { - it('throws if empty label', () => { - const label = ' '; - - expect(() => computeOptionValueFromLabelOrThrow(label)).toThrow(); - }); - - it('computes name for 1 char long label', () => { - const label = 'a'; - - expect(computeOptionValueFromLabelOrThrow(label)).toEqual('a'); - }); - - it('compute name if starts with digits', () => { - const label = '1'; - - expect(computeOptionValueFromLabelOrThrow(label)).toEqual('1'); - }); - - it('computes name for label with non-latin char', () => { - const label = 'λλλ'; - - expect(computeOptionValueFromLabelOrThrow(label)).toEqual('lll'); - }); -}); diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/compute-metadata-name-from-label.utils.ts b/packages/twenty-front/src/pages/settings/data-model/utils/compute-metadata-name-from-label.utils.ts index 9a8b46a40871..58d727bf90da 100644 --- a/packages/twenty-front/src/pages/settings/data-model/utils/compute-metadata-name-from-label.utils.ts +++ b/packages/twenty-front/src/pages/settings/data-model/utils/compute-metadata-name-from-label.utils.ts @@ -1,9 +1,22 @@ -import { METADATA_NAME_VALID_PATTERN } from '~/pages/settings/data-model/constants/MetadataNameValidPattern'; -import { transliterateAndFormatOrThrow } from '~/pages/settings/data-model/utils/transliterate-and-format.utils'; +import camelCase from 'lodash.camelcase'; +import { slugify } from 'transliteration'; -export const computeMetadataNameFromLabelOrThrow = (label: string): string => { - if (label === '') { +export const computeMetadataNameFromLabel = (label: string): string => { + const prefixedLabel = /^\d/.test(label) ? `n${label}` : label; + + if (prefixedLabel === '') { return ''; } - return transliterateAndFormatOrThrow(label, METADATA_NAME_VALID_PATTERN); + + const formattedString = slugify(prefixedLabel, { + trim: true, + separator: '_', + allowedChars: 'a-zA-Z0-9', + }); + + if (formattedString === '') { + throw new Error('Invalid label'); + } + + return camelCase(formattedString); }; diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/compute-option-value-from-label.utils.ts b/packages/twenty-front/src/pages/settings/data-model/utils/compute-option-value-from-label.utils.ts index ebf66346ade6..9298870ad3a9 100644 --- a/packages/twenty-front/src/pages/settings/data-model/utils/compute-option-value-from-label.utils.ts +++ b/packages/twenty-front/src/pages/settings/data-model/utils/compute-option-value-from-label.utils.ts @@ -1,6 +1,17 @@ -import { OPTION_VALUE_VALID_PATTERN } from '~/pages/settings/data-model/constants/OptionValueValidPattern'; -import { transliterateAndFormatOrThrow } from '~/pages/settings/data-model/utils/transliterate-and-format.utils'; +import { slugify } from 'transliteration'; -export const computeOptionValueFromLabelOrThrow = (label: string): string => { - return transliterateAndFormatOrThrow(label, OPTION_VALUE_VALID_PATTERN); +export const computeOptionValueFromLabel = (label: string): string => { + const prefixedLabel = /^\d/.test(label) ? `OPT${label}` : label; + + const formattedString = slugify(prefixedLabel, { + trim: true, + separator: '_', + allowedChars: 'a-zA-Z0-9_', + }); + + if (formattedString === '') { + throw new Error('Invalid label'); + } + + return formattedString.toUpperCase(); }; diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/transliterate-and-format.utils.ts b/packages/twenty-front/src/pages/settings/data-model/utils/transliterate-and-format.utils.ts deleted file mode 100644 index b3b4419021e4..000000000000 --- a/packages/twenty-front/src/pages/settings/data-model/utils/transliterate-and-format.utils.ts +++ /dev/null @@ -1,25 +0,0 @@ -import toCamelCase from 'lodash.camelcase'; -import { slugify, transliterate } from 'transliteration'; - -import { isDefined } from '~/utils/isDefined'; - -export const transliterateAndFormatOrThrow = ( - string: string, - validStringPattern: RegExp, -): string => { - let formattedString = string; - - if (isDefined(formattedString.match(validStringPattern))) { - return toCamelCase(formattedString); - } - - formattedString = toCamelCase( - slugify(transliterate(formattedString, { trim: true })), - ); - - if (!formattedString.match(validStringPattern)) { - throw new Error(`"${string}" is not a valid name`); - } - - return formattedString; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts index 8eaa446be2e6..d2333ae1bc11 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts @@ -1,5 +1,4 @@ -import toCamelCase from 'lodash.camelcase'; -import { slugify, transliterate } from 'transliteration'; +import { slugify } from 'transliteration'; import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input'; import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; @@ -63,30 +62,6 @@ export const validateObjectMetadataInputOrThrow = < validateNameIsNotTooLongThrow(objectMetadataInput.namePlural); }; -export const transliterateAndFormatOrThrow = (string?: string): string => { - if (!string) { - throw new ObjectMetadataException( - 'Name is required', - ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, - ); - } - let formattedString = string; - - if (formattedString.match(METADATA_NAME_VALID_PATTERN) !== null) { - return toCamelCase(formattedString); - } - - formattedString = toCamelCase( - slugify(transliterate(formattedString, { trim: true })), - ); - - if (!formattedString.match(METADATA_NAME_VALID_PATTERN)) { - throw new Error(`"${string}" is not a valid name`); - } - - return formattedString; -}; - const validateNameIsNotReservedKeywordOrThrow = (name?: string) => { if (name) { if (reservedKeywords.includes(name)) { @@ -133,17 +108,41 @@ const validateNameCharactersOrThrow = (name?: string) => { } }; -export const computeMetadataNameFromLabelOrThrow = (label: string): string => { - const formattedString = transliterateAndFormatOrThrow(label); +export const computeMetadataNameFromLabel = (label: string): string => { + if (!label) { + throw new ObjectMetadataException( + 'Label is required', + ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, + ); + } + + const prefixedLabel = /^\d/.test(label) ? `n${label}` : label; + + if (prefixedLabel === '') { + return ''; + } + + const formattedString = slugify(prefixedLabel, { + trim: true, + separator: '_', + allowedChars: 'a-zA-Z0-9', + }); + + if (formattedString === '') { + throw new ObjectMetadataException( + `Invalid label: "${label}"`, + ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, + ); + } - return formattedString; + return camelCase(formattedString); }; export const validateNameAndLabelAreSyncOrThrow = ( label: string, name: string, ) => { - const computedName = computeMetadataNameFromLabelOrThrow(label); + const computedName = computeMetadataNameFromLabel(label); if (name !== computedName) { throw new ObjectMetadataException( diff --git a/packages/twenty-ui/src/display/tag/components/Tag.tsx b/packages/twenty-ui/src/display/tag/components/Tag.tsx index 3b5f84b08a64..a8ab14dd3ff3 100644 --- a/packages/twenty-ui/src/display/tag/components/Tag.tsx +++ b/packages/twenty-ui/src/display/tag/components/Tag.tsx @@ -43,12 +43,11 @@ const StyledTag = styled.h3<{ border: ${({ variant, theme }) => variant === 'outline' || variant === 'border' ? `1px ${variant === 'border' ? 'solid' : 'dashed'} ${theme.border.color.strong}` - : ''}; + : 'none'}; gap: ${spacing1}; - min-width: ${({ preventShrink }) => - preventShrink ? 'fit-content' : 'none;'}; + min-width: ${({ preventShrink }) => (preventShrink ? 'fit-content' : 'none')}; `; const StyledContent = styled.span`