Skip to content

Commit

Permalink
CR changes
Browse files Browse the repository at this point in the history
  • Loading branch information
greghuels committed Aug 2, 2024
1 parent 89a5e6b commit 4026acc
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 126 deletions.
28 changes: 18 additions & 10 deletions src/client/eppo-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
FlagEvaluationDetailsBuilder,
IFlagEvaluationDetails,
} from '../flag-evaluation-details-builder';
import { FlagEvaluationError } from '../flag-evaluation-error';
import FetchHttpClient from '../http-client';
import {
BanditParameters,
Expand Down Expand Up @@ -693,16 +694,23 @@ export default class EppoClient {
};
} catch (error) {
const eppoValue = this.rethrowIfNotGraceful(error, defaultValue);
const flagEvaluationDetails = new FlagEvaluationDetailsBuilder(
'',
[],
'',
'',
).buildForNoneResult('ASSIGNMENT_ERROR', `Assignment Error: ${error.message}`);
return {
eppoValue,
flagEvaluationDetails,
};
if (error instanceof FlagEvaluationError && error.flagEvaluationDetails) {
return {
eppoValue,
flagEvaluationDetails: error.flagEvaluationDetails,
};
} else {
const flagEvaluationDetails = new FlagEvaluationDetailsBuilder(
'',
[],
'',
'',
).buildForNoneResult('ASSIGNMENT_ERROR', `Assignment Error: ${error.message}`);
return {
eppoValue,
flagEvaluationDetails,
};
}
}
}

