Skip to content

Main1 #1134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft

Main1 #1134

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/plugin-session-replay-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
"dependencies": {
"@amplitude/analytics-core": "^2.15.0",
"@amplitude/session-replay-browser": "^1.25.3",
"@amplitude/analytics-client-common": ">=1 <3",
"@amplitude/analytics-types": ">=1 <3",
"idb-keyval": "^6.2.1",
"tslib": "^2.4.1"
},
Expand Down
11 changes: 11 additions & 0 deletions packages/plugin-session-replay-browser/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { IdentifyOperation } from '@amplitude/analytics-types';

export const PROPERTY_ADD_OPERATIONS = [
IdentifyOperation.SET,
IdentifyOperation.SET_ONCE,
IdentifyOperation.ADD,
IdentifyOperation.APPEND,
IdentifyOperation.PREPEND,
IdentifyOperation.POSTINSERT,
IdentifyOperation.POSTINSERT,
];
22 changes: 22 additions & 0 deletions packages/plugin-session-replay-browser/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Event, IdentifyOperation } from '@amplitude/analytics-types';
import { PROPERTY_ADD_OPERATIONS } from './constants';

export const parseUserProperties = (event: Event) => {
if (!event.user_properties) {
return;
}
let userPropertiesObj = {};
const userPropertyKeys = Object.keys(event.user_properties);

userPropertyKeys.forEach((identifyKey) => {
if (PROPERTY_ADD_OPERATIONS.includes(identifyKey as IdentifyOperation)) {
const typedUserPropertiesOperation =
event.user_properties && (event.user_properties[identifyKey as IdentifyOperation] as Record<string, any>);
userPropertiesObj = {
...userPropertiesObj,
...typedUserPropertiesOperation,
};
}
});
return userPropertiesObj;
};
40 changes: 30 additions & 10 deletions packages/plugin-session-replay-browser/src/session-replay.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { BrowserClient, BrowserConfig, EnrichmentPlugin, Event } from '@amplitude/analytics-core';
import * as sessionReplay from '@amplitude/session-replay-browser';
import { BrowserClient, BrowserConfig, EnrichmentPlugin, Event, SpecialEventType } from '@amplitude/analytics-core';
import {
init,
setSessionId,
getSessionId,
getSessionReplayProperties,
flush,
shutdown,
evaluateTargetingAndCapture,
AmplitudeSessionReplay,
SessionReplayOptions as SessionReplayBrowserOptions,
} from '@amplitude/session-replay-browser';
import { getAnalyticsConnector } from '@amplitude/analytics-client-common';
import { parseUserProperties } from './helpers';
import { SessionReplayOptions } from './typings/session-replay';
import { VERSION } from './version';
import { AmplitudeSessionReplay } from '@amplitude/session-replay-browser';

export class SessionReplayPlugin implements EnrichmentPlugin<BrowserClient, BrowserConfig> {
static pluginName = '@amplitude/plugin-session-replay-browser';
Expand All @@ -13,14 +24,15 @@ export class SessionReplayPlugin implements EnrichmentPlugin<BrowserClient, Brow
// @ts-ignore
config: BrowserConfig;
options: SessionReplayOptions;
srInitOptions: sessionReplay.SessionReplayOptions;
srInitOptions: SessionReplayBrowserOptions;
sessionReplay: AmplitudeSessionReplay = {
flush: sessionReplay.flush,
getSessionId: sessionReplay.getSessionId,
getSessionReplayProperties: sessionReplay.getSessionReplayProperties,
init: sessionReplay.init,
setSessionId: sessionReplay.setSessionId,
shutdown: sessionReplay.shutdown,
flush: flush,
getSessionId: getSessionId,
getSessionReplayProperties: getSessionReplayProperties,
init: init,
setSessionId: setSessionId,
shutdown: shutdown,
evaluateTargetingAndCapture: evaluateTargetingAndCapture,
};

constructor(options?: SessionReplayOptions) {
Expand Down Expand Up @@ -52,6 +64,8 @@ export class SessionReplayPlugin implements EnrichmentPlugin<BrowserClient, Brow
};
}
}
const identityStore = getAnalyticsConnector(this.config.instanceName).identityStore;
const userProperties = identityStore.getIdentity().userProperties;

