Skip to content

Commit 87125db

Browse files
zhu-xiaoweixiaoweii
andauthored
feat: support adding global attributes (#22)
Co-authored-by: xiaoweii <xiaoweii@amazom.com>
1 parent db2b7ef commit 87125db

File tree

7 files changed

+159
-51
lines changed

7 files changed

+159
-51
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,17 @@ ClickstreamAnalytics.setUserAttributes({
7676

7777
When opening for the first time after integrating the SDK, you need to manually set the user attributes once, and current login user's attributes will be cached in localStorage, so the next time browser open you don't need to set all user's attribute again, of course you can use the same api `ClickstreamAnalytics.setUserAttributes()` to update the current user's attribute when it changes.
7878

79+
#### Add global attribute
80+
81+
```typescript
82+
ClickstreamAnalytics.setGlobalAttributes({
83+
_traffic_source_medium: "Search engine",
84+
_traffic_source_name: "Summer promotion",
85+
level: 10
86+
});
87+
```
88+
It is recommended to set global attributes after each SDK initialization, global attributes will be included in all events that occur after it is set.
89+
7990
#### Record event with items
8091

8192
You can add the following code to log an event with an item.

src/ClickstreamAnalytics.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,8 @@ export class ClickstreamAnalytics {
5353
public static updateConfigure(configure: Configuration) {
5454
this.provider.updateConfigure(configure);
5555
}
56+
57+
public static setGlobalAttributes(attributes: ClickstreamAttribute) {
58+
this.provider.setGlobalAttributes(attributes);
59+
}
5660
}

src/provider/AnalyticsEventBuilder.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,14 @@ export class AnalyticsEventBuilder {
3333
context: ClickstreamContext,
3434
event: ClickstreamEvent,
3535
userAttributes: UserAttribute,
36+
globalAttributes = {},
3637
session?: Session
3738
): AnalyticsEvent {
3839
const { browserInfo, configuration } = context;
39-
const attributes = this.getEventAttributesWithCheck(event.attributes);
40+
const attributes = this.getEventAttributesWithCheck(
41+
event.attributes,
42+
globalAttributes
43+
);
4044
if (session !== undefined) {
4145
attributes[Event.ReservedAttribute.SESSION_ID] = session.sessionId;
4246
attributes[Event.ReservedAttribute.SESSION_START_TIMESTAMP] =
@@ -83,12 +87,13 @@ export class AnalyticsEventBuilder {
8387
}
8488

8589
static getEventAttributesWithCheck(
86-
attributes: ClickstreamAttribute
90+
eventAttributes: ClickstreamAttribute,
91+
globalAttributes = {}
8792
): ClickstreamAttribute {
88-
const resultAttributes: ClickstreamAttribute = {};
93+
const resultAttributes: ClickstreamAttribute = globalAttributes;
8994
const { checkAttributes } = EventChecker;
90-
for (const key in attributes) {
91-
const value = attributes[key];
95+
for (const key in eventAttributes) {
96+
const value = eventAttributes[key];
9297
if (value !== null) {
9398
const currentNumber = Object.keys(resultAttributes).length;
9499
const result = checkAttributes(currentNumber, key, value);

src/provider/ClickstreamProvider.ts

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
ClickstreamConfiguration,
2929
ClickstreamEvent,
3030
Configuration,
31+
EventError,
3132
PageType,
3233
SendMode,
3334
UserAttribute,
@@ -40,7 +41,8 @@ const logger = new Logger('ClickstreamProvider');
4041
export class ClickstreamProvider implements AnalyticsProvider {
4142
configuration: ClickstreamConfiguration;
4243
eventRecorder: EventRecorder;
43-
userAttribute: UserAttribute;
44+
userAttributes: UserAttribute;
45+
globalAttributes: ClickstreamAttribute;
4446
context: ClickstreamContext;
4547
sessionTracker: SessionTracker;
4648
pageViewTracker: PageViewTracker;
@@ -85,7 +87,8 @@ export class ClickstreamProvider implements AnalyticsProvider {
8587
this.pageViewTracker.setUp();
8688
this.clickTracker.setUp();
8789
this.scrollTracker.setUp();
88-
this.userAttribute = StorageUtil.getUserAttributes();
90+
this.userAttributes = StorageUtil.getUserAttributes();
91+
this.globalAttributes = {};
8992
if (configuration.sendMode === SendMode.Batch) {
9093
this.startTimer();
9194
}
@@ -118,15 +121,8 @@ export class ClickstreamProvider implements AnalyticsProvider {
118121
record(event: ClickstreamEvent) {
119122
const result = EventChecker.checkEventName(event.name);
120123
if (result.error_code > 0) {
121-
const errorEvent = this.createEvent({
122-
name: Event.PresetEvent.CLICKSTREAM_ERROR,
123-
attributes: {
124-
[Event.ReservedAttribute.ERROR_CODE]: result.error_code,
125-
[Event.ReservedAttribute.ERROR_MESSAGE]: result.error_message,
126-
},
127-
});
128-
this.recordEvent(errorEvent);
129124
logger.error(result.error_message);
125+
this.recordClickstreamError(result);
130126
return;
131127
}
132128
const resultEvent = this.createEvent(event);
@@ -137,7 +133,8 @@ export class ClickstreamProvider implements AnalyticsProvider {
137133
return AnalyticsEventBuilder.createEvent(
138134
this.context,
139135
event,
140-
this.userAttribute,
136+
this.userAttributes,
137+
this.globalAttributes,
141138
this.sessionTracker.session
142139
);
143140
}
@@ -155,12 +152,12 @@ export class ClickstreamProvider implements AnalyticsProvider {
155152

156153
setUserId(userId: string | null) {
157154
let previousUserId = '';
158-
if (Event.ReservedAttribute.USER_ID in this.userAttribute) {
155+
if (Event.ReservedAttribute.USER_ID in this.userAttributes) {
159156
previousUserId =
160-
this.userAttribute[Event.ReservedAttribute.USER_ID].value.toString();
157+
this.userAttributes[Event.ReservedAttribute.USER_ID].value.toString();
161158
}
162159
if (userId === null) {
163-
delete this.userAttribute[Event.ReservedAttribute.USER_ID];
160+
delete this.userAttributes[Event.ReservedAttribute.USER_ID];
164161
} else if (userId !== previousUserId) {
165162
const userInfo = StorageUtil.getUserInfoFromMapping(userId);
166163
const newUserAttribute: UserAttribute = {};
@@ -169,40 +166,72 @@ export class ClickstreamProvider implements AnalyticsProvider {
169166
set_timestamp: new Date().getTime(),
170167
};
171168
Object.assign(newUserAttribute, userInfo);
172-
this.userAttribute = newUserAttribute;
169+
this.userAttributes = newUserAttribute;
173170
this.context.userUniqueId = StorageUtil.getCurrentUserUniqueId();
171+
this.recordProfileSet();
174172
}
175-
StorageUtil.updateUserAttributes(this.userAttribute);
173+
StorageUtil.updateUserAttributes(this.userAttributes);
176174
}
177175

178176
setUserAttributes(attributes: ClickstreamAttribute) {
179177
const timestamp = new Date().getTime();
180178
for (const key in attributes) {
181179
const value = attributes[key];
182180
if (value === null) {
183-
delete this.userAttribute[key];
181+
delete this.userAttributes[key];
184182
} else {
185-
const currentNumber = Object.keys(this.userAttribute).length;
183+
const currentNumber = Object.keys(this.userAttributes).length;
186184
const { checkUserAttribute } = EventChecker;
187185
const result = checkUserAttribute(currentNumber, key, value);
188186
if (result.error_code > 0) {
189-
const { ERROR_CODE, ERROR_MESSAGE } = Event.ReservedAttribute;
190-
this.record({
191-
name: Event.PresetEvent.CLICKSTREAM_ERROR,
192-
attributes: {
193-
[ERROR_CODE]: result.error_code,
194-
[ERROR_MESSAGE]: result.error_message,
195-
},
196-
});
187+
this.recordClickstreamError(result);
197188
} else {
198-
this.userAttribute[key] = {
189+
this.userAttributes[key] = {
199190
value: value,
200191
set_timestamp: timestamp,
201192
};
202193
}
203194
}
204195
}
205-
StorageUtil.updateUserAttributes(this.userAttribute);
196+
StorageUtil.updateUserAttributes(this.userAttributes);
197+
this.recordProfileSet();
198+
}
199+
200+
setGlobalAttributes(attributes: ClickstreamAttribute) {
201+
for (const key in attributes) {
202+
const value = attributes[key];
203+
if (value === null) {
204+
delete this.globalAttributes[key];
205+
} else {
206+
const currentNumber = Object.keys(this.globalAttributes).length;
207+
const { checkAttributes } = EventChecker;
208+
const result = checkAttributes(currentNumber, key, value);
209+
if (result.error_code > 0) {
210+
this.recordClickstreamError(result);
211+
} else {
212+
this.globalAttributes[key] = value;
213+
}
214+
}
215+
}
216+
}
217+
218+
recordClickstreamError(error: EventError) {
219+
const { ERROR_CODE, ERROR_MESSAGE } = Event.ReservedAttribute;
220+
const errorEvent = this.createEvent({
221+
name: Event.PresetEvent.CLICKSTREAM_ERROR,
222+
attributes: {
223+
[ERROR_CODE]: error.error_code,
224+
[ERROR_MESSAGE]: error.error_message,
225+
},
226+
});
227+
this.recordEvent(errorEvent);
228+
}
229+
230+
recordProfileSet() {
231+
const profileSetEvent = this.createEvent({
232+
name: Event.PresetEvent.PROFILE_SET,
233+
});
234+
this.recordEvent(profileSetEvent);
206235
}
207236

208237
startTimer() {

test/ClickstreamAnalytics.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
* and limitations under the License.
1212
*/
1313
import { ClickstreamAnalytics, SendMode } from '../src';
14+
import { Item } from '../src';
1415
import { NetRequest } from '../src/network/NetRequest';
1516
import { Event } from '../src/provider';
16-
import { Item } from '../src/types';
1717
import { StorageUtil } from '../src/util/StorageUtil';
1818

1919
describe('ClickstreamAnalytics test', () => {
@@ -69,6 +69,10 @@ describe('ClickstreamAnalytics test', () => {
6969
_user_name: 'carl',
7070
_user_age: 20,
7171
});
72+
ClickstreamAnalytics.setGlobalAttributes({
73+
brand: 'Samsung',
74+
level: 10,
75+
});
7276
const item: Item = {
7377
id: '1',
7478
name: 'Nature',
@@ -112,6 +116,27 @@ describe('ClickstreamAnalytics test', () => {
112116
}
113117
});
114118

119+
test('test add global attribute in subsequent event', async () => {
120+
ClickstreamAnalytics.init({
121+
appId: 'testApp',
122+
endpoint: 'https://localhost:8080/collect',
123+
sendMode: SendMode.Batch,
124+
});
125+
ClickstreamAnalytics.setGlobalAttributes({
126+
_traffic_source_medium: 'Search engine',
127+
_traffic_source_name: 'Summer promotion',
128+
});
129+
ClickstreamAnalytics.record({ name: 'testEvent' });
130+
await sleep(100);
131+
const eventList = JSON.parse(
132+
StorageUtil.getAllEvents() + Event.Constants.SUFFIX
133+
);
134+
const testEvent = eventList[eventList.length - 1];
135+
expect(testEvent.event_type).toBe('testEvent');
136+
expect(testEvent.attributes._traffic_source_medium).toBe('Search engine');
137+
expect(testEvent.attributes._traffic_source_name).toBe('Summer promotion');
138+
});
139+
115140
test('test update configuration', () => {
116141
ClickstreamAnalytics.init({
117142
appId: 'testApp',

0 commit comments

Comments
 (0)