-
Notifications
You must be signed in to change notification settings - Fork 256
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New Cloud Destination for Kevel (#1784)
* initial integration * t message for your changes. Lines starting * adding stuf for testAuthentication * adding unit tests * tidy up * removing default tests * updating mappings * fixing defaultMapping * fixing test * fixing tests
- Loading branch information
1 parent
69c5fa5
commit 247f8e0
Showing
8 changed files
with
374 additions
and
0 deletions.
There are no files selected for viewing
12 changes: 12 additions & 0 deletions
12
packages/destination-actions/src/destinations/kevel/generated-types.ts
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
47 changes: 47 additions & 0 deletions
47
packages/destination-actions/src/destinations/kevel/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import type { DestinationDefinition } from '@segment/actions-core' | ||
import type { Settings } from './generated-types' | ||
|
||
import syncAudience from './syncAudience' | ||
|
||
import syncTraits from './syncTraits' | ||
|
||
const destination: DestinationDefinition<Settings> = { | ||
name: 'Kevel', | ||
slug: 'actions-kevel', | ||
description: | ||
'Send Segment user profiles and Segment Audiences to Kevel. Only users with a Segment userId will be synced.', | ||
mode: 'cloud', | ||
|
||
authentication: { | ||
scheme: 'custom', | ||
fields: { | ||
networkId: { | ||
label: 'Kevel Network ID', | ||
description: 'Your Kevel Network ID', | ||
type: 'string', | ||
required: true | ||
}, | ||
apiKey: { | ||
label: 'Kevel API Key', | ||
description: 'Your Kevel API Key', | ||
type: 'string', | ||
required: true | ||
} | ||
} | ||
}, | ||
extendRequest({ settings }) { | ||
return { | ||
headers: { | ||
'X-Adzerk-ApiKey': settings.apiKey, | ||
'Content-Type': 'application/json', | ||
'X-Adzerk-Sdk-Version': 'adzerk-segment-integration:v1.0' | ||
} | ||
} | ||
}, | ||
actions: { | ||
syncAudience, | ||
syncTraits | ||
} | ||
} | ||
|
||
export default destination |
108 changes: 108 additions & 0 deletions
108
packages/destination-actions/src/destinations/kevel/syncAudience/__tests__/index.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import nock from 'nock' | ||
import { createTestEvent, createTestIntegration } from '@segment/actions-core' | ||
import Destination from '../../index' | ||
|
||
const testDestination = createTestIntegration(Destination) | ||
|
||
const goodTrackEvent = createTestEvent({ | ||
type: 'track', | ||
userId: 'uid1', | ||
context: { | ||
personas: { | ||
computation_class: 'audience', | ||
computation_key: 'kevel_segment_test_name' | ||
}, | ||
traits: { | ||
email: '[email protected]' | ||
} | ||
}, | ||
properties: { | ||
audience_key: 'kevel_segment_test_name', | ||
kevel_segment_test_name: true | ||
} | ||
}) | ||
|
||
const goodIdentifyEvent = createTestEvent({ | ||
type: 'identify', | ||
userId: 'uid1', | ||
context: { | ||
personas: { | ||
computation_class: 'audience', | ||
computation_key: 'kevel_segment_test_name' | ||
} | ||
}, | ||
traits: { | ||
audience_key: 'kevel_segment_test_name', | ||
kevel_segment_test_name: true | ||
}, | ||
properties: undefined | ||
}) | ||
|
||
const badEvent = createTestEvent({ | ||
userId: 'uid1', | ||
context: { | ||
personas: { | ||
computation_key: 'kevel_segment_test_name' | ||
}, | ||
traits: { | ||
email: '[email protected]' | ||
} | ||
}, | ||
properties: { | ||
audience_key: 'kevel_segment_test_name', | ||
kevel_segment_test_name: true | ||
} | ||
}) | ||
|
||
describe('Kevel.syncAudience', () => { | ||
it('should not throw an error if the audience creation succeed - track', async () => { | ||
const userId = 'uid1' | ||
const networkId1 = 'networkId1' | ||
const baseUrl = `https://e-${networkId1}.adzerk.net/udb/${networkId1}` | ||
|
||
nock(baseUrl) | ||
.post(`/interests?userKey=${userId}`, JSON.stringify(['kevel_segment_test_name'])) | ||
.reply(200) | ||
|
||
await expect( | ||
testDestination.testAction('syncAudience', { | ||
event: goodTrackEvent, | ||
settings: { | ||
networkId: networkId1, | ||
apiKey: 'apiKey1' | ||
}, | ||
useDefaultMappings: true | ||
}) | ||
).resolves.not.toThrowError() | ||
}) | ||
|
||
it('should not throw an error if the audience creation succeed - track', async () => { | ||
const userId = 'uid1' | ||
const networkId1 = 'networkId1' | ||
const baseUrl = `https://e-${networkId1}.adzerk.net/udb/${networkId1}` | ||
|
||
nock(baseUrl) | ||
.post(`/interests?userKey=${userId}`, JSON.stringify(['kevel_segment_test_name'])) | ||
.reply(200) | ||
|
||
await expect( | ||
testDestination.testAction('syncAudience', { | ||
event: goodIdentifyEvent, | ||
settings: { | ||
networkId: networkId1, | ||
apiKey: 'apiKey1' | ||
}, | ||
useDefaultMappings: true | ||
}) | ||
).resolves.not.toThrowError() | ||
}) | ||
|
||
it('should throw an error if audience creation event missing mandatory field', async () => { | ||
await expect( | ||
testDestination.testAction('syncAudience', { | ||
event: badEvent, | ||
useDefaultMappings: true | ||
}) | ||
).rejects.toThrowError("The root value is missing the required field 'segment_computation_action'") | ||
}) | ||
}) |
22 changes: 22 additions & 0 deletions
22
packages/destination-actions/src/destinations/kevel/syncAudience/generated-types.ts
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
71 changes: 71 additions & 0 deletions
71
packages/destination-actions/src/destinations/kevel/syncAudience/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import type { ActionDefinition } from '@segment/actions-core' | ||
import type { Settings } from '../generated-types' | ||
import type { Payload } from './generated-types' | ||
|
||
const action: ActionDefinition<Settings, Payload> = { | ||
title: 'Sync Audience', | ||
description: 'Sync a Segment Engage Audience to a Kevel Segment. Only users with a Segment userId will be synced.', | ||
defaultSubscription: 'type = "track" or type = "identify"', | ||
fields: { | ||
segment_computation_key: { | ||
label: 'Audience Key', | ||
description: 'Segment Audience name to which user identifier should be added or removed', | ||
type: 'string', | ||
unsafe_hidden: true, | ||
required: true, | ||
default: { | ||
'@path': '$.context.personas.computation_key' | ||
} | ||
}, | ||
segment_computation_action: { | ||
label: 'Segment Computation Action', | ||
description: | ||
"Segment computation class used to determine if input event is from an Engage Audience'. Value must be = 'audience'.", | ||
type: 'string', | ||
unsafe_hidden: true, | ||
required: true, | ||
default: { | ||
'@path': '$.context.personas.computation_class' | ||
}, | ||
choices: [{ label: 'audience', value: 'audience' }] | ||
}, | ||
segment_user_id: { | ||
label: 'User ID', | ||
description: "The user's unique ID", | ||
type: 'string', | ||
unsafe_hidden: true, | ||
required: true, | ||
default: { '@path': '$.userId' } | ||
}, | ||
traits_or_props: { | ||
label: 'Traits or properties object', | ||
description: 'A computed object for track and identify events. This field should not need to be edited.', | ||
type: 'object', | ||
required: true, | ||
unsafe_hidden: true, | ||
default: { | ||
'@if': { | ||
exists: { '@path': '$.properties' }, | ||
then: { '@path': '$.properties' }, | ||
else: { '@path': '$.traits' } | ||
} | ||
} | ||
} | ||
}, | ||
perform: async (request, data) => { | ||
const settings = data.settings | ||
|
||
const baseUrl = `https://e-${settings.networkId}.adzerk.net/udb/${settings.networkId}` | ||
|
||
const payload = data.payload | ||
|
||
const audienceValue = payload.traits_or_props[payload.segment_computation_key] | ||
|
||
return request(`${baseUrl}/interests?userKey=${payload.segment_user_id}`, { | ||
json: [payload.segment_computation_key], | ||
method: audienceValue ? 'POST' : 'DELETE' | ||
}) | ||
} | ||
} | ||
|
||
export default action |
53 changes: 53 additions & 0 deletions
53
packages/destination-actions/src/destinations/kevel/syncTraits/__tests__/index.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import nock from 'nock' | ||
import { createTestEvent, createTestIntegration } from '@segment/actions-core' | ||
import Destination from '../../index' | ||
|
||
const testDestination = createTestIntegration(Destination) | ||
|
||
const goodIdentifyEvent = createTestEvent({ | ||
type: 'identify', | ||
userId: 'uid1', | ||
|
||
traits: { | ||
first_name: 'Billy', | ||
last_name: 'Bob' | ||
} | ||
}) | ||
|
||
describe('Kevel.syncTraits', () => { | ||
it('should fetch and merge traits, and then not throw an error - track', async () => { | ||
const userId = 'uid1' | ||
const networkId1 = 'networkId1' | ||
const baseUrl = `https://e-${networkId1}.adzerk.net/udb/${networkId1}` | ||
|
||
const allTraits = { | ||
age: 24, | ||
first_name: 'Billy', | ||
last_name: 'Bob' | ||
} | ||
|
||
nock(baseUrl) | ||
.get(`/read?userKey=${userId}`) | ||
.reply( | ||
200, | ||
JSON.stringify({ | ||
custom: { | ||
age: 24 | ||
} | ||
}) | ||
) | ||
|
||
nock(baseUrl).post(`/customProperties?userKey=${userId}`, JSON.stringify(allTraits)).reply(200) | ||
|
||
await expect( | ||
testDestination.testAction('syncTraits', { | ||
event: goodIdentifyEvent, | ||
settings: { | ||
networkId: networkId1, | ||
apiKey: 'apiKey1' | ||
}, | ||
useDefaultMappings: true | ||
}) | ||
).resolves.not.toThrowError() | ||
}) | ||
}) |
14 changes: 14 additions & 0 deletions
14
packages/destination-actions/src/destinations/kevel/syncTraits/generated-types.ts
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
47 changes: 47 additions & 0 deletions
47
packages/destination-actions/src/destinations/kevel/syncTraits/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import type { ActionDefinition } from '@segment/actions-core' | ||
import type { Settings } from '../generated-types' | ||
import type { Payload } from './generated-types' | ||
|
||
const action: ActionDefinition<Settings, Payload> = { | ||
title: 'Sync Traits', | ||
description: 'Sync user profile traits from Segment to Kevel', | ||
defaultSubscription: 'type = "identify"', | ||
fields: { | ||
segment_user_id: { | ||
label: 'User ID', | ||
description: "The user's unique ID", | ||
type: 'string', | ||
required: true, | ||
default: { '@path': '$.userId' } | ||
}, | ||
traits: { | ||
label: 'Traits', | ||
description: "The user's profile traits / attributes", | ||
type: 'object', | ||
required: true, | ||
default: { '@path': '$.traits' } | ||
} | ||
}, | ||
perform: async (request, data) => { | ||
const settings = data.settings | ||
|
||
const baseUrl = `https://e-${settings.networkId}.adzerk.net/udb/${settings.networkId}` | ||
|
||
const payload = data.payload | ||
|
||
const existingResponse = await request(`${baseUrl}/read?userKey=${payload.segment_user_id}`, { | ||
method: 'GET' | ||
}) | ||
|
||
const existingRecord = await existingResponse.json() | ||
|
||
const mergedTraits = { ...existingRecord?.custom, ...payload.traits } | ||
|
||
return request(`${baseUrl}/customProperties?userKey=${payload.segment_user_id}`, { | ||
json: mergedTraits, | ||
method: 'POST' | ||
}) | ||
} | ||
} | ||
|
||
export default action |