All files / lib/builder traffic.js

89.7% Statements 61/68
73.07% Branches 38/52
100% Functions 12/12
89.39% Lines 59/66

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137  1x 1x 1x 1x 1x 1x 1x     13x 4x     4x         9x 21x   22x             13x 4x   9x 9x       13x 4x   9x 9x                     13x   13x 13x 13x 13x               13x     13x       13x 13x 13x 13x 13x               13x 13x   1x 1x 2x       2x 2x   1x   13x 13x 30x 30x       30x 30x     30x 30x 26x 26x           30x     13x 13x 28x     28x   13x       13x   13x    
"use strict";
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") {
        Eif (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;
    }
    Eif (!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
        Iif (parsedRule.variables) {
            traffic.variables = parsedRule.variables;
        }
        Iif (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);
        }
        Eif (Array.isArray(variations)) {
            variations.forEach(function (variation) {
                let weight = variation.weight;
                Iif (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) {
                    Eif (traffic.allocation) {
                        traffic.allocation.push({
                            variation: variation.value,
                            range,
                        });
                    }
                });
                updatedAvailableRanges = (0, allocator_1.getUpdatedAvailableRangesAfterFilling)(updatedAvailableRanges, toFillValue);
            });
        }
        Eif (traffic.allocation) {
            traffic.allocation = traffic.allocation.filter((a) => {
                Iif (a.range && a.range[0] === a.range[1]) {
                    return false;
                }
                return true;
            });
            Iif (traffic.allocation.length === 0) {
                delete traffic.allocation;
            }
        }
        result.push(traffic);
    });
    return result;
}
//# sourceMappingURL=traffic.js.map