Skip to content

Commit

Permalink
feat: Add support for client-side prerequisite events. (#606)
Browse files Browse the repository at this point in the history
This PR contains the server-side implementation for allFlagsState
evaluation for bootstrap, server-side LDEvaluationDetail, client-side
LDEvaluationDetail, and client-side events for prerequisites.

This version only includes direct pre-requisites, and the client-side
evaluation uses variation methods versus directly sending events.

BEGIN_COMMIT_OVERRIDE
feat: Add support for client-side prerequisite events.
feat: Add support for prerequisite details to evaluation detail.
feat: Add prerequisite information to server-side allFlagsState.
END_COMMIT_OVERRIDE

SDK-686
SDK-682
  • Loading branch information
kinyoklion authored Oct 15, 2024
1 parent a986478 commit 8c84e01
Show file tree
Hide file tree
Showing 18 changed files with 352 additions and 29 deletions.
1 change: 1 addition & 0 deletions contract-tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ app.get('/', (req, res) => {
'anonymous-redaction',
'evaluation-hooks',
'wrapper',
'client-prereq-events',
],
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default class TestHarnessWebSocket {
'inline-context',
'anonymous-redaction',
'strongly-typed',
'client-prereq-events',
];

break;
Expand Down
12 changes: 3 additions & 9 deletions packages/sdk/browser/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ const terserOpts = {
regex: /^_/,
// Do not mangle '_meta', because this is part of our JSON
// data model.
reserved: ['_meta']
reserved: ['_meta'],
},
}
},
};

export default [
Expand All @@ -53,12 +53,6 @@ export default [
},
{
...getSharedConfig('cjs', 'dist/index.cjs.js'),
plugins: [
typescript(),
common(),
resolve(),
terser(terserOpts),
json(),
],
plugins: [typescript(), common(), resolve(), terser(terserOpts), json()],
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -187,5 +187,7 @@ it('calls flag-details-changed inspectors when all flag values change', async ()
'moonshot-demo': { reason: null, value: true, variationIndex: 0 },
test1: { reason: null, value: 's1', variationIndex: 0 },
'this-is-a-test': { reason: null, value: true, variationIndex: 0 },
'has-prereq-depth-1': { reason: { kind: 'FALLTHROUGH' }, value: true, variationIndex: 0 },
'is-prereq': { reason: { kind: 'FALLTHROUGH' }, value: true, variationIndex: 0 },
});
});
41 changes: 41 additions & 0 deletions packages/shared/sdk-client/__tests__/LDClientImpl.events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,45 @@ describe('sdk-client object', () => {
expect.stringMatching(/was called with a non-numeric/),
);
});

