-
Notifications
You must be signed in to change notification settings - Fork 254
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Launchpad Segment Integration (#1010)
* Launchpad Segment Integration We are now adding the ability to send events from segment into Launchpad.pm - the mission control for Product teams at scale. We are adding the following: * trackEvent * groupIdentifyUser * identifyUser We have added unit tests and have tested this manually. It requires the following: * apiRegion - EU by default. We have added the US as means of future extensibility. * apiSecret - given by Launchpad.pm while onboarding. * sourceName - to be added. * updating snapshots * Changes implemented following call with Joe Track() [x] Explicitly indicate which fields are required or optional [x] Look for traits and if not context.traits [x] Need description for the Action [x] Make Timestamp field required [x] Make messageId required. It will always be there. [x] getEventProperties: This line looks incorrect: id: payload.event, [x] source mapping looks incorrect: source: integration?.name == 'Iterable' ? 'Iterable' : 'segment', Identify() [x] Description should be updated. [x] identify() calls are often fired when a trait is collected, or when a userId is collected. [x] Maybe use wording: “Creates or updates a user profile, and adds or updates trait values on the user profile…” [x] Refactor the perform() function group() [x] Group Key - Amend description to better explain that group key is a way to connect multiple organizations together [x] groupId field - Should be updated [x] Consider adding anonymousId as a field [x] Handle when there are no traits. * changes * tests passing, removed the check on user_id, anonymous_id since none are required * fixing the types as well * fixing test --------- Co-authored-by: joe-ayoub-segment <[email protected]>
- Loading branch information
1 parent
15b000c
commit b81dd49
Showing
20 changed files
with
1,119 additions
and
0 deletions.
There are no files selected for viewing
16 changes: 16 additions & 0 deletions
16
packages/destination-actions/src/destinations/launchpad/generated-types.ts
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
27 changes: 27 additions & 0 deletions
27
...rc/destinations/launchpad/groupIdentifyUser/__tests__/__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,27 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Testing snapshot for Launchpad's groupIdentifyUser destination action: all fields 1`] = ` | ||
Object { | ||
"$set": Object { | ||
"group_id": "$WBBWnR", | ||
"group_testType": "$WBBWnR", | ||
}, | ||
"anonymoud_id": "$WBBWnR", | ||
"api_key": "$WBBWnR", | ||
"distinct_id": "$WBBWnR", | ||
"event": "$identify", | ||
"type": "screen", | ||
"user_id": "$WBBWnR", | ||
} | ||
`; | ||
|
||
exports[`Testing snapshot for Launchpad's groupIdentifyUser destination action: required fields 1`] = ` | ||
Object { | ||
"$set": Object { | ||
"group_id": "$WBBWnR", | ||
}, | ||
"api_key": "$WBBWnR", | ||
"event": "$identify", | ||
"type": "screen", | ||
} | ||
`; |
55 changes: 55 additions & 0 deletions
55
.../destination-actions/src/destinations/launchpad/groupIdentifyUser/__tests__/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,55 @@ | ||
import nock from 'nock' | ||
import { createTestIntegration } from '@segment/actions-core' | ||
import Destination from '../../index' | ||
import { SegmentEvent } from '@segment/actions-core' | ||
|
||
const launchpadAPISecret = 'lp-api-key' | ||
const timestamp = '2023-01-28T15:21:15.449Z' | ||
|
||
const testDestination = createTestIntegration(Destination) | ||
|
||
const expectedTraits = { | ||
group_name: 'Launchpad', | ||
group_industry: 'Technology', | ||
group_employees: 3, | ||
group_plan: '1', | ||
'group_ARR(m)': 1503 | ||
} | ||
|
||
const testGroupIdentify: SegmentEvent = { | ||
messageId: 'test-message-t73406chv4', | ||
timestamp: timestamp, | ||
type: 'group', | ||
groupId: '12381923812', | ||
userId: '[email protected]', | ||
traits: { | ||
name: 'Launchpad', | ||
industry: 'Technology', | ||
employees: 3, | ||
plan: '1', | ||
'ARR(m)': 1503 | ||
} | ||
} | ||
|
||
describe('Launchpad.groupIdentifyUser', () => { | ||
it('should convert the type and event name', async () => { | ||
nock('https://data.launchpad.pm').post('/capture').reply(200, {}) | ||
|
||
const responses = await testDestination.testAction('groupIdentifyUser', { | ||
event: testGroupIdentify, | ||
useDefaultMappings: true, | ||
settings: { | ||
apiSecret: launchpadAPISecret, | ||
sourceName: 'example segment source name' | ||
} | ||
}) | ||
expect(responses.length).toBe(1) | ||
expect(responses[0].status).toBe(200) | ||
expect(responses[0].data).toMatchObject({}) | ||
expect(responses[0].options.json).toMatchObject({ | ||
event: '$identify', | ||
type: 'screen', | ||
$set: expect.objectContaining(expectedTraits) | ||
}) | ||
}) | ||
}) |
76 changes: 76 additions & 0 deletions
76
...stination-actions/src/destinations/launchpad/groupIdentifyUser/__tests__/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,76 @@ | ||
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 = 'groupIdentifyUser' | ||
const destinationSlug = 'Launchpad' | ||
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 | ||
}) | ||
event.userId = 'user1234' | ||
|
||
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() | ||
} | ||
}) | ||
}) |
26 changes: 26 additions & 0 deletions
26
packages/destination-actions/src/destinations/launchpad/groupIdentifyUser/generated-types.ts
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
89 changes: 89 additions & 0 deletions
89
packages/destination-actions/src/destinations/launchpad/groupIdentifyUser/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,89 @@ | ||
import { ActionDefinition } from '@segment/actions-core' | ||
import type { Settings } from '../generated-types' | ||
import { getApiServerUrl } from '../utils' | ||
import type { Payload } from './generated-types' | ||
|
||
const groupIdentifyUser: ActionDefinition<Settings, Payload> = { | ||
title: 'Group Identify User', | ||
description: | ||
'Updates or adds properties to a group profile. The profile is created if it does not exist. [Learn more about Group Analytics.](https://help.Launchpad.pm)', | ||
defaultSubscription: 'type = "group"', | ||
fields: { | ||
groupKey: { | ||
label: 'Group Key', | ||
type: 'string', | ||
required: false, | ||
description: | ||
'The group key you specified in Launchpad under the company corresponding to the group. If this is not specified, it will be defaulted to "$group_id". This is helpful when you have a group of companies that should be joined together as in when you have a multinational.' | ||
}, | ||
groupId: { | ||
label: 'Group ID', | ||
type: 'string', | ||
description: | ||
'The unique identifier of the group. If there is a trait that matches the group key, it will override this value.', | ||
required: true, | ||
default: { | ||
'@path': '$.groupId' | ||
} | ||
}, | ||
traits: { | ||
label: 'Group Properties', | ||
type: 'object', | ||
description: 'The properties to set on the group profile.', | ||
required: false, | ||
default: { | ||
'@path': '$.traits' | ||
} | ||
}, | ||
userId: { | ||
label: 'User ID', | ||
type: 'string', | ||
description: | ||
'A unique ID for a known user. This will be used as the Distinct ID. This field is required if the Anonymous ID field is empty', | ||
default: { | ||
'@path': '$.userId' | ||
} | ||
}, | ||
anonymousId: { | ||
label: 'Anonymous ID', | ||
type: 'string', | ||
description: | ||
'A unique ID for an anonymous user. This will be used as the Distinct ID if the User ID field is empty. This field is required if the User ID field is empty', | ||
default: { | ||
'@path': '$.anonymousId' | ||
} | ||
} | ||
}, | ||
|
||
perform: async (request, { payload, settings }) => { | ||
const groupId = payload.groupId | ||
const apiServerUrl = getApiServerUrl(settings.apiRegion) | ||
let transformed_traits | ||
|
||
if (payload.traits) { | ||
transformed_traits = { | ||
...Object.fromEntries(Object.entries(payload.traits).map(([k, v]) => [`group_${k}`, v])) | ||
} | ||
} | ||
const groupIdentifyEvent = { | ||
event: '$identify', | ||
type: 'screen', | ||
$set: { | ||
group_id: groupId, | ||
...transformed_traits | ||
}, | ||
distinct_id: payload.userId ? payload.userId : payload.anonymousId, | ||
user_id: payload.userId, | ||
anonymoud_id: payload.anonymousId, | ||
api_key: settings.apiSecret | ||
} | ||
const groupIdentifyResponse = await request(`${apiServerUrl}capture`, { | ||
method: 'post', | ||
json: groupIdentifyEvent | ||
}) | ||
|
||
return groupIdentifyResponse | ||
} | ||
} | ||
|
||
export default groupIdentifyUser |
29 changes: 29 additions & 0 deletions
29
...ons/src/destinations/launchpad/identifyUser/__tests__/__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,29 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Testing snapshot for Launchpad's identifyUser destination action: all fields 1`] = ` | ||
Object { | ||
"$ip": "5SpoCjYek1jtNwUC", | ||
"$set": Object { | ||
"testType": "5SpoCjYek1jtNwUC", | ||
}, | ||
"anonymous_id": "5SpoCjYek1jtNwUC", | ||
"api_key": "5SpoCjYek1jtNwUC", | ||
"distinct_id": "5SpoCjYek1jtNwUC", | ||
"event": "$identify", | ||
"type": "screen", | ||
"user_id": "5SpoCjYek1jtNwUC", | ||
} | ||
`; | ||
|
||
exports[`Testing snapshot for Launchpad's identifyUser destination action: required fields 1`] = ` | ||
Object { | ||
"$set": Object { | ||
"testType": "5SpoCjYek1jtNwUC", | ||
}, | ||
"api_key": "5SpoCjYek1jtNwUC", | ||
"distinct_id": "user1234", | ||
"event": "$identify", | ||
"type": "screen", | ||
"user_id": "user1234", | ||
} | ||
`; |
Oops, something went wrong.