Add composite Emails field and forbid creation of Email field type (#6689)

### Description

1. 
   - We are introducing new field type(Emails)


   - We are Forbiding creation of Email field


   - We Added support for filtering and sorting on Emails field


- We are using the same display mode as used on the Links field type
(chips), check the Domain field of the Company object


   - We are also using the same logic of the link when editing the field

   \
   How To Test\
   Follow the below steps for testing locally:\
   1. Checkout to TWENTY-6261\
2. Reset database using "npx nx database:reset twenty-server" command\
   3. Run both the backend and frontend app\
4. Go to Settings/Data model and choose one of the standard objects like
people\
   5. Click on Add Field button and choose Emails as the field type

   \
   ### Refs

   #6261\
   \
   ### Demo

    \

<https://www.loom.com/share/22979acac8134ed390fef93cc56fe07c?sid=adafba94-840d-4f01-872c-dc9ec256d987>

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
This commit is contained in:
gitstart-app[bot]
2024-08-29 11:42:24 +02:00
committed by GitHub
parent c87ccfa3c7
commit 7a9a43b85c
52 changed files with 866 additions and 318 deletions

View File

@ -205,12 +205,19 @@ const fieldActorMock = {
name: '',
},
};
const fieldEmailsMock = {
name: 'fieldEmails',
type: FieldMetadataType.EMAILS,
isNullable: false,
defaultValue: [{ primaryEmail: '', additionalEmails: {} }],
};
export const fields = [
fieldUuidMock,
fieldTextMock,
fieldPhoneMock,
fieldEmailMock,
fieldEmailsMock,
fieldDateTimeMock,
fieldDateMock,
fieldBooleanMock,

View File

@ -144,5 +144,13 @@ export const mapFieldMetadataToGraphqlQuery = (
name
}
`;
} else if (fieldType === FieldMetadataType.EMAILS) {
return `
${field.name}
{
primaryEmail
additionalEmails
}
`;
}
};

View File

@ -147,6 +147,17 @@ describe('computeSchemaComponents', () => {
},
type: 'object',
},
fieldEmails: {
properties: {
primaryEmail: {
type: 'string',
},
additionalEmails: {
type: 'object',
},
},
type: 'object',
},
},
},
'ObjectName with Relations': {

View File

@ -72,6 +72,7 @@ const getSchemaComponentsProperties = (
case FieldMetadataType.FULL_NAME:
case FieldMetadataType.ADDRESS:
case FieldMetadataType.ACTOR:
case FieldMetadataType.EMAILS:
itemProperty = {
type: 'object',
properties: compositeTypeDefinitions

View File

@ -0,0 +1,26 @@
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
export const emailsCompositeType: CompositeType = {
type: FieldMetadataType.EMAILS,
properties: [
{
name: 'primaryEmail',
type: FieldMetadataType.TEXT,
hidden: false,
isRequired: false,
},
{
name: 'additionalEmails',
type: FieldMetadataType.RAW_JSON,
hidden: false,
isRequired: false,
},
],
};
export type EmailsMetadata = {
primaryEmail: string;
additionalEmails: string[] | null;
};

View File

@ -4,6 +4,7 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada
import { actorCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { addressCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/address.composite-type';
import { currencyCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/currency.composite-type';
import { emailsCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/emails.composite-type';
import { fullNameCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type';
import { linkCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/link.composite-type';
import { linksCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/links.composite-type';
@ -23,4 +24,5 @@ export const compositeTypeDefinitions = new Map<
[FieldMetadataType.FULL_NAME, fullNameCompositeType],
[FieldMetadataType.ADDRESS, addressCompositeType],
[FieldMetadataType.ACTOR, actorCompositeType],
[FieldMetadataType.EMAILS, emailsCompositeType],
]);

View File

@ -175,3 +175,13 @@ export class FieldMetadataDefaultActor {
@IsString()
name: string;
}
export class FieldMetadataDefaultValueEmails {
@ValidateIf((_object, value) => value !== null)
@IsQuotedString()
primaryEmail: string | null;
@ValidateIf((_object, value) => value !== null)
@IsObject()
additionalEmails: string[] | null;
}

View File

@ -26,6 +26,7 @@ export enum FieldMetadataType {
TEXT = 'TEXT',
PHONE = 'PHONE',
EMAIL = 'EMAIL',
EMAILS = 'EMAILS',
DATE_TIME = 'DATE_TIME',
DATE = 'DATE',
BOOLEAN = 'BOOLEAN',

View File

@ -143,6 +143,13 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
);
}
if (fieldMetadataInput.type === FieldMetadataType.EMAIL) {
throw new FieldMetadataException(
'"Email" field types are being deprecated, please use Emails type instead',
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
);
}
this.validateFieldMetadataInput<CreateFieldInput>(
fieldMetadataInput,
objectMetadata,

View File

@ -4,6 +4,7 @@ import {
FieldMetadataDefaultValueBoolean,
FieldMetadataDefaultValueCurrency,
FieldMetadataDefaultValueDateTime,
FieldMetadataDefaultValueEmails,
FieldMetadataDefaultValueFullName,
FieldMetadataDefaultValueLink,
FieldMetadataDefaultValueLinks,
@ -27,6 +28,7 @@ type FieldMetadataDefaultValueMapping = {
[FieldMetadataType.TEXT]: FieldMetadataDefaultValueString;
[FieldMetadataType.PHONE]: FieldMetadataDefaultValueString;
[FieldMetadataType.EMAIL]: FieldMetadataDefaultValueString;
[FieldMetadataType.EMAILS]: FieldMetadataDefaultValueEmails;
[FieldMetadataType.DATE_TIME]:
| FieldMetadataDefaultValueDateTime
| FieldMetadataDefaultValueNowFunction;

View File

@ -10,6 +10,11 @@ export function generateDefaultValue(
case FieldMetadataType.PHONE:
case FieldMetadataType.EMAIL:
return "''";
case FieldMetadataType.EMAILS:
return {
primaryEmail: "''",
additionalEmails: null,
};
case FieldMetadataType.FULL_NAME:
return {
firstName: "''",

View File

@ -8,7 +8,8 @@ export const isCompositeFieldMetadataType = (
| FieldMetadataType.FULL_NAME
| FieldMetadataType.ADDRESS
| FieldMetadataType.LINKS
| FieldMetadataType.ACTOR => {
| FieldMetadataType.ACTOR
| FieldMetadataType.EMAILS => {
return [
FieldMetadataType.LINK,
FieldMetadataType.CURRENCY,
@ -16,5 +17,6 @@ export const isCompositeFieldMetadataType = (
FieldMetadataType.ADDRESS,
FieldMetadataType.LINKS,
FieldMetadataType.ACTOR,
FieldMetadataType.EMAILS,
].includes(type);
};

View File

@ -13,6 +13,7 @@ import {
FieldMetadataDefaultValueCurrency,
FieldMetadataDefaultValueDate,
FieldMetadataDefaultValueDateTime,
FieldMetadataDefaultValueEmails,
FieldMetadataDefaultValueFullName,
FieldMetadataDefaultValueLink,
FieldMetadataDefaultValueLinks,
@ -53,6 +54,7 @@ export const defaultValueValidatorsMap = {
[FieldMetadataType.RAW_JSON]: [FieldMetadataDefaultValueRawJson],
[FieldMetadataType.LINKS]: [FieldMetadataDefaultValueLinks],
[FieldMetadataType.ACTOR]: [FieldMetadataDefaultActor],
[FieldMetadataType.EMAILS]: [FieldMetadataDefaultValueEmails],
};
type ValidationResult = {

View File

@ -23,7 +23,8 @@ export type CompositeFieldMetadataType =
| FieldMetadataType.CURRENCY
| FieldMetadataType.FULL_NAME
| FieldMetadataType.LINK
| FieldMetadataType.LINKS;
| FieldMetadataType.LINKS
| FieldMetadataType.EMAILS;
@Injectable()
export class CompositeColumnActionFactory extends ColumnActionAbstractFactory<CompositeFieldMetadataType> {

View File

@ -97,6 +97,10 @@ export class WorkspaceMigrationFactory {
],
[FieldMetadataType.LINKS, { factory: this.compositeColumnActionFactory }],
[FieldMetadataType.ACTOR, { factory: this.compositeColumnActionFactory }],
[
FieldMetadataType.EMAILS,
{ factory: this.compositeColumnActionFactory },
],
]);
}