Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

1,630 changes: 1,120 additions & 510 deletions lib/shared/bucketing-assembly-script/__tests__/bucketing/segmentation.test.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { SDKVariable, VariableType as VariableTypeStr } from '@devcycle/types'
import {
EVAL_REASON_DETAILS,
EVAL_REASONS,
SDKVariable,
VariableType as VariableTypeStr,
} from '@devcycle/types'
import { variableForUser_PB, VariableType } from './bucketingImportHelper'
import { VariableForUserParams_PB, SDKVariable_PB } from '../protobuf/compiled'

Expand All @@ -9,6 +14,10 @@ type SDKVariable_PB_Type = {
boolValue: boolean
doubleValue: number
stringValue: string
eval?: {
reason: EVAL_REASONS
details?: string
}
_feature?: {
value: string
isNull: boolean
Expand All @@ -22,6 +31,7 @@ const pbSDKVariableToJS = (pbSDKVariable: SDKVariable_PB_Type): SDKVariable => {
key: pbSDKVariable.key,
value: pbSDKVariable.boolValue,
type: VariableTypeStr.boolean,
eval: pbSDKVariable.eval,
_feature: pbSDKVariable._feature?.value,
}
} else if (pbSDKVariable.type === 1) {
Expand All @@ -30,6 +40,7 @@ const pbSDKVariableToJS = (pbSDKVariable: SDKVariable_PB_Type): SDKVariable => {
key: pbSDKVariable.key,
value: pbSDKVariable.doubleValue,
type: VariableTypeStr.number,
eval: pbSDKVariable.eval,
_feature: pbSDKVariable._feature?.value,
}
} else if (pbSDKVariable.type === 2) {
Expand All @@ -38,6 +49,7 @@ const pbSDKVariableToJS = (pbSDKVariable: SDKVariable_PB_Type): SDKVariable => {
key: pbSDKVariable.key,
value: pbSDKVariable.stringValue,
type: VariableTypeStr.string,
eval: pbSDKVariable.eval,
_feature: pbSDKVariable._feature?.value,
}
} else if (pbSDKVariable.type === 3) {
Expand All @@ -46,6 +58,7 @@ const pbSDKVariableToJS = (pbSDKVariable: SDKVariable_PB_Type): SDKVariable => {
key: pbSDKVariable.key,
value: JSON.parse(pbSDKVariable.stringValue),
type: VariableTypeStr.json,
eval: pbSDKVariable.eval,
_feature: pbSDKVariable._feature?.value,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ describe('protobuf variable tests', () => {
isNull: true,
value: '',
},
eval: {
details: 'User ID AND Country',
reason: 'TARGETING_MATCH',
},
key: 'swagTest',
stringValue: 'YEEEEOWZA',
type: VariableType.String,
Expand Down Expand Up @@ -288,7 +292,6 @@ describe('protobuf variable tests', () => {
boolValue: true,
doubleValue: 0,
stringValue: '',
evalReason: { value: '', isNull: true },
_feature: {
value: '615356f120ed334a6054564f',
isNull: false,
Expand Down
20 changes: 20 additions & 0 deletions lib/shared/bucketing-assembly-script/__tests__/variable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ describe('variableForUser tests', () => {
expect(variable1).toEqual({
_id: '615356f120ed334a6054564c',
_feature: '614ef6aa473928459060721a',
eval: {
details: 'User ID AND Country',
reason: 'TARGETING_MATCH',
},
key: 'swagTest',
type: 'String',
value: 'YEEEEOWZA',
Expand All @@ -38,6 +42,10 @@ describe('variableForUser tests', () => {
expect(variable2).toEqual({
_id: '61538237b0a70b58ae6af71y',
_feature: '614ef6aa473928459060721a',
eval: {
details: 'User ID AND Country',
reason: 'TARGETING_MATCH',
},
key: 'bool-var',
type: 'Boolean',
value: false,
Expand All @@ -51,6 +59,10 @@ describe('variableForUser tests', () => {
expect(variable3).toEqual({
_id: '61538237b0a70b58ae6af71s',
_feature: '614ef6aa473928459060721a',
eval: {
details: 'User ID AND Country',
reason: 'TARGETING_MATCH',
},
key: 'num-var',
type: 'Number',
value: 610.61,
Expand All @@ -64,6 +76,10 @@ describe('variableForUser tests', () => {
expect(variable4).toEqual({
_id: '61538237b0a70b58ae6af71q',
_feature: '614ef6aa473928459060721a',
eval: {
details: 'User ID AND Country',
reason: 'TARGETING_MATCH',
},
key: 'json-var',
type: 'JSON',
value: '{"hello":"world","num":610,"bool":true}',
Expand All @@ -85,6 +101,10 @@ describe('variableForUser tests', () => {
expect(variable1).toEqual({
_id: '615356f120ed334a6054564c',
_feature: '614ef6aa473928459060721a',
eval: {
details: 'User ID AND Country',
reason: 'TARGETING_MATCH',
},
key: 'swagTest',
type: 'String',
value: 'YEEEEOWZA',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
Variation,
FeatureVariation,
FeatureV2 as Feature,
EvalReason,
EVAL_REASONS,
EVAL_REASON_DETAILS,
} from '../types'

import { murmurhashV3 } from '../helpers/murmurhash'
Expand Down Expand Up @@ -131,14 +134,20 @@ export function _doesUserPassRollout(
class SegmentedFeatureData {
public feature: PublicFeature
public target: PublicTarget
public reasonDetails: string | null
}

class TargetResult {
public target: PublicTarget
public reasonDetails: string
}

function evaluateSegmentationForFeature(
config: ConfigBody,
feature: Feature,
user: DVCPopulatedUser,
clientCustomData: JSON.Obj,
): Target | null {
): TargetResult | null {
// Returns the first target for which the user passes segmentation
for (let i = 0; i < feature.configuration.targets.length; i++) {
const target = feature.configuration.targets[i]
Expand All @@ -150,15 +159,20 @@ function evaluateSegmentationForFeature(
const rolloutHash = boundedHashData.rolloutHash
doesUserPassRollout = _doesUserPassRollout(target.rollout, rolloutHash)
}
if (
doesUserPassRollout && _evaluateOperator(

if (doesUserPassRollout) {
const evalResult = _evaluateOperator(
target._audience.filters,
config.audiences,
user,
clientCustomData,
)
) {
return target
if(evalResult.result) {
return {
target,
reasonDetails: evalResult.reasonDetails || ""
}
}
}
}
return null
Expand Down Expand Up @@ -197,6 +211,7 @@ export function getSegmentedFeatureDataFromConfig(
class TargetAndHashes {
public target: Target
public boundedHashData: BoundedHash
public reasonDetails: string
}

function doesUserQualifyForFeature(
Expand All @@ -205,13 +220,15 @@ function doesUserQualifyForFeature(
user: DVCPopulatedUser,
clientCustomData: JSON.Obj,
): TargetAndHashes | null {
const target = evaluateSegmentationForFeature(
const targetResult = evaluateSegmentationForFeature(
config,
feature,
user,
clientCustomData,
)
if (!target) return null
if (!targetResult) return null
const target = targetResult.target
const reasonDetails = targetResult.reasonDetails

const bucketingValue = _getUserValueForBucketingKey( user, target )
const boundedHashData = _generateBoundedHashes(bucketingValue, target._id)
Expand All @@ -223,27 +240,34 @@ function doesUserQualifyForFeature(
return {
target,
boundedHashData,
reasonDetails
}
}

export function bucketUserForVariation(
feature: Feature,
targetAndHashes: TargetAndHashes,
): Variation {
const variation_id = targetAndHashes.target.decideTargetVariation(
const variationResult = targetAndHashes.target.decideTargetVariation(
targetAndHashes.boundedHashData.bucketingHash,
)
const variation = feature.getVariationById(variation_id)
const variation = feature.getVariationById(variationResult.variation)
if (variation) {
return variation
} else {
throw new Error(`Config missing variation: ${variation_id}`)
throw new Error(`Config missing variation: ${variationResult.variation}`)
}
}

class BucketedFeature {
feature: Feature
variation: Variation
function _getEvalReason(
targetAndHashes: TargetAndHashes
): EvalReason {
const target = targetAndHashes.target
const hasRollout = target.rollout !== null
const hasMultipleDistributions = target.distribution.length !== 1

const reason = hasRollout || hasMultipleDistributions ? EVAL_REASONS.SPLIT : EVAL_REASONS.TARGETING_MATCH
return new EvalReason(reason, targetAndHashes.reasonDetails)
}

export function _generateBucketedConfig(
Expand Down Expand Up @@ -288,6 +312,10 @@ export function _generateBucketedConfig(
continue
}

const evalReason = featureOverride
? new EvalReason(EVAL_REASONS.OVERRIDE, EVAL_REASON_DETAILS.OVERRIDE)
: _getEvalReason(targetAndHashes!)

featureKeyMap.set(
feature.key,
new SDKFeature(
Expand All @@ -297,7 +325,7 @@ export function _generateBucketedConfig(
variation._id,
variation.name,
variation.key,
null,
evalReason,
),
)
featureVariationMap.set(feature._id, variation._id)
Expand All @@ -321,8 +349,8 @@ export function _generateBucketedConfig(
variable.type,
variable.key,
variationVar.value,
null,
feature._id,
evalReason,
)
variableMap.set(variable.key, newVar)
}
Expand All @@ -340,9 +368,7 @@ export function _generateBucketedConfig(

class BucketedVariableResponse {
public variable: SDKVariable

public variation: Variation

public feature: Feature
}

Expand Down Expand Up @@ -376,13 +402,15 @@ export function _generateBucketedVariableForUser(
throw new Error('Internal error processing configuration')
}

const evalReason = _getEvalReason(targetAndHashes)

const sdkVar = new SDKVariable(
variable._id,
variable.type,
variable.key,
variationVar.value,
null,
featureForVariable._id,
evalReason,
)
return { variable: sdkVar, variation, feature: featureForVariable }
}
Expand Down Expand Up @@ -411,4 +439,4 @@ export function _getUserValueForBucketingKey(
}
}
return user.user_id
}
}
Loading
Loading