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)
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 {