@featurevisor/sdk 0.34.0 → 0.35.0
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/CHANGELOG.md +11 -0
- package/coverage/clover.xml +333 -322
- package/coverage/coverage-final.json +2 -2
- package/coverage/lcov-report/bucket.ts.html +1 -1
- package/coverage/lcov-report/conditions.ts.html +1 -1
- package/coverage/lcov-report/datafileReader.ts.html +1 -1
- package/coverage/lcov-report/emitter.ts.html +1 -1
- package/coverage/lcov-report/feature.ts.html +13 -805
- package/coverage/lcov-report/index.html +28 -28
- package/coverage/lcov-report/instance.ts.html +1018 -109
- package/coverage/lcov-report/logger.ts.html +1 -1
- package/coverage/lcov-report/segments.ts.html +1 -1
- package/coverage/lcov.info +603 -578
- package/dist/index.js +1 -1
- package/dist/index.js.gz +0 -0
- package/dist/index.js.map +1 -1
- package/lib/feature.d.ts +2 -9
- package/lib/feature.js +1 -169
- package/lib/feature.js.map +1 -1
- package/lib/instance.d.ts +26 -1
- package/lib/instance.js +313 -81
- package/lib/instance.js.map +1 -1
- package/package.json +3 -3
- package/src/feature.ts +2 -266
- package/src/instance.ts +398 -95
package/src/instance.ts
CHANGED
|
@@ -12,18 +12,19 @@ import {
|
|
|
12
12
|
VariableValue,
|
|
13
13
|
VariationType,
|
|
14
14
|
VariationValue,
|
|
15
|
+
Variation,
|
|
16
|
+
RuleKey,
|
|
17
|
+
VariableKey,
|
|
18
|
+
VariableSchema,
|
|
15
19
|
} from "@featurevisor/types";
|
|
16
20
|
|
|
17
21
|
import { createLogger, Logger } from "./logger";
|
|
18
22
|
import { DatafileReader } from "./datafileReader";
|
|
19
23
|
import { Emitter } from "./emitter";
|
|
20
24
|
import { getBucketedNumber } from "./bucket";
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
getForcedVariation,
|
|
25
|
-
getForcedVariableValue,
|
|
26
|
-
} from "./feature";
|
|
25
|
+
import { findForceFromFeature, getMatchedTrafficAndAllocation } from "./feature";
|
|
26
|
+
import { allConditionsAreMatched } from "./conditions";
|
|
27
|
+
import { allGroupSegmentsAreMatched } from "./segments";
|
|
27
28
|
|
|
28
29
|
export type ReadyCallback = () => void;
|
|
29
30
|
|
|
@@ -73,6 +74,38 @@ const emptyDatafile: DatafileContent = {
|
|
|
73
74
|
|
|
74
75
|
export type DatafileFetchHandler = (datafileUrl: string) => Promise<DatafileContent>;
|
|
75
76
|
|
|
77
|
+
export enum EvaluationReason {
|
|
78
|
+
NOT_FOUND = "not_found",
|
|
79
|
+
FORCED = "forced",
|
|
80
|
+
INITIAL = "initial",
|
|
81
|
+
STICKY = "sticky",
|
|
82
|
+
RULE = "rule",
|
|
83
|
+
ALLOCATED = "allocated",
|
|
84
|
+
DEFAULTED = "defaulted",
|
|
85
|
+
OVERRIDE = "override",
|
|
86
|
+
ERROR = "error",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface Evaluation {
|
|
90
|
+
// required
|
|
91
|
+
featureKey: FeatureKey;
|
|
92
|
+
reason: EvaluationReason;
|
|
93
|
+
|
|
94
|
+
// common
|
|
95
|
+
bucketValue?: BucketValue;
|
|
96
|
+
ruleKey?: RuleKey;
|
|
97
|
+
error?: Error;
|
|
98
|
+
|
|
99
|
+
// variation
|
|
100
|
+
variation?: Variation;
|
|
101
|
+
variationValue?: VariationValue;
|
|
102
|
+
|
|
103
|
+
// variable
|
|
104
|
+
variableKey?: VariableKey;
|
|
105
|
+
variableValue?: VariableValue;
|
|
106
|
+
variableSchema?: VariableSchema;
|
|
107
|
+
}
|
|
108
|
+
|
|
76
109
|
function fetchDatafileContent(
|
|
77
110
|
datafileUrl,
|
|
78
111
|
handleDatafileFetch?: DatafileFetchHandler,
|
|
@@ -88,26 +121,30 @@ type FieldType = VariationType | VariableType;
|
|
|
88
121
|
type ValueType = VariableValue;
|
|
89
122
|
|
|
90
123
|
export function getValueByType(value: ValueType, fieldType: FieldType): ValueType {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
124
|
+
try {
|
|
125
|
+
if (value === undefined) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
94
128
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
129
|
+
switch (fieldType) {
|
|
130
|
+
case "string":
|
|
131
|
+
return typeof value === "string" ? value : undefined;
|
|
132
|
+
case "integer":
|
|
133
|
+
return parseInt(value as string, 10);
|
|
134
|
+
case "double":
|
|
135
|
+
return parseFloat(value as string);
|
|
136
|
+
case "boolean":
|
|
137
|
+
return value === true;
|
|
138
|
+
case "array":
|
|
139
|
+
return Array.isArray(value) ? value : undefined;
|
|
140
|
+
case "object":
|
|
141
|
+
return typeof value === "object" ? value : undefined;
|
|
142
|
+
// @NOTE: `json` is not handled here intentionally
|
|
143
|
+
default:
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
return undefined;
|
|
111
148
|
}
|
|
112
149
|
}
|
|
113
150
|
|
|
@@ -382,89 +419,190 @@ export class FeaturevisorInstance {
|
|
|
382
419
|
/**
|
|
383
420
|
* Variation
|
|
384
421
|
*/
|
|
422
|
+
evaluateVariation(featureKey: FeatureKey | Feature, attributes: Attributes = {}): Evaluation {
|
|
423
|
+
let evaluation: Evaluation;
|
|
385
424
|
|
|
386
|
-
getVariation(
|
|
387
|
-
featureKey: FeatureKey | Feature,
|
|
388
|
-
attributes: Attributes = {},
|
|
389
|
-
): VariationValue | undefined {
|
|
390
425
|
try {
|
|
391
426
|
const key = typeof featureKey === "string" ? featureKey : featureKey.key;
|
|
392
427
|
|
|
428
|
+
// sticky
|
|
393
429
|
if (this.stickyFeatures && this.stickyFeatures[key]) {
|
|
394
|
-
const
|
|
430
|
+
const variationValue = this.stickyFeatures[key].variation;
|
|
395
431
|
|
|
396
|
-
if (typeof
|
|
397
|
-
|
|
432
|
+
if (typeof variationValue !== "undefined") {
|
|
433
|
+
evaluation = {
|
|
398
434
|
featureKey: key,
|
|
399
|
-
|
|
400
|
-
|
|
435
|
+
reason: EvaluationReason.STICKY,
|
|
436
|
+
variationValue,
|
|
437
|
+
};
|
|
401
438
|
|
|
402
|
-
|
|
439
|
+
this.logger.debug("using sticky variation", evaluation);
|
|
440
|
+
|
|
441
|
+
return evaluation;
|
|
403
442
|
}
|
|
404
443
|
}
|
|
405
444
|
|
|
445
|
+
// initial
|
|
406
446
|
if (
|
|
407
447
|
this.statuses &&
|
|
408
448
|
!this.statuses.ready &&
|
|
409
449
|
this.initialFeatures &&
|
|
410
|
-
this.initialFeatures[key]
|
|
450
|
+
this.initialFeatures[key] &&
|
|
451
|
+
typeof this.initialFeatures[key].variation !== "undefined"
|
|
411
452
|
) {
|
|
412
|
-
const
|
|
453
|
+
const variationValue = this.initialFeatures[key].variation;
|
|
413
454
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
455
|
+
evaluation = {
|
|
456
|
+
featureKey: key,
|
|
457
|
+
reason: EvaluationReason.INITIAL,
|
|
458
|
+
variationValue,
|
|
459
|
+
};
|
|
419
460
|
|
|
420
|
-
|
|
421
|
-
|
|
461
|
+
this.logger.debug("using initial variation", evaluation);
|
|
462
|
+
|
|
463
|
+
return evaluation;
|
|
422
464
|
}
|
|
423
465
|
|
|
424
466
|
const feature = this.getFeature(featureKey);
|
|
425
467
|
|
|
468
|
+
// not found
|
|
426
469
|
if (!feature) {
|
|
427
|
-
|
|
470
|
+
evaluation = {
|
|
471
|
+
featureKey: key,
|
|
472
|
+
reason: EvaluationReason.NOT_FOUND,
|
|
473
|
+
};
|
|
428
474
|
|
|
429
|
-
|
|
475
|
+
this.logger.warn("feature not found in datafile", evaluation);
|
|
476
|
+
|
|
477
|
+
return evaluation;
|
|
430
478
|
}
|
|
431
479
|
|
|
432
480
|
const finalAttributes = this.interceptAttributes
|
|
433
481
|
? this.interceptAttributes(attributes)
|
|
434
482
|
: attributes;
|
|
435
483
|
|
|
436
|
-
|
|
484
|
+
// forced
|
|
485
|
+
const force = findForceFromFeature(feature, attributes, this.datafileReader);
|
|
437
486
|
|
|
438
|
-
if (
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
487
|
+
if (force && force.variation) {
|
|
488
|
+
const variation = feature.variations.find((v) => v.value === force.variation);
|
|
489
|
+
|
|
490
|
+
if (variation) {
|
|
491
|
+
evaluation = {
|
|
492
|
+
featureKey: feature.key,
|
|
493
|
+
reason: EvaluationReason.FORCED,
|
|
494
|
+
variation,
|
|
495
|
+
};
|
|
443
496
|
|
|
444
|
-
|
|
497
|
+
this.logger.debug("forced variation found", evaluation);
|
|
498
|
+
|
|
499
|
+
return evaluation;
|
|
500
|
+
}
|
|
445
501
|
}
|
|
446
502
|
|
|
503
|
+
// bucketing
|
|
447
504
|
const bucketValue = this.getBucketValue(feature, finalAttributes);
|
|
448
505
|
|
|
449
|
-
const
|
|
450
|
-
feature,
|
|
506
|
+
const { matchedTraffic, matchedAllocation } = getMatchedTrafficAndAllocation(
|
|
507
|
+
feature.traffic,
|
|
451
508
|
finalAttributes,
|
|
452
509
|
bucketValue,
|
|
453
510
|
this.datafileReader,
|
|
454
511
|
this.logger,
|
|
455
512
|
);
|
|
456
513
|
|
|
457
|
-
if (
|
|
458
|
-
|
|
459
|
-
|
|
514
|
+
if (matchedTraffic) {
|
|
515
|
+
// override from rule
|
|
516
|
+
if (matchedTraffic.variation) {
|
|
517
|
+
const variation = feature.variations.find((v) => v.value === matchedTraffic.variation);
|
|
518
|
+
|
|
519
|
+
if (variation) {
|
|
520
|
+
evaluation = {
|
|
521
|
+
featureKey: feature.key,
|
|
522
|
+
reason: EvaluationReason.RULE,
|
|
523
|
+
variation,
|
|
524
|
+
bucketValue,
|
|
525
|
+
ruleKey: matchedTraffic.key,
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
this.logger.debug("override from rule", evaluation);
|
|
529
|
+
|
|
530
|
+
return evaluation;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// regular allocation
|
|
535
|
+
if (matchedAllocation && matchedAllocation.variation) {
|
|
536
|
+
const variation = feature.variations.find((v) => v.value === matchedAllocation.variation);
|
|
537
|
+
|
|
538
|
+
if (variation) {
|
|
539
|
+
evaluation = {
|
|
540
|
+
featureKey: feature.key,
|
|
541
|
+
reason: EvaluationReason.ALLOCATED,
|
|
542
|
+
bucketValue,
|
|
543
|
+
variation,
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
this.logger.debug("allocated variation", evaluation);
|
|
547
|
+
|
|
548
|
+
return evaluation;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// fall back to default
|
|
554
|
+
const variation = feature.variations.find((v) => v.value === feature.defaultVariation);
|
|
555
|
+
|
|
556
|
+
if (variation) {
|
|
557
|
+
evaluation = {
|
|
558
|
+
featureKey: feature.key,
|
|
559
|
+
reason: EvaluationReason.DEFAULTED,
|
|
460
560
|
bucketValue,
|
|
461
|
-
variation
|
|
462
|
-
}
|
|
561
|
+
variation,
|
|
562
|
+
};
|
|
463
563
|
|
|
464
|
-
|
|
564
|
+
this.logger.debug("using default variation", evaluation);
|
|
565
|
+
|
|
566
|
+
return evaluation;
|
|
465
567
|
}
|
|
466
568
|
|
|
467
|
-
|
|
569
|
+
// nothing matched (this should never happen)
|
|
570
|
+
evaluation = {
|
|
571
|
+
featureKey: feature.key,
|
|
572
|
+
reason: EvaluationReason.ERROR,
|
|
573
|
+
bucketValue,
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
this.logger.error("no matched variation", evaluation);
|
|
577
|
+
|
|
578
|
+
return evaluation;
|
|
579
|
+
} catch (e) {
|
|
580
|
+
evaluation = {
|
|
581
|
+
featureKey: typeof featureKey === "string" ? featureKey : featureKey.key,
|
|
582
|
+
reason: EvaluationReason.ERROR,
|
|
583
|
+
error: e,
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
return evaluation;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
getVariation(
|
|
591
|
+
featureKey: FeatureKey | Feature,
|
|
592
|
+
attributes: Attributes = {},
|
|
593
|
+
): VariationValue | undefined {
|
|
594
|
+
try {
|
|
595
|
+
const evaluation = this.evaluateVariation(featureKey, attributes);
|
|
596
|
+
|
|
597
|
+
if (typeof evaluation.variationValue !== "undefined") {
|
|
598
|
+
return evaluation.variationValue;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (evaluation.variation) {
|
|
602
|
+
return evaluation.variation.value;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return undefined;
|
|
468
606
|
} catch (e) {
|
|
469
607
|
this.logger.error("getVariation", { featureKey, error: e });
|
|
470
608
|
|
|
@@ -513,7 +651,10 @@ export class FeaturevisorInstance {
|
|
|
513
651
|
*/
|
|
514
652
|
activate(featureKey: FeatureKey, attributes: Attributes = {}): VariationValue | undefined {
|
|
515
653
|
try {
|
|
516
|
-
const
|
|
654
|
+
const evaluation = this.evaluateVariation(featureKey, attributes);
|
|
655
|
+
const variationValue = evaluation.variation
|
|
656
|
+
? evaluation.variation.value
|
|
657
|
+
: evaluation.variationValue;
|
|
517
658
|
|
|
518
659
|
if (typeof variationValue === "undefined") {
|
|
519
660
|
return undefined;
|
|
@@ -541,6 +682,7 @@ export class FeaturevisorInstance {
|
|
|
541
682
|
variationValue,
|
|
542
683
|
finalAttributes,
|
|
543
684
|
captureAttributes,
|
|
685
|
+
evaluation,
|
|
544
686
|
);
|
|
545
687
|
|
|
546
688
|
return variationValue;
|
|
@@ -578,92 +720,253 @@ export class FeaturevisorInstance {
|
|
|
578
720
|
/**
|
|
579
721
|
* Variable
|
|
580
722
|
*/
|
|
581
|
-
|
|
582
|
-
getVariable(
|
|
723
|
+
evaluateVariable(
|
|
583
724
|
featureKey: FeatureKey | Feature,
|
|
584
|
-
variableKey:
|
|
725
|
+
variableKey: VariableKey,
|
|
585
726
|
attributes: Attributes = {},
|
|
586
|
-
):
|
|
727
|
+
): Evaluation {
|
|
728
|
+
let evaluation: Evaluation;
|
|
729
|
+
|
|
587
730
|
try {
|
|
588
731
|
const key = typeof featureKey === "string" ? featureKey : featureKey.key;
|
|
589
732
|
|
|
733
|
+
// sticky
|
|
590
734
|
if (this.stickyFeatures && this.stickyFeatures[key] && this.stickyFeatures[key].variables) {
|
|
591
735
|
const result = this.stickyFeatures[key].variables[variableKey];
|
|
592
736
|
|
|
593
737
|
if (typeof result !== "undefined") {
|
|
594
|
-
|
|
738
|
+
evaluation = {
|
|
595
739
|
featureKey: key,
|
|
740
|
+
reason: EvaluationReason.STICKY,
|
|
596
741
|
variableKey,
|
|
597
|
-
|
|
742
|
+
variableValue: result,
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
this.logger.debug("using sticky variable", evaluation);
|
|
598
746
|
|
|
599
|
-
return
|
|
747
|
+
return evaluation;
|
|
600
748
|
}
|
|
601
749
|
}
|
|
602
750
|
|
|
751
|
+
// initial
|
|
603
752
|
if (
|
|
604
753
|
this.statuses &&
|
|
605
754
|
!this.statuses.ready &&
|
|
606
755
|
this.initialFeatures &&
|
|
607
756
|
this.initialFeatures[key] &&
|
|
608
|
-
this.initialFeatures[key].variables
|
|
757
|
+
this.initialFeatures[key].variables &&
|
|
758
|
+
typeof this.initialFeatures[key].variables[variableKey] !== "undefined"
|
|
609
759
|
) {
|
|
610
|
-
|
|
760
|
+
evaluation = {
|
|
761
|
+
featureKey: key,
|
|
762
|
+
reason: EvaluationReason.INITIAL,
|
|
763
|
+
variableKey,
|
|
764
|
+
variableValue: this.initialFeatures[key].variables[variableKey],
|
|
765
|
+
};
|
|
611
766
|
|
|
612
|
-
|
|
613
|
-
this.logger.debug("using initial variable", {
|
|
614
|
-
featureKey: key,
|
|
615
|
-
variableKey,
|
|
616
|
-
});
|
|
767
|
+
this.logger.debug("using initial variable", evaluation);
|
|
617
768
|
|
|
618
|
-
|
|
619
|
-
}
|
|
769
|
+
return evaluation;
|
|
620
770
|
}
|
|
621
771
|
|
|
622
772
|
const feature = this.getFeature(featureKey);
|
|
623
773
|
|
|
774
|
+
// not found
|
|
624
775
|
if (!feature) {
|
|
625
|
-
|
|
776
|
+
evaluation = {
|
|
777
|
+
featureKey: key,
|
|
778
|
+
reason: EvaluationReason.NOT_FOUND,
|
|
779
|
+
variableKey,
|
|
780
|
+
};
|
|
626
781
|
|
|
627
|
-
|
|
782
|
+
this.logger.warn("feature not found in datafile", evaluation);
|
|
783
|
+
|
|
784
|
+
return evaluation;
|
|
628
785
|
}
|
|
629
786
|
|
|
630
787
|
const variableSchema = Array.isArray(feature.variablesSchema)
|
|
631
788
|
? feature.variablesSchema.find((v) => v.key === variableKey)
|
|
632
789
|
: undefined;
|
|
633
790
|
|
|
791
|
+
// variable schema not found
|
|
634
792
|
if (!variableSchema) {
|
|
635
|
-
|
|
793
|
+
evaluation = {
|
|
794
|
+
featureKey: key,
|
|
795
|
+
reason: EvaluationReason.NOT_FOUND,
|
|
796
|
+
variableKey,
|
|
797
|
+
};
|
|
636
798
|
|
|
637
|
-
|
|
799
|
+
this.logger.warn("variable schema not found", evaluation);
|
|
800
|
+
|
|
801
|
+
return evaluation;
|
|
638
802
|
}
|
|
639
803
|
|
|
640
804
|
const finalAttributes = this.interceptAttributes
|
|
641
805
|
? this.interceptAttributes(attributes)
|
|
642
806
|
: attributes;
|
|
643
807
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
variableSchema,
|
|
647
|
-
finalAttributes,
|
|
648
|
-
this.datafileReader,
|
|
649
|
-
);
|
|
808
|
+
// forced
|
|
809
|
+
const force = findForceFromFeature(feature, attributes, this.datafileReader);
|
|
650
810
|
|
|
651
|
-
if (typeof
|
|
652
|
-
|
|
811
|
+
if (force && force.variables && typeof force.variables[variableKey] !== "undefined") {
|
|
812
|
+
evaluation = {
|
|
813
|
+
featureKey: feature.key,
|
|
814
|
+
reason: EvaluationReason.FORCED,
|
|
815
|
+
variableKey,
|
|
816
|
+
variableSchema,
|
|
817
|
+
variableValue: force.variables[variableKey],
|
|
818
|
+
};
|
|
653
819
|
|
|
654
|
-
|
|
820
|
+
this.logger.debug("forced variable", evaluation);
|
|
821
|
+
|
|
822
|
+
return evaluation;
|
|
655
823
|
}
|
|
656
824
|
|
|
825
|
+
// bucketing
|
|
657
826
|
const bucketValue = this.getBucketValue(feature, finalAttributes);
|
|
658
827
|
|
|
659
|
-
|
|
660
|
-
feature,
|
|
661
|
-
variableSchema,
|
|
828
|
+
const { matchedTraffic, matchedAllocation } = getMatchedTrafficAndAllocation(
|
|
829
|
+
feature.traffic,
|
|
662
830
|
finalAttributes,
|
|
663
831
|
bucketValue,
|
|
664
832
|
this.datafileReader,
|
|
665
833
|
this.logger,
|
|
666
834
|
);
|
|
835
|
+
|
|
836
|
+
if (matchedTraffic) {
|
|
837
|
+
// override from rule
|
|
838
|
+
if (
|
|
839
|
+
matchedTraffic.variables &&
|
|
840
|
+
typeof matchedTraffic.variables[variableKey] !== "undefined"
|
|
841
|
+
) {
|
|
842
|
+
evaluation = {
|
|
843
|
+
featureKey: feature.key,
|
|
844
|
+
reason: EvaluationReason.RULE,
|
|
845
|
+
variableKey,
|
|
846
|
+
variableSchema,
|
|
847
|
+
variableValue: matchedTraffic.variables[variableKey],
|
|
848
|
+
bucketValue,
|
|
849
|
+
ruleKey: matchedTraffic.key,
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
this.logger.debug("override from rule", evaluation);
|
|
853
|
+
|
|
854
|
+
return evaluation;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// regular allocation
|
|
858
|
+
if (matchedAllocation && matchedAllocation.variation) {
|
|
859
|
+
const variation = feature.variations.find((v) => v.value === matchedAllocation.variation);
|
|
860
|
+
|
|
861
|
+
if (variation && variation.variables) {
|
|
862
|
+
const variableFromVariation = variation.variables.find((v) => v.key === variableKey);
|
|
863
|
+
|
|
864
|
+
if (variableFromVariation) {
|
|
865
|
+
if (variableFromVariation.overrides) {
|
|
866
|
+
const override = variableFromVariation.overrides.find((o) => {
|
|
867
|
+
if (o.conditions) {
|
|
868
|
+
return allConditionsAreMatched(
|
|
869
|
+
typeof o.conditions === "string" ? JSON.parse(o.conditions) : o.conditions,
|
|
870
|
+
finalAttributes,
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (o.segments) {
|
|
875
|
+
return allGroupSegmentsAreMatched(
|
|
876
|
+
typeof o.segments === "string" && o.segments !== "*"
|
|
877
|
+
? JSON.parse(o.segments)
|
|
878
|
+
: o.segments,
|
|
879
|
+
finalAttributes,
|
|
880
|
+
this.datafileReader,
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return false;
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
if (override) {
|
|
888
|
+
evaluation = {
|
|
889
|
+
featureKey: feature.key,
|
|
890
|
+
reason: EvaluationReason.OVERRIDE,
|
|
891
|
+
variableKey,
|
|
892
|
+
variableSchema,
|
|
893
|
+
variableValue: override.value,
|
|
894
|
+
bucketValue,
|
|
895
|
+
ruleKey: matchedTraffic.key,
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
this.logger.debug("variable override", evaluation);
|
|
899
|
+
|
|
900
|
+
return evaluation;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
if (typeof variableFromVariation.value !== "undefined") {
|
|
905
|
+
evaluation = {
|
|
906
|
+
featureKey: feature.key,
|
|
907
|
+
reason: EvaluationReason.ALLOCATED,
|
|
908
|
+
variableKey,
|
|
909
|
+
variableSchema,
|
|
910
|
+
variableValue: variableFromVariation.value,
|
|
911
|
+
bucketValue,
|
|
912
|
+
ruleKey: matchedTraffic.key,
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
this.logger.debug("allocated variable", evaluation);
|
|
916
|
+
|
|
917
|
+
return evaluation;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// fall back to default
|
|
925
|
+
evaluation = {
|
|
926
|
+
featureKey: feature.key,
|
|
927
|
+
reason: EvaluationReason.DEFAULTED,
|
|
928
|
+
variableKey,
|
|
929
|
+
variableSchema,
|
|
930
|
+
variableValue: variableSchema.defaultValue,
|
|
931
|
+
bucketValue,
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
this.logger.debug("using default value", evaluation);
|
|
935
|
+
|
|
936
|
+
return evaluation;
|
|
937
|
+
} catch (e) {
|
|
938
|
+
evaluation = {
|
|
939
|
+
featureKey: typeof featureKey === "string" ? featureKey : featureKey.key,
|
|
940
|
+
reason: EvaluationReason.ERROR,
|
|
941
|
+
variableKey,
|
|
942
|
+
error: e,
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
return evaluation;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
getVariable(
|
|
950
|
+
featureKey: FeatureKey | Feature,
|
|
951
|
+
variableKey: string,
|
|
952
|
+
attributes: Attributes = {},
|
|
953
|
+
): VariableValue | undefined {
|
|
954
|
+
try {
|
|
955
|
+
const evaluation = this.evaluateVariable(featureKey, variableKey, attributes);
|
|
956
|
+
|
|
957
|
+
if (typeof evaluation.variableValue !== "undefined") {
|
|
958
|
+
if (
|
|
959
|
+
evaluation.variableSchema &&
|
|
960
|
+
evaluation.variableSchema.type === "json" &&
|
|
961
|
+
typeof evaluation.variableValue === "string"
|
|
962
|
+
) {
|
|
963
|
+
return JSON.parse(evaluation.variableValue);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
return evaluation.variableValue;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
return undefined;
|
|
667
970
|
} catch (e) {
|
|
668
971
|
this.logger.error("getVariable", { featureKey, variableKey, error: e });
|
|
669
972
|
|