From e99e29ff5389c9e9592b9a6d958470f1c9a4cdad Mon Sep 17 00:00:00 2001 From: Matthew Saunders <109765412+msaunders-twilio@users.noreply.github.com> Date: Tue, 4 Jun 2024 08:40:32 -0400 Subject: [PATCH] Update MC contacts upsert to handle multiple identifiers (#2032) * Update MC contacts upsert to handle multiple identifiers * Update defaults for phone_number_id, anonymous_id * Update default for anonymous_id * Change anonymous_id to anonymousId --- .../__tests__/sendgrid-properties.test.ts | 60 ++++++++++++------- .../sendgrid/sendgrid-properties.ts | 15 +++++ .../updateUserProfile/generated-types.ts | 14 ++++- .../sendgrid/updateUserProfile/index.ts | 40 ++++++++++++- 4 files changed, 104 insertions(+), 25 deletions(-) diff --git a/packages/destination-actions/src/destinations/sendgrid/__tests__/sendgrid-properties.test.ts b/packages/destination-actions/src/destinations/sendgrid/__tests__/sendgrid-properties.test.ts index 02c4754e42..52d325a727 100644 --- a/packages/destination-actions/src/destinations/sendgrid/__tests__/sendgrid-properties.test.ts +++ b/packages/destination-actions/src/destinations/sendgrid/__tests__/sendgrid-properties.test.ts @@ -1,32 +1,48 @@ -import { tranformValueToAcceptedDataType } from '../sendgrid-properties' +import { tranformValueToAcceptedDataType, validatePayload } from '../sendgrid-properties' -describe('tranformValueToAcceptedDataType', () => { - it('should transform a boolean value into a string', () => { - expect(tranformValueToAcceptedDataType(true)).toBe('true') - }) +describe('sendgrid-properties', () => { + describe('tranformValueToAcceptedDataType', () => { + it('should transform a boolean value into a string', () => { + expect(tranformValueToAcceptedDataType(true)).toBe('true') + }) - it('should transform an array value into a string', () => { - expect(tranformValueToAcceptedDataType([1, 2, 3])).toBe('[1,2,3]') - }) + it('should transform an array value into a string', () => { + expect(tranformValueToAcceptedDataType([1, 2, 3])).toBe('[1,2,3]') + }) - it('should transform an object value into a string', () => { - expect(tranformValueToAcceptedDataType({ a: 1 })).toBe('{"a":1}') - }) + it('should transform an object value into a string', () => { + expect(tranformValueToAcceptedDataType({ a: 1 })).toBe('{"a":1}') + }) - it('should transform nested arrays and objects into a string', () => { - const data = [[1, 2, 3], { a: 1, b: 2, c: { d: 3, e: ['f', 'g'] } }] - expect(tranformValueToAcceptedDataType(data)).toBe('[[1,2,3],{"a":1,"b":2,"c":{"d":3,"e":["f","g"]}}]') - }) + it('should transform nested arrays and objects into a string', () => { + const data = [[1, 2, 3], { a: 1, b: 2, c: { d: 3, e: ['f', 'g'] } }] + expect(tranformValueToAcceptedDataType(data)).toBe('[[1,2,3],{"a":1,"b":2,"c":{"d":3,"e":["f","g"]}}]') + }) - it('should return the value for number types', () => { - expect(tranformValueToAcceptedDataType(123)).toBe(123) - }) + it('should return the value for number types', () => { + expect(tranformValueToAcceptedDataType(123)).toBe(123) + }) + + it('should return the value for string types', () => { + expect(tranformValueToAcceptedDataType('Hello, test')).toBe('Hello, test') + }) - it('should return the value for string types', () => { - expect(tranformValueToAcceptedDataType('Hello, test')).toBe('Hello, test') + it('should return the value for date types', () => { + expect(tranformValueToAcceptedDataType('2022-11-01T00:00:00Z')).toBe('2022-11-01T00:00:00Z') + }) }) - it('should return the value for date types', () => { - expect(tranformValueToAcceptedDataType('2022-11-01T00:00:00Z')).toBe('2022-11-01T00:00:00Z') + describe('validatePayload', () => { + it('should throw an error if no identifying field is included', () => { + const payload = {} + expect(() => validatePayload(payload)).toThrowError( + 'Contact must have at least one identifying field included (email, phone_number_id, external_id, anonymous_id).' + ) + }) + + it('should not throw an error if at least one identifying field is included', () => { + const payload = { anonymous_id: 'hip-hop-anonymous' } + expect(() => validatePayload(payload)).not.toThrow() + }) }) }) diff --git a/packages/destination-actions/src/destinations/sendgrid/sendgrid-properties.ts b/packages/destination-actions/src/destinations/sendgrid/sendgrid-properties.ts index 0f660b8be3..935a4e8101 100644 --- a/packages/destination-actions/src/destinations/sendgrid/sendgrid-properties.ts +++ b/packages/destination-actions/src/destinations/sendgrid/sendgrid-properties.ts @@ -94,9 +94,24 @@ const transformValuesToAcceptedDataTypes = (data: any): any => { return tranformedData } +// Validate the payload of each contact +export const validatePayload = (payload: Payload) => { + // Validate that 1 of the 4 identifier fields is included in the payload + if (!payload.primary_email && !payload.phone_number_id && !payload.external_id && !payload.anonymous_id) { + throw new IntegrationError( + 'Contact must have at least one identifying field included (email, phone_number_id, external_id, anonymous_id).', + 'Invalid value', + 400 + ) + } +} + export const convertPayload = (payload: Payload, accountCustomFields: CustomField[]) => { const { state, primary_email, enable_batching, customFields, ...rest } = payload + // Validate that each contact payload is correct (i.e. contains 1 of the 4 identifier fields) + validatePayload(payload) + // If there are any custom fields, convert their key from sendgrid Name to sendgrid ID if needed const updatedCustomFields = customFields ? convertCustomFieldNamesToIds(customFields, accountCustomFields) diff --git a/packages/destination-actions/src/destinations/sendgrid/updateUserProfile/generated-types.ts b/packages/destination-actions/src/destinations/sendgrid/updateUserProfile/generated-types.ts index 180f40c384..51f8d6571b 100644 --- a/packages/destination-actions/src/destinations/sendgrid/updateUserProfile/generated-types.ts +++ b/packages/destination-actions/src/destinations/sendgrid/updateUserProfile/generated-types.ts @@ -64,7 +64,19 @@ export interface Payload { /** * The contact's email address. */ - primary_email: string + primary_email?: string | null + /** + * The contact's Phone Number ID. This must be a valid phone number. + */ + phone_number_id?: string | null + /** + * The contact's External ID. + */ + external_id?: string | null + /** + * The contact's Anonymous ID. + */ + anonymous_id?: string | null /** * * Additional fields to send to SendGrid. On the left-hand side, input the SendGrid Custom Fields Id. On the right-hand side, map the Segment field that contains the value. diff --git a/packages/destination-actions/src/destinations/sendgrid/updateUserProfile/index.ts b/packages/destination-actions/src/destinations/sendgrid/updateUserProfile/index.ts index 7babd5a550..4cdc72fdc7 100644 --- a/packages/destination-actions/src/destinations/sendgrid/updateUserProfile/index.ts +++ b/packages/destination-actions/src/destinations/sendgrid/updateUserProfile/index.ts @@ -201,8 +201,8 @@ const action: ActionDefinition = { label: 'Email Address', description: `The contact's email address.`, type: 'string', - allowNull: false, - required: true, + allowNull: true, + required: false, default: { '@if': { exists: { '@path': '$.traits.email' }, @@ -211,6 +211,42 @@ const action: ActionDefinition = { } } }, + phone_number_id: { + label: 'Phone Number ID', + description: `The contact's Phone Number ID. This must be a valid phone number.`, + type: 'string', + allowNull: true, + required: false, + default: { + '@if': { + exists: { '@path': '$.traits.phone' }, + then: { '@path': '$.traits.phone' }, + else: { '@path': '$.properties.phone' } + } + } + }, + external_id: { + label: 'External ID', + description: `The contact's External ID.`, + type: 'string', + allowNull: true, + required: false, + default: { + '@if': { + exists: { '@path': '$.traits.external_id' }, + then: { '@path': '$.traits.external_id' }, + else: { '@path': '$.properties.external_id' } + } + } + }, + anonymous_id: { + label: 'Anonymous ID ', + description: `The contact's Anonymous ID.`, + type: 'string', + allowNull: true, + required: false, + default: { '@path': '$.anonymousId' } + }, customFields: customFields },