From f7a306a33c1a3cb1eb1bbda0d29109c35aeb86a9 Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Thu, 6 Feb 2025 15:31:50 +0800 Subject: [PATCH 01/10] support targeting context accessor --- src/feature-management/package-lock.json | 4 +-- src/feature-management/src/IFeatureManager.ts | 2 +- ...argetingContext.ts => targetingContext.ts} | 1 + src/feature-management/src/featureManager.ts | 28 +++++++++++++++---- .../src/filter/TargetingFilter.ts | 6 +--- .../test/targetingFilter.test.ts | 12 -------- 6 files changed, 28 insertions(+), 25 deletions(-) rename src/feature-management/src/common/{ITargetingContext.ts => targetingContext.ts} (71%) diff --git a/src/feature-management/package-lock.json b/src/feature-management/package-lock.json index 74c975d..f8c9a0a 100644 --- a/src/feature-management/package-lock.json +++ b/src/feature-management/package-lock.json @@ -20,7 +20,7 @@ "eslint": "^8.56.0", "mocha": "^10.2.0", "rimraf": "^5.0.5", - "rollup": "^4.9.4", + "rollup": "^4.22.4", "rollup-plugin-dts": "^6.1.0", "tslib": "^2.6.2", "typescript": "^5.3.3" @@ -3212,4 +3212,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/feature-management/src/IFeatureManager.ts b/src/feature-management/src/IFeatureManager.ts index d673dce..f982a6c 100644 --- a/src/feature-management/src/IFeatureManager.ts +++ b/src/feature-management/src/IFeatureManager.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { ITargetingContext } from "./common/ITargetingContext"; +import { ITargetingContext } from "./common/targetingContext"; import { Variant } from "./variant/Variant"; export interface IFeatureManager { diff --git a/src/feature-management/src/common/ITargetingContext.ts b/src/feature-management/src/common/targetingContext.ts similarity index 71% rename from src/feature-management/src/common/ITargetingContext.ts rename to src/feature-management/src/common/targetingContext.ts index 1d5a426..ccc8dfb 100644 --- a/src/feature-management/src/common/ITargetingContext.ts +++ b/src/feature-management/src/common/targetingContext.ts @@ -6,3 +6,4 @@ export interface ITargetingContext { groups?: string[]; } +export type TargetingContextAccessor = () => ITargetingContext; diff --git a/src/feature-management/src/featureManager.ts b/src/feature-management/src/featureManager.ts index a035b5d..9c2581f 100644 --- a/src/feature-management/src/featureManager.ts +++ b/src/feature-management/src/featureManager.ts @@ -8,13 +8,14 @@ import { IFeatureFlagProvider } from "./featureProvider.js"; import { TargetingFilter } from "./filter/TargetingFilter.js"; import { Variant } from "./variant/Variant.js"; import { IFeatureManager } from "./IFeatureManager.js"; -import { ITargetingContext } from "./common/ITargetingContext.js"; +import { ITargetingContext, TargetingContextAccessor } from "./common/targetingContext.js"; import { isTargetedGroup, isTargetedPercentile, isTargetedUser } from "./common/targetingEvaluator.js"; export class FeatureManager implements IFeatureManager { #provider: IFeatureFlagProvider; #featureFilters: Map = new Map(); #onFeatureEvaluated?: (event: EvaluationResult) => void; + #targetingContextAccessor?: TargetingContextAccessor; constructor(provider: IFeatureFlagProvider, options?: FeatureManagerOptions) { this.#provider = provider; @@ -27,6 +28,7 @@ export class FeatureManager implements IFeatureManager { } this.#onFeatureEvaluated = options?.onFeatureEvaluated; + this.#targetingContextAccessor = options?.targetingContextAccessor; } async listFeatureNames(): Promise { @@ -102,11 +104,19 @@ export class FeatureManager implements IFeatureManager { for (const clientFilter of clientFilters) { const matchedFeatureFilter = this.#featureFilters.get(clientFilter.name); const contextWithFeatureName = { featureName: featureFlag.id, parameters: clientFilter.parameters }; + let clientFilterEvaluationResult: boolean; if (matchedFeatureFilter === undefined) { console.warn(`Feature filter ${clientFilter.name} is not found.`); - return false; + clientFilterEvaluationResult = false; } - if (await matchedFeatureFilter.evaluate(contextWithFeatureName, context) === shortCircuitEvaluationResult) { + else { + let appContext = context; + if (clientFilter.name === "Microsoft.Targeting" && this.#targetingContextAccessor !== undefined) { + appContext = this.#targetingContextAccessor(); + } + clientFilterEvaluationResult = await matchedFeatureFilter.evaluate(contextWithFeatureName, appContext); + } + if (clientFilterEvaluationResult === shortCircuitEvaluationResult) { return shortCircuitEvaluationResult; } } @@ -130,7 +140,10 @@ export class FeatureManager implements IFeatureManager { // Evaluate if the feature is enabled. result.enabled = await this.#isEnabled(featureFlag, context); - const targetingContext = context as ITargetingContext; + let targetingContext = context as ITargetingContext; + if (this.#targetingContextAccessor !== undefined) { + targetingContext = this.#targetingContextAccessor(); + } result.targetingId = targetingContext?.userId; // Determine Variant @@ -151,7 +164,7 @@ export class FeatureManager implements IFeatureManager { } } else { // enabled, assign based on allocation - if (context !== undefined && featureFlag.allocation !== undefined) { + if (targetingContext !== undefined && featureFlag.allocation !== undefined) { const variantAndReason = await this.#assignVariant(featureFlag, targetingContext); variantDef = variantAndReason.variant; reason = variantAndReason.reason; @@ -202,6 +215,11 @@ export interface FeatureManagerOptions { * The callback function is called only when telemetry is enabled for the feature flag. */ onFeatureEvaluated?: (event: EvaluationResult) => void; + + /** + * The accessor function that provides the @see ITargetingContext for targeting evaluation. + */ + targetingContextAccessor?: TargetingContextAccessor; } export class EvaluationResult { diff --git a/src/feature-management/src/filter/TargetingFilter.ts b/src/feature-management/src/filter/TargetingFilter.ts index 2d7220e..e6da2f7 100644 --- a/src/feature-management/src/filter/TargetingFilter.ts +++ b/src/feature-management/src/filter/TargetingFilter.ts @@ -3,7 +3,7 @@ import { IFeatureFilter } from "./FeatureFilter.js"; import { isTargetedPercentile } from "../common/targetingEvaluator.js"; -import { ITargetingContext } from "../common/ITargetingContext.js"; +import { ITargetingContext } from "../common/targetingContext.js"; type TargetingFilterParameters = { Audience: { @@ -32,10 +32,6 @@ export class TargetingFilter implements IFeatureFilter { const { featureName, parameters } = context; TargetingFilter.#validateParameters(featureName, parameters); - if (appContext === undefined) { - throw new Error("The app context is required for targeting filter."); - } - if (parameters.Audience.Exclusion !== undefined) { // check if the user is in the exclusion list if (appContext?.userId !== undefined && diff --git a/src/feature-management/test/targetingFilter.test.ts b/src/feature-management/test/targetingFilter.test.ts index 91fe81b..4e92c42 100644 --- a/src/feature-management/test/targetingFilter.test.ts +++ b/src/feature-management/test/targetingFilter.test.ts @@ -130,16 +130,4 @@ describe("targeting filter", () => { expect(featureManager.isEnabled("ComplexTargeting", { userId: "Dave", groups: ["Stage1"] })).eventually.eq(false, "Dave is excluded because he is in the exclusion list"), ]); }); - - it("should throw error if app context is not provided", () => { - const dataSource = new Map(); - dataSource.set("feature_management", { - feature_flags: [complexTargetingFeature] - }); - - const provider = new ConfigurationMapFeatureFlagProvider(dataSource); - const featureManager = new FeatureManager(provider); - - return expect(featureManager.isEnabled("ComplexTargeting")).eventually.rejectedWith("The app context is required for targeting filter."); - }); }); From aa548186182a3be3f3b253989cd51aa2addb832c Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Thu, 6 Feb 2025 16:08:19 +0800 Subject: [PATCH 02/10] add test --- .../test/targetingFilter.test.ts | 24 +++++++++++++++++++ src/feature-management/test/variant.test.ts | 18 ++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/feature-management/test/targetingFilter.test.ts b/src/feature-management/test/targetingFilter.test.ts index 4e92c42..210972e 100644 --- a/src/feature-management/test/targetingFilter.test.ts +++ b/src/feature-management/test/targetingFilter.test.ts @@ -130,4 +130,28 @@ describe("targeting filter", () => { expect(featureManager.isEnabled("ComplexTargeting", { userId: "Dave", groups: ["Stage1"] })).eventually.eq(false, "Dave is excluded because he is in the exclusion list"), ]); }); + + it("should evaluate feature with targeting filter with targeting context accessor", async () => { + const dataSource = new Map(); + dataSource.set("feature_management", { + feature_flags: [complexTargetingFeature] + }); + + let userId = ""; + let groups: string[] = []; + const testTargetingContextAccessor = () => ({ userId, groups }); + const provider = new ConfigurationMapFeatureFlagProvider(dataSource); + const featureManager = new FeatureManager(provider, {targetingContextAccessor: testTargetingContextAccessor}); + + userId = "Aiden"; + expect(await featureManager.isEnabled("ComplexTargeting")).to.eq(false); + userId = "Blossom"; + expect(await featureManager.isEnabled("ComplexTargeting")).to.eq(true); + expect(await featureManager.isEnabled("ComplexTargeting", {userId: "Aiden"})).to.eq(true); // targeting id will be overridden by the context accessor + userId = "Aiden"; + groups = ["Stage2"]; + expect(await featureManager.isEnabled("ComplexTargeting")).to.eq(true); + userId = "Chris"; + expect(await featureManager.isEnabled("ComplexTargeting")).to.eq(false); + }); }); diff --git a/src/feature-management/test/variant.test.ts b/src/feature-management/test/variant.test.ts index ddfd90f..0484bf7 100644 --- a/src/feature-management/test/variant.test.ts +++ b/src/feature-management/test/variant.test.ts @@ -90,5 +90,23 @@ describe("feature variant", () => { it("throw exception for invalid doubles From and To in the Percentile section"); }); +}); +describe("variant assignment with targeting context accessor", () => { + it("should assign variant based on targeting context accessor", async () => { + let userId = ""; + let groups: string[] = []; + const testTargetingContextAccessor = () => ({ userId, groups }); + const provider = new ConfigurationObjectFeatureFlagProvider(featureFlagsConfigurationObject); + const featureManager = new FeatureManager(provider, {targetingContextAccessor: testTargetingContextAccessor}); + userId = "Marsha"; + let variant = await featureManager.getVariant(Features.VariantFeatureUser); + expect(variant).not.to.be.undefined; + expect(variant?.name).eq("Small"); + userId = "Jeff"; + variant = await featureManager.getVariant(Features.VariantFeatureUser); + expect(variant).to.be.undefined; + variant = await featureManager.getVariant(Features.VariantFeatureUser, {userId: "Marsha"}); // targeting id will be overridden by the context accessor + expect(variant).to.be.undefined; + }); }); From 7e2652474f19e08b9c77b9736ccc13adaac6f150 Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Thu, 6 Feb 2025 16:14:59 +0800 Subject: [PATCH 03/10] fix lint --- src/feature-management/src/featureManager.ts | 2 +- src/feature-management/test/variant.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/feature-management/src/featureManager.ts b/src/feature-management/src/featureManager.ts index 9c2581f..1d2d76c 100644 --- a/src/feature-management/src/featureManager.ts +++ b/src/feature-management/src/featureManager.ts @@ -114,7 +114,7 @@ export class FeatureManager implements IFeatureManager { if (clientFilter.name === "Microsoft.Targeting" && this.#targetingContextAccessor !== undefined) { appContext = this.#targetingContextAccessor(); } - clientFilterEvaluationResult = await matchedFeatureFilter.evaluate(contextWithFeatureName, appContext); + clientFilterEvaluationResult = await matchedFeatureFilter.evaluate(contextWithFeatureName, appContext); } if (clientFilterEvaluationResult === shortCircuitEvaluationResult) { return shortCircuitEvaluationResult; diff --git a/src/feature-management/test/variant.test.ts b/src/feature-management/test/variant.test.ts index 0484bf7..a610290 100644 --- a/src/feature-management/test/variant.test.ts +++ b/src/feature-management/test/variant.test.ts @@ -95,7 +95,7 @@ describe("feature variant", () => { describe("variant assignment with targeting context accessor", () => { it("should assign variant based on targeting context accessor", async () => { let userId = ""; - let groups: string[] = []; + const groups: string[] = []; const testTargetingContextAccessor = () => ({ userId, groups }); const provider = new ConfigurationObjectFeatureFlagProvider(featureFlagsConfigurationObject); const featureManager = new FeatureManager(provider, {targetingContextAccessor: testTargetingContextAccessor}); From 0c7c1a52f2be5e83c61ad59b29c75c605c12d5cb Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Thu, 6 Feb 2025 16:18:26 +0800 Subject: [PATCH 04/10] update --- src/feature-management/test/targetingFilter.test.ts | 2 +- src/feature-management/test/variant.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/feature-management/test/targetingFilter.test.ts b/src/feature-management/test/targetingFilter.test.ts index 210972e..c6a9e22 100644 --- a/src/feature-management/test/targetingFilter.test.ts +++ b/src/feature-management/test/targetingFilter.test.ts @@ -139,7 +139,7 @@ describe("targeting filter", () => { let userId = ""; let groups: string[] = []; - const testTargetingContextAccessor = () => ({ userId, groups }); + const testTargetingContextAccessor = () => ({ userId: userId, groups: groups }); const provider = new ConfigurationMapFeatureFlagProvider(dataSource); const featureManager = new FeatureManager(provider, {targetingContextAccessor: testTargetingContextAccessor}); diff --git a/src/feature-management/test/variant.test.ts b/src/feature-management/test/variant.test.ts index a610290..b1b746f 100644 --- a/src/feature-management/test/variant.test.ts +++ b/src/feature-management/test/variant.test.ts @@ -95,8 +95,8 @@ describe("feature variant", () => { describe("variant assignment with targeting context accessor", () => { it("should assign variant based on targeting context accessor", async () => { let userId = ""; - const groups: string[] = []; - const testTargetingContextAccessor = () => ({ userId, groups }); + let groups: string[] = []; + const testTargetingContextAccessor = () => ({ userId: userId, groups: groups }); const provider = new ConfigurationObjectFeatureFlagProvider(featureFlagsConfigurationObject); const featureManager = new FeatureManager(provider, {targetingContextAccessor: testTargetingContextAccessor}); userId = "Marsha"; From f612aadcab819e77c3cb000d377f7e40e2627c28 Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Thu, 6 Feb 2025 16:22:25 +0800 Subject: [PATCH 05/10] update --- src/feature-management/test/variant.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/feature-management/test/variant.test.ts b/src/feature-management/test/variant.test.ts index b1b746f..31d41f8 100644 --- a/src/feature-management/test/variant.test.ts +++ b/src/feature-management/test/variant.test.ts @@ -108,5 +108,9 @@ describe("variant assignment with targeting context accessor", () => { expect(variant).to.be.undefined; variant = await featureManager.getVariant(Features.VariantFeatureUser, {userId: "Marsha"}); // targeting id will be overridden by the context accessor expect(variant).to.be.undefined; + groups = ["Group1"]; + variant = await featureManager.getVariant(Features.VariantFeatureGroup); + expect(variant).not.to.be.undefined; + expect(variant?.name).eq("Small"); }); }); From 730bb2e34646aa89ef84a1415c2e09488d5a9b0c Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 10 Feb 2025 18:22:56 +0800 Subject: [PATCH 06/10] export targeting context --- src/feature-management/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/feature-management/src/index.ts b/src/feature-management/src/index.ts index 77d18c5..24eb7a8 100644 --- a/src/feature-management/src/index.ts +++ b/src/feature-management/src/index.ts @@ -5,4 +5,5 @@ export { FeatureManager, FeatureManagerOptions, EvaluationResult, VariantAssignm export { ConfigurationMapFeatureFlagProvider, ConfigurationObjectFeatureFlagProvider, IFeatureFlagProvider } from "./featureProvider.js"; export { createFeatureEvaluationEventProperties } from "./telemetry/featureEvaluationEvent.js"; export { IFeatureFilter } from "./filter/FeatureFilter.js"; +export { TargetingContextAccessor, ITargetingContext } from "./common/targetingContext.js"; export { VERSION } from "./version.js"; From b9bcbc99c53ed4a040e7c0d8f20776b3d6a88c0f Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 10 Feb 2025 18:36:46 +0800 Subject: [PATCH 07/10] add comments --- .../src/common/targetingContext.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/feature-management/src/common/targetingContext.ts b/src/feature-management/src/common/targetingContext.ts index ccc8dfb..db1d8d4 100644 --- a/src/feature-management/src/common/targetingContext.ts +++ b/src/feature-management/src/common/targetingContext.ts @@ -1,9 +1,21 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +/** + * Contextual information that is required to perform a targeting evaluation. + */ export interface ITargetingContext { + /** + * The user id that should be considered when evaluating if the context is being targeted. + */ userId?: string; + /** + * The groups that should be considered when evaluating if the context is being targeted. + */ groups?: string[]; } +/** + * Type definition for a function that, when invoked, returns the @see ITargetingContext for targeting evaluation. + */ export type TargetingContextAccessor = () => ITargetingContext; From a94fd8473bb2eeb7bfe60b6172d6080f9bc1ce72 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 10 Feb 2025 19:06:17 +0800 Subject: [PATCH 08/10] support telemetry processor & initializer --- .../src/index.ts | 2 +- .../src/telemetry.ts | 19 +++++++++++++++++-- .../src/index.ts | 2 +- .../src/telemetry.ts | 19 ++++++++++++++++++- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/feature-management-applicationinsights-browser/src/index.ts b/src/feature-management-applicationinsights-browser/src/index.ts index 6b53335..01dcb13 100644 --- a/src/feature-management-applicationinsights-browser/src/index.ts +++ b/src/feature-management-applicationinsights-browser/src/index.ts @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -export { createTelemetryPublisher, trackEvent } from "./telemetry.js"; +export { createTelemetryPublisher, trackEvent, createTargetingTelemetryInitializer } from "./telemetry.js"; export { VERSION } from "./version.js"; diff --git a/src/feature-management-applicationinsights-browser/src/telemetry.ts b/src/feature-management-applicationinsights-browser/src/telemetry.ts index 6877c0f..df7ee20 100644 --- a/src/feature-management-applicationinsights-browser/src/telemetry.ts +++ b/src/feature-management-applicationinsights-browser/src/telemetry.ts @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { EvaluationResult, createFeatureEvaluationEventProperties } from "@microsoft/feature-management"; -import { ApplicationInsights, IEventTelemetry } from "@microsoft/applicationinsights-web"; +import { EvaluationResult, createFeatureEvaluationEventProperties, TargetingContextAccessor } from "@microsoft/feature-management"; +import { ApplicationInsights, IEventTelemetry, ITelemetryItem } from "@microsoft/applicationinsights-web"; const TARGETING_ID = "TargetingId"; const FEATURE_EVALUATION_EVENT_NAME = "FeatureEvaluation"; @@ -39,3 +39,18 @@ export function trackEvent(client: ApplicationInsights, targetingId: string, eve properties[TARGETING_ID] = targetingId ? targetingId.toString() : ""; client.trackEvent(event, properties); } + +/** + * Creates a telemetry initializer that adds targeting id to telemetry item's custom properties. + * @param targetingContextAccessor The accessor function to get the targeting context. + * @returns A telemetry initializer that attaches targeting id to telemetry items. + */ +export function createTargetingTelemetryInitializer(targetingContextAccessor: TargetingContextAccessor): (item: ITelemetryItem) => void { + return (item: ITelemetryItem) => { + const targetingContext = targetingContextAccessor(); + if (targetingContext?.userId === undefined) { + console.warn("Targeting id is undefined."); + } + item.data = {...item.data, [TARGETING_ID]: targetingContext?.userId || ""}; + }; +} diff --git a/src/feature-management-applicationinsights-node/src/index.ts b/src/feature-management-applicationinsights-node/src/index.ts index 6b53335..df8dd95 100644 --- a/src/feature-management-applicationinsights-node/src/index.ts +++ b/src/feature-management-applicationinsights-node/src/index.ts @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -export { createTelemetryPublisher, trackEvent } from "./telemetry.js"; +export { createTelemetryPublisher, trackEvent, createTargetingTelemetryProcessor } from "./telemetry.js"; export { VERSION } from "./version.js"; diff --git a/src/feature-management-applicationinsights-node/src/telemetry.ts b/src/feature-management-applicationinsights-node/src/telemetry.ts index 11030e6..7e26f9c 100644 --- a/src/feature-management-applicationinsights-node/src/telemetry.ts +++ b/src/feature-management-applicationinsights-node/src/telemetry.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { EvaluationResult, createFeatureEvaluationEventProperties } from "@microsoft/feature-management"; +import { EvaluationResult, createFeatureEvaluationEventProperties, TargetingContextAccessor } from "@microsoft/feature-management"; import { TelemetryClient, Contracts } from "applicationinsights"; const TARGETING_ID = "TargetingId"; @@ -39,3 +39,20 @@ export function trackEvent(client: TelemetryClient, targetingId: string, event: }; client.trackEvent(event); } + +/** + * Creates a telemetry processor that adds targeting id to telemetry envelope's custom properties. + * @param targetingContextAccessor The accessor function to get the targeting context. + * @returns A telemetry processor that attaches targeting id to telemetry envelopes. + */ +export function createTargetingTelemetryProcessor(targetingContextAccessor: TargetingContextAccessor): (envelope: Contracts.EnvelopeTelemetry) => boolean { + return (envelope: Contracts.EnvelopeTelemetry) => { + const targetingContext = targetingContextAccessor(); + if (targetingContext?.userId === undefined) { + console.warn("Targeting id is undefined."); + } + envelope.data.baseData = envelope.data.baseData || {}; + envelope.data.baseData.properties = {...envelope.data.baseData.properties, [TARGETING_ID]: targetingContext.userId}; + return true; // If a telemetry processor returns false, that telemetry item isn't sent. + }; +} From 325fbef4820b7eaca1144c5b79a89b875e5a3e05 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 21 Apr 2025 15:51:00 +0800 Subject: [PATCH 09/10] update --- .../src/index.ts | 2 +- .../src/telemetry.ts | 6 +++--- .../src/index.ts | 2 +- .../src/telemetry.ts | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/feature-management-applicationinsights-browser/src/index.ts b/src/feature-management-applicationinsights-browser/src/index.ts index 01dcb13..efbc293 100644 --- a/src/feature-management-applicationinsights-browser/src/index.ts +++ b/src/feature-management-applicationinsights-browser/src/index.ts @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -export { createTelemetryPublisher, trackEvent, createTargetingTelemetryInitializer } from "./telemetry.js"; +export { createTargetingTelemetryInitializer, createTelemetryPublisher, trackEvent } from "./telemetry.js"; export { VERSION } from "./version.js"; diff --git a/src/feature-management-applicationinsights-browser/src/telemetry.ts b/src/feature-management-applicationinsights-browser/src/telemetry.ts index df7ee20..e02b425 100644 --- a/src/feature-management-applicationinsights-browser/src/telemetry.ts +++ b/src/feature-management-applicationinsights-browser/src/telemetry.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { EvaluationResult, createFeatureEvaluationEventProperties, TargetingContextAccessor } from "@microsoft/feature-management"; +import { EvaluationResult, createFeatureEvaluationEventProperties, ITargetingContextAccessor } from "@microsoft/feature-management"; import { ApplicationInsights, IEventTelemetry, ITelemetryItem } from "@microsoft/applicationinsights-web"; const TARGETING_ID = "TargetingId"; @@ -45,9 +45,9 @@ export function trackEvent(client: ApplicationInsights, targetingId: string, eve * @param targetingContextAccessor The accessor function to get the targeting context. * @returns A telemetry initializer that attaches targeting id to telemetry items. */ -export function createTargetingTelemetryInitializer(targetingContextAccessor: TargetingContextAccessor): (item: ITelemetryItem) => void { +export function createTargetingTelemetryInitializer(targetingContextAccessor: ITargetingContextAccessor): (item: ITelemetryItem) => void { return (item: ITelemetryItem) => { - const targetingContext = targetingContextAccessor(); + const targetingContext = targetingContextAccessor.getTargetingContext(); if (targetingContext?.userId === undefined) { console.warn("Targeting id is undefined."); } diff --git a/src/feature-management-applicationinsights-node/src/index.ts b/src/feature-management-applicationinsights-node/src/index.ts index df8dd95..e8509a8 100644 --- a/src/feature-management-applicationinsights-node/src/index.ts +++ b/src/feature-management-applicationinsights-node/src/index.ts @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -export { createTelemetryPublisher, trackEvent, createTargetingTelemetryProcessor } from "./telemetry.js"; +export { createTargetingTelemetryProcessor, createTelemetryPublisher, trackEvent } from "./telemetry.js"; export { VERSION } from "./version.js"; diff --git a/src/feature-management-applicationinsights-node/src/telemetry.ts b/src/feature-management-applicationinsights-node/src/telemetry.ts index 7e26f9c..8953ba8 100644 --- a/src/feature-management-applicationinsights-node/src/telemetry.ts +++ b/src/feature-management-applicationinsights-node/src/telemetry.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { EvaluationResult, createFeatureEvaluationEventProperties, TargetingContextAccessor } from "@microsoft/feature-management"; +import { EvaluationResult, createFeatureEvaluationEventProperties, ITargetingContextAccessor } from "@microsoft/feature-management"; import { TelemetryClient, Contracts } from "applicationinsights"; const TARGETING_ID = "TargetingId"; @@ -45,14 +45,14 @@ export function trackEvent(client: TelemetryClient, targetingId: string, event: * @param targetingContextAccessor The accessor function to get the targeting context. * @returns A telemetry processor that attaches targeting id to telemetry envelopes. */ -export function createTargetingTelemetryProcessor(targetingContextAccessor: TargetingContextAccessor): (envelope: Contracts.EnvelopeTelemetry) => boolean { +export function createTargetingTelemetryProcessor(targetingContextAccessor: ITargetingContextAccessor): (envelope: Contracts.EnvelopeTelemetry) => boolean { return (envelope: Contracts.EnvelopeTelemetry) => { - const targetingContext = targetingContextAccessor(); + const targetingContext = targetingContextAccessor.getTargetingContext(); if (targetingContext?.userId === undefined) { console.warn("Targeting id is undefined."); } envelope.data.baseData = envelope.data.baseData || {}; - envelope.data.baseData.properties = {...envelope.data.baseData.properties, [TARGETING_ID]: targetingContext.userId}; + envelope.data.baseData.properties = {...envelope.data.baseData.properties, [TARGETING_ID]: targetingContext?.userId || ""}; return true; // If a telemetry processor returns false, that telemetry item isn't sent. }; } From f6d60c1e48da69ca69f887f1539437d71ee9737e Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 21 Apr 2025 16:32:51 +0800 Subject: [PATCH 10/10] update --- .gitignore | 2 ++ .../src/telemetry.ts | 8 +++++--- .../src/telemetry.ts | 12 +++++++----- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 250bd47..e8b8fb0 100644 --- a/.gitignore +++ b/.gitignore @@ -413,3 +413,5 @@ examples/**/**/package-lock.json # playwright test result test-results + +**/public \ No newline at end of file diff --git a/src/feature-management-applicationinsights-browser/src/telemetry.ts b/src/feature-management-applicationinsights-browser/src/telemetry.ts index e02b425..3b9cfad 100644 --- a/src/feature-management-applicationinsights-browser/src/telemetry.ts +++ b/src/feature-management-applicationinsights-browser/src/telemetry.ts @@ -48,9 +48,11 @@ export function trackEvent(client: ApplicationInsights, targetingId: string, eve export function createTargetingTelemetryInitializer(targetingContextAccessor: ITargetingContextAccessor): (item: ITelemetryItem) => void { return (item: ITelemetryItem) => { const targetingContext = targetingContextAccessor.getTargetingContext(); - if (targetingContext?.userId === undefined) { - console.warn("Targeting id is undefined."); + if (targetingContext !== undefined) { + if (targetingContext?.userId === undefined) { + console.warn("Targeting id is undefined."); + } + item.data = {...item.data, [TARGETING_ID]: targetingContext?.userId || ""}; } - item.data = {...item.data, [TARGETING_ID]: targetingContext?.userId || ""}; }; } diff --git a/src/feature-management-applicationinsights-node/src/telemetry.ts b/src/feature-management-applicationinsights-node/src/telemetry.ts index 8953ba8..48f3320 100644 --- a/src/feature-management-applicationinsights-node/src/telemetry.ts +++ b/src/feature-management-applicationinsights-node/src/telemetry.ts @@ -48,11 +48,13 @@ export function trackEvent(client: TelemetryClient, targetingId: string, event: export function createTargetingTelemetryProcessor(targetingContextAccessor: ITargetingContextAccessor): (envelope: Contracts.EnvelopeTelemetry) => boolean { return (envelope: Contracts.EnvelopeTelemetry) => { const targetingContext = targetingContextAccessor.getTargetingContext(); - if (targetingContext?.userId === undefined) { - console.warn("Targeting id is undefined."); + if (targetingContext !== undefined) { + if (targetingContext?.userId === undefined) { + console.warn("Targeting id is undefined."); + } + envelope.data.baseData = envelope.data.baseData || {}; + envelope.data.baseData.properties = {...envelope.data.baseData.properties, [TARGETING_ID]: targetingContext?.userId || ""}; } - envelope.data.baseData = envelope.data.baseData || {}; - envelope.data.baseData.properties = {...envelope.data.baseData.properties, [TARGETING_ID]: targetingContext?.userId || ""}; - return true; // If a telemetry processor returns false, that telemetry item isn't sent. + return true; }; }