Skip to content

Commit cdab2ae

Browse files
author
Kelly Wallach
committed
fix(session replay): rebase fixes
1 parent 68dff21 commit cdab2ae

File tree

11 files changed

+225
-411
lines changed

11 files changed

+225
-411
lines changed

packages/plugin-session-replay-browser/src/session-replay.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ class SessionReplayEnrichmentPlugin implements EnrichmentPlugin {
3535
const identityStore = getAnalyticsConnector(this.config.instanceName).identityStore;
3636
userProperties = identityStore.getIdentity().userProperties;
3737
}
38-
await sessionReplay.setSessionId(this.config.sessionId, this.config.deviceId, { userProperties }).promise;
38+
await sessionReplay.setSessionId(this.config.sessionId, this.config.deviceId, {
39+
userProperties: userProperties || {},
40+
}).promise;
3941
}
4042

4143
// Treating config.sessionId as source of truth, if the event's session id doesn't match, the

packages/session-replay-browser/src/session-replay.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,11 @@ export class SessionReplay implements AmplitudeSessionReplay {
252252

253253
evaluateTargetingAndRecord = async (targetingParams?: Pick<TargetingParameters, 'event' | 'userProperties'>) => {
254254
if (!this.identifiers || !this.identifiers.sessionId || !this.config) {
255-
this.loggerProvider.error('Session replay init has not been called, cannot evaluate targeting.');
255+
if (!this.identifiers?.sessionId) {
256+
this.loggerProvider.warn('Session ID has not been set, cannot evaluate targeting for Session Replay.');
257+
} else {
258+
this.loggerProvider.warn('Session replay init has not been called, cannot evaluate targeting.');
259+
}
256260
return false;
257261
}
258262
// Return early if a targeting match has already been made

packages/targeting/jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ module.exports = {
77
rootDir: '.',
88
testEnvironment: 'jsdom',
99
coveragePathIgnorePatterns: ['index.ts'],
10+
setupFilesAfterEnv: ['./test/jest-setup.js'],
1011
};

packages/targeting/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,14 @@
4242
"@amplitude/analytics-core": ">=1 <3",
4343
"@amplitude/analytics-types": ">=1 <3",
4444
"@amplitude/experiment-core": "0.7.2",
45+
"idb": "^8.0.0",
4546
"tslib": "^2.4.1"
4647
},
4748
"devDependencies": {
4849
"@rollup/plugin-commonjs": "^23.0.4",
4950
"@rollup/plugin-node-resolve": "^15.0.1",
5051
"@rollup/plugin-typescript": "^10.0.1",
52+
"fake-indexeddb": "4.0.2",
5153
"rollup": "^2.79.1",
5254
"rollup-plugin-execute": "^1.1.1",
5355
"rollup-plugin-gzip": "^3.1.0",

packages/targeting/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
export { Targeting } from './targeting';
1+
import targeting from './targeting-factory';
2+
export const { evaluateTargeting } = targeting;
3+
export { TargetingFlag, TargetingParameters } from './typings/targeting';
Lines changed: 0 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,25 @@
1-
<<<<<<< HEAD
2-
<<<<<<< HEAD
31
import { Logger as ILogger } from '@amplitude/analytics-types';
4-
=======
5-
import { Logger as ILogger, SpecialEventType } from '@amplitude/analytics-types';
6-
>>>>>>> 3e83ab49 (feat(session replay): add ability to target on multiple events)
7-
=======
8-
import { Logger as ILogger } from '@amplitude/analytics-types';
9-
>>>>>>> 79705348 (test(targeting + session replay): get test coverage up to 100%)
102
import { DBSchema, IDBPDatabase, IDBPTransaction, openDB } from 'idb';
113

124
export const MAX_IDB_STORAGE_LENGTH = 1000 * 60 * 60 * 24 * 2; // 2 days
135

14-
<<<<<<< HEAD
15-
<<<<<<< HEAD
16-
=======
17-
>>>>>>> 7ea29d5c (fix(targeting): keep track of open db instances and ensure deduplication of events)
186
// This type is constructed to allow for future proofing - in the future we may want
197
// to track how many of each event is fired, and we may want to track event properties
208
// Any further fields, like event properties, can be added to this type without causing
219
// a breaking change
2210
type EventData = { event_type: string };
2311

2412
type EventTypeStore = { [event_type: string]: { [timestamp: number]: EventData } };
25-
<<<<<<< HEAD
26-
=======
27-
>>>>>>> 3e83ab49 (feat(session replay): add ability to target on multiple events)
28-
=======
29-
>>>>>>> 7ea29d5c (fix(targeting): keep track of open db instances and ensure deduplication of events)
3013
export interface TargetingDB extends DBSchema {
3114
eventTypesForSession: {
3215
key: number;
3316
value: {
3417
sessionId: number;
35-
<<<<<<< HEAD
36-
<<<<<<< HEAD
37-
<<<<<<< HEAD
38-
eventTypes: EventTypeStore;
39-
=======
40-
eventTypes: Set<string>;
41-
>>>>>>> 3e83ab49 (feat(session replay): add ability to target on multiple events)
42-
=======
43-
eventTypes: Array<{ event_type: string }>;
44-
>>>>>>> 79705348 (test(targeting + session replay): get test coverage up to 100%)
45-
=======
4618
eventTypes: EventTypeStore;
47-
>>>>>>> 7ea29d5c (fix(targeting): keep track of open db instances and ensure deduplication of events)
4819
};
4920
};
5021
}
5122

52-
<<<<<<< HEAD
53-
<<<<<<< HEAD
5423
export class TargetingIDBStore {
5524
dbs: { [apiKey: string]: IDBPDatabase<TargetingDB> } = {};
5625

@@ -177,155 +146,3 @@ export class TargetingIDBStore {
177146
}
178147

179148
export const targetingIDBStore = new TargetingIDBStore();
180-
=======
181-
export const createStore = async (dbName: string) => {
182-
return await openDB<TargetingDB>(dbName, 1, {
183-
upgrade: (db: IDBPDatabase<TargetingDB>) => {
184-
if (!db.objectStoreNames.contains('eventTypesForSession')) {
185-
db.createObjectStore('eventTypesForSession', {
186-
keyPath: 'sessionId',
187-
});
188-
}
189-
},
190-
});
191-
};
192-
=======
193-
export class TargetingIDBStore {
194-
dbs: { [apiKey: string]: IDBPDatabase<TargetingDB> } | undefined;
195-
>>>>>>> 7ea29d5c (fix(targeting): keep track of open db instances and ensure deduplication of events)
196-
197-
createStore = async (dbName: string) => {
198-
return await openDB<TargetingDB>(dbName, 1, {
199-
upgrade: (db: IDBPDatabase<TargetingDB>) => {
200-
if (!db.objectStoreNames.contains('eventTypesForSession')) {
201-
db.createObjectStore('eventTypesForSession', {
202-
keyPath: 'sessionId',
203-
});
204-
}
205-
},
206-
});
207-
};
208-
209-
openOrCreateDB = async (apiKey: string) => {
210-
if (this.dbs && this.dbs[apiKey]) {
211-
return this.dbs[apiKey];
212-
}
213-
const dbName = `${apiKey.substring(0, 10)}_amp_targeting`;
214-
const db = await this.createStore(dbName);
215-
this.dbs = {
216-
...this.dbs,
217-
[apiKey]: db,
218-
};
219-
return db;
220-
};
221-
222-
updateEventListForSession = async ({
223-
sessionId,
224-
eventType,
225-
eventTime,
226-
loggerProvider,
227-
tx,
228-
}: {
229-
sessionId: number;
230-
eventType: string;
231-
eventTime: number;
232-
loggerProvider: ILogger;
233-
tx: IDBPTransaction<TargetingDB, ['eventTypesForSession'], 'readwrite'>;
234-
}) => {
235-
try {
236-
const eventTypesForSessionStorage = await tx.store.get(sessionId);
237-
const eventTypesForSession = eventTypesForSessionStorage ? eventTypesForSessionStorage.eventTypes : {};
238-
const eventTypeStore = eventTypesForSession[eventType] || {};
239-
240-
const updatedEventTypes: EventTypeStore = {
241-
...eventTypesForSession,
242-
[eventType]: {
243-
...eventTypeStore,
244-
[eventTime]: { event_type: eventType },
245-
},
246-
};
247-
await tx.store.put({ sessionId, eventTypes: updatedEventTypes });
248-
return updatedEventTypes;
249-
} catch (e) {
250-
loggerProvider.warn(`Failed to store events for targeting ${sessionId}: ${e as string}`);
251-
}
252-
return undefined;
253-
};
254-
255-
deleteOldSessionEventTypes = async ({
256-
currentSessionId,
257-
loggerProvider,
258-
tx,
259-
}: {
260-
currentSessionId: number;
261-
loggerProvider: ILogger;
262-
tx: IDBPTransaction<TargetingDB, ['eventTypesForSession'], 'readwrite'>;
263-
}) => {
264-
try {
265-
const allEventTypeObjs = await tx.store.getAll();
266-
for (let i = 0; i < allEventTypeObjs.length; i++) {
267-
const eventTypeObj = allEventTypeObjs[i];
268-
const amountOfTimeSinceSession = Date.now() - eventTypeObj.sessionId;
269-
if (eventTypeObj.sessionId !== currentSessionId && amountOfTimeSinceSession > MAX_IDB_STORAGE_LENGTH) {
270-
await tx.store.delete(eventTypeObj.sessionId);
271-
}
272-
}
273-
} catch (e) {
274-
loggerProvider.warn(`Failed to clear old session events for targeting: ${e as string}`);
275-
}
276-
};
277-
278-
storeEventTypeForSession = async ({
279-
loggerProvider,
280-
sessionId,
281-
eventType,
282-
eventTime,
283-
apiKey,
284-
}: {
285-
loggerProvider: ILogger;
286-
apiKey: string;
287-
eventType: string;
288-
eventTime: number;
289-
sessionId: number;
290-
}) => {
291-
try {
292-
const db = await this.openOrCreateDB(apiKey);
293-
294-
const tx = db.transaction<'eventTypesForSession', 'readwrite'>('eventTypesForSession', 'readwrite');
295-
if (!tx) {
296-
return;
297-
}
298-
299-
<<<<<<< HEAD
300-
return updatedEventTypes;
301-
} catch (e) {
302-
loggerProvider.warn(`Failed to store events for targeting ${sessionId}: ${e as string}`);
303-
}
304-
return undefined;
305-
};
306-
>>>>>>> 3e83ab49 (feat(session replay): add ability to target on multiple events)
307-
=======
308-
// Update the list of events for the session
309-
const updatedEventTypes = await this.updateEventListForSession({
310-
sessionId,
311-
tx,
312-
loggerProvider,
313-
eventType,
314-
eventTime,
315-
});
316-
317-
// Clear out sessions older than 2 days
318-
await this.deleteOldSessionEventTypes({ currentSessionId: sessionId, tx, loggerProvider });
319-
320-
await tx.done;
321-
322-
return updatedEventTypes;
323-
} catch (e) {
324-
loggerProvider.warn(`Failed to store events for targeting ${sessionId}: ${e as string}`);
325-
}
326-
return undefined;
327-
};
328-
}
329-
330-
export const targetingIDBStore = new TargetingIDBStore();
331-
>>>>>>> 7ea29d5c (fix(targeting): keep track of open db instances and ensure deduplication of events)

packages/targeting/src/targeting.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { EvaluationEngine } from '@amplitude/experiment-core';
2+
import { targetingIDBStore } from './targeting-idb-store';
23
import { Targeting as AmplitudeTargeting, TargetingParameters } from './typings/targeting';
34

45
export class Targeting implements AmplitudeTargeting {
@@ -8,15 +9,38 @@ export class Targeting implements AmplitudeTargeting {
89
this.evaluationEngine = new EvaluationEngine();
910
}
1011

11-
evaluateTargeting({ event, sessionId, userProperties, deviceId, flag }: TargetingParameters) {
12+
evaluateTargeting = async ({
13+
apiKey,
14+
loggerProvider,
15+
event,
16+
sessionId,
17+
userProperties,
18+
deviceId,
19+
flag,
20+
}: TargetingParameters) => {
21+
const eventTypes =
22+
event && event.time
23+
? await targetingIDBStore.storeEventTypeForSession({
24+
loggerProvider: loggerProvider,
25+
apiKey: apiKey,
26+
sessionId,
27+
eventType: event.event_type,
28+
eventTime: event.time,
29+
})
30+
: undefined;
31+
32+
const eventStrings = eventTypes && new Set(Object.keys(eventTypes));
33+
1234
const context = {
1335
session_id: sessionId,
1436
event,
37+
event_types: eventStrings && Array.from(eventStrings),
1538
user: {
1639
device_id: deviceId,
1740
user_properties: userProperties,
1841
},
1942
};
20-
return this.evaluationEngine.evaluate(context, [flag]);
21-
}
43+
const targetingBucket = this.evaluationEngine.evaluate(context, [flag]);
44+
return targetingBucket;
45+
};
2246
}
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
import { Event, IdentifyUserProperties } from '@amplitude/analytics-types';
1+
import { Event, Logger } from '@amplitude/analytics-types';
22
import { EvaluationFlag, EvaluationVariant } from '@amplitude/experiment-core';
3+
4+
export type TargetingFlag = EvaluationFlag;
35
export interface TargetingParameters {
46
event?: Event;
5-
userProperties?: IdentifyUserProperties;
7+
userProperties?: { [key: string]: any };
68
deviceId?: string;
79
flag: EvaluationFlag;
8-
sessionId?: string;
10+
sessionId: number;
11+
apiKey: string;
12+
loggerProvider: Logger;
913
}
1014

1115
export interface Targeting {
12-
evaluateTargeting(args: TargetingParameters): Record<string, EvaluationVariant>;
16+
evaluateTargeting: (args: TargetingParameters) => Promise<Record<string, EvaluationVariant>>;
1317
}

packages/targeting/test/flag-config-data/catch-all.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,33 @@ export const flagCatchAll = {
3232
],
3333
],
3434
},
35+
{
36+
metadata: { segmentName: 'multiple event trigger' },
37+
bucket: {
38+
selector: ['context', 'session_id'],
39+
salt: 'xdfrewd', // Different salt for each bucket to allow for fallthrough
40+
allocations: [
41+
{
42+
range: [0, 19], // Selects 20% of users that match these conditions
43+
distributions: [
44+
{
45+
variant: 'on',
46+
range: [0, 42949673],
47+
},
48+
],
49+
},
50+
],
51+
},
52+
conditions: [
53+
[
54+
{
55+
selector: ['context', 'event_types'],
56+
op: 'set contains',
57+
values: ['Add to Cart', 'Purchase'],
58+
},
59+
],
60+
],
61+
},
3562
{
3663
metadata: { segmentName: 'user property' },
3764
bucket: {

0 commit comments

Comments
 (0)