Skip to content

Commit 6aadf04

Browse files
feat: Support inline context for custom and migration events (#810)
**Requirements** - [x] I have added test coverage for new or changed functionality - [x] I have followed the repository's [pull request submission guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests) - [x] I have validated my changes against all supported platform versions **Describe the solution you've provided** - Support inlining context for custom events. The logic for this needs to happen in EventProcessor which is shared between server and client sdks in js-core. It has been verified with product analytics that this is ok for the client sdks. - Support inlining context for migration events. This causes a change in the public interface for `LDClient.trackMigration` where the `LDMigrationOpEvent` parameter has been modified. `contextKeys` has been made optional and deprecated in favor of `context` and all internal uses of `LDMigrationOpEvent` have been changed to use `context`. BEGIN_COMMIT_OVERRIDE feat: Support inline context for custom and migration events fix: Deprecate LDMigrationOpEvent.contextKeys in favor of LDMigrationOpEvent.context END_COMMIT_OVERRIDE
1 parent 78279c7 commit 6aadf04

File tree

13 files changed

+182
-64
lines changed

13 files changed

+182
-64
lines changed

contract-tests/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ app.get('/', (req, res) => {
3434
'event-sampling',
3535
'strongly-typed',
3636
'polling-gzip',
37-
'inline-context',
37+
'inline-context-all',
3838
'anonymous-redaction',
3939
'evaluation-hooks',
4040
'wrapper',

packages/sdk/browser/__tests__/BrowserClient.test.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,9 @@ describe('given a mock platform for a BrowserClient', () => {
145145
kind: 'custom',
146146
creationDate: 1726704000000,
147147
key: 'user-key',
148-
contextKeys: {
149-
user: 'user-key',
148+
context: {
149+
key: 'user-key',
150+
kind: 'user',
150151
},
151152
metricValue: 1,
152153
url: 'http://browserclientintegration.com',
@@ -178,8 +179,9 @@ describe('given a mock platform for a BrowserClient', () => {
178179
kind: 'custom',
179180
creationDate: 1726704000000,
180181
key: 'user-key',
181-
contextKeys: {
182-
user: 'user-key',
182+
context: {
183+
key: 'user-key',
184+
kind: 'user',
183185
},
184186
metricValue: 1,
185187
url: 'http://filtered.org',

packages/sdk/browser/contract-tests/entity/src/TestHarnessWebSocket.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default class TestHarnessWebSocket {
3838
'service-endpoints',
3939
'tags',
4040
'user-type',
41-
'inline-context',
41+
'inline-context-all',
4242
'anonymous-redaction',
4343
'strongly-typed',
4444
'client-prereq-events',

packages/sdk/browser/contract-tests/suppressions.txt

+12
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,15 @@ streaming/requests/URL path is computed correctly/no environment filter/base URI
44
streaming/requests/context properties/single kind minimal/REPORT
55
streaming/requests/context properties/single kind with all attributes/REPORT
66
streaming/requests/context properties/multi-kind/REPORT
7+
tags/stream requests/{"applicationId":null,"applicationVersion":"._-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678"}
8+
tags/stream requests/{"applicationId":null,"applicationVersion":"________________________________________________________________"}
9+
tags/stream requests/{"applicationId":"","applicationVersion":"._-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678"}
10+
tags/stream requests/{"applicationId":"","applicationVersion":"________________________________________________________________"}
11+
tags/stream requests/{"applicationId":"._-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678","applicationVersion":null}
12+
tags/stream requests/{"applicationId":"._-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678","applicationVersion":""}
13+
tags/stream requests/{"applicationId":"._-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678","applicationVersion":"._-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678"}
14+
tags/stream requests/{"applicationId":"._-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678","applicationVersion":"________________________________________________________________"}
15+
tags/stream requests/{"applicationId":"________________________________________________________________","applicationVersion":null}
16+
tags/stream requests/{"applicationId":"________________________________________________________________","applicationVersion":""}
17+
tags/stream requests/{"applicationId":"________________________________________________________________","applicationVersion":"._-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678"}
18+
tags/stream requests/{"applicationId":"________________________________________________________________","applicationVersion":"________________________________________________________________"}

packages/shared/common/__tests__/internal/events/EventProcessor.test.ts

+3-9
Original file line numberDiff line numberDiff line change
@@ -671,9 +671,7 @@ describe('given an event processor', () => {
671671
key: 'eventkey',
672672
data: { thing: 'stuff' },
673673
creationDate: 1000,
674-
contextKeys: {
675-
user: 'userKey',
676-
},
674+
context: { ...user, kind: 'user' },
677675
},
678676
]);
679677
});
@@ -701,9 +699,7 @@ describe('given an event processor', () => {
701699
key: 'eventkey',
702700
data: { thing: 'stuff' },
703701
creationDate: 1000,
704-
contextKeys: {
705-
user: 'anon-user',
706-
},
702+
context: { ...anonUser, kind: 'user' },
707703
},
708704
]);
709705
});
@@ -733,9 +729,7 @@ describe('given an event processor', () => {
733729
key: 'eventkey',
734730
data: { thing: 'stuff' },
735731
creationDate: 1000,
736-
contextKeys: {
737-
user: 'userKey',
738-
},
732+
context: { ...user, kind: 'user' },
739733
metricValue: 1.5,
740734
},
741735
]);

packages/shared/common/src/internal/events/EventProcessor.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ interface CustomOutputEvent {
3232
kind: 'custom';
3333
creationDate: number;
3434
key: string;
35-
contextKeys: Record<string, string>;
35+
context: FilteredContext;
3636
data?: any;
3737
metricValue?: number;
3838
samplingRatio?: number;
@@ -83,9 +83,11 @@ interface PageviewOutputEvent {
8383
*/
8484
type DiagnosticEvent = any;
8585

86-
interface MigrationOutputEvent extends Omit<InputMigrationEvent, 'samplingRatio'> {
86+
interface MigrationOutputEvent extends Omit<InputMigrationEvent, 'samplingRatio' | 'context'> {
8787
// Make the sampling ratio optional so we can omit it when it is one.
8888
samplingRatio?: number;
89+
// Context is optional because contextKeys is supported for backwards compatbility and may be provided instead of context.
90+
context?: FilteredContext;
8991
}
9092

9193
type OutputEvent =
@@ -236,6 +238,7 @@ export default class EventProcessor implements LDEventProcessor {
236238
if (shouldSample(inputEvent.samplingRatio)) {
237239
const migrationEvent: MigrationOutputEvent = {
238240
...inputEvent,
241+
context: inputEvent.context ? this._contextFilter.filter(inputEvent.context) : undefined,
239242
};
240243
if (migrationEvent.samplingRatio === 1) {
241244
delete migrationEvent.samplingRatio;
@@ -331,7 +334,7 @@ export default class EventProcessor implements LDEventProcessor {
331334
kind: 'custom',
332335
creationDate: event.creationDate,
333336
key: event.key,
334-
contextKeys: event.context.kindsAndKeys,
337+
context: this._contextFilter.filter(event.context),
335338
};
336339

337340
if (event.samplingRatio !== 1) {

packages/shared/common/src/internal/events/InputMigrationEvent.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22
// shared implementation contains minimal typing. If/When migration events are
33
// to be supported by client-side SDKs the appropriate types would be moved
44
// to the common implementation.
5+
import Context from '../../Context';
56

67
export default interface InputMigrationEvent {
78
kind: 'migration_op';
89
operation: string;
910
creationDate: number;
10-
contextKeys: Record<string, string>;
11+
/**
12+
* @deprecated Use 'context' instead.
13+
*/
14+
contextKeys?: Record<string, string>;
15+
context?: Context;
1116
evaluation: any;
1217
measurements: any[];
1318
samplingRatio: number;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { LDContext, LDMigrationOpEvent, LDMigrationStage } from '../src';
2+
import migrationOpEventToInputEvent from '../src/MigrationOpEventConversion';
3+
4+
const baseEvent: LDMigrationOpEvent = {
5+
kind: 'migration_op',
6+
operation: 'read',
7+
creationDate: new Date().getTime(),
8+
evaluation: {
9+
default: LDMigrationStage.Off,
10+
key: 'flag',
11+
reason: { kind: 'FALLTHROUGH' },
12+
value: LDMigrationStage.Off,
13+
},
14+
measurements: [],
15+
samplingRatio: 1,
16+
};
17+
18+
it('handles event without either context or contextKeys', () => {
19+
expect(migrationOpEventToInputEvent(baseEvent)).toBeUndefined();
20+
});
21+
22+
it('handles event with only context', () => {
23+
const outEvent = migrationOpEventToInputEvent({
24+
...baseEvent,
25+
context: { key: 'user-key' },
26+
});
27+
expect(outEvent).toBeDefined();
28+
expect(outEvent?.context?.key()).toEqual('user-key');
29+
expect(outEvent?.context?.kind).toEqual('user');
30+
expect(outEvent?.contextKeys).toBeUndefined();
31+
});
32+
33+
it('handles event with only contextKeys', () => {
34+
const outEvent = migrationOpEventToInputEvent({
35+
...baseEvent,
36+
contextKeys: { user: 'bob' },
37+
});
38+
expect(outEvent).toBeDefined();
39+
expect(outEvent?.context).toBeUndefined();
40+
expect(outEvent?.contextKeys).toEqual({ user: 'bob' });
41+
});
42+
43+
it('handles invalid context', () => {
44+
const outEvent = migrationOpEventToInputEvent({
45+
...baseEvent,
46+
context: {} as LDContext,
47+
});
48+
expect(outEvent).toBeUndefined();
49+
});
50+
51+
it('handles invalid context even if contextKeys is provided', () => {
52+
const outEvent = migrationOpEventToInputEvent({
53+
...baseEvent,
54+
context: {} as LDContext,
55+
contextKeys: { user: 'bob' },
56+
});
57+
expect(outEvent).toBeUndefined();
58+
});
59+
60+
it('handles invalid key in contextKeys', () => {
61+
const outEvent = migrationOpEventToInputEvent({
62+
...baseEvent,
63+
contextKeys: { kind: 'user' },
64+
});
65+
expect(outEvent).toBeUndefined();
66+
});
67+
68+
it('handles invalid value in contextKeys', () => {
69+
const outEvent = migrationOpEventToInputEvent({
70+
...baseEvent,
71+
contextKeys: { user: '' },
72+
});
73+
expect(outEvent).toBeUndefined();
74+
});
75+
76+
it('uses context if both context and contextKeys are provided', () => {
77+
const outEvent = migrationOpEventToInputEvent({
78+
...baseEvent,
79+
context: { key: 'user-key' },
80+
contextKeys: { user: 'bob' },
81+
});
82+
expect(outEvent).toBeDefined();
83+
expect(outEvent?.context?.key()).toEqual('user-key');
84+
expect(outEvent?.context?.kind).toEqual('user');
85+
expect(outEvent?.contextKeys).toBeUndefined();
86+
});

0 commit comments

Comments
 (0)