Files
twenty_crm/packages/twenty-zapier/src/utils/computeInputFields.ts
T
Paul Rastoin 9ad8287dbc [REFACTOR] twenty-shared multi barrel and CJS/ESM build with preconstruct (#11083)
# Introduction

In this PR we've migrated `twenty-shared` from a `vite` app
[libary-mode](https://vite.dev/guide/build#library-mode) to a
[preconstruct](https://preconstruct.tools/) "atomic" application ( in
the future would like to introduce preconstruct to handle of all our
atomic dependencies such as `twenty-emails` `twenty-ui` etc it will be
integrated at the monorepo's root directly, would be to invasive in the
first, starting incremental via `twenty-shared`)

For more information regarding the motivations please refer to nor:
- https://github.com/twentyhq/core-team-issues/issues/587
-
https://github.com/twentyhq/core-team-issues/issues/281#issuecomment-2630949682

close https://github.com/twentyhq/core-team-issues/issues/589
close https://github.com/twentyhq/core-team-issues/issues/590

## How to test
In order to ease the review this PR will ship all the codegen at the
very end, the actual meaning full diff is `+2,411 −114`
In order to migrate existing dependent packages to `twenty-shared` multi
barrel new arch you need to run in local:
```sh
yarn tsx packages/twenty-shared/scripts/migrateFromSingleToMultiBarrelImport.ts && \
npx nx run-many -t lint --fix -p twenty-front twenty-ui twenty-server twenty-emails twenty-shared twenty-zapier
```
Note that `migrateFromSingleToMultiBarrelImport` is idempotent, it's atm
included in the PR but should not be merged. ( such as codegen will be
added before merging this script will be removed )

## Misc
- related opened issue preconstruct
https://github.com/preconstruct/preconstruct/issues/617

## Closed related PR
- https://github.com/twentyhq/twenty/pull/11028
- https://github.com/twentyhq/twenty/pull/10993
- https://github.com/twentyhq/twenty/pull/10960

## Upcoming enhancement: ( in others dedicated PRs )
- 1/ refactor generate barrel to export atomic module instead of `*`
- 2/ generate barrel own package with several files and tests
- 3/ Migration twenty-ui the same way
- 4/ Use `preconstruct` at monorepo global level

## Conclusion
As always any suggestions are welcomed !
2025-03-22 19:16:06 +01:00

276 lines
8.4 KiB
TypeScript

import { InputField, Node, NodeField } from '../utils/data.types';
import { FieldMetadataType } from 'twenty-shared/types';
const getListFromFieldMetadataType = (fieldMetadataType: FieldMetadataType) => {
return fieldMetadataType === FieldMetadataType.ARRAY;
};
const getTypeFromFieldMetadataType = (
fieldMetadataType: FieldMetadataType,
): string | undefined => {
switch (fieldMetadataType) {
case FieldMetadataType.UUID:
case FieldMetadataType.TEXT:
case FieldMetadataType.RICH_TEXT:
case FieldMetadataType.ARRAY:
case FieldMetadataType.RATING:
return 'string';
case FieldMetadataType.DATE_TIME:
return 'datetime';
case FieldMetadataType.DATE:
return 'date';
case FieldMetadataType.BOOLEAN:
return 'boolean';
case FieldMetadataType.NUMBER:
case FieldMetadataType.POSITION:
return 'integer';
case FieldMetadataType.NUMERIC:
return 'number';
default:
return undefined;
}
};
const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => {
switch (nodeField.type) {
case FieldMetadataType.FULL_NAME: {
const firstName: NodeField = {
type: FieldMetadataType.TEXT,
name: 'firstName',
label: 'First Name',
description: 'First Name',
isNullable: true,
defaultValue: null,
};
const lastName: NodeField = {
type: FieldMetadataType.TEXT,
name: 'lastName',
label: 'Last Name',
description: 'Last Name',
isNullable: true,
defaultValue: null,
};
return [firstName, lastName];
}
case FieldMetadataType.CURRENCY: {
const amountMicros: NodeField = {
type: FieldMetadataType.NUMBER,
name: 'amountMicros',
label: 'Amount Micros',
description: 'Amount Micros. eg: set 3210000 for 3.21$',
isNullable: true,
defaultValue: null,
};
const currencyCode: NodeField = {
type: FieldMetadataType.TEXT,
name: 'currencyCode',
label: 'Currency Code',
description: 'Currency Code. eg: USD, EUR, etc...',
isNullable: true,
defaultValue: null,
};
return [amountMicros, currencyCode];
}
case FieldMetadataType.ADDRESS: {
const address1: NodeField = {
type: FieldMetadataType.TEXT,
name: 'addressStreet1',
label: 'Address',
description: 'Address',
isNullable: true,
defaultValue: null,
};
const address2: NodeField = {
type: FieldMetadataType.TEXT,
name: 'addressStreet2',
label: 'Address 2',
description: 'Address 2',
isNullable: true,
defaultValue: null,
};
const city: NodeField = {
type: FieldMetadataType.TEXT,
name: 'addressCity',
label: 'City',
description: 'City',
isNullable: true,
defaultValue: null,
};
const state: NodeField = {
type: FieldMetadataType.TEXT,
name: 'addressState',
label: 'State',
description: 'State',
isNullable: true,
defaultValue: null,
};
const postalCode: NodeField = {
type: FieldMetadataType.TEXT,
name: 'addressPostalCode',
label: 'Postal Code',
description: 'Postal Code',
isNullable: true,
defaultValue: null,
};
const country: NodeField = {
type: FieldMetadataType.TEXT,
name: 'addressCountry',
label: 'Country',
description: 'Country',
isNullable: true,
defaultValue: null,
};
return [address1, address2, city, state, postalCode, country];
}
case FieldMetadataType.PHONES: {
const primaryPhoneNumber: NodeField = {
type: FieldMetadataType.TEXT,
name: 'primaryPhoneNumber',
label: 'Primary Phone Number',
description: 'Primary Phone Number. 600112233',
isNullable: true,
defaultValue: null,
};
const primaryPhoneCountryCode: NodeField = {
type: FieldMetadataType.TEXT,
name: 'primaryPhoneCountryCode',
label: 'Primary Phone Country Code',
description: 'Primary Phone Country Code. eg: +33',
isNullable: true,
defaultValue: null,
};
const additionalPhones: NodeField = {
type: FieldMetadataType.TEXT,
name: 'additionalPhones',
label: 'Additional Phones',
description: 'Additional Phones',
isNullable: true,
defaultValue: null,
placeholder: '{ number: "", callingCode: "" }',
list: true,
};
return [primaryPhoneNumber, primaryPhoneCountryCode, additionalPhones];
}
case FieldMetadataType.EMAILS: {
const primaryEmail: NodeField = {
type: FieldMetadataType.TEXT,
name: 'primaryEmail',
label: 'Primary Email',
description: 'Primary Email',
isNullable: true,
defaultValue: null,
};
const additionalEmails: NodeField = {
type: FieldMetadataType.TEXT,
name: 'additionalEmails',
label: 'Additional Emails',
description: 'Additional Emails',
list: true,
isNullable: true,
defaultValue: null,
};
return [primaryEmail, additionalEmails];
}
case FieldMetadataType.LINKS: {
const primaryLinkLabel: NodeField = {
type: FieldMetadataType.TEXT,
name: 'primaryLinkLabel',
label: 'Primary Link Label',
description: 'Primary Link Label',
isNullable: true,
defaultValue: null,
};
const primaryLinkUrl: NodeField = {
type: FieldMetadataType.TEXT,
name: 'primaryLinkUrl',
label: 'Primary Link Url',
description: 'Primary Link Url',
isNullable: true,
defaultValue: null,
};
const secondaryLinks: NodeField = {
type: FieldMetadataType.TEXT,
name: 'secondaryLinks',
label: 'Secondary Links',
description: 'Secondary Links',
isNullable: true,
defaultValue: null,
placeholder: '{ url: "", label: "" }',
list: true,
};
return [primaryLinkLabel, primaryLinkUrl, secondaryLinks];
}
default:
throw new Error(`Unknown nodeField type: ${nodeField.type}`);
}
};
const isFieldRequired = (nodeField: NodeField): boolean => {
return !nodeField.isNullable && !nodeField.defaultValue;
};
export const computeInputFields = (
node: Node,
isRequired = false,
): InputField[] => {
const result = [];
for (const field of node.fields.edges) {
const nodeField = field.node;
switch (nodeField.type) {
case FieldMetadataType.FULL_NAME:
case FieldMetadataType.CURRENCY:
case FieldMetadataType.PHONES:
case FieldMetadataType.EMAILS:
case FieldMetadataType.LINKS:
case FieldMetadataType.ADDRESS:
for (const subNodeField of get_subfieldsFromField(nodeField)) {
const field = {
key: `${nodeField.name}__${subNodeField.name}`,
label: `${nodeField.label}: ${subNodeField.label}`,
type: getTypeFromFieldMetadataType(subNodeField.type),
helpText: `${nodeField.description}: ${subNodeField.description}`,
required: isFieldRequired(subNodeField),
list: !!subNodeField.list,
placeholder: subNodeField.placeholder,
} as InputField;
result.push(field);
}
break;
case FieldMetadataType.UUID:
case FieldMetadataType.TEXT:
case FieldMetadataType.RICH_TEXT:
case FieldMetadataType.DATE_TIME:
case FieldMetadataType.DATE:
case FieldMetadataType.BOOLEAN:
case FieldMetadataType.NUMBER:
case FieldMetadataType.NUMERIC:
case FieldMetadataType.POSITION:
case FieldMetadataType.ARRAY:
case FieldMetadataType.RATING: {
const nodeFieldType = getTypeFromFieldMetadataType(nodeField.type);
if (!nodeFieldType) {
break;
}
const required =
(isRequired && nodeField.name === 'id') ||
(!isRequired && isFieldRequired(nodeField));
const field = {
key: nodeField.name,
label: nodeField.label,
type: nodeFieldType,
helpText: nodeField.description,
required,
list: getListFromFieldMetadataType(nodeField.type),
placeholder: undefined,
};
result.push(field);
break;
}
default:
break;
}
}
return result.sort((a, _) => (a.key === 'id' ? -1 : 1));
};