Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(versioning): fix cloning environments using v2 versioning #3999

Merged
merged 5 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
33 changes: 31 additions & 2 deletions api/environments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from environments.managers import EnvironmentManager
from features.models import Feature, FeatureSegment, FeatureState
from features.multivariate.models import MultivariateFeatureStateValue
from features.versioning.models import EnvironmentFeatureVersion
from metadata.models import Metadata
from projects.models import Project
from segments.models import Segment
Expand Down Expand Up @@ -174,8 +175,36 @@ def clone(self, name: str, api_key: str = None) -> "Environment":
# Since identities are closely tied to the environment
# it does not make much sense to clone them, hence
# only clone feature states without identities
for feature_state in self.feature_states.filter(identity=None):
feature_state.clone(clone, live_from=feature_state.live_from)
queryset = self.feature_states.filter(identity=None)

if self.use_v2_feature_versioning:
# Grab the latest feature versions from the source environment. Note that,
# to clone these later on in the logic, we need the ORM objects, not the
# RawSQL queryset, so we have to nest the queries here.
latest_environment_feature_versions = EnvironmentFeatureVersion.objects.filter(
uuid__in=[
efv.uuid
for efv in EnvironmentFeatureVersion.objects.get_latest_versions(
self
)
]
)

# Create a dictionary holding the environment feature versions (unique per feature)
# to use in the cloned environment.
clone_environment_feature_versions = {
efv.feature_id: efv.clone(environment=clone)
for efv in latest_environment_feature_versions
}

for feature_state in queryset.filter(
environment_feature_version__in=latest_environment_feature_versions
):
clone_efv = clone_environment_feature_versions[feature_state.feature_id]
feature_state.clone(clone, environment_feature_version=clone_efv)
else:
for feature_state in queryset:
feature_state.clone(clone, live_from=feature_state.live_from)

return clone

Expand Down
10 changes: 10 additions & 0 deletions api/features/versioning/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime
import typing
import uuid
from copy import deepcopy

from core.models import (
SoftDeleteExportableModel,
Expand Down Expand Up @@ -128,3 +129,12 @@ def publish(
if persist:
self.save()
environment_feature_version_published.send(self.__class__, instance=self)

def clone(self, environment: "Environment") -> "EnvironmentFeatureVersion":
_clone = deepcopy(self)

_clone.uuid = None
_clone.environment = environment

_clone.save()
return _clone
57 changes: 57 additions & 0 deletions api/tests/unit/environments/test_unit_environments_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
from features.models import Feature, FeatureState
from features.multivariate.models import MultivariateFeatureOption
from features.versioning.models import EnvironmentFeatureVersion
from features.versioning.tasks import enable_v2_versioning
from features.versioning.versioning_service import (
get_environment_flags_queryset,
)
from organisations.models import Organisation, OrganisationRole
from projects.models import EdgeV2MigrationStatus, Project
from segments.models import Segment
Expand Down Expand Up @@ -937,3 +941,56 @@ def test_create_environment_creates_feature_states_in_all_environments_and_envir
EnvironmentFeatureVersion.objects.filter(environment=environment).count() == 2
)
assert environment.feature_states.count() == 2


def test_clone_environment_v2_versioning(
feature: Feature,
feature_state: FeatureState,
segment: Segment,
segment_featurestate: FeatureState,
environment: Environment,
) -> None:
# Given
expected_environment_fs_enabled_value = True
expected_segment_fs_enabled_value = True

# First let's create some new versions via the old versioning methods
feature_state.clone(environment, version=2)
feature_state.clone(environment, version=3)

# and a draft version
feature_state.clone(environment, as_draft=True)

# Now let's enable v2 versioning for the environment
enable_v2_versioning(environment.id)
environment.refresh_from_db()

# Finally, let's create another version using the new versioning methods
# and update some values on the feature states in it.
v2 = EnvironmentFeatureVersion.objects.create(
feature=feature, environment=environment
)
v2.feature_states.filter(feature_segment__isnull=True).update(
enabled=expected_environment_fs_enabled_value
)
v2.feature_states.filter(feature_segment__isnull=False).update(
enabled=expected_segment_fs_enabled_value
)
v2.publish()

# When
cloned_environment = environment.clone(name="Cloned environment")

# Then
assert cloned_environment.use_v2_feature_versioning is True

cloned_environment_flags = get_environment_flags_queryset(cloned_environment)

assert (
cloned_environment_flags.get(feature_segment__isnull=True).enabled
is expected_environment_fs_enabled_value
)
assert (
cloned_environment_flags.get(feature_segment__segment=segment).enabled
is expected_segment_fs_enabled_value
)
Loading