Skip to content

Commit

Permalink
First push with CustomEvents Destination Action (#2570)
Browse files Browse the repository at this point in the history
* Saving progress

* Update packages/destination-actions/src/destinations/attentive/index.ts

Co-authored-by: Joe Ayoub <[email protected]>

* Update packages/destination-actions/src/destinations/attentive/customEvents/index.ts

Co-authored-by: Joe Ayoub <[email protected]>

* Update packages/destination-actions/src/destinations/attentive/customEvents/index.ts

Co-authored-by: Joe Ayoub <[email protected]>

* Update packages/destination-actions/src/destinations/attentive/customEvents/index.ts

Co-authored-by: Joe Ayoub <[email protected]>

* Update packages/destination-actions/src/destinations/attentive/customEvents/index.ts

Co-authored-by: Joe Ayoub <[email protected]>

* Update packages/destination-actions/src/destinations/attentive/customEvents/index.ts

Co-authored-by: Joe Ayoub <[email protected]>

* Update packages/destination-actions/src/destinations/attentive/customEvents/index.ts

Co-authored-by: Joe Ayoub <[email protected]>

* Update packages/destination-actions/src/destinations/attentive/customEvents/index.ts

Co-authored-by: Joe Ayoub <[email protected]>

* Update packages/destination-actions/src/destinations/attentive/customEvents/index.ts

Co-authored-by: Joe Ayoub <[email protected]>

* Update packages/destination-actions/src/destinations/attentive/customEvents/index.ts

Co-authored-by: Joe Ayoub <[email protected]>

* Update index.ts

Added default mapping for phone field

* refacting the fields and request

* correcting yarn version

* minor tweaks

* minor tweaks

* adding types

* Update index.test.ts

* Update index.test.ts

* Update index.test.ts

* Update index.test.ts

* adding types

* New endpoints

* added of endpoints

* bug fixes

* config file added

* index updates

* updating snapshot and fixing a field type issue

* adding a unit test

* new unit test

* fixing breaking test

* removing snapshot

---------

Co-authored-by: Joe Ayoub <[email protected]>
Co-authored-by: Joe Ayoub <[email protected]>
  • Loading branch information
3 people authored Nov 26, 2024
1 parent 181af10 commit b1c9e56
Show file tree
Hide file tree
Showing 6 changed files with 353 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import nock from 'nock'
import { createTestEvent, createTestIntegration, SegmentEvent, PayloadValidationError } from '@segment/actions-core'
import Definition from '../../index'
import { Settings } from '../../generated-types'

let testDestination = createTestIntegration(Definition)
const timestamp = '2024-01-08T13:52:50.212Z'

const settings: Settings = {
apiKey: 'test-api-key'
}

const validPayload = {
timestamp: timestamp,
event: 'Event Type 1',
messageId: '123e4567-e89b-12d3-a456-426614174000',
type: 'track',
userId: '123e4567-e89b-12d3-a456-426614174000',
context: {
traits: {
phone: '+3538675765689',
email: '[email protected]'
}
},
properties: {
tracking_url: 'https://tracking-url.com',
product_name: 'Product X'
}
} as Partial<SegmentEvent>

const mapping = {
type: { '@path': '$.event' },
userIdentifiers: {
phone: { '@path': '$.context.traits.phone' },
email: { '@path': '$.context.traits.email' },
clientUserId: { '@path': '$.userId' }
},
properties: { '@path': '$.properties' },
externalEventId: { '@path': '$.messageId' },
occurredAt: { '@path': '$.timestamp' }
}

const expectedPayload = {
type: 'Event Type 1',
properties: {
tracking_url: 'https://tracking-url.com',
product_name: 'Product X'
},
externalEventId: '123e4567-e89b-12d3-a456-426614174000',
occurredAt: '2024-01-08T13:52:50.212Z',
user: {
phone: '+3538675765689',
email: '[email protected]',
externalIdentifiers: {
clientUserId: '123e4567-e89b-12d3-a456-426614174000'
}
}
}

beforeEach((done) => {
testDestination = createTestIntegration(Definition)
nock.cleanAll()
done()
})

describe('Attentive.customEvents', () => {
it('should send a custom event to Attentive', async () => {
const event = createTestEvent(validPayload)

nock('https://api.attentivemobile.com').post('/v1/events/custom', expectedPayload).reply(200, {})

const responses = await testDestination.testAction('customEvents', {
event,
settings,
useDefaultMappings: true,
mapping
})

expect(responses.length).toBe(1)
expect(responses[0].status).toBe(200)
})

it('should throw error if no identifiers provided', async () => {
const badPayload = {
...validPayload
}
delete badPayload?.context?.traits?.phone
delete badPayload?.context?.traits?.email
badPayload.userId = undefined

const event = createTestEvent(badPayload)

await expect(
testDestination.testAction('customEvents', {
event,
settings,
useDefaultMappings: true,
mapping
})
).rejects.toThrowError(new PayloadValidationError('At least one user identifier is required.'))
})
})

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,135 @@
import { ActionDefinition, PayloadValidationError } from '@segment/actions-core'
import type { Settings } from '../generated-types'
import type { Payload } from './generated-types'
import { CustomEvent, User } from './types'

const action: ActionDefinition<Settings, Payload> = {
title: 'Custom Events',
description: 'Send Segment analytics events to Attentive.',
defaultSubscription: 'type = "track"',
fields: {
type: {
label: 'Type',
description: 'The type of event. This name is case sensitive. "Order shipped" and "Order Shipped" would be considered different event types.',
type: 'string',
required: true,
default: {
'@path': '$.event'
}
},
userIdentifiers: {
label: 'User Identifiers',
description: 'At least one identifier is required. Custom identifiers can be added as additional key:value pairs.',
type: 'object',
required: true,
additionalProperties: true,
defaultObjectUI: 'keyvalue:only',
properties: {
phone: {
label: 'Phone',
description: "The user's phone number in E.164 format.",
type: 'string',
required: false
},
email: {
label: 'Email',
description: "The user's email address.",
type: 'string',
format: 'email',
required: false
},
clientUserId: {
label: 'Client User ID',
description: 'A primary ID for a user. Should be a UUID.',
type: 'string',
format: 'uuid',
required: false
}
},
default: {
phone: {
'@if': {
exists: { '@path': '$.context.traits.phone' },
then: { '@path': '$.context.traits.phone' },
else: { '@path': '$.properties.phone' }
}
},
email: {
'@if': {
exists: { '@path': '$.context.traits.email' },
then: { '@path': '$.context.traits.email' },
else: { '@path': '$.properties.email' }
}
},
clientUserId: { '@path': '$.userId' }
}
},
properties: {
label: 'Properties',
description: 'Metadata to associate with the event.',
type: 'object',
required: false,
default: {
'@path': '$.properties'
}
},
externalEventId: {
label: 'External Event Id',
description: 'A unique identifier representing this specific event. Should be a UUID format.',
type: 'string',
format: 'uuid',
required: false,
default: {
'@path': '$.messageId'
}
},
occurredAt: {
label: 'Occurred At',
description: 'Timestamp for the event, ISO 8601 format.',
type: 'string',
required: false,
default: {
'@path': '$.timestamp'
}
}
},
perform: (request, { payload }) => {
const {
externalEventId,
type,
properties,
occurredAt,
userIdentifiers: { phone, email, clientUserId, ...customIdentifiers }
} = payload

if (!email && !phone && !clientUserId && Object.keys(customIdentifiers).length === 0) {
throw new PayloadValidationError('At least one user identifier is required.')
}

const json: CustomEvent = {
type,
properties,
externalEventId,
occurredAt,
user: {
phone,
email,
...(clientUserId || customIdentifiers
? {
externalIdentifiers: {
...(clientUserId ? { clientUserId } : undefined),
...(Object.entries(customIdentifiers).length>0 ? { customIdentifiers } : undefined)
}
}
: {})
} as User
}

return request('https://api.attentivemobile.com/v1/events/custom', {
method: 'post',
json
})
}
}

export default action
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface CustomEvent {
type: string
properties?: Record<string, unknown>
externalEventId?: string
occurredAt?: string
user: User
}

export interface User {
phone?: string
email?: string
externalIdentifiers?: {
clientUserId?: string
[key: string]: string | undefined
}
}

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

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

const destination: DestinationDefinition<Settings> = {
name: 'Attentive',
slug: 'actions-attentive',
mode: 'cloud',
description: 'Send Segment analytics events to Attentive.',
authentication: {
scheme: 'custom',
fields: {
apiKey: {
label: 'API Key',
description: 'Your Attentive API Key.',
type: 'string',
required: true
}
},
testAuthentication: async (request, { settings }) => {
return request('https://api.attentivemobile.com/v1/me', {
method: 'GET',
headers: {
Authorization: `Bearer ${settings.apiKey}`
}
})
}
},

extendRequest({ settings }) {
return {
headers: {
Authorization: `Bearer ${settings.apiKey}`,
'Content-Type': 'application/json'
}
}
},
actions: {
customEvents
},
presets: [
{
name: 'Track Event',
subscribe: 'type = "track"',
partnerAction: 'customEvents',
mapping: defaultValues(customEvents.fields),
type: 'automatic'
}
]
}

export default destination

0 comments on commit b1c9e56

Please sign in to comment.