Skip to content
This repository was archived by the owner on Jun 27, 2023. It is now read-only.

Commit d48ac83

Browse files
authored
fix: persist user traits across events (segmentio#581)
1 parent 489bffc commit d48ac83

File tree

7 files changed

+69
-17
lines changed

7 files changed

+69
-17
lines changed

packages/core/src/__tests__/methods/identify.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,49 @@ describe('methods #identify', () => {
102102
},
103103
});
104104
});
105+
106+
it('persists identity traits accross events', () => {
107+
const client = new SegmentClient(clientArgs);
108+
jest.spyOn(client, 'process');
109+
// @ts-ignore accessing the internal timeline to check the processed events
110+
jest.spyOn(client.timeline, 'process');
111+
112+
client.identify('new-user-id', { name: 'Mary', age: 30 });
113+
114+
const expectedEvent = {
115+
traits: {
116+
name: 'Mary',
117+
age: 30,
118+
},
119+
userId: 'new-user-id',
120+
type: 'identify',
121+
};
122+
123+
expect(client.process).toHaveBeenCalledTimes(1);
124+
expect(client.process).toHaveBeenCalledWith(expectedEvent);
125+
126+
expect(client.userInfo.get()).toEqual({
127+
...initialUserInfo,
128+
userId: 'new-user-id',
129+
traits: expectedEvent.traits,
130+
});
131+
132+
client.track('track event');
133+
134+
// @ts-ignore
135+
expect(client.timeline.process).toHaveBeenLastCalledWith({
136+
anonymousId: 'very-anonymous',
137+
event: 'track event',
138+
integrations: {},
139+
messageId: 'mocked-uuid',
140+
properties: {},
141+
timestamp: '2010-01-01T00:00:00.000Z',
142+
traits: {
143+
age: 30,
144+
name: 'Mary',
145+
},
146+
type: 'track',
147+
userId: 'new-user-id',
148+
});
149+
});
105150
});

packages/core/src/events.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,13 @@ const isAliasEvent = (event: SegmentEvent): event is AliasEventType =>
8282
event.type === EventType.AliasEvent;
8383
const isIdentifyEvent = (event: SegmentEvent): event is AliasEventType =>
8484
event.type === EventType.IdentifyEvent;
85+
const isGroupEvent = (event: SegmentEvent): event is GroupEventType =>
86+
event.type === EventType.GroupEvent;
8587

