Skip to content

Commit c040140

Browse files
authored
feat: persist subject key and attributes for precomputed logging (#163)
* Persist with instance variables * setSubjectDataAndPrecomputedFlagStore and tests * Include logging in test * Rename function * v4.6.1 * Update description * Make method to set subject from PrecomputedFlagsRequestParameters * Description update
1 parent 25f20f1 commit c040140

File tree

3 files changed

+84
-14
lines changed

3 files changed

+84
-14
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@eppo/js-client-sdk-common",
3-
"version": "4.6.0",
4-
"description": "Eppo SDK for client-side JavaScript applications (base for both web and react native)",
3+
"version": "4.6.1",
4+
"description": "Common library for Eppo JavaScript SDKs (web, react native, and node)",
55
"main": "dist/index.js",
66
"files": [
77
"/dist",

src/client/eppo-precomputed-client.spec.ts

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,8 @@ describe('EppoPrecomputedClient E2E test', () => {
430430
});
431431

432432
it('Fetches initial configuration with parameters in constructor', async () => {
433-
client = new EppoPrecomputedClient(precomputedFlagStore, requestParameters);
433+
client = new EppoPrecomputedClient(precomputedFlagStore);
434+
client.setSubjectAndPrecomputedFlagsRequestParameters(requestParameters);
434435
// no configuration loaded
435436
let variation = client.getStringAssignment(precomputedFlagKey, 'default');
436437
expect(variation).toBe('default');
@@ -442,7 +443,7 @@ describe('EppoPrecomputedClient E2E test', () => {
442443

443444
it('Fetches initial configuration with parameters provided later', async () => {
444445
client = new EppoPrecomputedClient(precomputedFlagStore);
445-
client.setPrecomputedFlagsRequestParameters(requestParameters);
446+
client.setSubjectAndPrecomputedFlagsRequestParameters(requestParameters);
446447
// no configuration loaded
447448
let variation = client.getStringAssignment(precomputedFlagKey, 'default');
448449
expect(variation).toBe('default');
@@ -462,7 +463,8 @@ describe('EppoPrecomputedClient E2E test', () => {
462463
}
463464
}
464465

465-
client = new EppoPrecomputedClient(new MockStore(), {
466+
client = new EppoPrecomputedClient(new MockStore());
467+
client.setSubjectAndPrecomputedFlagsRequestParameters({
466468
...requestParameters,
467469
pollAfterSuccessfulInitialization: true,
468470
});
@@ -491,7 +493,8 @@ describe('EppoPrecomputedClient E2E test', () => {
491493
}
492494
}
493495

494-
client = new EppoPrecomputedClient(new MockStore(), requestParameters);
496+
client = new EppoPrecomputedClient(new MockStore());
497+
client.setSubjectAndPrecomputedFlagsRequestParameters(requestParameters);
495498
// no configuration loaded
496499
let variation = client.getStringAssignment(precomputedFlagKey, 'default');
497500
expect(variation).toBe('default');
@@ -505,7 +508,8 @@ describe('EppoPrecomputedClient E2E test', () => {
505508
let client: EppoPrecomputedClient;
506509

507510
beforeEach(async () => {
508-
client = new EppoPrecomputedClient(storage, requestParameters);
511+
client = new EppoPrecomputedClient(storage);
512+
client.setSubjectAndPrecomputedFlagsRequestParameters(requestParameters);
509513
await client.fetchPrecomputedFlags();
510514
});
511515

@@ -576,7 +580,8 @@ describe('EppoPrecomputedClient E2E test', () => {
576580
...requestParameters,
577581
pollAfterSuccessfulInitialization,
578582
};
579-
client = new EppoPrecomputedClient(precomputedFlagStore, requestParameters);
583+
client = new EppoPrecomputedClient(precomputedFlagStore);
584+
client.setSubjectAndPrecomputedFlagsRequestParameters(requestParameters);
580585
// no configuration loaded
581586
let variation = client.getStringAssignment(precomputedFlagKey, 'default');
582587
expect(variation).toBe('default');
@@ -640,7 +645,8 @@ describe('EppoPrecomputedClient E2E test', () => {
640645
throwOnFailedInitialization,
641646
pollAfterFailedInitialization,
642647
};
643-
client = new EppoPrecomputedClient(precomputedFlagStore, requestParameters);
648+
client = new EppoPrecomputedClient(precomputedFlagStore);
649+
client.setSubjectAndPrecomputedFlagsRequestParameters(requestParameters);
644650
// no configuration loaded
645651
expect(client.getStringAssignment(precomputedFlagKey, 'default')).toBe('default');
646652

@@ -678,7 +684,7 @@ describe('EppoPrecomputedClient E2E test', () => {
678684
extraLogging: {},
679685
},
680686
});
681-
client = new EppoPrecomputedClient(storage, undefined, true);
687+
client = new EppoPrecomputedClient(storage, true);
682688
});
683689

684690
afterAll(() => {
@@ -705,4 +711,51 @@ describe('EppoPrecomputedClient E2E test', () => {
705711

706712
expect(loggedEvent.format).toEqual(FormatEnum.PRECOMPUTED);
707713
});
714+
715+
describe('EppoPrecomputedClient subject data and store initialization', () => {
716+
let client: EppoPrecomputedClient;
717+
let store: IConfigurationStore<PrecomputedFlag>;
718+
let mockLogger: IAssignmentLogger;
719+
720+
beforeEach(() => {
721+
store = new MemoryOnlyConfigurationStore<PrecomputedFlag>();
722+
mockLogger = td.object<IAssignmentLogger>();
723+
client = new EppoPrecomputedClient(store);
724+
client.setAssignmentLogger(mockLogger);
725+
});
726+
727+
it('returns default value and does not log when store is not initialized', () => {
728+
client.setSubjectAndPrecomputedFlagStore('test-subject', {}, store);
729+
expect(client.getStringAssignment('test-flag', 'default')).toBe('default');
730+
expect(td.explain(mockLogger.logAssignment).callCount).toEqual(0);
731+
});
732+
733+
it('returns assignment and logs subject data after store is initialized with flags', async () => {
734+
const subjectKey = 'test-subject';
735+
const subjectAttributes = { attr1: 'value1' };
736+
737+
await store.setEntries({
738+
'test-flag': {
739+
variationType: VariationType.STRING,
740+
variationKey: 'control',
741+
variationValue: 'test-value',
742+
allocationKey: 'allocation-1',
743+
doLog: true,
744+
extraLogging: {},
745+
},
746+
});
747+
client.setSubjectAndPrecomputedFlagStore(subjectKey, subjectAttributes, store);
748+
expect(client.getStringAssignment('test-flag', 'default')).toBe('test-value');
749+
750+
expect(td.explain(mockLogger.logAssignment).callCount).toEqual(1);
751+
const loggedEvent = td.explain(mockLogger.logAssignment).calls[0].args[0];
752+
expect(loggedEvent.subject).toEqual(subjectKey);
753+
expect(loggedEvent.subjectAttributes).toEqual(subjectAttributes);
754+
});
755+
756+
it('returns default value and does not log when subject data is not set', () => {
757+
expect(client.getStringAssignment('test-flag', 'default')).toBe('default');
758+
expect(td.explain(mockLogger.logAssignment).callCount).toEqual(0);
759+
});
760+
});
708761
});

src/client/eppo-precomputed-client.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,20 @@ export default class EppoPrecomputedClient {
4848
private assignmentLogger?: IAssignmentLogger;
4949
private assignmentCache?: AssignmentCache;
5050
private requestPoller?: IPoller;
51+
private precomputedFlagsRequestParameters?: PrecomputedFlagsRequestParameters;
52+
private subjectKey?: string;
53+
private subjectAttributes?: Attributes;
5154

5255
constructor(
5356
private precomputedFlagStore: IConfigurationStore<PrecomputedFlag>,
54-
private precomputedFlagsRequestParameters?: PrecomputedFlagsRequestParameters,
5557
private isObfuscated = false,
5658
) {}
5759

58-
public setPrecomputedFlagsRequestParameters(
60+
public setSubjectAndPrecomputedFlagsRequestParameters(
5961
precomputedFlagsRequestParameters: PrecomputedFlagsRequestParameters,
6062
) {
63+
this.subjectKey = precomputedFlagsRequestParameters.precompute.subjectKey;
64+
this.subjectAttributes = precomputedFlagsRequestParameters.precompute.subjectAttributes;
6165
this.precomputedFlagsRequestParameters = precomputedFlagsRequestParameters;
6266
}
6367

@@ -134,6 +138,19 @@ export default class EppoPrecomputedClient {
134138
}
135139
}
136140

141+
public setSubjectAndPrecomputedFlagStore(
142+
subjectKey: string,
143+
subjectAttributes: Attributes,
144+
precomputedFlagStore: IConfigurationStore<PrecomputedFlag>,
145+
) {
146+
// Save the new subject data and precomputed flag store together because they are related
147+
// Stop any polling process if it exists from previous subject data to protect consistency
148+
this.requestPoller?.stop();
149+
this.setPrecomputedFlagStore(precomputedFlagStore);
150+
this.subjectKey = subjectKey;
151+
this.subjectAttributes = subjectAttributes;
152+
}
153+
137154
private getPrecomputedAssignment<T>(
138155
flagKey: string,
139156
defaultValue: T,
@@ -160,8 +177,8 @@ export default class EppoPrecomputedClient {
160177
const result: FlagEvaluationWithoutDetails = {
161178
flagKey,
162179
format: this.precomputedFlagStore.getFormat() ?? '',
163-
subjectKey: this.precomputedFlagsRequestParameters?.precompute.subjectKey ?? '',
164-
subjectAttributes: this.precomputedFlagsRequestParameters?.precompute.subjectAttributes ?? {},
180+
subjectKey: this.subjectKey ?? '',
181+
subjectAttributes: this.subjectAttributes ?? {},
165182
variation: {
166183
key: preComputedFlag.variationKey,
167184
value: preComputedFlag.variationValue,

0 commit comments

Comments
 (0)