Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[submodule "flagengine/engine-test-data"]
path = flagengine/engine-test-data
url = git@github.com:Flagsmith/engine-test-data.git
branch = v3.11.0
branch = v3.12.1
[submodule "flagengine/sdk-benchmarks"]
path = flagengine/sdk-benchmarks
url = https://github.com/Flagsmith/sdk-benchmarks.git
Expand Down
6 changes: 3 additions & 3 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ func TestGetEnvironmentFlagsUseslocalEnvironmentWhenAvailable(t *testing.T) {
assert.Equal(t, fixtures.Feature1Name, allFlags[0].FeatureName)
assert.Equal(t, fixtures.Feature1ID, allFlags[0].FeatureID)
assert.Equal(t, fixtures.Feature1Value, allFlags[0].Value)
assert.Equal(t, "DEFAULT", allFlags[0].Reason)
assert.Equal(t, "STATIC", allFlags[0].Reason)
}

func TestGetEnvironmentFlagsCallsAPIWhenLocalEnvironmentNotAvailable(t *testing.T) {
Expand Down Expand Up @@ -515,7 +515,7 @@ func TestGetEnvironmentFlagsIgnoresSegmentOverridesNeedingAnIdentity(t *testing.
assert.NoError(t, err)
assert.True(t, flag.Enabled)
assert.Equal(t, fixtures.Feature1Value, flag.Value)
assert.Equal(t, "DEFAULT", flag.Reason)
assert.Equal(t, "STATIC", flag.Reason)
}

func TestGetEnvironmentFlagsAppliesSegmentOverrideDependingOnAnotherFlag(t *testing.T) {
Expand Down Expand Up @@ -655,7 +655,7 @@ func TestGetEnvironmentFlagsHasNoVariantForMultivariateFeature(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "control_value", flag.Value)
assert.Empty(t, flag.Variant)
assert.Equal(t, "DEFAULT", flag.Reason)
assert.Equal(t, "STATIC", flag.Reason)
}

func TestGetIdentityFlagsUseslocalEnvironmentWhenAvailable(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion flagengine/dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (r *flagDependencyResolver) resolveFeature(featureName string) {
return
}

reason := "DEFAULT"
reason := getFallbackReason(len(r.segmentKeysByFeatureName[featureName]) > 0)
if hasOverride {
featureContext = *override.featureContext
reason = fmt.Sprintf("TARGETING_MATCH; segment=%s", override.segmentName)
Expand Down
2 changes: 1 addition & 1 deletion flagengine/engine-test-data
Submodule engine-test-data updated 118 files
13 changes: 12 additions & 1 deletion flagengine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,29 @@ func getIdentityKey(ec *engine_eval.EngineEvaluationContext) *string {
return &ec.Identity.Key
}

// getFallbackReason returns the reason for a flag evaluated to its environment default:
// DEFAULT if segments override the feature but none matched, STATIC if none override it.
func getFallbackReason(isTargeted bool) string {
if isTargeted {
return "DEFAULT"
}
return "STATIC"
}

// getFlagResults evaluates every feature, reusing the flags already resolved as dependencies.
func getFlagResults(ec *engine_eval.EngineEvaluationContext, featureOverrides map[string]featureContextWithSegmentName, resolved map[string]*engine_eval.FlagResult) map[string]*engine_eval.FlagResult {
flags := make(map[string]*engine_eval.FlagResult, len(ec.Features))
identityKey := getIdentityKey(ec)
targetedFeatureNames := engine_eval.GetTargetedFeatureNames(ec)

if ec.Features != nil {
for featureName, featureContext := range ec.Features {
if flagResult, ok := resolved[featureName]; ok {
flags[featureName] = flagResult
continue
}
reason := "DEFAULT"
_, isTargeted := targetedFeatureNames[featureName]
reason := getFallbackReason(isTargeted)
// Check if there's an override for this feature
if override, ok := featureOverrides[featureName]; ok {
featureContext = *override.featureContext
Expand Down
3 changes: 3 additions & 0 deletions flagengine/engine_eval/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ type EngineEvaluationContext struct {
Identity *IdentityContext `json:"identity,omitempty"`
// Segments applicable to the evaluation context.
Segments map[string]SegmentContext `json:"segments,omitempty"`
// Names of the features overridden by at least one of Segments, precomputed by the
// mappers so that evaluations reuse them. Computed per evaluation when nil.
TargetedFeatureNames map[string]struct{} `json:"-"`
}

// Environment context required for evaluation.
Expand Down
17 changes: 17 additions & 0 deletions flagengine/engine_eval/mappers.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,24 @@ func MapEnvironmentDocumentToEvaluationContext(env *environments.EnvironmentMode
}
}

ctx.TargetedFeatureNames = GetTargetedFeatureNames(&ctx)
return ctx
}

// GetTargetedFeatureNames returns the names of the features overridden by at least one segment.
func GetTargetedFeatureNames(ec *EngineEvaluationContext) map[string]struct{} {
if ec.TargetedFeatureNames != nil {
return ec.TargetedFeatureNames
}
targetedFeatureNames := make(map[string]struct{}, len(ec.Features))
for _, segmentContext := range ec.Segments {
for i := range segmentContext.Overrides {
targetedFeatureNames[segmentContext.Overrides[i].Name] = struct{}{}
}
}
return targetedFeatureNames
}

// mapMultivariateFeatureStateValuesToVariants converts multivariate feature state values to FeatureValue variants.
func mapMultivariateFeatureStateValuesToVariants(multivariateValues []*features.MultivariateFeatureStateValueModel) []FeatureValue {
if len(multivariateValues) == 0 {
Expand Down Expand Up @@ -338,6 +353,8 @@ func RemoveIdentityFromContext(context *EngineEvaluationContext) EngineEvaluatio
}
newContext.Segments[key] = segment
}
newContext.TargetedFeatureNames = nil
newContext.TargetedFeatureNames = GetTargetedFeatureNames(&newContext)
return newContext
}

Expand Down
Loading