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
15 changes: 15 additions & 0 deletions posthog/feature_flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ def is_condition_match(
property_type = prop.get("type")
if property_type == "cohort":
matches = match_cohort(prop, properties, cohort_properties)
elif property_type == "flag":
log.warning(
"Flag dependency filters are not supported in local evaluation. "
"Skipping condition for flag '%s' with dependency on flag '%s'",
feature_flag.get("key", "unknown"),
prop.get("key", "unknown"),
)
continue
else:
matches = match_property(prop, properties)
if not matches:
Expand Down Expand Up @@ -317,6 +325,13 @@ def match_property_group(property_group, property_values, cohort_properties) ->
try:
if prop.get("type") == "cohort":
matches = match_cohort(prop, property_values, cohort_properties)
elif prop.get("type") == "flag":
log.warning(
"Flag dependency filters are not supported in local evaluation. "
"Skipping condition with dependency on flag '%s'",
prop.get("key", "unknown"),
)
continue
else:
matches = match_property(prop, property_values)

Expand Down
71 changes: 71 additions & 0 deletions posthog/test/test_feature_flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,77 @@ def test_feature_flags_local_evaluation_for_negated_cohorts(
self.assertEqual(patch_flags.call_count, 0)
self.assertEqual(patch_get.call_count, 0)

@mock.patch("posthog.feature_flags.log")
@mock.patch("posthog.client.flags")
@mock.patch("posthog.client.get")
def test_feature_flags_with_flag_dependencies(
self, patch_get, patch_flags, mock_log
):
client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
client.feature_flags = [
{
"id": 1,
"name": "Flag with Dependencies",
"key": "flag-with-dependencies",
"active": True,
"filters": {
"groups": [
{
"properties": [
{
"key": "beta-feature",
"operator": "exact",
"value": True,
"type": "flag",
},
{
"key": "email",
"operator": "icontains",
"value": "@example.com",
"type": "person",
},
],
"rollout_percentage": 100,
}
],
},
}
]

# Test that flag evaluation doesn't fail when encountering a flag dependency
# The flag should evaluate based on other conditions (email contains @example.com)
# Since flag dependencies aren't implemented, it should skip the flag condition
# and evaluate based on the email condition only
feature_flag_match = client.get_feature_flag(
"flag-with-dependencies",
"test-user",
person_properties={"email": "[email protected]"},
)
self.assertEqual(feature_flag_match, True)
self.assertEqual(patch_flags.call_count, 0)
self.assertEqual(patch_get.call_count, 0)

# Verify warning was logged for flag dependency
mock_log.warning.assert_called_with(
"Flag dependency filters are not supported in local evaluation. "
"Skipping condition for flag '%s' with dependency on flag '%s'",
"flag-with-dependencies",
"beta-feature",
)

# Test with email that doesn't match
feature_flag_match = client.get_feature_flag(
"flag-with-dependencies",
"test-user-2",
person_properties={"email": "[email protected]"},
)
self.assertEqual(feature_flag_match, False)
self.assertEqual(patch_flags.call_count, 0)
self.assertEqual(patch_get.call_count, 0)

# Verify warning was logged again for the second evaluation
self.assertEqual(mock_log.warning.call_count, 2)

@mock.patch("posthog.client.Poller")
@mock.patch("posthog.client.get")
def test_load_feature_flags(self, patch_get, patch_poll):
Expand Down
Loading