Skip to content

Commit

Permalink
feat: base apply functionality on backend response (#216)
Browse files Browse the repository at this point in the history
* chore: update proto and generated code

* feat: base apply functionality on backend decision

* fixup! feat: base apply functionality on backend decision
  • Loading branch information
nicklasl authored Feb 7, 2025
1 parent 2a56dd6 commit a190086
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 7 deletions.
3 changes: 3 additions & 0 deletions packages/sdk/proto/confidence/flags/resolver/v1/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,7 @@ message ResolvedFlag {

// The reason to why the flag could be resolved or not.
ResolveReason reason = 5;

// Determines whether the flag should be applied in the clients
bool should_apply = 6;
}
13 changes: 13 additions & 0 deletions packages/sdk/src/Confidence.int.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ const mockResolveResponse = {
value: { str: 'hello' },
flagSchema: { schema: { str: { stringSchema: {} } } },
reason: 'RESOLVE_REASON_MATCH',
shouldApply: true,
},
{
flag: 'flags/flag2',
variant: 'treatment',
value: { str: 'hello again' },
flagSchema: { schema: { str: { stringSchema: {} } } },
reason: 'RESOLVE_REASON_MATCH',
shouldApply: false,
},
],
resolveToken: 'xyz',
Expand Down Expand Up @@ -86,6 +95,10 @@ describe('Confidence integration tests', () => {
}),
);
});
it('should resolve a value but not send apply if shouldApply is false', async () => {
expect(await confidence.getFlag('flag2.str', 'goodbye')).toBe('hello again');
expect(applyHandlerMock).not.toHaveBeenCalled();
});
it('should abort previous requests when context changes', async () => {
confidence.setContext({ pants: 'yellow' });
const value = confidence.getFlag('flag1.str', 'goodbye');
Expand Down
13 changes: 8 additions & 5 deletions packages/sdk/src/FlagResolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type ResolvedFlag = {
| 'FLAG_ARCHIVED'
| 'TARGETING_KEY_ERROR'
| 'ERROR';
shouldApply: boolean;
};

export type Applier = (flagName: string) => void;
Expand All @@ -50,7 +51,7 @@ export class ReadyFlagResolution implements FlagResolution {
resolveResponse: ResolveFlagsResponse,
private readonly applier?: Applier,
) {
for (const { flag, variant, value, reason, flagSchema } of resolveResponse.resolvedFlags) {
for (const { flag, variant, value, reason, flagSchema, shouldApply } of resolveResponse.resolvedFlags) {
const name = flag.slice(FLAG_PREFIX.length);

const schema = flagSchema ? Schema.parse({ structSchema: flagSchema }) : Schema.ANY;
Expand All @@ -59,6 +60,7 @@ export class ReadyFlagResolution implements FlagResolution {
value: value! as Value.Struct,
variant,
reason: toEvaluationReason(reason),
shouldApply,
});
}
this.resolveToken = base64FromBytes(resolveResponse.resolveToken);
Expand All @@ -78,10 +80,12 @@ export class ReadyFlagResolution implements FlagResolution {
}
const reason = flag.reason;
if (reason === 'ERROR') throw new Error('Unknown resolve error');

if (flag.shouldApply && this.applier) {
this.applier?.(name);
}

if (reason !== 'MATCH') {
if (reason === 'NO_SEGMENT_MATCH' && this.applier) {
this.applier?.(name);
}
return {
reason,
value: defaultValue,
Expand All @@ -95,7 +99,6 @@ export class ReadyFlagResolution implements FlagResolution {
schema.assertAssignsTo(defaultValue);
});

this.applier?.(name);
return {
reason,
value,
Expand Down
23 changes: 22 additions & 1 deletion packages/sdk/src/FlagResolverClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ describe('Client environment Evaluation', () => {
});
});

it('should apply when a flag has no segment match', async () => {
it('should apply based on shouldApply', async () => {
const flagResolution = await instanceUnderTest.resolve({}, []);
flagResolution.evaluate('no-seg-flag.enabled', false);
const [applyRequest] = await nextMockArgs(applyHandlerMock);
Expand All @@ -122,6 +122,9 @@ describe('Client environment Evaluation', () => {
},
],
});
expect(applyHandlerMock).toHaveBeenCalledTimes(1);
flagResolution.evaluate('no-flag-apply-flag.str', 'default');
expect(applyHandlerMock).toHaveBeenCalledTimes(1);
});
});
});
Expand Down Expand Up @@ -562,12 +565,30 @@ function createFlagResolutionResponse(): unknown {
},
},
reason: 'RESOLVE_REASON_MATCH',
shouldApply: true,
},
{
flag: 'flags/no-seg-flag',
variant: '',
value: {},
reason: 'RESOLVE_REASON_NO_SEGMENT_MATCH',
shouldApply: true,
},
{
flag: 'flags/no-flag-apply-flag',
variant: '',
value: {
str: 'string',
},
reason: 'RESOLVE_REASON_MATCH',
flagSchema: {
schema: {
str: {
stringSchema: {},
},
},
},
shouldApply: true,
},
],
resolveToken: 'SGVsbG9Xb3JsZA==',
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a190086

Please sign in to comment.