Skip to content

Commit 3712926

Browse files
authored
feat: Custom attributes (#254)
* feat: Custom attributes * fix: Refactored recordPageView to add custom attributes in one param * fix: Default attributes not overwritten by custom attributes
1 parent 5bb2a6b commit 3712926

File tree

18 files changed

+536
-20
lines changed

18 files changed

+536
-20
lines changed

app/index.html

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,33 @@
8787
cwr('allowCookies', false);
8888
}
8989
</script>
90+
<script>
91+
function recordPageView() {
92+
cwr('recordPageView', '/page_view_one');
93+
}
94+
95+
function addSessionAttributes() {
96+
cwr('addSessionAttributes', {
97+
customPageAttributeAtRuntimeString:
98+
'stringCustomAttributeAtRunTimeValue',
99+
customPageAttributeAtRuntimeNumber: 1,
100+
customPageAttributeAtRuntimeBoolean: true
101+
});
102+
}
103+
</script>
90104
<button id="createHTTPError" onclick="createHTTPError()">
91105
Create HTTP Error
92106
</button>
93107
<button id="randomSessionClick">Random Session click</button>
94108
<button id="disallowCookies" onclick="disallowCookies()">
95109
Disallow Cookies
96110
</button>
111+
<button id="addSessionAttributes" onclick="addSessionAttributes()">
112+
Set Custom Attributes
113+
</button>
114+
<button id="recordPageView" onclick="recordPageView()">
115+
Record Page View
116+
</button>
97117
<span id="request"></span>
98118
<span id="response"></span>
99119
<table>

app/page_event.html

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,25 @@
7171
cwr('recordPageView', '/page_view_two');
7272
}
7373

