Handle migration of Phone field to Phones field (#7128)

This PR was created by [GitStart](https://gitstart.com/) to address the
requirements from this ticket:
[TWNTY-6260](https://clients.gitstart.com/twenty/5449/tickets/TWNTY-6260).
This ticket was imported from:
[TWNTY-6260](https://github.com/twentyhq/twenty/issues/6260)

 --- 

### Description

This is the second PR on TWNTY-6260 which handles data migration of
Phone field to Phones field.\
\
How to Test?\
 Follow the below steps:

- On the main branch, 
- go to
`packages/twenty-server/src/database/typeorm-seeds/workspace/people.ts`
and change any person's phone number to a string with characters for
example: "test invalid phone", and then reset the DB.
  - reset database using `npx nx database:reset twenty-server`
- This is to make sure that invalid numbers will be handled properly. We
should use the invalid value itself to avoid removing data and see how
the behavior is on the front end. should be the same as in the main, the
display shows the invalid value, but the input is empty when you click,
and then you can update.
- Checkout to `TWNTY-6260-phone-migration` branch
- Rebuild typescript using `npx nx build twenty-server`
- Run command `yarn command:prod upgrade-0.32` to do migration
- Run both backend and frontend to see the migrated field

### Demo

- **Loom Video:**\

<https://www.loom.com/share/4b9bcb423cee447d8ad09852a83b27da?sid=ed74ecaa-0339-4575-acdc-a863e95e94fd>

### Refs

#6260

---------

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: Marie Stoppa <marie.stoppa@essec.edu>
Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
gitstart-app[bot]
2024-09-24 16:31:30 +02:00
committed by GitHub
parent b83f0f46e5
commit fa241fa4e9
25 changed files with 709 additions and 78 deletions

View File

@ -197,6 +197,10 @@ name
lastName
}
phone
{
primaryPhoneNumber
primaryPhoneCountryCode
}
linkedinLink
{
primaryLinkUrl

View File

@ -46,7 +46,11 @@ describe('mapObjectMetadataToGraphQLQuery', () => {
primaryEmail
additionalEmails
}
phone
phone
{
primaryPhoneNumber
primaryPhoneCountryCode
}
createdAt
avatarUrl
jobTitle

View File

@ -107,7 +107,7 @@ export const getObjectMetadataItemsMock = () => {
{
"__typename": "field",
"id": "194ff398-99f9-4cbb-b87a-e44408f9c1ed",
"type": "PHONE",
"type": "PHONES",
"name": "whatsapp",
"label": "Whatsapp",
"description": "Contact's Whatsapp Number",
@ -614,7 +614,7 @@ export const getObjectMetadataItemsMock = () => {
{
"__typename": "field",
"id": "9c2bf923-304d-47b7-beb0-286e3229f6ac",
"type": "TEXT",
"type": "PHONES",
"name": "phone",
"label": "Phone",
"description": "Contacts phone number",

View File

@ -2,7 +2,11 @@ export const PERSON_FRAGMENT = `
__typename
updatedAt
myCustomObjectId
whatsapp
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
additionalPhones
}
linkedinLink {
primaryLinkUrl
primaryLinkLabel
@ -28,7 +32,11 @@ export const PERSON_FRAGMENT = `
}
performanceRating
createdAt
phone
phone {
primaryPhoneNumber
primaryPhoneCountryCode
additionalPhones
}
id
city
companyId

View File

@ -36,7 +36,10 @@ export const responseData = {
firstName: '',
lastName: '',
},
phone: '',
phones: {
primaryPhoneCountryCode: '',
primaryPhoneNumber: '',
},
linkedinLink: {
primaryLinkUrl: '',
primaryLinkLabel: '',

View File

@ -41,7 +41,10 @@ export const responseData = {
firstName: '',
lastName: '',
},
phone: '',
phones: {
primaryPhoneCountryCode: '',
primaryPhoneNumber: '',
},
linkedinLink: {
primaryLinkUrl: '',
primaryLinkLabel: '',

View File

@ -24,7 +24,6 @@ const basePerson = {
firstName: '',
lastName: '',
},
phone: '',
linkedinLink: {
primaryLinkUrl: '',
primaryLinkLabel: '',

View File

@ -1,6 +1,6 @@
import { ReactNode } from 'react';
import { MockedProvider } from '@apollo/client/testing';
import { act, renderHook } from '@testing-library/react';
import { ReactNode } from 'react';
import { RecoilRoot } from 'recoil';
import {
@ -11,8 +11,17 @@ import {
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
const person = { id: '36abbb63-34ed-4a16-89f5-f549ac55d0f9' };
const update = { name: { firstName: 'John', lastName: 'Doe' } };
const updatePerson = { ...person, ...responseData, ...update };
const update = {
name: {
firstName: 'John',
lastName: 'Doe',
},
};
const updatePerson = {
...person,
...responseData,
...update,
};
const mocks = [
{

View File

@ -6,10 +6,12 @@ import { isFieldAddress } from '@/object-record/record-field/types/guards/isFiel
import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency';
import { isFieldDateTime } from '@/object-record/record-field/types/guards/isFieldDateTime';
import { isFieldEmail } from '@/object-record/record-field/types/guards/isFieldEmail';
import { isFieldEmails } from '@/object-record/record-field/types/guards/isFieldEmails';
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
import { isFieldLink } from '@/object-record/record-field/types/guards/isFieldLink';
import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks';
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
import { isFieldPhones } from '@/object-record/record-field/types/guards/isFieldPhones';
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid';
@ -66,5 +68,17 @@ export const computeDraftValueFromString = <FieldValue>({
} as FieldInputDraftValue<FieldValue>;
}
if (isFieldEmails(fieldDefinition)) {
return {
primaryEmail: value,
} as FieldInputDraftValue<FieldValue>;
}
if (isFieldPhones(fieldDefinition)) {
return {
primaryPhoneNumber: value,
} as FieldInputDraftValue<FieldValue>;
}
throw new Error(`Record field type not supported : ${fieldDefinition.type}}`);
};

View File

@ -1,4 +1,5 @@
import { act, renderHook, waitFor } from '@testing-library/react';
import { ReactNode } from 'react';
import { percentage, sleep, useTableData } from '../useTableData';
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
@ -9,7 +10,6 @@ import { extractComponentState } from '@/ui/utilities/state/component-state/util
import { ViewType } from '@/views/types/ViewType';
import { MockedProvider, MockedResponse } from '@apollo/client/testing';
import gql from 'graphql-tag';
import { ReactNode } from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { RecoilRoot, useRecoilValue } from 'recoil';
@ -26,7 +26,11 @@ const mockPerson = {
__typename: 'Person',
updatedAt: '2021-08-03T19:20:06.000Z',
myCustomObjectId: '123',
whatsapp: '123',
whatsapp: {
primaryPhoneNumber: '+1',
primaryPhoneCountryCode: '234-567-890',
additionalPhones: [],
},
linkedinLink: {
primaryLinkUrl: 'https://www.linkedin.com',
primaryLinkLabel: 'linkedin',
@ -52,7 +56,11 @@ const mockPerson = {
},
performanceRating: 1,
createdAt: '2021-08-03T19:20:06.000Z',
phone: 'phone',
phone: {
primaryPhoneNumber: '+1',
primaryPhoneCountryCode: '234-567-890',
additionalPhones: [],
},
id: '123',
city: 'city',
companyId: '1',
@ -80,7 +88,11 @@ const mocks: MockedResponse[] = [
__typename
updatedAt
myCustomObjectId
whatsapp
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
additionalPhones
}
linkedinLink {
primaryLinkUrl
primaryLinkLabel
@ -106,7 +118,11 @@ const mocks: MockedResponse[] = [
}
performanceRating
createdAt
phone
phone {
primaryPhoneNumber
primaryPhoneCountryCode
additionalPhones
}
id
city
companyId

View File

@ -662,7 +662,10 @@ export const mockPerformance = {
},
id: '20202020-2d40-4e49-8df4-9c6a049191df',
email: 'lorie.vladim@google.com',
phone: '+33788901235',
phones: {
primaryPhoneCountryCode: '+33',
primaryPhoneNumber: '788901235',
},
linkedinLink: {
__typename: 'Link',
primaryLinkLabel: '',

View File

@ -56,16 +56,27 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
],
);
const parsePhoneNumberOrReturnInvalidValue = (number: string) => {
try {
return { parsedPhone: parsePhoneNumber(number) };
} catch (e) {
return { invalidPhone: number };
}
};
return isFocused ? (
<ExpandableList isChipCountDisplayed>
{phones.map(({ number, countryCode }, index) => {
const parsedPhone = parsePhoneNumber(countryCode + number);
const URI = parsedPhone.getURI();
const { parsedPhone, invalidPhone } =
parsePhoneNumberOrReturnInvalidValue(countryCode + number);
const URI = parsedPhone?.getURI();
return (
<RoundedLink
key={index}
href={URI}
label={parsedPhone.formatInternational()}
href={URI || ''}
label={
parsedPhone ? parsedPhone.formatInternational() : invalidPhone
}
/>
);
})}
@ -73,13 +84,16 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
) : (
<StyledContainer>
{phones.map(({ number, countryCode }, index) => {
const parsedPhone = parsePhoneNumber(countryCode + number);
const URI = parsedPhone.getURI();
const { parsedPhone, invalidPhone } =
parsePhoneNumberOrReturnInvalidValue(countryCode + number);
const URI = parsedPhone?.getURI();
return (
<RoundedLink
key={index}
href={URI}
label={parsedPhone.formatInternational()}
href={URI || ''}
label={
parsedPhone ? parsedPhone.formatInternational() : invalidPhone
}
/>
);
})}

View File

@ -45,7 +45,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:52:46.814Z',
city: 'ASd',
phone: '',
phones: {
primaryPhoneNumber: '781234562',
primaryPhoneCountryCode: '+33',
},
id: 'da3c2c4b-da01-4b81-9734-226069eb4cd0',
jobTitle: '',
position: 0,
@ -172,7 +175,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-01T09:50:00.000Z',
city: 'Seattle',
phone: '+33789012345',
phones: {
primaryPhoneNumber: '781234562',
primaryPhoneCountryCode: '+33',
},
id: '20202020-1c0e-494c-a1b6-85b1c6fefaa5',
jobTitle: '',
position: 1,
@ -299,7 +305,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'Los Angeles',
phone: '+33780123456',
phones: {
primaryPhoneNumber: '781234576',
primaryPhoneCountryCode: '+33',
},
id: '20202020-ac73-4797-824e-87a1f5aea9e0',
jobTitle: '',
position: 2,
@ -395,7 +404,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'Seattle',
phone: '+33789012345',
phones: {
primaryPhoneNumber: '781234545',
primaryPhoneCountryCode: '+33',
},
id: '20202020-f517-42fd-80ae-14173b3b70ae',
jobTitle: '',
position: 3,
@ -491,7 +503,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'Los Angeles',
phone: '+33780123456',
phones: {
primaryPhoneNumber: '781234587',
primaryPhoneCountryCode: '+33',
},
id: '20202020-eee1-4690-ad2c-8619e5b56a2e',
jobTitle: '',
position: 4,
@ -587,7 +602,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'Seattle',
phone: '+33781234567',
phones: {
primaryPhoneNumber: '781234599',
primaryPhoneCountryCode: '+33',
},
id: '20202020-6784-4449-afdf-dc62cb8702f2',
jobTitle: '',
position: 5,
@ -683,7 +701,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'New York',
phone: '+33782345678',
phones: {
primaryPhoneNumber: '781234572',
primaryPhoneCountryCode: '+33',
},
id: '20202020-490f-4466-8391-733cfd66a0c8',
jobTitle: '',
position: 6,
@ -779,7 +800,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'Seattle',
phone: '+33783456789',
phones: {
primaryPhoneNumber: '781234582',
primaryPhoneCountryCode: '+33',
},
id: '20202020-80f1-4dff-b570-a74942528de3',
jobTitle: '',
position: 7,
@ -875,7 +899,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'New York',
phone: '+33784567890',
phones: {
primaryPhoneNumber: '781234569',
primaryPhoneCountryCode: '+33',
},
id: '20202020-338b-46df-8811-fa08c7d19d35',
jobTitle: '',
position: 8,
@ -971,7 +998,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'San Francisco',
phone: '+33785678901',
phones: {
primaryPhoneNumber: '781234962',
primaryPhoneCountryCode: '+33',
},
id: '20202020-64ad-4b0e-bbfd-e9fd795b7016',
jobTitle: '',
position: 9,
@ -1067,7 +1097,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'New York',
phone: '+33786789012',
phones: {
primaryPhoneNumber: '781234502',
primaryPhoneCountryCode: '+33',
},
id: '20202020-5d54-41b7-ba36-f0d20e1417ae',
jobTitle: '',
position: 10,
@ -1163,7 +1196,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'Los Angeles',
phone: '+33787890123',
phones: {
primaryPhoneNumber: '781234563',
primaryPhoneCountryCode: '+33',
},
id: '20202020-623d-41fe-92e7-dd45b7c568e1',
jobTitle: '',
position: 11,
@ -1259,7 +1295,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'Seattle',
phone: '+33788901234',
phones: {
primaryPhoneNumber: '781234542',
primaryPhoneCountryCode: '+33',
},
id: '20202020-2d40-4e49-8df4-9c6a049190ef',
jobTitle: '',
position: 12,
@ -1355,7 +1394,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'Seattle',
phone: '+33788901234',
phones: {
primaryPhoneNumber: '782234562',
primaryPhoneCountryCode: '+33',
},
id: '20202020-2d40-4e49-8df4-9c6a049190df',
jobTitle: '',
position: 13,
@ -1451,7 +1493,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'Seattle',
phone: '+33788901234',
phones: {
primaryPhoneNumber: '781274562',
primaryPhoneCountryCode: '+33',
},
id: '20202020-2d40-4e49-8df4-9c6a049191de',
jobTitle: '',
position: 14,
@ -1547,7 +1592,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:48:36.193Z',
city: 'Seattle',
phone: '+33788901235',
phones: {
primaryPhoneNumber: '781239562',
primaryPhoneCountryCode: '+33',
},
id: '20202020-2d40-4e49-8df4-9c6a049191df',
jobTitle: '',
position: 15,

View File

@ -9,6 +9,7 @@ import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-wo
import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question';
import { UpgradeTo0_30CommandModule } from 'src/database/commands/upgrade-version/0-30/0-30-upgrade-version.module';
import { UpgradeTo0_31CommandModule } from 'src/database/commands/upgrade-version/0-31/0-31-upgrade-version.module';
import { UpgradeTo0_32CommandModule } from 'src/database/commands/upgrade-version/0-32/0-32-upgrade-version.module';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
@ -50,6 +51,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
UpgradeTo0_30CommandModule,
UpgradeTo0_31CommandModule,
FeatureFlagModule,
UpgradeTo0_32CommandModule,
],
providers: [
DataSeedWorkspaceCommand,

View File

@ -0,0 +1,338 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import { isDefined, isEmpty } from 'class-validator';
import { parsePhoneNumber } from 'libphonenumber-js';
import { Command } from 'nest-commander';
import { DataSource, QueryRunner, Repository } from 'typeorm';
import {
ActiveWorkspacesCommandOptions,
ActiveWorkspacesCommandRunner,
} from 'src/database/commands/active-workspaces.command';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import {
FieldMetadataEntity,
FieldMetadataType,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
import { PERSON_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { ViewService } from 'src/modules/view/services/view.service';
import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity';
type MigratePhoneFieldsToPhonesCommandOptions = ActiveWorkspacesCommandOptions;
@Command({
name: 'upgrade-0.32:migrate-phone-fields-to-phones',
description: 'Migrating fields of deprecated type PHONE to type PHONES',
})
export class MigratePhoneFieldsToPhonesCommand extends ActiveWorkspacesCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
@InjectDataSource('metadata')
private readonly metadataDataSource: DataSource,
private readonly fieldMetadataService: FieldMetadataService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly typeORMService: TypeORMService,
private readonly dataSourceService: DataSourceService,
private readonly viewService: ViewService,
) {
super(workspaceRepository);
}
async executeActiveWorkspacesCommand(
_passedParam: string[],
_options: MigratePhoneFieldsToPhonesCommandOptions,
workspaceIds: string[],
): Promise<void> {
this.logger.log(
'Running command to migrate phone type fields to phones type',
);
for (const workspaceId of workspaceIds) {
this.logger.log(`Running command for workspace ${workspaceId}`);
try {
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId(
workspaceId,
);
if (!dataSourceMetadata) {
throw new Error(
`Could not find dataSourceMetadata for workspace ${workspaceId}`,
);
}
const workspaceDataSource =
await this.typeORMService.connectToDataSource(dataSourceMetadata);
if (!workspaceDataSource) {
throw new Error(
`Could not connect to dataSource for workspace ${workspaceId}`,
);
}
const standardPersonPhoneFieldWithTextType =
await this.fieldMetadataRepository.findOneBy({
workspaceId,
standardId: PERSON_STANDARD_FIELD_IDS.phone,
});
if (!standardPersonPhoneFieldWithTextType) {
throw new Error(
`Could not find standard phone field on person for workspace ${workspaceId}`,
);
}
await this.migrateStandardPersonPhoneField({
standardPersonPhoneField: standardPersonPhoneFieldWithTextType,
workspaceDataSource,
workspaceSchemaName: dataSourceMetadata.schema,
});
const fieldsWithPhoneType = await this.fieldMetadataRepository.find({
where: {
workspaceId,
type: FieldMetadataType.PHONE,
},
});
for (const deprecatedPhoneField of fieldsWithPhoneType) {
await this.migrateCustomPhoneField({
phoneField: deprecatedPhoneField,
workspaceDataSource,
workspaceSchemaName: dataSourceMetadata.schema,
});
}
} catch (error) {
this.logger.log(
chalk.red(
`Field migration on workspace ${workspaceId} failed with error: ${error}`,
),
);
continue;
}
this.logger.log(chalk.green(`Command completed!`));
}
}
private async migrateStandardPersonPhoneField({
standardPersonPhoneField,
workspaceDataSource,
workspaceSchemaName,
}: {
standardPersonPhoneField: FieldMetadataEntity;
workspaceDataSource: DataSource;
workspaceSchemaName: string;
}) {
const personObjectMetadata = await this.objectMetadataRepository.findOne({
where: { id: standardPersonPhoneField.objectMetadataId },
});
if (!personObjectMetadata) {
throw new Error(
`Could not find Person objectMetadata (id ${standardPersonPhoneField.objectMetadataId})`,
);
}
this.logger.log(`Attempting to migrate standard person phone field.`);
const workspaceQueryRunner = workspaceDataSource.createQueryRunner();
await workspaceQueryRunner.connect();
const { id: _id, ...deprecatedPhoneFieldWithoutId } =
standardPersonPhoneField;
const workspaceId = standardPersonPhoneField.workspaceId;
try {
let standardPersonPhonesFieldType =
await this.fieldMetadataRepository.findOneBy({
workspaceId,
standardId: PERSON_STANDARD_FIELD_IDS.phones,
});
if (!standardPersonPhonesFieldType) {
standardPersonPhonesFieldType =
await this.fieldMetadataService.createOne({
...deprecatedPhoneFieldWithoutId,
type: FieldMetadataType.PHONES,
defaultValue: null,
name: 'phones',
} satisfies CreateFieldInput);
}
// Copy phone data from Text type to Phones type
await this.copyAndParseDeprecatedPhoneFieldDataIntoNewPhonesField({
workspaceQueryRunner,
workspaceSchemaName,
});
// Add new phones field to views and hide deprecated phone field
const viewFieldRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewFieldWorkspaceEntity>(
workspaceId,
'viewField',
);
const viewFieldsWithDeprecatedPhoneField = await viewFieldRepository.find(
{
where: {
fieldMetadataId: standardPersonPhoneField.id,
isVisible: true,
},
},
);
await this.viewService.addFieldToViews({
workspaceId: workspaceId,
fieldId: standardPersonPhonesFieldType.id,
viewsIds: viewFieldsWithDeprecatedPhoneField
.filter((viewField) => viewField.viewId !== null)
.map((viewField) => viewField.viewId as string),
positions: viewFieldsWithDeprecatedPhoneField.reduce(
(acc, viewField) => {
if (!viewField.viewId) {
return acc;
}
acc[viewField.viewId] = viewField.position;
return acc;
},
[],
),
});
await this.viewService.removeFieldFromViews({
workspaceId: workspaceId,
fieldId: standardPersonPhoneField.id,
});
this.logger.log(
`Migration of standard person phone field to phones is done!`,
);
} catch (error) {
this.logger.log(
chalk.red(
`Failed to migrate field standard person phone field to phones, rolling back. (Error: ${error})`,
),
);
// Delete new phones field if it was created
const newPhonesField =
await this.fieldMetadataService.findOneWithinWorkspace(workspaceId, {
where: {
name: 'phones',
objectMetadataId: standardPersonPhoneField.objectMetadataId,
},
});
if (newPhonesField) {
this.logger.log(
`Deleting phones field of type Phone as part of rolling back.`,
);
await this.fieldMetadataService.deleteOneField(
{ id: newPhonesField.id },
workspaceId,
);
}
} finally {
await workspaceQueryRunner.release();
}
}
private async migrateCustomPhoneField({
phoneField,
workspaceDataSource,
workspaceSchemaName,
}: {
phoneField: FieldMetadataEntity;
workspaceDataSource: DataSource;
workspaceSchemaName: string;
}) {
if (!phoneField) return;
const objectMetadata = await this.objectMetadataRepository.findOne({
where: { id: phoneField.objectMetadataId },
});
if (!objectMetadata) {
throw new Error(
`Could not find objectMetadata for field ${phoneField.name}`,
);
}
this.logger.log(
`Attempting to migrate field ${phoneField.name} on ${objectMetadata.nameSingular} from Phone to Text.`,
);
const workspaceQueryRunner = workspaceDataSource.createQueryRunner();
await workspaceQueryRunner.connect();
try {
await this.metadataDataSource.query(
`UPDATE "metadata"."fieldMetadata" SET "type" = $1 where "id"=$2`,
[FieldMetadataType.TEXT, phoneField.id],
);
await workspaceQueryRunner.query(
`ALTER TABLE "${workspaceSchemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}" ALTER COLUMN "${computeColumnName(phoneField.name)}" TYPE TEXT`,
);
} catch (error) {
this.logger.log(
chalk.red(
`Failed to migrate field ${phoneField.name} on ${objectMetadata.nameSingular} from Phone to Text.`,
),
);
} finally {
await workspaceQueryRunner.release();
}
}
private async copyAndParseDeprecatedPhoneFieldDataIntoNewPhonesField({
workspaceQueryRunner,
workspaceSchemaName,
}: {
workspaceQueryRunner: QueryRunner;
workspaceSchemaName: string;
}) {
const deprecatedPhoneFieldRows = await workspaceQueryRunner.query(
`SELECT id, phone FROM "${workspaceSchemaName}"."person" WHERE
phone IS NOT null`,
);
for (const row of deprecatedPhoneFieldRows) {
const phoneColumnValue = row['phone'];
if (isDefined(phoneColumnValue) && !isEmpty(phoneColumnValue)) {
const query = `UPDATE "${workspaceSchemaName}"."person" SET "phonesPrimaryPhoneCountryCode" = $1,"phonesPrimaryPhoneNumber" = $2 where "id"=$3 AND ("phonesPrimaryPhoneCountryCode" IS NULL OR "phonesPrimaryPhoneCountryCode" = '');`;
try {
const parsedPhoneColumnValue = parsePhoneNumber(phoneColumnValue);
await workspaceQueryRunner.query(query, [
`+${parsedPhoneColumnValue.countryCallingCode}`,
parsedPhoneColumnValue.nationalNumber,
row.id,
]);
} catch (error) {
this.logger.log(
chalk.red(
`Could not save phone number ${phoneColumnValue}, will try again storing value as is without parsing, with default country code.`,
),
);
// Store the invalid string for invalid phone numbers
await workspaceQueryRunner.query(query, [
'',
phoneColumnValue,
row.id,
]);
}
}
}
}
}

View File

@ -0,0 +1,46 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import {
ActiveWorkspacesCommandOptions,
ActiveWorkspacesCommandRunner,
} from 'src/database/commands/active-workspaces.command';
import { MigratePhoneFieldsToPhonesCommand } from 'src/database/commands/upgrade-version/0-32/0-32-migrate-phone-fields-to-phones.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
type UpdateTo0_32CommandOptions = ActiveWorkspacesCommandOptions;
@Command({
name: 'upgrade-0.32',
description: 'Upgrade to 0.32',
})
export class UpgradeTo0_32Command extends ActiveWorkspacesCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
private readonly migratePhoneFieldsToPhones: MigratePhoneFieldsToPhonesCommand,
) {
super(workspaceRepository);
}
async executeActiveWorkspacesCommand(
_passedParam: string[],
options: UpdateTo0_32CommandOptions,
workspaceIds: string[],
): Promise<void> {
await this.syncWorkspaceMetadataCommand.executeActiveWorkspacesCommand(
_passedParam,
{ ...options, force: true },
workspaceIds,
);
await this.migratePhoneFieldsToPhones.executeActiveWorkspacesCommand(
_passedParam,
options,
workspaceIds,
);
}
}

View File

@ -0,0 +1,32 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { MigratePhoneFieldsToPhonesCommand } from 'src/database/commands/upgrade-version/0-32/0-32-migrate-phone-fields-to-phones.command';
import { UpgradeTo0_32Command } from 'src/database/commands/upgrade-version/0-32/0-32-upgrade-version.command';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module';
import { ViewModule } from 'src/modules/view/view.module';
@Module({
imports: [
TypeOrmModule.forFeature([Workspace], 'core'),
WorkspaceSyncMetadataCommandsModule,
DataSourceModule,
WorkspaceMetadataVersionModule,
FieldMetadataModule,
TypeOrmModule.forFeature(
[FieldMetadataEntity, ObjectMetadataEntity],
'metadata',
),
TypeORMModule,
ViewModule,
],
providers: [UpgradeTo0_32Command, MigratePhoneFieldsToPhonesCommand],
})
export class UpgradeTo0_32CommandModule {}

View File

@ -92,7 +92,7 @@ export const getDevSeedPeopleCustomFields = (
},
{
workspaceId,
type: FieldMetadataType.PHONE,
type: FieldMetadataType.PHONES,
name: 'whatsapp',
label: 'Whatsapp',
description: "Contact's Whatsapp Number",

View File

@ -34,12 +34,14 @@ export const seedPeople = async (
'id',
'nameFirstName',
'nameLastName',
'phone',
'phonesPrimaryPhoneCountryCode',
'phonesPrimaryPhoneNumber',
'city',
'companyId',
'emailsPrimaryEmail',
'position',
'whatsapp',
'whatsappPrimaryPhoneCountryCode',
'whatsappPrimaryPhoneNumber',
'createdBySource',
'createdByWorkspaceMemberId',
'createdByName',
@ -50,12 +52,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.CHRISTOPH,
nameFirstName: 'Christoph',
nameLastName: 'Callisto',
phone: '+33789012345',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '789012345',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.LINKEDIN,
emailsPrimaryEmail: 'christoph.calisto@linkedin.com',
position: 1,
whatsapp: '+33789012345',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '789012345',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -64,12 +68,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.SYLVIE,
nameFirstName: 'Sylvie',
nameLastName: 'Palmer',
phone: '+33780123456',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '780123456',
city: 'Los Angeles',
companyId: DEV_SEED_COMPANY_IDS.LINKEDIN,
emailsPrimaryEmail: 'sylvie.palmer@linkedin.com',
position: 2,
whatsapp: '+33780123456',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '780123456',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -78,12 +84,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.CHRISTOPHER_G,
nameFirstName: 'Christopher',
nameLastName: 'Gonzalez',
phone: '+33789012345',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '789012345',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.QONTO,
emailsPrimaryEmail: 'christopher.gonzalez@qonto.com',
position: 3,
whatsapp: '+33789012345',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '789012345',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -92,12 +100,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.ASHLEY,
nameFirstName: 'Ashley',
nameLastName: 'Parker',
phone: '+33780123456',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '780123456',
city: 'Los Angeles',
companyId: DEV_SEED_COMPANY_IDS.QONTO,
emailsPrimaryEmail: 'ashley.parker@qonto.com',
position: 4,
whatsapp: '+33780123456',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '780123456',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -106,12 +116,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.NICHOLAS,
nameFirstName: 'Nicholas',
nameLastName: 'Wright',
phone: '+33781234567',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '781234567',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.MICROSOFT,
emailsPrimaryEmail: 'nicholas.wright@microsoft.com',
position: 5,
whatsapp: '+33781234567',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '781234567',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -120,12 +132,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.ISABELLA,
nameFirstName: 'Isabella',
nameLastName: 'Scott',
phone: '+33782345678',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '782345678',
city: 'New York',
companyId: DEV_SEED_COMPANY_IDS.MICROSOFT,
emailsPrimaryEmail: 'isabella.scott@microsoft.com',
position: 6,
whatsapp: '+33782345678',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '782345678',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -134,12 +148,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.MATTHEW,
nameFirstName: 'Matthew',
nameLastName: 'Green',
phone: '+33783456789',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '783456789',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.MICROSOFT,
emailsPrimaryEmail: 'matthew.green@microsoft.com',
position: 7,
whatsapp: '+33783456789',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '783456789',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -148,12 +164,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.ELIZABETH,
nameFirstName: 'Elizabeth',
nameLastName: 'Baker',
phone: '+33784567890',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '784567890',
city: 'New York',
companyId: DEV_SEED_COMPANY_IDS.AIRBNB,
emailsPrimaryEmail: 'elizabeth.baker@airbnb.com',
position: 8,
whatsapp: '+33784567890',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '784567890',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -162,12 +180,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.CHRISTOPHER_N,
nameFirstName: 'Christopher',
nameLastName: 'Nelson',
phone: '+33785678901',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '785678901',
city: 'San Francisco',
companyId: DEV_SEED_COMPANY_IDS.AIRBNB,
emailsPrimaryEmail: 'christopher.nelson@airbnb.com',
position: 9,
whatsapp: '+33785678901',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '785678901',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -176,12 +196,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.AVERY,
nameFirstName: 'Avery',
nameLastName: 'Carter',
phone: '+33786789012',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '786789012',
city: 'New York',
companyId: DEV_SEED_COMPANY_IDS.AIRBNB,
emailsPrimaryEmail: 'avery.carter@airbnb.com',
position: 10,
whatsapp: '+33786789012',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '786789012',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -190,12 +212,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.ETHAN,
nameFirstName: 'Ethan',
nameLastName: 'Mitchell',
phone: '+33787890123',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '787890123',
city: 'Los Angeles',
companyId: DEV_SEED_COMPANY_IDS.GOOGLE,
emailsPrimaryEmail: 'ethan.mitchell@google.com',
position: 11,
whatsapp: '+33787890123',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '787890123',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -204,12 +228,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.MADISON,
nameFirstName: 'Madison',
nameLastName: 'Perez',
phone: '+33788901234',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '788901234',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.GOOGLE,
emailsPrimaryEmail: 'madison.perez@google.com',
position: 12,
whatsapp: '+33788901234',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '788901234',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -218,12 +244,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.BERTRAND,
nameFirstName: 'Bertrand',
nameLastName: 'Voulzy',
phone: '+33788901234',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '788901234',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.GOOGLE,
emailsPrimaryEmail: 'bertrand.voulzy@google.com',
position: 13,
whatsapp: '+33788901234',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '788901234',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -232,12 +260,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.LOUIS,
nameFirstName: 'Louis',
nameLastName: 'Duss',
phone: '+33788901234',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '789012345',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.GOOGLE,
emailsPrimaryEmail: 'louis.duss@google.com',
position: 14,
whatsapp: '+33788901234',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '789012345',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
createdByName: 'Tim Apple',
@ -246,12 +276,14 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.LORIE,
nameFirstName: 'Lorie',
nameLastName: 'Vladim',
phone: '+33788901235',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '788901235',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.GOOGLE,
emailsPrimaryEmail: 'lorie.vladim@google.com',
position: 15,
whatsapp: '+33788901235',
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneNumber: '788901235',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: null,
createdByName: '',

View File

@ -1,5 +1,11 @@
import { EntityManager } from 'typeorm';
export const AIRBNB_ID = 'c776ee49-f608-4a77-8cc8-6fe96ae1e43f';
export const QONTO_ID = 'f45ee421-8a3e-4aa5-a1cf-7207cc6754e1';
export const STRIPE_ID = '1f70157c-4ea5-4d81-bc49-e1401abfbb94';
export const FIGMA_ID = '9d5bcf43-7d38-4e88-82cb-d6d4ce638bf0';
export const NOTION_ID = '06290608-8bf0-4806-99ae-a715a6a93fad';
export const companyPrefillData = async (
entityManager: EntityManager,
schemaName: string,
@ -8,6 +14,7 @@ export const companyPrefillData = async (
.createQueryBuilder()
.insert()
.into(`${schemaName}.company`, [
'id',
'name',
'domainNamePrimaryLinkUrl',
'addressAddressStreet1',
@ -25,6 +32,7 @@ export const companyPrefillData = async (
.orIgnore()
.values([
{
id: AIRBNB_ID,
name: 'Airbnb',
domainNamePrimaryLinkUrl: 'https://airbnb.com',
addressAddressStreet1: '888 Brannan St',
@ -40,6 +48,7 @@ export const companyPrefillData = async (
createdByName: 'System',
},
{
id: QONTO_ID,
name: 'Qonto',
domainNamePrimaryLinkUrl: 'https://qonto.com',
addressAddressStreet1: '18 rue de navarrin',
@ -55,6 +64,7 @@ export const companyPrefillData = async (
createdByName: 'System',
},
{
id: STRIPE_ID,
name: 'Stripe',
domainNamePrimaryLinkUrl: 'https://stripe.com',
addressAddressStreet1: 'Eutaw Street',
@ -70,6 +80,7 @@ export const companyPrefillData = async (
createdByName: 'System',
},
{
id: FIGMA_ID,
name: 'Figma',
domainNamePrimaryLinkUrl: 'https://figma.com',
addressAddressStreet1: '760 Market St',
@ -85,6 +96,7 @@ export const companyPrefillData = async (
createdByName: 'System',
},
{
id: NOTION_ID,
name: 'Notion',
domainNamePrimaryLinkUrl: 'https://notion.com',
addressAddressStreet1: '2300 Harrison St',

View File

@ -1,5 +1,13 @@
import { EntityManager } from 'typeorm';
import {
AIRBNB_ID,
FIGMA_ID,
NOTION_ID,
QONTO_ID,
STRIPE_ID,
} from 'src/engine/workspace-manager/standard-objects-prefill-data/company';
// FixMe: Is this file a duplicate of src/database/typeorm-seeds/workspace/people.ts
export const personPrefillData = async (
entityManager: EntityManager,
@ -18,6 +26,9 @@ export const personPrefillData = async (
'createdBySource',
'createdByWorkspaceMemberId',
'createdByName',
'phonesPrimaryPhoneNumber',
'phonesPrimaryPhoneCountryCode',
'companyId',
])
.orIgnore()
.values([
@ -32,6 +43,9 @@ export const personPrefillData = async (
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: null,
createdByName: 'System',
phonesPrimaryPhoneNumber: '1234567890',
phonesPrimaryPhoneCountryCode: '+1',
companyId: AIRBNB_ID,
},
{
nameFirstName: 'Alexandre',
@ -44,6 +58,9 @@ export const personPrefillData = async (
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: null,
createdByName: 'System',
phonesPrimaryPhoneNumber: '677118822',
phonesPrimaryPhoneCountryCode: '+33',
companyId: QONTO_ID,
},
{
nameFirstName: 'Patrick',
@ -56,6 +73,9 @@ export const personPrefillData = async (
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: null,
createdByName: 'System',
phonesPrimaryPhoneNumber: '987625341',
phonesPrimaryPhoneCountryCode: '+1',
companyId: STRIPE_ID,
},
{
nameFirstName: 'Dylan',
@ -68,6 +88,9 @@ export const personPrefillData = async (
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: null,
createdByName: 'System',
phonesPrimaryPhoneNumber: '09882261',
phonesPrimaryPhoneCountryCode: '+1',
companyId: FIGMA_ID,
},
{
nameFirstName: 'Ivan',
@ -80,6 +103,9 @@ export const personPrefillData = async (
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: null,
createdByName: 'System',
phonesPrimaryPhoneNumber: '88226173',
phonesPrimaryPhoneCountryCode: '+1',
companyId: NOTION_ID,
},
])
.returning('*')

View File

@ -57,7 +57,7 @@ export const peopleAllView = async (
{
fieldMetadataId:
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
PERSON_STANDARD_FIELD_IDS.phone
PERSON_STANDARD_FIELD_IDS.phones
],
position: 4,
isVisible: true,

View File

@ -310,6 +310,7 @@ export const PERSON_STANDARD_FIELD_IDS = {
xLink: '20202020-8fc2-487c-b84a-55a99b145cfd',
jobTitle: '20202020-b0d0-415a-bef9-640a26dacd9b',
phone: '20202020-4564-4b8b-a09f-05445f2e0bce',
phones: '34becd3e-3c51-43fa-8b6e-af39e29368ab',
city: '20202020-5243-4ffb-afc5-2c675da41346',
avatarUrl: '20202020-b8a6-40df-961c-373dc5d2ec21',
position: '20202020-fcd5-4231-aff5-fff583eaa0b1',

View File

@ -7,6 +7,7 @@ import {
import { EmailsMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/emails.composite-type';
import { FullNameMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type';
import { LinksMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/links.composite-type';
import { PhonesMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/phones.composite-type';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import {
RelationMetadataType,
@ -109,8 +110,18 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
description: 'Contacts phone number',
icon: 'IconPhone',
})
@WorkspaceIsDeprecated()
phone: string;
@WorkspaceField({
standardId: PERSON_STANDARD_FIELD_IDS.phones,
type: FieldMetadataType.PHONES,
label: 'Phones',
description: 'Contacts phone numbers',
icon: 'IconPhone',
})
phones: PhonesMetadata;
@WorkspaceField({
standardId: PERSON_STANDARD_FIELD_IDS.city,
type: FieldMetadataType.TEXT,

View File

@ -11,7 +11,10 @@ describe('peopleResolver (integration)', () => {
edges {
node {
jobTitle
phone
phones {
primaryPhoneNumber
primaryPhoneCountryCode
}
city
avatarUrl
position
@ -21,7 +24,9 @@ describe('peopleResolver (integration)', () => {
deletedAt
companyId
intro
whatsapp
whatsapp {
primaryPhoneNumber
}
workPrefereance
performanceRating
}
@ -37,6 +42,7 @@ describe('peopleResolver (integration)', () => {
.send(queryData)
.expect(200)
.expect((res) => {
console.log(res.body);
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
@ -52,7 +58,7 @@ describe('peopleResolver (integration)', () => {
const people = edges[0].node;
expect(people).toHaveProperty('jobTitle');
expect(people).toHaveProperty('phone');
expect(people).toHaveProperty('phones');
expect(people).toHaveProperty('city');
expect(people).toHaveProperty('avatarUrl');
expect(people).toHaveProperty('position');