Skip to content

Commit

Permalink
feat: persist subject key and attributes for precomputed logging (#163)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
sameerank authored Dec 9, 2024
1 parent 25f20f1 commit c040140
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 14 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@eppo/js-client-sdk-common",
"version": "4.6.0",
"description": "Eppo SDK for client-side JavaScript applications (base for both web and react native)",
"version": "4.6.1",
"description": "Common library for Eppo JavaScript SDKs (web, react native, and node)",
"main": "dist/index.js",
"files": [
"/dist",
Expand Down
69 changes: 61 additions & 8 deletions src/client/eppo-precomputed-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,8 @@ describe('EppoPrecomputedClient E2E test', () => {
});

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

it('Fetches initial configuration with parameters provided later', async () => {
client = new EppoPrecomputedClient(precomputedFlagStore);
client.setPrecomputedFlagsRequestParameters(requestParameters);
client.setSubjectAndPrecomputedFlagsRequestParameters(requestParameters);
// no configuration loaded
let variation = client.getStringAssignment(precomputedFlagKey, 'default');
expect(variation).toBe('default');
Expand All @@ -462,7 +463,8 @@ describe('EppoPrecomputedClient E2E test', () => {
}
}

client = new EppoPrecomputedClient(new MockStore(), {
client = new EppoPrecomputedClient(new MockStore());
client.setSubjectAndPrecomputedFlagsRequestParameters({
...requestParameters,
pollAfterSuccessfulInitialization: true,
});
Expand Down Expand Up @@ -491,7 +493,8 @@ describe('EppoPrecomputedClient E2E test', () => {
}
}

client = new EppoPrecomputedClient(new MockStore(), requestParameters);
client = new EppoPrecomputedClient(new MockStore());
client.setSubjectAndPrecomputedFlagsRequestParameters(requestParameters);
// no configuration loaded
let variation = client.getStringAssignment(precomputedFlagKey, 'default');
expect(variation).toBe('default');
Expand All @@ -505,7 +508,8 @@ describe('EppoPrecomputedClient E2E test', () => {
let client: EppoPrecomputedClient;

beforeEach(async () => {
client = new EppoPrecomputedClient(storage, requestParameters);
client = new EppoPrecomputedClient(storage);
client.setSubjectAndPrecomputedFlagsRequestParameters(requestParameters);
await client.fetchPrecomputedFlags();
});

Expand Down Expand Up @@ -576,7 +580,8 @@ describe('EppoPrecomputedClient E2E test', () => {
...requestParameters,
pollAfterSuccessfulInitialization,
};
client = new EppoPrecomputedClient(precomputedFlagStore, requestParameters);
client = new EppoPrecomputedClient(precomputedFlagStore);
client.setSubjectAndPrecomputedFlagsRequestParameters(requestParameters);
// no configuration loaded
let variation = client.getStringAssignment(precomputedFlagKey, 'default');
expect(variation).toBe('default');
Expand Down Expand Up @@ -640,7 +645,8 @@ describe('EppoPrecomputedClient E2E test', () => {
throwOnFailedInitialization,
pollAfterFailedInitialization,
};
client = new EppoPrecomputedClient(precomputedFlagStore, requestParameters);
client = new EppoPrecomputedClient(precomputedFlagStore);
client.setSubjectAndPrecomputedFlagsRequestParameters(requestParameters);
// no configuration loaded
expect(client.getStringAssignment(precomputedFlagKey, 'default')).toBe('default');

Expand Down Expand Up @@ -678,7 +684,7 @@ describe('EppoPrecomputedClient E2E test', () => {
extraLogging: {},
},
});
client = new EppoPrecomputedClient(storage, undefined, true);
client = new EppoPrecomputedClient(storage, true);
});

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

expect(loggedEvent.format).toEqual(FormatEnum.PRECOMPUTED);
});

describe('EppoPrecomputedClient subject data and store initialization', () => {
let client: EppoPrecomputedClient;
let store: IConfigurationStore<PrecomputedFlag>;
let mockLogger: IAssignmentLogger;

beforeEach(() => {
store = new MemoryOnlyConfigurationStore<PrecomputedFlag>();
mockLogger = td.object<IAssignmentLogger>();
client = new EppoPrecomputedClient(store);
client.setAssignmentLogger(mockLogger);
});

it('returns default value and does not log when store is not initialized', () => {
client.setSubjectAndPrecomputedFlagStore('test-subject', {}, store);
expect(client.getStringAssignment('test-flag', 'default')).toBe('default');
expect(td.explain(mockLogger.logAssignment).callCount).toEqual(0);
});

it('returns assignment and logs subject data after store is initialized with flags', async () => {
const subjectKey = 'test-subject';
const subjectAttributes = { attr1: 'value1' };

await store.setEntries({
'test-flag': {
variationType: VariationType.STRING,
variationKey: 'control',
variationValue: 'test-value',
allocationKey: 'allocation-1',
doLog: true,
extraLogging: {},
},
});
client.setSubjectAndPrecomputedFlagStore(subjectKey, subjectAttributes, store);
expect(client.getStringAssignment('test-flag', 'default')).toBe('test-value');

expect(td.explain(mockLogger.logAssignment).callCount).toEqual(1);
const loggedEvent = td.explain(mockLogger.logAssignment).calls[0].args[0];
expect(loggedEvent.subject).toEqual(subjectKey);
expect(loggedEvent.subjectAttributes).toEqual(subjectAttributes);
});

it('returns default value and does not log when subject data is not set', () => {
expect(client.getStringAssignment('test-flag', 'default')).toBe('default');
expect(td.explain(mockLogger.logAssignment).callCount).toEqual(0);
});
});
});
25 changes: 21 additions & 4 deletions src/client/eppo-precomputed-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,20 @@ export default class EppoPrecomputedClient {
private assignmentLogger?: IAssignmentLogger;
private assignmentCache?: AssignmentCache;
private requestPoller?: IPoller;
private precomputedFlagsRequestParameters?: PrecomputedFlagsRequestParameters;
private subjectKey?: string;
private subjectAttributes?: Attributes;

constructor(
private precomputedFlagStore: IConfigurationStore<PrecomputedFlag>,
private precomputedFlagsRequestParameters?: PrecomputedFlagsRequestParameters,
private isObfuscated = false,
) {}

public setPrecomputedFlagsRequestParameters(
public setSubjectAndPrecomputedFlagsRequestParameters(
precomputedFlagsRequestParameters: PrecomputedFlagsRequestParameters,
) {
this.subjectKey = precomputedFlagsRequestParameters.precompute.subjectKey;
this.subjectAttributes = precomputedFlagsRequestParameters.precompute.subjectAttributes;
this.precomputedFlagsRequestParameters = precomputedFlagsRequestParameters;
}

Expand Down Expand Up @@ -134,6 +138,19 @@ export default class EppoPrecomputedClient {
}
}

public setSubjectAndPrecomputedFlagStore(
subjectKey: string,
subjectAttributes: Attributes,
precomputedFlagStore: IConfigurationStore<PrecomputedFlag>,
) {
// Save the new subject data and precomputed flag store together because they are related
// Stop any polling process if it exists from previous subject data to protect consistency
this.requestPoller?.stop();
this.setPrecomputedFlagStore(precomputedFlagStore);
this.subjectKey = subjectKey;
this.subjectAttributes = subjectAttributes;
}

private getPrecomputedAssignment<T>(
flagKey: string,
defaultValue: T,
Expand All @@ -160,8 +177,8 @@ export default class EppoPrecomputedClient {
const result: FlagEvaluationWithoutDetails = {
flagKey,
format: this.precomputedFlagStore.getFormat() ?? '',
subjectKey: this.precomputedFlagsRequestParameters?.precompute.subjectKey ?? '',
subjectAttributes: this.precomputedFlagsRequestParameters?.precompute.subjectAttributes ?? {},
subjectKey: this.subjectKey ?? '',
subjectAttributes: this.subjectAttributes ?? {},
variation: {
key: preComputedFlag.variationKey,
value: preComputedFlag.variationValue,
Expand Down

0 comments on commit c040140

Please sign in to comment.