Skip to content

Commit

Permalink
feat(environment): Add toggle for identity override in local eval (#4576
Browse files Browse the repository at this point in the history
)
  • Loading branch information
gagantrivedi authored Sep 4, 2024
1 parent b19bf83 commit 5e82c97
Show file tree
Hide file tree
Showing 14 changed files with 148 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Generated by Django 4.2.15 on 2024-09-04 05:40

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("environments", "0034_alter_environment_project"),
]

operations = [
migrations.AddField(
model_name="environment",
name="use_identity_overrides_in_local_eval",
field=models.BooleanField(
default=False,
help_text="When enabled, identity overrides will be included in the environment document",
),
),
migrations.AddField(
model_name="historicalenvironment",
name="use_identity_overrides_in_local_eval",
field=models.BooleanField(
default=False,
help_text="When enabled, identity overrides will be included in the environment document",
),
),
# Update the default to true for new environments
migrations.AlterField(
model_name="environment",
name="use_identity_overrides_in_local_eval",
field=models.BooleanField(
default=True,
help_text="When enabled, identity overrides will be included in the environment document",
),
),
migrations.AlterField(
model_name="historicalenvironment",
name="use_identity_overrides_in_local_eval",
field=models.BooleanField(
default=True,
help_text="When enabled, identity overrides will be included in the environment document",
),
),
]
7 changes: 5 additions & 2 deletions api/environments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,12 @@ class Environment(
help_text="If true, will hide sensitive data(e.g: traits, description etc) from the SDK endpoints",
)

objects = EnvironmentManager()

use_v2_feature_versioning = models.BooleanField(default=False)
use_identity_overrides_in_local_eval = models.BooleanField(
default=True,
help_text="When enabled, identity overrides will be included in the environment document",
)
objects = EnvironmentManager()

class Meta:
ordering = ["id"]
Expand Down
1 change: 1 addition & 0 deletions api/environments/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class Meta:
"use_identity_composite_key_for_hashing",
"hide_sensitive_data",
"use_v2_feature_versioning",
"use_identity_overrides_in_local_eval",
)
read_only_fields = ("use_v2_feature_versioning",)

