Skip to content

Commit 787b55b

Browse files
committed
Merge branch 'main' into liquid-templates-metrics
2 parents fcfda4c + 861aa65 commit 787b55b

File tree

19 files changed

+763
-2
lines changed

19 files changed

+763
-2
lines changed

packages/destination-actions/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@segment/action-destinations",
33
"description": "Destination Actions engine and definitions.",
4-
"version": "3.404.0",
4+
"version": "3.405.0",
55
"repository": {
66
"type": "git",
77
"url": "https://github.com/segmentio/action-destinations",

packages/destination-actions/src/destinations/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ register('683ef14a3f9aac157e3a3446', './amazon-conversions-api')
203203
register('684a923b272a5d1e02f33914', './google-data-manager')
204204
register('68516bd8ca73bd53f38a0104', './roadwayai')
205205
register('68516c107fc235f3572f8a64', './posthog')
206+
register('6863e71f2a1e1ddc4b4612bf', './nudge')
206207

207208
function register(id: MetadataId, destinationPath: string) {
208209
// eslint-disable-next-line @typescript-eslint/no-var-requires
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { RequestClient } from '@segment/actions-core'
2+
import { Settings } from '../generated-types'
3+
import type { Payload as TrackPayload } from '../trackEvent/generated-types'
4+
import type { Payload as IdentifyPayload } from '../identifyUser/generated-types'
5+
6+
export default class BaseRequestInterface {
7+
private static baseURL = 'https://main-api.nudgenow.com/api/integration/segment'
8+
private static platform = '17'
9+
10+
public static async track(request: RequestClient, settings: Settings, payload: TrackPayload) {
11+
return await request(this.baseURL + '/events/batch', {
12+
method: 'POST',
13+
json: [payload],
14+
headers: {
15+
apikey: settings.apikey,
16+
p: this.platform
17+
}
18+
})
19+
}
20+
21+
public static async batchTrack(request: RequestClient, settings: Settings, payload: TrackPayload[]) {
22+
return await request(this.baseURL + '/events/batch', {
23+
method: 'POST',
24+
json: payload,
25+
headers: {
26+
apikey: settings.apikey,
27+
p: this.platform
28+
}
29+
})
30+
}
31+
32+
public static async identify(request: RequestClient, settings: Settings, payload: IdentifyPayload) {
33+
return await request(this.baseURL + '/identify', {
34+
method: 'PUT',
35+
json: payload,
36+
headers: {
37+
apikey: settings.apikey,
38+
p: this.platform
39+
}
40+
})
41+
}
42+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Payload as IdentifyPayload } from '../identifyUser/generated-types'
2+
3+
/**
4+
* returns the transformed value by converting all spaces to '_' and converting everything to lower case
5+
*/
6+
export function sanitize(value: string) {
7+
return value.replace(/ /g, '_').toLowerCase()
8+
}
9+
10+
/**
11+
* returns the cleaned props by removing the name, email and phone fields from the traits payload object for identify
12+
*/
13+
export function modifyProps(props?: IdentifyPayload['props']) {
14+
if (!props) return {}
15+
16+
const mutatedProps: { [key: string]: unknown } = {}
17+
for (const key in props) {
18+
if (!['name', 'phone', 'email'].includes(key)) {
19+
mutatedProps[key] = props[key]
20+
}
21+
}
22+
23+
return mutatedProps
24+
}

packages/destination-actions/src/destinations/nudge/generated-types.ts

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Testing snapshot for Nudge's identifyUser destination action: all fields 1`] = `
4+
Object {
5+
"email": "[email protected]",
6+
"ext_id": "olu!sgnptmy",
7+
"name": "oLU!sgnPTMy",
8+
"phone": "oLU!sgnPTMy",
9+
"props": Object {
10+
"testType": "oLU!sgnPTMy",
11+
},
12+
"tz": "oLU!sgnPTMy",
13+
}
14+
`;
15+
16+
exports[`Testing snapshot for Nudge's identifyUser destination action: required fields 1`] = `
17+
Object {
18+
"ext_id": "olu!sgnptmy",
19+
"props": Object {},
20+
}
21+
`;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import nock from 'nock'
2+
import { createTestEvent, createTestIntegration } from '@segment/actions-core'
3+
import Destination from '../../index'
4+
5+
const testDestination = createTestIntegration(Destination)
6+
const API_KEY = 'sample-api-token'
7+
const API_URL = 'https://main-api.nudgenow.com/api/'
8+
const timestamp = '2025-05-12T12:35:12.826Z'
9+
10+
describe('Nudge.identifyUser', () => {
11+
it('should successfully identify a new user', async () => {
12+
const event = createTestEvent({
13+
timestamp,
14+
traits: {
15+
phone: '9999999999',
16+
17+
name: 'John Doe'
18+
},
19+
userId: 'test-user'
20+
})
21+
22+
nock(API_URL).put('/integration/segment/identify').reply(200, {})
23+
24+
const responses = await testDestination.testAction('identifyUser', {
25+
event,
26+
useDefaultMappings: true,
27+
settings: {
28+
apikey: API_KEY
29+
}
30+
})
31+
32+
expect(responses[0].status).toBe(200)
33+
})
34+
35+
it('should throw error on invalid ext_id', async () => {
36+
const event = createTestEvent({
37+
timestamp,
38+
traits: {
39+
phone: '9999999999',
40+
41+
name: 'John Doe'
42+
},
43+
userId: 'Invalid User'
44+
})
45+
46+
nock(API_URL).put('/integration/segment/identify').reply(400, {
47+
message: 'VALIDATION_FAILED'
48+
})
49+
50+
const responses = testDestination.testAction('identifyUser', {
51+
event,
52+
useDefaultMappings: true,
53+
settings: {
54+
apikey: API_KEY
55+
}
56+
})
57+
58+
await expect(responses).rejects.toThrowError()
59+
})
60+
})
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { createTestEvent, createTestIntegration } from '@segment/actions-core'
2+
import { generateTestData } from '../../../../lib/test-data'
3+
import destination from '../../index'
4+
import nock from 'nock'
5+
6+
const testDestination = createTestIntegration(destination)
7+
const actionSlug = 'identifyUser'
8+
const destinationSlug = 'Nudge'
9+
const seedName = `${destinationSlug}#${actionSlug}`
10+
11+
describe(`Testing snapshot for ${destinationSlug}'s ${actionSlug} destination action:`, () => {
12+
it('required fields', async () => {
13+
const action = destination.actions[actionSlug]
14+
const [eventData, settingsData] = generateTestData(seedName, destination, action, true)
15+
16+
nock(/.*/).persist().get(/.*/).reply(200)
17+
nock(/.*/).persist().post(/.*/).reply(200)
18+
nock(/.*/).persist().put(/.*/).reply(200)
19+
20+
const event = createTestEvent({
21+
properties: eventData
22+
})
23+
24+
const responses = await testDestination.testAction(actionSlug, {
25+
event: event,
26+
mapping: event.properties,
27+
settings: settingsData,
28+
auth: undefined
29+
})
30+
31+
const request = responses[0].request
32+
const rawBody = await request.text()
33+
34+
try {
35+
const json = JSON.parse(rawBody)
36+
expect(json).toMatchSnapshot()
37+
return
38+
} catch (err) {
39+
expect(rawBody).toMatchSnapshot()
40+
}
41+
42+
expect(request.headers).toMatchSnapshot()
43+
})
44+
45+
it('all fields', async () => {
46+
const action = destination.actions[actionSlug]
47+
const [eventData, settingsData] = generateTestData(seedName, destination, action, false)
48+
49+
nock(/.*/).persist().get(/.*/).reply(200)
50+
nock(/.*/).persist().post(/.*/).reply(200)
51+
nock(/.*/).persist().put(/.*/).reply(200)
52+
53+
const event = createTestEvent({
54+
properties: eventData
55+
})
56+
57+
const responses = await testDestination.testAction(actionSlug, {
58+
event: event,
59+
mapping: event.properties,
60+
settings: settingsData,
61+
auth: undefined
62+
})
63+
64+
const request = responses[0].request
65+
const rawBody = await request.text()
66+
67+
try {
68+
const json = JSON.parse(rawBody)
69+
expect(json).toMatchSnapshot()
70+
return
71+
} catch (err) {
72+
expect(rawBody).toMatchSnapshot()
73+
}
74+
})
75+
})

packages/destination-actions/src/destinations/nudge/identifyUser/generated-types.ts

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import type { ActionDefinition } from '@segment/actions-core'
2+
import type { Settings } from '../generated-types'
3+
import type { Payload } from './generated-types'
4+
import BaseRequestInterface from '../common/baseRequestInterface'
5+
import { modifyProps, sanitize } from '../common/transforms'
6+
7+
const action: ActionDefinition<Settings, Payload> = {
8+
title: 'Identify User',
9+
description: 'Identify a user in Nudge',
10+
defaultSubscription: 'type = "identify"',
11+
fields: {
12+
ext_id: {
13+
label: 'User ID',
14+
type: 'string',
15+
description: 'The ID of the user performing the action.',
16+
required: true,
17+
default: {
18+
'@path': '$.userId'
19+
}
20+
},
21+
name: {
22+
label: 'User Name',
23+
type: 'string',
24+
description: 'The name of the user',
25+
required: false,
26+
default: {
27+
'@path': '$.traits.name'
28+
}
29+
},
30+
phone: {
31+
label: 'Phone Number',
32+
type: 'string',
33+
description: 'The phone number of the user',
34+
required: false,
35+
default: {
36+
'@path': '$.traits.phone'
37+
}
38+
},
39+
email: {
40+
label: 'Email',
41+
type: 'string',
42+
description: 'The email of the user.',
43+
required: false,
44+
default: {
45+
'@path': '$.traits.email'
46+
}
47+
},
48+
tz: {
49+
label: 'Timezone',
50+
type: 'string',
51+
description: 'The timezone of the user.',
52+
required: false,
53+
default: {
54+
'@path': '$.context.timezone'
55+
}
56+
},
57+
props: {
58+
label: 'Properties',
59+
type: 'object',
60+
description: 'Properties for the user',
61+
required: false,
62+
default: {
63+
'@path': '$.traits'
64+
}
65+
}
66+
},
67+
68+
perform: async (request, { settings, payload }) => {
69+
payload.ext_id = sanitize(payload.ext_id)
70+
payload.props = modifyProps(payload.props)
71+
72+
return BaseRequestInterface.identify(request, settings, payload)
73+
}
74+
}
75+
76+
export default action

0 commit comments

Comments
 (0)