Phone country code unique (#9035)

fix #8775
This commit is contained in:
Guillim
2024-12-19 16:42:18 +01:00
committed by GitHub
parent 3f58a41d2f
commit 360c34fd18
47 changed files with 878 additions and 132 deletions

View File

@ -88,6 +88,7 @@ const mocks = [
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
@ -95,6 +96,7 @@ const mocks = [
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference
@ -246,6 +248,7 @@ const mocks = [
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
@ -253,6 +256,7 @@ const mocks = [
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference

View File

@ -246,6 +246,7 @@ mutation UpdateOneFavorite(
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
@ -253,6 +254,7 @@ mutation UpdateOneFavorite(
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference
@ -532,6 +534,7 @@ export const mocks = [
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
@ -539,6 +542,7 @@ export const mocks = [
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference

View File

@ -198,6 +198,7 @@ phone
{
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
}
linkedinLink
{

View File

@ -48,6 +48,7 @@ describe('mapObjectMetadataToGraphQLQuery', () => {
{
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
}
createdAt
avatarUrl

View File

@ -157,6 +157,7 @@ ${mapObjectMetadataToGraphQLQuery({
{
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}`;
}

View File

@ -30,6 +30,7 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS = `
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
@ -37,6 +38,7 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS = `
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference
@ -229,6 +231,7 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
pointOfContactForOpportunities {
@ -305,6 +308,7 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference

View File

@ -38,6 +38,7 @@ export const responseData = {
},
phones: {
primaryPhoneCountryCode: '',
primaryPhoneCallingCode: '',
primaryPhoneNumber: '',
},
linkedinLink: {

View File

@ -43,6 +43,7 @@ export const responseData = {
},
phones: {
primaryPhoneCountryCode: '',
primaryPhoneCallingCode: '',
primaryPhoneNumber: '',
},
linkedinLink: {

View File

@ -178,6 +178,7 @@ const mocks: MockedResponse[] = [
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
@ -185,6 +186,7 @@ const mocks: MockedResponse[] = [
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference
@ -332,6 +334,7 @@ const mocks: MockedResponse[] = [
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
@ -339,6 +342,7 @@ const mocks: MockedResponse[] = [
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference

View File

@ -39,7 +39,8 @@ const mocks: MockedResponse[] = [
input: {
phones: {
primaryPhoneNumber: '123 456',
primaryPhoneCountryCode: '+1',
primaryPhoneCountryCode: 'US',
primaryPhoneCallingCode: '+1',
additionalPhones: [],
},
},
@ -134,7 +135,8 @@ describe('usePersistField', () => {
act(() => {
result.current.persistField({
primaryPhoneNumber: '123 456',
primaryPhoneCountryCode: '+1',
primaryPhoneCountryCode: 'US',
primaryPhoneCallingCode: '+1',
additionalPhones: [],
});
});

View File

@ -208,6 +208,7 @@ const mocks: MockedResponse[] = [
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
@ -215,6 +216,7 @@ const mocks: MockedResponse[] = [
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference

View File

@ -9,12 +9,11 @@ import { TEXT_INPUT_STYLE } from 'twenty-ui';
import { MultiItemFieldInput } from './MultiItemFieldInput';
import { createPhonesFromFieldValue } from '@/object-record/record-field/meta-types/input/utils/phonesUtils';
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
import { PhoneCountryPickerDropdownButton } from '@/ui/input/components/internal/phone/components/PhoneCountryPickerDropdownButton';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
export const DEFAULT_PHONE_COUNTRY_CODE = '1';
export const DEFAULT_PHONE_CALLING_CODE = '1';
const StyledCustomPhoneInput = styled(ReactPhoneNumberInput)`
font-family: ${({ theme }) => theme.font.family};
@ -60,22 +59,22 @@ export const PhonesFieldInput = ({
const phones = createPhonesFromFieldValue(fieldValue);
const defaultCallingCode =
stripSimpleQuotesFromString(
fieldDefinition?.defaultValue?.primaryPhoneCountryCode,
) ?? DEFAULT_PHONE_COUNTRY_CODE;
// TODO : improve once we store the real country code
const defaultCountry = useCountries().find(
(obj) => `+${obj.callingCode}` === defaultCallingCode,
)?.countryCode;
const defaultCountry = stripSimpleQuotesFromString(
fieldDefinition?.defaultValue?.primaryPhoneCountryCode,
);
const handlePersistPhones = (
updatedPhones: { number: string; callingCode: string }[],
updatedPhones: {
number: string;
countryCode: string;
callingCode: string;
}[],
) => {
const [nextPrimaryPhone, ...nextAdditionalPhones] = updatedPhones;
persistPhonesField({
primaryPhoneNumber: nextPrimaryPhone?.number ?? '',
primaryPhoneCountryCode: nextPrimaryPhone?.callingCode ?? '',
primaryPhoneCountryCode: nextPrimaryPhone?.countryCode ?? '',
primaryPhoneCallingCode: nextPrimaryPhone?.callingCode ?? '',
additionalPhones: nextAdditionalPhones,
});
};
@ -96,11 +95,13 @@ export const PhonesFieldInput = ({
return {
number: phone.nationalNumber,
callingCode: `+${phone.countryCallingCode}`,
countryCode: phone.country as string,
};
}
return {
number: '',
callingCode: '',
countryCode: '',
};
}}
renderItem={({

View File

@ -19,7 +19,8 @@ describe('createPhonesFromFieldValue test suite', () => {
it('should return an array with primary phone number if it is defined', () => {
const fieldValue: FieldPhonesValue = {
primaryPhoneNumber: '123456789',
primaryPhoneCountryCode: '+1',
primaryPhoneCountryCode: 'US',
primaryPhoneCallingCode: '+1',
additionalPhones: [],
};
const result = createPhonesFromFieldValue(fieldValue);
@ -27,6 +28,24 @@ describe('createPhonesFromFieldValue test suite', () => {
{
number: '123456789',
callingCode: '+1',
countryCode: 'US',
},
]);
});
it('should return an array with primary phone number if it is defined, even with incorrect callingCode', () => {
const fieldValue: FieldPhonesValue = {
primaryPhoneNumber: '123456789',
primaryPhoneCountryCode: 'US',
primaryPhoneCallingCode: '+33',
additionalPhones: [],
};
const result = createPhonesFromFieldValue(fieldValue);
expect(result).toEqual([
{
number: '123456789',
callingCode: '+33',
countryCode: 'US',
},
]);
});
@ -34,10 +53,11 @@ describe('createPhonesFromFieldValue test suite', () => {
it('should return an array with both primary and additional phones if they are defined', () => {
const fieldValue: FieldPhonesValue = {
primaryPhoneNumber: '123456789',
primaryPhoneCountryCode: '+1',
primaryPhoneCountryCode: 'US',
primaryPhoneCallingCode: '+1',
additionalPhones: [
{ number: '987654321', callingCode: '+44' },
{ number: '555555555', callingCode: '+33' },
{ number: '987654321', callingCode: '+44', countryCode: 'GB' },
{ number: '555555555', callingCode: '+33', countryCode: 'FR' },
],
};
const result = createPhonesFromFieldValue(fieldValue);
@ -45,9 +65,10 @@ describe('createPhonesFromFieldValue test suite', () => {
{
number: '123456789',
callingCode: '+1',
countryCode: 'US',
},
{ number: '987654321', callingCode: '+44' },
{ number: '555555555', callingCode: '+33' },
{ number: '987654321', callingCode: '+44', countryCode: 'GB' },
{ number: '555555555', callingCode: '+33', countryCode: 'FR' },
]);
});
@ -56,14 +77,14 @@ describe('createPhonesFromFieldValue test suite', () => {
primaryPhoneNumber: '',
primaryPhoneCountryCode: '',
additionalPhones: [
{ number: '987654321', callingCode: '+44' },
{ number: '555555555', callingCode: '+33' },
{ number: '987654321', callingCode: '+44', countryCode: 'GB' },
{ number: '555555555', callingCode: '+33', countryCode: 'FR' },
],
};
const result = createPhonesFromFieldValue(fieldValue);
expect(result).toEqual([
{ number: '987654321', callingCode: '+44' },
{ number: '555555555', callingCode: '+33' },
{ number: '987654321', callingCode: '+44', countryCode: 'GB' },
{ number: '555555555', callingCode: '+33', countryCode: 'FR' },
]);
});
@ -72,22 +93,34 @@ describe('createPhonesFromFieldValue test suite', () => {
primaryPhoneNumber: ' ',
primaryPhoneCountryCode: '',
additionalPhones: [
{ number: '987654321', callingCode: '+44' },
{ number: '555555555', callingCode: '+33' },
{ number: '987654321', callingCode: '+44', countryCode: 'GB' },
{ number: '555555555', callingCode: '+33', countryCode: 'FR' },
],
};
const result = createPhonesFromFieldValue(fieldValue);
expect(result).toEqual([
{ number: ' ', callingCode: '' },
{ number: '987654321', callingCode: '+44' },
{ number: '555555555', callingCode: '+33' },
{ number: ' ', callingCode: '', countryCode: '' },
{ number: '987654321', callingCode: '+44', countryCode: 'GB' },
{ number: '555555555', callingCode: '+33', countryCode: 'FR' },
]);
});
it('should return an empty array if only country code is defined', () => {
it('should return an empty array if only country and calling code are defined', () => {
const fieldValue: FieldPhonesValue = {
primaryPhoneNumber: '',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
additionalPhones: [],
};
const result = createPhonesFromFieldValue(fieldValue);
expect(result).toEqual([]);
});
it('should return an empty array if only calling code is defined', () => {
const fieldValue: FieldPhonesValue = {
primaryPhoneNumber: '',
primaryPhoneCallingCode: '+33',
primaryPhoneCountryCode: '',
additionalPhones: [],
};
const result = createPhonesFromFieldValue(fieldValue);

View File

@ -8,7 +8,10 @@ export const createPhonesFromFieldValue = (fieldValue: FieldPhonesValue) => {
fieldValue.primaryPhoneNumber
? {
number: fieldValue.primaryPhoneNumber,
callingCode: fieldValue.primaryPhoneCountryCode,
callingCode: fieldValue.primaryPhoneCallingCode
? fieldValue.primaryPhoneCallingCode
: fieldValue.primaryPhoneCountryCode,
countryCode: fieldValue.primaryPhoneCountryCode,
}
: null,
...(fieldValue.additionalPhones ?? []),

View File

@ -27,6 +27,7 @@ export type FieldDateTimeDraftValue = string;
export type FieldPhonesDraftValue = {
primaryPhoneNumber: string;
primaryPhoneCountryCode: string;
primaryPhoneCallingCode: string;
additionalPhones?: PhoneRecord[] | null;
};
export type FieldEmailsDraftValue = {

View File

@ -265,10 +265,15 @@ export type FieldActorValue = {
export type FieldArrayValue = string[];
export type PhoneRecord = { number: string; callingCode: string };
export type PhoneRecord = {
number: string;
callingCode: string;
countryCode: string;
};
export type FieldPhonesValue = {
primaryPhoneNumber: string;
primaryPhoneCountryCode: string;
primaryPhoneCallingCode?: string;
additionalPhones?: PhoneRecord[] | null;
};

View File

@ -5,8 +5,15 @@ import { FieldPhonesValue } from '../FieldMetadata';
export const phonesSchema = z.object({
primaryPhoneNumber: z.string(),
primaryPhoneCountryCode: z.string(),
primaryPhoneCallingCode: z.string(),
additionalPhones: z
.array(z.object({ number: z.string(), callingCode: z.string() }))
.array(
z.object({
number: z.string(),
callingCode: z.string(),
countryCode: z.string(),
}),
)
.nullable(),
}) satisfies z.ZodType<FieldPhonesValue>;

View File

@ -71,6 +71,9 @@ export const computeDraftValueFromFieldValue = <FieldValue>({
primaryPhoneCountryCode: stripSimpleQuotesFromString(
fieldDefinition?.defaultValue?.primaryPhoneCountryCode,
),
primaryPhoneCallingCode: stripSimpleQuotesFromString(
fieldDefinition?.defaultValue?.primaryPhoneCallingCode,
),
} as unknown as FieldInputDraftValue<FieldValue>;
}

View File

@ -21,6 +21,7 @@ const mockPerson = {
whatsapp: {
primaryPhoneNumber: '+1',
primaryPhoneCountryCode: '234-567-890',
primaryPhoneCallingCode: '+33',
additionalPhones: [],
},
linkedinLink: {

View File

@ -663,7 +663,8 @@ export const mockPerformance = {
id: '20202020-2d40-4e49-8df4-9c6a049191df',
email: 'lorie.vladim@google.com',
phones: {
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
primaryPhoneNumber: '788901235',
},
linkedinLink: {

View File

@ -207,6 +207,7 @@ const companyMocks = [
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
@ -214,6 +215,7 @@ const companyMocks = [
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference

View File

@ -95,6 +95,7 @@ export const generateEmptyFieldValue = (
return {
primaryPhoneNumber: '',
primaryPhoneCountryCode: '',
primaryPhoneCallingCode: '',
additionalPhones: null,
};
}

View File

@ -91,7 +91,9 @@ export const SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS = {
exampleValue: {
primaryPhoneNumber: '234-567-890',
primaryPhoneCountryCode: '+1',
additionalPhones: [{ number: '234-567-890', callingCode: '+1' }],
additionalPhones: [
{ number: '234-567-890', callingCode: '+1', countryCode: 'US' },
],
},
subFields: [
'primaryPhoneNumber',
@ -102,6 +104,7 @@ export const SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS = {
labelBySubField: {
primaryPhoneNumber: 'Primary Phone Number',
primaryPhoneCountryCode: 'Primary Phone Country Code',
primaryPhoneCallingCode: 'Primary Phone Calling Code',
additionalPhones: 'Additional Phones',
},
category: 'Basic',

View File

@ -3,8 +3,10 @@ import { Controller, useFormContext } from 'react-hook-form';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { phonesSchema as phonesFieldDefaultValueSchema } from '@/object-record/record-field/types/guards/isFieldPhonesValue';
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
import { countryCodeToCallingCode } from '@/settings/data-model/fields/preview/utils/getPhonesFieldPreviewValue';
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
import { Select } from '@/ui/input/components/Select';
import { CountryCode } from 'libphonenumber-js';
import { IconMap } from 'twenty-ui';
import { z } from 'zod';
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
@ -27,22 +29,27 @@ export type SettingsDataModelFieldTextFormValues = z.infer<
typeof settingsDataModelFieldPhonesFormSchema
>;
export type CountryCodeOrEmpty = CountryCode | '';
export const SettingsDataModelFieldPhonesForm = ({
disabled,
fieldMetadataItem,
}: SettingsDataModelFieldPhonesFormProps) => {
const { control } = useFormContext<SettingsDataModelFieldTextFormValues>();
const countries = useCountries()
.sort((a, b) => a.countryName.localeCompare(b.countryName))
.map((country) => ({
label: `${country.countryName} (+${country.callingCode})`,
value: `+${country.callingCode}`,
}));
countries.unshift({ label: 'No country', value: '' });
const countries = [
{ label: 'No country', value: '' },
...useCountries()
.sort((a, b) => a.countryName.localeCompare(b.countryName))
.map((country) => ({
label: `${country.countryName} (+${country.callingCode})`,
value: country.countryCode as CountryCodeOrEmpty,
})),
];
const defaultDefaultValue = {
primaryPhoneNumber: "''",
primaryPhoneCountryCode: "''",
primaryPhoneCallingCode: "''",
additionalPhones: null,
};
const fieldMetadataItemDefaultValue = fieldMetadataItem?.defaultValue;
@ -73,6 +80,9 @@ export const SettingsDataModelFieldPhonesForm = ({
...value,
primaryPhoneCountryCode:
applySimpleQuotesToString(newPhoneCountryCode),
primaryPhoneCallingCode: applySimpleQuotesToString(
countryCodeToCallingCode(newPhoneCountryCode),
),
})
}
disabled={disabled}

View File

@ -1,9 +1,29 @@
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { DEFAULT_PHONE_CALLING_CODE } from '@/object-record/record-field/meta-types/input/components/PhonesFieldInput';
import { FieldPhonesValue } from '@/object-record/record-field/types/FieldMetadata';
import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig';
import {
CountryCode,
getCountries,
getCountryCallingCode,
} from 'libphonenumber-js';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
const isStrCountryCodeGuard = (str: string): str is CountryCode => {
return getCountries().includes(str as CountryCode);
};
export const countryCodeToCallingCode = (countryCode: string): string => {
if (!countryCode || !isStrCountryCodeGuard(countryCode)) {
return `+${DEFAULT_PHONE_CALLING_CODE}`;
}
const callingCode = getCountryCallingCode(countryCode);
return callingCode ? `+${callingCode}` : `+${DEFAULT_PHONE_CALLING_CODE}`;
};
export const getPhonesFieldPreviewValue = ({
fieldMetadataItem,
}: {
@ -26,8 +46,16 @@ export const getPhonesFieldPreviewValue = ({
fieldMetadataItem.defaultValue?.primaryPhoneCountryCode,
)
: null;
const primaryPhoneCallingCode =
fieldMetadataItem.defaultValue?.primaryPhoneCallingCode &&
fieldMetadataItem.defaultValue.primaryPhoneCallingCode !== ''
? stripSimpleQuotesFromString(
fieldMetadataItem.defaultValue?.primaryPhoneCallingCode,
)
: null;
return {
...placeholderDefaultValue,
primaryPhoneCountryCode,
primaryPhoneCallingCode,
};
};

View File

@ -5,6 +5,7 @@ import { RoundedLink, THEME_COMMON } from 'twenty-ui';
import { FieldPhonesValue } from '@/object-record/record-field/types/FieldMetadata';
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
import { DEFAULT_PHONE_CALLING_CODE } from '@/object-record/record-field/meta-types/input/components/PhonesFieldInput';
import { parsePhoneNumber } from 'libphonenumber-js';
import { isDefined } from '~/utils/isDefined';
import { logError } from '~/utils/logError';
@ -36,7 +37,10 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
value?.primaryPhoneNumber
? {
number: value.primaryPhoneNumber,
callingCode: value.primaryPhoneCountryCode,
callingCode:
value.primaryPhoneCallingCode ||
value.primaryPhoneCountryCode ||
`+${DEFAULT_PHONE_CALLING_CODE}`,
}
: null,
...parseAdditionalPhones(value?.additionalPhones),
@ -50,11 +54,11 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
}),
[
value?.primaryPhoneNumber,
value?.primaryPhoneCallingCode,
value?.primaryPhoneCountryCode,
value?.additionalPhones,
],
);
const parsePhoneNumberOrReturnInvalidValue = (number: string) => {
try {
return { parsedPhone: parsePhoneNumber(number) };

View File

@ -19461,7 +19461,8 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
"defaultValue": {
"additionalPhones": null,
"primaryPhoneNumber": "''",
"primaryPhoneCountryCode": "''"
"primaryPhoneCountryCode": "''",
"primaryPhoneCallingCode": "''"
},
"options": null,
"isLabelSyncedWithName": false,
@ -19740,7 +19741,8 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
{
"additionalPhones": {},
"primaryPhoneNumber": "",
"primaryPhoneCountryCode": ""
"primaryPhoneCountryCode": "",
"primaryPhoneCallingCode": ""
}
],
"options": null,

View File

@ -47,7 +47,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'ASd',
phones: {
primaryPhoneNumber: '781234562',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: 'da3c2c4b-da01-4b81-9734-226069eb4cd0',
jobTitle: '',
@ -177,7 +178,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'Seattle',
phones: {
primaryPhoneNumber: '781234562',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-1c0e-494c-a1b6-85b1c6fefaa5',
jobTitle: '',
@ -307,7 +309,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'Los Angeles',
phones: {
primaryPhoneNumber: '781234576',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-ac73-4797-824e-87a1f5aea9e0',
jobTitle: '',
@ -406,7 +409,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'Seattle',
phones: {
primaryPhoneNumber: '781234545',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-f517-42fd-80ae-14173b3b70ae',
jobTitle: '',
@ -505,7 +509,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'Los Angeles',
phones: {
primaryPhoneNumber: '781234587',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-eee1-4690-ad2c-8619e5b56a2e',
jobTitle: '',
@ -604,7 +609,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'Seattle',
phones: {
primaryPhoneNumber: '781234599',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-6784-4449-afdf-dc62cb8702f2',
jobTitle: '',
@ -703,7 +709,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'New York',
phones: {
primaryPhoneNumber: '781234572',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-490f-4466-8391-733cfd66a0c8',
jobTitle: '',
@ -802,7 +809,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'Seattle',
phones: {
primaryPhoneNumber: '781234582',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-80f1-4dff-b570-a74942528de3',
jobTitle: '',
@ -901,7 +909,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'New York',
phones: {
primaryPhoneNumber: '781234569',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-338b-46df-8811-fa08c7d19d35',
jobTitle: '',
@ -1000,7 +1009,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'San Francisco',
phones: {
primaryPhoneNumber: '781234962',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-64ad-4b0e-bbfd-e9fd795b7016',
jobTitle: '',
@ -1099,7 +1109,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'New York',
phones: {
primaryPhoneNumber: '781234502',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-5d54-41b7-ba36-f0d20e1417ae',
jobTitle: '',
@ -1198,7 +1209,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'Los Angeles',
phones: {
primaryPhoneNumber: '781234563',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-623d-41fe-92e7-dd45b7c568e1',
jobTitle: '',
@ -1297,7 +1309,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'Seattle',
phones: {
primaryPhoneNumber: '781234542',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-2d40-4e49-8df4-9c6a049190ef',
jobTitle: '',
@ -1396,7 +1409,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'Seattle',
phones: {
primaryPhoneNumber: '782234562',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-2d40-4e49-8df4-9c6a049190df',
jobTitle: '',
@ -1495,7 +1509,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'Seattle',
phones: {
primaryPhoneNumber: '781274562',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-2d40-4e49-8df4-9c6a049191de',
jobTitle: '',
@ -1594,7 +1609,8 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
city: 'Seattle',
phones: {
primaryPhoneNumber: '781239562',
primaryPhoneCountryCode: '+33',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
},
id: '20202020-2d40-4e49-8df4-9c6a049191df',
jobTitle: '',

View File

@ -1,5 +1,3 @@
import { Logger } from '@nestjs/common';
import chalk from 'chalk';
import { Option } from 'nest-commander';
import { Repository } from 'typeorm';
@ -20,11 +18,8 @@ export type ActiveWorkspacesCommandOptions = BaseCommandOptions & {
export abstract class ActiveWorkspacesCommandRunner extends BaseCommandRunner {
private workspaceIds: string[] = [];
protected readonly logger: Logger;
constructor(protected readonly workspaceRepository: Repository<Workspace>) {
super();
this.logger = new Logger(this.constructor.name);
}
@Option({

View File

@ -3,6 +3,7 @@ import { Logger } from '@nestjs/common';
import chalk from 'chalk';
import { CommandRunner, Option } from 'nest-commander';
import { CommandLogger } from './logger';
export type BaseCommandOptions = {
workspaceId?: string;
dryRun?: boolean;
@ -10,11 +11,13 @@ export type BaseCommandOptions = {
};
export abstract class BaseCommandRunner extends CommandRunner {
protected readonly logger: Logger;
protected logger: CommandLogger | Logger;
constructor() {
super();
this.logger = new Logger(this.constructor.name);
this.logger = new CommandLogger({
verbose: false,
constructorName: this.constructor.name,
});
}
@Option({
@ -27,10 +30,11 @@ export abstract class BaseCommandRunner extends CommandRunner {
}
@Option({
flags: '--verbose',
flags: '-v, --verbose',
description: 'Verbose output',
required: false,
})
parseVerbose() {
parseVerbose(): boolean {
return true;
}
@ -38,6 +42,13 @@ export abstract class BaseCommandRunner extends CommandRunner {
passedParams: string[],
options: BaseCommandOptions,
): Promise<void> {
if (options.verbose) {
this.logger = new CommandLogger({
verbose: true,
constructorName: this.constructor.name,
});
}
try {
await this.executeBaseCommand(passedParams, options);
} catch (error) {

View File

@ -52,8 +52,8 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
UpgradeTo0_32CommandModule,
UpgradeTo0_33CommandModule,
UpgradeTo0_34CommandModule,
FeatureFlagModule,
UpgradeTo0_40CommandModule,
FeatureFlagModule,
],
providers: [
DataSeedWorkspaceCommand,

View File

@ -0,0 +1,46 @@
import { Logger } from '@nestjs/common';
interface CommandLoggerOptions {
verbose?: boolean;
constructorName: string;
}
export class CommandLogger {
private logger: Logger;
private verbose: boolean;
constructor(options: CommandLoggerOptions) {
this.logger = new Logger(options.constructorName);
this.verbose = options.verbose ?? true;
}
log(message: string, context?: string) {
if (this.verbose) {
this.logger.log(message, context);
}
}
error(message: string, stack?: string, context?: string) {
if (this.verbose) {
this.logger.error(message, stack, context);
}
}
warn(message: string, context?: string) {
if (this.verbose) {
this.logger.warn(message, context);
}
}
debug(message: string, context?: string) {
if (this.verbose) {
this.logger.debug(message, context);
}
}
verboseLog(message: string, context?: string) {
if (this.verbose) {
this.logger.verbose(message, context);
}
}
}

View File

@ -0,0 +1,144 @@
import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { v4 } from 'uuid';
import {
ActiveWorkspacesCommandOptions,
ActiveWorkspacesCommandRunner,
} from 'src/database/commands/active-workspaces.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import {
FieldMetadataEntity,
FieldMetadataType,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
import {
WorkspaceMigrationColumnActionType,
WorkspaceMigrationTableAction,
WorkspaceMigrationTableActionType,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
import { isDefined } from 'src/utils/is-defined';
@Command({
name: 'upgrade-0.40:phone-calling-code-create-column',
description: 'Create the callingCode column',
})
export class PhoneCallingCodeCreateColumnCommand 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>,
private readonly workspaceMigrationService: WorkspaceMigrationService,
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
) {
super(workspaceRepository);
}
async executeActiveWorkspacesCommand(
_passedParam: string[],
options: ActiveWorkspacesCommandOptions,
workspaceIds: string[],
): Promise<void> {
this.logger.log(
'Running command to add calling code and change country code with default one',
);
this.logger.log(`Part 1 - Workspace`);
let workspaceIterator = 1;
for (const workspaceId of workspaceIds) {
this.logger.log(
`Running command for workspace ${workspaceId} ${workspaceIterator}/${workspaceIds.length}`,
);
this.logger.log(
`P1 Step 1 - let's find all the fieldsMetadata that have the PHONES type, and extract the objectMetadataId`,
);
try {
const phonesFieldMetadata = await this.fieldMetadataRepository.find({
where: {
workspaceId,
type: FieldMetadataType.PHONES,
},
relations: ['object'],
});
for (const phoneFieldMetadata of phonesFieldMetadata) {
if (
isDefined(phoneFieldMetadata?.name && phoneFieldMetadata.object)
) {
this.logger.log(
`P1 Step 1 - Let's find the "nameSingular" of this objectMetadata: ${phoneFieldMetadata.object.nameSingular || 'not found'}`,
);
if (!phoneFieldMetadata.object?.nameSingular) continue;
this.logger.log(
`P1 Step 1 - Create migration for field ${phoneFieldMetadata.name}`,
);
if (options.dryRun === true) {
continue;
}
await this.workspaceMigrationService.createCustomMigration(
generateMigrationName(
`create-${phoneFieldMetadata.object.nameSingular}PrimaryPhoneCallingCode-for-field-${phoneFieldMetadata.name}`,
),
workspaceId,
[
{
name: computeObjectTargetTable(phoneFieldMetadata.object),
action: WorkspaceMigrationTableActionType.ALTER,
columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.CREATE,
{
id: v4(),
type: FieldMetadataType.TEXT,
name: `${phoneFieldMetadata.name}PrimaryPhoneCallingCode`,
label: `${phoneFieldMetadata.name}PrimaryPhoneCallingCode`,
objectMetadataId: phoneFieldMetadata.object.id,
workspaceId: workspaceId,
isNullable: true,
defaultValue: "''",
} satisfies Partial<FieldMetadataEntity>,
),
} satisfies WorkspaceMigrationTableAction,
],
);
}
}
this.logger.log(
`P1 Step 1 - RUN migration to create callingCodes for ${workspaceId.slice(0, 5)}`,
);
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
workspaceId,
);
await this.workspaceMetadataVersionService.incrementMetadataVersion(
workspaceId,
);
} catch (error) {
console.log(`Error in workspace ${workspaceId} : ${error}`);
}
workspaceIterator++;
}
this.logger.log(chalk.green(`Command completed!`));
}
}

View File

@ -0,0 +1,302 @@
import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import { getCountries, getCountryCallingCode } from 'libphonenumber-js';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { v4 } from 'uuid';
import {
ActiveWorkspacesCommandOptions,
ActiveWorkspacesCommandRunner,
} from 'src/database/commands/active-workspaces.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import {
FieldMetadataEntity,
FieldMetadataType,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
import {
WorkspaceMigrationColumnActionType,
WorkspaceMigrationTableAction,
WorkspaceMigrationTableActionType,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
import { isDefined } from 'src/utils/is-defined';
const callingCodeToCountryCode = (callingCode: string): string => {
if (!callingCode) {
return '';
}
let callingCodeSanitized = callingCode;
if (callingCode.startsWith('+')) {
callingCodeSanitized = callingCode.slice(1);
}
return (
getCountries().find(
(countryCode) =>
getCountryCallingCode(countryCode) === callingCodeSanitized,
) || ''
);
};
const isCallingCode = (callingCode: string): boolean => {
return callingCodeToCountryCode(callingCode) !== '';
};
@Command({
name: 'upgrade-0.40:phone-calling-code-migrate-data',
description: 'Add calling code and change country code with default one',
})
export class PhoneCallingCodeMigrateDataCommand 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>,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly workspaceMigrationService: WorkspaceMigrationService,
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
) {
super(workspaceRepository);
}
async executeActiveWorkspacesCommand(
_passedParam: string[],
options: ActiveWorkspacesCommandOptions,
workspaceIds: string[],
): Promise<void> {
this.logger.log(
'Running command to add calling code and change country code with default one',
);
this.logger.log(`Part 1 - Workspace`);
let workspaceIterator = 1;
for (const workspaceId of workspaceIds) {
this.logger.log(
`Running command for workspace ${workspaceId} ${workspaceIterator}/${workspaceIds.length}`,
);
this.logger.log(
`P1 Step 1 - let's find all the fieldsMetadata that have the PHONES type, and extract the objectMetadataId`,
);
try {
const phonesFieldMetadata = await this.fieldMetadataRepository.find({
where: {
workspaceId,
type: FieldMetadataType.PHONES,
},
relations: ['object'],
});
for (const phoneFieldMetadata of phonesFieldMetadata) {
if (
isDefined(phoneFieldMetadata?.name) &&
isDefined(phoneFieldMetadata.object)
) {
this.logger.log(
`P1 Step 1 - Let's find the "nameSingular" of this objectMetadata: ${phoneFieldMetadata.object.nameSingular || 'not found'}`,
);
if (!phoneFieldMetadata.object?.nameSingular) continue;
this.logger.log(
`P1 Step 1 - Create migration for field ${phoneFieldMetadata.name}`,
);
if (options.dryRun === false) {
await this.workspaceMigrationService.createCustomMigration(
generateMigrationName(
`create-${phoneFieldMetadata.object.nameSingular}PrimaryPhoneCallingCode-for-field-${phoneFieldMetadata.name}`,
),
workspaceId,
[
{
name: computeObjectTargetTable(phoneFieldMetadata.object),
action: WorkspaceMigrationTableActionType.ALTER,
columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.CREATE,
{
id: v4(),
type: FieldMetadataType.TEXT,
name: `${phoneFieldMetadata.name}PrimaryPhoneCallingCode`,
label: `${phoneFieldMetadata.name}PrimaryPhoneCallingCode`,
objectMetadataId: phoneFieldMetadata.object.id,
workspaceId: workspaceId,
isNullable: true,
defaultValue: "''",
},
),
} satisfies WorkspaceMigrationTableAction,
],
);
}
}
}
this.logger.log(
`P1 Step 1 - RUN migration to create callingCodes for ${workspaceId.slice(0, 5)}`,
);
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
workspaceId,
);
await this.workspaceMetadataVersionService.incrementMetadataVersion(
workspaceId,
);
this.logger.log(
`P1 Step 2 - Migrations for callingCode must be first. Now can use twentyORMGlobalManager to update countryCode`,
);
this.logger.log(
`P1 Step 3 (same time) - update CountryCode to letters: +33 => FR || +1 => US (if mulitple, first one)`,
);
this.logger.log(
`P1 Step 4 (same time) - update all additioanl phones to add a country code following the same logic`,
);
for (const phoneFieldMetadata of phonesFieldMetadata) {
this.logger.log(`P1 Step 2 - for ${phoneFieldMetadata.name}`);
if (
isDefined(phoneFieldMetadata) &&
isDefined(phoneFieldMetadata.name)
) {
const [objectMetadata] = await this.objectMetadataRepository.find({
where: {
id: phoneFieldMetadata?.objectMetadataId,
},
});
const repository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspaceId,
objectMetadata.nameSingular,
);
const records = await repository.find();
for (const record of records) {
if (
record?.[phoneFieldMetadata.name]?.primaryPhoneCountryCode &&
isCallingCode(
record[phoneFieldMetadata.name].primaryPhoneCountryCode,
)
) {
let additionalPhones = null;
if (record[phoneFieldMetadata.name].additionalPhones) {
additionalPhones = record[
phoneFieldMetadata.name
].additionalPhones.map((phone) => {
return {
...phone,
countryCode: callingCodeToCountryCode(phone.callingCode),
};
});
}
if (options.dryRun === false) {
await repository.update(record.id, {
[`${phoneFieldMetadata.name}PrimaryPhoneCallingCode`]:
record[phoneFieldMetadata.name].primaryPhoneCountryCode,
[`${phoneFieldMetadata.name}PrimaryPhoneCountryCode`]:
callingCodeToCountryCode(
record[phoneFieldMetadata.name].primaryPhoneCountryCode,
),
[`${phoneFieldMetadata.name}AdditionalPhones`]:
additionalPhones,
});
}
}
}
}
}
} catch (error) {
console.log(`Error in workspace ${workspaceId} : ${error}`);
}
workspaceIterator++;
}
this.logger.log(`
Part 2 - FieldMetadata`);
workspaceIterator = 1;
for (const workspaceId of workspaceIds) {
this.logger.log(
`Running command for workspace ${workspaceId} ${workspaceIterator}/${workspaceIds.length}`,
);
this.logger.log(
`P2 Step 1 - let's find all the fieldsMetadata that have the PHONES type, and extract the objectMetadataId`,
);
try {
const phonesFieldMetadata = await this.fieldMetadataRepository.find({
where: {
workspaceId,
type: FieldMetadataType.PHONES,
},
});
for (const phoneFieldMetadata of phonesFieldMetadata) {
if (
!isDefined(phoneFieldMetadata) ||
!isDefined(phoneFieldMetadata.defaultValue)
)
continue;
let defaultValue = phoneFieldMetadata.defaultValue;
// some cases look like it's an array. let's flatten it (not sure the case is supposed to happen but I saw it in my local db)
if (Array.isArray(defaultValue) && isDefined(defaultValue[0]))
defaultValue = phoneFieldMetadata.defaultValue[0];
if (!isDefined(defaultValue)) continue;
if (typeof defaultValue !== 'object') continue;
if (!('primaryPhoneCountryCode' in defaultValue)) continue;
if (!defaultValue.primaryPhoneCountryCode) continue;
const primaryPhoneCountryCode = defaultValue.primaryPhoneCountryCode;
const countryCode = callingCodeToCountryCode(
primaryPhoneCountryCode.replace(/["']/g, ''),
);
if (options.dryRun === false) {
await this.fieldMetadataRepository.update(phoneFieldMetadata.id, {
defaultValue: {
...defaultValue,
primaryPhoneCountryCode: countryCode
? `'${countryCode}'`
: "''",
primaryPhoneCallingCode: isCallingCode(
primaryPhoneCountryCode.replace(/["']/g, ''),
)
? primaryPhoneCountryCode
: "''",
},
});
}
}
} catch (error) {
console.log(`Error in workspace ${workspaceId} : ${error}`);
}
workspaceIterator++;
}
this.logger.log(chalk.green(`Command completed!`));
}
}

View File

@ -5,6 +5,8 @@ import { Repository } from 'typeorm';
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
import { BaseCommandOptions } from 'src/database/commands/base.command';
import { PhoneCallingCodeCreateColumnCommand } from 'src/database/commands/upgrade-version/0-40/0-40-phone-calling-code-create-column.command';
import { PhoneCallingCodeMigrateDataCommand } from 'src/database/commands/upgrade-version/0-40/0-40-phone-calling-code-migrate-data.command';
import { RecordPositionBackfillCommand } from 'src/database/commands/upgrade-version/0-40/0-40-record-position-backfill.command';
import { ViewGroupNoValueBackfillCommand } from 'src/database/commands/upgrade-version/0-40/0-40-view-group-no-value-backfill.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@ -18,9 +20,11 @@ export class UpgradeTo0_40Command extends ActiveWorkspacesCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
private readonly recordPositionBackfillCommand: RecordPositionBackfillCommand,
private readonly viewGroupNoValueBackfillCommand: ViewGroupNoValueBackfillCommand,
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
private readonly phoneCallingCodeMigrateDataCommand: PhoneCallingCodeMigrateDataCommand,
private readonly phoneCallingCodeCreateColumnCommand: PhoneCallingCodeCreateColumnCommand,
private readonly recordPositionBackfillCommand: RecordPositionBackfillCommand,
) {
super(workspaceRepository);
}
@ -30,6 +34,22 @@ export class UpgradeTo0_40Command extends ActiveWorkspacesCommandRunner {
options: BaseCommandOptions,
workspaceIds: string[],
): Promise<void> {
this.logger.log(
'Running command to upgrade to 0.40: must start with phone calling code otherwise SyncMetadata will fail',
);
await this.phoneCallingCodeCreateColumnCommand.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
await this.phoneCallingCodeMigrateDataCommand.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
await this.recordPositionBackfillCommand.executeActiveWorkspacesCommand(
passedParam,
options,

View File

@ -1,6 +1,8 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PhoneCallingCodeCreateColumnCommand } from 'src/database/commands/upgrade-version/0-40/0-40-phone-calling-code-create-column.command';
import { PhoneCallingCodeMigrateDataCommand } from 'src/database/commands/upgrade-version/0-40/0-40-phone-calling-code-migrate-data.command';
import { RecordPositionBackfillCommand } from 'src/database/commands/upgrade-version/0-40/0-40-record-position-backfill.command';
import { UpgradeTo0_40Command } from 'src/database/commands/upgrade-version/0-40/0-40-upgrade-version.command';
import { ViewGroupNoValueBackfillCommand } from 'src/database/commands/upgrade-version/0-40/0-40-view-group-no-value-backfill.command';
@ -8,18 +10,35 @@ import { RecordPositionBackfillModule } from 'src/engine/api/graphql/workspace-q
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
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 { SearchModule } from 'src/engine/metadata-modules/search/search.module';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module';
@Module({
imports: [
TypeOrmModule.forFeature([Workspace], 'core'),
TypeOrmModule.forFeature(
[ObjectMetadataEntity, FieldMetadataEntity],
'metadata',
),
TypeOrmModule.forFeature([FieldMetadataEntity], 'metadata'),
WorkspaceSyncMetadataCommandsModule,
SearchModule,
WorkspaceMigrationRunnerModule,
WorkspaceMetadataVersionModule,
WorkspaceMigrationModule,
RecordPositionBackfillModule,
FieldMetadataModule,
],
providers: [
UpgradeTo0_40Command,
PhoneCallingCodeMigrateDataCommand,
PhoneCallingCodeCreateColumnCommand,
WorkspaceMigrationFactory,
RecordPositionBackfillCommand,
ViewGroupNoValueBackfillCommand,
],

View File

@ -106,13 +106,12 @@ export const getDevSeedPeopleCustomFields = (
isActive: true,
isNullable: false,
isUnique: false,
defaultValue: [
{
primaryPhoneNumber: '',
primaryPhoneCountryCode: '',
additionalPhones: {},
},
],
defaultValue: {
primaryPhoneNumber: "''",
primaryPhoneCountryCode: "'FR'",
primaryPhoneCallingCode: "'+33'",
additionalPhones: null,
},
objectMetadataId,
},
{

View File

@ -35,12 +35,14 @@ export const seedPeople = async (
'nameFirstName',
'nameLastName',
'phonesPrimaryPhoneCountryCode',
'phonesPrimaryPhoneCallingCode',
'phonesPrimaryPhoneNumber',
'city',
'companyId',
'emailsPrimaryEmail',
'position',
'whatsappPrimaryPhoneCountryCode',
'whatsappPrimaryPhoneCallingCode',
'whatsappPrimaryPhoneNumber',
'createdBySource',
'createdByWorkspaceMemberId',
@ -52,13 +54,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.CHRISTOPH,
nameFirstName: 'Christoph',
nameLastName: 'Callisto',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '789012345',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '789012345',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.LINKEDIN,
emailsPrimaryEmail: 'christoph.calisto@linkedin.com',
position: 1,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '789012345',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -68,13 +72,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.SYLVIE,
nameFirstName: 'Sylvie',
nameLastName: 'Palmer',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '780123456',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '780123456',
city: 'Los Angeles',
companyId: DEV_SEED_COMPANY_IDS.LINKEDIN,
emailsPrimaryEmail: 'sylvie.palmer@linkedin.com',
position: 2,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '780123456',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -84,13 +90,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.CHRISTOPHER_G,
nameFirstName: 'Christopher',
nameLastName: 'Gonzalez',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '789012345',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '789012345',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.QONTO,
emailsPrimaryEmail: 'christopher.gonzalez@qonto.com',
position: 3,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '789012345',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -100,13 +108,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.ASHLEY,
nameFirstName: 'Ashley',
nameLastName: 'Parker',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '780123456',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '780123456',
city: 'Los Angeles',
companyId: DEV_SEED_COMPANY_IDS.QONTO,
emailsPrimaryEmail: 'ashley.parker@qonto.com',
position: 4,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '780123456',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -116,13 +126,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.NICHOLAS,
nameFirstName: 'Nicholas',
nameLastName: 'Wright',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '781234567',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '781234567',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.MICROSOFT,
emailsPrimaryEmail: 'nicholas.wright@microsoft.com',
position: 5,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '781234567',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -132,13 +144,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.ISABELLA,
nameFirstName: 'Isabella',
nameLastName: 'Scott',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '782345678',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '782345678',
city: 'New York',
companyId: DEV_SEED_COMPANY_IDS.MICROSOFT,
emailsPrimaryEmail: 'isabella.scott@microsoft.com',
position: 6,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '782345678',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -148,13 +162,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.MATTHEW,
nameFirstName: 'Matthew',
nameLastName: 'Green',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '783456789',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '783456789',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.MICROSOFT,
emailsPrimaryEmail: 'matthew.green@microsoft.com',
position: 7,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '783456789',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -164,13 +180,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.ELIZABETH,
nameFirstName: 'Elizabeth',
nameLastName: 'Baker',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '784567890',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '784567890',
city: 'New York',
companyId: DEV_SEED_COMPANY_IDS.AIRBNB,
emailsPrimaryEmail: 'elizabeth.baker@airbnb.com',
position: 8,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '784567890',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -180,13 +198,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.CHRISTOPHER_N,
nameFirstName: 'Christopher',
nameLastName: 'Nelson',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '785678901',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '785678901',
city: 'San Francisco',
companyId: DEV_SEED_COMPANY_IDS.AIRBNB,
emailsPrimaryEmail: 'christopher.nelson@airbnb.com',
position: 9,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '785678901',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -196,13 +216,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.AVERY,
nameFirstName: 'Avery',
nameLastName: 'Carter',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '786789012',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '786789012',
city: 'New York',
companyId: DEV_SEED_COMPANY_IDS.AIRBNB,
emailsPrimaryEmail: 'avery.carter@airbnb.com',
position: 10,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '786789012',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -212,13 +234,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.ETHAN,
nameFirstName: 'Ethan',
nameLastName: 'Mitchell',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '787890123',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '787890123',
city: 'Los Angeles',
companyId: DEV_SEED_COMPANY_IDS.GOOGLE,
emailsPrimaryEmail: 'ethan.mitchell@google.com',
position: 11,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '787890123',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -228,13 +252,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.MADISON,
nameFirstName: 'Madison',
nameLastName: 'Perez',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '788901234',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '788901234',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.GOOGLE,
emailsPrimaryEmail: 'madison.perez@google.com',
position: 12,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '788901234',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -244,13 +270,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.BERTRAND,
nameFirstName: 'Bertrand',
nameLastName: 'Voulzy',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '788901234',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '788901234',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.GOOGLE,
emailsPrimaryEmail: 'bertrand.voulzy@google.com',
position: 13,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '788901234',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -260,13 +288,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.LOUIS,
nameFirstName: 'Louis',
nameLastName: 'Duss',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '789012345',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '789012345',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.GOOGLE,
emailsPrimaryEmail: 'louis.duss@google.com',
position: 14,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '789012345',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
@ -276,13 +306,15 @@ export const seedPeople = async (
id: DEV_SEED_PERSON_IDS.LORIE,
nameFirstName: 'Lorie',
nameLastName: 'Vladim',
phonePrimaryPhoneCountryCode: '+33',
phonePrimaryPhoneNumber: '788901235',
phonesPrimaryPhoneCountryCode: 'FR',
phonesPrimaryPhoneCallingCode: '+33',
phonesPrimaryPhoneNumber: '788901235',
city: 'Seattle',
companyId: DEV_SEED_COMPANY_IDS.GOOGLE,
emailsPrimaryEmail: 'lorie.vladim@google.com',
position: 15,
whatsappPrimaryPhoneCountryCode: '+33',
whatsappPrimaryPhoneCountryCode: 'FR',
whatsappPrimaryPhoneCallingCode: '+33',
whatsappPrimaryPhoneNumber: '788901235',
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: null,

View File

@ -231,6 +231,7 @@ const fieldPhonesMock = {
{
primaryPhoneNumber: '',
primaryPhoneCountryCode: '',
primaryPhoneCallingCode: '',
additionalPhones: {},
},
],

View File

@ -150,6 +150,7 @@ export const mapFieldMetadataToGraphqlQuery = (
{
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
`;

View File

@ -31,6 +31,9 @@ describe('computeSchemaComponents', () => {
primaryPhoneCountryCode: {
type: 'string',
},
primaryPhoneCallingCode: {
type: 'string',
},
primaryPhoneNumber: {
type: 'string',
},
@ -216,6 +219,9 @@ describe('computeSchemaComponents', () => {
primaryPhoneCountryCode: {
type: 'string',
},
primaryPhoneCallingCode: {
type: 'string',
},
primaryPhoneNumber: {
type: 'string',
},
@ -400,6 +406,9 @@ describe('computeSchemaComponents', () => {
primaryPhoneCountryCode: {
type: 'string',
},
primaryPhoneCallingCode: {
type: 'string',
},
primaryPhoneNumber: {
type: 'string',
},

View File

@ -259,6 +259,9 @@ const getSchemaComponentsProperties = ({
primaryPhoneCountryCode: {
type: 'string',
},
primaryPhoneCallingCode: {
type: 'string',
},
primaryPhoneNumber: {
type: 'string',
},

View File

@ -18,6 +18,12 @@ export const phonesCompositeType: CompositeType = {
hidden: false,
isRequired: false,
},
{
name: 'primaryPhoneCallingCode',
type: FieldMetadataType.TEXT,
hidden: false,
isRequired: false,
},
{
name: 'additionalPhones',
type: FieldMetadataType.RAW_JSON,
@ -30,5 +36,6 @@ export const phonesCompositeType: CompositeType = {
export type PhonesMetadata = {
primaryPhoneNumber: string;
primaryPhoneCountryCode: string;
primaryPhoneCallingCode: string;
additionalPhones: object | null;
};

View File

@ -193,6 +193,10 @@ export class FieldMetadataDefaultValuePhones {
@IsQuotedString()
primaryPhoneCountryCode: string | null;
@ValidateIf((_object, value) => value !== null)
@IsQuotedString()
primaryPhoneCallingCode: string | null;
@ValidateIf((_object, value) => value !== null)
@IsObject()
additionalPhones: object | null;

View File

@ -44,6 +44,7 @@ export function generateDefaultValue(
return {
primaryPhoneNumber: "''",
primaryPhoneCountryCode: "''",
primaryPhoneCallingCode: "''",
additionalPhones: null,
};
default:

View File

@ -60,8 +60,11 @@ describe('creates.create_company', () => {
name: { firstName: 'John', lastName: 'Doe' },
phones: {
primaryPhoneNumber: '610203040',
primaryPhoneCountryCode: '+33',
additionalPhones: ['{number: "610203041", countryCode: "+33"}'],
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
additionalPhones: [
'{number: "610203041", countryCode: "FR", callingCode: "+33"}',
],
},
city: 'Paris',
});

View File

@ -25,8 +25,11 @@ describe('utils.handleQueryParams', () => {
},
phones: {
primaryPhoneNumber: '322110011',
primaryPhoneCountryCode: '+33',
additionalPhones: ["{ phoneNumber: '322110012', countryCode: '+33' }"],
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
additionalPhones: [
"{ phoneNumber: '322110012', countryCode: 'FR', callingCode: '+33' }",
],
},
xUrl__url: '/x_url',
xUrl__label: 'Test xUrl',
@ -42,7 +45,7 @@ describe('utils.handleQueryParams', () => {
'linkedinUrl: {url: "/linkedin_url", label: "Test linkedinUrl"}, ' +
'whatsapp: {primaryLinkUrl: "/whatsapp_url", primaryLinkLabel: "Whatsapp Link", secondaryLinks: [{url: \'/secondary_whatsapp_url\',label: \'Secondary Whatsapp Link\'}]}, ' +
'emails: {primaryEmail: "primary@email.com", additionalEmails: ["secondary@email.com"]}, ' +
'phones: {primaryPhoneNumber: "322110011", primaryPhoneCountryCode: "+33", additionalPhones: [{ phoneNumber: \'322110012\', countryCode: \'+33\' }]}, ' +
'phones: {primaryPhoneNumber: "322110011", primaryPhoneCountryCode: "FR", primaryPhoneCallingCode: "+33", additionalPhones: [{ phoneNumber: \'322110012\', countryCode: \'+33\' }]}, ' +
'xUrl: {url: "/x_url", label: "Test xUrl"}, ' +
'annualRecurringRevenue: 100000, ' +
'idealCustomerProfile: true, ' +