5425 - Introducing support for all Composite Fields Import (#5470)

Adding support for all Composite Fields while using the "import"
functionality. This includes:
- Currency
- Address

Edit : 
- Refactored a lot of types in the spreadsheet import module
- Renamed a lot of functions, hooks and types that were not
self-explanatory enough

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Aryan Singh
2024-07-23 21:32:23 +05:30
committed by GitHub
parent 2cc0597ee4
commit 5c8fe027f9
46 changed files with 888 additions and 535 deletions

View File

@ -1,6 +1,6 @@
import {
Data,
Field,
ImportedStructuredRow,
Info,
RowHook,
TableHook,
@ -8,11 +8,11 @@ import {
import { addErrorsAndRunHooks } from '@/spreadsheet-import/utils/dataMutations';
describe('addErrorsAndRunHooks', () => {
type FullData = Data<'name' | 'age' | 'country'>;
type FullData = ImportedStructuredRow<'name' | 'age' | 'country'>;
const requiredField: Field<'name'> = {
key: 'name',
label: 'Name',
validations: [{ rule: 'required' }],
fieldValidationDefinitions: [{ rule: 'required' }],
icon: null,
fieldType: { type: 'input' },
};
@ -20,7 +20,7 @@ describe('addErrorsAndRunHooks', () => {
const regexField: Field<'age'> = {
key: 'age',
label: 'Age',
validations: [
fieldValidationDefinitions: [
{ rule: 'regex', value: '\\d+', errorMessage: 'Regex error' },
],
icon: null,
@ -30,7 +30,7 @@ describe('addErrorsAndRunHooks', () => {
const uniqueField: Field<'country'> = {
key: 'country',
label: 'Country',
validations: [{ rule: 'unique' }],
fieldValidationDefinitions: [{ rule: 'unique' }],
icon: null,
fieldType: { type: 'input' },
};
@ -38,7 +38,7 @@ describe('addErrorsAndRunHooks', () => {
const functionValidationFieldTrue: Field<'email'> = {
key: 'email',
label: 'Email',
validations: [
fieldValidationDefinitions: [
{
rule: 'function',
isValid: () => true,
@ -52,7 +52,7 @@ describe('addErrorsAndRunHooks', () => {
const functionValidationFieldFalse: Field<'email'> = {
key: 'email',
label: 'Email',
validations: [
fieldValidationDefinitions: [
{
rule: 'function',
isValid: () => false,
@ -63,8 +63,11 @@ describe('addErrorsAndRunHooks', () => {
fieldType: { type: 'input' },
};
const validData: Data<'name' | 'age'> = { name: 'John', age: '30' };
const dataWithoutNameAndInvalidAge: Data<'name' | 'age'> = {
const validData: ImportedStructuredRow<'name' | 'age'> = {
name: 'John',
age: '30',
};
const dataWithoutNameAndInvalidAge: ImportedStructuredRow<'name' | 'age'> = {
name: '',
age: 'Invalid',
};
@ -74,7 +77,7 @@ describe('addErrorsAndRunHooks', () => {
country: 'Brazil',
};
const data: Data<'name' | 'age'>[] = [
const data: ImportedStructuredRow<'name' | 'age'>[] = [
validData,
dataWithoutNameAndInvalidAge,
];
@ -180,7 +183,12 @@ describe('addErrorsAndRunHooks', () => {
it('should not add errors for unique field with empty values if allowEmpty is true', () => {
const result = addErrorsAndRunHooks(
[{ country: '' }, { country: '' }],
[{ ...uniqueField, validations: [{ rule: 'unique', allowEmpty: true }] }],
[
{
...uniqueField,
fieldValidationDefinitions: [{ rule: 'unique', allowEmpty: true }],
},
],
);
expect(result[0].__errors).toBeUndefined();

View File

@ -2,7 +2,7 @@ import {
Column,
ColumnType,
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { Field, Validation } from '@/spreadsheet-import/types';
import { Field, FieldValidationDefinition } from '@/spreadsheet-import/types';
import { findUnmatchedRequiredFields } from '@/spreadsheet-import/utils/findUnmatchedRequiredFields';
const nameField: Field<'Name'> = {
@ -22,9 +22,15 @@ const ageField: Field<'Age'> = {
type: 'input',
},
};
const validations: Validation[] = [{ rule: 'required' }];
const nameFieldWithValidations: Field<'Name'> = { ...nameField, validations };
const ageFieldWithValidations: Field<'Age'> = { ...ageField, validations };
const validations: FieldValidationDefinition[] = [{ rule: 'required' }];
const nameFieldWithValidations: Field<'Name'> = {
...nameField,
fieldValidationDefinitions: validations,
};
const ageFieldWithValidations: Field<'Age'> = {
...ageField,
fieldValidationDefinitions: validations,
};
type ColumnValues = 'Name' | 'Age';

View File

@ -3,11 +3,11 @@ import { v4 } from 'uuid';
import {
Errors,
Meta,
ImportedStructuredRowMetadata,
} from '@/spreadsheet-import/steps/components/ValidationStep/types';
import {
Data,
Fields,
ImportedStructuredRow,
Info,
RowHook,
TableHook,
@ -16,11 +16,11 @@ import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const addErrorsAndRunHooks = <T extends string>(
data: (Data<T> & Partial<Meta>)[],
data: (ImportedStructuredRow<T> & Partial<ImportedStructuredRowMetadata>)[],
fields: Fields<T>,
rowHook?: RowHook<T>,
tableHook?: TableHook<T>,
): (Data<T> & Meta)[] => {
): (ImportedStructuredRow<T> & ImportedStructuredRowMetadata)[] => {
const errors: Errors = {};
const addHookError = (rowIndex: number, fieldKey: T, error: Info) => {
@ -41,8 +41,8 @@ export const addErrorsAndRunHooks = <T extends string>(
}
fields.forEach((field) => {
field.validations?.forEach((validation) => {
switch (validation.rule) {
field.fieldValidationDefinitions?.forEach((fieldValidationDefinition) => {
switch (fieldValidationDefinition.rule) {
case 'unique': {
const values = data.map((entry) => entry[field.key as T]);
@ -51,7 +51,7 @@ export const addErrorsAndRunHooks = <T extends string>(
values.forEach((value) => {
if (
validation.allowEmpty === true &&
fieldValidationDefinition.allowEmpty === true &&
(isUndefinedOrNull(value) || value === '' || !value)
) {
// If allowEmpty is set, we will not validate falsy fields such as undefined or empty string.
@ -70,8 +70,10 @@ export const addErrorsAndRunHooks = <T extends string>(
errors[index] = {
...errors[index],
[field.key]: {
level: validation.level || 'error',
message: validation.errorMessage || 'Field must be unique',
level: fieldValidationDefinition.level || 'error',
message:
fieldValidationDefinition.errorMessage ||
'Field must be unique',
},
};
}
@ -88,8 +90,10 @@ export const addErrorsAndRunHooks = <T extends string>(
errors[index] = {
...errors[index],
[field.key]: {
level: validation.level || 'error',
message: validation.errorMessage || 'Field is required',
level: fieldValidationDefinition.level || 'error',
message:
fieldValidationDefinition.errorMessage ||
'Field is required',
},
};
}
@ -97,7 +101,10 @@ export const addErrorsAndRunHooks = <T extends string>(
break;
}
case 'regex': {
const regex = new RegExp(validation.value, validation.flags);
const regex = new RegExp(
fieldValidationDefinition.value,
fieldValidationDefinition.flags,
);
data.forEach((entry, index) => {
const value = entry[field.key]?.toString();
@ -105,10 +112,10 @@ export const addErrorsAndRunHooks = <T extends string>(
errors[index] = {
...errors[index],
[field.key]: {
level: validation.level || 'error',
level: fieldValidationDefinition.level || 'error',
message:
validation.errorMessage ||
`Field did not match the regex /${validation.value}/${validation.flags} `,
fieldValidationDefinition.errorMessage ||
`Field did not match the regex /${fieldValidationDefinition.value}/${fieldValidationDefinition.flags} `,
},
};
}
@ -119,12 +126,17 @@ export const addErrorsAndRunHooks = <T extends string>(
data.forEach((entry, index) => {
const value = entry[field.key]?.toString();
if (isNonEmptyString(value) && !validation.isValid(value)) {
if (
isNonEmptyString(value) &&
!fieldValidationDefinition.isValid(value)
) {
errors[index] = {
...errors[index],
[field.key]: {
level: validation.level || 'error',
message: validation.errorMessage || 'Field is invalid',
level: fieldValidationDefinition.level || 'error',
message:
fieldValidationDefinition.errorMessage ||
'Field is invalid',
},
};
}
@ -140,7 +152,8 @@ export const addErrorsAndRunHooks = <T extends string>(
if (!('__index' in value)) {
value.__index = v4();
}
const newValue = value as Data<T> & Meta;
const newValue = value as ImportedStructuredRow<T> &
ImportedStructuredRowMetadata;
if (isDefined(errors[index])) {
return { ...newValue, __errors: errors[index] };

View File

@ -8,7 +8,9 @@ export const findUnmatchedRequiredFields = <T extends string>(
fields
.filter(
(field) =>
field.validations?.some((validation) => validation.rule === 'required'),
field.fieldValidationDefinitions?.some(
(validation) => validation.rule === 'required',
),
)
.filter(
(field) =>

View File

@ -2,13 +2,17 @@ import {
Columns,
ColumnType,
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { Data, Fields, RawData } from '@/spreadsheet-import/types';
import {
Fields,
ImportedRow,
ImportedStructuredRow,
} from '@/spreadsheet-import/types';
import { normalizeCheckboxValue } from './normalizeCheckboxValue';
export const normalizeTableData = <T extends string>(
columns: Columns<T>,
data: RawData[],
data: ImportedRow[],
fields: Fields<T>,
) =>
data.map((row) =>
@ -63,5 +67,5 @@ export const normalizeTableData = <T extends string>(
default:
return acc;
}
}, {} as Data<T>),
}, {} as ImportedStructuredRow<T>),
);