-
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.
adding new Action for Mixpanel (#1802)
* adding new Action for Mixpanel * max increments value change
- Loading branch information
1 parent
8ef31b5
commit 3cee076
Showing
7 changed files
with
365 additions
and
1 deletion.
There are no files selected for viewing
18 changes: 18 additions & 0 deletions
18
...rc/destinations/mixpanel/incrementProperties/__test__/__snapshots__/snapshot.test.ts.snap
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,18 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Testing snapshot for Mixpanel's identifyUser destination action: all fields 1`] = `"data=%7B%22event%22%3A%22%24identify%22%2C%22properties%22%3A%7B%22%24identified_id%22%3A%221SVsemB5FYy7%23Wu9%22%2C%22%24anon_id%22%3A%221SVsemB5FYy7%23Wu9%22%2C%22token%22%3A%221SVsemB5FYy7%23Wu9%22%2C%22segment_source_name%22%3A%221SVsemB5FYy7%23Wu9%22%7D%7D"`; | ||
|
||
exports[`Testing snapshot for Mixpanel's identifyUser destination action: required fields 1`] = `"data=%7B%22event%22%3A%22%24identify%22%2C%22properties%22%3A%7B%22%24identified_id%22%3A%221SVsemB5FYy7%23Wu9%22%2C%22%24anon_id%22%3A%221SVsemB5FYy7%23Wu9%22%2C%22token%22%3A%221SVsemB5FYy7%23Wu9%22%2C%22segment_source_name%22%3A%221SVsemB5FYy7%23Wu9%22%7D%7D"`; | ||
|
||
exports[`Testing snapshot for Mixpanel's identifyUser destination action: required fields 2`] = ` | ||
Headers { | ||
Symbol(map): Object { | ||
"Content-Type": Array [ | ||
"application/x-www-form-urlencoded;charset=UTF-8", | ||
], | ||
"user-agent": Array [ | ||
"Segment (Actions)", | ||
], | ||
}, | ||
} | ||
`; |
148 changes: 148 additions & 0 deletions
148
.../destination-actions/src/destinations/mixpanel/incrementProperties/__test__/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,148 @@ | ||
import nock from 'nock' | ||
import { createTestEvent, createTestIntegration } from '@segment/actions-core' | ||
import Destination from '../../index' | ||
import { ApiRegions } from '../../common/utils' | ||
|
||
const testDestination = createTestIntegration(Destination) | ||
const MIXPANEL_API_SECRET = 'test-api-key' | ||
const MIXPANEL_PROJECT_TOKEN = 'test-proj-token' | ||
const timestamp = '2021-08-17T15:21:15.449Z' | ||
|
||
describe('Mixpanel.incrementProperties', () => { | ||
const defaultProperties = { term: 'foo', increment: { searches: 1 } } | ||
it('should use EU server URL', async () => { | ||
const event = createTestEvent({ timestamp, event: 'search', properties: defaultProperties }) | ||
|
||
nock('https://api-eu.mixpanel.com').post('/engage').reply(200, {}) | ||
nock('https://api-eu.mixpanel.com').post('/track').reply(200, {}) | ||
|
||
const responses = await testDestination.testAction('incrementProperties', { | ||
event, | ||
useDefaultMappings: true, | ||
settings: { | ||
projectToken: MIXPANEL_PROJECT_TOKEN, | ||
apiSecret: MIXPANEL_API_SECRET, | ||
apiRegion: ApiRegions.EU | ||
} | ||
}) | ||
|
||
expect(responses[0].status).toBe(200) | ||
expect(responses[0].data).toMatchObject({}) | ||
expect(responses[0].options.body).toMatchObject( | ||
new URLSearchParams({ | ||
data: JSON.stringify({ | ||
$token: MIXPANEL_PROJECT_TOKEN, | ||
$distinct_id: 'user1234', | ||
$ip: '8.8.8.8', | ||
$add: { | ||
searches: 1 | ||
} | ||
}) | ||
}) | ||
) | ||
}) | ||
|
||
it('should default to US endpoint if apiRegion setting is undefined', async () => { | ||
const event = createTestEvent({ timestamp, event: 'search', properties: defaultProperties }) | ||
|
||
nock('https://api.mixpanel.com').post('/engage').reply(200, {}) | ||
nock('https://api.mixpanel.com').post('/track').reply(200, {}) | ||
|
||
const responses = await testDestination.testAction('incrementProperties', { | ||
event, | ||
useDefaultMappings: true, | ||
settings: { | ||
projectToken: MIXPANEL_PROJECT_TOKEN, | ||
apiSecret: MIXPANEL_API_SECRET | ||
} | ||
}) | ||
|
||
expect(responses[0].status).toBe(200) | ||
expect(responses[0].data).toMatchObject({}) | ||
expect(responses[0].options.body).toMatchObject( | ||
new URLSearchParams({ | ||
data: JSON.stringify({ | ||
$token: MIXPANEL_PROJECT_TOKEN, | ||
$distinct_id: 'user1234', | ||
$ip: '8.8.8.8', | ||
$add: { | ||
searches: 1 | ||
} | ||
}) | ||
}) | ||
) | ||
}) | ||
|
||
it('should use anonymous_id as distinct_id if user_id is missing', async () => { | ||
const event = createTestEvent({ userId: null, event: 'search', properties: defaultProperties }) | ||
|
||
nock('https://api.mixpanel.com').post('/track').reply(200, {}) | ||
nock('https://api.mixpanel.com').post('/engage').reply(200, {}) | ||
|
||
const responses = await testDestination.testAction('incrementProperties', { | ||
event, | ||
useDefaultMappings: true, | ||
settings: { | ||
projectToken: MIXPANEL_PROJECT_TOKEN, | ||
apiSecret: MIXPANEL_API_SECRET | ||
} | ||
}) | ||
|
||
expect(responses[0].status).toBe(200) | ||
expect(responses[0].data).toMatchObject({}) | ||
expect(responses[0].options.body).toMatchObject( | ||
new URLSearchParams({ | ||
data: JSON.stringify({ | ||
$token: MIXPANEL_PROJECT_TOKEN, | ||
$distinct_id: event.anonymousId, | ||
$ip: '8.8.8.8', | ||
$add: { | ||
searches: 1 | ||
} | ||
}) | ||
}) | ||
) | ||
}) | ||
|
||
it('should $add values to increment numerical properties', async () => { | ||
const event = createTestEvent({ | ||
timestamp, | ||
event: 'search', | ||
properties: { | ||
abc: '123', | ||
increment: { | ||
positive: 2, | ||
negative: -2 | ||
} | ||
} | ||
}) | ||
|
||
nock('https://api.mixpanel.com').post('/track').reply(200, {}) | ||
nock('https://api.mixpanel.com').post('/engage').reply(200, {}) | ||
|
||
const responses = await testDestination.testAction('incrementProperties', { | ||
event, | ||
useDefaultMappings: true, | ||
settings: { | ||
projectToken: MIXPANEL_PROJECT_TOKEN, | ||
apiSecret: MIXPANEL_API_SECRET | ||
} | ||
}) | ||
|
||
expect(responses[0].status).toBe(200) | ||
expect(responses[0].data).toMatchObject({}) | ||
expect(responses[0].options.body).toMatchObject( | ||
new URLSearchParams({ | ||
data: JSON.stringify({ | ||
$token: MIXPANEL_PROJECT_TOKEN, | ||
$distinct_id: 'user1234', | ||
$ip: '8.8.8.8', | ||
$add: { | ||
positive: 2, | ||
negative: -2 | ||
} | ||
}) | ||
}) | ||
) | ||
}) | ||
}) |
75 changes: 75 additions & 0 deletions
75
...stination-actions/src/destinations/mixpanel/incrementProperties/__test__/snapshot.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,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 = 'identifyUser' | ||
const destinationSlug = 'Mixpanel' | ||
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() | ||
} | ||
}) | ||
}) |
22 changes: 22 additions & 0 deletions
22
...ages/destination-actions/src/destinations/mixpanel/incrementProperties/generated-types.ts
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
96 changes: 96 additions & 0 deletions
96
packages/destination-actions/src/destinations/mixpanel/incrementProperties/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,96 @@ | ||
import { ActionDefinition, IntegrationError, PayloadValidationError } from '@segment/actions-core' | ||
import type { Settings } from '../generated-types' | ||
import { MixpanelEngageProperties } from '../mixpanel-types' | ||
import { getApiServerUrl } from '../common/utils' | ||
import type { Payload } from './generated-types' | ||
|
||
const action: ActionDefinition<Settings, Payload> = { | ||
title: 'Increment Properties', | ||
description: | ||
'Increment the value of a user profile property. [Learn More](https://developer.mixpanel.com/reference/profile-numerical-add).', | ||
defaultSubscription: 'type = "track"', | ||
fields: { | ||
ip: { | ||
label: 'IP Address', | ||
type: 'string', | ||
description: "The IP address of the user. This is only used for geolocation and won't be stored.", | ||
default: { | ||
'@path': '$.context.ip' | ||
} | ||
}, | ||
user_id: { | ||
label: 'User ID', | ||
type: 'string', | ||
allowNull: true, | ||
description: 'The unique user identifier set by you', | ||
default: { | ||
'@path': '$.userId' | ||
} | ||
}, | ||
anonymous_id: { | ||
label: 'Anonymous ID', | ||
type: 'string', | ||
allowNull: true, | ||
description: 'The generated anonymous ID for the user', | ||
default: { | ||
'@path': '$.anonymousId' | ||
} | ||
}, | ||
increment: { | ||
label: 'Increment Numerical Properties', | ||
type: 'object', | ||
description: | ||
'Object of properties and the values to increment or decrement. For example: `{"purchases": 1, "items": 6}}.', | ||
multiple: false, | ||
required: true, | ||
defaultObjectUI: 'keyvalue', | ||
default: { | ||
'@path': '$.properties.increment' | ||
} | ||
} | ||
}, | ||
|
||
perform: async (request, { payload, settings }) => { | ||
if (!settings.projectToken) { | ||
throw new IntegrationError('Missing project token', 'Missing required field', 400) | ||
} | ||
|
||
const apiServerUrl = getApiServerUrl(settings.apiRegion) | ||
|
||
const responses = [] | ||
|
||
if (payload.increment && Object.keys(payload.increment).length > 0) { | ||
const keys = Object.keys(payload.increment) | ||
if (keys.length > 20) { | ||
throw new PayloadValidationError('Exceeded maximum of 20 properties for increment call') | ||
} | ||
const data: MixpanelEngageProperties = { | ||
$token: settings.projectToken, | ||
$distinct_id: payload.user_id ?? payload.anonymous_id, | ||
$ip: payload.ip | ||
} | ||
data.$add = {} | ||
|
||
for (const key of keys) { | ||
const value = payload.increment[key] | ||
if (typeof value === 'string' || typeof value === 'number') { | ||
if (isNaN(+value)) { | ||
throw new IntegrationError(`The key "${key}" was not numeric`, 'Non numeric increment value', 400) | ||
} | ||
data.$add[key] = +value | ||
} | ||
} | ||
|
||
const response = request(`${apiServerUrl}/engage`, { | ||
method: 'post', | ||
body: new URLSearchParams({ data: JSON.stringify(data) }) | ||
}) | ||
|
||
responses.push(response) | ||
} | ||
|
||
return Promise.all(responses) | ||
} | ||
} | ||
|
||
export default action |
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
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