Skip to content

Commit

Permalink
feat: Custom attributes (#254)
Browse files Browse the repository at this point in the history
* feat: Custom attributes

* fix: Refactored recordPageView to add custom attributes in one param

* fix: Default attributes not overwritten by custom attributes
  • Loading branch information
limhjgrace committed Oct 14, 2022
1 parent 5bb2a6b commit 3712926
Show file tree
Hide file tree
Showing 18 changed files with 536 additions and 20 deletions.
20 changes: 20 additions & 0 deletions app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,33 @@
cwr('allowCookies', false);
}
</script>
<script>
function recordPageView() {
cwr('recordPageView', '/page_view_one');
}

function addSessionAttributes() {
cwr('addSessionAttributes', {
customPageAttributeAtRuntimeString:
'stringCustomAttributeAtRunTimeValue',
customPageAttributeAtRuntimeNumber: 1,
customPageAttributeAtRuntimeBoolean: true
});
}
</script>
<button id="createHTTPError" onclick="createHTTPError()">
Create HTTP Error
</button>
<button id="randomSessionClick">Random Session click</button>
<button id="disallowCookies" onclick="disallowCookies()">
Disallow Cookies
</button>
<button id="addSessionAttributes" onclick="addSessionAttributes()">
Set Custom Attributes
</button>
<button id="recordPageView" onclick="recordPageView()">
Record Page View
</button>
<span id="request"></span>
<span id="response"></span>
<table>
Expand Down
26 changes: 22 additions & 4 deletions app/page_event.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,25 @@
cwr('recordPageView', '/page_view_two');
}

