Skip to content

Commit e9c16e1

Browse files
feat: enable feature versioning for new environments (#5108)
1 parent a6a5b06 commit e9c16e1

3 files changed

Lines changed: 105 additions & 0 deletions

File tree

api/environments/models.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
AFTER_DELETE,
1616
AFTER_SAVE,
1717
AFTER_UPDATE,
18+
BEFORE_CREATE,
1819
LifecycleModel,
1920
hook,
2021
)
@@ -49,6 +50,7 @@
4950
)
5051
from features.models import Feature, FeatureSegment, FeatureState
5152
from features.multivariate.models import MultivariateFeatureStateValue
53+
from integrations.flagsmith.client import get_client
5254
from metadata.models import Metadata
5355
from projects.models import Project
5456
from segments.models import Segment
@@ -195,6 +197,24 @@ def delete_environment_document_from_cache(self) -> None:
195197
):
196198
environment_document_cache.delete(self.api_key)
197199

200+
@hook(BEFORE_CREATE) # type: ignore[misc]
201+
def enable_v2_versioning(self) -> None:
202+
if self.use_v2_feature_versioning:
203+
# if the environment has already been created with versioning enabled,
204+
# we don't want to disable it based on the flag state.
205+
return
206+
207+
flagsmith_client = get_client("local", local_eval=True)
208+
organisation = self.project.organisation
209+
enable_v2_versioning = flagsmith_client.get_identity_flags(
210+
organisation.flagsmith_identifier,
211+
traits={
212+
"organisation_id": organisation.id,
213+
"subscription.plan": organisation.subscription.plan,
214+
},
215+
).is_feature_enabled("enable_feature_versioning_for_new_environments")
216+
self.use_v2_feature_versioning = enable_v2_versioning
217+
198218
def __str__(self): # type: ignore[no-untyped-def]
199219
return "Project %s - Environment %s" % (self.project.name, self.name)
200220

api/tests/unit/environments/conftest.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import typing
12
from unittest.mock import Mock
23

34
import pytest
@@ -12,3 +13,31 @@ def mock_dynamo_env_wrapper(mocker: MockerFixture) -> Mock:
1213
@pytest.fixture()
1314
def mock_dynamo_env_v2_wrapper(mocker: MockerFixture) -> Mock:
1415
return mocker.patch("environments.models.environment_v2_wrapper")
16+
17+
18+
@pytest.fixture()
19+
def enable_v2_versioning_for_new_environments(
20+
mocker: MockerFixture,
21+
) -> typing.Callable[[], None]:
22+
"""
23+
This fixture returns a callable that allows us to enable the flag such that new environments
24+
are created with feature versioning v2 enabled. It returns a callable to ensure that we can
25+
control when the flag is enabled in the test code, to ensure that other fixtures are unaffected.
26+
27+
Relevant issue for improving this: https://github.com/Flagsmith/flagsmith-python-client/issues/135
28+
"""
29+
30+
def _enable_v2_versioning_for_new_environments() -> None:
31+
mock_flagsmith_client = mocker.MagicMock()
32+
mocker.patch(
33+
"environments.models.get_client", return_value=mock_flagsmith_client
34+
)
35+
36+
mocked_identity_flags = mock_flagsmith_client.get_identity_flags.return_value
37+
38+
def is_feature_enabled(feature_name: str) -> bool:
39+
return feature_name == "enable_feature_versioning_for_new_environments"
40+
41+
mocked_identity_flags.is_feature_enabled.side_effect = is_feature_enabled
42+
43+
return _enable_v2_versioning_for_new_environments

api/tests/unit/environments/test_unit_environments_models.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,3 +1208,59 @@ def test_environment_metric_query_helpers_match_expected_counts(
12081208
assert change_request_count_result == change_request_count
12091209
assert scheduled_count_result == scheduled_change_count
12101210
assert identity_override_count == 0
1211+
1212+
1213+
def test_environment_create_with_use_v2_feature_versioning_true(
1214+
project: Project,
1215+
feature: Feature,
1216+
enable_v2_versioning_for_new_environments: typing.Callable[[], None],
1217+
) -> None:
1218+
# Given
1219+
enable_v2_versioning_for_new_environments()
1220+
1221+
# When
1222+
new_environment = Environment.objects.create(
1223+
name="new-environment",
1224+
project=project,
1225+
)
1226+
1227+
# Then
1228+
assert EnvironmentFeatureVersion.objects.filter(
1229+
environment=new_environment, feature=feature
1230+
).exists()
1231+
1232+
1233+
def test_environment_clone_from_versioned_environment_with_use_v2_feature_versioning_true(
1234+
project: Project,
1235+
environment_v2_versioning: Environment,
1236+
feature: Feature,
1237+
enable_v2_versioning_for_new_environments: typing.Callable[[], None],
1238+
) -> None:
1239+
# Given
1240+
enable_v2_versioning_for_new_environments()
1241+
1242+
# When
1243+
new_environment = environment_v2_versioning.clone(name="new-environment")
1244+
1245+
# Then
1246+
assert EnvironmentFeatureVersion.objects.filter(
1247+
environment=new_environment, feature=feature
1248+
).exists()
1249+
1250+
1251+
def test_environment_clone_from_non_versioned_environment_with_use_v2_feature_versioning_true(
1252+
project: Project,
1253+
environment: Environment,
1254+
feature: Feature,
1255+
enable_v2_versioning_for_new_environments: typing.Callable[[], None],
1256+
) -> None:
1257+
# Given
1258+
enable_v2_versioning_for_new_environments()
1259+
1260+
# When
1261+
new_environment = environment.clone(name="new-environment")
1262+
1263+
# Then
1264+
assert not EnvironmentFeatureVersion.objects.filter(
1265+
environment=new_environment, feature=feature
1266+
).exists()

0 commit comments

Comments
 (0)