8688
export const applyRawEventData = (
8789
event: SegmentEvent,
8890
userInfo: UserInfoState
89-
) => {
91+
): SegmentEvent => {
9092
return {
9193
...event,
9294
anonymousId: userInfo.anonymousId,
@@ -97,5 +99,9 @@ export const applyRawEventData = (
9799
isAliasEvent(event) || isIdentifyEvent(event)
98100
? event.userId
99101
: userInfo.userId,
102+
traits:
103+
isIdentifyEvent(event) || isGroupEvent(event)
104+
? event.traits
105+
: userInfo.traits,
100106
};
101107
};

packages/core/src/types.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ interface BaseEventType {
2626
messageId?: string;
2727
userId?: string;
2828
timestamp?: string;
29+
traits?: UserTraits | GroupTraits;
2930

3031
context?: PartialContext;
3132
integrations?: SegmentAPIIntegrations;
@@ -46,13 +47,13 @@ export interface ScreenEventType extends BaseEventType {
4647

4748
export interface IdentifyEventType extends BaseEventType {
4849
type: EventType.IdentifyEvent;
49-
traits: UserTraits;
50+
traits?: UserTraits;
5051
}
5152

5253
export interface GroupEventType extends BaseEventType {
5354
type: EventType.GroupEvent;
5455
groupId: string;
55-
traits: GroupTraits;
56+
traits?: GroupTraits;
5657
}
5758

5859
export interface AliasEventType extends BaseEventType {
@@ -297,5 +298,5 @@ export enum EventType {
297298
export type UserInfoState = {
298299
anonymousId: string;
299300
userId?: string;
300-
traits?: UserTraits;
301+
traits?: UserTraits | GroupTraits;
301302
};

packages/plugins/plugin-braze/src/methods/identify.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default (payload: IdentifyEventType) => {
99
ReactAppboy.changeUser(payload.userId);
1010
}
1111

12-
if (payload.traits.birthday) {
12+
if (payload.traits?.birthday !== undefined) {
1313
const data = new Date(payload.traits.birthday);
1414
ReactAppboy.setDateOfBirth(
1515
data.getFullYear(),
@@ -19,19 +19,19 @@ export default (payload: IdentifyEventType) => {
1919
);
2020
}
2121

22-
if (payload.traits.email) {
22+
if (payload.traits?.email !== undefined) {
2323
ReactAppboy.setEmail(payload.traits.email);
2424
}
2525

26-
if (payload.traits.firstName) {
26+
if (payload.traits?.firstName !== undefined) {
2727
ReactAppboy.setFirstName(payload.traits.firstName);
2828
}
2929

30-
if (payload.traits.lastName) {
30+
if (payload.traits?.lastName !== undefined) {
3131
ReactAppboy.setLastName(payload.traits.lastName);
3232
}
3333

34-
if (payload.traits.gender) {
34+
if (payload.traits?.gender !== undefined) {
3535
const validGenders = ['m', 'f', 'n', 'o', 'p', 'u'];
3636
const isValidGender = validGenders.indexOf(payload.traits.gender) > -1;
3737
if (isValidGender) {
@@ -41,15 +41,15 @@ export default (payload: IdentifyEventType) => {
4141
}
4242
}
4343

44-
if (payload.traits.phone) {
44+
if (payload.traits?.phone !== undefined) {
4545
ReactAppboy.setPhoneNumber(payload.traits.phone);
4646
}
4747

48-
if (payload.traits.address) {
49-
if (payload.traits.address.city) {
48+
if (payload.traits?.address !== undefined) {
49+
if (payload.traits.address.city !== undefined) {
5050
ReactAppboy.setHomeCity(payload.traits.address.city);
5151
}
52-
if (payload.traits.address.country) {
52+
if (payload.traits?.address.country !== undefined) {
5353
ReactAppboy.setCountry(payload.traits.address.country);
5454
}
5555
}
@@ -64,7 +64,7 @@ export default (payload: IdentifyEventType) => {
6464
'address',
6565
];
6666

67-
Object.entries(payload.traits).forEach(([key, value]) => {
67+
Object.entries(payload.traits ?? {}).forEach(([key, value]) => {
6868
if (appBoyTraits.indexOf(key) < 0) {
6969
ReactAppboy.setCustomUserAttribute(key, value as any);
7070
}

packages/plugins/plugin-mixpanel/src/methods/__tests__/identify.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe('#identify', () => {
6363
});
6464

6565
it('registers superProperties', () => {
66-
payload.traits.prop1 = 'string';
66+
payload.traits!.prop1 = 'string';
6767
settings.superProperties = ['prop1'];
6868
let mockedTraits = { prop1: 'string' };
6969

packages/plugins/plugin-mixpanel/src/methods/identify.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default (
2424
settings: SegmentMixpanelSettings
2525
) => {
2626
const userId = event.userId;
27-
const mixpanelTraits = mapTransform(event.traits);
27+
const mixpanelTraits = mapTransform(event.traits ?? {});
2828

2929
if (userId !== undefined) {
3030
mixpanel.identify(userId);
@@ -54,6 +54,7 @@ export default (
5454
}
5555

5656
if (
57+
event.traits !== undefined &&
5758
settings.people === true &&
5859
settings.peopleProperties !== undefined &&
5960
settings.peopleProperties.length

release.config.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,4 @@ module.exports = {
1111
'@semantic-release/git',
1212
],
1313
extends: 'semantic-release-monorepo',
14-
dryRun: true,
1514
};

0 commit comments

Comments
 (0)