Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PE-102] Add Snap Audience destination #2474

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import nock from 'nock'
import { createTestIntegration, IntegrationError } from '@segment/actions-core'
import SnapAudiences from '../index'

const testDestination = createTestIntegration(SnapAudiences)
const audienceId = '11111111'
const ad_account_id = '22222222'

const createAudienceInput = {
settings: {
ad_account_id
},
audienceName: 'Segment Audience Name',
audienceSettings: {
customAudienceName: 'Custom Name',
description: '',
retention_in_days: 9999
}
}

describe('Snap Audiences', () => {
describe('createAudience', () => {
it('should fail if Segment audience name is available', async () => {
createAudienceInput.audienceName = ''
await expect(testDestination.createAudience(createAudienceInput)).rejects.toThrowError(IntegrationError)
})

it('create a new Snap Audience', async () => {
nock(`https://adsapi.snapchat.com`)
.post(`/v1/adaccounts/${ad_account_id}/segments`, {
segments: [
{
name: 'Custom Name',
source_type: 'FIRST_PARTY',
ad_account_id: ad_account_id,
description: '',
retention_in_days: 9999
}
]
})
.reply(200, {
segments: [
{
segment: {
id: audienceId
}
}
]
})

createAudienceInput.audienceName = 'Snap Audience Name'

const res = await testDestination.createAudience(createAudienceInput)
expect(res).toEqual({ externalId: audienceId })
})
})

// describe('getAudience', () => {
// })
})

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
@@ -0,0 +1,138 @@
import { AudienceDestinationDefinition, IntegrationError } from '@segment/actions-core'
import type { Settings, AudienceSettings } from './generated-types'
import syncAudience from './syncAudience'
const ACCESS_TOKEN_URL = 'https://accounts.snapchat.com/login/oauth2/access_token'

interface RefreshTokenResponse {
access_token: string
}
interface SnapAudienceResponse {
segments: {
segment: {
id: string
}
}[]
}
interface CreateAudienceReq {
segments: {
name: string
source_type: string
ad_account_id: string
description: string
retention_in_days: number
}[]
}

const destination: AudienceDestinationDefinition<Settings, AudienceSettings> = {
name: 'Snap Audiences (Actions)',
slug: 'actions-snap-audiences',
mode: 'cloud',

authentication: {
scheme: 'oauth2',
fields: {
ad_account_id: {
label: 'Ad Account ID',
description: 'The ID of the Snap Ad Account',
required: true,
type: 'string'
}
},
refreshAccessToken: async (request, { auth }) => {
const res = await request<RefreshTokenResponse>(ACCESS_TOKEN_URL, {
method: 'POST',
body: new URLSearchParams({
refresh_token: auth.refreshToken,
client_id: auth.clientId,
client_secret: auth.clientSecret,
grant_type: 'refresh_token'
})
})

return { accessToken: res.data.access_token }
}
},
extendRequest({ auth }) {
return {
headers: {
authorization: `Bearer ${auth?.accessToken}`
}
}
},

audienceFields: {
customAudienceName: {
type: 'string',
label: 'Audience Name',
description:
'Name for the audience that will be created in Snap. Defaults to the Segment audience name if left blank.',
default: '',
required: false
},
description: {
type: 'string',
label: 'Audience Description',
description: 'Description of for the audience that will be created in Snap.',
default: '',
required: false
},
retention_in_days: {
type: 'number',
label: 'Retention in days',
description: '# of days to retain audience members. (Default retention is lifetime represented as 9999)',
default: 9999,
required: false
}
},
audienceConfig: {
mode: {
type: 'synced',
full_audience_sync: false
},
async createAudience(request, createAudienceInput) {
const audienceName = createAudienceInput.audienceName
const ad_account_id = createAudienceInput.settings.ad_account_id
const { customAudienceName, description, retention_in_days } = createAudienceInput.audienceSettings || {}

if (!audienceName) {
throw new IntegrationError('Missing audience name value', 'MISSING_REQUIRED_FIELD', 400)
}

const response = await request(`https://adsapi.snapchat.com/v1/adaccounts/${ad_account_id}/segments`, {
method: 'POST',
json: {
segments: [
{
name: `${customAudienceName !== '' ? customAudienceName : audienceName}`,
source_type: 'FIRST_PARTY',
ad_account_id,
description,
retention_in_days
}
]
} as CreateAudienceReq
})

const data: SnapAudienceResponse = await response.json()
const snapAudienceId = data.segments[0].segment.id

return { externalId: snapAudienceId }
},

getAudience: async (request, { externalId }) => {
const response = await request(`https://adsapi.snapchat.com/v1/segments/${externalId}`, {
method: 'GET'
})

const data: SnapAudienceResponse = await response.json()
const snapAudienceId = data.segments[0].segment.id

return { externalId: snapAudienceId }
}
},
actions: {
syncAudience
}
}

export default destination
Loading
Loading