Skip to content

Commit c397db9

Browse files
allow onFeatureEvaluate callback
1 parent 75bb990 commit c397db9

File tree

8 files changed

+337
-83
lines changed

8 files changed

+337
-83
lines changed

sdk/feature-management/package-lock.json

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/feature-management/package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@
3131
},
3232
"homepage": "https://github.com/microsoft/FeatureManagement-JavaScript#readme",
3333
"devDependencies": {
34+
"@playwright/test": "^1.46.1",
3435
"@rollup/plugin-typescript": "^11.1.5",
35-
"@types/mocha": "^10.0.6",
36+
"@types/mocha": "^10.0.7",
3637
"@types/node": "^20.10.7",
3738
"@typescript-eslint/eslint-plugin": "^6.18.1",
3839
"@typescript-eslint/parser": "^6.18.1",
39-
"@playwright/test": "^1.46.1",
4040
"chai": "^4.4.0",
4141
"chai-as-promised": "^7.1.1",
4242
"eslint": "^8.56.0",
@@ -46,7 +46,5 @@
4646
"rollup-plugin-dts": "^6.1.0",
4747
"tslib": "^2.6.2",
4848
"typescript": "^5.3.3"
49-
},
50-
"dependencies": {
5149
}
5250
}

sdk/feature-management/src/featureManager.ts