it('sends events for prerequisite flags', async () => {
await ldc.identify({ kind: 'user', key: 'bob' });
ldc.variation('has-prereq-depth-1', false);
ldc.flush();

// Prerequisite evaluation event should be emitted before the evaluation event for the flag
// being evaluated.
expect(mockedSendEvent).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
context: expect.anything(),
creationDate: expect.any(Number),
default: undefined,
key: 'is-prereq',
kind: 'feature',
samplingRatio: 1,
trackEvents: true,
value: true,
variation: 0,
version: 1,
withReasons: false,
}),
);
expect(mockedSendEvent).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
context: expect.anything(),
creationDate: expect.any(Number),
default: false,
key: 'has-prereq-depth-1',
kind: 'feature',
samplingRatio: 1,
trackEvents: true,
value: true,
variation: 0,
version: 4,
withReasons: false,
}),
);
});
});
10 changes: 10 additions & 0 deletions packages/shared/sdk-client/__tests__/LDClientImpl.storage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ describe('sdk-client storage', () => {
'easter-i-tunes-special': false,
'easter-specials': 'no specials',
fdsafdsafdsafdsa: true,
'has-prereq-depth-1': true,
'is-prereq': true,
'log-level': 'warn',
'moonshot-demo': true,
test1: 's1',
Expand Down Expand Up @@ -156,6 +158,8 @@ describe('sdk-client storage', () => {
'easter-i-tunes-special': false,
'easter-specials': 'no specials',
fdsafdsafdsafdsa: true,
'has-prereq-depth-1': true,
'is-prereq': true,
'log-level': 'warn',
'moonshot-demo': true,
test1: 's1',
Expand Down Expand Up @@ -218,6 +222,8 @@ describe('sdk-client storage', () => {
'easter-i-tunes-special': false,
'easter-specials': 'no specials',
fdsafdsafdsafdsa: true,
'has-prereq-depth-1': true,
'is-prereq': true,
'log-level': 'warn',
'moonshot-demo': true,
test1: 's1',
Expand Down Expand Up @@ -388,6 +394,8 @@ describe('sdk-client storage', () => {
'easter-i-tunes-special': false,
'easter-specials': 'no specials',
fdsafdsafdsafdsa: true,
'has-prereq-depth-1': true,
'is-prereq': true,
'log-level': 'warn',
'moonshot-demo': true,
test1: 's1',
Expand Down Expand Up @@ -517,6 +525,8 @@ describe('sdk-client storage', () => {
'easter-i-tunes-special': false,
'easter-specials': 'no specials',
fdsafdsafdsafdsa: true,
'has-prereq-depth-1': true,
'is-prereq': true,
'log-level': 'warn',
'moonshot-demo': true,
test1: 's1',
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/sdk-client/__tests__/LDClientImpl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ describe('sdk-client object', () => {
'easter-i-tunes-special': false,
'easter-specials': 'no specials',
fdsafdsafdsafdsa: true,
'has-prereq-depth-1': true,
'is-prereq': true,
'log-level': 'warn',
'moonshot-demo': true,
test1: 's1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ describe('sdk-client identify timeout', () => {
'easter-i-tunes-special': false,
'easter-specials': 'no specials',
fdsafdsafdsafdsa: true,
'has-prereq-depth-1': true,
'is-prereq': true,
'log-level': 'warn',
'moonshot-demo': true,
test1: 's1',
Expand All @@ -112,6 +114,8 @@ describe('sdk-client identify timeout', () => {
'easter-i-tunes-special': false,
'easter-specials': 'no specials',
fdsafdsafdsafdsa: true,
'has-prereq-depth-1': true,
'is-prereq': true,
'log-level': 'warn',
'moonshot-demo': true,
test1: 's1',
Expand Down
19 changes: 19 additions & 0 deletions packages/shared/sdk-client/__tests__/evaluation/mockResponse.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,24 @@
"value": true,
"variation": 0,
"trackEvents": false
},
"is-prereq": {
"value": true,
"variation": 0,
"reason": {
"kind": "FALLTHROUGH"
},
"version": 1,
"trackEvents": true
},
"has-prereq-depth-1": {
"value": true,
"variation": 0,
"prerequisites": ["is-prereq"],
"reason": {
"kind": "FALLTHROUGH"
},
"version": 4,
"trackEvents": true
}
}
6 changes: 5 additions & 1 deletion packages/shared/sdk-client/src/LDClientImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ export default class LDClientImpl implements LDClient {
return createErrorEvaluationDetail(ErrorKinds.FlagNotFound, defaultValue);
}

const { reason, value, variation } = foundItem.flag;
const { reason, value, variation, prerequisites } = foundItem.flag;

if (typeChecker) {
const [matched, type] = typeChecker(value);
Expand Down Expand Up @@ -355,6 +355,10 @@ export default class LDClientImpl implements LDClient {
this.logger.debug('Result value is null. Providing default value.');
successDetail.value = defaultValue;
}

prerequisites?.forEach((prereqKey) => {
this.variation(prereqKey, undefined);
});
this._eventProcessor?.sendEvent(
eventFactory.evalEventClient(
flagKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ export function createSuccessEvaluationDetail(
variationIndex?: number,
reason?: LDEvaluationReason,
): LDEvaluationDetail {
return {
const res: LDEvaluationDetail = {
value,
variationIndex: variationIndex ?? null,
reason: reason ?? null,
};
return res;
}
1 change: 1 addition & 0 deletions packages/shared/sdk-client/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface Flag {
reason?: LDEvaluationReason;
debugEventsUntilDate?: number;
deleted?: boolean;
prerequisites?: string[];
}

export interface PatchFlag extends Flag {
Expand Down
54 changes: 54 additions & 0 deletions packages/shared/sdk-server/__tests__/LDClient.allFlags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,60 @@ describe('given an LDClient with test data', () => {
done();
});
});

it('includes prerequisites in flag meta', async () => {
await td.update(td.flag('is-prereq').valueForAll(true));
await td.usePreconfiguredFlag({
key: 'has-prereq-depth-1',
on: true,
prerequisites: [
{
key: 'is-prereq',
variation: 0,
},
],
fallthrough: {
variation: 0,
},
offVariation: 1,
variations: [true, false],
clientSideAvailability: {
usingMobileKey: true,
usingEnvironmentId: true,
},
clientSide: true,
version: 4,
});

const state = await client.allFlagsState(defaultUser, {
withReasons: true,
detailsOnlyForTrackedFlags: false,
});
expect(state.valid).toEqual(true);
expect(state.allValues()).toEqual({ 'is-prereq': true, 'has-prereq-depth-1': true });
expect(state.toJSON()).toEqual({
'is-prereq': true,
'has-prereq-depth-1': true,
$flagsState: {
'is-prereq': {
variation: 0,
reason: {
kind: 'FALLTHROUGH',
},
version: 1,
},
'has-prereq-depth-1': {
variation: 0,
prerequisites: ['is-prereq'],
reason: {
kind: 'FALLTHROUGH',
},
version: 4,
},
},
$valid: true,
});
});
});

describe('given an offline client', () => {
Expand Down
Loading

0 comments on commit 8c84e01

Please sign in to comment.