Skip to content

Commit

Permalink
Return when calling
Browse files Browse the repository at this point in the history
  • Loading branch information
Emmanuel Zamora committed Sep 23, 2023
1 parent ea83dbf commit d1f701c
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 22 deletions.
58 changes: 46 additions & 12 deletions src/evaluator/__tests__/evaluate-features.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ const mockStorage = {
},
getNamesByFlagSets(flagSets) {
let toReturn = new _Set([]);
// Forced thenable delayed response for testing purposes
if (flagSets[0] === 'delay') {
return new Promise((resolve) => {
setTimeout(() => {
resolve(toReturn);
}, 86);
});
}
flagSets.forEach(flagset => {
const featureFlagNames = flagSetsMock[flagset];
if (featureFlagNames) {
Expand Down Expand Up @@ -124,6 +132,7 @@ test('EVALUATOR - Multiple evaluations at once / should return right labels, tre
});

test('EVALUATOR - Multiple evaluations at once by flagsets / should return right labels, treatments and configs if storage returns without errors.', async function () {

const expectedOutput = {
config: {
treatment: 'on', label: 'in segment all',
Expand All @@ -134,29 +143,54 @@ test('EVALUATOR - Multiple evaluations at once by flagsets / should return right
},
};

const multipleEvaluationAtOnce = await evaluateFeaturesByFlagSets(
loggerMock,
'fake-key',
['reg_and_config', 'arch_and_killed'],
null,
mockStorage,
);
const getResultsByFlagsets = (flagSets: string[]) => {
return evaluateFeaturesByFlagSets(
loggerMock,
'fake-key',
flagSets,
null,
mockStorage,
);
};

let multipleResultsAtOnceByFlagSets = await getResultsByFlagsets(['delay']);
expect(multipleResultsAtOnceByFlagSets.elapsedMilliseconds).toBeGreaterThanOrEqual(86); // defined 86 ms delay for testing purposes in mocked storage


multipleResultsAtOnceByFlagSets = await getResultsByFlagsets(['reg_and_config', 'arch_and_killed']);
let multipleEvaluationAtOnceByFlagSets = multipleResultsAtOnceByFlagSets.evaluations;

// assert evaluationWithConfig
expect(multipleEvaluationAtOnce['config']).toEqual(expectedOutput['config']); // If the split is retrieved successfully we should get the right evaluation result, label and config.
expect(multipleEvaluationAtOnceByFlagSets['config']).toEqual(expectedOutput['config']); // If the split is retrieved successfully we should get the right evaluation result, label and config.
// @todo assert flagset not found - for input validations

// assert regular
expect(multipleEvaluationAtOnce['regular']).toEqual({ ...expectedOutput['config'], config: null }); // If the split is retrieved successfully we should get the right evaluation result, label and config. If Split has no config it should have config equal null.
expect(multipleEvaluationAtOnceByFlagSets['regular']).toEqual({ ...expectedOutput['config'], config: null }); // If the split is retrieved successfully we should get the right evaluation result, label and config. If Split has no config it should have config equal null.
// assert killed
expect(multipleEvaluationAtOnce['killed']).toEqual({ ...expectedOutput['config'], treatment: 'off', config: null, label: LabelsConstants.SPLIT_KILLED });
expect(multipleEvaluationAtOnceByFlagSets['killed']).toEqual({ ...expectedOutput['config'], treatment: 'off', config: null, label: LabelsConstants.SPLIT_KILLED });
// 'If the split is retrieved but is killed, we should get the right evaluation result, label and config.

// assert archived
expect(multipleEvaluationAtOnce['archived']).toEqual({ ...expectedOutput['config'], treatment: 'control', label: LabelsConstants.SPLIT_ARCHIVED, config: null });
expect(multipleEvaluationAtOnceByFlagSets['archived']).toEqual({ ...expectedOutput['config'], treatment: 'control', label: LabelsConstants.SPLIT_ARCHIVED, config: null });
// If the split is retrieved but is archived, we should get the right evaluation result, label and config.

// assert not_existent_split not in evaluation if it is not related to defined flagsets
expect(multipleEvaluationAtOnce['not_existent_split']).toEqual(undefined);
expect(multipleEvaluationAtOnceByFlagSets['not_existent_split']).toEqual(undefined);

multipleEvaluationAtOnceByFlagSets = await getResultsByFlagsets([]);
expect(multipleEvaluationAtOnceByFlagSets.evaluations).toEqual({});

multipleEvaluationAtOnceByFlagSets = await getResultsByFlagsets(['reg_and_config']).evaluations;
expect(multipleEvaluationAtOnceByFlagSets['config']).toEqual(expectedOutput['config']); // If the split is retrieved successfully we should get the right evaluation result, label and config.
// @todo assert flagset not found - for input validations

// assert regular
expect(multipleEvaluationAtOnceByFlagSets['regular']).toEqual({ ...expectedOutput['config'], config: null }); // If the split is retrieved successfully we should get the right evaluation result, label and config. If Split has no config it should have config equal null.
// assert killed
expect(multipleEvaluationAtOnceByFlagSets['killed']).toEqual(undefined);
// 'If the split is retrieved but is killed, we should get the right evaluation result, label and config.

// assert archived
expect(multipleEvaluationAtOnceByFlagSets['archived']).toEqual(undefined);

});
34 changes: 29 additions & 5 deletions src/evaluator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import * as LabelsConstants from '../utils/labels';
import { CONTROL } from '../utils/constants';
import { ISplit, MaybeThenable } from '../dtos/types';
import { IStorageAsync, IStorageSync } from '../storages/types';
import { IEvaluationResult } from './types';
import { IByFlagSetsResult, IEvaluationResult } from './types';
import { SplitIO } from '../types';
import { ILogger } from '../logger/types';
import { setToArray } from '../utils/lang/sets';
import { ISet, setToArray } from '../utils/lang/sets';
import { timer } from '../utils/timeTracker/timer';

const treatmentException = {
treatment: CONTROL,
Expand Down Expand Up @@ -94,18 +95,41 @@ export function evaluateFeaturesByFlagSets(
flagsets: string[],
attributes: SplitIO.Attributes | undefined,
storage: IStorageSync | IStorageAsync,
): MaybeThenable<Record<string, IEvaluationResult>> {
let storedFlagNames;
): MaybeThenable<IByFlagSetsResult> {
const stopTimer = timer(Date.now);
let elapsedMilliseconds: number;
let storedFlagNames: MaybeThenable<ISet<string>>;

// get ff by flagsets
try {
storedFlagNames = storage.splits.getNamesByFlagSets(flagsets);
} catch (e) {
// Exception on sync `getSplits` storage. Not possible ATM with InMemory and InLocal storages.
// @todo - review exception
return treatmentsException(flagsets);
elapsedMilliseconds = stopTimer();
return {evaluations:{}, elapsedMilliseconds};
}

const evaluatedFeatures = getByFlagSetsEvaluations(log, key, storedFlagNames, attributes, storage);

if (thenable(evaluatedFeatures)) {
return evaluatedFeatures.then((evaluations) => {
elapsedMilliseconds = stopTimer();
return {evaluations, elapsedMilliseconds};
});
}
elapsedMilliseconds = stopTimer();
return {evaluations: evaluatedFeatures, elapsedMilliseconds};
}


function getByFlagSetsEvaluations(
log: ILogger,
key: SplitIO.SplitKey,
storedFlagNames: MaybeThenable<ISet<string>>,
attributes: SplitIO.Attributes | undefined,
storage: IStorageSync | IStorageAsync,
): MaybeThenable<Record<string, IEvaluationResult>> {
return thenable(storedFlagNames) ?
storedFlagNames.then((splitNames) => evaluateFeatures(log, key, setToArray(splitNames), attributes, storage)) :
evaluateFeatures(log, key, setToArray(storedFlagNames), attributes, storage);
Expand Down
5 changes: 5 additions & 0 deletions src/evaluator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export interface IEvaluation {

export type IEvaluationResult = IEvaluation & { treatment: string }

export interface IByFlagSetsResult {
evaluations: Record<string, IEvaluationResult>,
elapsedMilliseconds: number
}

export type ISplitEvaluator = (log: ILogger, key: SplitIO.SplitKey, splitName: string, attributes: SplitIO.Attributes | undefined, storage: IStorageSync | IStorageAsync) => MaybeThenable<IEvaluation>

export type IEvaluator = (key: SplitIO.SplitKey, seed: number, trafficAllocation?: number, trafficAllocationSeed?: number, attributes?: SplitIO.Attributes, splitEvaluator?: ISplitEvaluator) => MaybeThenable<IEvaluation | undefined>
Expand Down
13 changes: 8 additions & 5 deletions src/sdkClient/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { validateSplitExistance } from '../utils/inputValidation/splitExistance'
import { validateTrafficTypeExistance } from '../utils/inputValidation/trafficTypeExistance';
import { SDK_NOT_READY } from '../utils/labels';
import { CONTROL, TREATMENT, TREATMENTS, TREATMENT_WITH_CONFIG, TREATMENTS_WITH_CONFIG, TRACK } from '../utils/constants';
import { IEvaluationResult } from '../evaluator/types';
import { IByFlagSetsResult, IEvaluationResult } from '../evaluator/types';
import { SplitIO, ImpressionDTO } from '../types';
import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants';
import { ISdkFactoryContext } from '../sdkFactory/types';
Expand Down Expand Up @@ -84,21 +84,24 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
function getTreatmentsByFlagSets(key: SplitIO.SplitKey, flagSetNames: string[], attributes: SplitIO.Attributes | undefined, withConfig = false) {
const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENTS_WITH_CONFIG : TREATMENTS);

const wrapUp = (evaluationResults: Record<string, IEvaluationResult>) => {
const wrapUp = (evaluationResults: IByFlagSetsResult) => {
const queue: ImpressionDTO[] = [];
const treatments: Record<string, SplitIO.Treatment | SplitIO.TreatmentWithConfig> = {};
Object.keys(evaluationResults).forEach(featureFlagName => {
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, attributes, withConfig, `getTreatmentsByFlagSets${withConfig ? 'WithConfig' : ''}`, queue);
const evaluations = evaluationResults.evaluations;
Object.keys(evaluations).forEach(featureFlagName => {
treatments[featureFlagName] = processEvaluation(evaluations[featureFlagName], featureFlagName, key, attributes, withConfig, `getTreatmentsByFlagSets${withConfig ? 'WithConfig' : ''}`, queue);
});
impressionsTracker.track(queue, attributes);

stopTelemetryTracker(queue[0] && queue[0].label);
return treatments;
};

const emptyEvaluationByFlagSet = {evaluations: {}, elapsedMilliseconds: 0};

const evaluations = readinessManager.isReady() || readinessManager.isReadyFromCache() ?
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage) :
isStorageSync(settings) ? {} : Promise.resolve({}); // Promisify if async
isStorageSync(settings) ? emptyEvaluationByFlagSet : Promise.resolve(emptyEvaluationByFlagSet); // Promisify if async

return thenable(evaluations) ? evaluations.then((res) => wrapUp(res)) : wrapUp(evaluations);
}
Expand Down

0 comments on commit d1f701c

Please sign in to comment.