-
Notifications
You must be signed in to change notification settings - Fork 449
feat: enable feature versioning for environments in newly created projects #5108
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
base: main
Are you sure you want to change the base?
Changes from all commits
ad6f7bb
dfc01de
9250a79
59d1e7c
a75a41c
48f3b65
aa08b19
2b4ec8c
50bd191
beb7028
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,3 +66,5 @@ | |
ENABLE_POSTPONE_DECORATOR = False | ||
|
||
DEBUG = True | ||
|
||
USE_GLOBAL_FLAGSMITH_CLIENTS = False |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,13 +25,15 @@ | |
def get_client(name: str = "default", local_eval: bool = False) -> Flagsmith: | ||
global _flagsmith_clients | ||
|
||
try: | ||
_flagsmith_client = _flagsmith_clients[name] | ||
except (KeyError, TypeError): | ||
kwargs = _get_client_kwargs() | ||
kwargs["enable_local_evaluation"] = local_eval | ||
_flagsmith_client = Flagsmith(**kwargs) | ||
_flagsmith_clients[name] = _flagsmith_client | ||
if settings.USE_GLOBAL_FLAGSMITH_CLIENTS and ( | ||
client := _flagsmith_clients.get(name) | ||
): | ||
Comment on lines
+28
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use global Flagsmith clients? If it's just for tests, I'd prefer a fixture mocking them than a Django setting. This will also solve the coverage issue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, TBH, I have no idea why this logic exists. I wrote it far too long ago to remember the context. When I came back to it yesterday, I also hated it :) |
||
return client | ||
|
||
kwargs = _get_client_kwargs() | ||
kwargs["enable_local_evaluation"] = local_eval | ||
_flagsmith_client = Flagsmith(**kwargs) | ||
_flagsmith_clients[name] = _flagsmith_client | ||
|
||
return _flagsmith_client | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,24 @@ | ||
import typing | ||
from unittest.mock import MagicMock | ||
|
||
import pytest | ||
from django.core.cache import BaseCache | ||
from flag_engine.environments.models import EnvironmentModel | ||
from flag_engine.features.models import FeatureModel, FeatureStateModel | ||
from flag_engine.organisations.models import OrganisationModel | ||
from flag_engine.projects.models import ProjectModel | ||
from flagsmith.offline_handlers import BaseOfflineHandler | ||
from pytest_django.fixtures import SettingsWrapper | ||
from pytest_mock import MockerFixture | ||
|
||
from environments.enums import EnvironmentDocumentCacheMode | ||
from environments.models import Environment | ||
from features.feature_types import STANDARD | ||
from features.models import Feature | ||
from organisations.models import Organisation, OrganisationRole | ||
from projects.models import Project | ||
from projects.tags.models import Tag | ||
from tests.types import TestFlagData | ||
from users.models import FFAdminUser | ||
from util.mappers import map_environment_to_environment_document | ||
|
||
|
@@ -232,3 +240,43 @@ def populate_environment_document_cache( | |
persistent_environment_document_cache.get.return_value = ( | ||
map_environment_to_environment_document(environment) | ||
) | ||
|
||
|
||
@pytest.fixture() | ||
def set_flagsmith_client_flags( | ||
mocker: MockerFixture, | ||
) -> typing.Callable[[list[TestFlagData]], None]: | ||
Comment on lines
+246
to
+248
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: I'm curious to see this fixture used and evolved. IMO we can include it as a pytest plugin along with the Python client when it's mature enough. |
||
class TestOfflineHandler(BaseOfflineHandler): | ||
def __init__(self) -> None: | ||
_organisation_model = OrganisationModel(id=1, name="flagsmith-organisation") | ||
_project_model = ProjectModel( | ||
id=1, name="flagsmith-project", organisation=_organisation_model | ||
) | ||
self.environment = EnvironmentModel( | ||
id=1, api_key="test-key", project=_project_model | ||
) | ||
|
||
def set_flags(self, flags: list[TestFlagData]) -> None: | ||
self.environment.feature_states = [ | ||
FeatureStateModel( | ||
feature=FeatureModel( | ||
id=i, name=flag_data.feature_name, type=STANDARD | ||
), | ||
enabled=flag_data.enabled, | ||
feature_state_value=flag_data.value, | ||
) | ||
for i, flag_data in enumerate(flags) | ||
] | ||
|
||
def get_environment(self) -> EnvironmentModel: | ||
return self.environment | ||
|
||
offline_handler = TestOfflineHandler() | ||
mocker.patch( | ||
"integrations.flagsmith.client.LocalFileHandler", return_value=offline_handler | ||
) | ||
|
||
def _setter(flags: list[TestFlagData]) -> None: | ||
offline_handler.set_flags(flags) | ||
|
||
return _setter |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,6 +39,7 @@ | |
from organisations.models import Organisation, OrganisationRole | ||
from projects.models import EdgeV2MigrationStatus, Project | ||
from segments.models import Segment | ||
from tests.types import TestFlagData | ||
from users.models import FFAdminUser | ||
from util.mappers import map_environment_to_environment_document | ||
|
||
|
@@ -1208,3 +1209,66 @@ def test_environment_metric_query_helpers_match_expected_counts( | |
assert change_request_count_result == change_request_count | ||
assert scheduled_count_result == scheduled_change_count | ||
assert identity_override_count == 0 | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess I'd like to see a) a test with project with |
||
|
||
def test_environment_create_with_use_v2_feature_versioning_true( | ||
project: Project, | ||
environment_v2_versioning: Environment, | ||
feature: Feature, | ||
set_flagsmith_client_flags: typing.Callable[[list[TestFlagData]], None], | ||
) -> None: | ||
# Given | ||
set_flagsmith_client_flags( | ||
[TestFlagData("enable_feature_versioning_for_new_projects", True, "2025-02-17")] | ||
) | ||
|
||
# When | ||
new_environment = Environment.objects.create( | ||
name="new-environment", | ||
project=project, | ||
) | ||
|
||
# Then | ||
assert EnvironmentFeatureVersion.objects.filter( | ||
environment=new_environment, feature=feature | ||
).exists() | ||
|
||
|
||
def test_environment_clone_from_versioned_environment_with_use_v2_feature_versioning_true( | ||
project: Project, | ||
environment_v2_versioning: Environment, | ||
feature: Feature, | ||
set_flagsmith_client_flags: typing.Callable[[list[TestFlagData]], None], | ||
) -> None: | ||
# Given | ||
set_flagsmith_client_flags( | ||
[TestFlagData("enable_feature_versioning_for_new_projects", True, "2025-02-17")] | ||
) | ||
|
||
# When | ||
new_environment = environment_v2_versioning.clone(name="new-environment") | ||
|
||
# Then | ||
assert EnvironmentFeatureVersion.objects.filter( | ||
environment=new_environment, feature=feature | ||
).exists() | ||
|
||
|
||
def test_environment_clone_from_non_versioned_environment_with_use_v2_feature_versioning_true( | ||
project: Project, | ||
environment: Environment, | ||
feature: Feature, | ||
set_flagsmith_client_flags: typing.Callable[[list[TestFlagData]], None], | ||
) -> None: | ||
# Given | ||
set_flagsmith_client_flags( | ||
[TestFlagData("enable_feature_versioning_for_new_projects", True, "2025-02-17")] | ||
) | ||
|
||
# When | ||
new_environment = environment.clone(name="new-environment") | ||
|
||
# Then | ||
assert not EnvironmentFeatureVersion.objects.filter( | ||
environment=new_environment, feature=feature | ||
).exists() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side note: to me, this code is a good example of current limitations in Flagsmith's segmenting; if the engine supported datetime comparisons, this could've been
or, if we had multi-context support, an even better
— both of the above are much safer and clearer than the code I'm reviewing.