Lines changed: 67 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { isTargetedGroup, isTargetedPercentile, isTargetedUser } from "./common/
1414
export class FeatureManager implements IFeatureManager {
1515
#provider: IFeatureFlagProvider;
1616
#featureFilters: Map<string, IFeatureFilter> = new Map();
17+
#onFeatureEvaluated?: (event: EvaluationResult) => void;
1718

1819
constructor(provider: IFeatureFlagProvider, options?: FeatureManagerOptions) {
1920
this.#provider = provider;
@@ -24,6 +25,8 @@ export class FeatureManager implements IFeatureManager {
2425
for (const filter of [...builtinFilters, ...(options?.customFilters ?? [])]) {
2526
this.#featureFilters.set(filter.name, filter);
2627
}
28+
29+
this.#onFeatureEvaluated = options?.onFeatureEvaluated;
2730
}
2831

2932
async listFeatureNames(): Promise<string[]> {
@@ -127,6 +130,9 @@ export class FeatureManager implements IFeatureManager {
127130
// Evaluate if the feature is enabled.
128131
result.enabled = await this.#isEnabled(featureFlag, context);
129132

133+
const targetingContext = context as ITargetingContext;
134+
result.userId = targetingContext?.userId;
135+
130136
// Determine Variant
131137
let variantDef: VariantDefinition | undefined;
132138
let reason: VariantAssignmentReason = VariantAssignmentReason.None;
@@ -146,7 +152,7 @@ export class FeatureManager implements IFeatureManager {
146152
} else {
147153
// enabled, assign based on allocation
148154
if (context !== undefined && featureFlag.allocation !== undefined) {
149-
const variantAndReason = await this.#assignVariant(featureFlag, context as ITargetingContext);
155+
const variantAndReason = await this.#assignVariant(featureFlag, targetingContext);
150156
variantDef = variantAndReason.variant;
151157
reason = variantAndReason.reason;
152158
}
@@ -164,9 +170,6 @@ export class FeatureManager implements IFeatureManager {
164170
}
165171
}
166172

167-
// TODO: send telemetry for variant assignment reason in the future.
168-
console.log(`Variant assignment for feature ${featureName}: ${variantDef?.name ?? "default"} (${reason})`);
169-
170173
if (variantDef?.configuration_reference !== undefined) {
171174
console.warn("Configuration reference is not supported yet.");
172175
}
@@ -183,12 +186,71 @@ export class FeatureManager implements IFeatureManager {
183186
}
184187
}
185188

189+
if (featureFlag.telemetry?.enabled && this.#onFeatureEvaluated !== undefined) {
190+
this.#onFeatureEvaluated(result);
191+
}
192+
186193
return result;
187194
}
188195
}
189196

190-
interface FeatureManagerOptions {
197+
export interface FeatureManagerOptions {
198+
/**
199+
* The custom filters to be used by the feature manager.
200+
*/
191201
customFilters?: IFeatureFilter[];
202+
203+
/**
204+
* The callback function that is called when a feature flag is evaluated.
205+
*/
206+
onFeatureEvaluated?: (event: EvaluationResult) => void;
207+
}
208+
209+
export class EvaluationResult {
210+
constructor(
211+
// feature flag definition
212+
public readonly feature: FeatureFlag | undefined,
213+
214+
// enabled state
215+
public enabled: boolean = false,
216+
217+
// variant assignment
218+
public userId: string | undefined = undefined,
219+
public variant: Variant | undefined = undefined,
220+
public variantAssignmentReason: VariantAssignmentReason = VariantAssignmentReason.None
221+
) { }
222+
}
223+
224+
export enum VariantAssignmentReason {
225+
/**
226+
* Variant allocation did not happen. No variant is assigned.
227+
*/
228+
None = "None",
229+
230+
/**
231+
* The default variant is assigned when a feature flag is disabled.
232+
*/
233+
DefaultWhenDisabled = "DefaultWhenDisabled",
234+
235+
/**
236+
* The default variant is assigned because of no applicable user/group/percentile allocation when a feature flag is enabled.
237+
*/
238+
DefaultWhenEnabled = "DefaultWhenEnabled",
239+
240+
/**
241+
* The variant is assigned because of the user allocation when a feature flag is enabled.
242+
*/
243+
User = "User",
244+
245+
/**
246+
* The variant is assigned because of the group allocation when a feature flag is enabled.
247+
*/
248+
Group = "Group",
249+
250+
/**
251+
* The variant is assigned because of the percentile allocation when a feature flag is enabled.
252+
*/
253+
Percentile = "Percentile"
192254
}
193255

194256
/**
@@ -229,49 +291,3 @@ type VariantAssignment = {
229291
variant: VariantDefinition | undefined;
230292
reason: VariantAssignmentReason;
231293
};
232-
233-
enum VariantAssignmentReason {
234-
/**
235-
* Variant allocation did not happen. No variant is assigned.
236-
*/
237-
None,
238-
239-
/**
240-
* The default variant is assigned when a feature flag is disabled.
241-
*/
242-
DefaultWhenDisabled,
243-
244-
/**
245-
* The default variant is assigned because of no applicable user/group/percentile allocation when a feature flag is enabled.
246-
*/
247-
DefaultWhenEnabled,
248-
249-
/**
250-
* The variant is assigned because of the user allocation when a feature flag is enabled.
251-
*/
252-
User,
253-
254-
/**
255-
* The variant is assigned because of the group allocation when a feature flag is enabled.
256-
*/
257-
Group,
258-
259-
/**
260-
* The variant is assigned because of the percentile allocation when a feature flag is enabled.
261-
*/
262-
Percentile
263-
}
264-
265-
class EvaluationResult {
266-
constructor(
267-
// feature flag definition
268-
public readonly feature: FeatureFlag | undefined,
269-
270-
// enabled state
271-
public enabled: boolean = false,
272-
273-
// variant assignment
274-
public variant: Variant | undefined = undefined,
275-
public variantAssignmentReason: VariantAssignmentReason = VariantAssignmentReason.None
276-
) { }
277-
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
33

4-
export { FeatureManager } from "./featureManager.js";
4+
export { FeatureManager, FeatureManagerOptions, EvaluationResult, VariantAssignmentReason } from "./featureManager.js";
55
export { ConfigurationMapFeatureFlagProvider, ConfigurationObjectFeatureFlagProvider, IFeatureFlagProvider } from "./featureProvider.js";
66
export { IFeatureFilter } from "./filter/FeatureFilter.js";

0 commit comments

Comments
 (0)