@microsoft/feature-management 2.0.0-preview.3 → 2.0.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.
- package/README.md +15 -7
- package/dist/commonjs/featureProvider.js +10 -6
- package/dist/commonjs/featureProvider.js.map +1 -1
- package/dist/commonjs/filter/TargetingFilter.js +4 -4
- package/dist/commonjs/filter/TargetingFilter.js.map +1 -1
- package/dist/commonjs/schema/validator.js +48 -48
- package/dist/commonjs/schema/validator.js.map +1 -1
- package/dist/commonjs/telemetry/featureEvaluationEvent.js +0 -26
- package/dist/commonjs/telemetry/featureEvaluationEvent.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/commonjs/version.js.map +1 -1
- package/dist/esm/featureProvider.js +10 -6
- package/dist/esm/featureProvider.js.map +1 -1
- package/dist/esm/filter/TargetingFilter.js +4 -4
- package/dist/esm/filter/TargetingFilter.js.map +1 -1
- package/dist/esm/schema/validator.js +48 -48
- package/dist/esm/schema/validator.js.map +1 -1
- package/dist/esm/telemetry/featureEvaluationEvent.js +0 -26
- package/dist/esm/telemetry/featureEvaluationEvent.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/umd/index.js +63 -84
- package/dist/umd/index.js.map +1 -1
- package/package.json +4 -1
- package/types/index.d.ts +1 -1
package/dist/umd/index.js
CHANGED
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
name = "Microsoft.Targeting";
|
|
142
142
|
async evaluate(context, appContext) {
|
|
143
143
|
const { featureName, parameters } = context;
|
|
144
|
-
TargetingFilter.#validateParameters(parameters);
|
|
144
|
+
TargetingFilter.#validateParameters(featureName, parameters);
|
|
145
145
|
if (appContext === undefined) {
|
|
146
146
|
throw new Error("The app context is required for targeting filter.");
|
|
147
147
|
}
|
|
@@ -184,15 +184,15 @@
|
|
|
184
184
|
const hint = featureName;
|
|
185
185
|
return isTargetedPercentile(appContext?.userId, hint, 0, parameters.Audience.DefaultRolloutPercentage);
|
|
186
186
|
}
|
|
187
|
-
static #validateParameters(parameters) {
|
|
187
|
+
static #validateParameters(featureName, parameters) {
|
|
188
188
|
if (parameters.Audience.DefaultRolloutPercentage < 0 || parameters.Audience.DefaultRolloutPercentage > 100) {
|
|
189
|
-
throw new Error(
|
|
189
|
+
throw new Error(`Invalid feature flag: ${featureName}. Audience.DefaultRolloutPercentage must be a number between 0 and 100.`);
|
|
190
190
|
}
|
|
191
191
|
// validate RolloutPercentage for each group
|
|
192
192
|
if (parameters.Audience.Groups !== undefined) {
|
|
193
193
|
for (const group of parameters.Audience.Groups) {
|
|
194
194
|
if (group.RolloutPercentage < 0 || group.RolloutPercentage > 100) {
|
|
195
|
-
throw new Error(`RolloutPercentage of group ${group.Name} must be a number between 0 and 100.`);
|
|
195
|
+
throw new Error(`Invalid feature flag: ${featureName}. RolloutPercentage of group ${group.Name} must be a number between 0 and 100.`);
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
}
|
|
@@ -471,157 +471,157 @@
|
|
|
471
471
|
throw new TypeError("Feature flag 'id' must be a string.");
|
|
472
472
|
}
|
|
473
473
|
if (featureFlag.enabled !== undefined && typeof featureFlag.enabled !== "boolean") {
|
|
474
|
-
throw new TypeError(
|
|
474
|
+
throw new TypeError(`Invalid feature flag: ${featureFlag.id}. Feature flag 'enabled' must be a boolean.`);
|
|
475
475
|
}
|
|
476
476
|
if (featureFlag.conditions !== undefined) {
|
|
477
|
-
validateFeatureEnablementConditions(featureFlag.conditions);
|
|
477
|
+
validateFeatureEnablementConditions(featureFlag.id, featureFlag.conditions);
|
|
478
478
|
}
|
|
479
479
|
if (featureFlag.variants !== undefined) {
|
|
480
|
-
validateVariants(featureFlag.variants);
|
|
480
|
+
validateVariants(featureFlag.id, featureFlag.variants);
|
|
481
481
|
}
|
|
482
482
|
if (featureFlag.allocation !== undefined) {
|
|
483
|
-
validateVariantAllocation(featureFlag.allocation);
|
|
483
|
+
validateVariantAllocation(featureFlag.id, featureFlag.allocation);
|
|
484
484
|
}
|
|
485
485
|
if (featureFlag.telemetry !== undefined) {
|
|
486
|
-
validateTelemetryOptions(featureFlag.telemetry);
|
|
486
|
+
validateTelemetryOptions(featureFlag.id, featureFlag.telemetry);
|
|
487
487
|
}
|
|
488
488
|
}
|
|
489
|
-
function validateFeatureEnablementConditions(conditions) {
|
|
489
|
+
function validateFeatureEnablementConditions(id, conditions) {
|
|
490
490
|
if (typeof conditions !== "object") {
|
|
491
|
-
throw new TypeError(
|
|
491
|
+
throw new TypeError(`Invalid feature flag: ${id}. Feature flag 'conditions' must be an object.`);
|
|
492
492
|
}
|
|
493
493
|
if (conditions.requirement_type !== undefined && conditions.requirement_type !== "Any" && conditions.requirement_type !== "All") {
|
|
494
|
-
throw new TypeError(
|
|
494
|
+
throw new TypeError(`Invalid feature flag: ${id}. 'requirement_type' must be 'Any' or 'All'.`);
|
|
495
495
|
}
|
|
496
496
|
if (conditions.client_filters !== undefined) {
|
|
497
|
-
validateClientFilters(conditions.client_filters);
|
|
497
|
+
validateClientFilters(id, conditions.client_filters);
|
|
498
498
|
}
|
|
499
499
|
}
|
|
500
|
-
function validateClientFilters(client_filters) {
|
|
500
|
+
function validateClientFilters(id, client_filters) {
|
|
501
501
|
if (!Array.isArray(client_filters)) {
|
|
502
|
-
throw new TypeError(
|
|
502
|
+
throw new TypeError(`Invalid feature flag: ${id}. Feature flag conditions 'client_filters' must be an array.`);
|
|
503
503
|
}
|
|
504
504
|
for (const filter of client_filters) {
|
|
505
505
|
if (typeof filter.name !== "string") {
|
|
506
|
-
throw new TypeError(
|
|
506
|
+
throw new TypeError(`Invalid feature flag: ${id}. Client filter 'name' must be a string.`);
|
|
507
507
|
}
|
|
508
508
|
if (filter.parameters !== undefined && typeof filter.parameters !== "object") {
|
|
509
|
-
throw new TypeError(
|
|
509
|
+
throw new TypeError(`Invalid feature flag: ${id}. Client filter 'parameters' must be an object.`);
|
|
510
510
|
}
|
|
511
511
|
}
|
|
512
512
|
}
|
|
513
|
-
function validateVariants(variants) {
|
|
513
|
+
function validateVariants(id, variants) {
|
|
514
514
|
if (!Array.isArray(variants)) {
|
|
515
|
-
throw new TypeError(
|
|
515
|
+
throw new TypeError(`Invalid feature flag: ${id}. Feature flag 'variants' must be an array.`);
|
|
516
516
|
}
|
|
517
517
|
for (const variant of variants) {
|
|
518
518
|
if (typeof variant.name !== "string") {
|
|
519
|
-
throw new TypeError(
|
|
519
|
+
throw new TypeError(`Invalid feature flag: ${id}. Variant 'name' must be a string.`);
|
|
520
520
|
}
|
|
521
521
|
// skip configuration_value validation as it accepts any type
|
|
522
522
|
if (variant.status_override !== undefined && typeof variant.status_override !== "string") {
|
|
523
|
-
throw new TypeError(
|
|
523
|
+
throw new TypeError(`Invalid feature flag: ${id}. Variant 'status_override' must be a string.`);
|
|
524
524
|
}
|
|
525
525
|
if (variant.status_override !== undefined && variant.status_override !== "None" && variant.status_override !== "Enabled" && variant.status_override !== "Disabled") {
|
|
526
|
-
throw new TypeError(
|
|
526
|
+
throw new TypeError(`Invalid feature flag: ${id}. Variant 'status_override' must be 'None', 'Enabled', or 'Disabled'.`);
|
|
527
527
|
}
|
|
528
528
|
}
|
|
529
529
|
}
|
|
530
|
-
function validateVariantAllocation(allocation) {
|
|
530
|
+
function validateVariantAllocation(id, allocation) {
|
|
531
531
|
if (typeof allocation !== "object") {
|
|
532
|
-
throw new TypeError(
|
|
532
|
+
throw new TypeError(`Invalid feature flag: ${id}. Variant 'allocation' must be an object.`);
|
|
533
533
|
}
|
|
534
534
|
if (allocation.default_when_disabled !== undefined && typeof allocation.default_when_disabled !== "string") {
|
|
535
|
-
throw new TypeError(
|
|
535
|
+
throw new TypeError(`Invalid feature flag: ${id}. Variant allocation 'default_when_disabled' must be a string.`);
|
|
536
536
|
}
|
|
537
537
|
if (allocation.default_when_enabled !== undefined && typeof allocation.default_when_enabled !== "string") {
|
|
538
|
-
throw new TypeError(
|
|
538
|
+
throw new TypeError(`Invalid feature flag: ${id}. Variant allocation 'default_when_enabled' must be a string.`);
|
|
539
539
|
}
|
|
540
540
|
if (allocation.user !== undefined) {
|
|
541
|
-
validateUserVariantAllocation(allocation.user);
|
|
541
|
+
validateUserVariantAllocation(id, allocation.user);
|
|
542
542
|
}
|
|
543
543
|
if (allocation.group !== undefined) {
|
|
544
|
-
validateGroupVariantAllocation(allocation.group);
|
|
544
|
+
validateGroupVariantAllocation(id, allocation.group);
|
|
545
545
|
}
|
|
546
546
|
if (allocation.percentile !== undefined) {
|
|
547
|
-
validatePercentileVariantAllocation(allocation.percentile);
|
|
547
|
+
validatePercentileVariantAllocation(id, allocation.percentile);
|
|
548
548
|
}
|
|
549
549
|
if (allocation.seed !== undefined && typeof allocation.seed !== "string") {
|
|
550
|
-
throw new TypeError(
|
|
550
|
+
throw new TypeError(`Invalid feature flag: ${id}. Variant allocation 'seed' must be a string.`);
|
|
551
551
|
}
|
|
552
552
|
}
|
|
553
|
-
function validateUserVariantAllocation(UserAllocations) {
|
|
553
|
+
function validateUserVariantAllocation(id, UserAllocations) {
|
|
554
554
|
if (!Array.isArray(UserAllocations)) {
|
|
555
|
-
throw new TypeError(
|
|
555
|
+
throw new TypeError(`Invalid feature flag: ${id}. Variant 'user' allocation must be an array.`);
|
|
556
556
|
}
|
|
557
557
|
for (const allocation of UserAllocations) {
|
|
558
558
|
if (typeof allocation !== "object") {
|
|
559
|
-
throw new TypeError(
|
|
559
|
+
throw new TypeError(`Invalid feature flag: ${id}. Elements in variant 'user' allocation must be an object.`);
|
|
560
560
|
}
|
|
561
561
|
if (typeof allocation.variant !== "string") {
|
|
562
|
-
throw new TypeError(
|
|
562
|
+
throw new TypeError(`Invalid feature flag: ${id}. User allocation 'variant' must be a string.`);
|
|
563
563
|
}
|
|
564
564
|
if (!Array.isArray(allocation.users)) {
|
|
565
|
-
throw new TypeError(
|
|
565
|
+
throw new TypeError(`Invalid feature flag: ${id}. User allocation 'users' must be an array.`);
|
|
566
566
|
}
|
|
567
567
|
for (const user of allocation.users) {
|
|
568
568
|
if (typeof user !== "string") {
|
|
569
|
-
throw new TypeError(
|
|
569
|
+
throw new TypeError(`Invalid feature flag: ${id}. Elements in user allocation 'users' must be strings.`);
|
|
570
570
|
}
|
|
571
571
|
}
|
|
572
572
|
}
|
|
573
573
|
}
|
|
574
|
-
function validateGroupVariantAllocation(groupAllocations) {
|
|
574
|
+
function validateGroupVariantAllocation(id, groupAllocations) {
|
|
575
575
|
if (!Array.isArray(groupAllocations)) {
|
|
576
|
-
throw new TypeError(
|
|
576
|
+
throw new TypeError(`Invalid feature flag: ${id}. Variant 'group' allocation must be an array.`);
|
|
577
577
|
}
|
|
578
578
|
for (const allocation of groupAllocations) {
|
|
579
579
|
if (typeof allocation !== "object") {
|
|
580
|
-
throw new TypeError(
|
|
580
|
+
throw new TypeError(`Invalid feature flag: ${id}. Elements in variant 'group' allocation must be an object.`);
|
|
581
581
|
}
|
|
582
582
|
if (typeof allocation.variant !== "string") {
|
|
583
|
-
throw new TypeError(
|
|
583
|
+
throw new TypeError(`Invalid feature flag: ${id}. Group allocation 'variant' must be a string.`);
|
|
584
584
|
}
|
|
585
585
|
if (!Array.isArray(allocation.groups)) {
|
|
586
|
-
throw new TypeError(
|
|
586
|
+
throw new TypeError(`Invalid feature flag: ${id}. Group allocation 'groups' must be an array.`);
|
|
587
587
|
}
|
|
588
588
|
for (const group of allocation.groups) {
|
|
589
589
|
if (typeof group !== "string") {
|
|
590
|
-
throw new TypeError(
|
|
590
|
+
throw new TypeError(`Invalid feature flag: ${id}. Elements in group allocation 'groups' must be strings.`);
|
|
591
591
|
}
|
|
592
592
|
}
|
|
593
593
|
}
|
|
594
594
|
}
|
|
595
|
-
function validatePercentileVariantAllocation(percentileAllocations) {
|
|
595
|
+
function validatePercentileVariantAllocation(id, percentileAllocations) {
|
|
596
596
|
if (!Array.isArray(percentileAllocations)) {
|
|
597
|
-
throw new TypeError(
|
|
597
|
+
throw new TypeError(`Invalid feature flag: ${id}. Variant 'percentile' allocation must be an array.`);
|
|
598
598
|
}
|
|
599
599
|
for (const allocation of percentileAllocations) {
|
|
600
600
|
if (typeof allocation !== "object") {
|
|
601
|
-
throw new TypeError(
|
|
601
|
+
throw new TypeError(`Invalid feature flag: ${id}. Elements in variant 'percentile' allocation must be an object.`);
|
|
602
602
|
}
|
|
603
603
|
if (typeof allocation.variant !== "string") {
|
|
604
|
-
throw new TypeError(
|
|
604
|
+
throw new TypeError(`Invalid feature flag: ${id}. Percentile allocation 'variant' must be a string.`);
|
|
605
605
|
}
|
|
606
606
|
if (typeof allocation.from !== "number" || allocation.from < 0 || allocation.from > 100) {
|
|
607
|
-
throw new TypeError(
|
|
607
|
+
throw new TypeError(`Invalid feature flag: ${id}. Percentile allocation 'from' must be a number between 0 and 100.`);
|
|
608
608
|
}
|
|
609
609
|
if (typeof allocation.to !== "number" || allocation.to < 0 || allocation.to > 100) {
|
|
610
|
-
throw new TypeError(
|
|
610
|
+
throw new TypeError(`Invalid feature flag: ${id}. Percentile allocation 'to' must be a number between 0 and 100.`);
|
|
611
611
|
}
|
|
612
612
|
}
|
|
613
613
|
}
|
|
614
614
|
// #endregion
|
|
615
615
|
// #region Telemetry
|
|
616
|
-
function validateTelemetryOptions(telemetry) {
|
|
616
|
+
function validateTelemetryOptions(id, telemetry) {
|
|
617
617
|
if (typeof telemetry !== "object") {
|
|
618
|
-
throw new TypeError(
|
|
618
|
+
throw new TypeError(`Invalid feature flag: ${id}. Feature flag 'telemetry' must be an object.`);
|
|
619
619
|
}
|
|
620
620
|
if (telemetry.enabled !== undefined && typeof telemetry.enabled !== "boolean") {
|
|
621
|
-
throw new TypeError(
|
|
621
|
+
throw new TypeError(`Invalid feature flag: ${id}. Telemetry 'enabled' must be a boolean.`);
|
|
622
622
|
}
|
|
623
623
|
if (telemetry.metadata !== undefined && typeof telemetry.metadata !== "object") {
|
|
624
|
-
throw new TypeError(
|
|
624
|
+
throw new TypeError(`Invalid feature flag: ${id}. Telemetry 'metadata' must be an object.`);
|
|
625
625
|
}
|
|
626
626
|
}
|
|
627
627
|
// #endregion
|
|
@@ -644,9 +644,11 @@
|
|
|
644
644
|
}
|
|
645
645
|
async getFeatureFlags() {
|
|
646
646
|
const featureConfig = this.#configuration.get(FEATURE_MANAGEMENT_KEY);
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
647
|
+
const featureFlags = featureConfig?.[FEATURE_FLAGS_KEY] ?? [];
|
|
648
|
+
featureFlags.forEach(featureFlag => {
|
|
649
|
+
validateFeatureFlag(featureFlag);
|
|
650
|
+
});
|
|
651
|
+
return featureFlags;
|
|
650
652
|
}
|
|
651
653
|
}
|
|
652
654
|
/**
|
|
@@ -664,15 +666,17 @@
|
|
|
664
666
|
return featureFlag;
|
|
665
667
|
}
|
|
666
668
|
async getFeatureFlags() {
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
|
|
669
|
+
const featureFlags = this.#configuration[FEATURE_MANAGEMENT_KEY]?.[FEATURE_FLAGS_KEY] ?? [];
|
|
670
|
+
featureFlags.forEach(featureFlag => {
|
|
671
|
+
validateFeatureFlag(featureFlag);
|
|
672
|
+
});
|
|
673
|
+
return featureFlags;
|
|
670
674
|
}
|
|
671
675
|
}
|
|
672
676
|
|
|
673
677
|
// Copyright (c) Microsoft Corporation.
|
|
674
678
|
// Licensed under the MIT license.
|
|
675
|
-
const VERSION$1 = "2.0.
|
|
679
|
+
const VERSION$1 = "2.0.1";
|
|
676
680
|
const EVALUATION_EVENT_VERSION = "1.0.0";
|
|
677
681
|
|
|
678
682
|
// Copyright (c) Microsoft Corporation.
|
|
@@ -683,8 +687,6 @@
|
|
|
683
687
|
const TARGETING_ID = "TargetingId";
|
|
684
688
|
const VARIANT = "Variant";
|
|
685
689
|
const VARIANT_ASSIGNMENT_REASON = "VariantAssignmentReason";
|
|
686
|
-
const DEFAULT_WHEN_ENABLED = "DefaultWhenEnabled";
|
|
687
|
-
const VARIANT_ASSIGNMENT_PERCENTAGE = "VariantAssignmentPercentage";
|
|
688
690
|
function createFeatureEvaluationEventProperties(result) {
|
|
689
691
|
if (result.feature === undefined) {
|
|
690
692
|
return undefined;
|
|
@@ -698,29 +700,6 @@
|
|
|
698
700
|
[VARIANT]: result.variant ? result.variant.name : "",
|
|
699
701
|
[VARIANT_ASSIGNMENT_REASON]: result.variantAssignmentReason,
|
|
700
702
|
};
|
|
701
|
-
if (result.feature.allocation?.default_when_enabled) {
|
|
702
|
-
eventProperties[DEFAULT_WHEN_ENABLED] = result.feature.allocation.default_when_enabled;
|
|
703
|
-
}
|
|
704
|
-
if (result.variantAssignmentReason === exports.VariantAssignmentReason.DefaultWhenEnabled) {
|
|
705
|
-
let percentileAllocationPercentage = 0;
|
|
706
|
-
if (result.variant !== undefined && result.feature.allocation !== undefined && result.feature.allocation.percentile !== undefined) {
|
|
707
|
-
for (const percentile of result.feature.allocation.percentile) {
|
|
708
|
-
percentileAllocationPercentage += percentile.to - percentile.from;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
eventProperties[VARIANT_ASSIGNMENT_PERCENTAGE] = (100 - percentileAllocationPercentage).toString();
|
|
712
|
-
}
|
|
713
|
-
else if (result.variantAssignmentReason === exports.VariantAssignmentReason.Percentile) {
|
|
714
|
-
let percentileAllocationPercentage = 0;
|
|
715
|
-
if (result.variant !== undefined && result.feature.allocation !== undefined && result.feature.allocation.percentile !== undefined) {
|
|
716
|
-
for (const percentile of result.feature.allocation.percentile) {
|
|
717
|
-
if (percentile.variant === result.variant.name) {
|
|
718
|
-
percentileAllocationPercentage += percentile.to - percentile.from;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
eventProperties[VARIANT_ASSIGNMENT_PERCENTAGE] = percentileAllocationPercentage.toString();
|
|
723
|
-
}
|
|
724
703
|
const metadata = result.feature.telemetry?.metadata;
|
|
725
704
|
if (metadata) {
|
|
726
705
|
for (const key in metadata) {
|