this.srInitOptions = {
instanceName: this.config.instanceName,
Expand All @@ -77,6 +91,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin<BrowserClient, Brow
performanceConfig: this.options.performanceConfig,
storeType: this.options.storeType,
experimental: this.options.experimental,
userProperties: userProperties,
omitElementTags: this.options.omitElementTags,
applyBackgroundColorToBlockedElements: this.options.applyBackgroundColorToBlockedElements,
interactionConfig: this.options.interactionConfig,
Expand Down Expand Up @@ -140,6 +155,11 @@ export class SessionReplayPlugin implements EnrichmentPlugin<BrowserClient, Brow
// Treating config.sessionId as source of truth, if the event's session id doesn't match, the
// event is not of the current session (offline/late events). In that case, don't tag the events
if (sessionId && sessionId === event.session_id) {
let userProperties;
if (event.event_type === SpecialEventType.IDENTIFY) {
userProperties = parseUserProperties(event);
}
await this.sessionReplay.evaluateTargetingAndCapture({ event, userProperties });
const sessionRecordingProperties = this.sessionReplay.getSessionReplayProperties();
event.event_properties = {
...event.event_properties,
Expand Down
189 changes: 189 additions & 0 deletions packages/plugin-session-replay-browser/test/plugin-helpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { parseUserProperties } from '../src/helpers';
import { Event, IdentifyOperation } from '@amplitude/analytics-types';

describe('Plugin Helpers', () => {
describe('parseUserProperties', () => {
test('should return undefined when event has no user_properties', () => {
const event: Event = {
event_type: 'test_event',
};

const result = parseUserProperties(event);
expect(result).toBeUndefined();
});

test('should parse SET operation correctly', () => {
const event: Event = {
event_type: 'test_event',
user_properties: {
[IdentifyOperation.SET]: {
name: 'John',
age: 30,
},
},
};

const result = parseUserProperties(event);
expect(result).toEqual({
name: 'John',
age: 30,
});
});

test('should parse SET_ONCE operation correctly', () => {
const event: Event = {
event_type: 'test_event',
user_properties: {
[IdentifyOperation.SET_ONCE]: {
initial_signup: '2023-01-01',
},
},
};

const result = parseUserProperties(event);
expect(result).toEqual({
initial_signup: '2023-01-01',
});
});

test('should parse ADD operation correctly', () => {
const event: Event = {
event_type: 'test_event',
user_properties: {
[IdentifyOperation.ADD]: {
score: 100,
},
},
};

const result = parseUserProperties(event);
expect(result).toEqual({
score: 100,
});
});

test('should parse APPEND operation correctly', () => {
const event: Event = {
event_type: 'test_event',
user_properties: {
[IdentifyOperation.APPEND]: {
tags: 'new_tag',
},
},
};

const result = parseUserProperties(event);
expect(result).toEqual({
tags: 'new_tag',
});
});

test('should parse PREPEND operation correctly', () => {
const event: Event = {
event_type: 'test_event',
user_properties: {
[IdentifyOperation.PREPEND]: {
history: 'first_item',
},
},
};

const result = parseUserProperties(event);
expect(result).toEqual({
history: 'first_item',
});
});

test('should ignore PREINSERT operation (not in PROPERTY_ADD_OPERATIONS)', () => {
const event: Event = {
event_type: 'test_event',
user_properties: {
[IdentifyOperation.PREINSERT]: {
list: 'item',
},
[IdentifyOperation.SET]: {
name: 'John',
},
},
};

const result = parseUserProperties(event);
expect(result).toEqual({
name: 'John',
});
});

test('should parse POSTINSERT operation correctly', () => {
const event: Event = {
event_type: 'test_event',
user_properties: {
[IdentifyOperation.POSTINSERT]: {
queue: 'new_item',
},
},
};

const result = parseUserProperties(event);
expect(result).toEqual({
queue: 'new_item',
});
});

test('should parse multiple operations correctly', () => {
const event: Event = {
event_type: 'test_event',
user_properties: {
[IdentifyOperation.SET]: {
name: 'John',
age: 30,
},
[IdentifyOperation.ADD]: {
score: 100,
},
[IdentifyOperation.APPEND]: {
tags: 'premium',
},
},
};

const result = parseUserProperties(event);
expect(result).toEqual({
name: 'John',
age: 30,
score: 100,
tags: 'premium',
});
});

test('should ignore non-identify operations', () => {
const event: Event = {
event_type: 'test_event',
user_properties: {
[IdentifyOperation.SET]: {
name: 'John',
},
[IdentifyOperation.UNSET]: ['old_property'],
[IdentifyOperation.CLEAR_ALL]: true,
custom_property: 'should_be_ignored',
},
};

const result = parseUserProperties(event);
expect(result).toEqual({
name: 'John',
});
});

test('should handle empty operations object', () => {
const event: Event = {
event_type: 'test_event',
user_properties: {
[IdentifyOperation.SET]: {},
},
};

const result = parseUserProperties(event);
expect(result).toEqual({});
});
});
});
Loading