Skip to content

Commit

Permalink
Add profile to list in upsertProfile (#1706)
Browse files Browse the repository at this point in the history
* Add profile to list in upsertProfile
-> Add list Id mapping in upsertProfile action
-> Add profile to list if list Id is provided
-> Update test case

* Update snapshot tests

* Update mapping description
  • Loading branch information
harsh-joshi99 authored Nov 14, 2023
1 parent d995b53 commit 37fdeb0
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ describe(`Testing snapshot for ${destinationSlug} destination:`, () => {
const [eventData, settingsData] = generateTestData(seedName, destination, action, false)

nock(/.*/).persist().get(/.*/).reply(200)
nock(/.*/).persist().post(/.*/).reply(200)
nock(/.*/)
.persist()
.post(/.*/)
.reply(200, { data: { id: 'fake-id' } })
nock(/.*/).persist().put(/.*/).reply(200)

const event = createTestEvent({
Expand Down
50 changes: 50 additions & 0 deletions packages/destination-actions/src/destinations/klaviyo/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { RequestClient, DynamicFieldResponse, APIError } from '@segment/actions-core'
import { API_URL, REVISION_DATE } from './config'
import { GetListResultContent } from './types'
import { Settings } from './generated-types'

export async function getListIdDynamicData(request: RequestClient, settings: Settings): Promise<DynamicFieldResponse> {
try {
const result = await request(`${API_URL}/lists/`, {
method: 'get',
headers: {
Authorization: `Klaviyo-API-Key ${settings.api_key}`,
Accept: 'application/json',
revision: REVISION_DATE
},
skipResponseCloning: true
})
const parsedContent = JSON.parse(result.content) as GetListResultContent
const choices = parsedContent.data.map((list: { id: string; attributes: { name: string } }) => {
return { value: list.id, label: list.attributes.name }
})
return {
choices
}
} catch (err) {
return {
choices: [],
nextPage: '',
error: {
message: (err as APIError).message ?? 'Unknown error',
code: (err as APIError).status + '' ?? 'Unknown error'
}
}
}
}

export async function addProfileToList(request: RequestClient, profileId: string, listId: string) {
const listData = {
data: [
{
type: 'profile',
id: profileId
}
]
}

await request(`${API_URL}/lists/${listId}/relationships/profiles/`, {
method: 'POST',
json: listData
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,12 @@ export interface EventData {
}
}
}

export interface GetListResultContent {
data: {
id: string
attributes: {
name: string
}
}[]
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import nock from 'nock'
import { IntegrationError, createTestEvent, createTestIntegration } from '@segment/actions-core'
import Definition from '../../index'
import { API_URL } from '../../config'
import * as Functions from '../../functions'

const testDestination = createTestIntegration(Definition)

Expand All @@ -11,7 +12,16 @@ export const settings = {
api_key: apiKey
}

jest.mock('../../functions', () => ({
...jest.requireActual('../../functions'),
addProfileToList: jest.fn(() => Promise.resolve())
}))

describe('Upsert Profile', () => {
afterEach(() => {
jest.resetAllMocks()
})

it('should throw error if no email, phone_number, or external_id is provided', async () => {
const event = createTestEvent({
type: 'track',
Expand Down Expand Up @@ -138,4 +148,118 @@ describe('Upsert Profile', () => {
testDestination.testAction('upsertProfile', { event, settings, useDefaultMappings: true })
).rejects.toThrowError('An error occurred while processing the request')
})

it('should add a profile to a list if list_id is provided', async () => {
const listId = 'abc123'
const profileId = '123'
const mapping = { list_id: 'abc123' }

const requestBody = {
data: {
type: 'profile',
attributes: {
email: '[email protected]',
first_name: 'John',
last_name: 'Doe',
location: {},
properties: {}
}
}
}

nock(`${API_URL}`)
.post('/profiles/', requestBody)
.reply(
200,
JSON.stringify({
data: {
id: profileId
}
})
)

nock(`${API_URL}`).post(`/lists/${listId}/relationships/profiles/`).reply(200)

const event = createTestEvent({
type: 'track',
userId: '123',
traits: {
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
list_id: listId
}
})

await expect(
testDestination.testAction('upsertProfile', { event, mapping, settings, useDefaultMappings: true })
).resolves.not.toThrowError()

expect(Functions.addProfileToList).toHaveBeenCalledWith(expect.anything(), profileId, listId)
})

it('should add an existing profile to a list if list_id is provided', async () => {
const listId = 'abc123'
const profileId = '123'
const mapping = { list_id: 'abc123' }

const requestBody = {
data: {
type: 'profile',
attributes: {
email: '[email protected]',
first_name: 'John',
last_name: 'Doe',
location: {},
properties: {}
}
}
}

const errorResponse = JSON.stringify({
errors: [
{
meta: {
duplicate_profile_id: profileId
}
}
]
})

nock(`${API_URL}`).post('/profiles/', requestBody).reply(409, errorResponse)

const updateRequestBody = {
data: {
type: 'profile',
id: profileId,
attributes: {
email: '[email protected]',
first_name: 'John',
last_name: 'Doe',
location: {},
properties: {}
}
}
}
nock(`${API_URL}`).patch(`/profiles/${profileId}`, updateRequestBody).reply(200, {})

nock(`${API_URL}`).post(`/lists/${listId}/relationships/profiles/`).reply(200)

const event = createTestEvent({
type: 'track',
userId: '123',
traits: {
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
list_id: listId
}
})

await expect(
testDestination.testAction('upsertProfile', { event, mapping, settings, useDefaultMappings: true })
).resolves.not.toThrowError()

expect(Functions.addProfileToList).toHaveBeenCalledWith(expect.anything(), profileId, listId)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ describe(`Testing snapshot for ${destinationSlug}'s ${actionSlug} destination ac
const [eventData, settingsData] = generateTestData(seedName, destination, action, false)

nock(/.*/).persist().get(/.*/).reply(200)
nock(/.*/).persist().post(/.*/).reply(200)
nock(/.*/)
.persist()
.post(/.*/)
.reply(200, { data: { id: 'fake-id' } })
nock(/.*/).persist().put(/.*/).reply(200)

const event = createTestEvent({
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { ActionDefinition } from '@segment/actions-core'
import type { ActionDefinition, DynamicFieldResponse } from '@segment/actions-core'
import type { Settings } from '../generated-types'
import type { Payload } from './generated-types'

import { API_URL } from '../config'
import { APIError, PayloadValidationError } from '@segment/actions-core'
import { KlaviyoAPIError, ProfileData } from '../types'
import { addProfileToList, getListIdDynamicData } from '../functions'

const action: ActionDefinition<Settings, Payload> = {
title: 'Upsert Profile',
Expand Down Expand Up @@ -120,10 +121,21 @@ const action: ActionDefinition<Settings, Payload> = {
default: {
'@path': '$.properties'
}
},
list_id: {
label: 'List',
description: `The Klaviyo list to add the profile to.`,
type: 'string',
dynamic: true
}
},
dynamicFields: {
list_id: async (request, { settings }): Promise<DynamicFieldResponse> => {
return getListIdDynamicData(request, settings)
}
},
perform: async (request, { payload }) => {
const { email, external_id, phone_number, ...otherAttributes } = payload
const { email, external_id, phone_number, list_id, ...otherAttributes } = payload

if (!email && !phone_number && !external_id) {
throw new PayloadValidationError('One of External ID, Phone Number and Email is required.')
Expand All @@ -146,6 +158,11 @@ const action: ActionDefinition<Settings, Payload> = {
method: 'POST',
json: profileData
})
if (list_id) {
const content = JSON.parse(profile?.content)
const id = content.data.id
await addProfileToList(request, id, list_id)
}
return profile
} catch (error) {
const { response } = error as KlaviyoAPIError
Expand All @@ -161,6 +178,10 @@ const action: ActionDefinition<Settings, Payload> = {
method: 'PATCH',
json: profileData
})

if (list_id) {
await addProfileToList(request, id, list_id)
}
return profile
}
}
Expand Down

0 comments on commit 37fdeb0

Please sign in to comment.