@@ -325,13 +325,32 @@ func NewDatafileProjectConfig(jsonDatafile []byte, logger logging.OptimizelyLogP
325325 groupMap , experimentGroupMap := mappers .MapGroups (datafile .Groups )
326326 experimentIDMap , experimentKeyMap := mappers .MapExperiments (allExperiments , experimentGroupMap )
327327
328+ validExperimentTypes := map [entities.ExperimentType ]bool {
329+ entities .ExperimentTypeAB : true ,
330+ entities .ExperimentTypeMAB : true ,
331+ entities .ExperimentTypeCMAB : true ,
332+ entities .ExperimentTypeTD : true ,
333+ entities .ExperimentTypeFR : true ,
334+ }
335+ for _ , experiment := range experimentIDMap {
336+ if experiment .Type != "" && ! validExperimentTypes [experiment .Type ] {
337+ err = fmt .Errorf (`experiment "%s" has invalid type "%s"` , experiment .Key , experiment .Type )
338+ logger .Error (err .Error (), err )
339+ return nil , err
340+ }
341+ }
342+
328343 rollouts , rolloutMap := mappers .MapRollouts (datafile .Rollouts )
329344 integrations := []entities.Integration {}
330345 for _ , integration := range datafile .Integrations {
331346 integrations = append (integrations , entities.Integration {Key : * integration .Key , Host : integration .Host , PublicKey : integration .PublicKey })
332347 }
333348 eventMap := mappers .MapEvents (datafile .Events )
334349 featureMap := mappers .MapFeatures (datafile .FeatureFlags , rolloutMap , experimentIDMap )
350+
351+ // Inject "everyone else" variation into feature_rollout experiments
352+ injectFeatureRolloutVariations (featureMap , experimentIDMap )
353+
335354 audienceMap , audienceSegmentList := mappers .MapAudiences (append (datafile .TypedAudiences , datafile .Audiences ... ))
336355 flagVariationsMap := mappers .MapFlagVariations (featureMap )
337356 holdouts , holdoutIDMap , flagHoldoutsMap := mappers .MapHoldouts (datafile .Holdouts , featureMap )
@@ -385,3 +404,57 @@ func NewDatafileProjectConfig(jsonDatafile []byte, logger logging.OptimizelyLogP
385404 logger .Info ("Datafile is valid." )
386405 return config , nil
387406}
407+
408+ // injectFeatureRolloutVariations injects the "everyone else" variation from a flag's rollout
409+ // into any experiment with type "feature_rollout". This enables Feature Rollout experiments
410+ // to fall back to the everyone else variation when users are outside the rollout percentage.
411+ func injectFeatureRolloutVariations (featureMap map [string ]entities.Feature , experimentMap map [string ]entities.Experiment ) {
412+ for _ , feature := range featureMap {
413+ everyoneElseVariation := getEveryoneElseVariation (feature )
414+ if everyoneElseVariation == nil {
415+ continue
416+ }
417+
418+ for _ , experimentID := range feature .ExperimentIDs {
419+ experiment , ok := experimentMap [experimentID ]
420+ if ! ok {
421+ continue
422+ }
423+ if experiment .Type != entities .ExperimentTypeFR {
424+ continue
425+ }
426+
427+ // Inject the everyone else variation
428+ experiment .Variations [everyoneElseVariation .ID ] = * everyoneElseVariation
429+ experiment .VariationKeyToIDMap [everyoneElseVariation .Key ] = everyoneElseVariation .ID
430+ experiment .TrafficAllocation = append (experiment .TrafficAllocation , entities.Range {
431+ EntityID : everyoneElseVariation .ID ,
432+ EndOfRange : 10000 ,
433+ })
434+
435+ // Update the experiment in the map
436+ experimentMap [experimentID ] = experiment
437+ }
438+ }
439+ }
440+
441+ // getEveryoneElseVariation retrieves the first variation from the last experiment
442+ // in the flag's rollout (the "everyone else" rule).
443+ func getEveryoneElseVariation (feature entities.Feature ) * entities.Variation {
444+ rollout := feature .Rollout
445+ if rollout .ID == "" {
446+ return nil
447+ }
448+ if len (rollout .Experiments ) == 0 {
449+ return nil
450+ }
451+ everyoneElseRule := rollout .Experiments [len (rollout .Experiments )- 1 ]
452+ if len (everyoneElseRule .Variations ) == 0 {
453+ return nil
454+ }
455+ // Get the first variation from the everyone else rule
456+ for _ , variation := range everyoneElseRule .Variations {
457+ return & variation
458+ }
459+ return nil
460+ }
0 commit comments