From 8e007c8b5f54a8e484c4627f3a9c6479c1f8c142 Mon Sep 17 00:00:00 2001 From: Etienne <45695613+etiennejouan@users.noreply.github.com> Date: Fri, 13 Jun 2025 15:52:32 +0200 Subject: [PATCH] Import - fixes (#12569) Screenshot 2025-06-12 at 15 22 49 To test : - unselect an option on select/multi-select matching (matching step) - match a mutli-select field with an other field closes : https://github.com/twentyhq/core-team-issues/issues/1065 closes : https://github.com/twentyhq/core-team-issues/issues/1066 --- ...etSpreadSheetFieldValidationDefinitions.ts | 17 ++++++++++ .../SubMatchingSelectRowLeftSelect.tsx | 12 ------- .../components/TemplateColumn.tsx | 10 ++++++ .../components/UnmatchColumn.tsx | 4 +++ .../components/UnmatchColumnBanner.tsx | 28 ++++++++++------ .../types/SpreadsheetColumn.ts | 11 ++++++- .../types/SpreadsheetColumnType.ts | 1 + .../spreadsheet-import/utils/setColumn.ts | 32 ++++++++++++------- .../spreadsheet-import/utils/setSubColumn.ts | 9 +++++- 9 files changed, 89 insertions(+), 35 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/spreadsheet-import/utils/getSpreadSheetFieldValidationDefinitions.ts b/packages/twenty-front/src/modules/object-record/spreadsheet-import/utils/getSpreadSheetFieldValidationDefinitions.ts index 777483767..fba6efedd 100644 --- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/utils/getSpreadSheetFieldValidationDefinitions.ts +++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/utils/getSpreadSheetFieldValidationDefinitions.ts @@ -1,3 +1,4 @@ +import { RATING_VALUES } from '@/object-record/record-field/meta-types/constants/RatingValues'; import { emailSchema } from '@/object-record/record-field/validation-schemas/emailSchema'; import { SpreadsheetImportFieldValidationDefinition } from '@/spreadsheet-import/types'; import { t } from '@lingui/core/macro'; @@ -214,6 +215,22 @@ export const getSpreadSheetFieldValidationDefinitions = ( level: 'error', }, ]; + case FieldMetadataType.RATING: { + const ratingValues = RATING_VALUES.join(', '); + + return [ + { + rule: 'function', + isValid: (value: string) => { + return RATING_VALUES.includes( + value as (typeof RATING_VALUES)[number], + ); + }, + errorMessage: `${fieldName} ${t` must be one of ${ratingValues} values`}`, + level: 'error', + }, + ]; + } default: return []; } diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowLeftSelect.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowLeftSelect.tsx index e08a5eb26..0434cc777 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowLeftSelect.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectRowLeftSelect.tsx @@ -1,13 +1,7 @@ import { SubMatchingSelectControlContainer } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/SubMatchingSelectControlContainer'; import { SpreadsheetMatchedOptions } from '@/spreadsheet-import/types/SpreadsheetMatchedOptions'; -import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { IconChevronDown } from 'twenty-ui/display'; - -const StyledIconChevronDown = styled(IconChevronDown)` - color: ${({ theme }) => theme.font.color.tertiary}; -`; const StyledLabel = styled.span` color: ${({ theme }) => theme.font.color.primary}; @@ -28,17 +22,11 @@ export type SubMatchingSelectRowLeftSelectProps = { export const SubMatchingSelectRowLeftSelect = ({ option, }: SubMatchingSelectRowLeftSelectProps) => { - const theme = useTheme(); - return ( {option.entry} - ); }; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn.tsx index f7232a204..4bb36d6af 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn.tsx @@ -18,6 +18,13 @@ const StyledContainer = styled.div` width: 100%; `; +const StyledErrorMessage = styled.span` + color: ${({ theme }) => theme.font.color.danger}; + font-size: ${({ theme }) => theme.font.size.sm}; + font-weight: ${({ theme }) => theme.font.weight.regular}; + margin-top: ${({ theme }) => theme.spacing(1)}; +`; + type TemplateColumnProps = { columns: SpreadsheetColumns; columnIndex: number; @@ -72,6 +79,9 @@ export const TemplateColumn = ({ suggestedOptions={suggestedFieldOptions} columnIndex={column.index.toString()} /> + {column.type === SpreadsheetColumnType.matchedError && ( + {`"${column.header}" ${column.errorMessage}`} + )} ); }; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumn.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumn.tsx index e01d3eb41..55f4320d3 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumn.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumn.tsx @@ -4,6 +4,7 @@ import { UnmatchColumnBanner } from '@/spreadsheet-import/steps/components/Match import { SpreadsheetImportFields } from '@/spreadsheet-import/types'; import { SpreadsheetColumn } from '@/spreadsheet-import/types/SpreadsheetColumn'; import { SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetColumns'; +import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useState } from 'react'; @@ -54,6 +55,8 @@ export const UnmatchColumn = ({ const isSelect = 'matchedOptions' in column; const { t } = useLingui(); + const allMatched = column.type === SpreadsheetColumnType.matchedSelectOptions; + if (!isSelect) return null; return ( @@ -62,6 +65,7 @@ export const UnmatchColumn = ({ message={getExpandableContainerTitle(fields, column)} buttonOnClick={() => setIsExpanded(!isExpanded)} isExpanded={isExpanded} + allMatched={allMatched} /> theme.accent.secondary}; +const StyledBanner = styled(Banner)<{ allMatched: boolean }>` + background: ${({ allMatched, theme }) => + allMatched ? theme.accent.secondary : theme.background.transparent.light}; border-radius: ${({ theme }) => theme.spacing(2)}; padding: ${({ theme }) => theme.spacing(2) + ' ' + theme.spacing(2.5)}; `; -const StyledText = styled.div` - color: ${({ theme }) => theme.color.blue}; +const StyledText = styled.div<{ allMatched: boolean }>` + color: ${({ allMatched, theme }) => + allMatched ? theme.color.blue : theme.font.color.secondary}; flex: 1; overflow: hidden; text-overflow: ellipsis; @@ -19,8 +21,10 @@ const StyledText = styled.div` const StyledTransitionedIconChevronDown = styled(IconChevronDown)<{ isExpanded: boolean; + allMatched: boolean; }>` - color: ${({ theme }) => theme.color.blue}; + color: ${({ allMatched, theme }) => + allMatched ? theme.color.blue : theme.font.color.secondary}; transform: ${({ isExpanded }) => isExpanded ? 'rotate(180deg)' : 'rotate(0deg)'}; transition: ${({ theme }) => @@ -42,26 +46,32 @@ export const UnmatchColumnBanner = ({ message, isExpanded, buttonOnClick, + allMatched, }: { message: string; isExpanded: boolean; buttonOnClick?: () => void; + allMatched: boolean; }) => { const theme = useTheme(); return ( - - + + {isDefined(buttonOnClick) ? ( - {message} + {message} ) : ( - {message} + {message} )} ); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/types/SpreadsheetColumn.ts b/packages/twenty-front/src/modules/spreadsheet-import/types/SpreadsheetColumn.ts index 475bb65d3..74404b854 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/types/SpreadsheetColumn.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/types/SpreadsheetColumn.ts @@ -43,10 +43,19 @@ export type SpreadsheetMatchedSelectOptionsColumn = { matchedOptions: SpreadsheetMatchedOptions[]; }; +export type SpreadsheetErrorColumn = { + type: SpreadsheetColumnType.matchedError; + index: number; + header: string; + value: T; + errorMessage: string; +}; + export type SpreadsheetColumn = | SpreadsheetEmptyColumn | SpreadsheetIgnoredColumn | SpreadsheetMatchedColumn | SpreadsheetMatchedSwitchColumn | SpreadsheetMatchedSelectColumn - | SpreadsheetMatchedSelectOptionsColumn; + | SpreadsheetMatchedSelectOptionsColumn + | SpreadsheetErrorColumn; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/types/SpreadsheetColumnType.ts b/packages/twenty-front/src/modules/spreadsheet-import/types/SpreadsheetColumnType.ts index d7cb38871..c2af81018 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/types/SpreadsheetColumnType.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/types/SpreadsheetColumnType.ts @@ -5,4 +5,5 @@ export enum SpreadsheetColumnType { matchedCheckbox, matchedSelect, matchedSelectOptions, + matchedError, } 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 d1d8100a0..10ed524ca 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/utils/setColumn.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/setColumn.ts @@ -4,6 +4,7 @@ import { SpreadsheetImportField } from '@/spreadsheet-import/types'; import { SpreadsheetColumn } from '@/spreadsheet-import/types/SpreadsheetColumn'; import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType'; import { SpreadsheetMatchedOptions } from '@/spreadsheet-import/types/SpreadsheetMatchedOptions'; +import { t } from '@lingui/core/macro'; import { z } from 'zod'; import { uniqueEntries } from './uniqueEntries'; @@ -45,21 +46,28 @@ export const setColumn = ( if (field?.fieldType.type === 'multiSelect') { const fieldOptions = field.fieldType.options; - const entries = [ - ...new Set( - data - ?.flatMap((row) => { - try { + let entries: string[] = []; + try { + entries = [ + ...new Set( + data + ?.flatMap((row) => { const value = row[oldColumn.index]; const options = JSON.parse(z.string().parse(value)); return z.array(z.string()).parse(options); - } catch { - return []; - } - }) - .filter((entry) => typeof entry === 'string'), - ), - ]; + }) + .filter((entry) => typeof entry === 'string'), + ), + ]; + } catch { + return { + index: oldColumn.index, + header: oldColumn.header, + type: SpreadsheetColumnType.matchedError, + value: field.key, + errorMessage: t`column data is not compatible with Multi-Select.`, + }; + } const matchedOptions = entries.map((entry) => { const value = fieldOptions.find( diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/setSubColumn.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/setSubColumn.ts index 033096043..5bd63010d 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/utils/setSubColumn.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/setSubColumn.ts @@ -14,9 +14,16 @@ export const setSubColumn = ( ): | SpreadsheetMatchedSelectColumn | SpreadsheetMatchedSelectOptionsColumn => { + const shouldUnselectValue = + oldColumn.matchedOptions.find((option) => option.entry === entry)?.value === + value; + const options = oldColumn.matchedOptions.map((option) => - option.entry === entry ? { ...option, value } : option, + option.entry === entry + ? { ...option, value: shouldUnselectValue ? undefined : value } + : option, ); + const allMatched = options.every(({ value }) => !!value); if (allMatched) { return {