74-
function recordPageViewWithPageAttributes() {
74+
function recordPageViewWithPageTagAttribute() {
7575
cwr('recordPageView', {
7676
pageId: '/page_view_two',
7777
pageTags: ['pageGroup1']
7878
});
7979
}
8080

81+
function recordPageViewWithCustomPageAttributes() {
82+
cwr('recordPageView', {
83+
pageId: '/page_view_two',
84+
pageTags: ['pageGroup1'],
85+
pageAttributes: {
86+
customPageAttributeString: 'customPageAttributeValue',
87+
customPageAttributeNumber: 1,
88+
customPageAttributeBoolean: true
89+
}
90+
});
91+
}
92+
8193
const parseEvents = () => {
8294
const requestBody = document.getElementById('request_body');
8395
const events = JSON.parse(requestBody.innerText).batch.events;
@@ -153,10 +165,16 @@
153165
Record Page View
154166
</button>
155167
<button
156-
id="recordPageViewWithPageAttributes"
157-
onclick="recordPageViewWithPageAttributes()"
168+
id="recordPageViewWithPageTagAttribute"
169+
onclick="recordPageViewWithPageTagAttribute()"
170+
>
171+
Record Page View with page tag attribute
172+
</button>
173+
<button
174+
id="recordPageViewWithCustomPageAttributes"
175+
onclick="recordPageViewWithCustomPageAttributes()"
158176
>
159-
Record Page View with page attributes
177+
Record Page View with custom page attributes
160178
</button>
161179
<button id="doNotRecordPageView" onclick="doNotRecordPageView()">
162180
Do Not Record Page View

src/CommandQueue.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type CommandFunction = (payload?: any) => void;
66

77
interface CommandFunctions {
88
setAwsCredentials: CommandFunction;
9+
addSessionAttributes: CommandFunction;
910
recordPageView: CommandFunction;
1011
recordError: CommandFunction;
1112
registerDomEvents: CommandFunction;
@@ -52,6 +53,9 @@ export class CommandQueue {
5253
): void => {
5354
this.orchestration.setAwsCredentials(payload);
5455
},
56+
addSessionAttributes: (payload: { [k: string]: any }): void => {
57+
this.orchestration.addSessionAttributes(payload);
58+
},
5559
recordPageView: (payload: any): void => {
5660
this.orchestration.recordPageView(payload);
5761
},

src/__tests__/CommandQueue.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const enable = jest.fn();
4242
const dispatch = jest.fn();
4343
const dispatchBeacon = jest.fn();
4444
const setAwsCredentials = jest.fn();
45+
const addSessionAttributes = jest.fn();
4546
const allowCookies = jest.fn();
4647
const recordPageView = jest.fn();
4748
const recordError = jest.fn();
@@ -53,6 +54,7 @@ jest.mock('../orchestration/Orchestration', () => ({
5354
dispatch,
5455
dispatchBeacon,
5556
setAwsCredentials,
57+
addSessionAttributes,
5658
allowCookies,
5759
recordPageView,
5860
recordError,
@@ -245,6 +247,16 @@ describe('CommandQueue tests', () => {
245247
expect(setAwsCredentials).toHaveBeenCalled();
246248
});
247249

250+
test('addSessionAttributes calls Orchestration.addSessionAttributes', async () => {
251+
const cq: CommandQueue = getCommandQueue();
252+
const result = await cq.push({
253+
c: 'addSessionAttributes',
254+
p: { customAttribute: 'customAttributeValue' }
255+
});
256+
expect(Orchestration).toHaveBeenCalled();
257+
expect(addSessionAttributes).toHaveBeenCalled();
258+
});
259+
248260
test('allowCookies calls Orchestration.allowCookies', async () => {
249261
const cq: CommandQueue = getCommandQueue();
250262
await cq.push({

src/event-cache/EventCache.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,17 @@ export class EventCache {
151151
};
152152
}
153153

154+
/**
155+
* Set custom session attributes to add them to all event metadata.
156+
*
157+
* @param payload object containing custom attribute data in the form of key, value pairs
158+
*/
159+
public addSessionAttributes(sessionAttributes: {
160+
[k: string]: string | number | boolean;
161+
}): void {
162+
this.sessionManager.addSessionAttributes(sessionAttributes);
163+
}
164+
154165
/**
155166
* Add a session start event to the cache.
156167
*/
@@ -198,9 +209,9 @@ export class EventCache {
198209
// objects with their own attribute sets. Instead, we store session
199210
// attributes and page attributes together as 'meta data'.
200211
const metaData: MetaData = {
201-
version: '1.0.0',
202212
...this.sessionManager.getAttributes(),
203-
...this.pageManager.getAttributes()
213+
...this.pageManager.getAttributes(),
214+
version: '1.0.0'
204215
};
205216

206217
this.events.push({

src/event-cache/__tests__/EventCache.integ.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,44 @@ describe('EventCache tests', () => {
6969
expect(JSON.parse(event.metadata)).toMatchObject(expectedMetaData);
7070
});
7171
});
72+
73+
test('meta data contains default attributes not overridden from custom attributes', async () => {
74+
// Init
75+
const EVENT1_SCHEMA = 'com.amazon.rum.event1';
76+
const config = {
77+
...DEFAULT_CONFIG,
78+
...{
79+
allowCookies: false,
80+
sessionLengthSeconds: 0,
81+
sessionAttributes: {
82+
version: '2.0.0',
83+
domain: 'overridden.console.aws.amazon.com',
84+
browserLanguage: 'en-UK',
85+
browserName: 'Chrome',
86+
deviceType: 'Mac'
87+
}
88+
}
89+
};
90+
91+
const eventCache: EventCache = Utils.createEventCache(config);
92+
const expectedMetaData = {
93+
version: '1.0.0',
94+
domain: 'us-east-1.console.aws.amazon.com',
95+
browserLanguage: 'en-US',
96+
browserName: 'WebKit',
97+
deviceType: 'desktop',
98+
platformType: 'web',
99+
pageId: '/console/home'
100+
};
101+
102+
// Run
103+
eventCache.recordPageView('/console/home');
104+
eventCache.recordEvent(EVENT1_SCHEMA, {});
105+
106+
// Assert
107+
const events: RumEvent[] = eventCache.getEventBatch();
108+
events.forEach((event) => {
109+
expect(JSON.parse(event.metadata)).toMatchObject(expectedMetaData);
110+
});
111+
});
72112
});

src/event-cache/__tests__/EventCache.test.ts

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ const getSession = jest.fn(() => ({
1414
const getUserId = jest.fn(() => 'b');
1515
const getAttributes = jest.fn();
1616
const incrementSessionEventCount = jest.fn();
17+
const addSessionAttributes = jest.fn();
1718
jest.mock('../../sessions/SessionManager', () => ({
1819
SessionManager: jest.fn().mockImplementation(() => ({
1920
getSession,
2021
getUserId,
2122
getAttributes,
22-
incrementSessionEventCount
23+
incrementSessionEventCount,
24+
addSessionAttributes
2325
}))
2426
}));
2527

@@ -236,7 +238,7 @@ describe('EventCache tests', () => {
236238
timestamp: new Date(),
237239
type: EVENT1_SCHEMA,
238240
metadata:
239-
'{"version":"1.0.0","title":"","pageId":"/rum/home","pageTags":["pageGroup1"]}',
241+
'{"title":"","pageId":"/rum/home","pageTags":["pageGroup1"],"version":"1.0.0"}',
240242
details: '{"version":"1.0.0","pageId":"/rum/home"}'
241243
}
242244
];
@@ -253,6 +255,66 @@ describe('EventCache tests', () => {
253255
);
254256
});
255257

258+
test('when page is recorded with custom page attributes, metadata records the custom page attributes', async () => {
259+
// Init
260+
const EVENT1_SCHEMA = 'com.amazon.rum.page_view_event';
261+
const eventCache: EventCache = Utils.createEventCache({
262+
...DEFAULT_CONFIG
263+
});
264+
const expectedEvents: RumEvent[] = [
265+
{
266+
id: expect.stringMatching(/[0-9a-f\-]+/),
267+
timestamp: new Date(),
268+
type: EVENT1_SCHEMA,
269+
metadata:
270+
'{"customPageAttributeString":"customPageAttributeValue","customPageAttributeNumber":1,"customPageAttributeBoolean":true,"title":"","pageId":"/rum/home","pageTags":["pageGroup1"],"version":"1.0.0"}',
271+
details: '{"version":"1.0.0","pageId":"/rum/home"}'
272+
}
273+
];
274+
275+
// Run
276+
eventCache.recordPageView({
277+
pageId: '/rum/home',
278+
pageTags: ['pageGroup1'],
279+
pageAttributes: {
280+
customPageAttributeString: 'customPageAttributeValue',
281+
customPageAttributeNumber: 1,
282+
customPageAttributeBoolean: true
283+
}
284+
});
285+
286+
// Assert
287+
expect(eventCache.getEventBatch()).toEqual(
288+
expect.arrayContaining(expectedEvents)
289+
);
290+
});
291+
292+
/**
293+
* Test title truncated to meet lint requirements
294+
* Full title: when EventCache.addSessionAttributes() is called then SessionManager.addSessionAttributes() is called
295+
*/
296+
test('EventCache.addSessionAttributes() calls SessionManager.addSessionAttributes()', async () => {
297+
// Init
298+
const eventCache: EventCache = Utils.createEventCache({
299+
...DEFAULT_CONFIG
300+
});
301+
302+
const expected = {
303+
customAttributeString: 'customAttributeValue',
304+
customAttributeNumber: 1,
305+
customAttributeBoolean: true
306+
};
307+
308+
// Run
309+
eventCache.addSessionAttributes(expected);
310+
311+
// Assert
312+
expect(addSessionAttributes).toHaveBeenCalledTimes(1);
313+
const actual = addSessionAttributes.mock.calls[0][0];
314+
315+
expect(actual).toEqual(expected);
316+
});
317+
256318
test('when page matches both allowed and denied, recordEvent does not record the event', async () => {
257319
// Init
258320
const EVENT1_SCHEMA = 'com.amazon.rum.event1';

src/event-schemas/meta-data.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,18 @@
6565
},
6666
"pageTags": { "type": "array", "items": { "type": "string" } }
6767
},
68+
"patternProperties": {
69+
"^(?!pageTags).{1,128}$": {
70+
"maxLength": 256,
71+
"type": ["string", "boolean", "number"]
72+
},
73+
"pageTags": {
74+
"type": "array",
75+
"items": {
76+
"type": "string"
77+
}
78+
}
79+
},
6880
"additionalProperties": false,
6981
"required": ["version", "domain"]
7082
}

src/loader/loader-standard.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ loader('cwr', 'abc123', '1.0', 'us-west-2', './rum_javascript_telemetry.js', {
44
allowCookies: true,
55
dispatchInterval: 0,
66
telemetries: ['performance'],
7-
clientBuilder: showRequestClientBuilder
7+
clientBuilder: showRequestClientBuilder,
8+
sessionAttributes: {
9+
customAttributeAtInit: 'customAttributeAtInitValue'
10+
}
811
});
912
window.cwr('setAwsCredentials', {
1013
accessKeyId: 'a',

src/orchestration/Orchestration.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export type PartialConfig = {
6363
batchLimit?: number;
6464
clientBuilder?: ClientBuilder;
6565
cookieAttributes?: PartialCookieAttributes;
66+
sessionAttributes?: { [k: string]: string | number | boolean };
6667
disableAutoPageView?: boolean;
6768
dispatchInterval?: number;
6869
enableRumClient?: boolean;
@@ -113,6 +114,7 @@ export const defaultConfig = (cookieAttributes: CookieAttributes): Config => {
113114
allowCookies: false,
114115
batchLimit: 100,
115116
cookieAttributes,
117+
sessionAttributes: {},
116118
disableAutoPageView: false,
117119
dispatchInterval: 5 * 1000,
118120
enableRumClient: true,
@@ -150,6 +152,7 @@ export type Config = {
150152
batchLimit: number;
151153
clientBuilder?: ClientBuilder;
152154
cookieAttributes: CookieAttributes;
155+
sessionAttributes: { [k: string]: string | number | boolean };
153156
disableAutoPageView: boolean;
154157
dispatchInterval: number;
155158
enableRumClient: boolean;
@@ -280,6 +283,17 @@ export class Orchestration {
280283
this.dispatchManager.setAwsCredentials(credentials);
281284
}
282285

286+
/**
287+
* Set custom session attributes to add them to all event metadata.
288+
*
289+
* @param payload object containing custom attribute data in the form of key, value pairs
290+
*/
291+
public addSessionAttributes(sessionAttributes: {
292+
[key: string]: string | boolean | number;
293+
}): void {
294+
this.eventCache.addSessionAttributes(sessionAttributes);
295+
}
296+
283297
/**
284298
* Add a telemetry plugin.
285299
*

0 commit comments

Comments
 (0)