Skip to content

[Epsilon] New Destination #2935

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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,20 @@
import nock from 'nock'
import { createTestEvent, createTestIntegration } from '@segment/actions-core'
import Definition from '../index'

const testDestination = createTestIntegration(Definition)

describe('Epsilon', () => {
describe('testAuthentication', () => {
it('should validate authentication inputs', async () => {
nock('https://your.destination.endpoint').get('*').reply(200, {})

const settings = {
username: '<test username>',
password: '<test password>'
}

await expect(testDestination.testAuthentication(settings)).resolves.not.toThrowError()
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { createTestEvent, createTestIntegration } from '@segment/actions-core'
import { generateTestData } from '../../../lib/test-data'
import destination from '../index'
import nock from 'nock'

const testDestination = createTestIntegration(destination)
const destinationSlug = 'actions-epsilon'

describe(`Testing snapshot for ${destinationSlug} destination:`, () => {
for (const actionSlug in destination.actions) {
it(`${actionSlug} action - required fields`, async () => {
const seedName = `${destinationSlug}#${actionSlug}`
const action = destination.actions[actionSlug]
const [eventData, settingsData] = generateTestData(seedName, destination, action, true)

nock(/.*/).persist().get(/.*/).reply(200)
nock(/.*/).persist().post(/.*/).reply(200)
nock(/.*/).persist().put(/.*/).reply(200)

const event = createTestEvent({
properties: eventData
})

const responses = await testDestination.testAction(actionSlug, {
event: event,
mapping: event.properties,
settings: settingsData,
auth: undefined
})

const request = responses[0].request
const rawBody = await request.text()

try {
const json = JSON.parse(rawBody)
expect(json).toMatchSnapshot()
return
} catch (err) {
expect(rawBody).toMatchSnapshot()
}

expect(request.headers).toMatchSnapshot()
})

it(`${actionSlug} action - all fields`, async () => {
const seedName = `${destinationSlug}#${actionSlug}`
const action = destination.actions[actionSlug]
const [eventData, settingsData] = generateTestData(seedName, destination, action, false)

nock(/.*/).persist().get(/.*/).reply(200)
nock(/.*/).persist().post(/.*/).reply(200)
nock(/.*/).persist().put(/.*/).reply(200)

const event = createTestEvent({
properties: eventData
})

const responses = await testDestination.testAction(actionSlug, {
event: event,
mapping: event.properties,
settings: settingsData,
auth: undefined
})

const request = responses[0].request
const rawBody = await request.text()

try {
const json = JSON.parse(rawBody)
expect(json).toMatchSnapshot()
return
} catch (err) {
expect(rawBody).toMatchSnapshot()
}
})
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import nock from 'nock'
import { createTestEvent, createTestIntegration } from '@segment/actions-core'
import Destination from '../../index'

const testDestination = createTestIntegration(Destination)

describe('Epsilon.send', () => {
// TODO: Test your action
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { createTestEvent, createTestIntegration } from '@segment/actions-core'
import { generateTestData } from '../../../../lib/test-data'
import destination from '../../index'
import nock from 'nock'

const testDestination = createTestIntegration(destination)
const actionSlug = 'send'
const destinationSlug = 'Epsilon'
const seedName = `${destinationSlug}#${actionSlug}`

describe(`Testing snapshot for ${destinationSlug}'s ${actionSlug} destination action:`, () => {
it('required fields', async () => {
const action = destination.actions[actionSlug]
const [eventData, settingsData] = generateTestData(seedName, destination, action, true)

nock(/.*/).persist().get(/.*/).reply(200)
nock(/.*/).persist().post(/.*/).reply(200)
nock(/.*/).persist().put(/.*/).reply(200)

const event = createTestEvent({
properties: eventData
})

const responses = await testDestination.testAction(actionSlug, {
event: event,
mapping: event.properties,
settings: settingsData,
auth: undefined
})

const request = responses[0].request
const rawBody = await request.text()

try {
const json = JSON.parse(rawBody)
expect(json).toMatchSnapshot()
return
} catch (err) {
expect(rawBody).toMatchSnapshot()
}

expect(request.headers).toMatchSnapshot()
})

it('all fields', async () => {
const action = destination.actions[actionSlug]
const [eventData, settingsData] = generateTestData(seedName, destination, action, false)

nock(/.*/).persist().get(/.*/).reply(200)
nock(/.*/).persist().post(/.*/).reply(200)
nock(/.*/).persist().put(/.*/).reply(200)

const event = createTestEvent({
properties: eventData
})

const responses = await testDestination.testAction(actionSlug, {
event: event,
mapping: event.properties,
settings: settingsData,
auth: undefined
})

const request = responses[0].request
const rawBody = await request.text()

try {
const json = JSON.parse(rawBody)
expect(json).toMatchSnapshot()
return
} catch (err) {
expect(rawBody).toMatchSnapshot()
}
})
})

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,24 @@
import type { ActionDefinition } from '@segment/actions-core'
import type { Settings } from '../generated-types'
import type { Payload } from './generated-types'
import { cachebuster, formId, identifiers } from '../fields'

const action: ActionDefinition<Settings, Payload> = {
title: 'App Open',
description: 'Sync an App Open events to Epsilon',
defaultSubscription: 'type = "track" and event = "Application Opened"',
fields: {
cachebuster,
formId,
identifiers
},
perform: (request, data) => {
// Make your partner api request here!
// return request('https://example.com', {
// method: 'post',
// json: data.payload
// })
}
}

export default action
70 changes: 70 additions & 0 deletions packages/destination-actions/src/destinations/epsilon/fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { InputField } from '@segment/actions-core/destination-kit/types'

export const eventName: InputField = {
label: 'Event Name',
description: 'The name of the event to send to Epsilon.',
type: 'string',
required: true,
default: {
'@path': '$.event'
}
}

export const formId: InputField = {
label: 'Form ID',
description: 'Form ID used in Epsilon’s system to identify app visits vs. conversions.',
type: 'string',
required: true
}

export const identifiers: InputField = {
label: 'Identifiers',
description: 'Unique identifiers for the user.',
type: 'object',
required: true,
properties: {
emailHash: {
label: 'Email address',
description: 'Accepts hashed or unhashed emails. Segment will ensure that a non hashed email is hashed before being sent to Epsilon',
type: 'string',
default: {
'@if': {
exists: { '@path': '$.context.traits.email' },
then: { '@path': '$.context.traits.email' },
else: { '@path': '$.properties.email' }
}
}
},
userAgent: {
label: 'User Agent',
description: 'User agent of the mobile device.',
type: 'string',
default: { '@path': '$.context.userAgent' }
},
userIp: {
label: 'IP Address',
description: 'IP address of the user.',
type: 'string',
default: { '@path': '$.context.ip' }
},
userId: {
label: 'User ID',
description: 'Unique identifier for the user.',
type: 'string',
default: { '@path': '$.userId' }
},
dvcid: {
label: 'Device ID',
description: 'Unique identifier for the device.',
type: 'string',
default: { '@path': '$.context.device.id' }
}
}
}

export const cachebuster: InputField = {
label: 'Cache Buster',
description: 'Unique identifier for the message. Used for cache busting.',
type: 'string',
default: { '@path': '$.messageId' }
}

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

59 changes: 59 additions & 0 deletions packages/destination-actions/src/destinations/epsilon/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { DestinationDefinition } from '@segment/actions-core'
import type { Settings } from './generated-types'

import send from './appOpen'

const destination: DestinationDefinition<Settings> = {
name: 'Epsilon (Actions)',
slug: 'actions-epsilon',
mode: 'cloud',
description: 'Sync analytics events and user profile details to Epsilon',

authentication: {
scheme: 'basic',
fields: {
username: {
label: 'Username',
description: 'Your Epsilon username',
type: 'string',
required: true
},
password: {
label: 'password',
description: 'Your Epsilon password.',
type: 'string',
required: true
},
companyId: {
label: 'Company ID',
description: 'Your Company ID. Contact Epsilon support for assistance.',
type: 'number',
required: true
},
},
testAuthentication: (request) => {
// Return a request that tests/validates the user's credentials.
// If you do not have a way to validate the authentication fields safely,
// you can remove the `testAuthentication` function, though discouraged.
}
},

extendRequest({ settings }) {
return {
username: settings.username,
password: settings.password
}
},

onDelete: async (request, { settings, payload }) => {
// Return a request that performs a GDPR delete for the provided Segment userId or anonymousId
// provided in the payload. If your destination does not support GDPR deletion you should not
// implement this function and should remove it completely.
},

actions: {
send
}
}

export default destination
Loading