Expand Down
17 changes: 3 additions & 14 deletions api/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ environs = "~9.2.0"
django-lifecycle = "~1.0.0"
drf-writable-nested = "~0.6.2"
django-filter = "~2.4.0"
flagsmith-flag-engine = "^5.1.1"
flagsmith-flag-engine = "^5.2.0"
boto3 = "~1.28.78"
slack-sdk = "~3.9.0"
asgiref = "~3.8.1"
Expand Down
2 changes: 2 additions & 0 deletions api/tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ def identity_document(
}
return {
"composite_key": f"{environment_api_key}_user_1_test",
"dashboard_alias": None,
"identity_traits": identity_traits,
"identity_features": [
_environment_feature_state_1_document,
Expand All @@ -457,6 +458,7 @@ def identity_document_without_fs(environment_api_key, identity_traits):
"environment_api_key": environment_api_key,
"identity_uuid": "59efa2a7-6a45-46d6-b953-a7073a90eacf",
"django_id": None,
"dashboard_alias": None,
}


Expand Down
1 change: 1 addition & 0 deletions api/tests/unit/edge_api/identities/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def identity_document_without_fs(environment):
"environment_api_key": environment.api_key,
"identity_uuid": "59efa2a7-6a45-46d6-b953-a7073a90eacf",
"django_id": None,
"dashboard_alias": None,
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ def test_sync_identity_document_features_removes_deleted_features(
"feature_state_value": "feature_1_value",
"featurestate_uuid": "4a8fbe06-d4cd-4686-a184-d924844bb422",
"django_id": 1,
"dashboard_alias": None,
"feature": {
"name": "feature_that_does_not_exists",
"type": "STANDARD",
Expand Down
7 changes: 7 additions & 0 deletions api/tests/unit/environments/test_unit_environments_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ def test_should_create_environments(
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["description"] == description
assert response.json()["use_mv_v2_evaluation"] is True
assert response.json()["use_identity_overrides_in_local_eval"] is True
assert response.json()["use_identity_composite_key_for_hashing"] is True

# and user is admin
Expand Down Expand Up @@ -787,6 +788,7 @@ def test_audit_log_entry_created_when_environment_updated(
banner_colour = "#FF0000"
hide_disabled_flags = True
use_identity_composite_key_for_hashing = True
use_identity_overrides_in_local_eval = True
hide_sensitive_data = True

data = {
Expand All @@ -796,6 +798,7 @@ def test_audit_log_entry_created_when_environment_updated(
"banner_colour": banner_colour,
"hide_disabled_flags": hide_disabled_flags,
"use_identity_composite_key_for_hashing": use_identity_composite_key_for_hashing,
"use_identity_overrides_in_local_eval": use_identity_overrides_in_local_eval,
"hide_sensitive_data": hide_sensitive_data,
}

Expand All @@ -816,6 +819,10 @@ def test_audit_log_entry_created_when_environment_updated(
assert response.json()["banner_colour"] == banner_colour
assert response.json()["hide_disabled_flags"] == hide_disabled_flags
assert response.json()["hide_sensitive_data"] == hide_sensitive_data
assert (
response.json()["use_identity_overrides_in_local_eval"]
== use_identity_overrides_in_local_eval
)
assert (
response.json()["use_identity_composite_key_for_hashing"]
== use_identity_composite_key_for_hashing
Expand Down
3 changes: 3 additions & 0 deletions api/tests/unit/util/mappers/test_unit_mappers_dynamodb.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def test_map_environment_to_environment_document__call_expected(
"segment_config": None,
"updated_at": expected_updated_at,
"use_identity_composite_key_for_hashing": True,
"use_identity_overrides_in_local_eval": True,
"webhook_config": None,
}

Expand Down Expand Up @@ -130,6 +131,7 @@ def test_map_identity_to_identity_document__call_expected(
"identity_features": [],
"identity_traits": [{"trait_key": "key1", "trait_value": "value1"}],
"identity_uuid": mocker.ANY,
"dashboard_alias": None,
}
assert uuid.UUID(result["identity_uuid"])

Expand Down Expand Up @@ -195,6 +197,7 @@ def test_map_environment_to_environment_v2_document__call_expected(
"segment_config": None,
"updated_at": expected_updated_at,
"use_identity_composite_key_for_hashing": True,
"use_identity_overrides_in_local_eval": True,
"webhook_config": None,
}

Expand Down
1 change: 1 addition & 0 deletions api/tests/unit/util/mappers/test_unit_mappers_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ def test_map_environment_to_engine__return_expected(
allow_client_traits=environment.allow_client_traits,
updated_at=environment.updated_at,
use_identity_composite_key_for_hashing=environment.use_identity_composite_key_for_hashing,
use_identity_overrides_in_local_eval=environment.use_identity_overrides_in_local_eval,
hide_sensitive_data=environment.hide_sensitive_data,
hide_disabled_flags=environment.hide_disabled_flags,
amplitude_config=None,
Expand Down
70 changes: 70 additions & 0 deletions api/tests/unit/util/mappers/test_unit_mappers_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def test_map_environment_to_sdk_document__return_expected(
"django_id": identity.pk,
"environment_api_key": expected_api_key,
"identifier": identity.identifier,
"dashboard_alias": None,
"identity_features": [
{
"django_id": identity_featurestate.pk,
Expand Down Expand Up @@ -105,4 +106,73 @@ def test_map_environment_to_sdk_document__return_expected(
},
"updated_at": environment.updated_at,
"use_identity_composite_key_for_hashing": True,
"use_identity_overrides_in_local_eval": True,
}


def test_map_environment_to_sdk_document__for_environment_with_identity_overrides_in_local_eval_disabled(
mocker: "MockerFixture",
environment: "Environment",
feature_state: "FeatureState",
identity: Identity,
identity_featurestate: "FeatureState",
identity_without_overrides: Identity,
) -> None:
# Given
expected_overridden_value = "some-overridden-value"
expected_api_key = environment.api_key

identity_featurestate.feature_state_value.string_value = expected_overridden_value
identity_featurestate.feature_state_value.save()
identity_featurestate.enabled = True
identity_featurestate.save()

environment.use_identity_overrides_in_local_eval = False
environment.save()

# When
result = map_environment_to_sdk_document(environment)

# Then
assert result == {
"allow_client_traits": True,
"api_key": expected_api_key,
"feature_states": [
{
"django_id": feature_state.pk,
"enabled": False,
"feature": {
"id": feature_state.feature.pk,
"name": "Test Feature1",
"type": "STANDARD",
},
"feature_segment": None,
"feature_state_value": None,
"featurestate_uuid": feature_state.uuid,
"multivariate_feature_state_values": [],
}
],
"identity_overrides": [],
"hide_disabled_flags": None,
"hide_sensitive_data": False,
"id": environment.pk,
"name": "Test Environment",
"project": {
"enable_realtime_updates": False,
"hide_disabled_flags": False,
"id": environment.project.pk,
"name": "Test Project",
"organisation": {
"feature_analytics": False,
"id": environment.project.organisation.pk,
"name": "Test Org",
"persist_trait_data": True,
"stop_serving_flags": False,
},
"segments": [],
"server_key_only_feature_ids": [],
},
"updated_at": environment.updated_at,
"use_identity_composite_key_for_hashing": True,
"use_identity_overrides_in_local_eval": False,
}
1 change: 1 addition & 0 deletions api/util/mappers/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ def map_environment_to_engine(
use_identity_composite_key_for_hashing=environment.use_identity_composite_key_for_hashing,
hide_sensitive_data=environment.hide_sensitive_data,
hide_disabled_flags=environment.hide_disabled_flags,
use_identity_overrides_in_local_eval=environment.use_identity_overrides_in_local_eval,
#
# Relationships:
project=project_model,
Expand Down
11 changes: 6 additions & 5 deletions api/util/mappers/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ def map_environment_to_sdk_document(environment: "Environment") -> SDKDocument:
"""
# Read relationships.
identities_with_overrides = {}
for feature_state in environment.feature_states.all():
if (identity_id := feature_state.identity_id) and (
identity_id not in identities_with_overrides
):
identities_with_overrides[identity_id] = feature_state.identity
if environment.use_identity_overrides_in_local_eval:
for feature_state in environment.feature_states.all():
if (identity_id := feature_state.identity_id) and (
identity_id not in identities_with_overrides
):
identities_with_overrides[identity_id] = feature_state.identity

# Get the engine data.
engine_environment = map_environment_to_engine(environment, with_integrations=False)
Expand Down

0 comments on commit 5e82c97

Please sign in to comment.