@microsoft/feature-management 2.0.2 → 2.1.0-preview.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,42 +4,50 @@ import { isTargetedPercentile } from '../common/targetingEvaluator.js';
4
4
  // Licensed under the MIT license.
5
5
  class TargetingFilter {
6
6
  name = "Microsoft.Targeting";
7
+ #targetingContextAccessor;
8
+ constructor(targetingContextAccessor) {
9
+ this.#targetingContextAccessor = targetingContextAccessor;
10
+ }
7
11
  async evaluate(context, appContext) {
8
12
  const { featureName, parameters } = context;
9
13
  TargetingFilter.#validateParameters(featureName, parameters);
10
- if (appContext === undefined) {
11
- throw new Error("The app context is required for targeting filter.");
14
+ let targetingContext;
15
+ if (appContext?.userId !== undefined || appContext?.groups !== undefined) {
16
+ targetingContext = appContext;
17
+ }
18
+ else if (this.#targetingContextAccessor !== undefined) {
19
+ targetingContext = this.#targetingContextAccessor.getTargetingContext();
12
20
  }
13
21
  if (parameters.Audience.Exclusion !== undefined) {
14
22
  // check if the user is in the exclusion list
15
- if (appContext?.userId !== undefined &&
23
+ if (targetingContext?.userId !== undefined &&
16
24
  parameters.Audience.Exclusion.Users !== undefined &&
17
- parameters.Audience.Exclusion.Users.includes(appContext.userId)) {
25
+ parameters.Audience.Exclusion.Users.includes(targetingContext.userId)) {
18
26
  return false;
19
27
  }
20
28
  // check if the user is in a group within exclusion list
21
- if (appContext?.groups !== undefined &&
29
+ if (targetingContext?.groups !== undefined &&
22
30
  parameters.Audience.Exclusion.Groups !== undefined) {
23
31
  for (const excludedGroup of parameters.Audience.Exclusion.Groups) {
24
- if (appContext.groups.includes(excludedGroup)) {
32
+ if (targetingContext.groups.includes(excludedGroup)) {
25
33
  return false;
26
34
  }
27
35
  }
28
36
  }
29
37
  }
30
38
  // check if the user is being targeted directly
31
- if (appContext?.userId !== undefined &&
39
+ if (targetingContext?.userId !== undefined &&
32
40
  parameters.Audience.Users !== undefined &&
33
- parameters.Audience.Users.includes(appContext.userId)) {
41
+ parameters.Audience.Users.includes(targetingContext.userId)) {
34
42
  return true;
35
43
  }
36
44
  // check if the user is in a group that is being targeted
37
- if (appContext?.groups !== undefined &&
45
+ if (targetingContext?.groups !== undefined &&
38
46
  parameters.Audience.Groups !== undefined) {
39
47
  for (const group of parameters.Audience.Groups) {
40
- if (appContext.groups.includes(group.Name)) {
48
+ if (targetingContext.groups.includes(group.Name)) {
41
49
  const hint = `${featureName}\n${group.Name}`;
42
- if (await isTargetedPercentile(appContext.userId, hint, 0, group.RolloutPercentage)) {
50
+ if (await isTargetedPercentile(targetingContext.userId, hint, 0, group.RolloutPercentage)) {
43
51
  return true;
44
52
  }
45
53
  }
@@ -47,7 +55,7 @@ class TargetingFilter {
47
55
  }
48
56
  // check if the user is being targeted by a default rollout percentage
49
57
  const hint = featureName;
50
- return isTargetedPercentile(appContext?.userId, hint, 0, parameters.Audience.DefaultRolloutPercentage);
58
+ return isTargetedPercentile(targetingContext?.userId, hint, 0, parameters.Audience.DefaultRolloutPercentage);
51
59
  }
52
60
  static #validateParameters(featureName, parameters) {
53
61
  if (parameters.Audience.DefaultRolloutPercentage < 0 || parameters.Audience.DefaultRolloutPercentage > 100) {
@@ -1 +1 @@
1
- {"version":3,"file":"TargetingFilter.js","sources":["../../../src/filter/TargetingFilter.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { IFeatureFilter } from \"./FeatureFilter.js\";\nimport { isTargetedPercentile } from \"../common/targetingEvaluator.js\";\nimport { ITargetingContext } from \"../common/ITargetingContext.js\";\n\ntype TargetingFilterParameters = {\n Audience: {\n DefaultRolloutPercentage: number;\n Users?: string[];\n Groups?: {\n Name: string;\n RolloutPercentage: number;\n }[];\n Exclusion?: {\n Users?: string[];\n Groups?: string[];\n };\n }\n}\n\ntype TargetingFilterEvaluationContext = {\n featureName: string;\n parameters: TargetingFilterParameters;\n}\n\nexport class TargetingFilter implements IFeatureFilter {\n name: string = \"Microsoft.Targeting\";\n\n async evaluate(context: TargetingFilterEvaluationContext, appContext?: ITargetingContext): Promise<boolean> {\n const { featureName, parameters } = context;\n TargetingFilter.#validateParameters(featureName, parameters);\n\n if (appContext === undefined) {\n throw new Error(\"The app context is required for targeting filter.\");\n }\n\n if (parameters.Audience.Exclusion !== undefined) {\n // check if the user is in the exclusion list\n if (appContext?.userId !== undefined &&\n parameters.Audience.Exclusion.Users !== undefined &&\n parameters.Audience.Exclusion.Users.includes(appContext.userId)) {\n return false;\n }\n // check if the user is in a group within exclusion list\n if (appContext?.groups !== undefined &&\n parameters.Audience.Exclusion.Groups !== undefined) {\n for (const excludedGroup of parameters.Audience.Exclusion.Groups) {\n if (appContext.groups.includes(excludedGroup)) {\n return false;\n }\n }\n }\n }\n\n // check if the user is being targeted directly\n if (appContext?.userId !== undefined &&\n parameters.Audience.Users !== undefined &&\n parameters.Audience.Users.includes(appContext.userId)) {\n return true;\n }\n\n // check if the user is in a group that is being targeted\n if (appContext?.groups !== undefined &&\n parameters.Audience.Groups !== undefined) {\n for (const group of parameters.Audience.Groups) {\n if (appContext.groups.includes(group.Name)) {\n const hint = `${featureName}\\n${group.Name}`;\n if (await isTargetedPercentile(appContext.userId, hint, 0, group.RolloutPercentage)) {\n return true;\n }\n }\n }\n }\n\n // check if the user is being targeted by a default rollout percentage\n const hint = featureName;\n return isTargetedPercentile(appContext?.userId, hint, 0, parameters.Audience.DefaultRolloutPercentage);\n }\n\n static #validateParameters(featureName: string, parameters: TargetingFilterParameters): void {\n if (parameters.Audience.DefaultRolloutPercentage < 0 || parameters.Audience.DefaultRolloutPercentage > 100) {\n throw new Error(`Invalid feature flag: ${featureName}. Audience.DefaultRolloutPercentage must be a number between 0 and 100.`);\n }\n // validate RolloutPercentage for each group\n if (parameters.Audience.Groups !== undefined) {\n for (const group of parameters.Audience.Groups) {\n if (group.RolloutPercentage < 0 || group.RolloutPercentage > 100) {\n throw new Error(`Invalid feature flag: ${featureName}. RolloutPercentage of group ${group.Name} must be a number between 0 and 100.`);\n }\n }\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TargetingFilter.js","sources":["../../../src/filter/TargetingFilter.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { IFeatureFilter } from \"./FeatureFilter.js\";\nimport { isTargetedPercentile } from \"../common/targetingEvaluator.js\";\nimport { ITargetingContext, ITargetingContextAccessor } from \"../common/targetingContext.js\";\n\ntype TargetingFilterParameters = {\n Audience: {\n DefaultRolloutPercentage: number;\n Users?: string[];\n Groups?: {\n Name: string;\n RolloutPercentage: number;\n }[];\n Exclusion?: {\n Users?: string[];\n Groups?: string[];\n };\n }\n}\n\ntype TargetingFilterEvaluationContext = {\n featureName: string;\n parameters: TargetingFilterParameters;\n}\n\nexport class TargetingFilter implements IFeatureFilter {\n readonly name: string = \"Microsoft.Targeting\";\n readonly #targetingContextAccessor?: ITargetingContextAccessor;\n\n constructor(targetingContextAccessor?: ITargetingContextAccessor) {\n this.#targetingContextAccessor = targetingContextAccessor;\n }\n\n async evaluate(context: TargetingFilterEvaluationContext, appContext?: ITargetingContext): Promise<boolean> {\n const { featureName, parameters } = context;\n TargetingFilter.#validateParameters(featureName, parameters);\n\n let targetingContext: ITargetingContext | undefined;\n if (appContext?.userId !== undefined || appContext?.groups !== undefined) {\n targetingContext = appContext;\n } else if (this.#targetingContextAccessor !== undefined) {\n targetingContext = this.#targetingContextAccessor.getTargetingContext();\n }\n\n if (parameters.Audience.Exclusion !== undefined) {\n // check if the user is in the exclusion list\n if (targetingContext?.userId !== undefined &&\n parameters.Audience.Exclusion.Users !== undefined &&\n parameters.Audience.Exclusion.Users.includes(targetingContext.userId)) {\n return false;\n }\n // check if the user is in a group within exclusion list\n if (targetingContext?.groups !== undefined &&\n parameters.Audience.Exclusion.Groups !== undefined) {\n for (const excludedGroup of parameters.Audience.Exclusion.Groups) {\n if (targetingContext.groups.includes(excludedGroup)) {\n return false;\n }\n }\n }\n }\n\n // check if the user is being targeted directly\n if (targetingContext?.userId !== undefined &&\n parameters.Audience.Users !== undefined &&\n parameters.Audience.Users.includes(targetingContext.userId)) {\n return true;\n }\n\n // check if the user is in a group that is being targeted\n if (targetingContext?.groups !== undefined &&\n parameters.Audience.Groups !== undefined) {\n for (const group of parameters.Audience.Groups) {\n if (targetingContext.groups.includes(group.Name)) {\n const hint = `${featureName}\\n${group.Name}`;\n if (await isTargetedPercentile(targetingContext.userId, hint, 0, group.RolloutPercentage)) {\n return true;\n }\n }\n }\n }\n\n // check if the user is being targeted by a default rollout percentage\n const hint = featureName;\n return isTargetedPercentile(targetingContext?.userId, hint, 0, parameters.Audience.DefaultRolloutPercentage);\n }\n\n static #validateParameters(featureName: string, parameters: TargetingFilterParameters): void {\n if (parameters.Audience.DefaultRolloutPercentage < 0 || parameters.Audience.DefaultRolloutPercentage > 100) {\n throw new Error(`Invalid feature flag: ${featureName}. Audience.DefaultRolloutPercentage must be a number between 0 and 100.`);\n }\n // validate RolloutPercentage for each group\n if (parameters.Audience.Groups !== undefined) {\n for (const group of parameters.Audience.Groups) {\n if (group.RolloutPercentage < 0 || group.RolloutPercentage > 100) {\n throw new Error(`Invalid feature flag: ${featureName}. RolloutPercentage of group ${group.Name} must be a number between 0 and 100.`);\n }\n }\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TimeWindowFilter.js","sources":["../../../src/filter/TimeWindowFilter.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { IFeatureFilter } from \"./FeatureFilter.js\";\n\n// [Start, End)\ntype TimeWindowParameters = {\n Start?: string;\n End?: string;\n}\n\ntype TimeWindowFilterEvaluationContext = {\n featureName: string;\n parameters: TimeWindowParameters;\n}\n\nexport class TimeWindowFilter implements IFeatureFilter {\n name: string = \"Microsoft.TimeWindow\";\n\n evaluate(context: TimeWindowFilterEvaluationContext): boolean {\n const {featureName, parameters} = context;\n const startTime = parameters.Start !== undefined ? new Date(parameters.Start) : undefined;\n const endTime = parameters.End !== undefined ? new Date(parameters.End) : undefined;\n\n if (startTime === undefined && endTime === undefined) {\n // If neither start nor end time is specified, then the filter is not applicable.\n console.warn(`The ${this.name} feature filter is not valid for feature ${featureName}. It must specify either 'Start', 'End', or both.`);\n return false;\n }\n const now = new Date();\n return (startTime === undefined || startTime <= now) && (endTime === undefined || now < endTime);\n }\n}\n"],"names":[],"mappings":"AAAA;AACA;MAea,gBAAgB,CAAA;IACzB,IAAI,GAAW,sBAAsB,CAAC;AAEtC,IAAA,QAAQ,CAAC,OAA0C,EAAA;AAC/C,QAAA,MAAM,EAAC,WAAW,EAAE,UAAU,EAAC,GAAG,OAAO,CAAC;QAC1C,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,KAAK,SAAS,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC;QAC1F,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,KAAK,SAAS,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;QAEpF,IAAI,SAAS,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,EAAE;;YAElD,OAAO,CAAC,IAAI,CAAC,CAAO,IAAA,EAAA,IAAI,CAAC,IAAI,CAA4C,yCAAA,EAAA,WAAW,CAAmD,iDAAA,CAAA,CAAC,CAAC;AACzI,YAAA,OAAO,KAAK,CAAC;SAChB;AACD,QAAA,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;AACvB,QAAA,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,SAAS,IAAI,GAAG,MAAM,OAAO,KAAK,SAAS,IAAI,GAAG,GAAG,OAAO,CAAC,CAAC;KACpG;AACJ;;;;"}
1
+ {"version":3,"file":"TimeWindowFilter.js","sources":["../../../src/filter/TimeWindowFilter.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { IFeatureFilter } from \"./FeatureFilter.js\";\n\n// [Start, End)\ntype TimeWindowParameters = {\n Start?: string;\n End?: string;\n}\n\ntype TimeWindowFilterEvaluationContext = {\n featureName: string;\n parameters: TimeWindowParameters;\n}\n\nexport class TimeWindowFilter implements IFeatureFilter {\n readonly name: string = \"Microsoft.TimeWindow\";\n\n evaluate(context: TimeWindowFilterEvaluationContext): boolean {\n const {featureName, parameters} = context;\n const startTime = parameters.Start !== undefined ? new Date(parameters.Start) : undefined;\n const endTime = parameters.End !== undefined ? new Date(parameters.End) : undefined;\n\n if (startTime === undefined && endTime === undefined) {\n // If neither start nor end time is specified, then the filter is not applicable.\n console.warn(`The ${this.name} feature filter is not valid for feature ${featureName}. It must specify either 'Start', 'End', or both.`);\n return false;\n }\n const now = new Date();\n return (startTime === undefined || startTime <= now) && (endTime === undefined || now < endTime);\n }\n}\n"],"names":[],"mappings":"AAAA;AACA;MAea,gBAAgB,CAAA;IAChB,IAAI,GAAW,sBAAsB,CAAC;AAE/C,IAAA,QAAQ,CAAC,OAA0C,EAAA;AAC/C,QAAA,MAAM,EAAC,WAAW,EAAE,UAAU,EAAC,GAAG,OAAO,CAAC;QAC1C,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,KAAK,SAAS,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC;QAC1F,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,KAAK,SAAS,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;QAEpF,IAAI,SAAS,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,EAAE;;YAElD,OAAO,CAAC,IAAI,CAAC,CAAO,IAAA,EAAA,IAAI,CAAC,IAAI,CAA4C,yCAAA,EAAA,WAAW,CAAmD,iDAAA,CAAA,CAAC,CAAC;AACzI,YAAA,OAAO,KAAK,CAAC;SAChB;AACD,QAAA,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;AACvB,QAAA,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,SAAS,IAAI,GAAG,MAAM,OAAO,KAAK,SAAS,IAAI,GAAG,GAAG,OAAO,CAAC,CAAC;KACpG;AACJ;;;;"}
@@ -1,3 +1,4 @@
1
+ import { VariantAssignmentReason } from '../featureManager.js';
1
2
  import { EVALUATION_EVENT_VERSION } from '../version.js';
2
3
 
3
4
  // Copyright (c) Microsoft Corporation.
@@ -8,6 +9,8 @@ const ENABLED = "Enabled";
8
9
  const TARGETING_ID = "TargetingId";
9
10
  const VARIANT = "Variant";
10
11
  const VARIANT_ASSIGNMENT_REASON = "VariantAssignmentReason";
12
+ const DEFAULT_WHEN_ENABLED = "DefaultWhenEnabled";
13
+ const VARIANT_ASSIGNMENT_PERCENTAGE = "VariantAssignmentPercentage";
11
14
  function createFeatureEvaluationEventProperties(result) {
12
15
  if (result.feature === undefined) {
13
16
  return undefined;
@@ -21,6 +24,29 @@ function createFeatureEvaluationEventProperties(result) {
21
24
  [VARIANT]: result.variant ? result.variant.name : "",
22
25
  [VARIANT_ASSIGNMENT_REASON]: result.variantAssignmentReason,
23
26
  };
27
+ if (result.feature.allocation?.default_when_enabled) {
28
+ eventProperties[DEFAULT_WHEN_ENABLED] = result.feature.allocation.default_when_enabled;
29
+ }
30
+ if (result.variantAssignmentReason === VariantAssignmentReason.DefaultWhenEnabled) {
31
+ let percentileAllocationPercentage = 0;
32
+ if (result.variant !== undefined && result.feature.allocation !== undefined && result.feature.allocation.percentile !== undefined) {
33
+ for (const percentile of result.feature.allocation.percentile) {
34
+ percentileAllocationPercentage += percentile.to - percentile.from;
35
+ }
36
+ }
37
+ eventProperties[VARIANT_ASSIGNMENT_PERCENTAGE] = (100 - percentileAllocationPercentage).toString();
38
+ }
39
+ else if (result.variantAssignmentReason === VariantAssignmentReason.Percentile) {
40
+ let percentileAllocationPercentage = 0;
41
+ if (result.variant !== undefined && result.feature.allocation !== undefined && result.feature.allocation.percentile !== undefined) {
42
+ for (const percentile of result.feature.allocation.percentile) {
43
+ if (percentile.variant === result.variant.name) {
44
+ percentileAllocationPercentage += percentile.to - percentile.from;
45
+ }
46
+ }
47
+ }
48
+ eventProperties[VARIANT_ASSIGNMENT_PERCENTAGE] = percentileAllocationPercentage.toString();
49
+ }
24
50
  const metadata = result.feature.telemetry?.metadata;
25
51
  if (metadata) {
26
52
  for (const key in metadata) {
@@ -1 +1 @@
1
- {"version":3,"file":"featureEvaluationEvent.js","sources":["../../../src/telemetry/featureEvaluationEvent.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { EvaluationResult } from \"../featureManager\";\nimport { EVALUATION_EVENT_VERSION } from \"../version.js\";\n\nconst VERSION = \"Version\";\nconst FEATURE_NAME = \"FeatureName\";\nconst ENABLED = \"Enabled\";\nconst TARGETING_ID = \"TargetingId\";\nconst VARIANT = \"Variant\";\nconst VARIANT_ASSIGNMENT_REASON = \"VariantAssignmentReason\";\n\nexport function createFeatureEvaluationEventProperties(result: EvaluationResult): any {\n if (result.feature === undefined) {\n return undefined;\n }\n\n const eventProperties = {\n [VERSION]: EVALUATION_EVENT_VERSION,\n [FEATURE_NAME]: result.feature ? result.feature.id : \"\",\n [ENABLED]: result.enabled ? \"True\" : \"False\",\n // Ensure targetingId is string so that it will be placed in customDimensions\n [TARGETING_ID]: result.targetingId ? result.targetingId.toString() : \"\",\n [VARIANT]: result.variant ? result.variant.name : \"\",\n [VARIANT_ASSIGNMENT_REASON]: result.variantAssignmentReason,\n };\n\n const metadata = result.feature.telemetry?.metadata;\n if (metadata) {\n for (const key in metadata) {\n if (!(key in eventProperties)) {\n eventProperties[key] = metadata[key];\n }\n }\n }\n\n return eventProperties;\n}\n"],"names":[],"mappings":";;AAAA;AACA;AAKA,MAAM,OAAO,GAAG,SAAS,CAAC;AAC1B,MAAM,YAAY,GAAG,aAAa,CAAC;AACnC,MAAM,OAAO,GAAG,SAAS,CAAC;AAC1B,MAAM,YAAY,GAAG,aAAa,CAAC;AACnC,MAAM,OAAO,GAAG,SAAS,CAAC;AAC1B,MAAM,yBAAyB,GAAG,yBAAyB,CAAC;AAEtD,SAAU,sCAAsC,CAAC,MAAwB,EAAA;AAC3E,IAAA,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE;AAC9B,QAAA,OAAO,SAAS,CAAC;KACpB;AAED,IAAA,MAAM,eAAe,GAAG;QACpB,CAAC,OAAO,GAAG,wBAAwB;AACnC,QAAA,CAAC,YAAY,GAAG,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE;AACvD,QAAA,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,GAAG,MAAM,GAAG,OAAO;;AAE5C,QAAA,CAAC,YAAY,GAAG,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,GAAG,EAAE;AACvE,QAAA,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,GAAG,EAAE;AACpD,QAAA,CAAC,yBAAyB,GAAG,MAAM,CAAC,uBAAuB;KAC9D,CAAC;IAEF,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC;IACpD,IAAI,QAAQ,EAAE;AACV,QAAA,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE;AACxB,YAAA,IAAI,EAAE,GAAG,IAAI,eAAe,CAAC,EAAE;gBAC3B,eAAe,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;aACxC;SACJ;KACJ;AAED,IAAA,OAAO,eAAe,CAAC;AAC3B;;;;"}
1
+ {"version":3,"file":"featureEvaluationEvent.js","sources":["../../../src/telemetry/featureEvaluationEvent.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { EvaluationResult, VariantAssignmentReason } from \"../featureManager\";\nimport { EVALUATION_EVENT_VERSION } from \"../version.js\";\n\nconst VERSION = \"Version\";\nconst FEATURE_NAME = \"FeatureName\";\nconst ENABLED = \"Enabled\";\nconst TARGETING_ID = \"TargetingId\";\nconst VARIANT = \"Variant\";\nconst VARIANT_ASSIGNMENT_REASON = \"VariantAssignmentReason\";\nconst DEFAULT_WHEN_ENABLED = \"DefaultWhenEnabled\";\nconst VARIANT_ASSIGNMENT_PERCENTAGE = \"VariantAssignmentPercentage\";\n\nexport function createFeatureEvaluationEventProperties(result: EvaluationResult): any {\n if (result.feature === undefined) {\n return undefined;\n }\n\n const eventProperties = {\n [VERSION]: EVALUATION_EVENT_VERSION,\n [FEATURE_NAME]: result.feature ? result.feature.id : \"\",\n [ENABLED]: result.enabled ? \"True\" : \"False\",\n // Ensure targetingId is string so that it will be placed in customDimensions\n [TARGETING_ID]: result.targetingId ? result.targetingId.toString() : \"\",\n [VARIANT]: result.variant ? result.variant.name : \"\",\n [VARIANT_ASSIGNMENT_REASON]: result.variantAssignmentReason,\n };\n\n if (result.feature.allocation?.default_when_enabled) {\n eventProperties[DEFAULT_WHEN_ENABLED] = result.feature.allocation.default_when_enabled;\n }\n\n if (result.variantAssignmentReason === VariantAssignmentReason.DefaultWhenEnabled) {\n let percentileAllocationPercentage = 0;\n if (result.variant !== undefined && result.feature.allocation !== undefined && result.feature.allocation.percentile !== undefined) {\n for (const percentile of result.feature.allocation.percentile) {\n percentileAllocationPercentage += percentile.to - percentile.from;\n }\n }\n eventProperties[VARIANT_ASSIGNMENT_PERCENTAGE] = (100 - percentileAllocationPercentage).toString();\n }\n else if (result.variantAssignmentReason === VariantAssignmentReason.Percentile) {\n let percentileAllocationPercentage = 0;\n if (result.variant !== undefined && result.feature.allocation !== undefined && result.feature.allocation.percentile !== undefined) {\n for (const percentile of result.feature.allocation.percentile) {\n if (percentile.variant === result.variant.name) {\n percentileAllocationPercentage += percentile.to - percentile.from;\n }\n }\n }\n eventProperties[VARIANT_ASSIGNMENT_PERCENTAGE] = percentileAllocationPercentage.toString();\n }\n\n const metadata = result.feature.telemetry?.metadata;\n if (metadata) {\n for (const key in metadata) {\n if (!(key in eventProperties)) {\n eventProperties[key] = metadata[key];\n }\n }\n }\n\n return eventProperties;\n}\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT license.
3
- const VERSION = "2.0.2";
3
+ const VERSION = "2.1.0-preview.1";
4
4
  const EVALUATION_EVENT_VERSION = "1.0.0";
5
5
 
6
6
  export { EVALUATION_EVENT_VERSION, VERSION };
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sources":["../../src/version.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nexport const VERSION = \"2.0.2\";\nexport const EVALUATION_EVENT_VERSION = \"1.0.0\";\n"],"names":[],"mappings":"AAAA;AACA;AAEO,MAAM,OAAO,GAAG,QAAQ;AACxB,MAAM,wBAAwB,GAAG;;;;"}
1
+ {"version":3,"file":"version.js","sources":["../../src/version.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nexport const VERSION = \"2.1.0-preview.1\";\nexport const EVALUATION_EVENT_VERSION = \"1.0.0\";\n"],"names":[],"mappings":"AAAA;AACA;AAEO,MAAM,OAAO,GAAG,kBAAkB;AAClC,MAAM,wBAAwB,GAAG;;;;"}
package/dist/umd/index.js CHANGED
@@ -139,42 +139,50 @@
139
139
  // Licensed under the MIT license.
140
140
  class TargetingFilter {
141
141
  name = "Microsoft.Targeting";
142
+ #targetingContextAccessor;
143
+ constructor(targetingContextAccessor) {
144
+ this.#targetingContextAccessor = targetingContextAccessor;
145
+ }
142
146
  async evaluate(context, appContext) {
143
147
  const { featureName, parameters } = context;
144
148
  TargetingFilter.#validateParameters(featureName, parameters);
145
- if (appContext === undefined) {
146
- throw new Error("The app context is required for targeting filter.");
149
+ let targetingContext;
150
+ if (appContext?.userId !== undefined || appContext?.groups !== undefined) {
151
+ targetingContext = appContext;
152
+ }
153
+ else if (this.#targetingContextAccessor !== undefined) {
154
+ targetingContext = this.#targetingContextAccessor.getTargetingContext();
147
155
  }
148
156
  if (parameters.Audience.Exclusion !== undefined) {
149
157
  // check if the user is in the exclusion list
150
- if (appContext?.userId !== undefined &&
158
+ if (targetingContext?.userId !== undefined &&
151
159
  parameters.Audience.Exclusion.Users !== undefined &&
152
- parameters.Audience.Exclusion.Users.includes(appContext.userId)) {
160
+ parameters.Audience.Exclusion.Users.includes(targetingContext.userId)) {
153
161
  return false;
154
162
  }
155
163
  // check if the user is in a group within exclusion list
156
- if (appContext?.groups !== undefined &&
164
+ if (targetingContext?.groups !== undefined &&
157
165
  parameters.Audience.Exclusion.Groups !== undefined) {
158
166
  for (const excludedGroup of parameters.Audience.Exclusion.Groups) {
159
- if (appContext.groups.includes(excludedGroup)) {
167
+ if (targetingContext.groups.includes(excludedGroup)) {
160
168
  return false;
161
169
  }
162
170
  }
163
171
  }
164
172
  }
165
173
  // check if the user is being targeted directly
166
- if (appContext?.userId !== undefined &&
174
+ if (targetingContext?.userId !== undefined &&
167
175
  parameters.Audience.Users !== undefined &&
168
- parameters.Audience.Users.includes(appContext.userId)) {
176
+ parameters.Audience.Users.includes(targetingContext.userId)) {
169
177
  return true;
170
178
  }
171
179
  // check if the user is in a group that is being targeted
172
- if (appContext?.groups !== undefined &&
180
+ if (targetingContext?.groups !== undefined &&
173
181
  parameters.Audience.Groups !== undefined) {
174
182
  for (const group of parameters.Audience.Groups) {
175
- if (appContext.groups.includes(group.Name)) {
183
+ if (targetingContext.groups.includes(group.Name)) {
176
184
  const hint = `${featureName}\n${group.Name}`;
177
- if (await isTargetedPercentile(appContext.userId, hint, 0, group.RolloutPercentage)) {
185
+ if (await isTargetedPercentile(targetingContext.userId, hint, 0, group.RolloutPercentage)) {
178
186
  return true;
179
187
  }
180
188
  }
@@ -182,7 +190,7 @@
182
190
  }
183
191
  // check if the user is being targeted by a default rollout percentage
184
192
  const hint = featureName;
185
- return isTargetedPercentile(appContext?.userId, hint, 0, parameters.Audience.DefaultRolloutPercentage);
193
+ return isTargetedPercentile(targetingContext?.userId, hint, 0, parameters.Audience.DefaultRolloutPercentage);
186
194
  }
187
195
  static #validateParameters(featureName, parameters) {
188
196
  if (parameters.Audience.DefaultRolloutPercentage < 0 || parameters.Audience.DefaultRolloutPercentage > 100) {
@@ -216,14 +224,16 @@
216
224
  #provider;
217
225
  #featureFilters = new Map();
218
226
  #onFeatureEvaluated;
227
+ #targetingContextAccessor;
219
228
  constructor(provider, options) {
220
229
  this.#provider = provider;
221
- const builtinFilters = [new TimeWindowFilter(), new TargetingFilter()];
230
+ this.#onFeatureEvaluated = options?.onFeatureEvaluated;
231
+ this.#targetingContextAccessor = options?.targetingContextAccessor;
232
+ const builtinFilters = [new TimeWindowFilter(), new TargetingFilter(options?.targetingContextAccessor)];
222
233
  // If a custom filter shares a name with an existing filter, the custom filter overrides the existing one.
223
234
  for (const filter of [...builtinFilters, ...(options?.customFilters ?? [])]) {
224
235
  this.#featureFilters.set(filter.name, filter);
225
236
  }
226
- this.#onFeatureEvaluated = options?.onFeatureEvaluated;
227
237
  }
228
238
  async listFeatureNames() {
229
239
  const features = await this.#provider.getFeatureFlags();
@@ -267,7 +277,7 @@
267
277
  }
268
278
  return { variant: undefined, reason: exports.VariantAssignmentReason.None };
269
279
  }
270
- async #isEnabled(featureFlag, context) {
280
+ async #isEnabled(featureFlag, appContext) {
271
281
  if (featureFlag.enabled !== true) {
272
282
  // If the feature is not explicitly enabled, then it is disabled by default.
273
283
  return false;
@@ -291,14 +301,14 @@
291
301
  console.warn(`Feature filter ${clientFilter.name} is not found.`);
292
302
  return false;
293
303
  }
294
- if (await matchedFeatureFilter.evaluate(contextWithFeatureName, context) === shortCircuitEvaluationResult) {
304
+ if (await matchedFeatureFilter.evaluate(contextWithFeatureName, appContext) === shortCircuitEvaluationResult) {
295
305
  return shortCircuitEvaluationResult;
296
306
  }
297
307
  }
298
308
  // If we get here, then we have not found a client filter that matches the requirement type.
299
309
  return !shortCircuitEvaluationResult;
300
310
  }
301
- async #evaluateFeature(featureName, context) {
311
+ async #evaluateFeature(featureName, appContext) {
302
312
  const featureFlag = await this.#provider.getFeatureFlag(featureName);
303
313
  const result = new EvaluationResult(featureFlag);
304
314
  if (featureFlag === undefined) {
@@ -308,8 +318,9 @@
308
318
  // TODO: move to the feature flag provider implementation.
309
319
  validateFeatureFlagFormat(featureFlag);
310
320
  // Evaluate if the feature is enabled.
311
- result.enabled = await this.#isEnabled(featureFlag, context);
312
- const targetingContext = context;
321
+ result.enabled = await this.#isEnabled(featureFlag, appContext);
322
+ // Get targeting context from the app context or the targeting context accessor
323
+ const targetingContext = this.#getTargetingContext(appContext);
313
324
  result.targetingId = targetingContext?.userId;
314
325
  // Determine Variant
315
326
  let variantDef;
@@ -330,7 +341,7 @@
330
341
  }
331
342
  else {
332
343
  // enabled, assign based on allocation
333
- if (context !== undefined && featureFlag.allocation !== undefined) {
344
+ if (targetingContext !== undefined && featureFlag.allocation !== undefined) {
334
345
  const variantAndReason = await this.#assignVariant(featureFlag, targetingContext);
335
346
  variantDef = variantAndReason.variant;
336
347
  reason = variantAndReason.reason;
@@ -365,6 +376,15 @@
365
376
  }
366
377
  return result;
367
378
  }
379
+ #getTargetingContext(context) {
380
+ let targetingContext = context;
381
+ if (targetingContext?.userId === undefined &&
382
+ targetingContext?.groups === undefined &&
383
+ this.#targetingContextAccessor !== undefined) {
384
+ targetingContext = this.#targetingContextAccessor.getTargetingContext();
385
+ }
386
+ return targetingContext;
387
+ }
368
388
  }
369
389
  class EvaluationResult {
370
390
  feature;
@@ -676,7 +696,7 @@
676
696
 
677
697
  // Copyright (c) Microsoft Corporation.
678
698
  // Licensed under the MIT license.
679
- const VERSION$1 = "2.0.2";
699
+ const VERSION$1 = "2.1.0-preview.1";
680
700
  const EVALUATION_EVENT_VERSION = "1.0.0";
681
701
 
682
702
  // Copyright (c) Microsoft Corporation.
@@ -687,6 +707,8 @@
687
707
  const TARGETING_ID = "TargetingId";
688
708
  const VARIANT = "Variant";
689
709
  const VARIANT_ASSIGNMENT_REASON = "VariantAssignmentReason";
710
+ const DEFAULT_WHEN_ENABLED = "DefaultWhenEnabled";
711
+ const VARIANT_ASSIGNMENT_PERCENTAGE = "VariantAssignmentPercentage";
690
712
  function createFeatureEvaluationEventProperties(result) {
691
713
  if (result.feature === undefined) {
692
714
  return undefined;
@@ -700,6 +722,29 @@
700
722
  [VARIANT]: result.variant ? result.variant.name : "",
701
723
  [VARIANT_ASSIGNMENT_REASON]: result.variantAssignmentReason,
702
724
  };
725
+ if (result.feature.allocation?.default_when_enabled) {
726
+ eventProperties[DEFAULT_WHEN_ENABLED] = result.feature.allocation.default_when_enabled;
727
+ }
728
+ if (result.variantAssignmentReason === exports.VariantAssignmentReason.DefaultWhenEnabled) {
729
+ let percentileAllocationPercentage = 0;
730
+ if (result.variant !== undefined && result.feature.allocation !== undefined && result.feature.allocation.percentile !== undefined) {
731
+ for (const percentile of result.feature.allocation.percentile) {
732
+ percentileAllocationPercentage += percentile.to - percentile.from;
733
+ }
734
+ }
735
+ eventProperties[VARIANT_ASSIGNMENT_PERCENTAGE] = (100 - percentileAllocationPercentage).toString();
736
+ }
737
+ else if (result.variantAssignmentReason === exports.VariantAssignmentReason.Percentile) {
738
+ let percentileAllocationPercentage = 0;
739
+ if (result.variant !== undefined && result.feature.allocation !== undefined && result.feature.allocation.percentile !== undefined) {
740
+ for (const percentile of result.feature.allocation.percentile) {
741
+ if (percentile.variant === result.variant.name) {
742
+ percentileAllocationPercentage += percentile.to - percentile.from;
743
+ }
744
+ }
745
+ }
746
+ eventProperties[VARIANT_ASSIGNMENT_PERCENTAGE] = percentileAllocationPercentage.toString();
747
+ }
703
748
  const metadata = result.feature.telemetry?.metadata;
704
749
  if (metadata) {
705
750
  for (const key in metadata) {