From 447892b7ef7d7fad0d1ce939a6f13edb9cc0b9f3 Mon Sep 17 00:00:00 2001 From: Etienne <45695613+etiennejouan@users.noreply.github.com> Date: Tue, 2 Sep 2025 14:18:45 +0200 Subject: [PATCH] fix - update error message and ease importable format for multi select (#14203) - Add other string format ('option1,option2,..') for multi select import - Update error message closes : https://github.com/twentyhq/twenty/issues/14103 --- ...portParseMultiSelectOptionsOrThrow.test.ts | 20 +++++++++++++++++++ .../utils/normalizeTableData.ts | 8 ++++---- .../spreadsheet-import/utils/setColumn.ts | 7 +++---- ...eetImportParseMultiSelectOptionsOrThrow.ts | 15 ++++++++++++++ 4 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 packages/twenty-front/src/modules/spreadsheet-import/utils/__tests__/spreadsheetImportParseMultiSelectOptionsOrThrow.test.ts create mode 100644 packages/twenty-front/src/modules/spreadsheet-import/utils/spreadsheetImportParseMultiSelectOptionsOrThrow.ts diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/__tests__/spreadsheetImportParseMultiSelectOptionsOrThrow.test.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/__tests__/spreadsheetImportParseMultiSelectOptionsOrThrow.test.ts new file mode 100644 index 0000000000..24ceb7065b --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/__tests__/spreadsheetImportParseMultiSelectOptionsOrThrow.test.ts @@ -0,0 +1,20 @@ +import { spreadsheetImportParseMultiSelectOptionsOrThrow } from '@/spreadsheet-import/utils/spreadsheetImportParseMultiSelectOptionsOrThrow'; + +describe('spreadsheetImportParseMultiSelectOptionsOrThrow', () => { + it('should parse multi select options', () => { + const options = spreadsheetImportParseMultiSelectOptionsOrThrow( + '["option1", "option2"]', + ); + expect(options).toEqual(['option1', 'option2']); + }); + + it('should parse multi select options with comma', () => { + const options = + spreadsheetImportParseMultiSelectOptionsOrThrow('option1,option2'); + expect(options).toEqual(['option1', 'option2']); + }); + + it('should throw an error if the value is not parsable', () => { + expect(() => spreadsheetImportParseMultiSelectOptionsOrThrow({})).toThrow(); + }); +}); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/normalizeTableData.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/normalizeTableData.ts index 4bdc2e01a0..8504452114 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/utils/normalizeTableData.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/normalizeTableData.ts @@ -5,6 +5,7 @@ import { } from '@/spreadsheet-import/types'; import { type SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetColumns'; import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType'; +import { spreadsheetImportParseMultiSelectOptionsOrThrow } from '@/spreadsheet-import/utils/spreadsheetImportParseMultiSelectOptionsOrThrow'; import { isDefined } from 'twenty-shared/utils'; import { z } from 'zod'; import { normalizeCheckboxValue } from './normalizeCheckboxValue'; @@ -60,10 +61,9 @@ export const normalizeTableData = ( } if (field.fieldType.type === 'multiSelect' && isDefined(curr)) { - const currentOptionsSchema = z.preprocess( - (value) => JSON.parse(z.string().parse(value)), - z.array(z.unknown()), - ); + const currentOptionsSchema = z.preprocess((value) => { + return spreadsheetImportParseMultiSelectOptionsOrThrow(value); + }, z.array(z.unknown())); const rawCurrentOptions = currentOptionsSchema.safeParse(curr).data; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/setColumn.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/setColumn.ts index b20d9102f1..e2efe165df 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/utils/setColumn.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/setColumn.ts @@ -4,9 +4,9 @@ import { type SpreadsheetImportField } from '@/spreadsheet-import/types'; import { type SpreadsheetColumn } from '@/spreadsheet-import/types/SpreadsheetColumn'; import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType'; import { type SpreadsheetMatchedOptions } from '@/spreadsheet-import/types/SpreadsheetMatchedOptions'; +import { spreadsheetImportParseMultiSelectOptionsOrThrow } from '@/spreadsheet-import/utils/spreadsheetImportParseMultiSelectOptionsOrThrow'; import { t } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; -import { z } from 'zod'; import { uniqueEntries } from './uniqueEntries'; export const setColumn = ( @@ -55,8 +55,7 @@ export const setColumn = ( ?.flatMap((row) => { const value = row[oldColumn.index]; if (!isDefined(value)) return []; - const options = JSON.parse(z.string().parse(value)); - return z.array(z.string()).parse(options); + return spreadsheetImportParseMultiSelectOptionsOrThrow(value); }) .filter((entry) => typeof entry === 'string'), ), @@ -67,7 +66,7 @@ export const setColumn = ( header: oldColumn.header, type: SpreadsheetColumnType.matchedError, value: field.key, - errorMessage: t`column data is not compatible with Multi-Select.`, + errorMessage: t`column data is not compatible with Multi-Select. Format required is '["option1", "option2"]' or option1,option2.`, }; } diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/spreadsheetImportParseMultiSelectOptionsOrThrow.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/spreadsheetImportParseMultiSelectOptionsOrThrow.ts new file mode 100644 index 0000000000..a06c9bee4f --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/spreadsheetImportParseMultiSelectOptionsOrThrow.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; + +export const spreadsheetImportParseMultiSelectOptionsOrThrow = ( + value: unknown, +) => { + try { + return JSON.parse(z.string().parse(value)); + } catch { + return z + .string() + .parse(value) + .split(',') + .map((item) => item.trim()); + } +};