@featurevisor/core
Version:
Core package of Featurevisor for Node.js usage
137 lines • 6.14 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectIfVariationsChanged = detectIfVariationsChanged;
exports.getRulePercentageDiff = getRulePercentageDiff;
exports.detectIfRangesChanged = detectIfRangesChanged;
exports.getTraffic = getTraffic;
const sdk_1 = require("@featurevisor/sdk");
const allocator_1 = require("./allocator");
function detectIfVariationsChanged(yamlVariations, // as exists in latest YAML
existingFeature) {
if (!existingFeature || typeof existingFeature.variations === "undefined") {
if (Array.isArray(yamlVariations) && yamlVariations.length > 0) {
// feature did not previously have any variations,
// but now variations have been added
return true;
}
// variations didn't exist before, and not even now
return false;
}
const checkVariations = Array.isArray(yamlVariations)
? JSON.stringify(yamlVariations.map(({ value, weight }) => ({ value, weight })))
: undefined;
return (JSON.stringify(existingFeature.variations.map(({ value, weight }) => ({
value,
weight,
}))) !== checkVariations);
}
function getRulePercentageDiff(trafficPercentage, // 0 to 100k
existingTrafficRule) {
if (!existingTrafficRule) {
return 0;
}
const existingPercentage = existingTrafficRule.percentage;
return trafficPercentage - existingPercentage;
}
function detectIfRangesChanged(availableRanges, // as exists in latest YAML
existingFeature) {
if (!existingFeature) {
return false;
}
if (!existingFeature.ranges) {
return false;
}
return JSON.stringify(existingFeature.ranges) !== JSON.stringify(availableRanges);
}
function getTraffic(
// from current YAML
variations, parsedRules,
// from previous release
existingFeature,
// ranges from group slots
ranges) {
const result = [];
// @NOTE: may be pass from builder directly?
const availableRanges = ranges && ranges.length > 0 ? ranges : [[0, sdk_1.MAX_BUCKETED_NUMBER]];
parsedRules.forEach(function (parsedRule) {
const rulePercentage = parsedRule.percentage; // 0 - 100
const traffic = {
key: parsedRule.key,
segments: parsedRule.segments,
percentage: rulePercentage * (sdk_1.MAX_BUCKETED_NUMBER / 100),
allocation: [],
variationWeights: parsedRule.variationWeights,
};
// overrides
if (parsedRule.variables) {
traffic.variables = parsedRule.variables;
}
if (parsedRule.variation) {
traffic.variation = parsedRule.variation;
}
// detect changes
const variationsChanged = detectIfVariationsChanged(variations, existingFeature);
const existingTrafficRule = existingFeature?.traffic.find((t) => t.key === parsedRule.key);
const rulePercentageDiff = getRulePercentageDiff(traffic.percentage, existingTrafficRule);
const rangesChanged = detectIfRangesChanged(availableRanges, existingFeature);
const needsRebucketing = !existingTrafficRule || // new rule
variationsChanged || // variations changed
rulePercentageDiff < 0 || // percentage decreased
rangesChanged || // belongs to a group, and group ranges changed
// @NOTE: this means, if variationWeights is present, it will always rebucket.
// worth checking if we can maintain consistent bucketing for this use case as well.
// but this use case is unlikely to hit in practice because it doesn't matter if the feature itself is 100% rolled out.
traffic.variationWeights; // variation weights overridden
let updatedAvailableRanges = JSON.parse(JSON.stringify(availableRanges));
if (existingTrafficRule && existingTrafficRule.allocation && !needsRebucketing) {
// increase: build on top of existing allocations
let existingSum = 0;
traffic.allocation = existingTrafficRule.allocation.map(function ({ variation, range }) {
const result = {
variation,
range: range,
};
existingSum += range[1] - range[0];
return result;
});
updatedAvailableRanges = (0, allocator_1.getUpdatedAvailableRangesAfterFilling)(availableRanges, existingSum);
}
if (Array.isArray(variations)) {
variations.forEach(function (variation) {
let weight = variation.weight;
if (traffic.variationWeights && traffic.variationWeights[variation.value]) {
// override weight from rule
weight = traffic.variationWeights[variation.value];
}
const percentage = weight * (sdk_1.MAX_BUCKETED_NUMBER / 100);
const toFillValue = needsRebucketing
? percentage * (rulePercentage / 100) // whole value
: (weight / 100) * rulePercentageDiff; // incrementing
const rangesToFill = (0, allocator_1.getAllocation)(updatedAvailableRanges, toFillValue);
rangesToFill.forEach(function (range) {
if (traffic.allocation) {
traffic.allocation.push({
variation: variation.value,
range,
});
}
});
updatedAvailableRanges = (0, allocator_1.getUpdatedAvailableRangesAfterFilling)(updatedAvailableRanges, toFillValue);
});
}
if (traffic.allocation) {
traffic.allocation = traffic.allocation.filter((a) => {
if (a.range && a.range[0] === a.range[1]) {
return false;
}
return true;
});
if (traffic.allocation.length === 0) {
delete traffic.allocation;
}
}
result.push(traffic);
});
return result;
}
//# sourceMappingURL=traffic.js.map