@featurevisor/core 0.37.1 → 0.39.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/coverage/clover.xml +110 -106
- package/coverage/coverage-final.json +2 -2
- package/coverage/lcov-report/index.html +21 -21
- package/coverage/lcov-report/lib/allocator.js.html +1 -1
- package/coverage/lcov-report/lib/index.html +15 -15
- package/coverage/lcov-report/lib/traffic.js.html +44 -29
- package/coverage/lcov-report/src/allocator.ts.html +1 -1
- package/coverage/lcov-report/src/index.html +15 -15
- package/coverage/lcov-report/src/traffic.ts.html +48 -30
- package/coverage/lcov.info +197 -182
- package/lib/builder.d.ts +9 -1
- package/lib/builder.js +65 -53
- package/lib/builder.js.map +1 -1
- package/lib/generate-code/typescript.js +2 -2
- package/lib/generate-code/typescript.js.map +1 -1
- package/lib/linter.js +11 -11
- package/lib/linter.js.map +1 -1
- package/lib/site.js +27 -25
- package/lib/site.js.map +1 -1
- package/lib/tester.js +14 -1
- package/lib/tester.js.map +1 -1
- package/lib/traffic.d.ts +2 -2
- package/lib/traffic.js +23 -18
- package/lib/traffic.js.map +1 -1
- package/package.json +5 -5
- package/src/builder.ts +83 -61
- package/src/generate-code/typescript.ts +6 -2
- package/src/linter.ts +11 -16
- package/src/site.ts +29 -27
- package/src/tester.ts +18 -1
- package/src/traffic.ts +29 -23
package/src/builder.ts
CHANGED
|
@@ -57,8 +57,16 @@ export function getExistingStateFilePath(
|
|
|
57
57
|
return path.join(projectConfig.stateDirectoryPath, `existing-state-${environment}.json`);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
export
|
|
60
|
+
export type FeatureRanges = Map<FeatureKey, Range[]>;
|
|
61
|
+
|
|
62
|
+
interface FeatureRangesResult {
|
|
63
|
+
featureRanges: FeatureRanges;
|
|
64
|
+
featureIsInGroup: { [key: string]: boolean };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getFeatureRanges(projectConfig: ProjectConfig): FeatureRangesResult {
|
|
61
68
|
const featureRanges = new Map<FeatureKey, Range[]>();
|
|
69
|
+
const featureIsInGroup = {}; // featureKey => boolean
|
|
62
70
|
|
|
63
71
|
const groups: Group[] = [];
|
|
64
72
|
if (fs.existsSync(projectConfig.groupsDirectoryPath)) {
|
|
@@ -82,6 +90,11 @@ export function getFeatureRanges(projectConfig: ProjectConfig): Map<FeatureKey,
|
|
|
82
90
|
|
|
83
91
|
if (slot.feature) {
|
|
84
92
|
const featureKey = slot.feature;
|
|
93
|
+
|
|
94
|
+
if (typeof featureKey === "string") {
|
|
95
|
+
featureIsInGroup[featureKey] = true;
|
|
96
|
+
}
|
|
97
|
+
|
|
85
98
|
const featureRangesForFeature = featureRanges.get(featureKey) || [];
|
|
86
99
|
|
|
87
100
|
const start = isFirstSlot ? accumulatedPercentage : accumulatedPercentage + 1;
|
|
@@ -97,7 +110,7 @@ export function getFeatureRanges(projectConfig: ProjectConfig): Map<FeatureKey,
|
|
|
97
110
|
}
|
|
98
111
|
}
|
|
99
112
|
|
|
100
|
-
return featureRanges;
|
|
113
|
+
return { featureRanges, featureIsInGroup };
|
|
101
114
|
}
|
|
102
115
|
|
|
103
116
|
export function buildDatafile(
|
|
@@ -115,7 +128,7 @@ export function buildDatafile(
|
|
|
115
128
|
|
|
116
129
|
const segmentKeysUsedByTag = new Set<SegmentKey>();
|
|
117
130
|
const attributeKeysUsedByTag = new Set<AttributeKey>();
|
|
118
|
-
const featureRanges = getFeatureRanges(projectConfig);
|
|
131
|
+
const { featureRanges, featureIsInGroup } = getFeatureRanges(projectConfig);
|
|
119
132
|
|
|
120
133
|
// features
|
|
121
134
|
const features: Feature[] = [];
|
|
@@ -148,79 +161,85 @@ export function buildDatafile(
|
|
|
148
161
|
|
|
149
162
|
const feature: Feature = {
|
|
150
163
|
key: featureKey,
|
|
151
|
-
defaultVariation: parsedFeature.defaultVariation,
|
|
152
164
|
bucketBy: parsedFeature.bucketBy || projectConfig.defaultBucketBy,
|
|
153
|
-
variations: parsedFeature.variations
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (!variation.variables) {
|
|
160
|
-
return mappedVariation;
|
|
161
|
-
}
|
|
165
|
+
variations: Array.isArray(parsedFeature.variations)
|
|
166
|
+
? parsedFeature.variations.map((variation: Variation) => {
|
|
167
|
+
const mappedVariation: any = {
|
|
168
|
+
value: variation.value,
|
|
169
|
+
weight: variation.weight, // @TODO: added so state files can maintain weight info, but datafiles don't need this. find a way to remove it from datafiles later
|
|
170
|
+
};
|
|
162
171
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
key: variable.key,
|
|
166
|
-
value: variable.value,
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
if (!variable.overrides) {
|
|
170
|
-
return mappedVariable;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
mappedVariable.overrides = variable.overrides.map((override: VariableOverride) => {
|
|
174
|
-
if (typeof override.conditions !== "undefined") {
|
|
175
|
-
const extractedAttributeKeys = extractAttributeKeysFromConditions(
|
|
176
|
-
override.conditions,
|
|
177
|
-
);
|
|
178
|
-
extractedAttributeKeys.forEach((attributeKey) =>
|
|
179
|
-
attributeKeysUsedByTag.add(attributeKey),
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
conditions: JSON.stringify(override.conditions),
|
|
184
|
-
value: override.value,
|
|
185
|
-
};
|
|
172
|
+
if (!variation.variables) {
|
|
173
|
+
return mappedVariation;
|
|
186
174
|
}
|
|
187
175
|
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
extractedSegmentKeys.forEach((segmentKey) => segmentKeysUsedByTag.add(segmentKey));
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
segments: JSON.stringify(override.segments),
|
|
196
|
-
value: override.value,
|
|
176
|
+
mappedVariation.variables = variation.variables.map((variable: Variable) => {
|
|
177
|
+
const mappedVariable: any = {
|
|
178
|
+
key: variable.key,
|
|
179
|
+
value: variable.value,
|
|
197
180
|
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return override;
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
return mappedVariable;
|
|
204
|
-
});
|
|
205
181
|
|
|
206
|
-
|
|
207
|
-
|
|
182
|
+
if (!variable.overrides) {
|
|
183
|
+
return mappedVariable;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
mappedVariable.overrides = variable.overrides.map((override: VariableOverride) => {
|
|
187
|
+
if (typeof override.conditions !== "undefined") {
|
|
188
|
+
const extractedAttributeKeys = extractAttributeKeysFromConditions(
|
|
189
|
+
override.conditions,
|
|
190
|
+
);
|
|
191
|
+
extractedAttributeKeys.forEach((attributeKey) =>
|
|
192
|
+
attributeKeysUsedByTag.add(attributeKey),
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
conditions: JSON.stringify(override.conditions),
|
|
197
|
+
value: override.value,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (typeof override.segments !== "undefined") {
|
|
202
|
+
const extractedSegmentKeys = extractSegmentKeysFromGroupSegments(
|
|
203
|
+
override.segments as GroupSegment | GroupSegment[],
|
|
204
|
+
);
|
|
205
|
+
extractedSegmentKeys.forEach((segmentKey) =>
|
|
206
|
+
segmentKeysUsedByTag.add(segmentKey),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
segments: JSON.stringify(override.segments),
|
|
211
|
+
value: override.value,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return override;
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
return mappedVariable;
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return mappedVariation;
|
|
222
|
+
})
|
|
223
|
+
: undefined,
|
|
208
224
|
traffic: getTraffic(
|
|
209
225
|
parsedFeature.variations,
|
|
210
226
|
parsedFeature.environments[options.environment].rules,
|
|
211
227
|
existingState.features[featureKey],
|
|
212
228
|
featureRanges.get(featureKey) || [],
|
|
213
229
|
),
|
|
230
|
+
ranges: featureRanges.get(featureKey) || undefined,
|
|
214
231
|
};
|
|
215
232
|
|
|
216
233
|
// update state in memory, so that next datafile build can use it (in case it contains the same feature)
|
|
217
234
|
existingState.features[featureKey] = {
|
|
218
|
-
variations: feature.variations
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
235
|
+
variations: Array.isArray(feature.variations)
|
|
236
|
+
? feature.variations.map((v: Variation) => {
|
|
237
|
+
return {
|
|
238
|
+
value: v.value,
|
|
239
|
+
weight: v.weight || 0,
|
|
240
|
+
};
|
|
241
|
+
})
|
|
242
|
+
: undefined,
|
|
224
243
|
traffic: feature.traffic.map((t: Traffic) => {
|
|
225
244
|
return {
|
|
226
245
|
key: t.key,
|
|
@@ -233,9 +252,12 @@ export function buildDatafile(
|
|
|
233
252
|
}),
|
|
234
253
|
};
|
|
235
254
|
}),
|
|
236
|
-
ranges: featureRanges.get(feature.key) || undefined,
|
|
237
255
|
};
|
|
238
256
|
|
|
257
|
+
if (featureIsInGroup[featureKey] === true) {
|
|
258
|
+
feature.ranges = featureRanges.get(feature.key);
|
|
259
|
+
}
|
|
260
|
+
|
|
239
261
|
if (parsedFeature.variablesSchema) {
|
|
240
262
|
feature.variablesSchema = parsedFeature.variablesSchema;
|
|
241
263
|
}
|
|
@@ -138,7 +138,7 @@ ${attributeProperties}
|
|
|
138
138
|
const featureKey = path.basename(featureFile, ".yml");
|
|
139
139
|
const parsedFeature = parseYaml(fs.readFileSync(featureFile, "utf8")) as ParsedFeature;
|
|
140
140
|
|
|
141
|
-
const variationType =
|
|
141
|
+
const variationType = "string";
|
|
142
142
|
const variationTypeScriptType = convertFeaturevisorTypeToTypeScriptType(variationType);
|
|
143
143
|
|
|
144
144
|
if (typeof parsedFeature.archived !== "undefined" && parsedFeature.archived) {
|
|
@@ -182,8 +182,12 @@ import { getInstance } from "./instance";
|
|
|
182
182
|
export namespace ${namespaceValue} {
|
|
183
183
|
export const key = "${featureKey}";
|
|
184
184
|
|
|
185
|
+
export function isEnabled(context: Context = {}) {
|
|
186
|
+
return getInstance().isEnabled(key, context);
|
|
187
|
+
}
|
|
188
|
+
|
|
185
189
|
export function getVariation(context: Context = {}) {
|
|
186
|
-
return getInstance().getVariation
|
|
190
|
+
return getInstance().getVariation(key, context);
|
|
187
191
|
}${variableMethods}
|
|
188
192
|
}
|
|
189
193
|
`.trimStart();
|
package/src/linter.ts
CHANGED
|
@@ -173,9 +173,10 @@ export function getFeatureJoiSchema(
|
|
|
173
173
|
conditionsJoiSchema,
|
|
174
174
|
availableSegmentKeys: string[],
|
|
175
175
|
) {
|
|
176
|
-
const variationValueJoiSchema = Joi.
|
|
176
|
+
const variationValueJoiSchema = Joi.string().required();
|
|
177
177
|
const variableValueJoiSchema = Joi.alternatives()
|
|
178
178
|
.try(
|
|
179
|
+
// @TODO: make it stricter based on variableSchema.type
|
|
179
180
|
Joi.string(),
|
|
180
181
|
Joi.number(),
|
|
181
182
|
Joi.boolean(),
|
|
@@ -230,7 +231,9 @@ export function getFeatureJoiSchema(
|
|
|
230
231
|
key: Joi.string(),
|
|
231
232
|
segments: groupSegmentsJoiSchema,
|
|
232
233
|
percentage: Joi.number().precision(3).min(0).max(100),
|
|
233
|
-
|
|
234
|
+
|
|
235
|
+
enabled: Joi.boolean().optional(),
|
|
236
|
+
variation: variationValueJoiSchema.optional(), // @TODO: only allowed if feature.variations is present
|
|
234
237
|
variables: Joi.object().optional(), // @TODO: make it stricter
|
|
235
238
|
}),
|
|
236
239
|
)
|
|
@@ -242,7 +245,8 @@ export function getFeatureJoiSchema(
|
|
|
242
245
|
segments: groupSegmentsJoiSchema.optional(),
|
|
243
246
|
conditions: conditionsJoiSchema.optional(),
|
|
244
247
|
|
|
245
|
-
|
|
248
|
+
enabled: Joi.boolean().optional(),
|
|
249
|
+
variation: variationValueJoiSchema.optional(),
|
|
246
250
|
variables: Joi.object().optional(), // @TODO: make it stricter
|
|
247
251
|
}),
|
|
248
252
|
),
|
|
@@ -269,8 +273,6 @@ export function getFeatureJoiSchema(
|
|
|
269
273
|
)
|
|
270
274
|
.required(),
|
|
271
275
|
|
|
272
|
-
defaultVariation: variationValueJoiSchema,
|
|
273
|
-
|
|
274
276
|
bucketBy: Joi.alternatives()
|
|
275
277
|
.try(
|
|
276
278
|
// plain
|
|
@@ -331,23 +333,15 @@ export function getFeatureJoiSchema(
|
|
|
331
333
|
}),
|
|
332
334
|
)
|
|
333
335
|
.custom((value, helpers) => {
|
|
334
|
-
var total = value.reduce((
|
|
336
|
+
var total = value.reduce((acc, v) => acc + v.weight, 0);
|
|
335
337
|
|
|
336
338
|
if (total !== 100) {
|
|
337
339
|
throw new Error(`Sum of all variation weights must be 100, got ${total}`);
|
|
338
340
|
}
|
|
339
341
|
|
|
340
|
-
const typeOf = new Set(value.map((v) => typeof v.value));
|
|
341
|
-
|
|
342
|
-
if (typeOf.size > 1) {
|
|
343
|
-
throw new Error(
|
|
344
|
-
`All variations must have the same type, got ${Array.from(typeOf).join(", ")}`,
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
342
|
return value;
|
|
349
343
|
})
|
|
350
|
-
.
|
|
344
|
+
.optional(),
|
|
351
345
|
|
|
352
346
|
environments: allEnvironmentsJoiSchema.required(),
|
|
353
347
|
});
|
|
@@ -382,7 +376,8 @@ export function getTestsJoiSchema(
|
|
|
382
376
|
at: Joi.number().precision(3).min(0).max(100),
|
|
383
377
|
context: Joi.object(),
|
|
384
378
|
|
|
385
|
-
// @TODO: one or
|
|
379
|
+
// @TODO: one or all below
|
|
380
|
+
expectedToBeEnabled: Joi.boolean().required(),
|
|
386
381
|
expectedVariation: Joi.alternatives().try(
|
|
387
382
|
Joi.string(),
|
|
388
383
|
Joi.number(),
|
package/src/site.ts
CHANGED
|
@@ -310,37 +310,39 @@ export function generateSiteSearchIndex(
|
|
|
310
310
|
const fileContent = fs.readFileSync(filePath, "utf8");
|
|
311
311
|
const parsed = parseYaml(fileContent) as ParsedFeature;
|
|
312
312
|
|
|
313
|
-
parsed.variations
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
313
|
+
if (Array.isArray(parsed.variations)) {
|
|
314
|
+
parsed.variations.forEach((variation) => {
|
|
315
|
+
if (!variation.variables) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
317
318
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
319
|
+
variation.variables.forEach((v) => {
|
|
320
|
+
if (v.overrides) {
|
|
321
|
+
v.overrides.forEach((o) => {
|
|
322
|
+
if (o.conditions) {
|
|
323
|
+
extractAttributeKeysFromConditions(o.conditions).forEach((attributeKey) => {
|
|
324
|
+
if (!attributesUsedInFeatures[attributeKey]) {
|
|
325
|
+
attributesUsedInFeatures[attributeKey] = new Set();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
attributesUsedInFeatures[attributeKey].add(entityName);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
330
331
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
332
|
+
if (o.segments && o.segments !== "*") {
|
|
333
|
+
extractSegmentKeysFromGroupSegments(o.segments).forEach((segmentKey) => {
|
|
334
|
+
if (!segmentsUsedInFeatures[segmentKey]) {
|
|
335
|
+
segmentsUsedInFeatures[segmentKey] = new Set();
|
|
336
|
+
}
|
|
336
337
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
338
|
+
segmentsUsedInFeatures[segmentKey].add(entityName);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
});
|
|
342
344
|
});
|
|
343
|
-
}
|
|
345
|
+
}
|
|
344
346
|
|
|
345
347
|
Object.keys(parsed.environments).forEach((environmentKey) => {
|
|
346
348
|
const env = parsed.environments[environmentKey];
|
package/src/tester.ts
CHANGED
|
@@ -118,7 +118,7 @@ export function testProject(rootDirectoryPath: string, projectConfig: ProjectCon
|
|
|
118
118
|
}
|
|
119
119
|
});
|
|
120
120
|
});
|
|
121
|
-
} else {
|
|
121
|
+
} else if (test.environment && test.tag && test.features) {
|
|
122
122
|
// feature testing
|
|
123
123
|
const datafilePath = getDatafilePath(projectConfig, test.environment, test.tag);
|
|
124
124
|
|
|
@@ -158,6 +158,20 @@ export function testProject(rootDirectoryPath: string, projectConfig: ProjectCon
|
|
|
158
158
|
let assertionHasError = false;
|
|
159
159
|
currentAt = assertion.at * (MAX_BUCKETED_NUMBER / 100);
|
|
160
160
|
|
|
161
|
+
// isEnabled
|
|
162
|
+
if ("expectedToBeEnabled" in assertion) {
|
|
163
|
+
const isEnabled = sdk.isEnabled(featureKey, assertion.context);
|
|
164
|
+
|
|
165
|
+
if (isEnabled !== assertion.expectedToBeEnabled) {
|
|
166
|
+
hasError = true;
|
|
167
|
+
assertionHasError = true;
|
|
168
|
+
|
|
169
|
+
console.error(
|
|
170
|
+
` isEnabled failed: expected "${assertion.expectedToBeEnabled}", got "${isEnabled}"`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
161
175
|
// variation
|
|
162
176
|
if ("expectedVariation" in assertion) {
|
|
163
177
|
const variation = sdk.getVariation(featureKey, assertion.context);
|
|
@@ -203,6 +217,9 @@ export function testProject(rootDirectoryPath: string, projectConfig: ProjectCon
|
|
|
203
217
|
}
|
|
204
218
|
});
|
|
205
219
|
});
|
|
220
|
+
} else {
|
|
221
|
+
console.error(` => Invalid test: ${JSON.stringify(test)}`);
|
|
222
|
+
hasError = true;
|
|
206
223
|
}
|
|
207
224
|
});
|
|
208
225
|
}
|
package/src/traffic.ts
CHANGED
|
@@ -4,20 +4,24 @@ import { MAX_BUCKETED_NUMBER } from "@featurevisor/sdk";
|
|
|
4
4
|
import { getAllocation, getUpdatedAvailableRangesAfterFilling } from "./allocator";
|
|
5
5
|
|
|
6
6
|
export function detectIfVariationsChanged(
|
|
7
|
-
yamlVariations: Variation[], // as exists in latest YAML
|
|
7
|
+
yamlVariations: Variation[] | undefined, // as exists in latest YAML
|
|
8
8
|
existingFeature?: ExistingFeature, // from state file
|
|
9
9
|
): boolean {
|
|
10
|
-
if (!existingFeature) {
|
|
10
|
+
if (!existingFeature || typeof existingFeature.variations === "undefined") {
|
|
11
11
|
return false;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
const checkVariations = Array.isArray(yamlVariations)
|
|
15
|
+
? JSON.stringify(yamlVariations.map(({ value, weight }) => ({ value, weight })))
|
|
16
|
+
: undefined;
|
|
17
|
+
|
|
14
18
|
return (
|
|
15
19
|
JSON.stringify(
|
|
16
20
|
existingFeature.variations.map(({ value, weight }) => ({
|
|
17
21
|
value,
|
|
18
22
|
weight,
|
|
19
23
|
})),
|
|
20
|
-
) !==
|
|
24
|
+
) !== checkVariations
|
|
21
25
|
);
|
|
22
26
|
}
|
|
23
27
|
|
|
@@ -51,7 +55,7 @@ export function detectIfRangesChanged(
|
|
|
51
55
|
|
|
52
56
|
export function getTraffic(
|
|
53
57
|
// from current YAML
|
|
54
|
-
variations: Variation[],
|
|
58
|
+
variations: Variation[] | undefined,
|
|
55
59
|
parsedRules: Rule[],
|
|
56
60
|
// from previous release
|
|
57
61
|
existingFeature: ExistingFeature | undefined,
|
|
@@ -118,27 +122,29 @@ export function getTraffic(
|
|
|
118
122
|
updatedAvailableRanges = getUpdatedAvailableRangesAfterFilling(availableRanges, existingSum);
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
if (Array.isArray(variations)) {
|
|
126
|
+
variations.forEach(function (variation) {
|
|
127
|
+
const weight = variation.weight as number;
|
|
128
|
+
const percentage = weight * (MAX_BUCKETED_NUMBER / 100);
|
|
129
|
+
|
|
130
|
+
let toFillValue = needsRebucketing
|
|
131
|
+
? percentage * (rulePercentage / 100) // whole value
|
|
132
|
+
: (weight / 100) * rulePercentageDiff; // incrementing
|
|
133
|
+
const rangesToFill = getAllocation(updatedAvailableRanges, toFillValue);
|
|
134
|
+
|
|
135
|
+
rangesToFill.forEach(function (range) {
|
|
136
|
+
traffic.allocation.push({
|
|
137
|
+
variation: variation.value,
|
|
138
|
+
range,
|
|
139
|
+
});
|
|
134
140
|
});
|
|
135
|
-
});
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
updatedAvailableRanges = getUpdatedAvailableRangesAfterFilling(
|
|
143
|
+
updatedAvailableRanges,
|
|
144
|
+
toFillValue,
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
142
148
|
|
|
143
149
|
traffic.allocation = traffic.allocation.filter((a) => {
|
|
144
150
|
if (a.range && a.range[0] === a.range[1]) {
|