Skip to content

Commit

Permalink
feat(flagd-core): add bulk evaluation method (#1010)
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
  • Loading branch information
beeme1mr and toddbaert authored Aug 2, 2024
1 parent a6768a0 commit 52aebed
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 7 deletions.
111 changes: 111 additions & 0 deletions libs/shared/flagd-core/src/lib/__snapshots__/flagd-core.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`flagd-core resolving falsy variant values should resolve all flags 1`] = `
[
{
"flagKey": "myBoolFlag",
"flagMetadata": {},
"reason": "STATIC",
"value": false,
"variant": "off",
},
{
"flagKey": "myStringFlag",
"flagMetadata": {},
"reason": "STATIC",
"value": "",
"variant": "key1",
},
{
"flagKey": "myFloatFlag",
"flagMetadata": {},
"reason": "STATIC",
"value": 0,
"variant": "zero",
},
{
"flagKey": "myIntFlag",
"flagMetadata": {},
"reason": "STATIC",
"value": 0,
"variant": "zero",
},
{
"flagKey": "myObjectFlag",
"flagMetadata": {},
"reason": "STATIC",
"value": {},
"variant": "object1",
},
{
"flagKey": "fibAlgo",
"flagMetadata": {},
"reason": "DEFAULT",
"value": "recursive",
"variant": "recursive",
},
{
"flagKey": "targetedFlag",
"flagMetadata": {},
"reason": "DEFAULT",
"value": "AAA",
"variant": "first",
},
]
`;

exports[`flagd-core resolving truthy variant values should resolve all flags 1`] = `
[
{
"flagKey": "myBoolFlag",
"flagMetadata": {},
"reason": "STATIC",
"value": true,
"variant": "on",
},
{
"flagKey": "myStringFlag",
"flagMetadata": {},
"reason": "STATIC",
"value": "val1",
"variant": "key1",
},
{
"flagKey": "myFloatFlag",
"flagMetadata": {},
"reason": "STATIC",
"value": 1.23,
"variant": "one",
},
{
"flagKey": "myIntFlag",
"flagMetadata": {},
"reason": "STATIC",
"value": 1,
"variant": "one",
},
{
"flagKey": "myObjectFlag",
"flagMetadata": {},
"reason": "STATIC",
"value": {
"key": "val",
},
"variant": "object1",
},
{
"flagKey": "fibAlgo",
"flagMetadata": {},
"reason": "DEFAULT",
"value": "recursive",
"variant": "recursive",
},
{
"flagKey": "targetedFlag",
"flagMetadata": {},
"reason": "DEFAULT",
"value": "AAA",
"variant": "first",
},
]
`;
16 changes: 16 additions & 0 deletions libs/shared/flagd-core/src/lib/flagd-core.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ describe('flagd-core resolving', () => {
expect(resolved.reason).toBe(StandardResolutionReasons.STATIC);
expect(resolved.variant).toBe('object1');
});

it('should resolve all flags', () => {
const resolved = core.resolveAll({}, console);
expect(resolved).toMatchSnapshot();
});
});

describe('falsy variant values', () => {
Expand Down Expand Up @@ -76,6 +81,11 @@ describe('flagd-core resolving', () => {
expect(resolved.reason).toBe(StandardResolutionReasons.STATIC);
expect(resolved.variant).toBe('object1');
});

it('should resolve all flags', () => {
const resolved = core.resolveAll({}, console);
expect(resolved).toMatchSnapshot();
});
});
});

Expand Down Expand Up @@ -157,6 +167,12 @@ describe('flagd-core validations', () => {
it('should validate variant', () => {
expect(() => core.resolveStringEvaluation('myStringFlag', 'hello', {}, console)).toThrow(TypeMismatchError);
});

it('should only resolve enabled flags', () => {
const resolved = core.resolveAll({}, console);
expect(resolved).toHaveLength(1);
expect(resolved[0]).toHaveProperty('flagKey', 'myStringFlag');
});
});

describe('flagd-core common flag definitions', () => {
Expand Down
54 changes: 47 additions & 7 deletions libs/shared/flagd-core/src/lib/flagd-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
Logger,
SafeLogger,
DefaultLogger,
EvaluationDetails,
} from '@openfeature/core';
import { Targeting } from './targeting/targeting';
import { FeatureFlag } from './feature-flag';
Expand Down Expand Up @@ -121,6 +122,32 @@ export class FlagdCore implements Storage {
return this.resolve('object', flagKey, defaultValue, evalCtx, logger);
}

/**
* Resolve the flag evaluation for all enabled flags.
* @param evalCtx - The evaluation context to be used for targeting.
* @param logger - The logger to be used to troubleshoot targeting errors. Overrides the default logger.
* @returns - The list of evaluation details for all enabled flags.
*/
resolveAll(evalCtx?: EvaluationContext, logger?: Logger): EvaluationDetails<JsonValue>[] {
const values: EvaluationDetails<JsonValue>[] = [];
for (const [key, flag] of this.getFlags()) {
try {
if (flag.state === 'DISABLED') {
continue;
}
const result = this.evaluate(key, evalCtx, logger);
values.push({
...result,
flagKey: key,
flagMetadata: Object.freeze(result.flagMetadata ?? {}),
});
} catch (e) {
this._logger.error(`Error resolving flag ${key}: ${(e as Error).message}`);
}
}
return values;
}

/**
* Resolves the value of a flag based on the specified type type.
* @template T - The type of the flag value.
Expand All @@ -141,6 +168,25 @@ export class FlagdCore implements Storage {
evalCtx: EvaluationContext = {},
logger?: Logger,
): ResolutionDetails<T> {
const { value, reason, variant } = this.evaluate(flagKey, evalCtx, logger);

if (typeof value !== type) {
throw new TypeMismatchError(
`Evaluated type of the flag ${flagKey} does not match. Expected ${type}, got ${typeof value}`,
);
}

return {
value: value as T,
reason,
variant,
};
}

/**
* Evaluates the flag and returns the resolved value regardless of the type.
*/
private evaluate(flagKey: string, evalCtx: EvaluationContext = {}, logger?: Logger): ResolutionDetails<JsonValue> {
logger ??= this._logger;
const flag = this._storage.getFlag(flagKey);
// flag exist check
Expand Down Expand Up @@ -188,14 +234,8 @@ export class FlagdCore implements Storage {
throw new GeneralError(`Variant ${variant} not found in flag with key ${flagKey}`);
}

if (typeof resolvedVariant !== type) {
throw new TypeMismatchError(
`Evaluated type of the flag ${flagKey} does not match. Expected ${type}, got ${typeof resolvedVariant}`,
);
}

return {
value: resolvedVariant as T,
value: resolvedVariant,
reason,
variant,
};
Expand Down

0 comments on commit 52aebed

Please sign in to comment.