-
Notifications
You must be signed in to change notification settings - Fork 268
Added Functionaility for Custom Attributes and Subscribers API from Attentive #2873
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"name": "@segment/analytics-browser-actions-braze-cloud-plugins", | ||
"version": "1.85.0", | ||
"version": "1.86.0", | ||
"license": "MIT", | ||
"publishConfig": { | ||
"access": "public", | ||
|
@@ -15,7 +15,7 @@ | |
}, | ||
"typings": "./dist/esm", | ||
"dependencies": { | ||
"@segment/analytics-browser-actions-braze": "^1.85.0", | ||
"@segment/analytics-browser-actions-braze": "^1.86.0", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hi @duynguyen100 . Did you mean to change this file? If not can you revert the change to this file please? |
||
"@segment/browser-destination-runtime": "^1.77.0" | ||
}, | ||
"peerDependencies": { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"name": "@segment/analytics-browser-actions-braze", | ||
"version": "1.85.0", | ||
"version": "1.86.0", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hi @duynguyen100 . Did you mean to change this file? If not can you revert the change to this file please? |
||
"license": "MIT", | ||
"publishConfig": { | ||
"access": "public", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
{ | ||
"name": "@segment/action-destinations", | ||
"description": "Destination Actions engine and definitions.", | ||
"version": "3.372.0", | ||
"version": "3.373.0", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/segmentio/action-destinations", | ||
|
@@ -36,7 +36,7 @@ | |
"@types/google-libphonenumber": "^7.4.23", | ||
"@types/jest": "^27.0.0", | ||
"@types/ssh2-sftp-client": "^9.0.0", | ||
"jest": "^27.3.1", | ||
"jest": "^27.5.1", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hi @duynguyen100 . Did you mean to change this file? If not can you revert the change to this file please? |
||
"nock": "^13.1.4" | ||
}, | ||
"dependencies": { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
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: 'Custom Attribute Event', | ||
messageId: '123e4567-e89b-12d3-a456-426614174000', | ||
type: 'track', | ||
userId: '123e4567-e89b-12d3-a456-426614174000', | ||
context: { | ||
traits: { | ||
phone: '+3538675765689', | ||
email: '[email protected]' | ||
} | ||
}, | ||
properties: { | ||
// Properties section | ||
age: '24', | ||
birthday: '1986-11-16', | ||
'sign up': '2021-04-23T16:04:33Z', | ||
'favorite team': 'Minnesota Vikings', | ||
'Gift card balance': '50.89', | ||
VIP: 'TRUE' | ||
} | ||
} as Partial<SegmentEvent> | ||
|
||
const mapping = { | ||
userIdentifiers: { | ||
phone: { '@path': '$.context.traits.phone' }, | ||
email: { '@path': '$.context.traits.email' }, | ||
clientUserId: { '@path': '$.userId' } | ||
}, | ||
properties: { '@path': '$.properties' } | ||
} | ||
|
||
const _expectedPayload = { | ||
properties: { | ||
// Expected payload for the API (properties are mapped directly) | ||
age: '24', | ||
birthday: '1986-11-16', | ||
'sign up': '2021-04-23T16:04:33Z', | ||
'favorite team': 'Minnesota Vikings', | ||
'Gift card balance': '50.89', | ||
VIP: 'TRUE' | ||
}, | ||
user: { | ||
phone: '+3538675765689', | ||
email: '[email protected]', | ||
externalIdentifiers: { | ||
clientUserId: '123e4567-e89b-12d3-a456-426614174000' | ||
} | ||
} | ||
} | ||
|
||
beforeEach((done) => { | ||
testDestination = createTestIntegration(Definition) | ||
nock.cleanAll() | ||
done() | ||
}) | ||
|
||
describe('Attentive.customAttributes', () => { | ||
it('should send custom attributes to Attentive', async () => { | ||
const event = createTestEvent(validPayload) | ||
|
||
// Mock the correct API endpoint and response for custom attributes | ||
nock('https://api.attentivemobile.com') | ||
.post('/v1/attributes/custom', (body) => { | ||
return ( | ||
body.properties.age === '24' && | ||
body.properties.birthday === '1986-11-16' && | ||
body.user.phone === '+3538675765689' && | ||
body.user.email === '[email protected]' && | ||
body.user.externalIdentifiers.clientUserId === '123e4567-e89b-12d3-a456-426614174000' | ||
) | ||
}) | ||
.matchHeader('authorization', 'Bearer test-api-key') | ||
.matchHeader('content-type', 'application/json') | ||
.reply(200, {}) | ||
|
||
// Test sending the custom attributes | ||
const responses = await testDestination.testAction('customAttributes', { | ||
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('customAttributes', { | ||
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,128 @@ | ||||||
import { ActionDefinition, PayloadValidationError } from '@segment/actions-core' | ||||||
import type { Settings } from '../generated-types' | ||||||
import type { Payload } from './generated-types' | ||||||
import { CustomAttribute, User } from './types' | ||||||
|
||||||
const action: ActionDefinition<Settings, Payload> = { | ||||||
title: 'Custom Attributes', | ||||||
description: 'Send custom attributes to Attentive.', | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
defaultSubscription: 'type = "identify"', | ||||||
fields: { | ||||||
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: 'Custom attributes to associate with the user.', | ||||||
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, | ||||||
properties, | ||||||
occurredAt, | ||||||
userIdentifiers: { phone, email, clientUserId, ...customIdentifiers } | ||||||
} = payload | ||||||
|
||||||
// Ensure at least one identifier exists | ||||||
if (!email && !phone && !clientUserId && Object.keys(customIdentifiers).length === 0) { | ||||||
throw new PayloadValidationError('At least one user identifier is required.') | ||||||
} | ||||||
|
||||||
// Construct the custom attributes payload | ||||||
const json: CustomAttribute = { | ||||||
properties, | ||||||
externalEventId, | ||||||
occurredAt, | ||||||
user: { | ||||||
phone, | ||||||
email, | ||||||
...(clientUserId || customIdentifiers | ||||||
? { | ||||||
externalIdentifiers: { | ||||||
...(clientUserId ? { clientUserId } : undefined), | ||||||
...(Object.entries(customIdentifiers).length > 0 ? { customIdentifiers } : undefined) | ||||||
} | ||||||
} | ||||||
: {}) | ||||||
} as User | ||||||
} | ||||||
|
||||||
// Send the request to the Attentive API | ||||||
return request('https://api.attentivemobile.com/v1/attributes/custom', { | ||||||
method: 'post', | ||||||
json | ||||||
}) | ||||||
} | ||||||
} | ||||||
|
||||||
export default action |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// types.ts | ||
|
||
export interface CustomAttributes { | ||
user: User // User object | ||
attributes: Record<string, any> // Custom attributes to be sent to Attentive | ||
} | ||
|
||
export interface User { | ||
phone?: string // Optional phone number | ||
email?: string // Optional email address | ||
externalIdentifiers?: { | ||
// Optional external identifiers | ||
clientUserId?: string // Optional custom user ID | ||
[key: string]: string | undefined // Additional custom identifiers | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hi @duynguyen100 - can you undo this change please? This affects the entire solution and it's a file that should be changed.