@@ -30,7 +30,7 @@ import { EppoValue, ValueType } from '../eppo_value';
30
30
import ExperimentConfigurationRequestor from '../experiment-configuration-requestor' ;
31
31
import HttpClient from '../http-client' ;
32
32
import { getMD5Hash } from '../obfuscation' ;
33
- import initPoller , { IPoller } from '../poller' ;
33
+ import initPoller , { IPoller , _pollerStats } from '../poller' ;
34
34
import { findMatchingRule } from '../rule_evaluator' ;
35
35
import { getShard , isShardInRange } from '../shard' ;
36
36
import { validateNotBlank } from '../validation' ;
@@ -59,6 +59,18 @@ export interface IEppoClient {
59
59
assignmentHooks ?: IAssignmentHooks ,
60
60
) : string | null ;
61
61
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ _pollerStats ( ) : any ;
64
+
65
+ _getStringAssignmentWithReason (
66
+ subjectKey : string ,
67
+ flagKey : string ,
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ subjectAttributes ?: Record < string , any > ,
70
+ assignmentHooks ?: IAssignmentHooks ,
71
+ obfuscated ?: boolean ,
72
+ ) : { assignment : string | null ; reason : string } ;
73
+
62
74
/**
63
75
* Maps a subject to a variation for a given experiment.
64
76
*
@@ -162,6 +174,13 @@ export default class EppoClient implements IEppoClient {
162
174
this . configurationRequestParameters = configurationRequestParameters ;
163
175
}
164
176
177
+ /**
178
+ * @deprecated added for temporary debugging
179
+ */
180
+ public _pollerStats ( ) {
181
+ return _pollerStats ( ) ;
182
+ }
183
+
165
184
public async fetchFlagConfigurations ( ) {
166
185
if ( ! this . configurationRequestParameters ) {
167
186
throw new Error (
@@ -264,6 +283,43 @@ export default class EppoClient implements IEppoClient {
264
283
}
265
284
}
266
285
286
+ /**
287
+ * @deprecated added for temporary debugging
288
+ */
289
+ public _getStringAssignmentWithReason (
290
+ subjectKey : string ,
291
+ flagKey : string ,
292
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
293
+ subjectAttributes : Record < string , any > = { } ,
294
+ assignmentHooks ?: IAssignmentHooks | undefined ,
295
+ obfuscated = false ,
296
+ ) : { assignment : string | null ; reason : string } {
297
+ let assignment : string | null = null ;
298
+ let reason = 'Unknown; pre-getAssignmentVariation' ;
299
+ try {
300
+ const eppoValue = this . getAssignmentVariation (
301
+ subjectKey ,
302
+ flagKey ,
303
+ subjectAttributes ,
304
+ assignmentHooks ,
305
+ obfuscated ,
306
+ ValueType . StringType ,
307
+ ) ;
308
+ const eppoValueAssignment = eppoValue . stringValue ;
309
+ reason = eppoValue . reason ?? 'Unknown; post-getAssignmentVariation' ;
310
+ if ( eppoValueAssignment === undefined ) {
311
+ assignment = null ;
312
+ reason += '; coalesced to null' ;
313
+ } else {
314
+ assignment = eppoValueAssignment ;
315
+ }
316
+ } catch ( error ) {
317
+ reason = 'Caught error: ' + error . message + '\n' + error . stack ;
318
+ this . rethrowIfNotGraceful ( error ) ;
319
+ }
320
+ return { assignment, reason } ;
321
+ }
322
+
267
323
getBoolAssignment (
268
324
subjectKey : string ,
269
325
flagKey : string ,
@@ -431,21 +487,30 @@ export default class EppoClient implements IEppoClient {
431
487
experimentConfig ,
432
488
expectedValueType ,
433
489
) ;
490
+ allowListOverride . reason = 'In override list' ;
434
491
435
492
if ( ! allowListOverride . isNullType ( ) ) {
436
493
if ( ! allowListOverride . isExpectedType ( ) ) {
494
+ nullAssignment . assignment . reason = 'Allow list override is not the expected type' ;
437
495
return nullAssignment ;
438
496
}
439
497
return { ...nullAssignment , assignment : allowListOverride } ;
440
498
}
441
499
442
500
// Check for disabled flag.
443
- if ( ! experimentConfig ?. enabled ) return nullAssignment ;
501
+ if ( ! experimentConfig ?. enabled ) {
502
+ nullAssignment . assignment . reason = 'Experiment is not enabled' ;
503
+ return nullAssignment ;
504
+ }
444
505
445
506
// check for overridden assignment via hook
446
507
const overriddenAssignment = assignmentHooks ?. onPreAssignment ( flagKey , subjectKey ) ;
447
508
if ( overriddenAssignment !== null && overriddenAssignment !== undefined ) {
448
- if ( ! overriddenAssignment . isExpectedType ( ) ) return nullAssignment ;
509
+ if ( ! overriddenAssignment . isExpectedType ( ) ) {
510
+ nullAssignment . assignment . reason = 'Override via hook is wrong type' ;
511
+ return nullAssignment ;
512
+ }
513
+ overriddenAssignment . reason = 'Overriden via hook' ;
449
514
return { ...nullAssignment , assignment : overriddenAssignment } ;
450
515
}
451
516
@@ -455,12 +520,17 @@ export default class EppoClient implements IEppoClient {
455
520
experimentConfig . rules ,
456
521
obfuscated ,
457
522
) ;
458
- if ( ! matchedRule ) return nullAssignment ;
523
+ if ( ! matchedRule ) {
524
+ nullAssignment . assignment . reason = 'No matching targeting rule' ;
525
+ return nullAssignment ;
526
+ }
459
527
460
528
// Check if subject is in allocation sample.
461
529
const allocation = experimentConfig . allocations [ matchedRule . allocationKey ] ;
462
- if ( ! this . isInExperimentSample ( subjectKey , flagKey , experimentConfig , allocation ) )
530
+ if ( ! this . isInExperimentSample ( subjectKey , flagKey , experimentConfig , allocation ) ) {
531
+ nullAssignment . assignment . reason = 'Not in experiment sample' ;
463
532
return nullAssignment ;
533
+ }
464
534
465
535
// Compute variation for subject.
466
536
const { subjectShards } = experimentConfig ;
@@ -469,23 +539,27 @@ export default class EppoClient implements IEppoClient {
469
539
let assignedVariation : IVariation | undefined ;
470
540
let holdoutVariation = null ;
471
541
542
+ let variationReason = '' ;
472
543
const holdoutShard = getShard ( `holdout-${ subjectKey } ` , subjectShards ) ;
473
544
const matchingHoldout = holdouts ?. find ( ( holdout ) => {
474
545
const { statusQuoShardRange, shippedShardRange } = holdout ;
475
546
if ( isShardInRange ( holdoutShard , statusQuoShardRange ) ) {
476
547
assignedVariation = variations . find (
477
548
( variation ) => variation . variationKey === statusQuoVariationKey ,
478
549
) ;
550
+ variationReason = 'Holdout during in-flight experiment, status quo variation' ;
479
551
// Only log the holdout variation if this is a rollout allocation
480
552
// Only rollout allocations have shippedShardRange specified
481
553
if ( shippedShardRange ) {
482
554
holdoutVariation = HoldoutVariationEnum . STATUS_QUO ;
555
+ variationReason = 'Holdout after rollout, status quo variation' ;
483
556
}
484
557
} else if ( shippedShardRange && isShardInRange ( holdoutShard , shippedShardRange ) ) {
485
558
assignedVariation = variations . find (
486
559
( variation ) => variation . variationKey === shippedVariationKey ,
487
560
) ;
488
561
holdoutVariation = HoldoutVariationEnum . ALL_SHIPPED ;
562
+ variationReason = 'Holdout after rollout, shipped variation' ;
489
563
}
490
564
return assignedVariation ;
491
565
} ) ;
@@ -495,24 +569,33 @@ export default class EppoClient implements IEppoClient {
495
569
assignedVariation = variations . find ( ( variation ) =>
496
570
isShardInRange ( assignmentShard , variation . shardRange ) ,
497
571
) ;
572
+ variationReason = 'Normal assignment randomization' ;
498
573
}
499
574
575
+ const variationEppoValue = EppoValue . generateEppoValue (
576
+ expectedValueType ,
577
+ assignedVariation ?. value ,
578
+ assignedVariation ?. typedValue ,
579
+ ) ;
580
+ variationEppoValue . reason = variationReason ;
581
+
582
+ const typeMismatchAssignment = nullAssignment ;
583
+ typeMismatchAssignment . assignment . reason = 'Uexpected variation assignment type' ;
584
+
500
585
const internalAssignment : {
501
586
allocationKey : string ;
502
587
assignment : EppoValue ;
503
588
holdoutKey : string | null ;
504
589
holdoutVariation : NullableHoldoutVariationType ;
505
590
} = {
506
591
allocationKey : matchedRule . allocationKey ,
507
- assignment : EppoValue . generateEppoValue (
508
- expectedValueType ,
509
- assignedVariation ?. value ,
510
- assignedVariation ?. typedValue ,
511
- ) ,
592
+ assignment : variationEppoValue ,
512
593
holdoutKey,
513
594
holdoutVariation : holdoutVariation as NullableHoldoutVariationType ,
514
595
} ;
515
- return internalAssignment . assignment . isExpectedType ( ) ? internalAssignment : nullAssignment ;
596
+ return internalAssignment . assignment . isExpectedType ( )
597
+ ? internalAssignment
598
+ : typeMismatchAssignment ;
516
599
}
517
600
518
601
public setLogger ( logger : IAssignmentLogger ) {
0 commit comments