Expand Down
164 changes: 83 additions & 81 deletions src/evaluator.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { checkValueTypeMatch } from './client/eppo-client';
import {
AllocationEvaluation,
AllocationEvaluationCode,
IFlagEvaluationDetails,
FlagEvaluationDetailsBuilder,
FlagEvaluationCode,
} from './flag-evaluation-details-builder';
import { FlagEvaluationError } from './flag-evaluation-error';
import {
Flag,
Shard,
Expand Down Expand Up @@ -52,97 +52,99 @@ export class Evaluator {
configDetails.configFetchedAt,
configDetails.configPublishedAt,
);
try {
if (!flag.enabled) {
return noneResult(
flag.key,
subjectKey,
subjectAttributes,
flagEvaluationDetailsBuilder.buildForNoneResult(
'FLAG_UNRECOGNIZED_OR_DISABLED',
`Unrecognized or disabled flag: ${flag.key}`,
),
);
}

const now = new Date();
for (let i = 0; i < flag.allocations.length; i++) {
const allocation = flag.allocations[i];
const addUnmatchedAllocation = (code: AllocationEvaluationCode) => {
flagEvaluationDetailsBuilder.addUnmatchedAllocation({
key: allocation.key,
allocationEvaluationCode: code,
orderPosition: i + 1,
});
};

if (!flag.enabled) {
if (allocation.startAt && now < new Date(allocation.startAt)) {
addUnmatchedAllocation(AllocationEvaluationCode.BEFORE_START_TIME);
continue;
}
if (allocation.endAt && now > new Date(allocation.endAt)) {
addUnmatchedAllocation(AllocationEvaluationCode.AFTER_END_TIME);
continue;
}
const { matched, matchedRule } = matchesRules(
allocation?.rules ?? [],
{ id: subjectKey, ...subjectAttributes },
obfuscated,
);
if (matched) {
for (const split of allocation.splits) {
if (
split.shards.every((shard) => this.matchesShard(shard, subjectKey, flag.totalShards))
) {
const variation = flag.variations[split.variationKey];
const { flagEvaluationCode, flagEvaluationDescription } =
this.getMatchedEvaluationCodeAndDescription(
variation,
allocation,
split,
subjectKey,
expectedVariationType,
);
const flagEvaluationDetails = flagEvaluationDetailsBuilder
.setMatch(i, variation, allocation, matchedRule, expectedVariationType)
.build(flagEvaluationCode, flagEvaluationDescription);
return {
flagKey: flag.key,
subjectKey,
subjectAttributes,
allocationKey: allocation.key,
variation,
extraLogging: split.extraLogging ?? {},
doLog: allocation.doLog,
flagEvaluationDetails,
};
}
}
// matched, but does not fall within split range
addUnmatchedAllocation(AllocationEvaluationCode.TRAFFIC_EXPOSURE_MISS);
} else {
addUnmatchedAllocation(AllocationEvaluationCode.FAILING_RULE);
}
}
return noneResult(
flag.key,
subjectKey,
subjectAttributes,
flagEvaluationDetailsBuilder.buildForNoneResult(
'FLAG_UNRECOGNIZED_OR_DISABLED',
`Unrecognized or disabled flag: ${flag.key}`,
'DEFAULT_ALLOCATION_NULL',
'No allocations matched. Falling back to "Default Allocation", serving NULL',
),
);
}

const now = new Date();
const unmatchedAllocations: Array<AllocationEvaluation> = [];
for (let i = 0; i < flag.allocations.length; i++) {
const allocation = flag.allocations[i];
const addUnmatchedAllocation = (code: AllocationEvaluationCode) => {
unmatchedAllocations.push({
key: allocation.key,
allocationEvaluationCode: code,
orderPosition: i + 1,
});
};

if (allocation.startAt && now < new Date(allocation.startAt)) {
addUnmatchedAllocation(AllocationEvaluationCode.BEFORE_START_TIME);
continue;
}
if (allocation.endAt && now > new Date(allocation.endAt)) {
addUnmatchedAllocation(AllocationEvaluationCode.AFTER_END_TIME);
continue;
}
const { matched, matchedRule } = matchesRules(
allocation?.rules ?? [],
{ id: subjectKey, ...subjectAttributes },
obfuscated,
} catch (err) {
const flagEvaluationDetails = flagEvaluationDetailsBuilder.gracefulBuild(
'ASSIGNMENT_ERROR',
`Assignment Error: ${err.message}`,
);
if (matched) {
for (const split of allocation.splits) {
if (
split.shards.every((shard) => this.matchesShard(shard, subjectKey, flag.totalShards))
) {
const variation = flag.variations[split.variationKey];
const { flagEvaluationCode, flagEvaluationDescription } =
this.getMatchedEvaluationCodeAndDescription(
variation,
allocation,
split,
subjectKey,
expectedVariationType,
);
const flagEvaluationDetails = flagEvaluationDetailsBuilder
.setMatch(
i,
variation,
allocation,
matchedRule,
unmatchedAllocations,
expectedVariationType,
)
.build(flagEvaluationCode, flagEvaluationDescription);
return {
flagKey: flag.key,
subjectKey,
subjectAttributes,
allocationKey: allocation.key,
variation,
extraLogging: split.extraLogging ?? {},
doLog: allocation.doLog,
flagEvaluationDetails,
};
}
}
// matched, but does not fall within split range
addUnmatchedAllocation(AllocationEvaluationCode.TRAFFIC_EXPOSURE_MISS);
} else {
addUnmatchedAllocation(AllocationEvaluationCode.FAILING_RULE);
if (flagEvaluationDetails) {
const flagEvaluationError = new FlagEvaluationError(err.message);
flagEvaluationError.flagEvaluationDetails = flagEvaluationDetails;
throw flagEvaluationError;
}
throw err;
}
return noneResult(
flag.key,
subjectKey,
subjectAttributes,
flagEvaluationDetailsBuilder
.setNoMatchFound(unmatchedAllocations)
.build(
'DEFAULT_ALLOCATION_NULL',
'No allocations matched. Falling back to "Default Allocation", serving NULL',
),
);
}

matchesShard(shard: Shard, subjectKey: string, totalShards: number): boolean {
Expand Down
68 changes: 33 additions & 35 deletions src/flag-evaluation-details-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ export class FlagEvaluationDetailsBuilder {
private variationValue: IFlagEvaluationDetails['variationValue'] = null;
private matchedRule: IFlagEvaluationDetails['matchedRule'] = null;
private matchedAllocation: IFlagEvaluationDetails['matchedAllocation'] = null;
private unmatchedAllocations: IFlagEvaluationDetails['unmatchedAllocations'] = [];
private unevaluatedAllocations: IFlagEvaluationDetails['unevaluatedAllocations'] = [];
private readonly unmatchedAllocations: IFlagEvaluationDetails['unmatchedAllocations'] = [];
private readonly unevaluatedAllocations: IFlagEvaluationDetails['unevaluatedAllocations'] = [];

constructor(
private readonly environmentName: string,
Expand All @@ -61,31 +61,15 @@ export class FlagEvaluationDetailsBuilder {
this.setNone();
}

setNone = (): FlagEvaluationDetailsBuilder => {
this.variationKey = null;
this.variationValue = null;
this.matchedRule = null;
this.matchedAllocation = null;
this.unmatchedAllocations = [];
this.unevaluatedAllocations = this.allocations.map(
(allocation, i): AllocationEvaluation => ({
key: allocation.key,
allocationEvaluationCode: AllocationEvaluationCode.UNEVALUATED,
orderPosition: i + 1,
}),
);
return this;
addUnmatchedAllocation = (allocationEvaluation: AllocationEvaluation) => {
this.unmatchedAllocations.push(allocationEvaluation);
};

setNoMatchFound = (
unmatchedAllocations: Array<AllocationEvaluation> = [],
): FlagEvaluationDetailsBuilder => {
setNone = (): FlagEvaluationDetailsBuilder => {
this.variationKey = null;
this.variationValue = null;
this.matchedAllocation = null;
this.matchedRule = null;
this.unmatchedAllocations = unmatchedAllocations;
this.unevaluatedAllocations = [];
this.matchedAllocation = null;
return this;
};

Expand All @@ -94,7 +78,6 @@ export class FlagEvaluationDetailsBuilder {
variation: Variation,
allocation: Allocation,
matchedRule: Rule | null,
unmatchedAllocations: Array<AllocationEvaluation>,
expectedVariationType: VariationType | undefined,
): FlagEvaluationDetailsBuilder => {
this.variationKey = variation.key;
Expand All @@ -110,17 +93,6 @@ export class FlagEvaluationDetailsBuilder {
allocationEvaluationCode: AllocationEvaluationCode.MATCH,
orderPosition: indexPosition + 1, // orderPosition is 1-indexed to match UI
};
this.unmatchedAllocations = unmatchedAllocations;
const unevaluatedStartIndex = indexPosition + 1;
const unevaluatedStartOrderPosition = unevaluatedStartIndex + 1; // orderPosition is 1-indexed to match UI
this.unevaluatedAllocations = this.allocations.slice(unevaluatedStartIndex).map(
(allocation, i) =>
({
key: allocation.key,
allocationEvaluationCode: AllocationEvaluationCode.UNEVALUATED,
orderPosition: unevaluatedStartOrderPosition + i,
} as AllocationEvaluation),
);
return this;
};

Expand All @@ -145,6 +117,32 @@ export class FlagEvaluationDetailsBuilder {
matchedRule: this.matchedRule,
matchedAllocation: this.matchedAllocation,
unmatchedAllocations: this.unmatchedAllocations,
unevaluatedAllocations: this.unevaluatedAllocations,
unevaluatedAllocations: this.calculateUnevaluatedAllocations(),
});

gracefulBuild = (
flagEvaluationCode: FlagEvaluationCode,
flagEvaluationDescription: string,
): IFlagEvaluationDetails | null => {
try {
return this.build(flagEvaluationCode, flagEvaluationDescription);
} catch (err) {
return null;
}
};

private calculateUnevaluatedAllocations = (): Array<AllocationEvaluation> => {
const unevaluatedStartIndex = this.matchedAllocation
? this.unmatchedAllocations.length + 1
: this.unmatchedAllocations.length;
const unevaluatedStartOrderPosition = unevaluatedStartIndex + 1; // orderPosition is 1-indexed to match UI
return this.allocations.slice(unevaluatedStartIndex).map(
(allocation, i) =>
({
key: allocation.key,
allocationEvaluationCode: AllocationEvaluationCode.UNEVALUATED,
orderPosition: unevaluatedStartOrderPosition + i,
} as AllocationEvaluation),
);
};
}
5 changes: 5 additions & 0 deletions src/flag-evaluation-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { IFlagEvaluationDetails } from './flag-evaluation-details-builder';

export class FlagEvaluationError extends Error {
flagEvaluationDetails: IFlagEvaluationDetails | undefined;
}

0 comments on commit 4026acc

Please sign in to comment.