diff --git a/.gitmodules b/.gitmodules index 935bed8..e1bc1d5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "tests/engine_tests/engine-test-data"] path = tests/engine_tests/engine-test-data url = https://github.com/flagsmith/engine-test-data.git - branch = v3.11.0 + branch = v3.12.1 diff --git a/flag_engine/segments/evaluator.py b/flag_engine/segments/evaluator.py index 9b96efa..57b7150 100644 --- a/flag_engine/segments/evaluator.py +++ b/flag_engine/segments/evaluator.py @@ -213,6 +213,7 @@ def evaluate_features( return {} flags: dict[str, FlagResult[FeatureMetadataT]] = {} + targeted_feature_names = get_targeted_feature_names(context) for feature_context in features.values(): feature_name = feature_context["name"] @@ -226,12 +227,42 @@ def evaluate_features( flags[feature_name] = get_flag_result_from_context( context=context, feature_context=context["features"][feature_name], - reason="DEFAULT", + reason=get_fallback_reason(feature_name, targeted_feature_names), ) return flags +def get_targeted_feature_names( + context: _EvaluationContextAnyMeta, +) -> set[str]: + """ + Get the names of the features overridden by at least one segment. + """ + targeted_feature_names: set[str] = set() + segment_contexts = context.get("segments") or {} + + for segment_context in segment_contexts.values(): + overrides = segment_context.get("overrides") or () + for override in overrides: + targeted_feature_names.add(override["name"]) + + return targeted_feature_names + + +def get_fallback_reason( + feature_name: str, + targeted_feature_names: typing.Container[str], +) -> str: + """ + Get the reason for a flag evaluated to its environment default. + + `DEFAULT` if the feature has targeting rules that did not match, + `STATIC` if it has none. + """ + return "DEFAULT" if feature_name in targeted_feature_names else "STATIC" + + _JSONPATH_PREFIX = "$." CIRCULAR_DEPENDENCY_REASON = "ERROR; code=CIRCULAR_DEPENDENCY" @@ -295,7 +326,9 @@ def resolve_feature(self, feature_name: str) -> None: self._flags[feature_name] = get_flag_result_from_context( context=self._context, feature_context=feature_context, - reason="DEFAULT", + reason=get_fallback_reason( + feature_name, self._segment_keys_by_feature_name + ), ) def matches_segment(self, segment_key: str) -> bool: diff --git a/tests/engine_tests/engine-test-data b/tests/engine_tests/engine-test-data index 28363b3..cb06740 160000 --- a/tests/engine_tests/engine-test-data +++ b/tests/engine_tests/engine-test-data @@ -1 +1 @@ -Subproject commit 28363b388de2850c7c3f1b6e7a8bdd41d8da433c +Subproject commit cb067401093766e55c3354c1de7c73d9331334df diff --git a/tests/unit/test_engine.py b/tests/unit/test_engine.py index 54da757..88a0f27 100644 --- a/tests/unit/test_engine.py +++ b/tests/unit/test_engine.py @@ -24,14 +24,14 @@ def test_get_evaluation_result__no_overrides__returns_expected( "feature_1": { "enabled": True, "name": "feature_1", - "reason": "DEFAULT", + "reason": "STATIC", "value": None, "variant": None, }, "feature_2": { "enabled": False, "name": "feature_2", - "reason": "DEFAULT", + "reason": "STATIC", "value": None, "variant": None, }, @@ -59,7 +59,7 @@ def test_get_evaluation_result__segment_override__returns_expected( "feature_2": { "enabled": False, "name": "feature_2", - "reason": "DEFAULT", + "reason": "STATIC", "value": None, "variant": None, }, @@ -115,7 +115,7 @@ def test_get_evaluation_result__identity_override__returns_expected( "feature_2": { "enabled": False, "name": "feature_2", - "reason": "DEFAULT", + "reason": "STATIC", "value": None, "variant": None, }, @@ -213,7 +213,7 @@ def test_get_evaluation_result__two_segments_override_same_feature__returns_expe "feature_2": { "enabled": False, "name": "feature_2", - "reason": "DEFAULT", + "reason": "STATIC", "value": None, "variant": None, },