Fix Entering special character in object's field name crashes the app (#14455)
Fixes https://github.com/twentyhq/twenty/issues/14289. We should leave the error management to the form validation, and only throw in the frame of this validation where it will be properly handled. This is enough to prevent form submission with an undesired value. So we can use an empty metadata name everywhere else on the page to prevent crash. <img width="708" height="229" alt="Capture d’écran 2025-09-12 à 17 27 51" src="https://github.com/user-attachments/assets/f257c220-674e-49fc-b560-5ad585ae6c5f" /> --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
+5
-3
@@ -1,8 +1,8 @@
|
||||
import { errors } from '@/settings/data-model/fields/forms/utils/errorMessages';
|
||||
import { z } from 'zod';
|
||||
import { computeMetadataNameFromLabelOrThrow } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabelOrThrow';
|
||||
|
||||
import { METADATA_LABEL_VALID_PATTERN } from '~/pages/settings/data-model/constants/MetadataLabelValidPattern';
|
||||
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 {
|
||||
computeMetadataNameFromLabel(label);
|
||||
computeMetadataNameFromLabelOrThrow(label);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
@@ -28,7 +28,9 @@ export const metadataLabelSchema = (existingLabels?: string[]) => {
|
||||
if (!existingLabels || !label?.length) {
|
||||
return true;
|
||||
}
|
||||
return !existingLabels.includes(computeMetadataNameFromLabel(label));
|
||||
return !existingLabels.includes(
|
||||
computeMetadataNameFromLabelOrThrow(label),
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
||||
|
||||
import { type FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { themeColorSchema } from 'twenty-ui/theme';
|
||||
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils';
|
||||
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/computeOptionValueFromLabel';
|
||||
|
||||
const selectOptionSchema = z
|
||||
.object({
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import {
|
||||
TooltipDelay,
|
||||
} from 'twenty-ui/display';
|
||||
import { Card } from 'twenty-ui/layout';
|
||||
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils';
|
||||
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabel';
|
||||
|
||||
export const settingsDataModelFieldIconLabelFormSchema = (
|
||||
existingOtherLabels: string[] = [],
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import {
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
import { MenuItem, MenuItemSelectColor } from 'twenty-ui/navigation';
|
||||
import { MAIN_COLOR_NAMES } from 'twenty-ui/theme';
|
||||
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils';
|
||||
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/computeOptionValueFromLabel';
|
||||
|
||||
type SettingsDataModelFieldSelectFormOptionRowProps = {
|
||||
className?: string;
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { v4 } from 'uuid';
|
||||
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
|
||||
import { type FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type SettingsDataModelFieldSelectFormValues } from '@/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm';
|
||||
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils';
|
||||
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/computeOptionValueFromLabel';
|
||||
|
||||
const DEFAULT_OPTION: FieldMetadataItemOption = {
|
||||
color: 'green',
|
||||
|
||||
+1
-1
@@ -2,8 +2,8 @@ import { v4 } from 'uuid';
|
||||
|
||||
import { type FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { generateNewSelectOptionLabel } from '@/settings/data-model/fields/forms/select/utils/generateNewSelectOptionLabel';
|
||||
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils';
|
||||
import { getNextThemeColor } from 'twenty-ui/theme';
|
||||
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/computeOptionValueFromLabel';
|
||||
|
||||
export const generateNewSelectOption = (
|
||||
options: FieldMetadataItemOption[],
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte
|
||||
import { SettingsDataModelFieldPreview } from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreview';
|
||||
import { SettingsDataModelObjectPreview } from '@/settings/data-model/objects/components/SettingsDataModelObjectSummary';
|
||||
import { Card, CardContent } from 'twenty-ui/layout';
|
||||
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils';
|
||||
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabel';
|
||||
|
||||
type SettingsDataModelFieldPreviewWidgetProps = {
|
||||
className?: string;
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import {
|
||||
} from 'twenty-ui/display';
|
||||
import { Card } from 'twenty-ui/layout';
|
||||
import { type StringKeyOf } from 'type-fest';
|
||||
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils';
|
||||
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabel';
|
||||
|
||||
type SettingsDataModelObjectAboutFormProps = {
|
||||
disableEdition?: boolean;
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { TextArea } from '@/ui/input/components/TextArea';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useGetRolesQuery } from '~/generated-metadata/graphql';
|
||||
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils';
|
||||
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabel';
|
||||
import { type SettingsAIAgentFormValues } from '../../hooks/useSettingsAgentFormState';
|
||||
|
||||
const StyledFormContainer = styled.div`
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils';
|
||||
|
||||
describe('computeMetadataNameFromLabel', () => {
|
||||
it('computes name for label with non-latin char', () => {
|
||||
const label = 'λλλ!';
|
||||
|
||||
expect(computeMetadataNameFromLabel(label)).toEqual('lll');
|
||||
});
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabel';
|
||||
|
||||
describe('computeMetadataNameFromLabel', () => {
|
||||
it('computes name for label with non-latin char', () => {
|
||||
const label = 'λλλ!';
|
||||
|
||||
expect(computeMetadataNameFromLabel(label)).toEqual('lll');
|
||||
});
|
||||
|
||||
it('returns empty string for empty label', () => {
|
||||
const label = '';
|
||||
|
||||
expect(computeMetadataNameFromLabel(label)).toEqual('');
|
||||
});
|
||||
|
||||
it('returns empty string for invalid label', () => {
|
||||
const label = '/';
|
||||
|
||||
expect(computeMetadataNameFromLabel(label)).toEqual('');
|
||||
});
|
||||
});
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { computeMetadataNameFromLabelOrThrow } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabelOrThrow';
|
||||
|
||||
export const computeMetadataNameFromLabel = (label: string): string => {
|
||||
try {
|
||||
return computeMetadataNameFromLabelOrThrow(label);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import camelCase from 'lodash.camelcase';
|
||||
import { slugify } from 'transliteration';
|
||||
|
||||
export const computeMetadataNameFromLabel = (label: string): string => {
|
||||
export const computeMetadataNameFromLabelOrThrow = (label: string): string => {
|
||||
const prefixedLabel = /^\d/.test(label) ? `n${label}` : label;
|
||||
|
||||
if (prefixedLabel === '') {
|
||||
Reference in New Issue
Block a user