@featurevisor/sdk 0.35.0 → 0.37.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 +19 -0
- package/README.md +14 -14
- package/coverage/clover.xml +224 -224
- package/coverage/coverage-final.json +4 -4
- package/coverage/lcov-report/bucket.ts.html +1 -1
- package/coverage/lcov-report/conditions.ts.html +38 -38
- 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 +7 -7
- package/coverage/lcov-report/index.html +1 -1
- package/coverage/lcov-report/instance.ts.html +74 -113
- package/coverage/lcov-report/logger.ts.html +1 -1
- package/coverage/lcov-report/segments.ts.html +10 -10
- package/coverage/lcov.info +391 -391
- package/dist/index.js +1 -1
- package/dist/index.js.gz +0 -0
- package/dist/index.js.map +1 -1
- package/lib/conditions.d.ts +3 -3
- package/lib/conditions.js +35 -35
- package/lib/conditions.js.map +1 -1
- package/lib/feature.d.ts +3 -3
- package/lib/feature.js +5 -5
- package/lib/feature.js.map +1 -1
- package/lib/instance.d.ts +27 -26
- package/lib/instance.js +80 -86
- package/lib/instance.js.map +1 -1
- package/lib/segments.d.ts +3 -3
- package/lib/segments.js +8 -8
- package/lib/segments.js.map +1 -1
- package/package.json +3 -3
- package/src/conditions.ts +37 -37
- package/src/feature.ts +6 -6
- package/src/instance.spec.ts +6 -6
- package/src/instance.ts +70 -83
- package/src/segments.ts +9 -9
package/src/conditions.ts
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
1
|
import { compareVersions } from "compare-versions";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Context, Condition, PlainCondition } from "@featurevisor/types";
|
|
4
4
|
|
|
5
|
-
export function conditionIsMatched(condition: PlainCondition,
|
|
5
|
+
export function conditionIsMatched(condition: PlainCondition, context: Context): boolean {
|
|
6
6
|
const { attribute, operator, value } = condition;
|
|
7
7
|
|
|
8
8
|
if (operator === "equals") {
|
|
9
|
-
return
|
|
9
|
+
return context[attribute] === value;
|
|
10
10
|
} else if (operator === "notEquals") {
|
|
11
|
-
return
|
|
11
|
+
return context[attribute] !== value;
|
|
12
12
|
} else if (operator === "before" || operator === "after") {
|
|
13
13
|
// date comparisons
|
|
14
|
-
const
|
|
14
|
+
const valueInContext = context[attribute] as string | Date;
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
|
|
16
|
+
const dateInContext =
|
|
17
|
+
valueInContext instanceof Date ? valueInContext : new Date(valueInContext);
|
|
18
18
|
const dateInCondition = value instanceof Date ? value : new Date(value as string);
|
|
19
19
|
|
|
20
20
|
return operator === "before"
|
|
21
|
-
?
|
|
22
|
-
:
|
|
23
|
-
} else if (typeof
|
|
21
|
+
? dateInContext < dateInCondition
|
|
22
|
+
: dateInContext > dateInCondition;
|
|
23
|
+
} else if (typeof context[attribute] === "string" && Array.isArray(value)) {
|
|
24
24
|
// array
|
|
25
|
-
const
|
|
25
|
+
const valueInContext = context[attribute] as string;
|
|
26
26
|
|
|
27
27
|
if (operator === "in") {
|
|
28
|
-
return value.indexOf(
|
|
28
|
+
return value.indexOf(valueInContext) !== -1;
|
|
29
29
|
} else if (operator === "notIn") {
|
|
30
|
-
return value.indexOf(
|
|
30
|
+
return value.indexOf(valueInContext) === -1;
|
|
31
31
|
}
|
|
32
|
-
} else if (typeof
|
|
32
|
+
} else if (typeof context[attribute] === "string" && typeof value === "string") {
|
|
33
33
|
// string
|
|
34
|
-
const
|
|
34
|
+
const valueInContext = context[attribute] as string;
|
|
35
35
|
|
|
36
36
|
if (operator === "contains") {
|
|
37
|
-
return
|
|
37
|
+
return valueInContext.indexOf(value) !== -1;
|
|
38
38
|
} else if (operator === "notContains") {
|
|
39
|
-
return
|
|
39
|
+
return valueInContext.indexOf(value) === -1;
|
|
40
40
|
} else if (operator === "startsWith") {
|
|
41
|
-
return
|
|
41
|
+
return valueInContext.startsWith(value);
|
|
42
42
|
} else if (operator === "endsWith") {
|
|
43
|
-
return
|
|
43
|
+
return valueInContext.endsWith(value);
|
|
44
44
|
} else if (operator === "semverEquals") {
|
|
45
|
-
return compareVersions(
|
|
45
|
+
return compareVersions(valueInContext, value) === 0;
|
|
46
46
|
} else if (operator === "semverNotEquals") {
|
|
47
|
-
return compareVersions(
|
|
47
|
+
return compareVersions(valueInContext, value) !== 0;
|
|
48
48
|
} else if (operator === "semverGreaterThan") {
|
|
49
|
-
return compareVersions(
|
|
49
|
+
return compareVersions(valueInContext, value) === 1;
|
|
50
50
|
} else if (operator === "semverGreaterThanOrEquals") {
|
|
51
|
-
return compareVersions(
|
|
51
|
+
return compareVersions(valueInContext, value) >= 0;
|
|
52
52
|
} else if (operator === "semverLessThan") {
|
|
53
|
-
return compareVersions(
|
|
53
|
+
return compareVersions(valueInContext, value) === -1;
|
|
54
54
|
} else if (operator === "semverLessThanOrEquals") {
|
|
55
|
-
return compareVersions(
|
|
55
|
+
return compareVersions(valueInContext, value) <= 0;
|
|
56
56
|
}
|
|
57
|
-
} else if (typeof
|
|
57
|
+
} else if (typeof context[attribute] === "number" && typeof value === "number") {
|
|
58
58
|
// numeric
|
|
59
|
-
const
|
|
59
|
+
const valueInContext = context[attribute] as number;
|
|
60
60
|
|
|
61
61
|
if (operator === "greaterThan") {
|
|
62
|
-
return
|
|
62
|
+
return valueInContext > value;
|
|
63
63
|
} else if (operator === "greaterThanOrEquals") {
|
|
64
|
-
return
|
|
64
|
+
return valueInContext >= value;
|
|
65
65
|
} else if (operator === "lessThan") {
|
|
66
|
-
return
|
|
66
|
+
return valueInContext < value;
|
|
67
67
|
} else if (operator === "lessThanOrEquals") {
|
|
68
|
-
return
|
|
68
|
+
return valueInContext <= value;
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -74,18 +74,18 @@ export function conditionIsMatched(condition: PlainCondition, attributes: Attrib
|
|
|
74
74
|
|
|
75
75
|
export function allConditionsAreMatched(
|
|
76
76
|
conditions: Condition[] | Condition,
|
|
77
|
-
|
|
77
|
+
context: Context,
|
|
78
78
|
): boolean {
|
|
79
79
|
if ("attribute" in conditions) {
|
|
80
|
-
return conditionIsMatched(conditions,
|
|
80
|
+
return conditionIsMatched(conditions, context);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
if ("and" in conditions && Array.isArray(conditions.and)) {
|
|
84
|
-
return conditions.and.every((c) => allConditionsAreMatched(c,
|
|
84
|
+
return conditions.and.every((c) => allConditionsAreMatched(c, context));
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
if ("or" in conditions && Array.isArray(conditions.or)) {
|
|
88
|
-
return conditions.or.some((c) => allConditionsAreMatched(c,
|
|
88
|
+
return conditions.or.some((c) => allConditionsAreMatched(c, context));
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
if ("not" in conditions && Array.isArray(conditions.not)) {
|
|
@@ -95,13 +95,13 @@ export function allConditionsAreMatched(
|
|
|
95
95
|
{
|
|
96
96
|
and: conditions.not,
|
|
97
97
|
},
|
|
98
|
-
|
|
98
|
+
context,
|
|
99
99
|
) === false,
|
|
100
100
|
);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
if (Array.isArray(conditions)) {
|
|
104
|
-
return conditions.every((c) => allConditionsAreMatched(c,
|
|
104
|
+
return conditions.every((c) => allConditionsAreMatched(c, context));
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
return false;
|
package/src/feature.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Allocation,
|
|
1
|
+
import { Allocation, Context, Traffic, Feature, Force } from "@featurevisor/types";
|
|
2
2
|
import { DatafileReader } from "./datafileReader";
|
|
3
3
|
import { allGroupSegmentsAreMatched } from "./segments";
|
|
4
4
|
import { allConditionsAreMatched } from "./conditions";
|
|
@@ -26,7 +26,7 @@ export interface MatchedTrafficAndAllocation {
|
|
|
26
26
|
|
|
27
27
|
export function getMatchedTrafficAndAllocation(
|
|
28
28
|
traffic: Traffic[],
|
|
29
|
-
|
|
29
|
+
context: Context,
|
|
30
30
|
bucketValue: number,
|
|
31
31
|
datafileReader: DatafileReader,
|
|
32
32
|
logger: Logger,
|
|
@@ -37,7 +37,7 @@ export function getMatchedTrafficAndAllocation(
|
|
|
37
37
|
if (
|
|
38
38
|
!allGroupSegmentsAreMatched(
|
|
39
39
|
typeof t.segments === "string" && t.segments !== "*" ? JSON.parse(t.segments) : t.segments,
|
|
40
|
-
|
|
40
|
+
context,
|
|
41
41
|
datafileReader,
|
|
42
42
|
)
|
|
43
43
|
) {
|
|
@@ -61,7 +61,7 @@ export function getMatchedTrafficAndAllocation(
|
|
|
61
61
|
|
|
62
62
|
export function findForceFromFeature(
|
|
63
63
|
feature: Feature,
|
|
64
|
-
|
|
64
|
+
context: Context,
|
|
65
65
|
datafileReader: DatafileReader,
|
|
66
66
|
): Force | undefined {
|
|
67
67
|
if (!feature.force) {
|
|
@@ -70,11 +70,11 @@ export function findForceFromFeature(
|
|
|
70
70
|
|
|
71
71
|
return feature.force.find((f: Force) => {
|
|
72
72
|
if (f.conditions) {
|
|
73
|
-
return allConditionsAreMatched(f.conditions,
|
|
73
|
+
return allConditionsAreMatched(f.conditions, context);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
if (f.segments) {
|
|
77
|
-
return allGroupSegmentsAreMatched(f.segments,
|
|
77
|
+
return allGroupSegmentsAreMatched(f.segments, context, datafileReader);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
return false;
|
package/src/instance.spec.ts
CHANGED
|
@@ -73,7 +73,7 @@ describe("sdk: instance", function () {
|
|
|
73
73
|
attributes: [],
|
|
74
74
|
segments: [],
|
|
75
75
|
},
|
|
76
|
-
configureBucketKey: function (feature,
|
|
76
|
+
configureBucketKey: function (feature, context, bucketKey) {
|
|
77
77
|
capturedBucketKey = bucketKey;
|
|
78
78
|
|
|
79
79
|
return bucketKey;
|
|
@@ -117,7 +117,7 @@ describe("sdk: instance", function () {
|
|
|
117
117
|
attributes: [],
|
|
118
118
|
segments: [],
|
|
119
119
|
},
|
|
120
|
-
configureBucketKey: function (feature,
|
|
120
|
+
configureBucketKey: function (feature, context, bucketKey) {
|
|
121
121
|
capturedBucketKey = bucketKey;
|
|
122
122
|
|
|
123
123
|
return bucketKey;
|
|
@@ -162,7 +162,7 @@ describe("sdk: instance", function () {
|
|
|
162
162
|
attributes: [],
|
|
163
163
|
segments: [],
|
|
164
164
|
},
|
|
165
|
-
configureBucketKey: function (feature,
|
|
165
|
+
configureBucketKey: function (feature, context, bucketKey) {
|
|
166
166
|
capturedBucketKey = bucketKey;
|
|
167
167
|
|
|
168
168
|
return bucketKey;
|
|
@@ -185,7 +185,7 @@ describe("sdk: instance", function () {
|
|
|
185
185
|
expect(capturedBucketKey).toEqual("456.test");
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
-
it("should intercept
|
|
188
|
+
it("should intercept context", function () {
|
|
189
189
|
let intercepted = false;
|
|
190
190
|
|
|
191
191
|
const sdk = createInstance({
|
|
@@ -214,11 +214,11 @@ describe("sdk: instance", function () {
|
|
|
214
214
|
attributes: [],
|
|
215
215
|
segments: [],
|
|
216
216
|
},
|
|
217
|
-
|
|
217
|
+
interceptContext: function (context) {
|
|
218
218
|
intercepted = true;
|
|
219
219
|
|
|
220
220
|
return {
|
|
221
|
-
...
|
|
221
|
+
...context,
|
|
222
222
|
};
|
|
223
223
|
},
|
|
224
224
|
});
|
package/src/instance.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
Context,
|
|
3
3
|
AttributeValue,
|
|
4
4
|
BucketKey,
|
|
5
5
|
BucketValue,
|
|
@@ -31,13 +31,13 @@ export type ReadyCallback = () => void;
|
|
|
31
31
|
export type ActivationCallback = (
|
|
32
32
|
featureName: string,
|
|
33
33
|
variation: VariationValue,
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
context: Context,
|
|
35
|
+
captureContext: Context,
|
|
36
36
|
) => void;
|
|
37
37
|
|
|
38
|
-
export type ConfigureBucketKey = (feature,
|
|
38
|
+
export type ConfigureBucketKey = (feature, context, bucketKey: BucketKey) => BucketKey;
|
|
39
39
|
|
|
40
|
-
export type ConfigureBucketValue = (feature,
|
|
40
|
+
export type ConfigureBucketValue = (feature, context, bucketValue: BucketValue) => BucketValue;
|
|
41
41
|
|
|
42
42
|
export interface Statuses {
|
|
43
43
|
ready: boolean;
|
|
@@ -46,6 +46,8 @@ export interface Statuses {
|
|
|
46
46
|
|
|
47
47
|
const DEFAULT_BUCKET_KEY_SEPARATOR = ".";
|
|
48
48
|
|
|
49
|
+
export type InterceptContext = (context: Context) => Context;
|
|
50
|
+
|
|
49
51
|
export interface InstanceOptions {
|
|
50
52
|
bucketKeySeparator?: string;
|
|
51
53
|
configureBucketKey?: ConfigureBucketKey;
|
|
@@ -54,7 +56,7 @@ export interface InstanceOptions {
|
|
|
54
56
|
datafileUrl?: string;
|
|
55
57
|
handleDatafileFetch?: (datafileUrl: string) => Promise<DatafileContent>;
|
|
56
58
|
initialFeatures?: InitialFeatures;
|
|
57
|
-
|
|
59
|
+
interceptContext?: InterceptContext;
|
|
58
60
|
logger?: Logger;
|
|
59
61
|
onActivation?: ActivationCallback;
|
|
60
62
|
onReady?: ReadyCallback;
|
|
@@ -156,7 +158,7 @@ export class FeaturevisorInstance {
|
|
|
156
158
|
private datafileUrl?: string;
|
|
157
159
|
private handleDatafileFetch?: DatafileFetchHandler;
|
|
158
160
|
private initialFeatures?: InitialFeatures;
|
|
159
|
-
private
|
|
161
|
+
private interceptContext?: InterceptContext;
|
|
160
162
|
private logger: Logger;
|
|
161
163
|
private refreshInterval?: number; // seconds
|
|
162
164
|
private stickyFeatures?: StickyFeatures;
|
|
@@ -182,7 +184,7 @@ export class FeaturevisorInstance {
|
|
|
182
184
|
this.datafileUrl = options.datafileUrl;
|
|
183
185
|
this.handleDatafileFetch = options.handleDatafileFetch;
|
|
184
186
|
this.initialFeatures = options.initialFeatures;
|
|
185
|
-
this.
|
|
187
|
+
this.interceptContext = options.interceptContext;
|
|
186
188
|
this.logger = options.logger || createLogger();
|
|
187
189
|
this.refreshInterval = options.refreshInterval;
|
|
188
190
|
this.stickyFeatures = options.stickyFeatures;
|
|
@@ -281,7 +283,7 @@ export class FeaturevisorInstance {
|
|
|
281
283
|
/**
|
|
282
284
|
* Bucketing
|
|
283
285
|
*/
|
|
284
|
-
private getBucketKey(feature: Feature,
|
|
286
|
+
private getBucketKey(feature: Feature, context: Context): BucketKey {
|
|
285
287
|
const featureKey = feature.key;
|
|
286
288
|
|
|
287
289
|
let type;
|
|
@@ -305,7 +307,7 @@ export class FeaturevisorInstance {
|
|
|
305
307
|
const bucketKey: AttributeValue[] = [];
|
|
306
308
|
|
|
307
309
|
attributeKeys.forEach((attributeKey) => {
|
|
308
|
-
const attributeValue =
|
|
310
|
+
const attributeValue = context[attributeKey];
|
|
309
311
|
|
|
310
312
|
if (typeof attributeValue === "undefined") {
|
|
311
313
|
return;
|
|
@@ -326,19 +328,19 @@ export class FeaturevisorInstance {
|
|
|
326
328
|
const result = bucketKey.join(this.bucketKeySeparator);
|
|
327
329
|
|
|
328
330
|
if (this.configureBucketKey) {
|
|
329
|
-
return this.configureBucketKey(feature,
|
|
331
|
+
return this.configureBucketKey(feature, context, result);
|
|
330
332
|
}
|
|
331
333
|
|
|
332
334
|
return result;
|
|
333
335
|
}
|
|
334
336
|
|
|
335
|
-
private getBucketValue(feature: Feature,
|
|
336
|
-
const bucketKey = this.getBucketKey(feature,
|
|
337
|
+
private getBucketValue(feature: Feature, context: Context): BucketValue {
|
|
338
|
+
const bucketKey = this.getBucketKey(feature, context);
|
|
337
339
|
|
|
338
340
|
const value = getBucketedNumber(bucketKey);
|
|
339
341
|
|
|
340
342
|
if (this.configureBucketValue) {
|
|
341
|
-
return this.configureBucketValue(feature,
|
|
343
|
+
return this.configureBucketValue(feature, context, value);
|
|
342
344
|
}
|
|
343
345
|
|
|
344
346
|
return value;
|
|
@@ -419,7 +421,7 @@ export class FeaturevisorInstance {
|
|
|
419
421
|
/**
|
|
420
422
|
* Variation
|
|
421
423
|
*/
|
|
422
|
-
evaluateVariation(featureKey: FeatureKey | Feature,
|
|
424
|
+
evaluateVariation(featureKey: FeatureKey | Feature, context: Context = {}): Evaluation {
|
|
423
425
|
let evaluation: Evaluation;
|
|
424
426
|
|
|
425
427
|
try {
|
|
@@ -477,12 +479,10 @@ export class FeaturevisorInstance {
|
|
|
477
479
|
return evaluation;
|
|
478
480
|
}
|
|
479
481
|
|
|
480
|
-
const
|
|
481
|
-
? this.interceptAttributes(attributes)
|
|
482
|
-
: attributes;
|
|
482
|
+
const finalContext = this.interceptContext ? this.interceptContext(context) : context;
|
|
483
483
|
|
|
484
484
|
// forced
|
|
485
|
-
const force = findForceFromFeature(feature,
|
|
485
|
+
const force = findForceFromFeature(feature, context, this.datafileReader);
|
|
486
486
|
|
|
487
487
|
if (force && force.variation) {
|
|
488
488
|
const variation = feature.variations.find((v) => v.value === force.variation);
|
|
@@ -501,11 +501,11 @@ export class FeaturevisorInstance {
|
|
|
501
501
|
}
|
|
502
502
|
|
|
503
503
|
// bucketing
|
|
504
|
-
const bucketValue = this.getBucketValue(feature,
|
|
504
|
+
const bucketValue = this.getBucketValue(feature, finalContext);
|
|
505
505
|
|
|
506
506
|
const { matchedTraffic, matchedAllocation } = getMatchedTrafficAndAllocation(
|
|
507
507
|
feature.traffic,
|
|
508
|
-
|
|
508
|
+
finalContext,
|
|
509
509
|
bucketValue,
|
|
510
510
|
this.datafileReader,
|
|
511
511
|
this.logger,
|
|
@@ -589,10 +589,10 @@ export class FeaturevisorInstance {
|
|
|
589
589
|
|
|
590
590
|
getVariation(
|
|
591
591
|
featureKey: FeatureKey | Feature,
|
|
592
|
-
|
|
592
|
+
context: Context = {},
|
|
593
593
|
): VariationValue | undefined {
|
|
594
594
|
try {
|
|
595
|
-
const evaluation = this.evaluateVariation(featureKey,
|
|
595
|
+
const evaluation = this.evaluateVariation(featureKey, context);
|
|
596
596
|
|
|
597
597
|
if (typeof evaluation.variationValue !== "undefined") {
|
|
598
598
|
return evaluation.variationValue;
|
|
@@ -612,36 +612,27 @@ export class FeaturevisorInstance {
|
|
|
612
612
|
|
|
613
613
|
getVariationBoolean(
|
|
614
614
|
featureKey: FeatureKey | Feature,
|
|
615
|
-
|
|
615
|
+
context: Context = {},
|
|
616
616
|
): boolean | undefined {
|
|
617
|
-
const variationValue = this.getVariation(featureKey,
|
|
617
|
+
const variationValue = this.getVariation(featureKey, context);
|
|
618
618
|
|
|
619
619
|
return getValueByType(variationValue, "boolean") as boolean | undefined;
|
|
620
620
|
}
|
|
621
621
|
|
|
622
|
-
getVariationString(
|
|
623
|
-
|
|
624
|
-
attributes: Attributes = {},
|
|
625
|
-
): string | undefined {
|
|
626
|
-
const variationValue = this.getVariation(featureKey, attributes);
|
|
622
|
+
getVariationString(featureKey: FeatureKey | Feature, context: Context = {}): string | undefined {
|
|
623
|
+
const variationValue = this.getVariation(featureKey, context);
|
|
627
624
|
|
|
628
625
|
return getValueByType(variationValue, "string") as string | undefined;
|
|
629
626
|
}
|
|
630
627
|
|
|
631
|
-
getVariationInteger(
|
|
632
|
-
|
|
633
|
-
attributes: Attributes = {},
|
|
634
|
-
): number | undefined {
|
|
635
|
-
const variationValue = this.getVariation(featureKey, attributes);
|
|
628
|
+
getVariationInteger(featureKey: FeatureKey | Feature, context: Context = {}): number | undefined {
|
|
629
|
+
const variationValue = this.getVariation(featureKey, context);
|
|
636
630
|
|
|
637
631
|
return getValueByType(variationValue, "integer") as number | undefined;
|
|
638
632
|
}
|
|
639
633
|
|
|
640
|
-
getVariationDouble(
|
|
641
|
-
|
|
642
|
-
attributes: Attributes = {},
|
|
643
|
-
): number | undefined {
|
|
644
|
-
const variationValue = this.getVariation(featureKey, attributes);
|
|
634
|
+
getVariationDouble(featureKey: FeatureKey | Feature, context: Context = {}): number | undefined {
|
|
635
|
+
const variationValue = this.getVariation(featureKey, context);
|
|
645
636
|
|
|
646
637
|
return getValueByType(variationValue, "double") as number | undefined;
|
|
647
638
|
}
|
|
@@ -649,9 +640,9 @@ export class FeaturevisorInstance {
|
|
|
649
640
|
/**
|
|
650
641
|
* Activate
|
|
651
642
|
*/
|
|
652
|
-
activate(featureKey: FeatureKey,
|
|
643
|
+
activate(featureKey: FeatureKey, context: Context = {}): VariationValue | undefined {
|
|
653
644
|
try {
|
|
654
|
-
const evaluation = this.evaluateVariation(featureKey,
|
|
645
|
+
const evaluation = this.evaluateVariation(featureKey, context);
|
|
655
646
|
const variationValue = evaluation.variation
|
|
656
647
|
? evaluation.variation.value
|
|
657
648
|
: evaluation.variationValue;
|
|
@@ -660,19 +651,17 @@ export class FeaturevisorInstance {
|
|
|
660
651
|
return undefined;
|
|
661
652
|
}
|
|
662
653
|
|
|
663
|
-
const
|
|
664
|
-
? this.interceptAttributes(attributes)
|
|
665
|
-
: attributes;
|
|
654
|
+
const finalContext = this.interceptContext ? this.interceptContext(context) : context;
|
|
666
655
|
|
|
667
|
-
const
|
|
656
|
+
const captureContext: Context = {};
|
|
668
657
|
|
|
669
658
|
const attributesForCapturing = this.datafileReader
|
|
670
659
|
.getAllAttributes()
|
|
671
660
|
.filter((a) => a.capture === true);
|
|
672
661
|
|
|
673
662
|
attributesForCapturing.forEach((a) => {
|
|
674
|
-
if (typeof
|
|
675
|
-
|
|
663
|
+
if (typeof finalContext[a.key] !== "undefined") {
|
|
664
|
+
captureContext[a.key] = context[a.key];
|
|
676
665
|
}
|
|
677
666
|
});
|
|
678
667
|
|
|
@@ -680,8 +669,8 @@ export class FeaturevisorInstance {
|
|
|
680
669
|
"activation",
|
|
681
670
|
featureKey,
|
|
682
671
|
variationValue,
|
|
683
|
-
|
|
684
|
-
|
|
672
|
+
finalContext,
|
|
673
|
+
captureContext,
|
|
685
674
|
evaluation,
|
|
686
675
|
);
|
|
687
676
|
|
|
@@ -693,26 +682,26 @@ export class FeaturevisorInstance {
|
|
|
693
682
|
}
|
|
694
683
|
}
|
|
695
684
|
|
|
696
|
-
activateBoolean(featureKey: FeatureKey,
|
|
697
|
-
const variationValue = this.activate(featureKey,
|
|
685
|
+
activateBoolean(featureKey: FeatureKey, context: Context = {}): boolean | undefined {
|
|
686
|
+
const variationValue = this.activate(featureKey, context);
|
|
698
687
|
|
|
699
688
|
return getValueByType(variationValue, "boolean") as boolean | undefined;
|
|
700
689
|
}
|
|
701
690
|
|
|
702
|
-
activateString(featureKey: FeatureKey,
|
|
703
|
-
const variationValue = this.activate(featureKey,
|
|
691
|
+
activateString(featureKey: FeatureKey, context: Context = {}): string | undefined {
|
|
692
|
+
const variationValue = this.activate(featureKey, context);
|
|
704
693
|
|
|
705
694
|
return getValueByType(variationValue, "string") as string | undefined;
|
|
706
695
|
}
|
|
707
696
|
|
|
708
|
-
activateInteger(featureKey: FeatureKey,
|
|
709
|
-
const variationValue = this.activate(featureKey,
|
|
697
|
+
activateInteger(featureKey: FeatureKey, context: Context = {}): number | undefined {
|
|
698
|
+
const variationValue = this.activate(featureKey, context);
|
|
710
699
|
|
|
711
700
|
return getValueByType(variationValue, "integer") as number | undefined;
|
|
712
701
|
}
|
|
713
702
|
|
|
714
|
-
activateDouble(featureKey: FeatureKey,
|
|
715
|
-
const variationValue = this.activate(featureKey,
|
|
703
|
+
activateDouble(featureKey: FeatureKey, context: Context = {}): number | undefined {
|
|
704
|
+
const variationValue = this.activate(featureKey, context);
|
|
716
705
|
|
|
717
706
|
return getValueByType(variationValue, "double") as number | undefined;
|
|
718
707
|
}
|
|
@@ -723,7 +712,7 @@ export class FeaturevisorInstance {
|
|
|
723
712
|
evaluateVariable(
|
|
724
713
|
featureKey: FeatureKey | Feature,
|
|
725
714
|
variableKey: VariableKey,
|
|
726
|
-
|
|
715
|
+
context: Context = {},
|
|
727
716
|
): Evaluation {
|
|
728
717
|
let evaluation: Evaluation;
|
|
729
718
|
|
|
@@ -801,12 +790,10 @@ export class FeaturevisorInstance {
|
|
|
801
790
|
return evaluation;
|
|
802
791
|
}
|
|
803
792
|
|
|
804
|
-
const
|
|
805
|
-
? this.interceptAttributes(attributes)
|
|
806
|
-
: attributes;
|
|
793
|
+
const finalContext = this.interceptContext ? this.interceptContext(context) : context;
|
|
807
794
|
|
|
808
795
|
// forced
|
|
809
|
-
const force = findForceFromFeature(feature,
|
|
796
|
+
const force = findForceFromFeature(feature, context, this.datafileReader);
|
|
810
797
|
|
|
811
798
|
if (force && force.variables && typeof force.variables[variableKey] !== "undefined") {
|
|
812
799
|
evaluation = {
|
|
@@ -823,11 +810,11 @@ export class FeaturevisorInstance {
|
|
|
823
810
|
}
|
|
824
811
|
|
|
825
812
|
// bucketing
|
|
826
|
-
const bucketValue = this.getBucketValue(feature,
|
|
813
|
+
const bucketValue = this.getBucketValue(feature, finalContext);
|
|
827
814
|
|
|
828
815
|
const { matchedTraffic, matchedAllocation } = getMatchedTrafficAndAllocation(
|
|
829
816
|
feature.traffic,
|
|
830
|
-
|
|
817
|
+
finalContext,
|
|
831
818
|
bucketValue,
|
|
832
819
|
this.datafileReader,
|
|
833
820
|
this.logger,
|
|
@@ -867,7 +854,7 @@ export class FeaturevisorInstance {
|
|
|
867
854
|
if (o.conditions) {
|
|
868
855
|
return allConditionsAreMatched(
|
|
869
856
|
typeof o.conditions === "string" ? JSON.parse(o.conditions) : o.conditions,
|
|
870
|
-
|
|
857
|
+
finalContext,
|
|
871
858
|
);
|
|
872
859
|
}
|
|
873
860
|
|
|
@@ -876,7 +863,7 @@ export class FeaturevisorInstance {
|
|
|
876
863
|
typeof o.segments === "string" && o.segments !== "*"
|
|
877
864
|
? JSON.parse(o.segments)
|
|
878
865
|
: o.segments,
|
|
879
|
-
|
|
866
|
+
finalContext,
|
|
880
867
|
this.datafileReader,
|
|
881
868
|
);
|
|
882
869
|
}
|
|
@@ -949,10 +936,10 @@ export class FeaturevisorInstance {
|
|
|
949
936
|
getVariable(
|
|
950
937
|
featureKey: FeatureKey | Feature,
|
|
951
938
|
variableKey: string,
|
|
952
|
-
|
|
939
|
+
context: Context = {},
|
|
953
940
|
): VariableValue | undefined {
|
|
954
941
|
try {
|
|
955
|
-
const evaluation = this.evaluateVariable(featureKey, variableKey,
|
|
942
|
+
const evaluation = this.evaluateVariable(featureKey, variableKey, context);
|
|
956
943
|
|
|
957
944
|
if (typeof evaluation.variableValue !== "undefined") {
|
|
958
945
|
if (
|
|
@@ -977,9 +964,9 @@ export class FeaturevisorInstance {
|
|
|
977
964
|
getVariableBoolean(
|
|
978
965
|
featureKey: FeatureKey | Feature,
|
|
979
966
|
variableKey: string,
|
|
980
|
-
|
|
967
|
+
context: Context = {},
|
|
981
968
|
): boolean | undefined {
|
|
982
|
-
const variableValue = this.getVariable(featureKey, variableKey,
|
|
969
|
+
const variableValue = this.getVariable(featureKey, variableKey, context);
|
|
983
970
|
|
|
984
971
|
return getValueByType(variableValue, "boolean") as boolean | undefined;
|
|
985
972
|
}
|
|
@@ -987,9 +974,9 @@ export class FeaturevisorInstance {
|
|
|
987
974
|
getVariableString(
|
|
988
975
|
featureKey: FeatureKey | Feature,
|
|
989
976
|
variableKey: string,
|
|
990
|
-
|
|
977
|
+
context: Context = {},
|
|
991
978
|
): string | undefined {
|
|
992
|
-
const variableValue = this.getVariable(featureKey, variableKey,
|
|
979
|
+
const variableValue = this.getVariable(featureKey, variableKey, context);
|
|
993
980
|
|
|
994
981
|
return getValueByType(variableValue, "string") as string | undefined;
|
|
995
982
|
}
|
|
@@ -997,9 +984,9 @@ export class FeaturevisorInstance {
|
|
|
997
984
|
getVariableInteger(
|
|
998
985
|
featureKey: FeatureKey | Feature,
|
|
999
986
|
variableKey: string,
|
|
1000
|
-
|
|
987
|
+
context: Context = {},
|
|
1001
988
|
): number | undefined {
|
|
1002
|
-
const variableValue = this.getVariable(featureKey, variableKey,
|
|
989
|
+
const variableValue = this.getVariable(featureKey, variableKey, context);
|
|
1003
990
|
|
|
1004
991
|
return getValueByType(variableValue, "integer") as number | undefined;
|
|
1005
992
|
}
|
|
@@ -1007,9 +994,9 @@ export class FeaturevisorInstance {
|
|
|
1007
994
|
getVariableDouble(
|
|
1008
995
|
featureKey: FeatureKey | Feature,
|
|
1009
996
|
variableKey: string,
|
|
1010
|
-
|
|
997
|
+
context: Context = {},
|
|
1011
998
|
): number | undefined {
|
|
1012
|
-
const variableValue = this.getVariable(featureKey, variableKey,
|
|
999
|
+
const variableValue = this.getVariable(featureKey, variableKey, context);
|
|
1013
1000
|
|
|
1014
1001
|
return getValueByType(variableValue, "double") as number | undefined;
|
|
1015
1002
|
}
|
|
@@ -1017,9 +1004,9 @@ export class FeaturevisorInstance {
|
|
|
1017
1004
|
getVariableArray(
|
|
1018
1005
|
featureKey: FeatureKey | Feature,
|
|
1019
1006
|
variableKey: string,
|
|
1020
|
-
|
|
1007
|
+
context: Context = {},
|
|
1021
1008
|
): string[] | undefined {
|
|
1022
|
-
const variableValue = this.getVariable(featureKey, variableKey,
|
|
1009
|
+
const variableValue = this.getVariable(featureKey, variableKey, context);
|
|
1023
1010
|
|
|
1024
1011
|
return getValueByType(variableValue, "array") as string[] | undefined;
|
|
1025
1012
|
}
|
|
@@ -1027,9 +1014,9 @@ export class FeaturevisorInstance {
|
|
|
1027
1014
|
getVariableObject<T>(
|
|
1028
1015
|
featureKey: FeatureKey | Feature,
|
|
1029
1016
|
variableKey: string,
|
|
1030
|
-
|
|
1017
|
+
context: Context = {},
|
|
1031
1018
|
): T | undefined {
|
|
1032
|
-
const variableValue = this.getVariable(featureKey, variableKey,
|
|
1019
|
+
const variableValue = this.getVariable(featureKey, variableKey, context);
|
|
1033
1020
|
|
|
1034
1021
|
return getValueByType(variableValue, "object") as T | undefined;
|
|
1035
1022
|
}
|
|
@@ -1037,9 +1024,9 @@ export class FeaturevisorInstance {
|
|
|
1037
1024
|
getVariableJSON<T>(
|
|
1038
1025
|
featureKey: FeatureKey | Feature,
|
|
1039
1026
|
variableKey: string,
|
|
1040
|
-
|
|
1027
|
+
context: Context = {},
|
|
1041
1028
|
): T | undefined {
|
|
1042
|
-
const variableValue = this.getVariable(featureKey, variableKey,
|
|
1029
|
+
const variableValue = this.getVariable(featureKey, variableKey, context);
|
|
1043
1030
|
|
|
1044
1031
|
return getValueByType(variableValue, "json") as T | undefined;
|
|
1045
1032
|
}
|