Skip to content

Commit

Permalink
Merge pull request #77 from statsig-io/fix-user-bucket-condition
Browse files Browse the repository at this point in the history
Fix user bucket condition
  • Loading branch information
jkw-statsig authored Jun 30, 2021
2 parents d16f453 + e143df5 commit 9370ce9
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 13 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "statsig-node",
"version": "4.0.1",
"version": "4.1.0",
"description": "Statsig Node.js SDK for usage in multi-user server environments.",
"main": "src/index.js",
"scripts": {
Expand Down
16 changes: 10 additions & 6 deletions src/Evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const SpecStore = require('./SpecStore');
const UAParser = require('ua-parser-js');

const TYPE_DYNAMIC_CONFIG = 'dynamic_config';
const CONDITION_SEGMENT_COUNT = 10 * 1000;
const USER_BUCKET_COUNT = 1000;

const Evaluator = {
async init(options, secretKey) {
Expand Down Expand Up @@ -74,10 +76,12 @@ const Evaluator = {
},

_evalPassPercent(user, rule, salt) {
const bucket = computeUserHashBucket(
const hash = computeUserHash(
salt + '.' + rule.name + '.' + user?.userID ?? '',
);
return bucket < rule.passPercentage * 100;
return (
Number(hash % BigInt(CONDITION_SEGMENT_COUNT)) < rule.passPercentage * 100
);
},

/**
Expand Down Expand Up @@ -141,7 +145,8 @@ const Evaluator = {
break;
case 'user_bucket':
const salt = condition.additionalValues?.salt;
value = computeUserHashBucket(salt + '.' + user?.userID ?? '');
const userHash = computeUserHash(salt + '.' + user?.userID ?? '');
value = Number(userHash % BigInt(USER_BUCKET_COUNT));
break;
default:
return FETCH_FROM_SERVER;
Expand Down Expand Up @@ -276,13 +281,12 @@ const Evaluator = {
},
};

function computeUserHashBucket(userHash) {
const hash = crypto
function computeUserHash(userHash) {
return crypto
.createHash('sha256')
.update(userHash)
.digest()
.readBigUInt64BE();
return Number(hash % BigInt(10000));
}

function getFromUser(user, field) {
Expand Down
12 changes: 8 additions & 4 deletions src/__tests__/Evaluator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,14 @@ describe('Test condition evaluation', () => {
['current_time', 'on', Date.now() - 24 * 3600 * 1000, null, user, false],

// user bucket
['user_bucket', 'lt', 1000, null, { userID: 1}, false, { salt:'himalayan salt' }],
['user_bucket', 'lt', 1000, null, { userID: 18}, true, { salt:'himalayan salt' }],
['user_bucket', 'gt', 8800, null, { userID: 1}, true, { salt:'himalayan salt' }],
['user_bucket', 'gt', 8800, null, { userID: 18}, false, { salt:'himalayan salt' }],
['user_bucket', 'lt', 981, null, { userID: 1}, true, { salt:'himalayan salt' }],
['user_bucket', 'lt', 229, null, { userID: 18}, true, { salt:'himalayan salt' }],
['user_bucket', 'gt', 980, null, { userID: 1}, false, { salt:'himalayan salt' }],
['user_bucket', 'gt', 229, null, { userID: 18}, false, { salt:'himalayan salt' }],
['user_bucket', 'any', [228, 333, 555],null, { userID: 18}, true, { salt:'himalayan salt' }],
['user_bucket', 'any', [229, 333, 555],null, { userID: 18}, false, { salt:'himalayan salt' }],
['user_bucket', 'none', [229, 333, 555],null, { userID: 18}, true, { salt:'himalayan salt' }],
// ['user_bucket', '', []]

// some random type not implemented yet
['derived_field', 'eq', '0.25', 'd1_retention', user, FETCH_FROM_SERVER],
Expand Down

0 comments on commit 9370ce9

Please sign in to comment.