@@ -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
*
@@ -152,6 +164,13 @@ export default class EppoClient implements IEppoClient {
152
164
this . configurationRequestConfig = configurationRequestConfig ;
153
165
}
154
166
167
+ /**
168
+ * @deprecated added for temporary debugging
169
+ */
170
+ public _pollerStats ( ) {
171
+ return _pollerStats ( ) ;
172
+ }
173
+
155
174
public async fetchFlagConfigurations ( ) {
156
175
if ( ! this . configurationRequestConfig ) {
157
176
throw new Error (
@@ -253,6 +272,43 @@ export default class EppoClient implements IEppoClient {
253
272
}
254
273
}
255
274
275
+ /**
276
+ * @deprecated added for temporary debugging
277
+ */
278
+ public _getStringAssignmentWithReason (
279
+ subjectKey : string ,
280
+ flagKey : string ,
281
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
282
+ subjectAttributes : Record < string , any > = { } ,
283
+ assignmentHooks ?: IAssignmentHooks | undefined ,
284
+ obfuscated = false ,
285
+ ) : { assignment : string | null ; reason : string } {
286
+ let assignment : string | null = null ;
287
+ let reason = 'Unknown; pre-getAssignmentVariation' ;
288
+ try {
289
+ const eppoValue = this . getAssignmentVariation (
290
+ subjectKey ,
291
+ flagKey ,
292
+ subjectAttributes ,
293
+ assignmentHooks ,
294
+ obfuscated ,
295
+ ValueType . StringType ,
296
+ ) ;
297
+ const eppoValueAssignment = eppoValue . stringValue ;
298
+ reason = eppoValue . reason ?? 'Unknown; post-getAssignmentVariation' ;
299
+ if ( eppoValueAssignment === undefined ) {
300
+ assignment = null ;
301
+ reason += '; coalesced to null' ;
302
+ } else {
303
+ assignment = eppoValueAssignment ;
304
+ }
305
+ } catch ( error ) {
306
+ reason = 'Caught error: ' + error . message + '\n' + error . stack ;
307
+ this . rethrowIfNotGraceful ( error ) ;
308
+ }
309
+ return { assignment, reason } ;
310
+ }
311
+
256
312
getBoolAssignment (
257
313
subjectKey : string ,
258
314
flagKey : string ,
@@ -420,21 +476,30 @@ export default class EppoClient implements IEppoClient {
420
476
experimentConfig ,
421
477
expectedValueType ,
422
478
) ;
479
+ allowListOverride . reason = 'In override list' ;
423
480
424
481
if ( ! allowListOverride . isNullType ( ) ) {
425
482
if ( ! allowListOverride . isExpectedType ( ) ) {
483
+ nullAssignment . assignment . reason = 'Allow list override is not the expected type' ;
426
484
return nullAssignment ;
427
485
}
428
486
return { ...nullAssignment , assignment : allowListOverride } ;
429
487
}
430
488
431
489
// Check for disabled flag.
432
- if ( ! experimentConfig ?. enabled ) return nullAssignment ;
490
+ if ( ! experimentConfig ?. enabled ) {
491
+ nullAssignment . assignment . reason = 'Experiment is not enabled' ;
492
+ return nullAssignment ;
493
+ }
433
494
434
495
// check for overridden assignment via hook
435
496
const overriddenAssignment = assignmentHooks ?. onPreAssignment ( flagKey , subjectKey ) ;
436
497
if ( overriddenAssignment !== null && overriddenAssignment !== undefined ) {
437
- if ( ! overriddenAssignment . isExpectedType ( ) ) return nullAssignment ;
498
+ if ( ! overriddenAssignment . isExpectedType ( ) ) {
499
+ nullAssignment . assignment . reason = 'Override via hook is wrong type' ;
500
+ return nullAssignment ;
501
+ }
502
+ overriddenAssignment . reason = 'Overriden via hook' ;
438
503
return { ...nullAssignment , assignment : overriddenAssignment } ;
439
504
}
440
505
@@ -444,12 +509,17 @@ export default class EppoClient implements IEppoClient {
444
509
experimentConfig . rules ,
445
510
obfuscated ,
446
511
) ;
447
- if ( ! matchedRule ) return nullAssignment ;
512
+ if ( ! matchedRule ) {
513
+ nullAssignment . assignment . reason = 'No matching targeting rule' ;
514
+ return nullAssignment ;
515
+ }
448
516
449
517
// Check if subject is in allocation sample.
450
518
const allocation = experimentConfig . allocations [ matchedRule . allocationKey ] ;
451
- if ( ! this . isInExperimentSample ( subjectKey , flagKey , experimentConfig , allocation ) )
519
+ if ( ! this . isInExperimentSample ( subjectKey , flagKey , experimentConfig , allocation ) ) {
520
+ nullAssignment . assignment . reason = 'Not in experiment sample' ;
452
521
return nullAssignment ;
522
+ }
453
523
454
524
// Compute variation for subject.
455
525
const { subjectShards } = experimentConfig ;
@@ -458,23 +528,27 @@ export default class EppoClient implements IEppoClient {
458
528
let assignedVariation : IVariation | undefined ;
459
529
let holdoutVariation = null ;
460
530
531
+ let variationReason = '' ;
461
532
const holdoutShard = getShard ( `holdout-${ subjectKey } ` , subjectShards ) ;
462
533
const matchingHoldout = holdouts ?. find ( ( holdout ) => {
463
534
const { statusQuoShardRange, shippedShardRange } = holdout ;
464
535
if ( isShardInRange ( holdoutShard , statusQuoShardRange ) ) {
465
536
assignedVariation = variations . find (
466
537
( variation ) => variation . variationKey === statusQuoVariationKey ,
467
538
) ;
539
+ variationReason = 'Holdout during in-flight experiment, status quo variation' ;
468
540
// Only log the holdout variation if this is a rollout allocation
469
541
// Only rollout allocations have shippedShardRange specified
470
542
if ( shippedShardRange ) {
471
543
holdoutVariation = HoldoutVariationEnum . STATUS_QUO ;
544
+ variationReason = 'Holdout after rollout, status quo variation' ;
472
545
}
473
546
} else if ( shippedShardRange && isShardInRange ( holdoutShard , shippedShardRange ) ) {
474
547
assignedVariation = variations . find (
475
548
( variation ) => variation . variationKey === shippedVariationKey ,
476
549
) ;
477
550
holdoutVariation = HoldoutVariationEnum . ALL_SHIPPED ;
551
+ variationReason = 'Holdout after rollout, shipped variation' ;
478
552
}
479
553
return assignedVariation ;
480
554
} ) ;
@@ -484,24 +558,33 @@ export default class EppoClient implements IEppoClient {
484
558
assignedVariation = variations . find ( ( variation ) =>
485
559
isShardInRange ( assignmentShard , variation . shardRange ) ,
486
560
) ;
561
+ variationReason = 'Normal assignment randomization' ;
487
562
}
488
563
564
+ const variationEppoValue = EppoValue . generateEppoValue (
565
+ expectedValueType ,
566
+ assignedVariation ?. value ,
567
+ assignedVariation ?. typedValue ,
568
+ ) ;
569
+ variationEppoValue . reason = variationReason ;
570
+
571
+ const typeMismatchAssignment = nullAssignment ;
572
+ typeMismatchAssignment . assignment . reason = 'Uexpected variation assignment type' ;
573
+
489
574
const internalAssignment : {
490
575
allocationKey : string ;
491
576
assignment : EppoValue ;
492
577
holdoutKey : string | null ;
493
578
holdoutVariation : NullableHoldoutVariationType ;
494
579
} = {
495
580
allocationKey : matchedRule . allocationKey ,
496
- assignment : EppoValue . generateEppoValue (
497
- expectedValueType ,
498
- assignedVariation ?. value ,
499
- assignedVariation ?. typedValue ,
500
- ) ,
581
+ assignment : variationEppoValue ,
501
582
holdoutKey,
502
583
holdoutVariation : holdoutVariation as NullableHoldoutVariationType ,
503
584
} ;
504
- return internalAssignment . assignment . isExpectedType ( ) ? internalAssignment : nullAssignment ;
585
+ return internalAssignment . assignment . isExpectedType ( )
586
+ ? internalAssignment
587
+ : typeMismatchAssignment ;
505
588
}
506
589
507
590
public setLogger ( logger : IAssignmentLogger ) {
0 commit comments