diff --git a/api/tests/unit/util/mappers/test_unit_mappers_engine.py b/api/tests/unit/util/mappers/test_unit_mappers_engine.py index e70b85ab63b7..6e08270cbbf5 100644 --- a/api/tests/unit/util/mappers/test_unit_mappers_engine.py +++ b/api/tests/unit/util/mappers/test_unit_mappers_engine.py @@ -33,6 +33,7 @@ from environments.models import Environment from features.models import FeatureSegment, FeatureState +from features.versioning.models import EnvironmentFeatureVersion from features.versioning.tasks import enable_v2_versioning from integrations.common.models import IntegrationsModel from integrations.dynatrace.models import DynatraceConfiguration @@ -40,6 +41,7 @@ from integrations.segment.models import SegmentConfiguration from integrations.webhook.models import WebhookConfiguration from segments.models import Segment, SegmentRule +from users.models import FFAdminUser from util.mappers import engine if TYPE_CHECKING: @@ -598,3 +600,59 @@ def test_map_environment_to_engine_following_migration_to_v2_versioning( assert mapped_segment_override.django_id == v2_segment_override.id assert mapped_segment_override.enabled is True assert mapped_segment_override.feature_state_value == v2_segment_override_value + + +def test_map_environment_to_engine_v2_versioning_segment_overrides( + environment_v2_versioning: Environment, + segment: Segment, + feature: "Feature", + staff_user: FFAdminUser, +) -> None: + # Given + # Another segment + another_segment = Segment.objects.create( + name="another_segment", project=feature.project + ) + + # First, let's create a version that includes 2 segment overrides + v2 = EnvironmentFeatureVersion.objects.create( + feature=feature, environment=environment_v2_versioning + ) + for _segment in [segment, another_segment]: + FeatureState.objects.create( + feature=feature, + environment=environment_v2_versioning, + environment_feature_version=v2, + feature_segment=FeatureSegment.objects.create( + feature=feature, + segment=_segment, + environment=environment_v2_versioning, + environment_feature_version=v2, + ), + ) + v2.publish(staff_user) + + # Now, let's create another new version which will keep one of the segment overrides + # and remove the other. + v3 = EnvironmentFeatureVersion.objects.create( + feature=feature, environment=environment_v2_versioning + ) + + v3_segment_override = FeatureState.objects.get( + feature_segment__segment=segment, environment_feature_version=v3 + ) + FeatureState.objects.filter( + feature_segment__segment=another_segment, environment_feature_version=v3 + ).delete() + + v3.publish(staff_user) + + # When + environment_model = engine.map_environment_to_engine(environment_v2_versioning) + + # Then + assert len(environment_model.project.segments[0].feature_states) == 1 + assert ( + environment_model.project.segments[0].feature_states[0].django_id + == v3_segment_override.id + ) diff --git a/api/util/mappers/engine.py b/api/util/mappers/engine.py index 243b1fff6eec..e5e8c733ceef 100644 --- a/api/util/mappers/engine.py +++ b/api/util/mappers/engine.py @@ -1,6 +1,7 @@ from collections.abc import Iterable from itertools import chain from typing import TYPE_CHECKING, Dict, List, Optional +from uuid import UUID from flag_engine.environments.integrations.models import IntegrationModel from flag_engine.environments.models import ( @@ -25,6 +26,7 @@ ) from environments.constants import IDENTITY_INTEGRATIONS_RELATION_NAMES +from features.versioning.models import EnvironmentFeatureVersion if TYPE_CHECKING: # pragma: no cover from environments.identities.models import Identity, Trait @@ -200,6 +202,16 @@ def map_environment_to_engine( project_segment_feature_states_by_segment_id = _get_segment_feature_states( project_segments, environment.pk, + latest_environment_feature_version_uuids=( + { + efv.uuid + for efv in EnvironmentFeatureVersion.objects.get_latest_versions( + environment + ) + } + if environment.use_v2_feature_versioning + else [] + ), ) environment_feature_states: List["FeatureState"] = _get_prioritised_feature_states( [ @@ -419,14 +431,26 @@ def _get_prioritised_feature_states( def _get_segment_feature_states( segments: Iterable["Segment"], environment_id: int, + latest_environment_feature_version_uuids: Iterable[UUID], ) -> Dict[int, List["FeatureState"]]: feature_states_by_segment_id = {} + for segment in segments: segment_feature_states = feature_states_by_segment_id.setdefault(segment.pk, []) + for feature_segment in segment.feature_segments.all(): if feature_segment.environment_id != environment_id: continue + + if ( + latest_environment_feature_version_uuids + and feature_segment.environment_feature_version_id + not in latest_environment_feature_version_uuids + ): + continue + segment_feature_states += _get_prioritised_feature_states( feature_segment.feature_states.all() ) + return feature_states_by_segment_id