function recordPageViewWithPageAttributes() {
function recordPageViewWithPageTagAttribute() {
cwr('recordPageView', {
pageId: '/page_view_two',
pageTags: ['pageGroup1']
});
}

function recordPageViewWithCustomPageAttributes() {
cwr('recordPageView', {
pageId: '/page_view_two',
pageTags: ['pageGroup1'],
pageAttributes: {
customPageAttributeString: 'customPageAttributeValue',
customPageAttributeNumber: 1,
customPageAttributeBoolean: true
}
});
}

const parseEvents = () => {
const requestBody = document.getElementById('request_body');
const events = JSON.parse(requestBody.innerText).batch.events;
Expand Down Expand Up @@ -153,10 +165,16 @@
Record Page View
</button>
<button
id="recordPageViewWithPageAttributes"
onclick="recordPageViewWithPageAttributes()"
id="recordPageViewWithPageTagAttribute"
onclick="recordPageViewWithPageTagAttribute()"
>
Record Page View with page tag attribute
</button>
<button
id="recordPageViewWithCustomPageAttributes"
onclick="recordPageViewWithCustomPageAttributes()"
>
Record Page View with page attributes
Record Page View with custom page attributes
</button>
<button id="doNotRecordPageView" onclick="doNotRecordPageView()">
Do Not Record Page View
Expand Down
4 changes: 4 additions & 0 deletions src/CommandQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type CommandFunction = (payload?: any) => void;

interface CommandFunctions {
setAwsCredentials: CommandFunction;
addSessionAttributes: CommandFunction;
recordPageView: CommandFunction;
recordError: CommandFunction;
registerDomEvents: CommandFunction;
Expand Down Expand Up @@ -52,6 +53,9 @@ export class CommandQueue {
): void => {
this.orchestration.setAwsCredentials(payload);
},
addSessionAttributes: (payload: { [k: string]: any }): void => {
this.orchestration.addSessionAttributes(payload);
},
recordPageView: (payload: any): void => {
this.orchestration.recordPageView(payload);
},
Expand Down
12 changes: 12 additions & 0 deletions src/__tests__/CommandQueue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const enable = jest.fn();
const dispatch = jest.fn();
const dispatchBeacon = jest.fn();
const setAwsCredentials = jest.fn();
const addSessionAttributes = jest.fn();
const allowCookies = jest.fn();
const recordPageView = jest.fn();
const recordError = jest.fn();
Expand All @@ -53,6 +54,7 @@ jest.mock('../orchestration/Orchestration', () => ({
dispatch,
dispatchBeacon,
setAwsCredentials,
addSessionAttributes,
allowCookies,
recordPageView,
recordError,
Expand Down Expand Up @@ -245,6 +247,16 @@ describe('CommandQueue tests', () => {
expect(setAwsCredentials).toHaveBeenCalled();
});

test('addSessionAttributes calls Orchestration.addSessionAttributes', async () => {
const cq: CommandQueue = getCommandQueue();
const result = await cq.push({
c: 'addSessionAttributes',
p: { customAttribute: 'customAttributeValue' }
});
expect(Orchestration).toHaveBeenCalled();
expect(addSessionAttributes).toHaveBeenCalled();
});

test('allowCookies calls Orchestration.allowCookies', async () => {
const cq: CommandQueue = getCommandQueue();
await cq.push({
Expand Down
15 changes: 13 additions & 2 deletions src/event-cache/EventCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ export class EventCache {
};
}

/**
* Set custom session attributes to add them to all event metadata.
*
* @param payload object containing custom attribute data in the form of key, value pairs
*/
public addSessionAttributes(sessionAttributes: {
[k: string]: string | number | boolean;
}): void {
this.sessionManager.addSessionAttributes(sessionAttributes);
}

/**
* Add a session start event to the cache.
*/
Expand Down Expand Up @@ -198,9 +209,9 @@ export class EventCache {
// objects with their own attribute sets. Instead, we store session
// attributes and page attributes together as 'meta data'.
const metaData: MetaData = {
version: '1.0.0',
...this.sessionManager.getAttributes(),
...this.pageManager.getAttributes()
...this.pageManager.getAttributes(),
version: '1.0.0'
};

this.events.push({
Expand Down
40 changes: 40 additions & 0 deletions src/event-cache/__tests__/EventCache.integ.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,44 @@ describe('EventCache tests', () => {
expect(JSON.parse(event.metadata)).toMatchObject(expectedMetaData);
});
});

test('meta data contains default attributes not overridden from custom attributes', async () => {
// Init
const EVENT1_SCHEMA = 'com.amazon.rum.event1';
const config = {
...DEFAULT_CONFIG,
...{
allowCookies: false,
sessionLengthSeconds: 0,
sessionAttributes: {
version: '2.0.0',
domain: 'overridden.console.aws.amazon.com',
browserLanguage: 'en-UK',
browserName: 'Chrome',
deviceType: 'Mac'
}
}
};

const eventCache: EventCache = Utils.createEventCache(config);
const expectedMetaData = {
version: '1.0.0',
domain: 'us-east-1.console.aws.amazon.com',
browserLanguage: 'en-US',
browserName: 'WebKit',
deviceType: 'desktop',
platformType: 'web',
pageId: '/console/home'
};

// Run
eventCache.recordPageView('/console/home');
eventCache.recordEvent(EVENT1_SCHEMA, {});

// Assert
const events: RumEvent[] = eventCache.getEventBatch();
events.forEach((event) => {
expect(JSON.parse(event.metadata)).toMatchObject(expectedMetaData);
});
});
});
66 changes: 64 additions & 2 deletions src/event-cache/__tests__/EventCache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ const getSession = jest.fn(() => ({
const getUserId = jest.fn(() => 'b');
const getAttributes = jest.fn();
const incrementSessionEventCount = jest.fn();
const addSessionAttributes = jest.fn();
jest.mock('../../sessions/SessionManager', () => ({
SessionManager: jest.fn().mockImplementation(() => ({
getSession,
getUserId,
getAttributes,
incrementSessionEventCount
incrementSessionEventCount,
addSessionAttributes
}))
}));

Expand Down Expand Up @@ -236,7 +238,7 @@ describe('EventCache tests', () => {
timestamp: new Date(),
type: EVENT1_SCHEMA,
metadata:
'{"version":"1.0.0","title":"","pageId":"/rum/home","pageTags":["pageGroup1"]}',
'{"title":"","pageId":"/rum/home","pageTags":["pageGroup1"],"version":"1.0.0"}',
details: '{"version":"1.0.0","pageId":"/rum/home"}'
}
];
Expand All @@ -253,6 +255,66 @@ describe('EventCache tests', () => {
);
});

test('when page is recorded with custom page attributes, metadata records the custom page attributes', async () => {
// Init
const EVENT1_SCHEMA = 'com.amazon.rum.page_view_event';
const eventCache: EventCache = Utils.createEventCache({
...DEFAULT_CONFIG
});
const expectedEvents: RumEvent[] = [
{
id: expect.stringMatching(/[0-9a-f\-]+/),
timestamp: new Date(),
type: EVENT1_SCHEMA,
metadata:
'{"customPageAttributeString":"customPageAttributeValue","customPageAttributeNumber":1,"customPageAttributeBoolean":true,"title":"","pageId":"/rum/home","pageTags":["pageGroup1"],"version":"1.0.0"}',
details: '{"version":"1.0.0","pageId":"/rum/home"}'
}
];

// Run
eventCache.recordPageView({
pageId: '/rum/home',
pageTags: ['pageGroup1'],
pageAttributes: {
customPageAttributeString: 'customPageAttributeValue',
customPageAttributeNumber: 1,
customPageAttributeBoolean: true
}
});

// Assert
expect(eventCache.getEventBatch()).toEqual(
expect.arrayContaining(expectedEvents)
);
});

/**
* Test title truncated to meet lint requirements
* Full title: when EventCache.addSessionAttributes() is called then SessionManager.addSessionAttributes() is called
*/
test('EventCache.addSessionAttributes() calls SessionManager.addSessionAttributes()', async () => {
// Init
const eventCache: EventCache = Utils.createEventCache({
...DEFAULT_CONFIG
});

const expected = {
customAttributeString: 'customAttributeValue',
customAttributeNumber: 1,
customAttributeBoolean: true
};

// Run
eventCache.addSessionAttributes(expected);

// Assert
expect(addSessionAttributes).toHaveBeenCalledTimes(1);
const actual = addSessionAttributes.mock.calls[0][0];

expect(actual).toEqual(expected);
});

test('when page matches both allowed and denied, recordEvent does not record the event', async () => {
// Init
const EVENT1_SCHEMA = 'com.amazon.rum.event1';
Expand Down
12 changes: 12 additions & 0 deletions src/event-schemas/meta-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@
},
"pageTags": { "type": "array", "items": { "type": "string" } }
},
"patternProperties": {
"^(?!pageTags).{1,128}$": {
"maxLength": 256,
"type": ["string", "boolean", "number"]
},
"pageTags": {
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false,
"required": ["version", "domain"]
}
5 changes: 4 additions & 1 deletion src/loader/loader-standard.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ loader('cwr', 'abc123', '1.0', 'us-west-2', './rum_javascript_telemetry.js', {
allowCookies: true,
dispatchInterval: 0,
telemetries: ['performance'],
clientBuilder: showRequestClientBuilder
clientBuilder: showRequestClientBuilder,
sessionAttributes: {
customAttributeAtInit: 'customAttributeAtInitValue'
}
});
window.cwr('setAwsCredentials', {
accessKeyId: 'a',
Expand Down
14 changes: 14 additions & 0 deletions src/orchestration/Orchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export type PartialConfig = {
batchLimit?: number;
clientBuilder?: ClientBuilder;
cookieAttributes?: PartialCookieAttributes;
sessionAttributes?: { [k: string]: string | number | boolean };
disableAutoPageView?: boolean;
dispatchInterval?: number;
enableRumClient?: boolean;
Expand Down Expand Up @@ -113,6 +114,7 @@ export const defaultConfig = (cookieAttributes: CookieAttributes): Config => {
allowCookies: false,
batchLimit: 100,
cookieAttributes,
sessionAttributes: {},
disableAutoPageView: false,
dispatchInterval: 5 * 1000,
enableRumClient: true,
Expand Down Expand Up @@ -150,6 +152,7 @@ export type Config = {
batchLimit: number;
clientBuilder?: ClientBuilder;
cookieAttributes: CookieAttributes;
sessionAttributes: { [k: string]: string | number | boolean };
disableAutoPageView: boolean;
dispatchInterval: number;
enableRumClient: boolean;
Expand Down Expand Up @@ -280,6 +283,17 @@ export class Orchestration {
this.dispatchManager.setAwsCredentials(credentials);
}

/**
* Set custom session attributes to add them to all event metadata.
*
* @param payload object containing custom attribute data in the form of key, value pairs
*/
public addSessionAttributes(sessionAttributes: {
[key: string]: string | boolean | number;
}): void {
this.eventCache.addSessionAttributes(sessionAttributes);
}

/**
* Add a telemetry plugin.
*
Expand Down

0 comments on commit 3712926

Please sign in to comment.