Skip to content
Open
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
73 changes: 73 additions & 0 deletions pkg/config/datafileprojectconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,13 +325,32 @@ func NewDatafileProjectConfig(jsonDatafile []byte, logger logging.OptimizelyLogP
groupMap, experimentGroupMap := mappers.MapGroups(datafile.Groups)
experimentIDMap, experimentKeyMap := mappers.MapExperiments(allExperiments, experimentGroupMap)

validExperimentTypes := map[entities.ExperimentType]bool{
entities.ExperimentTypeAB: true,
entities.ExperimentTypeMAB: true,
entities.ExperimentTypeCMAB: true,
entities.ExperimentTypeTD: true,
entities.ExperimentTypeFR: true,
}
for _, experiment := range experimentIDMap {
if experiment.Type != "" && !validExperimentTypes[experiment.Type] {
err = fmt.Errorf(`experiment "%s" has invalid type "%s"`, experiment.Key, experiment.Type)
logger.Error(err.Error(), err)
return nil, err
}
}

rollouts, rolloutMap := mappers.MapRollouts(datafile.Rollouts)
integrations := []entities.Integration{}
for _, integration := range datafile.Integrations {
integrations = append(integrations, entities.Integration{Key: *integration.Key, Host: integration.Host, PublicKey: integration.PublicKey})
}
eventMap := mappers.MapEvents(datafile.Events)
featureMap := mappers.MapFeatures(datafile.FeatureFlags, rolloutMap, experimentIDMap)

// Inject "everyone else" variation into feature_rollout experiments
injectFeatureRolloutVariations(featureMap, experimentIDMap)

audienceMap, audienceSegmentList := mappers.MapAudiences(append(datafile.TypedAudiences, datafile.Audiences...))
flagVariationsMap := mappers.MapFlagVariations(featureMap)
holdouts, holdoutIDMap, flagHoldoutsMap := mappers.MapHoldouts(datafile.Holdouts, featureMap)
Expand Down Expand Up @@ -385,3 +404,57 @@ func NewDatafileProjectConfig(jsonDatafile []byte, logger logging.OptimizelyLogP
logger.Info("Datafile is valid.")
return config, nil
}

// injectFeatureRolloutVariations injects the "everyone else" variation from a flag's rollout
// into any experiment with type "feature_rollout". This enables Feature Rollout experiments
// to fall back to the everyone else variation when users are outside the rollout percentage.
func injectFeatureRolloutVariations(featureMap map[string]entities.Feature, experimentMap map[string]entities.Experiment) {
for _, feature := range featureMap {
everyoneElseVariation := getEveryoneElseVariation(feature)
if everyoneElseVariation == nil {
continue
}

for _, experimentID := range feature.ExperimentIDs {
experiment, ok := experimentMap[experimentID]
if !ok {
continue
}
if experiment.Type != entities.ExperimentTypeFR {
continue
}

// Inject the everyone else variation
experiment.Variations[everyoneElseVariation.ID] = *everyoneElseVariation
experiment.VariationKeyToIDMap[everyoneElseVariation.Key] = everyoneElseVariation.ID
experiment.TrafficAllocation = append(experiment.TrafficAllocation, entities.Range{
EntityID: everyoneElseVariation.ID,
EndOfRange: 10000,
})

// Update the experiment in the map
experimentMap[experimentID] = experiment
}
}
}

// getEveryoneElseVariation retrieves the first variation from the last experiment
// in the flag's rollout (the "everyone else" rule).
func getEveryoneElseVariation(feature entities.Feature) *entities.Variation {
rollout := feature.Rollout
if rollout.ID == "" {
return nil
}
if len(rollout.Experiments) == 0 {
return nil
}
everyoneElseRule := rollout.Experiments[len(rollout.Experiments)-1]
if len(everyoneElseRule.Variations) == 0 {
return nil
}
// Get the first variation from the everyone else rule
for _, variation := range everyoneElseRule.Variations {
return &variation
}
return nil
}
1 change: 1 addition & 0 deletions pkg/config/datafileprojectconfig/entities/entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type Experiment struct {
AudienceIds []string `json:"audienceIds"`
ForcedVariations map[string]string `json:"forcedVariations"`
AudienceConditions interface{} `json:"audienceConditions"`
Type string `json:"type,omitempty"`
Cmab *Cmab `json:"cmab,omitempty"` // is optional
}

Expand Down
Loading
Loading